diff --git a/.devcontainer/README.md b/.devcontainer/README.md index 5f6e1cd99f3a7..809b0f6aa5573 100644 --- a/.devcontainer/README.md +++ b/.devcontainer/README.md @@ -1,6 +1,6 @@ # Code - OSS Development Container -[![Open in Dev Containers](https://img.shields.io/static/v1?label=Dev%20Containers&message=Open&color=blue&logo=visualstudiocode)](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/microsoft/vscode) +[![Open in Dev Containers](https://img.shields.io/static/v1?label=Dev%20Containers&message=Open&color=blue)](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/microsoft/vscode) This repository includes configuration for a development container for working with Code - OSS in a local container or using [GitHub Codespaces](https://github.com/features/codespaces). diff --git a/.github/classifier.json b/.github/classifier.json index 5f322e25edd51..6240035d80854 100644 --- a/.github/classifier.json +++ b/.github/classifier.json @@ -127,7 +127,7 @@ "layout": {"assign": ["benibenj"]}, "lcd-text-rendering": {"assign": []}, "list-widget": {"assign": ["joaomoreno"]}, - "live-preview": {"assign": ["andreamah"]}, + "live-preview": {"assign": []}, "log": {"assign": ["sandy081"]}, "markdown": {"assign": ["mjbvz"]}, "marketplace": {"assign": ["isidorn"]}, @@ -199,9 +199,9 @@ "sash-widget": {"assign": ["joaomoreno"]}, "scm": {"assign": ["lszomoru"]}, "screencast-mode": {"assign": ["joaomoreno"]}, - "search": {"assign": ["andreamah", "roblourens"]}, - "search-api": {"assign": ["andreamah", "roblourens"]}, - "search-editor": {"assign": ["andreamah", "roblourens"]}, + "search": {"assign": ["roblourens"]}, + "search-api": {"assign": ["roblourens"]}, + "search-editor": {"assign": ["roblourens"]}, "search-replace": {"assign": ["sandy081"]}, "semantic-tokens": {"assign": ["alexdima", "aeschli"]}, "server": {"assign": ["alexdima"]}, @@ -215,7 +215,6 @@ "snap": {"assign": ["deepak1556"]}, "snippets": {"assign": ["jrieken"]}, "splitview-widget": {"assign": ["joaomoreno"]}, - "ssh": {"assign": ["eleanorjboyd"]}, "suggest": {"assign": ["jrieken"]}, "table-widget": {"assign": ["joaomoreno"]}, "tasks": {"assign": ["meganrogge"], "accuracy": 0.85}, diff --git a/.npmrc b/.npmrc index e3a35cce6831a..30ecf60a970cc 100644 --- a/.npmrc +++ b/.npmrc @@ -1,6 +1,6 @@ disturl="https://electronjs.org/headers" -target="32.2.1" -ms_build_id="10427718" +target="32.2.3" +ms_build_id="10561341" runtime="electron" build_from_source="true" legacy-peer-deps="true" diff --git a/.vscode-test.js b/.vscode-test.js index 1e5de54470c6e..ce539a6572157 100644 --- a/.vscode-test.js +++ b/.vscode-test.js @@ -42,6 +42,11 @@ const extensions = [ workspaceFolder: `extensions/vscode-colorize-tests/test`, mocha: { timeout: 60_000 } }, + { + label: 'vscode-colorize-perf-tests', + workspaceFolder: `extensions/vscode-colorize-perf-tests/test`, + mocha: { timeout: 6000_000 } + }, { label: 'configuration-editing', workspaceFolder: path.join(os.tmpdir(), `confeditout-${Math.floor(Math.random() * 100000)}`), diff --git a/.vscode/extensions/vscode-selfhost-test-provider/src/extension.ts b/.vscode/extensions/vscode-selfhost-test-provider/src/extension.ts index 2732ef3b3f619..cbb8d50bf9954 100644 --- a/.vscode/extensions/vscode-selfhost-test-provider/src/extension.ts +++ b/.vscode/extensions/vscode-selfhost-test-provider/src/extension.ts @@ -86,10 +86,11 @@ export async function activate(context: vscode.ExtensionContext) { }, uri => ctrl.items.get(uri.toString().toLowerCase())); ctrl.relatedCodeProvider = graph; - context.subscriptions.push( - new FailureTracker(context, folder.uri.fsPath), - fileChangedEmitter.event(e => graph.didChange(e.uri, e.removed)), - ); + if (context.storageUri) { + context.subscriptions.push(new FailureTracker(context.storageUri.fsPath, folder.uri.fsPath)); + } + + context.subscriptions.push(fileChangedEmitter.event(e => graph.didChange(e.uri, e.removed))); }); const createRunHandler = ( diff --git a/.vscode/extensions/vscode-selfhost-test-provider/src/failureTracker.ts b/.vscode/extensions/vscode-selfhost-test-provider/src/failureTracker.ts index e04d4beede4c6..5bed5dd63e335 100644 --- a/.vscode/extensions/vscode-selfhost-test-provider/src/failureTracker.ts +++ b/.vscode/extensions/vscode-selfhost-test-provider/src/failureTracker.ts @@ -33,8 +33,8 @@ export class FailureTracker { private readonly logFile: string; private logs?: ITrackedRemediation[]; - constructor(context: vscode.ExtensionContext, private readonly rootDir: string) { - this.logFile = join(context.globalStorageUri.fsPath, '.build/vscode-test-failures.json'); + constructor(storageLocation: string, private readonly rootDir: string) { + this.logFile = join(storageLocation, '.build/vscode-test-failures.json'); mkdirSync(dirname(this.logFile), { recursive: true }); const oldLogFile = join(rootDir, '.build/vscode-test-failures.json'); diff --git a/.vscode/launch.json b/.vscode/launch.json index b2e25927a8a73..1a6be10d6c1b6 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -202,6 +202,24 @@ "order": 5 } }, + { + "type": "extensionHost", + "request": "launch", + "name": "VS Code Tokenizer Performance Tests", + "runtimeExecutable": "${execPath}", + "args": [ + "${workspaceFolder}/extensions/vscode-colorize-perf-tests/test", + "--extensionDevelopmentPath=${workspaceFolder}/extensions/vscode-colorize-perf-tests", + "--extensionTestsPath=${workspaceFolder}/extensions/vscode-colorize-perf-tests/out" + ], + "outFiles": [ + "${workspaceFolder}/out/**/*.js" + ], + "presentation": { + "group": "5_tests", + "order": 6 + } + }, { "type": "chrome", "request": "attach", diff --git a/.vscode/notebooks/api.github-issues b/.vscode/notebooks/api.github-issues index 35ea9042ef5c9..0423e9e3afc8f 100644 --- a/.vscode/notebooks/api.github-issues +++ b/.vscode/notebooks/api.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPO=repo:microsoft/vscode\n$MILESTONE=milestone:\"October 2024\"" + "value": "$REPO=repo:microsoft/vscode\n$MILESTONE=milestone:\"November 2024\"" }, { "kind": 1, diff --git a/.vscode/notebooks/my-work.github-issues b/.vscode/notebooks/my-work.github-issues index c58ceae0d0105..7577d9626c876 100644 --- a/.vscode/notebooks/my-work.github-issues +++ b/.vscode/notebooks/my-work.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "// list of repos we work in\n$REPOS=repo:microsoft/lsprotocol repo:microsoft/monaco-editor repo:microsoft/vscode repo:microsoft/vscode-anycode repo:microsoft/vscode-autopep8 repo:microsoft/vscode-black-formatter repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-dev repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-flake8 repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-hexeditor repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-isort repo:microsoft/vscode-js-debug repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-l10n repo:microsoft/vscode-livepreview repo:microsoft/vscode-markdown-languageservice repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-mypy repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-pylint repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-references-view repo:microsoft/vscode-remote-release repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-unpkg repo:microsoft/vscode-vsce\n\n// current milestone name\n$MILESTONE=milestone:\"October 2024\"\n" + "value": "// list of repos we work in\n$REPOS=repo:microsoft/lsprotocol repo:microsoft/monaco-editor repo:microsoft/vscode repo:microsoft/vscode-anycode repo:microsoft/vscode-autopep8 repo:microsoft/vscode-black-formatter repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-dev repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-flake8 repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-hexeditor repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-isort repo:microsoft/vscode-js-debug repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-l10n repo:microsoft/vscode-livepreview repo:microsoft/vscode-markdown-languageservice repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-mypy repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-pylint repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-references-view repo:microsoft/vscode-remote-release repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-unpkg repo:microsoft/vscode-vsce\n\n// current milestone name\n$MILESTONE=milestone:\"November 2024\"\n" }, { "kind": 1, diff --git a/.vscode/settings.json b/.vscode/settings.json index 63733b35c0839..29f501a1de834 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -168,5 +168,7 @@ }, "css.format.spaceAroundSelectorSeparator": true, "typescript.enablePromptUseWorkspaceTsdk": true, - "eslint.useFlatConfig": true + "eslint.useFlatConfig": true, + "editor.occurrencesHighlightDelay": 0, + "typescript.experimental.expandableHover": true, } diff --git a/build/.webignore b/build/.webignore index d42f9775ba9a8..31c366cc1faf1 100644 --- a/build/.webignore +++ b/build/.webignore @@ -26,6 +26,9 @@ vscode-textmate/webpack.config.js @xterm/addon-image/src/** @xterm/addon-image/out/** +@xterm/addon-ligatures/src/** +@xterm/addon-ligatures/out/** + @xterm/addon-search/src/** @xterm/addon-search/out/** @xterm/addon-search/fixtures/** diff --git a/build/azure-pipelines/alpine/product-build-alpine.yml b/build/azure-pipelines/alpine/product-build-alpine.yml index 890bc68c7380d..d6fe74a9d610b 100644 --- a/build/azure-pipelines/alpine/product-build-alpine.yml +++ b/build/azure-pipelines/alpine/product-build-alpine.yml @@ -10,7 +10,7 @@ steps: - task: AzureKeyVault@2 displayName: "Azure Key Vault: Get Secrets" inputs: - azureSubscription: "vscode-builds-subscription" + azureSubscription: vscode KeyVaultName: vscode-build-secrets SecretsFilter: "github-distro-mixin-password" @@ -59,7 +59,7 @@ steps: - task: Docker@1 inputs: - azureSubscriptionEndpoint: "vscode-builds-subscription" + azureSubscriptionEndpoint: vscode azureContainerRegistry: vscodehub.azurecr.io command: "Run an image" imageName: "vscode-linux-build-agent:alpine-$(VSCODE_ARCH)" diff --git a/build/azure-pipelines/cli/cli-compile.yml b/build/azure-pipelines/cli/cli-compile.yml index 09380b4c83416..71cd3f71e78b1 100644 --- a/build/azure-pipelines/cli/cli-compile.yml +++ b/build/azure-pipelines/cli/cli-compile.yml @@ -115,6 +115,9 @@ steps: SearchPattern: 'code.pdb' SymbolServerType: TeamServices SymbolsProduct: 'code' + ArtifactServices.Symbol.AccountName: microsoft + ArtifactServices.Symbol.PAT: $(System.AccessToken) + ArtifactServices.Symbol.UseAAD: false displayName: Publish Symbols - task: CopyFiles@2 diff --git a/build/azure-pipelines/cli/cli-darwin-sign.yml b/build/azure-pipelines/cli/cli-darwin-sign.yml index b6dc424d690b2..ba8150651a788 100644 --- a/build/azure-pipelines/cli/cli-darwin-sign.yml +++ b/build/azure-pipelines/cli/cli-darwin-sign.yml @@ -4,20 +4,21 @@ parameters: default: [] steps: - - task: AzureKeyVault@2 - displayName: "Azure Key Vault: Get Secrets" - inputs: - azureSubscription: "vscode-builds-subscription" - KeyVaultName: vscode-build-secrets - SecretsFilter: "ESRP-PKI,esrp-aad-username,esrp-aad-password" - - task: UseDotNet@2 inputs: version: 6.x - - task: EsrpClientTool@1 - continueOnError: true - displayName: Download ESRPClient + - task: EsrpCodeSigning@5 + inputs: + UseMSIAuthentication: true + ConnectedServiceName: vscode-esrp + AppRegistrationClientId: $(ESRP_CLIENT_ID) + AppRegistrationTenantId: $(ESRP_TENANT_ID) + AuthAKVName: vscode-esrp + AuthSignCertName: esrp-sign + FolderPath: . + Pattern: noop + displayName: 'Install ESRP Tooling' - ${{ each target in parameters.VSCODE_CLI_ARTIFACTS }}: - task: DownloadPipelineArtifact@2 @@ -32,10 +33,14 @@ steps: archiveFilePatterns: $(Build.ArtifactStagingDirectory)/pkg/${{ target }}/*.zip destinationFolder: $(Build.ArtifactStagingDirectory)/sign/${{ target }} - - script: node build/azure-pipelines/common/sign $(Agent.ToolsDirectory)/esrpclient/*/*/net6.0/esrpcli.dll sign-darwin $(ESRP-PKI) $(esrp-aad-username) $(esrp-aad-password) $(Build.ArtifactStagingDirectory)/pkg "*.zip" + - script: node build/azure-pipelines/common/sign $(Agent.RootDirectory)/_tasks/EsrpCodeSigning_*/*/net6.0/esrpcli.dll sign-darwin $(Build.ArtifactStagingDirectory)/pkg "*.zip" + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) displayName: Codesign - - script: node build/azure-pipelines/common/sign $(Agent.ToolsDirectory)/esrpclient/*/*/net6.0/esrpcli.dll notarize-darwin $(ESRP-PKI) $(esrp-aad-username) $(esrp-aad-password) $(Build.ArtifactStagingDirectory)/pkg "*.zip" + - script: node build/azure-pipelines/common/sign $(Agent.RootDirectory)/_tasks/EsrpCodeSigning_*/*/net6.0/esrpcli.dll notarize-darwin $(Build.ArtifactStagingDirectory)/pkg "*.zip" + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) displayName: Notarize - ${{ each target in parameters.VSCODE_CLI_ARTIFACTS }}: diff --git a/build/azure-pipelines/cli/cli-win32-sign.yml b/build/azure-pipelines/cli/cli-win32-sign.yml index e39df13c998ea..bc711bec4a73d 100644 --- a/build/azure-pipelines/cli/cli-win32-sign.yml +++ b/build/azure-pipelines/cli/cli-win32-sign.yml @@ -4,19 +4,29 @@ parameters: default: [] steps: - - task: AzureKeyVault@2 - displayName: "Azure Key Vault: Get Secrets" - inputs: - azureSubscription: "vscode-builds-subscription" - KeyVaultName: vscode-build-secrets - SecretsFilter: "ESRP-PKI,esrp-aad-username,esrp-aad-password" - - task: UseDotNet@2 inputs: version: 6.x - - task: EsrpClientTool@1 - displayName: "Use ESRP client" + - task: EsrpCodeSigning@5 + inputs: + UseMSIAuthentication: true + ConnectedServiceName: vscode-esrp + AppRegistrationClientId: $(ESRP_CLIENT_ID) + AppRegistrationTenantId: $(ESRP_TENANT_ID) + AuthAKVName: vscode-esrp + AuthSignCertName: esrp-sign + FolderPath: . + Pattern: noop + displayName: 'Install ESRP Tooling' + + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + $EsrpCodeSigningTool = (gci -directory -filter EsrpCodeSigning_* $(Agent.RootDirectory)\_tasks | Select-Object -last 1).FullName + $Version = (gci -directory $EsrpCodeSigningTool | Select-Object -last 1).FullName + echo "##vso[task.setvariable variable=EsrpCliDllPath]$Version\net6.0\esrpcli.dll" + displayName: Find ESRP CLI - ${{ each target in parameters.VSCODE_CLI_ARTIFACTS }}: - task: DownloadPipelineArtifact@2 @@ -31,18 +41,9 @@ steps: archiveFilePatterns: $(Build.ArtifactStagingDirectory)/pkg/${{ target }}/*.zip destinationFolder: $(Build.ArtifactStagingDirectory)/sign/${{ target }} - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - $EsrpClientTool = (gci -directory -filter EsrpClientTool_* $(Agent.RootDirectory)\_tasks | Select-Object -last 1).FullName - $EsrpCliZip = (gci -recurse -filter esrpcli.*.zip $EsrpClientTool | Select-Object -last 1).FullName - mkdir -p $(Agent.TempDirectory)\esrpcli - Expand-Archive -Path $EsrpCliZip -DestinationPath $(Agent.TempDirectory)\esrpcli - $EsrpCliDllPath = (gci -recurse -filter esrpcli.dll $(Agent.TempDirectory)\esrpcli | Select-Object -last 1).FullName - echo "##vso[task.setvariable variable=EsrpCliDllPath]$EsrpCliDllPath" - displayName: Find ESRP CLI - - - powershell: node build\azure-pipelines\common\sign $env:EsrpCliDllPath sign-windows $(ESRP-PKI) $(esrp-aad-username) $(esrp-aad-password) $(Build.ArtifactStagingDirectory)/sign "*.exe" + - powershell: node build\azure-pipelines\common\sign $env:EsrpCliDllPath sign-windows $(Build.ArtifactStagingDirectory)/sign "*.exe" + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) displayName: Codesign - ${{ each target in parameters.VSCODE_CLI_ARTIFACTS }}: diff --git a/build/azure-pipelines/common/createBuild.js b/build/azure-pipelines/common/createBuild.js index 29c3448edc193..c605ed6218efb 100644 --- a/build/azure-pipelines/common/createBuild.js +++ b/build/azure-pipelines/common/createBuild.js @@ -40,7 +40,7 @@ async function main() { assets: [], updates: {} }; - const aadCredentials = new identity_1.ClientSecretCredential(process.env['AZURE_TENANT_ID'], process.env['AZURE_CLIENT_ID'], process.env['AZURE_CLIENT_SECRET']); + const aadCredentials = new identity_1.ClientAssertionCredential(process.env['AZURE_TENANT_ID'], process.env['AZURE_CLIENT_ID'], () => Promise.resolve(process.env['AZURE_ID_TOKEN'])); const client = new cosmos_1.CosmosClient({ endpoint: process.env['AZURE_DOCUMENTDB_ENDPOINT'], aadCredentials }); const scripts = client.database('builds').container(quality).scripts; await (0, retry_1.retry)(() => scripts.storedProcedure('createBuild').execute('', [{ ...build, _partitionKey: '' }])); diff --git a/build/azure-pipelines/common/createBuild.ts b/build/azure-pipelines/common/createBuild.ts index afc5f59003a26..6afeb01e6cc78 100644 --- a/build/azure-pipelines/common/createBuild.ts +++ b/build/azure-pipelines/common/createBuild.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ClientSecretCredential } from '@azure/identity'; +import { ClientAssertionCredential } from '@azure/identity'; import { CosmosClient } from '@azure/cosmos'; import { retry } from './retry'; @@ -47,7 +47,7 @@ async function main(): Promise { updates: {} }; - const aadCredentials = new ClientSecretCredential(process.env['AZURE_TENANT_ID']!, process.env['AZURE_CLIENT_ID']!, process.env['AZURE_CLIENT_SECRET']!); + const aadCredentials = new ClientAssertionCredential(process.env['AZURE_TENANT_ID']!, process.env['AZURE_CLIENT_ID']!, () => Promise.resolve(process.env['AZURE_ID_TOKEN']!)); const client = new CosmosClient({ endpoint: process.env['AZURE_DOCUMENTDB_ENDPOINT']!, aadCredentials }); const scripts = client.database('builds').container(quality).scripts; await retry(() => scripts.storedProcedure('createBuild').execute('', [{ ...build, _partitionKey: '' }])); diff --git a/build/azure-pipelines/common/getPublishAuthTokens.js b/build/azure-pipelines/common/getPublishAuthTokens.js new file mode 100644 index 0000000000000..9c22e9ad94bc9 --- /dev/null +++ b/build/azure-pipelines/common/getPublishAuthTokens.js @@ -0,0 +1,47 @@ +"use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.getAccessToken = getAccessToken; +const msal_node_1 = require("@azure/msal-node"); +function e(name) { + const result = process.env[name]; + if (typeof result !== 'string') { + throw new Error(`Missing env: ${name}`); + } + return result; +} +async function getAccessToken(endpoint, tenantId, clientId, idToken) { + const app = new msal_node_1.ConfidentialClientApplication({ + auth: { + clientId, + authority: `https://login.microsoftonline.com/${tenantId}`, + clientAssertion: idToken + } + }); + const result = await app.acquireTokenByClientCredential({ scopes: [`${endpoint}.default`] }); + if (!result) { + throw new Error('Failed to get access token'); + } + return { + token: result.accessToken, + expiresOnTimestamp: result.expiresOn.getTime(), + refreshAfterTimestamp: result.refreshOn?.getTime() + }; +} +async function main() { + const cosmosDBAccessToken = await getAccessToken(e('AZURE_DOCUMENTDB_ENDPOINT'), e('AZURE_TENANT_ID'), e('AZURE_CLIENT_ID'), e('AZURE_ID_TOKEN')); + const blobServiceAccessToken = await getAccessToken(`https://${e('VSCODE_STAGING_BLOB_STORAGE_ACCOUNT_NAME')}.blob.core.windows.net/`, process.env['AZURE_TENANT_ID'], process.env['AZURE_CLIENT_ID'], process.env['AZURE_ID_TOKEN']); + console.log(JSON.stringify({ cosmosDBAccessToken, blobServiceAccessToken })); +} +if (require.main === module) { + main().then(() => { + process.exit(0); + }, err => { + console.error(err); + process.exit(1); + }); +} +//# sourceMappingURL=getPublishAuthTokens.js.map \ No newline at end of file diff --git a/build/azure-pipelines/common/getPublishAuthTokens.ts b/build/azure-pipelines/common/getPublishAuthTokens.ts new file mode 100644 index 0000000000000..68e76de1a832f --- /dev/null +++ b/build/azure-pipelines/common/getPublishAuthTokens.ts @@ -0,0 +1,54 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { AccessToken } from '@azure/core-auth'; +import { ConfidentialClientApplication } from '@azure/msal-node'; + +function e(name: string): string { + const result = process.env[name]; + + if (typeof result !== 'string') { + throw new Error(`Missing env: ${name}`); + } + + return result; +} + +export async function getAccessToken(endpoint: string, tenantId: string, clientId: string, idToken: string): Promise { + const app = new ConfidentialClientApplication({ + auth: { + clientId, + authority: `https://login.microsoftonline.com/${tenantId}`, + clientAssertion: idToken + } + }); + + const result = await app.acquireTokenByClientCredential({ scopes: [`${endpoint}.default`] }); + + if (!result) { + throw new Error('Failed to get access token'); + } + + return { + token: result.accessToken, + expiresOnTimestamp: result.expiresOn!.getTime(), + refreshAfterTimestamp: result.refreshOn?.getTime() + }; +} + +async function main() { + const cosmosDBAccessToken = await getAccessToken(e('AZURE_DOCUMENTDB_ENDPOINT')!, e('AZURE_TENANT_ID')!, e('AZURE_CLIENT_ID')!, e('AZURE_ID_TOKEN')!); + const blobServiceAccessToken = await getAccessToken(`https://${e('VSCODE_STAGING_BLOB_STORAGE_ACCOUNT_NAME')}.blob.core.windows.net/`, process.env['AZURE_TENANT_ID']!, process.env['AZURE_CLIENT_ID']!, process.env['AZURE_ID_TOKEN']!); + console.log(JSON.stringify({ cosmosDBAccessToken, blobServiceAccessToken })); +} + +if (require.main === module) { + main().then(() => { + process.exit(0); + }, err => { + console.error(err); + process.exit(1); + }); +} diff --git a/build/azure-pipelines/common/publish.js b/build/azure-pipelines/common/publish.js index aa185ed836998..3816db385a0e6 100644 --- a/build/azure-pipelines/common/publish.js +++ b/build/azure-pipelines/common/publish.js @@ -12,10 +12,12 @@ const yauzl = require("yauzl"); const crypto = require("crypto"); const retry_1 = require("./retry"); const cosmos_1 = require("@azure/cosmos"); -const identity_1 = require("@azure/identity"); const cp = require("child_process"); const os = require("os"); const node_worker_threads_1 = require("node:worker_threads"); +const msal_node_1 = require("@azure/msal-node"); +const storage_blob_1 = require("@azure/storage-blob"); +const jws = require("jws"); function e(name) { const result = process.env[name]; if (typeof result !== 'string') { @@ -23,241 +25,236 @@ function e(name) { } return result; } -class Temp { - _files = []; - tmpNameSync() { - const file = path.join(os.tmpdir(), crypto.randomBytes(20).toString('hex')); - this._files.push(file); - return file; - } - dispose() { - for (const file of this._files) { - try { - fs.unlinkSync(file); - } - catch (err) { - // noop - } - } - } -} -function isCreateProvisionedFilesErrorResponse(response) { - return response?.ErrorDetails?.Code !== undefined; -} -class ProvisionService { - log; - accessToken; - constructor(log, accessToken) { - this.log = log; - this.accessToken = accessToken; - } - async provision(releaseId, fileId, fileName) { - const body = JSON.stringify({ - ReleaseId: releaseId, - PortalName: 'VSCode', - PublisherCode: 'VSCode', - ProvisionedFilesCollection: [{ - PublisherKey: fileId, - IsStaticFriendlyFileName: true, - FriendlyFileName: fileName, - MaxTTL: '1440', - CdnMappings: ['ECN'] - }] - }); - this.log(`Provisioning ${fileName} (releaseId: ${releaseId}, fileId: ${fileId})...`); - const res = await (0, retry_1.retry)(() => this.request('POST', '/api/v2/ProvisionedFiles/CreateProvisionedFiles', { body })); - if (isCreateProvisionedFilesErrorResponse(res) && res.ErrorDetails.Code === 'FriendlyFileNameAlreadyProvisioned') { - this.log(`File already provisioned (most likley due to a re-run), skipping: ${fileName}`); - return; - } - if (!res.IsSuccess) { - throw new Error(`Failed to submit provisioning request: ${JSON.stringify(res.ErrorDetails)}`); - } - this.log(`Successfully provisioned ${fileName}`); - } - async request(method, url, options) { - const opts = { - method, - body: options?.body, - headers: { - Authorization: `Bearer ${this.accessToken}`, - 'Content-Type': 'application/json' - } - }; - const res = await fetch(`https://dsprovisionapi.microsoft.com${url}`, opts); - // 400 normally means the request is bad or something is already provisioned, so we will return as retries are useless - // Otherwise log the text body and headers. We do text because some responses are not JSON. - if ((!res.ok || res.status < 200 || res.status >= 500) && res.status !== 400) { - throw new Error(`Unexpected status code: ${res.status}\nResponse Headers: ${JSON.stringify(res.headers)}\nBody Text: ${await res.text()}`); - } - return await res.json(); - } -} function hashStream(hashName, stream) { return new Promise((c, e) => { const shasum = crypto.createHash(hashName); stream .on('data', shasum.update.bind(shasum)) .on('error', e) - .on('close', () => c(shasum.digest('hex'))); + .on('close', () => c(shasum.digest())); }); } -class ESRPClient { +var StatusCode; +(function (StatusCode) { + StatusCode["Pass"] = "pass"; + StatusCode["Inprogress"] = "inprogress"; + StatusCode["FailCanRetry"] = "failCanRetry"; + StatusCode["FailDoNotRetry"] = "failDoNotRetry"; + StatusCode["PendingAnalysis"] = "pendingAnalysis"; + StatusCode["Cancelled"] = "cancelled"; +})(StatusCode || (StatusCode = {})); +function getCertificateBuffer(input) { + return Buffer.from(input.replace(/-----BEGIN CERTIFICATE-----|-----END CERTIFICATE-----|\n/g, ''), 'base64'); +} +function getThumbprint(input, algorithm) { + const buffer = getCertificateBuffer(input); + return crypto.createHash(algorithm).update(buffer).digest(); +} +function getKeyFromPFX(pfx) { + const pfxCertificatePath = path.join(os.tmpdir(), 'cert.pfx'); + const pemKeyPath = path.join(os.tmpdir(), 'key.pem'); + try { + const pfxCertificate = Buffer.from(pfx, 'base64'); + fs.writeFileSync(pfxCertificatePath, pfxCertificate); + cp.execSync(`openssl pkcs12 -in "${pfxCertificatePath}" -nocerts -nodes -out "${pemKeyPath}" -passin pass:`); + const raw = fs.readFileSync(pemKeyPath, 'utf-8'); + const result = raw.match(/-----BEGIN PRIVATE KEY-----[\s\S]+?-----END PRIVATE KEY-----/g)[0]; + return result; + } + finally { + fs.rmSync(pfxCertificatePath, { force: true }); + fs.rmSync(pemKeyPath, { force: true }); + } +} +function getCertificatesFromPFX(pfx) { + const pfxCertificatePath = path.join(os.tmpdir(), 'cert.pfx'); + const pemCertificatePath = path.join(os.tmpdir(), 'cert.pem'); + try { + const pfxCertificate = Buffer.from(pfx, 'base64'); + fs.writeFileSync(pfxCertificatePath, pfxCertificate); + cp.execSync(`openssl pkcs12 -in "${pfxCertificatePath}" -nokeys -out "${pemCertificatePath}" -passin pass:`); + const raw = fs.readFileSync(pemCertificatePath, 'utf-8'); + const matches = raw.match(/-----BEGIN CERTIFICATE-----[\s\S]+?-----END CERTIFICATE-----/g); + return matches ? matches.reverse() : []; + } + finally { + fs.rmSync(pfxCertificatePath, { force: true }); + fs.rmSync(pemCertificatePath, { force: true }); + } +} +class ESRPReleaseService { log; - tmp; - authPath; - constructor(log, tmp, tenantId, clientId, authCertSubjectName, requestSigningCertSubjectName) { - this.log = log; - this.tmp = tmp; - this.authPath = this.tmp.tmpNameSync(); - fs.writeFileSync(this.authPath, JSON.stringify({ - Version: '1.0.0', - AuthenticationType: 'AAD_CERT', - TenantId: tenantId, - ClientId: clientId, - AuthCert: { - SubjectName: authCertSubjectName, - StoreLocation: 'LocalMachine', - StoreName: 'My', - SendX5c: 'true' - }, - RequestSigningCert: { - SubjectName: requestSigningCertSubjectName, - StoreLocation: 'LocalMachine', - StoreName: 'My' + clientId; + accessToken; + requestSigningCertificates; + requestSigningKey; + containerClient; + static async create(log, tenantId, clientId, authCertificatePfx, requestSigningCertificatePfx, containerClient) { + const authKey = getKeyFromPFX(authCertificatePfx); + const authCertificate = getCertificatesFromPFX(authCertificatePfx)[0]; + const requestSigningKey = getKeyFromPFX(requestSigningCertificatePfx); + const requestSigningCertificates = getCertificatesFromPFX(requestSigningCertificatePfx); + const app = new msal_node_1.ConfidentialClientApplication({ + auth: { + clientId, + authority: `https://login.microsoftonline.com/${tenantId}`, + clientCertificate: { + thumbprintSha256: getThumbprint(authCertificate, 'sha256').toString('hex'), + privateKey: authKey, + x5c: authCertificate + } } - })); + }); + const response = await app.acquireTokenByClientCredential({ + scopes: ['https://api.esrp.microsoft.com/.default'] + }); + return new ESRPReleaseService(log, clientId, response.accessToken, requestSigningCertificates, requestSigningKey, containerClient); } - async release(version, filePath) { - this.log(`Submitting release for ${version}: ${filePath}`); - const submitReleaseResult = await this.SubmitRelease(version, filePath); - if (submitReleaseResult.submissionResponse.statusCode !== 'pass') { - throw new Error(`Unexpected status code: ${submitReleaseResult.submissionResponse.statusCode}`); - } - const releaseId = submitReleaseResult.submissionResponse.operationId; - this.log(`Successfully submitted release ${releaseId}. Polling for completion...`); - let details; - // Poll every 5 seconds, wait 60 minutes max -> poll 60/5*60=720 times - for (let i = 0; i < 720; i++) { - details = await this.ReleaseDetails(releaseId); - if (details.releaseDetails[0].statusCode === 'pass') { - break; + static API_URL = 'https://api.esrp.microsoft.com/api/v3/releaseservices/clients/'; + constructor(log, clientId, accessToken, requestSigningCertificates, requestSigningKey, containerClient) { + this.log = log; + this.clientId = clientId; + this.accessToken = accessToken; + this.requestSigningCertificates = requestSigningCertificates; + this.requestSigningKey = requestSigningKey; + this.containerClient = containerClient; + } + async createRelease(version, filePath, friendlyFileName) { + const correlationId = crypto.randomUUID(); + const blobClient = this.containerClient.getBlockBlobClient(correlationId); + this.log(`Uploading ${filePath} to ${blobClient.url}`); + await blobClient.uploadFile(filePath); + this.log('Uploaded blob successfully'); + try { + this.log(`Submitting release for ${version}: ${filePath}`); + const submitReleaseResult = await this.submitRelease(version, filePath, friendlyFileName, correlationId, blobClient); + this.log(`Successfully submitted release ${submitReleaseResult.operationId}. Polling for completion...`); + // Poll every 5 seconds, wait 60 minutes max -> poll 60/5*60=720 times + for (let i = 0; i < 720; i++) { + await new Promise(c => setTimeout(c, 5000)); + const releaseStatus = await this.getReleaseStatus(submitReleaseResult.operationId); + if (releaseStatus.status === 'pass') { + break; + } + else if (releaseStatus.status !== 'inprogress') { + throw new Error(`Failed to submit release: ${JSON.stringify(releaseStatus)}`); + } } - else if (details.releaseDetails[0].statusCode !== 'inprogress') { - throw new Error(`Failed to submit release: ${JSON.stringify(details)}`); + const releaseDetails = await this.getReleaseDetails(submitReleaseResult.operationId); + if (releaseDetails.status !== 'pass') { + throw new Error(`Timed out waiting for release: ${JSON.stringify(releaseDetails)}`); } - await new Promise(c => setTimeout(c, 5000)); + this.log('Successfully created release:', releaseDetails.files[0].fileDownloadDetails[0].downloadUrl); + return releaseDetails.files[0].fileDownloadDetails[0].downloadUrl; } - if (details.releaseDetails[0].statusCode !== 'pass') { - throw new Error(`Timed out waiting for release ${releaseId}: ${JSON.stringify(details)}`); + finally { + this.log(`Deleting blob ${blobClient.url}`); + await blobClient.delete(); + this.log('Deleted blob successfully'); } - const fileId = details.releaseDetails[0].fileDetails[0].publisherKey; - this.log('Release completed successfully with fileId: ', fileId); - return { releaseId, fileId }; - } - async SubmitRelease(version, filePath) { - const policyPath = this.tmp.tmpNameSync(); - fs.writeFileSync(policyPath, JSON.stringify({ - Version: '1.0.0', - Audience: 'InternalLimited', - Intent: 'distribution', - ContentType: 'InstallPackage' - })); - const inputPath = this.tmp.tmpNameSync(); + } + async submitRelease(version, filePath, friendlyFileName, correlationId, blobClient) { const size = fs.statSync(filePath).size; - const istream = fs.createReadStream(filePath); - const sha256 = await hashStream('sha256', istream); - fs.writeFileSync(inputPath, JSON.stringify({ - Version: '1.0.0', - ReleaseInfo: { - ReleaseMetadata: { - Title: 'VS Code', - Properties: { - ReleaseContentType: 'InstallPackage' - }, - MinimumNumberOfApprovers: 1 - }, - ProductInfo: { - Name: 'VS Code', - Version: version, - Description: path.basename(filePath, path.extname(filePath)), - }, - Owners: [ - { - Owner: { - UserPrincipalName: 'jomo@microsoft.com' - } - } - ], - Approvers: [ - { - Approver: { - UserPrincipalName: 'jomo@microsoft.com' - }, - IsAutoApproved: true, - IsMandatory: false - } - ], - AccessPermissions: { - MainPublisher: 'VSCode', - ChannelDownloadEntityDetails: { - Consumer: ['VSCode'] - } + const hash = await hashStream('sha256', fs.createReadStream(filePath)); + const message = { + customerCorrelationId: correlationId, + esrpCorrelationId: correlationId, + driEmail: ['joao.moreno@microsoft.com'], + createdBy: { userPrincipalName: 'jomo@microsoft.com' }, + owners: [{ owner: { userPrincipalName: 'jomo@microsoft.com' } }], + approvers: [{ approver: { userPrincipalName: 'jomo@microsoft.com' }, isAutoApproved: true, isMandatory: false }], + releaseInfo: { + title: 'VS Code', + properties: { + 'ReleaseContentType': 'InstallPackage' }, - CreatedBy: { - UserPrincipalName: 'jomo@microsoft.com' - } + minimumNumberOfApprovers: 1 }, - ReleaseBatches: [ - { - ReleaseRequestFiles: [ - { - SizeInBytes: size, - SourceHash: sha256, - HashType: 'SHA256', - SourceLocation: path.basename(filePath) - } - ], - SourceLocationType: 'UNC', - SourceRootDirectory: path.dirname(filePath), - DestinationLocationType: 'AzureBlob' + productInfo: { + name: 'VS Code', + version, + description: 'VS Code' + }, + accessPermissionsInfo: { + mainPublisher: 'VSCode', + channelDownloadEntityDetails: { + AllDownloadEntities: ['VSCode'] } - ] - })); - const outputPath = this.tmp.tmpNameSync(); - cp.execSync(`ESRPClient SubmitRelease -a ${this.authPath} -p ${policyPath} -i ${inputPath} -o ${outputPath}`, { stdio: 'inherit' }); - const output = fs.readFileSync(outputPath, 'utf8'); - return JSON.parse(output); - } - async ReleaseDetails(releaseId) { - const inputPath = this.tmp.tmpNameSync(); - fs.writeFileSync(inputPath, JSON.stringify({ - Version: '1.0.0', - OperationIds: [releaseId] - })); - const outputPath = this.tmp.tmpNameSync(); - cp.execSync(`ESRPClient ReleaseDetails -a ${this.authPath} -i ${inputPath} -o ${outputPath}`, { stdio: 'inherit' }); - const output = fs.readFileSync(outputPath, 'utf8'); - return JSON.parse(output); + }, + routingInfo: { + intent: 'filedownloadlinkgeneration' + }, + files: [{ + name: path.basename(filePath), + friendlyFileName, + tenantFileLocation: blobClient.url, + tenantFileLocationType: 'AzureBlob', + sourceLocation: { + type: 'azureBlob', + blobUrl: blobClient.url + }, + hashType: 'sha256', + hash: Array.from(hash), + sizeInBytes: size + }] + }; + message.jwsToken = await this.generateJwsToken(message); + const res = await fetch(`${ESRPReleaseService.API_URL}${this.clientId}/workflows/release/operations`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${this.accessToken}` + }, + body: JSON.stringify(message) + }); + if (!res.ok) { + const text = await res.text(); + throw new Error(`Failed to submit release: ${res.statusText}\n${text}`); + } + return await res.json(); } -} -async function releaseAndProvision(log, releaseTenantId, releaseClientId, releaseAuthCertSubjectName, releaseRequestSigningCertSubjectName, provisionTenantId, provisionAADUsername, provisionAADPassword, version, quality, filePath) { - const fileName = `${quality}/${version}/${path.basename(filePath)}`; - const result = `${e('PRSS_CDN_URL')}/${fileName}`; - const res = await (0, retry_1.retry)(() => fetch(result)); - if (res.status === 200) { - log(`Already released and provisioned: ${result}`); - return result; + async getReleaseStatus(releaseId) { + const url = `${ESRPReleaseService.API_URL}${this.clientId}/workflows/release/operations/grs/${releaseId}`; + const res = await fetch(url, { + headers: { + 'Authorization': `Bearer ${this.accessToken}` + } + }); + if (!res.ok) { + const text = await res.text(); + throw new Error(`Failed to get release status: ${res.statusText}\n${text}`); + } + return await res.json(); + } + async getReleaseDetails(releaseId) { + const url = `${ESRPReleaseService.API_URL}${this.clientId}/workflows/release/operations/grd/${releaseId}`; + const res = await fetch(url, { + headers: { + 'Authorization': `Bearer ${this.accessToken}` + } + }); + if (!res.ok) { + const text = await res.text(); + throw new Error(`Failed to get release status: ${res.statusText}\n${text}`); + } + return await res.json(); + } + async generateJwsToken(message) { + return jws.sign({ + header: { + alg: 'RS256', + crit: ['exp', 'x5t'], + // Release service uses ticks, not seconds :roll_eyes: (https://stackoverflow.com/a/7968483) + exp: ((Date.now() + (6 * 60 * 1000)) * 10000) + 621355968000000000, + // Release service uses hex format, not base64url :roll_eyes: + x5t: getThumbprint(this.requestSigningCertificates[0], 'sha1').toString('hex'), + // Release service uses a '.' separated string, not an array of strings :roll_eyes: + x5c: this.requestSigningCertificates.map(c => getCertificateBuffer(c).toString('base64url')).join('.'), + }, + payload: message, + privateKey: this.requestSigningKey, + }); } - const tmp = new Temp(); - process.on('exit', () => tmp.dispose()); - const esrpclient = new ESRPClient(log, tmp, releaseTenantId, releaseClientId, releaseAuthCertSubjectName, releaseRequestSigningCertSubjectName); - const release = await esrpclient.release(version, filePath); - const credential = new identity_1.ClientSecretCredential(provisionTenantId, provisionAADUsername, provisionAADPassword); - const accessToken = await credential.getToken(['https://microsoft.onmicrosoft.com/DS.Provisioning.WebApi/.default']); - const service = new ProvisionService(log, accessToken.token); - await service.provision(release.releaseId, release.fileId, fileName); - return result; } class State { statePath; @@ -473,31 +470,42 @@ function getRealType(type) { return type; } } -async function processArtifact(artifact, artifactFilePath) { - const log = (...args) => console.log(`[${artifact.name}]`, ...args); +async function processArtifact(artifact, filePath) { const match = /^vscode_(?[^_]+)_(?[^_]+)(?:_legacy)?_(?[^_]+)_(?[^_]+)$/.exec(artifact.name); if (!match) { throw new Error(`Invalid artifact name: ${artifact.name}`); } // getPlatform needs the unprocessedType + const { cosmosDBAccessToken, blobServiceAccessToken } = JSON.parse(e('PUBLISH_AUTH_TOKENS')); const quality = e('VSCODE_QUALITY'); - const commit = e('BUILD_SOURCEVERSION'); + const version = e('BUILD_SOURCEVERSION'); const { product, os, arch, unprocessedType } = match.groups; const isLegacy = artifact.name.includes('_legacy'); const platform = getPlatform(product, os, arch, unprocessedType, isLegacy); const type = getRealType(unprocessedType); - const size = fs.statSync(artifactFilePath).size; - const stream = fs.createReadStream(artifactFilePath); + const size = fs.statSync(filePath).size; + const stream = fs.createReadStream(filePath); const [hash, sha256hash] = await Promise.all([hashStream('sha1', stream), hashStream('sha256', stream)]); // CodeQL [SM04514] Using SHA1 only for legacy reasons, we are actually only respecting SHA256 - const url = await releaseAndProvision(log, e('RELEASE_TENANT_ID'), e('RELEASE_CLIENT_ID'), e('RELEASE_AUTH_CERT_SUBJECT_NAME'), e('RELEASE_REQUEST_SIGNING_CERT_SUBJECT_NAME'), e('PROVISION_TENANT_ID'), e('PROVISION_AAD_USERNAME'), e('PROVISION_AAD_PASSWORD'), commit, quality, artifactFilePath); - const asset = { platform, type, url, hash, sha256hash, size, supportsFastUpdate: true }; + const log = (...args) => console.log(`[${artifact.name}]`, ...args); + const blobServiceClient = new storage_blob_1.BlobServiceClient(`https://${e('VSCODE_STAGING_BLOB_STORAGE_ACCOUNT_NAME')}.blob.core.windows.net/`, { getToken: async () => blobServiceAccessToken }); + const containerClient = blobServiceClient.getContainerClient('staging'); + const releaseService = await ESRPReleaseService.create(log, e('RELEASE_TENANT_ID'), e('RELEASE_CLIENT_ID'), e('RELEASE_AUTH_CERT'), e('RELEASE_REQUEST_SIGNING_CERT'), containerClient); + const friendlyFileName = `${quality}/${version}/${path.basename(filePath)}`; + const url = `${e('PRSS_CDN_URL')}/${friendlyFileName}`; + const res = await (0, retry_1.retry)(() => fetch(url)); + if (res.status === 200) { + log(`Already released and provisioned: ${url}`); + } + else { + await releaseService.createRelease(version, filePath, friendlyFileName); + } + const asset = { platform, type, url, hash: hash.toString('hex'), sha256hash: sha256hash.toString('hex'), size, supportsFastUpdate: true }; log('Creating asset...', JSON.stringify(asset, undefined, 2)); await (0, retry_1.retry)(async (attempt) => { log(`Creating asset in Cosmos DB (attempt ${attempt})...`); - const aadCredentials = new identity_1.ClientSecretCredential(e('AZURE_TENANT_ID'), e('AZURE_CLIENT_ID'), e('AZURE_CLIENT_SECRET')); - const client = new cosmos_1.CosmosClient({ endpoint: e('AZURE_DOCUMENTDB_ENDPOINT'), aadCredentials }); + const client = new cosmos_1.CosmosClient({ endpoint: e('AZURE_DOCUMENTDB_ENDPOINT'), tokenProvider: () => Promise.resolve(`type=aad&ver=1.0&sig=${cosmosDBAccessToken.token}`) }); const scripts = client.database('builds').container(quality).scripts; - await scripts.storedProcedure('createAsset').execute('', [commit, asset, true]); + await scripts.storedProcedure('createAsset').execute('', [version, asset, true]); }); log('Asset successfully created'); } diff --git a/build/azure-pipelines/common/publish.ts b/build/azure-pipelines/common/publish.ts index 652cd1683351f..a3760c0343492 100644 --- a/build/azure-pipelines/common/publish.ts +++ b/build/azure-pipelines/common/publish.ts @@ -12,10 +12,12 @@ import * as yauzl from 'yauzl'; import * as crypto from 'crypto'; import { retry } from './retry'; import { CosmosClient } from '@azure/cosmos'; -import { ClientSecretCredential } from '@azure/identity'; import * as cp from 'child_process'; import * as os from 'os'; import { Worker, isMainThread, workerData } from 'node:worker_threads'; +import { ConfidentialClientApplication } from '@azure/msal-node'; +import { BlobClient, BlobServiceClient, ContainerClient } from '@azure/storage-blob'; +import * as jws from 'jws'; function e(name: string): string { const result = process.env[name]; @@ -27,345 +29,495 @@ function e(name: string): string { return result; } -class Temp { - private _files: string[] = []; +function hashStream(hashName: string, stream: Readable): Promise { + return new Promise((c, e) => { + const shasum = crypto.createHash(hashName); - tmpNameSync(): string { - const file = path.join(os.tmpdir(), crypto.randomBytes(20).toString('hex')); - this._files.push(file); - return file; - } + stream + .on('data', shasum.update.bind(shasum)) + .on('error', e) + .on('close', () => c(shasum.digest())); + }); +} - dispose(): void { - for (const file of this._files) { - try { - fs.unlinkSync(file); - } catch (err) { - // noop - } - } - } +interface ReleaseSubmitResponse { + operationId: string; + esrpCorrelationId: string; + code?: string; + message?: string; + target?: string; + innerError?: any; } -interface RequestOptions { - readonly body?: string; +interface ReleaseActivityInfo { + activityId: string; + activityType: string; + name: string; + status: string; + errorCode: number; + errorMessages: string[]; + beginTime?: Date; + endTime?: Date; + lastModifiedAt?: Date; } -interface CreateProvisionedFilesSuccessResponse { - IsSuccess: true; - ErrorDetails: null; +interface InnerServiceError { + code: string; + details: { [key: string]: string }; + innerError?: InnerServiceError; } -interface CreateProvisionedFilesErrorResponse { - IsSuccess: false; - ErrorDetails: { - Code: string; - Category: string; - Message: string; - CanRetry: boolean; - AdditionalProperties: Record; - }; +interface ReleaseError { + errorCode: number; + errorMessages: string[]; } -type CreateProvisionedFilesResponse = CreateProvisionedFilesSuccessResponse | CreateProvisionedFilesErrorResponse; +const enum StatusCode { + Pass = 'pass', + Inprogress = 'inprogress', + FailCanRetry = 'failCanRetry', + FailDoNotRetry = 'failDoNotRetry', + PendingAnalysis = 'pendingAnalysis', + Cancelled = 'cancelled' +} -function isCreateProvisionedFilesErrorResponse(response: unknown): response is CreateProvisionedFilesErrorResponse { - return (response as CreateProvisionedFilesErrorResponse)?.ErrorDetails?.Code !== undefined; +interface ReleaseResultMessage { + activities: ReleaseActivityInfo[]; + childWorkflowType: string; + clientId: string; + customerCorrelationId: string; + errorInfo: InnerServiceError; + groupId: string; + lastModifiedAt: Date; + operationId: string; + releaseError: ReleaseError; + requestSubmittedAt: Date; + routedRegion: string; + status: StatusCode; + totalFileCount: number; + totalReleaseSize: number; + version: string; } -class ProvisionService { +interface ReleaseFileInfo { + name?: string; + hash?: number[]; + sourceLocation?: FileLocation; + sizeInBytes?: number; + hashType?: FileHashType; + fileId?: any; + distributionRelativePath?: string; + partNumber?: string; + friendlyFileName?: string; + tenantFileLocationType?: string; + tenantFileLocation?: string; + signedEngineeringCopyLocation?: string; + encryptedDistributionBlobLocation?: string; + preEncryptedDistributionBlobLocation?: string; + secondaryDistributionHashRequired?: boolean; + secondaryDistributionHashType?: FileHashType; + lastModifiedAt?: Date; + cultureCodes?: string[]; + displayFileInDownloadCenter?: boolean; + isPrimaryFileInDownloadCenter?: boolean; + fileDownloadDetails?: FileDownloadDetails[]; +} - constructor( - private readonly log: (...args: any[]) => void, - private readonly accessToken: string - ) { } +interface ReleaseDetailsFileInfo extends ReleaseFileInfo { } + +interface ReleaseDetailsMessage extends ReleaseResultMessage { + clusterRegion: string; + correlationVector: string; + releaseCompletedAt?: Date; + releaseInfo: ReleaseInfo; + productInfo: ProductInfo; + createdBy: UserInfo; + owners: OwnerInfo[]; + accessPermissionsInfo: AccessPermissionsInfo; + files: ReleaseDetailsFileInfo[]; + comments: string[]; + cancellationReason: string; + downloadCenterInfo: DownloadCenterInfo; +} - async provision(releaseId: string, fileId: string, fileName: string) { - const body = JSON.stringify({ - ReleaseId: releaseId, - PortalName: 'VSCode', - PublisherCode: 'VSCode', - ProvisionedFilesCollection: [{ - PublisherKey: fileId, - IsStaticFriendlyFileName: true, - FriendlyFileName: fileName, - MaxTTL: '1440', - CdnMappings: ['ECN'] - }] - }); - this.log(`Provisioning ${fileName} (releaseId: ${releaseId}, fileId: ${fileId})...`); - const res = await retry(() => this.request('POST', '/api/v2/ProvisionedFiles/CreateProvisionedFiles', { body })); +interface ProductInfo { + name?: string; + version?: string; + description?: string; +} - if (isCreateProvisionedFilesErrorResponse(res) && res.ErrorDetails.Code === 'FriendlyFileNameAlreadyProvisioned') { - this.log(`File already provisioned (most likley due to a re-run), skipping: ${fileName}`); - return; - } +interface ReleaseInfo { + title?: string; + minimumNumberOfApprovers: number; + properties?: { [key: string]: string }; + isRevision?: boolean; + revisionNumber?: string; +} - if (!res.IsSuccess) { - throw new Error(`Failed to submit provisioning request: ${JSON.stringify(res.ErrorDetails)}`); - } +type FileLocationType = 'azureBlob'; - this.log(`Successfully provisioned ${fileName}`); - } +interface FileLocation { + type: FileLocationType; + blobUrl: string; + uncPath?: string; + url?: string; +} - private async request(method: string, url: string, options?: RequestOptions): Promise { - const opts: RequestInit = { - method, - body: options?.body, - headers: { - Authorization: `Bearer ${this.accessToken}`, - 'Content-Type': 'application/json' - } - }; +type FileHashType = 'sha256' | 'sha1'; - const res = await fetch(`https://dsprovisionapi.microsoft.com${url}`, opts); +interface FileDownloadDetails { + portalName: string; + downloadUrl: string; +} +interface RoutingInfo { + intent?: string; + contentType?: string; + contentOrigin?: string; + productState?: string; + audience?: string; +} - // 400 normally means the request is bad or something is already provisioned, so we will return as retries are useless - // Otherwise log the text body and headers. We do text because some responses are not JSON. - if ((!res.ok || res.status < 200 || res.status >= 500) && res.status !== 400) { - throw new Error(`Unexpected status code: ${res.status}\nResponse Headers: ${JSON.stringify(res.headers)}\nBody Text: ${await res.text()}`); - } +interface ReleaseFileInfo { + name?: string; + hash?: number[]; + sourceLocation?: FileLocation; + sizeInBytes?: number; + hashType?: FileHashType; + fileId?: any; + distributionRelativePath?: string; + partNumber?: string; + friendlyFileName?: string; + tenantFileLocationType?: string; + tenantFileLocation?: string; + signedEngineeringCopyLocation?: string; + encryptedDistributionBlobLocation?: string; + preEncryptedDistributionBlobLocation?: string; + secondaryDistributionHashRequired?: boolean; + secondaryDistributionHashType?: FileHashType; + lastModifiedAt?: Date; + cultureCodes?: string[]; + displayFileInDownloadCenter?: boolean; + isPrimaryFileInDownloadCenter?: boolean; + fileDownloadDetails?: FileDownloadDetails[]; +} - return await res.json(); - } +interface UserInfo { + userPrincipalName?: string; } -function hashStream(hashName: string, stream: Readable): Promise { - return new Promise((c, e) => { - const shasum = crypto.createHash(hashName); +interface OwnerInfo { + owner: UserInfo; +} - stream - .on('data', shasum.update.bind(shasum)) - .on('error', e) - .on('close', () => c(shasum.digest('hex'))); - }); +interface ApproverInfo { + approver: UserInfo; + isAutoApproved: boolean; + isMandatory: boolean; } -interface Release { - readonly releaseId: string; - readonly fileId: string; +interface AccessPermissionsInfo { + mainPublisher?: string; + releasePublishers?: string[]; + channelDownloadEntityDetails?: { [key: string]: string[] }; } -interface SubmitReleaseResult { - submissionResponse: { - operationId: string; - statusCode: string; - }; +interface DownloadCenterLocaleInfo { + cultureCode?: string; + downloadTitle?: string; + shortName?: string; + shortDescription?: string; + longDescription?: string; + instructions?: string; + additionalInfo?: string; + keywords?: string[]; + version?: string; + relatedLinks?: { [key: string]: URL }; } -interface ReleaseDetailsResult { - releaseDetails: [{ - fileDetails: [{ publisherKey: string }]; - statusCode: 'inprogress' | 'pass'; - }]; +interface DownloadCenterInfo { + downloadCenterId: number; + publishToDownloadCenter?: boolean; + publishingGroup?: string; + operatingSystems?: string[]; + relatedReleases?: string[]; + kbNumbers?: string[]; + sbNumbers?: string[]; + locales?: DownloadCenterLocaleInfo[]; + additionalProperties?: { [key: string]: string }; } -class ESRPClient { +interface ReleaseRequestMessage { + driEmail: string[]; + groupId?: string; + customerCorrelationId: string; + esrpCorrelationId: string; + contextData?: { [key: string]: string }; + releaseInfo: ReleaseInfo; + productInfo: ProductInfo; + files: ReleaseFileInfo[]; + routingInfo?: RoutingInfo; + createdBy: UserInfo; + owners: OwnerInfo[]; + approvers: ApproverInfo[]; + accessPermissionsInfo: AccessPermissionsInfo; + jwsToken?: string; + publisherId?: string; + downloadCenterInfo?: DownloadCenterInfo; +} - private readonly authPath: string; +function getCertificateBuffer(input: string) { + return Buffer.from(input.replace(/-----BEGIN CERTIFICATE-----|-----END CERTIFICATE-----|\n/g, ''), 'base64'); +} - constructor( - private readonly log: (...args: any[]) => void, - private readonly tmp: Temp, +function getThumbprint(input: string, algorithm: string): Buffer { + const buffer = getCertificateBuffer(input); + return crypto.createHash(algorithm).update(buffer).digest(); +} + +function getKeyFromPFX(pfx: string): string { + const pfxCertificatePath = path.join(os.tmpdir(), 'cert.pfx'); + const pemKeyPath = path.join(os.tmpdir(), 'key.pem'); + + try { + const pfxCertificate = Buffer.from(pfx, 'base64'); + fs.writeFileSync(pfxCertificatePath, pfxCertificate); + cp.execSync(`openssl pkcs12 -in "${pfxCertificatePath}" -nocerts -nodes -out "${pemKeyPath}" -passin pass:`); + const raw = fs.readFileSync(pemKeyPath, 'utf-8'); + const result = raw.match(/-----BEGIN PRIVATE KEY-----[\s\S]+?-----END PRIVATE KEY-----/g)![0]; + return result; + } finally { + fs.rmSync(pfxCertificatePath, { force: true }); + fs.rmSync(pemKeyPath, { force: true }); + } +} + +function getCertificatesFromPFX(pfx: string): string[] { + const pfxCertificatePath = path.join(os.tmpdir(), 'cert.pfx'); + const pemCertificatePath = path.join(os.tmpdir(), 'cert.pem'); + + try { + const pfxCertificate = Buffer.from(pfx, 'base64'); + fs.writeFileSync(pfxCertificatePath, pfxCertificate); + cp.execSync(`openssl pkcs12 -in "${pfxCertificatePath}" -nokeys -out "${pemCertificatePath}" -passin pass:`); + const raw = fs.readFileSync(pemCertificatePath, 'utf-8'); + const matches = raw.match(/-----BEGIN CERTIFICATE-----[\s\S]+?-----END CERTIFICATE-----/g); + return matches ? matches.reverse() : []; + } finally { + fs.rmSync(pfxCertificatePath, { force: true }); + fs.rmSync(pemCertificatePath, { force: true }); + } +} + +class ESRPReleaseService { + + static async create( + log: (...args: any[]) => void, tenantId: string, clientId: string, - authCertSubjectName: string, - requestSigningCertSubjectName: string, + authCertificatePfx: string, + requestSigningCertificatePfx: string, + containerClient: ContainerClient ) { - this.authPath = this.tmp.tmpNameSync(); - fs.writeFileSync(this.authPath, JSON.stringify({ - Version: '1.0.0', - AuthenticationType: 'AAD_CERT', - TenantId: tenantId, - ClientId: clientId, - AuthCert: { - SubjectName: authCertSubjectName, - StoreLocation: 'LocalMachine', - StoreName: 'My', - SendX5c: 'true' - }, - RequestSigningCert: { - SubjectName: requestSigningCertSubjectName, - StoreLocation: 'LocalMachine', - StoreName: 'My' + const authKey = getKeyFromPFX(authCertificatePfx); + const authCertificate = getCertificatesFromPFX(authCertificatePfx)[0]; + const requestSigningKey = getKeyFromPFX(requestSigningCertificatePfx); + const requestSigningCertificates = getCertificatesFromPFX(requestSigningCertificatePfx); + + const app = new ConfidentialClientApplication({ + auth: { + clientId, + authority: `https://login.microsoftonline.com/${tenantId}`, + clientCertificate: { + thumbprintSha256: getThumbprint(authCertificate, 'sha256').toString('hex'), + privateKey: authKey, + x5c: authCertificate + } } - })); + }); + + const response = await app.acquireTokenByClientCredential({ + scopes: ['https://api.esrp.microsoft.com/.default'] + }); + + return new ESRPReleaseService(log, clientId, response!.accessToken, requestSigningCertificates, requestSigningKey, containerClient); } - async release( - version: string, - filePath: string - ): Promise { - this.log(`Submitting release for ${version}: ${filePath}`); - const submitReleaseResult = await this.SubmitRelease(version, filePath); + private static API_URL = 'https://api.esrp.microsoft.com/api/v3/releaseservices/clients/'; - if (submitReleaseResult.submissionResponse.statusCode !== 'pass') { - throw new Error(`Unexpected status code: ${submitReleaseResult.submissionResponse.statusCode}`); - } + private constructor( + private readonly log: (...args: any[]) => void, + private readonly clientId: string, + private readonly accessToken: string, + private readonly requestSigningCertificates: string[], + private readonly requestSigningKey: string, + private readonly containerClient: ContainerClient + ) { } - const releaseId = submitReleaseResult.submissionResponse.operationId; - this.log(`Successfully submitted release ${releaseId}. Polling for completion...`); + async createRelease(version: string, filePath: string, friendlyFileName: string) { + const correlationId = crypto.randomUUID(); + const blobClient = this.containerClient.getBlockBlobClient(correlationId); - let details!: ReleaseDetailsResult; + this.log(`Uploading ${filePath} to ${blobClient.url}`); + await blobClient.uploadFile(filePath); + this.log('Uploaded blob successfully'); - // Poll every 5 seconds, wait 60 minutes max -> poll 60/5*60=720 times - for (let i = 0; i < 720; i++) { - details = await this.ReleaseDetails(releaseId); + try { + this.log(`Submitting release for ${version}: ${filePath}`); + const submitReleaseResult = await this.submitRelease(version, filePath, friendlyFileName, correlationId, blobClient); - if (details.releaseDetails[0].statusCode === 'pass') { - break; - } else if (details.releaseDetails[0].statusCode !== 'inprogress') { - throw new Error(`Failed to submit release: ${JSON.stringify(details)}`); - } + this.log(`Successfully submitted release ${submitReleaseResult.operationId}. Polling for completion...`); - await new Promise(c => setTimeout(c, 5000)); - } + // Poll every 5 seconds, wait 60 minutes max -> poll 60/5*60=720 times + for (let i = 0; i < 720; i++) { + await new Promise(c => setTimeout(c, 5000)); + const releaseStatus = await this.getReleaseStatus(submitReleaseResult.operationId); - if (details.releaseDetails[0].statusCode !== 'pass') { - throw new Error(`Timed out waiting for release ${releaseId}: ${JSON.stringify(details)}`); - } + if (releaseStatus.status === 'pass') { + break; + } else if (releaseStatus.status !== 'inprogress') { + throw new Error(`Failed to submit release: ${JSON.stringify(releaseStatus)}`); + } + } - const fileId = details.releaseDetails[0].fileDetails[0].publisherKey; - this.log('Release completed successfully with fileId: ', fileId); + const releaseDetails = await this.getReleaseDetails(submitReleaseResult.operationId); + + if (releaseDetails.status !== 'pass') { + throw new Error(`Timed out waiting for release: ${JSON.stringify(releaseDetails)}`); + } - return { releaseId, fileId }; + this.log('Successfully created release:', releaseDetails.files[0].fileDownloadDetails![0].downloadUrl); + return releaseDetails.files[0].fileDownloadDetails![0].downloadUrl; + } finally { + this.log(`Deleting blob ${blobClient.url}`); + await blobClient.delete(); + this.log('Deleted blob successfully'); + } } - private async SubmitRelease( + private async submitRelease( version: string, - filePath: string - ): Promise { - const policyPath = this.tmp.tmpNameSync(); - fs.writeFileSync(policyPath, JSON.stringify({ - Version: '1.0.0', - Audience: 'InternalLimited', - Intent: 'distribution', - ContentType: 'InstallPackage' - })); - - const inputPath = this.tmp.tmpNameSync(); + filePath: string, + friendlyFileName: string, + correlationId: string, + blobClient: BlobClient + ): Promise { const size = fs.statSync(filePath).size; - const istream = fs.createReadStream(filePath); - const sha256 = await hashStream('sha256', istream); - fs.writeFileSync(inputPath, JSON.stringify({ - Version: '1.0.0', - ReleaseInfo: { - ReleaseMetadata: { - Title: 'VS Code', - Properties: { - ReleaseContentType: 'InstallPackage' - }, - MinimumNumberOfApprovers: 1 - }, - ProductInfo: { - Name: 'VS Code', - Version: version, - Description: path.basename(filePath, path.extname(filePath)), - }, - Owners: [ - { - Owner: { - UserPrincipalName: 'jomo@microsoft.com' - } - } - ], - Approvers: [ - { - Approver: { - UserPrincipalName: 'jomo@microsoft.com' - }, - IsAutoApproved: true, - IsMandatory: false - } - ], - AccessPermissions: { - MainPublisher: 'VSCode', - ChannelDownloadEntityDetails: { - Consumer: ['VSCode'] - } + const hash = await hashStream('sha256', fs.createReadStream(filePath)); + + const message: ReleaseRequestMessage = { + customerCorrelationId: correlationId, + esrpCorrelationId: correlationId, + driEmail: ['joao.moreno@microsoft.com'], + createdBy: { userPrincipalName: 'jomo@microsoft.com' }, + owners: [{ owner: { userPrincipalName: 'jomo@microsoft.com' } }], + approvers: [{ approver: { userPrincipalName: 'jomo@microsoft.com' }, isAutoApproved: true, isMandatory: false }], + releaseInfo: { + title: 'VS Code', + properties: { + 'ReleaseContentType': 'InstallPackage' }, - CreatedBy: { - UserPrincipalName: 'jomo@microsoft.com' - } + minimumNumberOfApprovers: 1 }, - ReleaseBatches: [ - { - ReleaseRequestFiles: [ - { - SizeInBytes: size, - SourceHash: sha256, - HashType: 'SHA256', - SourceLocation: path.basename(filePath) - } - ], - SourceLocationType: 'UNC', - SourceRootDirectory: path.dirname(filePath), - DestinationLocationType: 'AzureBlob' + productInfo: { + name: 'VS Code', + version, + description: 'VS Code' + }, + accessPermissionsInfo: { + mainPublisher: 'VSCode', + channelDownloadEntityDetails: { + AllDownloadEntities: ['VSCode'] } - ] - })); - - const outputPath = this.tmp.tmpNameSync(); - cp.execSync(`ESRPClient SubmitRelease -a ${this.authPath} -p ${policyPath} -i ${inputPath} -o ${outputPath}`, { stdio: 'inherit' }); + }, + routingInfo: { + intent: 'filedownloadlinkgeneration' + }, + files: [{ + name: path.basename(filePath), + friendlyFileName, + tenantFileLocation: blobClient.url, + tenantFileLocationType: 'AzureBlob', + sourceLocation: { + type: 'azureBlob', + blobUrl: blobClient.url + }, + hashType: 'sha256', + hash: Array.from(hash), + sizeInBytes: size + }] + }; - const output = fs.readFileSync(outputPath, 'utf8'); - return JSON.parse(output) as SubmitReleaseResult; - } + message.jwsToken = await this.generateJwsToken(message); - private async ReleaseDetails( - releaseId: string - ): Promise { - const inputPath = this.tmp.tmpNameSync(); - fs.writeFileSync(inputPath, JSON.stringify({ - Version: '1.0.0', - OperationIds: [releaseId] - })); + const res = await fetch(`${ESRPReleaseService.API_URL}${this.clientId}/workflows/release/operations`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${this.accessToken}` + }, + body: JSON.stringify(message) + }); - const outputPath = this.tmp.tmpNameSync(); - cp.execSync(`ESRPClient ReleaseDetails -a ${this.authPath} -i ${inputPath} -o ${outputPath}`, { stdio: 'inherit' }); + if (!res.ok) { + const text = await res.text(); + throw new Error(`Failed to submit release: ${res.statusText}\n${text}`); + } - const output = fs.readFileSync(outputPath, 'utf8'); - return JSON.parse(output) as ReleaseDetailsResult; + return await res.json() as ReleaseSubmitResponse; } -} -async function releaseAndProvision( - log: (...args: any[]) => void, - releaseTenantId: string, - releaseClientId: string, - releaseAuthCertSubjectName: string, - releaseRequestSigningCertSubjectName: string, - provisionTenantId: string, - provisionAADUsername: string, - provisionAADPassword: string, - version: string, - quality: string, - filePath: string -): Promise { - const fileName = `${quality}/${version}/${path.basename(filePath)}`; - const result = `${e('PRSS_CDN_URL')}/${fileName}`; + private async getReleaseStatus(releaseId: string): Promise { + const url = `${ESRPReleaseService.API_URL}${this.clientId}/workflows/release/operations/grs/${releaseId}`; - const res = await retry(() => fetch(result)); + const res = await fetch(url, { + headers: { + 'Authorization': `Bearer ${this.accessToken}` + } + }); - if (res.status === 200) { - log(`Already released and provisioned: ${result}`); - return result; + if (!res.ok) { + const text = await res.text(); + throw new Error(`Failed to get release status: ${res.statusText}\n${text}`); + } + + return await res.json() as ReleaseResultMessage; } - const tmp = new Temp(); - process.on('exit', () => tmp.dispose()); + private async getReleaseDetails(releaseId: string): Promise { + const url = `${ESRPReleaseService.API_URL}${this.clientId}/workflows/release/operations/grd/${releaseId}`; - const esrpclient = new ESRPClient(log, tmp, releaseTenantId, releaseClientId, releaseAuthCertSubjectName, releaseRequestSigningCertSubjectName); - const release = await esrpclient.release(version, filePath); + const res = await fetch(url, { + headers: { + 'Authorization': `Bearer ${this.accessToken}` + } + }); - const credential = new ClientSecretCredential(provisionTenantId, provisionAADUsername, provisionAADPassword); - const accessToken = await credential.getToken(['https://microsoft.onmicrosoft.com/DS.Provisioning.WebApi/.default']); - const service = new ProvisionService(log, accessToken.token); - await service.provision(release.releaseId, release.fileId, fileName); + if (!res.ok) { + const text = await res.text(); + throw new Error(`Failed to get release status: ${res.statusText}\n${text}`); + } - return result; + return await res.json() as ReleaseDetailsMessage; + } + + private async generateJwsToken(message: ReleaseRequestMessage): Promise { + return jws.sign({ + header: { + alg: 'RS256', + crit: ['exp', 'x5t'], + // Release service uses ticks, not seconds :roll_eyes: (https://stackoverflow.com/a/7968483) + exp: ((Date.now() + (6 * 60 * 1000)) * 10000) + 621355968000000000, + // Release service uses hex format, not base64url :roll_eyes: + x5t: getThumbprint(this.requestSigningCertificates[0], 'sha1').toString('hex'), + // Release service uses a '.' separated string, not an array of strings :roll_eyes: + x5c: this.requestSigningCertificates.map(c => getCertificateBuffer(c).toString('base64url')).join('.') as any, + }, + payload: message, + privateKey: this.requestSigningKey, + }); + } } class State { @@ -636,8 +788,10 @@ function getRealType(type: string) { } } -async function processArtifact(artifact: Artifact, artifactFilePath: string): Promise { - const log = (...args: any[]) => console.log(`[${artifact.name}]`, ...args); +async function processArtifact( + artifact: Artifact, + filePath: string +) { const match = /^vscode_(?[^_]+)_(?[^_]+)(?:_legacy)?_(?[^_]+)_(?[^_]+)$/.exec(artifact.name); if (!match) { @@ -645,39 +799,48 @@ async function processArtifact(artifact: Artifact, artifactFilePath: string): Pr } // getPlatform needs the unprocessedType + const { cosmosDBAccessToken, blobServiceAccessToken } = JSON.parse(e('PUBLISH_AUTH_TOKENS')); const quality = e('VSCODE_QUALITY'); - const commit = e('BUILD_SOURCEVERSION'); + const version = e('BUILD_SOURCEVERSION'); const { product, os, arch, unprocessedType } = match.groups!; const isLegacy = artifact.name.includes('_legacy'); const platform = getPlatform(product, os, arch, unprocessedType, isLegacy); const type = getRealType(unprocessedType); - const size = fs.statSync(artifactFilePath).size; - const stream = fs.createReadStream(artifactFilePath); + const size = fs.statSync(filePath).size; + const stream = fs.createReadStream(filePath); const [hash, sha256hash] = await Promise.all([hashStream('sha1', stream), hashStream('sha256', stream)]); // CodeQL [SM04514] Using SHA1 only for legacy reasons, we are actually only respecting SHA256 - const url = await releaseAndProvision( + const log = (...args: any[]) => console.log(`[${artifact.name}]`, ...args); + const blobServiceClient = new BlobServiceClient(`https://${e('VSCODE_STAGING_BLOB_STORAGE_ACCOUNT_NAME')}.blob.core.windows.net/`, { getToken: async () => blobServiceAccessToken }); + const containerClient = blobServiceClient.getContainerClient('staging'); + + const releaseService = await ESRPReleaseService.create( log, e('RELEASE_TENANT_ID'), e('RELEASE_CLIENT_ID'), - e('RELEASE_AUTH_CERT_SUBJECT_NAME'), - e('RELEASE_REQUEST_SIGNING_CERT_SUBJECT_NAME'), - e('PROVISION_TENANT_ID'), - e('PROVISION_AAD_USERNAME'), - e('PROVISION_AAD_PASSWORD'), - commit, - quality, - artifactFilePath + e('RELEASE_AUTH_CERT'), + e('RELEASE_REQUEST_SIGNING_CERT'), + containerClient ); - const asset: Asset = { platform, type, url, hash, sha256hash, size, supportsFastUpdate: true }; + const friendlyFileName = `${quality}/${version}/${path.basename(filePath)}`; + const url = `${e('PRSS_CDN_URL')}/${friendlyFileName}`; + const res = await retry(() => fetch(url)); + + if (res.status === 200) { + log(`Already released and provisioned: ${url}`); + } else { + await releaseService.createRelease(version, filePath, friendlyFileName); + } + + const asset: Asset = { platform, type, url, hash: hash.toString('hex'), sha256hash: sha256hash.toString('hex'), size, supportsFastUpdate: true }; log('Creating asset...', JSON.stringify(asset, undefined, 2)); await retry(async (attempt) => { log(`Creating asset in Cosmos DB (attempt ${attempt})...`); - const aadCredentials = new ClientSecretCredential(e('AZURE_TENANT_ID'), e('AZURE_CLIENT_ID'), e('AZURE_CLIENT_SECRET')); - const client = new CosmosClient({ endpoint: e('AZURE_DOCUMENTDB_ENDPOINT'), aadCredentials }); + const client = new CosmosClient({ endpoint: e('AZURE_DOCUMENTDB_ENDPOINT')!, tokenProvider: () => Promise.resolve(`type=aad&ver=1.0&sig=${cosmosDBAccessToken.token}`) }); const scripts = client.database('builds').container(quality).scripts; - await scripts.storedProcedure('createAsset').execute('', [commit, asset, true]); + await scripts.storedProcedure('createAsset').execute('', [version, asset, true]); }); log('Asset successfully created'); diff --git a/build/azure-pipelines/common/releaseBuild.js b/build/azure-pipelines/common/releaseBuild.js index c2aab2075d0ec..fa69cb4e258a5 100644 --- a/build/azure-pipelines/common/releaseBuild.js +++ b/build/azure-pipelines/common/releaseBuild.js @@ -31,7 +31,7 @@ async function getConfig(client, quality) { async function main(force) { const commit = getEnv('BUILD_SOURCEVERSION'); const quality = getEnv('VSCODE_QUALITY'); - const aadCredentials = new identity_1.ClientSecretCredential(process.env['AZURE_TENANT_ID'], process.env['AZURE_CLIENT_ID'], process.env['AZURE_CLIENT_SECRET']); + const aadCredentials = new identity_1.ClientAssertionCredential(process.env['AZURE_TENANT_ID'], process.env['AZURE_CLIENT_ID'], () => Promise.resolve(process.env['AZURE_ID_TOKEN'])); const client = new cosmos_1.CosmosClient({ endpoint: process.env['AZURE_DOCUMENTDB_ENDPOINT'], aadCredentials }); if (!force) { const config = await getConfig(client, quality); diff --git a/build/azure-pipelines/common/releaseBuild.ts b/build/azure-pipelines/common/releaseBuild.ts index 2e8fff04fb409..b7762de7df608 100644 --- a/build/azure-pipelines/common/releaseBuild.ts +++ b/build/azure-pipelines/common/releaseBuild.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ClientSecretCredential } from '@azure/identity'; +import { ClientAssertionCredential } from '@azure/identity'; import { CosmosClient } from '@azure/cosmos'; import { retry } from './retry'; @@ -45,7 +45,7 @@ async function main(force: boolean): Promise { const commit = getEnv('BUILD_SOURCEVERSION'); const quality = getEnv('VSCODE_QUALITY'); - const aadCredentials = new ClientSecretCredential(process.env['AZURE_TENANT_ID']!, process.env['AZURE_CLIENT_ID']!, process.env['AZURE_CLIENT_SECRET']!); + const aadCredentials = new ClientAssertionCredential(process.env['AZURE_TENANT_ID']!, process.env['AZURE_CLIENT_ID']!, () => Promise.resolve(process.env['AZURE_ID_TOKEN']!)); const client = new CosmosClient({ endpoint: process.env['AZURE_DOCUMENTDB_ENDPOINT']!, aadCredentials }); if (!force) { diff --git a/build/azure-pipelines/common/sign-win32.js b/build/azure-pipelines/common/sign-win32.js index da899cd3fc096..aa197bb1198bb 100644 --- a/build/azure-pipelines/common/sign-win32.js +++ b/build/azure-pipelines/common/sign-win32.js @@ -9,9 +9,6 @@ const path = require("path"); (0, sign_1.main)([ process.env['EsrpCliDllPath'], 'sign-windows', - process.env['ESRPPKI'], - process.env['ESRPAADUsername'], - process.env['ESRPAADPassword'], path.dirname(process.argv[2]), path.basename(process.argv[2]) ]); diff --git a/build/azure-pipelines/common/sign-win32.ts b/build/azure-pipelines/common/sign-win32.ts index 76828b42e1eb5..c2f3dbda1516f 100644 --- a/build/azure-pipelines/common/sign-win32.ts +++ b/build/azure-pipelines/common/sign-win32.ts @@ -9,9 +9,6 @@ import * as path from 'path'; main([ process.env['EsrpCliDllPath']!, 'sign-windows', - process.env['ESRPPKI']!, - process.env['ESRPAADUsername']!, - process.env['ESRPAADPassword']!, path.dirname(process.argv[2]), path.basename(process.argv[2]) ]); diff --git a/build/azure-pipelines/common/sign.js b/build/azure-pipelines/common/sign.js index 32996a7db0309..f944e1adff8a7 100644 --- a/build/azure-pipelines/common/sign.js +++ b/build/azure-pipelines/common/sign.js @@ -109,33 +109,34 @@ function getParams(type) { throw new Error(`Sign type ${type} not found`); } } -function main([esrpCliPath, type, cert, username, password, folderPath, pattern]) { +function main([esrpCliPath, type, folderPath, pattern]) { const tmp = new Temp(); process.on('exit', () => tmp.dispose()); const patternPath = tmp.tmpNameSync(); fs.writeFileSync(patternPath, pattern); const paramsPath = tmp.tmpNameSync(); fs.writeFileSync(paramsPath, JSON.stringify(getParams(type))); - const keyFile = tmp.tmpNameSync(); - const key = crypto.randomBytes(32); - const iv = crypto.randomBytes(16); - fs.writeFileSync(keyFile, JSON.stringify({ key: key.toString('hex'), iv: iv.toString('hex') })); - const clientkeyPath = tmp.tmpNameSync(); - const clientkeyCypher = crypto.createCipheriv('aes-256-cbc', key, iv); - let clientkey = clientkeyCypher.update(password, 'utf8', 'hex'); - clientkey += clientkeyCypher.final('hex'); - fs.writeFileSync(clientkeyPath, clientkey); - const clientcertPath = tmp.tmpNameSync(); - const clientcertCypher = crypto.createCipheriv('aes-256-cbc', key, iv); - let clientcert = clientcertCypher.update(cert, 'utf8', 'hex'); - clientcert += clientcertCypher.final('hex'); - fs.writeFileSync(clientcertPath, clientcert); + const dotnetVersion = cp.execSync('dotnet --version', { encoding: 'utf8' }).trim(); + const adoTaskVersion = path.basename(path.dirname(path.dirname(esrpCliPath))); + const federatedTokenData = { + jobId: process.env['SYSTEM_JOBID'], + planId: process.env['SYSTEM_PLANID'], + projectId: process.env['SYSTEM_TEAMPROJECTID'], + hub: process.env['SYSTEM_HOSTTYPE'], + uri: process.env['SYSTEM_COLLECTIONURI'], + managedIdentityId: process.env['VSCODE_ESRP_CLIENT_ID'], + managedIdentityTenantId: process.env['VSCODE_ESRP_TENANT_ID'], + serviceConnectionId: process.env['VSCODE_ESRP_SERVICE_CONNECTION_ID'], + tempDirectory: os.tmpdir(), + systemAccessToken: process.env['SYSTEM_ACCESSTOKEN'] + }; const args = [ esrpCliPath, 'vsts.sign', - '-a', username, - '-k', clientkeyPath, - '-z', clientcertPath, + '-a', process.env['ESRP_CLIENT_ID'], + '-d', process.env['ESRP_TENANT_ID'], + '-k', JSON.stringify({ akv: 'vscode-esrp' }), + '-z', JSON.stringify({ akv: 'vscode-esrp', cert: 'esrp-sign' }), '-f', folderPath, '-p', patternPath, '-u', 'false', @@ -154,7 +155,14 @@ function main([esrpCliPath, type, cert, username, password, folderPath, pattern] '-i', 'https://www.microsoft.com', '-n', '5', '-r', 'true', - '-e', keyFile, + '-w', dotnetVersion, + '-skipAdoReportAttachment', 'false', + '-pendingAnalysisWaitTimeoutMinutes', '5', + '-adoTaskVersion', adoTaskVersion, + '-resourceUri', 'https://msazurecloud.onmicrosoft.com/api.esrp.microsoft.com', + '-esrpClientId', process.env['ESRP_CLIENT_ID'], + '-useMSIAuthentication', 'true', + '-federatedTokenData', JSON.stringify(federatedTokenData) ]; try { cp.execFileSync('dotnet', args, { stdio: 'inherit' }); diff --git a/build/azure-pipelines/common/sign.ts b/build/azure-pipelines/common/sign.ts index 28fca31205e93..184ea5b334227 100644 --- a/build/azure-pipelines/common/sign.ts +++ b/build/azure-pipelines/common/sign.ts @@ -120,7 +120,7 @@ function getParams(type: string): Params[] { } } -export function main([esrpCliPath, type, cert, username, password, folderPath, pattern]: string[]) { +export function main([esrpCliPath, type, folderPath, pattern]: string[]) { const tmp = new Temp(); process.on('exit', () => tmp.dispose()); @@ -130,29 +130,29 @@ export function main([esrpCliPath, type, cert, username, password, folderPath, p const paramsPath = tmp.tmpNameSync(); fs.writeFileSync(paramsPath, JSON.stringify(getParams(type))); - const keyFile = tmp.tmpNameSync(); - const key = crypto.randomBytes(32); - const iv = crypto.randomBytes(16); - fs.writeFileSync(keyFile, JSON.stringify({ key: key.toString('hex'), iv: iv.toString('hex') })); + const dotnetVersion = cp.execSync('dotnet --version', { encoding: 'utf8' }).trim(); + const adoTaskVersion = path.basename(path.dirname(path.dirname(esrpCliPath))); - const clientkeyPath = tmp.tmpNameSync(); - const clientkeyCypher = crypto.createCipheriv('aes-256-cbc', key, iv); - let clientkey = clientkeyCypher.update(password, 'utf8', 'hex'); - clientkey += clientkeyCypher.final('hex'); - fs.writeFileSync(clientkeyPath, clientkey); - - const clientcertPath = tmp.tmpNameSync(); - const clientcertCypher = crypto.createCipheriv('aes-256-cbc', key, iv); - let clientcert = clientcertCypher.update(cert, 'utf8', 'hex'); - clientcert += clientcertCypher.final('hex'); - fs.writeFileSync(clientcertPath, clientcert); + const federatedTokenData = { + jobId: process.env['SYSTEM_JOBID'], + planId: process.env['SYSTEM_PLANID'], + projectId: process.env['SYSTEM_TEAMPROJECTID'], + hub: process.env['SYSTEM_HOSTTYPE'], + uri: process.env['SYSTEM_COLLECTIONURI'], + managedIdentityId: process.env['VSCODE_ESRP_CLIENT_ID'], + managedIdentityTenantId: process.env['VSCODE_ESRP_TENANT_ID'], + serviceConnectionId: process.env['VSCODE_ESRP_SERVICE_CONNECTION_ID'], + tempDirectory: os.tmpdir(), + systemAccessToken: process.env['SYSTEM_ACCESSTOKEN'] + }; const args = [ esrpCliPath, 'vsts.sign', - '-a', username, - '-k', clientkeyPath, - '-z', clientcertPath, + '-a', process.env['ESRP_CLIENT_ID']!, + '-d', process.env['ESRP_TENANT_ID']!, + '-k', JSON.stringify({ akv: 'vscode-esrp' }), + '-z', JSON.stringify({ akv: 'vscode-esrp', cert: 'esrp-sign' }), '-f', folderPath, '-p', patternPath, '-u', 'false', @@ -171,7 +171,14 @@ export function main([esrpCliPath, type, cert, username, password, folderPath, p '-i', 'https://www.microsoft.com', '-n', '5', '-r', 'true', - '-e', keyFile, + '-w', dotnetVersion, + '-skipAdoReportAttachment', 'false', + '-pendingAnalysisWaitTimeoutMinutes', '5', + '-adoTaskVersion', adoTaskVersion, + '-resourceUri', 'https://msazurecloud.onmicrosoft.com/api.esrp.microsoft.com', + '-esrpClientId', process.env['ESRP_CLIENT_ID']!, + '-useMSIAuthentication', 'true', + '-federatedTokenData', JSON.stringify(federatedTokenData) ]; try { diff --git a/build/azure-pipelines/darwin/product-build-darwin-sign.yml b/build/azure-pipelines/darwin/product-build-darwin-sign.yml index 82a1e89f2ab1f..8ae5c217db8ef 100644 --- a/build/azure-pipelines/darwin/product-build-darwin-sign.yml +++ b/build/azure-pipelines/darwin/product-build-darwin-sign.yml @@ -9,25 +9,30 @@ steps: inputs: version: 6.x - - task: EsrpClientTool@1 - continueOnError: true - displayName: Download ESRPClient - - - task: AzureKeyVault@2 - displayName: "Azure Key Vault: Get Secrets" + - task: EsrpCodeSigning@5 inputs: - azureSubscription: "vscode-builds-subscription" - KeyVaultName: vscode-build-secrets - SecretsFilter: "ESRP-PKI,esrp-aad-username,esrp-aad-password" + UseMSIAuthentication: true + ConnectedServiceName: vscode-esrp + AppRegistrationClientId: $(ESRP_CLIENT_ID) + AppRegistrationTenantId: $(ESRP_TENANT_ID) + AuthAKVName: vscode-esrp + AuthSignCertName: esrp-sign + FolderPath: . + Pattern: noop + displayName: 'Install ESRP Tooling' - download: current artifact: unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive displayName: Download $(VSCODE_ARCH) artifact - - script: node build/azure-pipelines/common/sign $(Agent.ToolsDirectory)/esrpclient/*/*/net6.0/esrpcli.dll sign-darwin $(ESRP-PKI) $(esrp-aad-username) $(esrp-aad-password) $(Pipeline.Workspace)/unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive VSCode-darwin-$(VSCODE_ARCH).zip + - script: node build/azure-pipelines/common/sign $(Agent.RootDirectory)/_tasks/EsrpCodeSigning_*/*/net6.0/esrpcli.dll sign-darwin $(Pipeline.Workspace)/unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive VSCode-darwin-$(VSCODE_ARCH).zip + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) displayName: Codesign - - script: node build/azure-pipelines/common/sign $(Agent.ToolsDirectory)/esrpclient/*/*/net6.0/esrpcli.dll notarize-darwin $(ESRP-PKI) $(esrp-aad-username) $(esrp-aad-password) $(Pipeline.Workspace)/unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive VSCode-darwin-$(VSCODE_ARCH).zip + - script: node build/azure-pipelines/common/sign $(Agent.RootDirectory)/_tasks/EsrpCodeSigning_*/*/net6.0/esrpcli.dll notarize-darwin $(Pipeline.Workspace)/unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive VSCode-darwin-$(VSCODE_ARCH).zip + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) displayName: Notarize - script: unzip $(Pipeline.Workspace)/unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin-$(VSCODE_ARCH).zip -d $(Agent.BuildDirectory)/VSCode-darwin-$(VSCODE_ARCH) diff --git a/build/azure-pipelines/darwin/product-build-darwin-test.yml b/build/azure-pipelines/darwin/product-build-darwin-test.yml index 359aa460a5901..9e054574c81cf 100644 --- a/build/azure-pipelines/darwin/product-build-darwin-test.yml +++ b/build/azure-pipelines/darwin/product-build-darwin-test.yml @@ -61,6 +61,7 @@ steps: compile-extension:typescript-language-features \ compile-extension:vscode-api-tests \ compile-extension:vscode-colorize-tests \ + compile-extension:vscode-colorize-perf-tests \ compile-extension:vscode-test-resolver displayName: Build integration tests diff --git a/build/azure-pipelines/darwin/product-build-darwin-universal.yml b/build/azure-pipelines/darwin/product-build-darwin-universal.yml index e9c532857955e..27408f71432e2 100644 --- a/build/azure-pipelines/darwin/product-build-darwin-universal.yml +++ b/build/azure-pipelines/darwin/product-build-darwin-universal.yml @@ -10,7 +10,7 @@ steps: - task: AzureKeyVault@2 displayName: "Azure Key Vault: Get Secrets" inputs: - azureSubscription: "vscode-builds-subscription" + azureSubscription: vscode KeyVaultName: vscode-build-secrets SecretsFilter: "github-distro-mixin-password,macos-developer-certificate,macos-developer-certificate-key" diff --git a/build/azure-pipelines/darwin/product-build-darwin.yml b/build/azure-pipelines/darwin/product-build-darwin.yml index 6709b9149c3f0..e77000d431b6d 100644 --- a/build/azure-pipelines/darwin/product-build-darwin.yml +++ b/build/azure-pipelines/darwin/product-build-darwin.yml @@ -28,7 +28,7 @@ steps: - task: AzureKeyVault@2 displayName: "Azure Key Vault: Get Secrets" inputs: - azureSubscription: "vscode-builds-subscription" + azureSubscription: vscode KeyVaultName: vscode-build-secrets SecretsFilter: "github-distro-mixin-password,macos-developer-certificate,macos-developer-certificate-key" diff --git a/build/azure-pipelines/distro/download-distro.yml b/build/azure-pipelines/distro/download-distro.yml index 92325762a60c3..5c9ed0e56cd3f 100644 --- a/build/azure-pipelines/distro/download-distro.yml +++ b/build/azure-pipelines/distro/download-distro.yml @@ -2,7 +2,7 @@ steps: - task: AzureKeyVault@2 displayName: "Azure Key Vault: Get Secrets" inputs: - azureSubscription: "vscode-builds-subscription" + azureSubscription: vscode KeyVaultName: vscode-build-secrets SecretsFilter: "github-distro-mixin-password" diff --git a/build/azure-pipelines/linux/product-build-linux-legacy-server.yml b/build/azure-pipelines/linux/product-build-linux-legacy-server.yml index 63f71890f1994..fcbdb3254f081 100644 --- a/build/azure-pipelines/linux/product-build-linux-legacy-server.yml +++ b/build/azure-pipelines/linux/product-build-linux-legacy-server.yml @@ -18,7 +18,7 @@ steps: - task: AzureKeyVault@2 displayName: "Azure Key Vault: Get Secrets" inputs: - azureSubscription: "vscode-builds-subscription" + azureSubscription: vscode KeyVaultName: vscode-build-secrets SecretsFilter: "github-distro-mixin-password" @@ -77,7 +77,7 @@ steps: - task: Docker@1 displayName: "Pull Docker image" inputs: - azureSubscriptionEndpoint: "vscode-builds-subscription" + azureSubscriptionEndpoint: vscode azureContainerRegistry: vscodehub.azurecr.io command: "Run an image" imageName: vscode-linux-build-agent:centos7-devtoolset8-$(VSCODE_ARCH) diff --git a/build/azure-pipelines/linux/product-build-linux-test.yml b/build/azure-pipelines/linux/product-build-linux-test.yml index 5f7381d487c47..6796339c738f8 100644 --- a/build/azure-pipelines/linux/product-build-linux-test.yml +++ b/build/azure-pipelines/linux/product-build-linux-test.yml @@ -80,6 +80,7 @@ steps: compile-extension:typescript-language-features \ compile-extension:vscode-api-tests \ compile-extension:vscode-colorize-tests \ + compile-extension:vscode-colorize-perf-tests \ compile-extension:vscode-test-resolver displayName: Build integration tests diff --git a/build/azure-pipelines/linux/product-build-linux.yml b/build/azure-pipelines/linux/product-build-linux.yml index fe3951c9e5fca..b87a82b9fb3e8 100644 --- a/build/azure-pipelines/linux/product-build-linux.yml +++ b/build/azure-pipelines/linux/product-build-linux.yml @@ -30,9 +30,9 @@ steps: - task: AzureKeyVault@2 displayName: "Azure Key Vault: Get Secrets" inputs: - azureSubscription: "vscode-builds-subscription" + azureSubscription: vscode KeyVaultName: vscode-build-secrets - SecretsFilter: "github-distro-mixin-password,ESRP-PKI,esrp-aad-username,esrp-aad-password" + SecretsFilter: "github-distro-mixin-password" - task: DownloadPipelineArtifact@2 inputs: @@ -349,14 +349,26 @@ steps: inputs: version: 6.x - - task: EsrpClientTool@1 - continueOnError: true - displayName: Download ESRPClient - - - script: node build/azure-pipelines/common/sign $(Agent.ToolsDirectory)/esrpclient/*/*/net6.0/esrpcli.dll sign-pgp $(ESRP-PKI) $(esrp-aad-username) $(esrp-aad-password) .build/linux/deb '*.deb' + - task: EsrpCodeSigning@5 + inputs: + UseMSIAuthentication: true + ConnectedServiceName: vscode-esrp + AppRegistrationClientId: $(ESRP_CLIENT_ID) + AppRegistrationTenantId: $(ESRP_TENANT_ID) + AuthAKVName: vscode-esrp + AuthSignCertName: esrp-sign + FolderPath: . + Pattern: noop + displayName: 'Install ESRP Tooling' + + - script: node build/azure-pipelines/common/sign $(Agent.RootDirectory)/_tasks/EsrpCodeSigning_*/*/net6.0/esrpcli.dll sign-pgp .build/linux/deb '*.deb' + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) displayName: Codesign deb - - script: node build/azure-pipelines/common/sign $(Agent.ToolsDirectory)/esrpclient/*/*/net6.0/esrpcli.dll sign-pgp $(ESRP-PKI) $(esrp-aad-username) $(esrp-aad-password) .build/linux/rpm '*.rpm' + - script: node build/azure-pipelines/common/sign $(Agent.RootDirectory)/_tasks/EsrpCodeSigning_*/*/net6.0/esrpcli.dll sign-pgp .build/linux/rpm '*.rpm' + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) displayName: Codesign rpm - script: echo "##vso[task.setvariable variable=ARTIFACT_PREFIX]attempt$(System.JobAttempt)_" diff --git a/build/azure-pipelines/product-build.yml b/build/azure-pipelines/product-build.yml index a196a1117f83b..d56ebc1cf1230 100644 --- a/build/azure-pipelines/product-build.yml +++ b/build/azure-pipelines/product-build.yml @@ -134,16 +134,22 @@ variables: value: ${{ eq(parameters.VSCODE_STEP_ON_IT, true) }} - name: VSCODE_BUILD_MACOS_UNIVERSAL value: ${{ and(eq(parameters.VSCODE_BUILD_MACOS, true), eq(parameters.VSCODE_BUILD_MACOS_ARM64, true), eq(parameters.VSCODE_BUILD_MACOS_UNIVERSAL, true)) }} + - name: VSCODE_STAGING_BLOB_STORAGE_ACCOUNT_NAME + value: vscodeesrp - name: PRSS_CDN_URL value: https://vscode.download.prss.microsoft.com/dbazure/download - - name: PRSS_RELEASE_TENANT_ID + - name: VSCODE_ESRP_SERVICE_CONNECTION_ID + value: fe07e6ce-6ffb-4df9-8d27-d129523a3f3e + - name: VSCODE_ESRP_TENANT_ID value: 975f013f-7f24-47e8-a7d3-abc4752bf346 - - name: PRSS_RELEASE_CLIENT_ID + - name: VSCODE_ESRP_CLIENT_ID + value: 4ac7ed59-b5e9-4f66-9c30-8d1afa72d32d + - name: ESRP_TENANT_ID + value: 975f013f-7f24-47e8-a7d3-abc4752bf346 + - name: ESRP_CLIENT_ID value: c24324f7-e65f-4c45-8702-ed2d4c35df99 - - name: PRSS_PROVISION_TENANT_ID - value: 72f988bf-86f1-41af-91ab-2d7cd011db47 - name: AZURE_DOCUMENTDB_ENDPOINT - value: https://vscode.documents.azure.com:443/ + value: https://vscode.documents.azure.com/ - name: VSCODE_MIXIN_REPO value: microsoft/vscode-distro - name: skipComponentGovernanceDetection @@ -320,8 +326,6 @@ extends: os: windows jobs: - job: WindowsSDL - variables: - - group: 'API Scan' steps: - template: build/azure-pipelines/win32/sdl-scan-win32.yml@self parameters: diff --git a/build/azure-pipelines/product-compile.yml b/build/azure-pipelines/product-compile.yml index facc7af4bc282..ea71c18ff7d91 100644 --- a/build/azure-pipelines/product-compile.yml +++ b/build/azure-pipelines/product-compile.yml @@ -15,7 +15,7 @@ steps: - task: AzureKeyVault@2 displayName: "Azure Key Vault: Get Secrets" inputs: - azureSubscription: "vscode-builds-subscription" + azureSubscription: vscode KeyVaultName: vscode-build-secrets SecretsFilter: "github-distro-mixin-password" @@ -131,33 +131,24 @@ steps: - task: AzureCLI@2 displayName: Fetch secrets inputs: - azureSubscription: "vscode-builds-subscription" + azureSubscription: vscode scriptType: pscore scriptLocation: inlineScript addSpnToEnvironment: true inlineScript: | Write-Host "##vso[task.setvariable variable=AZURE_TENANT_ID]$env:tenantId" Write-Host "##vso[task.setvariable variable=AZURE_CLIENT_ID]$env:servicePrincipalId" - Write-Host "##vso[task.setvariable variable=AZURE_CLIENT_SECRET;issecret=true]$env:servicePrincipalKey" + Write-Host "##vso[task.setvariable variable=AZURE_ID_TOKEN;issecret=true]$env:idToken" - script: | set -e AZURE_STORAGE_ACCOUNT="vscodeweb" \ AZURE_TENANT_ID="$(AZURE_TENANT_ID)" \ AZURE_CLIENT_ID="$(AZURE_CLIENT_ID)" \ - AZURE_CLIENT_SECRET="$(AZURE_CLIENT_SECRET)" \ + AZURE_ID_TOKEN="$(AZURE_ID_TOKEN)" \ node build/azure-pipelines/upload-sourcemaps displayName: Upload sourcemaps to Azure - - script: | - set -e - AZURE_STORAGE_ACCOUNT="ticino" \ - AZURE_TENANT_ID="$(AZURE_TENANT_ID)" \ - AZURE_CLIENT_ID="$(AZURE_CLIENT_ID)" \ - AZURE_CLIENT_SECRET="$(AZURE_CLIENT_SECRET)" \ - node build/azure-pipelines/upload-sourcemaps - displayName: Upload sourcemaps to Azure (Deprecated) - - script: ./build/azure-pipelines/common/extract-telemetry.sh displayName: Generate lists of telemetry events diff --git a/build/azure-pipelines/product-publish.yml b/build/azure-pipelines/product-publish.yml index 59012a938acca..c9728a2a113cd 100644 --- a/build/azure-pipelines/product-publish.yml +++ b/build/azure-pipelines/product-publish.yml @@ -5,22 +5,19 @@ steps: versionFilePath: .nvmrc nodejsMirror: https://github.com/joaomoreno/node-mirror/releases/download - - task: SFP.build-tasks.esrpclient-tools-task.EsrpClientTool@2 - displayName: "Use EsrpClient" - - task: AzureKeyVault@2 displayName: "Azure Key Vault: Get Secrets" inputs: - azureSubscription: "vscode-builds-subscription" + azureSubscription: vscode KeyVaultName: vscode-build-secrets - SecretsFilter: "github-distro-mixin-password,esrp-aad-username,esrp-aad-password" + SecretsFilter: "github-distro-mixin-password" - task: AzureKeyVault@2 - displayName: "Azure Key Vault: Get Secrets" + displayName: "Azure Key Vault: Get ESRP Secrets" inputs: - azureSubscription: "vscode-builds-subscription" - KeyVaultName: vscode-build-packages - SecretsFilter: "vscode-esrp,c24324f7-e65f-4c45-8702-ed2d4c35df99" + azureSubscription: vscode-esrp + KeyVaultName: vscode-esrp + SecretsFilter: esrp-auth,esrp-sign # allow-any-unicode-next-line - pwsh: Write-Host "##vso[build.addbuildtag]🚀" @@ -38,14 +35,14 @@ steps: - task: AzureCLI@2 displayName: Fetch secrets inputs: - azureSubscription: "vscode-builds-subscription" + azureSubscription: vscode scriptType: pscore scriptLocation: inlineScript addSpnToEnvironment: true inlineScript: | Write-Host "##vso[task.setvariable variable=AZURE_TENANT_ID]$env:tenantId" Write-Host "##vso[task.setvariable variable=AZURE_CLIENT_ID]$env:servicePrincipalId" - Write-Host "##vso[task.setvariable variable=AZURE_CLIENT_SECRET;issecret=true]$env:servicePrincipalKey" + Write-Host "##vso[task.setvariable variable=AZURE_ID_TOKEN;issecret=true]$env:idToken" - pwsh: | . build/azure-pipelines/win32/exec.ps1 @@ -61,41 +58,30 @@ steps: env: AZURE_TENANT_ID: "$(AZURE_TENANT_ID)" AZURE_CLIENT_ID: "$(AZURE_CLIENT_ID)" - AZURE_CLIENT_SECRET: "$(AZURE_CLIENT_SECRET)" + AZURE_ID_TOKEN: "$(AZURE_ID_TOKEN)" displayName: Create build if it hasn't been created before - pwsh: | - $ErrorActionPreference = "Stop" - $CertCollection = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2Collection - $AuthCertBytes = [System.Convert]::FromBase64String("$(vscode-esrp)") - $CertCollection.Import($AuthCertBytes, $null, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable -bxor [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::PersistKeySet) - $RequestSigningCertIndex = $CertCollection.Count - $RequestSigningCertBytes = [System.Convert]::FromBase64String("$(c24324f7-e65f-4c45-8702-ed2d4c35df99)") - $CertCollection.Import($RequestSigningCertBytes, $null, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable -bxor [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::PersistKeySet) - $CertStore = New-Object System.Security.Cryptography.X509Certificates.X509Store("My","LocalMachine") - $CertStore.Open("ReadWrite") - $CertStore.AddRange($CertCollection) - $CertStore.Close() - $AuthCertSubjectName = $CertCollection[0].Subject - $RequestSigningCertSubjectName = $CertCollection[$RequestSigningCertIndex].Subject - Write-Host "##vso[task.setvariable variable=RELEASE_AUTH_CERT_SUBJECT_NAME]$AuthCertSubjectName" - Write-Host "##vso[task.setvariable variable=RELEASE_REQUEST_SIGNING_CERT_SUBJECT_NAME]$RequestSigningCertSubjectName" - displayName: Import certificates + $publishAuthTokens = (node build/azure-pipelines/common/getPublishAuthTokens) + Write-Host "##vso[task.setvariable variable=PUBLISH_AUTH_TOKENS;issecret=true]$publishAuthTokens" + env: + AZURE_TENANT_ID: "$(AZURE_TENANT_ID)" + AZURE_CLIENT_ID: "$(AZURE_CLIENT_ID)" + AZURE_ID_TOKEN: "$(AZURE_ID_TOKEN)" + displayName: Get publish auth tokens - pwsh: node build/azure-pipelines/common/publish.js env: GITHUB_TOKEN: "$(github-distro-mixin-password)" AZURE_TENANT_ID: "$(AZURE_TENANT_ID)" AZURE_CLIENT_ID: "$(AZURE_CLIENT_ID)" - AZURE_CLIENT_SECRET: "$(AZURE_CLIENT_SECRET)" + AZURE_ID_TOKEN: "$(AZURE_ID_TOKEN)" SYSTEM_ACCESSTOKEN: $(System.AccessToken) - RELEASE_TENANT_ID: "$(PRSS_RELEASE_TENANT_ID)" - RELEASE_CLIENT_ID: "$(PRSS_RELEASE_CLIENT_ID)" - RELEASE_AUTH_CERT_SUBJECT_NAME: "$(RELEASE_AUTH_CERT_SUBJECT_NAME)" - RELEASE_REQUEST_SIGNING_CERT_SUBJECT_NAME: "$(RELEASE_REQUEST_SIGNING_CERT_SUBJECT_NAME)" - PROVISION_TENANT_ID: "$(PRSS_PROVISION_TENANT_ID)" - PROVISION_AAD_USERNAME: "$(esrp-aad-username)" - PROVISION_AAD_PASSWORD: "$(esrp-aad-password)" + PUBLISH_AUTH_TOKENS: "$(PUBLISH_AUTH_TOKENS)" + RELEASE_TENANT_ID: "$(ESRP_TENANT_ID)" + RELEASE_CLIENT_ID: "$(ESRP_CLIENT_ID)" + RELEASE_AUTH_CERT: "$(esrp-auth)" + RELEASE_REQUEST_SIGNING_CERT: "$(esrp-sign)" displayName: Process artifacts retryCountOnTaskFailure: 3 diff --git a/build/azure-pipelines/product-release.yml b/build/azure-pipelines/product-release.yml index 8afdcf10053e6..87896f9340b82 100644 --- a/build/azure-pipelines/product-release.yml +++ b/build/azure-pipelines/product-release.yml @@ -12,18 +12,16 @@ steps: - task: AzureCLI@2 displayName: Fetch secrets inputs: - azureSubscription: "vscode-builds-subscription" + azureSubscription: vscode scriptType: pscore scriptLocation: inlineScript addSpnToEnvironment: true inlineScript: | Write-Host "##vso[task.setvariable variable=AZURE_TENANT_ID]$env:tenantId" Write-Host "##vso[task.setvariable variable=AZURE_CLIENT_ID]$env:servicePrincipalId" - Write-Host "##vso[task.setvariable variable=AZURE_CLIENT_SECRET;issecret=true]$env:servicePrincipalKey" + Write-Host "##vso[task.setvariable variable=AZURE_ID_TOKEN;issecret=true]$env:idToken" - - script: | - set -e - npm ci + - script: npm ci workingDirectory: build displayName: Install /build dependencies @@ -31,6 +29,6 @@ steps: set -e AZURE_TENANT_ID="$(AZURE_TENANT_ID)" \ AZURE_CLIENT_ID="$(AZURE_CLIENT_ID)" \ - AZURE_CLIENT_SECRET="$(AZURE_CLIENT_SECRET)" \ - node build/azure-pipelines/common/releaseBuild.js ${{ parameters.VSCODE_RELEASE }} + AZURE_ID_TOKEN="$(AZURE_ID_TOKEN)" \ + node build/azure-pipelines/common/releaseBuild.js ${{ parameters.VSCODE_RELEASE }} displayName: Release build diff --git a/build/azure-pipelines/upload-cdn.js b/build/azure-pipelines/upload-cdn.js index 62247de06bfd1..8ec40a0108e1b 100644 --- a/build/azure-pipelines/upload-cdn.js +++ b/build/azure-pipelines/upload-cdn.js @@ -13,7 +13,7 @@ const mime = require("mime"); const identity_1 = require("@azure/identity"); const azure = require('gulp-azure-storage'); const commit = process.env['BUILD_SOURCEVERSION']; -const credential = new identity_1.ClientSecretCredential(process.env['AZURE_TENANT_ID'], process.env['AZURE_CLIENT_ID'], process.env['AZURE_CLIENT_SECRET']); +const credential = new identity_1.ClientAssertionCredential(process.env['AZURE_TENANT_ID'], process.env['AZURE_CLIENT_ID'], () => Promise.resolve(process.env['AZURE_ID_TOKEN'])); mime.define({ 'application/typescript': ['ts'], 'application/json': ['code-snippets'], diff --git a/build/azure-pipelines/upload-cdn.ts b/build/azure-pipelines/upload-cdn.ts index 81a4ac14eabb6..a4a5857afe5ca 100644 --- a/build/azure-pipelines/upload-cdn.ts +++ b/build/azure-pipelines/upload-cdn.ts @@ -9,11 +9,11 @@ import * as vfs from 'vinyl-fs'; import * as filter from 'gulp-filter'; import * as gzip from 'gulp-gzip'; import * as mime from 'mime'; -import { ClientSecretCredential } from '@azure/identity'; +import { ClientAssertionCredential } from '@azure/identity'; const azure = require('gulp-azure-storage'); const commit = process.env['BUILD_SOURCEVERSION']; -const credential = new ClientSecretCredential(process.env['AZURE_TENANT_ID']!, process.env['AZURE_CLIENT_ID']!, process.env['AZURE_CLIENT_SECRET']!); +const credential = new ClientAssertionCredential(process.env['AZURE_TENANT_ID']!, process.env['AZURE_CLIENT_ID']!, () => Promise.resolve(process.env['AZURE_ID_TOKEN']!)); mime.define({ 'application/typescript': ['ts'], diff --git a/build/azure-pipelines/upload-nlsmetadata.js b/build/azure-pipelines/upload-nlsmetadata.js index 5b6cd3ed1fd55..de75dcb8b3ab0 100644 --- a/build/azure-pipelines/upload-nlsmetadata.js +++ b/build/azure-pipelines/upload-nlsmetadata.js @@ -13,7 +13,7 @@ const path = require("path"); const fs_1 = require("fs"); const azure = require('gulp-azure-storage'); const commit = process.env['BUILD_SOURCEVERSION']; -const credential = new identity_1.ClientSecretCredential(process.env['AZURE_TENANT_ID'], process.env['AZURE_CLIENT_ID'], process.env['AZURE_CLIENT_SECRET']); +const credential = new identity_1.ClientAssertionCredential(process.env['AZURE_TENANT_ID'], process.env['AZURE_CLIENT_ID'], () => Promise.resolve(process.env['AZURE_ID_TOKEN'])); function main() { return new Promise((c, e) => { const combinedMetadataJson = es.merge( diff --git a/build/azure-pipelines/upload-nlsmetadata.ts b/build/azure-pipelines/upload-nlsmetadata.ts index 030cc8f0e5a6c..89a9eb6c536d9 100644 --- a/build/azure-pipelines/upload-nlsmetadata.ts +++ b/build/azure-pipelines/upload-nlsmetadata.ts @@ -8,13 +8,13 @@ import * as Vinyl from 'vinyl'; import * as vfs from 'vinyl-fs'; import * as merge from 'gulp-merge-json'; import * as gzip from 'gulp-gzip'; -import { ClientSecretCredential } from '@azure/identity'; +import { ClientAssertionCredential } from '@azure/identity'; import path = require('path'); import { readFileSync } from 'fs'; const azure = require('gulp-azure-storage'); const commit = process.env['BUILD_SOURCEVERSION']; -const credential = new ClientSecretCredential(process.env['AZURE_TENANT_ID']!, process.env['AZURE_CLIENT_ID']!, process.env['AZURE_CLIENT_SECRET']!); +const credential = new ClientAssertionCredential(process.env['AZURE_TENANT_ID']!, process.env['AZURE_CLIENT_ID']!, () => Promise.resolve(process.env['AZURE_ID_TOKEN']!)); interface NlsMetadata { keys: { [module: string]: string }; diff --git a/build/azure-pipelines/upload-sourcemaps.js b/build/azure-pipelines/upload-sourcemaps.js index 83c1cae596d51..6f5f73fb8b0e2 100644 --- a/build/azure-pipelines/upload-sourcemaps.js +++ b/build/azure-pipelines/upload-sourcemaps.js @@ -14,7 +14,7 @@ const identity_1 = require("@azure/identity"); const azure = require('gulp-azure-storage'); const root = path.dirname(path.dirname(__dirname)); const commit = process.env['BUILD_SOURCEVERSION']; -const credential = new identity_1.ClientSecretCredential(process.env['AZURE_TENANT_ID'], process.env['AZURE_CLIENT_ID'], process.env['AZURE_CLIENT_SECRET']); +const credential = new identity_1.ClientAssertionCredential(process.env['AZURE_TENANT_ID'], process.env['AZURE_CLIENT_ID'], () => Promise.resolve(process.env['AZURE_ID_TOKEN'])); // optionally allow to pass in explicit base/maps to upload const [, , base, maps] = process.argv; function src(base, maps = `${base}/**/*.map`) { diff --git a/build/azure-pipelines/upload-sourcemaps.ts b/build/azure-pipelines/upload-sourcemaps.ts index 8e148c6095f2c..2eb5e69698305 100644 --- a/build/azure-pipelines/upload-sourcemaps.ts +++ b/build/azure-pipelines/upload-sourcemaps.ts @@ -10,12 +10,12 @@ import * as vfs from 'vinyl-fs'; import * as util from '../lib/util'; // @ts-ignore import * as deps from '../lib/dependencies'; -import { ClientSecretCredential } from '@azure/identity'; +import { ClientAssertionCredential } from '@azure/identity'; const azure = require('gulp-azure-storage'); const root = path.dirname(path.dirname(__dirname)); const commit = process.env['BUILD_SOURCEVERSION']; -const credential = new ClientSecretCredential(process.env['AZURE_TENANT_ID']!, process.env['AZURE_CLIENT_ID']!, process.env['AZURE_CLIENT_SECRET']!); +const credential = new ClientAssertionCredential(process.env['AZURE_TENANT_ID']!, process.env['AZURE_CLIENT_ID']!, () => Promise.resolve(process.env['AZURE_ID_TOKEN']!)); // optionally allow to pass in explicit base/maps to upload const [, , base, maps] = process.argv; diff --git a/build/azure-pipelines/web/product-build-web.yml b/build/azure-pipelines/web/product-build-web.yml index 3866dc5cf8124..e0e91c1c58985 100644 --- a/build/azure-pipelines/web/product-build-web.yml +++ b/build/azure-pipelines/web/product-build-web.yml @@ -10,7 +10,7 @@ steps: - task: AzureKeyVault@2 displayName: "Azure Key Vault: Get Secrets" inputs: - azureSubscription: "vscode-builds-subscription" + azureSubscription: vscode KeyVaultName: vscode-build-secrets SecretsFilter: "github-distro-mixin-password" @@ -113,21 +113,21 @@ steps: - task: AzureCLI@2 displayName: Fetch secrets from Azure inputs: - azureSubscription: "vscode-builds-subscription" + azureSubscription: vscode scriptType: pscore scriptLocation: inlineScript addSpnToEnvironment: true inlineScript: | Write-Host "##vso[task.setvariable variable=AZURE_TENANT_ID]$env:tenantId" Write-Host "##vso[task.setvariable variable=AZURE_CLIENT_ID]$env:servicePrincipalId" - Write-Host "##vso[task.setvariable variable=AZURE_CLIENT_SECRET;issecret=true]$env:servicePrincipalKey" + Write-Host "##vso[task.setvariable variable=AZURE_ID_TOKEN;issecret=true]$env:idToken" - script: | set -e AZURE_STORAGE_ACCOUNT="vscodeweb" \ AZURE_TENANT_ID="$(AZURE_TENANT_ID)" \ AZURE_CLIENT_ID="$(AZURE_CLIENT_ID)" \ - AZURE_CLIENT_SECRET="$(AZURE_CLIENT_SECRET)" \ + AZURE_ID_TOKEN="$(AZURE_ID_TOKEN)" \ node build/azure-pipelines/upload-cdn displayName: Upload to CDN @@ -136,7 +136,7 @@ steps: AZURE_STORAGE_ACCOUNT="vscodeweb" \ AZURE_TENANT_ID="$(AZURE_TENANT_ID)" \ AZURE_CLIENT_ID="$(AZURE_CLIENT_ID)" \ - AZURE_CLIENT_SECRET="$(AZURE_CLIENT_SECRET)" \ + AZURE_ID_TOKEN="$(AZURE_ID_TOKEN)" \ node build/azure-pipelines/upload-sourcemaps out-vscode-web-min out-vscode-web-min/vs/workbench/workbench.web.main.js.map displayName: Upload sourcemaps (Web Main) @@ -145,28 +145,16 @@ steps: AZURE_STORAGE_ACCOUNT="vscodeweb" \ AZURE_TENANT_ID="$(AZURE_TENANT_ID)" \ AZURE_CLIENT_ID="$(AZURE_CLIENT_ID)" \ - AZURE_CLIENT_SECRET="$(AZURE_CLIENT_SECRET)" \ + AZURE_ID_TOKEN="$(AZURE_ID_TOKEN)" \ node build/azure-pipelines/upload-sourcemaps out-vscode-web-min out-vscode-web-min/vs/workbench/workbench.web.main.internal.js.map displayName: Upload sourcemaps (Web Internal) - # upload only the workbench.web.main.js source maps because - # we just compiled these bits in the previous step and the - # general task to upload source maps has already been run - - script: | - set -e - AZURE_STORAGE_ACCOUNT="ticino" \ - AZURE_TENANT_ID="$(AZURE_TENANT_ID)" \ - AZURE_CLIENT_ID="$(AZURE_CLIENT_ID)" \ - AZURE_CLIENT_SECRET="$(AZURE_CLIENT_SECRET)" \ - node build/azure-pipelines/upload-sourcemaps out-vscode-web-min out-vscode-web-min/vs/workbench/workbench.web.main.js.map - displayName: Upload sourcemaps (Deprecated) - - script: | set -e AZURE_STORAGE_ACCOUNT="vscodeweb" \ AZURE_TENANT_ID="$(AZURE_TENANT_ID)" \ AZURE_CLIENT_ID="$(AZURE_CLIENT_ID)" \ - AZURE_CLIENT_SECRET="$(AZURE_CLIENT_SECRET)" \ + AZURE_ID_TOKEN="$(AZURE_ID_TOKEN)" \ node build/azure-pipelines/upload-nlsmetadata displayName: Upload NLS Metadata diff --git a/build/azure-pipelines/win32/cli-build-win32.yml b/build/azure-pipelines/win32/cli-build-win32.yml index 19409272ff07b..d61f0e722f5ff 100644 --- a/build/azure-pipelines/win32/cli-build-win32.yml +++ b/build/azure-pipelines/win32/cli-build-win32.yml @@ -53,7 +53,8 @@ steps: VSCODE_CLI_ENV: OPENSSL_LIB_DIR: $(Build.ArtifactStagingDirectory)/openssl/x64-windows-static/lib OPENSSL_INCLUDE_DIR: $(Build.ArtifactStagingDirectory)/openssl/x64-windows-static/include - RUSTFLAGS: "-C target-feature=+crt-static" + RUSTFLAGS: "-Ctarget-feature=+crt-static -Clink-args=/guard:cf -Clink-args=/CETCOMPAT" + CFLAGS: "/guard:cf /Qspectre" - ${{ if eq(parameters.VSCODE_BUILD_WIN32_ARM64, true) }}: - template: ../cli/cli-compile.yml@self @@ -65,7 +66,8 @@ steps: VSCODE_CLI_ENV: OPENSSL_LIB_DIR: $(Build.ArtifactStagingDirectory)/openssl/arm64-windows-static/lib OPENSSL_INCLUDE_DIR: $(Build.ArtifactStagingDirectory)/openssl/arm64-windows-static/include - RUSTFLAGS: "-C target-feature=+crt-static" + RUSTFLAGS: "-C target-feature=+crt-static -Clink-args=/guard:cf -Clink-args=/CETCOMPAT:NO" + CFLAGS: "/guard:cf /Qspectre" - ${{ if not(parameters.VSCODE_CHECK_ONLY) }}: - ${{ if eq(parameters.VSCODE_BUILD_WIN32_ARM64, true) }}: diff --git a/build/azure-pipelines/win32/product-build-win32-test.yml b/build/azure-pipelines/win32/product-build-win32-test.yml index cdcccd7684bc4..09db30d1914a1 100644 --- a/build/azure-pipelines/win32/product-build-win32-test.yml +++ b/build/azure-pipelines/win32/product-build-win32-test.yml @@ -63,6 +63,7 @@ steps: compile-extension:typescript-language-features ` compile-extension:vscode-api-tests ` compile-extension:vscode-colorize-tests ` + compile-extension:vscode-colorize-perf-tests ` compile-extension:vscode-test-resolver ` } displayName: Build integration tests diff --git a/build/azure-pipelines/win32/product-build-win32.yml b/build/azure-pipelines/win32/product-build-win32.yml index f52bf07de0847..de8b5b072b210 100644 --- a/build/azure-pipelines/win32/product-build-win32.yml +++ b/build/azure-pipelines/win32/product-build-win32.yml @@ -35,9 +35,9 @@ steps: - task: AzureKeyVault@2 displayName: "Azure Key Vault: Get Secrets" inputs: - azureSubscription: "vscode-builds-subscription" + azureSubscription: vscode KeyVaultName: vscode-build-secrets - SecretsFilter: "github-distro-mixin-password,ESRP-PKI,esrp-aad-username,esrp-aad-password" + SecretsFilter: "github-distro-mixin-password" - task: DownloadPipelineArtifact@2 inputs: @@ -206,25 +206,35 @@ steps: inputs: version: 6.x - - task: EsrpClientTool@1 - displayName: Download ESRPClient + - task: EsrpCodeSigning@5 + inputs: + UseMSIAuthentication: true + ConnectedServiceName: vscode-esrp + AppRegistrationClientId: $(ESRP_CLIENT_ID) + AppRegistrationTenantId: $(ESRP_TENANT_ID) + AuthAKVName: vscode-esrp + AuthSignCertName: esrp-sign + FolderPath: . + Pattern: noop + displayName: 'Install ESRP Tooling' - powershell: | . build/azure-pipelines/win32/exec.ps1 $ErrorActionPreference = "Stop" - $EsrpClientTool = (gci -directory -filter EsrpClientTool_* $(Agent.RootDirectory)\_tasks | Select-Object -last 1).FullName - $EsrpCliZip = (gci -recurse -filter esrpcli.*.zip $EsrpClientTool | Select-Object -last 1).FullName - mkdir -p $(Agent.TempDirectory)\esrpcli - Expand-Archive -Path $EsrpCliZip -DestinationPath $(Agent.TempDirectory)\esrpcli - $EsrpCliDllPath = (gci -recurse -filter esrpcli.dll $(Agent.TempDirectory)\esrpcli | Select-Object -last 1).FullName - echo "##vso[task.setvariable variable=EsrpCliDllPath]$EsrpCliDllPath" + $EsrpCodeSigningTool = (gci -directory -filter EsrpCodeSigning_* $(Agent.RootDirectory)\_tasks | Select-Object -last 1).FullName + $Version = (gci -directory $EsrpCodeSigningTool | Select-Object -last 1).FullName + echo "##vso[task.setvariable variable=EsrpCliDllPath]$Version\net6.0\esrpcli.dll" displayName: Find ESRP CLI - - powershell: node build\azure-pipelines\common\sign $env:EsrpCliDllPath sign-windows $(ESRP-PKI) $(esrp-aad-username) $(esrp-aad-password) $(CodeSigningFolderPath) '*.dll,*.exe,*.node' + - powershell: node build\azure-pipelines\common\sign $env:EsrpCliDllPath sign-windows $(CodeSigningFolderPath) '*.dll,*.exe,*.node' + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) displayName: Codesign executables and shared libraries - ${{ if eq(parameters.VSCODE_QUALITY, 'insider') }}: - - powershell: node build\azure-pipelines\common\sign $env:EsrpCliDllPath sign-windows-appx $(ESRP-PKI) $(esrp-aad-username) $(esrp-aad-password) $(CodeSigningFolderPath) '*.appx' + - powershell: node build\azure-pipelines\common\sign $env:EsrpCliDllPath sign-windows-appx $(CodeSigningFolderPath) '*.appx' + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) displayName: Codesign context menu appx package - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: @@ -268,25 +278,23 @@ steps: - powershell: | . build/azure-pipelines/win32/exec.ps1 $ErrorActionPreference = "Stop" - $env:ESRPPKI = "$(ESRP-PKI)" - $env:ESRPAADUsername = "$(esrp-aad-username)" - $env:ESRPAADPassword = "$(esrp-aad-password)" exec { npm run -- gulp "vscode-win32-$(VSCODE_ARCH)-system-setup" --sign } $SetupPath = ".build\win32-$(VSCODE_ARCH)\system-setup\VSCodeSetup-$(VSCODE_ARCH)-$(VSCODE_VERSION).exe" mv .build\win32-$(VSCODE_ARCH)\system-setup\VSCodeSetup.exe $SetupPath echo "##vso[task.setvariable variable=SYSTEM_SETUP_PATH]$SetupPath" + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) displayName: Build system setup - powershell: | . build/azure-pipelines/win32/exec.ps1 $ErrorActionPreference = "Stop" - $env:ESRPPKI = "$(ESRP-PKI)" - $env:ESRPAADUsername = "$(esrp-aad-username)" - $env:ESRPAADPassword = "$(esrp-aad-password)" exec { npm run -- gulp "vscode-win32-$(VSCODE_ARCH)-user-setup" --sign } $SetupPath = ".build\win32-$(VSCODE_ARCH)\user-setup\VSCodeUserSetup-$(VSCODE_ARCH)-$(VSCODE_VERSION).exe" mv .build\win32-$(VSCODE_ARCH)\user-setup\VSCodeSetup.exe $SetupPath echo "##vso[task.setvariable variable=USER_SETUP_PATH]$SetupPath" + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) displayName: Build user setup - powershell: echo "##vso[task.setvariable variable=ARTIFACT_PREFIX]attempt$(System.JobAttempt)_" diff --git a/build/azure-pipelines/win32/sdl-scan-win32.yml b/build/azure-pipelines/win32/sdl-scan-win32.yml index ddec05386201b..def3cb53dfcc7 100644 --- a/build/azure-pipelines/win32/sdl-scan-win32.yml +++ b/build/azure-pipelines/win32/sdl-scan-win32.yml @@ -21,7 +21,7 @@ steps: - task: AzureKeyVault@2 displayName: "Azure Key Vault: Get Secrets" inputs: - azureSubscription: "vscode-builds-subscription" + azureSubscription: vscode KeyVaultName: vscode-build-secrets SecretsFilter: "github-distro-mixin-password" @@ -146,6 +146,9 @@ steps: SearchPattern: '**\*.pdb' SymbolServerType: TeamServices SymbolsProduct: 'code' + ArtifactServices.Symbol.AccountName: microsoft + ArtifactServices.Symbol.PAT: $(System.AccessToken) + ArtifactServices.Symbol.UseAAD: false displayName: Publish Symbols condition: succeeded() @@ -161,7 +164,7 @@ steps: displayName: Run ApiScan condition: succeeded() env: - AzureServicesAuthConnectionString: $(apiscan-connectionstring) + AzureServicesAuthConnectionString: RunAs=App;AppId=c0940da5-8bd3-4dd3-8af1-40774b50edbd;TenantId=72f988bf-86f1-41af-91ab-2d7cd011db47;ServiceConnectionId=3e55d992-b60d-414d-9071-e4fad359c748; SYSTEM_ACCESSTOKEN: $(System.AccessToken) - task: PublishSecurityAnalysisLogs@3 diff --git a/build/buildfile.js b/build/buildfile.js index 607e79e13d783..683e20fc46b79 100644 --- a/build/buildfile.js +++ b/build/buildfile.js @@ -36,7 +36,6 @@ exports.workbenchDesktop = [ createModuleDescription('vs/platform/files/node/watcher/watcherMain'), createModuleDescription('vs/platform/terminal/node/ptyHostMain'), createModuleDescription('vs/workbench/api/node/extensionHostProcess'), - createModuleDescription('vs/workbench/contrib/issue/electron-sandbox/issueReporterMain'), createModuleDescription('vs/workbench/workbench.desktop.main') ]; @@ -55,8 +54,6 @@ exports.code = [ createModuleDescription('vs/code/electron-utility/sharedProcess/sharedProcessMain'), createModuleDescription('vs/code/electron-sandbox/processExplorer/processExplorerMain'), createModuleDescription('vs/code/electron-sandbox/workbench/workbench'), - // TODO: @justchen https://github.com/microsoft/vscode/issues/213332 make sure to remove when we use window.open on desktop. - createModuleDescription('vs/workbench/contrib/issue/electron-sandbox/issueReporter'), createModuleDescription('vs/code/electron-sandbox/processExplorer/processExplorer') ]; diff --git a/build/checksums/electron.txt b/build/checksums/electron.txt index 4d7f8de85600b..bc6e80cdb9683 100644 --- a/build/checksums/electron.txt +++ b/build/checksums/electron.txt @@ -1,75 +1,75 @@ -29acb63bb116a08e97797042505d48eecfa396f5d84a12114573aa70acaa48ec *chromedriver-v32.2.1-darwin-arm64.zip -a1fd00f8634c6b4d9e28ce8ac69684ea24f5274c9f17c0e39bd149b34568b84b *chromedriver-v32.2.1-darwin-x64.zip -6b311318f5a537e21d2d832609ce8306b4806e4c62aaa132ee87e063d45f5b00 *chromedriver-v32.2.1-linux-arm64.zip -ac1529a8f6e4c77fdae3bc92bc5bfcb40c3b19def0772de9d1874da7223517b7 *chromedriver-v32.2.1-linux-armv7l.zip -2329d1307729c714bef71d9f8250ed510b5a1ae07beefddee2371af70f712297 *chromedriver-v32.2.1-linux-x64.zip -84566e08029ea9b3d939f2329332b6b6d0c4a886f2aa2f2f53818b90af16a717 *chromedriver-v32.2.1-mas-arm64.zip -71c6e443617b6dd9b9962ff566ac7b8856db0a2e81b8b6ee7f985ffc96bb409a *chromedriver-v32.2.1-mas-x64.zip -0340ecc564b68a1632ea76f7e77fc06a4f150ea2fbb3c599c0dc8d78499c39e3 *chromedriver-v32.2.1-win32-arm64.zip -9d6d1a0b4863a4de2587e746b1a25da698076eda9268ef70ca24d43b39514859 *chromedriver-v32.2.1-win32-ia32.zip -1dc504383f63b2f178b902de41ba0efa28650bde54c3b2ebeee827c87a2768f3 *chromedriver-v32.2.1-win32-x64.zip -ba8e9ac663cc2edea61e7ddf12af835bf6ebb02b8d4ae6362c1f39c2390e7d22 *electron-api.json -a3544e9894f1ca544b0c8231f7c34f90a29f0ce3fd7853d592d51eb4ad4b31c5 *electron-v32.2.1-darwin-arm64-dsym-snapshot.zip -89377cde729f99707cb822e88999cfc312c4b82495600f38d13593c3de1b47f4 *electron-v32.2.1-darwin-arm64-dsym.zip -4e13b04efd03c237c3421b551180bc2b8dc6c35d49acd475e42c11aaa6b199aa *electron-v32.2.1-darwin-arm64-symbols.zip -906fbf9e7a5ee6d49ea107fdfd0e98bc80884fbf1f6ff38d824453f58c6ec259 *electron-v32.2.1-darwin-arm64.zip -fb3e5eb15915b4328820ebaf2c4a056f4ac374eb8e24479bdfd6f0cf8e1da1be *electron-v32.2.1-darwin-x64-dsym-snapshot.zip -0a95df2a44e0a42b9076e58d7e539e91ba7e583de77a8e94695d9c6dd03f201a *electron-v32.2.1-darwin-x64-dsym.zip -4864122e38f423f6ff9a8625696f323e908e613ebdab8ed7d40b374d6f9dec13 *electron-v32.2.1-darwin-x64-symbols.zip -56e2e4252b4d4e92075345f0b9dbefc8db49bdc6a4c45a87000f3cc705057907 *electron-v32.2.1-darwin-x64.zip -692aaf464bdb7bd7538e6392885571ef4d5f4d02319f84b99ada1827fbdfabf9 *electron-v32.2.1-linux-arm64-debug.zip -86161e2f6b1ca5cd6eb998863798186d9be270535d6912075001588e3e35e90d *electron-v32.2.1-linux-arm64-symbols.zip -6500fdbff988e0cda909643ba8439660a207c9a2d393fa63f680a0337e530342 *electron-v32.2.1-linux-arm64.zip -692aaf464bdb7bd7538e6392885571ef4d5f4d02319f84b99ada1827fbdfabf9 *electron-v32.2.1-linux-armv7l-debug.zip -cfe4cfb7a6818902b5cc1b493ec2f7a9e4dc8fcb63346ddf75bec3496658a363 *electron-v32.2.1-linux-armv7l-symbols.zip -7ffcce19ebdb30a9db78671c7f222edde66181a37c895834682d224e459200fc *electron-v32.2.1-linux-armv7l.zip -1e0318a7d125ebe015a5d4f214d186cd10e36021cc8555d376d8fda15a28a5ac *electron-v32.2.1-linux-x64-debug.zip -9d857cd5bdc81abb965e2e1bb73af8de31ef74cd182de52160b7afe805837574 *electron-v32.2.1-linux-x64-symbols.zip -4fc58e6e79e5b5793ec9b5d35c8926fcad5352b6a1b21b3edf42343487c90185 *electron-v32.2.1-linux-x64.zip -5fba9ea6c0d49ecd8bbbc87a9da6f860b901892e7ea487013e353bc2e951fbde *electron-v32.2.1-mas-arm64-dsym-snapshot.zip -71c2cdc23e61b7f13bda837fb9dfb5fdb9c6ca4fa755f2596f70874caaeeacea *electron-v32.2.1-mas-arm64-dsym.zip -448df71d1e62ca570b3f8b7d35b21eaa2870ce4877f12465cf6e54e90a16ac12 *electron-v32.2.1-mas-arm64-symbols.zip -4c53ef19385ab5a0040e6eda3a8f88f42b5f53de0e9a6118333613a1388fc39f *electron-v32.2.1-mas-arm64.zip -cbc5b08014cda37d6943f8a388ba1386f5ee1af3ca7a5ed28c12bf5fbd00f633 *electron-v32.2.1-mas-x64-dsym-snapshot.zip -a6bfe31ea9cef19794418d169872d5b68130b49989d7e2ee3d83a2853d4e706c *electron-v32.2.1-mas-x64-dsym.zip -a9ce94d21c61d3cf9f8319ae394c779058bc2377916e0330f0447e8c79b5b0e1 *electron-v32.2.1-mas-x64-symbols.zip -8b57ced11b88fa80f9a986662658cf4cb40a1138811ad6129fc826988b31f9ab *electron-v32.2.1-mas-x64.zip -5fcb399829066859399e8e3e7c5574b2e8885f632661fc2830da02be3d5803d2 *electron-v32.2.1-win32-arm64-pdb.zip -eb16ad799a8db120b1e4b13533f9f52e844b6252308ea9e182f290c7657a5361 *electron-v32.2.1-win32-arm64-symbols.zip -48b81d28fdceb4ab3ca27650d79bab910a1a19dbda72271882bfdc877c71975f *electron-v32.2.1-win32-arm64-toolchain-profile.zip -3ac484f124c2012c0bff7640e82bed268876de1e3c6776716b5883d2de043a4f *electron-v32.2.1-win32-arm64.zip -75b6117bd0462641d93de9e0a7aac9c6a1a052c688f59426a66aafe34c7bb914 *electron-v32.2.1-win32-ia32-pdb.zip -10d4b64e7d2abeef1c93f2ec58d55715462229d20f61edf50bf5ceb4fce5719b *electron-v32.2.1-win32-ia32-symbols.zip -48b81d28fdceb4ab3ca27650d79bab910a1a19dbda72271882bfdc877c71975f *electron-v32.2.1-win32-ia32-toolchain-profile.zip -e5d4e2b10e5215b8a7133cc3fa39875ea18e8d4ea41f9ba9a9ae9f13a4090f53 *electron-v32.2.1-win32-ia32.zip -83055f775e93c0be5c17a4312a552d3d0abb86a36b354f30973917a44d7a5656 *electron-v32.2.1-win32-x64-pdb.zip -38158fd465eb41674767707bfbd87ec67874aac9bd42c550aad6901035884697 *electron-v32.2.1-win32-x64-symbols.zip -48b81d28fdceb4ab3ca27650d79bab910a1a19dbda72271882bfdc877c71975f *electron-v32.2.1-win32-x64-toolchain-profile.zip -494282c481eca93e1ee1d3e0df65ba0da5cec09b0c15bcc81521eee108839190 *electron-v32.2.1-win32-x64.zip -27050115afac161a368be0b92e842f65d5c7021b5b508b71ad972ce252bbbb3f *electron.d.ts -d8c054da57903f4e3297edd4de69177e9556feca9f2fc71b833608a486a7cae2 *ffmpeg-v32.2.1-darwin-arm64.zip -ac22a993719b804b560ed73ff1ad339df3eb126eeb9f5d496174a293ba952d78 *ffmpeg-v32.2.1-darwin-x64.zip -3f1eafaf4cd90ab43ba0267429189be182435849a166a2cbe1faefc0d07217c4 *ffmpeg-v32.2.1-linux-arm64.zip -3db919bc57e1a5bf7c1bae1d7aeacf4a331990ea82750391c0b24a046d9a2812 *ffmpeg-v32.2.1-linux-armv7l.zip -fe7d779dddbfb5da5999a7607fc5e3c7a6ab7c65e8da9fee1384918865231612 *ffmpeg-v32.2.1-linux-x64.zip -de4b05b040207d6807444f4289c0adc7f4947de0e32a0441073085cd76676648 *ffmpeg-v32.2.1-mas-arm64.zip -b007a2c582cd55727453fdf51ca3521d76f3ebeda8bfb3c2eeb56d56ec17a6a7 *ffmpeg-v32.2.1-mas-x64.zip -c72c467834669575ca1a5e34a624db71da3cbe63223f63d8f92bc4d2551a4164 *ffmpeg-v32.2.1-win32-arm64.zip -a9d26ba87262631ba279f6eae4164bcb289abe99c5a10e56c2e28e1e05b530bf *ffmpeg-v32.2.1-win32-ia32.zip -98c917caa3cd7ad10f2c48669c377a028d42673515034c05c3cac461213d5535 *ffmpeg-v32.2.1-win32-x64.zip -8b685975c9aeae9e5d8df85ad797492419e6414aa68a87d14f6fbb923d0f7dad *hunspell_dictionaries.zip -ee3871c7b533fc1c24baab89d25b60fc3e5f339b4c3e7767c768d833b0a828f4 *libcxx-objects-v32.2.1-linux-arm64.zip -3a01ecfc2f4e91bdc20280d8d3954347c0abd1bd53256e79a053d05f6a3ec664 *libcxx-objects-v32.2.1-linux-armv7l.zip -ab43146f8ca665a7064da6a82af2c7e3c3adce0788fb55862991f3a491bc692d *libcxx-objects-v32.2.1-linux-x64.zip -bb9dc46ad47b265fa353c42fc54ba584c2c890521069ea9de4c12cddb96297ea *libcxx_headers.zip -6846d928164a74dcad442da06cd79ecc788aa52815b8334e3a8a187f1650ce4f *libcxxabi_headers.zip -8f4cb6c9358c1bf9a0c81dcf94dd1f3683c42f3407441ed1ce074851bca0cbed *mksnapshot-v32.2.1-darwin-arm64.zip -ca0919eaa60722e8e864eeae331a571e10ecf02bb1bcd9028849436a15db4416 *mksnapshot-v32.2.1-darwin-x64.zip -1858861baadfd453eac7e78de2b1837253d4e44084aff22d8b6813602e0a3f4e *mksnapshot-v32.2.1-linux-arm64-x64.zip -15a713a85f2a3082c6b8943315bb7b9ab850f34a73544a619414f586e69b9b08 *mksnapshot-v32.2.1-linux-armv7l-x64.zip -5b82fba9a2ee305ef4f0818c406c726cfebc090b84fdaf39e76954f360740445 *mksnapshot-v32.2.1-linux-x64.zip -eef68fa0ea8ab11be45447e7e89fcac8dce3261d207807cd0d8366785964d7fc *mksnapshot-v32.2.1-mas-arm64.zip -26fcd020007a857611adad7ce7ba0b83b008edb130d87c93183c053b73f61a76 *mksnapshot-v32.2.1-mas-x64.zip -fbe7b665451fc4c48c4b09fe949374aedf95563b7d0b7f1927974b347205a8f9 *mksnapshot-v32.2.1-win32-arm64-x64.zip -3e0cc1d1cfa749a6364e2124b63583d7543c95c744c10400a6834726337468d0 *mksnapshot-v32.2.1-win32-ia32.zip -ef506867e5bb87c7e4380f9d9e93902d180a7425e1528acdd8283779e902f51e *mksnapshot-v32.2.1-win32-x64.zip +3dd66ff8058b2f42de3b34435eb00c3efec52a96397519b5b406859400a479f5 *chromedriver-v32.2.3-darwin-arm64.zip +b32a6da15cf24bc42cbfb54003d63317b7a5e6bbeb11f169c8734dc0988f0f4b *chromedriver-v32.2.3-darwin-x64.zip +13836465f2b59cc60b6493c7e55c3d96bac1a27be903aabaf27b5f88100c3aeb *chromedriver-v32.2.3-linux-arm64.zip +6f4933a7cb857b95ab8dda83e49679745d1d4b0753c680ad94919b88ad92469b *chromedriver-v32.2.3-linux-armv7l.zip +9fd34aee11d83693f2cffabb7fd8748bf05eddf4531677398d503f6be1d10102 *chromedriver-v32.2.3-linux-x64.zip +c1e7f22ffe54f07e849a06b62e4e540940760f1b58e3437517af971e1f61d733 *chromedriver-v32.2.3-mas-arm64.zip +4a79bbe0fab80ffdb132ddb710bffd3eaec26cb45945479a65725915d47da2dd *chromedriver-v32.2.3-mas-x64.zip +d29a542d87e84b3acef4d664c601771e4b6efce07123134cac80471397b7f062 *chromedriver-v32.2.3-win32-arm64.zip +da40875c612f69f3f072500f40ea5d356395ef0a046eaf56a0c790e44724386e *chromedriver-v32.2.3-win32-ia32.zip +8721a4ea9bf95ab37202e70b17f2bf8f43ca0f93560c34620276c15debacc732 *chromedriver-v32.2.3-win32-x64.zip +cd847cf0bd4dc8677755d6943f354540a4d7a5271f788d389c01d16a5bae47e8 *electron-api.json +438e4af8dd3c376228c34295ecdb144bb12caa51d855db621215afe975403603 *electron-v32.2.3-darwin-arm64-dsym-snapshot.zip +b678040bb9f18b7284434d3a5fb994fc8b4d87e467caa10b66e28f72412273b5 *electron-v32.2.3-darwin-arm64-dsym.zip +b8e14d9d964823c19bd69ceac044b2406c0b7f730c0a6757b02a6de4134e6c65 *electron-v32.2.3-darwin-arm64-symbols.zip +a7e57f17813cfff459f60982bd07328ebaf1728e2e9abc2d9e4debc185749f83 *electron-v32.2.3-darwin-arm64.zip +752f9ca100f8b2cdd6316783d509e52001f433cc79b6f2b66c67838bfe5b160a *electron-v32.2.3-darwin-x64-dsym-snapshot.zip +7a733751aed9fbbbea90394122a488a08e498e165f53f0d9cf284f19fc12da8d *electron-v32.2.3-darwin-x64-dsym.zip +7df6a51b10cf9d2f2d045d61fee05144a0db08495c89304eef41c07948254f8b *electron-v32.2.3-darwin-x64-symbols.zip +5e824c357281d85415c215de58ebe5c5daaa4fab2bc76e563d3da78655a7d57d *electron-v32.2.3-darwin-x64.zip +0f03c5002747c292388cacd0f95bfc01694cf0f54b63ec3224101eeae6b9896e *electron-v32.2.3-linux-arm64-debug.zip +e70af1c2c36c54b0ed912e9af52063ce1f6d856338f207eb297611eaccd1afcd *electron-v32.2.3-linux-arm64-symbols.zip +e435f44bad0f649f2a2b980197df3bfb581c5e78f41e4b5a8b7ca5bfb5d8e344 *electron-v32.2.3-linux-arm64.zip +0f03c5002747c292388cacd0f95bfc01694cf0f54b63ec3224101eeae6b9896e *electron-v32.2.3-linux-armv7l-debug.zip +49db1e2b7bf965af352d7fc41d74026a26ebabba443dafd71a8d7da8e7605690 *electron-v32.2.3-linux-armv7l-symbols.zip +5483ef724d5b363645e039b299c9480bff0e6ef4ad9d9f677675061e0c7010c4 *electron-v32.2.3-linux-armv7l.zip +72ccc7ebb4f46b034a6fdde0ba08b142141387c52086ab47ba647bdc82030ebb *electron-v32.2.3-linux-x64-debug.zip +08e209e9740c9460f8f584c7b3fe58e67e6b62652e3051d18babe950400760b2 *electron-v32.2.3-linux-x64-symbols.zip +f406b8c5f4a33da0c712f558a766348b26b8e2aac0e7bf945a00931161dc4229 *electron-v32.2.3-linux-x64.zip +2b7debd213c4eb73a47040a067346266a37d4c9cad4c25451dc53511658ad806 *electron-v32.2.3-mas-arm64-dsym-snapshot.zip +c603ff407f65233bfc58395e88d231743a96aaf6c27005e05826f6e02e5d124d *electron-v32.2.3-mas-arm64-dsym.zip +26e974578a3c0417e45cf64abc8edfd87a703e4122de5fdfd0473cea7bb3eddd *electron-v32.2.3-mas-arm64-symbols.zip +6427af76443900f5b1ba109ac83ab51bafae74166bb3cc8f17fd0ccba2fef9d4 *electron-v32.2.3-mas-arm64.zip +7f95d9b8ee3f6ee77adbd5c5ef154cafa29da9b4824990d2793e746943124252 *electron-v32.2.3-mas-x64-dsym-snapshot.zip +452c6f1bd79c3149a6acbee953b61454975879aecee882fcec2b80516e624c86 *electron-v32.2.3-mas-x64-dsym.zip +1f35c9a4c88b8678b15716a52a5b436ceb873893414318d4e4b77554835aafdb *electron-v32.2.3-mas-x64-symbols.zip +2a7910eae161e8a19102f037f1356e0a269a3291852dc278f966ff782dc740db *electron-v32.2.3-mas-x64.zip +be1bdbb291c8cd81661947c3cba8529429aba9b919568c51b84dcf49b78b5505 *electron-v32.2.3-win32-arm64-pdb.zip +7b9d111e1d744c1a5383bed379f2c98f7ee1ef8e6dd2c825340388cfbe4f091d *electron-v32.2.3-win32-arm64-symbols.zip +48b81d28fdceb4ab3ca27650d79bab910a1a19dbda72271882bfdc877c71975f *electron-v32.2.3-win32-arm64-toolchain-profile.zip +bda5da0bcc17cca0f8d58ab56a693aa9f95b513079380adf3509fe1484625fff *electron-v32.2.3-win32-arm64.zip +22d67116167fd021042623283e662c0290a370b0dc63e117e9ca90a3adad19c0 *electron-v32.2.3-win32-ia32-pdb.zip +6c032b28e246afdf36ceb1f648169ebc7b282756f3b3293949aa4e057236d3c3 *electron-v32.2.3-win32-ia32-symbols.zip +48b81d28fdceb4ab3ca27650d79bab910a1a19dbda72271882bfdc877c71975f *electron-v32.2.3-win32-ia32-toolchain-profile.zip +2b38140726382b7fbcb8d24f7a40e45f7567c3987e9fd2397010603d7c1661fc *electron-v32.2.3-win32-ia32.zip +8ecf644906c7c65da1874a723388216e7dd1a7373326520fe8b040808c04fca5 *electron-v32.2.3-win32-x64-pdb.zip +56a5a17300a226118eab5e4bac2e79de26ae2d7e380862de93540ed2fb0c464a *electron-v32.2.3-win32-x64-symbols.zip +48b81d28fdceb4ab3ca27650d79bab910a1a19dbda72271882bfdc877c71975f *electron-v32.2.3-win32-x64-toolchain-profile.zip +16c996275a3bfc00ac18a4ce733c688fb7cf260a3a5c166514acd047cefe9b78 *electron-v32.2.3-win32-x64.zip +037ca881119c29a88425115b67c5feba8fba34f3b4fce72a225eeb3b55233518 *electron.d.ts +f321aad7cab379e52fd564c2726ef1bba833a7a42b49b74eb01971fe1f21c615 *ffmpeg-v32.2.3-darwin-arm64.zip +e4bda65b109fbc4b6575fdb610ddb0da2e5b6d45fab25cab04e83b858495599e *ffmpeg-v32.2.3-darwin-x64.zip +3f1eafaf4cd90ab43ba0267429189be182435849a166a2cbe1faefc0d07217c4 *ffmpeg-v32.2.3-linux-arm64.zip +3db919bc57e1a5bf7c1bae1d7aeacf4a331990ea82750391c0b24a046d9a2812 *ffmpeg-v32.2.3-linux-armv7l.zip +fe7d779dddbfb5da5999a7607fc5e3c7a6ab7c65e8da9fee1384918865231612 *ffmpeg-v32.2.3-linux-x64.zip +6721b25ed05b2bbc7e4dfa4acad278ca50673aeb33dd98aab1bfdd2cfb775c7f *ffmpeg-v32.2.3-mas-arm64.zip +0dbd22f17adcbcc9b61a129d1e5d2c61edbf8f9bcaeb21482ddb668ac5314f39 *ffmpeg-v32.2.3-mas-x64.zip +e60af8bc9ac21fc23219d2d7be3bb50a787cae24a8950cb06ff63eff470da41a *ffmpeg-v32.2.3-win32-arm64.zip +a952f14410d3dfd8e604467db93e186a9e79c48afdebcb9f70d4b8e6ab1cbc9f *ffmpeg-v32.2.3-win32-ia32.zip +07335b144f838654abbbaa05d12828c2379dde8ff92c41b702b83a859103c856 *ffmpeg-v32.2.3-win32-x64.zip +cbccfdbf9bdd62824622afaf1d03e587cfc3af15a17c0b0a6aee85dd8a8704cc *hunspell_dictionaries.zip +32b03e672adb2dd6839a53fccd7bf69221fe5d2460a4159b143f5da90f2a3eb6 *libcxx-objects-v32.2.3-linux-arm64.zip +bd307af290cebf9f89f7a13110e33f27f05c5a54e798f0651259944536cd1219 *libcxx-objects-v32.2.3-linux-armv7l.zip +71eee68df857411eebd60240c4314c0fab5e52e7d38d20d95a0e957569255c7d *libcxx-objects-v32.2.3-linux-x64.zip +d8e209919e726a30f0b1164c2973469f24e7927be32a8db37d51cf35a98f4a8a *libcxx_headers.zip +153994318f1bb933bb26c5b8f02294da9baa586affd7f3e6ddcc363d1b363722 *libcxxabi_headers.zip +30fd6c5205f18feafe47a9f0dbc75d5b0bb964dc9b3d744666410a67a3584b99 *mksnapshot-v32.2.3-darwin-arm64.zip +5986e9ddc4dc8ed073c5cd7cec3f9041e94f9d5647c344071db3665e5e23c0e6 *mksnapshot-v32.2.3-darwin-x64.zip +d5d94482a0d250c6c0714e33c0f21a03b0908007a97337a415f4bd8347dd5ae6 *mksnapshot-v32.2.3-linux-arm64-x64.zip +21abdb1b267edefee9320ea5bd5fd91758844add73ffd6b6a16fb7a760c464c7 *mksnapshot-v32.2.3-linux-armv7l-x64.zip +444dd1e5b56c1cee3f10be59ba9434aa6c7cbf21864f383f9f4c7f89b8bf7e8f *mksnapshot-v32.2.3-linux-x64.zip +0410382fa750f770d1e22cd299d47545ceab27cdc795d19da5b7a3b708d2eef3 *mksnapshot-v32.2.3-mas-arm64.zip +001c8cf8330f0e4f6c10e8dbcf9800b8a9a9f9a6e823a5514d3ad200d4d6b620 *mksnapshot-v32.2.3-mas-x64.zip +f661a64e235a02c7ba7daa812a063f2b8c4d251515bf9b18f8bc4bc256190c73 *mksnapshot-v32.2.3-win32-arm64-x64.zip +85c2eb6e3737511e367bf8492a612f5ef9f3107492aea5461c3acb7c48192264 *mksnapshot-v32.2.3-win32-ia32.zip +626aa98144e94c585d0b87196c0d9c5b84381b0e2b4bcb379e550836139f93f3 *mksnapshot-v32.2.3-win32-x64.zip diff --git a/build/darwin/create-universal-app.js b/build/darwin/create-universal-app.js index 1f53369356b43..bced5a7166f3b 100644 --- a/build/darwin/create-universal-app.js +++ b/build/darwin/create-universal-app.js @@ -24,6 +24,8 @@ async function main(buildDir) { const filesToSkip = [ '**/CodeResources', '**/Credits.rtf', + // TODO: Should we consider expanding this to other files in this area? + '**/node_modules/@parcel/node-addon-api/nothing.target.mk' ]; await (0, vscode_universal_bundler_1.makeUniversalApp)({ x64AppPath, diff --git a/build/darwin/create-universal-app.ts b/build/darwin/create-universal-app.ts index 1f19053494d27..e05f780b38d0c 100644 --- a/build/darwin/create-universal-app.ts +++ b/build/darwin/create-universal-app.ts @@ -28,6 +28,8 @@ async function main(buildDir?: string) { const filesToSkip = [ '**/CodeResources', '**/Credits.rtf', + // TODO: Should we consider expanding this to other files in this area? + '**/node_modules/@parcel/node-addon-api/nothing.target.mk' ]; await makeUniversalApp({ diff --git a/build/gulpfile.extensions.js b/build/gulpfile.extensions.js index 4f745aebdc76b..13f27d6db4774 100644 --- a/build/gulpfile.extensions.js +++ b/build/gulpfile.extensions.js @@ -65,6 +65,7 @@ const compilations = [ 'extensions/typescript-language-features/tsconfig.json', 'extensions/vscode-api-tests/tsconfig.json', 'extensions/vscode-colorize-tests/tsconfig.json', + 'extensions/vscode-colorize-perf-tests/tsconfig.json', 'extensions/vscode-test-resolver/tsconfig.json', '.vscode/extensions/vscode-selfhost-test-provider/tsconfig.json', @@ -229,27 +230,61 @@ exports.compileExtensionMediaBuildTask = compileExtensionMediaBuildTask; //#region Azure Pipelines +/** + * Cleans the build directory for extensions + */ const cleanExtensionsBuildTask = task.define('clean-extensions-build', util.rimraf('.build/extensions')); -const compileExtensionsBuildTask = task.define('compile-extensions-build', task.series( +exports.cleanExtensionsBuildTask = cleanExtensionsBuildTask; + +/** + * brings in the marketplace extensions for the build + */ +const bundleMarketplaceExtensionsBuildTask = task.define('bundle-marketplace-extensions-build', () => ext.packageMarketplaceExtensionsStream(false).pipe(gulp.dest('.build'))); + +/** + * Compiles the non-native extensions for the build + * @note this does not clean the directory ahead of it. See {@link cleanExtensionsBuildTask} for that. + */ +const compileNonNativeExtensionsBuildTask = task.define('compile-non-native-extensions-build', task.series( + bundleMarketplaceExtensionsBuildTask, + task.define('bundle-non-native-extensions-build', () => ext.packageNonNativeLocalExtensionsStream().pipe(gulp.dest('.build'))) +)); +gulp.task(compileNonNativeExtensionsBuildTask); +exports.compileNonNativeExtensionsBuildTask = compileNonNativeExtensionsBuildTask; + +/** + * Compiles the native extensions for the build + * @note this does not clean the directory ahead of it. See {@link cleanExtensionsBuildTask} for that. + */ +const compileNativeExtensionsBuildTask = task.define('compile-native-extensions-build', () => ext.packageNativeLocalExtensionsStream().pipe(gulp.dest('.build'))); +gulp.task(compileNativeExtensionsBuildTask); +exports.compileNativeExtensionsBuildTask = compileNativeExtensionsBuildTask; + +/** + * Compiles the extensions for the build. + * This is essentially a helper task that combines {@link cleanExtensionsBuildTask}, {@link compileNonNativeExtensionsBuildTask} and {@link compileNativeExtensionsBuildTask} + */ +const compileAllExtensionsBuildTask = task.define('compile-extensions-build', task.series( cleanExtensionsBuildTask, - task.define('bundle-marketplace-extensions-build', () => ext.packageMarketplaceExtensionsStream(false).pipe(gulp.dest('.build'))), - task.define('bundle-extensions-build', () => ext.packageLocalExtensionsStream(false, false).pipe(gulp.dest('.build'))), + bundleMarketplaceExtensionsBuildTask, + task.define('bundle-extensions-build', () => ext.packageAllLocalExtensionsStream(false, false).pipe(gulp.dest('.build'))), )); +gulp.task(compileAllExtensionsBuildTask); +exports.compileAllExtensionsBuildTask = compileAllExtensionsBuildTask; -gulp.task(compileExtensionsBuildTask); -gulp.task(task.define('extensions-ci', task.series(compileExtensionsBuildTask, compileExtensionMediaBuildTask))); +// This task is run in the compilation stage of the CI pipeline. We only compile the non-native extensions since those can be fully built regardless of platform. +// This defers the native extensions to the platform specific stage of the CI pipeline. +gulp.task(task.define('extensions-ci', task.series(compileNonNativeExtensionsBuildTask, compileExtensionMediaBuildTask))); const compileExtensionsBuildPullRequestTask = task.define('compile-extensions-build-pr', task.series( cleanExtensionsBuildTask, - task.define('bundle-marketplace-extensions-build', () => ext.packageMarketplaceExtensionsStream(false).pipe(gulp.dest('.build'))), - task.define('bundle-extensions-build-pr', () => ext.packageLocalExtensionsStream(false, true).pipe(gulp.dest('.build'))), + bundleMarketplaceExtensionsBuildTask, + task.define('bundle-extensions-build-pr', () => ext.packageAllLocalExtensionsStream(false, true).pipe(gulp.dest('.build'))), )); - gulp.task(compileExtensionsBuildPullRequestTask); -gulp.task(task.define('extensions-ci-pr', task.series(compileExtensionsBuildPullRequestTask, compileExtensionMediaBuildTask))); - -exports.compileExtensionsBuildTask = compileExtensionsBuildTask; +// This task is run in the compilation stage of the PR pipeline. We compile all extensions in it to verify compilation. +gulp.task(task.define('extensions-ci-pr', task.series(compileExtensionsBuildPullRequestTask, compileExtensionMediaBuildTask))); //#endregion diff --git a/build/gulpfile.reh.js b/build/gulpfile.reh.js index 53ef6f38b02cf..4f00317173dd3 100644 --- a/build/gulpfile.reh.js +++ b/build/gulpfile.reh.js @@ -27,7 +27,7 @@ const File = require('vinyl'); const fs = require('fs'); const glob = require('glob'); const { compileBuildTask } = require('./gulpfile.compile'); -const { compileExtensionsBuildTask, compileExtensionMediaBuildTask } = require('./gulpfile.extensions'); +const { cleanExtensionsBuildTask, compileNonNativeExtensionsBuildTask, compileNativeExtensionsBuildTask, compileExtensionMediaBuildTask } = require('./gulpfile.extensions'); const { vscodeWebResourceIncludes, createVSCodeWebFileContentMapper } = require('./gulpfile.vscode.web'); const cp = require('child_process'); const log = require('fancy-log'); @@ -72,7 +72,7 @@ const serverResourceIncludes = [ 'out-build/vs/workbench/contrib/terminal/common/scripts/shellIntegration-profile.zsh', 'out-build/vs/workbench/contrib/terminal/common/scripts/shellIntegration-rc.zsh', 'out-build/vs/workbench/contrib/terminal/common/scripts/shellIntegration-login.zsh', - 'out-build/vs/workbench/contrib/terminal/common/scripts/fish_xdg_data/fish/vendor_conf.d/shellIntegration.fish', + 'out-build/vs/workbench/contrib/terminal/common/scripts/shellIntegration.fish', ]; @@ -468,6 +468,7 @@ function tweakProductForServerWeb(product) { const destinationFolderName = `vscode-${type}${dashed(platform)}${dashed(arch)}`; const serverTaskCI = task.define(`vscode-${type}${dashed(platform)}${dashed(arch)}${dashed(minified)}-ci`, task.series( + compileNativeExtensionsBuildTask, gulp.task(`node-${platform}-${arch}`), util.rimraf(path.join(BUILD_ROOT, destinationFolderName)), packageTask(type, platform, arch, sourceFolderName, destinationFolderName) @@ -476,7 +477,8 @@ function tweakProductForServerWeb(product) { const serverTask = task.define(`vscode-${type}${dashed(platform)}${dashed(arch)}${dashed(minified)}`, task.series( compileBuildTask, - compileExtensionsBuildTask, + cleanExtensionsBuildTask, + compileNonNativeExtensionsBuildTask, compileExtensionMediaBuildTask, minified ? minifyTask : bundleTask, serverTaskCI diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index da753e9c265ce..030c39a861e84 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -31,7 +31,7 @@ const { config } = require('./lib/electron'); const createAsar = require('./lib/asar').createAsar; const minimist = require('minimist'); const { compileBuildTask } = require('./gulpfile.compile'); -const { compileExtensionsBuildTask, compileExtensionMediaBuildTask } = require('./gulpfile.extensions'); +const { compileNonNativeExtensionsBuildTask, compileNativeExtensionsBuildTask, compileAllExtensionsBuildTask, compileExtensionMediaBuildTask, cleanExtensionsBuildTask } = require('./gulpfile.extensions'); const { promisify } = require('util'); const glob = promisify(require('glob')); const rcedit = promisify(require('rcedit')); @@ -74,7 +74,7 @@ const vscodeResourceIncludes = [ 'out-build/vs/workbench/contrib/externalTerminal/**/*.scpt', // Terminal shell integration - 'out-build/vs/workbench/contrib/terminal/common/scripts/fish_xdg_data/fish/vendor_conf.d/*.fish', + 'out-build/vs/workbench/contrib/terminal/common/scripts/*.fish', 'out-build/vs/workbench/contrib/terminal/common/scripts/*.ps1', 'out-build/vs/workbench/contrib/terminal/common/scripts/*.psm1', 'out-build/vs/workbench/contrib/terminal/common/scripts/*.sh', @@ -101,9 +101,6 @@ const vscodeResourceIncludes = [ // Tree Sitter highlights 'out-build/vs/editor/common/languages/highlights/*.scm', - - // Issue Reporter - 'out-build/vs/workbench/contrib/issue/electron-sandbox/issueReporter.html' ]; const vscodeResources = [ @@ -144,8 +141,6 @@ const bundleVSCodeTask = task.define('bundle-vscode', task.series( fileContentMapper: filePath => { if ( filePath.endsWith('vs/code/electron-sandbox/workbench/workbench.js') || - // TODO: @justchen https://github.com/microsoft/vscode/issues/213332 make sure to remove when we use window.open on desktop - filePath.endsWith('vs/workbench/contrib/issue/electron-sandbox/issueReporter.js') || filePath.endsWith('vs/code/electron-sandbox/processExplorer/processExplorer.js')) { return async (content) => { const bootstrapWindowContent = await fs.promises.readFile(path.join(root, 'out-build', 'bootstrap-window.js'), 'utf-8'); @@ -156,8 +151,6 @@ const bundleVSCodeTask = task.define('bundle-vscode', task.series( }, skipTSBoilerplateRemoval: entryPoint => entryPoint === 'vs/code/electron-sandbox/workbench/workbench' || - // TODO: @justchen https://github.com/microsoft/vscode/issues/213332 make sure to remove when we use window.open on desktop - entryPoint === 'vs/workbench/contrib/issue/electron-sandbox/issueReporter' || entryPoint === 'vs/code/electron-sandbox/processExplorer/processExplorer', } } @@ -494,6 +487,7 @@ BUILD_TARGETS.forEach(buildTarget => { const destinationFolderName = `VSCode${dashed(platform)}${dashed(arch)}`; const tasks = [ + compileNativeExtensionsBuildTask, util.rimraf(path.join(buildRoot, destinationFolderName)), packageTask(platform, arch, sourceFolderName, destinationFolderName, opts) ]; @@ -507,7 +501,8 @@ BUILD_TARGETS.forEach(buildTarget => { const vscodeTask = task.define(`vscode${dashed(platform)}${dashed(arch)}${dashed(minified)}`, task.series( compileBuildTask, - compileExtensionsBuildTask, + cleanExtensionsBuildTask, + compileNonNativeExtensionsBuildTask, compileExtensionMediaBuildTask, minified ? minifyVSCodeTask : bundleVSCodeTask, vscodeTaskCI @@ -544,7 +539,7 @@ gulp.task(task.define( 'vscode-translations-export', task.series( core, - compileExtensionsBuildTask, + compileAllExtensionsBuildTask, function () { const pathToMetadata = './out-build/nls.metadata.json'; const pathToExtensions = '.build/extensions/*'; diff --git a/build/gulpfile.vscode.web.js b/build/gulpfile.vscode.web.js index 1b498cea83713..02b17022fa888 100644 --- a/build/gulpfile.vscode.web.js +++ b/build/gulpfile.vscode.web.js @@ -202,7 +202,7 @@ function packageTask(sourceFolderName, destinationFolderName) { const compileWebExtensionsBuildTask = task.define('compile-web-extensions-build', task.series( task.define('clean-web-extensions-build', util.rimraf('.build/web/extensions')), - task.define('bundle-web-extensions-build', () => extensions.packageLocalExtensionsStream(true, false).pipe(gulp.dest('.build/web'))), + task.define('bundle-web-extensions-build', () => extensions.packageAllLocalExtensionsStream(true, false).pipe(gulp.dest('.build/web'))), task.define('bundle-marketplace-web-extensions-build', () => extensions.packageMarketplaceExtensionsStream(true).pipe(gulp.dest('.build/web'))), task.define('bundle-web-extension-media-build', () => extensions.buildExtensionMedia(false, '.build/web/extensions')), )); diff --git a/build/lib/extensions.js b/build/lib/extensions.js index fbf11ee1ee4ee..8630c8fa061ff 100644 --- a/build/lib/extensions.js +++ b/build/lib/extensions.js @@ -6,7 +6,9 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.fromMarketplace = fromMarketplace; exports.fromGithub = fromGithub; -exports.packageLocalExtensionsStream = packageLocalExtensionsStream; +exports.packageNonNativeLocalExtensionsStream = packageNonNativeLocalExtensionsStream; +exports.packageNativeLocalExtensionsStream = packageNativeLocalExtensionsStream; +exports.packageAllLocalExtensionsStream = packageAllLocalExtensionsStream; exports.packageMarketplaceExtensionsStream = packageMarketplaceExtensionsStream; exports.scanBuiltinExtensions = scanBuiltinExtensions; exports.translatePackageJSON = translatePackageJSON; @@ -158,10 +160,12 @@ function fromLocalWebpack(extensionPath, webpackConfigFileName, disableMangle) { // source map handling: // * rewrite sourceMappingURL // * save to disk so that upload-task picks this up - const contents = data.contents.toString('utf8'); - data.contents = Buffer.from(contents.replace(/\n\/\/# sourceMappingURL=(.*)$/gm, function (_m, g1) { - return `\n//# sourceMappingURL=${sourceMappingURLBase}/extensions/${path.basename(extensionPath)}/${relativeOutputPath}/${g1}`; - }), 'utf8'); + if (path.extname(data.basename) === '.js') { + const contents = data.contents.toString('utf8'); + data.contents = Buffer.from(contents.replace(/\n\/\/# sourceMappingURL=(.*)$/gm, function (_m, g1) { + return `\n//# sourceMappingURL=${sourceMappingURLBase}/extensions/${path.basename(extensionPath)}/${relativeOutputPath}/${g1}`; + }), 'utf8'); + } this.emit('data', data); })); }); @@ -243,9 +247,17 @@ function fromGithub({ name, version, repo, sha256, metadata }) { .pipe(json({ __metadata: metadata })) .pipe(packageJsonFilter.restore); } +/** + * All extensions that are known to have some native component and thus must be built on the + * platform that is being built. + */ +const nativeExtensions = [ + 'microsoft-authentication', +]; const excludedExtensions = [ 'vscode-api-tests', 'vscode-colorize-tests', + 'vscode-colorize-perf-tests', 'vscode-test-resolver', 'ms-vscode.node-debug', 'ms-vscode.node-debug2', @@ -286,7 +298,46 @@ function isWebExtension(manifest) { } return true; } -function packageLocalExtensionsStream(forWeb, disableMangle) { +/** + * Package local extensions that are known to not have native dependencies. Mutually exclusive to {@link packageNativeLocalExtensionsStream}. + * @param forWeb build the extensions that have web targets + * @param disableMangle disable the mangler + * @returns a stream + */ +function packageNonNativeLocalExtensionsStream(forWeb, disableMangle) { + return doPackageLocalExtensionsStream(forWeb, disableMangle, false); +} +/** + * Package local extensions that are known to have native dependencies. Mutually exclusive to {@link packageNonNativeLocalExtensionsStream}. + * @note it's possible that the extension does not have native dependencies for the current platform, especially if building for the web, + * but we simplify the logic here by having a flat list of extensions (See {@link nativeExtensions}) that are known to have native + * dependencies on some platform and thus should be packaged on the platform that they are building for. + * @param forWeb build the extensions that have web targets + * @param disableMangle disable the mangler + * @returns a stream + */ +function packageNativeLocalExtensionsStream(forWeb, disableMangle) { + return doPackageLocalExtensionsStream(forWeb, disableMangle, true); +} +/** + * Package all the local extensions... both those that are known to have native dependencies and those that are not. + * @param forWeb build the extensions that have web targets + * @param disableMangle disable the mangler + * @returns a stream + */ +function packageAllLocalExtensionsStream(forWeb, disableMangle) { + return es.merge([ + packageNonNativeLocalExtensionsStream(forWeb, disableMangle), + packageNativeLocalExtensionsStream(forWeb, disableMangle) + ]); +} +/** + * @param forWeb build the extensions that have web targets + * @param disableMangle disable the mangler + * @param native build the extensions that are marked as having native dependencies + */ +function doPackageLocalExtensionsStream(forWeb, disableMangle, native) { + const nativeExtensionsSet = new Set(nativeExtensions); const localExtensionsDescriptions = (glob.sync('extensions/*/package.json') .map(manifestPath => { const absoluteManifestPath = path.join(root, manifestPath); @@ -294,6 +345,7 @@ function packageLocalExtensionsStream(forWeb, disableMangle) { const extensionName = path.basename(extensionPath); return { name: extensionName, path: extensionPath, manifestPath: absoluteManifestPath }; }) + .filter(({ name }) => native ? nativeExtensionsSet.has(name) : !nativeExtensionsSet.has(name)) .filter(({ name }) => excludedExtensions.indexOf(name) === -1) .filter(({ name }) => builtInExtensions.every(b => b.name !== name)) .filter(({ manifestPath }) => (forWeb ? isWebExtension(require(manifestPath)) : true))); diff --git a/build/lib/extensions.ts b/build/lib/extensions.ts index 628cf90c4c919..a881d3153daba 100644 --- a/build/lib/extensions.ts +++ b/build/lib/extensions.ts @@ -170,10 +170,12 @@ function fromLocalWebpack(extensionPath: string, webpackConfigFileName: string, // source map handling: // * rewrite sourceMappingURL // * save to disk so that upload-task picks this up - const contents = (data.contents).toString('utf8'); - data.contents = Buffer.from(contents.replace(/\n\/\/# sourceMappingURL=(.*)$/gm, function (_m, g1) { - return `\n//# sourceMappingURL=${sourceMappingURLBase}/extensions/${path.basename(extensionPath)}/${relativeOutputPath}/${g1}`; - }), 'utf8'); + if (path.extname(data.basename) === '.js') { + const contents = (data.contents).toString('utf8'); + data.contents = Buffer.from(contents.replace(/\n\/\/# sourceMappingURL=(.*)$/gm, function (_m, g1) { + return `\n//# sourceMappingURL=${sourceMappingURLBase}/extensions/${path.basename(extensionPath)}/${relativeOutputPath}/${g1}`; + }), 'utf8'); + } this.emit('data', data); })); @@ -275,9 +277,18 @@ export function fromGithub({ name, version, repo, sha256, metadata }: IExtension .pipe(packageJsonFilter.restore); } +/** + * All extensions that are known to have some native component and thus must be built on the + * platform that is being built. + */ +const nativeExtensions = [ + 'microsoft-authentication', +]; + const excludedExtensions = [ 'vscode-api-tests', 'vscode-colorize-tests', + 'vscode-colorize-perf-tests', 'vscode-test-resolver', 'ms-vscode.node-debug', 'ms-vscode.node-debug2', @@ -331,7 +342,49 @@ function isWebExtension(manifest: IExtensionManifest): boolean { return true; } -export function packageLocalExtensionsStream(forWeb: boolean, disableMangle: boolean): Stream { +/** + * Package local extensions that are known to not have native dependencies. Mutually exclusive to {@link packageNativeLocalExtensionsStream}. + * @param forWeb build the extensions that have web targets + * @param disableMangle disable the mangler + * @returns a stream + */ +export function packageNonNativeLocalExtensionsStream(forWeb: boolean, disableMangle: boolean): Stream { + return doPackageLocalExtensionsStream(forWeb, disableMangle, false); +} + +/** + * Package local extensions that are known to have native dependencies. Mutually exclusive to {@link packageNonNativeLocalExtensionsStream}. + * @note it's possible that the extension does not have native dependencies for the current platform, especially if building for the web, + * but we simplify the logic here by having a flat list of extensions (See {@link nativeExtensions}) that are known to have native + * dependencies on some platform and thus should be packaged on the platform that they are building for. + * @param forWeb build the extensions that have web targets + * @param disableMangle disable the mangler + * @returns a stream + */ +export function packageNativeLocalExtensionsStream(forWeb: boolean, disableMangle: boolean): Stream { + return doPackageLocalExtensionsStream(forWeb, disableMangle, true); +} + +/** + * Package all the local extensions... both those that are known to have native dependencies and those that are not. + * @param forWeb build the extensions that have web targets + * @param disableMangle disable the mangler + * @returns a stream + */ +export function packageAllLocalExtensionsStream(forWeb: boolean, disableMangle: boolean): Stream { + return es.merge([ + packageNonNativeLocalExtensionsStream(forWeb, disableMangle), + packageNativeLocalExtensionsStream(forWeb, disableMangle) + ]); +} + +/** + * @param forWeb build the extensions that have web targets + * @param disableMangle disable the mangler + * @param native build the extensions that are marked as having native dependencies + */ +function doPackageLocalExtensionsStream(forWeb: boolean, disableMangle: boolean, native: boolean): Stream { + const nativeExtensionsSet = new Set(nativeExtensions); const localExtensionsDescriptions = ( (glob.sync('extensions/*/package.json')) .map(manifestPath => { @@ -340,6 +393,7 @@ export function packageLocalExtensionsStream(forWeb: boolean, disableMangle: boo const extensionName = path.basename(extensionPath); return { name: extensionName, path: extensionPath, manifestPath: absoluteManifestPath }; }) + .filter(({ name }) => native ? nativeExtensionsSet.has(name) : !nativeExtensionsSet.has(name)) .filter(({ name }) => excludedExtensions.indexOf(name) === -1) .filter(({ name }) => builtInExtensions.every(b => b.name !== name)) .filter(({ manifestPath }) => (forWeb ? isWebExtension(require(manifestPath)) : true)) diff --git a/build/lib/layersChecker.js b/build/lib/layersChecker.js index ffc21be21f5c8..978ce2625b52a 100644 --- a/build/lib/layersChecker.js +++ b/build/lib/layersChecker.js @@ -67,7 +67,10 @@ const CORE_TYPES = [ 'fetch', 'RequestInit', 'Headers', + 'Request', 'Response', + 'Body', + '__type', '__global', 'PerformanceMark', 'PerformanceObserver', diff --git a/build/lib/layersChecker.ts b/build/lib/layersChecker.ts index 3ecde09be21fc..454f8874e3d02 100644 --- a/build/lib/layersChecker.ts +++ b/build/lib/layersChecker.ts @@ -68,7 +68,10 @@ const CORE_TYPES = [ 'fetch', 'RequestInit', 'Headers', + 'Request', 'Response', + 'Body', + '__type', '__global', 'PerformanceMark', 'PerformanceObserver', diff --git a/build/lib/optimize.js b/build/lib/optimize.js index 478390c42288b..83f34dc0745c9 100644 --- a/build/lib/optimize.js +++ b/build/lib/optimize.js @@ -71,7 +71,7 @@ function bundleESMTask(opts) { newContents = contents; } // File Content Mapper - const mapper = opts.fileContentMapper?.(path); + const mapper = opts.fileContentMapper?.(path.replace(/\\/g, '/')); if (mapper) { newContents = await mapper(newContents); } diff --git a/build/lib/optimize.ts b/build/lib/optimize.ts index 40efe87d6749e..8c49fa8188822 100644 --- a/build/lib/optimize.ts +++ b/build/lib/optimize.ts @@ -106,7 +106,7 @@ function bundleESMTask(opts: IBundleESMTaskOpts): NodeJS.ReadWriteStream { } // File Content Mapper - const mapper = opts.fileContentMapper?.(path); + const mapper = opts.fileContentMapper?.(path.replace(/\\/g, '/')); if (mapper) { newContents = await mapper(newContents); } diff --git a/build/lib/stylelint/vscode-known-variables.json b/build/lib/stylelint/vscode-known-variables.json index 2929f17986e8a..c66764a97611b 100644 --- a/build/lib/stylelint/vscode-known-variables.json +++ b/build/lib/stylelint/vscode-known-variables.json @@ -782,6 +782,9 @@ "--vscode-testing-uncoveredBorder", "--vscode-testing-uncoveredBranchBackground", "--vscode-testing-uncoveredGutterBackground", + "--vscode-testing-message-error-badgeForeground", + "--vscode-testing-message-error-badgeBackground", + "--vscode-testing-message-error-badgeBorder", "--vscode-textBlockQuote-background", "--vscode-textBlockQuote-border", "--vscode-textCodeBlock-background", @@ -821,6 +824,7 @@ "--chat-current-response-min-height", "--dropdown-padding-bottom", "--dropdown-padding-top", + "--inline-chat-frame-progress", "--insert-border-color", "--last-tab-margin-right", "--monaco-monospace-font", @@ -831,6 +835,9 @@ "--notebook-diff-view-viewport-slider", "--notebook-find-horizontal-padding", "--notebook-find-width", + "--notebook-editor-font-family", + "--notebook-editor-font-size", + "--notebook-editor-font-weight", "--outline-element-color", "--separator-border", "--status-border-top-color", @@ -896,4 +903,4 @@ "--widget-color", "--text-link-decoration" ] -} \ No newline at end of file +} diff --git a/build/linux/debian/dep-lists.js b/build/linux/debian/dep-lists.js index 4ee215b225864..3bb58fb1215d7 100644 --- a/build/linux/debian/dep-lists.js +++ b/build/linux/debian/dep-lists.js @@ -39,7 +39,6 @@ exports.referenceGeneratedDepsByArch = { 'libdrm2 (>= 2.4.75)', 'libexpat1 (>= 2.1~beta3)', 'libgbm1 (>= 17.1.0~rc2)', - 'libgdk-pixbuf-2.0-0 (>= 2.36.9)', 'libglib2.0-0 (>= 2.37.3)', 'libgtk-3-0 (>= 3.9.10)', 'libgtk-3-0 (>= 3.9.10) | libgtk-4-1', @@ -77,7 +76,6 @@ exports.referenceGeneratedDepsByArch = { 'libdrm2 (>= 2.4.75)', 'libexpat1 (>= 2.1~beta3)', 'libgbm1 (>= 17.1.0~rc2)', - 'libgdk-pixbuf-2.0-0 (>= 2.36.9)', 'libglib2.0-0 (>= 2.37.3)', 'libgtk-3-0 (>= 3.9.10)', 'libgtk-3-0 (>= 3.9.10) | libgtk-4-1', @@ -116,7 +114,6 @@ exports.referenceGeneratedDepsByArch = { 'libdrm2 (>= 2.4.75)', 'libexpat1 (>= 2.1~beta3)', 'libgbm1 (>= 17.1.0~rc2)', - 'libgdk-pixbuf-2.0-0 (>= 2.36.9)', 'libglib2.0-0 (>= 2.37.3)', 'libgtk-3-0 (>= 3.9.10)', 'libgtk-3-0 (>= 3.9.10) | libgtk-4-1', diff --git a/build/linux/debian/dep-lists.ts b/build/linux/debian/dep-lists.ts index 6c8f053d26b3e..e3d78d1139ad5 100644 --- a/build/linux/debian/dep-lists.ts +++ b/build/linux/debian/dep-lists.ts @@ -39,7 +39,6 @@ export const referenceGeneratedDepsByArch = { 'libdrm2 (>= 2.4.75)', 'libexpat1 (>= 2.1~beta3)', 'libgbm1 (>= 17.1.0~rc2)', - 'libgdk-pixbuf-2.0-0 (>= 2.36.9)', 'libglib2.0-0 (>= 2.37.3)', 'libgtk-3-0 (>= 3.9.10)', 'libgtk-3-0 (>= 3.9.10) | libgtk-4-1', @@ -77,7 +76,6 @@ export const referenceGeneratedDepsByArch = { 'libdrm2 (>= 2.4.75)', 'libexpat1 (>= 2.1~beta3)', 'libgbm1 (>= 17.1.0~rc2)', - 'libgdk-pixbuf-2.0-0 (>= 2.36.9)', 'libglib2.0-0 (>= 2.37.3)', 'libgtk-3-0 (>= 3.9.10)', 'libgtk-3-0 (>= 3.9.10) | libgtk-4-1', @@ -116,7 +114,6 @@ export const referenceGeneratedDepsByArch = { 'libdrm2 (>= 2.4.75)', 'libexpat1 (>= 2.1~beta3)', 'libgbm1 (>= 17.1.0~rc2)', - 'libgdk-pixbuf-2.0-0 (>= 2.36.9)', 'libglib2.0-0 (>= 2.37.3)', 'libgtk-3-0 (>= 3.9.10)', 'libgtk-3-0 (>= 3.9.10) | libgtk-4-1', diff --git a/build/linux/rpm/dep-lists.js b/build/linux/rpm/dep-lists.js index 04abee1d30ac8..6857f6b7c1a13 100644 --- a/build/linux/rpm/dep-lists.js +++ b/build/linux/rpm/dep-lists.js @@ -70,7 +70,6 @@ exports.referenceGeneratedDepsByArch = { 'libgcc_s.so.1(GCC_3.0)(64bit)', 'libgcc_s.so.1(GCC_3.3)(64bit)', 'libgcc_s.so.1(GCC_4.2.0)(64bit)', - 'libgdk_pixbuf-2.0.so.0()(64bit)', 'libgio-2.0.so.0()(64bit)', 'libglib-2.0.so.0()(64bit)', 'libgobject-2.0.so.0()(64bit)', @@ -160,7 +159,6 @@ exports.referenceGeneratedDepsByArch = { 'libgcc_s.so.1(GCC_3.0)', 'libgcc_s.so.1(GCC_3.5)', 'libgcc_s.so.1(GCC_4.3.0)', - 'libgdk_pixbuf-2.0.so.0', 'libgio-2.0.so.0', 'libglib-2.0.so.0', 'libgobject-2.0.so.0', @@ -255,7 +253,6 @@ exports.referenceGeneratedDepsByArch = { 'libgcc_s.so.1(GCC_3.3)(64bit)', 'libgcc_s.so.1(GCC_4.2.0)(64bit)', 'libgcc_s.so.1(GCC_4.5.0)(64bit)', - 'libgdk_pixbuf-2.0.so.0()(64bit)', 'libgio-2.0.so.0()(64bit)', 'libglib-2.0.so.0()(64bit)', 'libgobject-2.0.so.0()(64bit)', diff --git a/build/linux/rpm/dep-lists.ts b/build/linux/rpm/dep-lists.ts index 8761e40cb1ec8..8fd025e51c238 100644 --- a/build/linux/rpm/dep-lists.ts +++ b/build/linux/rpm/dep-lists.ts @@ -69,7 +69,6 @@ export const referenceGeneratedDepsByArch = { 'libgcc_s.so.1(GCC_3.0)(64bit)', 'libgcc_s.so.1(GCC_3.3)(64bit)', 'libgcc_s.so.1(GCC_4.2.0)(64bit)', - 'libgdk_pixbuf-2.0.so.0()(64bit)', 'libgio-2.0.so.0()(64bit)', 'libglib-2.0.so.0()(64bit)', 'libgobject-2.0.so.0()(64bit)', @@ -159,7 +158,6 @@ export const referenceGeneratedDepsByArch = { 'libgcc_s.so.1(GCC_3.0)', 'libgcc_s.so.1(GCC_3.5)', 'libgcc_s.so.1(GCC_4.3.0)', - 'libgdk_pixbuf-2.0.so.0', 'libgio-2.0.so.0', 'libglib-2.0.so.0', 'libgobject-2.0.so.0', @@ -254,7 +252,6 @@ export const referenceGeneratedDepsByArch = { 'libgcc_s.so.1(GCC_3.3)(64bit)', 'libgcc_s.so.1(GCC_4.2.0)(64bit)', 'libgcc_s.so.1(GCC_4.5.0)(64bit)', - 'libgdk_pixbuf-2.0.so.0()(64bit)', 'libgio-2.0.so.0()(64bit)', 'libglib-2.0.so.0()(64bit)', 'libgobject-2.0.so.0()(64bit)', diff --git a/build/npm/dirs.js b/build/npm/dirs.js index b9645e6e13717..9f653c7247948 100644 --- a/build/npm/dirs.js +++ b/build/npm/dirs.js @@ -44,6 +44,7 @@ const dirs = [ 'extensions/typescript-language-features', 'extensions/vscode-api-tests', 'extensions/vscode-colorize-tests', + 'extensions/vscode-colorize-perf-tests', 'extensions/vscode-test-resolver', 'remote', 'remote/web', diff --git a/build/package-lock.json b/build/package-lock.json index 1e373f2e68f16..e8f5ce67f2481 100644 --- a/build/package-lock.json +++ b/build/package-lock.json @@ -9,8 +9,11 @@ "version": "1.0.0", "license": "MIT", "devDependencies": { + "@azure/core-auth": "^1.9.0", "@azure/cosmos": "^3", "@azure/identity": "^4.2.1", + "@azure/msal-node": "^2.16.1", + "@azure/storage-blob": "^12.25.0", "@electron/get": "^2.0.0", "@types/ansi-colors": "^3.2.0", "@types/byline": "^4.2.32", @@ -26,6 +29,7 @@ "@types/gulp-rename": "^0.0.33", "@types/gulp-sort": "^2.0.4", "@types/gulp-sourcemaps": "^0.0.32", + "@types/jws": "^3.2.10", "@types/mime": "0.0.29", "@types/minimatch": "^3.0.3", "@types/minimist": "^1.2.1", @@ -47,6 +51,7 @@ "gulp-merge-json": "^2.1.1", "gulp-sort": "^2.0.0", "jsonc-parser": "^2.3.0", + "jws": "^4.0.0", "mime": "^1.4.1", "source-map": "0.6.1", "ternary-stream": "^3.0.0", @@ -73,107 +78,188 @@ "node": ">=8.0.0" } }, - "node_modules/@azure/core-asynciterator-polyfill": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@azure/core-asynciterator-polyfill/-/core-asynciterator-polyfill-1.0.0.tgz", - "integrity": "sha512-kmv8CGrPfN9SwMwrkiBK9VTQYxdFQEGe0BmQk+M8io56P9KNzpAxcWE/1fxJj7uouwN4kXF0BHW8DNlgx+wtCg==", - "dev": true - }, "node_modules/@azure/core-auth": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.5.0.tgz", - "integrity": "sha512-udzoBuYG1VBoHVohDTrvKjyzel34zt77Bhp7dQntVGGD0ehVq48owENbBG8fIgkHRNUBQH5k1r0hpoMu5L8+kw==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.9.0.tgz", + "integrity": "sha512-FPwHpZywuyasDSLMqJ6fhbOK3TqUdviZNF8OqRGA4W5Ewib2lEEZ+pBsYcBa88B2NGO/SEnYPGhyBqNlE8ilSw==", "dev": true, + "license": "MIT", "dependencies": { - "@azure/abort-controller": "^1.0.0", - "@azure/core-util": "^1.1.0", - "tslib": "^2.2.0" + "@azure/abort-controller": "^2.0.0", + "@azure/core-util": "^1.11.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-auth/node_modules/@azure/abort-controller": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", + "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, "node_modules/@azure/core-client": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.5.0.tgz", - "integrity": "sha512-YNk8i9LT6YcFdFO+RRU0E4Ef+A8Y5lhXo6lz61rwbG8Uo7kSqh0YqK04OexiilM43xd6n3Y9yBhLnb1NFNI9dA==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.9.2.tgz", + "integrity": "sha512-kRdry/rav3fUKHl/aDLd/pDLcB+4pOFwPPTVEExuMyaI5r+JBbMWqRbCY1pn5BniDaU3lRxO9eaQ1AmSMehl/w==", "dev": true, + "license": "MIT", "dependencies": { - "@azure/abort-controller": "^1.0.0", - "@azure/core-asynciterator-polyfill": "^1.0.0", - "@azure/core-auth": "^1.3.0", - "@azure/core-rest-pipeline": "^1.5.0", - "@azure/core-tracing": "1.0.0-preview.13", + "@azure/abort-controller": "^2.0.0", + "@azure/core-auth": "^1.4.0", + "@azure/core-rest-pipeline": "^1.9.1", + "@azure/core-tracing": "^1.0.0", + "@azure/core-util": "^1.6.1", "@azure/logger": "^1.0.0", - "tslib": "^2.2.0" + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-client/node_modules/@azure/abort-controller": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", + "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.6.2" }, "engines": { - "node": ">=12.0.0" + "node": ">=18.0.0" } }, - "node_modules/@azure/core-client/node_modules/@azure/core-tracing": { - "version": "1.0.0-preview.13", - "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.0.0-preview.13.tgz", - "integrity": "sha512-KxDlhXyMlh2Jhj2ykX6vNEU0Vou4nHr025KoSEiz7cS3BNiHNaZcdECk/DmLkEB0as5T7b/TpRcehJ5yV6NeXQ==", + "node_modules/@azure/core-http-compat": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@azure/core-http-compat/-/core-http-compat-2.1.2.tgz", + "integrity": "sha512-5MnV1yqzZwgNLLjlizsU3QqOeQChkIXw781Fwh1xdAqJR5AA32IUaq6xv1BICJvfbHoa+JYcaij2HFkhLbNTJQ==", "dev": true, + "license": "MIT", "dependencies": { - "@opentelemetry/api": "^1.0.1", - "tslib": "^2.2.0" + "@azure/abort-controller": "^2.0.0", + "@azure/core-client": "^1.3.0", + "@azure/core-rest-pipeline": "^1.3.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-http-compat/node_modules/@azure/abort-controller": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", + "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-lro": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/@azure/core-lro/-/core-lro-2.7.2.tgz", + "integrity": "sha512-0YIpccoX8m/k00O7mDDMdJpbr6mf1yWo2dfmxt5A8XVZVVMz2SSKaEbMCeJRvgQ0IaSlqhjT47p4hVIRRy90xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.0.0", + "@azure/core-util": "^1.2.0", + "@azure/logger": "^1.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-lro/node_modules/@azure/abort-controller": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", + "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-paging": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/@azure/core-paging/-/core-paging-1.6.2.tgz", + "integrity": "sha512-YKWi9YuCU04B55h25cnOYZHxXYtEvQEbKST5vqRga7hWY9ydd3FZHdeQF8pyh+acWZvppw13M/LMGx0LABUVMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.6.2" }, "engines": { - "node": ">=12.0.0" + "node": ">=18.0.0" } }, "node_modules/@azure/core-rest-pipeline": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.7.0.tgz", - "integrity": "sha512-e2awPzwMKHrmvYgZ0qIKNkqnCM1QoDs7A0rOiS3OSAlOQOz/kL7PPKHXwFMuBeaRvS8i7fgobJn79q2Cji5f+Q==", + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.18.0.tgz", + "integrity": "sha512-QSoGUp4Eq/gohEFNJaUOwTN7BCc2nHTjjbm75JT0aD7W65PWM1H/tItz0GsABn22uaKyGxiMhWQLt2r+FGU89Q==", "dev": true, + "license": "MIT", "dependencies": { - "@azure/abort-controller": "^1.0.0", - "@azure/core-auth": "^1.3.0", - "@azure/core-tracing": "1.0.0-preview.13", + "@azure/abort-controller": "^2.0.0", + "@azure/core-auth": "^1.8.0", + "@azure/core-tracing": "^1.0.1", + "@azure/core-util": "^1.11.0", "@azure/logger": "^1.0.0", - "form-data": "^4.0.0", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", - "tslib": "^2.2.0", - "uuid": "^8.3.0" + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=12.0.0" + "node": ">=18.0.0" } }, - "node_modules/@azure/core-rest-pipeline/node_modules/@azure/core-tracing": { - "version": "1.0.0-preview.13", - "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.0.0-preview.13.tgz", - "integrity": "sha512-KxDlhXyMlh2Jhj2ykX6vNEU0Vou4nHr025KoSEiz7cS3BNiHNaZcdECk/DmLkEB0as5T7b/TpRcehJ5yV6NeXQ==", + "node_modules/@azure/core-rest-pipeline/node_modules/@azure/abort-controller": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", + "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", "dev": true, + "license": "MIT", "dependencies": { - "@opentelemetry/api": "^1.0.1", - "tslib": "^2.2.0" + "tslib": "^2.6.2" }, "engines": { - "node": ">=12.0.0" + "node": ">=18.0.0" } }, "node_modules/@azure/core-tracing": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.0.1.tgz", - "integrity": "sha512-I5CGMoLtX+pI17ZdiFJZgxMJApsK6jjfm85hpgp3oazCdq5Wxgh4wMr7ge/TTWW1B5WBuvIOI1fMU/FrOAMKrw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.2.0.tgz", + "integrity": "sha512-UKTiEJPkWcESPYJz3X5uKRYyOcJD+4nYph+KpfdPRnQJVrZfk0KJgdnaAWKfhsBBtAf/D58Az4AvCJEmWgIBAg==", "dev": true, + "license": "MIT", "dependencies": { - "tslib": "^2.2.0" + "tslib": "^2.6.2" }, "engines": { - "node": ">=12.0.0" + "node": ">=18.0.0" } }, "node_modules/@azure/core-util": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.9.0.tgz", - "integrity": "sha512-AfalUQ1ZppaKuxPPMsFEUdX6GZPB3d9paR9d/TTL7Ow2De8cJaC7ibi7kWVlFAVPCYo31OcnGymc0R89DX8Oaw==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.11.0.tgz", + "integrity": "sha512-DxOSLua+NdpWoSqULhjDyAZTXFdP/LKkqtYuxxz1SCN289zk3OG8UOpnCQAz/tygyACBtWp/BoO72ptK7msY8g==", "dev": true, + "license": "MIT", "dependencies": { "@azure/abort-controller": "^2.0.0", "tslib": "^2.6.2" @@ -194,6 +280,20 @@ "node": ">=18.0.0" } }, + "node_modules/@azure/core-xml": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/@azure/core-xml/-/core-xml-1.4.4.tgz", + "integrity": "sha512-J4FYAqakGXcbfeZjwjMzjNcpcH4E+JtEBv+xcV1yL0Ydn/6wbQfeFKTCHh9wttAi0lmajHw7yBbHPRG+YHckZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-xml-parser": "^4.4.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@azure/cosmos": { "version": "3.17.3", "resolved": "https://registry.npmjs.org/@azure/cosmos/-/cosmos-3.17.3.tgz", @@ -277,12 +377,13 @@ } }, "node_modules/@azure/msal-node": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-2.9.2.tgz", - "integrity": "sha512-8tvi6Cos3m+0KmRbPjgkySXi+UQU/QiuVRFnrxIwt5xZlEEFa69O04RTaNESGgImyBBlYbo2mfE8/U8Bbdk1WQ==", + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-2.16.1.tgz", + "integrity": "sha512-1NEFpTmMMT2A7RnZuvRl/hUmJU+GLPjh+ShyIqPktG2PvSd2yvPnzGd/BxIBAAvJG5nr9lH4oYcQXepDbaE7fg==", "dev": true, + "license": "MIT", "dependencies": { - "@azure/msal-common": "14.12.0", + "@azure/msal-common": "14.16.0", "jsonwebtoken": "^9.0.0", "uuid": "^8.3.0" }, @@ -290,6 +391,54 @@ "node": ">=16" } }, + "node_modules/@azure/msal-node/node_modules/@azure/msal-common": { + "version": "14.16.0", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-14.16.0.tgz", + "integrity": "sha512-1KOZj9IpcDSwpNiQNjt0jDYZpQvNZay7QAEi/5DLubay40iGYtLzya/jbjRPLyOTZhEKyL1MzPuw2HqBCjceYA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@azure/storage-blob": { + "version": "12.25.0", + "resolved": "https://registry.npmjs.org/@azure/storage-blob/-/storage-blob-12.25.0.tgz", + "integrity": "sha512-oodouhA3nCCIh843tMMbxty3WqfNT+Vgzj3Xo5jqR9UPnzq3d7mzLjlHAYz7lW+b4km3SIgz+NAgztvhm7Z6kQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@azure/core-auth": "^1.4.0", + "@azure/core-client": "^1.6.2", + "@azure/core-http-compat": "^2.0.0", + "@azure/core-lro": "^2.2.0", + "@azure/core-paging": "^1.1.1", + "@azure/core-rest-pipeline": "^1.10.1", + "@azure/core-tracing": "^1.1.2", + "@azure/core-util": "^1.6.1", + "@azure/core-xml": "^1.4.3", + "@azure/logger": "^1.0.0", + "events": "^3.0.0", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/storage-blob/node_modules/@azure/abort-controller": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", + "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@electron/asar": { "version": "3.2.10", "resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.2.10.tgz", @@ -743,15 +892,6 @@ "node": ">= 12.13.0" } }, - "node_modules/@opentelemetry/api": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.0.3.tgz", - "integrity": "sha512-puWxACExDe9nxbBB3lOymQFrLYml2dVOrd7USiVRnSbgXE+KwBu+HxFvxrzfqsiSda9IWsXJG1ef7C1O2/GmKQ==", - "dev": true, - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/@sindresorhus/is": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", @@ -776,15 +916,6 @@ "node": ">=10" } }, - "node_modules/@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, "node_modules/@types/ansi-colors": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/@types/ansi-colors/-/ansi-colors-3.2.0.tgz", @@ -969,6 +1100,16 @@ "integrity": "sha512-/siF86XrwDKLuHe8l7h6NhrAWgLdgqbxmjZv9NvGWmgYRZoTipkjKiWb0SQHy/jcR+ee0GvbG6uGd+LEBMGNvA==", "dev": true }, + "node_modules/@types/jws": { + "version": "3.2.10", + "resolved": "https://registry.npmjs.org/@types/jws/-/jws-3.2.10.tgz", + "integrity": "sha512-cOevhttJmssERB88/+XvZXvsq5m9JLKZNUiGfgjUb5lcPRdV2ZQciU6dU76D/qXXFYpSqkP3PrSg4hMTiafTZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/keyv": { "version": "3.1.4", "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", @@ -1197,6 +1338,16 @@ "node": ">=10" } }, + "node_modules/@vscode/vsce/node_modules/yazl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/yazl/-/yazl-2.5.1.tgz", + "integrity": "sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3" + } + }, "node_modules/@xmldom/xmldom": { "version": "0.8.10", "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz", @@ -1207,15 +1358,16 @@ } }, "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", "dev": true, + "license": "MIT", "dependencies": { - "debug": "4" + "debug": "^4.3.4" }, "engines": { - "node": ">= 6.0.0" + "node": ">= 14" } }, "node_modules/ansi-colors": { @@ -1324,12 +1476,6 @@ "node": ">= 0.10" } }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k= sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true - }, "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", @@ -1705,18 +1851,6 @@ "color-support": "bin.js" } }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/compare-version": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/compare-version/-/compare-version-0.1.2.tgz", @@ -1881,15 +2015,6 @@ "node": ">= 0.4" } }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk= sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/detect-libc": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", @@ -2207,6 +2332,29 @@ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true }, + "node_modules/fast-xml-parser": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.0.tgz", + "integrity": "sha512-/PlTQCI96+fZMAOLMZK4CWG1ItCbfZ/0jx7UIJFChPNrx7tcEgerUgWbeieCM9MfHInUDyK8DWYZ+YrywDJuTg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + } + ], + "license": "MIT", + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, "node_modules/fd-slicer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", @@ -2261,20 +2409,6 @@ "integrity": "sha512-Pqq5NnT78ehvUnAk/We/Jr22vSvanRlFTpAmQ88xBY/M1TlHe+P0ILuEyXS595ysdGfaj22634LBkGMA2GTcpA==", "dev": true }, - "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dev": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", @@ -2631,17 +2765,17 @@ "dev": true }, "node_modules/http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "dev": true, + "license": "MIT", "dependencies": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" + "agent-base": "^7.1.0", + "debug": "^4.3.4" }, "engines": { - "node": ">= 6" + "node": ">= 14" } }, "node_modules/http2-wrapper": { @@ -2658,16 +2792,17 @@ } }, "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", "dev": true, + "license": "MIT", "dependencies": { - "agent-base": "6", + "agent-base": "^7.0.2", "debug": "4" }, "engines": { - "node": ">= 6" + "node": ">= 14" } }, "node_modules/ieee754": { @@ -2959,6 +3094,7 @@ "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", "dev": true, + "license": "MIT", "dependencies": { "jwa": "^2.0.0", "safe-buffer": "^5.0.1" @@ -3111,27 +3247,6 @@ "node": ">=4" } }, - "node_modules/mime-db": { - "version": "1.45.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.45.0.tgz", - "integrity": "sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.28", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.28.tgz", - "integrity": "sha512-0TO2yJ5YHYr7M2zzT7gDU1tbwHxEUWBCLt0lscSNpcdAfFyJOVEpRYNS7EXVcTLNj/25QO8gulHC5JtTzSE2UQ==", - "dev": true, - "dependencies": { - "mime-db": "1.45.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/mimic-response": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", @@ -3904,6 +4019,13 @@ "node": ">=0.10.0" } }, + "node_modules/strnum": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==", + "dev": true, + "license": "MIT" + }, "node_modules/sumchecker": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz", @@ -4371,15 +4493,6 @@ "fd-slicer": "~1.1.0" } }, - "node_modules/yazl": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/yazl/-/yazl-2.5.1.tgz", - "integrity": "sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw==", - "dev": true, - "dependencies": { - "buffer-crc32": "~0.2.3" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/build/package.json b/build/package.json index aa94a210e9c69..c85b600983f43 100644 --- a/build/package.json +++ b/build/package.json @@ -3,8 +3,11 @@ "version": "1.0.0", "license": "MIT", "devDependencies": { + "@azure/core-auth": "^1.9.0", "@azure/cosmos": "^3", "@azure/identity": "^4.2.1", + "@azure/msal-node": "^2.16.1", + "@azure/storage-blob": "^12.25.0", "@electron/get": "^2.0.0", "@types/ansi-colors": "^3.2.0", "@types/byline": "^4.2.32", @@ -20,6 +23,7 @@ "@types/gulp-rename": "^0.0.33", "@types/gulp-sort": "^2.0.4", "@types/gulp-sourcemaps": "^0.0.32", + "@types/jws": "^3.2.10", "@types/mime": "0.0.29", "@types/minimatch": "^3.0.3", "@types/minimist": "^1.2.1", @@ -41,6 +45,7 @@ "gulp-merge-json": "^2.1.1", "gulp-sort": "^2.0.0", "jsonc-parser": "^2.3.0", + "jws": "^4.0.0", "mime": "^1.4.1", "source-map": "0.6.1", "ternary-stream": "^3.0.0", diff --git a/build/win32/code.iss b/build/win32/code.iss index fca3d1e9d9b85..2f73942ba09df 100644 --- a/build/win32/code.iss +++ b/build/win32/code.iss @@ -35,6 +35,11 @@ ArchitecturesAllowed={#ArchitecturesAllowed} ArchitecturesInstallIn64BitMode={#ArchitecturesInstallIn64BitMode} WizardStyle=modern +// We've seen an uptick on broken installations from updates which were unable +// to shutdown VS Code. We rely on the fact that the update signals +// that VS Code is ready to be shutdown, so we're good to use `force` here. +CloseApplications=force + #ifdef Sign SignTool=esrp #endif diff --git a/cgmanifest.json b/cgmanifest.json index 57fd8db5f4503..5be1cdf48cd88 100644 --- a/cgmanifest.json +++ b/cgmanifest.json @@ -528,12 +528,12 @@ "git": { "name": "electron", "repositoryUrl": "https://github.com/electron/electron", - "commitHash": "db2050e9d24022b5d7d1bf4873e4725d75383a1f" + "commitHash": "bcd7ac5481acc54ba28c6399b279d843ba427b19" } }, "isOnlyProductionDependency": true, "license": "MIT", - "version": "32.2.1" + "version": "32.2.3" }, { "component": { diff --git a/cli/.cargo/config.toml b/cli/.cargo/config.toml new file mode 100644 index 0000000000000..ad9374d4a9087 --- /dev/null +++ b/cli/.cargo/config.toml @@ -0,0 +1,6 @@ +[target.'cfg(all(target_os = "windows", any(target_arch = "i686", target_arch = "x86_64", target_arch = "x86")))'] +rustflags = ["-Ctarget-feature=+crt-static", "-Clink-args=/guard:cf", "-Clink-args=/CETCOMPAT"] + +# CETCOMPAT is not supported on ARM binaries +[target.'cfg(all(target_os = "windows", not(any(target_arch = "i686", target_arch = "x86_64", target_arch = "x86"))))'] +rustflags = ["-Ctarget-feature=+crt-static", "-Clink-args=/guard:cf"] diff --git a/cli/CONTRIBUTING.md b/cli/CONTRIBUTING.md index d119f1ac98a87..4809fccd08093 100644 --- a/cli/CONTRIBUTING.md +++ b/cli/CONTRIBUTING.md @@ -8,7 +8,7 @@ For the moment, we require OpenSSL on Windows, where it is not usually installed by default. To install it: -1. Install (clone) vcpkg [using their instructions](https://github.com/Microsoft/vcpkg#quick-start-windows) +1. Follow steps 1 and 2 of [Set up vcpkg](https://learn.microsoft.com/en-us/vcpkg/get_started/get-started-msbuild?pivots=shell-powershell#1---set-up-vcpkg) to obtain the executable. 1. Add the location of the `vcpkg` directory to your system or user PATH. 1. Run`vcpkg install openssl:x64-windows-static-md` (after restarting your terminal for PATH changes to apply) 1. You should be able to then `cargo build` successfully diff --git a/cli/src/commands/serve_web.rs b/cli/src/commands/serve_web.rs index eef4f331d1099..3dedf4b3f059d 100644 --- a/cli/src/commands/serve_web.rs +++ b/cli/src/commands/serve_web.rs @@ -252,6 +252,7 @@ fn get_release_from_path(path: &str, platform: Platform) -> Option<(Release, Str let (quality_commit, remaining) = path.split_at(i); let (quality, commit) = quality_commit.split_at(quality_commit_sep); + let commit = &commit[1..]; if !is_commit_hash(commit) { return None; diff --git a/cli/src/tunnels/control_server.rs b/cli/src/tunnels/control_server.rs index 913efdce23e2d..74fd247cfcde5 100644 --- a/cli/src/tunnels/control_server.rs +++ b/cli/src/tunnels/control_server.rs @@ -1062,7 +1062,6 @@ fn handle_challenge_issue( let mut auth_state = auth_state.lock().unwrap(); if let AuthState::WaitingForChallenge(Some(s)) = &*auth_state { - println!("looking for token {}, got {:?}", s, params.token); match ¶ms.token { Some(t) if s != t => return Err(CodeError::AuthChallengeBadToken.into()), None => return Err(CodeError::AuthChallengeBadToken.into()), diff --git a/eslint.config.js b/eslint.config.js index c09ba2ab55b09..aa9fd4930c51b 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -256,6 +256,13 @@ export default tseslint.config( 'local': pluginLocal, }, rules: { + 'no-restricted-syntax': [ + 'warn', + { + 'selector': `TSArrayType > TSUnionType`, + 'message': 'Use Array<...> for arrays of union types.' + }, + ], 'local/vscode-dts-create-func': 'warn', 'local/vscode-dts-literal-or-types': 'warn', 'local/vscode-dts-string-type-literals': 'warn', @@ -818,6 +825,7 @@ export default tseslint.config( 'string_decoder', 'tas-client-umd', 'tls', + 'undici-types', 'url', 'util', 'v8-inspect-profiler', @@ -826,6 +834,7 @@ export default tseslint.config( 'worker_threads', '@xterm/addon-clipboard', '@xterm/addon-image', + '@xterm/addon-ligatures', '@xterm/addon-search', '@xterm/addon-serialize', '@xterm/addon-unicode11', diff --git a/extensions/coffeescript/package.json b/extensions/coffeescript/package.json index 44f423c7dfc7c..8480ca1f9ab0d 100644 --- a/extensions/coffeescript/package.json +++ b/extensions/coffeescript/package.json @@ -49,7 +49,8 @@ ], "configurationDefaults": { "[coffeescript]": { - "diffEditor.ignoreTrimWhitespace": false + "diffEditor.ignoreTrimWhitespace": false, + "editor.defaultColorDecorators": false } } }, diff --git a/extensions/docker/cgmanifest.json b/extensions/docker/cgmanifest.json index dbbf43607eef4..4f568542aed88 100644 --- a/extensions/docker/cgmanifest.json +++ b/extensions/docker/cgmanifest.json @@ -9,10 +9,10 @@ "commitHash": "abd39744c6f3ed854500e423f5fabf952165161f" } }, - "license": "Apache2", + "license": "Apache-2.0", "description": "The file syntaxes/docker.tmLanguage was included from https://github.com/moby/moby/blob/master/contrib/syntax/textmate/Docker.tmbundle/Syntaxes/Dockerfile.tmLanguage.", "version": "0.0.0" } ], "version": 1 -} \ No newline at end of file +} diff --git a/extensions/emmet/package-lock.json b/extensions/emmet/package-lock.json index aa01c4104caa7..131ce39675840 100644 --- a/extensions/emmet/package-lock.json +++ b/extensions/emmet/package-lock.json @@ -50,7 +50,8 @@ "node_modules/@emmetio/html-matcher": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/@emmetio/html-matcher/-/html-matcher-0.3.3.tgz", - "integrity": "sha1-C72twIguGFlQ8Dc33G2/j3vZByg= sha512-+aeGmFXoR36nk2qzqPhBnWjnB38BV+dreTh/tsfbWP9kHv7fqRa9XuG1BSQFbPtKzsjUsBvGXkgGU3G8MkMw6A==", + "integrity": "sha512-+aeGmFXoR36nk2qzqPhBnWjnB38BV+dreTh/tsfbWP9kHv7fqRa9XuG1BSQFbPtKzsjUsBvGXkgGU3G8MkMw6A==", + "license": "ISC", "dependencies": { "@emmetio/stream-reader": "^2.0.0", "@emmetio/stream-reader-utils": "^0.1.0" @@ -89,21 +90,23 @@ } }, "node_modules/@vscode/emmet-helper": { - "version": "2.9.3", - "resolved": "https://registry.npmjs.org/@vscode/emmet-helper/-/emmet-helper-2.9.3.tgz", - "integrity": "sha512-rB39LHWWPQYYlYfpv9qCoZOVioPCftKXXqrsyqN1mTWZM6dTnONT63Db+03vgrBbHzJN45IrgS/AGxw9iiqfEw==", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@vscode/emmet-helper/-/emmet-helper-2.10.0.tgz", + "integrity": "sha512-UHw1EQRgLbSYkyB73/7wR/IzV6zTBnbzEHuuU4Z6b95HKf2lmeTdGwBIwspWBSRrnIA1TI2x2tetBym6ErA7Gw==", + "license": "MIT", "dependencies": { "emmet": "^2.4.3", "jsonc-parser": "^2.3.0", "vscode-languageserver-textdocument": "^1.0.1", "vscode-languageserver-types": "^3.15.1", - "vscode-uri": "^2.1.2" + "vscode-uri": "^3.0.8" } }, "node_modules/emmet": { - "version": "2.4.7", - "resolved": "https://registry.npmjs.org/emmet/-/emmet-2.4.7.tgz", - "integrity": "sha512-O5O5QNqtdlnQM2bmKHtJgyChcrFMgQuulI+WdiOw2NArzprUqqxUW6bgYtKvzKgrsYpuLWalOkdhNP+1jluhCA==", + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/emmet/-/emmet-2.4.11.tgz", + "integrity": "sha512-23QPJB3moh/U9sT4rQzGgeyyGIrcM+GH5uVYg2C6wZIxAIJq7Ng3QLT79tl8FUwDXhyq9SusfknOrofAKqvgyQ==", + "license": "MIT", "workspaces": [ "./packages/scanner", "./packages/abbreviation", @@ -154,19 +157,22 @@ "dev": true }, "node_modules/vscode-languageserver-textdocument": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.3.tgz", - "integrity": "sha512-ynEGytvgTb6HVSUwPJIAZgiHQmPCx8bZ8w5um5Lz+q5DjP0Zj8wTFhQpyg8xaMvefDytw2+HH5yzqS+FhsR28A==" + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", + "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==", + "license": "MIT" }, "node_modules/vscode-languageserver-types": { - "version": "3.17.3", - "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.3.tgz", - "integrity": "sha512-SYU4z1dL0PyIMd4Vj8YOqFvHu7Hz/enbWtpfnVbJHU4Nd1YNYx8u0ennumc6h48GQNeOLxmwySmnADouT/AuZA==" + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", + "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==", + "license": "MIT" }, "node_modules/vscode-uri": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-2.1.2.tgz", - "integrity": "sha512-8TEXQxlldWAuIODdukIb+TR5s+9Ds40eSJrw+1iDDA9IFORPjMELarNQE3myz5XIkWWpdprmJjm1/SxMlWOC8A==" + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz", + "integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==", + "license": "MIT" } } } diff --git a/extensions/go/cgmanifest.json b/extensions/go/cgmanifest.json index ae7228fa1aea4..5276d2824a718 100644 --- a/extensions/go/cgmanifest.json +++ b/extensions/go/cgmanifest.json @@ -6,12 +6,12 @@ "git": { "name": "go-syntax", "repositoryUrl": "https://github.com/worlpaker/go-syntax", - "commitHash": "ce0d1d19653dc264d8e53f97cb45cc8d1689f221" + "commitHash": "32bbaebcf218fa552e8f0397401e12f6e94fa3c5" } }, "license": "MIT", "description": "The file syntaxes/go.tmLanguage.json is from https://github.com/worlpaker/go-syntax, which in turn was derived from https://github.com/jeff-hykin/better-go-syntax.", - "version": "0.7.7" + "version": "0.7.8" } ], "version": 1 diff --git a/extensions/go/syntaxes/go.tmLanguage.json b/extensions/go/syntaxes/go.tmLanguage.json index 8ae31fdbe31c0..ed6ead03480da 100644 --- a/extensions/go/syntaxes/go.tmLanguage.json +++ b/extensions/go/syntaxes/go.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/worlpaker/go-syntax/commit/ce0d1d19653dc264d8e53f97cb45cc8d1689f221", + "version": "https://github.com/worlpaker/go-syntax/commit/32bbaebcf218fa552e8f0397401e12f6e94fa3c5", "name": "Go", "scopeName": "source.go", "patterns": [ @@ -1370,7 +1370,53 @@ } }, { - "include": "#parameter-variable-types" + "begin": "(?:([\\w\\.\\*]+)?(\\[))", + "beginCaptures": { + "1": { + "patterns": [ + { + "include": "#type-declarations" + }, + { + "match": "(?:\\w+)", + "name": "entity.name.type.go" + } + ] + }, + "2": { + "name": "punctuation.definition.begin.bracket.square.go" + } + }, + "end": "\\]", + "endCaptures": { + "0": { + "name": "punctuation.definition.end.bracket.square.go" + } + }, + "patterns": [ + { + "include": "#generic_param_types" + } + ] + }, + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.begin.bracket.round.go" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.end.bracket.round.go" + } + }, + "patterns": [ + { + "include": "#function_param_types" + } + ] }, { "comment": "other types", @@ -1483,7 +1529,53 @@ } }, { - "include": "#parameter-variable-types" + "begin": "(?:([\\w\\.\\*]+)?(\\[))", + "beginCaptures": { + "1": { + "patterns": [ + { + "include": "#type-declarations" + }, + { + "match": "(?:\\w+)", + "name": "entity.name.type.go" + } + ] + }, + "2": { + "name": "punctuation.definition.begin.bracket.square.go" + } + }, + "end": "\\]", + "endCaptures": { + "0": { + "name": "punctuation.definition.end.bracket.square.go" + } + }, + "patterns": [ + { + "include": "#generic_param_types" + } + ] + }, + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.begin.bracket.round.go" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.end.bracket.round.go" + } + }, + "patterns": [ + { + "include": "#function_param_types" + } + ] }, { "comment": "other types", diff --git a/extensions/html-language-features/cgmanifest.json b/extensions/html-language-features/cgmanifest.json index 7f57d61af72f0..8a0745255f7b6 100644 --- a/extensions/html-language-features/cgmanifest.json +++ b/extensions/html-language-features/cgmanifest.json @@ -107,7 +107,7 @@ "", "END OF TERMS AND CONDITIONS" ], - "license": "Apache2", + "license": "Apache-2.0", "version": "1.2.4" } ], diff --git a/extensions/javascript/javascript-language-configuration.json b/extensions/javascript/javascript-language-configuration.json index f7c332337cb8e..46ee043c52cf0 100644 --- a/extensions/javascript/javascript-language-configuration.json +++ b/extensions/javascript/javascript-language-configuration.json @@ -228,5 +228,18 @@ "appendText": "\t", } }, + // Add // when pressing enter from inside line comment + { + "beforeText": { + "pattern": "\/\/.*" + }, + "afterText": { + "pattern": "^(?!\\s*$).+" + }, + "action": { + "indent": "none", + "appendText": "// " + } + }, ] } diff --git a/extensions/julia/package.json b/extensions/julia/package.json index 31cdd4ce71a4c..f27b1ca822ae6 100644 --- a/extensions/julia/package.json +++ b/extensions/julia/package.json @@ -50,6 +50,11 @@ "meta.embedded.inline.sql": "sql" } } - ] + ], + "configurationDefaults": { + "[julia]": { + "editor.defaultColorDecorators": false + } + } } } diff --git a/extensions/markdown-language-features/package-lock.json b/extensions/markdown-language-features/package-lock.json index cabfda2e0fe29..91a1a6c5cb9f5 100644 --- a/extensions/markdown-language-features/package-lock.json +++ b/extensions/markdown-language-features/package-lock.json @@ -19,7 +19,7 @@ "punycode": "^2.3.1", "vscode-languageclient": "^8.0.2", "vscode-languageserver-textdocument": "^1.0.11", - "vscode-markdown-languageserver": "^0.5.0-alpha.8", + "vscode-markdown-languageserver": "^0.5.0-alpha.9", "vscode-uri": "^3.0.3" }, "devDependencies": { @@ -618,15 +618,15 @@ "integrity": "sha512-SYU4z1dL0PyIMd4Vj8YOqFvHu7Hz/enbWtpfnVbJHU4Nd1YNYx8u0ennumc6h48GQNeOLxmwySmnADouT/AuZA==" }, "node_modules/vscode-markdown-languageserver": { - "version": "0.5.0-alpha.8", - "resolved": "https://registry.npmjs.org/vscode-markdown-languageserver/-/vscode-markdown-languageserver-0.5.0-alpha.8.tgz", - "integrity": "sha512-Bp6YXHy4EMQ8JpsmXpQHa78byLvv83wVnLmhVfTaJsZTBwF8IOJ56NBUasepg9L6QVffUA9T2H/PfBqEOGpeOQ==", + "version": "0.5.0-alpha.9", + "resolved": "https://registry.npmjs.org/vscode-markdown-languageserver/-/vscode-markdown-languageserver-0.5.0-alpha.9.tgz", + "integrity": "sha512-60jiPHgkstgkyODCN8qCJ4xAOrP/EoKyISmEAcJ7ILT5k2kAJF9JFEl3LvVZ+11HGGMJ2lm1L+lT2/JHvu5Pgg==", "dependencies": { "@vscode/l10n": "^0.0.11", "vscode-languageserver": "^8.1.0", "vscode-languageserver-textdocument": "^1.0.8", "vscode-languageserver-types": "^3.17.3", - "vscode-markdown-languageservice": "^0.5.0-alpha.7", + "vscode-markdown-languageservice": "^0.5.0-alpha.8", "vscode-uri": "^3.0.7" }, "engines": { @@ -639,9 +639,9 @@ "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==" }, "node_modules/vscode-markdown-languageserver/node_modules/vscode-markdown-languageservice": { - "version": "0.5.0-alpha.7", - "resolved": "https://registry.npmjs.org/vscode-markdown-languageservice/-/vscode-markdown-languageservice-0.5.0-alpha.7.tgz", - "integrity": "sha512-Iq9S5YGHm3D/UG9Usm8a/O5tYCo9FwaMF7nJsDQCxKgVZu5OzwOj3ixDkhoM+c8GKXiwt23DxhhWRuvI4odkTg==", + "version": "0.5.0-alpha.8", + "resolved": "https://registry.npmjs.org/vscode-markdown-languageservice/-/vscode-markdown-languageservice-0.5.0-alpha.8.tgz", + "integrity": "sha512-b2NgVMZvzI/7hRL32Kcu9neAAPFQzkcf/Fqwlxbz9p1/Q7aIorGACOGGo00s72AJtwjkCJ29eVJwUlFMFbPKqA==", "dependencies": { "@vscode/l10n": "^0.0.10", "node-html-parser": "^6.1.5", diff --git a/extensions/markdown-language-features/package.json b/extensions/markdown-language-features/package.json index 0670265610861..3f1c3a5fd5814 100644 --- a/extensions/markdown-language-features/package.json +++ b/extensions/markdown-language-features/package.json @@ -773,7 +773,7 @@ "punycode": "^2.3.1", "vscode-languageclient": "^8.0.2", "vscode-languageserver-textdocument": "^1.0.11", - "vscode-markdown-languageserver": "^0.5.0-alpha.8", + "vscode-markdown-languageserver": "^0.5.0-alpha.9", "vscode-uri": "^3.0.3" }, "devDependencies": { diff --git a/extensions/markdown-language-features/src/client/fileWatchingManager.ts b/extensions/markdown-language-features/src/client/fileWatchingManager.ts index dabb8f1fc880b..e2010edda8a45 100644 --- a/extensions/markdown-language-features/src/client/fileWatchingManager.ts +++ b/extensions/markdown-language-features/src/client/fileWatchingManager.ts @@ -11,7 +11,7 @@ import { Schemes } from '../util/schemes'; type DirWatcherEntry = { readonly uri: vscode.Uri; - readonly listeners: IDisposable[]; + readonly disposables: readonly IDisposable[]; }; @@ -28,6 +28,11 @@ export class FileWatcherManager { }>(); create(id: number, uri: vscode.Uri, watchParentDirs: boolean, listeners: { create?: () => void; change?: () => void; delete?: () => void }): void { + // Non-writable file systems do not support file watching + if (!vscode.workspace.fs.isWritableFileSystem(uri.scheme)) { + return; + } + const watcher = vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(uri, '*'), !listeners.create, !listeners.change, !listeners.delete); const parentDirWatchers: DirWatcherEntry[] = []; this._fileWatchers.set(id, { watcher, dirWatchers: parentDirWatchers }); @@ -39,7 +44,7 @@ export class FileWatcherManager { if (watchParentDirs && uri.scheme !== Schemes.untitled) { // We need to watch the parent directories too for when these are deleted / created for (let dirUri = Utils.dirname(uri); dirUri.path.length > 1; dirUri = Utils.dirname(dirUri)) { - const dirWatcher: DirWatcherEntry = { uri: dirUri, listeners: [] }; + const disposables: IDisposable[] = []; let parentDirWatcher = this._dirWatchers.get(dirUri); if (!parentDirWatcher) { @@ -51,7 +56,7 @@ export class FileWatcherManager { parentDirWatcher.refCount++; if (listeners.create) { - dirWatcher.listeners.push(parentDirWatcher.watcher.onDidCreate(async () => { + disposables.push(parentDirWatcher.watcher.onDidCreate(async () => { // Just because the parent dir was created doesn't mean our file was created try { const stat = await vscode.workspace.fs.stat(uri); @@ -67,10 +72,10 @@ export class FileWatcherManager { if (listeners.delete) { // When the parent dir is deleted, consider our file deleted too // TODO: this fires if the file previously did not exist and then the parent is deleted - dirWatcher.listeners.push(parentDirWatcher.watcher.onDidDelete(listeners.delete)); + disposables.push(parentDirWatcher.watcher.onDidDelete(listeners.delete)); } - parentDirWatchers.push(dirWatcher); + parentDirWatchers.push({ uri: dirUri, disposables }); } } } @@ -79,7 +84,7 @@ export class FileWatcherManager { const entry = this._fileWatchers.get(id); if (entry) { for (const dirWatcher of entry.dirWatchers) { - disposeAll(dirWatcher.listeners); + disposeAll(dirWatcher.disposables); const dirWatcherEntry = this._dirWatchers.get(dirWatcher.uri); if (dirWatcherEntry) { diff --git a/extensions/microsoft-authentication/.vscodeignore b/extensions/microsoft-authentication/.vscodeignore index 98b90d34d825d..e7feddb5da862 100644 --- a/extensions/microsoft-authentication/.vscodeignore +++ b/extensions/microsoft-authentication/.vscodeignore @@ -12,3 +12,4 @@ vsc-extension-quickstart.md **/tslint.json **/*.map **/*.ts +packageMocks/ diff --git a/extensions/microsoft-authentication/extension.webpack.config.js b/extensions/microsoft-authentication/extension.webpack.config.js index 45600607fc5b4..a85bb7b9b77b7 100644 --- a/extensions/microsoft-authentication/extension.webpack.config.js +++ b/extensions/microsoft-authentication/extension.webpack.config.js @@ -8,10 +8,47 @@ 'use strict'; const withDefaults = require('../shared.webpack.config'); +const CopyWebpackPlugin = require('copy-webpack-plugin'); +const path = require('path'); +const { NormalModuleReplacementPlugin } = require('webpack'); + +const isWindows = process.platform === 'win32'; module.exports = withDefaults({ context: __dirname, entry: { extension: './src/extension.ts' - } + }, + externals: { + // The @azure/msal-node-runtime package requires this native node module (.node). + // It is currently only included on Windows, but the package handles unsupported platforms + // gracefully. + './msal-node-runtime': 'commonjs ./msal-node-runtime' + }, + resolve: { + alias: { + 'keytar': path.resolve(__dirname, 'packageMocks', 'keytar', 'index.js') + } + }, + plugins: [ + ...withDefaults.nodePlugins(__dirname), + new CopyWebpackPlugin({ + patterns: [ + { + // The native files we need to ship with the extension + from: '**/dist/msal*.(node|dll)', + to: '[name][ext]', + // These will only be present on Windows for now + noErrorOnMissing: !isWindows + } + ] + }), + // We don't use the feature that uses Dpapi, so we can just replace it with a mock. + // This is a bit of a hack, but it's the easiest way to do it. Really, msal should + // handle when this native node module is not available. + new NormalModuleReplacementPlugin( + /\.\.\/Dpapi\.mjs/, + path.resolve(__dirname, 'packageMocks', 'dpapi', 'dpapi.js') + ) + ] }); diff --git a/extensions/microsoft-authentication/package-lock.json b/extensions/microsoft-authentication/package-lock.json index 8f05b14f02aef..c52e019da9a96 100644 --- a/extensions/microsoft-authentication/package-lock.json +++ b/extensions/microsoft-authentication/package-lock.json @@ -11,7 +11,9 @@ "dependencies": { "@azure/ms-rest-azure-env": "^2.0.0", "@azure/msal-node": "^2.13.1", + "@azure/msal-node-extensions": "^1.3.0", "@vscode/extension-telemetry": "^0.9.0", + "keytar": "file:./packageMocks/keytar", "vscode-tas-client": "^0.1.84" }, "devDependencies": { @@ -51,6 +53,40 @@ "node": ">=16" } }, + "node_modules/@azure/msal-node-extensions": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@azure/msal-node-extensions/-/msal-node-extensions-1.3.0.tgz", + "integrity": "sha512-7rXN+9hDm3NncIfNnMyoFtsnz2AlUtmK5rsY3P+fhhbH+GOk0W5Y1BASvAB6RCcKdO+qSIK3ZA6VHQYy4iS/1w==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@azure/msal-common": "14.15.0", + "@azure/msal-node-runtime": "^0.17.1", + "keytar": "^7.8.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@azure/msal-node-extensions/node_modules/@azure/msal-common": { + "version": "14.15.0", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-14.15.0.tgz", + "integrity": "sha512-ImAQHxmpMneJ/4S8BRFhjt1MZ3bppmpRPYYNyzeQPeFN288YKbb8TmmISQEbtfkQ1BPASvYZU5doIZOPBAqENQ==", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@azure/msal-node-extensions/packageMocks/keytar": { + "extraneous": true + }, + "node_modules/@azure/msal-node-runtime": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/@azure/msal-node-runtime/-/msal-node-runtime-0.17.1.tgz", + "integrity": "sha512-qAfTg+iGJsg+XvD9nmknI63+XuoX32oT+SX4wJdFz7CS6ETVpSHoroHVaUmsTU1H7H0+q1/ZkP988gzPRMYRsg==", + "hasInstallScript": true, + "license": "MIT" + }, "node_modules/@microsoft/1ds-core-js": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/@microsoft/1ds-core-js/-/1ds-core-js-4.0.3.tgz", @@ -315,6 +351,10 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/keytar": { + "resolved": "packageMocks/keytar", + "link": true + }, "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", @@ -376,6 +416,9 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, + "node_modules/packageMocks/keytar": { + "extraneous": true + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -435,6 +478,9 @@ "engines": { "vscode": "^1.85.0" } + }, + "packageMocks/keytar": { + "version": "7.9.0" } } } diff --git a/extensions/microsoft-authentication/package.json b/extensions/microsoft-authentication/package.json index 15acb5db286d4..0f5b707f18a88 100644 --- a/extensions/microsoft-authentication/package.json +++ b/extensions/microsoft-authentication/package.json @@ -14,7 +14,8 @@ ], "activationEvents": [], "enabledApiProposals": [ - "idToken" + "idToken", + "nativeWindowHandle" ], "capabilities": { "virtualWorkspaces": true, @@ -101,7 +102,11 @@ "properties": { "microsoft.useMsal": { "type": "boolean", - "description": "%useMsal.description%" + "description": "%useMsal.description%", + "tags": [ + "onExP", + "preview" + ] } } } @@ -127,7 +132,9 @@ "dependencies": { "@azure/ms-rest-azure-env": "^2.0.0", "@azure/msal-node": "^2.13.1", + "@azure/msal-node-extensions": "^1.3.0", "@vscode/extension-telemetry": "^0.9.0", + "keytar": "file:./packageMocks/keytar", "vscode-tas-client": "^0.1.84" }, "repository": { diff --git a/extensions/microsoft-authentication/packageMocks/dpapi/dpapi.js b/extensions/microsoft-authentication/packageMocks/dpapi/dpapi.js new file mode 100644 index 0000000000000..636112a188ff9 --- /dev/null +++ b/extensions/microsoft-authentication/packageMocks/dpapi/dpapi.js @@ -0,0 +1,15 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +class defaultDpapi { + protectData() { + throw new Error('Dpapi bindings unavailable'); + } + unprotectData() { + throw new Error('Dpapi bindings unavailable'); + } +} +const Dpapi = new defaultDpapi(); +export { Dpapi }; diff --git a/src/vscode-dts/vscode.proposed.contribIssueReporter.d.ts b/extensions/microsoft-authentication/packageMocks/keytar/index.js similarity index 71% rename from src/vscode-dts/vscode.proposed.contribIssueReporter.d.ts rename to extensions/microsoft-authentication/packageMocks/keytar/index.js index 522d33344678a..418d592ffddb6 100644 --- a/src/vscode-dts/vscode.proposed.contribIssueReporter.d.ts +++ b/extensions/microsoft-authentication/packageMocks/keytar/index.js @@ -3,5 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -// empty placeholder declaration for the `issue reporter`-submenu contribution point -// https://github.com/microsoft/vscode/issues/196863 +exports.setPassword = () => Promise.resolve(); +exports.getPassword = () => Promise.resolve(); +exports.deletePassword = () => Promise.resolve(); diff --git a/extensions/microsoft-authentication/packageMocks/keytar/package.json b/extensions/microsoft-authentication/packageMocks/keytar/package.json new file mode 100644 index 0000000000000..0014152ac9352 --- /dev/null +++ b/extensions/microsoft-authentication/packageMocks/keytar/package.json @@ -0,0 +1,7 @@ +{ + "name": "keytar", + "version": "7.9.0", + "description": "OVERRIDE Keytar since we don't need the feature", + "homepage": "https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/node", + "main": "index.js" +} diff --git a/extensions/microsoft-authentication/src/common/accountAccess.ts b/extensions/microsoft-authentication/src/common/accountAccess.ts new file mode 100644 index 0000000000000..a8fdeefef987d --- /dev/null +++ b/extensions/microsoft-authentication/src/common/accountAccess.ts @@ -0,0 +1,106 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable, Event, EventEmitter, SecretStorage } from 'vscode'; +import { AccountInfo } from '@azure/msal-node'; + +interface IAccountAccess { + onDidAccountAccessChange: Event; + isAllowedAccess(account: AccountInfo): boolean; + setAllowedAccess(account: AccountInfo, allowed: boolean): void; +} + +export class ScopedAccountAccess implements IAccountAccess { + private readonly _onDidAccountAccessChangeEmitter = new EventEmitter(); + readonly onDidAccountAccessChange = this._onDidAccountAccessChangeEmitter.event; + + private readonly _accountAccessSecretStorage: AccountAccessSecretStorage; + + private value = new Array(); + + constructor( + private readonly _secretStorage: SecretStorage, + private readonly _cloudName: string, + private readonly _clientId: string, + private readonly _authority: string + ) { + this._accountAccessSecretStorage = new AccountAccessSecretStorage(this._secretStorage, this._cloudName, this._clientId, this._authority); + this._accountAccessSecretStorage.onDidChange(() => this.update()); + } + + initialize() { + return this.update(); + } + + isAllowedAccess(account: AccountInfo): boolean { + return this.value.includes(account.homeAccountId); + } + + async setAllowedAccess(account: AccountInfo, allowed: boolean): Promise { + if (allowed) { + if (this.value.includes(account.homeAccountId)) { + return; + } + await this._accountAccessSecretStorage.store([...this.value, account.homeAccountId]); + return; + } + await this._accountAccessSecretStorage.store(this.value.filter(id => id !== account.homeAccountId)); + } + + private async update() { + const current = new Set(this.value); + const value = await this._accountAccessSecretStorage.get(); + + this.value = value ?? []; + if (current.size !== this.value.length || !this.value.every(id => current.has(id))) { + this._onDidAccountAccessChangeEmitter.fire(); + } + } +} + +export class AccountAccessSecretStorage { + private _disposable: Disposable; + + private readonly _onDidChangeEmitter = new EventEmitter; + readonly onDidChange: Event = this._onDidChangeEmitter.event; + + private readonly _key = `accounts-${this._cloudName}-${this._clientId}-${this._authority}`; + + constructor( + private readonly _secretStorage: SecretStorage, + private readonly _cloudName: string, + private readonly _clientId: string, + private readonly _authority: string + ) { + this._disposable = Disposable.from( + this._onDidChangeEmitter, + this._secretStorage.onDidChange(e => { + if (e.key === this._key) { + this._onDidChangeEmitter.fire(); + } + }) + ); + } + + async get(): Promise { + const value = await this._secretStorage.get(this._key); + if (!value) { + return undefined; + } + return JSON.parse(value); + } + + store(value: string[]): Thenable { + return this._secretStorage.store(this._key, JSON.stringify(value)); + } + + delete(): Thenable { + return this._secretStorage.delete(this._key); + } + + dispose() { + this._disposable.dispose(); + } +} diff --git a/extensions/microsoft-authentication/src/node/authProvider.ts b/extensions/microsoft-authentication/src/node/authProvider.ts index 0c285e0cb2ba0..81c4008d3811d 100644 --- a/extensions/microsoft-authentication/src/node/authProvider.ts +++ b/extensions/microsoft-authentication/src/node/authProvider.ts @@ -156,6 +156,7 @@ export class MsalAuthProvider implements AuthenticationProvider { let result: AuthenticationResult | undefined; try { + const windowHandle = env.handle ? Buffer.from(env.handle, 'base64') : undefined; result = await cachedPca.acquireTokenInteractive({ openBrowser: async (url: string) => { await env.openExternal(Uri.parse(url)); }, scopes: scopeData.scopesToSend, @@ -167,7 +168,8 @@ export class MsalAuthProvider implements AuthenticationProvider { loginHint: options.account?.label, // If we aren't logging in to a specific account, then we can use the prompt to make sure they get // the option to choose a different account. - prompt: options.account?.label ? undefined : 'select_account' + prompt: options.account?.label ? undefined : 'select_account', + windowHandle }); } catch (e) { if (e instanceof CancellationError) { @@ -196,12 +198,14 @@ export class MsalAuthProvider implements AuthenticationProvider { // The user wants to try the loopback client or we got an error likely due to spinning up the server const loopbackClient = new UriHandlerLoopbackClient(this._uriHandler, redirectUri, this._logger); try { + const windowHandle = env.handle ? Buffer.from(env.handle) : undefined; result = await cachedPca.acquireTokenInteractive({ openBrowser: (url: string) => loopbackClient.openBrowser(url), scopes: scopeData.scopesToSend, loopbackClient, loginHint: options.account?.label, - prompt: options.account?.label ? undefined : 'select_account' + prompt: options.account?.label ? undefined : 'select_account', + windowHandle }); } catch (e) { this._telemetryReporter.sendLoginFailedEvent(); diff --git a/extensions/microsoft-authentication/src/node/cachedPublicClientApplication.ts b/extensions/microsoft-authentication/src/node/cachedPublicClientApplication.ts index c679610466a24..e23ffeb3b9cb3 100644 --- a/extensions/microsoft-authentication/src/node/cachedPublicClientApplication.ts +++ b/extensions/microsoft-authentication/src/node/cachedPublicClientApplication.ts @@ -4,11 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import { PublicClientApplication, AccountInfo, Configuration, SilentFlowRequest, AuthenticationResult, InteractiveRequest, LogLevel } from '@azure/msal-node'; +import { NativeBrokerPlugin } from '@azure/msal-node-extensions'; import { Disposable, Memento, SecretStorage, LogOutputChannel, window, ProgressLocation, l10n, EventEmitter } from 'vscode'; import { Delayer, raceCancellationAndTimeoutError } from '../common/async'; import { SecretStorageCachePlugin } from '../common/cachePlugin'; import { MsalLoggerOptions } from '../common/loggerOptions'; import { ICachedPublicClientApplication } from '../common/publicClientCache'; +import { ScopedAccountAccess } from '../common/accountAccess'; export class CachedPublicClientApplication implements ICachedPublicClientApplication { private _pca: PublicClientApplication; @@ -24,19 +26,24 @@ export class CachedPublicClientApplication implements ICachedPublicClientApplica // Include the prefix as a differentiator to other secrets `pca:${JSON.stringify({ clientId: this._clientId, authority: this._authority })}` ); + private readonly _accountAccess = new ScopedAccountAccess(this._secretStorage, this._cloudName, this._clientId, this._authority); private readonly _config: Configuration = { auth: { clientId: this._clientId, authority: this._authority }, system: { loggerOptions: { correlationId: `${this._clientId}] [${this._authority}`, loggerCallback: (level, message, containsPii) => this._loggerOptions.loggerCallback(level, message, containsPii), - logLevel: LogLevel.Trace + logLevel: LogLevel.Info } }, + broker: { + nativeBrokerPlugin: new NativeBrokerPlugin() + }, cache: { cachePlugin: this._secretStorageCachePlugin } }; + private readonly _isBrokerAvailable = this._config.broker?.nativeBrokerPlugin?.isBrokerAvailable ?? false; /** * We keep track of the last time an account was removed so we can recreate the PCA if we detect that an account was removed. @@ -59,6 +66,7 @@ export class CachedPublicClientApplication implements ICachedPublicClientApplica constructor( private readonly _clientId: string, private readonly _authority: string, + private readonly _cloudName: string, private readonly _globalMemento: Memento, private readonly _secretStorage: SecretStorage, private readonly _logger: LogOutputChannel @@ -76,8 +84,11 @@ export class CachedPublicClientApplication implements ICachedPublicClientApplica get clientId(): string { return this._clientId; } get authority(): string { return this._authority; } - initialize(): Promise { - return this._update(); + async initialize(): Promise { + if (this._isBrokerAvailable) { + await this._accountAccess.initialize(); + } + await this._update(); } dispose(): void { @@ -88,7 +99,7 @@ export class CachedPublicClientApplication implements ICachedPublicClientApplica this._logger.debug(`[acquireTokenSilent] [${this._clientId}] [${this._authority}] [${request.scopes.join(' ')}] [${request.account.username}] starting...`); const result = await this._sequencer.queue(() => this._pca.acquireTokenSilent(request)); this._logger.debug(`[acquireTokenSilent] [${this._clientId}] [${this._authority}] [${request.scopes.join(' ')}] [${request.account.username}] got result`); - if (result.account && !result.fromCache) { + if (result.account && !result.fromCache && this._verifyIfUsingBroker(result)) { this._logger.debug(`[acquireTokenSilent] [${this._clientId}] [${this._authority}] [${request.scopes.join(' ')}] [${request.account.username}] firing event due to change`); this._setupRefresh(result); this._onDidAccountsChangeEmitter.fire({ added: [], changed: [result.account], deleted: [] }); @@ -111,18 +122,48 @@ export class CachedPublicClientApplication implements ICachedPublicClientApplica ) ); this._setupRefresh(result); + if (this._isBrokerAvailable) { + await this._accountAccess.setAllowedAccess(result.account!, true); + } return result; } removeAccount(account: AccountInfo): Promise { this._globalMemento.update(`lastRemoval:${this._clientId}:${this._authority}`, new Date()); + if (this._isBrokerAvailable) { + return this._accountAccess.setAllowedAccess(account, false); + } return this._pca.getTokenCache().removeAccount(account); } private _registerOnSecretStorageChanged() { + if (this._isBrokerAvailable) { + return this._accountAccess.onDidAccountAccessChange(() => this._update()); + } return this._secretStorageCachePlugin.onDidChange(() => this._update()); } + private _lastSeen = new Map(); + private _verifyIfUsingBroker(result: AuthenticationResult): boolean { + // If we're not brokering, we don't need to verify the date + // the cache check will be sufficient + if (!result.fromNativeBroker) { + return true; + } + const key = result.account!.homeAccountId; + const lastSeen = this._lastSeen.get(key); + const lastTimeAuthed = result.account!.idTokenClaims!.iat!; + if (!lastSeen) { + this._lastSeen.set(key, lastTimeAuthed); + return true; + } + if (lastSeen === lastTimeAuthed) { + return false; + } + this._lastSeen.set(key, lastTimeAuthed); + return true; + } + private async _update() { const before = this._accounts; this._logger.debug(`[update] [${this._clientId}] [${this._authority}] CachedPublicClientApplication update before: ${before.length}`); @@ -134,7 +175,10 @@ export class CachedPublicClientApplication implements ICachedPublicClientApplica this._lastCreated = new Date(); } - const after = await this._pca.getAllAccounts(); + let after = await this._pca.getAllAccounts(); + if (this._isBrokerAvailable) { + after = after.filter(a => this._accountAccess.isAllowedAccess(a)); + } this._accounts = after; this._logger.debug(`[update] [${this._clientId}] [${this._authority}] CachedPublicClientApplication update after: ${after.length}`); @@ -167,8 +211,7 @@ export class CachedPublicClientApplication implements ICachedPublicClientApplica this._logger.debug(`[_setupRefresh] [${this._clientId}] [${this._authority}] [${scopes.join(' ')}] [${account.username}] timeToRefresh: ${timeToRefresh}`); this._refreshDelayer.trigger( key, - // This may need the redirectUri when we switch to the broker - () => this.acquireTokenSilent({ account, scopes, redirectUri: undefined, forceRefresh: true }), + () => this.acquireTokenSilent({ account, scopes, redirectUri: 'https://vscode.dev/redirect', forceRefresh: true }), timeToRefresh > 0 ? timeToRefresh : 0 ); } diff --git a/extensions/microsoft-authentication/src/node/publicClientCache.ts b/extensions/microsoft-authentication/src/node/publicClientCache.ts index fc6ce38e9757c..c6f2508e560be 100644 --- a/extensions/microsoft-authentication/src/node/publicClientCache.ts +++ b/extensions/microsoft-authentication/src/node/publicClientCache.ts @@ -28,9 +28,9 @@ export class CachedPublicClientApplicationManager implements ICachedPublicClient private readonly _globalMemento: Memento, private readonly _secretStorage: SecretStorage, private readonly _logger: LogOutputChannel, - cloudName: string + private readonly _cloudName: string ) { - this._pcasSecretStorage = new PublicClientApplicationsSecretStorage(_secretStorage, cloudName); + this._pcasSecretStorage = new PublicClientApplicationsSecretStorage(_secretStorage, _cloudName); this._disposable = Disposable.from( this._pcasSecretStorage, this._registerSecretStorageHandler(), @@ -111,7 +111,7 @@ export class CachedPublicClientApplicationManager implements ICachedPublicClient } private async _doCreatePublicClientApplication(clientId: string, authority: string, pcasKey: string) { - const pca = new CachedPublicClientApplication(clientId, authority, this._globalMemento, this._secretStorage, this._logger); + const pca = new CachedPublicClientApplication(clientId, authority, this._cloudName, this._globalMemento, this._secretStorage, this._logger); this._pcas.set(pcasKey, pca); const disposable = Disposable.from( pca, @@ -160,11 +160,7 @@ export class CachedPublicClientApplicationManager implements ICachedPublicClient // Handle the deleted ones for (const pcaKey of this._pcas.keys()) { if (!pcaKeysFromStorage.delete(pcaKey)) { - // This PCA has been removed in another window - this._pcaDisposables.get(pcaKey)?.dispose(); - this._pcaDisposables.delete(pcaKey); - this._pcas.delete(pcaKey); - this._logger.debug(`[_handleSecretStorageChange] Disposed PCA that was deleted in another window: ${pcaKey}`); + this._logger.debug(`[_handleSecretStorageChange] PCA was deleted in another window: ${pcaKey}`); } } diff --git a/extensions/microsoft-authentication/tsconfig.json b/extensions/microsoft-authentication/tsconfig.json index 4b9d06d1847ef..b40c2eb8716de 100644 --- a/extensions/microsoft-authentication/tsconfig.json +++ b/extensions/microsoft-authentication/tsconfig.json @@ -22,6 +22,7 @@ "include": [ "src/**/*", "../../src/vscode-dts/vscode.d.ts", - "../../src/vscode-dts/vscode.proposed.idToken.d.ts" + "../../src/vscode-dts/vscode.proposed.idToken.d.ts", + "../../src/vscode-dts/vscode.proposed.nativeWindowHandle.d.ts" ] } diff --git a/extensions/package-lock.json b/extensions/package-lock.json index aa07b93e52e89..54a698f1ef784 100644 --- a/extensions/package-lock.json +++ b/extensions/package-lock.json @@ -10,7 +10,7 @@ "hasInstallScript": true, "license": "MIT", "dependencies": { - "typescript": "5.6.3" + "typescript": "^5.7.1-rc" }, "devDependencies": { "@parcel/watcher": "2.1.0", @@ -606,9 +606,10 @@ } }, "node_modules/typescript": { - "version": "5.6.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", - "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "version": "5.7.1-rc", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.1-rc.tgz", + "integrity": "sha512-d6m+HT78uZtyUbXbUyIvuJ6kXCTSJEfy+2pZSUwt9d6JZ0kOMNDwhIILfV5FnaxMwVa48Yfw4sK0ISC4Qyq5tw==", + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/extensions/package.json b/extensions/package.json index a1a224947891b..90d556783aaac 100644 --- a/extensions/package.json +++ b/extensions/package.json @@ -4,7 +4,7 @@ "license": "MIT", "description": "Dependencies shared by all extensions", "dependencies": { - "typescript": "5.6.3" + "typescript": "^5.7.1-rc" }, "scripts": { "postinstall": "node ./postinstall.mjs" diff --git a/extensions/python/package.json b/extensions/python/package.json index 543268de71570..a1cae47db17e2 100644 --- a/extensions/python/package.json +++ b/extensions/python/package.json @@ -49,7 +49,8 @@ ], "configurationDefaults": { "[python]": { - "diffEditor.ignoreTrimWhitespace": false + "diffEditor.ignoreTrimWhitespace": false, + "editor.defaultColorDecorators": false } } }, diff --git a/extensions/ruby/package.json b/extensions/ruby/package.json index 70dd99f26b54a..8210a7825af59 100644 --- a/extensions/ruby/package.json +++ b/extensions/ruby/package.json @@ -66,7 +66,12 @@ "scopeName": "source.ruby", "path": "./syntaxes/ruby.tmLanguage.json" } - ] + ], + "configurationDefaults": { + "[ruby]": { + "editor.defaultColorDecorators": false + } + } }, "repository": { "type": "git", diff --git a/extensions/shellscript/package.json b/extensions/shellscript/package.json index 93333abd313da..22ee260005e8a 100644 --- a/extensions/shellscript/package.json +++ b/extensions/shellscript/package.json @@ -94,7 +94,8 @@ ], "configurationDefaults": { "[shellscript]": { - "files.eol": "\n" + "files.eol": "\n", + "editor.defaultColorDecorators": false } } }, diff --git a/extensions/typescript-basics/language-configuration.json b/extensions/typescript-basics/language-configuration.json index 25a2368573841..876c11e81437a 100644 --- a/extensions/typescript-basics/language-configuration.json +++ b/extensions/typescript-basics/language-configuration.json @@ -246,5 +246,18 @@ "appendText": "\t", } }, + // Add // when pressing enter from inside line comment + { + "beforeText": { + "pattern": "\/\/.*" + }, + "afterText": { + "pattern": "^(?!\\s*$).+" + }, + "action": { + "indent": "none", + "appendText": "// " + } + }, ] } diff --git a/extensions/typescript-language-features/cgmanifest.json b/extensions/typescript-language-features/cgmanifest.json index be9a7614995de..3a77b5cdfde20 100644 --- a/extensions/typescript-language-features/cgmanifest.json +++ b/extensions/typescript-language-features/cgmanifest.json @@ -131,7 +131,7 @@ "commitHash": "10778afe95b5d46c99f7a77565328b7108091510" } }, - "license": "Apache2" + "license": "Apache-2.0" } ], "version": 1 diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index 4815098a0f09e..c6363dfc48e16 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -1098,6 +1098,7 @@ "properties": { "caseSensitivity": { "type": "string", + "markdownDescription": "%typescript.preferences.organizeImports.caseSensitivity%", "enum": [ "auto", "caseInsensitive", @@ -1112,6 +1113,7 @@ }, "typeOrder": { "type": "string", + "markdownDescription": "%typescript.preferences.organizeImports.typeOrder%", "enum": [ "auto", "last", @@ -1128,6 +1130,7 @@ }, "unicodeCollation": { "type": "string", + "markdownDescription": "%typescript.preferences.organizeImports.unicodeCollation%", "enum": [ "ordinal", "unicode" @@ -1158,6 +1161,11 @@ "upper", "lower" ], + "markdownEnumDescriptions": [ + "%typescript.preferences.organizeImports.caseFirst.default%", + "%typescript.preferences.organizeImports.caseFirst.upper%", + "%typescript.preferences.organizeImports.caseFirst.lower%" + ], "default": "default" } } @@ -1168,6 +1176,7 @@ "properties": { "caseSensitivity": { "type": "string", + "markdownDescription": "%typescript.preferences.organizeImports.caseSensitivity%", "enum": [ "auto", "caseInsensitive", @@ -1182,6 +1191,7 @@ }, "typeOrder": { "type": "string", + "markdownDescription": "%typescript.preferences.organizeImports.typeOrder%", "enum": [ "auto", "last", @@ -1198,6 +1208,7 @@ }, "unicodeCollation": { "type": "string", + "markdownDescription": "%typescript.preferences.organizeImports.unicodeCollation%", "enum": [ "ordinal", "unicode" @@ -1228,6 +1239,11 @@ "upper", "lower" ], + "markdownEnumDescriptions": [ + "%typescript.preferences.organizeImports.caseFirst.default%", + "%typescript.preferences.organizeImports.caseFirst.upper%", + "%typescript.preferences.organizeImports.caseFirst.lower%" + ], "default": "default" } } @@ -1490,23 +1506,17 @@ "description": "%typescript.tsserver.enableRegionDiagnostics%", "scope": "window" }, - "javascript.experimental.updateImportsOnPaste": { + "javascript.updateImportsOnPaste.enabled": { "scope": "window", "type": "boolean", - "default": false, - "description": "%configuration.updateImportsOnPaste%", - "tags": [ - "experimental" - ] + "default": true, + "markdownDescription": "%configuration.updateImportsOnPaste%" }, - "typescript.experimental.updateImportsOnPaste": { + "typescript.updateImportsOnPaste.enabled": { "scope": "window", "type": "boolean", - "default": false, - "description": "%configuration.updateImportsOnPaste%", - "tags": [ - "experimental" - ] + "default": true, + "markdownDescription": "%configuration.updateImportsOnPaste%" }, "typescript.experimental.expandableHover": { "type": "boolean", @@ -1777,71 +1787,6 @@ } } } - ], - "codeActions": [ - { - "languages": [ - "javascript", - "javascriptreact", - "typescript", - "typescriptreact" - ], - "actions": [ - { - "kind": "refactor.extract.constant", - "title": "%codeActions.refactor.extract.constant.title%", - "description": "%codeActions.refactor.extract.constant.description%" - }, - { - "kind": "refactor.extract.function", - "title": "%codeActions.refactor.extract.function.title%", - "description": "%codeActions.refactor.extract.function.description%" - }, - { - "kind": "refactor.extract.interface", - "title": "%codeActions.refactor.extract.interface.title%", - "description": "%codeActions.refactor.extract.interface.description%" - }, - { - "kind": "refactor.extract.type", - "title": "%codeActions.refactor.extract.type.title%", - "description": "%codeActions.refactor.extract.type.description%" - }, - { - "kind": "refactor.rewrite.import", - "title": "%codeActions.refactor.rewrite.import.title%", - "description": "%codeActions.refactor.rewrite.import.description%" - }, - { - "kind": "refactor.rewrite.export", - "title": "%codeActions.refactor.rewrite.export.title%", - "description": "%codeActions.refactor.rewrite.export.description%" - }, - { - "kind": "refactor.rewrite.arrow.braces", - "title": "%codeActions.refactor.rewrite.arrow.braces.title%", - "description": "%codeActions.refactor.rewrite.arrow.braces.description%" - }, - { - "kind": "refactor.rewrite.parameters.toDestructured", - "title": "%codeActions.refactor.rewrite.parameters.toDestructured.title%" - }, - { - "kind": "refactor.rewrite.property.generateAccessors", - "title": "%codeActions.refactor.rewrite.property.generateAccessors.title%", - "description": "%codeActions.refactor.rewrite.property.generateAccessors.description%" - }, - { - "kind": "refactor.move.newFile", - "title": "%codeActions.refactor.move.newFile.title%", - "description": "%codeActions.refactor.move.newFile.description%" - }, - { - "kind": "source.organizeImports", - "title": "%codeActions.source.organizeImports.title%" - } - ] - } ] }, "repository": { diff --git a/extensions/typescript-language-features/package.nls.json b/extensions/typescript-language-features/package.nls.json index 4027c8187d28e..1e5ca174b55ab 100644 --- a/extensions/typescript-language-features/package.nls.json +++ b/extensions/typescript-language-features/package.nls.json @@ -74,7 +74,7 @@ "configuration.tsserver.maxTsServerMemory": "The maximum amount of memory (in MB) to allocate to the TypeScript server process. To use a memory limit greater than 4 GB, use `#typescript.tsserver.nodePath#` to run TS Server with a custom Node installation.", "configuration.tsserver.experimental.enableProjectDiagnostics": "Enables project wide error reporting.", "typescript.locale": "Sets the locale used to report JavaScript and TypeScript errors. Defaults to use VS Code's locale.", - "typescript.locale.auto": "Use VS Code's configured display language", + "typescript.locale.auto": "Use VS Code's configured display language.", "configuration.implicitProjectConfig.module": "Sets the module system for the program. See more: https://www.typescriptlang.org/tsconfig#module.", "configuration.implicitProjectConfig.target": "Set target JavaScript language version for emitted JavaScript and include library declarations. See more: https://www.typescriptlang.org/tsconfig#target.", "configuration.implicitProjectConfig.checkJs": "Enable/disable semantic checking of JavaScript files. Existing `jsconfig.json` or `tsconfig.json` files override this setting.", @@ -85,7 +85,7 @@ "configuration.implicitProjectConfig.strictFunctionTypes": "Enable/disable [strict function types](https://www.typescriptlang.org/tsconfig#strictFunctionTypes) in JavaScript and TypeScript files that are not part of a project. Existing `jsconfig.json` or `tsconfig.json` files override this setting.", "configuration.suggest.jsdoc.generateReturns": "Enable/disable generating `@returns` annotations for JSDoc templates.", "configuration.suggest.autoImports": "Enable/disable auto import suggestions.", - "configuration.preferGoToSourceDefinition": "Makes Go to Definition avoid type declaration files when possible by triggering Go to Source Definition instead. This allows Go to Source Definition to be triggered with the mouse gesture.", + "configuration.preferGoToSourceDefinition": "Makes `Go to Definition` avoid type declaration files when possible by triggering `Go to Source Definition` instead. This allows `Go to Source Definition` to be triggered with the mouse gesture.", "inlayHints.parameterNames.none": "Disable parameter name hints.", "inlayHints.parameterNames.literals": "Enable parameter name hints only for literal arguments.", "inlayHints.parameterNames.all": "Enable parameter name hints for literal and non-literal arguments.", @@ -146,8 +146,8 @@ "typescript.preferences.importModuleSpecifierEnding.index": "Shorten `./component/index.js` to `./component/index`.", "typescript.preferences.importModuleSpecifierEnding.js": "Do not shorten path endings; include the `.js` or `.ts` extension.", "typescript.preferences.jsxAttributeCompletionStyle": "Preferred style for JSX attribute completions.", - "javascript.preferences.jsxAttributeCompletionStyle.auto": "Insert `={}` or `=\"\"` after attribute names based on the prop type. See `javascript.preferences.quoteStyle` to control the type of quotes used for string attributes.", - "typescript.preferences.jsxAttributeCompletionStyle.auto": "Insert `={}` or `=\"\"` after attribute names based on the prop type. See `typescript.preferences.quoteStyle` to control the type of quotes used for string attributes.", + "javascript.preferences.jsxAttributeCompletionStyle.auto": "Insert `={}` or `=\"\"` after attribute names based on the prop type. See `#javascript.preferences.quoteStyle#` to control the type of quotes used for string attributes.", + "typescript.preferences.jsxAttributeCompletionStyle.auto": "Insert `={}` or `=\"\"` after attribute names based on the prop type. See `#typescript.preferences.quoteStyle#` to control the type of quotes used for string attributes.", "typescript.preferences.jsxAttributeCompletionStyle.braces": "Insert `={}` after attribute names.", "typescript.preferences.jsxAttributeCompletionStyle.none": "Only insert attribute names.", "typescript.preferences.includePackageJsonAutoImports": "Enable/disable searching `package.json` dependencies for available auto imports.", @@ -157,7 +157,7 @@ "typescript.preferences.autoImportFileExcludePatterns": "Specify glob patterns of files to exclude from auto imports. Relative paths are resolved relative to the workspace root. Patterns are evaluated using tsconfig.json [`exclude`](https://www.typescriptlang.org/tsconfig#exclude) semantics.", "typescript.preferences.autoImportSpecifierExcludeRegexes": "Specify regular expressions to exclude auto imports with matching import specifiers. Examples:\n\n- `^node:`\n- `lib/internal` (slashes don't need to be escaped...)\n- `/lib\\/internal/i` (...unless including surrounding slashes for `i` or `u` flags)\n- `^lodash$` (only allow subpath imports from lodash)", "typescript.preferences.preferTypeOnlyAutoImports": "Include the `type` keyword in auto-imports whenever possible. Requires using TypeScript 5.3+ in the workspace.", - "typescript.workspaceSymbols.excludeLibrarySymbols": "Exclude symbols that come from library files in Go to Symbol in Workspace results. Requires using TypeScript 5.3+ in the workspace.", + "typescript.workspaceSymbols.excludeLibrarySymbols": "Exclude symbols that come from library files in `Go to Symbol in Workspace` results. Requires using TypeScript 5.3+ in the workspace.", "typescript.updateImportsOnFileMove.enabled": "Enable/disable automatic updating of import paths when you rename or move a file in VS Code.", "typescript.updateImportsOnFileMove.enabled.prompt": "Prompt on each rename.", "typescript.updateImportsOnFileMove.enabled.always": "Always update paths automatically.", @@ -192,42 +192,28 @@ "typescript.preferences.renameMatchingJsxTags": "When on a JSX tag, try to rename the matching tag instead of renaming the symbol. Requires using TypeScript 5.1+ in the workspace.", "typescript.preferences.organizeImports": "Advanced preferences that control how imports are ordered.", "javascript.preferences.organizeImports": "Advanced preferences that control how imports are ordered.", + "typescript.preferences.organizeImports.caseSensitivity": "Specifies how imports should be sorted with regards to case-sensitivity. If `auto` or unspecified, we will detect the case-sensitivity per file", "typescript.preferences.organizeImports.caseSensitivity.auto": "Detect case-sensitivity for import sorting.", "typescript.preferences.organizeImports.caseSensitivity.insensitive": "Sort imports case-insensitively.", "typescript.preferences.organizeImports.caseSensitivity.sensitive": "Sort imports case-sensitively.", + "typescript.preferences.organizeImports.typeOrder": "Specify how type-only named imports should be sorted.", "typescript.preferences.organizeImports.typeOrder.auto": "Detect where type-only named imports should be sorted.", - "typescript.preferences.organizeImports.typeOrder.last": "Type only named imports are sorted to the end of the import list.", - "typescript.preferences.organizeImports.typeOrder.inline": "Named imports are sorted by name only.", - "typescript.preferences.organizeImports.typeOrder.first": "Type only named imports are sorted to the end of the import list.", + "typescript.preferences.organizeImports.typeOrder.last": "Type only named imports are sorted to the end of the import list. E.g. `import { B, Z, type A, type Y } from 'module';`", + "typescript.preferences.organizeImports.typeOrder.inline": "Named imports are sorted by name only. E.g. `import { type A, B, type Y, Z } from 'module';`", + "typescript.preferences.organizeImports.typeOrder.first": "Type only named imports are sorted to the beginning of the import list. E.g. `import { type A, type Y, B, Z } from 'module';`", + "typescript.preferences.organizeImports.unicodeCollation": "Specify whether to sort imports using Unicode or Ordinal collation.", "typescript.preferences.organizeImports.unicodeCollation.ordinal": "Sort imports using the numeric value of each code point.", "typescript.preferences.organizeImports.unicodeCollation.unicode": "Sort imports using the Unicode code collation.", - "typescript.preferences.organizeImports.locale": "Overrides the locale used for collation. Specify `auto` to use the UI locale. Only applies to `organizeImportsCollation: 'unicode'`.", - "typescript.preferences.organizeImports.caseFirst": "Indicates whether upper-case comes before lower-case. Only applies to `organizeImportsCollation: 'unicode'`.", - "typescript.preferences.organizeImports.numericCollation": "Sort numeric strings by integer value.", - "typescript.preferences.organizeImports.accentCollation": "Compare characters with diacritical marks as unequal to base character.", + "typescript.preferences.organizeImports.locale": "Requires `organizeImports.unicodeCollation: 'unicode'`. Overrides the locale used for collation. Specify `auto` to use the UI locale.", + "typescript.preferences.organizeImports.caseFirst": "Requires `organizeImports.unicodeCollation: 'unicode'`, and `organizeImports.caseSensitivity` is not `caseInsensitive`. Indicates whether upper-case will sort before lower-case.", + "typescript.preferences.organizeImports.caseFirst.default": "Default order given by `locale`.", + "typescript.preferences.organizeImports.caseFirst.lower": "Lower-case comes before upper-case. E.g.` a, A, z, Z`.", + "typescript.preferences.organizeImports.caseFirst.upper": "Upper-case comes before lower-case. E.g. ` A, a, B, b`.", + "typescript.preferences.organizeImports.numericCollation": "Requires `organizeImports.unicodeCollation: 'unicode'`. Sort numeric strings by integer value.", + "typescript.preferences.organizeImports.accentCollation": "Requires `organizeImports.unicodeCollation: 'unicode'`. Compare characters with diacritical marks as unequal to base character.", "typescript.workspaceSymbols.scope": "Controls which files are searched by [Go to Symbol in Workspace](https://code.visualstudio.com/docs/editor/editingevolved#_open-symbol-by-name).", "typescript.workspaceSymbols.scope.allOpenProjects": "Search all open JavaScript or TypeScript projects for symbols.", "typescript.workspaceSymbols.scope.currentProject": "Only search for symbols in the current JavaScript or TypeScript project.", - "codeActions.refactor.extract.constant.title": "Extract constant", - "codeActions.refactor.extract.constant.description": "Extract expression to constant.", - "codeActions.refactor.extract.function.title": "Extract function", - "codeActions.refactor.extract.function.description": "Extract expression to method or function.", - "codeActions.refactor.extract.type.title": "Extract type", - "codeActions.refactor.extract.type.description": "Extract type to a type alias.", - "codeActions.refactor.extract.interface.title": "Extract interface", - "codeActions.refactor.extract.interface.description": "Extract type to an interface.", - "codeActions.refactor.rewrite.import.title": "Convert import", - "codeActions.refactor.rewrite.import.description": "Convert between named imports and namespace imports.", - "codeActions.refactor.rewrite.export.title": "Convert export", - "codeActions.refactor.rewrite.export.description": "Convert between default export and named export.", - "codeActions.refactor.move.newFile.title": "Move to a new file", - "codeActions.refactor.move.newFile.description": "Move the expression to a new file.", - "codeActions.refactor.rewrite.arrow.braces.title": "Rewrite arrow braces", - "codeActions.refactor.rewrite.arrow.braces.description": "Add or remove braces in an arrow function.", - "codeActions.refactor.rewrite.parameters.toDestructured.title": "Convert parameters to destructured object", - "codeActions.refactor.rewrite.property.generateAccessors.title": "Generate accessors", - "codeActions.refactor.rewrite.property.generateAccessors.description": "Generate 'get' and 'set' accessors", - "codeActions.source.organizeImports.title": "Organize Imports", "typescript.sortImports": "Sort Imports", "typescript.removeUnusedImports": "Remove Unused Imports", "typescript.findAllFileReferences": "Find File References", @@ -238,7 +224,7 @@ "configuration.tsserver.web.projectWideIntellisense.suppressSemanticErrors": "Suppresses semantic errors on web even when project wide IntelliSense is enabled. This is always on when project wide IntelliSense is not enabled or available. See `#typescript.tsserver.web.projectWideIntellisense.enabled#`", "configuration.tsserver.web.typeAcquisition.enabled": "Enable/disable package acquisition on the web. This enables IntelliSense for imported packages. Requires `#typescript.tsserver.web.projectWideIntellisense.enabled#`. Currently not supported for Safari.", "configuration.tsserver.nodePath": "Run TS Server on a custom Node installation. This can be a path to a Node executable, or 'node' if you want VS Code to detect a Node installation.", - "configuration.updateImportsOnPaste": "Automatically update imports when pasting code. Requires TypeScript 5.7+.", + "configuration.updateImportsOnPaste": "Enable updating imports when pasting code. Requires TypeScript 5.7+.\n\nBy default this shows a option to update imports after pasting. You can use the `#editor.pasteAs.preferences#` setting to update imports automatically when pasting: `\"editor.pasteAs.preferences\": [ \"text.updateImports.jsts\" ]`.", "configuration.expandableHover": "Enable/disable expanding on hover.", "walkthroughs.nodejsWelcome.title": "Get started with JavaScript and Node.js", "walkthroughs.nodejsWelcome.description": "Make the most of Visual Studio Code's first-class JavaScript experience.", diff --git a/extensions/typescript-language-features/src/commands/tsserverRequests.ts b/extensions/typescript-language-features/src/commands/tsserverRequests.ts index ba545dfbea9fe..50aeedfc8f853 100644 --- a/extensions/typescript-language-features/src/commands/tsserverRequests.ts +++ b/extensions/typescript-language-features/src/commands/tsserverRequests.ts @@ -16,7 +16,7 @@ export class TSServerRequestCommand implements Command { private readonly lazyClientHost: Lazy ) { } - public execute(requestID: keyof TypeScriptRequests, args?: any, config?: any) { + public async execute(command: keyof TypeScriptRequests, args?: any, config?: any): Promise { // A cancellation token cannot be passed through the command infrastructure const token = nulToken; @@ -36,8 +36,10 @@ export class TSServerRequestCommand implements Command { 'completionInfo' ]; - if (!allowList.includes(requestID)) { return; } - return this.lazyClientHost.value.serviceClient.execute(requestID, args, token, config); + if (allowList.includes(command) || command.startsWith('_')) { + return this.lazyClientHost.value.serviceClient.execute(command, args, token, config); + } + return undefined; } } diff --git a/extensions/typescript-language-features/src/languageFeatures/copilotRelated.ts b/extensions/typescript-language-features/src/languageFeatures/copilotRelated.ts deleted file mode 100644 index f1ceac1a584a7..0000000000000 --- a/extensions/typescript-language-features/src/languageFeatures/copilotRelated.ts +++ /dev/null @@ -1,86 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as vscode from 'vscode'; -import { isSupportedLanguageMode } from '../configuration/languageIds'; -import { DocumentSelector } from '../configuration/documentSelector'; -import { API } from '../tsServer/api'; -import type * as Proto from '../tsServer/protocol/protocol'; -import { ITypeScriptServiceClient } from '../typescriptService'; -import { conditionalRegistration, requireMinVersion } from './util/dependentRegistration'; - -const minVersion = API.v570; - -export function register( - selector: DocumentSelector, - client: ITypeScriptServiceClient, -): vscode.Disposable { - return conditionalRegistration([ - requireMinVersion(client, minVersion), - ], () => { - const ext = vscode.extensions.getExtension('github.copilot'); - if (!ext) { - return new vscode.Disposable(() => { }); - } - const disposers: vscode.Disposable[] = []; - ext.activate().then(() => { - const relatedAPI = ext.exports as { - registerRelatedFilesProvider( - providerId: { extensionId: string; languageId: string }, - callback: ( - uri: vscode.Uri, - context: { flags: Record }, - cancellationToken: vscode.CancellationToken - ) => Promise<{ - entries: vscode.Uri[]; - traits?: Array<{ name: string; value: string; includeInPrompt?: boolean; promptTextOverride?: string }>; - }> - ): vscode.Disposable; - } | undefined; - if (relatedAPI?.registerRelatedFilesProvider) { - for (const syntax of selector.syntax) { - if (!syntax.language) { - continue; - } - const id = { - extensionId: 'vscode.typescript-language-features', - languageId: syntax.language - }; - disposers.push(relatedAPI.registerRelatedFilesProvider(id, async (uri, _context, token) => { - let document; - try { - document = await vscode.workspace.openTextDocument(uri); - } catch { - if (!vscode.window.activeTextEditor) { - vscode.window.showErrorMessage(vscode.l10n.t("Related files provider failed. No active text editor.")); - return { entries: [] }; - } - // something is REALLY wrong if you can't open the active text editor's document, so don't catch that - document = await vscode.workspace.openTextDocument(vscode.window.activeTextEditor.document.uri); - } - - if (!isSupportedLanguageMode(document)) { - vscode.window.showErrorMessage(vscode.l10n.t("Related files provider failed. Copilot requested file with unsupported language mode.")); - return { entries: [] }; - } - - const file = client.toOpenTsFilePath(document); - if (!file) { - return { entries: [] }; - } - // @ts-expect-error until ts5.7 - const response = await client.execute('copilotRelated', { file, }, token) as Proto.CopilotRelatedResponse; - if (response.type !== 'response' || !response.body) { - return { entries: [] }; - } - // @ts-expect-error until ts5.7 - return { entries: response.body.relatedFiles.map(f => client.toResource(f)), traits: [] }; - })); - } - } - }); - return vscode.Disposable.from(...disposers); - }); -} diff --git a/extensions/typescript-language-features/src/languageFeatures/copyPaste.ts b/extensions/typescript-language-features/src/languageFeatures/copyPaste.ts index ca3f7708397d9..de6d9569976ed 100644 --- a/extensions/typescript-language-features/src/languageFeatures/copyPaste.ts +++ b/extensions/typescript-language-features/src/languageFeatures/copyPaste.ts @@ -38,11 +38,11 @@ class CopyMetadata { } } -const settingId = 'experimental.updateImportsOnPaste'; +const enabledSettingId = 'updateImportsOnPaste.enabled'; class DocumentPasteProvider implements vscode.DocumentPasteEditProvider { - static readonly kind = vscode.DocumentDropOrPasteEditKind.Empty.append('text', 'jsts', 'pasteWithImports'); + static readonly kind = vscode.DocumentDropOrPasteEditKind.Empty.append('text', 'updateImports', 'jsts'); static readonly metadataMimeType = 'application/vnd.code.jsts.metadata'; constructor( @@ -127,6 +127,8 @@ class DocumentPasteProvider implements vscode.DocumentPasteEditProvider { } const edit = new vscode.DocumentPasteEdit('', vscode.l10n.t("Paste with imports"), DocumentPasteProvider.kind); + edit.yieldTo = [vscode.DocumentDropOrPasteEditKind.Empty.append('text', 'plain')]; + const additionalEdit = new vscode.WorkspaceEdit(); for (const edit of response.body.edits) { additionalEdit.set(this._client.toResource(edit.fileName), edit.textChanges.map(typeConverters.TextEdit.fromCodeEdit)); @@ -146,7 +148,7 @@ class DocumentPasteProvider implements vscode.DocumentPasteEditProvider { private isEnabled(document: vscode.TextDocument) { const config = vscode.workspace.getConfiguration(this._modeId, document.uri); - return config.get(settingId, false); + return config.get(enabledSettingId, true); } } @@ -154,7 +156,7 @@ export function register(selector: DocumentSelector, language: LanguageDescripti return conditionalRegistration([ requireSomeCapability(client, ClientCapability.Semantic), requireMinVersion(client, API.v570), - requireGlobalConfiguration(language.id, settingId), + requireGlobalConfiguration(language.id, enabledSettingId), ], () => { return vscode.languages.registerDocumentPasteEditProvider(selector.semantic, new DocumentPasteProvider(language.id, client), { providedPasteEditKinds: [DocumentPasteProvider.kind], diff --git a/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts b/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts index 5d92a3a6163e1..9b0c10a48b7f4 100644 --- a/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts +++ b/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts @@ -238,15 +238,23 @@ export default class FileConfigurationManager extends Disposable { } private getOrganizeImportsPreferences(config: vscode.WorkspaceConfiguration): Proto.UserPreferences { + const organizeImportsCollation = config.get<'ordinal' | 'unicode'>('organizeImports.unicodeCollation'); + const organizeImportsCaseSensitivity = config.get<'auto' | 'caseInsensitive' | 'caseSensitive'>('organizeImports.caseSensitivity'); return { // More specific settings - organizeImportsAccentCollation: config.get('organizeImports.accentCollation'), - organizeImportsCaseFirst: withDefaultAsUndefined(config.get<'default' | 'upper' | 'lower'>('organizeImports.caseFirst', 'default'), 'default'), - organizeImportsCollation: config.get<'ordinal' | 'unicode'>('organizeImports.collation'), - organizeImportsIgnoreCase: withDefaultAsUndefined(config.get<'auto' | 'caseInsensitive' | 'caseSensitive'>('organizeImports.caseSensitivity'), 'auto'), - organizeImportsLocale: config.get('organizeImports.locale'), - organizeImportsNumericCollation: config.get('organizeImports.numericCollation'), organizeImportsTypeOrder: withDefaultAsUndefined(config.get<'auto' | 'last' | 'inline' | 'first'>('organizeImports.typeOrder', 'auto'), 'auto'), + organizeImportsIgnoreCase: organizeImportsCaseSensitivity === 'caseInsensitive' ? true + : organizeImportsCaseSensitivity === 'caseSensitive' ? false + : 'auto', + organizeImportsCollation, + + // The rest of the settings are only applicable when using unicode collation + ...(organizeImportsCollation === 'unicode' ? { + organizeImportsCaseFirst: organizeImportsCaseSensitivity === 'caseInsensitive' ? undefined : withDefaultAsUndefined(config.get<'default' | 'upper' | 'lower' | false>('organizeImports.caseFirst', false), 'default'), + organizeImportsAccentCollation: config.get('organizeImports.accentCollation'), + organizeImportsLocale: config.get('organizeImports.locale'), + organizeImportsNumericCollation: config.get('organizeImports.numericCollation'), + } : {}), }; } } diff --git a/extensions/typescript-language-features/src/languageProvider.ts b/extensions/typescript-language-features/src/languageProvider.ts index 09a4fe3ccc9c6..7b95591604bd0 100644 --- a/extensions/typescript-language-features/src/languageProvider.ts +++ b/extensions/typescript-language-features/src/languageProvider.ts @@ -91,7 +91,6 @@ export default class LanguageProvider extends Disposable { import('./languageFeatures/sourceDefinition').then(provider => this._register(provider.register(this.client, this.commandManager))), import('./languageFeatures/tagClosing').then(provider => this._register(provider.register(selector, this.description, this.client))), import('./languageFeatures/typeDefinitions').then(provider => this._register(provider.register(selector, this.client))), - import('./languageFeatures/copilotRelated').then(provider => this._register(provider.register(selector, this.client))), ]); } diff --git a/extensions/typescript-language-features/src/tsServer/fileWatchingManager.ts b/extensions/typescript-language-features/src/tsServer/fileWatchingManager.ts index 27b9ec3d7f6c6..847584a300cb8 100644 --- a/extensions/typescript-language-features/src/tsServer/fileWatchingManager.ts +++ b/extensions/typescript-language-features/src/tsServer/fileWatchingManager.ts @@ -12,7 +12,7 @@ import { ResourceMap } from '../utils/resourceMap'; interface DirWatcherEntry { readonly uri: vscode.Uri; - readonly listeners: IDisposable[]; + readonly disposables: readonly IDisposable[]; } @@ -49,6 +49,11 @@ export class FileWatcherManager implements IDisposable { create(id: number, uri: vscode.Uri, watchParentDirs: boolean, isRecursive: boolean, listeners: { create?: (uri: vscode.Uri) => void; change?: (uri: vscode.Uri) => void; delete?: (uri: vscode.Uri) => void }): void { this.logger.trace(`Creating file watcher for ${uri.toString()}`); + // Non-writable file systems do not support file watching + if (!vscode.workspace.fs.isWritableFileSystem(uri.scheme)) { + return; + } + const watcher = vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(uri, isRecursive ? '**' : '*'), !listeners.create, !listeners.change, !listeners.delete); const parentDirWatchers: DirWatcherEntry[] = []; this._fileWatchers.set(id, { uri, watcher, dirWatchers: parentDirWatchers }); @@ -60,7 +65,7 @@ export class FileWatcherManager implements IDisposable { if (watchParentDirs && uri.scheme !== Schemes.untitled) { // We need to watch the parent directories too for when these are deleted / created for (let dirUri = Utils.dirname(uri); dirUri.path.length > 1; dirUri = Utils.dirname(dirUri)) { - const dirWatcher: DirWatcherEntry = { uri: dirUri, listeners: [] }; + const disposables: IDisposable[] = []; let parentDirWatcher = this._dirWatchers.get(dirUri); if (!parentDirWatcher) { @@ -73,7 +78,7 @@ export class FileWatcherManager implements IDisposable { parentDirWatcher.refCount++; if (listeners.create) { - dirWatcher.listeners.push(parentDirWatcher.watcher.onDidCreate(async () => { + disposables.push(parentDirWatcher.watcher.onDidCreate(async () => { // Just because the parent dir was created doesn't mean our file was created try { const stat = await vscode.workspace.fs.stat(uri); @@ -89,10 +94,10 @@ export class FileWatcherManager implements IDisposable { if (listeners.delete) { // When the parent dir is deleted, consider our file deleted too // TODO: this fires if the file previously did not exist and then the parent is deleted - dirWatcher.listeners.push(parentDirWatcher.watcher.onDidDelete(listeners.delete)); + disposables.push(parentDirWatcher.watcher.onDidDelete(listeners.delete)); } - parentDirWatchers.push(dirWatcher); + parentDirWatchers.push({ uri: dirUri, disposables }); } } } @@ -104,7 +109,7 @@ export class FileWatcherManager implements IDisposable { this.logger.trace(`Deleting file watcher for ${entry.uri}`); for (const dirWatcher of entry.dirWatchers) { - disposeAll(dirWatcher.listeners); + disposeAll(dirWatcher.disposables); const dirWatcherEntry = this._dirWatchers.get(dirWatcher.uri); if (dirWatcherEntry) { diff --git a/extensions/typescript-language-features/src/tsServer/protocol/protocol.d.ts b/extensions/typescript-language-features/src/tsServer/protocol/protocol.d.ts index cd70b6b7d41c3..747e7c22e3724 100644 --- a/extensions/typescript-language-features/src/tsServer/protocol/protocol.d.ts +++ b/extensions/typescript-language-features/src/tsServer/protocol/protocol.d.ts @@ -19,18 +19,5 @@ declare module '../../../../node_modules/typescript/lib/typescript' { interface Response { readonly _serverType?: ServerType; } - - //#region PreparePasteEdits - interface PreparePasteEditsRequest extends FileRequest { - command: 'preparePasteEdits'; - arguments: PreparePasteEditsRequestArgs; - } - interface PreparePasteEditsRequestArgs extends FileRequestArgs { - copiedTextSpan: TextSpan[]; - } - interface PreparePasteEditsResponse extends Response { - body: boolean; - } - //#endregion } } diff --git a/extensions/typescript-language-features/src/typescriptService.ts b/extensions/typescript-language-features/src/typescriptService.ts index 33d89e1df8d5b..90528ee47dc8c 100644 --- a/extensions/typescript-language-features/src/typescriptService.ts +++ b/extensions/typescript-language-features/src/typescriptService.ts @@ -77,8 +77,6 @@ interface StandardTsServerRequests { 'getMoveToRefactoringFileSuggestions': [Proto.GetMoveToRefactoringFileSuggestionsRequestArgs, Proto.GetMoveToRefactoringFileSuggestions]; 'linkedEditingRange': [Proto.FileLocationRequestArgs, Proto.LinkedEditingRangeResponse]; 'mapCode': [Proto.MapCodeRequestArgs, Proto.MapCodeResponse]; - // @ts-expect-error until ts5.7 - 'copilotRelated': [Proto.FileRequestArgs, Proto.CopilotRelatedResponse]; 'getPasteEdits': [Proto.GetPasteEditsRequestArgs, Proto.GetPasteEditsResponse]; 'preparePasteEdits': [Proto.PreparePasteEditsRequestArgs, Proto.PreparePasteEditsResponse]; } diff --git a/extensions/typescript-language-features/src/typescriptServiceClient.ts b/extensions/typescript-language-features/src/typescriptServiceClient.ts index b5fdb185101e9..d8ed0b2b284cb 100644 --- a/extensions/typescript-language-features/src/typescriptServiceClient.ts +++ b/extensions/typescript-language-features/src/typescriptServiceClient.ts @@ -125,7 +125,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType private _isPromptingAfterCrash = false; private isRestarting: boolean = false; private hasServerFatallyCrashedTooManyTimes = false; - private readonly loadingIndicator = this._register(new ServerInitializingIndicator()); + private readonly loadingIndicator: ServerInitializingIndicator; public readonly telemetryReporter: TelemetryReporter; public readonly bufferSyncSupport: BufferSyncSupport; @@ -158,6 +158,8 @@ export default class TypeScriptServiceClient extends Disposable implements IType ) { super(); + this.loadingIndicator = this._register(new ServerInitializingIndicator(this)); + this.logger = services.logger; this.tracer = new Tracer(this.logger); @@ -1254,6 +1256,12 @@ class ServerInitializingIndicator extends Disposable { private _task?: { project: string; resolve: () => void }; + constructor( + private readonly client: ITypeScriptServiceClient, + ) { + super(); + } + public reset(): void { if (this._task) { this._task.resolve(); @@ -1269,15 +1277,28 @@ class ServerInitializingIndicator extends Disposable { // the incoming project loading task is. this.reset(); - const projectDisplayName = vscode.workspace.asRelativePath(projectName); + const projectDisplayName = this.getProjectDisplayName(projectName); + vscode.window.withProgress({ location: vscode.ProgressLocation.Window, - title: vscode.l10n.t("Initializing project '{0}'", projectDisplayName), + title: vscode.l10n.t("Initializing '{0}'", projectDisplayName), }, () => new Promise(resolve => { this._task = { project: projectName, resolve }; })); } + private getProjectDisplayName(projectName: string): string { + const projectUri = this.client.toResource(projectName); + const relPath = vscode.workspace.asRelativePath(projectUri); + + const maxDisplayLength = 60; + if (relPath.length > maxDisplayLength) { + return '...' + relPath.slice(-maxDisplayLength); + } + + return relPath; + } + public startedLoadingFile(fileName: string, task: Promise): void { if (!this._task) { vscode.window.withProgress({ diff --git a/extensions/typescript-language-features/src/utils/dispose.ts b/extensions/typescript-language-features/src/utils/dispose.ts index a3730ee4540a6..e7687bb6941ef 100644 --- a/extensions/typescript-language-features/src/utils/dispose.ts +++ b/extensions/typescript-language-features/src/utils/dispose.ts @@ -5,11 +5,23 @@ import * as vscode from 'vscode'; -export function disposeAll(disposables: vscode.Disposable[]) { + +export function disposeAll(disposables: Iterable) { + const errors: any[] = []; + for (const disposable of disposables) { - disposable.dispose(); + try { + disposable.dispose(); + } catch (e) { + errors.push(e); + } + } + + if (errors.length === 1) { + throw errors[0]; + } else if (errors.length > 1) { + throw new AggregateError(errors, 'Encountered errors while disposing of store'); } - disposables.length = 0; } export interface IDisposable { diff --git a/extensions/typescript-language-features/web/src/serverHost.ts b/extensions/typescript-language-features/web/src/serverHost.ts index dedec85991fbb..d746501682adf 100644 --- a/extensions/typescript-language-features/web/src/serverHost.ts +++ b/extensions/typescript-language-features/web/src/serverHost.ts @@ -344,8 +344,13 @@ function createServerHost( return path; } - const isNm = looksLikeNodeModules(path) && !path.startsWith('/vscode-global-typings/'); - // skip paths without .. or ./ or /. And things that look like node_modules + const isNm = looksLikeNodeModules(path) + && !path.startsWith('/vscode-global-typings/') + // Handle the case where a local folder has been opened in VS Code + // In these cases we do not want to use the mapped node_module + && !path.startsWith('/file/'); + + // skip paths without .. or ./ or / if (!isNm && !path.match(/\.\.|\/\.|\.\//)) { return path; } diff --git a/extensions/vscode-api-tests/package.json b/extensions/vscode-api-tests/package.json index f62982eca5022..5f7681bf9d61a 100644 --- a/extensions/vscode-api-tests/package.json +++ b/extensions/vscode-api-tests/package.json @@ -30,6 +30,7 @@ "interactive", "languageStatusText", "mappedEditsProvider", + "nativeWindowHandle", "notebookCellExecutionState", "notebookDeprecated", "notebookLiveShare", diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/lm.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/lm.test.ts index 178119a1197c7..8ab5c5949a15f 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/lm.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/lm.test.ts @@ -26,7 +26,7 @@ suite('lm', function () { test('lm request and stream', async function () { - let p: vscode.Progress | undefined; + let p: vscode.Progress | undefined; const defer = new DeferredPromise(); disposables.push(vscode.lm.registerChatModelProvider('test-lm', { @@ -69,7 +69,7 @@ suite('lm', function () { assert.strictEqual(responseText, ''); assert.strictEqual(streamDone, false); - p.report({ index: 0, part: 'Hello' }); + p.report({ index: 0, part: new vscode.LanguageModelTextPart('Hello') }); defer.complete(); await pp; diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/terminal.shellIntegration.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/terminal.shellIntegration.test.ts index ac6287c2f354a..521a41ea865ad 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/terminal.shellIntegration.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/terminal.shellIntegration.test.ts @@ -217,7 +217,7 @@ import { assertNoRpc } from '../utils'; const { execution, endEvent } = executeCommandAsync(shellIntegration, 'echo', ['hello']); const executionSync = await execution; const expectedCommandLine: TerminalShellExecutionCommandLine = { - value: 'echo "hello"', + value: 'echo hello', isTrusted: true, confidence: TerminalShellExecutionCommandLineConfidence.High }; diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts index 231625697053d..b871093df39b0 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts @@ -598,19 +598,19 @@ suite('vscode API - workspace', () => { }); test('`findFiles2`', () => { - return vscode.workspace.findFiles2('**/image.png').then((res) => { + return vscode.workspace.findFiles2(['**/image.png']).then((res) => { assert.strictEqual(res.length, 2); }); }); test('findFiles2 - null exclude', async () => { - await vscode.workspace.findFiles2('**/file.txt', { useDefaultExcludes: true, useDefaultSearchExcludes: false }).then((res) => { + await vscode.workspace.findFiles2(['**/file.txt'], { useExcludeSettings: vscode.ExcludeSettingOptions.FilesExclude }).then((res) => { // file.exclude folder is still searched, search.exclude folder is not assert.strictEqual(res.length, 1); assert.strictEqual(basename(vscode.workspace.asRelativePath(res[0])), 'file.txt'); }); - await vscode.workspace.findFiles2('**/file.txt', { useDefaultExcludes: false, useDefaultSearchExcludes: false }).then((res) => { + await vscode.workspace.findFiles2(['**/file.txt'], { useExcludeSettings: vscode.ExcludeSettingOptions.None }).then((res) => { // search.exclude and files.exclude folders are both searched assert.strictEqual(res.length, 2); assert.strictEqual(basename(vscode.workspace.asRelativePath(res[0])), 'file.txt'); @@ -618,7 +618,7 @@ suite('vscode API - workspace', () => { }); test('findFiles2, exclude', () => { - return vscode.workspace.findFiles2('**/image.png', { exclude: '**/sub/**' }).then((res) => { + return vscode.workspace.findFiles2(['**/image.png'], { exclude: ['**/sub/**'] }).then((res) => { res.forEach(r => console.log(r.toString())); assert.strictEqual(res.length, 1); }); @@ -630,7 +630,7 @@ suite('vscode API - workspace', () => { const token = source.token; // just to get an instance first source.cancel(); - return vscode.workspace.findFiles2('*.js', {}, token).then((res) => { + return vscode.workspace.findFiles2(['*.js'], {}, token).then((res) => { assert.deepStrictEqual(res, []); }); }); diff --git a/extensions/vscode-colorize-perf-tests/.gitignore b/extensions/vscode-colorize-perf-tests/.gitignore new file mode 100644 index 0000000000000..8e5962ee72741 --- /dev/null +++ b/extensions/vscode-colorize-perf-tests/.gitignore @@ -0,0 +1,2 @@ +out +node_modules \ No newline at end of file diff --git a/extensions/vscode-colorize-perf-tests/.npmrc b/extensions/vscode-colorize-perf-tests/.npmrc new file mode 100644 index 0000000000000..a9c57709666b2 --- /dev/null +++ b/extensions/vscode-colorize-perf-tests/.npmrc @@ -0,0 +1,2 @@ +legacy-peer-deps="true" +timeout=180000 diff --git a/extensions/vscode-colorize-perf-tests/.vscode/launch.json b/extensions/vscode-colorize-perf-tests/.vscode/launch.json new file mode 100644 index 0000000000000..73c3753c75c2c --- /dev/null +++ b/extensions/vscode-colorize-perf-tests/.vscode/launch.json @@ -0,0 +1,17 @@ +// A launch configuration that compiles the extension and then opens it inside a new window +{ + "version": "0.1.0", + "configurations": [ + { + "name": "Launch Tests", + "type": "extensionHost", + "request": "launch", + "runtimeExecutable": "${execPath}", + "args": ["${workspaceFolder}/../../", "${workspaceFolder}/test", "--extensionDevelopmentPath=${workspaceFolder}", "--extensionTestsPath=${workspaceFolder}/out" ], + "stopOnEntry": false, + "sourceMaps": true, + "outDir": "${workspaceFolder}/out", + "preLaunchTask": "npm" + } + ] +} \ No newline at end of file diff --git a/extensions/vscode-colorize-perf-tests/.vscode/tasks.json b/extensions/vscode-colorize-perf-tests/.vscode/tasks.json new file mode 100644 index 0000000000000..390a93a3a7fed --- /dev/null +++ b/extensions/vscode-colorize-perf-tests/.vscode/tasks.json @@ -0,0 +1,11 @@ +{ + "version": "2.0.0", + "command": "npm", + "type": "shell", + "presentation": { + "reveal": "silent" + }, + "args": ["run", "compile"], + "isBackground": true, + "problemMatcher": "$tsc-watch" +} diff --git a/extensions/vscode-colorize-perf-tests/media/icon.png b/extensions/vscode-colorize-perf-tests/media/icon.png new file mode 100644 index 0000000000000..f785ef7031624 Binary files /dev/null and b/extensions/vscode-colorize-perf-tests/media/icon.png differ diff --git a/extensions/vscode-colorize-perf-tests/package-lock.json b/extensions/vscode-colorize-perf-tests/package-lock.json new file mode 100644 index 0000000000000..b0516cc1e2809 --- /dev/null +++ b/extensions/vscode-colorize-perf-tests/package-lock.json @@ -0,0 +1,42 @@ +{ + "name": "vscode-colorize-perf-tests", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "vscode-colorize-perf-tests", + "version": "0.0.1", + "license": "MIT", + "dependencies": { + "jsonc-parser": "^3.2.0" + }, + "devDependencies": { + "@types/node": "20.x" + }, + "engines": { + "vscode": "*" + } + }, + "node_modules/@types/node": { + "version": "20.11.24", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.24.tgz", + "integrity": "sha512-Kza43ewS3xoLgCEpQrsT+xRo/EJej1y0kVYGiLFE1NEODXGzTfwiC6tXTLMQskn1X4/Rjlh0MQUvx9W+L9long==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "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==" + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + } + } +} diff --git a/extensions/vscode-colorize-perf-tests/package.json b/extensions/vscode-colorize-perf-tests/package.json new file mode 100644 index 0000000000000..2e72152e2847b --- /dev/null +++ b/extensions/vscode-colorize-perf-tests/package.json @@ -0,0 +1,31 @@ +{ + "name": "vscode-colorize-perf-tests", + "description": "Colorize performance tests for VS Code", + "version": "0.0.1", + "publisher": "vscode", + "license": "MIT", + "private": true, + "activationEvents": [ + "onLanguage:json" + ], + "main": "./out/colorizerTestMain", + "engines": { + "vscode": "*" + }, + "icon": "media/icon.png", + "scripts": { + "vscode:prepublish": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:vscode-colorize-perf-tests ./tsconfig.json", + "watch": "gulp watch-extension:vscode-colorize-perf-tests", + "compile": "gulp compile-extension:vscode-colorize-perf-tests" + }, + "dependencies": { + "jsonc-parser": "^3.2.0" + }, + "devDependencies": { + "@types/node": "20.x" + }, + "repository": { + "type": "git", + "url": "https://github.com/microsoft/vscode.git" + } +} diff --git a/extensions/vscode-colorize-perf-tests/src/colorizer.test.ts b/extensions/vscode-colorize-perf-tests/src/colorizer.test.ts new file mode 100644 index 0000000000000..7e1df20ca296f --- /dev/null +++ b/extensions/vscode-colorize-perf-tests/src/colorizer.test.ts @@ -0,0 +1,146 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as fs from 'fs'; +import 'mocha'; +import { basename, join, normalize } from 'path'; +import { commands, ConfigurationTarget, Uri, workspace } from 'vscode'; + +interface BestsAndWorsts { + bestParse?: number; + bestCapture?: number; + bestMetadata?: number; + bestCombined: number; + worstParse?: number; + worstCapture?: number; + worstMetadata?: number; + worstCombined: number; +} + +function findBestsAndWorsts(results: { parseTime?: number; captureTime?: number; metadataTime?: number; tokenizeTime?: number }[]): BestsAndWorsts { + let bestParse: number | undefined; + let bestCapture: number | undefined; + let bestMetadata: number | undefined; + let bestCombined: number | undefined; + let worstParse: number | undefined; + let worstCapture: number | undefined; + let worstMetadata: number | undefined; + let worstCombined: number | undefined; + + for (let i = 0; i < results.length; i++) { + const result = results[i]; + if (result.parseTime && result.captureTime && result.metadataTime) { + // Tree Sitter + const combined = result.parseTime + result.captureTime + result.metadataTime; + if (bestParse === undefined || result.parseTime < bestParse) { + bestParse = result.parseTime; + } + if (bestCapture === undefined || result.captureTime < bestCapture) { + bestCapture = result.captureTime; + } + if (bestMetadata === undefined || result.metadataTime < bestMetadata) { + bestMetadata = result.metadataTime; + } + if (bestCombined === undefined || combined < bestCombined) { + bestCombined = combined; + } + if (i !== 0) { + if (worstParse === undefined || result.parseTime > worstParse) { + worstParse = result.parseTime; + } + if (worstCapture === undefined || result.captureTime > worstCapture) { + worstCapture = result.captureTime; + } + if (worstMetadata === undefined || result.metadataTime > worstMetadata) { + worstMetadata = result.metadataTime; + } + if (worstCombined === undefined || combined > worstCombined) { + worstCombined = combined; + } + } + } else if (result.tokenizeTime) { + // TextMate + if (bestCombined === undefined || result.tokenizeTime < bestCombined) { + bestCombined = result.tokenizeTime; + } + if (i !== 0 && (worstCombined === undefined || result.tokenizeTime > worstCombined)) { + worstCombined = result.tokenizeTime; + } + } + } + return { + bestParse, + bestCapture, + bestMetadata, + bestCombined: bestCombined!, + worstParse, + worstCapture, + worstMetadata, + worstCombined: worstCombined!, + }; +} + +interface TreeSitterTimes { + parseTime: number; + captureTime: number; + metadataTime: number; +} + +interface TextMateTimes { + tokenizeTime: number; +} + +async function runCommand(command: string, file: Uri, times: number): Promise { + const results: TimesType[] = []; + for (let i = 0; i < times; i++) { + results.push(await commands.executeCommand(command, file)); + } + return results; +} + +async function doTest(file: Uri, times: number) { + const treeSitterResults = await runCommand('_workbench.colorizeTreeSitterTokens', file, times); + + const { bestParse, bestCapture, bestMetadata, bestCombined, worstParse, worstCapture, worstMetadata, worstCombined } = findBestsAndWorsts(treeSitterResults); + const textMateResults = await runCommand('_workbench.colorizeTextMateTokens', file, times); + const textMateBestWorst = findBestsAndWorsts(textMateResults); + + const toString = (time: number, charLength: number) => { + // truncate time to charLength characters + return time.toString().slice(0, charLength).padEnd(charLength, ' '); + }; + const numLength = 7; + const resultString = ` | First | Best | Worst | +| --------------------- | ------- | ------- | ------- | +| TreeSitter (parse) | ${toString(treeSitterResults[0].parseTime, numLength)} | ${toString(bestParse!, numLength)} | ${toString(worstParse!, numLength)} | +| TreeSitter (capture) | ${toString(treeSitterResults[0].captureTime, numLength)} | ${toString(bestCapture!, numLength)} | ${toString(worstCapture!, numLength)} | +| TreeSitter (metadata) | ${toString(treeSitterResults[0].metadataTime, numLength)} | ${toString(bestMetadata!, numLength)} | ${toString(worstMetadata!, numLength)} | +| TreeSitter (total) | ${toString(treeSitterResults[0].parseTime + treeSitterResults[0].captureTime + treeSitterResults[0].metadataTime, numLength)} | ${toString(bestCombined, numLength)} | ${toString(worstCombined, numLength)} | +| TextMate | ${toString(textMateResults[0].tokenizeTime, numLength)} | ${toString(textMateBestWorst.bestCombined, numLength)} | ${toString(textMateBestWorst.worstCombined, numLength)} | +`; + console.log(`File ${basename(file.fsPath)}:`); + console.log(resultString); +} + +suite('Tokenization Performance', () => { + const testPath = normalize(join(__dirname, '../test')); + const fixturesPath = join(testPath, 'colorize-fixtures'); + let originalSettingValue: any; + + suiteSetup(async function () { + originalSettingValue = workspace.getConfiguration('editor').get('experimental.preferTreeSitter'); + await workspace.getConfiguration('editor').update('experimental.preferTreeSitter', ["typescript"], ConfigurationTarget.Global); + }); + suiteTeardown(async function () { + await workspace.getConfiguration('editor').update('experimental.preferTreeSitter', originalSettingValue, ConfigurationTarget.Global); + }); + + for (const fixture of fs.readdirSync(fixturesPath)) { + test(`Full file colorize: ${fixture}`, async function () { + await commands.executeCommand('workbench.action.closeAllEditors'); + await doTest(Uri.file(join(fixturesPath, fixture)), 6); + }); + } +}); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookAccessibility.ts b/extensions/vscode-colorize-perf-tests/src/colorizerTestMain.ts similarity index 77% rename from src/vs/workbench/contrib/notebook/browser/notebookAccessibility.ts rename to extensions/vscode-colorize-perf-tests/src/colorizerTestMain.ts index 11a10ed5950c1..3508834f8c6ec 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookAccessibility.ts +++ b/extensions/vscode-colorize-perf-tests/src/colorizerTestMain.ts @@ -3,3 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as vscode from 'vscode'; + +export function activate(_context: vscode.ExtensionContext): any { + +} diff --git a/extensions/vscode-colorize-perf-tests/src/index.ts b/extensions/vscode-colorize-perf-tests/src/index.ts new file mode 100644 index 0000000000000..4376f31accd91 --- /dev/null +++ b/extensions/vscode-colorize-perf-tests/src/index.ts @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as path from 'path'; +import * as testRunner from '../../../test/integration/electron/testrunner'; + +const suite = 'Performance Colorize Tests'; + +const options: import('mocha').MochaOptions = { + ui: 'tdd', + color: true, + timeout: 60000 +}; + +if (process.env.BUILD_ARTIFACTSTAGINGDIRECTORY) { + options.reporter = 'mocha-multi-reporters'; + options.reporterOptions = { + reporterEnabled: 'spec, mocha-junit-reporter', + mochaJunitReporterReporterOptions: { + testsuitesTitle: `${suite} ${process.platform}`, + mochaFile: path.join(process.env.BUILD_ARTIFACTSTAGINGDIRECTORY, `test-results/${process.platform}-${process.arch}-${suite.toLowerCase().replace(/[^\w]/g, '-')}-results.xml`) + } + }; +} + +testRunner.configure(options); + +export = testRunner; diff --git a/extensions/vscode-colorize-perf-tests/test/colorize-fixtures/test-checker.ts b/extensions/vscode-colorize-perf-tests/test/colorize-fixtures/test-checker.ts new file mode 100644 index 0000000000000..ace6bd2a57b91 --- /dev/null +++ b/extensions/vscode-colorize-perf-tests/test/colorize-fixtures/test-checker.ts @@ -0,0 +1,146620 @@ +// Copied from https://github.com/microsoft/TypeScript for running tests against a large file + +import { + __String, + AccessExpression, + AccessFlags, + AccessorDeclaration, + addRange, + addRelatedInfo, + addSyntheticLeadingComment, + AliasDeclarationNode, + AllAccessorDeclarations, + AmbientModuleDeclaration, + and, + AnonymousType, + AnyImportOrJsDocImport, + AnyImportOrReExport, + append, + appendIfUnique, + ArrayBindingPattern, + arrayFrom, + arrayIsHomogeneous, + ArrayLiteralExpression, + arrayOf, + arraysEqual, + arrayToMultiMap, + ArrayTypeNode, + ArrowFunction, + AsExpression, + AssertionExpression, + AssignmentDeclarationKind, + AssignmentKind, + AssignmentPattern, + AwaitExpression, + BaseType, + BigIntLiteral, + BigIntLiteralType, + BinaryExpression, + BinaryOperator, + BinaryOperatorToken, + binarySearch, + BindableObjectDefinePropertyCall, + BindableStaticNameExpression, + BindingElement, + BindingElementGrandparent, + BindingName, + BindingPattern, + bindSourceFile, + Block, + BooleanLiteral, + BreakOrContinueStatement, + CallChain, + CallExpression, + CallLikeExpression, + CallSignatureDeclaration, + CancellationToken, + canHaveDecorators, + canHaveExportModifier, + canHaveFlowNode, + canHaveIllegalDecorators, + canHaveIllegalModifiers, + canHaveJSDoc, + canHaveLocals, + canHaveModifiers, + canHaveSymbol, + canIncludeBindAndCheckDiagnostics, + canUsePropertyAccess, + cartesianProduct, + CaseBlock, + CaseClause, + CaseOrDefaultClause, + cast, + chainDiagnosticMessages, + CharacterCodes, + CheckFlags, + ClassDeclaration, + ClassElement, + classElementOrClassElementParameterIsDecorated, + ClassExpression, + ClassLikeDeclaration, + classOrConstructorParameterIsDecorated, + 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, + createDetachedDiagnostic, + createDiagnosticCollection, + createDiagnosticForFileFromMessageChain, + createDiagnosticForNode, + createDiagnosticForNodeArray, + createDiagnosticForNodeArrayFromMessageChain, + createDiagnosticForNodeFromMessageChain, + createDiagnosticMessageChainFromDiagnostic, + createEmptyExports, + createEvaluator, + createFileDiagnostic, + createFlowNode, + createGetSymbolWalker, + createModeAwareCacheKey, + createModuleNotFoundChain, + createMultiMap, + createNameResolver, + createPrinterWithDefaults, + createPrinterWithRemoveComments, + createPrinterWithRemoveCommentsNeverAsciiEscape, + createPrinterWithRemoveCommentsOmitTrailingSemicolon, + createPropertyNameNodeForIdentifierOrLiteral, + createScanner, + createSymbolTable, + createSyntacticTypeNodeBuilder, + createTextWriter, + Debug, + Declaration, + DeclarationName, + declarationNameToString, + DeclarationStatement, + DeclarationWithTypeParameterChildren, + DeclarationWithTypeParameters, + Decorator, + deduplicate, + DefaultClause, + defaultMaximumTruncationLength, + DeferredTypeReference, + DeleteExpression, + Diagnostic, + DiagnosticAndArguments, + DiagnosticArguments, + DiagnosticCategory, + DiagnosticMessage, + DiagnosticMessageChain, + DiagnosticRelatedInformation, + Diagnostics, + DiagnosticWithLocation, + DoStatement, + DynamicNamedDeclaration, + ElementAccessChain, + ElementAccessExpression, + ElementFlags, + EmitFlags, + EmitHint, + emitModuleKindIsNonNodeESM, + EmitResolver, + EmitTextWriter, + emptyArray, + endsWith, + EntityName, + EntityNameExpression, + EntityNameOrEntityNameExpression, + entityNameToString, + EnumDeclaration, + EnumMember, + EnumType, + equateValues, + escapeLeadingUnderscores, + escapeString, + EvaluatorResult, + evaluatorResult, + every, + EvolvingArrayType, + ExclamationToken, + ExportAssignment, + exportAssignmentIsAlias, + ExportDeclaration, + ExportSpecifier, + Expression, + expressionResultIsUnused, + ExpressionStatement, + ExpressionWithTypeArguments, + Extension, + ExternalEmitHelpers, + externalHelpersModuleNameText, + factory, + fileExtensionIs, + fileExtensionIsOneOf, + filter, + find, + findAncestor, + findBestPatternMatch, + findConstructorDeclaration, + findIndex, + findLast, + findLastIndex, + findUseStrictPrologue, + first, + firstDefined, + firstIterator, + firstOrUndefined, + firstOrUndefinedIterator, + flatMap, + flatten, + FlowArrayMutation, + FlowAssignment, + FlowCall, + FlowCondition, + FlowFlags, + FlowLabel, + FlowNode, + FlowReduceLabel, + FlowStart, + FlowSwitchClause, + FlowSwitchClauseData, + FlowType, + forEach, + forEachChild, + forEachChildRecursively, + forEachEnclosingBlockScopeContainer, + forEachEntry, + forEachKey, + forEachReturnStatement, + forEachYieldExpression, + ForInOrOfStatement, + ForInStatement, + formatMessage, + ForOfStatement, + ForStatement, + FreshableIntrinsicType, + FreshableType, + FreshObjectLiteralType, + FunctionDeclaration, + FunctionExpression, + FunctionFlags, + FunctionLikeDeclaration, + FunctionOrConstructorTypeNode, + FunctionTypeNode, + GenericType, + GetAccessorDeclaration, + getAliasDeclarationFromName, + getAllJSDocTags, + getAllowSyntheticDefaultImports, + getAncestor, + getAssignedExpandoInitializer, + getAssignmentDeclarationKind, + getAssignmentDeclarationPropertyAccessKind, + getAssignmentTargetKind, + getCanonicalDiagnostic, + getCheckFlags, + getClassExtendsHeritageElement, + getClassLikeDeclarationOfSymbol, + getCombinedLocalAndExportSymbolFlags, + getCombinedModifierFlags, + getCombinedNodeFlags, + getContainingClass, + getContainingClassExcludingClassDecorators, + getContainingClassStaticBlock, + getContainingFunction, + getContainingFunctionOrClassStaticBlock, + getDeclarationModifierFlagsFromSymbol, + getDeclarationOfKind, + getDeclarationsOfKind, + getDeclaredExpandoInitializer, + getDecorators, + getDirectoryPath, + getEffectiveBaseTypeNode, + getEffectiveConstraintOfTypeParameter, + getEffectiveContainerForJSDocTemplateTag, + getEffectiveImplementsTypeNodes, + getEffectiveInitializer, + getEffectiveJSDocHost, + getEffectiveModifierFlags, + getEffectiveReturnTypeNode, + getEffectiveSetAccessorTypeAnnotationNode, + getEffectiveTypeAnnotationNode, + getEffectiveTypeParameterDeclarations, + getElementOrPropertyAccessName, + getEmitDeclarations, + getEmitFlags, + getEmitModuleKind, + getEmitModuleResolutionKind, + getEmitScriptTarget, + getEmitStandardClassFields, + getEnclosingBlockScopeContainer, + getEnclosingContainer, + getEntityNameFromTypeNode, + getErrorSpanForNode, + getEscapedTextOfIdentifierOrLiteral, + getEscapedTextOfJsxAttributeName, + getEscapedTextOfJsxNamespacedName, + getESModuleInterop, + getExpandoInitializer, + getExportAssignmentExpression, + getExternalModuleImportEqualsDeclarationExpression, + getExternalModuleName, + getExternalModuleRequireArgument, + getFirstConstructorWithBody, + getFirstIdentifier, + getFunctionFlags, + getHostSignatureFromJSDoc, + getIdentifierGeneratedImportReference, + getIdentifierTypeArguments, + getImmediatelyInvokedFunctionExpression, + getInitializerOfBinaryExpression, + getInterfaceBaseTypeNodes, + getInvokedExpression, + getIsolatedModules, + getJSDocClassTag, + getJSDocDeprecatedTag, + getJSDocEnumTag, + getJSDocHost, + getJSDocOverloadTags, + getJSDocParameterTags, + getJSDocRoot, + getJSDocSatisfiesExpressionType, + getJSDocTags, + getJSDocThisTag, + getJSDocType, + getJSDocTypeAssertionType, + getJSDocTypeParameterDeclarations, + getJSDocTypeTag, + getJSXImplicitImportBase, + getJSXRuntimeImport, + getJSXTransformEnabled, + getLeftmostAccessExpression, + getLineAndCharacterOfPosition, + getMembersOfDeclaration, + getModifiers, + getModuleInstanceState, + getNameFromImportAttribute, + getNameFromIndexInfo, + getNameOfDeclaration, + getNameOfExpando, + getNamespaceDeclarationNode, + getNewTargetContainer, + getNonAugmentationDeclaration, + getNormalizedAbsolutePath, + getObjectFlags, + getOriginalNode, + getOrUpdate, + getParameterSymbolFromJSDoc, + getParseTreeNode, + getPropertyAssignmentAliasLikeExpression, + getPropertyNameForPropertyNameNode, + getPropertyNameFromType, + getResolutionDiagnostic, + getResolutionModeOverride, + getResolveJsonModule, + getRestParameterElementType, + getRootDeclaration, + getScriptTargetFeatures, + getSelectedEffectiveModifierFlags, + getSemanticJsxChildren, + getSetAccessorValueParameter, + getSingleVariableOfVariableStatement, + getSourceFileOfModule, + getSourceFileOfNode, + getSpanOfTokenAtPosition, + getSpellingSuggestion, + getStrictOptionValue, + getSuperContainer, + getSymbolNameForPrivateIdentifier, + getTextOfIdentifierOrLiteral, + getTextOfJSDocComment, + getTextOfJsxAttributeName, + getTextOfNode, + getTextOfPropertyName, + getThisContainer, + getThisParameter, + getTrailingSemicolonDeferringWriter, + getTypeParameterFromJsDoc, + getUseDefineForClassFields, + group, + hasAbstractModifier, + hasAccessorModifier, + hasAmbientModifier, + hasContextSensitiveParameters, + HasDecorators, + hasDecorators, + hasDynamicName, + hasEffectiveModifier, + hasEffectiveModifiers, + hasEffectiveReadonlyModifier, + HasExpressionInitializer, + hasExtension, + HasIllegalDecorators, + HasIllegalModifiers, + hasInferredType, + HasInitializer, + hasInitializer, + hasJSDocNodes, + hasJSDocParameterTags, + hasJsonModuleEmitEnabled, + HasLocals, + HasModifiers, + hasOnlyExpressionInitializer, + hasOverrideModifier, + hasPossibleExternalModuleReference, + hasQuestionToken, + hasResolutionModeOverride, + hasRestParameter, + hasScopeMarker, + hasStaticModifier, + hasSyntacticModifier, + hasSyntacticModifiers, + hasType, + HeritageClause, + Identifier, + identifierToKeywordKind, + IdentifierTypePredicate, + idText, + IfStatement, + ImportAttribute, + ImportAttributes, + ImportCall, + ImportClause, + ImportDeclaration, + ImportEqualsDeclaration, + ImportOrExportSpecifier, + ImportSpecifier, + ImportTypeNode, + IndexedAccessType, + IndexedAccessTypeNode, + IndexFlags, + IndexInfo, + IndexKind, + indexOfNode, + IndexSignatureDeclaration, + IndexType, + indicesOf, + InferenceContext, + InferenceFlags, + InferenceInfo, + InferencePriority, + InferTypeNode, + InstanceofExpression, + InstantiableType, + InstantiationExpressionType, + InterfaceDeclaration, + InterfaceType, + InterfaceTypeWithDeclaredMembers, + InternalSymbolName, + IntersectionFlags, + IntersectionType, + IntersectionTypeNode, + intrinsicTagNameToString, + IntrinsicType, + introducesArgumentsExoticObject, + isAccessExpression, + isAccessor, + isAliasableExpression, + isAmbientModule, + isArray, + isArrayBindingPattern, + isArrayLiteralExpression, + isArrowFunction, + isAssertionExpression, + isAssignmentDeclaration, + isAssignmentExpression, + isAssignmentOperator, + isAssignmentPattern, + isAssignmentTarget, + isAutoAccessorPropertyDeclaration, + isAwaitExpression, + isBinaryExpression, + isBindableObjectDefinePropertyCall, + isBindableStaticElementAccessExpression, + isBindableStaticNameExpression, + isBindingElement, + isBindingElementOfBareOrAccessedRequire, + isBindingPattern, + isBlock, + isBlockOrCatchScoped, + isBlockScopedContainerTopLevel, + isBooleanLiteral, + isCallChain, + isCallExpression, + isCallLikeExpression, + isCallLikeOrFunctionLikeExpression, + isCallOrNewExpression, + isCallSignatureDeclaration, + isCatchClause, + isCatchClauseVariableDeclaration, + isCatchClauseVariableDeclarationOrBindingElement, + isCheckJsEnabledForFile, + isClassDeclaration, + isClassElement, + isClassExpression, + isClassInstanceProperty, + isClassLike, + isClassStaticBlockDeclaration, + isCommaSequence, + isCommonJsExportedExpression, + isCommonJsExportPropertyAssignment, + isCompoundAssignment, + isComputedNonLiteralName, + isComputedPropertyName, + isConditionalTypeNode, + isConstAssertion, + isConstructorDeclaration, + isConstructorTypeNode, + isConstructSignatureDeclaration, + isConstTypeReference, + isDeclaration, + isDeclarationFileName, + isDeclarationName, + isDeclarationReadonly, + isDecorator, + isDefaultedExpandoInitializer, + isDeleteTarget, + isDottedName, + isDynamicName, + isEffectiveExternalModule, + isElementAccessExpression, + isEntityName, + isEntityNameExpression, + isEnumConst, + isEnumDeclaration, + isEnumMember, + isExclusivelyTypeOnlyImportOrExport, + isExpandoPropertyDeclaration, + isExportAssignment, + isExportDeclaration, + isExportsIdentifier, + isExportSpecifier, + isExpression, + isExpressionNode, + isExpressionOfOptionalChainRoot, + isExpressionStatement, + isExpressionWithTypeArguments, + isExpressionWithTypeArgumentsInClassExtendsClause, + isExternalModule, + isExternalModuleAugmentation, + isExternalModuleImportEqualsDeclaration, + isExternalModuleIndicator, + isExternalModuleNameRelative, + isExternalModuleReference, + isExternalModuleSymbol, + isExternalOrCommonJsModule, + isForInOrOfStatement, + isForInStatement, + isForOfStatement, + isForStatement, + isFunctionDeclaration, + isFunctionExpression, + isFunctionExpressionOrArrowFunction, + isFunctionLike, + isFunctionLikeDeclaration, + isFunctionLikeOrClassStaticBlockDeclaration, + isFunctionOrModuleBlock, + isFunctionTypeNode, + isGeneratedIdentifier, + isGetAccessor, + isGetAccessorDeclaration, + isGetOrSetAccessorDeclaration, + isGlobalScopeAugmentation, + isGlobalSourceFile, + isHeritageClause, + isIdentifier, + isIdentifierText, + isIdentifierTypePredicate, + isIdentifierTypeReference, + isIfStatement, + isImportAttributes, + isImportCall, + isImportClause, + isImportDeclaration, + isImportEqualsDeclaration, + isImportKeyword, + isImportOrExportSpecifier, + isImportSpecifier, + isImportTypeNode, + isInCompoundLikeAssignment, + isIndexedAccessTypeNode, + isInExpressionContext, + isInfinityOrNaNString, + isInitializedProperty, + isInJSDoc, + isInJSFile, + isInJsonFile, + isInstanceOfExpression, + isInterfaceDeclaration, + isInternalModuleImportEqualsDeclaration, + isInTopLevelContext, + isIntrinsicJsxName, + isInTypeQuery, + isIterationStatement, + isJSDocAllType, + isJSDocAugmentsTag, + isJSDocCallbackTag, + isJSDocConstructSignature, + isJSDocFunctionType, + isJSDocImportTag, + isJSDocIndexSignature, + isJSDocLinkLike, + isJSDocMemberName, + isJSDocNameReference, + isJSDocNode, + isJSDocNonNullableType, + isJSDocNullableType, + isJSDocOptionalParameter, + isJSDocOptionalType, + isJSDocOverloadTag, + isJSDocParameterTag, + isJSDocPropertyLikeTag, + isJSDocPropertyTag, + isJSDocSatisfiesExpression, + isJSDocSatisfiesTag, + isJSDocSignature, + isJSDocTemplateTag, + isJSDocThisTag, + isJSDocTypeAlias, + isJSDocTypeAssertion, + isJSDocTypedefTag, + isJSDocTypeExpression, + isJSDocTypeLiteral, + isJSDocUnknownType, + isJSDocVariadicType, + isJsonSourceFile, + isJsxAttribute, + isJsxAttributeLike, + isJsxAttributes, + isJsxElement, + isJsxNamespacedName, + isJsxOpeningElement, + isJsxOpeningFragment, + isJsxOpeningLikeElement, + isJsxSelfClosingElement, + isJsxSpreadAttribute, + isJSXTagName, + isKnownSymbol, + isLateVisibilityPaintedStatement, + isLeftHandSideExpression, + isLineBreak, + isLiteralComputedPropertyDeclarationName, + isLiteralExpression, + isLiteralExpressionOfObject, + isLiteralImportTypeNode, + isLiteralTypeNode, + isLogicalOrCoalescingBinaryExpression, + isLogicalOrCoalescingBinaryOperator, + isMappedTypeNode, + isMetaProperty, + isMethodDeclaration, + isMethodSignature, + isModifier, + isModuleBlock, + isModuleDeclaration, + isModuleExportsAccessExpression, + isModuleIdentifier, + isModuleOrEnumDeclaration, + isModuleWithStringLiteralName, + isNamedDeclaration, + isNamedEvaluationSource, + isNamedExports, + isNamedTupleMember, + isNamespaceExport, + isNamespaceExportDeclaration, + isNamespaceReexportDeclaration, + isNewExpression, + isNodeDescendantOf, + isNonNullAccess, + isNonNullExpression, + isNumericLiteral, + isNumericLiteralName, + isObjectBindingPattern, + isObjectLiteralElementLike, + isObjectLiteralExpression, + isObjectLiteralMethod, + isObjectLiteralOrClassExpressionMethodOrAccessor, + isOmittedExpression, + isOptionalChain, + isOptionalChainRoot, + isOptionalDeclaration, + isOptionalJSDocPropertyLikeTag, + isOptionalTypeNode, + isOutermostOptionalChain, + isParameter, + isParameterPropertyDeclaration, + isParenthesizedExpression, + isParenthesizedTypeNode, + isPartOfParameterDeclaration, + isPartOfTypeNode, + isPartOfTypeQuery, + isPlainJsFile, + isPrefixUnaryExpression, + isPrivateIdentifier, + isPrivateIdentifierClassElementDeclaration, + isPrivateIdentifierPropertyAccessExpression, + isPropertyAccessEntityNameExpression, + isPropertyAccessExpression, + isPropertyAccessOrQualifiedName, + isPropertyAccessOrQualifiedNameOrImportTypeNode, + isPropertyAssignment, + isPropertyDeclaration, + isPropertyName, + isPropertyNameLiteral, + isPropertySignature, + isPrototypeAccess, + isPrototypePropertyAssignment, + isPushOrUnshiftIdentifier, + isQualifiedName, + isRequireCall, + isRestParameter, + isRestTypeNode, + isRightSideOfAccessExpression, + isRightSideOfInstanceofExpression, + isRightSideOfQualifiedNameOrPropertyAccess, + isRightSideOfQualifiedNameOrPropertyAccessOrJSDocMemberName, + isSameEntityName, + isSatisfiesExpression, + isSetAccessor, + isSetAccessorDeclaration, + 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, + isThisTypeNode, + isThisTypeParameter, + isThisTypePredicate, + isTransientSymbol, + isTupleTypeNode, + isTypeAlias, + isTypeAliasDeclaration, + isTypeDeclaration, + isTypeLiteralNode, + isTypeNode, + isTypeNodeKind, + isTypeOfExpression, + isTypeOnlyImportDeclaration, + isTypeOnlyImportOrExportDeclaration, + isTypeOperatorNode, + isTypeParameterDeclaration, + isTypePredicateNode, + isTypeQueryNode, + isTypeReferenceNode, + isTypeReferenceType, + isTypeUsableAsPropertyName, + isUMDExportSymbol, + isValidBigIntString, + isValidESSymbolDeclaration, + isValidTypeOnlyAliasUseSite, + isValueSignatureDeclaration, + isVariableDeclaration, + isVariableDeclarationInitializedToBareOrAccessedRequire, + isVariableDeclarationInVariableStatement, + isVariableDeclarationList, + isVariableLike, + isVariableLikeOrAccessor, + isVariableStatement, + isWriteAccess, + isWriteOnlyAccess, + IterableOrIteratorType, + IterationTypes, + JSDoc, + JSDocAugmentsTag, + JSDocCallbackTag, + JSDocComment, + JSDocFunctionType, + JSDocImplementsTag, + JSDocImportTag, + JSDocLink, + JSDocLinkCode, + JSDocLinkPlain, + JSDocMemberName, + JSDocNullableType, + JSDocOptionalType, + JSDocOverloadTag, + JSDocParameterTag, + JSDocPrivateTag, + JSDocPropertyLikeTag, + JSDocPropertyTag, + JSDocProtectedTag, + JSDocPublicTag, + JSDocSatisfiesTag, + JSDocSignature, + JSDocTemplateTag, + JSDocThisTag, + JSDocTypeAssertion, + JSDocTypedefTag, + JSDocTypeExpression, + JSDocTypeLiteral, + JSDocTypeReferencingNode, + JSDocTypeTag, + JSDocVariadicType, + JsxAttribute, + JsxAttributeLike, + JsxAttributeName, + JsxAttributes, + JsxAttributeValue, + JsxChild, + JsxClosingElement, + JsxElement, + JsxEmit, + JsxExpression, + JsxFlags, + JsxFragment, + JsxNamespacedName, + JsxOpeningElement, + JsxOpeningFragment, + JsxOpeningLikeElement, + JsxReferenceKind, + JsxSelfClosingElement, + JsxSpreadAttribute, + JsxTagNameExpression, + KeywordTypeNode, + LabeledStatement, + LanguageFeatureMinimumTarget, + last, + lastOrUndefined, + LateBoundBinaryExpressionDeclaration, + LateBoundDeclaration, + LateBoundName, + LateVisibilityPaintedStatement, + LazyNodeCheckFlags, + length, + LiteralExpression, + LiteralType, + LiteralTypeNode, + map, + mapDefined, + MappedSymbol, + MappedType, + MappedTypeNode, + MatchingKeys, + maybeBind, + MemberOverrideStatus, + MetaProperty, + MethodDeclaration, + MethodSignature, + minAndMax, + MinusToken, + Modifier, + ModifierFlags, + modifiersToFlags, + modifierToFlag, + ModuleBlock, + ModuleDeclaration, + ModuleExportName, + moduleExportNameIsDefault, + moduleExportNameTextEscaped, + moduleExportNameTextUnescaped, + ModuleInstanceState, + ModuleKind, + ModuleResolutionKind, + ModuleSpecifierResolutionHost, + Mutable, + NamedDeclaration, + NamedExports, + NamedImportsOrExports, + NamedTupleMember, + NamespaceDeclaration, + NamespaceExport, + NamespaceExportDeclaration, + NamespaceImport, + needsScopeMarker, + NewExpression, + Node, + NodeArray, + NodeBuilderFlags, + nodeCanBeDecorated, + NodeCheckFlags, + NodeFlags, + nodeHasName, + nodeIsMissing, + nodeIsPresent, + nodeIsSynthesized, + NodeLinks, + nodeStartsNewLexicalEnvironment, + NodeWithTypeArguments, + NonNullChain, + NonNullExpression, + not, + noTruncationMaximumTruncationLength, + NumberLiteralType, + NumericLiteral, + objectAllocator, + ObjectBindingPattern, + ObjectFlags, + ObjectFlagsType, + ObjectLiteralElementLike, + ObjectLiteralExpression, + ObjectType, + OptionalChain, + OptionalTypeNode, + or, + orderedRemoveItemAt, + OuterExpressionKinds, + ParameterDeclaration, + parameterIsThisKeyword, + ParameterPropertyDeclaration, + ParenthesizedExpression, + ParenthesizedTypeNode, + parseIsolatedEntityName, + parseNodeFactory, + parsePseudoBigInt, + parseValidBigInt, + Path, + pathIsRelative, + PatternAmbientModule, + PlusToken, + PostfixUnaryExpression, + PrefixUnaryExpression, + PrivateIdentifier, + Program, + PromiseOrAwaitableType, + PropertyAccessChain, + PropertyAccessEntityNameExpression, + PropertyAccessExpression, + PropertyAssignment, + PropertyDeclaration, + PropertyName, + PropertySignature, + PseudoBigInt, + pseudoBigIntToString, + PunctuationSyntaxKind, + pushIfUnique, + QualifiedName, + QuestionToken, + rangeEquals, + rangeOfNode, + rangeOfTypeParameters, + ReadonlyKeyword, + reduceLeft, + RegularExpressionLiteral, + RelationComparisonResult, + relativeComplement, + removeExtension, + removePrefix, + replaceElement, + resolutionExtensionIsTSOrJson, + ResolutionMode, + ResolvedModuleFull, + ResolvedType, + resolvingEmptyArray, + RestTypeNode, + ReturnStatement, + ReverseMappedSymbol, + ReverseMappedType, + sameMap, + SatisfiesExpression, + Scanner, + scanTokenAtPosition, + ScriptKind, + ScriptTarget, + SetAccessorDeclaration, + setCommentRange as setCommentRangeWorker, + setEmitFlags, + setIdentifierTypeArguments, + setNodeFlags, + setOriginalNode, + setParent, + setSyntheticLeadingComments, + setTextRange as setTextRangeWorker, + setTextRangePosEnd, + setValueDeclaration, + ShorthandPropertyAssignment, + shouldAllowImportingTsExtension, + shouldPreserveConstEnums, + Signature, + SignatureDeclaration, + SignatureFlags, + SignatureKind, + singleElementArray, + SingleSignatureType, + skipOuterExpressions, + skipParentheses, + skipTrivia, + skipTypeChecking, + skipTypeParentheses, + some, + SourceFile, + SpreadAssignment, + SpreadElement, + startsWith, + Statement, + StringLiteral, + StringLiteralLike, + StringLiteralType, + StringMappingType, + stripQuotes, + StructuredType, + SubstitutionType, + 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, + TrackedSymbol, + TransientSymbol, + TransientSymbolLinks, + tryAddToSet, + tryCast, + tryExtractTSExtension, + tryGetClassImplementingOrExtendingExpressionWithTypeArguments, + tryGetExtensionFromPath, + tryGetJSDocSatisfiesTypeNode, + 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, + unescapeLeadingUnderscores, + UnionOrIntersectionType, + UnionOrIntersectionTypeNode, + UnionReduction, + UnionType, + UnionTypeNode, + UniqueESSymbolType, + usingSingleLineStringWriter, + VariableDeclaration, + VariableDeclarationList, + VariableLikeDeclaration, + VariableStatement, + VarianceFlags, + visitEachChild as visitEachChildWorker, + visitNode, + visitNodes, + Visitor, + VisitResult, + VoidExpression, + walkUpBindingElementsAndPatterns, + walkUpOuterExpressions, + walkUpParenthesizedExpressions, + walkUpParenthesizedTypes, + walkUpParenthesizedTypesAndGetParentAndChild, + WhileStatement, + WideningContext, + WithStatement, + YieldExpression, +} from "./_namespaces/ts.js"; +import * as moduleSpecifiers from "./_namespaces/ts.moduleSpecifiers.js"; +import * as performance from "./_namespaces/ts.performance.js"; + +const ambientModuleSymbolRegex = /^".+"$/; +const anon = "(anonymous)" as __String & string; + +const enum ReferenceHint { + Unspecified, + Identifier, + Property, + ExportAssignment, + Jsx, + AsyncFunction, + ExportImportEquals, + ExportSpecifier, + Decorator, +} + +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, +} + +// dprint-ignore +/** @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 typeofNEFacts: ReadonlyMap = new Map(Object.entries({ + 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, + ResolvedTypeArguments, + ResolvedBaseTypes, + WriteType, + ParameterInitializerContainsUndefined, +} + +// dprint-ignore +/** @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 + RestBindingElement = 1 << 5, // 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 + TypeOnly = 1 << 6, // Called from getTypeOfExpression, diagnostics may be omitted +} + +/** @internal */ +export const enum SignatureCheckMode { + None = 0, + BivariantCallback = 1 << 0, + StrictCallback = 1 << 1, + IgnoreReturnTypes = 1 << 2, + StrictArity = 1 << 3, + StrictTopSignature = 1 << 4, + Callback = BivariantCallback | StrictCallback, +} + +const enum IntersectionState { + None = 0, + Source = 1 << 0, // Source type is a constituent of an outer intersection + Target = 1 << 1, // Target type is a constituent of an outer intersection +} + +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 MappedTypeNameTypeKind { + None, + Filtering, + Remapping, +} + +const enum ExpandingFlags { + None = 0, + Source = 1, + Target = 1 << 1, + Both = Source | Target, +} + +const enum MembersOrExportsResolutionKind { + resolvedExports = "resolvedExports", + resolvedMembers = "resolvedMembers", +} + +const enum UnusedKind { + Local, + Parameter, +} + +/** @param containingNode Node to check for parse error */ +type AddUnusedDiagnostic = (containingNode: Node, type: UnusedKind, diagnostic: DiagnosticWithLocation) => void; + +const isNotOverloadAndNotAccessor = and(isNotOverload, isNotAccessor); + +const enum DeclarationMeaning { + GetAccessor = 1, + SetAccessor = 2, + PropertyAssignment = 4, + Method = 8, + PrivateStatic = 16, + GetOrSetAccessor = GetAccessor | SetAccessor, + PropertyAssignmentOrMethod = PropertyAssignment | Method, +} + +const enum DeclarationSpaces { + None = 0, + ExportValue = 1 << 0, + ExportType = 1 << 1, + ExportNamespace = 1 << 2, +} + +const enum MinArgumentCountFlags { + None = 0, + StrongArityForUntypedJS = 1 << 0, + VoidIsNonOptional = 1 << 1, +} + +const enum IntrinsicTypeKind { + Uppercase, + Lowercase, + Capitalize, + Uncapitalize, + NoInfer, +} + +const intrinsicTypeKinds: ReadonlyMap = new Map(Object.entries({ + Uppercase: IntrinsicTypeKind.Uppercase, + Lowercase: IntrinsicTypeKind.Lowercase, + Capitalize: IntrinsicTypeKind.Capitalize, + Uncapitalize: IntrinsicTypeKind.Uncapitalize, + NoInfer: IntrinsicTypeKind.NoInfer, +})); + +const SymbolLinks = class implements SymbolLinks { + declare _symbolLinksBrand: any; +}; + +function NodeLinks(this: NodeLinks) { + this.flags = NodeCheckFlags.None; +} + +/** @internal */ +export function getNodeId(node: Node): number { + if (!node.id) { + node.id = nextNodeId; + nextNodeId++; + } + return node.id; +} + +/** @internal */ +export function getSymbolId(symbol: Symbol): SymbolId { + if (!symbol.id) { + symbol.id = nextSymbolId; + nextSymbolId++; + } + + return symbol.id; +} + +/** @internal */ +export function isInstantiatedModule(node: ModuleDeclaration, preserveConstEnums: boolean) { + const moduleState = getModuleInstanceState(node); + return moduleState === ModuleInstanceState.Instantiated || + (preserveConstEnums && moduleState === ModuleInstanceState.ConstEnumOnly); +} + +/** @internal */ +export function createTypeChecker(host: TypeCheckerHost): TypeChecker { + // Why var? It avoids TDZ checks in the runtime which can be costly. + // See: https://github.com/microsoft/TypeScript/issues/52924 + /* eslint-disable no-var */ + var deferredDiagnosticsCallbacks: (() => void)[] = []; + + var 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). + var cancellationToken: CancellationToken | undefined; + + var scanner: Scanner | undefined; + + var Symbol = objectAllocator.getSymbolConstructor(); + var Type = objectAllocator.getTypeConstructor(); + var Signature = objectAllocator.getSignatureConstructor(); + + var typeCount = 0; + var symbolCount = 0; + var totalInstantiationCount = 0; + var instantiationCount = 0; + var instantiationDepth = 0; + var inlineLevel = 0; + var currentNode: Node | undefined; + var varianceTypeParameter: TypeParameter | undefined; + var isInferencePartiallyBlocked = false; + + var emptySymbols = createSymbolTable(); + var arrayVariances = [VarianceFlags.Covariant]; + + var compilerOptions = host.getCompilerOptions(); + var languageVersion = getEmitScriptTarget(compilerOptions); + var moduleKind = getEmitModuleKind(compilerOptions); + var legacyDecorators = !!compilerOptions.experimentalDecorators; + var useDefineForClassFields = getUseDefineForClassFields(compilerOptions); + var emitStandardClassFields = getEmitStandardClassFields(compilerOptions); + var allowSyntheticDefaultImports = getAllowSyntheticDefaultImports(compilerOptions); + var strictNullChecks = getStrictOptionValue(compilerOptions, "strictNullChecks"); + var strictFunctionTypes = getStrictOptionValue(compilerOptions, "strictFunctionTypes"); + var strictBindCallApply = getStrictOptionValue(compilerOptions, "strictBindCallApply"); + var strictPropertyInitialization = getStrictOptionValue(compilerOptions, "strictPropertyInitialization"); + var noImplicitAny = getStrictOptionValue(compilerOptions, "noImplicitAny"); + var noImplicitThis = getStrictOptionValue(compilerOptions, "noImplicitThis"); + var useUnknownInCatchVariables = getStrictOptionValue(compilerOptions, "useUnknownInCatchVariables"); + var exactOptionalPropertyTypes = compilerOptions.exactOptionalPropertyTypes; + + var checkBinaryExpression = createCheckBinaryExpression(); + var emitResolver = createResolver(); + var nodeBuilder = createNodeBuilder(); + var syntacticNodeBuilder = createSyntacticTypeNodeBuilder(compilerOptions, { + isEntityNameVisible, + isExpandoFunctionDeclaration, + getAllAccessorDeclarations: getAllAccessorDeclarationsForDeclaration, + requiresAddingImplicitUndefined, + isUndefinedIdentifierExpression(node: Identifier) { + Debug.assert(isExpressionNode(node)); + return getSymbolAtLocation(node) === undefinedSymbol; + }, + isDefinitelyReferenceToGlobalSymbolObject, + }); + var evaluate = createEvaluator({ + evaluateElementAccessExpression, + evaluateEntityNameExpression, + }); + + var globals = createSymbolTable(); + var undefinedSymbol = createSymbol(SymbolFlags.Property, "undefined" as __String); + undefinedSymbol.declarations = []; + + var globalThisSymbol = createSymbol(SymbolFlags.Module, "globalThis" as __String, CheckFlags.Readonly); + globalThisSymbol.exports = globals; + globalThisSymbol.declarations = []; + globals.set(globalThisSymbol.escapedName, globalThisSymbol); + + var argumentsSymbol = createSymbol(SymbolFlags.Property, "arguments" as __String); + var requireSymbol = createSymbol(SymbolFlags.Property, "require" as __String); + var isolatedModulesLikeFlagName = compilerOptions.verbatimModuleSyntax ? "verbatimModuleSyntax" : "isolatedModules"; + var canCollectSymbolAliasAccessabilityData = !compilerOptions.verbatimModuleSyntax; + + /** This will be set during calls to `getResolvedSignature` where services determines an apparent number of arguments greater than what is actually provided. */ + var apparentArgumentCount: number | undefined; + + var lastGetCombinedNodeFlagsNode: Node | undefined; + var lastGetCombinedNodeFlagsResult = NodeFlags.None; + var lastGetCombinedModifierFlagsNode: Declaration | undefined; + var lastGetCombinedModifierFlagsResult = ModifierFlags.None; + var resolveName = createNameResolver({ + compilerOptions, + requireSymbol, + argumentsSymbol, + globals, + getSymbolOfDeclaration, + error, + getRequiresScopeChangeCache, + setRequiresScopeChangeCache, + lookup: getSymbol, + onPropertyWithInvalidInitializer: checkAndReportErrorForInvalidInitializer, + onFailedToResolveSymbol, + onSuccessfullyResolvedSymbol, + }); + + var resolveNameForSymbolSuggestion = createNameResolver({ + compilerOptions, + requireSymbol, + argumentsSymbol, + globals, + getSymbolOfDeclaration, + error, + getRequiresScopeChangeCache, + setRequiresScopeChangeCache, + lookup: getSuggestionForSymbolNameLookup, + }); + // 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: () => reduceLeft(host.getSourceFiles(), (n, s) => n + s.nodeCount, 0), + getIdentifierCount: () => reduceLeft(host.getSourceFiles(), (n, s) => n + s.identifierCount, 0), + getSymbolCount: () => reduceLeft(host.getSourceFiles(), (n, s) => n + s.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, + symbolIsValue, + 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."); + Debug.assert(isParameterPropertyDeclaration(parameter, parameter.parent)); + 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, + getWidenedLiteralType, + getTypeFromTypeNode: nodeIn => { + const node = getParseTreeNode(nodeIn, isTypeNode); + return node ? getTypeFromTypeNode(node) : errorType; + }, + getParameterType: getTypeAtPosition, + getParameterIdentifierInfoAtPosition, + 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), + getCandidateSignaturesForStringLiteralCompletions, + getResolvedSignatureForSignatureHelp: (node, candidatesOutArray, argumentCount) => runWithoutResolvedSignatureCaching(node, () => 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, + requiresAddingImplicitUndefined, + 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, + getStringLiteralType, + getNumberType: () => numberType, + getNumberLiteralType, + getBigIntType: () => bigintType, + getBigIntLiteralType, + 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, + isEmptyAnonymousObjectType, + isTypeInvalidDueToUnionDiscriminant, + getExactOptionalProperties, + getAllPossiblePropertiesOfTypes, + getSuggestedSymbolForNonexistentProperty, + getSuggestedSymbolForNonexistentJSXAttribute, + getSuggestedSymbolForNonexistentSymbol: (location, name, meaning) => getSuggestedSymbolForNonexistentSymbol(location, escapeLeadingUnderscores(name), meaning), + getSuggestedSymbolForNonexistentModule, + 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, /*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; + } + + 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; + + // 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)); + + 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 }); + } + }); + + return diagnostics || emptyArray; + } + finally { + cancellationToken = undefined; + } + }, + + runWithCancellationToken: (token, callback) => { + try { + cancellationToken = token; + return callback(checker); + } + finally { + cancellationToken = undefined; + } + }, + + getLocalTypeParametersOfClassOrInterfaceOrTypeAlias, + isDeclarationVisible, + isPropertyAccessible, + getTypeOnlyAliasDeclaration, + getMemberOverrideModifierStatus, + isTypeParameterPossiblyReferenced, + typeHasCallOrConstructSignatures, + getSymbolFlags, + }; + + function getCandidateSignaturesForStringLiteralCompletions(call: CallLikeExpression, editingArgument: Node) { + const candidatesSet = new Set(); + const candidates: Signature[] = []; + + // first, get candidates when inference is blocked from the source node. + runWithInferenceBlockedFromSourceNode(editingArgument, () => getResolvedSignatureWorker(call, candidates, /*argumentCount*/ undefined, CheckMode.Normal)); + for (const candidate of candidates) { + candidatesSet.add(candidate); + } + + // reset candidates for second pass + candidates.length = 0; + + // next, get candidates where the source node is considered for inference. + runWithoutResolvedSignatureCaching(editingArgument, () => getResolvedSignatureWorker(call, candidates, /*argumentCount*/ undefined, CheckMode.Normal)); + for (const candidate of candidates) { + candidatesSet.add(candidate); + } + + return arrayFrom(candidatesSet); + } + + function runWithoutResolvedSignatureCaching(node: Node | undefined, fn: () => T): T { + node = findAncestor(node, isCallLikeOrFunctionLikeExpression); + if (node) { + const cachedResolvedSignatures = []; + const cachedTypes = []; + while (node) { + const nodeLinks = getNodeLinks(node); + cachedResolvedSignatures.push([nodeLinks, nodeLinks.resolvedSignature] as const); + nodeLinks.resolvedSignature = undefined; + if (isFunctionExpressionOrArrowFunction(node)) { + const symbolLinks = getSymbolLinks(getSymbolOfDeclaration(node)); + const type = symbolLinks.type; + cachedTypes.push([symbolLinks, type] as const); + symbolLinks.type = undefined; + } + node = findAncestor(node.parent, isCallLikeOrFunctionLikeExpression); + } + const result = fn(); + for (const [nodeLinks, resolvedSignature] of cachedResolvedSignatures) { + nodeLinks.resolvedSignature = resolvedSignature; + } + for (const [symbolLinks, type] of cachedTypes) { + symbolLinks.type = type; + } + return result; + } + return fn(); + } + + function runWithInferenceBlockedFromSourceNode(node: Node | undefined, fn: () => T): T { + const containingCall = findAncestor(node, isCallLikeExpression); + if (containingCall) { + let toMarkSkip = node!; + do { + getNodeLinks(toMarkSkip).skipDirectInference = true; + toMarkSkip = toMarkSkip.parent; + } + while (toMarkSkip && toMarkSkip !== containingCall); + } + + isInferencePartiallyBlocked = true; + const result = runWithoutResolvedSignatureCaching(node, fn); + isInferencePartiallyBlocked = false; + + if (containingCall) { + let toMarkSkip = node!; + do { + getNodeLinks(toMarkSkip).skipDirectInference = undefined; + toMarkSkip = toMarkSkip.parent; + } + while (toMarkSkip && toMarkSkip !== containingCall); + } + return result; + } + + function getResolvedSignatureWorker(nodeIn: CallLikeExpression, candidatesOutArray: Signature[] | undefined, argumentCount: number | undefined, checkMode: CheckMode): Signature | undefined { + const node = getParseTreeNode(nodeIn, isCallLikeExpression); + apparentArgumentCount = argumentCount; + const res = !node ? undefined : getResolvedSignature(node, candidatesOutArray, checkMode); + apparentArgumentCount = undefined; + return res; + } + + var tupleTypes = new Map(); + var unionTypes = new Map(); + var unionOfUnionTypes = new Map(); + var intersectionTypes = new Map(); + var stringLiteralTypes = new Map(); + var numberLiteralTypes = new Map(); + var bigIntLiteralTypes = new Map(); + var enumLiteralTypes = new Map(); + var indexedAccessTypes = new Map(); + var templateLiteralTypes = new Map(); + var stringMappingTypes = new Map(); + var substitutionTypes = new Map(); + var subtypeReductionCache = new Map(); + var decoratorContextOverrideTypeCache = new Map(); + var cachedTypes = new Map(); + var evolvingArrayTypes: EvolvingArrayType[] = []; + var undefinedProperties: SymbolTable = new Map(); + var markerTypes = new Set(); + + var unknownSymbol = createSymbol(SymbolFlags.Property, "unknown" as __String); + var resolvingSymbol = createSymbol(0, InternalSymbolName.Resolving); + var unresolvedSymbols = new Map(); + var errorTypes = new Map(); + + // We specifically create the `undefined` and `null` types before any other types that can occur in + // unions such that they are given low type IDs and occur first in the sorted list of union constituents. + // We can then just examine the first constituent(s) of a union to check for their presence. + + var seenIntrinsicNames = new Set(); + + var anyType = createIntrinsicType(TypeFlags.Any, "any"); + var autoType = createIntrinsicType(TypeFlags.Any, "any", ObjectFlags.NonInferrableType, "auto"); + var wildcardType = createIntrinsicType(TypeFlags.Any, "any", /*objectFlags*/ undefined, "wildcard"); + var blockedStringType = createIntrinsicType(TypeFlags.Any, "any", /*objectFlags*/ undefined, "blocked string"); + var errorType = createIntrinsicType(TypeFlags.Any, "error"); + var unresolvedType = createIntrinsicType(TypeFlags.Any, "unresolved"); + var nonInferrableAnyType = createIntrinsicType(TypeFlags.Any, "any", ObjectFlags.ContainsWideningType, "non-inferrable"); + var intrinsicMarkerType = createIntrinsicType(TypeFlags.Any, "intrinsic"); + var unknownType = createIntrinsicType(TypeFlags.Unknown, "unknown"); + var undefinedType = createIntrinsicType(TypeFlags.Undefined, "undefined"); + var undefinedWideningType = strictNullChecks ? undefinedType : createIntrinsicType(TypeFlags.Undefined, "undefined", ObjectFlags.ContainsWideningType, "widening"); + var missingType = createIntrinsicType(TypeFlags.Undefined, "undefined", /*objectFlags*/ undefined, "missing"); + var undefinedOrMissingType = exactOptionalPropertyTypes ? missingType : undefinedType; + var optionalType = createIntrinsicType(TypeFlags.Undefined, "undefined", /*objectFlags*/ undefined, "optional"); + var nullType = createIntrinsicType(TypeFlags.Null, "null"); + var nullWideningType = strictNullChecks ? nullType : createIntrinsicType(TypeFlags.Null, "null", ObjectFlags.ContainsWideningType, "widening"); + var stringType = createIntrinsicType(TypeFlags.String, "string"); + var numberType = createIntrinsicType(TypeFlags.Number, "number"); + var bigintType = createIntrinsicType(TypeFlags.BigInt, "bigint"); + var falseType = createIntrinsicType(TypeFlags.BooleanLiteral, "false", /*objectFlags*/ undefined, "fresh") as FreshableIntrinsicType; + var regularFalseType = createIntrinsicType(TypeFlags.BooleanLiteral, "false") as FreshableIntrinsicType; + var trueType = createIntrinsicType(TypeFlags.BooleanLiteral, "true", /*objectFlags*/ undefined, "fresh") as FreshableIntrinsicType; + var 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; + var booleanType = getUnionType([regularFalseType, regularTrueType]); + var esSymbolType = createIntrinsicType(TypeFlags.ESSymbol, "symbol"); + var voidType = createIntrinsicType(TypeFlags.Void, "void"); + var neverType = createIntrinsicType(TypeFlags.Never, "never"); + var silentNeverType = createIntrinsicType(TypeFlags.Never, "never", ObjectFlags.NonInferrableType, "silent"); + var implicitNeverType = createIntrinsicType(TypeFlags.Never, "never", /*objectFlags*/ undefined, "implicit"); + var unreachableNeverType = createIntrinsicType(TypeFlags.Never, "never", /*objectFlags*/ undefined, "unreachable"); + var nonPrimitiveType = createIntrinsicType(TypeFlags.NonPrimitive, "object"); + var stringOrNumberType = getUnionType([stringType, numberType]); + var stringNumberSymbolType = getUnionType([stringType, numberType, esSymbolType]); + var numberOrBigIntType = getUnionType([numberType, bigintType]); + var templateConstraintType = getUnionType([stringType, numberType, booleanType, bigintType, nullType, undefinedType]) as UnionType; + var numericStringType = getTemplateLiteralType(["", ""], [numberType]); // The `${number}` type + + var restrictiveMapper: TypeMapper = makeFunctionTypeMapper(t => t.flags & TypeFlags.TypeParameter ? getRestrictiveTypeParameter(t as TypeParameter) : t, () => "(restrictive mapper)"); + var permissiveMapper: TypeMapper = makeFunctionTypeMapper(t => t.flags & TypeFlags.TypeParameter ? wildcardType : t, () => "(permissive mapper)"); + var uniqueLiteralType = createIntrinsicType(TypeFlags.Never, "never", /*objectFlags*/ undefined, "unique literal"); // `uniqueLiteralType` is a special `never` flagged by union reduction to behave as a literal + var uniqueLiteralMapper: TypeMapper = makeFunctionTypeMapper(t => t.flags & TypeFlags.TypeParameter ? uniqueLiteralType : t, () => "(unique literal mapper)"); // replace all type parameters with the unique literal type (disregarding constraints) + var outofbandVarianceMarkerHandler: ((onlyUnreliable: boolean) => void) | undefined; + var reportUnreliableMapper = makeFunctionTypeMapper(t => { + if (outofbandVarianceMarkerHandler && (t === markerSuperType || t === markerSubType || t === markerOtherType)) { + outofbandVarianceMarkerHandler(/*onlyUnreliable*/ true); + } + return t; + }, () => "(unmeasurable reporter)"); + var reportUnmeasurableMapper = makeFunctionTypeMapper(t => { + if (outofbandVarianceMarkerHandler && (t === markerSuperType || t === markerSubType || t === markerOtherType)) { + outofbandVarianceMarkerHandler(/*onlyUnreliable*/ false); + } + return t; + }, () => "(unreliable reporter)"); + + var emptyObjectType = createAnonymousType(/*symbol*/ undefined, emptySymbols, emptyArray, emptyArray, emptyArray); + var emptyJsxObjectType = createAnonymousType(/*symbol*/ undefined, emptySymbols, emptyArray, emptyArray, emptyArray); + emptyJsxObjectType.objectFlags |= ObjectFlags.JsxAttributes; + + var emptyTypeLiteralSymbol = createSymbol(SymbolFlags.TypeLiteral, InternalSymbolName.Type); + emptyTypeLiteralSymbol.members = createSymbolTable(); + var emptyTypeLiteralType = createAnonymousType(emptyTypeLiteralSymbol, emptySymbols, emptyArray, emptyArray, emptyArray); + + var unknownEmptyObjectType = createAnonymousType(/*symbol*/ undefined, emptySymbols, emptyArray, emptyArray, emptyArray); + var unknownUnionType = strictNullChecks ? getUnionType([undefinedType, nullType, unknownEmptyObjectType]) : unknownType; + + var emptyGenericType = createAnonymousType(/*symbol*/ undefined, emptySymbols, emptyArray, emptyArray, emptyArray) as ObjectType as GenericType; + emptyGenericType.instantiations = new Map(); + + var anyFunctionType = createAnonymousType(/*symbol*/ 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; + + var noConstraintType = createAnonymousType(/*symbol*/ undefined, emptySymbols, emptyArray, emptyArray, emptyArray); + var circularConstraintType = createAnonymousType(/*symbol*/ undefined, emptySymbols, emptyArray, emptyArray, emptyArray); + var resolvingDefaultType = createAnonymousType(/*symbol*/ undefined, emptySymbols, emptyArray, emptyArray, emptyArray); + + var markerSuperType = createTypeParameter(); + var markerSubType = createTypeParameter(); + markerSubType.constraint = markerSuperType; + var markerOtherType = createTypeParameter(); + + var markerSuperTypeForCheck = createTypeParameter(); + var markerSubTypeForCheck = createTypeParameter(); + markerSubTypeForCheck.constraint = markerSuperTypeForCheck; + + var noTypePredicate = createTypePredicate(TypePredicateKind.Identifier, "<>", 0, anyType); + + var anySignature = createSignature(/*declaration*/ undefined, /*typeParameters*/ undefined, /*thisParameter*/ undefined, emptyArray, anyType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); + var unknownSignature = createSignature(/*declaration*/ undefined, /*typeParameters*/ undefined, /*thisParameter*/ undefined, emptyArray, errorType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); + var resolvingSignature = createSignature(/*declaration*/ undefined, /*typeParameters*/ undefined, /*thisParameter*/ undefined, emptyArray, anyType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); + var silentNeverSignature = createSignature(/*declaration*/ undefined, /*typeParameters*/ undefined, /*thisParameter*/ undefined, emptyArray, silentNeverType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); + + var enumNumberIndexInfo = createIndexInfo(numberType, stringType, /*isReadonly*/ true); + + var iterationTypesCache = new Map(); // cache for common IterationTypes instances + var 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"); + }, + }; + + var anyIterationTypes = createIterationTypes(anyType, anyType, anyType); + var anyIterationTypesExceptNext = createIterationTypes(anyType, anyType, unknownType); + var defaultIterationTypes = createIterationTypes(neverType, anyType, undefinedType); // default iteration types for `Iterator`. + + var asyncIterationTypesResolver: IterationTypesResolver = { + iterableCacheKey: "iterationTypesOfAsyncIterable", + iteratorCacheKey: "iterationTypesOfAsyncIterator", + iteratorSymbolName: "asyncIterator", + getGlobalIteratorType: getGlobalAsyncIteratorType, + getGlobalIterableType: getGlobalAsyncIterableType, + getGlobalIterableIteratorType: getGlobalAsyncIterableIteratorType, + getGlobalGeneratorType: getGlobalAsyncGeneratorType, + resolveIterationType: (type, errorNode) => getAwaitedType(type, errorNode, Diagnostics.Type_of_await_operand_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member), + 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, + }; + + var 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: Map; + } + /** Key is "/path/to/a.ts|/path/to/b.ts". */ + var amalgamatedDuplicates: Map | undefined; + var reverseMappedCache = new Map(); + var 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. + */ + var patternAmbientModules: PatternAmbientModule[]; + var patternAmbientModuleAugmentations: Map | undefined; + + var globalObjectType: ObjectType; + var globalFunctionType: ObjectType; + var globalCallableFunctionType: ObjectType; + var globalNewableFunctionType: ObjectType; + var globalArrayType: GenericType; + var globalReadonlyArrayType: GenericType; + var globalStringType: ObjectType; + var globalNumberType: ObjectType; + var globalBooleanType: ObjectType; + var globalRegExpType: ObjectType; + var globalThisType: GenericType; + var anyArrayType: Type; + var autoArrayType: Type; + var anyReadonlyArrayType: Type; + var 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 + var deferredGlobalESSymbolConstructorSymbol: Symbol | undefined; + var deferredGlobalESSymbolConstructorTypeSymbol: Symbol | undefined; + var deferredGlobalESSymbolType: ObjectType | undefined; + var deferredGlobalTypedPropertyDescriptorType: GenericType; + var deferredGlobalPromiseType: GenericType | undefined; + var deferredGlobalPromiseLikeType: GenericType | undefined; + var deferredGlobalPromiseConstructorSymbol: Symbol | undefined; + var deferredGlobalPromiseConstructorLikeType: ObjectType | undefined; + var deferredGlobalIterableType: GenericType | undefined; + var deferredGlobalIteratorType: GenericType | undefined; + var deferredGlobalIterableIteratorType: GenericType | undefined; + var deferredGlobalGeneratorType: GenericType | undefined; + var deferredGlobalIteratorYieldResultType: GenericType | undefined; + var deferredGlobalIteratorReturnResultType: GenericType | undefined; + var deferredGlobalAsyncIterableType: GenericType | undefined; + var deferredGlobalAsyncIteratorType: GenericType | undefined; + var deferredGlobalAsyncIterableIteratorType: GenericType | undefined; + var deferredGlobalAsyncGeneratorType: GenericType | undefined; + var deferredGlobalTemplateStringsArrayType: ObjectType | undefined; + var deferredGlobalImportMetaType: ObjectType; + var deferredGlobalImportMetaExpressionType: ObjectType; + var deferredGlobalImportCallOptionsType: ObjectType | undefined; + var deferredGlobalImportAttributesType: ObjectType | undefined; + var deferredGlobalDisposableType: ObjectType | undefined; + var deferredGlobalAsyncDisposableType: ObjectType | undefined; + var deferredGlobalExtractSymbol: Symbol | undefined; + var deferredGlobalOmitSymbol: Symbol | undefined; + var deferredGlobalAwaitedSymbol: Symbol | undefined; + var deferredGlobalBigIntType: ObjectType | undefined; + var deferredGlobalNaNSymbol: Symbol | undefined; + var deferredGlobalRecordSymbol: Symbol | undefined; + var deferredGlobalClassDecoratorContextType: GenericType | undefined; + var deferredGlobalClassMethodDecoratorContextType: GenericType | undefined; + var deferredGlobalClassGetterDecoratorContextType: GenericType | undefined; + var deferredGlobalClassSetterDecoratorContextType: GenericType | undefined; + var deferredGlobalClassAccessorDecoratorContextType: GenericType | undefined; + var deferredGlobalClassAccessorDecoratorTargetType: GenericType | undefined; + var deferredGlobalClassAccessorDecoratorResultType: GenericType | undefined; + var deferredGlobalClassFieldDecoratorContextType: GenericType | undefined; + + var allPotentiallyUnusedIdentifiers = new Map(); // key is file name + + var flowLoopStart = 0; + var flowLoopCount = 0; + var sharedFlowCount = 0; + var flowAnalysisDisabled = false; + var flowInvocationCount = 0; + var lastFlowNode: FlowNode | undefined; + var lastFlowNodeReachable: boolean; + var flowTypeCache: Type[] | undefined; + + var contextualTypeNodes: Node[] = []; + var contextualTypes: (Type | undefined)[] = []; + var contextualIsCache: boolean[] = []; + var contextualTypeCount = 0; + + var inferenceContextNodes: Node[] = []; + var inferenceContexts: (InferenceContext | undefined)[] = []; + var inferenceContextCount = 0; + + var emptyStringType = getStringLiteralType(""); + var zeroType = getNumberLiteralType(0); + var zeroBigIntType = getBigIntLiteralType({ negative: false, base10Value: "0" }); + + var resolutionTargets: TypeSystemEntity[] = []; + var resolutionResults: boolean[] = []; + var resolutionPropertyNames: TypeSystemPropertyName[] = []; + var resolutionStart = 0; + var inVarianceComputation = false; + + var suggestionCount = 0; + var maximumSuggestionCount = 10; + var mergedSymbols: Symbol[] = []; + var symbolLinks: SymbolLinks[] = []; + var nodeLinks: NodeLinks[] = []; + var flowLoopCaches: Map[] = []; + var flowLoopNodes: FlowNode[] = []; + var flowLoopKeys: string[] = []; + var flowLoopTypes: Type[][] = []; + var sharedFlowNodes: FlowNode[] = []; + var sharedFlowTypes: FlowType[] = []; + var flowNodeReachable: (boolean | undefined)[] = []; + var flowNodePostSuper: (boolean | undefined)[] = []; + var potentialThisCollisions: Node[] = []; + var potentialNewTargetCollisions: Node[] = []; + var potentialWeakMapSetCollisions: Node[] = []; + var potentialReflectCollisions: Node[] = []; + var potentialUnusedRenamedBindingElementsInTypes: BindingElement[] = []; + var awaitedTypeStack: number[] = []; + var reverseMappedSourceStack: Type[] = []; + var reverseMappedTargetStack: Type[] = []; + var reverseExpandingFlags = ExpandingFlags.None; + + var diagnostics = createDiagnosticCollection(); + var suggestionDiagnostics = createDiagnosticCollection(); + + var typeofType = createTypeofType(); + + var _jsxNamespace: __String; + var _jsxFactoryEntity: EntityName | undefined; + + var subtypeRelation = new Map(); + var strictSubtypeRelation = new Map(); + var assignableRelation = new Map(); + var comparableRelation = new Map(); + var identityRelation = new Map(); + var enumRelation = new Map(); + + // 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". + var 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"], + ]; + /* eslint-enable no-var */ + + initializeTypeChecker(); + + return checker; + + function isDefinitelyReferenceToGlobalSymbolObject(node: Node): boolean { + if (!isPropertyAccessExpression(node)) return false; + if (!isIdentifier(node.name)) return false; + if (!isPropertyAccessExpression(node.expression) && !isIdentifier(node.expression)) return false; + if (isIdentifier(node.expression)) { + // Exactly `Symbol.something` and `Symbol` either does not resolve or definitely resolves to the global Symbol + return idText(node.expression) === "Symbol" && getResolvedSymbol(node.expression) === (getGlobalSymbol("Symbol" as __String, SymbolFlags.Value | SymbolFlags.ExportValue, /*diagnostic*/ undefined) || unknownSymbol); + } + if (!isIdentifier(node.expression.expression)) return false; + // Exactly `globalThis.Symbol.something` and `globalThis` resolves to the global `globalThis` + return idText(node.expression.name) === "Symbol" && idText(node.expression.expression) === "globalThis" && getResolvedSymbol(node.expression.expression) === globalThisSymbol; + } + + 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 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, isEntityName); + 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; + } + } + else { + const localJsxNamespace = getLocalJsxNamespace(file); + if (localJsxNamespace) { + return file.localJsxNamespace = localJsxNamespace; + } + } + } + } + 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); + } + } + 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, isEntityName); + if (file.localJsxFactory) { + return file.localJsxNamespace = getFirstIdentifier(file.localJsxFactory).escapedText; + } + } + } + + function markAsSynthetic(node: T): VisitResult { + setTextRangePosEnd(node, -1, -1); + return visitEachChildWorker(node, markAsSynthetic, /*context*/ undefined); + } + + function getEmitResolver(sourceFile: SourceFile, cancellationToken: CancellationToken, skipDiagnostics?: boolean) { + // 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. + if (!skipDiagnostics) getDiagnostics(sourceFile, cancellationToken); + return emitResolver; + } + + function lookupOrIssueError(location: Node | undefined, message: DiagnosticMessage, ...args: DiagnosticArguments): Diagnostic { + const diagnostic = location + ? createDiagnosticForNode(location, message, ...args) + : createCompilerDiagnostic(message, ...args); + const existing = diagnostics.lookup(diagnostic); + if (existing) { + return existing; + } + else { + diagnostics.add(diagnostic); + return diagnostic; + } + } + + function errorSkippedOn(key: keyof CompilerOptions, location: Node | undefined, message: DiagnosticMessage, ...args: DiagnosticArguments): Diagnostic { + const diagnostic = error(location, message, ...args); + diagnostic.skippedOn = key; + return diagnostic; + } + + function createError(location: Node | undefined, message: DiagnosticMessage, ...args: DiagnosticArguments): Diagnostic { + return location + ? createDiagnosticForNode(location, message, ...args) + : createCompilerDiagnostic(message, ...args); + } + + function error(location: Node | undefined, message: DiagnosticMessage, ...args: DiagnosticArguments): Diagnostic { + const diagnostic = createError(location, message, ...args); + diagnostics.add(diagnostic); + return diagnostic; + } + + function addErrorOrSuggestion(isError: boolean, diagnostic: Diagnostic) { + if (isError) { + diagnostics.add(diagnostic); + } + else { + suggestionDiagnostics.add({ ...diagnostic, category: DiagnosticCategory.Suggestion }); + } + } + function errorOrSuggestion(isError: boolean, location: Node, message: DiagnosticMessage | DiagnosticMessageChain, ...args: DiagnosticArguments): 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, ...args) : createDiagnosticForFileFromMessageChain(file, message)); // eslint-disable-line local/no-in-operator + return; + } + addErrorOrSuggestion(isError, "message" in message ? createDiagnosticForNode(location, message, ...args) : createDiagnosticForNodeFromMessageChain(getSourceFileOfNode(location), location, message)); // eslint-disable-line local/no-in-operator + } + + function errorAndMaybeSuggestAwait( + location: Node, + maybeMissingAwait: boolean, + message: DiagnosticMessage, + ...args: DiagnosticArguments + ): Diagnostic { + const diagnostic = error(location, message, ...args); + if (maybeMissingAwait) { + const related = createDiagnosticForNode(location, Diagnostics.Did_you_forget_to_use_await); + addRelatedInfo(diagnostic, related); + } + return diagnostic; + } + + 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 isDeprecatedSymbol(symbol: Symbol) { + const parentSymbol = getParentOfSymbol(symbol); + if (parentSymbol && length(symbol.declarations) > 1) { + return parentSymbol.flags & SymbolFlags.Interface ? some(symbol.declarations, isDeprecatedDeclaration) : every(symbol.declarations, isDeprecatedDeclaration); + } + return !!symbol.valueDeclaration && isDeprecatedDeclaration(symbol.valueDeclaration) + || length(symbol.declarations) && every(symbol.declarations, isDeprecatedDeclaration); + } + + function isDeprecatedDeclaration(declaration: Declaration) { + return !!(getCombinedNodeFlagsCached(declaration) & NodeFlags.Deprecated); + } + + function addDeprecatedSuggestion(location: Node, declarations: Node[], deprecatedEntity: string) { + const diagnostic = createDiagnosticForNode(location, Diagnostics._0_is_deprecated, deprecatedEntity); + return addDeprecatedSuggestionWorker(declarations, diagnostic); + } + + 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 createSymbol(flags: SymbolFlags, name: __String, checkFlags?: CheckFlags) { + symbolCount++; + const symbol = new Symbol(flags | SymbolFlags.Transient, name) as TransientSymbol; + symbol.links = new SymbolLinks() as TransientSymbolLinks; + symbol.links.checkFlags = checkFlags || CheckFlags.None; + return symbol; + } + + function createParameter(name: __String, type: Type) { + const symbol = createSymbol(SymbolFlags.FunctionScopedVariable, name); + symbol.links.type = type; + return symbol; + } + + function createProperty(name: __String, type: Type) { + const symbol = createSymbol(SymbolFlags.Property, name); + symbol.links.type = type; + return symbol; + } + + 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 recordMergedSymbol(target: Symbol, source: Symbol) { + if (!source.mergeId) { + source.mergeId = nextMergeId; + nextMergeId++; + } + mergedSymbols[source.mergeId] = target; + } + + function cloneSymbol(symbol: Symbol): TransientSymbol { + 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; + } + + /** + * 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; + } + if ( + !(resolvedTarget.flags & getExcludedSymbolFlags(source.flags)) || + (source.flags | resolvedTarget.flags) & SymbolFlags.Assignment + ) { + target = cloneSymbol(resolvedTarget); + } + else { + reportMergeSymbolError(target, source); + return source; + } + } + // 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 { + reportMergeSymbolError(target, source); + } + return target; + + function reportMergeSymbolError(target: Symbol, source: Symbol) { + 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}`, (): DuplicateInfoForFiles => ({ firstFile, secondFile, conflictingSymbols: new Map() })); + const conflictingSymbolInfo = getOrUpdate(filesDuplicates.conflictingSymbols, symbolName, (): DuplicateInfoForSymbol => ({ isBlockScoped: isEitherBlockScoped, firstFileLocations: [], secondFileLocations: [] })); + 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); + } + } + + 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 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 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; + } + + 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); + } + 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); + } + } + 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 addUndefinedToGlobalsOrErrorOnRedeclaration() { + const name = undefinedSymbol.escapedName; + const targetSymbol = globals.get(name); + if (targetSymbol) { + forEach(targetSymbol.declarations, declaration => { + // checkTypeNameIsReserved will have added better diagnostics for type declarations. + if (!isTypeDeclaration(declaration)) { + diagnostics.add(createDiagnosticForNode(declaration, Diagnostics.Declaration_name_conflicts_with_built_in_global_identifier_0, unescapeLeadingUnderscores(name))); + } + }); + } + else { + globals.set(name, undefinedSymbol); + } + } + + function getSymbolLinks(symbol: Symbol): SymbolLinks { + if (symbol.flags & SymbolFlags.Transient) return (symbol as TransientSymbol).links; + const id = getSymbolId(symbol); + return symbolLinks[id] ??= new SymbolLinks(); + } + + 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) { + if (symbol.flags & meaning) { + return symbol; + } + if (symbol.flags & SymbolFlags.Alias) { + const targetFlags = getSymbolFlags(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. + } + + /** + * 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: ParameterPropertyDeclaration, 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); + + if (parameterSymbol && propertySymbol) { + return [parameterSymbol, propertySymbol]; + } + + return Debug.fail("There should exist two symbols, one as property declaration and one as parameter declaration"); + } + + 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)) || + (!compilerOptions.outFile) || + 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); + } + + // deferred usage in a type context is always OK regardless of the usage position: + if (!!(usage.flags & NodeFlags.JSDoc) || isInTypeQuery(usage) || isInAmbientOrTypeNode(usage)) { + 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); + } + 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 (isClassLike(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]() {} }) + // or when used within a decorator in the class (e.g. `@dec(A.x) class A { static x = "x" }`), + // except when used in a function that is not an IIFE (e.g., `@dec(() => A.x) class A { ... }`) + const container = findAncestor(usage, n => + n === declaration ? "quit" : + isComputedPropertyName(n) ? n.parent.parent === declaration : + !legacyDecorators && isDecorator(n) && (n.parent === declaration || + isMethodDeclaration(n.parent) && n.parent.parent === declaration || + isGetOrSetAccessorDeclaration(n.parent) && n.parent.parent === declaration || + isPropertyDeclaration(n.parent) && n.parent.parent === declaration || + isParameter(n.parent) && n.parent.parent.parent === declaration)); + if (!container) { + return true; + } + if (!legacyDecorators && isDecorator(container)) { + return !!findAncestor(usage, n => n === container ? "quit" : isFunctionLike(n) && !getImmediatelyInvokedFunctionExpression(n)); + } + return false; + } + 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 emitStandardClassFields when bar is a parameter property + return !(emitStandardClassFields + && getContainingClass(declaration) === getContainingClass(usage) + && isUsedInFunctionOrInstanceProperty(usage, declaration)); + } + return true; + } + + // 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 emitStandardClassFields: 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) + 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 (isUsedInFunctionOrInstanceProperty(usage, declaration)) { + if ( + emitStandardClassFields + && getContainingClass(declaration) + && (isPropertyDeclaration(declaration) || isParameterPropertyDeclaration(declaration, declaration.parent)) + ) { + return !isPropertyImmediatelyReferencedWithinDeclaration(declaration, usage, /*stopAtAnyPropertyDeclaration*/ true); + } + else { + return true; + } + } + return false; + + 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); + } + + 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(getSymbolOfDeclaration(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; + } + } + } + } + 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; + } + } + + function getRequiresScopeChangeCache(node: FunctionLikeDeclaration) { + return getNodeLinks(node).declarationRequiresScopeChange; + } + function setRequiresScopeChangeCache(node: FunctionLikeDeclaration, value: boolean) { + getNodeLinks(node).declarationRequiresScopeChange = value; + } + // 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(errorLocation: Node | undefined, name: __String, propertyWithInvalidInitializer: PropertyDeclaration, result: Symbol | undefined) { + if (!emitStandardClassFields) { + if (errorLocation && !result && checkAndReportErrorForMissingPrefix(errorLocation, name, name)) { + return true; + } + // 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 emitStandardClassFields 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(name), + ); + return true; + } + return false; + } + function onFailedToResolveSymbol( + errorLocation: Node | undefined, + nameArg: __String | Identifier, + meaning: SymbolFlags, + nameNotFoundMessage: DiagnosticMessage, + ) { + const name = isString(nameArg) ? nameArg : (nameArg as Identifier).escapedText; + addLazyDiagnostic(() => { + if ( + !errorLocation || + errorLocation.parent.kind !== SyntaxKind.JSDocLink && + !checkAndReportErrorForMissingPrefix(errorLocation, name, nameArg) && + !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 && suggestionCount < maximumSuggestionCount) { + suggestion = getSuggestedSymbolForNonexistentSymbol(errorLocation, 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(errorLocation, 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); + diagnostic.canonicalHead = getCanonicalDiagnostic(nameNotFoundMessage, diagnosticName(nameArg)); + 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++; + } + }); + } + + function onSuccessfullyResolvedSymbol( + errorLocation: Node | undefined, + result: Symbol, + meaning: SymbolFlags, + lastLocation: Node | undefined, + associatedDeclarationForContainingInitializerOrBindingName: ParameterDeclaration | BindingElement | undefined, + withinDeferredContext: boolean, + ) { + addLazyDiagnostic(() => { + const name = result.escapedName; + const isInExternalModule = lastLocation && isSourceFile(lastLocation) && isExternalOrCommonJsModule(lastLocation); + // 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 (isInExternalModule && (meaning & SymbolFlags.Value) === SymbolFlags.Value && !(errorLocation!.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 (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 === getSymbolOfDeclaration(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 && getSymbol(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 (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 || typeOnlyDeclaration.kind === SyntaxKind.ExportDeclaration || typeOnlyDeclaration.kind === SyntaxKind.NamespaceExport + ? 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, + ); + } + } + + // Look at 'compilerOptions.isolatedModules' and not 'getIsolatedModules(...)' (which considers 'verbatimModuleSyntax') + // here because 'verbatimModuleSyntax' will already have an error for importing a type without 'import type'. + if (compilerOptions.isolatedModules && result && isInExternalModule && (meaning & SymbolFlags.Value) === SymbolFlags.Value) { + const isGlobal = getSymbol(globals, name, meaning) === result; + const nonValueSymbol = isGlobal && isSourceFile(lastLocation) && lastLocation.locals && getSymbol(lastLocation.locals, name, ~SymbolFlags.Value); + if (nonValueSymbol) { + const importDecl = nonValueSymbol.declarations?.find(d => d.kind === SyntaxKind.ImportSpecifier || d.kind === SyntaxKind.ImportClause || d.kind === SyntaxKind.NamespaceImport || d.kind === SyntaxKind.ImportEqualsDeclaration); + if (importDecl && !isTypeOnlyImportDeclaration(importDecl)) { + error(importDecl, Diagnostics.Import_0_conflicts_with_global_value_used_in_this_file_so_must_be_declared_with_a_type_only_import_when_isolatedModules_is_enabled, unescapeLeadingUnderscores(name)); + } + } + } + }); + } + + function addTypeOnlyDeclarationRelatedInfo(diagnostic: Diagnostic, typeOnlyDeclaration: TypeOnlyCompatibleAliasDeclaration | undefined, unescapedName: string) { + if (!typeOnlyDeclaration) return diagnostic; + return addRelatedInfo( + diagnostic, + createDiagnosticForNode( + typeOnlyDeclaration, + typeOnlyDeclaration.kind === SyntaxKind.ExportSpecifier || typeOnlyDeclaration.kind === SyntaxKind.ExportDeclaration || typeOnlyDeclaration.kind === SyntaxKind.NamespaceExport + ? Diagnostics._0_was_exported_here + : Diagnostics._0_was_imported_here, + unescapedName, + ), + ); + } + + function diagnosticName(nameArg: __String | Identifier | PrivateIdentifier) { + return isString(nameArg) ? unescapeLeadingUnderscores(nameArg as __String) : declarationNameToString(nameArg as Identifier); + } + + 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, /*includeClassComputedPropertyName*/ false); + let location: Node = container; + while (location) { + if (isClassLike(location.parent)) { + const classSymbol = getSymbolOfDeclaration(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; + } + + // 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; + } + + 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; + } + /** + * 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, /*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; + } + } + + 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, /*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 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 checkAndReportErrorForUsingTypeAsValue(errorLocation: Node, name: __String, meaning: SymbolFlags): boolean { + if (meaning & SymbolFlags.Value) { + if (isPrimitiveTypeName(name)) { + const grandparent = errorLocation.parent.parent; + if (grandparent && grandparent.parent && isHeritageClause(grandparent)) { + const heritageKind = grandparent.token; + const containerKind = grandparent.parent.kind; + if (containerKind === SyntaxKind.InterfaceDeclaration && heritageKind === SyntaxKind.ExtendsKeyword) { + error(errorLocation, Diagnostics.An_interface_cannot_extend_a_primitive_type_like_0_It_can_only_extend_other_named_object_types, unescapeLeadingUnderscores(name)); + } + else if (containerKind === SyntaxKind.ClassDeclaration && heritageKind === SyntaxKind.ExtendsKeyword) { + error(errorLocation, Diagnostics.A_class_cannot_extend_a_primitive_type_like_0_Classes_can_only_extend_constructable_values, unescapeLeadingUnderscores(name)); + } + else if (containerKind === SyntaxKind.ClassDeclaration && heritageKind === SyntaxKind.ImplementsKeyword) { + error(errorLocation, Diagnostics.A_class_cannot_implement_a_primitive_type_like_0_It_can_only_implement_other_named_object_types, 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, /*isUse*/ false)); + const allFlags = symbol && getSymbolFlags(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; + } + } + 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 checkAndReportErrorForUsingNamespaceAsTypeOrValue(errorLocation: Node, name: __String, meaning: SymbolFlags): boolean { + if (meaning & (SymbolFlags.Value & ~SymbolFlags.Type)) { + const symbol = resolveSymbol(resolveName(errorLocation, name, SymbolFlags.NamespaceModule, /*nameNotFoundMessage*/ 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, /*isUse*/ false)); + if (symbol) { + error(errorLocation, Diagnostics.Cannot_use_namespace_0_as_a_type, unescapeLeadingUnderscores(name)); + return true; + } + } + 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), + ); + + 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) { + diagnosticMessage = error(errorLocation, Diagnostics.Enum_0_used_before_its_declaration, declarationName); + } + else { + Debug.assert(!!(result.flags & SymbolFlags.ConstEnum)); + if (getIsolatedModules(compilerOptions)) { + diagnosticMessage = error(errorLocation, Diagnostics.Enum_0_used_before_its_declaration, 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) || (getFunctionFlags(n) & FunctionFlags.AsyncGenerator)) ? "quit" : false)); + } + + function getAnyImportSyntax(node: Node): AnyImportOrJsDocImport | 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); + } + + /** + * 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; + } + + function checkAndReportErrorForResolvingImportAliasToTypeOnlySymbol(node: ImportEqualsDeclaration, resolved: Symbol | undefined) { + if (markSymbolOfAliasDeclarationIfTypeOnly(node, /*immediateTarget*/ undefined, resolved, /*overwriteEmpty*/ false) && !node.isTypeOnly) { + const typeOnlyDeclaration = getTypeOnlyAliasDeclaration(getSymbolOfDeclaration(node))!; + const isExport = typeOnlyDeclaration.kind === SyntaxKind.ExportSpecifier || typeOnlyDeclaration.kind === SyntaxKind.ExportDeclaration; + 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; + + // TODO: how to get name for export *? + const name = typeOnlyDeclaration.kind === SyntaxKind.ExportDeclaration ? "*" : moduleExportNameTextUnescaped(typeOnlyDeclaration.name); + addRelatedInfo(error(node.moduleReference, message), createDiagnosticForNode(typeOnlyDeclaration, relatedMessage, name)); + } + } + + 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, /*skipObjectFunctionPropertyAugment*/ true) + : moduleSymbol.exports!.get(name); + const resolved = resolveSymbol(exportSymbol, dontResolveAlias); + markSymbolOfAliasDeclarationIfTypeOnly(sourceNode, exportSymbol, resolved, /*overwriteEmpty*/ false); + return resolved; + } + + function isSyntacticDefault(node: Node) { + return ((isExportAssignment(node) && !node.isExportEquals) + || hasSyntacticModifier(node, ModifierFlags.Default) + || isExportSpecifier(node) + || isNamespaceExport(node)); + } + + function getUsageModeForExpression(usage: Expression) { + return isStringLiteralLike(usage) ? host.getModeForUsageLocation(getSourceFileOfNode(usage), usage) : undefined; + } + + function isESMFormatImportImportingCommonjsFormatFile(usageMode: ResolutionMode, targetMode: ResolutionMode) { + 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 && ModuleKind.Node16 <= moduleKind && moduleKind <= ModuleKind.NodeNext) { + 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 + } + 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; + } + // 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); + } + // 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 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) && !allowSyntheticDefaultImports) { + 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, /*overwriteEmpty*/ false); + return resolved; + } + markSymbolOfAliasDeclarationIfTypeOnly(node, exportDefaultSymbol, /*finalTarget*/ undefined, /*overwriteEmpty*/ false); + 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); + } + } + + 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 { + 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 getTargetOfNamespaceImport(node: NamespaceImport, dontResolveAlias: boolean): Symbol | undefined { + const moduleSpecifier = node.parent.parent.moduleSpecifier; + const immediate = resolveExternalModuleName(node, moduleSpecifier); + const resolved = resolveESModuleSymbol(immediate, moduleSpecifier, dontResolveAlias, /*suppressInteropError*/ 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, /*suppressInteropError*/ false); + markSymbolOfAliasDeclarationIfTypeOnly(node, immediate, resolved, /*overwriteEmpty*/ false); + return resolved; + } + + // 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); + Debug.assert(valueSymbol.declarations || typeSymbol.declarations); + 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 getExportOfModule(symbol: Symbol, nameText: __String, specifier: Declaration, dontResolveAlias: boolean): Symbol | undefined { + if (symbol.flags & SymbolFlags.Module) { + const exportSymbol = getExportsOfSymbol(symbol).get(nameText); + const resolved = resolveSymbol(exportSymbol, dontResolveAlias); + const exportStarDeclaration = getSymbolLinks(symbol).typeOnlyExportStarMap?.get(nameText); + markSymbolOfAliasDeclarationIfTypeOnly(specifier, exportSymbol, resolved, /*overwriteEmpty*/ false, exportStarDeclaration, nameText); + 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 getExternalModuleMember(node: ImportDeclaration | ExportDeclaration | VariableDeclaration | JSDocImportTag, specifier: ImportOrExportSpecifier | BindingElement | PropertyAccessExpression, dontResolveAlias = false): Symbol | undefined { + const moduleSpecifier = getExternalModuleRequireArgument(node) || (node as ImportDeclaration | ExportDeclaration | JSDocImportTag).moduleSpecifier!; + const moduleSymbol = resolveExternalModuleName(node, moduleSpecifier)!; // TODO: GH#18217 + const name = !isPropertyAccessExpression(specifier) && specifier.propertyName || specifier.name; + if (!isIdentifier(name) && name.kind !== SyntaxKind.StringLiteral) { + return undefined; + } + const nameText = moduleExportNameTextEscaped(name); + const suppressInteropError = nameText === InternalSymbolName.Default && allowSyntheticDefaultImports; + const targetSymbol = resolveESModuleSymbol(moduleSymbol, moduleSpecifier, /*dontResolveAlias*/ false, suppressInteropError); + if (targetSymbol) { + // Note: The empty string is a valid module export name: + // + // import { "" as foo } from "./foo"; + // export { foo as "" }; + // + if (nameText || name.kind === SyntaxKind.StringLiteral) { + if (isShorthandAmbientModuleSymbol(moduleSymbol)) { + return moduleSymbol; + } + + 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), nameText, /*skipObjectFunctionPropertyAugment*/ true); + } + else { + symbolFromVariable = getPropertyOfVariable(targetSymbol, nameText); + } + // if symbolFromVariable is export - get its final target + symbolFromVariable = resolveSymbol(symbolFromVariable, dontResolveAlias); + + let symbolFromModule = getExportOfModule(targetSymbol, nameText, specifier, dontResolveAlias); + if (symbolFromModule === undefined && nameText === 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: ModuleExportName) { + const moduleName = getFullyQualifiedName(moduleSymbol, node); + const declarationName = declarationNameToString(name); + const suggestion = isIdentifier(name) ? getSuggestedSymbolForNonexistentModule(name, targetSymbol) : undefined; + 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 { + reportNonExportedMember(node, name, declarationName, moduleSymbol, moduleName); + } + } + } + + function reportNonExportedMember(node: Node, name: ModuleExportName, declarationName: string, moduleSymbol: Symbol, moduleName: string): void { + const localSymbol = tryCast(moduleSymbol.valueDeclaration, canHaveLocals)?.locals?.get(moduleExportNameTextEscaped(name)); + 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))); + } + } + } + else { + error(name, Diagnostics.Module_0_has_no_exported_member_1, moduleName, declarationName); + } + } + + function reportInvalidImportEqualsExportMember(node: Node, name: ModuleExportName, 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); + } + 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); + } + } + } + + function getTargetOfImportSpecifier(node: ImportSpecifier | BindingElement, dontResolveAlias: boolean): Symbol | undefined { + if (isImportSpecifier(node) && moduleExportNameIsDefault(node.propertyName || node.name)) { + const specifier = getModuleSpecifierForImportOrExport(node); + const moduleSymbol = specifier && resolveExternalModuleName(node, specifier); + if (moduleSymbol) { + return getTargetofModuleDefault(moduleSymbol, node, dontResolveAlias); + } + } + 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 getCommonJSPropertyAccess(node: Node) { + if (isVariableDeclaration(node) && node.initializer && isPropertyAccessExpression(node.initializer)) { + return node.initializer; + } + } + + function getTargetOfNamespaceExportDeclaration(node: NamespaceExportDeclaration, dontResolveAlias: boolean): Symbol | undefined { + if (canHaveSymbol(node.parent)) { + const resolved = resolveExternalModuleSymbol(node.parent.symbol, dontResolveAlias); + markSymbolOfAliasDeclarationIfTypeOnly(node, /*immediateTarget*/ undefined, resolved, /*overwriteEmpty*/ false); + return resolved; + } + } + + function getTargetOfExportSpecifier(node: ExportSpecifier, meaning: SymbolFlags, dontResolveAlias?: boolean) { + const name = node.propertyName || node.name; + if (moduleExportNameIsDefault(name)) { + 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) : + name.kind === SyntaxKind.StringLiteral ? undefined : // Skip for invalid syntax like this: export { "x" } + resolveEntityName(name, meaning, /*ignoreErrors*/ false, dontResolveAlias); + markSymbolOfAliasDeclarationIfTypeOnly(node, /*immediateTarget*/ undefined, resolved, /*overwriteEmpty*/ false); + return resolved; + } + + 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 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; + } + checkExpressionCached(expression); + return getNodeLinks(expression).resolvedSymbol; + } + + function getTargetOfAccessExpression(node: AccessExpression, dontRecursivelyResolve: boolean): Symbol | undefined { + if (!(isBinaryExpression(node.parent) && node.parent.left === node && node.parent.operatorToken.kind === SyntaxKind.EqualsToken)) { + return undefined; + } + + 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(); + } + } + + /** + * 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 { + error(node, Diagnostics.Circular_definition_of_import_alias_0, symbolToString(symbol)); + } + } + 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; + } + + /** + * 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 getSymbolFlags(symbol: Symbol, excludeTypeOnlyMeanings?: boolean, excludeLocalMeanings?: boolean): SymbolFlags { + const typeOnlyDeclaration = excludeTypeOnlyMeanings && getTypeOnlyAliasDeclaration(symbol); + const typeOnlyDeclarationIsExportStar = typeOnlyDeclaration && isExportDeclaration(typeOnlyDeclaration); + const typeOnlyResolution = typeOnlyDeclaration && ( + typeOnlyDeclarationIsExportStar + ? resolveExternalModuleName(typeOnlyDeclaration.moduleSpecifier, typeOnlyDeclaration.moduleSpecifier, /*ignoreErrors*/ true) + : resolveAlias(typeOnlyDeclaration.symbol) + ); + const typeOnlyExportStarTargets = typeOnlyDeclarationIsExportStar && typeOnlyResolution ? getExportsOfModule(typeOnlyResolution) : undefined; + let flags = excludeLocalMeanings ? SymbolFlags.None : symbol.flags; + let seenSymbols; + while (symbol.flags & SymbolFlags.Alias) { + const target = getExportSymbolOfValueSymbolIfExported(resolveAlias(symbol)); + if ( + !typeOnlyDeclarationIsExportStar && target === typeOnlyResolution || + typeOnlyExportStarTargets?.get(target.escapedName) === target + ) { + break; + } + if (target === unknownSymbol) { + return SymbolFlags.All; + } + + // 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); + } + 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, + exportStarDeclaration?: ExportDeclaration & { readonly isTypeOnly: true; readonly moduleSpecifier: Expression; }, + exportStarName?: __String, + ): 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 = getSymbolOfDeclaration(aliasDeclaration); + if (isTypeOnlyImportOrExportDeclaration(aliasDeclaration)) { + const links = getSymbolLinks(sourceSymbol); + links.typeOnlyDeclaration = aliasDeclaration; + return true; + } + if (exportStarDeclaration) { + const links = getSymbolLinks(sourceSymbol); + links.typeOnlyDeclaration = exportStarDeclaration; + if (sourceSymbol.escapedName !== exportStarName) { + links.typeOnlyExportStarName = exportStarName; + } + return true; + } + + 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 (links.typeOnlyDeclaration === undefined) { + // We need to set a WIP value here to prevent reentrancy during `getImmediateAliasedSymbol` which, paradoxically, can depend on this + links.typeOnlyDeclaration = false; + const resolved = resolveSymbol(symbol); // do this before the `resolveImmediate` below, as it uses a different circularity cache and we might hide a circularity error if we blindly get the immediate alias first + // While usually the alias will have been marked during the pass by the full typecheck, we may still need to calculate the alias declaration now + markSymbolOfAliasDeclarationIfTypeOnly(symbol.declarations?.[0], getDeclarationOfAliasSymbol(symbol) && getImmediateAliasedSymbol(symbol), resolved, /*overwriteEmpty*/ true); + } + if (include === undefined) { + return links.typeOnlyDeclaration || undefined; + } + if (links.typeOnlyDeclaration) { + const resolved = links.typeOnlyDeclaration.kind === SyntaxKind.ExportDeclaration + ? resolveSymbol(getExportsOfModule(links.typeOnlyDeclaration.symbol.parent!).get(links.typeOnlyExportStarName || symbol.escapedName))! + : resolveAlias(links.typeOnlyDeclaration.symbol); + return getSymbolFlags(resolved) & include ? links.typeOnlyDeclaration : undefined; + } + return undefined; + } + + // 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 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, SymbolFlags.Value, /*nameNotFoundMessage*/ undefined, /*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; + } + 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)) { + 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, meaning, ignoreErrors || symbolFromJSPrototype ? undefined : message, /*isUse*/ true, /*excludeGlobals*/ 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; + } + else if (namespace === unknownSymbol) { + return namespace; + } + if ( + namespace.valueDeclaration && + isInJSFile(namespace.valueDeclaration) && + getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.Bundler && + 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 && (namespace.flags & SymbolFlags.Alias)) { + // `namespace` can be resolved further if there was a symbol merge with a re-export + symbol = getMergedSymbol(getSymbol(getExportsOfSymbol(resolveAlias(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; + } + + 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; + } + } + else { + Debug.assertNever(name, "Unknown entity name kind."); + } + 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, meaning, /*nameNotFoundMessage*/ undefined, /*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 = getSymbolOfDeclaration(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 = getSymbolOfDeclaration(host.parent.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 = getSymbolOfDeclaration(host.parent.parent.left as BindableStaticNameExpression); + if (symbol) { + return getDeclarationOfJSPrototypeContainer(symbol); + } + } + const sig = getEffectiveJSDocHost(node); + if (sig && isFunctionLike(sig)) { + const symbol = getSymbolOfDeclaration(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; + } + + /** + * 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_nodenext_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 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 + : (isModuleDeclaration(location) ? location : location.parent && isModuleDeclaration(location.parent) && location.parent.name === location ? location.parent : undefined)?.name || + (isLiteralImportTypeNode(location) ? location : undefined)?.argument.literal || + (isInJSFile(location) && isJSDocImportTag(location) ? location.moduleSpecifier : undefined) || + (isVariableDeclaration(location) && location.initializer && isRequireCall(location.initializer, /*requireStringLiteralLikeArgument*/ true) ? location.initializer.arguments[0] : undefined) || + findAncestor(location, isImportCall)?.arguments[0] || + findAncestor(location, isImportDeclaration)?.moduleSpecifier || + findAncestor(location, isExternalModuleImportEqualsDeclaration)?.moduleReference.expression || + findAncestor(location, isExportDeclaration)?.moduleSpecifier; + const mode = contextSpecifier && isStringLiteralLike(contextSpecifier) ? host.getModeForUsageLocation(currentSourceFile, contextSpecifier) : currentSourceFile.impliedNodeFormat; + const moduleResolutionKind = getEmitModuleResolutionKind(compilerOptions); + const resolvedModule = host.getResolvedModule(currentSourceFile, moduleReference, mode)?.resolvedModule; + const resolutionDiagnostic = resolvedModule && getResolutionDiagnostic(compilerOptions, resolvedModule, currentSourceFile); + 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 (resolvedModule.resolvedUsingTsExtension && isDeclarationFileName(moduleReference)) { + const importOrExport = findAncestor(location, isImportDeclaration)?.importClause || + findAncestor(location, or(isImportEqualsDeclaration, isExportDeclaration)); + if (importOrExport && !importOrExport.isTypeOnly || findAncestor(location, isImportCall)) { + error( + errorNode, + Diagnostics.A_declaration_file_cannot_be_imported_without_import_type_Did_you_mean_to_import_an_implementation_file_0_instead, + getSuggestedImportSource(Debug.checkDefined(tryExtractTSExtension(moduleReference))), + ); + } + } + else if (resolvedModule.resolvedUsingTsExtension && !shouldAllowImportingTsExtension(compilerOptions, currentSourceFile.fileName)) { + const importOrExport = findAncestor(location, isImportDeclaration)?.importClause || + findAncestor(location, or(isImportEqualsDeclaration, isExportDeclaration)); + if (!(importOrExport?.isTypeOnly || findAncestor(location, isImportTypeNode))) { + const tsExtension = Debug.checkDefined(tryExtractTSExtension(moduleReference)); + error(errorNode, Diagnostics.An_import_path_can_only_end_with_a_0_extension_when_allowImportingTsExtensions_is_enabled, tsExtension); + } + } + + if (sourceFile.symbol) { + if (resolvedModule.isExternalLibraryImport && !resolutionExtensionIsTSOrJson(resolvedModule.extension)) { + errorOnImplicitAnyModule(/*isError*/ false, errorNode, currentSourceFile, mode, resolvedModule, moduleReference); + } + if (moduleResolutionKind === ModuleResolutionKind.Node16 || moduleResolutionKind === ModuleResolutionKind.NodeNext) { + const isSyncImport = (currentSourceFile.impliedNodeFormat === ModuleKind.CommonJS && !findAncestor(location, isImportCall)) || !!findAncestor(location, isImportEqualsDeclaration); + const overrideHost = findAncestor(location, l => isImportTypeNode(l) || isExportDeclaration(l) || isImportDeclaration(l)) as ImportTypeNode | ImportDeclaration | ExportDeclaration | undefined; + // 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 && !hasResolutionModeOverride(overrideHost)) { + 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 { + 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( + getSourceFileOfNode(errorNode), + 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; + } + + 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, currentSourceFile, mode, 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; + } + } + + if (resolutionDiagnostic) { + error(errorNode, resolutionDiagnostic, moduleReference, resolvedModule.resolvedFileName); + } + else { + const isExtensionlessRelativePathImport = pathIsRelative(moduleReference) && !hasExtension(moduleReference); + const resolutionIsNode16OrNext = moduleResolutionKind === ModuleResolutionKind.Node16 || + moduleResolutionKind === ModuleResolutionKind.NodeNext; + if ( + !getResolveJsonModule(compilerOptions) && + fileExtensionIs(moduleReference, Extension.Json) && + moduleResolutionKind !== 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 (host.getResolvedModule(currentSourceFile, moduleReference, mode)?.alternateResult) { + const errorInfo = createModuleNotFoundChain(currentSourceFile, host, moduleReference, mode, moduleReference); + errorOrSuggestion(/*isError*/ true, errorNode, chainDiagnosticMessages(errorInfo, moduleNotFoundError, moduleReference)); + } + else { + error(errorNode, moduleNotFoundError, moduleReference); + } + } + } + } + return undefined; + + function getSuggestedImportSource(tsExtension: string) { + const importSourceWithoutExtension = removeExtension(moduleReference, tsExtension); + /** + * Direct users to import source with .js extension if outputting an ES module. + * @see https://github.com/microsoft/TypeScript/issues/42151 + */ + if (emitModuleKindIsNonNodeESM(moduleKind) || mode === ModuleKind.ESNext) { + const preferTs = isDeclarationFileName(moduleReference) && shouldAllowImportingTsExtension(compilerOptions); + const ext = tsExtension === Extension.Mts || tsExtension === Extension.Dmts ? preferTs ? ".mts" : ".mjs" : + tsExtension === Extension.Cts || tsExtension === Extension.Dmts ? preferTs ? ".cts" : ".cjs" : + preferTs ? ".ts" : ".js"; + return importSourceWithoutExtension + ext; + } + return importSourceWithoutExtension; + } + } + + function errorOnImplicitAnyModule(isError: boolean, errorNode: Node, sourceFile: SourceFile, mode: ResolutionMode, { packageId, resolvedFileName }: ResolvedModuleFull, moduleReference: string): void { + let errorInfo: DiagnosticMessageChain | undefined; + if (!isExternalModuleNameRelative(moduleReference) && packageId) { + errorInfo = createModuleNotFoundChain(sourceFile, host, moduleReference, mode, packageId.name); + } + errorOrSuggestion( + isError, + errorNode, + chainDiagnosticMessages( + errorInfo, + Diagnostics.Could_not_find_a_declaration_file_for_module_0_1_implicitly_has_an_any_type, + moduleReference, + resolvedFileName, + ), + ); + } + + function resolveExternalModuleSymbol(moduleSymbol: Symbol, dontResolveAlias?: boolean): Symbol; + function resolveExternalModuleSymbol(moduleSymbol: Symbol | undefined, dontResolveAlias?: boolean): Symbol | undefined; + function resolveExternalModuleSymbol(moduleSymbol: Symbol | undefined, 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); + }); + if (merged === exported) { + // We just mutated a symbol, reset any cached links we may have already set + // (Notably required to make late bound members appear) + getSymbolLinks(merged).resolvedExports = undefined; + getSymbolLinks(merged).resolvedMembers = undefined; + } + getSymbolLinks(merged).cjsExportMerged = merged; + return links.cjsExportMerged = merged; + } + + // 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 = type.flags & TypeFlags.StructuredType + ? getTypeWithSyntheticDefaultImportType(type, symbol, moduleSymbol!, reference) + : createDefaultPropertyWrapperForModule(symbol, symbol.parent); + return cloneTypeAsModuleType(symbol, moduleType, referenceParent); + } + } + } + } + return symbol; + } + + /** + * 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.links.target = symbol; + result.links.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.links.type = createAnonymousType(result, resolvedModuleType.members, emptyArray, emptyArray, resolvedModuleType.indexInfos); + return result; + } + + function hasExportAssignmentSymbol(moduleSymbol: Symbol): boolean { + return moduleSymbol.exports!.get(InternalSymbolName.ExportEquals) !== 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); + if (shouldTreatPropertiesOfExternalModuleAsExports(type)) { + addRange(exports, getPropertiesOfType(type)); + } + } + 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 exportEquals = resolveExternalModuleSymbol(moduleSymbol); + if (exportEquals !== moduleSymbol) { + const type = getTypeOfSymbol(exportEquals); + if (shouldTreatPropertiesOfExternalModuleAsExports(type)) { + forEachPropertyOfType(type, (symbol, escapedName) => { + cb(symbol, escapedName); + }); + } + } + } + + function tryGetMemberInModuleExports(memberName: __String, moduleSymbol: Symbol): Symbol | undefined { + const symbolTable = getExportsOfModule(moduleSymbol); + if (symbolTable) { + return symbolTable.get(memberName); + } + } + + function tryGetMemberInModuleExportsAndProperties(memberName: __String, moduleSymbol: Symbol): Symbol | undefined { + const symbol = tryGetMemberInModuleExports(memberName, moduleSymbol); + if (symbol) { + return symbol; + } + + const exportEquals = resolveExternalModuleSymbol(moduleSymbol); + if (exportEquals === moduleSymbol) { + return undefined; + } + + const type = getTypeOfSymbol(exportEquals); + return shouldTreatPropertiesOfExternalModuleAsExports(type) ? getPropertyOfType(type, memberName) : undefined; + } + + 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 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); + if (!links.resolvedExports) { + const { exports, typeOnlyExportStarMap } = getExportsOfModuleWorker(moduleSymbol); + links.resolvedExports = exports; + links.typeOnlyExportStarMap = typeOnlyExportStarMap; + } + return links.resolvedExports; + } + + interface ExportCollisionTracker { + specifierText: string; + exportsWithDuplicate?: ExportDeclaration[]; + } + + type ExportCollisionTrackerTable = Map<__String, ExportCollisionTracker>; + + /** + * 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; + + const targetSymbol = target.get(id); + if (!targetSymbol) { + target.set(id, sourceSymbol); + if (lookupTable && exportNode) { + lookupTable.set(id, { + specifierText: getTextOfNode(exportNode.moduleSpecifier!), + }); + } + } + 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 getExportsOfModuleWorker(moduleSymbol: Symbol) { + const visitedSymbols: Symbol[] = []; + let typeOnlyExportStarMap: Map<__String, ExportDeclaration & { readonly isTypeOnly: true; readonly moduleSpecifier: Expression; }> | undefined; + const nonTypeOnlyNames = new Set<__String>(); + + // A module defined by an 'export=' consists of one export that needs to be resolved + moduleSymbol = resolveExternalModuleSymbol(moduleSymbol); + const exports = visit(moduleSymbol) || emptySymbols; + + if (typeOnlyExportStarMap) { + nonTypeOnlyNames.forEach(name => typeOnlyExportStarMap!.delete(name)); + } + + return { + exports, + typeOnlyExportStarMap, + }; + + // 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, exportStar?: ExportDeclaration, isTypeOnly?: boolean): SymbolTable | undefined { + if (!isTypeOnly && symbol?.exports) { + // Add non-type-only names before checking if we've visited this module, + // because we might have visited it via an 'export type *', and visiting + // again with 'export *' will override the type-onlyness of its exports. + symbol.exports.forEach((_, name) => nonTypeOnlyNames.add(name)); + } + if (!(symbol && symbol.exports && pushIfUnique(visitedSymbols, symbol))) { + return; + } + 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, node as ExportDeclaration, isTypeOnly || (node as ExportDeclaration).isTypeOnly); + 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); + } + if (exportStar?.isTypeOnly) { + typeOnlyExportStarMap ??= new Map(); + symbols.forEach((_, escapedName) => + typeOnlyExportStarMap!.set( + escapedName, + exportStar as ExportDeclaration & { readonly isTypeOnly: true; readonly moduleSpecifier: Expression; }, + ) + ); + } + 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 getSymbolOfDeclaration(node: Declaration): Symbol { + return getMergedSymbol(node.symbol && getLateBoundSymbol(node.symbol)); + } + + /** + * Get the merged symbol for a node. If you know the node is a `Declaration`, it is faster and more type safe to + * use use `getSymbolOfDeclaration` instead. + */ + function getSymbolOfNode(node: Node): Symbol | undefined { + return canHaveSymbol(node) ? getSymbolOfDeclaration(node) : undefined; + } + + function getParentOfSymbol(symbol: Symbol): Symbol | undefined { + return getMergedSymbol(symbol.parent && getLateBoundSymbol(symbol.parent)); + } + + function getFunctionExpressionParentSymbolOrSymbol(symbol: Symbol) { + return symbol.valueDeclaration?.kind === SyntaxKind.ArrowFunction || symbol.valueDeclaration?.kind === SyntaxKind.FunctionExpression + ? getSymbolOfNode(symbol.valueDeclaration.parent) || symbol + : symbol; + } + + 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 = getSymbolOfDeclaration(file); + const ref = getAliasForSymbolInContainer(sym, symbol); + if (!ref) continue; + results = append(results, sym); + } + return links.extendedContainers = results || emptyArray; + } + + /** + * 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)) { + return getWithAlternativeContainers(container); + } + const candidates = mapDefined(symbol.declarations, d => { + if (!isAmbientModule(d) && d.parent) { + // direct children of a module + if (hasNonGlobalAugmentationExternalModuleSymbol(d.parent)) { + return getSymbolOfDeclaration(d.parent as Declaration); + } + // export ='d member of an ambient module + if (isModuleBlock(d.parent) && d.parent.parent && resolveExternalModuleSymbol(getSymbolOfDeclaration(d.parent.parent)) === symbol) { + return getSymbolOfDeclaration(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 getSymbolOfDeclaration(getSourceFileOfNode(d)); + } + checkExpressionCached(d.parent.left.expression); + return getNodeLinks(d.parent.left.expression).resolvedSymbol; + } + }); + if (!length(candidates)) { + return undefined; + } + const containers = mapDefined(candidates, candidate => getAliasForSymbolInContainer(candidate, symbol) ? candidate : undefined); + + let bestContainers: Symbol[] = []; + let alternativeContainers: Symbol[] = []; + + for (const container of containers) { + const [bestMatch, ...rest] = getWithAlternativeContainers(container); + bestContainers = append(bestContainers, bestMatch); + alternativeContainers = addRange(alternativeContainers, rest); + } + + return concatenate(bestContainers, alternativeContainers); + + function getWithAlternativeContainers(container: Symbol) { + 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, /*useOnlyExternalAliasing*/ 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 fileSymbolIfFileSymbolExportEqualsContainer(d: Declaration) { + return container && getFileSymbolIfFileSymbolExportEqualsContainer(d, container); + } + } + + 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 getSymbolOfDeclaration(firstDecl.parent); + } + } + } + + 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 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; + } + 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; + } + }); + } + + /** + * 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; + } + } + + 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 symbolIsValue(symbol: Symbol, includeTypeOnlyMembers?: boolean): boolean { + return !!( + symbol.flags & SymbolFlags.Value || + symbol.flags & SymbolFlags.Alias && getSymbolFlags(symbol, !includeTypeOnlyMembers) & SymbolFlags.Value + ); + } + + function createType(flags: TypeFlags): Type { + const result = new Type(checker, flags); + typeCount++; + result.id = typeCount; + tracing?.recordType(result); + return result; + } + + function createTypeWithSymbol(flags: TypeFlags, symbol: Symbol): Type { + const result = createType(flags); + result.symbol = symbol; + return result; + } + + function createOriginType(flags: TypeFlags): Type { + return new Type(checker, flags); + } + + function createIntrinsicType(kind: TypeFlags, intrinsicName: string, objectFlags = ObjectFlags.None, debugIntrinsicName?: string): IntrinsicType { + checkIntrinsicName(intrinsicName, debugIntrinsicName); + const type = createType(kind) as IntrinsicType; + type.intrinsicName = intrinsicName; + type.debugIntrinsicName = debugIntrinsicName; + type.objectFlags = objectFlags | ObjectFlags.CouldContainTypeVariablesComputed | ObjectFlags.IsGenericTypeComputed | ObjectFlags.IsUnknownLikeUnionComputed | ObjectFlags.IsNeverIntersectionComputed; + return type; + } + + function checkIntrinsicName(name: string, debug: string | undefined) { + const key = `${name},${debug ?? ""}`; + if (seenIntrinsicNames.has(key)) { + Debug.fail(`Duplicate intrinsic type name ${name}${debug ? ` (${debug})` : ""}; you may need to pass a name to createIntrinsicType.`); + } + seenIntrinsicNames.add(key); + } + + 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; + } + + function createTypeofType() { + return getUnionType(arrayFrom(typeofNEFacts.keys(), getStringLiteralType)); + } + + function createTypeParameter(symbol?: Symbol) { + return createTypeWithSymbol(TypeFlags.TypeParameter, symbol!) as TypeParameter; + } + + // 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 getNamedMembers(members: SymbolTable): Symbol[] { + let result: Symbol[] | undefined; + members.forEach((symbol, id) => { + if (isNamedMember(symbol, id)) { + (result || (result = [])).push(symbol); + } + }); + return result || emptyArray; + } + + function isNamedMember(member: Symbol, escapedName: __String) { + return !isReservedMemberName(escapedName) && symbolIsValue(member); + } + + function getNamedOrIndexSignatureMembers(members: SymbolTable): Symbol[] { + const result = getNamedMembers(members); + const index = getIndexSymbolFromSymbolTable(members); + return index ? concatenate(result, [index]) : 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 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 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 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 (canHaveLocals(location) && 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 = getSymbolOfDeclaration(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: Map<__String, Symbol> | undefined; + // TODO: Should this filtered table be cached in some way? + (getSymbolOfDeclaration(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; + } + } + + return callback(globals, /*ignoreQualification*/ undefined, /*isLocalNameLookup*/ true); + } + + 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 getAccessibleSymbolChain(symbol: Symbol | undefined, enclosingDeclaration: Node | undefined, meaning: SymbolFlags, useOnlyExternalAliasing: boolean, visitedSymbolTablesMap = 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); + } + + 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; + + /** + * @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; + } + + const result = trySymbolTable(symbols, ignoreQualification, isLocalNameLookup); + visitedSymbolTables!.pop(); + return result; + } + + 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)) + ) { + 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), /*resolvedAliasSymbol*/ undefined, ignoreQualification)) { + return [symbol!]; + } + } + }); + + // 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 getCandidateListForSymbol(symbolFromSymbolTable: Symbol, resolvedImportedSymbol: Symbol, ignoreQualification: boolean | undefined) { + if (isAccessible(symbolFromSymbolTable, resolvedImportedSymbol, ignoreQualification)) { + return [symbolFromSymbolTable]; + } + + // 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 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 ? getSymbolFlags(symbolFromSymbolTable) : symbolFromSymbolTable.flags; + if (flags & meaning) { + qualify = true; + return true; + } + + // 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; + } + + 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, + }; + } + } + + // 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 + + const containers = getContainersOfSymbol(symbol, enclosingDeclaration, meaning); + const parentResult = isAnySymbolAccessible(containers, enclosingDeclaration, initialSymbol, initialSymbol === symbol ? getQualifiedLeftMeaning(meaning) : meaning, shouldComputeAliasesToMakeVisible, allowModules); + if (parentResult) { + return parentResult; + } + } + + if (earlyModuleBail) { + return { + accessibility: SymbolAccessibility.Accessible, + }; + } + + if (hadAccessibleChain) { + return { + accessibility: SymbolAccessibility.NotAccessible, + errorSymbolName: symbolToString(initialSymbol, enclosingDeclaration, meaning), + errorModuleName: hadAccessibleChain !== initialSymbol ? symbolToString(hadAccessibleChain, enclosingDeclaration, SymbolFlags.Namespace) : undefined, + }; + } + } + + /** + * 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); + } + + 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; + } + + // 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, + }; + } + } + + // Just a local name that is not accessible + return { + accessibility: SymbolAccessibility.NotAccessible, + errorSymbolName: symbolToString(symbol, enclosingDeclaration, meaning), + }; + } + + return { accessibility: SymbolAccessibility.Accessible }; + } + + function getExternalModuleContainer(declaration: Node) { + const node = findAncestor(declaration, hasExternalModuleSymbol); + return node && getSymbolOfDeclaration(node as AmbientModuleDeclaration | SourceFile); + } + + 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); + } + } + + // Declaration is not visible + return false; + } + + 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 getMeaningOfEntityNameReference(entityName: EntityNameOrEntityNameExpression): SymbolFlags { + // 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 || + entityName.parent.kind === SyntaxKind.TypePredicate && (entityName.parent as TypePredicateNode).parameterName === entityName + ) { + // Typeof value + meaning = SymbolFlags.Value | SymbolFlags.ExportValue; + } + else if ( + entityName.kind === SyntaxKind.QualifiedName || entityName.kind === SyntaxKind.PropertyAccessExpression || + entityName.parent.kind === SyntaxKind.ImportEqualsDeclaration || + (entityName.parent.kind === SyntaxKind.QualifiedName && (entityName.parent as QualifiedName).left === entityName) || + (entityName.parent.kind === SyntaxKind.PropertyAccessExpression && (entityName.parent as PropertyAccessExpression).expression === entityName) || + (entityName.parent.kind === SyntaxKind.ElementAccessExpression && (entityName.parent as ElementAccessExpression).expression === entityName) + ) { + // 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 meaning; + } + + function isEntityNameVisible(entityName: EntityNameOrEntityNameExpression, enclosingDeclaration: Node, shouldComputeAliasToMakeVisible = true): SymbolVisibilityResult { + const meaning = getMeaningOfEntityNameReference(entityName); + const firstIdentifier = getFirstIdentifier(entityName); + const symbol = resolveName(enclosingDeclaration, firstIdentifier.escapedText, meaning, /*nameNotFoundMessage*/ undefined, /*isUse*/ false); + if (symbol && symbol.flags & SymbolFlags.TypeParameter && meaning & SymbolFlags.Type) { + return { accessibility: SymbolAccessibility.Accessible }; + } + if (!symbol && isThisIdentifier(firstIdentifier) && isSymbolAccessible(getSymbolOfDeclaration(getThisContainer(firstIdentifier, /*includeArrowFunctions*/ false, /*includeClassComputedPropertyName*/ false)), firstIdentifier, meaning, /*shouldComputeAliasesToMakeVisible*/ false).accessibility === SymbolAccessibility.Accessible) { + return { accessibility: SymbolAccessibility.Accessible }; + } + + if (!symbol) { + return { + accessibility: SymbolAccessibility.NotResolved, + errorSymbolName: getTextOfNode(firstIdentifier), + errorNode: firstIdentifier, + }; + } + // Verify if the symbol is accessible + return hasVisibleDeclarations(symbol, shouldComputeAliasToMakeVisible) || { + 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 symbolToStringWorker(writer: EmitTextWriter) { + const entity = builder(symbol, meaning!, enclosingDeclaration, nodeFlags)!; // TODO: GH#18217 + // add neverAsciiEscape for GH#39027 + const printer = enclosingDeclaration?.kind === SyntaxKind.SourceFile + ? createPrinterWithRemoveCommentsNeverAsciiEscape() + : createPrinterWithRemoveComments(); + const sourceFile = enclosingDeclaration && getSourceFileOfNode(enclosingDeclaration); + printer.writeNode(EmitHint.Unspecified, entity, /*sourceFile*/ sourceFile, writer); + return writer; + } + } + + 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; + } + const sig = nodeBuilder.signatureToSignatureDeclaration(signature, sigOutput, enclosingDeclaration, toNodeBuilderFlags(flags) | NodeBuilderFlags.IgnoreErrors | NodeBuilderFlags.WriteTypeParametersInQualifiedName); + const printer = createPrinterWithRemoveCommentsOmitTrailingSemicolon(); + const sourceFile = enclosingDeclaration && getSourceFileOfNode(enclosingDeclaration); + printer.writeNode(EmitHint.Unspecified, sig!, /*sourceFile*/ sourceFile, getTrailingSemicolonDeferringWriter(writer)); // TODO: GH#18217 + return writer; + } + } + + 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)); + 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 printer = type !== unresolvedType ? createPrinterWithRemoveComments() : createPrinterWithDefaults(); + 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 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 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 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))); + } + /** + * Same as getTypeFromTypeNode, but for use in createNodeBuilder + * Inside createNodeBuilder we shadow getTypeFromTypeNode to make sure anyone using this function will call the local version that does type mapping if appropriate + * This function is used to still be able to call the original getTypeFromTypeNode from the local scope version of getTypeFromTypeNode + */ + function getTypeFromTypeNodeWithoutContext(node: TypeNode) { + return getTypeFromTypeNode(node); + } + function createNodeBuilder() { + return { + typeToTypeNode: (type: Type, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => withContext(enclosingDeclaration, flags, tracker, context => typeToTypeNodeHelper(type, context)), + typePredicateToTypePredicateNode: (typePredicate: TypePredicate, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => withContext(enclosingDeclaration, flags, tracker, context => typePredicateToTypePredicateNodeHelper(typePredicate, context)), + expressionOrTypeToTypeNode: (expr: Expression | JsxAttributeValue | undefined, type: Type, addUndefined?: boolean, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => withContext(enclosingDeclaration, flags, tracker, context => expressionOrTypeToTypeNode(context, expr, type, addUndefined)), + serializeTypeForDeclaration: (declaration: Declaration, type: Type, symbol: Symbol, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => withContext(enclosingDeclaration, flags, tracker, context => serializeTypeForDeclaration(context, declaration, type, symbol)), + serializeReturnTypeForSignature: (signature: Signature, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => withContext(enclosingDeclaration, flags, tracker, context => serializeReturnTypeForSignature(context, signature)), + 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) => withContext(enclosingDeclaration, flags, tracker, context => symbolTableToDeclarationStatements(symbolTable, context)), + symbolToNode: (symbol: Symbol, meaning: SymbolFlags, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => withContext(enclosingDeclaration, flags, tracker, context => symbolToNode(symbol, context, meaning)), + }; + + function getTypeFromTypeNode(context: NodeBuilderContext, node: TypeNode, noMappedTypes?: false): Type; + function getTypeFromTypeNode(context: NodeBuilderContext, node: TypeNode, noMappedTypes: true): Type | undefined; + function getTypeFromTypeNode(context: NodeBuilderContext, node: TypeNode, noMappedTypes?: boolean): Type | undefined { + const type = getTypeFromTypeNodeWithoutContext(node); + if (!context.mapper) return type; + + const mappedType = instantiateType(type, context.mapper); + return noMappedTypes && mappedType !== type ? undefined : mappedType; + } + + /** + * Unlike the utilities `setTextRange`, this checks if the `location` we're trying to set on `range` is within the + * same file as the active context. If not, the range is not applied. This prevents us from copying ranges across files, + * which will confuse the node printer (as it assumes all node ranges are within the current file). + * Additionally, if `range` _isn't synthetic_, or isn't in the current file, it will _copy_ it to _remove_ its' position + * information. + * + * It also calls `setOriginalNode` to setup a `.original` pointer, since you basically *always* want these in the node builder. + */ + function setTextRange(context: NodeBuilderContext, range: T, location: Node | undefined): T { + if (!nodeIsSynthesized(range) || !(range.flags & NodeFlags.Synthesized) || !context.enclosingFile || context.enclosingFile !== getSourceFileOfNode(getOriginalNode(range))) { + range = factory.cloneNode(range); // if `range` is synthesized or originates in another file, copy it so it definitely has synthetic positions + } + if (range === location) return range; + if (!location) { + return range; + } + if (!context.enclosingFile || context.enclosingFile !== getSourceFileOfNode(getOriginalNode(location))) { + return setOriginalNode(range, location); // if `location` is from another file, only set/update original pointer, and not positions, since copying text across files isn't supported by the emitter + } + return setTextRangeWorker(setOriginalNode(range, location), location); + } + + /** + * Same as expressionOrTypeToTypeNodeHelper, but also checks if the expression can be syntactically typed. + */ + function expressionOrTypeToTypeNode(context: NodeBuilderContext, expr: Expression | JsxAttributeValue | undefined, type: Type, addUndefined?: boolean) { + const oldFlags = context.flags; + if (expr && !(context.flags & NodeBuilderFlags.NoSyntacticPrinter)) { + syntacticNodeBuilder.serializeTypeOfExpression(expr, context, addUndefined); + } + context.flags |= NodeBuilderFlags.NoSyntacticPrinter; + const result = expressionOrTypeToTypeNodeHelper(context, expr, type, addUndefined); + context.flags = oldFlags; + return result; + } + function expressionOrTypeToTypeNodeHelper(context: NodeBuilderContext, expr: Expression | JsxAttributeValue | undefined, type: Type, addUndefined?: boolean) { + if (expr) { + const typeNode = isAssertionExpression(expr) ? expr.type + : isJSDocTypeAssertion(expr) ? getJSDocTypeAssertionType(expr) + : undefined; + if (typeNode && !isConstTypeReference(typeNode)) { + const result = tryReuseExistingTypeNode(context, typeNode, type, expr.parent, addUndefined); + if (result) { + return result; + } + } + } + + if (addUndefined) { + type = getOptionalType(type); + } + + return typeToTypeNodeHelper(type, context); + } + + function tryReuseExistingTypeNode( + context: NodeBuilderContext, + typeNode: TypeNode, + type: Type, + host: Node, + addUndefined?: boolean, + ) { + const originalType = type; + if (addUndefined) { + type = getOptionalType(type); + } + const clone = tryReuseExistingNonParameterTypeNode(context, typeNode, type, host); + if (clone) { + if (addUndefined && !someType(getTypeFromTypeNode(context, typeNode), t => !!(t.flags & TypeFlags.Undefined))) { + return factory.createUnionTypeNode([clone, factory.createKeywordTypeNode(SyntaxKind.UndefinedKeyword)]); + } + return clone; + } + if (addUndefined && originalType !== type) { + const cloneMissingUndefined = tryReuseExistingNonParameterTypeNode(context, typeNode, originalType, host); + if (cloneMissingUndefined) { + return factory.createUnionTypeNode([cloneMissingUndefined, factory.createKeywordTypeNode(SyntaxKind.UndefinedKeyword)]); + } + } + return undefined; + } + + function tryReuseExistingNonParameterTypeNode( + context: NodeBuilderContext, + existing: TypeNode, + type: Type, + host = context.enclosingDeclaration, + annotationType = getTypeFromTypeNode(context, existing, /*noMappedTypes*/ true), + ) { + if (annotationType && typeNodeIsEquivalentToType(host, type, annotationType) && existingTypeNodeIsNotReferenceOrIsReferenceWithCompatibleTypeArgumentCount(existing, type)) { + const result = tryReuseExistingTypeNodeHelper(context, existing); + if (result) { + return result; + } + } + return undefined; + } + + 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 { + const moduleResolverHost = tracker?.trackSymbol ? tracker.moduleResolverHost : + flags! & NodeBuilderFlags.DoNotIncludeSymbolChain ? createBasicNodeBuilderModuleSpecifierResolutionHost(host) : + undefined; + const context: NodeBuilderContext = { + enclosingDeclaration, + enclosingFile: enclosingDeclaration && getSourceFileOfNode(enclosingDeclaration), + flags: flags || NodeBuilderFlags.None, + tracker: undefined!, + encounteredError: false, + reportedDiagnostic: false, + visitedTypes: undefined, + symbolDepth: undefined, + inferTypeParameters: undefined, + approximateLength: 0, + trackedSymbols: undefined, + bundled: !!compilerOptions.outFile && !!enclosingDeclaration && isExternalOrCommonJsModule(getSourceFileOfNode(enclosingDeclaration)), + truncating: false, + usedSymbolNames: undefined, + remappedSymbolNames: undefined, + remappedSymbolReferences: undefined, + reverseMappedStack: undefined, + mustCreateTypeParameterSymbolList: true, + typeParameterSymbolList: undefined, + mustCreateTypeParametersNamesLookups: true, + typeParameterNames: undefined, + typeParameterNamesByText: undefined, + typeParameterNamesByTextNextNameCount: undefined, + mapper: undefined, + }; + context.tracker = new SymbolTrackerImpl(context, tracker, moduleResolverHost); + const resultingNode = cb(context); + if (context.truncating && context.flags & NodeBuilderFlags.NoTruncation) { + context.tracker.reportTruncationError(); + } + return context.encounteredError ? undefined : resultingNode; + } + + function checkTruncationLength(context: NodeBuilderContext): boolean { + if (context.truncating) return context.truncating; + return context.truncating = context.approximateLength > ((context.flags & NodeBuilderFlags.NoTruncation) ? noTruncationMaximumTruncationLength : defaultMaximumTruncationLength); + } + + function typeToTypeNodeHelper(type: Type, context: NodeBuilderContext): TypeNode { + const savedFlags = context.flags; + const typeNode = typeToTypeNodeWorker(type, context); + context.flags = savedFlags; + return typeNode; + } + + function typeToTypeNodeWorker(type: Type, context: NodeBuilderContext): TypeNode { + if (cancellationToken && cancellationToken.throwIfCancellationRequested) { + cancellationToken.throwIfCancellationRequested(); + } + const inTypeAlias = context.flags & NodeBuilderFlags.InTypeAlias; + context.flags &= ~NodeBuilderFlags.InTypeAlias; + + if (!type) { + if (!(context.flags & NodeBuilderFlags.AllowEmptyUnionOrIntersection)) { + context.encounteredError = true; + return undefined!; // TODO: GH#18217 + } + context.approximateLength += 3; + return factory.createKeywordTypeNode(SyntaxKind.AnyKeyword); + } + + if (!(context.flags & NodeBuilderFlags.NoTypeReduction)) { + type = getReducedType(type); + } + + if (type.flags & TypeFlags.Any) { + if (type.aliasSymbol) { + return factory.createTypeReferenceNode(symbolToEntityNameNode(type.aliasSymbol), mapToTypeNodes(type.aliasTypeArguments, context)); + } + if (type === unresolvedType) { + return addSyntheticLeadingComment(factory.createKeywordTypeNode(SyntaxKind.AnyKeyword), SyntaxKind.MultiLineCommentTrivia, "unresolved"); + } + 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.ES5)) { + 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`."); + } + } + 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(); + } + } + 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; + } + context.tracker.reportInaccessibleThisError?.(); + } + 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); + } + + const objectFlags = getObjectFlags(type); + + if (objectFlags & ObjectFlags.Reference) { + Debug.assert(!!(type.flags & TypeFlags.Object)); + return (type as TypeReference).node ? visitAndTransformType(type as TypeReference, 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 + ) { + 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); + } + 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); + } + 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 + } + } + 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) { + const typeNode = typeToTypeNodeHelper((type as SubstitutionType).baseType, context); + const noInferSymbol = isNoInferType(type) && getGlobalTypeSymbol("NoInfer" as __String, /*reportErrors*/ false); + return noInferSymbol ? symbolToTypeNode(noInferSymbol, context, SymbolFlags.Type, [typeNode]) : typeNode; + } + + 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(context, type.root.node.trueType), newMapper)); + const falseTypeNode = typeToTypeNodeOrCircularityElision(instantiateType(getTypeFromTypeNode(context, 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); + } + return visitAndTransformType(type, type => typeToTypeNodeHelper(type, context)); + } + return typeToTypeNodeHelper(type, context); + } + + function isMappedTypeHomomorphic(type: MappedType) { + return !!getHomomorphicTypeVariable(type); + } + + function isHomomorphicMappedTypeWithNonHomomorphicInstantiation(type: MappedType) { + return !!type.target && isMappedTypeHomomorphic(type.target as MappedType) && !isMappedTypeHomomorphic(type); + } + + 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 the mapped type isn't `keyof` constraint-declared, _but_ still has modifiers preserved, and its naive instantiation won't preserve modifiers because its constraint isn't `keyof` constrained, we have work to do + const needsModifierPreservingWrapper = !isMappedTypeWithKeyofConstraintDeclaration(type) + && !(getModifiersTypeFromMappedType(type).flags & TypeFlags.Unknown) + && context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams + && !(getConstraintTypeFromMappedType(type).flags & TypeFlags.TypeParameter && getConstraintOfTypeParameter(getConstraintTypeFromMappedType(type))?.flags! & TypeFlags.Index); + 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 if (needsModifierPreservingWrapper) { + // So, step 1: new type variable + const newParam = createTypeParameter(createSymbol(SymbolFlags.TypeParameter, "T" as __String)); + const name = typeParameterToName(newParam, context); + newTypeVariable = factory.createTypeReferenceNode(name); + // step 2: make that new type variable itself the constraint node, making the mapped type `{[K in T_1]: Template}` + appropriateConstraintTypeNode = newTypeVariable; + } + 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(context, (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), + ); + } + else if (needsModifierPreservingWrapper) { + // and step 3: once the mapped type is reconstructed, create a `ConstraintType extends infer T_1 extends keyof ModifiersType ? {[K in T_1]: Template} : never` + // subtly different from the `keyof` constraint case, by including the `keyof` constraint on the `infer` type parameter, it doesn't rely on the constraint type being itself + // constrained to a `keyof` type to preserve its modifier-preserving behavior. This is all basically because we preserve modifiers for a wider set of mapped types than + // just homomorphic ones. + return factory.createConditionalTypeNode( + typeToTypeNodeHelper(getConstraintTypeFromMappedType(type), context), + factory.createInferTypeNode(factory.createTypeParameterDeclaration(/*modifiers*/ undefined, factory.cloneNode(newTypeVariable!.typeName) as Identifier, factory.createTypeOperatorNode(SyntaxKind.KeyOfKeyword, typeToTypeNodeHelper(getModifiersTypeFromMappedType(type), context)))), + result, + factory.createKeywordTypeNode(SyntaxKind.NeverKeyword), + ); + } + return result; + } + + function createAnonymousTypeNode(type: ObjectType): TypeNode { + const typeId = type.id; + const symbol = type.symbol; + if (symbol) { + const isInstantiationExpressionType = !!(getObjectFlags(type) & ObjectFlags.InstantiationExpressionType); + if (isInstantiationExpressionType) { + const instantiationExpressionType = type as InstantiationExpressionType; + const existing = instantiationExpressionType.node; + if (isTypeQueryNode(existing)) { + const typeNode = tryReuseExistingNonParameterTypeNode(context, existing, type); + if (typeNode) { + return typeNode; + } + } + if (context.visitedTypes?.has(typeId)) { + return createElidedInformationPlaceholder(context); + } + return visitAndTransformType(type, createTypeNodeFromObjectType); + } + 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, /*shouldComputeAliasesToMakeVisible*/ 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); + } + } + else { + 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: T, transform: (type: T) => TypeNode) { + 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 & T).node ? "N" + getNodeId((type as TypeReference & T).node!) : + type.flags & TypeFlags.Conditional ? "N" + getNodeId((type as ConditionalType & T).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) { + // TODO:: check if we instead store late painted statements associated with this? + cachedResult.trackedSymbols?.forEach( + ([symbol, enclosingDeclaration, meaning]) => + context.tracker.trackSymbol( + symbol, + enclosingDeclaration, + meaning, + ), + ); + if (cachedResult.truncating) { + context.truncating = true; + } + context.approximateLength += cachedResult.addedLength; + return deepCloneOrReuseNode(cachedResult.node); + } + + let depth: number | undefined; + if (id) { + depth = context.symbolDepth!.get(id) || 0; + if (depth > 10) { + return createElidedInformationPlaceholder(context); + } + context.symbolDepth!.set(id, depth + 1); + } + context.visitedTypes.add(typeId); + const prevTrackedSymbols = context.trackedSymbols; + context.trackedSymbols = undefined; + const startLength = context.approximateLength; + const result = transform(type); + const addedLength = context.approximateLength - startLength; + if (!context.reportedDiagnostic && !context.encounteredError) { + links?.serializedTypes?.set(key, { + node: result, + truncating: context.truncating, + addedLength, + trackedSymbols: context.trackedSymbols, + }); + } + context.visitedTypes.delete(typeId); + if (id) { + context.symbolDepth!.set(id, depth!); + } + context.trackedSymbols = prevTrackedSymbols; + return result; + + function deepCloneOrReuseNode(node: T): T { + if (!nodeIsSynthesized(node) && getParseTreeNode(node) === node) { + return node; + } + return setTextRange(context, factory.cloneNode(visitEachChildWorker(node, deepCloneOrReuseNode, /*context*/ undefined, deepCloneOrReuseNodes, deepCloneOrReuseNode)), node); + } + + function deepCloneOrReuseNodes( + nodes: NodeArray | undefined, + visitor: Visitor, + 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 setTextRangeWorker(factory.createNodeArray(/*elements*/ 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, s => getOrCreateTypeFromSignature(s)); + // 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) { + const { labeledElementDeclarations } = type.target as TupleType; + for (let i = 0; i < tupleConstituentNodes.length; i++) { + const flags = (type.target as TupleType).elementFlags[i]; + const labeledElementDeclaration = labeledElementDeclarations?.[i]; + + if (labeledElementDeclaration) { + tupleConstituentNodes[i] = factory.createNamedTupleMember( + flags & ElementFlags.Variable ? factory.createToken(SyntaxKind.DotDotDotToken) : undefined, + factory.createIdentifier(unescapeLeadingUnderscores(getTupleElementLabel(labeledElementDeclaration))), + flags & ElementFlags.Optional ? factory.createToken(SyntaxKind.QuestionToken) : undefined, + flags & ElementFlags.Rest ? factory.createArrayTypeNode(tupleConstituentNodes[i]) : + tupleConstituentNodes[i], + ); + } + else { + 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 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)) { + if (typeArguments !== getIdentifierTypeArguments(qualifier)) { + qualifier = setIdentifierTypeArguments(factory.cloneNode(qualifier), typeArguments); + } + } + else { + if (typeArguments !== getIdentifierTypeArguments(qualifier.right)) { + qualifier = factory.updateQualifiedName(qualifier, qualifier.left, setIdentifierTypeArguments(factory.cloneNode(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.attributes, + qualifier, + typeArguments, + root.isTypeOf, + ); + } + else { + // first shift type arguments + let typeArguments = root.typeArguments; + let typeName = root.typeName; + if (isIdentifier(typeName)) { + if (typeArguments !== getIdentifierTypeArguments(typeName)) { + typeName = setIdentifierTypeArguments(factory.cloneNode(typeName), typeArguments); + } + } + else { + if (typeArguments !== getIdentifierTypeArguments(typeName.right)) { + typeName = factory.updateQualifiedName(typeName, typeName.left, setIdentifierTypeArguments(factory.cloneNode(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 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; + } + 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); + } + return typeElements.length ? typeElements : undefined; + } + } + + 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 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).links.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.canTrackSymbol && 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); + } + } + } + else { + context.tracker.reportNonSerializableProperty(symbolToString(propertySymbol)); + } + } + context.enclosingDeclaration = propertySymbol.valueDeclaration || propertySymbol.declarations?.[0] || saveEnclosingDeclaration; + const propertyName = getPropertyNameNodeForSymbol(propertySymbol, context); + context.enclosingDeclaration = saveEnclosingDeclaration; + context.approximateLength += symbolName(propertySymbol).length + 1; + + if (propertySymbol.flags & SymbolFlags.Accessor) { + const writeType = getWriteTypeOfSymbol(propertySymbol); + if (propertyType !== writeType && !isErrorType(propertyType) && !isErrorType(writeType)) { + const getterDeclaration = getDeclarationOfKind(propertySymbol, SyntaxKind.GetAccessor)!; + const getterSignature = getSignatureFromDeclaration(getterDeclaration); + typeElements.push( + setCommentRange( + context, + signatureToSignatureDeclarationHelper(getterSignature, SyntaxKind.GetAccessor, context, { name: propertyName }) as GetAccessorDeclaration, + getterDeclaration, + ), + ); + const setterDeclaration = getDeclarationOfKind(propertySymbol, SyntaxKind.SetAccessor)!; + const setterSignature = getSignatureFromDeclaration(setterDeclaration); + typeElements.push( + setCommentRange( + context, + signatureToSignatureDeclarationHelper(setterSignature, SyntaxKind.SetAccessor, context, { name: propertyName }) as SetAccessorDeclaration, + setterDeclaration, + ), + ); + return; + } + } + + 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)); + } + if (signatures.length || !optionalToken) { + return; + } + } + let propertyTypeNode: TypeNode; + if (shouldUsePlaceholderForProperty(propertySymbol, context)) { + propertyTypeNode = createElidedInformationPlaceholder(context); + } + else { + if (propertyIsReverseMapped) { + context.reverseMappedStack ||= []; + context.reverseMappedStack.push(propertySymbol as ReverseMappedSymbol); + } + propertyTypeNode = propertyType ? serializeTypeForDeclaration(context, /*declaration*/ undefined, propertyType, propertySymbol) : factory.createKeywordTypeNode(SyntaxKind.AnyKeyword); + if (propertyIsReverseMapped) { + context.reverseMappedStack!.pop(); + } + } + + 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) { + const jsdocPropertyTag = propertySymbol.declarations?.find((d): d is JSDocPropertyTag => d.kind === SyntaxKind.JSDocPropertyTag); + if (jsdocPropertyTag) { + const commentText = getTextOfJSDocComment(jsdocPropertyTag.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(context, node, propertySymbol.valueDeclaration); + } + return node; + } + } + + function setCommentRange(context: NodeBuilderContext, node: T, range: Node): T { + if (context.enclosingFile && context.enclosingFile === getSourceFileOfNode(range)) { + // Copy comments to node for declaration emit + return setCommentRangeWorker(node, range); + } + return node; + } + + 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 ? createMultiMap<__String, [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; + } + 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 (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; + } + + 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); + + 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, + ); + } + + interface SignatureToSignatureDeclarationOptions { + modifiers?: readonly Modifier[]; + name?: PropertyName; + questionToken?: QuestionToken; + } + + function signatureToSignatureDeclarationHelper(signature: Signature, kind: SignatureDeclaration["kind"], context: NodeBuilderContext, options?: SignatureToSignatureDeclarationOptions): SignatureDeclaration { + let typeParameters: TypeParameterDeclaration[] | undefined; + let typeArguments: TypeNode[] | undefined; + + const expandedParams = getExpandedParameters(signature, /*skipUnionExpanding*/ true)[0]; + const cleanup = enterNewScope(context, signature.declaration, expandedParams, signature.typeParameters, signature.parameters, signature.mapper); + context.approximateLength += 3; // Usually a signature contributes a few more characters than this, but 3 is the minimum + + 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 flags = context.flags; + context.flags &= ~NodeBuilderFlags.SuppressAnyReturnType; // SuppressAnyReturnType should only apply to the signature `return` position + // 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)); + const thisParameter = context.flags & NodeBuilderFlags.OmitThisParameter ? undefined : tryGetThisParameterDeclaration(signature, context); + if (thisParameter) { + parameters.unshift(thisParameter); + } + context.flags = flags; + + const returnTypeNode = serializeReturnTypeForSignature(context, signature); + + let modifiers = options?.modifiers; + if ((kind === SyntaxKind.ConstructorType) && signature.flags & SignatureFlags.Abstract) { + const flags = modifiersToFlags(modifiers); + modifiers = factory.createModifiersFromModifierFlags(flags | ModifierFlags.Abstract); + } + + 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); + } + if (signature.declaration?.kind === SyntaxKind.JSDocSignature && signature.declaration.parent.kind === SyntaxKind.JSDocOverloadTag) { + const comment = getTextOfNode(signature.declaration.parent.parent, /*includeTrivia*/ true).slice(2, -2).split(/\r\n|\n|\r/).map(line => line.replace(/^\s+/, " ")).join("\n"); + addSyntheticLeadingComment(node, SyntaxKind.MultiLineCommentTrivia, comment, /*hasTrailingNewLine*/ true); + } + + cleanup?.(); + return node; + } + + type IntroducesNewScopeNode = SignatureDeclaration | JSDocSignature | MappedTypeNode; + + function isNewScopeNode(node: Node): node is IntroducesNewScopeNode { + return isFunctionLike(node) + || isJSDocSignature(node) + || isMappedTypeNode(node); + } + + function getTypeParametersInScope(node: IntroducesNewScopeNode | ConditionalTypeNode) { + return isFunctionLike(node) || isJSDocSignature(node) ? getSignatureFromDeclaration(node).typeParameters : + isConditionalTypeNode(node) ? getInferTypeParameters(node) : + [getDeclaredTypeOfTypeParameter(getSymbolOfDeclaration(node.typeParameter))]; + } + + function getParametersInScope(node: IntroducesNewScopeNode | ConditionalTypeNode) { + return isFunctionLike(node) || isJSDocSignature(node) ? getSignatureFromDeclaration(node).parameters : undefined; + } + + function enterNewScope( + context: NodeBuilderContext, + declaration: IntroducesNewScopeNode | ConditionalTypeNode | undefined, + expandedParams: readonly Symbol[] | undefined, + typeParameters: readonly TypeParameter[] | undefined, + originalParameters?: readonly Symbol[] | undefined, + mapper?: TypeMapper, + ) { + const cleanupContext = cloneNodeBuilderContext(context); + // For regular function/method declarations, the enclosing declaration will already be signature.declaration, + // so this is a no-op, but for arrow functions and function expressions, the enclosing declaration will be + // the declaration that the arrow function / function expression is assigned to. + // + // If the parameters or return type include "typeof globalThis.paramName", using the wrong scope will lead + // us to believe that we can emit "typeof paramName" instead, even though that would refer to the parameter, + // not the global. Make sure we are in the right scope by changing the enclosingDeclaration to the function. + // + // We can't use the declaration directly; it may be in another file and so we may lose access to symbols + // accessible to the current enclosing declaration, or gain access to symbols not accessible to the current + // enclosing declaration. To keep this chain accurate, insert a fake scope into the chain which makes the + // function's parameters visible. + let cleanupParams: (() => void) | undefined; + let cleanupTypeParams: (() => void) | undefined; + const oldEnclosingDecl = context.enclosingDeclaration; + const oldMapper = context.mapper; + if (mapper) { + context.mapper = mapper; + } + if (context.enclosingDeclaration && declaration) { + // As a performance optimization, reuse the same fake scope within this chain. + // This is especially needed when we are working on an excessively deep type; + // if we don't do this, then we spend all of our time adding more and more + // scopes that need to be searched in isSymbolAccessible later. Since all we + // really want to do is to mark certain names as unavailable, we can just keep + // all of the names we're introducing in one large table and push/pop from it as + // needed; isSymbolAccessible will walk upward and find the closest "fake" scope, + // which will conveniently report on any and all faked scopes in the chain. + // + // It'd likely be better to store this somewhere else for isSymbolAccessible, but + // since that API _only_ uses the enclosing declaration (and its parents), this is + // seems like the best way to inject names into that search process. + // + // Note that we only check the most immediate enclosingDeclaration; the only place we + // could potentially add another fake scope into the chain is right here, so we don't + // traverse all ancestors. + cleanupParams = !some(expandedParams) ? undefined : pushFakeScope( + "params", + add => { + if (!expandedParams) return; + for (let pIndex = 0; pIndex < expandedParams.length; pIndex++) { + const param = expandedParams[pIndex]; + const originalParam = originalParameters?.[pIndex]; + if (originalParameters && originalParam !== param) { + // Can't reference parameters that come from an expansion + add(param.escapedName, unknownSymbol); + // Can't reference the original expanded parameter either + if (originalParam) { + add(originalParam.escapedName, unknownSymbol); + } + } + else if ( + !forEach(param.declarations, d => { + if (isParameter(d) && isBindingPattern(d.name)) { + bindPattern(d.name); + return true; + } + return undefined; + function bindPattern(p: BindingPattern): void { + forEach(p.elements, e => { + switch (e.kind) { + case SyntaxKind.OmittedExpression: + return; + case SyntaxKind.BindingElement: + return bindElement(e); + default: + return Debug.assertNever(e); + } + }); + } + function bindElement(e: BindingElement): void { + if (isBindingPattern(e.name)) { + return bindPattern(e.name); + } + const symbol = getSymbolOfDeclaration(e); + add(symbol.escapedName, symbol); + } + }) + ) { + add(param.escapedName, param); + } + } + }, + ); + + if (context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams && some(typeParameters)) { + cleanupTypeParams = pushFakeScope( + "typeParams", + add => { + for (const typeParam of typeParameters ?? emptyArray) { + const typeParamName = typeParameterToName(typeParam, context).escapedText; + add(typeParamName, typeParam.symbol); + } + }, + ); + } + + function pushFakeScope(kind: "params" | "typeParams", addAll: (addSymbol: (name: __String, symbol: Symbol) => void) => void) { + // We only ever need to look two declarations upward. + Debug.assert(context.enclosingDeclaration); + let existingFakeScope: Node | undefined; + if (getNodeLinks(context.enclosingDeclaration).fakeScopeForSignatureDeclaration === kind) { + existingFakeScope = context.enclosingDeclaration; + } + else if (context.enclosingDeclaration.parent && getNodeLinks(context.enclosingDeclaration.parent).fakeScopeForSignatureDeclaration === kind) { + existingFakeScope = context.enclosingDeclaration.parent; + } + Debug.assertOptionalNode(existingFakeScope, isBlock); + + const locals = existingFakeScope?.locals ?? createSymbolTable(); + let newLocals: __String[] | undefined; + let oldLocals: { name: __String; oldSymbol: Symbol; }[] | undefined; + addAll((name, symbol) => { + // Add cleanup information only if we don't own the fake scope + if (existingFakeScope) { + const oldSymbol = locals.get(name); + if (!oldSymbol) { + newLocals = append(newLocals, name); + } + else { + oldLocals = append(oldLocals, { name, oldSymbol }); + } + } + locals.set(name, symbol); + }); + + if (!existingFakeScope) { + // Use a Block for this; the type of the node doesn't matter so long as it + // has locals, and this is cheaper/easier than using a function-ish Node. + const fakeScope = factory.createBlock(emptyArray); + getNodeLinks(fakeScope).fakeScopeForSignatureDeclaration = kind; + fakeScope.locals = locals; + + setParent(fakeScope, context.enclosingDeclaration); + context.enclosingDeclaration = fakeScope; + } + else { + // We did not create the current scope, so we have to clean it up + return function undo() { + forEach(newLocals, s => locals.delete(s)); + forEach(oldLocals, s => locals.set(s.name, s.oldSymbol)); + }; + } + } + } + + return () => { + cleanupParams?.(); + cleanupTypeParams?.(); + cleanupContext(); + context.enclosingDeclaration = oldEnclosingDecl; + context.mapper = oldMapper; + }; + } + + function tryGetThisParameterDeclaration(signature: Signature, context: NodeBuilderContext) { + if (signature.thisParameter) { + return symbolToParameterDeclaration(signature.thisParameter, context); + } + if (signature.declaration && isInJSFile(signature.declaration)) { + const thisTag = getJSDocThisTag(signature.declaration); + if (thisTag && thisTag.typeExpression) { + return factory.createParameterDeclaration( + /*modifiers*/ undefined, + /*dotDotDotToken*/ undefined, + "this", + /*questionToken*/ undefined, + typeToTypeNodeHelper(getTypeFromTypeNode(context, thisTag.typeExpression), context), + ); + } + } + } + + 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(getTypeParameterModifiers(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 typeToTypeNodeHelperWithPossibleReusableTypeNode(type: Type, typeNode: TypeNode | undefined, context: NodeBuilderContext) { + return typeNode && tryReuseExistingNonParameterTypeNode(context, typeNode, type) || typeToTypeNodeHelper(type, context); + } + + function typeParameterToDeclaration(type: TypeParameter, context: NodeBuilderContext, constraint = getConstraintOfTypeParameter(type)): TypeParameterDeclaration { + const constraintNode = constraint && typeToTypeNodeHelperWithPossibleReusableTypeNode(constraint, getConstraintDeclaration(type), context); + return typeParameterToDeclarationWithConstraint(type, context, constraintNode); + } + + function typePredicateToTypePredicateNodeHelper(typePredicate: TypePredicate, context: NodeBuilderContext): TypePredicateNode { + 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); + return factory.createTypePredicateNode(assertsModifier, parameterName, typeNode); + } + + function getEffectiveParameterDeclaration(parameterSymbol: Symbol): ParameterDeclaration | JSDocParameterTag | undefined { + const parameterDeclaration: ParameterDeclaration | JSDocParameterTag | undefined = getDeclarationOfKind(parameterSymbol, SyntaxKind.Parameter); + if (parameterDeclaration) { + return parameterDeclaration; + } + if (!isTransientSymbol(parameterSymbol)) { + return getDeclarationOfKind(parameterSymbol, SyntaxKind.JSDocParameterTag); + } + } + + function symbolToParameterDeclaration(parameterSymbol: Symbol, context: NodeBuilderContext, preserveModifierFlags?: boolean): ParameterDeclaration { + const parameterDeclaration = getEffectiveParameterDeclaration(parameterSymbol); + + const parameterType = getTypeOfSymbol(parameterSymbol); + const parameterTypeNode = serializeTypeForDeclaration(context, parameterDeclaration, parameterType, parameterSymbol); + + 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 = parameterToParameterDeclarationName(parameterSymbol, parameterDeclaration, context); + 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 parameterToParameterDeclarationName(parameterSymbol: Symbol, parameterDeclaration: ParameterDeclaration | JSDocParameterTag | undefined, context: NodeBuilderContext) { + return 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); + + function cloneBindingName(node: BindingName): BindingName { + return elideInitializerAndSetEmitFlags(node) as BindingName; + function elideInitializerAndSetEmitFlags(node: Node): Node { + if (context.tracker.canTrackSymbol && isComputedPropertyName(node) && isLateBindableName(node)) { + trackComputedName(node.expression, context.enclosingDeclaration, context); + } + let visited = visitEachChildWorker(node, elideInitializerAndSetEmitFlags, /*context*/ undefined, /*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 trackComputedName(accessExpression: EntityNameOrEntityNameExpression, enclosingDeclaration: Node | undefined, context: NodeBuilderContext) { + if (!context.tracker.canTrackSymbol) return; + // get symbol of the first identifier of the entityName + const firstIdentifier = getFirstIdentifier(accessExpression); + const name = resolveName(firstIdentifier, firstIdentifier.escapedText, SymbolFlags.Value | SymbolFlags.ExportValue, /*nameNotFoundMessage*/ undefined, /*isUse*/ true); + if (name) { + context.tracker.trackSymbol(name, enclosingDeclaration, SymbolFlags.Value); + } + } + + function lookupSymbolChain(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags, yieldModuleSymbol?: boolean) { + context.tracker.trackSymbol(symbol, context.enclosingDeclaration, meaning); + 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]; + } + + 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; + } + 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 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; + } + if (context.mustCreateTypeParameterSymbolList) { + context.mustCreateTypeParameterSymbolList = false; + context.typeParameterSymbolList = new Set(context.typeParameterSymbolList); + } + context.typeParameterSymbolList!.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, + ); + // NOTE: cast to TransientSymbol should be safe because only TransientSymbol can have CheckFlags.Instantiated + typeParameterNodes = mapToTypeNodes(map(params, t => getMappedType(t, (nextSymbol as TransientSymbol).links.mapper!)), context); + } + else { + typeParameterNodes = typeParametersToTypeParameterDeclarations(symbol, context); + } + } + 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; + } + + function getSpecifierForModuleSymbol(symbol: Symbol, context: NodeBuilderContext, overrideImportMode?: ResolutionMode) { + 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 (ambientModuleSymbolRegex.test(symbol.escapedName as string)) { + return (symbol.escapedName as string).substring(1, (symbol.escapedName as string).length - 1); + } + } + if (!context.enclosingFile || !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 = context.enclosingFile; + const resolutionMode = overrideImportMode || contextFile?.impliedNodeFormat; + const cacheKey = createModeAwareCacheKey(contextFile.path, resolutionMode); + const links = getSymbolLinks(symbol); + let specifier = links.specifierCache && links.specifierCache.get(cacheKey); + if (!specifier) { + const isBundle = !!compilerOptions.outFile; + // 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 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 attributes: ImportAttributes | 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); + attributes = factory.createImportAttributes( + factory.createNodeArray([ + factory.createImportAttribute( + factory.createStringLiteral("resolution-mode"), + factory.createStringLiteral("import"), + ), + ]), + ); + } + } + if (!specifier) { + specifier = getSpecifierForModuleSymbol(chain[0], context); + } + if (!(context.flags & NodeBuilderFlags.AllowNodeModulesRelativePaths) && getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.Classic && specifier.includes("/node_modules/")) { + 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.includes("/node_modules/")) { + // Still unreachable :( + specifier = oldSpecifier; + } + else { + attributes = factory.createImportAttributes( + factory.createNodeArray([ + factory.createImportAttribute( + factory.createStringLiteral("resolution-mode"), + factory.createStringLiteral(swappedMode === ModuleKind.ESNext ? "import" : "require"), + ), + ]), + ); + } + } + + if (!attributes) { + // 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)); + context.approximateLength += specifier.length + 10; // specifier + import("") + if (!nonRootParts || isEntityName(nonRootParts)) { + if (nonRootParts) { + const lastId = isIdentifier(nonRootParts) ? nonRootParts : nonRootParts.right; + setIdentifierTypeArguments(lastId, /*typeArguments*/ undefined); + } + return factory.createImportTypeNode(lit, attributes, 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, attributes, 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); + } + else { + const lastId = isIdentifier(entityName) ? entityName : entityName.right; + const lastTypeArgs = getIdentifierTypeArguments(lastId); + setIdentifierTypeArguments(lastId, /*typeArguments*/ undefined); + return factory.createTypeReferenceNode(entityName, lastTypeArgs as NodeArray); + } + + 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]; + + 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; + } + }); + } + } + + 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)); + } + 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 + 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))); + } + } + + const identifier = setEmitFlags(factory.createIdentifier(symbolName), EmitFlags.NoAsciiEscaping); + if (typeParameterNodes) setIdentifierTypeArguments(identifier, factory.createNodeArray(typeParameterNodes)); + 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 factory.createQualifiedName(LHS, identifier); + } + return identifier; + } + } + + function typeParameterShadowsOtherTypeParameterInScope(escapedName: __String, context: NodeBuilderContext, type: TypeParameter) { + const result = resolveName(context.enclosingDeclaration, escapedName, SymbolFlags.Type, /*nameNotFoundMessage*/ undefined, /*isUse*/ false); + if (result && result.flags & SymbolFlags.TypeParameter) { + return result !== type.symbol; + } + 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; + } + } + let result = symbolToName(type.symbol, context, SymbolFlags.Type, /*expectsIdentifier*/ true); + if (!(result.kind & SyntaxKind.Identifier)) { + return factory.createIdentifier("(Missing type parameter)"); + } + const decl = type.symbol?.declarations?.[0]; + if (decl && isTypeParameterDeclaration(decl)) { + result = setTextRange(context, result, decl.name); + } + 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) || typeParameterShadowsOtherTypeParameterInScope(text as __String, context, type)) { + i++; + text = `${rawtext}_${i}`; + } + if (text !== rawtext) { + const typeArguments = getIdentifierTypeArguments(result); + result = factory.createIdentifier(text); + setIdentifierTypeArguments(result, typeArguments); + } + if (context.mustCreateTypeParametersNamesLookups) { + context.mustCreateTypeParametersNamesLookups = false; + context.typeParameterNames = new Map(context.typeParameterNames); + context.typeParameterNamesByTextNextNameCount = new Map(context.typeParameterNamesByTextNextNameCount); + context.typeParameterNamesByText = new Set(context.typeParameterNamesByText); + } + // 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!.set(rawtext, i); + context.typeParameterNames!.set(getTypeId(type), result); + context.typeParameterNamesByText!.add(text); + } + 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); + + 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; + } + + const identifier = setEmitFlags(factory.createIdentifier(symbolName), EmitFlags.NoAsciiEscaping); + if (typeParameterNodes) setIdentifierTypeArguments(identifier, factory.createNodeArray(typeParameterNodes)); + identifier.symbol = symbol; + + 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)); + } + if (index === 0 || canUsePropertyAccess(symbolName, languageVersion)) { + const identifier = setEmitFlags(factory.createIdentifier(symbolName), EmitFlags.NoAsciiEscaping); + if (typeParameterNodes) setIdentifierTypeArguments(identifier, factory.createNodeArray(typeParameterNodes)); + identifier.symbol = symbol; + + 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) { + const identifier = setEmitFlags(factory.createIdentifier(symbolName), EmitFlags.NoAsciiEscaping); + if (typeParameterNodes) setIdentifierTypeArguments(identifier, factory.createNodeArray(typeParameterNodes)); + identifier.symbol = symbol; + expression = identifier; + } + return factory.createElementAccessExpression(createExpressionFromSymbolChain(chain, index - 1), expression); + } + } + } + + function isStringNamed(d: Declaration) { + const name = getNameOfDeclaration(d); + if (!name) { + return false; + } + if (isComputedPropertyName(name)) { + const type = checkExpression(name.expression); + return !!(type.flags & TypeFlags.StringLike); + } + if (isElementAccessExpression(name)) { + const type = checkExpression(name.argumentExpression); + return !!(type.flags & TypeFlags.StringLike); + } + return isStringLiteral(name); + } + + function isSingleQuotedStringNamed(d: Declaration) { + const name = getNameOfDeclaration(d); + return !!(name && isStringLiteral(name) && (name.singleQuote || !nodeIsSynthesized(name) && startsWith(getTextOfNode(name, /*includeTrivia*/ false), "'"))); + } + + function getPropertyNameNodeForSymbol(symbol: Symbol, context: NodeBuilderContext) { + const stringNamed = !!length(symbol.declarations) && every(symbol.declarations, isStringNamed); + const singleQuote = !!length(symbol.declarations) && every(symbol.declarations, isSingleQuotedStringNamed); + const isMethod = !!(symbol.flags & SymbolFlags.Method); + const fromNameType = getPropertyNameNodeForSymbolFromNameType(symbol, context, singleQuote, stringNamed, isMethod); + if (fromNameType) { + return fromNameType; + } + const rawName = unescapeLeadingUnderscores(symbol.escapedName); + return createPropertyNameNodeForIdentifierOrLiteral(rawName, getEmitScriptTarget(compilerOptions), singleQuote, stringNamed, isMethod); + } + + // See getNameForSymbolFromNameType for a stringy equivalent + function getPropertyNameNodeForSymbolFromNameType(symbol: Symbol, context: NodeBuilderContext, singleQuote: boolean, stringNamed: boolean, isMethod: 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)) && (stringNamed || !isNumericLiteralName(name))) { + return factory.createStringLiteral(name, !!singleQuote); + } + if (isNumericLiteralName(name) && startsWith(name, "-")) { + return factory.createComputedPropertyName(factory.createPrefixUnaryExpression(SyntaxKind.MinusToken, factory.createNumericLiteral(-name))); + } + return createPropertyNameNodeForIdentifierOrLiteral(name, getEmitScriptTarget(compilerOptions), singleQuote, stringNamed, isMethod); + } + if (nameType.flags & TypeFlags.UniqueESSymbol) { + return factory.createComputedPropertyName(symbolToExpression((nameType as UniqueESSymbolType).symbol, context, SymbolFlags.Value)); + } + } + } + + function cloneNodeBuilderContext(context: NodeBuilderContext) { + // 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 + const oldMustCreateTypeParameterSymbolList = context.mustCreateTypeParameterSymbolList; + const oldMustCreateTypeParametersNamesLookups = context.mustCreateTypeParametersNamesLookups; + context.mustCreateTypeParameterSymbolList = true; + context.mustCreateTypeParametersNamesLookups = true; + const oldTypeParameterNames = context.typeParameterNames; + const oldTypeParameterNamesByText = context.typeParameterNamesByText; + const oldTypeParameterNamesByTextNextNameCount = context.typeParameterNamesByTextNextNameCount; + const oldTypeParameterSymbolList = context.typeParameterSymbolList; + return () => { + context.typeParameterNames = oldTypeParameterNames; + context.typeParameterNamesByText = oldTypeParameterNamesByText; + context.typeParameterNamesByTextNextNameCount = oldTypeParameterNamesByTextNextNameCount; + context.typeParameterSymbolList = oldTypeParameterSymbolList; + context.mustCreateTypeParameterSymbolList = oldMustCreateTypeParameterSymbolList; + context.mustCreateTypeParametersNamesLookups = oldMustCreateTypeParametersNamesLookups; + }; + } + + function getDeclarationWithTypeAnnotation(symbol: Symbol, enclosingDeclaration?: Node | undefined) { + return symbol.declarations && find(symbol.declarations, s => !!getNonlocalEffectiveTypeAnnotationNode(s) && (!enclosingDeclaration || !!findAncestor(s, n => n === enclosingDeclaration))); + } + + function existingTypeNodeIsNotReferenceOrIsReferenceWithCompatibleTypeArgumentCount(existing: TypeNode, type: Type) { + // In JS, you can say something like `Foo` and get a `Foo` implicitly - we don't want to preserve that original `Foo` in these cases, though. + if (!(getObjectFlags(type) & ObjectFlags.Reference)) return true; + if (!isTypeReferenceNode(existing)) return true; + // `type` is a reference type, and `existing` is a type reference node, but we still need to make sure they refer to the _same_ target type + // before we go comparing their type argument counts. + void getTypeFromTypeReference(existing); // call to ensure symbol is resolved + const symbol = getNodeLinks(existing).resolvedSymbol; + const existingTarget = symbol && getDeclaredTypeOfSymbol(symbol); + if (!existingTarget || existingTarget !== (type as TypeReference).target) return true; + return length(existing.typeArguments) >= getMinTypeArgumentCount((type as TypeReference).target.typeParameters); + } + + function getEnclosingDeclarationIgnoringFakeScope(enclosingDeclaration: Node) { + while (getNodeLinks(enclosingDeclaration).fakeScopeForSignatureDeclaration) { + enclosingDeclaration = enclosingDeclaration.parent; + } + return enclosingDeclaration; + } + + /** + * 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` + * @param context - The node builder context. Any reused nodes are checked to be pulled from within the scope of the context's enclosingDeclaration. + * @param declaration - The preferred declaration to pull existing type nodes from (the symbol will be used as a fallback to find any annotated declaration) + * @param type - The type to write; an existing annotation must match this type if it's used, otherwise this is the type serialized as a new type node + * @param symbol - The symbol is used both to find an existing annotation if declaration is not provided, and to determine if `unique symbol` should be printed + */ + function serializeTypeForDeclaration(context: NodeBuilderContext, declaration: Declaration | undefined, type: Type, symbol: Symbol) { + const addUndefined = declaration && (isParameter(declaration) || isJSDocParameterTag(declaration)) && requiresAddingImplicitUndefined(declaration); + const enclosingDeclaration = context.enclosingDeclaration; + const oldFlags = context.flags; + if (declaration && hasInferredType(declaration) && !(context.flags & NodeBuilderFlags.NoSyntacticPrinter)) { + syntacticNodeBuilder.serializeTypeOfDeclaration(declaration, context); + } + context.flags |= NodeBuilderFlags.NoSyntacticPrinter; + if (enclosingDeclaration && (!isErrorType(type) || (context.flags & NodeBuilderFlags.AllowUnresolvedNames))) { + const declWithExistingAnnotation = declaration && getNonlocalEffectiveTypeAnnotationNode(declaration) + ? declaration + : getDeclarationWithTypeAnnotation(symbol); + if (declWithExistingAnnotation && !isFunctionLikeDeclaration(declWithExistingAnnotation) && !isGetAccessorDeclaration(declWithExistingAnnotation)) { + // try to reuse the existing annotation + const existing = getNonlocalEffectiveTypeAnnotationNode(declWithExistingAnnotation)!; + const result = !isTypePredicateNode(existing) && tryReuseExistingTypeNode(context, existing, type, declWithExistingAnnotation, addUndefined); + if (result) { + context.flags = oldFlags; + return result; + } + } + } + if ( + type.flags & TypeFlags.UniqueESSymbol && + type.symbol === symbol && (!context.enclosingDeclaration || some(symbol.declarations, d => getSourceFileOfNode(d) === getSourceFileOfNode(context.enclosingDeclaration!))) + ) { + context.flags |= NodeBuilderFlags.AllowUniqueESSymbolType; + } + + const decl = declaration ?? symbol.valueDeclaration ?? symbol.declarations?.[0]; + const expr = decl && isDeclarationWithPossibleInnerTypeNodeReuse(decl) ? getPossibleTypeNodeReuseExpression(decl) : undefined; + + const result = expressionOrTypeToTypeNode(context, expr, type, addUndefined); + context.flags = oldFlags; + return result; + } + + function typeNodeIsEquivalentToType(annotatedDeclaration: Node | undefined, type: Type, typeFromTypeNode: Type) { + if (typeFromTypeNode === type) { + return true; + } + if (annotatedDeclaration && (isParameter(annotatedDeclaration) || isPropertySignature(annotatedDeclaration) || isPropertyDeclaration(annotatedDeclaration)) && annotatedDeclaration.questionToken) { + return getTypeWithFacts(type, TypeFacts.NEUndefined) === typeFromTypeNode; + } + return false; + } + + function serializeReturnTypeForSignature(context: NodeBuilderContext, signature: Signature) { + const suppressAny = context.flags & NodeBuilderFlags.SuppressAnyReturnType; + const flags = context.flags; + if (suppressAny) context.flags &= ~NodeBuilderFlags.SuppressAnyReturnType; // suppress only toplevel `any`s + let returnTypeNode: TypeNode | undefined; + const returnType = getReturnTypeOfSignature(signature); + if (returnType && !(suppressAny && isTypeAny(returnType))) { + if (signature.declaration && !(context.flags & NodeBuilderFlags.NoSyntacticPrinter)) { + syntacticNodeBuilder.serializeReturnTypeForSignature(signature.declaration, context); + } + context.flags |= NodeBuilderFlags.NoSyntacticPrinter; + returnTypeNode = serializeReturnTypeForSignatureWorker(context, signature); + } + else if (!suppressAny) { + returnTypeNode = factory.createKeywordTypeNode(SyntaxKind.AnyKeyword); + } + context.flags = flags; + return returnTypeNode; + } + + function serializeReturnTypeForSignatureWorker(context: NodeBuilderContext, signature: Signature) { + const typePredicate = getTypePredicateOfSignature(signature); + const type = getReturnTypeOfSignature(signature); + if (context.enclosingDeclaration && (!isErrorType(type) || (context.flags & NodeBuilderFlags.AllowUnresolvedNames)) && signature.declaration && !nodeIsSynthesized(signature.declaration)) { + const annotation = signature.declaration && getNonlocalEffectiveReturnTypeAnnotationNode(signature.declaration); + // Default constructor signatures inherited from base classes return the derived class but have the base class declaration + // To ensure we don't serialize the wrong type we check that that return type of the signature corresponds to the declaration return type signature + if (annotation && getTypeFromTypeNode(context, annotation) === type) { + const result = tryReuseExistingTypeNodeHelper(context, annotation); + if (result) { + return result; + } + } + } + if (typePredicate) { + return typePredicateToTypePredicateNodeHelper(typePredicate, context); + } + const expr = signature.declaration && getPossibleTypeNodeReuseExpression(signature.declaration); + return expressionOrTypeToTypeNode(context, expr, type); + } + + function trackExistingEntityName(node: T, context: NodeBuilderContext) { + 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 meaning = getMeaningOfEntityNameReference(node); + let sym: Symbol | undefined; + if (isThisIdentifier(leftmost)) { + // `this` isn't a bindable identifier - skip resolution, find a relevant `this` symbol directly and avoid exhaustive scope traversal + sym = getSymbolOfDeclaration(getThisContainer(leftmost, /*includeArrowFunctions*/ false, /*includeClassComputedPropertyName*/ false)); + if (isSymbolAccessible(sym, leftmost, meaning, /*shouldComputeAliasesToMakeVisible*/ false).accessibility !== SymbolAccessibility.Accessible) { + introducesError = true; + context.tracker.reportInaccessibleThisError(); + } + return { introducesError, node: attachSymbolToLeftmostIdentifier(node) as T }; + } + sym = resolveEntityName(leftmost, meaning, /*ignoreErrors*/ true, /*dontResolveAlias*/ true); + if ( + context.enclosingDeclaration && + !(sym && sym.flags & SymbolFlags.TypeParameter) + ) { + sym = getExportSymbolOfValueSymbolIfExported(sym); + // Some declarations may be transplanted to a new location. + // When this happens we need to make sure that the name has the same meaning at both locations + // We also check for the unknownSymbol because when we create a fake scope some parameters may actually not be usable + // either because they are the expanded rest parameter, + // or because they are the newly added parameters from the tuple, which might have different meanings in the original context + const symAtLocation = resolveEntityName(leftmost, meaning, /*ignoreErrors*/ true, /*dontResolveAlias*/ true, context.enclosingDeclaration); + if ( + // Check for unusable parameters symbols + symAtLocation === unknownSymbol || + // If the symbol is not found, but was not found in the original scope either we probably have an error, don't reuse the node + (symAtLocation === undefined && sym !== undefined) || + // If the symbol is found both in declaration scope and in current scope then it shoudl point to the same reference + (symAtLocation && sym && !getSymbolIfSameReference(getExportSymbolOfValueSymbolIfExported(symAtLocation), sym)) + ) { + // In isolated declaration we will not do rest parameter expansion so there is no need to report on these. + if (symAtLocation !== unknownSymbol) { + context.tracker.reportInferenceFallback(node); + } + introducesError = true; + return { introducesError, node, sym }; + } + } + + if (sym) { + // If a parameter is resolvable in the current context it is also visible, so no need to go to symbol accesibility + if ( + sym.flags & SymbolFlags.FunctionScopedVariable + && sym.valueDeclaration + ) { + if (isPartOfParameterDeclaration(sym.valueDeclaration) || isJSDocParameterTag(sym.valueDeclaration)) { + return { introducesError, node: attachSymbolToLeftmostIdentifier(node) as T }; + } + } + if ( + !(sym.flags & SymbolFlags.TypeParameter) && // Type parameters are visible in the current context if they are are resolvable + !isDeclarationName(node) && + isSymbolAccessible(sym, context.enclosingDeclaration, meaning, /*shouldComputeAliasesToMakeVisible*/ false).accessibility !== SymbolAccessibility.Accessible + ) { + context.tracker.reportInferenceFallback(node); + introducesError = true; + } + else { + context.tracker.trackSymbol(sym, context.enclosingDeclaration, meaning); + } + return { introducesError, node: attachSymbolToLeftmostIdentifier(node) as T }; + } + + return { introducesError, node }; + + /** + * Attaches a `.symbol` member to an identifier, cloning it to do so, so symbol information + * is smuggled out for symbol display information. + */ + function attachSymbolToLeftmostIdentifier(node: Node): Node { + if (node === leftmost) { + const type = getDeclaredTypeOfSymbol(sym!); + const name = sym!.flags & SymbolFlags.TypeParameter ? typeParameterToName(type, context) : factory.cloneNode(node as Identifier); + name.symbol = sym!; // for quickinfo, which uses identifier symbol information + return setTextRange(context, setEmitFlags(name, EmitFlags.NoAsciiEscaping), node); + } + const updated = visitEachChildWorker(node, c => attachSymbolToLeftmostIdentifier(c), /*context*/ undefined); + if (updated !== node) { + setTextRange(context, updated, node); + } + return updated; + } + } + + function serializeTypeName(context: NodeBuilderContext, node: EntityName, isTypeOf?: boolean, typeArguments?: readonly TypeNode[]) { + const meaning = isTypeOf ? SymbolFlags.Value : SymbolFlags.Type; + const symbol = resolveEntityName(node, meaning, /*ignoreErrors*/ true); + if (!symbol) return undefined; + const resolvedSymbol = symbol.flags & SymbolFlags.Alias ? resolveAlias(symbol) : symbol; + if (isSymbolAccessible(symbol, context.enclosingDeclaration, meaning, /*shouldComputeAliasesToMakeVisible*/ false).accessibility !== SymbolAccessibility.Accessible) return undefined; + return symbolToTypeNode(resolvedSymbol, context, meaning, typeArguments); + } + + function canReuseTypeNode(context: NodeBuilderContext, existing: TypeNode) { + if (isInJSFile(existing)) { + if (isLiteralImportTypeNode(existing)) { + // Ensure resolvedSymbol is present + void getTypeFromImportTypeNode(existing); + const nodeSymbol = getNodeLinks(existing).resolvedSymbol; + return ( + !nodeSymbol || + !( + // The import type resolved using jsdoc fallback logic + (!existing.isTypeOf && !(nodeSymbol.flags & SymbolFlags.Type)) || + // The import type had type arguments autofilled by js fallback logic + !(length(existing.typeArguments) >= getMinTypeArgumentCount(getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(nodeSymbol))) + ) + ); + } + } + if (isThisTypeNode(existing)) { + if (context.mapper === undefined) return true; + const type = getTypeFromTypeNode(context, existing, /*noMappedTypes*/ true); + return !!type; + } + if (isTypeReferenceNode(existing)) { + if (isConstTypeReference(existing)) return false; + const type = getTypeFromTypeReference(existing); + const symbol = getNodeLinks(existing).resolvedSymbol; + if (!symbol) return false; + if (symbol.flags & SymbolFlags.TypeParameter) { + const type = getDeclaredTypeOfSymbol(symbol); + if (context.mapper && getMappedType(type, context.mapper) !== type) { + return false; + } + } + if (isInJSDoc(existing)) { + return existingTypeNodeIsNotReferenceOrIsReferenceWithCompatibleTypeArgumentCount(existing, type) + && !getIntendedTypeFromJSDocTypeReference(existing) // We should probably allow the reuse of JSDoc reference types such as String Number etc + && (symbol.flags & SymbolFlags.Type); // JSDoc type annotations can reference values (meaning typeof value) as well as types. We only reuse type nodes + } + } + if ( + isTypeOperatorNode(existing) && + existing.operator === SyntaxKind.UniqueKeyword && + existing.type.kind === SyntaxKind.SymbolKeyword + ) { + const effectiveEnclosingContext = context.enclosingDeclaration && getEnclosingDeclarationIgnoringFakeScope(context.enclosingDeclaration); + return !!findAncestor(existing, n => n === effectiveEnclosingContext); + } + return true; + } + + function serializeExistingTypeNode(context: NodeBuilderContext, typeNode: TypeNode) { + const type = getTypeFromTypeNode(context, typeNode); + return typeToTypeNodeHelper(type, context); + } + + /** + * Do you mean to call this directly? You probably should use `tryReuseExistingTypeNode` instead, + * which performs sanity checking on the type before doing this. + */ + function tryReuseExistingTypeNodeHelper(context: NodeBuilderContext, existing: TypeNode) { + if (cancellationToken && cancellationToken.throwIfCancellationRequested) { + cancellationToken.throwIfCancellationRequested(); + } + let hadError = false; + const { finalizeBoundary, startRecoveryScope } = createRecoveryBoundary(); + const transformed = visitNode(existing, visitExistingNodeTreeSymbols, isTypeNode); + if (!finalizeBoundary()) { + return undefined; + } + context.approximateLength += existing.end - existing.pos; + return transformed; + + function visitExistingNodeTreeSymbols(node: Node): Node | undefined { + // If there was an error in a sibling node bail early, the result will be discarded anyway + if (hadError) return node; + const recover = startRecoveryScope(); + const onExitNewScope = isNewScopeNode(node) ? onEnterNewScope(node) : undefined; + const result = visitExistingNodeTreeSymbolsWorker(node); + onExitNewScope?.(); + + // If there was an error, maybe we can recover by serializing the actual type of the node + if (hadError) { + if (isTypeNode(node) && !isTypePredicateNode(node)) { + recover(); + return serializeExistingTypeNode(context, node); + } + return node; + } + // We want to clone the subtree, so when we mark it up with __pos and __end in quickfixes, + // we don't get odd behavior because of reused nodes. We also need to clone to _remove_ + // the position information if the node comes from a different file than the one the node builder + // is set to build for (even though we are reusing the node structure, the position information + // would make the printer print invalid spans for literals and identifiers, and the formatter would + // choke on the mismatched positonal spans between a parent and an injected child from another file). + return result ? setTextRange(context, result, node) : undefined; + } + + function createRecoveryBoundary() { + let trackedSymbols: TrackedSymbol[]; + let unreportedErrors: (() => void)[]; + const oldTracker = context.tracker; + const oldTrackedSymbols = context.trackedSymbols; + context.trackedSymbols = undefined; + const oldEncounteredError = context.encounteredError; + context.tracker = new SymbolTrackerImpl(context, { + ...oldTracker.inner, + reportCyclicStructureError() { + markError(() => oldTracker.reportCyclicStructureError()); + }, + reportInaccessibleThisError() { + markError(() => oldTracker.reportInaccessibleThisError()); + }, + reportInaccessibleUniqueSymbolError() { + markError(() => oldTracker.reportInaccessibleUniqueSymbolError()); + }, + reportLikelyUnsafeImportRequiredError(specifier) { + markError(() => oldTracker.reportLikelyUnsafeImportRequiredError(specifier)); + }, + reportNonSerializableProperty(name) { + markError(() => oldTracker.reportNonSerializableProperty(name)); + }, + trackSymbol(sym, decl, meaning) { + (trackedSymbols ??= []).push([sym, decl, meaning]); + return false; + }, + moduleResolverHost: context.tracker.moduleResolverHost, + }, context.tracker.moduleResolverHost); + + return { + startRecoveryScope, + finalizeBoundary, + }; + + function markError(unreportedError: () => void) { + hadError = true; + (unreportedErrors ??= []).push(unreportedError); + } + + function startRecoveryScope() { + const trackedSymbolsTop = trackedSymbols?.length ?? 0; + const unreportedErrorsTop = unreportedErrors?.length ?? 0; + return () => { + hadError = false; + // Reset the tracked symbols to before the error + if (trackedSymbols) { + trackedSymbols.length = trackedSymbolsTop; + } + if (unreportedErrors) { + unreportedErrors.length = unreportedErrorsTop; + } + }; + } + + function finalizeBoundary() { + context.tracker = oldTracker; + context.trackedSymbols = oldTrackedSymbols; + context.encounteredError = oldEncounteredError; + + unreportedErrors?.forEach(fn => fn()); + if (hadError) { + return false; + } + trackedSymbols?.forEach( + ([symbol, enclosingDeclaration, meaning]) => + context.tracker.trackSymbol( + symbol, + enclosingDeclaration, + meaning, + ), + ); + return true; + } + } + function onEnterNewScope(node: IntroducesNewScopeNode | ConditionalTypeNode) { + return enterNewScope(context, node, getParametersInScope(node), getTypeParametersInScope(node)); + } + + function tryVisitSimpleTypeNode(node: TypeNode): TypeNode | undefined { + const innerNode = skipTypeParentheses(node); + switch (innerNode.kind) { + case SyntaxKind.TypeReference: + return tryVisitTypeReference(innerNode as TypeReferenceNode); + case SyntaxKind.TypeQuery: + return tryVisitTypeQuery(innerNode as TypeQueryNode); + case SyntaxKind.IndexedAccessType: + return tryVisitIndexedAccess(innerNode as IndexedAccessTypeNode); + case SyntaxKind.TypeOperator: + const typeOperatorNode = innerNode as TypeOperatorNode; + if (typeOperatorNode.operator === SyntaxKind.KeyOfKeyword) { + return tryVisitKeyOf(typeOperatorNode); + } + } + return visitNode(node, visitExistingNodeTreeSymbols, isTypeNode); + } + + function tryVisitIndexedAccess(node: IndexedAccessTypeNode): TypeNode | undefined { + const resultObjectType = tryVisitSimpleTypeNode(node.objectType); + if (resultObjectType === undefined) { + return undefined; + } + return factory.updateIndexedAccessTypeNode(node, resultObjectType, visitNode(node.indexType, visitExistingNodeTreeSymbols, isTypeNode)!); + } + + function tryVisitKeyOf(node: TypeOperatorNode): TypeNode | undefined { + Debug.assertEqual(node.operator, SyntaxKind.KeyOfKeyword); + const type = tryVisitSimpleTypeNode(node.type); + if (type === undefined) { + return undefined; + } + return factory.updateTypeOperatorNode(node, type); + } + + function tryVisitTypeQuery(node: TypeQueryNode): TypeNode | undefined { + const { introducesError, node: exprName } = trackExistingEntityName(node.exprName, context); + if (!introducesError) { + return factory.updateTypeQueryNode( + node, + exprName, + visitNodes(node.typeArguments, visitExistingNodeTreeSymbols, isTypeNode), + ); + } + + const serializedName = serializeTypeName(context, node.exprName, /*isTypeOf*/ true); + if (serializedName) { + return setTextRange(context, serializedName, node.exprName); + } + } + + function tryVisitTypeReference(node: TypeReferenceNode): TypeNode | undefined { + if (canReuseTypeNode(context, node)) { + const { introducesError, node: newName } = trackExistingEntityName(node.typeName, context); + const typeArguments = visitNodes(node.typeArguments, visitExistingNodeTreeSymbols, isTypeNode); + + if (!introducesError) { + const updated = factory.updateTypeReferenceNode( + node, + newName, + typeArguments, + ); + return setTextRange(context, updated, node); + } + else { + const serializedName = serializeTypeName(context, node.typeName, /*isTypeOf*/ false, typeArguments); + if (serializedName) { + return setTextRange(context, serializedName, node.typeName); + } + } + } + } + + function visitExistingNodeTreeSymbolsWorker(node: Node): Node | undefined { + if (isJSDocTypeExpression(node)) { + // Unwrap JSDocTypeExpressions + return visitNode(node.type, visitExistingNodeTreeSymbols, isTypeNode); + } + // 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, isTypeNode)!, factory.createLiteralTypeNode(factory.createNull())]); + } + if (isJSDocOptionalType(node)) { + return factory.createUnionTypeNode([visitNode(node.type, visitExistingNodeTreeSymbols, isTypeNode)!, factory.createKeywordTypeNode(SyntaxKind.UndefinedKeyword)]); + } + if (isJSDocNonNullableType(node)) { + return visitNode(node.type, visitExistingNodeTreeSymbols); + } + if (isJSDocVariadicType(node)) { + return factory.createArrayTypeNode(visitNode(node.type, visitExistingNodeTreeSymbols, isTypeNode)!); + } + if (isJSDocTypeLiteral(node)) { + return factory.createTypeLiteralNode(map(node.jsDocPropertyTags, t => { + const name = visitNode(isIdentifier(t.name) ? t.name : t.name.right, visitExistingNodeTreeSymbols, isIdentifier)!; + const typeViaParent = getTypeOfPropertyOfType(getTypeFromTypeNode(context, node), name.escapedText); + const overrideTypeNode = typeViaParent && t.typeExpression && getTypeFromTypeNode(context, t.typeExpression.type) !== typeViaParent ? typeToTypeNodeHelper(typeViaParent, context) : undefined; + + 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, isTypeNode)) || 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, isTypeNode), + )], + visitNode(node.typeArguments![1], visitExistingNodeTreeSymbols, isTypeNode), + )]); + } + if (isJSDocFunctionType(node)) { + if (isJSDocConstructSignature(node)) { + let newTypeNode: TypeNode | undefined; + return factory.createConstructorTypeNode( + /*modifiers*/ undefined, + visitNodes(node.typeParameters, visitExistingNodeTreeSymbols, isTypeParameterDeclaration), + mapDefined(node.parameters, (p, i) => + p.name && isIdentifier(p.name) && p.name.escapedText === "new" ? (newTypeNode = p.type, undefined) : factory.createParameterDeclaration( + /*modifiers*/ undefined, + getEffectiveDotDotDotForParameter(p), + setTextRange(context, factory.createIdentifier(getNameForJSDocFunctionParameter(p, i)), p), + factory.cloneNode(p.questionToken), + visitNode(p.type, visitExistingNodeTreeSymbols, isTypeNode), + /*initializer*/ undefined, + )), + visitNode(newTypeNode || node.type, visitExistingNodeTreeSymbols, isTypeNode) || factory.createKeywordTypeNode(SyntaxKind.AnyKeyword), + ); + } + else { + return factory.createFunctionTypeNode( + visitNodes(node.typeParameters, visitExistingNodeTreeSymbols, isTypeParameterDeclaration), + map(node.parameters, (p, i) => + factory.createParameterDeclaration( + /*modifiers*/ undefined, + getEffectiveDotDotDotForParameter(p), + setTextRange(context, factory.createIdentifier(getNameForJSDocFunctionParameter(p, i)), p), + factory.cloneNode(p.questionToken), + visitNode(p.type, visitExistingNodeTreeSymbols, isTypeNode), + /*initializer*/ undefined, + )), + visitNode(node.type, visitExistingNodeTreeSymbols, isTypeNode) || factory.createKeywordTypeNode(SyntaxKind.AnyKeyword), + ); + } + } + if (isThisTypeNode(node)) { + if (canReuseTypeNode(context, node)) { + return node; + } + hadError = true; + return node; + } + if (isTypeParameterDeclaration(node)) { + return factory.updateTypeParameterDeclaration( + node, + visitNodes(node.modifiers, visitExistingNodeTreeSymbols, isModifier), + setTextRange(context, typeParameterToName(getDeclaredTypeOfSymbol(getSymbolOfDeclaration(node)), context), node), + visitNode(node.constraint, visitExistingNodeTreeSymbols, isTypeNode), + visitNode(node.default, visitExistingNodeTreeSymbols, isTypeNode), + ); + } + + if (isIndexedAccessTypeNode(node)) { + const result = tryVisitIndexedAccess(node); + if (!result) { + hadError = true; + return node; + } + return result; + } + + if (isTypeReferenceNode(node)) { + const result = tryVisitTypeReference(node); + if (result) { + return result; + } + hadError = true; + return 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 setTextRange(context, typeToTypeNodeHelper(getTypeFromTypeNode(context, node), context), node); + } + return factory.updateImportTypeNode( + node, + factory.updateLiteralTypeNode(node.argument, rewriteModuleSpecifier(node, node.argument.literal)), + visitNode(node.attributes, visitExistingNodeTreeSymbols, isImportAttributes), + visitNode(node.qualifier, visitExistingNodeTreeSymbols, isEntityName), + visitNodes(node.typeArguments, visitExistingNodeTreeSymbols, isTypeNode), + node.isTypeOf, + ); + } + if (isNamedDeclaration(node) && node.name.kind === SyntaxKind.ComputedPropertyName && !isLateBindableName(node.name)) { + if (!(context.flags & NodeBuilderFlags.AllowUnresolvedNames && hasDynamicName(node) && isEntityNameExpression(node.name.expression) && checkComputedPropertyName(node.name).flags & TypeFlags.Any)) { + return undefined; + } + } + if ( + (isFunctionLike(node) && !node.type) + || (isPropertyDeclaration(node) && !node.type && !node.initializer) + || (isPropertySignature(node) && !node.type && !node.initializer) + || (isParameter(node) && !node.type && !node.initializer) + ) { + let visited = visitEachChild(node, visitExistingNodeTreeSymbols); + if (visited === node) { + visited = setTextRange(context, factory.cloneNode(node), node); + } + (visited as Mutable).type = factory.createKeywordTypeNode(SyntaxKind.AnyKeyword); + if (isParameter(node)) { + (visited as Mutable).modifiers = undefined; + } + return visited; + } + if (isTypeQueryNode(node)) { + const result = tryVisitTypeQuery(node); + if (!result) { + hadError = true; + return node; + } + return result; + } + if (isComputedPropertyName(node) && isEntityNameExpression(node.expression)) { + const { node: result, introducesError } = trackExistingEntityName(node.expression, context); + if (!introducesError) { + return factory.updateComputedPropertyName(node, result); + } + else { + const type = getWidenedType(getRegularTypeOfExpression(node.expression)); + const computedPropertyNameType = typeToTypeNodeHelper(type, context); + let literal; + if (isLiteralTypeNode(computedPropertyNameType)) { + literal = computedPropertyNameType.literal; + } + else { + const evaluated = evaluateEntityNameExpression(node.expression); + const literalNode = typeof evaluated.value === "string" ? factory.createStringLiteral(evaluated.value, /*isSingleQuote*/ undefined) : + typeof evaluated.value === "number" ? factory.createNumericLiteral(evaluated.value, /*numericLiteralFlags*/ 0) : + undefined; + if (!literalNode) { + if (isImportTypeNode(computedPropertyNameType)) { + trackComputedName(node.expression, context.enclosingDeclaration, context); + } + return node; + } + literal = literalNode; + } + if (literal.kind === SyntaxKind.StringLiteral && isIdentifierText(literal.text, getEmitScriptTarget(compilerOptions))) { + return factory.createIdentifier(literal.text); + } + if (literal.kind === SyntaxKind.NumericLiteral && !literal.text.startsWith("-")) { + return literal; + } + return factory.updateComputedPropertyName(node, literal); + } + } + if (isTypePredicateNode(node)) { + let parameterName; + if (isIdentifier(node.parameterName)) { + const { node: result, introducesError } = trackExistingEntityName(node.parameterName, context); + // Should not usually happen the only case is when a type predicate comes from a JSDoc type annotation with it's own parameter symbol definition. + // /** @type {(v: unknown) => v is undefined} */ + // const isUndef = v => v === undefined; + hadError = hadError || introducesError; + parameterName = result; + } + else { + parameterName = factory.cloneNode(node.parameterName); + } + return factory.updateTypePredicateNode(node, factory.cloneNode(node.assertsModifier), parameterName, visitNode(node.type, visitExistingNodeTreeSymbols, isTypeNode)); + } + + if (isTupleTypeNode(node) || isTypeLiteralNode(node) || isMappedTypeNode(node)) { + const visited = visitEachChild(node, visitExistingNodeTreeSymbols); + const clone = setTextRange(context, visited === node ? factory.cloneNode(node) : visited, node); + const flags = getEmitFlags(clone); + setEmitFlags(clone, flags | (context.flags & NodeBuilderFlags.MultilineObjectLiterals && isTypeLiteralNode(node) ? 0 : EmitFlags.SingleLine)); + return clone; + } + if (isStringLiteral(node) && !!(context.flags & NodeBuilderFlags.UseSingleQuotesForStringLiteralType) && !node.singleQuote) { + const clone = factory.cloneNode(node); + (clone as Mutable).singleQuote = true; + return clone; + } + if (isConditionalTypeNode(node)) { + const checkType = visitNode(node.checkType, visitExistingNodeTreeSymbols, isTypeNode)!; + + const disposeScope = onEnterNewScope(node); + const extendType = visitNode(node.extendsType, visitExistingNodeTreeSymbols, isTypeNode)!; + const trueType = visitNode(node.trueType, visitExistingNodeTreeSymbols, isTypeNode)!; + disposeScope(); + const falseType = visitNode(node.falseType, visitExistingNodeTreeSymbols, isTypeNode)!; + return factory.updateConditionalTypeNode( + node, + checkType, + extendType, + trueType, + falseType, + ); + } + + if (isTypeOperatorNode(node)) { + if (node.operator === SyntaxKind.UniqueKeyword && node.type.kind === SyntaxKind.SymbolKeyword) { + if (!canReuseTypeNode(context, node)) { + hadError = true; + return node; + } + } + else if (node.operator === SyntaxKind.KeyOfKeyword) { + const result = tryVisitKeyOf(node); + if (!result) { + hadError = true; + return node; + } + return result; + } + } + + return visitEachChild(node, visitExistingNodeTreeSymbols); + + function visitEachChild(node: T, visitor: Visitor): T; + function visitEachChild(node: T | undefined, visitor: Visitor): T | undefined; + function visitEachChild(node: T | undefined, visitor: Visitor): T | undefined { + const nonlocalNode = !context.enclosingFile || context.enclosingFile !== getSourceFileOfNode(node); + return visitEachChildWorker(node, visitor, /*context*/ undefined, nonlocalNode ? visitNodesWithoutCopyingPositions : undefined); + } + + function visitNodesWithoutCopyingPositions( + nodes: NodeArray | undefined, + visitor: Visitor, + test?: (node: Node) => boolean, + start?: number, + count?: number, + ): NodeArray | undefined { + let result = visitNodes(nodes, visitor, test, start, count); + if (result) { + if (result.pos !== -1 || result.end !== -1) { + if (result === nodes) { + result = factory.createNodeArray(nodes, nodes.hasTrailingComma); + } + setTextRangePosEnd(result, -1, -1); + } + } + return result; + } + + function getEffectiveDotDotDotForParameter(p: ParameterDeclaration) { + return p.dotDotDotToken || (p.type && isJSDocVariadicType(p.type) ? factory.createToken(SyntaxKind.DotDotDotToken) : 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}`; + } + + function rewriteModuleSpecifier(parent: ImportTypeNode, lit: StringLiteral) { + if (context.bundled || context.enclosingFile !== getSourceFileOfNode(lit)) { + let name = lit.text; + const nodeSymbol = getNodeLinks(node).resolvedSymbol; + const meaning = parent.isTypeOf ? SymbolFlags.Value : SymbolFlags.Type; + const parentSymbol = nodeSymbol + && isSymbolAccessible(nodeSymbol, context.enclosingDeclaration, meaning, /*shouldComputeAliasesToMakeVisible*/ false).accessibility === SymbolAccessibility.Accessible + && lookupSymbolChain(nodeSymbol, context, meaning, /*yieldModuleSymbol*/ true)[0]; + if (parentSymbol && isExternalModuleSymbol(parentSymbol)) { + name = getSpecifierForModuleSymbol(parentSymbol, context); + } + else { + const targetFile = getExternalModuleFileFromDeclaration(parent); + if (targetFile) { + name = getSpecifierForModuleSymbol(targetFile.symbol, context); + } + } + if (name.includes("/node_modules/")) { + context.encounteredError = true; + if (context.tracker.reportLikelyUnsafeImportRequiredError) { + context.tracker.reportLikelyUnsafeImportRequiredError(name); + } + } + if (name !== lit.text) { + return setOriginalNode(factory.createStringLiteral(name), lit); + } + } + return visitNode(lit, visitExistingNodeTreeSymbols, isStringLiteral)!; + } + } + } + + function symbolTableToDeclarationStatements(symbolTable: SymbolTable, context: NodeBuilderContext): Statement[] { + const serializePropertySymbolForClass = makeSerializePropertySymbol(factory.createPropertyDeclaration, SyntaxKind.MethodDeclaration, /*useAccessors*/ true); + const serializePropertySymbolForInterfaceWorker = makeSerializePropertySymbol((mods, name, question, type) => factory.createPropertySignature(mods, name, question, type), SyntaxKind.MethodSignature, /*useAccessors*/ 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: Map[] = []; + const oldcontext = context; + context = { + ...oldcontext, + usedSymbolNames: new Set(oldcontext.usedSymbolNames), + remappedSymbolNames: new Map(), + remappedSymbolReferences: new Map(oldcontext.remappedSymbolReferences?.entries()), + tracker: undefined!, + }; + const tracker: SymbolTracker = { + ...oldcontext.tracker.inner, + trackSymbol: (sym, decl, meaning) => { + if (context.remappedSymbolNames?.has(getSymbolId(sym))) return false; // If the context has a remapped name for the symbol, it *should* mean it's been made visible + const accessibleResult = isSymbolAccessible(sym, decl, meaning, /*shouldComputeAliasesToMakeVisible*/ 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)) { + // Only include referenced privates in the same file. Weird JS aliases may expose privates + // from other files - assume JS transforms will make those available via expected means + const root = chain[0]; + const contextFile = getSourceFileOfNode(oldcontext.enclosingDeclaration); + if (some(root.declarations, d => getSourceFileOfNode(d) === contextFile)) { + includePrivateSymbol(root); + } + } + } + else if (oldcontext.tracker.inner?.trackSymbol) { + return oldcontext.tracker.inner.trackSymbol(sym, decl, meaning); + } + return false; + }, + }; + context.tracker = new SymbolTrackerImpl(context, tracker, oldcontext.tracker.moduleResolverHost); + forEachEntry(symbolTable, (symbol, name) => { + const baseName = unescapeLeadingUnderscores(name); + void getInternalSymbolName(symbol, baseName); // Called to cache values into `usedSymbolNames` and `remappedSymbolNames` + }); + let addingDeclare = !context.bundled; + const exportEquals = symbolTable.get(InternalSymbolName.ExportEquals); + if (exportEquals && symbolTable.size > 1 && exportEquals.flags & (SymbolFlags.Alias | SymbolFlags.Module)) { + 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, /*propertyName*/ 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]; + } + } + 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.includes(s as ExportDeclaration)), + factory.createExportDeclaration( + /*modifiers*/ undefined, + /*isTypeOnly*/ false, + factory.createNamedExports(flatMap(group, e => cast(e.exportClause, isNamedExports).elements)), + group[0].moduleSpecifier, + ), + ]; + } + } + } + } + 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.attributes && !!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 && e.name.kind !== SyntaxKind.StringLiteral) { + // export {name} - look thru `statements` for `name`, and if all results can take an `export` modifier, do so and filter it + const name = e.name; + const indices = indicesOf(statements); + const associatedIndices = filter(indices, i => nodeHasName(statements[i], 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.attributes, + ); + } + } + return 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; + } + + function addExportModifier(node: Extract) { + const flags = (getEffectiveModifierFlags(node) | ModifierFlags.Export) & ~ModifierFlags.Ambient; + return factory.replaceModifiers(node, flags); + } + + function removeExportModifier(node: Extract) { + const flags = getEffectiveModifierFlags(node) & ~ModifierFlags.Export; + return factory.replaceModifiers(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); + }); + 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 { + void getPropertiesOfType(getTypeOfSymbol(symbol)); // resolve symbol's type and properties, which should trigger any required merges + // 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 scopeCleanup = cloneNodeBuilderContext(context); + serializeSymbolWorker(symbol, isPrivate, propertyAsAlias); + scopeCleanup(); + } + } + + // 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, escapedSymbolName = symbol.escapedName): void { + const symbolName = unescapeLeadingUnderscores(escapedSymbolName); + const isDefault = escapedSymbolName === 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) && + escapedSymbolName !== 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 | SymbolFlags.Accessor) + && escapedSymbolName !== 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 (type.symbol && type.symbol !== symbol && type.symbol.flags & SymbolFlags.Function && some(type.symbol.declarations, isFunctionExpressionOrArrowFunction) && (type.symbol.members?.size || type.symbol.exports?.size)) { + // assignment of a anonymous expando/class-like function, the func/ns/merge branch below won't trigger, + // and the assignment form has to reference the unreachable anonymous type so will error. + // Instead, serialize the type's symbol, but with the current symbol's name, rather than the anonymous one. + if (!context.remappedSymbolReferences) { + context.remappedSymbolReferences = new Map(); + } + context.remappedSymbolReferences.set(getSymbolId(type.symbol), symbol); // save name remapping as local name for target symbol + serializeSymbolWorker(type.symbol, isPrivate, propertyAsAlias, escapedSymbolName); + context.remappedSymbolReferences.delete(getSymbolId(type.symbol)); + } + else 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 { + // 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 + : isConstantVariable(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 { + const statement = setTextRange( + context, + factory.createVariableStatement( + /*modifiers*/ undefined, + factory.createVariableDeclarationList([ + factory.createVariableDeclaration(name, /*exclamationToken*/ undefined, serializeTypeForDeclaration(context, /*declaration*/ undefined, type, symbol)), + ], 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; + } + } + } + } + } + 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) { + 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*/ (node as ExportDeclaration).isTypeOnly, /*exportClause*/ undefined, factory.createStringLiteral(getSpecifierForModuleSymbol(resolvedModule, context))), ModifierFlags.None); + } + } + } + if (needsPostExportDefault) { + addResult(factory.createExportAssignment(/*modifiers*/ undefined, /*isExportEquals*/ 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, isPartOfParameterDeclaration)) 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.replaceModifiers(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) + && tryReuseExistingNonParameterTypeNode(context, jsdocAliasDecl.typeExpression.type, aliasType, /*host*/ undefined) + || 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) { + let exports = arrayFrom(getExportsOfSymbol(symbol).values()); + const merged = getMergedSymbol(symbol); + if (merged !== symbol) { + const membersSet = new Set(exports); + for (const exported of getExportsOfSymbol(merged).values()) { + if (!(getSymbolFlags(resolveSymbol(exported)) & SymbolFlags.Value)) { + membersSet.add(exported); + } + } + exports = arrayFrom(membersSet); + } + return filter(exports, m => isNamespaceMember(m) && isIdentifierText(m.escapedName as string, ScriptTarget.ESNext)); + } + + function isTypeOnlyNamespace(symbol: Symbol) { + return every(getNamespaceMembersForSerialization(symbol), m => !(getSymbolFlags(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, + factory.createIdentifier(localName), + nsBody, + NodeFlags.Namespace, + ), + ModifierFlags.None, + ); + } + } + + 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 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) }) as FunctionDeclaration; + addResult(setTextRange(context, 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 getSignatureTextRangeLocation(signature: Signature) { + if (signature.declaration && signature.declaration.parent) { + if (isBinaryExpression(signature.declaration.parent) && getAssignmentDeclarationKind(signature.declaration.parent) === AssignmentDeclarationKind.Property) { + return signature.declaration.parent; + } + // 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 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 as Extract[], 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)); + if (introducesError) { + return cleanup(/*result*/ undefined); + } + } + return cleanup(factory.createExpressionWithTypeArguments( + expr, + map(e.typeArguments, a => + tryReuseExistingNonParameterTypeNode(context, a, getTypeFromTypeNode(context, a)) + || typeToTypeNodeHelper(getTypeFromTypeNode(context, a), context)), + )); + + function cleanup(result: T): T { + context.enclosingDeclaration = oldEnclosing; + return result; + } + }); + 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 = getTypeWithThisArgument(getDeclaredTypeOfClassOrInterface(symbol)) as InterfaceType; + 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( + context, + 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 getSomeTargetNameFromDeclarations(declarations: Declaration[] | undefined) { + return firstDefined(declarations, d => { + if (isImportSpecifier(d) || isExportSpecifier(d)) { + return moduleExportNameTextUnescaped(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; + }); + } + + 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 && 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( + /*isTypeOnly*/ false, + /*name*/ undefined, + factory.createNamedImports([factory.createImportSpecifier( + /*isTypeOnly*/ false, + propertyName && isIdentifier(propertyName) ? factory.createIdentifier(idText(propertyName)) : undefined, + factory.createIdentifier(localName), + )]), + ), + factory.createStringLiteral(specifier), + /*attributes*/ 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, d => isSourceFile(d) && isJsonSourceFile(d))) { + 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: { + const generatedSpecifier = getSpecifierForModuleSymbol(target.parent || target, context); // generate specifier (even though we're reusing and existing one) for ambient module reference include side effects + const specifier = context.bundled ? factory.createStringLiteral(generatedSpecifier) : (node as ImportClause).parent.moduleSpecifier; + const attributes = isImportDeclaration(node.parent) ? node.parent.attributes : undefined; + const isTypeOnly = isJSDocImportTag((node as ImportClause).parent); + addResult( + factory.createImportDeclaration( + /*modifiers*/ undefined, + factory.createImportClause(isTypeOnly, factory.createIdentifier(localName), /*namedBindings*/ undefined), + specifier, + attributes, + ), + ModifierFlags.None, + ); + break; + } + case SyntaxKind.NamespaceImport: { + const generatedSpecifier = getSpecifierForModuleSymbol(target.parent || target, context); // generate specifier (even though we're reusing and existing one) for ambient module reference include side effects + const specifier = context.bundled ? factory.createStringLiteral(generatedSpecifier) : (node as NamespaceImport).parent.parent.moduleSpecifier; + const isTypeOnly = isJSDocImportTag((node as NamespaceImport).parent.parent); + addResult( + factory.createImportDeclaration( + /*modifiers*/ undefined, + factory.createImportClause(isTypeOnly, /*name*/ undefined, factory.createNamespaceImport(factory.createIdentifier(localName))), + specifier, + (node as ImportClause).parent.attributes, + ), + 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: { + const generatedSpecifier = getSpecifierForModuleSymbol(target.parent || target, context); // generate specifier (even though we're reusing and existing one) for ambient module reference include side effects + const specifier = context.bundled ? factory.createStringLiteral(generatedSpecifier) : (node as ImportSpecifier).parent.parent.parent.moduleSpecifier; + const isTypeOnly = isJSDocImportTag((node as ImportSpecifier).parent.parent.parent); + addResult( + factory.createImportDeclaration( + /*modifiers*/ undefined, + factory.createImportClause( + isTypeOnly, + /*name*/ undefined, + factory.createNamedImports([ + factory.createImportSpecifier( + /*isTypeOnly*/ false, + localName !== verbatimTargetName ? factory.createIdentifier(verbatimTargetName) : undefined, + factory.createIdentifier(localName), + ), + ]), + ), + specifier, + (node as ImportSpecifier).parent.parent.parent.attributes, + ), + 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; + if (specifier) { + const propertyName = (node as ExportSpecifier).propertyName; + if (propertyName && moduleExportNameIsDefault(propertyName)) { + verbatimTargetName = InternalSymbolName.Default; + } + } + // 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 { + 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 prevDisableTrackSymbol = context.tracker.disableTrackSymbol; + context.tracker.disableTrackSymbol = true; + if (isExportAssignmentCompatibleSymbolName) { + results.push(factory.createExportAssignment( + /*modifiers*/ undefined, + isExportEquals, + symbolToExpression(target, context, SymbolFlags.All), + )); + } + 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); + } + } + context.tracker.disableTrackSymbol = prevDisableTrackSymbol; + 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 flags = context.enclosingDeclaration?.kind === SyntaxKind.ModuleDeclaration && (!(symbol.flags & SymbolFlags.Accessor) || symbol.flags & SymbolFlags.SetAccessor) ? NodeFlags.Let : NodeFlags.Const; + const statement = factory.createVariableStatement( + /*modifiers*/ undefined, + factory.createVariableDeclarationList([ + factory.createVariableDeclaration(varName, /*exclamationToken*/ undefined, serializeTypeForDeclaration(context, /*declaration*/ undefined, typeToSerialize, symbol)), + ], flags), + ); + // 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) && + !some(typeToSerialize.symbol?.declarations, isTypeNode) && // If the type comes straight from a type node, we shouldn't try to break it up + !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 => { + if (!isIdentifierText(symbolName(p), languageVersion)) { + return false; + } + if (!(p.flags & SymbolFlags.Accessor)) { + return true; + } + return getNonMissingTypeOfSymbol(p) === getWriteTypeOfSymbol(p); + }); + } + + 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 || p.escapedName === "constructor" || + (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) { + const setter = p.declarations && forEach(p.declarations, d => { + if (d.kind === SyntaxKind.SetAccessor) { + return d as SetAccessorDeclaration; + } + if (isCallExpression(d) && isBindableObjectDefinePropertyCall(d)) { + return forEach(d.arguments[2].properties, propDecl => { + const id = getNameOfDeclaration(propDecl); + if (!!id && isIdentifier(id) && idText(id) === "set") { + return propDecl; + } + }); + } + }); + + Debug.assert(!!setter); + const paramSymbol = isFunctionLikeDeclaration(setter) ? getSignatureFromDeclaration(setter).parameters[0] : undefined; + + result.push(setTextRange( + context, + factory.createSetAccessorDeclaration( + factory.createModifiersFromModifierFlags(flag), + name, + [factory.createParameterDeclaration( + /*modifiers*/ undefined, + /*dotDotDotToken*/ undefined, + paramSymbol ? parameterToParameterDeclarationName(paramSymbol, getEffectiveParameterDeclaration(paramSymbol), context) : "value", + /*questionToken*/ undefined, + isPrivate ? undefined : serializeTypeForDeclaration(context, /*declaration*/ undefined, getWriteTypeOfSymbol(p), p), + )], + /*body*/ undefined, + ), + p.declarations?.find(isSetAccessor) || firstPropertyLikeDecl, + )); + } + if (p.flags & SymbolFlags.GetAccessor) { + const isPrivate = modifierFlags & ModifierFlags.Private; + result.push(setTextRange( + context, + factory.createGetAccessorDeclaration( + factory.createModifiersFromModifierFlags(flag), + name, + [], + isPrivate ? undefined : serializeTypeForDeclaration(context, /*declaration*/ undefined, getTypeOfSymbol(p), p), + /*body*/ undefined, + ), + p.declarations?.find(isGetAccessor) || firstPropertyLikeDecl, + )); + } + 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( + context, + createProperty( + factory.createModifiersFromModifierFlags((isReadonlySymbol(p) ? ModifierFlags.Readonly : 0) | flag), + name, + p.flags & SymbolFlags.Optional ? factory.createToken(SyntaxKind.QuestionToken) : undefined, + isPrivate ? undefined : serializeTypeForDeclaration(context, /*declaration*/ undefined, getWriteTypeOfSymbol(p), p), + // 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( + context, + 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], + ); + } + + 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(context, decl, location)); + } + 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 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 (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 + } + } + } + let privateProtected: ModifierFlags = 0; + for (const s of signatures) { + if (s.declaration) { + privateProtected |= getSelectedEffectiveModifierFlags(s.declaration, ModifierFlags.Private | ModifierFlags.Protected); + } + } + if (privateProtected) { + return [setTextRange( + context, + factory.createConstructorDeclaration( + factory.createModifiersFromModifierFlags(privateProtected), + /*parameters*/ [], + /*body*/ undefined, + ), + signatures[0].declaration, + )]; + } + } + + 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(context, 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; + } + + 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), /*typeArguments*/ 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); + } + } + + 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), /*typeArguments*/ undefined); + } + } + + 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; + } + + 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 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 typePredicateToString(typePredicate: TypePredicate, enclosingDeclaration?: Node, flags: TypeFormatFlags = TypeFormatFlags.UseAliasDefinedOutsideCurrentScope, writer?: EmitTextWriter): string { + return writer ? typePredicateToStringWorker(writer).getText() : usingSingleLineStringWriter(typePredicateToStringWorker); + + function typePredicateToStringWorker(writer: EmitTextWriter) { + const nodeBuilderFlags = toNodeBuilderFlags(flags) | NodeBuilderFlags.IgnoreErrors | NodeBuilderFlags.WriteTypeParametersInQualifiedName; + const predicate = nodeBuilder.typePredicateToTypePredicateNode(typePredicate, enclosingDeclaration, nodeBuilderFlags)!; // TODO: GH#18217 + const printer = createPrinterWithRemoveComments(); + 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 = 0 as TypeFlags; + 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.EnumLike)) { + const baseType = t.flags & TypeFlags.BooleanLiteral ? booleanType : getBaseTypeOfEnumLikeType(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); + } + } + if (flags & TypeFlags.Null) result.push(nullType); + if (flags & TypeFlags.Undefined) result.push(undefinedType); + return result || types; + } + + function visibilityToString(flags: ModifierFlags): string { + if (flags === ModifierFlags.Private) { + return "private"; + } + if (flags === ModifierFlags.Protected) { + return "protected"; + } + return "public"; + } + + 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 (isTypeAliasDeclaration(node)) { + return getSymbolOfDeclaration(node); + } + } + return undefined; + } + + function isTopLevelInExternalModuleAugmentation(node: Node): boolean { + return node && node.parent && + node.parent.kind === SyntaxKind.ModuleBlock && + isExternalModuleAugmentation(node.parent.parent); + } + + 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; + } + 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?.remappedSymbolReferences?.has(getSymbolId(symbol))) { + symbol = context.remappedSymbolReferences.get(getSymbolId(symbol))!; + } + 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)"; + } + } + 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; + } + + 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 ( + !(getCombinedModifierFlagsCached(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 + + 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: ModuleExportName, setVisibility?: boolean): Node[] | undefined { + let exportSymbol: Symbol | undefined; + if (node.kind !== SyntaxKind.StringLiteral && node.parent && node.parent.kind === SyntaxKind.ExportAssignment) { + exportSymbol = resolveName(node, node, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias, /*nameNotFoundMessage*/ undefined, /*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, /*nameNotFoundMessage*/ 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; + } + + function findResolutionCycleStartIndex(target: TypeSystemEntity, propertyName: TypeSystemPropertyName): number { + for (let i = resolutionTargets.length - 1; i >= resolutionStart; i--) { + if (resolutionTargetHasProperty(resolutionTargets[i], resolutionPropertyNames[i])) { + return -1; + } + if (resolutionTargets[i] === target && resolutionPropertyNames[i] === propertyName) { + return i; + } + } + return -1; + } + + function resolutionTargetHasProperty(target: TypeSystemEntity, propertyName: TypeSystemPropertyName): boolean { + switch (propertyName) { + case TypeSystemPropertyName.Type: + return !!getSymbolLinks(target as Symbol).type; + 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; + case TypeSystemPropertyName.ParameterInitializerContainsUndefined: + return getNodeLinks(target as ParameterDeclaration).parameterInitializerContainsUndefined !== undefined; + } + 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; + } + + // 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; + } + + /** + * Return the type of the matching property or index signature in the given type, or undefined + * if no matching property or index signature exists. Add optionality to index signature types. + */ + function getTypeOfPropertyOrIndexSignatureOfType(type: Type, name: __String): Type | undefined { + let propType; + return getTypeOfPropertyOfType(type, name) || + (propType = getApplicableIndexInfoForName(type, name)?.type) && + addOptionality(propType, /*isProperty*/ true, /*isOptional*/ true); + } + + function isTypeAny(type: Type | undefined) { + return type && (type.flags & TypeFlags.Any) !== 0; + } + + 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); + } + + // 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 = getSymbolOfDeclaration(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)); + } + + let omitKeyType = getUnionType(map(properties, getLiteralTypeFromPropertyName)); + + const spreadableProperties: Symbol[] = []; + const unspreadableToRestKeys: Type[] = []; + + 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); + } + } + + 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]); + } + + if (omitKeyType.flags & TypeFlags.Never) { + return source; + } + + 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; + } + + function isGenericTypeWithUndefinedConstraint(type: Type) { + return !!(type.flags & TypeFlags.Instantiable) && maybeTypeOfKind(getBaseConstraintOfType(type) || unknownType, TypeFlags.Undefined); + } + + 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); + } + + // 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 && canHaveFlowNode(parentAccess) && parentAccess.flowNode) { + const propName = getDestructuringPropertyName(node); + if (propName) { + const literal = setTextRangeWorker(parseNodeFactory.createStringLiteral(propName), node); + const lhsExpr = isLeftHandSideExpression(parentAccess) ? parentAccess : parseNodeFactory.createParenthesizedExpression(parentAccess); + const result = setTextRangeWorker(parseNodeFactory.createElementAccessExpression(lhsExpr, literal), node); + setParent(literal, result); + setParent(result, node); + if (lhsExpr !== parentAccess) { + setParent(lhsExpr, result); + } + result.flowNode = parentAccess.flowNode; + return result; + } + } + } + + 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 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 getLiteralPropertyNameText(name: PropertyName) { + const type = getLiteralTypeFromPropertyName(name); + return type.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral) ? "" + (type as StringLiteralType | NumberLiteralType).value : undefined; + } + + /** 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, /*noTupleBoundsCheck*/ false); + } + + function getBindingElementTypeFromParentType(declaration: BindingElement, parentType: Type, noTupleBoundsCheck: boolean): 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 && isPartOfParameterDeclaration(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 && !(hasTypeFacts(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); + } + 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); + } + } + 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. + const baseConstraint = mapType(parentType, t => t.flags & TypeFlags.InstantiableNonPrimitive ? getBaseConstraintOrType(t) : t); + type = everyType(baseConstraint, isTupleType) ? + mapType(baseConstraint, t => sliceTupleType(t as TupleTypeReference, index)) : + createArrayType(elementType); + } + else if (isArrayLikeType(parentType)) { + const indexType = getNumberLiteralType(index); + const accessFlags = AccessFlags.ExpressionPosition | (noTupleBoundsCheck || 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 && !(hasTypeFacts(checkDeclarationInitializer(declaration, CheckMode.Normal), TypeFacts.IsUndefined)) ? getNonUndefinedType(type) : type; + } + 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); + } + 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; + } + + function isEmptyArrayLiteral(node: Expression) { + const expr = skipParentheses(node, /*excludeJSDocTypeAssertions*/ true); + return expr.kind === SyntaxKind.ArrayLiteralExpression && (expr as ArrayLiteralExpression).elements.length === 0; + } + + function addOptionality(type: Type, isProperty = false, isOptional = true): Type { + return strictNullChecks && isOptional ? getOptionalType(type, isProperty) : type; + } + + // 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) || isJSDocPropertyTag(declaration); + const isOptional = includeOptionality && isOptionalDeclaration(declaration); + + // Use type from type annotation if one is present + const declaredType = tryGetTypeFromEffectiveTypeNode(declaration); + if (isCatchClauseVariableDeclarationOrBindingElement(declaration)) { + if (declaredType) { + // If the catch clause is explicitly annotated with any or unknown, accept it, otherwise error. + return isTypeAny(declaredType) || declaredType === unknownType ? declaredType : errorType; + } + // If the catch clause is not explicitly annotated, treat it as though it were explicitly + // annotated with unknown or any, depending on useUnknownInCatchVariables. + return useUnknownInCatchVariables ? unknownType : anyType; + } + if (declaredType) { + return addOptionality(declaredType, isProperty, isOptional); + } + + if ( + (noImplicitAny || isInJSFile(declaration)) && + isVariableDeclaration(declaration) && !isBindingPattern(declaration.name) && + !(getCombinedModifierFlagsCached(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 (!(getCombinedNodeFlagsCached(declaration) & NodeFlags.Constant) && (!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)) { + if (!declaration.symbol) { + // parameters of function types defined in JSDoc in TS files don't have symbols + return; + } + 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(getSymbolOfDeclaration(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); + } + } + + // 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, getSymbolOfDeclaration(declaration), getDeclaredExpandoInitializer(declaration)); + if (containerObjectType) { + return containerObjectType; + } + } + const type = widenTypeInferredFromInitializer(declaration, checkDeclarationInitializer(declaration, checkMode)); + return addOptionality(type, isProperty, 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); + } + } + + 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 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); + } + + // 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; + } + + 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, /*includeClassComputedPropertyName*/ 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 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 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 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 = isInJSFile(container) ? getJSDocTypeTag(container) : undefined; + 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); + } + } + 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); + definedInConstructor = true; + } + } + 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 && isInJSFile(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); + } + 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 (symbol.parent?.valueDeclaration) { + const possiblyAnnotatedSymbol = getFunctionExpressionParentSymbolOrSymbol(symbol.parent); + if (possiblyAnnotatedSymbol.valueDeclaration) { + const typeNode = getEffectiveTypeAnnotationNode(possiblyAnnotatedSymbol.valueDeclaration); + if (typeNode) { + const annotationSymbol = getPropertyOfType(getTypeFromTypeNode(typeNode), symbol.escapedName); + if (annotationSymbol) { + return getNonMissingTypeOfSymbol(annotationSymbol); + } + } + } + } + + 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; + } + 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.links.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 |= getPropagatingFlagsOfTypes([type]) | getObjectFlags(type) & (ObjectFlags.JSLiteral | ObjectFlags.ArrayLiteral | ObjectFlags.ObjectLiteral); + 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, /*includeClassComputedPropertyName*/ 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, reportErrors ? CheckMode.Normal : CheckMode.Contextual, 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 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 exprType = getLiteralTypeFromPropertyName(name); + if (!isTypeUsableAsPropertyName(exprType)) { + // do not include computed properties in the implied type + objectFlags |= ObjectFlags.ObjectLiteralPatternWithComputedProperties; + return; + } + const text = getPropertyNameFromType(exprType); + const flags = SymbolFlags.Property | (e.initializer ? SymbolFlags.Optional : 0); + const symbol = createSymbol(flags, text); + symbol.links.type = getTypeFromBindingElement(e, includePatternInType, reportErrors); + symbol.links.bindingElement = e; + members.set(symbol.escapedName, symbol); + }); + const result = createAnonymousType(/*symbol*/ 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 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 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 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 getTypeFromImportAttributes(node: ImportAttributes): Type { + const links = getNodeLinks(node); + if (!links.resolvedType) { + const symbol = createSymbol(SymbolFlags.ObjectLiteral, InternalSymbolName.ImportAttributes); + const members = createSymbolTable(); + forEach(node.elements, attr => { + const member = createSymbol(SymbolFlags.Property, getNameFromImportAttribute(attr)); + member.parent = symbol; + member.links.type = checkImportAttribute(attr); + member.links.target = member; + members.set(member.escapedName, member); + }); + const type = createAnonymousType(symbol, members, emptyArray, emptyArray, emptyArray); + type.objectFlags |= ObjectFlags.ObjectLiteral | ObjectFlags.NonInferrableType; + links.resolvedType = type; + } + return links.resolvedType; + } + + function isGlobalSymbolConstructor(node: Node) { + const symbol = getSymbolOfNode(node); + const globalSymbol = getGlobalESSymbolConstructorTypeSymbol(/*reportErrors*/ false); + return globalSymbol && symbol && symbol === globalSymbol; + } + + 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); + } + + // 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 !== getSymbolOfDeclaration(declaration)) { + type = esSymbolType; + } + + 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); + } + } + return type; + } + + 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 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, checkMode?: CheckMode): Type { + const links = getSymbolLinks(symbol); + if (!links.type) { + const type = getTypeOfVariableOrParameterOrPropertyWorker(symbol, checkMode); + // 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) && !checkMode) { + links.type = type; + } + return type; + } + return links.type; + } + + function getTypeOfVariableOrParameterOrPropertyWorker(symbol: Symbol, checkMode?: CheckMode): 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 = getSymbolOfDeclaration(getSourceFileOfNode(symbol.valueDeclaration)); + const result = createSymbol(fileSymbol.flags, "exports" as __String); + result.declarations = fileSymbol.declarations ? fileSymbol.declarations.slice() : []; + result.parent = symbol; + result.links.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); + } + Debug.assertIsDefined(symbol.valueDeclaration); + const declaration = symbol.valueDeclaration; + // 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); + } + + // When trying to get the *contextual* type of a binding element, it's possible to fall in a loop and therefore + // end up in a circularity-like situation. This is not a true circularity so we should not report such an error. + // For example, here the looping could happen when trying to get the type of `a` (binding element): + // + // const { a, b = a } = { a: 0 } + // + if (isBindingElement(declaration) && checkMode === CheckMode.Contextual) { + return errorType; + } + 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, /*reportErrors*/ 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); + } + + // When trying to get the *contextual* type of a binding element, it's possible to fall in a loop and therefore + // end up in a circularity-like situation. This is not a true circularity so we should not report such an error. + // For example, here the looping could happen when trying to get the type of `a` (binding element): + // + // const { a, b = a } = { a: 0 } + // + if (isBindingElement(declaration) && checkMode === CheckMode.Contextual) { + 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; + } + } + return undefined; + } + + 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 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; + } + 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, /*reportErrors*/ 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; + } + 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; + } + links.type ??= type; + } + return links.type; + } + + function getWriteTypeOfAccessors(symbol: Symbol): Type { + const links = getSymbolLinks(symbol); + if (!links.writeType) { + if (!pushTypeResolution(symbol, TypeSystemPropertyName.WriteType)) { + return errorType; + } + + 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; + } + // Absent an explicit setter type annotation we use the read type of the accessor. + links.writeType ??= writeType || getTypeOfAccessors(symbol); + } + return links.writeType; + } + + 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 = merged; + links = merged.links; + } + } + originalLinks.type = links.type = getTypeOfFuncClassEnumModuleWorker(symbol); + } + return links.type; + } + + 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; + } + } + 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, /*isProperty*/ true) : type; + } + } + + 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) { + if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) { + return errorType; + } + const targetSymbol = resolveAlias(symbol); + const exportSymbol = symbol.declarations && getTargetOfAliasDeclaration(getDeclarationOfAliasSymbol(symbol)!, /*dontRecursivelyResolve*/ 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 + : getSymbolFlags(targetSymbol) & SymbolFlags.Value ? getTypeOfSymbol(targetSymbol) + : errorType; + + if (!popTypeResolution()) { + reportCircularityError(exportSymbol ?? symbol); + return links.type ??= 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 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; + // Check if variable has type annotation that circularly references the variable itself + if (declaration) { + 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.Alias) { + const node = getDeclarationOfAliasSymbol(symbol); + if (node) { + error(node, Diagnostics.Circular_definition_of_import_alias_0, 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 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 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) : + // NOTE: cast to TransientSymbol should be safe because only TransientSymbols can have CheckFlags.SyntheticProperty + (symbol as TransientSymbol).links.writeType || (symbol as TransientSymbol).links.type! : + removeMissingType(getTypeOfSymbol(symbol), !!(symbol.flags & SymbolFlags.Optional)); + } + if (symbol.flags & SymbolFlags.Accessor) { + return checkFlags & CheckFlags.Instantiated ? + getWriteTypeOfInstantiatedSymbol(symbol) : + getWriteTypeOfAccessors(symbol); + } + return getTypeOfSymbol(symbol); + } + + function getTypeOfSymbol(symbol: Symbol, checkMode?: CheckMode): 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, checkMode); + } + 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; + } + + 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(getSymbolOfDeclaration(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 = getSymbolOfDeclaration(node.left as BindableStaticNameExpression | PropertyAssignment); + if (symbol && symbol.parent && !findAncestor(symbol.parent.valueDeclaration, d => node === d)) { + node = symbol.parent.valueDeclaration!; + } + } + } + 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(getSymbolOfDeclaration((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(getSymbolOfDeclaration(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; + } + } + } + } + + // 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.flags & SymbolFlags.Function) + ? symbol.valueDeclaration + : symbol.declarations?.find(decl => { + if (decl.kind === SyntaxKind.InterfaceDeclaration) { + return true; + } + if (decl.kind !== SyntaxKind.VariableDeclaration) { + return false; + } + const initializer = (decl as VariableDeclaration).initializer; + return !!initializer && (initializer.kind === SyntaxKind.FunctionExpression || initializer.kind === SyntaxKind.ArrowFunction); + })!; + 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)); + } + } + 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)); + } + + // 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; + } + + 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 getBaseTypeNodeOfClass(type: InterfaceType): ExpressionWithTypeArguments | undefined { + const decl = getClassLikeDeclarationOfSymbol(type.symbol); + return decl && getEffectiveBaseTypeNode(decl); + } + + 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 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, + * * errorType 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))); + } + } + return type.resolvedBaseConstructorType ??= errorType; + } + 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); + } + } + } + } + } + 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 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); + } + } + 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; + } + + 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))) { + 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]); + } + + 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(getSourceFileOfNode(baseTypeNode.expression), 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; + } + + // 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); + } + } + else { + 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; + } + } + } + } + } + } + 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 = merged; + links = merged.links; + } + + 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; + } + + 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); + } + } + 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)); + } + } + links.declaredType ??= type; + } + return links.declaredType; + } + + function getBaseTypeOfEnumLikeType(type: Type) { + return type.flags & TypeFlags.EnumLike && type.symbol.flags & SymbolFlags.EnumMember ? 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 = getSymbolOfDeclaration(member); + const value = getEnumMemberValue(member).value; + const memberType = getFreshTypeOfLiteralType( + value !== undefined ? + getEnumLiteralType(value, getSymbolId(symbol), memberSymbol) : + createComputedEnumType(memberSymbol), + ); + getSymbolLinks(memberSymbol).declaredType = memberType; + memberTypeList.push(getRegularTypeOfLiteralType(memberType)); + } + } + } + } + } + const enumType = memberTypeList.length ? + getUnionType(memberTypeList, UnionReduction.Literal, symbol, /*aliasTypeArguments*/ undefined) : + createComputedEnumType(symbol); + if (enumType.flags & TypeFlags.Union) { + enumType.flags |= TypeFlags.EnumLiteral; + enumType.symbol = symbol; + } + links.declaredType = enumType; + } + return links.declaredType; + } + + function createComputedEnumType(symbol: Symbol) { + const regularType = createTypeWithSymbol(TypeFlags.Enum, symbol) as EnumType; + const freshType = createTypeWithSymbol(TypeFlags.Enum, symbol) as EnumType; + regularType.regularType = regularType; + regularType.freshType = freshType; + freshType.regularType = regularType; + freshType.freshType = freshType; + return regularType; + } + + function getDeclaredTypeOfEnumMember(symbol: Symbol): Type { + const links = getSymbolLinks(symbol); + if (!links.declaredType) { + const enumType = getDeclaredTypeOfEnum(getParentOfSymbol(symbol)!); + if (!links.declaredType) { + links.declaredType = enumType; + } + } + return links.declaredType; + } + + 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 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; + } + + /** + * 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; + } + + /** 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; + } + + // 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 base of baseSymbols) { + if (isStaticPrivateIdentifierProperty(base)) { + continue; + } + const derived = symbols.get(base.escapedName); + if ( + !derived + // non-constructor/static-block assignment declarations are ignored here; they're not treated as overrides + || derived.valueDeclaration + && isBinaryExpression(derived.valueDeclaration) + && !isConstructorDeclaredProperty(derived) + && !getContainingClassStaticBlock(derived.valueDeclaration) + ) { + symbols.set(base.escapedName, base); + symbols.set(base.escapedName, base); + } + } + } + + 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; + + (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 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; + } + + /** + * 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 name is a dynamic name that cannot be late-bound. + */ + function isNonBindableDynamicName(node: DeclarationName) { + return isDynamicName(node) && !isLateBindableName(node); + } + + /** + * 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: Map<__String, TransientSymbol>, 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 there's a symbol declaration with the same name and conflicting flags. + const earlySymbol = earlySymbols && earlySymbols.get(memberName); + // Duplicate property declarations of classes are checked in checkClassForDuplicateDeclarations. + if (!(parent.flags & SymbolFlags.Class) && lateSymbol.flags & getExcludedSymbolFlags(symbolFlags)) { + // 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.links.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; + } + + function getResolvedMembersOrExportsOfSymbol(symbol: Symbol, resolutionKind: MembersOrExportsResolutionKind): Map<__String, Symbol> { + const links = getSymbolLinks(symbol); + if (!links[resolutionKind]) { + const isStatic = resolutionKind === MembersOrExportsResolutionKind.resolvedExports; + const earlySymbols = !isStatic ? symbol.members : + symbol.flags & SymbolFlags.Module ? getExportsOfModuleWorker(symbol).exports : + 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 Map<__String, TransientSymbol>; + 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 = getFunctionExpressionParentSymbolOrSymbol(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); + } + } + } + } + + let resolved = combineSymbolTables(earlySymbols, lateSymbols); + if (symbol.flags & SymbolFlags.Transient && links.cjsExportMerged && symbol.declarations) { + for (const decl of symbol.declarations) { + const original = getSymbolLinks(decl.symbol)[resolutionKind]; + if (!resolved) { + resolved = original; + continue; + } + if (!original) continue; + original.forEach((s, name) => { + const existing = resolved!.get(name); + if (!existing) resolved!.set(name, s); + else if (existing === s) return; + else resolved!.set(name, mergeSymbol(existing, s)); + }); + } + } + links[resolutionKind] = resolved || emptySymbols; + } + + return links[resolutionKind]!; + } + + /** + * 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; + } + + 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); + return length(target.typeParameters) === length(typeArguments) ? createTypeReference(target, concatenate(typeArguments, [thisArgument || target.thisType!])) : 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)) { + const symbolTable = createSymbolTable(source.declaredProperties); + // copy index signature symbol as well (for quickinfo) + const sourceIndex = getIndexSymbol(source.symbol); + if (sourceIndex) { + symbolTable.set(InternalSymbolName.Index, sourceIndex); + } + members = symbolTable; + } + 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 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 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 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 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 restName = sig.parameters[restIndex].escapedName; + const restType = getTypeOfSymbol(sig.parameters[restIndex]); + if (isTupleType(restType)) { + return [expandSignatureParametersWithTupleMembers(restType, restIndex, restName)]; + } + 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, restName)); + } + } + return [sig.parameters]; + + function expandSignatureParametersWithTupleMembers(restType: TupleTypeReference, restIndex: number, restName: __String) { + const elementTypes = getTypeArguments(restType); + const associatedNames = getUniqAssociatedNamesFromTupleType(restType, restName); + 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 name = associatedNames && associatedNames[i] ? associatedNames[i] : + 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.links.type = flags & ElementFlags.Rest ? createArrayType(t) : t; + return symbol; + }); + return concatenate(sig.parameters.slice(0, restIndex), restParams); + } + + function getUniqAssociatedNamesFromTupleType(type: TupleTypeReference, restName: __String) { + const associatedNamesMap = new Map<__String, number>(); + return map(type.target.labeledElementDeclarations, (labeledElement, i) => { + const name = getTupleElementLabel(labeledElement, i, restName); + const prevCounter = associatedNamesMap.get(name); + if (prevCounter === undefined) { + associatedNamesMap.set(name, 1); + return name; + } + else { + associatedNamesMap.set(name, prevCounter + 1); + return `${name}_${prevCounter}` as __String; + } + }); + } + } + + 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(/*declaration*/ undefined, classType.localTypeParameters, /*thisParameter*/ 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]; + } + let result: Signature[] | undefined; + for (let i = 0; i < signatureLists.length; i++) { + // Allow matching non-generic signatures to have excess parameters (as a fallback if exact parameter match is not found) and different return types. + // Prefer matching this types if possible. + const match = i === listIndex + ? signature + : findMatchingSignature(signatureLists[i], signature, /*partialMatch*/ false, /*ignoreThisTypes*/ false, /*ignoreReturnTypes*/ true) + || findMatchingSignature(signatureLists[i], signature, /*partialMatch*/ true, /*ignoreThisTypes*/ false, /*ignoreReturnTypes*/ true); + if (!match) { + return undefined; + } + 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; + } + + 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 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, + isRestParam ? CheckFlags.RestParameter : isOptional ? CheckFlags.OptionalParameter : 0, + ); + paramSymbol.links.type = isRestParam ? createArrayType(unionParamType) : unionParamType; + params[i] = paramSymbol; + } + if (needsExtraRestElement) { + const restParamSymbol = createSymbol(SymbolFlags.FunctionScopedVariable, "args" as __String, CheckFlags.RestParameter); + restParamSymbol.links.type = createArrayType(getTypeAtPosition(shorter, longestCount)); + if (shorter === right) { + restParamSymbol.links.type = instantiateType(restParamSymbol.links.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; + } + else if (left.compositeKind !== TypeFlags.Intersection && left.mapper && left.compositeSignatures) { + result.mapper = left.mapper; + } + return result; + } + + 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; + } + + 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); + } + + 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 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); + } + else if (mixinFlags[i]) { + mixedTypes.push(getReturnTypeOfSignature(getSignaturesOfType(types[i], SignatureKind.Construct)[0])); + } + } + 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; + }); + } + 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 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 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); + } + + /** + * 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 = getExportsOfSymbol(symbol); + let indexInfos: IndexInfo[] | undefined; + if (symbol === globalThisSymbol) { + const varsOnly = new Map<__String, Symbol>(); + 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); + } + } + + 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); + } + } + 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; + } + } + + 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])])); + } + + // If the original mapped type had an intersection constraint we extract its components, + // and we make an attempt to do so even if the intersection has been reduced to a union. + // This entire process allows us to possibly retrieve the filtering type literals. + // e.g. { [K in keyof U & ("a" | "b") ] } -> "a" | "b" + function getLimitedConstraint(type: ReverseMappedType) { + const constraint = getConstraintTypeFromMappedType(type.mappedType); + if (!(constraint.flags & TypeFlags.Union || constraint.flags & TypeFlags.Intersection)) { + return; + } + const origin = (constraint.flags & TypeFlags.Union) ? (constraint as UnionType).origin : (constraint as IntersectionType); + if (!origin || !(origin.flags & TypeFlags.Intersection)) { + return; + } + const limitedConstraint = getIntersectionType((origin as IntersectionType).types.filter(t => t !== type.constraintType)); + return limitedConstraint !== neverType ? limitedConstraint : undefined; + } + + 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) || unknownType, readonlyMask && indexInfo.isReadonly)] : emptyArray; + const members = createSymbolTable(); + const limitedConstraint = getLimitedConstraint(type); + for (const prop of getPropertiesOfType(type.source)) { + // In case of a reverse mapped type with an intersection constraint, if we were able to + // extract the filtering type literals we skip those properties that are not assignable to them, + // because the extra properties wouldn't get through the application of the mapped type anyway + if (limitedConstraint) { + const propertyNameType = getLiteralTypeFromProperty(prop, TypeFlags.StringOrNumberLiteralOrUnique); + if (!isTypeAssignableTo(propertyNameType, limitedConstraint)) { + continue; + } + } + 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.links.nameType = getSymbolLinks(prop).nameType; + inferredProp.links.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.links.mappedType = newMappedType as MappedType; + inferredProp.links.constraintType = getIndexType(newTypeParam) as IndexType; + } + else { + inferredProp.links.mappedType = type.mappedType; + inferredProp.links.constraintType = type.constraintType; + } + members.set(prop.escapedName, inferredProp); + } + setStructuredTypeMembers(type, members, emptyArray, emptyArray, indexInfos); + } + + // 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), /*forConstraint*/ false); + } + } + return type; + } + if (type.flags & TypeFlags.Union) { + return mapType(type as UnionType, getLowerBoundOfKeyType, /*noReductions*/ true); + } + 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 getIsLateCheckFlag(s: Symbol): CheckFlags { + return getCheckFlags(s) & CheckFlags.Late; + } + + function forEachMappedTypePropertyKeyTypeAndIndexSignatureKeyType(type: Type, include: TypeFlags, stringsOnly: boolean, cb: (keyType: Type) => void) { + for (const prop of getPropertiesOfType(type)) { + cb(getLiteralTypeFromProperty(prop, include)); + } + if (type.flags & TypeFlags.Any) { + cb(stringType); + } + else { + for (const info of getIndexInfosOfType(type)) { + if (!stringsOnly || info.keyType.flags & (TypeFlags.String | TypeFlags.TemplateLiteral)) { + cb(info.keyType); + } + } + } + } + + /** 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 mappedType = (type.target as MappedType) || type; + const nameType = getNameTypeFromMappedType(mappedType); + const shouldLinkPropDeclarations = getMappedTypeNameTypeKind(mappedType) !== MappedTypeNameTypeKind.Remapping; + const templateType = getTemplateTypeFromMappedType(mappedType); + const modifiersType = getApparentType(getModifiersTypeFromMappedType(type)); // The 'T' in 'keyof T' + const templateModifiers = getMappedTypeModifiers(type); + const include = TypeFlags.StringOrNumberLiteralOrUnique; + if (isMappedTypeWithKeyofConstraintDeclaration(type)) { + // We have a { [P in keyof T]: X } + forEachMappedTypePropertyKeyTypeAndIndexSignatureKeyType(modifiersType, include, /*stringsOnly*/ false, 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.links.nameType = getUnionType([existingProp.links.nameType!, propNameType]); + existingProp.links.keyType = getUnionType([existingProp.links.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.links.mappedType = type; + prop.links.nameType = propNameType; + prop.links.keyType = keyType; + if (modifiersProp) { + prop.links.syntheticOrigin = modifiersProp; + prop.declarations = shouldLinkPropDeclarations ? modifiersProp.declarations : undefined; + } + 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 modifiersIndexInfo = getApplicableIndexInfo(modifiersType, propNameType); + const isReadonly = !!(templateModifiers & MappedTypeModifiers.IncludeReadonly || + !(templateModifiers & MappedTypeModifiers.ExcludeReadonly) && modifiersIndexInfo?.isReadonly); + const indexInfo = createIndexInfo(indexKeyType, propType, isReadonly); + indexInfos = appendIndexInfo(indexInfos, indexInfo, /*union*/ true); + } + } + } + + function getTypeOfMappedSymbol(symbol: MappedSymbol) { + if (!symbol.links.type) { + const mappedType = symbol.links.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.links.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.links.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.links.type ??= type; + } + return symbol.links.type; + } + + function getTypeParameterFromMappedType(type: MappedType) { + return type.typeParameter || + (type.typeParameter = getDeclaredTypeOfTypeParameter(getSymbolOfDeclaration(type.declaration.typeParameter))); + } + + 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 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 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); + } + + // Return -1, 0, or 1, where -1 means optionality is stripped (i.e. -?), 0 means optionality is unchanged, and 1 means + // optionality is added (i.e. +?). + function getMappedTypeOptionality(type: MappedType): number { + const modifiers = getMappedTypeModifiers(type); + return modifiers & MappedTypeModifiers.ExcludeOptional ? -1 : modifiers & MappedTypeModifiers.IncludeOptional ? 1 : 0; + } + + // Return -1, 0, or 1, for stripped, unchanged, or added optionality respectively. When a homomorphic mapped type doesn't + // modify optionality, recursively consult the optionality of the type being mapped over to see if it strips or adds optionality. + // For intersections, return -1 or 1 when all constituents strip or add optionality, otherwise return 0. + function getCombinedMappedTypeOptionality(type: Type): number { + if (getObjectFlags(type) & ObjectFlags.Mapped) { + return getMappedTypeOptionality(type as MappedType) || getCombinedMappedTypeOptionality(getModifiersTypeFromMappedType(type as MappedType)); + } + if (type.flags & TypeFlags.Intersection) { + const optionality = getCombinedMappedTypeOptionality((type as IntersectionType).types[0]); + return every((type as IntersectionType).types, (t, i) => i === 0 || getCombinedMappedTypeOptionality(t) === optionality) ? optionality : 0; + } + return 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; + } + + function getMappedTypeNameTypeKind(type: MappedType): MappedTypeNameTypeKind { + const nameType = getNameTypeFromMappedType(type); + if (!nameType) { + return MappedTypeNameTypeKind.None; + } + return isTypeAssignableTo(nameType, getTypeParameterFromMappedType(type)) ? MappedTypeNameTypeKind.Filtering : MappedTypeNameTypeKind.Remapping; + } + + 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)); + } + } + 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; + } + + /** 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, /*skipObjectFunctionPropertyAugment*/ !!(type.flags & TypeFlags.Intersection)); + 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); + } + return type.resolvedProperties; + } + + function getPropertiesOfType(type: Type): Symbol[] { + type = getReducedApparentType(type); + return type.flags & TypeFlags.UnionOrIntersection ? + getPropertiesOfUnionOrIntersectionType(type as UnionType) : + getPropertiesOfObjectType(type); + } + + 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 isTypeInvalidDueToUnionDiscriminant(contextualType: Type, obj: ObjectLiteralExpression | JsxAttributes): boolean { + const list = obj.properties as NodeArray; + return list.some(property => { + const nameType = property.name && (isJsxNamespacedName(property.name) ? getStringLiteralType(getTextOfJsxAttributeName(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 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()); + } + + 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 isConstMappedType(type: MappedType, depth: number): boolean { + const typeVariable = getHomomorphicTypeVariable(type); + return !!typeVariable && isConstTypeVariable(typeVariable, depth); + } + + function isConstTypeVariable(type: Type | undefined, depth = 0): boolean { + return depth < 5 && !!(type && ( + type.flags & TypeFlags.TypeParameter && some((type as TypeParameter).symbol?.declarations, d => hasSyntacticModifier(d, ModifierFlags.Const)) || + type.flags & TypeFlags.UnionOrIntersection && some((type as UnionOrIntersectionType).types, t => isConstTypeVariable(t, depth)) || + type.flags & TypeFlags.IndexedAccess && isConstTypeVariable((type as IndexedAccessType).objectType, depth + 1) || + type.flags & TypeFlags.Conditional && isConstTypeVariable(getConstraintOfConditionalType(type as ConditionalType), depth + 1) || + type.flags & TypeFlags.Substitution && isConstTypeVariable((type as SubstitutionType).baseType, depth) || + getObjectFlags(type) & ObjectFlags.Mapped && isConstMappedType(type as MappedType, depth) || + isGenericTupleType(type) && findIndex(getElementTypes(type), (t, i) => !!(type.target.elementFlags[i] & ElementFlags.Variadic) && isConstTypeVariable(t, depth)) >= 0 + )); + } + + 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 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 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 { + if (type.resolvedConstraintOfDistributive !== undefined) { + return type.resolvedConstraintOfDistributive || 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), /*forConstraint*/ true); + if (!(instantiated.flags & TypeFlags.Never)) { + type.resolvedConstraintOfDistributive = instantiated; + return instantiated; + } + } + } + type.resolvedConstraintOfDistributive = false; + 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); + } + if (constraint) { + constraints = append(constraints, constraint); + if (targetIsUnion) { + 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, IntersectionFlags.NoConstraintReduction), /*writing*/ false); + } + return undefined; + } + + function getBaseConstraintOfType(type: Type): Type | undefined { + if (type.flags & (TypeFlags.InstantiableNonPrimitive | TypeFlags.UnionOrIntersection | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) || isGenericTupleType(type)) { + const constraint = getResolvedBaseConstraint(type as InstantiableType | UnionOrIntersectionType); + return constraint !== noConstraintType && constraint !== circularConstraintType ? constraint : undefined; + } + return type.flags & TypeFlags.Index ? stringNumberSymbolType : 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; + } + + 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 = getImmediateBaseConstraint(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; + } + return t.immediateBaseConstraint; + } + + 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 { + different = true; + } + } + 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 stringNumberSymbolType; + } + 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)); + } + 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)); + } + if (isGenericTupleType(t)) { + // We substitute constraints for variadic elements only when the constraints are array types or + // non-variadic tuple types as we want to avoid further (possibly unbounded) recursion. + const newElements = map(getElementTypes(t), (v, i) => { + const constraint = v.flags & TypeFlags.TypeParameter && t.target.elementFlags[i] & ElementFlags.Variadic && getBaseConstraint(v) || v; + return constraint !== v && everyType(constraint, c => isArrayOrTupleType(c) && !isGenericTupleType(c)) ? constraint : v; + }); + return createTupleType(newElements, t.target.elementFlags, t.target.readonly, t.target.labeledElementDeclarations); + } + return t; + } + } + + function getApparentTypeOfIntersectionType(type: IntersectionType, thisArgument: Type) { + if (type === thisArgument) { + return type.resolvedApparentType || (type.resolvedApparentType = getTypeWithThisArgument(type, thisArgument, /*needApparentType*/ true)); + } + const key = `I${getTypeId(type)},${getTypeId(thisArgument)}`; + return getCachedType(key) ?? setCachedType(key, getTypeWithThisArgument(type, thisArgument, /*needApparentType*/ 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; + } + } + } + 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; + } + + /** + * 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; + } + + function hasNonCircularTypeParameterDefault(typeParameter: TypeParameter) { + return getResolvedTypeParameterDefault(typeParameter) !== circularConstraintType; + } + + /** + * 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 getApparentTypeOfMappedType(type: MappedType) { + return type.resolvedApparentType || (type.resolvedApparentType = getResolvedApparentTypeOfMappedType(type)); + } + + function getResolvedApparentTypeOfMappedType(type: MappedType): Type { + const target = (type.target ?? type) as MappedType; + const typeVariable = getHomomorphicTypeVariable(target); + if (typeVariable && !target.declaration.nameType) { + // We have a homomorphic mapped type or an instantiation of a homomorphic mapped type, i.e. a type + // of the form { [P in keyof T]: X }. Obtain the modifiers type (the T of the keyof T), and if it is + // another generic mapped type, recursively obtain its apparent type. Otherwise, obtain its base + // constraint. Then, if every constituent of the base constraint is an array or tuple type, apply + // this mapped type to the base constraint. It is safe to recurse when the modifiers type is a + // mapped type because we protect again circular constraints in getTypeFromMappedTypeNode. + const modifiersType = getModifiersTypeFromMappedType(type); + const baseConstraint = isGenericMappedType(modifiersType) ? getApparentTypeOfMappedType(modifiersType) : getBaseConstraintOfType(modifiersType); + if (baseConstraint && everyType(baseConstraint, t => isArrayOrTupleType(t) || isArrayOrTupleOrIntersection(t))) { + return instantiateType(target, prependTypeMapping(typeVariable, baseConstraint, type.mapper)); + } + } + return type; + } + + function isArrayOrTupleOrIntersection(type: Type) { + return !!(type.flags & TypeFlags.Intersection) && every((type as IntersectionType).types, isArrayOrTupleType); + } + + 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 ? getBaseConstraintOfType(type) || unknownType : type; + const objectFlags = getObjectFlags(t); + return objectFlags & ObjectFlags.Mapped ? getApparentTypeOfMappedType(t as MappedType) : + objectFlags & ObjectFlags.Reference && t !== type ? getTypeWithThisArgument(t, type) : + t.flags & TypeFlags.Intersection ? getApparentTypeOfIntersectionType(t as IntersectionType, type) : + 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 ? stringNumberSymbolType : + 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: Map | 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; + } + 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(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 links = tryCast(singleProp, isTransientSymbol)?.links; + const clone = createSymbolWithType(singleProp, links?.type); + clone.parent = singleProp.valueDeclaration?.symbol?.parent; + clone.links.containingType = containingType; + clone.links.mapper = links?.mapper; + clone.links.writeType = getWriteTypeOfSymbol(singleProp); + return clone; + } + 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 writeType = getWriteTypeOfSymbol(prop); + if (writeTypes || writeType !== type) { + writeTypes = append(!writeTypes ? propTypes.slice() : writeTypes, writeType); + } + if (type !== firstType) { + checkFlags |= CheckFlags.HasNonUniformType; + } + if (isLiteralType(type) || isPatternLiteralType(type)) { + checkFlags |= CheckFlags.HasLiteralType; + } + if (type.flags & TypeFlags.Never && type !== uniqueLiteralType) { + checkFlags |= CheckFlags.HasNeverType; + } + propTypes.push(type); + } + addRange(propTypes, indexTypes); + const result = createSymbol(SymbolFlags.Property | (optionalFlag ?? 0), name, syntheticFlag | checkFlags); + result.links.containingType = containingType; + if (!hasNonUniformValueDeclaration && firstValueDeclaration) { + result.valueDeclaration = firstValueDeclaration; + + // Inherit information about parent type. + if (firstValueDeclaration.symbol.parent) { + result.parent = firstValueDeclaration.symbol.parent; + } + } + + result.declarations = declarations; + result.links.nameType = nameType; + if (propTypes.length > 2) { + // When `propTypes` has the potential to explode in size when normalized, defer normalization until absolutely needed + result.links.checkFlags |= CheckFlags.DeferredType; + result.links.deferralParent = containingType; + result.links.deferralConstituents = propTypes; + result.links.deferralWriteConstituents = writeTypes; + } + else { + result.links.type = isUnion ? getUnionType(propTypes) : getIntersectionType(propTypes); + if (writeTypes) { + result.links.writeType = isUnion ? getUnionType(writeTypes) : getIntersectionType(writeTypes); + } + } + return result; + } + + // 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 = skipObjectFunctionPropertyAugment ? + type.propertyCacheWithoutObjectFunctionPropertyAugment?.get(name) : + type.propertyCache?.get(name); + if (!property) { + property = createUnionOrIntersectionProperty(type, name, skipObjectFunctionPropertyAugment); + if (property) { + const properties = skipObjectFunctionPropertyAugment ? + type.propertyCacheWithoutObjectFunctionPropertyAugment ||= createSymbolTable() : + type.propertyCache ||= createSymbolTable(); + properties.set(name, property); + // Propagate an entry from the non-augmented cache to the augmented cache unless the property is partial. + if (skipObjectFunctionPropertyAugment && !(getCheckFlags(property) & CheckFlags.Partial) && !type.propertyCache?.get(name)) { + const properties = type.propertyCache ||= createSymbolTable(); + properties.set(name, property); + } + } + } + return property; + } + + function getCommonDeclarationsOfSymbols(symbols: Iterable) { + 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; + } + } + return commonDeclarations; + } + + 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 (type as IntersectionType).objectFlags & ObjectFlags.IsNeverIntersection ? neverType : type; + } + return type; + } + + 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; + } + return reduced; + } + + function isNeverReducedProperty(prop: Symbol) { + return isDiscriminantWithNeverType(prop) || isConflictingPrivateProperty(prop); + } + + 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); + } + + /** + * 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 isGenericReducibleType(type: Type): boolean { + return !!(type.flags & TypeFlags.Union && (type as UnionType).objectFlags & ObjectFlags.ContainsIntersections && some((type as UnionType).types, isGenericReducibleType) || + type.flags & TypeFlags.Intersection && isReducibleIntersection(type as IntersectionType)); + } + + function isReducibleIntersection(type: IntersectionType) { + const uniqueFilled = type.uniqueLiteralFilledInstantiation || (type.uniqueLiteralFilledInstantiation = instantiateType(type, uniqueLiteralMapper)); + return getReducedType(uniqueFilled) !== uniqueFilled; + } + + 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 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 && !includeTypeOnlyMembers && type.symbol?.flags & SymbolFlags.ValueModule && getSymbolLinks(type.symbol).typeOnlyExportStarMap?.has(name)) { + // If this is the type of a module, `resolved.members.get(name)` might have effectively skipped over + // an `export type * from './foo'`, leaving `symbolIsValue` unable to see that the symbol is being + // viewed through a type-only export. + return undefined; + } + 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; + } + } + return getPropertyOfObjectType(globalObjectType, name); + } + if (type.flags & TypeFlags.Intersection) { + const prop = getPropertyOfUnionOrIntersectionType(type as UnionOrIntersectionType, name, /*skipObjectFunctionPropertyAugment*/ true); + if (prop) { + return prop; + } + if (!skipObjectFunctionPropertyAugment) { + return getPropertyOfUnionOrIntersectionType(type as UnionOrIntersectionType, name, skipObjectFunctionPropertyAugment); + } + return undefined; + } + if (type.flags & TypeFlags.Union) { + return getPropertyOfUnionOrIntersectionType(type as UnionOrIntersectionType, name, skipObjectFunctionPropertyAugment); + } + return undefined; + } + + 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; + } + + /** + * 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[] { + const result = getSignaturesOfStructuredType(getReducedApparentType(type), kind); + if (kind === SignatureKind.Call && !length(result) && type.flags & TypeFlags.Union) { + if ((type as UnionType).arrayFallbackSignatures) { + return (type as UnionType).arrayFallbackSignatures!; + } + // If the union is all different instantiations of a member of the global array type... + let memberName: __String; + if (everyType(type, t => !!t.symbol?.parent && isArrayOrTupleSymbol(t.symbol.parent) && (!memberName ? (memberName = t.symbol.escapedName, true) : memberName === t.symbol.escapedName))) { + // Transform the type from `(A[] | B[])["member"]` to `(A | B)[]["member"]` (since we pretend array is covariant anyway) + const arrayArg = mapType(type, t => getMappedType((isReadonlyArraySymbol(t.symbol.parent) ? globalReadonlyArrayType : globalArrayType).typeParameters![0], (t as AnonymousType).mapper!)); + const arrayType = createArrayType(arrayArg, someType(type, t => isReadonlyArraySymbol(t.symbol.parent))); + return (type as UnionType).arrayFallbackSignatures = getSignaturesOfType(getTypeOfPropertyOfType(arrayType, memberName!)!, kind); + } + (type as UnionType).arrayFallbackSignatures = result; + } + return result; + } + + function isArrayOrTupleSymbol(symbol: Symbol | undefined) { + if (!symbol || !globalArrayType.symbol || !globalReadonlyArrayType.symbol) { + return false; + } + return !!getSymbolIfSameReference(symbol, globalArrayType.symbol) || !!getSymbolIfSameReference(symbol, globalReadonlyArrayType.symbol); + } + + function isReadonlyArraySymbol(symbol: Symbol | undefined) { + if (!symbol || !globalReadonlyArrayType.symbol) { + return false; + } + return !!getSymbolIfSameReference(symbol, globalReadonlyArrayType.symbol); + } + + 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 { + (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; + } + + 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 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 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 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))); + } + + // 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; + } + + function symbolsToArray(symbols: SymbolTable): Symbol[] { + const result: Symbol[] = []; + symbols.forEach((symbol, id) => { + if (!isReservedMemberName(id)) { + result.push(symbol); + } + }); + return result; + } + + 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 hasEffectiveQuestionToken(node: ParameterDeclaration | JSDocParameterTag | JSDocPropertyTag) { + return hasQuestionToken(node) || isOptionalJSDocPropertyLikeTag(node) || isParameter(node) && isJSDocOptionalParameter(node); + } + + function isOptionalParameter(node: ParameterDeclaration | JSDocParameterTag | JSDocPropertyTag) { + if (hasEffectiveQuestionToken(node)) { + return true; + } + if (!isParameter(node)) { + 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); + } + const iife = getImmediatelyInvokedFunctionExpression(node.parent); + if (iife) { + return !node.type && + !node.dotDotDotToken && + node.parent.parameters.indexOf(node) >= getEffectiveCallArguments(iife).length; + } + + return false; + } + + 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; + } + + /** + * 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 thisTag: JSDocThisTag | undefined = isInJSFile(declaration) ? getJSDocThisTag(declaration) : 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]; + if (isInJSFile(param) && isJSDocThisTag(param)) { + thisTag = param; + continue; + } + + 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, /*nameNotFoundMessage*/ 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; + } + + // Record a new minimum argument count if this is not an optional parameter + const isOptionalParameter = hasEffectiveQuestionToken(param) || + isParameter(param) && param.initializer || isRestParameter(param) || + iife && parameters.length > iife.arguments.length && !type; + if (!isOptionalParameter) { + minArgumentCount = parameters.length; + } + } + + // 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(getSymbolOfDeclaration(declaration), otherKind); + if (other) { + thisParameter = getAnnotatedAccessorThisParameter(other); + } + } + + if (thisTag && thisTag.typeExpression) { + thisParameter = createSymbolWithType(createSymbol(SymbolFlags.FunctionScopedVariable, InternalSymbolName.This), getTypeFromTypeNode(thisTag.typeExpression)); + } + + const hostDeclaration = isJSDocSignature(declaration) ? getEffectiveJSDocHost(declaration) : declaration; + const classType = hostDeclaration && isConstructorDeclaration(hostDeclaration) ? + getDeclaredTypeOfClassOrInterface(getMergedSymbol((hostDeclaration.parent as ClassDeclaration).symbol)) + : undefined; + const typeParameters = classType ? classType.localTypeParameters : getTypeParametersFromDeclaration(declaration); + if (hasRestParameter(declaration) || isInJSFile(declaration) && maybeAddJsSyntheticRestParameter(declaration, parameters)) { + flags |= SignatureFlags.HasRestParameter; + } + 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; + } + + /** + * 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.links.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.links.checkFlags |= CheckFlags.DeferredType; + syntheticArgsSymbol.links.deferralParent = neverType; + syntheticArgsSymbol.links.deferralConstituents = [anyArrayType]; + syntheticArgsSymbol.links.deferralWriteConstituents = [anyArrayType]; + } + if (lastParamVariadicType) { + // Replace the last parameter with a rest parameter. + parameters.pop(); + } + parameters.push(syntheticArgsSymbol); + return true; + } + + 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 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!); + } + } + 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; + + 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.PropertyAssignment: + return traverse((node as PropertyAssignment).initializer); + + 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; + } + } + if (isInJSFile(decl) && decl.jsDoc) { + const tags = getJSDocOverloadTags(decl); + if (length(tags)) { + for (const tag of tags) { + const jsDocSignature = tag.typeExpression; + if (jsDocSignature.type === undefined && !isConstructorDeclaration(decl)) { + reportImplicitAny(jsDocSignature, anyType); + } + result.push(getSignatureFromDeclaration(jsDocSignature)); + } + 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; + } + + 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); + } + } + + 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); + } + } + if (type || jsdocPredicate) { + signature.resolvedTypePredicate = type && isTypePredicateNode(type) ? + createTypePredicateFromTypePredicateNode(type, signature) : + jsdocPredicate || noTypePredicate; + } + else if (signature.declaration && isFunctionLikeDeclaration(signature.declaration) && (!signature.resolvedReturnType || signature.resolvedReturnType.flags & TypeFlags.Boolean) && getParameterCount(signature) > 0) { + const { declaration } = signature; + signature.resolvedTypePredicate = noTypePredicate; // avoid infinite loop + signature.resolvedTypePredicate = getTypePredicateFromBody(declaration) || noTypePredicate; + } + else { + signature.resolvedTypePredicate = noTypePredicate; + } + } + 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 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); + } + 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); + } + } + } + type = anyType; + } + signature.resolvedReturnType ??= type; + } + return signature.resolvedReturnType; + } + + function getReturnTypeFromAnnotation(declaration: SignatureDeclaration | JSDocSignature) { + if (declaration.kind === SyntaxKind.Constructor) { + return getDeclaredTypeOfClassOrInterface(getMergedSymbol((declaration.parent as ClassDeclaration).symbol)); + } + const typeNode = getEffectiveReturnTypeNode(declaration); + if (isJSDocSignature(declaration)) { + const root = getJSDocRoot(declaration); + if (root && isConstructorDeclaration(root.parent) && !typeNode) { + return getDeclaredTypeOfClassOrInterface(getMergedSymbol((root.parent.parent as ClassDeclaration).symbol)); + } + } + if (isJSDocConstructSignature(declaration)) { + return getTypeFromTypeNode((declaration.parameters[0] as ParameterDeclaration).type!); // TODO: GH#18217 + } + 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(getSymbolOfDeclaration(declaration), SyntaxKind.SetAccessor); + const setterType = getAnnotatedAccessorType(setter); + if (setterType) { + return setterType; + } + } + return getReturnTypeOfTypeTag(declaration); + } + + function isResolvingReturnTypeOfSignature(signature: Signature): boolean { + return signature.compositeSignatures && some(signature.compositeSignatures, isResolvingReturnTypeOfSignature) || + !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 getSignatureInstantiation(signature: Signature, typeArguments: readonly 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 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 createSignatureInstantiation(signature: Signature, typeArguments: readonly Type[] | undefined): Signature { + return instantiateSignature(signature, createSignatureTypeMapper(signature, typeArguments), /*eraseTypeParameters*/ true); + } + + function createSignatureTypeMapper(signature: Signature, typeArguments: readonly Type[] | undefined): TypeMapper { + return createTypeMapper(signature.typeParameters!, typeArguments); + } + + function getErasedSignature(signature: Signature): Signature { + return signature.typeParameters ? + signature.erasedSignatureCache || (signature.erasedSignatureCache = createErasedSignature(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 getCanonicalSignature(signature: Signature): Signature { + return signature.typeParameters ? + signature.canonicalSignatureCache || (signature.canonicalSignatureCache = createCanonicalSignature(signature)) : + 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 getImplementationSignature(signature: Signature) { + return signature.typeParameters ? + signature.implementationSignatureCache ||= createImplementationSignature(signature) : + signature; + } + + function createImplementationSignature(signature: Signature) { + return signature.typeParameters ? instantiateSignature(signature, createTypeMapper([], [])) : 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 getOrCreateTypeFromSignature(signature: Signature, outerTypeParameters?: TypeParameter[]): 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; + + // 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; + + // The type must have a symbol with a `Function` flag and a declaration in order to be correctly flagged as possibly containing + // type variables by `couldContainTypeVariables` + const type = createObjectType(ObjectFlags.Anonymous | ObjectFlags.SingleSignatureType, createSymbol(SymbolFlags.Function, InternalSymbolName.Function)) as SingleSignatureType; + if (signature.declaration && !nodeIsSynthesized(signature.declaration)) { // skip synthetic declarations - keeping those around could be bad, since they lack a parent pointer + type.symbol.declarations = [signature.declaration]; + type.symbol.valueDeclaration = signature.declaration; + } + outerTypeParameters ||= signature.declaration && getOuterTypeParameters(signature.declaration, /*includeThisTypes*/ true); + type.outerTypeParameters = outerTypeParameters; + + type.members = emptySymbols; + type.properties = emptyArray; + type.callSignatures = !isConstructor ? [signature] : emptyArray; + type.constructSignatures = isConstructor ? [signature] : emptyArray; + type.indexInfos = emptyArray; + signature.isolatedSignatureType = type; + } + + return signature.isolatedSignatureType; + } + + function getIndexSymbol(symbol: Symbol): Symbol | undefined { + return symbol.members ? getIndexSymbolFromSymbolTable(symbol.members) : undefined; + } + + function getIndexSymbolFromSymbolTable(symbolTable: SymbolTable): Symbol | undefined { + return symbolTable.get(InternalSymbolName.Index); + } + + function createIndexInfo(keyType: Type, type: Type, isReadonly: boolean, declaration?: IndexSignatureDeclaration): IndexInfo { + return { keyType, type, isReadonly, 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; + } + + 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 = getTypeParametersForTypeReferenceOrImport(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, stringNumberSymbolType); + } + // 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(getSymbolOfDeclaration(checkMappedType.typeParameter)), checkMappedType.typeParameter.constraint ? getTypeFromTypeNode(checkMappedType.typeParameter.constraint) : stringNumberSymbolType))); + } + } + } + } + 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; + } + 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 stringNumberSymbolType 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 ? stringNumberSymbolType : unknownType; + } + typeParameter.constraint = type; + } + } + } + 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 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; + } + } + 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); + } + } + return result & ObjectFlags.PropagatingFlags; + } + + function tryCreateTypeReference(target: GenericType, typeArguments: readonly Type[] | undefined): Type { + if (some(typeArguments) && target === emptyGenericType) { + return unknownType; + } + + return createTypeReference(target, typeArguments); + } + + 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 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; + } + + 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 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; + } + } + 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); + } + 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) { + const typeKind = intrinsicTypeKinds.get(symbol.escapedName as string); + if (typeKind !== undefined && typeArguments && typeArguments.length === 1) { + return typeKind === IntrinsicTypeKind.NoInfer ? getNoInferType(typeArguments[0]) : 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; + } + + /** + * 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", /*objectFlags*/ undefined, `alias ${id}`); + 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, + ); + 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); + let newAliasSymbol = aliasSymbol && (isLocalTypeAlias(symbol) || !isLocalTypeAlias(aliasSymbol)) ? aliasSymbol : undefined; + let aliasTypeArguments: Type[] | undefined; + if (newAliasSymbol) { + aliasTypeArguments = getTypeArgumentsForAliasSymbol(newAliasSymbol); + } + else if (isTypeReferenceType(node)) { + const aliasSymbol = resolveTypeReferenceName(node, SymbolFlags.Alias, /*ignoreErrors*/ true); + // refers to an alias import/export/reexport - by making sure we use the target as an aliasSymbol, + // we ensure the exported symbol is used to refer to the type when it's reserialized later + if (aliasSymbol && aliasSymbol !== unknownSymbol) { + const resolved = resolveAlias(aliasSymbol); + if (resolved && resolved.flags & SymbolFlags.TypeAlias) { + newAliasSymbol = resolved; + aliasTypeArguments = typeArgumentsFromTypeReferenceNode(node) || (typeParameters ? [] : undefined); + } + } + } + return getTypeAliasInstantiation(symbol, typeArgumentsFromTypeReferenceNode(node), newAliasSymbol, aliasTypeArguments); + } + 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; + } + + 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.links.declaredType = unresolvedType; + } + return result; + } + return unknownSymbol; + } + + 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 getTypeReferenceType(node: NodeWithTypeArguments, symbol: Symbol): Type { + if (symbol === unknownSymbol) { + 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; + } + + /** + * 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 getNoInferType(type: Type) { + return isNoInferTargetType(type) ? getOrCreateSubstitutionType(type, unknownType) : type; + } + + function isNoInferTargetType(type: Type): boolean { + // This is effectively a more conservative and predictable form of couldContainTypeVariables. We want to + // preserve NoInfer only for types that could contain type variables, but we don't want to exhaustively + // examine all object type members. + return !!(type.flags & TypeFlags.UnionOrIntersection && some((type as UnionOrIntersectionType).types, isNoInferTargetType) || + type.flags & TypeFlags.Substitution && !isNoInferType(type) && isNoInferTargetType((type as SubstitutionType).baseType) || + type.flags & TypeFlags.Object && !isEmptyAnonymousObjectType(type) || + type.flags & (TypeFlags.Instantiable & ~TypeFlags.Substitution) && !isPatternLiteralType(type)); + } + + function isNoInferType(type: Type) { + // A NoInfer type is represented as a substitution type with a TypeFlags.Unknown constraint. + return !!(type.flags & TypeFlags.Substitution && (type as SubstitutionType).constraint.flags & TypeFlags.Unknown); + } + + function getSubstitutionType(baseType: Type, constraint: Type) { + return constraint.flags & TypeFlags.AnyOrUnknown || constraint === baseType || baseType.flags & TypeFlags.Any ? + baseType : + getOrCreateSubstitutionType(baseType, constraint); + } + + function getOrCreateSubstitutionType(baseType: Type, constraint: Type) { + 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 getSubstitutionIntersection(substitutionType: SubstitutionType) { + return isNoInferType(substitutionType) ? substitutionType.baseType : 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); + } + } + // 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 && !(parent as MappedTypeNode).nameType && 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; + } + + function isJSDocTypeReference(node: Node): node is TypeReferenceNode { + return !!(node.flags & NodeFlags.JSDoc) && (node.kind === SyntaxKind.TypeReference || node.kind === SyntaxKind.ImportType); + } + + 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 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(/*symbol*/ undefined, emptySymbols, emptyArray, emptyArray, indexInfo); + } + return anyType; + } + checkNoTypeArguments(node); + return !noImplicitAny ? anyType : undefined; + } + } + } + + 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); + } + 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 (!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; + } + + function typeArgumentsFromTypeReferenceNode(node: NodeWithTypeArguments): Type[] | undefined { + return map(node.typeArguments, getTypeFromTypeNode); + } + + 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 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; + } + } + } + } + + if (!symbol) { + return arity ? emptyGenericType : emptyObjectType; + } + 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; + } + 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 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 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 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(/*location*/ undefined, name, meaning, diagnostic, /*isUse*/ false, /*excludeGlobals*/ false); + } + + 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; + } + + 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; + } + + 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 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 getGlobalImportMetaExpressionType() { + if (!deferredGlobalImportMetaExpressionType) { + // Create a synthetic type `ImportMetaExpression { meta: MetaProperty }` + const symbol = createSymbol(SymbolFlags.None, "ImportMetaExpression" as __String); + const importMetaType = getGlobalImportMetaType(); + + const metaPropertySymbol = createSymbol(SymbolFlags.Property, "meta" as __String, CheckFlags.Readonly); + metaPropertySymbol.parent = symbol; + metaPropertySymbol.links.type = importMetaType; + + const members = createSymbolTable([metaPropertySymbol]); + symbol.members = members; + + deferredGlobalImportMetaExpressionType = createAnonymousType(symbol, members, emptyArray, emptyArray, emptyArray); + } + return deferredGlobalImportMetaExpressionType; + } + + function getGlobalImportCallOptionsType(reportErrors: boolean) { + return (deferredGlobalImportCallOptionsType ||= getGlobalType("ImportCallOptions" as __String, /*arity*/ 0, reportErrors)) || emptyObjectType; + } + + function getGlobalImportAttributesType(reportErrors: boolean) { + return (deferredGlobalImportAttributesType ||= getGlobalType("ImportAttributes" as __String, /*arity*/ 0, reportErrors)) || emptyObjectType; + } + + function getGlobalESSymbolConstructorSymbol(reportErrors: boolean): Symbol | undefined { + return deferredGlobalESSymbolConstructorSymbol ||= getGlobalValueSymbol("Symbol" as __String, reportErrors); + } + + function getGlobalESSymbolConstructorTypeSymbol(reportErrors: boolean): Symbol | undefined { + return deferredGlobalESSymbolConstructorTypeSymbol ||= getGlobalTypeSymbol("SymbolConstructor" as __String, reportErrors); + } + + function getGlobalESSymbolType() { + return (deferredGlobalESSymbolType ||= getGlobalType("Symbol" as __String, /*arity*/ 0, /*reportErrors*/ false)) || emptyObjectType; + } + + function getGlobalPromiseType(reportErrors: boolean) { + return (deferredGlobalPromiseType ||= getGlobalType("Promise" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; + } + + function getGlobalPromiseLikeType(reportErrors: boolean) { + return (deferredGlobalPromiseLikeType ||= getGlobalType("PromiseLike" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; + } + + function getGlobalPromiseConstructorSymbol(reportErrors: boolean): Symbol | undefined { + return deferredGlobalPromiseConstructorSymbol ||= getGlobalValueSymbol("Promise" as __String, reportErrors); + } + + function getGlobalPromiseConstructorLikeType(reportErrors: boolean) { + return (deferredGlobalPromiseConstructorLikeType ||= getGlobalType("PromiseConstructorLike" as __String, /*arity*/ 0, reportErrors)) || emptyObjectType; + } + + function getGlobalAsyncIterableType(reportErrors: boolean) { + return (deferredGlobalAsyncIterableType ||= getGlobalType("AsyncIterable" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; + } + + function getGlobalAsyncIteratorType(reportErrors: boolean) { + return (deferredGlobalAsyncIteratorType ||= getGlobalType("AsyncIterator" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType; + } + + function getGlobalAsyncIterableIteratorType(reportErrors: boolean) { + return (deferredGlobalAsyncIterableIteratorType ||= getGlobalType("AsyncIterableIterator" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; + } + + function getGlobalAsyncGeneratorType(reportErrors: boolean) { + return (deferredGlobalAsyncGeneratorType ||= getGlobalType("AsyncGenerator" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType; + } + + function getGlobalIterableType(reportErrors: boolean) { + return (deferredGlobalIterableType ||= getGlobalType("Iterable" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; + } + + function getGlobalIteratorType(reportErrors: boolean) { + return (deferredGlobalIteratorType ||= getGlobalType("Iterator" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType; + } + + function getGlobalIterableIteratorType(reportErrors: boolean) { + return (deferredGlobalIterableIteratorType ||= getGlobalType("IterableIterator" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; + } + + function getGlobalGeneratorType(reportErrors: boolean) { + return (deferredGlobalGeneratorType ||= getGlobalType("Generator" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType; + } + + function getGlobalIteratorYieldResultType(reportErrors: boolean) { + return (deferredGlobalIteratorYieldResultType ||= getGlobalType("IteratorYieldResult" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; + } + + function getGlobalIteratorReturnResultType(reportErrors: boolean) { + return (deferredGlobalIteratorReturnResultType ||= getGlobalType("IteratorReturnResult" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; + } + + function getGlobalDisposableType(reportErrors: boolean) { + return (deferredGlobalDisposableType ||= getGlobalType("Disposable" as __String, /*arity*/ 0, reportErrors)) || emptyObjectType; + } + + function getGlobalAsyncDisposableType(reportErrors: boolean) { + return (deferredGlobalAsyncDisposableType ||= getGlobalType("AsyncDisposable" as __String, /*arity*/ 0, reportErrors)) || emptyObjectType; + } + + function getGlobalTypeOrUndefined(name: __String, arity = 0): ObjectType | undefined { + const symbol = getGlobalSymbol(name, SymbolFlags.Type, /*diagnostic*/ undefined); + return symbol && getTypeOfGlobalSymbol(symbol, arity) as GenericType; + } + + 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 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 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 getGlobalBigIntType() { + return (deferredGlobalBigIntType ||= getGlobalType("BigInt" as __String, /*arity*/ 0, /*reportErrors*/ false)) || emptyObjectType; + } + + function getGlobalClassDecoratorContextType(reportErrors: boolean) { + return (deferredGlobalClassDecoratorContextType ??= getGlobalType("ClassDecoratorContext" as __String, /*arity*/ 1, reportErrors)) ?? emptyGenericType; + } + + function getGlobalClassMethodDecoratorContextType(reportErrors: boolean) { + return (deferredGlobalClassMethodDecoratorContextType ??= getGlobalType("ClassMethodDecoratorContext" as __String, /*arity*/ 2, reportErrors)) ?? emptyGenericType; + } + + function getGlobalClassGetterDecoratorContextType(reportErrors: boolean) { + return (deferredGlobalClassGetterDecoratorContextType ??= getGlobalType("ClassGetterDecoratorContext" as __String, /*arity*/ 2, reportErrors)) ?? emptyGenericType; + } + + function getGlobalClassSetterDecoratorContextType(reportErrors: boolean) { + return (deferredGlobalClassSetterDecoratorContextType ??= getGlobalType("ClassSetterDecoratorContext" as __String, /*arity*/ 2, reportErrors)) ?? emptyGenericType; + } + + function getGlobalClassAccessorDecoratorContextType(reportErrors: boolean) { + return (deferredGlobalClassAccessorDecoratorContextType ??= getGlobalType("ClassAccessorDecoratorContext" as __String, /*arity*/ 2, reportErrors)) ?? emptyGenericType; + } + + function getGlobalClassAccessorDecoratorTargetType(reportErrors: boolean) { + return (deferredGlobalClassAccessorDecoratorTargetType ??= getGlobalType("ClassAccessorDecoratorTarget" as __String, /*arity*/ 2, reportErrors)) ?? emptyGenericType; + } + + function getGlobalClassAccessorDecoratorResultType(reportErrors: boolean) { + return (deferredGlobalClassAccessorDecoratorResultType ??= getGlobalType("ClassAccessorDecoratorResult" as __String, /*arity*/ 2, reportErrors)) ?? emptyGenericType; + } + + function getGlobalClassFieldDecoratorContextType(reportErrors: boolean) { + return (deferredGlobalClassFieldDecoratorContextType ??= getGlobalType("ClassFieldDecoratorContext" as __String, /*arity*/ 2, reportErrors)) ?? emptyGenericType; + } + + function getGlobalNaNSymbol(): Symbol | undefined { + return (deferredGlobalNaNSymbol ||= getGlobalValueSymbol("NaN" as __String, /*reportErrors*/ false)); + } + + function getGlobalRecordSymbol(): Symbol | undefined { + deferredGlobalRecordSymbol ||= getGlobalTypeAliasSymbol("Record" as __String, /*arity*/ 2, /*reportErrors*/ true) || unknownSymbol; + return deferredGlobalRecordSymbol === unknownSymbol ? undefined : deferredGlobalRecordSymbol; + } + + /** + * 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 createTypedPropertyDescriptorType(propertyType: Type): Type { + return createTypeFromGenericGlobalType(getGlobalTypedPropertyDescriptorType(), [propertyType]); + } + + function createIterableType(iteratedType: Type): Type { + return createTypeFromGenericGlobalType(getGlobalIterableType(/*reportErrors*/ true), [iteratedType]); + } + + 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; + } + } + + 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); + return getTupleTargetType(elementFlags, readonly, map((node as TupleTypeNode).elements, memberIfLabeledElementDeclaration)); + } + + function memberIfLabeledElementDeclaration(member: Node): NamedTupleMember | ParameterDeclaration | undefined { + return isNamedTupleMember(member) || isParameter(member) ? member : undefined; + } + + // 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; + } + + // 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; + } + + 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 | undefined)[] = []) { + 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 | undefined)[]): 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" : "") + + (some(namedMemberDeclarations, node => !!node) ? "," + map(namedMemberDeclarations, node => node ? getNodeId(node) : "_").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 = 0 as ElementFlags; + 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.links.tupleLabelDeclaration = namedMemberDeclarations?.[i]; + property.links.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.links.type = numberType; + } + else { + const literalTypes = []; + for (let i = minLength; i <= arity; i++) literalTypes.push(getNumberLiteralType(i)); + lengthSymbol.links.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[] = []; + const 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.Any) { + addElement(type, ElementFlags.Rest, target.labeledElementDeclarations?.[i]); + } + else 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 = getElementTypes(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]); + } + } + else { + // Copy other element kinds with no change. + addElement(type, flags, 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); + } + 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); + expandedDeclarations.push(declaration); + } + } + + 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))); + } + + // 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; + } + + function getTotalFixedElementCount(type: TupleType) { + return type.fixedLength + getEndElementCount(type, ElementFlags.Fixed); + } + + function getElementTypes(type: TupleTypeReference): readonly Type[] { + const typeArguments = getTypeArguments(type); + const arity = getTypeReferenceArity(type); + return typeArguments.length === arity ? typeArguments : typeArguments.slice(0, arity); + } + + function getTypeFromOptionalTypeNode(node: OptionalTypeNode): Type { + return addOptionality(getTypeFromTypeNode(node.type), /*isProperty*/ true); + } + + function getTypeId(type: Type): TypeId { + return type.id; + } + + 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 addTypeToUnion(typeSet: Type[], includes: TypeFlags, type: Type) { + const flags = type.flags; + // We ignore 'never' types in unions + if (!(flags & TypeFlags.Never)) { + includes |= flags & TypeFlags.IncludesMask; + if (flags & TypeFlags.Instantiable) includes |= TypeFlags.IncludesInstantiable; + if (flags & TypeFlags.Intersection && getObjectFlags(type) & ObjectFlags.IsConstrainedTypeVariable) includes |= TypeFlags.IncludesConstrainedTypeVariable; + if (type === wildcardType) includes |= TypeFlags.IncludesWildcard; + if (isErrorType(type)) includes |= TypeFlags.IncludesError; + 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); + } + } + } + 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 { + let lastType: Type | undefined; + for (const type of types) { + // We skip the type if it is the same as the last type we processed. This simple test particularly + // saves a lot of work for large lists of the same union type, such as when resolving `Record[A]`, + // where A and B are large union types. + if (type !== lastType) { + includes = type.flags & TypeFlags.Union ? + addTypesToUnion(typeSet, includes | (isNamedUnionType(type) ? TypeFlags.Union : 0), (type as UnionType).types) : + addTypeToUnion(typeSet, includes, type); + lastType = 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; + } + + // 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) { + // A type parameter with a union constraint may be a subtype of some union, but not a subtype of the + // individual constituents of that union. For example, `T extends A | B` is a subtype of `A | B`, but not + // a subtype of just `A` or just `B`. When we encounter such a type parameter, we therefore check if the + // type parameter is a subtype of a union of all the other types. + if (source.flags & TypeFlags.TypeParameter && getBaseConstraintOrType(source).flags & TypeFlags.Union) { + if (isTypeRelatedTo(source, getUnionType(map(types, t => t === source ? neverType : t)), strictSubtypeRelation)) { + orderedRemoveItemAt(types, i); + } + continue; + } + // 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; + } + } + 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; + } + + 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 removeStringLiteralsMatchedByTemplateLiterals(types: Type[]) { + const templates = filter(types, isPatternLiteralType) as (TemplateLiteralType | StringMappingType)[]; + if (templates.length) { + let i = types.length; + while (i > 0) { + i--; + const t = types[i]; + if (t.flags & TypeFlags.StringLiteral && some(templates, template => isTypeMatchedByTemplateLiteralOrStringMapping(t, template))) { + orderedRemoveItemAt(types, i); + } + } + } + } + + function isTypeMatchedByTemplateLiteralOrStringMapping(type: Type, template: TemplateLiteralType | StringMappingType) { + return template.flags & TypeFlags.TemplateLiteral ? + isTypeMatchedByTemplateLiteralType(type, template as TemplateLiteralType) : + isMemberOfStringMapping(type, template); + } + + function removeConstrainedTypeVariables(types: Type[]) { + const typeVariables: TypeVariable[] = []; + // First collect a list of the type variables occurring in constraining intersections. + for (const type of types) { + if (type.flags & TypeFlags.Intersection && getObjectFlags(type) & ObjectFlags.IsConstrainedTypeVariable) { + const index = (type as IntersectionType).types[0].flags & TypeFlags.TypeVariable ? 0 : 1; + pushIfUnique(typeVariables, (type as IntersectionType).types[index]); + } + } + // For each type variable, check if the constraining intersections for that type variable fully + // cover the constraint of the type variable; if so, remove the constraining intersections and + // substitute the type variable. + for (const typeVariable of typeVariables) { + const primitives: Type[] = []; + // First collect the primitive types from the constraining intersections. + for (const type of types) { + if (type.flags & TypeFlags.Intersection && getObjectFlags(type) & ObjectFlags.IsConstrainedTypeVariable) { + const index = (type as IntersectionType).types[0].flags & TypeFlags.TypeVariable ? 0 : 1; + if ((type as IntersectionType).types[index] === typeVariable) { + insertType(primitives, (type as IntersectionType).types[1 - index]); + } + } + } + // If every constituent in the type variable's constraint is covered by an intersection of the type + // variable and that constituent, remove those intersections and substitute the type variable. + const constraint = getBaseConstraintOfType(typeVariable)!; + if (everyType(constraint, t => containsType(primitives, t))) { + let i = types.length; + while (i > 0) { + i--; + const type = types[i]; + if (type.flags & TypeFlags.Intersection && getObjectFlags(type) & ObjectFlags.IsConstrainedTypeVariable) { + const index = (type as IntersectionType).types[0].flags & TypeFlags.TypeVariable ? 0 : 1; + if ((type as IntersectionType).types[index] === typeVariable && containsType(primitives, (type as IntersectionType).types[1 - index])) { + orderedRemoveItemAt(types, i); + } + } + } + insertType(types, typeVariable); + } + } + } + + 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 createOriginUnionOrIntersectionType(flags: TypeFlags, types: Type[]) { + const result = createOriginType(flags) as UnionOrIntersectionType; + result.types = types; + return result; + } + + // 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]; + } + // We optimize for the common case of unioning a union type with some other type (such as `undefined`). + if (types.length === 2 && !origin && (types[0].flags & TypeFlags.Union || types[1].flags & TypeFlags.Union)) { + const infix = unionReduction === UnionReduction.None ? "N" : unionReduction === UnionReduction.Subtype ? "S" : "L"; + const index = types[0].id < types[1].id ? 0 : 1; + const id = types[index].id + infix + types[1 - index].id + getAliasId(aliasSymbol, aliasTypeArguments); + let type = unionOfUnionTypes.get(id); + if (!type) { + type = getUnionTypeWorker(types, unionReduction, aliasSymbol, aliasTypeArguments, /*origin*/ undefined); + unionOfUnionTypes.set(id, type); + } + return type; + } + return getUnionTypeWorker(types, unionReduction, aliasSymbol, aliasTypeArguments, origin); + } + + function getUnionTypeWorker(types: readonly Type[], unionReduction: UnionReduction, aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined, origin: Type | undefined): Type { + let typeSet: Type[] | undefined = []; + const includes = addTypesToUnion(typeSet, 0 as TypeFlags, types); + if (unionReduction !== UnionReduction.None) { + if (includes & TypeFlags.AnyOrUnknown) { + return includes & TypeFlags.Any ? + includes & TypeFlags.IncludesWildcard ? wildcardType : + includes & TypeFlags.IncludesError ? errorType : anyType : + unknownType; + } + if (includes & TypeFlags.Undefined) { + // If type set contains both undefinedType and missingType, remove missingType + if (typeSet.length >= 2 && typeSet[0] === undefinedType && typeSet[1] === missingType) { + orderedRemoveItemAt(typeSet, 1); + } + } + if (includes & (TypeFlags.Enum | 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 | TypeFlags.StringMapping)) { + removeStringLiteralsMatchedByTemplateLiterals(typeSet); + } + if (includes & TypeFlags.IncludesConstrainedTypeVariable) { + removeConstrainedTypeVariables(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 (!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); + } + } + 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 last: TypePredicate | undefined; + const types: Type[] = []; + for (const sig of signatures) { + const pred = getTypePredicateOfSignature(sig); + if (pred) { + // Constituent type predicates must all have matching kinds. We don't create composite type predicates for assertions. + if (pred.kind !== TypePredicateKind.This && pred.kind !== TypePredicateKind.Identifier || last && !typePredicateKindsMatch(last, pred)) { + return undefined; + } + last = pred; + types.push(pred.type); + } + else { + // In composite union signatures we permit and ignore signatures with a return type `false`. + const returnType = kind !== TypeFlags.Intersection ? getReturnTypeOfSignature(sig) : undefined; + if (returnType !== falseType && returnType !== regularFalseType) { + return undefined; + } + } + } + if (!last) { + return undefined; + } + const compositeType = getUnionOrIntersectionType(types, kind); + return createTypePredicate(last.kind, last.parameterName, last.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[], precomputedObjectFlags: ObjectFlags, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[], origin?: Type): Type { + if (types.length === 0) { + return neverType; + } + 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 = precomputedObjectFlags | 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"; + } + unionTypes.set(id, type); + } + return type; + } + + 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 addTypeToIntersection(typeSet: Map, 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); + } + } + else { + if (flags & TypeFlags.AnyOrUnknown) { + if (type === wildcardType) includes |= TypeFlags.IncludesWildcard; + if (isErrorType(type)) includes |= TypeFlags.IncludesError; + } + else if (strictNullChecks || !(flags & TypeFlags.Nullable)) { + if (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; + } + return includes; + } + + // 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: Map, includes: TypeFlags, types: readonly Type[]) { + for (const type of types) { + includes = addTypeToIntersection(typeSet, includes, getRegularTypeOfLiteralType(type)); + } + return includes; + } + + 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); + } + } + } + + // 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.Enum | TypeFlags.NumberLiteral) ? numberType : + type.flags & TypeFlags.BigIntLiteral ? bigintType : + type.flags & TypeFlags.UniqueESSymbol ? esSymbolType : + undefined; + if (!primitive || !containsType(u.types, primitive)) { + return false; + } + } + } + return true; + } + + /** + * Returns true if the intersection of the template literals and string literals is the empty set, + * for example `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 | TypeFlags.StringMapping))) continue; + for (const t2 of literals) { + if (isTypeSubtypeOf(t2, t)) { + // For example, `get${T}` & "getX" is just "getX", and Lowercase & "foo" is just "foo" + orderedRemoveItemAt(types, i); + break; + } + else if (isPatternLiteralType(t)) { + return true; + } + } + } + return false; + } + + function removeFromEach(types: Type[], flag: TypeFlags) { + for (let i = 0; i < types.length; i++) { + types[i] = filterType(types[i], t => !(t.flags & flag)); + } + } + + // 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 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); + } + } + } + } + // Finally replace the first union with the result + types[index] = getUnionTypeFromSortedList(result, ObjectFlags.PrimitiveUnion); + return true; + } + + function createIntersectionType(types: Type[], objectFlags: ObjectFlags, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]) { + const result = createType(TypeFlags.Intersection) as IntersectionType; + result.objectFlags = objectFlags | getPropagatingFlagsOfTypes(types, /*excludeKinds*/ TypeFlags.Nullable); + result.types = types; + result.aliasSymbol = aliasSymbol; + result.aliasTypeArguments = aliasTypeArguments; + return result; + } + + // We normalize combinations of intersection and union types based on the distributive property of the '&' + // operator. Specifically, because X & (A | B) is equivalent to X & A | X & B, we can transform intersection + // types with union type constituents into equivalent union types with intersection type constituents and + // 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[], flags = IntersectionFlags.None, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type { + const typeMembershipMap = new Map(); + const includes = addTypesToIntersection(typeMembershipMap, 0 as TypeFlags, types); + const typeSet: Type[] = arrayFrom(typeMembershipMap.values()); + let objectFlags = ObjectFlags.None; + // 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 | TypeFlags.StringMapping) && includes & TypeFlags.StringLiteral && extractRedundantTemplateLiterals(typeSet)) { + return neverType; + } + if (includes & TypeFlags.Any) { + return includes & TypeFlags.IncludesWildcard ? wildcardType : includes & TypeFlags.IncludesError ? errorType : 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 (!(flags & IntersectionFlags.NoSupertypeReduction)) removeRedundantSupertypes(typeSet, includes); + } + if (includes & TypeFlags.IncludesMissingType) { + typeSet[typeSet.indexOf(undefinedType)] = missingType; + } + if (typeSet.length === 0) { + return unknownType; + } + if (typeSet.length === 1) { + return typeSet[0]; + } + if (typeSet.length === 2 && !(flags & IntersectionFlags.NoConstraintReduction)) { + const typeVarIndex = typeSet[0].flags & TypeFlags.TypeVariable ? 0 : 1; + const typeVariable = typeSet[typeVarIndex]; + const primitiveType = typeSet[1 - typeVarIndex]; + if ( + typeVariable.flags & TypeFlags.TypeVariable && + (primitiveType.flags & (TypeFlags.Primitive | TypeFlags.NonPrimitive) && !isGenericStringLikeType(primitiveType) || includes & TypeFlags.IncludesEmptyObject) + ) { + // We have an intersection T & P or P & T, where T is a type variable and P is a primitive type, the object type, or {}. + const constraint = getBaseConstraintOfType(typeVariable); + // Check that T's constraint is similarly composed of primitive types, the object type, or {}. + if (constraint && everyType(constraint, t => !!(t.flags & (TypeFlags.Primitive | TypeFlags.NonPrimitive)) || isEmptyAnonymousObjectType(t))) { + // If T's constraint is a subtype of P, simply return T. For example, given `T extends "a" | "b"`, + // the intersection `T & string` reduces to just T. + if (isTypeStrictSubtypeOf(constraint, primitiveType)) { + return typeVariable; + } + if (!(constraint.flags & TypeFlags.Union && someType(constraint, c => isTypeStrictSubtypeOf(c, primitiveType)))) { + // No constituent of T's constraint is a subtype of P. If P is also not a subtype of T's constraint, + // then the constraint and P are unrelated, and the intersection reduces to never. For example, given + // `T extends "a" | "b"`, the intersection `T & number` reduces to never. + if (!isTypeStrictSubtypeOf(primitiveType, constraint)) { + return neverType; + } + } + // Some constituent of T's constraint is a subtype of P, or P is a subtype of T's constraint. Thus, + // the intersection further constrains the type variable. For example, given `T extends string | number`, + // the intersection `T & "a"` is marked as a constrained type variable. Likewise, given `T extends "a" | 1`, + // the intersection `T & number` is marked as a constrained type variable. + objectFlags = ObjectFlags.IsConstrainedTypeVariable; + } + } + } + const id = getTypeListId(typeSet) + (flags & IntersectionFlags.NoConstraintReduction ? "*" : 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, flags, aliasSymbol, aliasTypeArguments); + } + else if (every(typeSet, t => !!(t.flags & TypeFlags.Union && (t as UnionType).types[0].flags & TypeFlags.Undefined))) { + const containedUndefinedType = some(typeSet, containsMissingType) ? missingType : undefinedType; + removeFromEach(typeSet, TypeFlags.Undefined); + result = getUnionType([getIntersectionType(typeSet, flags), containedUndefinedType], UnionReduction.Literal, aliasSymbol, aliasTypeArguments); + } + else if (every(typeSet, t => !!(t.flags & TypeFlags.Union && ((t as UnionType).types[0].flags & TypeFlags.Null || (t as UnionType).types[1].flags & TypeFlags.Null)))) { + removeFromEach(typeSet, TypeFlags.Null); + result = getUnionType([getIntersectionType(typeSet, flags), nullType], UnionReduction.Literal, aliasSymbol, aliasTypeArguments); + } + else if (typeSet.length >= 4) { + // When we have four or more constituents, some of which are unions, we employ a "divide and conquer" strategy + // where A & B & C & D is processed as (A & B) & (C & D). Since intersections of unions often produce far smaller + // unions of intersections than the full cartesian product (due to some intersections becoming `never`), this can + // dramatically reduce the overall work. + const middle = Math.floor(typeSet.length / 2); + result = getIntersectionType([getIntersectionType(typeSet.slice(0, middle), flags), getIntersectionType(typeSet.slice(middle), flags)], flags, 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, flags); + // 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 { + result = createIntersectionType(typeSet, objectFlags, aliasSymbol, aliasTypeArguments); + } + intersectionTypes.set(id, result); + } + return result; + } + + 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 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 getCrossProductIntersections(types: readonly Type[], flags: IntersectionFlags) { + 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); + } + } + const t = getIntersectionType(constituents, flags); + if (!(t.flags & TypeFlags.Never)) intersections.push(t); + } + return intersections; + } + + 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 getTypeFromIntersectionTypeNode(node: IntersectionTypeNode): Type { + const links = getNodeLinks(node); + if (!links.resolvedType) { + const aliasSymbol = getAliasSymbolForTypeNode(node); + const types = map(node.types, getTypeFromTypeNode); + // We perform no supertype reduction for X & {} or {} & X, where X is one of string, number, bigint, + // or a pattern literal template type. This enables union types like "a" | "b" | string & {} or + // "aa" | "ab" | `a${string}` which preserve the literal types for purposes of statement completion. + const emptyIndex = types.length === 2 ? types.indexOf(emptyTypeLiteralType) : -1; + const t = emptyIndex >= 0 ? types[1 - emptyIndex] : unknownType; + const noSupertypeReduction = !!(t.flags & (TypeFlags.String | TypeFlags.Number | TypeFlags.BigInt) || t.flags & TypeFlags.TemplateLiteral && isPatternLiteralType(t)); + links.resolvedType = getIntersectionType(types, noSupertypeReduction ? IntersectionFlags.NoSupertypeReduction : 0, aliasSymbol, getTypeArgumentsForAliasSymbol(aliasSymbol)); + } + return links.resolvedType; + } + + function createIndexType(type: InstantiableType | UnionOrIntersectionType, indexFlags: IndexFlags) { + const result = createType(TypeFlags.Index) as IndexType; + result.type = type; + result.indexFlags = indexFlags; + return result; + } + + function createOriginIndexType(type: InstantiableType | UnionOrIntersectionType) { + const result = createOriginType(TypeFlags.Index) as IndexType; + result.type = type; + return result; + } + + function getIndexTypeForGenericType(type: InstantiableType | UnionOrIntersectionType, indexFlags: IndexFlags) { + return indexFlags & IndexFlags.StringsOnly ? + type.resolvedStringIndexType || (type.resolvedStringIndexType = createIndexType(type, IndexFlags.StringsOnly)) : + type.resolvedIndexType || (type.resolvedIndexType = createIndexType(type, IndexFlags.None)); + } + + /** + * 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, indexFlags: IndexFlags) { + const typeParameter = getTypeParameterFromMappedType(type); + const constraintType = getConstraintTypeFromMappedType(type); + const nameType = getNameTypeFromMappedType(type.target as MappedType || type); + if (!nameType && !(indexFlags & IndexFlags.NoIndexSignatures)) { + // no mapping and no filtering required, just quickly bail to returning the constraint in the common case + return constraintType; + } + const keyTypes: Type[] = []; + // Calling getApparentType on the `T` of a `keyof T` in the constraint type of a generic mapped type can + // trigger a circularity. For example, `T extends { [P in keyof T & string as Captitalize

]: any }` is + // a circular definition. For this reason, we only eagerly manifest the keys if the constraint is non-generic. + if (isGenericIndexType(constraintType)) { + if (isMappedTypeWithKeyofConstraintDeclaration(type)) { + // 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, indexFlags); + } + // Include the generic component in the resulting type. + forEachType(constraintType, addMemberForKeyType); + } + else if (isMappedTypeWithKeyofConstraintDeclaration(type)) { + const modifiersType = getApparentType(getModifiersTypeFromMappedType(type)); // The 'T' in 'keyof T' + forEachMappedTypePropertyKeyTypeAndIndexSignatureKeyType(modifiersType, TypeFlags.StringOrNumberLiteralOrUnique, !!(indexFlags & IndexFlags.StringsOnly), addMemberForKeyType); + } + else { + forEachType(getLowerBoundOfKeyType(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 = indexFlags & IndexFlags.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); + } + } + + // 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 getLiteralTypeFromPropertyName(name: PropertyName | JsxAttributeName) { + if (isPrivateIdentifier(name)) { + return neverType; + } + if (isNumericLiteral(name)) { + return getRegularTypeOfLiteralType(checkExpression(name)); + } + if (isComputedPropertyName(name)) { + return getRegularTypeOfLiteralType(checkComputedPropertyName(name)); + } + const propertyName = getPropertyNameForPropertyNameNode(name); + if (propertyName !== undefined) { + return getStringLiteralType(unescapeLeadingUnderscores(propertyName)); + } + if (isExpression(name)) { + return getRegularTypeOfLiteralType(checkExpression(name)); + } + return neverType; + } + + 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 | JsxAttributeName; + type = prop.escapedName === InternalSymbolName.Default ? getStringLiteralType("default") : + name && getLiteralTypeFromPropertyName(name) || (!isKnownSymbol(prop) ? getStringLiteralType(symbolName(prop)) : undefined); + } + if (type && type.flags & include) { + return type; + } + } + return neverType; + } + + 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 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 shouldDeferIndexType(type: Type, indexFlags = IndexFlags.None) { + return !!(type.flags & TypeFlags.InstantiableNonPrimitive || + isGenericTupleType(type) || + isGenericMappedType(type) && (!hasDistributiveNameType(type) || getMappedTypeNameTypeKind(type) === MappedTypeNameTypeKind.Remapping) || + type.flags & TypeFlags.Union && !(indexFlags & IndexFlags.NoReducibleCheck) && isGenericReducibleType(type) || + type.flags & TypeFlags.Intersection && maybeTypeOfKind(type, TypeFlags.Instantiable) && some((type as IntersectionType).types, isEmptyAnonymousObjectType)); + } + + function getIndexType(type: Type, indexFlags = IndexFlags.None): Type { + type = getReducedType(type); + return isNoInferType(type) ? getNoInferType(getIndexType((type as SubstitutionType).baseType, indexFlags)) : + shouldDeferIndexType(type, indexFlags) ? getIndexTypeForGenericType(type as InstantiableType | UnionOrIntersectionType, indexFlags) : + type.flags & TypeFlags.Union ? getIntersectionType(map((type as UnionType).types, t => getIndexType(t, indexFlags))) : + type.flags & TypeFlags.Intersection ? getUnionType(map((type as IntersectionType).types, t => getIndexType(t, indexFlags))) : + getObjectFlags(type) & ObjectFlags.Mapped ? getIndexTypeForMappedType(type as MappedType, indexFlags) : + type === wildcardType ? wildcardType : + type.flags & TypeFlags.Unknown ? neverType : + type.flags & (TypeFlags.Any | TypeFlags.Never) ? stringNumberSymbolType : + getLiteralTypeFromProperties(type, (indexFlags & IndexFlags.NoIndexSignatures ? TypeFlags.StringLiteral : TypeFlags.StringLike) | (indexFlags & IndexFlags.StringsOnly ? 0 : TypeFlags.NumberLike | TypeFlags.ESSymbolLike), indexFlags === IndexFlags.None); + } + + function getExtractStringType(type: Type) { + const extractTypeAlias = getGlobalExtractSymbol(); + return extractTypeAlias ? getTypeAliasInstantiation(extractTypeAlias, [type, stringType]) : stringType; + } + + function getIndexTypeOrString(type: Type): Type { + const indexType = getExtractStringType(getIndexType(type)); + return indexType.flags & TypeFlags.Never ? stringType : indexType; + } + + 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: + Debug.assertNever(node.operator); + } + } + 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 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); + if (!type) { + templateLiteralTypes.set(id, type = createTemplateLiteralType(newTexts, newTypes)); + } + return type; + + function addSpans(texts: readonly string[], types: readonly Type[]): boolean { + for (let i = 0; i < types.length; i++) { + const t = types[i]; + if (t.flags & (TypeFlags.Literal | TypeFlags.Null | TypeFlags.Undefined)) { + text += getTemplateStringForType(t) || ""; + text += texts[i + 1]; + } + 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 += texts[i + 1]; + } + else if (isGenericIndexType(t) || isPatternLiteralPlaceholderType(t)) { + newTypes.push(t); + newTexts.push(text); + text = texts[i + 1]; + } + else { + return false; + } + } + return true; + } + } + + 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 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]; + } + + 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 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 getPropertyNameFromIndex(indexType: Type, accessNode: PropertyName | ObjectBindingPattern | ArrayBindingPattern | 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 every(symbol.declarations, d => !isFunctionLike(d) || isDeprecatedDeclaration(d)); + } + 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); + + 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); + } + 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; + } + } + const propType = accessFlags & AccessFlags.Writing ? getWriteTypeOfSymbol(prop) : getTypeOfSymbol(prop); + return accessExpression && getAssignmentTargetKind(accessExpression) !== AssignmentKind.Definite ? getFlowTypeOfReference(accessExpression, propType) : + accessNode && isIndexedAccessTypeNode(accessNode) && containsMissingType(propType) ? getUnionType([propType, undefinedType]) : + propType; + } + if (everyType(objectType, isTupleType) && isNumericLiteralName(propName)) { + const index = +propName; + if (accessNode && everyType(objectType, t => !((t as TupleTypeReference).target.combinedFlags & ElementFlags.Variable)) && !(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)); + } + } + if (index >= 0) { + errorIfWritingToReadonlyIndex(getIndexInfoOfType(objectType, numberType)); + return getTupleElementTypeOutOfStartCount(objectType, index, accessFlags & AccessFlags.IncludeUndefined ? missingType : 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) { + if (accessFlags & AccessFlags.Writing) { + error(accessExpression, Diagnostics.Type_0_is_generic_and_can_only_be_indexed_for_reading, typeToString(originalObjectType)); + } + else { + error(accessExpression, Diagnostics.Type_0_cannot_be_used_to_index_type_1, typeToString(indexType), typeToString(originalObjectType)); + } + } + return undefined; + } + 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, missingType]) : 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, missingType]); + } + 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)); + } + else if (noImplicitAny && !(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 { + 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 { + 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 { + 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(getSourceFileOfNode(accessExpression), accessExpression, errorInfo)); + } + } + } + } + return undefined; + } + } + 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)); + } + 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)); + } + } + 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 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 { + if (type.flags & TypeFlags.Intersection) { + // Return true if the intersection consists of one or more placeholders and zero or + // more object type tags. + let seenPlaceholder = false; + for (const t of (type as IntersectionType).types) { + if (t.flags & (TypeFlags.Literal | TypeFlags.Nullable) || isPatternLiteralPlaceholderType(t)) { + seenPlaceholder = true; + } + else if (!(t.flags & TypeFlags.Object)) { + return false; + } + } + return seenPlaceholder; + } + return !!(type.flags & (TypeFlags.Any | TypeFlags.String | TypeFlags.Number | TypeFlags.BigInt)) || isPatternLiteralType(type); + } + + function isPatternLiteralType(type: Type) { + // A pattern literal type is a template literal or a string mapping type that contains only + // non-generic pattern literal placeholders. + return !!(type.flags & TypeFlags.TemplateLiteral) && every((type as TemplateLiteralType).types, isPatternLiteralPlaceholderType) || + !!(type.flags & TypeFlags.StringMapping) && isPatternLiteralPlaceholderType((type as StringMappingType).type); + } + + function isGenericStringLikeType(type: Type) { + return !!(type.flags & (TypeFlags.TemplateLiteral | TypeFlags.StringMapping)) && !isPatternLiteralType(type); + } + + function isGenericType(type: Type): boolean { + return !!getGenericObjectFlags(type); + } + + function isGenericObjectType(type: Type): boolean { + return !!(getGenericObjectFlags(type) & ObjectFlags.IsGenericObjectType); + } + + 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; + } + 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.flags & TypeFlags.InstantiableNonPrimitive || isGenericMappedType(type) || isGenericTupleType(type) ? ObjectFlags.IsGenericObjectType : 0) | + (type.flags & (TypeFlags.InstantiableNonPrimitive | TypeFlags.Index) || isGenericStringLikeType(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 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); + } + } + + // 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; + } + } + // 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] + + // 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)) { + if (getMappedTypeNameTypeKind(objectType) !== MappedTypeNameTypeKind.Remapping) { + return type[cache] = mapType(substituteIndexedMappedType(objectType, type.indexType), t => getSimplifiedType(t, writing)); + } + } + return type[cache] = type; + } + + function getSimplifiedConditionalType(type: ConditionalType, writing: boolean) { + const checkType = type.checkType; + const extendsType = type.extendsType; + const trueType = getTrueTypeFromConditionalType(type); + const falseType = getFalseTypeFromConditionalType(type); + // Simplifications for types of the form `T extends U ? T : never` and `T extends U ? never : T`. + if (falseType.flags & TypeFlags.Never && getActualTypeVariable(trueType) === getActualTypeVariable(checkType)) { + if (checkType.flags & TypeFlags.Any || isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(extendsType))) { // Always true + return getSimplifiedType(trueType, writing); + } + else if (isIntersectionEmpty(checkType, extendsType)) { // Always false + return neverType; + } + } + else if (trueType.flags & TypeFlags.Never && getActualTypeVariable(falseType) === getActualTypeVariable(checkType)) { + if (!(checkType.flags & TypeFlags.Any) && isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(extendsType))) { // Always true + return neverType; + } + else if (checkType.flags & TypeFlags.Any || isIntersectionEmpty(checkType, extendsType)) { // Always false + return getSimplifiedType(falseType, writing); + } + } + return type; + } + + /** + * Invokes union simplification logic to determine if an intersection is considered empty as a union constituent + */ + function isIntersectionEmpty(type1: Type, type2: Type) { + return !!(getUnionType([intersectTypes(type1, type2), neverType]).flags & TypeFlags.Never); + } + + // Given an indexed access on a mapped type of the form { [P in K]: E }[X], return an instantiation of E where P is + // replaced with X. Since this simplification doesn't account for mapped type modifiers, add 'undefined' to the + // resulting type if the mapped type includes a '?' modifier or if the modifiers type indicates that some properties + // are optional. If the modifiers type is generic, conservatively estimate optionality by recursively looking for + // mapped types that include '?' modifiers. + function substituteIndexedMappedType(objectType: MappedType, index: Type) { + const mapper = createTypeMapper([getTypeParameterFromMappedType(objectType)], [index]); + const templateMapper = combineTypeMappers(objectType.mapper, mapper); + const instantiatedTemplateType = instantiateType(getTemplateTypeFromMappedType(objectType.target as MappedType || objectType), templateMapper); + const isOptional = getMappedTypeOptionality(objectType) > 0 || (isGenericType(objectType) ? + getCombinedMappedTypeOptionality(getModifiersTypeFromMappedType(objectType)) > 0 : + couldAccessOptionalProperty(objectType, index)); + return addOptionality(instantiatedTemplateType, /*isProperty*/ true, isOptional); + } + + // Return true if an indexed access with the given object and index types could access an optional property. + function couldAccessOptionalProperty(objectType: Type, indexType: Type) { + const indexConstraint = getBaseConstraintOfType(indexType); + return !!indexConstraint && some(getPropertiesOfType(objectType), p => + !!(p.flags & SymbolFlags.Optional) && + isTypeAssignableTo(getLiteralTypeFromProperty(p, TypeFlags.StringOrNumberLiteralOrUnique), indexConstraint)); + } + + 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); + } + + 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 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; + } + objectType = getReducedType(objectType); + // 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, getTotalFixedElementCount(objectType.target)) : + isGenericObjectType(objectType) && !(isTupleType(objectType) && indexTypeLessThan(indexType, getTotalFixedElementCount(objectType.target))) || isGenericReducibleType(objectType)) + ) { + 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)); + } + + 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; + } + return accessFlags & AccessFlags.Writing + ? getIntersectionType(propTypes, IntersectionFlags.None, aliasSymbol, aliasTypeArguments) + : getUnionType(propTypes, UnionReduction.Literal, aliasSymbol, aliasTypeArguments); + } + return getPropertyTypeForIndexType(objectType, apparentObjectType, indexType, indexType, accessNode, accessFlags | AccessFlags.CacheSymbol | AccessFlags.ReportDeprecated); + } + + 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 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 getActualTypeVariable(type: Type): Type { + if (type.flags & TypeFlags.Substitution) { + return getActualTypeVariable((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 isSimpleTupleType(node: TypeNode): boolean { + return isTupleTypeNode(node) && length(node.elements) > 0 && + !some(node.elements, e => isOptionalTypeNode(e) || isRestTypeNode(e) || isNamedTupleMember(e) && !!(e.questionToken || e.dotDotDotToken)); + } + + function isDeferredType(type: Type, checkTuples: boolean) { + return isGenericType(type) || checkTuples && isTupleType(type) && some(getElementTypes(type), isGenericType); + } + + function getConditionalType(root: ConditionalRoot, mapper: TypeMapper | undefined, forConstraint: boolean, 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); + return errorType; + } + const checkType = instantiateType(getActualTypeVariable(root.checkType), mapper); + const extendsType = instantiateType(root.extendsType, mapper); + if (checkType === errorType || extendsType === errorType) { + return errorType; + } + if (checkType === wildcardType || extendsType === wildcardType) { + return wildcardType; + } + const checkTypeNode = skipTypeParentheses(root.node.checkType); + const extendsTypeNode = skipTypeParentheses(root.node.extendsType); + // When the check and extends types are simple tuple types of the same arity, we defer resolution of the + // conditional type when any tuple elements are generic. This is such that non-distributable conditional + // types can be written `[X] extends [Y] ? ...` and be deferred similarly to `X extends Y ? ...`. + const checkTuples = isSimpleTupleType(checkTypeNode) && isSimpleTupleType(extendsTypeNode) && + length((checkTypeNode as TupleTypeNode).elements) === length((extendsTypeNode as TupleTypeNode).elements); + const checkTypeDeferred = isDeferredType(checkType, checkTuples); + 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 context - 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: + // * combine `context.nonFixingMapper` with `mapper` so their constraints can be instantiated in the context of `mapper` (otherwise they'd only get inference context information) + // * incorporate all of the component mappers into the combined mapper for the true and false members + // This means we have two mappers that need applying: + // * The original `mapper` used to create this conditional + // * The mapper that maps the infer type parameter to its inference result (`context.mapper`) + const context = createInferenceContext(root.inferTypeParameters, /*signature*/ undefined, InferenceFlags.None); + if (mapper) { + context.nonFixingMapper = combineTypeMappers(context.nonFixingMapper, mapper); + } + if (!checkTypeDeferred) { + // 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, extendsType, InferencePriority.NoConstraints | InferencePriority.AlwaysStrict); + } + // 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(context.mapper, mapper) : context.mapper; + } + // Instantiate the extends type including inferences for 'infer T' type parameters + const inferredExtendsType = combinedMapper ? instantiateType(root.extendsType, combinedMapper) : extendsType; + // We attempt to resolve the conditional type only when the check and extends types are non-generic + if (!checkTypeDeferred && !isDeferredType(inferredExtendsType, checkTuples)) { + // 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 || !isTypeAssignableTo(getPermissiveInstantiation(checkType), getPermissiveInstantiation(inferredExtendsType)))) { + // Return union of trueType and falseType for 'any' since it matches anything. Furthermore, for a + // distributive conditional type applied to the constraint of a type variable, include trueType if + // there are possible values of the check type that are also possible values of the extends type. + // We use a reverse assignability check as it is less expensive than the comparable relationship + // and avoids false positives of a non-empty intersection check. + if (checkType.flags & TypeFlags.Any || forConstraint && !(inferredExtendsType.flags & TypeFlags.Never) && someType(getPermissiveInstantiation(inferredExtendsType), t => isTypeAssignableTo(t, getPermissiveInstantiation(checkType)))) { + (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; + } + } + // 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; + } + } + + function getTrueTypeFromConditionalType(type: ConditionalType) { + return type.resolvedTrueType || (type.resolvedTrueType = instantiateType(getTypeFromTypeNode(type.root.node.trueType), type.mapper)); + } + + function getFalseTypeFromConditionalType(type: ConditionalType) { + return type.resolvedFalseType || (type.resolvedFalseType = instantiateType(getTypeFromTypeNode(type.root.node.falseType), type.mapper)); + } + + function getInferredTrueTypeFromConditionalType(type: ConditionalType) { + return type.resolvedInferredTrueType || (type.resolvedInferredTrueType = type.combinedMapper ? instantiateType(getTypeFromTypeNode(type.root.node.trueType), type.combinedMapper) : getTrueTypeFromConditionalType(type)); + } + + 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 isDistributionDependent(root: ConditionalRoot) { + return root.isDistributive && ( + isTypeParameterPossiblyReferenced(root.checkType as TypeParameter, root.node.trueType) || + isTypeParameterPossiblyReferenced(root.checkType as TypeParameter, root.node.falseType) + ); + } + + 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, /*forConstraint*/ false); + if (outerTypeParameters) { + root.instantiations = new Map(); + root.instantiations.set(getTypeListId(outerTypeParameters), links.resolvedType); + } + } + return links.resolvedType; + } + + function getTypeFromInferTypeNode(node: InferTypeNode): Type { + const links = getNodeLinks(node); + if (!links.resolvedType) { + links.resolvedType = getDeclaredTypeOfTypeParameter(getSymbolOfDeclaration(node.typeParameter)); + } + return links.resolvedType; + } + + function getIdentifierChain(node: EntityName): Identifier[] { + if (isIdentifier(node)) { + return [node]; + } + else { + return append(getIdentifierChain(node.left), node.right); + } + } + + function getTypeFromImportTypeNode(node: ImportTypeNode): Type { + const links = getNodeLinks(node); + if (!links.resolvedType) { + 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); + } + 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 links.resolvedType; + } + + function resolveImportSymbolType(node: ImportTypeNode, links: NodeLinks, symbol: Symbol, meaning: SymbolFlags) { + const resolvedSymbol = resolveSymbol(symbol); + links.resolvedSymbol = resolvedSymbol; + if (meaning === SymbolFlags.Value) { + return getInstantiationExpressionType(getTypeOfSymbol(symbol), node); // 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: TypeLiteralNode | FunctionOrConstructorTypeNode | JSDocTypeLiteral | JSDocFunctionType | JSDocSignature): 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; + } + } + return links.resolvedType; + } + + 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) ? getSymbolOfDeclaration(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 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 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.links.type = isSetonlyAccessor ? undefinedType : addOptionality(getTypeOfSymbol(prop), /*isProperty*/ true); + result.declarations = prop.declarations; + result.links.nameType = getSymbolLinks(prop).nameType; + result.links.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; + } + } + + /** + * 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]); + } + + const members = createSymbolTable(); + const skippedPrivateMembers = new Set<__String>(); + const indexInfos = left === emptyObjectType ? getIndexInfosOfType(right) : getUnionIndexInfos([left, 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)); + } + } + + 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); + // Optimization: avoid calculating the union type if spreading into the exact same type. + // This is common, e.g. spreading one options bag into another where the bags have the + // same type, or have properties which overlap. If the unions are large, it may turn out + // to be expensive to perform subtype reduction. + const leftType = getTypeOfSymbol(leftProp); + const leftTypeWithoutUndefined = removeMissingOrUndefinedType(leftType); + const rightTypeWithoutUndefined = removeMissingOrUndefinedType(rightType); + result.links.type = leftTypeWithoutUndefined === rightTypeWithoutUndefined ? leftType : getUnionType([leftType, rightTypeWithoutUndefined], UnionReduction.Subtype); + result.links.leftSpread = leftProp; + result.links.rightSpread = rightProp; + result.declarations = declarations; + result.links.nameType = getSymbolLinks(leftProp).nameType; + members.set(leftProp.escapedName, result); + } + } + else { + members.set(leftProp.escapedName, getSpreadSymbol(leftProp, readonly)); + } + } + + const spread = createAnonymousType(symbol, members, emptyArray, emptyArray, sameMap(indexInfos, info => getIndexInfoWithReadonly(info, readonly))); + spread.objectFlags |= ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral | ObjectFlags.ContainsSpread | objectFlags; + return spread; + } + + /** 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 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.links.type = isSetonlyAccessor ? undefinedType : getTypeOfSymbol(prop); + result.declarations = prop.declarations; + result.links.nameType = getSymbolLinks(prop).nameType; + result.links.syntheticOrigin = prop; + return result; + } + + function getIndexInfoWithReadonly(info: IndexInfo, readonly: boolean) { + return info.isReadonly !== readonly ? createIndexInfo(info.keyType, info.type, readonly, info.declaration) : info; + } + + 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 getFreshTypeOfLiteralType(type: Type): Type { + if (type.flags & TypeFlags.Freshable) { + if (!(type as FreshableType).freshType) { + const freshType = createLiteralType(type.flags, (type as LiteralType).value, (type as LiteralType).symbol, type as LiteralType); + freshType.freshType = freshType; + (type as FreshableType).freshType = freshType; + } + return (type as FreshableType).freshType; + } + return type; + } + + function getRegularTypeOfLiteralType(type: Type): Type { + return type.flags & TypeFlags.Freshable ? (type as FreshableType).regularType : + type.flags & TypeFlags.Union ? ((type as UnionType).regularType || ((type as UnionType).regularType = mapType(type, getRegularTypeOfLiteralType) as UnionType)) : + type; + } + + function isFreshLiteralType(type: Type) { + return !!(type.flags & TypeFlags.Freshable) && (type as LiteralType).freshType === type; + } + + function getStringLiteralType(value: string): StringLiteralType { + let type; + return stringLiteralTypes.get(value) || + (stringLiteralTypes.set(value, type = createLiteralType(TypeFlags.StringLiteral, value) as StringLiteralType), type); + } + + function getNumberLiteralType(value: number): NumberLiteralType { + let type; + return numberLiteralTypes.get(value) || + (numberLiteralTypes.set(value, type = createLiteralType(TypeFlags.NumberLiteral, value) as NumberLiteralType), type); + } + + 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 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 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; + } + + 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 getESSymbolLikeTypeForNode(node: Node) { + if (isInJSFile(node) && isJSDocTypeExpression(node)) { + const host = getJSDocHost(node); + if (host) { + node = getSingleVariableOfVariableStatement(host) || host; + } + } + 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 getThisType(node: Node): Type { + const container = getThisContainer(node, /*includeArrowFunctions*/ false, /*includeClassComputedPropertyName*/ 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(getSymbolOfDeclaration(parent as ClassLikeDeclaration | InterfaceDeclaration)).thisType!; + } + } + + // inside x.prototype = { ... } + if (parent && isObjectLiteralExpression(parent) && isBinaryExpression(parent.parent) && getAssignmentDeclarationKind(parent.parent) === AssignmentDeclarationKind.Prototype) { + return getDeclaredTypeOfClassOrInterface(getSymbolOfNode(parent.parent.left)!.parent!).thisType!; + } + // /** @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!; + } + // inside constructor function C() { ... } + if (isJSConstructor(container) && isNodeDescendantOf(node, container.body)) { + return getDeclaredTypeOfClassOrInterface(getSymbolOfDeclaration(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); + } + return links.resolvedType; + } + + function getTypeFromRestTypeNode(node: RestTypeNode | NamedTupleMember) { + return getTypeFromTypeNode(getArrayElementTypeNode(node.type) || node.type); + } + + 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 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 getTypeFromTypeNode(node: TypeNode): Type { + return getConditionalFlowTypeOfType(getTypeFromTypeNodeWorker(node), node); + } + + 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 as TypeLiteralNode | FunctionOrConstructorTypeNode | JSDocTypeLiteral | JSDocFunctionType | JSDocSignature); + 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 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 items; + } + + 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 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 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 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 the type of the symbol is already resolved, and if that type could not possibly + // be affected by instantiation, simply return the symbol itself. + if (links.type && !couldContainTypeVariables(links.type)) { + if (!(symbol.flags & SymbolFlags.SetAccessor)) { + return symbol; + } + // If we're a setter, check writeType. + if (links.writeType && !couldContainTypeVariables(links.writeType)) { + 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.links.target = symbol; + result.links.mapper = mapper; + if (symbol.valueDeclaration) { + result.valueDeclaration = symbol.valueDeclaration; + } + if (links.nameType) { + result.links.nameType = links.nameType; + } + return result; + } + + 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 = type.objectFlags & ObjectFlags.SingleSignatureType ? (type as SingleSignatureType).outerTypeParameters : 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 = (type.objectFlags & ObjectFlags.SingleSignatureType ? "S" : "") + 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) { + if (type.objectFlags & ObjectFlags.SingleSignatureType) { + result = instantiateAnonymousType(type, mapper); + target.instantiations.set(id, result); + return 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); // Set cached result early in case we recursively invoke instantiation while eagerly computing type variable visibility below + const resultObjectFlags = getObjectFlags(result); + if (result.flags & TypeFlags.ObjectFlagsType && !(resultObjectFlags & ObjectFlags.CouldContainTypeVariablesComputed)) { + const resultCouldContainTypeVariables = some(typeArguments, couldContainTypeVariables); // one of the input type arguments might be or contain the result + if (!(getObjectFlags(result) & ObjectFlags.CouldContainTypeVariablesComputed)) { + // if `result` is one of the object types we tried to make (it may not be, due to how `instantiateMappedType` works), we can carry forward the type variable containment check from the input type arguments + if (resultObjectFlags & (ObjectFlags.Mapped | ObjectFlags.Anonymous | ObjectFlags.Reference)) { + (result as ObjectFlagsType).objectFlags |= ObjectFlags.CouldContainTypeVariablesComputed | (resultCouldContainTypeVariables ? ObjectFlags.CouldContainTypeVariables : 0); + } + // If none of the type arguments for the outer type parameters contain type variables, it follows + // that the instantiated type doesn't reference type variables. + // Intrinsics have `CouldContainTypeVariablesComputed` pre-set, so this should only cover unions and intersections resulting from `instantiateMappedType` + else { + (result as ObjectFlagsType).objectFlags |= !resultCouldContainTypeVariables ? ObjectFlags.CouldContainTypeVariablesComputed : 0; + } + } + } + } + return result; + } + return type; + } + + 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; + } + } + 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); + if (!isThisIdentifier(firstIdentifier)) { // Don't attempt to analyze typeof this.xxx + const firstIdentifierSymbol = getResolvedSymbol(firstIdentifier); + const tpDeclaration = tp.symbol.declarations![0]; // There is exactly one declaration, otherwise `containsReference` is not called + const tpScope = tpDeclaration.kind === SyntaxKind.TypeParameter ? tpDeclaration.parent : // Type parameter is a regular type parameter, e.g. foo + tp.isThisType ? tpDeclaration : // Type parameter is the this type, and its declaration is the class declaration. + undefined; // Type parameter's declaration was unrecognized, e.g. comes from JSDoc annotation. + if (firstIdentifierSymbol.declarations && tpScope) { + 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 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 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. + // * If T is an intersection of array or tuple types we map to an intersection of transformed array or tuple types. + // * 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), instantiateConstituent, aliasSymbol, aliasTypeArguments); + } + } + // 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 instantiateConstituent(t: Type): Type { + 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 (isTupleType(t)) { + return instantiateMappedTupleType(t, type, typeVariable!, mapper); + } + if (isArrayOrTupleOrIntersection(t)) { + return getIntersectionType(map((t as IntersectionType).types, instantiateConstituent)); + } + } + return instantiateAnonymousType(type, prependTypeMapping(typeVariable!, t, mapper)); + } + return t; + } + } + + function getModifiedReadonlyState(state: boolean, modifiers: MappedTypeModifiers) { + return modifiers & MappedTypeModifiers.IncludeReadonly ? true : modifiers & MappedTypeModifiers.ExcludeReadonly ? false : state; + } + + function instantiateMappedTupleType(tupleType: TupleTypeReference, mappedType: MappedType, typeVariable: TypeVariable, mapper: TypeMapper) { + // We apply the mapped type's template type to each of the fixed part elements. For variadic elements, we + // apply the mapped type itself to the variadic element type. For other elements in the variable part of the + // tuple, we surround the element type with an array type and apply the mapped type to that. This ensures + // that we get sequential property key types for the fixed part of the tuple, and property key type number + // for the remaining elements. For example + // + // type Keys = { [K in keyof T]: K }; + // type Foo = Keys<[string, string, ...T, string]>; // ["0", "1", ...Keys, number] + // + const elementFlags = tupleType.target.elementFlags; + const fixedLength = tupleType.target.fixedLength; + const fixedMapper = fixedLength ? prependTypeMapping(typeVariable, tupleType, mapper) : mapper; + const newElementTypes = map(getElementTypes(tupleType), (type, i) => { + const flags = elementFlags[i]; + return i < fixedLength ? instantiateMappedTypeTemplate(mappedType, getStringLiteralType("" + i), !!(flags & ElementFlags.Optional), fixedMapper) : + flags & ElementFlags.Variadic ? instantiateType(mappedType, prependTypeMapping(typeVariable, type, mapper)) : + getElementTypeOfArrayType(instantiateType(mappedType, prependTypeMapping(typeVariable, createArrayType(type), mapper))) ?? unknownType; + }); + const modifiers = getMappedTypeModifiers(mappedType); + const newElementFlags = 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, getMappedTypeModifiers(mappedType)); + return contains(newElementTypes, errorType) ? errorType : + createTupleType(newElementTypes, newElementFlags, newReadonly, tupleType.target.labeledElementDeclarations); + } + + 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 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 { + Debug.assert(type.symbol, "anonymous type must have symbol to be instantiated"); + const result = createObjectType(type.objectFlags & ~(ObjectFlags.CouldContainTypeVariablesComputed | ObjectFlags.CouldContainTypeVariables) | 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; + } + if (type.objectFlags & ObjectFlags.SingleSignatureType) { + (result as SingleSignatureType).outerTypeParameters = (type as SingleSignatureType).outerTypeParameters; + } + 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, forConstraint: boolean, 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 = (forConstraint ? "C" : "") + 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 ? getReducedType(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(distributionType, t => getConditionalType(root, prependTypeMapping(checkType, t, newMapper), forConstraint), aliasSymbol, aliasTypeArguments) : + getConditionalType(root, newMapper, forConstraint, aliasSymbol, aliasTypeArguments); + root.instantiations!.set(id, result); + } + return result; + } + 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; + } + + 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); + } + 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, IntersectionFlags.None, 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), /*forConstraint*/ false, aliasSymbol, aliasTypeArguments); + } + if (flags & TypeFlags.Substitution) { + const newBaseType = instantiateType((type as SubstitutionType).baseType, mapper); + if (isNoInferType(type)) { + return getNoInferType(newBaseType); + } + 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 (newConstraint.flags & TypeFlags.AnyOrUnknown || isTypeAssignableTo(getRestrictiveInstantiation(newBaseType), getRestrictiveInstantiation(newConstraint))) { + return newBaseType; + } + return newBaseType.flags & TypeFlags.TypeVariable ? getSubstitutionType(newBaseType, newConstraint) : getIntersectionType([newConstraint, newBaseType]); + } + 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)) { + 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 + } + + function getPermissiveInstantiation(type: Type) { + return type.flags & (TypeFlags.Primitive | TypeFlags.AnyOrUnknown | TypeFlags.Never) ? type : + type.permissiveInstantiation || (type.permissiveInstantiation = instantiateType(type, permissiveMapper)); + } + + function getRestrictiveInstantiation(type: Type) { + if (type.flags & (TypeFlags.Primitive | TypeFlags.AnyOrUnknown | TypeFlags.Never)) { + return type; + } + 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 instantiateIndexInfo(info: IndexInfo, mapper: TypeMapper) { + return createIndexInfo(info.keyType, instantiateType(info.type, mapper), info.isReadonly, info.declaration); + } + + // 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) { + if (node.typeParameters || getEffectiveReturnTypeNode(node) || !node.body) { + return false; + } + if (node.body.kind !== SyntaxKind.Block) { + return isContextSensitive(node.body); + } + return !!forEachReturnStatement(node.body as Block, statement => !!statement.expression && isContextSensitive(statement.expression)); + } + + 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; + } + } + else if (type.flags & TypeFlags.Intersection) { + return getIntersectionType(map((type as IntersectionType).types, getTypeWithoutSignatures)); + } + return type; + } + + // TYPE CHECKING + + 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 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 isTypeSubtypeOf(source: Type, target: Type): boolean { + return isTypeRelatedTo(source, target, subtypeRelation); + } + + function isTypeStrictSubtypeOf(source: Type, target: Type): boolean { + return isTypeRelatedTo(source, target, strictSubtypeRelation); + } + + 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 an intersection type and some constituent of S is derived from T, or + // S is a type variable with a base constraint that is derived from T, or + // T is {} and S is an object-like type (ensuring {} is less derived than Object), or + // 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.Intersection ? some((source as IntersectionType).types, t => isTypeDerivedFrom(t, target)) : + source.flags & TypeFlags.InstantiableNonPrimitive ? isTypeDerivedFrom(getBaseConstraintOfType(source) || unknownType, target) : + isEmptyAnonymousObjectType(target) ? !!(source.flags & (TypeFlags.Object | TypeFlags.NonPrimitive)) : + target === globalObjectType ? !!(source.flags & (TypeFlags.Object | TypeFlags.NonPrimitive)) && !isEmptyAnonymousObjectType(source) : + 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); + } + + 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); + } + + /** + * 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: Map, + 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 elaborateError( + node: Expression | undefined, + source: Type, + target: Type, + relation: Map, + 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.AsExpression: + if (!isConstAssertion(node)) { + break; + } + // fallthrough + case SyntaxKind.JsxExpression: + case SyntaxKind.ParenthesizedExpression: + return elaborateError((node as AsExpression | 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 elaborateDidYouMeanToCallOrConstruct( + node: Expression, + source: Type, + target: Type, + relation: Map, + 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; + } + } + return false; + } + + function elaborateArrowFunction( + node: ArrowFunction, + source: Type, + target: Type, + relation: Map, + 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, 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, /*headMessage*/ 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 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); + } + } + } + + function checkExpressionForMutableLocationWithContextualType(next: Expression, sourcePropType: Type) { + pushContextualType(next, sourcePropType, /*isCache*/ false); + const result = checkExpressionForMutableLocation(next, CheckMode.Contextual); + popContextualType(); + return result; + } + + 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: Map, + 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 (const value of iterator) { + const { errorNode: prop, innerExpression: next, nameType, errorMessage } = 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), + ), + ); + } + } + } + } + } + } + return reportedError; + } + + /** + * Assumes `target` type is assignable to the `Iterable` type, if `Iterable` is defined, + * or that it's an array or tuple-like type, if `Iterable` is not defined. + */ + function elaborateIterableOrArrayLikeTargetElementwise( + iterator: ElaborationIterator, + source: Type, + target: Type, + relation: Map, + containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, + errorOutputContainer: { errors?: Diagnostic[]; skipLogging?: boolean; } | undefined, + ) { + const tupleOrArrayLikeTargetParts = filterType(target, isArrayOrTupleLikeType); + const nonTupleOrArrayLikeTargetParts = filterType(target, t => !isArrayOrTupleLikeType(t)); + // If `nonTupleOrArrayLikeTargetParts` is not `never`, then that should mean `Iterable` is defined. + const iterationType = nonTupleOrArrayLikeTargetParts !== neverType + ? getIterationTypeOfIterable(IterationUse.ForOf, IterationTypeKind.Yield, nonTupleOrArrayLikeTargetParts, /*errorNode*/ undefined) + : undefined; + + let reportedError = false; + for (let status = iterator.next(); !status.done; status = iterator.next()) { + const { errorNode: prop, innerExpression: next, nameType, errorMessage } = status.value; + let targetPropType = iterationType; + const targetIndexedPropType = tupleOrArrayLikeTargetParts !== neverType ? getBestMatchIndexedAccessTypeOrUndefined(source, tupleOrArrayLikeTargetParts, nameType) : undefined; + if (targetIndexedPropType && !(targetIndexedPropType.flags & TypeFlags.IndexedAccess)) { // Don't elaborate on indexes on generic variables + targetPropType = iterationType ? getUnionType([iterationType, targetIndexedPropType]) : targetIndexedPropType; + } + if (!targetPropType) continue; + 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(tupleOrArrayLikeTargetParts, 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); + } + } + } + } + } + return reportedError; + } + + function* generateJsxAttributes(node: JsxAttributes): ElaborationIterator { + if (!length(node.properties)) return; + for (const prop of node.properties) { + if (isJsxSpreadAttribute(prop) || isHyphenatedJsxName(getTextOfJsxAttributeName(prop.name))) continue; + yield { errorNode: prop.name, innerExpression: prop.initializer, nameType: getStringLiteralType(getTextOfJsxAttributeName(prop.name)) }; + } + } + + 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++; + } + } + } + + 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: Map, + 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; + let arrayLikeTargetParts: Type; + let nonArrayLikeTargetParts: Type; + const iterableType = getGlobalIterableType(/*reportErrors*/ false); + if (iterableType !== emptyGenericType) { + const anyIterable = createIterableType(anyType); + arrayLikeTargetParts = filterType(childrenTargetType, t => isTypeAssignableTo(t, anyIterable)); + nonArrayLikeTargetParts = filterType(childrenTargetType, t => !isTypeAssignableTo(t, anyIterable)); + } + else { + arrayLikeTargetParts = filterType(childrenTargetType, isArrayOrTupleLikeType); + nonArrayLikeTargetParts = filterType(childrenTargetType, t => !isArrayOrTupleLikeType(t)); + } + if (moreThanOneRealChildren) { + if (arrayLikeTargetParts !== neverType) { + const realSource = createTupleType(checkJsxChildren(containingElement, CheckMode.Normal)); + const children = generateJsxChildren(containingElement, getInvalidTextualChildDiagnostic); + result = elaborateIterableOrArrayLikeTargetElementwise(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); + } + } + } + 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 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(diagnostic, tagNameText, childrenPropName, typeToString(childrenTargetType)) }; + } + return invalidTextDiagnostic; + } + } + + 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); + const checkNode = getEffectiveCheckNode(elem); + yield { errorNode: checkNode, innerExpression: checkNode, nameType }; + } + } + + function elaborateArrayLiteral( + node: ArrayLiteralExpression, + source: Type, + target: Type, + relation: Map, + 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 + pushContextualType(node, target, /*isCache*/ false); + const tupleizedType = checkArrayLiteral(node, CheckMode.Contextual, /*forceTuple*/ true); + popContextualType(); + if (isTupleLikeType(tupleizedType)) { + return elaborateElementwise(generateLimitedTupleElements(node, target), tupleizedType, target, relation, containingMessageChain, errorOutputContainer); + } + return false; + } + + function* generateObjectLiteralElements(node: ObjectLiteralExpression): ElaborationIterator { + if (!length(node.properties)) return; + for (const prop of node.properties) { + if (isSpreadAssignment(prop)) continue; + const type = getLiteralTypeFromProperty(getSymbolOfDeclaration(prop), TypeFlags.StringOrNumberLiteralOrUnique); + if (!type || (type.flags & TypeFlags.Never)) { + continue; + } + 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); + } + } + } + + function elaborateObjectLiteral( + node: ObjectLiteralExpression, + source: Type, + target: Type, + relation: Map, + 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); + } + + /** + * 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 isSignatureAssignableTo(source: Signature, target: Signature, ignoreReturnTypes: boolean): boolean { + return compareSignaturesRelated(source, target, ignoreReturnTypes ? SignatureCheckMode.IgnoreReturnTypes : SignatureCheckMode.None, /*reportErrors*/ false, /*errorReporter*/ undefined, /*incompatibleErrorReporter*/ undefined, compareTypesAssignable, /*reportUnreliableMarkers*/ undefined) !== Ternary.False; + } + + type ErrorReporter = (message: DiagnosticMessage, ...args: DiagnosticArguments) => void; + + /** + * Returns true if `s` is `(...args: A) => R` where `A` is `any`, `any[]`, `never`, or `never[]`, and `R` is `any` or `unknown`. + */ + function isTopSignature(s: Signature) { + if (!s.typeParameters && (!s.thisParameter || isTypeAny(getTypeOfParameter(s.thisParameter))) && s.parameters.length === 1 && signatureHasRestParameter(s)) { + const paramType = getTypeOfParameter(s.parameters[0]); + const restType = isArrayType(paramType) ? getTypeArguments(paramType)[0] : paramType; + return !!(restType.flags & (TypeFlags.Any | TypeFlags.Never) && getReturnTypeOfSignature(s).flags & TypeFlags.AnyOrUnknown); + } + return 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 (!(checkMode & SignatureCheckMode.StrictTopSignature && isTopSignature(source)) && isTopSignature(target)) { + return Ternary.True; + } + if (checkMode & SignatureCheckMode.StrictTopSignature && isTopSignature(source) && !isTopSignature(target)) { + return Ternary.False; + } + + const targetCount = getParameterCount(target); + const sourceHasMoreParameters = !hasEffectiveRestParameter(target) && + (checkMode & SignatureCheckMode.StrictArity ? hasEffectiveRestParameter(source) || getParameterCount(source) > targetCount : getMinArgumentCount(source) > targetCount); + if (sourceHasMoreParameters) { + if (reportErrors && !(checkMode & SignatureCheckMode.StrictArity)) { + // the second condition should be redundant, because there is no error reporting when comparing signatures by strict arity + // since it is only done for subtype reduction + errorReporter!(Diagnostics.Target_signature_provides_too_few_arguments_Expected_0_or_more_but_got_1, getMinArgumentCount(source), targetCount); + } + 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); + } + return Ternary.False; + } + result &= related; + } + } + + 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 ? getRestOrAnyTypeAtPosition(source, i) : tryGetTypeAtPosition(source, i); + const targetType = i === restIndex ? getRestOrAnyTypeAtPosition(target, i) : tryGetTypeAtPosition(target, i); + if (sourceType && targetType && (sourceType !== targetType || checkMode & SignatureCheckMode.StrictArity)) { + // 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 || isInstantiatedGenericParameter(source, i) ? undefined : getSingleCallSignature(getNonNullableType(sourceType)); + const targetSig = checkMode & SignatureCheckMode.Callback || isInstantiatedGenericParameter(target, i) ? 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; + } + 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) || isThisTypePredicate(targetTypePredicate)) { + if (reportErrors) { + errorReporter!(Diagnostics.Signature_0_must_be_a_type_predicate, signatureToString(source)); + } + return 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); + } + } + } + + 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 (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; + } + } + + 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 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); + } + + 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 !!((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 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 sourceProperty of getPropertiesOfType(getTypeOfSymbol(sourceSymbol))) { + if (sourceProperty.flags & SymbolFlags.EnumMember) { + const targetProperty = getPropertyOfType(targetEnumType, sourceProperty.escapedName); + if (!targetProperty || !(targetProperty.flags & SymbolFlags.EnumMember)) { + if (errorReporter) { + errorReporter(Diagnostics.Property_0_is_missing_in_type_1, symbolName(sourceProperty), typeToString(getDeclaredTypeOfSymbol(targetSymbol), /*enclosingDeclaration*/ undefined, TypeFormatFlags.UseFullyQualifiedType)); + enumRelation.set(id, RelationComparisonResult.Failed | RelationComparisonResult.Reported); + } + else { + enumRelation.set(id, RelationComparisonResult.Failed); + } + return false; + } + const sourceValue = getEnumMemberValue(getDeclarationOfKind(sourceProperty, SyntaxKind.EnumMember)!).value; + const targetValue = getEnumMemberValue(getDeclarationOfKind(targetProperty, SyntaxKind.EnumMember)!).value; + if (sourceValue !== targetValue) { + const sourceIsString = typeof sourceValue === "string"; + const targetIsString = typeof targetValue === "string"; + + // If we have 2 enums with *known* values that differ, they are incompatible. + if (sourceValue !== undefined && targetValue !== undefined) { + if (!errorReporter) { + enumRelation.set(id, RelationComparisonResult.Failed); + } + else { + const escapedSource = sourceIsString ? `"${escapeString(sourceValue)}"` : sourceValue; + const escapedTarget = targetIsString ? `"${escapeString(targetValue)}"` : targetValue; + errorReporter(Diagnostics.Each_declaration_of_0_1_differs_in_its_value_where_2_was_expected_but_3_was_given, symbolName(targetSymbol), symbolName(targetProperty), escapedTarget, escapedSource); + enumRelation.set(id, RelationComparisonResult.Failed | RelationComparisonResult.Reported); + } + return false; + } + + // At this point we know that at least one of the values is 'undefined'. + // This may mean that we have an opaque member from an ambient enum declaration, + // or that we were not able to calculate it (which is basically an error). + // + // Either way, we can assume that it's numeric. + // If the other is a string, we have a mismatch in types. + if (sourceIsString || targetIsString) { + if (!errorReporter) { + enumRelation.set(id, RelationComparisonResult.Failed); + } + else { + const knownStringValue = sourceValue ?? targetValue; + Debug.assert(typeof knownStringValue === "string"); + const escapedValue = `"${escapeString(knownStringValue)}"`; + errorReporter(Diagnostics.One_value_of_0_1_is_the_string_2_and_the_other_is_assumed_to_be_an_unknown_numeric_value, symbolName(targetSymbol), symbolName(targetProperty), escapedValue); + enumRelation.set(id, RelationComparisonResult.Failed | RelationComparisonResult.Reported); + } + return false; + } + } + } + } + enumRelation.set(id, RelationComparisonResult.Succeeded); + return true; + } + + function isSimpleTypeRelatedTo(source: Type, target: Type, relation: Map, errorReporter?: ErrorReporter) { + const s = source.flags; + const t = target.flags; + if (t & TypeFlags.Any || s & TypeFlags.Never || source === wildcardType) return true; + if (t & TypeFlags.Unknown && !(relation === strictSubtypeRelation && s & TypeFlags.Any)) 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; + } + // 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 is assignable to any computed numeric enum type or any numeric enum literal type, and + // a numeric literal type is assignable any computed numeric enum type or any numeric enum literal type + // with a matching value. These rules exist such that enums can be used for bit-flag purposes. + if (s & TypeFlags.Number && (t & TypeFlags.Enum || t & TypeFlags.NumberLiteral && t & TypeFlags.EnumLiteral)) return true; + if ( + s & TypeFlags.NumberLiteral && !(s & TypeFlags.EnumLiteral) && (t & TypeFlags.Enum || + t & TypeFlags.NumberLiteral && t & TypeFlags.EnumLiteral && + (source as NumberLiteralType).value === (target as NumberLiteralType).value) + ) 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: Map) { + 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; + } + } + 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); + } + + function getNormalizedType(type: Type, writing: boolean): Type { + while (true) { + const t = isFreshLiteralType(type) ? (type as FreshableType).regularType : + isGenericTupleType(type) ? getNormalizedTupleType(type, writing) : + 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; + } + } + + function getNormalizedUnionOrIntersectionType(type: UnionOrIntersectionType, writing: boolean) { + const reduced = getReducedType(type); + if (reduced !== type) { + return reduced; + } + if (type.flags & TypeFlags.Intersection && shouldNormalizeIntersection(type as IntersectionType)) { + // Normalization handles cases like + // Partial[K] & ({} | null) ==> + // Partial[K] & {} | Partial[K} & null ==> + // (T[K] | undefined) & {} | (T[K] | undefined) & null ==> + // T[K] & {} | undefined & {} | T[K] & null | undefined & null ==> + // T[K] & {} | T[K] & null + const normalizedTypes = sameMap(type.types, t => getNormalizedType(t, writing)); + if (normalizedTypes !== type.types) { + return getIntersectionType(normalizedTypes); + } + } + return type; + } + + function shouldNormalizeIntersection(type: IntersectionType) { + let hasInstantiable = false; + let hasNullableOrEmpty = false; + for (const t of type.types) { + hasInstantiable ||= !!(t.flags & TypeFlags.Instantiable); + hasNullableOrEmpty ||= !!(t.flags & TypeFlags.Nullable) || isEmptyAnonymousObjectType(t); + if (hasInstantiable && hasNullableOrEmpty) return true; + } + return false; + } + + function getNormalizedTupleType(type: TupleTypeReference, writing: boolean): Type { + const elements = getElementTypes(type); + const normalizedElements = sameMap(elements, t => t.flags & TypeFlags.Simplifiable ? getSimplifiedType(t, writing) : t); + return elements !== normalizedElements ? createNormalizedTupleType(type.target, normalizedElements) : type; + } + + /** + * 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: Map, + 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 maybeKeysSet: Set; + 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 skipParentCounter = 0; // How many errors should be skipped 'above' in the elaboration pyramid + let lastSkippedInfo: [Type, Type] | undefined; + let incompatibleStack: DiagnosticAndArguments[] | undefined; + // In Node.js, the maximum number of elements in a map is 2^24. We limit the number of entries an invocation + // of checkTypeRelatedTo can add to a relation to 1/8th of its remaining capacity. + let relationCount = (16_000_000 - relation.size) >> 3; + + 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) { + // Record this relation as having failed such that we don't attempt the overflowing operation again. + const id = getRelationKey(source, target, /*intersectionState*/ IntersectionState.None, relation, /*ignoreConstraints*/ false); + relation.set(id, RelationComparisonResult.Reported | RelationComparisonResult.Failed); + tracing?.instant(tracing.Phase.CheckTypes, "checkTypeRelatedTo_DepthLimit", { sourceId: source.id, targetId: target.id, depth: sourceDepth, targetDepth }); + const message = relationCount <= 0 ? + Diagnostics.Excessive_complexity_comparing_types_0_and_1 : + Diagnostics.Excessive_stack_depth_comparing_types_0_and_1; + const diag = error(errorNode || currentNode, message, 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(getSourceFileOfNode(errorNode!), errorNode!, errorInfo, relatedInformation); + if (relatedInfo) { + addRelatedInfo(diag, ...relatedInfo); + } + 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; + + function resetErrorInfo(saved: ReturnType) { + errorInfo = saved.errorInfo; + lastSkippedInfo = saved.lastSkippedInfo; + incompatibleStack = saved.incompatibleStack; + overrideNextErrorInfo = saved.overrideNextErrorInfo; + skipParentCounter = saved.skipParentCounter; + relatedInfo = saved.relatedInfo; + } + + function captureErrorCalculationState() { + return { + errorInfo, + lastSkippedInfo, + incompatibleStack: incompatibleStack?.slice(), + overrideNextErrorInfo, + skipParentCounter, + relatedInfo: relatedInfo?.slice() as [DiagnosticRelatedInformation, ...DiagnosticRelatedInformation[]] | undefined, + }; + } + + function reportIncompatibleError(message: DiagnosticMessage, ...args: DiagnosticArguments) { + overrideNextErrorInfo++; // Suppress the next relation error + lastSkippedInfo = undefined; // Reset skipped info cache + (incompatibleStack ||= []).push([message, ...args]); + } + + 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(/*message*/ undefined, ...info); + } + 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: DiagnosticAndArguments[] = []; + 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; + } + 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]]); + } + 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})`; + } + 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(/*message*/ undefined, ...info); + } + } + + function reportError(message: DiagnosticMessage, ...args: DiagnosticArguments): void { + Debug.assert(!!errorNode); + if (incompatibleStack) reportIncompatibleStack(); + if (message.elidedInCompatabilityPyramid) return; + if (skipParentCounter === 0) { + errorInfo = chainDiagnosticMessages(errorInfo, message, ...args); + } + else { + skipParentCounter--; + } + } + + function reportParentSkippedError(message: DiagnosticMessage, ...args: DiagnosticArguments): void { + reportError(message, ...args); + skipParentCounter++; + } + + 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; + + if (isLiteralType(source) && !typeCouldHaveTopLevelSingletonTypes(target)) { + generalizedSource = getBaseTypeOfLiteralType(source); + Debug.assert(!isTypeAssignableTo(generalizedSource, target), "generalized source shouldn't be assignable"); + generalizedSourceType = getTypeNameForErrorDisplay(generalizedSource); + } + + // If `target` is of indexed access type (And `source` it is not), we use the object type of `target` for better error reporting + const targetFlags = target.flags & TypeFlags.IndexedAccess && !(source.flags & TypeFlags.IndexedAccess) ? + (target as IndexedAccessType).objectType.flags : + target.flags; + + if (targetFlags & 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, + ); + } + } + + 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; + } + } + 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); + } + + 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); + } + } + + /** + * 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 { + /** + * 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 (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 true; + } + + 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 { + if (originalSource === originalTarget) return Ternary.True; + + // 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); + + 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); + } + + // 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; + } + } + + 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; + } + } + + 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; + } + + 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; + } + } + + 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 (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); + } + // Used by, eg, missing property checking to replace the top-level message with a more informative one. + if (!headMessage && maybeSuppress) { + // We suppress a call to `reportRelationError` or not depending on the state of the type checker, so + // we call `reportRelationError` here and then undo its effects to figure out what would be the diagnostic + // if we hadn't supress it, and save that as a canonical diagnostic for deduplication purposes. + const savedErrorState = captureErrorCalculationState(); + reportRelationError(headMessage, source, target); + let canonical; + if (errorInfo && errorInfo !== savedErrorState.errorInfo) { + canonical = { code: errorInfo.code, messageText: errorInfo.messageText }; + } + resetErrorInfo(savedErrorState); + if (canonical && errorInfo) { + errorInfo.canonicalHead = canonical; + } + + lastSkippedInfo = [source, target]; + 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; + } + + 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; + } + + 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 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)); + } + } + 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); + + const name = propDeclaration.name!; + errorNode = name; + + if (isIdentifier(name)) { + suggestion = getSuggestionForNonexistentProperty(name, errorTarget); + } + } + if (suggestion !== undefined) { + reportParentSkippedError(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 { + reportParentSkippedError(Diagnostics.Object_literal_may_only_specify_known_properties_and_0_does_not_exist_in_type_1, symbolToString(prop), 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)); + } + return true; + } + } + } + return false; + } + + 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) { + if (target.flags & TypeFlags.Union) { + // Intersections of union types are normalized into unions of intersection types, and such normalized + // unions can get very large and expensive to relate. The following fast path checks if the source union + // originated in an intersection. If so, and if that intersection contains the target type, then we know + // the result to be true (for any two types A and B, A & B is related to both A and B). + const sourceOrigin = (source as UnionType).origin; + if (sourceOrigin && sourceOrigin.flags & TypeFlags.Intersection && target.aliasSymbol && contains((sourceOrigin as IntersectionType).types, target)) { + return Ternary.True; + } + // Similarly, in unions of unions the we preserve the original list of unions. This original list is often + // much shorter than the normalized result, so we scan it in the following fast path. + const targetOrigin = (target as UnionType).origin; + if (targetOrigin && targetOrigin.flags & TypeFlags.Union && source.aliasSymbol && contains((targetOrigin as UnionType).types, source)) { + return Ternary.True; + } + } + 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), intersectionState); + } + 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); + } + + 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, IntersectionState.None); + if (!related) { + return Ternary.False; + } + result &= related; + } + return result; + } + + function typeRelatedToSomeType(source: Type, target: UnionOrIntersectionType, reportErrors: boolean, intersectionState: IntersectionState): Ternary { + const targetTypes = target.types; + if (target.flags & TypeFlags.Union) { + if (containsType(targetTypes, source)) { + return Ternary.True; + } + if ( + relation !== comparableRelation && getObjectFlags(target) & ObjectFlags.PrimitiveUnion && !(source.flags & TypeFlags.EnumLiteral) && ( + source.flags & (TypeFlags.StringLiteral | TypeFlags.BooleanLiteral | TypeFlags.BigIntLiteral) || + (relation === subtypeRelation || relation === strictSubtypeRelation) && source.flags & TypeFlags.NumberLiteral + ) + ) { + // When relating a literal type to a union of primitive types, we know the relation is false unless + // the union contains the base primitive type or the literal type in one of its fresh/regular forms. + // We exclude numeric literals for non-subtype relations because numeric literals are assignable to + // numeric enum literals with the same value. Similarly, we exclude enum literal types because + // identically named enum types are related (see isEnumTypeRelatedTo). We exclude the comparable + // relation in entirety because it needs to be checked in both directions. + const alternateForm = source === (source as StringLiteralType).regularType ? (source as StringLiteralType).freshType : (source as StringLiteralType).regularType; + const primitive = source.flags & TypeFlags.StringLiteral ? stringType : + source.flags & TypeFlags.NumberLiteral ? numberType : + source.flags & TypeFlags.BigIntLiteral ? bigintType : + undefined; + return primitive && containsType(targetTypes, primitive) || alternateForm && containsType(targetTypes, alternateForm) ? Ternary.True : Ternary.False; + } + const match = getMatchingUnionConstituentForType(target as UnionType, source); + if (match) { + const related = isRelatedTo(source, match, RecursionFlags.Target, /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState); + if (related) { + return related; + } + } + } + for (const type of targetTypes) { + const related = isRelatedTo(source, type, RecursionFlags.Target, /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState); + 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, /*headMessage*/ undefined, intersectionState); + } + } + 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; + } + 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; + } + 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; + } + } + return Ternary.False; + } + + function getUndefinedStrippedTargetIfNeeded(source: Type, target: Type) { + 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) { + result &= related; + continue; + } + } + const related = isRelatedTo(sourceType, target, RecursionFlags.Source, reportErrors, /*headMessage*/ undefined, intersectionState); + if (!related) { + return Ternary.False; + } + result &= related; + } + 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; + } + 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); + } + } + if (!related) { + return Ternary.False; + } + result &= related; + } + } + 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, /*ignoreConstraints*/ 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 (relationCount <= 0) { + overflow = true; + return Ternary.False; + } + if (!maybeKeys) { + maybeKeys = []; + maybeKeysSet = new Set(); + sourceStack = []; + targetStack = []; + } + else { + // If source and target are already being compared, consider them related with assumptions + if (maybeKeysSet.has(id)) { + return Ternary.Maybe; + } + + // 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; + if (broadestEquivalentId && maybeKeysSet.has(broadestEquivalentId)) { + return Ternary.Maybe; + } + + if (sourceDepth === 100 || targetDepth === 100) { + overflow = true; + return Ternary.False; + } + } + const maybeStart = maybeCount; + maybeKeys[maybeCount] = id; + maybeKeysSet.add(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 = 0 as RelationComparisonResult; + 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. + resetMaybeStack(/*markAllAsSucceeded*/ true); + } + else { + resetMaybeStack(/*markAllAsSucceeded*/ false); + } + } + // Note: it's intentional that we don't reset in the else case; + // we leave them on the stack such that when we hit depth zero + // above, we can report all of them as successful. + } + 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); + relationCount--; + resetMaybeStack(/*markAllAsSucceeded*/ false); + } + return result; + + function resetMaybeStack(markAllAsSucceeded: boolean) { + for (let i = maybeStart; i < maybeCount; i++) { + maybeKeysSet.delete(maybeKeys[i]); + if (markAllAsSucceeded) { + relation.set(maybeKeys[i], RelationComparisonResult.Succeeded | propagatingVarianceFlags); + relationCount--; + } + } + maybeCount = maybeStart; + } + } + + 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); + } + } + // When the target is an intersection we need an extra property check in order to detect nested excess + // properties and nested weak types. 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 + // + if ( + result && !(intersectionState & IntersectionState.Target) && target.flags & TypeFlags.Intersection && + !isGenericObjectType(target) && source.flags & (TypeFlags.Object | TypeFlags.Intersection) + ) { + result &= propertiesRelatedTo(source, target, reportErrors, /*excludedProperties*/ undefined, /*optionalsOnly*/ false, IntersectionState.None); + if (result && isObjectLiteralType(source) && getObjectFlags(source) & ObjectFlags.FreshLiteral) { + result &= indexSignaturesRelatedTo(source, target, /*sourceIsPrimitive*/ false, reportErrors, IntersectionState.None); + } + } + // When the source is an intersection we need an extra check of any optional properties in the target to + // detect possible mismatched property types. For example: + // + // function foo(x: { a?: string }, y: T & { a: boolean }) { + // x = y; // Mismatched property in source intersection + // } + // + else if ( + result && isNonGenericObjectType(target) && !isArrayOrTupleType(target) && + source.flags & TypeFlags.Intersection && getApparentType(source).flags & TypeFlags.StructuredType && + !some((source as IntersectionType).types, t => t === target || !!(getObjectFlags(t) & ObjectFlags.NonInferrableType)) + ) { + result &= propertiesRelatedTo(source, target, reportErrors, /*excludedProperties*/ undefined, /*optionalsOnly*/ true, intersectionState); + } + } + if (result) { + resetErrorInfo(saveErrorInfo); + } + return result; + } + + function getApparentMappedTypeKeys(nameType: Type, targetType: MappedType) { + 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))), + ); + return getUnionType(mappedKeys); + } + + 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 (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; + } + } + + // 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 params = getSymbolLinks(source.aliasSymbol).typeParameters!; + const minParams = getMinTypeArgumentCount(params); + const sourceTypes = fillMissingTypeArguments(source.aliasTypeArguments, params, minParams, isInJSFile(source.aliasSymbol.valueDeclaration)); + const targetTypes = fillMissingTypeArguments(target.aliasTypeArguments, params, minParams, isInJSFile(source.aliasSymbol.valueDeclaration)); + const varianceResult = relateVariances(sourceTypes, targetTypes, 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; + } + + 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 (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) { + while (constraint && someType(constraint, c => !!(c.flags & TypeFlags.TypeParameter))) { + if (result = isRelatedTo(constraint, target, RecursionFlags.Source, /*reportErrors*/ false)) { + return result; + } + constraint = getConstraintOfTypeParameter(constraint); + } + } + 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; + } + } + 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; + } + } + 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).indexFlags | IndexFlags.NoReducibleCheck), 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 mappedKeys = getApparentMappedTypeKeys(nameType, targetType); + // 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; + } + } + } + } + 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)) { + 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, IndexFlags.NoIndexSignatures); + 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; + } + } + } + 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, is not distributive or is distributive but doesn't reference the check type + // parameter in either of the result types, and the source isn't an instantiation of the same + // conditional type (as happens when computing variance). + if (!c.root.inferTypeParameters && !isDistributionDependent(c.root) && !(source.flags & TypeFlags.Conditional && (source as ConditionalType).root === 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 (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; + } + } + } + } + } + else if (sourceFlags & TypeFlags.Index) { + const isDeferredMappedIndex = shouldDeferIndexType((source as IndexType).type, (source as IndexType).indexFlags) && getObjectFlags((source as IndexType).type) & ObjectFlags.Mapped; + if (result = isRelatedTo(stringNumberSymbolType, target, RecursionFlags.Source, reportErrors && !isDeferredMappedIndex)) { + return result; + } + if (isDeferredMappedIndex) { + const mappedType = (source as IndexType).type as MappedType; + const nameType = getNameTypeFromMappedType(mappedType); + // Unlike on the target side, on the source side we do *not* include the generic part of the `nameType`, since that comes from a + // (potentially anonymous) mapped type local type parameter, so that'd never assign outside the mapped type body, but we still want to + // allow assignments of index types of identical (or similar enough) mapped types. + // eg, `keyof {[X in keyof A]: Obj[X]}` should be assignable to `keyof {[Y in keyof A]: Tup[Y]}` because both map over the same set of keys (`keyof A`). + // Without this source-side breakdown, a `keyof {[X in keyof A]: Obj[X]}` style type won't be assignable to anything except itself, which is much too strict. + const sourceMappedKeys = nameType && isMappedTypeWithKeyofConstraintDeclaration(mappedType) ? getApparentMappedTypeKeys(nameType, mappedType) : (nameType || getConstraintTypeFromMappedType(mappedType)); + if (result = isRelatedTo(sourceMappedKeys, 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 { + 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) { + 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)) { + return result; + } + } + // 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 = !(targetFlags & TypeFlags.Conditional) && hasNonCircularBaseConstraint(source) ? getConstraintOfDistributiveConditionalType(source as ConditionalType) : undefined; + if (distributiveConstraint) { + resetErrorInfo(saveErrorInfo); + if (result = isRelatedTo(distributiveConstraint, 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; + } + 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; + } + // 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; + } + } + else if (isReadonlyArrayType(target) ? everyType(source, isArrayOrTupleType) : isArrayType(target) && everyType(source, t => isTupleType(t) && !t.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; + } + } + else if (isGenericTupleType(source) && isTupleType(target) && !isGenericTupleType(target)) { + const constraint = getBaseConstraintOrType(source); + if (constraint !== source) { + return isRelatedTo(constraint, target, RecursionFlags.Source, reportErrors); + } + } + // A fresh empty object type is never a subtype of a non-empty object type. This ensures fresh({}) <: { [x: string]: xxx } + // but not vice-versa. Without this rule, those types would be mutual subtypes. + 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, /*optionalsOnly*/ false, intersectionState); + if (result) { + result &= signaturesRelatedTo(source, target, SignatureKind.Call, reportStructuralErrors, intersectionState); + if (result) { + result &= signaturesRelatedTo(source, target, SignatureKind.Construct, reportStructuralErrors, intersectionState); + 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; + + 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); + } + } + } + + // 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; + } + + 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; + } + } + 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, /*optionalsOnly*/ false, IntersectionState.None); + if (result) { + result &= signaturesRelatedTo(source, type, SignatureKind.Call, /*reportErrors*/ false, IntersectionState.None); + if (result) { + result &= signaturesRelatedTo(source, type, SignatureKind.Construct, /*reportErrors*/ false, IntersectionState.None); + 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, /*reportErrors*/ false, IntersectionState.None); + } + } + } + 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]); + } + } + else if (!result) { + result = properties.slice(0, i); + } + } + return result || properties; + } + + 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)); + } + } + 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++; + } + } + // No array like or unmatched property error - just issue top level error (errorInfo = undefined) + } + + function propertiesRelatedTo(source: Type, target: Type, reportErrors: boolean, excludedProperties: Set<__String> | undefined, optionalsOnly: boolean, 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 targetHasRestElement = !!(target.target.combinedFlags & ElementFlags.Variable); + const sourceMinLength = isTupleType(source) ? source.target.minLength : 0; + const targetMinLength = target.target.minLength; + if (!sourceRestFlag && sourceArity < targetMinLength) { + if (reportErrors) { + reportError(Diagnostics.Source_has_0_element_s_but_target_requires_1, sourceArity, targetMinLength); + } + return Ternary.False; + } + if (!targetHasRestElement && targetArity < sourceMinLength) { + if (reportErrors) { + reportError(Diagnostics.Source_has_0_element_s_but_target_allows_only_1, sourceMinLength, targetArity); + } + return Ternary.False; + } + if (!targetHasRestElement && (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; + } + const sourceTypeArguments = getTypeArguments(source); + const targetTypeArguments = getTypeArguments(target); + const targetStartCount = getStartElementCount(target.target, ElementFlags.NonRest); + const targetEndCount = getEndElementCount(target.target, ElementFlags.NonRest); + 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; + } + 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; + } + } + + 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); + } + } + return Ternary.False; + } + result &= related; + } + return result; + } + if (target.target.combinedFlags & ElementFlags.Variable) { + 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") && (!optionalsOnly || targetProp.flags & SymbolFlags.Optional)) { + 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; + } + + 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 related = compareProperties(sourceProp, targetProp, isRelatedTo); + if (!related) { + return Ternary.False; + } + result &= related; + } + return result; + } + + function signaturesRelatedTo(source: Type, target: Type, kind: SignatureKind, reportErrors: boolean, intersectionState: IntersectionState): 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 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; + } + } + + 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. + Debug.assertEqual(sourceSignatures.length, targetSignatures.length); + for (let i = 0; i < targetSignatures.length; i++) { + const related = signatureRelatedTo(sourceSignatures[i], targetSignatures[i], /*erase*/ true, reportErrors, intersectionState, 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; + const sourceSignature = first(sourceSignatures); + const targetSignature = first(targetSignatures); + result = signatureRelatedTo(sourceSignature, targetSignature, eraseGenerics, reportErrors, intersectionState, 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, intersectionState, 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)); + } + return Ternary.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 + } + return false; + } + return true; + } + + 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)); + } + + 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, intersectionState: IntersectionState, incompatibleReporter: (source: Type, target: Type) => void): Ternary { + const checkMode = relation === subtypeRelation ? SignatureCheckMode.StrictTopSignature : + relation === strictSubtypeRelation ? SignatureCheckMode.StrictTopSignature | SignatureCheckMode.StrictArity : + SignatureCheckMode.None; + return compareSignaturesRelated(erase ? getErasedSignature(source) : source, erase ? getErasedSignature(target) : target, checkMode, reportErrors, reportError, incompatibleReporter, isRelatedToWorker, reportUnreliableMapper); + function isRelatedToWorker(source: Type, target: Type, reportErrors?: boolean) { + return isRelatedTo(source, target, RecursionFlags.Both, reportErrors, /*headMessage*/ undefined, intersectionState); + } + } + + 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; + } + result &= related; + } + return result; + } + + function membersRelatedToIndexInfo(source: Type, targetInfo: IndexInfo, reportErrors: boolean, intersectionState: IntersectionState): 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, /*headMessage*/ undefined, intersectionState); + if (!related) { + if (reportErrors) { + reportError(Diagnostics.Property_0_is_incompatible_with_index_signature, symbolToString(prop)); + } + return Ternary.False; + } + result &= related; + } + } + for (const info of getIndexInfosOfType(source)) { + if (isApplicableIndexType(info.keyType, keyType)) { + const related = indexInfoRelatedTo(info, targetInfo, reportErrors, intersectionState); + if (!related) { + return Ternary.False; + } + result &= related; + } + } + return result; + } + + function indexInfoRelatedTo(sourceInfo: IndexInfo, targetInfo: IndexInfo, reportErrors: boolean, intersectionState: IntersectionState) { + const related = isRelatedTo(sourceInfo.type, targetInfo.type, RecursionFlags.Both, reportErrors, /*headMessage*/ undefined, intersectionState); + 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 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 = relation !== strictSubtypeRelation && !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; + } + result &= related; + } + 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, intersectionState); + } + // Intersection constituents are never considered to have an inferred index signature. Also, in the strict subtype relation, + // only fresh object literals are considered to have inferred index signatures. This ensures { [x: string]: xxx } <: {} but + // not vice-versa. Without this rule, those types would be mutual strict subtypes. + if (!(intersectionState & IntersectionState.Source) && (relation !== strictSubtypeRelation || getObjectFlags(source) & ObjectFlags.FreshLiteral) && isObjectTypeWithInferableIndex(source)) { + return membersRelatedToIndexInfo(source, targetInfo, reportErrors, intersectionState); + } + if (reportErrors) { + reportError(Diagnostics.Index_signature_for_type_0_is_missing_in_type_1, typeToString(targetInfo.keyType), typeToString(source)); + } + return Ternary.False; + } + + 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; + } + + 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; + } + + // Only a public signature is assignable to public signature. + if (targetAccessibility !== ModifierFlags.Protected && !sourceAccessibility) { + return true; + } + + if (reportErrors) { + reportError(Diagnostics.Cannot_assign_a_0_constructor_type_to_a_1_constructor_type, visibilityToString(sourceAccessibility), visibilityToString(targetAccessibility)); + } + + return false; + } + } + + 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; + } + + if (type.flags & TypeFlags.UnionOrIntersection) { + return !!forEach((type as IntersectionType).types, typeCouldHaveTopLevelSingletonTypes); + } + + if (type.flags & TypeFlags.Instantiable) { + const constraint = getConstraintOfType(type); + if (constraint && constraint !== type) { + return typeCouldHaveTopLevelSingletonTypes(constraint); + } + } + + return isUnitType(type) || !!(type.flags & TypeFlags.TemplateLiteral) || !!(type.flags & TypeFlags.StringMapping); + } + + 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) || + findMatchingTypeReferenceOrTypeAliasReference(source, target) || + findBestTypeForObjectLiteral(source, target) || + findBestTypeForInvokable(source, target) || + findMostOverlappyType(source, target); + } + + function discriminateTypeByDiscriminableItems(target: UnionType, discriminators: (readonly [() => Type, __String])[], related: (source: Type, target: Type) => boolean | Ternary) { + const types = target.types; + const include: Ternary[] = types.map(t => t.flags & TypeFlags.Primitive ? Ternary.False : Ternary.True); + for (const [getDiscriminatingType, propertyName] of discriminators) { + // If the remaining target types include at least one with a matching discriminant, eliminate those that + // have non-matching discriminants. This ensures that we ignore erroneous discriminators and gradually + // refine the target set without eliminating every constituent (which would lead to `never`). + let matched = false; + for (let i = 0; i < types.length; i++) { + if (include[i]) { + const targetType = getTypeOfPropertyOrIndexSignatureOfType(types[i], propertyName); + if (targetType && related(getDiscriminatingType(), targetType)) { + matched = true; + } + else { + include[i] = Ternary.Maybe; + } + } + } + // Turn each Ternary.Maybe into Ternary.False if there was a match. Otherwise, revert to Ternary.True. + for (let i = 0; i < types.length; i++) { + if (include[i] === Ternary.Maybe) { + include[i] = matched ? Ternary.False : Ternary.True; + } + } + } + const filtered = contains(include, Ternary.False) ? getUnionType(types.filter((_, i) => include[i]), UnionReduction.None) : target; + return filtered.flags & TypeFlags.Never ? target : filtered; + } + + /** + * 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.Substitution) { + return isWeakType((type as SubstitutionType).baseType); + } + 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; + } + } + 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 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)) }); + const oldVarianceComputation = inVarianceComputation; + const saveResolutionStart = resolutionStart; + if (!inVarianceComputation) { + inVarianceComputation = true; + resolutionStart = resolutionTargets.length; + } + links.variances = emptyArray; + const variances = []; + for (const tp of typeParameters) { + const modifiers = getTypeParameterModifiers(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); + } + if (!oldVarianceComputation) { + inVarianceComputation = false; + resolutionStart = saveResolutionStart; + } + 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 isMarkerType(type: Type) { + return markerTypes.has(getTypeId(type)); + } + + function getTypeParameterModifiers(tp: TypeParameter): ModifierFlags { + return reduceLeft(tp.symbol?.declarations, (modifiers, d) => modifiers | getEffectiveModifierFlags(d), ModifierFlags.None) & (ModifierFlags.In | ModifierFlags.Out | ModifierFlags.Const); + } + + // 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; + } + + 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 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; + } + // 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) + ">"; + 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: Map, 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) { + // NOTE: cast to TransientSymbol should be safe because only TransientSymbols can have CheckFlags.Synthetic + for (const t of (prop as TransientSymbol).links.containingType!.types) { + const p = getPropertyOfType(t, prop.escapedName); + const result = p && forEachProperty(p, callback); + if (result) { + return result; + } + } + 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 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 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 the given stack contains + // maxDepth or more occurrences of types with the same recursion identity as the given type. The recursion identity + // provides a shared identity for type instantiations that repeat in some (possibly infinite) pattern. For example, + // in `type Deep = { next: Deep> }`, repeatedly referencing the `next` property leads to an infinite + // sequence of ever deeper instantiations with the same recursion identity (in this case the symbol associated with + // the object type literal). + // A homomorphic mapped type is considered deeply nested if its target type is deeply nested, and an intersection is + // considered deeply nested if any constituent of the intersection is deeply nested. + // It is possible, though highly unlikely, for the deeply nested check 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. + function isDeeplyNestedType(type: Type, stack: Type[], depth: number, maxDepth = 3): boolean { + if (depth >= maxDepth) { + if ((getObjectFlags(type) & ObjectFlags.InstantiatedMapped) === ObjectFlags.InstantiatedMapped) { + type = getMappedTargetWithSymbol(type); + } + if (type.flags & TypeFlags.Intersection) { + return some((type as IntersectionType).types, t => isDeeplyNestedType(t, stack, depth, maxDepth)); + } + const identity = getRecursionIdentity(type); + let count = 0; + let lastTypeId = 0; + for (let i = 0; i < depth; i++) { + const t = stack[i]; + if (hasMatchingRecursionIdentity(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; + } + } + } + return false; + } + + // Unwrap nested homomorphic mapped types and return the deepest target type that has a symbol. This better + // preserves unique type identities for mapped types applied to explicitly written object literals. For example + // in `Mapped<{ x: Mapped<{ x: Mapped<{ x: string }>}>}>`, each of the mapped type applications will have a + // unique recursion identity (that of their target object type literal) and thus avoid appearing deeply nested. + function getMappedTargetWithSymbol(type: Type) { + let target; + while ( + (getObjectFlags(type) & ObjectFlags.InstantiatedMapped) === ObjectFlags.InstantiatedMapped && + (target = getModifiersTypeFromMappedType(type as MappedType)) && + (target.symbol || target.flags & TypeFlags.Intersection && some((target as IntersectionType).types, t => !!t.symbol)) + ) { + type = target; + } + return type; + } + + function hasMatchingRecursionIdentity(type: Type, identity: object): boolean { + if ((getObjectFlags(type) & ObjectFlags.InstantiatedMapped) === ObjectFlags.InstantiatedMapped) { + type = getMappedTargetWithSymbol(type); + } + if (type.flags & TypeFlags.Intersection) { + return some((type as IntersectionType).types, t => hasMatchingRecursionIdentity(t, identity)); + } + return getRecursionIdentity(type) === identity; + } + + // 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 object types that have a symbol by that symbol (representing the origin of the type), but + // exclude the static side of a class since it shares its symbol with the instance side. + return type.symbol; + } + if (isTupleType(type)) { + return type.target; + } + } + if (type.flags & TypeFlags.TypeParameter) { + // We use the symbol of the type parameter such that all "fresh" instantiations of that type parameter + // have the same recursion identity. + return type.symbol; + } + if (type.flags & TypeFlags.IndexedAccess) { + // Identity is the leftmost object type in a chain of indexed accesses, eg, in A[P1][P2][P3] 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; + } + return type; + } + + 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; + } + } + else { + if ((sourceProp.flags & SymbolFlags.Optional) !== (targetProp.flags & SymbolFlags.Optional)) { + return Ternary.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; + } + } + 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)); + } + return result; + } + + 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; + } + + function getCombinedTypeFlags(types: Type[]): TypeFlags { + return reduceLeft(types, (flags, t) => flags | (t.flags & TypeFlags.Union ? getCombinedTypeFlags((t as UnionType).types) : t.flags), 0 as TypeFlags); + } + + 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)!; + } + + 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 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 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 isMutableArrayLikeType(type: Type): boolean { + // A type is mutable-array-like if it is a reference to the global Array type, or if it is not the + // any, undefined or null type and if it is assignable to Array + return isMutableArrayOrTuple(type) || !(type.flags & (TypeFlags.Any | TypeFlags.Nullable)) && isTypeAssignableTo(type, anyArrayType); + } + + 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) { + 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 { + let lengthType; + return isTupleType(type) || + !!getPropertyOfType(type, "0" as __String) || + isArrayLikeType(type) && !!(lengthType = getTypeOfPropertyOfType(type, "length" as __String)) && everyType(lengthType, t => !!(t.flags & TypeFlags.NumberLiteral)); + } + + function isArrayOrTupleLikeType(type: Type): boolean { + return isArrayLikeType(type) || isTupleLikeType(type); + } + + function getTupleElementType(type: Type, index: number) { + const propType = getTypeOfPropertyOfType(type, "" + index as __String); + if (propType) { + return propType; + } + if (everyType(type, isTupleType)) { + return getTupleElementTypeOutOfStartCount(type, index, compilerOptions.noUncheckedIndexedAccess ? undefinedType : undefined); + } + return undefined; + } + + function isNeitherUnitTypeNorNever(type: Type): boolean { + return !(type.flags & (TypeFlags.Unit | TypeFlags.Never)); + } + + function isUnitType(type: Type): boolean { + return !!(type.flags & TypeFlags.Unit); + } + + function isUnitLikeType(type: Type): boolean { + // Intersections that reduce to 'never' (e.g. 'T & null' where 'T extends {}') are not unit types. + const t = getBaseConstraintOrType(type); + // Scan intersections such that tagged literal types are considered unit types. + return t.flags & TypeFlags.Intersection ? some((t as IntersectionType).types, isUnitType) : isUnitType(t); + } + + function extractUnitType(type: Type) { + return type.flags & TypeFlags.Intersection ? find((type as IntersectionType).types, isUnitType) || type : 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 getBaseTypeOfLiteralType(type: Type): Type { + return type.flags & TypeFlags.EnumLike ? getBaseTypeOfEnumLikeType(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 getBaseTypeOfLiteralTypeUnion(type: UnionType) { + const key = `B${getTypeId(type)}`; + return getCachedType(key) ?? setCachedType(key, mapType(type, getBaseTypeOfLiteralType)); + } + + // This like getBaseTypeOfLiteralType, but instead treats enum literals as strings/numbers instead + // of returning their enum base type (which depends on the types of other literals in the enum). + function getBaseTypeOfLiteralTypeForComparison(type: Type): Type { + return type.flags & (TypeFlags.StringLiteral | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) ? stringType : + type.flags & (TypeFlags.NumberLiteral | TypeFlags.Enum) ? numberType : + type.flags & TypeFlags.BigIntLiteral ? bigintType : + type.flags & TypeFlags.BooleanLiteral ? booleanType : + type.flags & TypeFlags.Union ? mapType(type, getBaseTypeOfLiteralTypeForComparison) : + type; + } + + function getWidenedLiteralType(type: Type): Type { + return type.flags & TypeFlags.EnumLike && isFreshLiteralType(type) ? getBaseTypeOfEnumLikeType(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 getWidenedUniqueESSymbolType(type: Type): Type { + return type.flags & TypeFlags.UniqueESSymbol ? esSymbolType : + type.flags & TypeFlags.Union ? mapType(type as UnionType, getWidenedUniqueESSymbolType) : + type; + } + + function getWidenedLiteralLikeTypeForContextualType(type: Type, contextualType: Type | undefined) { + if (!isLiteralOfContextualType(type, contextualType)) { + type = getWidenedUniqueESSymbolType(getWidenedLiteralType(type)); + } + return getRegularTypeOfLiteralType(type); + } + + 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 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; + } + + /** + * 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 isGenericTupleType(type: Type): type is TupleTypeReference { + return isTupleType(type) && !!(type.target.combinedFlags & ElementFlags.Variadic); + } + + function isSingleElementGenericTupleType(type: Type): type is TupleTypeReference { + return isGenericTupleType(type) && type.target.elementFlags.length === 1; + } + + function getRestTypeOfTupleType(type: TupleTypeReference) { + return getElementTypeOfSliceOfTupleType(type, type.target.fixedLength); + } + + function getTupleElementTypeOutOfStartCount(type: Type, index: number, undefinedOrMissingType: Type | undefined) { + return mapType(type, t => { + const tupleType = t as TupleTypeReference; + const restType = getRestTypeOfTupleType(tupleType); + if (!restType) { + return undefinedType; + } + if (undefinedOrMissingType && index >= getTotalFixedElementCount(tupleType.target)) { + return getUnionType([restType, undefinedOrMissingType]); + } + return restType; + }); + } + + function getRestArrayTypeOfTupleType(type: TupleTypeReference) { + const restType = getRestTypeOfTupleType(type); + return restType && createArrayType(restType); + } + + function getElementTypeOfSliceOfTupleType(type: TupleTypeReference, index: number, endSkipCount = 0, writing = false, noReductions = 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, noReductions ? UnionReduction.None : UnionReduction.Literal); + } + return undefined; + } + + 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 isZeroBigInt({ value }: BigIntLiteralType) { + return value.base10Value === "0"; + } + + function removeDefinitelyFalsyTypes(type: Type): Type { + return filterType(type, t => hasTypeFacts(t, TypeFacts.Truthy)); + } + + function extractDefinitelyFalsyTypes(type: Type): Type { + return mapType(type, getDefinitelyFalsyPartOfType); + } + + 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; + } + + /** + * 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 getOptionalType(type: Type, isProperty = false): Type { + Debug.assert(strictNullChecks); + const missingOrUndefined = isProperty ? undefinedOrMissingType : undefinedType; + return type === missingOrUndefined || type.flags & TypeFlags.Union && (type as UnionType).types[0] === missingOrUndefined ? type : getUnionType([type, missingOrUndefined]); + } + + 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 getNonNullableType(type: Type): Type { + return strictNullChecks ? getAdjustedTypeWithFacts(type, TypeFacts.NEUndefinedOrNull) : type; + } + + function addOptionalTypeMarker(type: Type) { + return strictNullChecks ? getUnionType([type, optionalType]) : type; + } + + function removeOptionalTypeMarker(type: Type): Type { + return strictNullChecks ? removeType(type, optionalType) : type; + } + + function propagateOptionalTypeMarker(type: Type, node: OptionalChain, wasOptional: boolean) { + return wasOptional ? isOutermostOptionalChain(node) ? getOptionalType(type) : addOptionalTypeMarker(type) : type; + } + + function getOptionalExpressionType(exprType: Type, expression: Expression) { + return isExpressionOfOptionalChainRoot(expression) ? getNonNullableType(exprType) : + isOptionalChain(expression) ? removeOptionalTypeMarker(exprType) : + exprType; + } + + function removeMissingType(type: Type, isOptional: boolean) { + return exactOptionalPropertyTypes && isOptional ? removeType(type, missingType) : type; + } + + function containsMissingType(type: Type) { + return type === missingType || !!(type.flags & TypeFlags.Union) && (type as UnionType).types[0] === missingType; + } + + function removeMissingOrUndefinedType(type: Type): Type { + return exactOptionalPropertyTypes ? removeType(type, missingType) : getTypeWithFacts(type, TypeFacts.NEUndefined); + } + + /** + * 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); + } + + /** + * 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.links.type = type; + symbol.links.target = source; + if (source.valueDeclaration) { + symbol.valueDeclaration = source.valueDeclaration; + } + const nameType = getSymbolLinks(source).nameType; + if (nameType) { + symbol.links.nameType = nameType; + } + return symbol; + } + + 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; + } + + /** + * 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; + } + + 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); + }); + } + } + } + context.siblings = siblings; + } + return context.siblings; + } + + function getPropertiesOfContext(context: WideningContext): Symbol[] { + if (!context.resolvedProperties) { + const names = new Map<__String, Symbol>(); + 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; + } + + 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, undefinedOrMissingType); + 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)); + } + } + } + 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 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; + } + 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) { + errorReported ||= reportWideningErrorsInType(t); + } + } + } + else if (isArrayOrTupleType(type)) { + for (const t of getTypeArguments(type)) { + errorReported ||= reportWideningErrorsInType(t); + } + } + else if (isObjectLiteralType(type)) { + for (const p of getPropertiesOfObjectType(type)) { + const t = getTypeOfSymbol(p); + if (getObjectFlags(t) & ObjectFlags.ContainsWideningType) { + errorReported = reportWideningErrorsInType(t); + if (!errorReported) { + // we need to account for property types coming from object literal type normalization in unions + const valueDeclaration = p.declarations?.find(d => d.symbol.valueDeclaration?.parent === type.symbol.valueDeclaration); + if (valueDeclaration) { + error(valueDeclaration, Diagnostics.Object_literal_s_property_0_implicitly_has_an_1_type, symbolToString(p), typeToString(getWidenedType(t))); + errorReported = true; + } + } + } + } + } + } + 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)) { + const originalKeywordKind = identifierToKeywordKind(param.name); + if ( + (isCallSignatureDeclaration(param.parent) || isMethodSignature(param.parent) || isFunctionTypeNode(param.parent)) && + param.parent.parameters.includes(param) && + (resolveName(param, param.name.escapedText, SymbolFlags.Type, /*nameNotFoundMessage*/ undefined, /*isUse*/ true) || + originalKeywordKind && isTypeNodeKind(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); + return; + case SyntaxKind.JSDocSignature: + if (noImplicitAny && isJSDocOverloadTag(declaration.parent)) { + error(declaration.parent.tagName, Diagnostics.This_overload_implicitly_returns_the_type_0_because_it_lacks_a_return_type_annotation, 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); + } + else { + error(declaration, Diagnostics.Function_expression_which_lacks_return_type_annotation_implicitly_has_an_0_return_type, typeAsString); + } + return; + } + 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 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); + } + } + }); + } + + 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, /*readonly*/ isConstTypeVariable(targetRestType) && !someType(targetRestType, isMutableArrayLikeType)), targetRestType); + } + } + + function applyToReturnTypes(source: Signature, target: Signature, callback: (s: Type, t: Type) => void) { + const targetTypePredicate = getTypePredicateOfSignature(target); + if (targetTypePredicate) { + const sourceTypePredicate = getTypePredicateOfSignature(source); + if (sourceTypePredicate && typePredicateKindsMatch(sourceTypePredicate, targetTypePredicate) && sourceTypePredicate.type && targetTypePredicate.type) { + callback(sourceTypePredicate.type, targetTypePredicate.type); + return; + } + } + const targetReturnType = getReturnTypeOfSignature(target); + if (couldContainTypeVariables(targetReturnType)) { + callback(getReturnTypeOfSignature(source), targetReturnType); + } + } + + 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 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 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 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); + } + } + 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 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 getMapperFromContext(context: T): TypeMapper | T & undefined { + return context && context.mapper; + } + + // 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 || some(getTypeArguments(type as TypeReference), couldContainTypeVariables)) || + objectFlags & ObjectFlags.SingleSignatureType && !!length((type as SingleSignatureType).outerTypeParameters) || + 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 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; + } + + function isTypeParameterAtTopLevel(type: Type, tp: TypeParameter, depth = 0): boolean { + return !!(type === tp || + type.flags & TypeFlags.UnionOrIntersection && some((type as UnionOrIntersectionType).types, t => isTypeParameterAtTopLevel(t, tp, depth)) || + depth < 3 && type.flags & TypeFlags.Conditional && ( + isTypeParameterAtTopLevel(getTrueTypeFromConditionalType(type as ConditionalType), tp, depth + 1) || + isTypeParameterAtTopLevel(getFalseTypeFromConditionalType(type as ConditionalType), tp, depth + 1) + )); + } + + function isTypeParameterAtTopLevelInReturnType(signature: Signature, typeParameter: TypeParameter) { + const typePredicate = getTypePredicateOfSignature(signature); + return typePredicate ? !!typePredicate.type && isTypeParameterAtTopLevel(typePredicate.type, typeParameter) : + isTypeParameterAtTopLevel(getReturnTypeOfSignature(signature), typeParameter); + } + + /** 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.links.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(/*symbol*/ undefined, members, emptyArray, emptyArray, indexInfos); + } + + /** + * 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 { + const cacheKey = source.id + "," + target.id + "," + constraint.id; + if (reverseMappedCache.has(cacheKey)) { + return reverseMappedCache.get(cacheKey); + } + const type = createReverseMappedType(source, target, constraint); + reverseMappedCache.set(cacheKey, type); + return type; + } + + // We consider a type to be partially inferable if it isn't marked non-inferable or if it is + // an object literal type with at least one property of an inferable type. For example, an object + // literal { a: 123, b: x => true } is marked non-inferable because it contains a context sensitive + // arrow function, but is considered partially inferable because property 'a' has an inferable type. + function isPartiallyInferableType(type: Type): boolean { + return !(getObjectFlags(type) & ObjectFlags.NonInferrableType) || + isObjectLiteralType(type) && some(getPropertiesOfType(type), prop => isPartiallyInferableType(getTypeOfSymbol(prop))) || + isTupleType(type) && some(getElementTypes(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)) { + const elementType = inferReverseMappedType(getTypeArguments(source)[0], target, constraint); + if (!elementType) { + return undefined; + } + return createArrayType(elementType, isReadonlyArrayType(source)); + } + if (isTupleType(source)) { + const elementTypes = map(getElementTypes(source), t => inferReverseMappedType(t, target, constraint)); + if (!every(elementTypes, (t): t is Type => !!t)) { + return undefined; + } + 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): Type { + const links = getSymbolLinks(symbol); + if (!links.type) { + links.type = inferReverseMappedType(symbol.links.propertyType, symbol.links.mappedType, symbol.links.constraintType) || unknownType; + } + return links.type; + } + + function inferReverseMappedTypeWorker(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 inferReverseMappedType(source: Type, target: MappedType, constraint: IndexType): Type | undefined { + const cacheKey = source.id + "," + target.id + "," + constraint.id; + if (reverseMappedCache.has(cacheKey)) { + return reverseMappedCache.get(cacheKey) || unknownType; + } + reverseMappedSourceStack.push(source); + reverseMappedTargetStack.push(target); + const saveExpandingFlags = reverseExpandingFlags; + if (isDeeplyNestedType(source, reverseMappedSourceStack, reverseMappedSourceStack.length, 2)) reverseExpandingFlags |= ExpandingFlags.Source; + if (isDeeplyNestedType(target, reverseMappedTargetStack, reverseMappedTargetStack.length, 2)) reverseExpandingFlags |= ExpandingFlags.Target; + let type; + if (reverseExpandingFlags !== ExpandingFlags.Both) { + type = inferReverseMappedTypeWorker(source, target, constraint); + } + reverseMappedSourceStack.pop(); + reverseMappedTargetStack.pop(); + reverseExpandingFlags = saveExpandingFlags; + reverseMappedCache.set(cacheKey, type); + return 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; + } + 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; + } + } + } + } + } + } + + function getUnmatchedProperty(source: Type, target: Type, requireOptionalProperties: boolean, matchDiscriminantProperties: boolean): Symbol | undefined { + return firstOrUndefinedIterator(getUnmatchedProperties(source, target, requireOptionalProperties, matchDiscriminantProperties)); + } + + function tupleTypesDefinitelyUnrelated(source: TupleTypeReference, target: TupleTypeReference) { + return !(target.target.combinedFlags & ElementFlags.Variadic) && target.target.minLength > source.target.minLength || + !(target.target.combinedFlags & ElementFlags.Variable) && (!!(source.target.combinedFlags & ElementFlags.Variable) || 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 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 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); + } + + /** + * 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) { + return getBigIntLiteralType(parseValidBigInt(text)); + } + + function isMemberOfStringMapping(source: Type, target: Type): boolean { + if (target.flags & TypeFlags.Any) { + return true; + } + if (target.flags & (TypeFlags.String | 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; + } + + function isValidTypeForTemplateLiteralPlaceholder(source: Type, target: Type): boolean { + if (target.flags & TypeFlags.Intersection) { + return every((target as IntersectionType).types, t => t === emptyTypeLiteralType || isValidTypeForTemplateLiteralPlaceholder(source, t)); + } + if (target.flags & TypeFlags.String || isTypeAssignableTo(source, target)) { + 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) || + target.flags & TypeFlags.TemplateLiteral && isTypeMatchedByTemplateLiteralType(source, target as TemplateLiteralType)); + } + 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 false; + } + + 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, (s, i) => { + return isTypeAssignableTo(getBaseConstraintOrType(s), getBaseConstraintOrType(target.types[i])) ? s : getStringLikeTypeForType(s); + }) : + 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; + } + 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; + } + } + 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; + } + } + + /** + * @returns `true` if `type` has the shape `[T[0]]` where `T` is `typeParameter` + */ + function isTupleOfSelf(typeParameter: TypeParameter, type: Type) { + return isTupleType(type) && getTupleElementType(type, 0) === getIndexedAccessType(typeParameter, getNumberLiteralType(0)) && !getTypeOfPropertyOfType(type, "1" as __String); + } + + function inferTypes(inferences: InferenceInfo[], originalSource: Type, originalTarget: Type, priority = InferencePriority.None, contravariant = false) { + let bivariant = false; + let propagationType: Type; + let inferencePriority: number = InferencePriority.MaxValue; + let visited: Map; + let sourceStack: Type[]; + let targetStack: Type[]; + let expandingFlags = ExpandingFlags.None; + inferFromTypes(originalSource, originalTarget); + + function inferFromTypes(source: Type, target: Type): void { + if (!couldContainTypeVariables(target) || isNoInferType(target)) { + return; + } + if (source === wildcardType || source === blockedStringType) { + // 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, with defaults applied. + const params = getSymbolLinks(source.aliasSymbol).typeParameters!; + const minParams = getMinTypeArgumentCount(params); + const sourceTypes = fillMissingTypeArguments(source.aliasTypeArguments, params, minParams, isInJSFile(source.aliasSymbol.valueDeclaration)); + const targetTypes = fillMissingTypeArguments(target.aliasTypeArguments, params, minParams, isInJSFile(source.aliasSymbol.valueDeclaration)); + inferFromTypeArguments(sourceTypes, targetTypes!, 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 (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); + } + 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); + 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); + } + } + if (target.flags & (TypeFlags.IndexedAccess | TypeFlags.Substitution)) { + if (isNoInferType(target)) { + return; + } + 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; + } + 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) { + const candidate = propagationType || source; + if (candidate === blockedStringType) { + return; + } + if (inference.priority === undefined || priority < inference.priority) { + inference.candidates = undefined; + inference.contraCandidates = undefined; + inference.topLevel = true; + inference.priority = priority; + } + if (priority === inference.priority) { + // Inferring A to [A[0]] is a zero information inference (it guarantees A becomes its constraint), but oft arises from generic argument list inferences + // By discarding it early, we can allow more fruitful results to be used instead. + if (isTupleOfSelf(inference.typeParameter, candidate)) { + return; + } + // 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); + clearCachedInferences(inferences); + } + } + 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; + } + // 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 ( + 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 (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 as ConditionalType, 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 (target.flags & TypeFlags.TemplateLiteral) { + inferToTemplateLiteralType(source, target as TemplateLiteralType); + } + else { + source = getReducedType(source); + if (isGenericMappedType(source) && isGenericMappedType(target)) { + invokeOnce(source, target, inferFromGenericMappedTypes); + } + 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 && !(apparentSource.flags & (TypeFlags.Object | TypeFlags.Intersection))) { + 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 inferToMultipleTypesWithPriority(source: Type, targets: Type[], targetFlags: TypeFlags, newPriority: InferencePriority) { + const savePriority = priority; + priority |= newPriority; + inferToMultipleTypes(source, targets, targetFlags); + priority = savePriority; + } + + // Ensure an inference action is performed only once for the given source and target types. + // This includes two things: + // Avoiding inferring between the same pair of source and target types, + // and avoiding circularly inferring between source and target types. + // For an example of the last, consider if we are inferring between source type + // `type Deep = { next: Deep> }` and target type `type Loop = { next: Loop }`. + // We would then infer between the types of the `next` property: `Deep>` = `{ next: Deep>> }` and `Loop` = `{ next: Loop }`. + // We will then infer again between the types of the `next` property: + // `Deep>>` and `Loop`, and so on, such that we would be forever inferring + // between instantiations of the same types `Deep` and `Loop`. + // In particular, we would be inferring from increasingly deep instantiations of `Deep` to `Loop`, + // such that we would go on inferring forever, even though we would never infer + // between the same pair of types. + function invokeOnce(source: Source, target: Target, action: (source: Source, target: Target) => 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; + (sourceStack ??= []).push(source); + (targetStack ??= []).push(target); + if (isDeeplyNestedType(source, sourceStack, sourceStack.length, 2)) expandingFlags |= ExpandingFlags.Source; + if (isDeeplyNestedType(target, targetStack, targetStack.length, 2)) expandingFlags |= ExpandingFlags.Target; + if (expandingFlags !== ExpandingFlags.Both) { + action(source, target); + } + else { + inferencePriority = InferencePriority.Circularity; + } + targetStack.pop(); + sourceStack.pop(); + expandingFlags = saveExpandingFlags; + visited.set(key, inferencePriority); + inferencePriority = Math.min(inferencePriority, saveInferencePriority); + } + + 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 inferFromTypeArguments(sourceTypes: readonly Type[], targetTypes: readonly Type[], variances: readonly VarianceFlags[]) { + const count = sourceTypes.length < targetTypes.length ? sourceTypes.length : targetTypes.length; + for (let i = 0; i < count; i++) { + if (i < variances.length && (variances[i] & VarianceFlags.VarianceMask) === VarianceFlags.Contravariant) { + inferFromContravariantTypes(sourceTypes[i], targetTypes[i]); + } + else { + inferFromTypes(sourceTypes[i], targetTypes[i]); + } + } + } + + function inferFromContravariantTypes(source: Type, target: Type) { + 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; + } + + 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; + } + 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; + } + // 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 { + // 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); + } + } + } + // 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); + } + } + } + } + + function inferToMappedType(source: Type, target: MappedType, constraintType: Type): boolean { + if ((constraintType.flags & TypeFlags.Union) || (constraintType.flags & TypeFlags.Intersection)) { + let result = false; + for (const type of (constraintType as (UnionType | IntersectionType)).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, + ); + } + } + 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, /*indexFlags*/ !!source.pattern ? IndexFlags.NoIndexSignatures : IndexFlags.None), 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; + } + // 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; + } + + // 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); + } + } + } + + function inferFromGenericMappedTypes(source: MappedType, target: MappedType) { + // 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); + } + + 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)) { + inferFromGenericMappedTypes(source, target); + } + 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.combinedFlags & ElementFlags.Variable ? 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) { + if (elementFlags[startLength] & elementFlags[startLength + 1] & ElementFlags.Variadic) { + // 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 (elementFlags[startLength] & ElementFlags.Variadic && elementFlags[startLength + 1] & ElementFlags.Rest) { + // Middle of target is [...T, ...rest] and source is tuple type + // if T is constrained by a fixed-size tuple we might be able to use its arity to infer T + const param = getInferenceInfoForType(elementTypes[startLength])?.typeParameter; + const constraint = param && getBaseConstraintOfType(param); + if (constraint && isTupleType(constraint) && !(constraint.target.combinedFlags & ElementFlags.Variable)) { + const impliedArity = constraint.target.fixedLength; + inferFromTypes(sliceTupleType(source, startLength, sourceArity - (startLength + impliedArity)), elementTypes[startLength]); + inferFromTypes(getElementTypeOfSliceOfTupleType(source, startLength + impliedArity, endLength)!, elementTypes[startLength + 1]); + } + } + else if (elementFlags[startLength] & ElementFlags.Rest && elementFlags[startLength + 1] & ElementFlags.Variadic) { + // Middle of target is [...rest, ...T] and source is tuple type + // if T is constrained by a fixed-size tuple we might be able to use its arity to infer T + const param = getInferenceInfoForType(elementTypes[startLength + 1])?.typeParameter; + const constraint = param && getBaseConstraintOfType(param); + if (constraint && isTupleType(constraint) && !(constraint.target.combinedFlags & ElementFlags.Variable)) { + const impliedArity = constraint.target.fixedLength; + const endIndex = sourceArity - getEndElementCount(target.target, ElementFlags.Fixed); + const startIndex = endIndex - impliedArity; + const trailingSlice = createTupleType(getTypeArguments(source).slice(startIndex, endIndex), source.target.elementFlags.slice(startIndex, endIndex), /*readonly*/ false, source.target.labeledElementDeclarations && source.target.labeledElementDeclarations.slice(startIndex, endIndex)); + + inferFromTypes(getElementTypeOfSliceOfTupleType(source, startLength, endLength + impliedArity)!, elementTypes[startLength]); + inferFromTypes(trailingSlice, 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 = sliceTupleType(source, startLength, endLength); + 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 = getElementTypeOfSliceOfTupleType(source, startLength, endLength); + if (restType) { + inferFromTypes(restType, elementTypes[startLength]); + } + } + } + // Infer between ending fixed elements + for (let i = 0; i < endLength; i++) { + inferFromTypes(getTypeArguments(source)[sourceArity - i - 1], elementTypes[targetArity - i - 1]); + } + return; + } + if (isArrayType(target)) { + inferFromIndexTypes(source, target); + 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( + removeMissingType(getTypeOfSymbol(sourceProp), !!(sourceProp.flags & SymbolFlags.Optional)), + removeMissingType(getTypeOfSymbol(targetProp), !!(targetProp.flags & SymbolFlags.Optional)), + ); + } + } + } + + function inferFromSignatures(source: Type, target: Type, kind: SignatureKind) { + const sourceSignatures = getSignaturesOfType(source, kind); + const sourceLen = sourceSignatures.length; + if (sourceLen > 0) { + // We match source and target signatures from the bottom up, and if the source has fewer signatures + // than the target, we infer from the first source signature to the excess target signatures. + const targetSignatures = getSignaturesOfType(target, kind); + const targetLen = targetSignatures.length; + for (let i = 0; i < targetLen; i++) { + const sourceIndex = Math.max(sourceLen - targetLen + i, 0); + inferFromSignature(getBaseSignature(sourceSignatures[sourceIndex]), getErasedSignature(targetSignatures[i])); + } + } + } + + function inferFromSignature(source: Signature, target: Signature) { + if (!(source.flags & SignatureFlags.IsNonInferrable)) { + 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); + } + } + for (const info of getIndexInfosOfType(source)) { + if (isApplicableIndexType(info.keyType, targetInfo.keyType)) { + propTypes.push(info.type); + } + } + 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 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 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 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]); + } + } + return candidates; + } + + 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) || isConstTypeVariable(inference.typeParameter); + const widenLiteralTypes = !primitiveConstraint && inference.topLevel && + (inference.isFixed || !isTypeParameterAtTopLevelInReturnType(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; + let fallbackType: Type | undefined; + if (context.signature) { + const inferredCovariantType = inference.candidates ? getCovariantInference(inference, context.signature) : undefined; + const inferredContravariantType = inference.contraCandidates ? getContravariantInference(inference) : undefined; + if (inferredCovariantType || inferredContravariantType) { + // If we have both co- and contra-variant inferences, we prefer the co-variant inference if it is not 'never', + // all co-variant inferences are assignable to it (i.e. it isn't one of a conflicting set of candidates), it is + // assignable to some contra-variant inference, and no other type parameter is constrained to this type parameter + // and has inferences that would conflict. Otherwise, we prefer the contra-variant inference. + // Similarly ignore co-variant `any` inference when both are available as almost everything is assignable to it + // and it would spoil the overall inference. + const preferCovariantType = inferredCovariantType && (!inferredContravariantType || + !(inferredCovariantType.flags & (TypeFlags.Never | TypeFlags.Any)) && + some(inference.contraCandidates, t => isTypeAssignableTo(inferredCovariantType, t)) && + every(context.inferences, other => + other !== inference && getConstraintOfTypeParameter(other.typeParameter) !== inference.typeParameter || + every(other.candidates, t => isTypeAssignableTo(t, inferredCovariantType)))); + inferredType = preferCovariantType ? inferredCovariantType : inferredContravariantType; + fallbackType = preferCovariantType ? inferredContravariantType : 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)); + } + } + } + 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))) { + // If the fallback type satisfies the constraint, we pick it. Otherwise, we pick the constraint. + inference.inferredType = fallbackType && context.compareTypes(fallbackType, getTypeWithThisArgument(instantiatedConstraint, fallbackType)) ? fallbackType : instantiatedConstraint; + } + } + } + + 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 "Bun": + return compilerOptions.types + ? Diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_Bun_Try_npm_i_save_dev_types_Slashbun_and_then_add_bun_to_the_types_field_in_your_tsconfig + : Diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_Bun_Try_npm_i_save_dev_types_Slashbun; + 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, + SymbolFlags.Value | SymbolFlags.ExportValue, + getCannotFindNameDiagnosticForName(node), + !isWriteOnlyAccess(node), + /*excludeGlobals*/ false, + ) || unknownSymbol; + } + return links.resolvedSymbol; + } + + function isInAmbientOrTypeNode(node: Node): boolean { + return !!(node.flags & NodeFlags.Ambient || findAncestor(node, n => isInterfaceDeclaration(n) || isTypeAliasDeclaration(n) || isTypeLiteralNode(n))); + } + + // 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}`; + } + if (isElementAccessExpression(node) && isIdentifier(node.argumentExpression)) { + const symbol = getResolvedSymbol(node.argumentExpression); + if (isConstantVariable(symbol) || isParameterOrMutableLocalVariable(symbol) && !isSymbolAssigned(symbol)) { + const key = getFlowCacheKey((node as AccessExpression).expression, declaredType, initialType, flowContainer); + return key && `${key}.@${getSymbolId(symbol)}`; + } + } + 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 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) || + (isVariableDeclaration(target) || isBindingElement(target)) && + getExportSymbolOfValueSymbolIfExported(getResolvedSymbol(source as Identifier)) === getSymbolOfDeclaration(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); + if (sourcePropertyName !== undefined) { + const targetPropertyName = isAccessExpression(target) ? getAccessedPropertyName(target) : undefined; + if (targetPropertyName !== undefined) { + return targetPropertyName === sourcePropertyName && isMatchingReference((source as AccessExpression).expression, (target as AccessExpression).expression); + } + } + if (isElementAccessExpression(source) && isElementAccessExpression(target) && isIdentifier(source.argumentExpression) && isIdentifier(target.argumentExpression)) { + const symbol = getResolvedSymbol(source.argumentExpression); + if (symbol === getResolvedSymbol(target.argumentExpression) && (isConstantVariable(symbol) || isParameterOrMutableLocalVariable(symbol) && !isSymbolAssigned(symbol))) { + return isMatchingReference(source.expression, target.expression); + } + } + break; + 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; + } + + 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 tryGetNameFromType(type: Type) { + return type.flags & TypeFlags.UniqueESSymbol ? (type as UniqueESSymbolType).escapedName : + type.flags & TypeFlags.StringOrNumberLiteral ? escapeLeadingUnderscores("" + (type as StringLiteralType | NumberLiteralType).value) : undefined; + } + + function tryGetElementAccessExpressionName(node: ElementAccessExpression) { + return isStringOrNumericLiteralLike(node.argumentExpression) ? escapeLeadingUnderscores(node.argumentExpression.text) : + isEntityNameExpression(node.argumentExpression) ? tryGetNameFromEntityNameExpression(node.argumentExpression) : undefined; + } + + function tryGetNameFromEntityNameExpression(node: EntityNameOrEntityNameExpression) { + const symbol = resolveEntityName(node, SymbolFlags.Value, /*ignoreErrors*/ true); + if (!symbol || !(isConstantVariable(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; + } + } + if (hasOnlyExpressionInitializer(declaration) && isBlockScopedNameDeclaredBeforeUse(declaration, node)) { + const initializer = getEffectiveInitializer(declaration); + if (initializer) { + const initializerType = isBindingPattern(declaration.parent) ? getTypeForBindingElement(declaration as BindingElement) : getTypeOfExpression(initializer); + return initializerType && tryGetNameFromType(initializerType); + } + if (isEnumMember(declaration)) { + return getTextOfPropertyName(declaration.name); + } + } + return undefined; + } + + function containsMatchingReference(source: Node, target: Node) { + while (isAccessExpression(source)) { + source = source.expression; + if (isMatchingReference(source, target)) { + return true; + } + } + return false; + } + + function optionalChainContainsReference(source: Node, target: Node) { + while (isOptionalChain(source)) { + source = source.expression; + if (isMatchingReference(source, 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) { + // NOTE: cast to TransientSymbol should be safe because only TransientSymbols can have CheckFlags.SyntheticProperty + if ((prop as TransientSymbol).links.isDiscriminantProperty === undefined) { + (prop as TransientSymbol).links.isDiscriminantProperty = ((prop as TransientSymbol).links.checkFlags & CheckFlags.Discriminant) === CheckFlags.Discriminant && + !isGenericType(getTypeOfSymbol(prop)); + } + return !!(prop as TransientSymbol).links.isDiscriminantProperty; + } + } + return false; + } + + 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; + } + + // 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; + } + + // 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; + } + + // 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 hasMatchingArgument(expression: CallExpression | NewExpression, reference: Node) { + if (expression.arguments) { + for (const argument of expression.arguments) { + if (isOrContainsMatchingReference(reference, argument) || optionalChainContainsReference(argument, reference)) { + return true; + } + } + } + if ( + expression.expression.kind === SyntaxKind.PropertyAccessExpression && + isOrContainsMatchingReference(reference, (expression.expression as PropertyAccessExpression).expression) + ) { + return true; + } + return false; + } + + function getFlowNodeId(flow: FlowNode): number { + if (flow.id <= 0) { + flow.id = nextFlowId; + nextFlowId++; + } + return flow.id; + } + + 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 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; + } + const key = `A${getTypeId(declaredType)},${getTypeId(assignedType)}`; + return getCachedType(key) ?? setCachedType(key, getAssignmentReducedTypeWorker(declaredType, assignedType)); + } + + 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 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 getTypeFacts(type: Type, mask: TypeFacts): TypeFacts { + return getTypeFactsWorker(type, mask) & mask; + } + + function hasTypeFacts(type: Type, mask: TypeFacts): boolean { + return getTypeFacts(type, mask) !== 0; + } + + function getTypeFactsWorker(type: Type, callerOnlyNeeds: TypeFacts): 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) { + const possibleFacts = strictNullChecks + ? TypeFacts.EmptyObjectStrictFacts | TypeFacts.FunctionStrictFacts | TypeFacts.ObjectStrictFacts + : TypeFacts.EmptyObjectFacts | TypeFacts.FunctionFacts | TypeFacts.ObjectFacts; + + if ((callerOnlyNeeds & possibleFacts) === 0) { + // If the caller doesn't care about any of the facts that we could possibly produce, + // return zero so we can skip resolving members. + return 0; + } + + 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 | getTypeFactsWorker(t, callerOnlyNeeds), TypeFacts.None); + } + if (flags & TypeFlags.Intersection) { + return getIntersectionTypeFacts(type as IntersectionType, callerOnlyNeeds); + } + return TypeFacts.UnknownFacts; + } + + function getIntersectionTypeFacts(type: IntersectionType, callerOnlyNeeds: TypeFacts): 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 = getTypeFactsWorker(t, callerOnlyNeeds); + oredFacts |= f; + andedFacts &= f; + } + } + return oredFacts & TypeFacts.OrFactsMask | andedFacts & TypeFacts.AndFactsMask; + } + + function getTypeWithFacts(type: Type, include: TypeFacts) { + return filterType(type, t => hasTypeFacts(t, include)); + } + + // 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 removeNullableByIntersection(reduced, TypeFacts.EQUndefined, TypeFacts.EQNull, TypeFacts.IsNull, nullType); + case TypeFacts.NENull: + return removeNullableByIntersection(reduced, TypeFacts.EQNull, TypeFacts.EQUndefined, TypeFacts.IsUndefined, undefinedType); + case TypeFacts.NEUndefinedOrNull: + case TypeFacts.Truthy: + return mapType(reduced, t => hasTypeFacts(t, TypeFacts.EQUndefinedOrNull) ? getGlobalNonNullableTypeInstantiation(t) : t); + } + } + return reduced; + } + + function removeNullableByIntersection(type: Type, targetFacts: TypeFacts, otherFacts: TypeFacts, otherIncludesFacts: TypeFacts, otherType: Type) { + const facts = getTypeFacts(type, TypeFacts.EQUndefined | TypeFacts.EQNull | TypeFacts.IsUndefined | TypeFacts.IsNull); + // Simply return the type if it never compares equal to the target nullable. + if (!(facts & targetFacts)) { + return type; + } + // By default we intersect with a union of {} and the opposite nullable. + const emptyAndOtherUnion = getUnionType([emptyObjectType, otherType]); + // For each constituent type that can compare equal to the target nullable, intersect with the above union + // if the type doesn't already include the opppsite nullable and the constituent can compare equal to the + // opposite nullable; otherwise, just intersect with {}. + return mapType(type, t => hasTypeFacts(t, targetFacts) ? getIntersectionType([t, !(facts & otherIncludesFacts) && hasTypeFacts(t, otherFacts) ? emptyAndOtherUnion : emptyObjectType]) : t); + } + + 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, missingType]) : + 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; + 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 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 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 getInitialTypeOfVariableDeclaration(node: VariableDeclaration) { + if (node.initializer) { + return getTypeOfInitializer(node.initializer); + } + if (node.parent.parent.kind === SyntaxKind.ForInStatement) { + return stringType; + } + if (node.parent.parent.kind === SyntaxKind.ForOfStatement) { + return checkRightHandSideOfForOf(node.parent.parent) || errorType; + } + return errorType; + } + + function getInitialType(node: VariableDeclaration | BindingElement) { + return node.kind === SyntaxKind.VariableDeclaration ? + getInitialTypeOfVariableDeclaration(node) : + getInitialTypeOfBindingElement(node); + } + + 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 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 getTypeOfSwitchClause(clause: CaseClause | DefaultClause) { + if (clause.kind === SyntaxKind.CaseClause) { + return getRegularTypeOfLiteralType(getTypeOfExpression(clause.expression)); + } + return neverType; + } + + 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; + } + + // 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 eachTypeContainedIn(source: Type, types: Type[]) { + return source.flags & TypeFlags.Union ? !forEach((source as UnionType).types, t => !contains(types, t)) : contains(types, source); + } + + function isTypeSubsetOf(source: Type, target: Type) { + return !!(source === target || source.flags & TypeFlags.Never || target.flags & TypeFlags.Union && isTypeSubsetOfUnion(source, target as UnionType)); + } + + 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.EnumLike && getBaseTypeOfEnumLikeType(source as LiteralType) === target) { + return true; + } + return containsType(target.types, source); + } + + function forEachType(type: Type, f: (t: Type) => T | undefined): T | undefined { + return type.flags & TypeFlags.Union ? forEach((type as UnionType).types, f) : f(type); + } + + 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; + } + 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); + } + } + // filtering could remove intersections so `ContainsIntersections` might be forwarded "incorrectly" + // it is purely an optimization hint so there is no harm in accidentally forwarding it + return getUnionTypeFromSortedList(filtered, (type as UnionType).objectFlags & (ObjectFlags.PrimitiveUnion | ObjectFlags.ContainsIntersections), /*aliasSymbol*/ undefined, /*aliasTypeArguments*/ undefined, newOrigin); + } + return type.flags & TypeFlags.Never || f(type) ? type : neverType; + } + + 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; + } + + // 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; + } + + function isIncomplete(flowType: FlowType) { + return flowType.flags === 0; + } + + function getTypeFromFlowType(flowType: FlowType) { + return flowType.flags === 0 ? flowType.type : flowType as 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; + } + + 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])); + } + + 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)); + } + + 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 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 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 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).links.syntheticOrigin; + if (origin && getExplicitTypeOfSymbol(origin)) { + 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))); + } + } + } + } + + // 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); + } + return prop && getExplicitTypeOfSymbol(prop, diagnostic); + } + return undefined; + } + case SyntaxKind.ParenthesizedExpression: + return getTypeOfDottedName((node as ParenthesizedExpression).expression, diagnostic); + } + } + } + + function getEffectsSignature(node: CallExpression | InstanceofExpression) { + 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 (isBinaryExpression(node)) { + const rightType = checkNonNullExpression(node.right); + funcType = getSymbolHasInstanceMethodOfObjectType(rightType); + } + else 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; + } + + 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 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 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; + } + 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) { + 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).antecedent, f => isReachableFlowNodeWorker(f, /*noCacheCheck*/ false)); + } + else if (flags & FlowFlags.LoopLabel) { + const antecedents = (flow as FlowLabel).antecedent; + if (antecedents === undefined || antecedents.length === 0) { + 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. + const data = (flow as FlowSwitchClause).node; + if (data.clauseStart === data.clauseEnd && isExhaustiveSwitchStatement(data.switchStatement)) { + return false; + } + 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).node.target; + const saveAntecedents = target.antecedent; + target.antecedent = (flow as FlowReduceLabel).node.antecedents; + const result = isReachableFlowNodeWorker((flow as FlowReduceLabel).antecedent, /*noCacheCheck*/ false); + target.antecedent = 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).antecedent, 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).antecedent![0]; + } + else if (flags & FlowFlags.ReduceLabel) { + const target = (flow as FlowReduceLabel).node.target; + const saveAntecedents = target.antecedent; + target.antecedent = (flow as FlowReduceLabel).node.antecedents; + const result = isPostSuperFlowNode((flow as FlowReduceLabel).antecedent, /*noCacheCheck*/ false); + target.antecedent = 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.ThisKeyword: + return true; + case SyntaxKind.Identifier: + if (!isThisInTypeQuery(node)) { + const symbol = getResolvedSymbol(node as Identifier); + return isConstantVariable(symbol) + || isParameterOrMutableLocalVariable(symbol) && !isSymbolAssigned(symbol) + || !!symbol.valueDeclaration && isFunctionExpression(symbol.valueDeclaration); + } + break; + 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); + case SyntaxKind.ObjectBindingPattern: + case SyntaxKind.ArrayBindingPattern: + const rootDeclaration = getRootDeclaration(node.parent); + return isParameter(rootDeclaration) || isCatchClauseVariableDeclaration(rootDeclaration) + ? !isSomeSymbolAssigned(rootDeclaration) + : isVariableDeclaration(rootDeclaration) && isVarConstLike(rootDeclaration); + } + return false; + } + + function getFlowTypeOfReference(reference: Node, declaredType: Type, initialType = declaredType, flowContainer?: Node, flowNode = tryCast(reference, canHaveFlowNode)?.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; + } + return resultType; + + function getOrSetCacheKey() { + if (isKeySet) { + return key; + } + 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; + } + 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).antecedent!.length === 1) { + flow = (flow as FlowLabel).antecedent![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).node.target; + const saveAntecedents = target.antecedent; + target.antecedent = (flow as FlowReduceLabel).node.antecedents; + type = getTypeAtFlowNode((flow as FlowReduceLabel).antecedent); + target.antecedent = 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 && container.kind !== SyntaxKind.ArrowFunction) + ) { + 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++; + } + flowDepth--; + return type; + } + } + + 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; + } + const t = isInCompoundLikeAssignment(node) ? getBaseTypeOfLiteralType(declaredType) : declaredType; + if (t.flags & TypeFlags.Union) { + return getAssignmentReducedType(t as UnionType, getInitialOrAssignedType(flow)); + } + return t; + } + // 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) || isVarConstLike(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) || optionalChainContainsReference(node.parent.parent.expression, reference)) + ) { + 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; + } + 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)]); + } + } + 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; + } + + 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); + } + } + return evolvedType === type ? flowType : createFlowType(evolvedType, isIncomplete(flowType)); + } + return flowType; + } + } + return undefined; + } + + 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 = skipParentheses(flow.node.switchStatement.expression); + const flowType = getTypeAtFlowNode(flow.antecedent); + let type = getTypeFromFlowType(flowType); + if (isMatchingReference(reference, expr)) { + type = narrowTypeBySwitchOnDiscriminant(type, flow.node); + } + else if (expr.kind === SyntaxKind.TypeOfExpression && isMatchingReference(reference, (expr as TypeOfExpression).expression)) { + type = narrowTypeBySwitchOnTypeOf(type, flow.node); + } + else if (expr.kind === SyntaxKind.TrueKeyword) { + type = narrowTypeBySwitchOnTrue(type, flow.node); + } + else { + if (strictNullChecks) { + if (optionalChainContainsReference(expr, reference)) { + type = narrowTypeBySwitchOptionalChainContainment(type, flow.node, t => !(t.flags & (TypeFlags.Undefined | TypeFlags.Never))); + } + else if (expr.kind === SyntaxKind.TypeOfExpression && optionalChainContainsReference((expr as TypeOfExpression).expression, reference)) { + type = narrowTypeBySwitchOptionalChainContainment(type, flow.node, 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.node); + } + } + 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.antecedent!) { + if (!bypassFlow && antecedent.flags & FlowFlags.SwitchClause && (antecedent as FlowSwitchClause).node.clauseStart === (antecedent as FlowSwitchClause).node.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 (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. + if (!isTypeSubsetOf(type, initialType)) { + subtypeReduction = true; + } + if (isIncomplete(flowType)) { + 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 (!(type.flags & TypeFlags.Never) && !contains(antecedentTypes, type) && !isExhaustiveSwitchStatement(bypassFlow.node.switchStatement)) { + if (type === declaredType && declaredType === initialType) { + return type; + } + antecedentTypes.push(type); + if (!isTypeSubsetOf(type, initialType)) { + subtypeReduction = true; + } + if (isIncomplete(flowType)) { + seenIncomplete = true; + } + } + } + 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; + } + // 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.antecedent!) { + 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, initialType)) { + 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; + } + } + // 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; + } + + 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 (isIdentifier(expr)) { + const symbol = getResolvedSymbol(expr); + if (isConstantVariable(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; + } + + function getDiscriminantPropertyAccess(expr: Expression, computedType: Type) { + // As long as the computed type is a subset of the declared type, we use the full declared type to detect + // a discriminant property. In cases where the computed type isn't a subset, e.g because of a preceding type + // predicate narrowing, we use the actual computed type. + if (declaredType.flags & TypeFlags.Union || computedType.flags & TypeFlags.Union) { + const access = getCandidateDiscriminantPropertyAccess(expr); + if (access) { + const name = getAccessedPropertyName(access); + if (name) { + const type = declaredType.flags & TypeFlags.Union && isTypeSubsetOf(computedType, declaredType) ? declaredType : computedType; + if (isDiscriminantProperty(type, name)) { + return access; + } + } + } + } + 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 optionalChain = isOptionalChain(access); + const removeNullable = strictNullChecks && (optionalChain || isNonNullAccess(access)) && maybeTypeOfKind(type, TypeFlags.Nullable); + let propType = getTypeOfPropertyOfType(removeNullable ? getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull) : type, propName); + if (!propType) { + return type; + } + propType = removeNullable && optionalChain ? getOptionalType(propType) : propType; + const narrowedPropType = narrowType(propType); + return filterType(type, t => { + const discriminantType = getTypeOfPropertyOrIndexSignatureOfType(t, propName) || unknownType; + return !(discriminantType.flags & TypeFlags.Never) && !(narrowedPropType.flags & TypeFlags.Never) && areTypesComparable(narrowedPropType, discriminantType); + }); + } + + 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)); + } + + function narrowTypeBySwitchOnDiscriminantProperty(type: Type, access: AccessExpression | BindingElement | ParameterDeclaration, data: FlowSwitchClauseData) { + if (data.clauseStart < data.clauseEnd && type.flags & TypeFlags.Union && getKeyPropertyName(type as UnionType) === getAccessedPropertyName(access)) { + const clauseTypes = getSwitchClauseTypes(data.switchStatement).slice(data.clauseStart, data.clauseEnd); + const candidate = getUnionType(map(clauseTypes, t => getConstituentTypeForKeyType(type as UnionType, t) || unknownType)); + if (candidate !== unknownType) { + return candidate; + } + } + return narrowTypeByDiscriminant(type, access, t => narrowTypeBySwitchOnDiscriminant(t, data)); + } + + 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 isTypePresencePossible(type: Type, propName: __String, assumeTrue: boolean) { + const prop = getPropertyOfType(type, propName); + return prop ? + !!(prop.flags & SymbolFlags.Optional || getCheckFlags(prop) & CheckFlags.Partial) || 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; + } + + function narrowTypeByBooleanComparison(type: Type, expr: Expression, bool: BooleanLiteral, operator: BinaryOperator, assumeTrue: boolean): Type { + assumeTrue = (assumeTrue !== (bool.kind === SyntaxKind.TrueKeyword)) !== (operator !== SyntaxKind.ExclamationEqualsEqualsToken && operator !== SyntaxKind.ExclamationEqualsToken); + return narrowType(type, expr, 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); + } + 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); + } + if (isMatchingConstructorReference(left)) { + return narrowTypeByConstructor(type, operator, right, assumeTrue); + } + if (isMatchingConstructorReference(right)) { + return narrowTypeByConstructor(type, operator, left, assumeTrue); + } + if (isBooleanLiteral(right) && !isAccessExpression(left)) { + return narrowTypeByBooleanComparison(type, left, right, operator, assumeTrue); + } + if (isBooleanLiteral(left) && !isAccessExpression(right)) { + return narrowTypeByBooleanComparison(type, right, left, operator, assumeTrue); + } + break; + case SyntaxKind.InstanceOfKeyword: + return narrowTypeByInstanceof(type, expr as InstanceofExpression, assumeTrue); + case SyntaxKind.InKeyword: + if (isPrivateIdentifier(expr.left)) { + return narrowTypeByPrivateIdentifierInInExpression(type, expr, assumeTrue); + } + const target = getReferenceCandidate(expr.right); + if (containsMissingType(type) && isAccessExpression(reference) && isMatchingReference(reference.expression, target)) { + const leftType = getTypeOfExpression(expr.left); + if (isTypeUsableAsPropertyName(leftType) && getAccessedPropertyName(reference) === getPropertyNameFromType(leftType)) { + return getTypeWithFacts(type, assumeTrue ? TypeFacts.NEUndefined : TypeFacts.EQUndefined); + } + } + if (isMatchingReference(reference, target)) { + const leftType = getTypeOfExpression(expr.left); + if (isTypeUsableAsPropertyName(leftType)) { + return narrowTypeByInKeyword(type, leftType, 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; + } + + 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) { + 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; + } + 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))); + } + 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)) { + if (strictNullChecks && optionalChainContainsReference(target, reference) && assumeTrue === (literal.text !== "undefined")) { + type = getAdjustedTypeWithFacts(type, TypeFacts.NEUndefinedOrNull); + } + const propertyAccess = getDiscriminantPropertyAccess(target, type); + if (propertyAccess) { + return narrowTypeByDiscriminant(type, propertyAccess, t => narrowTypeByLiteralExpression(t, literal, assumeTrue)); + } + return type; + } + return narrowTypeByLiteralExpression(type, literal, assumeTrue); + } + + function narrowTypeByLiteralExpression(type: Type, literal: LiteralExpression, assumeTrue: boolean) { + return assumeTrue ? + narrowTypeByTypeName(type, literal.text) : + getAdjustedTypeWithFacts(type, typeofNEFacts.get(literal.text) || TypeFacts.TypeofNEHostObject); + } + + function narrowTypeBySwitchOptionalChainContainment(type: Type, { switchStatement, clauseStart, clauseEnd }: FlowSwitchClauseData, 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, clauseStart, clauseEnd }: FlowSwitchClauseData) { + // 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, t.flags & TypeFlags.Undefined ? undefinedType : 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) ? hasTypeFacts(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. + hasTypeFacts(t, facts) ? getIntersectionType([t, impliedType]) : + neverType); + } + + function narrowTypeBySwitchOnTypeOf(type: Type, { switchStatement, clauseStart, clauseEnd }: FlowSwitchClauseData): 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 narrowTypeBySwitchOnTrue(type: Type, { switchStatement, clauseStart, clauseEnd }: FlowSwitchClauseData): Type { + const defaultIndex = findIndex(switchStatement.caseBlock.clauses, clause => clause.kind === SyntaxKind.DefaultClause); + const hasDefaultClause = clauseStart === clauseEnd || (defaultIndex >= clauseStart && defaultIndex < clauseEnd); + + // First, narrow away all of the cases that preceded this set of cases. + for (let i = 0; i < clauseStart; i++) { + const clause = switchStatement.caseBlock.clauses[i]; + if (clause.kind === SyntaxKind.CaseClause) { + type = narrowType(type, clause.expression, /*assumeTrue*/ false); + } + } + + // If our current set has a default, then none the other cases were hit either. + // There's no point in narrowing by the the other cases in the set, since we can + // get here through other paths. + if (hasDefaultClause) { + for (let i = clauseEnd; i < switchStatement.caseBlock.clauses.length; i++) { + const clause = switchStatement.caseBlock.clauses[i]; + if (clause.kind === SyntaxKind.CaseClause) { + type = narrowType(type, clause.expression, /*assumeTrue*/ false); + } + } + return type; + } + + // Now, narrow based on the cases in this set. + const clauses = switchStatement.caseBlock.clauses.slice(clauseStart, clauseEnd); + return getUnionType(map(clauses, clause => clause.kind === SyntaxKind.CaseClause ? narrowType(type, clause.expression, /*assumeTrue*/ true) : 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 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; + } + + // 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; + } + + // 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; + } + + // 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; + } + + // 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; + } + + // 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); + } + } + + function narrowTypeByInstanceof(type: Type, expr: InstanceofExpression, 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; + } + const right = expr.right; + const rightType = getTypeOfExpression(right); + if (!isTypeDerivedFrom(rightType, globalObjectType)) { + return type; + } + + // if the right-hand side has an object type with a custom `[Symbol.hasInstance]` method, and that method + // has a type predicate, use the type predicate to perform narrowing. This allows normal `object` types to + // participate in `instanceof`, as per Step 2 of https://tc39.es/ecma262/#sec-instanceofoperator. + const signature = getEffectsSignature(expr); + const predicate = signature && getTypePredicateOfSignature(signature); + if (predicate && predicate.kind === TypePredicateKind.Identifier && predicate.parameterIndex === 0) { + return getNarrowedType(type, predicate.type, assumeTrue, /*checkDerived*/ true); + } + if (!isTypeDerivedFrom(rightType, globalFunctionType)) { + return type; + } + const instanceType = mapType(rightType, getInstanceType); + // Don't narrow from `any` if the target type is exactly `Object` or `Function`, and narrow + // in the false branch only if the target is a non-empty object type. + if ( + isTypeAny(type) && (instanceType === globalObjectType || instanceType === globalFunctionType) || + !assumeTrue && !(instanceType.flags & TypeFlags.Object && !isEmptyAnonymousObjectType(instanceType)) + ) { + return type; + } + return getNarrowedType(type, instanceType, assumeTrue, /*checkDerived*/ true); + } + + function getInstanceType(constructorType: Type) { + const prototypePropertyType = getTypeOfPropertyOfType(constructorType, "prototype" as __String); + if (prototypePropertyType && !isTypeAny(prototypePropertyType)) { + return prototypePropertyType; + } + const constructSignatures = getSignaturesOfType(constructorType, SignatureKind.Construct); + if (constructSignatures.length) { + return getUnionType(map(constructSignatures, signature => getReturnTypeOfSignature(getErasedSignature(signature)))); + } + // We use the empty object type to indicate we don't know the type of objects created by + // this constructor function. + return emptyObjectType; + } + + function getNarrowedType(type: Type, candidate: Type, assumeTrue: boolean, checkDerived: boolean): Type { + 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)); + } + + function getNarrowedTypeWorker(type: Type, candidate: Type, assumeTrue: boolean, checkDerived: boolean) { + if (!assumeTrue) { + if (type === candidate) { + return neverType; + } + if (checkDerived) { + return filterType(type, t => !isTypeDerivedFrom(t, candidate)); + } + const trueType = getNarrowedType(type, candidate, /*assumeTrue*/ true, /*checkDerived*/ false); + return filterType(type, t => !isTypeSubsetOf(t, trueType)); + } + if (type.flags & TypeFlags.AnyOrUnknown) { + return candidate; + } + if (type === candidate) { + return candidate; + } + + // We first attempt to filter the current type, narrowing constituents as appropriate and removing + // constituents that are unrelated to the candidate. + const isRelated = checkDerived ? isTypeDerivedFrom : isTypeSubtypeOf; + 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 => isTypeStrictSubtypeOf(t, c) ? t : isTypeStrictSubtypeOf(c, t) ? c : isTypeSubtypeOf(t, c) ? t : isTypeSubtypeOf(c, t) ? c : 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); + } + } + } + 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 && optionalChainContainsReference(predicateArgument, reference) && + ( + assumeTrue && !(hasTypeFacts(predicate.type, TypeFacts.EQUndefined)) || + !assumeTrue && everyType(predicate.type, isNullableType) + ) + ) { + 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; + } + + // 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.operatorToken.kind === SyntaxKind.QuestionQuestionEqualsToken) && 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 (isConstantVariable(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; + } + } + + function getTypeOfSymbolAtLocation(symbol: Symbol, location: Node) { + symbol = getExportSymbolOfValueSymbolIfExported(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 = removeOptionalTypeMarker( + isWriteAccess(location) && location.kind === SyntaxKind.PropertyAccessExpression ? + checkPropertyAccessExpression(location as PropertyAccessExpression, /*checkMode*/ undefined, /*writeOnly*/ true) : + getTypeOfExpression(location as Expression), + ); + if (getExportSymbolOfValueSymbolIfExported(getNodeLinks(location).resolvedSymbol) === symbol) { + return type; + } + } + } + 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 isRightSideOfAccessExpression(location) && isWriteAccess(location.parent) ? getWriteTypeOfSymbol(symbol) : getNonMissingTypeOfSymbol(symbol); + } + + 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) { + return !isPastLastAssignment(symbol, /*location*/ undefined); + } + + // Return true if there are no assignments to the given symbol or if the given location + // is past the last assignment to the symbol. + function isPastLastAssignment(symbol: Symbol, location: Node | undefined) { + const parent = findAncestor(symbol.valueDeclaration, isFunctionOrSourceFile); + if (!parent) { + return false; + } + const links = getNodeLinks(parent); + if (!(links.flags & NodeCheckFlags.AssignmentsMarked)) { + links.flags |= NodeCheckFlags.AssignmentsMarked; + if (!hasParentWithAssignmentsMarked(parent)) { + markNodeAssignments(parent); + } + } + return !symbol.lastAssignmentPos || location && symbol.lastAssignmentPos < location.pos; + } + + // Check if a parameter or catch variable (or their bindings elements) is assigned anywhere + function isSomeSymbolAssigned(rootDeclaration: Node) { + Debug.assert(isVariableDeclaration(rootDeclaration) || isParameter(rootDeclaration)); + return isSomeSymbolAssignedWorker(rootDeclaration.name); + } + + function isSomeSymbolAssignedWorker(node: BindingName): boolean { + if (node.kind === SyntaxKind.Identifier) { + return isSymbolAssigned(getSymbolOfDeclaration(node.parent as Declaration)); + } + + return some(node.elements, e => e.kind !== SyntaxKind.OmittedExpression && isSomeSymbolAssignedWorker(e.name)); + } + + function hasParentWithAssignmentsMarked(node: Node) { + return !!findAncestor(node.parent, node => isFunctionOrSourceFile(node) && !!(getNodeLinks(node).flags & NodeCheckFlags.AssignmentsMarked)); + } + + function isFunctionOrSourceFile(node: Node) { + return isFunctionLikeDeclaration(node) || isSourceFile(node); + } + + // For all assignments within the given root node, record the last assignment source position for all + // referenced parameters and mutable local variables. When assignments occur in nested functions or + // references occur in export specifiers, record Number.MAX_VALUE as the assignment position. When + // assignments occur in compound statements, record the ending source position of the compound statement + // as the assignment position (this is more conservative than full control flow analysis, but requires + // only a single walk over the AST). + function markNodeAssignments(node: Node) { + switch (node.kind) { + case SyntaxKind.Identifier: + if (isAssignmentTarget(node)) { + const symbol = getResolvedSymbol(node as Identifier); + if (isParameterOrMutableLocalVariable(symbol) && symbol.lastAssignmentPos !== Number.MAX_VALUE) { + const referencingFunction = findAncestor(node, isFunctionOrSourceFile); + const declaringFunction = findAncestor(symbol.valueDeclaration, isFunctionOrSourceFile); + symbol.lastAssignmentPos = referencingFunction === declaringFunction ? extendAssignmentPosition(node, symbol.valueDeclaration!) : Number.MAX_VALUE; + } + } + return; + case SyntaxKind.ExportSpecifier: + const exportDeclaration = (node as ExportSpecifier).parent.parent; + const name = (node as ExportSpecifier).propertyName || (node as ExportSpecifier).name; + if (!(node as ExportSpecifier).isTypeOnly && !exportDeclaration.isTypeOnly && !exportDeclaration.moduleSpecifier && name.kind !== SyntaxKind.StringLiteral) { + const symbol = resolveEntityName(name, SymbolFlags.Value, /*ignoreErrors*/ true, /*dontResolveAlias*/ true); + if (symbol && isParameterOrMutableLocalVariable(symbol)) { + symbol.lastAssignmentPos = Number.MAX_VALUE; + } + } + return; + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.EnumDeclaration: + return; + } + if (isTypeNode(node)) { + return; + } + forEachChild(node, markNodeAssignments); + } + + // Extend the position of the given assignment target node to the end of any intervening variable statement, + // expression statement, compound statement, or class declaration occurring between the node and the given + // declaration node. + function extendAssignmentPosition(node: Node, declaration: Declaration) { + let pos = node.pos; + while (node && node.pos > declaration.pos) { + switch (node.kind) { + case SyntaxKind.VariableStatement: + case SyntaxKind.ExpressionStatement: + case SyntaxKind.IfStatement: + case SyntaxKind.DoStatement: + case SyntaxKind.WhileStatement: + case SyntaxKind.ForStatement: + case SyntaxKind.ForInStatement: + case SyntaxKind.ForOfStatement: + case SyntaxKind.WithStatement: + case SyntaxKind.SwitchStatement: + case SyntaxKind.TryStatement: + case SyntaxKind.ClassDeclaration: + pos = node.end; + } + node = node.parent; + } + return pos; + } + + function isConstantVariable(symbol: Symbol) { + return symbol.flags & SymbolFlags.Variable && (getDeclarationNodeFlagsFromSymbol(symbol) & NodeFlags.Constant) !== 0; + } + + function isParameterOrMutableLocalVariable(symbol: Symbol) { + // Return true if symbol is a parameter, a catch clause variable, or a mutable local variable + const declaration = symbol.valueDeclaration && getRootDeclaration(symbol.valueDeclaration); + return !!declaration && ( + isParameter(declaration) || + isVariableDeclaration(declaration) && (isCatchClause(declaration.parent) || isMutableLocalVariableDeclaration(declaration)) + ); + } + + function isMutableLocalVariableDeclaration(declaration: VariableDeclaration) { + // Return true if symbol is a non-exported and non-global `let` variable + return !!(declaration.parent.flags & NodeFlags.Let) && !( + getCombinedModifierFlags(declaration) & ModifierFlags.Export || + declaration.parent.parent.kind === SyntaxKind.VariableStatement && isGlobalSourceFile(declaration.parent.parent.parent) + ); + } + + function parameterInitializerContainsUndefined(declaration: ParameterDeclaration): boolean { + const links = getNodeLinks(declaration); + + if (links.parameterInitializerContainsUndefined === undefined) { + if (!pushTypeResolution(declaration, TypeSystemPropertyName.ParameterInitializerContainsUndefined)) { + reportCircularityError(declaration.symbol); + return true; + } + + const containsUndefined = !!(hasTypeFacts(checkDeclarationInitializer(declaration, CheckMode.Normal), TypeFacts.IsUndefined)); + + if (!popTypeResolution()) { + reportCircularityError(declaration.symbol); + return true; + } + + links.parameterInitializerContainsUndefined ??= containsUndefined; + } + + return links.parameterInitializerContainsUndefined; + } + + /** 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 { + const removeUndefined = strictNullChecks && + declaration.kind === SyntaxKind.Parameter && + declaration.initializer && + hasTypeFacts(declaredType, TypeFacts.IsUndefined) && + !parameterInitializerContainsUndefined(declaration); + + return removeUndefined ? getTypeWithFacts(declaredType, TypeFacts.NEUndefined) : 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.NewExpression && (parent as NewExpression).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) { + if (isNoInferType(type)) { + type = (type as SubstitutionType).baseType; + } + // 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; + } + return false; + }); + } + + /** + * This function marks all the imports the given location refers to as `.referenced` in `NodeLinks` (transitively through local import aliases). + * (This corresponds to not getting elided in JS emit.) + * It can be called on *most* nodes in the AST with `ReferenceHint.Unspecified` and will filter its inputs, but care should be taken to avoid calling it on the RHS of an `import =` or specifiers in a `import {} from "..."`, + * unless you *really* want to *definitely* mark those as referenced. + * These shouldn't be directly marked, and should only get marked transitively by the internals of this function. + * + * @param location The location to mark js import refernces for + * @param hint The kind of reference `location` has already been checked to be + * @param propSymbol The optional symbol of the property we're looking up - this is used for property accesses when `const enum`s do not count as references (no `isolatedModules`, no `preserveConstEnums` + export). It will be calculated if not provided. + * @param parentType The optional type of the parent of the LHS of the property access - this will be recalculated if not provided (but is costly). + */ + function markLinkedReferences(location: PropertyAccessExpression | QualifiedName, hint: ReferenceHint.Property, propSymbol: Symbol | undefined, parentType: Type): void; + function markLinkedReferences(location: Identifier, hint: ReferenceHint.Identifier): void; + function markLinkedReferences(location: ExportAssignment, hint: ReferenceHint.ExportAssignment): void; + function markLinkedReferences(location: JsxOpeningLikeElement | JsxOpeningFragment, hint: ReferenceHint.Jsx): void; + function markLinkedReferences(location: FunctionLikeDeclaration | MethodSignature, hint: ReferenceHint.AsyncFunction): void; + function markLinkedReferences(location: ImportEqualsDeclaration, hint: ReferenceHint.ExportImportEquals): void; + function markLinkedReferences(location: ExportSpecifier, hint: ReferenceHint.ExportSpecifier): void; + function markLinkedReferences(location: HasDecorators, hint: ReferenceHint.Decorator): void; + function markLinkedReferences(location: Node, hint: ReferenceHint.Unspecified, propSymbol?: Symbol, parentType?: Type): void; + function markLinkedReferences(location: Node, hint: ReferenceHint, propSymbol?: Symbol, parentType?: Type) { + if (!canCollectSymbolAliasAccessabilityData) { + return; + } + if (location.flags & NodeFlags.Ambient) { + return; // References within types and declaration files are never going to contribute to retaining a JS import + } + switch (hint) { + case ReferenceHint.Identifier: + return markIdentifierAliasReferenced(location as Identifier); + case ReferenceHint.Property: + return markPropertyAliasReferenced(location as PropertyAccessExpression | QualifiedName, propSymbol, parentType); + case ReferenceHint.ExportAssignment: + return markExportAssignmentAliasReferenced(location as ExportAssignment); + case ReferenceHint.Jsx: + return markJsxAliasReferenced(location as JsxOpeningLikeElement | JsxOpeningFragment); + case ReferenceHint.AsyncFunction: + return markAsyncFunctionAliasReferenced(location as FunctionLikeDeclaration | MethodSignature); + case ReferenceHint.ExportImportEquals: + return markImportEqualsAliasReferenced(location as ImportEqualsDeclaration); + case ReferenceHint.ExportSpecifier: + return markExportSpecifierAliasReferenced(location as ExportSpecifier); + case ReferenceHint.Decorator: + return markDecoratorAliasReferenced(location as HasDecorators); + case ReferenceHint.Unspecified: { + // Identifiers in expression contexts are emitted, so we need to follow their referenced aliases and mark them as used + // Some non-expression identifiers are also treated as expression identifiers for this purpose, eg, `a` in `b = {a}` or `q` in `import r = q` + // This is the exception, rather than the rule - most non-expression identifiers are declaration names. + if (isIdentifier(location) && (isExpressionNode(location) || isShorthandPropertyAssignment(location.parent) || (isImportEqualsDeclaration(location.parent) && location.parent.moduleReference === location)) && shouldMarkIdentifierAliasReferenced(location)) { + if (isPropertyAccessOrQualifiedName(location.parent)) { + const left = isPropertyAccessExpression(location.parent) ? location.parent.expression : location.parent.left; + if (left !== location) return; // Only mark the LHS (the RHS is a property lookup) + } + markIdentifierAliasReferenced(location); + return; + } + if (isPropertyAccessOrQualifiedName(location)) { + let topProp: Node | undefined = location; + while (isPropertyAccessOrQualifiedName(topProp)) { + if (isPartOfTypeNode(topProp)) return; + topProp = topProp.parent; + } + return markPropertyAliasReferenced(location); + } + if (isExportAssignment(location)) { + return markExportAssignmentAliasReferenced(location); + } + if (isJsxOpeningLikeElement(location) || isJsxOpeningFragment(location)) { + return markJsxAliasReferenced(location); + } + if (isImportEqualsDeclaration(location)) { + if (isInternalModuleImportEqualsDeclaration(location) || checkExternalImportOrExportDeclaration(location)) { + return markImportEqualsAliasReferenced(location); + } + return; + } + if (isExportSpecifier(location)) { + return markExportSpecifierAliasReferenced(location); + } + if (isFunctionLikeDeclaration(location) || isMethodSignature(location)) { + markAsyncFunctionAliasReferenced(location); + // Might be decorated, fall through to decorator final case + } + if (!compilerOptions.emitDecoratorMetadata) { + return; + } + if (!canHaveDecorators(location) || !hasDecorators(location) || !location.modifiers || !nodeCanBeDecorated(legacyDecorators, location, location.parent, location.parent.parent)) { + return; + } + + return markDecoratorAliasReferenced(location); + } + default: + Debug.assertNever(hint, `Unhandled reference hint: ${hint}`); + } + } + + function markIdentifierAliasReferenced(location: Identifier) { + const symbol = getResolvedSymbol(location); + if (symbol && symbol !== argumentsSymbol && symbol !== unknownSymbol && !isThisInTypeQuery(location)) { + markAliasReferenced(symbol, location); + } + } + + function markPropertyAliasReferenced(location: PropertyAccessExpression | QualifiedName, propSymbol?: Symbol, parentType?: Type) { + const left = isPropertyAccessExpression(location) ? location.expression : location.left; + if (isThisIdentifier(left) || !isIdentifier(left)) { + return; + } + const parentSymbol = getResolvedSymbol(left); + if (!parentSymbol || parentSymbol === unknownSymbol) { + return; + } + // 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`. + // + // The property lookup is deferred as much as possible, in as many situations as possible, to avoid alias marking + // pulling on types/symbols it doesn't strictly need to. + if (getIsolatedModules(compilerOptions) || (shouldPreserveConstEnums(compilerOptions) && isExportOrExportExpression(location))) { + markAliasReferenced(parentSymbol, location); + return; + } + // Hereafter, this relies on type checking - but every check prior to this only used symbol information + const leftType = parentType || checkExpressionCached(left); + if (isTypeAny(leftType) || leftType === silentNeverType) { + markAliasReferenced(parentSymbol, location); + return; + } + let prop = propSymbol; + if (!prop && !parentType) { + const right = isPropertyAccessExpression(location) ? location.name : location.right; + const lexicallyScopedSymbol = isPrivateIdentifier(right) && lookupSymbolForPrivateIdentifierDeclaration(right.escapedText, right); + const assignmentKind = getAssignmentTargetKind(location); + const apparentType = getApparentType(assignmentKind !== AssignmentKind.None || isMethodAccessForCall(location) ? getWidenedType(leftType) : leftType); + prop = isPrivateIdentifier(right) ? lexicallyScopedSymbol && getPrivateIdentifierPropertyOfType(apparentType, lexicallyScopedSymbol) || undefined : getPropertyOfType(apparentType, right.escapedText); + } + if ( + !(prop && (isConstEnumOrConstEnumOnlyModule(prop) || prop.flags & SymbolFlags.EnumMember && location.parent.kind === SyntaxKind.EnumMember)) + ) { + markAliasReferenced(parentSymbol, location); + } + return; + } + + function markExportAssignmentAliasReferenced(location: ExportAssignment) { + if (isIdentifier(location.expression)) { + const id = location.expression; + const sym = getExportSymbolOfValueSymbolIfExported(resolveEntityName(id, SymbolFlags.All, /*ignoreErrors*/ true, /*dontResolveAlias*/ true, location)); + if (sym) { + markAliasReferenced(sym, id); + } + } + } + + function markJsxAliasReferenced(node: JsxOpeningLikeElement | JsxOpeningFragment) { + 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 = isJsxOpeningLikeElement(node) ? node.tagName : node; + + // allow null as jsxFragmentFactory + let jsxFactorySym: Symbol | undefined; + if (!(isJsxOpeningFragment(node) && jsxFactoryNamespace === "null")) { + jsxFactorySym = resolveName(jsxFactoryLocation, jsxFactoryNamespace, SymbolFlags.Value, jsxFactoryRefErr, /*isUse*/ true); + } + + 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; + + // If react/jsxFactory symbol is alias, mark it as refereced + if (canCollectSymbolAliasAccessabilityData && jsxFactorySym.flags & SymbolFlags.Alias && !getTypeOnlyAliasDeclaration(jsxFactorySym)) { + markAliasSymbolAsReferenced(jsxFactorySym); + } + } + + // 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, /*isUse*/ true); + } + } + } + return; + } + + function markAsyncFunctionAliasReferenced(location: FunctionLikeDeclaration | MethodSignature) { + if (languageVersion < ScriptTarget.ES2015) { + if (getFunctionFlags(location) & FunctionFlags.Async) { + const returnTypeNode = getEffectiveReturnTypeNode(location); + markTypeNodeAsReferenced(returnTypeNode); + } + } + } + + function markImportEqualsAliasReferenced(location: ImportEqualsDeclaration) { + if (hasSyntacticModifier(location, ModifierFlags.Export)) { + markExportAsReferenced(location); + } + } + + function markExportSpecifierAliasReferenced(location: ExportSpecifier) { + if (!location.parent.parent.moduleSpecifier && !location.isTypeOnly && !location.parent.parent.isTypeOnly) { + const exportedName = location.propertyName || location.name; + if (exportedName.kind === SyntaxKind.StringLiteral) { + return; // Skip for invalid syntax like this: export { "x" } + } + const symbol = resolveName(exportedName, exportedName.escapedText, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias, /*nameNotFoundMessage*/ undefined, /*isUse*/ true); + if (symbol && (symbol === undefinedSymbol || symbol === globalThisSymbol || symbol.declarations && isGlobalSourceFile(getDeclarationContainer(symbol.declarations[0])))) { + // Do nothing, non-local symbol + } + else { + const target = symbol && (symbol.flags & SymbolFlags.Alias ? resolveAlias(symbol) : symbol); + if (!target || getSymbolFlags(target) & SymbolFlags.Value) { + markExportAsReferenced(location); // marks export as used + markIdentifierAliasReferenced(exportedName); // marks target of export as used + } + } + return; + } + } + + function markDecoratorAliasReferenced(node: HasDecorators) { + if (compilerOptions.emitDecoratorMetadata) { + const firstDecorator = find(node.modifiers, isDecorator); + if (!firstDecorator) { + return; + } + + checkExternalEmitHelpers(firstDecorator, ExternalEmitHelpers.Metadata); + + // 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; + + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + const otherKind = node.kind === SyntaxKind.GetAccessor ? SyntaxKind.SetAccessor : SyntaxKind.GetAccessor; + const otherAccessor = getDeclarationOfKind(getSymbolOfDeclaration(node), otherKind); + markDecoratorMedataDataTypeNodeAsReferenced(getAnnotatedAccessorTypeNode(node) || otherAccessor && getAnnotatedAccessorTypeNode(otherAccessor)); + break; + case SyntaxKind.MethodDeclaration: + for (const parameter of node.parameters) { + markDecoratorMedataDataTypeNodeAsReferenced(getParameterTypeNodeForDecoratorCheck(parameter)); + } + + markDecoratorMedataDataTypeNodeAsReferenced(getEffectiveReturnTypeNode(node)); + break; + + case SyntaxKind.PropertyDeclaration: + markDecoratorMedataDataTypeNodeAsReferenced(getEffectiveTypeAnnotationNode(node)); + break; + + case SyntaxKind.Parameter: + markDecoratorMedataDataTypeNodeAsReferenced(getParameterTypeNodeForDecoratorCheck(node)); + const containingSignature = node.parent; + for (const parameter of containingSignature.parameters) { + markDecoratorMedataDataTypeNodeAsReferenced(getParameterTypeNodeForDecoratorCheck(parameter)); + } + markDecoratorMedataDataTypeNodeAsReferenced(getEffectiveReturnTypeNode(containingSignature)); + break; + } + } + } + + function markAliasReferenced(symbol: Symbol, location: Node) { + if (!canCollectSymbolAliasAccessabilityData) { + return; + } + if (isNonLocalAlias(symbol, /*excludes*/ SymbolFlags.Value) && !isInTypeQuery(location)) { + const target = resolveAlias(symbol); + if (getSymbolFlags(symbol, /*excludeTypeOnlyMeanings*/ true) & (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 ( + getIsolatedModules(compilerOptions) || + shouldPreserveConstEnums(compilerOptions) && isExportOrExportExpression(location) || + !isConstEnumOrConstEnumOnlyModule(getExportSymbolOfValueSymbolIfExported(target)) + ) { + 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) { + Debug.assert(canCollectSymbolAliasAccessabilityData); + 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 (getSymbolFlags(resolveSymbol(symbol)) & SymbolFlags.Value) { + // import foo = + const left = getFirstIdentifier(node.moduleReference as EntityNameExpression); + markIdentifierAliasReferenced(left); + } + } + } + } + + function markExportAsReferenced(node: ImportEqualsDeclaration | ExportSpecifier) { + const symbol = getSymbolOfDeclaration(node); + const target = resolveAlias(symbol); + if (target) { + const markAlias = target === unknownSymbol || + ((getSymbolFlags(symbol, /*excludeTypeOnlyMeanings*/ true) & SymbolFlags.Value) && !isConstEnumOrConstEnumOnlyModule(target)); + + if (markAlias) { + markAliasSymbolAsReferenced(symbol); + } + } + } + + 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, /*isUse*/ true); + if (rootSymbol && rootSymbol.flags & SymbolFlags.Alias) { + if ( + canCollectSymbolAliasAccessabilityData + && symbolIsValue(rootSymbol) + && !isConstEnumOrConstEnumOnlyModule(resolveAlias(rootSymbol)) + && !getTypeOnlyAliasDeclaration(rootSymbol) + ) { + markAliasSymbolAsReferenced(rootSymbol); + } + else if ( + forDecoratorMetadata + && getIsolatedModules(compilerOptions) + && 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))); + } + } + } + } + + /** + * 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 | undefined) { + markEntityNameOrEntityExpressionAsReference(node && getEntityNameFromTypeNode(node), /*forDecoratorMetadata*/ false); + } + + /** + * 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 getNarrowedTypeOfSymbol(symbol: Symbol, location: Identifier, checkMode?: CheckMode) { + const type = getTypeOfSymbol(symbol, checkMode); + 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; + const rootDeclaration = getRootDeclaration(parent); + if (rootDeclaration.kind === SyntaxKind.VariableDeclaration && getCombinedNodeFlagsCached(rootDeclaration) & NodeFlags.Constant || rootDeclaration.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 && !(rootDeclaration.kind === SyntaxKind.Parameter && isSomeSymbolAssigned(rootDeclaration))) { + const pattern = declaration.parent; + const narrowedType = getFlowTypeOfReference(pattern, parentTypeConstraint, parentTypeConstraint, /*flowContainer*/ undefined, location.flowNode); + if (narrowedType.flags & TypeFlags.Never) { + return neverType; + } + // Destructurings are validated against the parent type elsewhere. Here we disable tuple bounds + // checks because the narrowed type may have lower arity than the full parent type. For example, + // for the declaration [x, y]: [1, 2] | [3], we may have narrowed the parent type to just [3]. + return getBindingElementTypeFromParentType(declaration, narrowedType, /*noTupleBoundsCheck*/ true); + } + } + } + } + // 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(instantiateType(getTypeOfSymbol(contextualSignature.parameters[0]), getInferenceContext(func)?.nonFixingMapper)); + if (restType.flags & TypeFlags.Union && everyType(restType, isTupleType) && !some(func.parameters, isSomeSymbolAssigned)) { + 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 type; + } + + /** + * This part of `checkIdentifier` is kept seperate from the rest, so `NodeCheckFlags` (and related diagnostics) can be lazily calculated + * without calculating the flow type of the identifier. + */ + function checkIdentifierCalculateNodeCheckFlags(node: Identifier, symbol: Symbol) { + if (isThisInTypeQuery(node)) return; + + // 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; + } + + let container = getContainingFunction(node); + if (container) { + if (languageVersion < ScriptTarget.ES2015) { + if (container.kind === SyntaxKind.ArrowFunction) { + error(node, Diagnostics.The_arguments_object_cannot_be_referenced_in_an_arrow_function_in_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_ES5_Consider_using_a_standard_function_or_method); + } + } + + getNodeLinks(container).flags |= NodeCheckFlags.CaptureArguments; + while (container && isArrowFunction(container)) { + container = getContainingFunction(container); + if (container) { + getNodeLinks(container).flags |= NodeCheckFlags.CaptureArguments; + } + } + } + return; + } + + const localOrExportSymbol = getExportSymbolOfValueSymbolIfExported(symbol); + const targetSymbol = resolveAliasWithDeprecationCheck(localOrExportSymbol, node); + if (isDeprecatedSymbol(targetSymbol) && isUncalledFunctionReference(node, targetSymbol) && targetSymbol.declarations) { + addDeprecatedSuggestion(node, targetSymbol.declarations, node.escapedText as string); + } + + const declaration = localOrExportSymbol.valueDeclaration; + if (declaration && localOrExportSymbol.flags & SymbolFlags.Class) { + // When we downlevel classes we may emit some code outside of the class body. Due to the fact the + // class name is double-bound, we must ensure we mark references to the class name so that we can + // emit an alias to the class later. + if (isClassLike(declaration) && declaration.name !== node) { + let container = getThisContainer(node, /*includeArrowFunctions*/ false, /*includeClassComputedPropertyName*/ false); + while (container.kind !== SyntaxKind.SourceFile && container.parent !== declaration) { + container = getThisContainer(container, /*includeArrowFunctions*/ false, /*includeClassComputedPropertyName*/ false); + } + + if (container.kind !== SyntaxKind.SourceFile) { + getNodeLinks(declaration).flags |= NodeCheckFlags.ContainsConstructorReference; + getNodeLinks(container).flags |= NodeCheckFlags.ContainsConstructorReference; + getNodeLinks(node).flags |= NodeCheckFlags.ConstructorReference; + } + } + } + + checkNestedBlockScopedBinding(node, symbol); + } + + function checkIdentifier(node: Identifier, checkMode: CheckMode | undefined): Type { + if (isThisInTypeQuery(node)) { + return checkThisExpression(node); + } + + const symbol = getResolvedSymbol(node); + if (symbol === unknownSymbol) { + return errorType; + } + + checkIdentifierCalculateNodeCheckFlags(node, symbol); + + if (symbol === argumentsSymbol) { + if (isInPropertyInitializerOrClassStaticBlock(node)) { + return errorType; + } + return getTypeOfSymbol(symbol); + } + + if (shouldMarkIdentifierAliasReferenced(node)) { + markLinkedReferences(node, ReferenceHint.Identifier); + } + + const localOrExportSymbol = getExportSymbolOfValueSymbolIfExported(symbol); + let declaration = localOrExportSymbol.valueDeclaration; + + let type = getNarrowedTypeOfSymbol(localOrExportSymbol, node, checkMode); + 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; + + 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; + } + } + + 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 isInCompoundLikeAssignment(node) ? getBaseTypeOfLiteralType(type) : type; + } + } + else if (isAlias) { + declaration = getDeclarationOfAliasSymbol(symbol); + } + else { + return type; + } + + if (!declaration) { + return type; + } + + 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; + const typeIsAutomatic = type === autoType || type === autoArrayType; + const isAutomaticTypeInNonNull = typeIsAutomatic && node.parent.kind === SyntaxKind.NonNullExpression; + // When the control flow originates in a function expression, arrow function, method, or accessor, and + // we are referencing a closed-over const variable or parameter or mutable local variable past its last + // assignment, we extend the origin of the control flow analysis to include the immediately enclosing + // control flow container. + while ( + flowContainer !== declarationContainer && ( + flowContainer.kind === SyntaxKind.FunctionExpression || + flowContainer.kind === SyntaxKind.ArrowFunction || + isObjectLiteralOrClassExpressionMethodOrAccessor(flowContainer) + ) && ( + isConstantVariable(localOrExportSymbol) && type !== autoArrayType || + isParameterOrMutableLocalVariable(localOrExportSymbol) && isPastLastAssignment(localOrExportSymbol, node) + ) + ) { + 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) || isInAmbientOrTypeNode(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 = isAutomaticTypeInNonNull ? undefinedType : + assumeInitialized ? (isParameter ? removeOptionalityFromDeclaredType(type, declaration as VariableLikeDeclaration) : type) : + typeIsAutomatic ? undefinedType : getOptionalType(type); + const flowType = isAutomaticTypeInNonNull ? getNonNullableType(getFlowTypeOfReference(node, type, initialType, flowContainer)) : + 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 + return type; + } + return assignmentKind ? getBaseTypeOfLiteralType(flowType) : flowType; + } + + function isSameScopedBindingElement(node: Identifier, declaration: Declaration) { + if (isBindingElement(declaration)) { + const bindingElement = findAncestor(node, isBindingElement); + return bindingElement && getRootDeclaration(bindingElement) === getRootDeclaration(declaration); + } + } + + 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 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; + } + + // 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; + } + } + + // 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; + } + } + + // set 'declared inside loop' bit on the block-scoped binding + getNodeLinks(symbol.valueDeclaration).flags |= NodeCheckFlags.BlockScopedBindingInLoop; + } + + if (isCaptured) { + getNodeLinks(symbol.valueDeclaration).flags |= NodeCheckFlags.CapturedBlockScopedBinding; + } + } + + function isBindingCapturedByNode(node: Node, decl: VariableDeclaration | BindingElement) { + const links = getNodeLinks(node); + return !!links && contains(links.capturedBlockScopeBindings, getSymbolOfDeclaration(decl)); + } + + 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 (!isAssigned) { + return false; + } + + // 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 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: ClassLikeDeclaration): boolean { + const classSymbol = getSymbolOfDeclaration(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); + + // 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 (canHaveFlowNode(node) && node.flowNode && !isPostSuperFlowNode(node.flowNode, /*noCacheCheck*/ false)) { + error(node, diagnosticMessage); + } + } + } + + function checkThisInStaticClassFieldInitializerInDecoratedClass(thisExpression: Node, container: Node) { + if ( + isPropertyDeclaration(container) && hasStaticModifier(container) && legacyDecorators && + 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, /*includeClassComputedPropertyName*/ true); + let capturedByArrowFunction = false; + let thisInComputedPropertyName = false; + + if (container.kind === SyntaxKind.Constructor) { + checkThisBeforeSuper(node, container, Diagnostics.super_must_be_called_before_accessing_this_in_the_constructor_of_a_derived_class); + } + + while (true) { + // Now skip arrow functions to get the "real" owner of 'this'. + if (container.kind === SyntaxKind.ArrowFunction) { + container = getThisContainer(container, /*includeArrowFunctions*/ false, !thisInComputedPropertyName); + capturedByArrowFunction = true; + } + + if (container.kind === SyntaxKind.ComputedPropertyName) { + container = getThisContainer(container, !capturedByArrowFunction, /*includeClassComputedPropertyName*/ false); + thisInComputedPropertyName = true; + continue; + } + + break; + } + + checkThisInStaticClassFieldInitializerInDecoratedClass(node, container); + if (thisInComputedPropertyName) { + error(node, Diagnostics.this_cannot_be_referenced_in_a_computed_property_name); + } + else { + 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; + } + } + + // 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)); + } + } + } + } + return type || anyType; + } + + function tryGetThisTypeAt(node: Node, includeGlobalThis = true, container = getThisContainer(node, /*includeArrowFunctions*/ false, /*includeClassComputedPropertyName*/ 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; + } + thisType ||= getContextualThisParameterType(container); + } + + if (thisType) { + return getFlowTypeOfReference(node, thisType); + } + } + + if (isClassLike(container.parent)) { + const symbol = getSymbolOfDeclaration(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 = getSymbolOfDeclaration(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); + } + } + } + + function getExplicitThisType(node: Expression) { + const container = getThisContainer(node, /*includeArrowFunctions*/ false, /*includeClassComputedPropertyName*/ false); + if (isFunctionLike(container)) { + const signature = getSignatureFromDeclaration(container); + if (signature.thisParameter) { + return getExplicitTypeOfSymbol(signature.thisParameter); + } + } + if (isClassLike(container.parent)) { + const symbol = getSymbolOfDeclaration(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 getTypeForThisExpressionFromJSDoc(node: SignatureDeclaration) { + const thisTag = getJSDocThisTag(node); + if (thisTag && thisTag.typeExpression) { + return getTypeFromTypeNode(thisTag.typeExpression); + } + const signature = getSignatureOfTypeTag(node); + if (signature) { + return getThisTypeOfSignature(signature); + } + } + + function isInConstructorArgumentInitializer(node: Node, constructorDecl: Node): boolean { + return !!findAncestor(node, n => isFunctionLikeDeclaration(n) ? "quit" : n.kind === SyntaxKind.Parameter && n.parent === constructorDecl); + } + + function checkSuperExpression(node: Node): Type { + const isCallExpression = node.parent.kind === SyntaxKind.CallExpression && (node.parent as CallExpression).expression === node; + + const immediateContainer = getSuperContainer(node, /*stopOnFunctions*/ true); + let container = immediateContainer; + let needToCaptureLexicalThis = false; + let inAsyncFunction = false; + + // 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; + } + + let nodeCheckFlag: NodeCheckFlags = 0; + + if (!container || !isLegalUsageOfSuperExpression(container)) { + // 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; + } + + 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); + } + + 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; + } + + getNodeLinks(node).flags |= nodeCheckFlag; + + // 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 (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.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; + } + } + + // 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; + } + + if (classDeclarationExtendsNull(classLikeDeclaration)) { + return isCallExpression ? errorType : nullWideningType; + } + + const classType = getDeclaredTypeOfSymbol(getSymbolOfDeclaration(classLikeDeclaration)) as InterfaceType; + const baseClassType = classType && getBaseTypes(classType)[0]; + if (!baseClassType) { + return errorType; + } + + 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; + } + + return nodeCheckFlag === NodeCheckFlags.SuperStatic + ? getBaseConstructorTypeOfClass(classType) + : getTypeWithThisArgument(baseClassType, classType.thisType); + + function isLegalUsageOfSuperExpression(container: Node): boolean { + 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; + } + } + + 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 getThisTypeOfObjectLiteralFromContextualType(containingLiteral: ObjectLiteralExpression, contextualType: Type | undefined) { + let literal = containingLiteral; + let type = contextualType; + while (type) { + const thisType = getThisTypeFromContextualType(type); + if (thisType) { + return thisType; + } + if (literal.parent.kind !== SyntaxKind.PropertyAssignment) { + break; + } + literal = literal.parent.parent as ObjectLiteralExpression; + type = getApparentTypeOfContextualType(literal, /*contextFlags*/ undefined); + } + } + + function getContextualThisParameterType(func: SignatureDeclaration): Type | undefined { + if (func.kind === SyntaxKind.ArrowFunction) { + return undefined; + } + 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); + const thisType = getThisTypeOfObjectLiteralFromContextualType(containingLiteral, contextualType); + if (thisType) { + return instantiateType(thisType, getMapperFromContext(getInferenceContext(containingLiteral))); + } + // 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 (isAssignmentExpression(parent)) { + const target = parent.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)); + } + } + } + return undefined; + } + + // 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; + } + 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) || (isInJSFile(declaration) ? tryGetJSDocSatisfiesTypeNode(declaration) : undefined); + 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); + } + // By default, do nothing and return undefined - only the above cases have context implied by a parent + } + } + + 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); + } + } + + 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, getSymbolOfDeclaration(declaration).escapedName); + } + + // 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); + } + } + return undefined; + } + + 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 + } + + 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)]); + } + + return contextualReturnType; // Regular function or Generator function + } + } + 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)]); + } + return 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) { + const isAsyncGenerator = (functionFlags & FunctionFlags.Async) !== 0; + if (!node.asteriskToken && contextualReturnType.flags & TypeFlags.Union) { + contextualReturnType = filterType(contextualReturnType, type => !!getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Return, type, isAsyncGenerator)); + } + if (node.asteriskToken) { + const iterationTypes = getIterationTypesOfGeneratorFunctionReturnType(contextualReturnType, isAsyncGenerator); + const yieldType = iterationTypes?.yieldType ?? silentNeverType; + const returnType = getContextualType(node, contextFlags) ?? silentNeverType; + const nextType = iterationTypes?.nextType ?? unknownType; + const generatorType = createGeneratorType(yieldType, returnType, nextType, /*isAsyncGenerator*/ false); + if (isAsyncGenerator) { + const asyncGeneratorType = createGeneratorType(yieldType, returnType, nextType, /*isAsyncGenerator*/ true); + return getUnionType([generatorType, asyncGeneratorType]); + } + return generatorType; + } + return getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Yield, contextualReturnType, isAsyncGenerator); + } + } + + 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; + } + + node = node.parent; + } + + return false; + } + + 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; + } + + 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)) { + const returnType = getReturnTypeOfSignature(signature); + const functionFlags = getFunctionFlags(functionDecl); + if (functionFlags & FunctionFlags.Generator) { + return filterType(returnType, t => { + return !!(t.flags & (TypeFlags.AnyOrUnknown | TypeFlags.Void | TypeFlags.InstantiableNonPrimitive)) || checkGeneratorInstantiationAssignabilityToReturnType(t, functionFlags, /*errorNode*/ undefined); + }); + } + if (functionFlags & FunctionFlags.Async) { + return filterType(returnType, t => { + return !!(t.flags & (TypeFlags.AnyOrUnknown | TypeFlags.Void | TypeFlags.InstantiableNonPrimitive)) || !!getAwaitedTypeOfPromise(t); + }); + } + return returnType; + } + const iife = getImmediatelyInvokedFunctionExpression(functionDecl); + if (iife) { + return getContextualType(iife, contextFlags); + } + 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; + } + + // 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); + + 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 getContextualTypeForDecorator(decorator: Decorator): Type | undefined { + const signature = getDecoratorCallSignature(decorator); + return signature ? getOrCreateTypeFromSignature(signature) : undefined; + } + + function getContextualTypeForSubstitutionExpression(template: TemplateExpression, substitutionExpression: Expression) { + if (template.parent.kind === SyntaxKind.TaggedTemplateExpression) { + return getContextualTypeForArgument(template.parent as TaggedTemplateExpression, substitutionExpression); + } + + 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; + } + } + + /** + * 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 (canHaveSymbol(e) && 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; + + function tryGetPrivateIdentifierPropertyOfType(type: Type, id: PrivateIdentifier) { + const lexicallyScopedSymbol = lookupSymbolForPrivateIdentifierDeclaration(id.escapedText, id); + return lexicallyScopedSymbol && getPrivateIdentifierPropertyOfType(type, lexicallyScopedSymbol); + } + } + + // 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 (!canHaveSymbol(binaryExpression.left) || !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, /*nameNotFoundMessage*/ undefined, /*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) || decl === binaryExpression.left ? 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 = canHaveSymbol(binaryExpression.left) ? binaryExpression.left.symbol?.valueDeclaration : undefined; + } + 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 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, /*nameNotFoundMessage*/ undefined, /*isUse*/ true, /*excludeGlobals*/ true); + return isThisInitializedDeclaration(symbol?.valueDeclaration); + } + + 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, /*includeClassComputedPropertyName*/ false))) { + return undefined; + } + const thisType = checkThisExpression(thisAccess.expression); + const nameStr = getElementOrPropertyAccessName(thisAccess); + return nameStr !== undefined && getTypeOfPropertyOfContextualType(thisType, nameStr) || undefined; + } + + function isCircularMappedProperty(symbol: Symbol) { + return !!(getCheckFlags(symbol) & CheckFlags.Mapped && !(symbol as MappedSymbol).links.type && findResolutionCycleStartIndex(symbol, TypeSystemPropertyName.Type) >= 0); + } + + 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); + } + } + else if (t.flags & TypeFlags.StructuredType) { + const prop = getPropertyOfType(t, name); + if (prop) { + return isCircularMappedProperty(prop) ? undefined : removeMissingType(getTypeOfSymbol(prop), !!(prop.flags & SymbolFlags.Optional)); + } + if (isTupleType(t) && isNumericLiteralName(name) && +name >= 0) { + const restType = getElementTypeOfSliceOfTupleType(t, t.target.fixedLength, /*endSkipCount*/ 0, /*writing*/ false, /*noReductions*/ true); + if (restType) { + return restType; + } + } + 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 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 = getSymbolOfDeclaration(element); + return getTypeOfPropertyOfContextualType(type, symbol.escapedName, getSymbolLinks(symbol).nameType); + } + if (hasDynamicName(element)) { + const name = getNameOfDeclaration(element); + if (name && isComputedPropertyName(name)) { + const exprType = checkExpression(name.expression); + const propType = isTypeUsableAsPropertyName(exprType) && getTypeOfPropertyOfContextualType(type, getPropertyNameFromType(exprType)); + if (propType) { + return propType; + } + } + } + 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 getSpreadIndices(elements: readonly Node[]) { + let first, last; + for (let i = 0; i < elements.length; i++) { + if (isSpreadElement(elements[i])) { + first ??= i; + last = i; + } + } + return { first, last }; + } + + function getContextualTypeForElementExpression(type: Type | undefined, index: number, length?: number, firstSpreadIndex?: number, lastSpreadIndex?: number): Type | undefined { + return type && mapType(type, t => { + if (isTupleType(t)) { + // If index is before any spread element and within the fixed part of the contextual tuple type, return + // the type of the contextual tuple element. + if ((firstSpreadIndex === undefined || index < firstSpreadIndex) && index < t.target.fixedLength) { + return removeMissingType(getTypeArguments(t)[index], !!(t.target.elementFlags[index] && ElementFlags.Optional)); + } + // When the length is known and the index is after all spread elements we compute the offset from the element + // to the end and the number of ending fixed elements in the contextual tuple type. + const offset = length !== undefined && (lastSpreadIndex === undefined || index > lastSpreadIndex) ? length - index : 0; + const fixedEndLength = offset > 0 && (t.target.combinedFlags & ElementFlags.Variable) ? getEndElementCount(t.target, ElementFlags.Fixed) : 0; + // If the offset is within the ending fixed part of the contextual tuple type, return the type of the contextual + // tuple element. + if (offset > 0 && offset <= fixedEndLength) { + return getTypeArguments(t)[getTypeReferenceArity(t) - offset]; + } + // Return a union of the possible contextual element types with no subtype reduction. + return getElementTypeOfSliceOfTupleType(t, firstSpreadIndex === undefined ? t.target.fixedLength : Math.min(t.target.fixedLength, firstSpreadIndex), length === undefined || lastSpreadIndex === undefined ? fixedEndLength : Math.min(fixedEndLength, length - lastSpreadIndex), /*writing*/ false, /*noReductions*/ true); + } + // If element index is known and a contextual property with that name exists, return it. Otherwise return the + // iterated or element type of the contextual type. + return (!firstSpreadIndex || index < firstSpreadIndex) && getTypeOfPropertyOfContextualType(t, "" + index as __String) || + 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.attributes, 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 t; + } + }, /*noReductions*/ true)); + } + + 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 getTypeOfPropertyOfContextualType(attributesType, getEscapedTextOfJsxAttributeName(attribute.name)); + } + else { + return getContextualType(attribute.parent, contextFlags); + } + } + + // 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.TemplateExpression: + 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 discriminateContextualTypeByObjectMembers(node: ObjectLiteralExpression, contextualType: UnionType) { + const key = `D${getNodeId(node)},${getTypeId(contextualType)}`; + return getCachedType(key) ?? setCachedType( + key, + getMatchingUnionConstituentForObjectLiteral(contextualType, node) ?? discriminateTypeByDiscriminableItems( + contextualType, + concatenate( + map( + filter(node.properties, (p): p is PropertyAssignment | ShorthandPropertyAssignment => { + if (!p.symbol) { + return false; + } + if (p.kind === SyntaxKind.PropertyAssignment) { + return isPossiblyDiscriminantValue(p.initializer) && isDiscriminantProperty(contextualType, p.symbol.escapedName); + } + if (p.kind === SyntaxKind.ShorthandPropertyAssignment) { + return isDiscriminantProperty(contextualType, p.symbol.escapedName); + } + return false; + }), + prop => ([() => getContextFreeTypeOfExpression(prop.kind === SyntaxKind.PropertyAssignment ? prop.initializer : prop.name), prop.symbol.escapedName] as const), + ), + 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 const, + ), + ), + isTypeAssignableTo, + ), + ); + } + + function discriminateContextualTypeByJSXAttributes(node: JsxAttributes, contextualType: UnionType) { + const key = `D${getNodeId(node)},${getTypeId(contextualType)}`; + const cached = getCachedType(key); + if (cached) return cached; + const jsxChildrenPropertyName = getJsxElementChildrenPropertyName(getJsxNamespaceAt(node)); + return setCachedType( + key, + 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 const), + ), + map( + filter(getPropertiesOfType(contextualType), s => { + if (!(s.flags & SymbolFlags.Optional) || !node?.symbol?.members) { + return false; + } + const element = node.parent.parent; + if (s.escapedName === jsxChildrenPropertyName && isJsxElement(element) && getSemanticJsxChildren(element.children).length) { + return false; + } + return !node.symbol.members.has(s.escapedName) && isDiscriminantProperty(contextualType, s.escapedName); + }), + s => [() => undefinedType, s.escapedName] as const, + ), + ), + isTypeAssignableTo, + ), + ); + } + + // 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, + // When obtaining apparent type of *contextual* type we don't want to get apparent type of mapped types. + // That would evaluate mapped types with array or tuple type constraints too eagerly + // and thus it would prevent `getTypeOfPropertyOfContextualType` from obtaining per-position contextual type for elements of array literal expressions. + // Apparent type of other mapped types is already the mapped type itself so we can just avoid calling `getApparentType` here for all mapped types. + t => getObjectFlags(t) & ObjectFlags.Mapped ? t : getApparentType(t), + /*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; + } + } + 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; + } + + /** + * 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; + } + // Cached contextual types are obtained with no ContextFlags, so we can only consult them for + // requests with no ContextFlags. + const index = findContextualNode(node, /*includeCaches*/ !contextFlags); + if (index >= 0) { + return contextualTypes[index]; + } + 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 | Decorator, node); + case SyntaxKind.Decorator: + return getContextualTypeForDecorator(parent as Decorator); + case SyntaxKind.TypeAssertionExpression: + case SyntaxKind.AsExpression: + return isConstTypeReference((parent as AssertionExpression).type) ? getContextualType(parent as AssertionExpression, contextFlags) : 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); + const elementIndex = indexOfNode(arrayLiteral.elements, node); + const spreadIndices = getNodeLinks(arrayLiteral).spreadIndices ??= getSpreadIndices(arrayLiteral.elements); + return getContextualTypeForElementExpression(type, elementIndex, arrayLiteral.elements.length, spreadIndices.first, spreadIndices.last); + } + 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: { + if (isInJSFile(parent)) { + if (isJSDocSatisfiesExpression(parent)) { + return getTypeFromTypeNode(getJSDocSatisfiesExpressionType(parent)); + } + // Like in `checkParenthesizedExpression`, an `/** @type {xyz} */` comment before a parenthesized expression acts as a type cast. + const typeTag = getJSDocTypeTag(parent); + if (typeTag && !isConstTypeReference(typeTag.typeExpression.type)) { + return getTypeFromTypeNode(typeTag.typeExpression.type); + } + } + return getContextualType(parent as ParenthesizedExpression, contextFlags); + } + 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); + case SyntaxKind.ImportAttribute: + return getContextualImportAttributeType(parent as ImportAttribute); + } + return undefined; + } + + function pushCachedContextualType(node: Expression) { + pushContextualType(node, getContextualType(node, /*contextFlags*/ undefined), /*isCache*/ true); + } + + function pushContextualType(node: Expression, type: Type | undefined, isCache: boolean) { + contextualTypeNodes[contextualTypeCount] = node; + contextualTypes[contextualTypeCount] = type; + contextualIsCache[contextualTypeCount] = isCache; + contextualTypeCount++; + } + + function popContextualType() { + contextualTypeCount--; + } + + function findContextualNode(node: Node, includeCaches: boolean) { + for (let i = contextualTypeCount - 1; i >= 0; i--) { + if (node === contextualTypeNodes[i] && (includeCaches || !contextualIsCache[i])) { + return i; + } + } + return -1; + } + + function pushInferenceContext(node: Node, inferenceContext: InferenceContext | undefined) { + inferenceContextNodes[inferenceContextCount] = node; + inferenceContexts[inferenceContextCount] = inferenceContext; + inferenceContextCount++; + } + + function popInferenceContext() { + inferenceContextCount--; + } + + function getInferenceContext(node: Node) { + for (let i = inferenceContextCount - 1; i >= 0; i--) { + if (isNodeDescendantOf(node, inferenceContextNodes[i])) { + return inferenceContexts[i]; + } + } + } + + function getContextualImportAttributeType(node: ImportAttribute) { + return getTypeOfPropertyOfContextualType(getGlobalImportAttributesType(/*reportErrors*/ false), getNameFromImportAttribute(node)); + } + + function getContextualJsxElementAttributesType(node: JsxOpeningLikeElement, contextFlags: ContextFlags | undefined) { + if (isJsxOpeningElement(node) && contextFlags !== ContextFlags.Completions) { + const index = findContextualNode(node.parent, /*includeCaches*/ !contextFlags); + if (index >= 0) { + // 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 contextualTypes[index]; + } + } + return getContextualTypeForArgumentAtIndex(node, 0); + } + + function getEffectiveFirstArgumentForJsxSignature(signature: Signature, node: JsxOpeningLikeElement) { + return getJsxReferenceKind(node) !== JsxReferenceKind.Component + ? getJsxPropsTypeFromCallSignature(signature, node) + : getJsxPropsTypeFromClassType(signature, node); + } + + 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 instanceType = getReturnTypeOfSignature(sig); + return isTypeAny(instanceType) ? instanceType : getTypeOfPropertyOfType(instanceType, forcedLookupLocation); + } + + function getStaticTypeOfReferencedJsxConstructor(context: JsxOpeningLikeElement) { + if (isJsxIntrinsicTagName(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; + } + const fakeSignature = createSignatureForJSXIntrinsic(context, result); + return getOrCreateTypeFromSignature(fakeSignature); + } + return tagType; + } + + function getJsxManagedAttributesFromLocatedAttributes(context: JsxOpeningLikeElement, ns: Symbol, attributesType: Type) { + const managedSym = getJsxLibraryManagedAttributes(ns); + if (managedSym) { + const ctorType = getStaticTypeOfReferencedJsxConstructor(context); + const result = instantiateAliasOrInterfaceWithDefaults(managedSym, isInJSFile(context), ctorType, attributesType); + if (result) { + return result; + } + } + return attributesType; + } + + 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 unknownType; + } + + attributesType = getJsxManagedAttributesFromLocatedAttributes(context, ns, attributesType); + + 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)); + } + // 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; + } + } + + function getIntersectedSignatures(signatures: readonly Signature[]) { + return getStrictOptionValue(compilerOptions, "noImplicitAny") + ? reduceLeft( + signatures, + (left: Signature | undefined, 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 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.links.type = isRestParam ? createArrayType(unionParamType) : unionParamType; + params[i] = paramSymbol; + } + if (needsExtraRestElement) { + const restParamSymbol = createSymbol(SymbolFlags.FunctionScopedVariable, "args" as __String); + restParamSymbol.links.type = createArrayType(getTypeAtPosition(shorter, longestCount)); + if (shorter === right) { + restParamSymbol.links.type = instantiateType(restParamSymbol.links.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, + ); + 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 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; + } + } + if (target.parameters.length && parameterIsThisKeyword(target.parameters[0])) { + targetParameterCount--; + } + 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; + } + + // 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 checkGrammarRegularExpressionLiteral(node: RegularExpressionLiteral) { + const sourceFile = getSourceFileOfNode(node); + if (!hasParseDiagnostics(sourceFile) && !node.isUnterminated) { + let lastError: DiagnosticWithLocation | undefined; + scanner ??= createScanner(ScriptTarget.ESNext, /*skipTrivia*/ true); + scanner.setScriptTarget(sourceFile.languageVersion); + scanner.setLanguageVariant(sourceFile.languageVariant); + scanner.setOnError((message, length, arg0) => { + // For providing spelling suggestions + const start = scanner!.getTokenEnd(); + if (message.category === DiagnosticCategory.Message && lastError && start === lastError.start && length === lastError.length) { + const error = createDetachedDiagnostic(sourceFile.fileName, sourceFile.text, start, length, message, arg0); + addRelatedInfo(lastError, error); + } + else if (!lastError || start !== lastError.start) { + lastError = createFileDiagnostic(sourceFile, start, length, message, arg0); + diagnostics.add(lastError); + } + }); + scanner.setText(sourceFile.text, node.pos, node.end - node.pos); + try { + scanner.scan(); + Debug.assert(scanner.reScanSlashToken(/*reportErrors*/ true) === SyntaxKind.RegularExpressionLiteral, "Expected scanner to rescan RegularExpressionLiteral"); + return !!lastError; + } + finally { + scanner.setText(""); + scanner.setOnError(/*onError*/ undefined); + } + } + return false; + } + + function checkRegularExpressionLiteral(node: RegularExpressionLiteral) { + const nodeLinks = getNodeLinks(node); + if (!(nodeLinks.flags & NodeCheckFlags.TypeChecked)) { + nodeLinks.flags |= NodeCheckFlags.TypeChecked; + addLazyDiagnostic(() => checkGrammarRegularExpressionLiteral(node)); + } + return globalRegExpType; + } + + function checkSpreadExpression(node: SpreadElement, checkMode?: CheckMode): Type { + if (languageVersion < LanguageFeatureMinimumTarget.SpreadElements) { + checkExternalEmitHelpers(node, compilerOptions.downlevelIteration ? ExternalEmitHelpers.SpreadIncludes : ExternalEmitHelpers.SpreadArray); + } + + const arrayOrIterableType = checkExpression(node.expression, checkMode); + return checkIteratedTypeOrElementType(IterationUse.Spread, arrayOrIterableType, undefinedType, node.expression); + } + + 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 isSpreadIntoCallOrNew(node: ArrayLiteralExpression) { + const parent = walkUpParenthesizedExpressions(node.parent); + return isSpreadElement(parent) && isCallOrNewExpression(parent.parent); + } + + 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[] = []; + pushCachedContextualType(node); + const inDestructuringPattern = isAssignmentTarget(node); + const inConstContext = isConstContext(node); + const contextualType = getApparentTypeOfContextualType(node, /*contextFlags*/ undefined); + const inTupleContext = isSpreadIntoCallOrNew(node) || !!contextualType && someType(contextualType, t => isTupleLikeType(t) || isGenericMappedType(t) && !t.nameType && !!getHomomorphicTypeVariable(t.target as MappedType || t)); + + let hasOmittedExpression = false; + for (let i = 0; i < elementCount; i++) { + const e = elements[i]; + if (e.kind === SyntaxKind.SpreadElement) { + if (languageVersion < LanguageFeatureMinimumTarget.SpreadElements) { + 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); + } + } + else if (exactOptionalPropertyTypes && e.kind === SyntaxKind.OmittedExpression) { + hasOmittedExpression = true; + elementTypes.push(undefinedOrMissingType); + elementFlags.push(ElementFlags.Optional); + } + else { + const type = checkExpressionForMutableLocation(e, checkMode, forceTuple); + elementTypes.push(addOptionality(type, /*isProperty*/ true, hasOmittedExpression)); + elementFlags.push(hasOmittedExpression ? ElementFlags.Optional : ElementFlags.Required); + if (inTupleContext && 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); + } + } + } + popContextualType(); + if (inDestructuringPattern) { + return createTupleType(elementTypes, elementFlags); + } + if (forceTuple || inConstContext || inTupleContext) { + return createArrayLiteralType(createTupleType(elementTypes, elementFlags, /*readonly*/ inConstContext && !(contextualType && someType(contextualType, isMutableArrayLikeType)))); + } + 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; + } + + 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 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 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 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])); + } + } + 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 checkObjectLiteral(node: ObjectLiteralExpression, checkMode: CheckMode = CheckMode.Normal): 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; + + pushCachedContextualType(node); + 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 = isInJavascript ? getJSDocEnumTag(node) : undefined; + const isJSObjectLiteral = !contextualType && isInJavascript && !enumTag; + let objectFlags: ObjectFlags = ObjectFlags.FreshLiteral; + 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 = getSymbolOfDeclaration(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.links.nameType = nameType; + } + + 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; + } + else if (!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)); + } + } + + prop.declarations = member.declarations; + prop.parent = member.parent; + if (member.valueDeclaration) { + prop.valueDeclaration = member.valueDeclaration; + } + + prop.links.type = type; + prop.links.target = member; + member = prop; + allPropertiesTable?.set(prop.escapedName, prop); + + if ( + contextualType && 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 < LanguageFeatureMinimumTarget.ObjectAssign) { + 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, checkMode & CheckMode.Inferential)); + 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; + } + 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); + } + popContextualType(); + + // 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 || tryCast(prop, isTransientSymbol)?.links.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); + } + } + } + } + + if (isErrorType(spread)) { + return errorType; + } + + 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); + } + + 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; + } + if (patternWithComputedProperties) { + result.objectFlags |= ObjectFlags.ObjectLiteralPatternWithComputedProperties; + } + if (inDestructuringPattern) { + result.pattern = node; + } + return result; + } + } + + 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 checkJsxSelfClosingElementDeferred(node: JsxSelfClosingElement) { + checkJsxOpeningLikeElementOrOpeningFragment(node); + } + + function checkJsxSelfClosingElement(node: JsxSelfClosingElement, _checkMode: CheckMode | undefined): Type { + checkNodeDeferred(node); + return getJsxElementTypeAt(node) || anyType; + } + + function checkJsxElementDeferred(node: JsxElement) { + // Check attributes + checkJsxOpeningLikeElementOrOpeningFragment(node.openingElement); + + // Perform resolution on the closing tag so that rename/go to definition/etc work + if (isJsxIntrinsicTagName(node.closingElement.tagName)) { + getIntrinsicTagSymbol(node.closingElement); + } + else { + checkExpression(node.closingElement.tagName); + } + + checkJsxChildren(node); + } + + function checkJsxElement(node: JsxElement, _checkMode: CheckMode | undefined): Type { + checkNodeDeferred(node); + + return getJsxElementTypeAt(node) || anyType; + } + + function checkJsxFragment(node: JsxFragment): Type { + checkJsxOpeningLikeElementOrOpeningFragment(node.openingFragment); + + // 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, + ); + } + + checkJsxChildren(node); + return getJsxElementTypeAt(node) || anyType; + } + + function isHyphenatedJsxName(name: string | __String) { + return (name as string).includes("-"); + } + + /** + * Returns true iff React would emit this tag name as a string rather than an identifier or qualified name + */ + function isJsxIntrinsicTagName(tagName: Node): tagName is Identifier | JsxNamespacedName { + return isIdentifier(tagName) && isIntrinsicJsxName(tagName.escapedText) || isJsxNamespacedName(tagName); + } + + function checkJsxAttribute(node: JsxAttribute, checkMode?: CheckMode) { + return node.initializer + ? checkExpressionForMutableLocation(node.initializer, checkMode) + : trueType; // is sugar for + } + + /** + * 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 = CheckMode.Normal) { + const attributes = openingLikeElement.attributes; + const contextualType = 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.links.type = exprType; + attributeSymbol.links.target = member; + attributesTable.set(attributeSymbol.escapedName, attributeSymbol); + allAttributesTable?.set(attributeSymbol.escapedName, attributeSymbol); + if (getEscapedTextOfJsxAttributeName(attributeDecl.name) === jsxChildrenPropertyName) { + explicitlySpecifyChildrenAttribute = true; + } + if (contextualType) { + const prop = getPropertyOfType(contextualType, member.escapedName); + if (prop && prop.declarations && isDeprecatedSymbol(prop) && isIdentifier(attributeDecl.name)) { + addDeprecatedSuggestion(attributeDecl.name, prop.declarations, attributeDecl.name.escapedText as string); + } + } + if (contextualType && checkMode & CheckMode.Inferential && !(checkMode & CheckMode.SkipContextSensitive) && isContextSensitive(attributeDecl)) { + const inferenceContext = getInferenceContext(attributes); + Debug.assert(inferenceContext); // In CheckMode.Inferential we should always have an inference context + const inferenceNode = (attributeDecl.initializer as JsxExpression).expression!; + addIntraExpressionInferenceSite(inferenceContext, inferenceNode, exprType); + } + } + 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(checkExpression(attributeDecl.expression, checkMode & CheckMode.Inferential)); + 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; + } + } + } + + if (!hasSpreadAnyType) { + if (attributesTable.size > 0) { + spread = getSpreadType(spread, createJsxAttributesType(), attributes.symbol, objectFlags, /*readonly*/ false); + } + } + + // 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 && getSemanticJsxChildren(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.links.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); + } + } + + if (hasSpreadAnyType) { + return anyType; + } + if (typeToIntersect && spread !== emptyJsxObjectType) { + return getIntersectionType([typeToIntersect, spread]); + } + return typeToIntersect || (spread === emptyJsxObjectType ? createJsxAttributesType() : spread); + + /** + * 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 |= ObjectFlags.FreshLiteral; + const result = createAnonymousType(attributes.symbol, attributesTable, emptyArray, emptyArray, emptyArray); + result.objectFlags |= objectFlags | ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral; + return result; + } + } + + 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); + } + } + else if (child.kind === SyntaxKind.JsxExpression && !child.expression) { + continue; // empty jsx expressions don't *really* count as present children + } + else { + childrenTypes.push(checkExpressionForMutableLocation(child, checkMode)); + } + } + 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)); + } + } + } + } + + /** + * 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); + } + + 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) && !isJsxNamespacedName(node.tagName)) return Debug.fail(); + const propName = isJsxNamespacedName(node.tagName) ? getEscapedTextOfJsxNamespacedName(node.tagName) : node.tagName.escapedText; + const intrinsicProp = getPropertyOfType(intrinsicElementsType, propName); + if (intrinsicProp) { + links.jsxFlags |= JsxFlags.IntrinsicNamedElement; + return links.resolvedSymbol = intrinsicProp; + } + + // Intrinsic string indexer case + const indexSymbol = getApplicableIndexSymbol(intrinsicElementsType, getStringLiteralType(unescapeLeadingUnderscores(propName))); + if (indexSymbol) { + links.jsxFlags |= JsxFlags.IntrinsicIndexedElement; + return links.resolvedSymbol = indexSymbol; + } + + if (getTypeOfPropertyOrIndexSignatureOfType(intrinsicElementsType, propName)) { + links.jsxFlags |= JsxFlags.IntrinsicIndexedElement; + return links.resolvedSymbol = intrinsicElementsType.symbol; + } + + // Wasn't found + error(node, Diagnostics.Property_0_does_not_exist_on_type_1, intrinsicTagNameToString(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 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; + } + 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_nodenext_or_to_add_aliases_to_the_paths_option + : Diagnostics.Cannot_find_module_0_or_its_corresponding_type_declarations; + const specifier = getJSXRuntimeImportSpecifier(file, runtimeImportSpecifier); + const mod = resolveExternalModule(specifier || location!, runtimeImportSpecifier, errorMessage, location!); + const result = mod && mod !== unknownSymbol ? getMergedSymbol(resolveSymbol(mod)) : undefined; + if (links) { + links.jsxImplicitImportContainer = result || false; + } + return result; + } + + 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); + + if (!resolvedNamespace || resolvedNamespace === unknownSymbol) { + const namespaceName = getJsxNamespace(location); + resolvedNamespace = resolveName(location, namespaceName, SymbolFlags.Namespace, /*nameNotFoundMessage*/ undefined, /*isUse*/ false); + } + + if (resolvedNamespace) { + const candidate = resolveSymbol(getSymbol(getExportsOfSymbol(resolveSymbol(resolvedNamespace)), JsxNames.JSX, SymbolFlags.Namespace)); + if (candidate && candidate !== unknownSymbol) { + if (links) { + links.jsxNamespace = candidate; + } + return candidate; + } + } + if (links) { + links.jsxNamespace = false; + } + } + // JSX global fallback + const s = resolveSymbol(getGlobalSymbol(JsxNames.JSX, SymbolFlags.Namespace, /*diagnostic*/ undefined)); + if (s === unknownSymbol) { + return undefined!; // TODO: GH#18217 + } + return s!; // TODO: GH#18217 + } + + /** + * 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); + } + + function getJsxElementTypeSymbol(jsxNamespace: Symbol) { + // JSX.ElementType [symbol] + return jsxNamespace && getSymbol(jsxNamespace.exports!, JsxNames.ElementType, 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 { + 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 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; + } + + 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); + } + } + 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 generateInitialErrorChain(): DiagnosticMessageChain { + const componentName = getTextOfNode(openingLikeElement.tagName); + return chainDiagnosticMessages(/*details*/ undefined, Diagnostics._0_cannot_be_used_as_a_JSX_component, componentName); + } + } + + /** + * 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(isJsxIntrinsicTagName(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) { + const propName = isJsxNamespacedName(node.tagName) ? getEscapedTextOfJsxNamespacedName(node.tagName) : node.tagName.escapedText; + return links.resolvedJsxElementAttributesType = getApplicableIndexInfoForName(getJsxType(JsxNames.IntrinsicElements, node), propName)?.type || errorType; + } + else { + return links.resolvedJsxElementAttributesType = errorType; + } + } + return links.resolvedJsxElementAttributesType; + } + + function getJsxElementClassTypeAt(location: Node): Type | undefined { + const type = getJsxType(JsxNames.ElementClass, location); + if (isErrorType(type)) return undefined; + return type; + } + + function getJsxElementTypeAt(location: Node): Type { + return getJsxType(JsxNames.Element, location); + } + + function getJsxStatelessElementTypeAt(location: Node): Type | undefined { + const jsxElementType = getJsxElementTypeAt(location); + if (jsxElementType) { + return getUnionType([jsxElementType, nullType]); + } + } + + function getJsxElementTypeTypeAt(location: Node): Type | undefined { + const ns = getJsxNamespaceAt(location); + if (!ns) return undefined; + const sym = getJsxElementTypeSymbol(ns); + if (!sym) return undefined; + const type = instantiateAliasOrInterfaceWithDefaults(sym, isInJSFile(location)); + if (!type || isErrorType(type)) return undefined; + return type; + } + + function instantiateAliasOrInterfaceWithDefaults(managedSym: Symbol, inJs: boolean, ...typeArguments: Type[]) { + const declaredManagedType = getDeclaredTypeOfSymbol(managedSym); // fetches interface type, or initializes symbol links type parmaeters + if (managedSym.flags & SymbolFlags.TypeAlias) { + const params = getSymbolLinks(managedSym).typeParameters; + if (length(params) >= typeArguments.length) { + const args = fillMissingTypeArguments(typeArguments, params, typeArguments.length, inJs); + return length(args) === 0 ? declaredManagedType : getTypeAliasInstantiation(managedSym, args); + } + } + if (length((declaredManagedType as GenericType).typeParameters) >= typeArguments.length) { + const args = fillMissingTypeArguments(typeArguments, (declaredManagedType as GenericType).typeParameters, typeArguments.length, inJs); + return createTypeReference(declaredManagedType as GenericType, args); + } + return undefined; + } + + /** + * 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 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 (getJsxElementTypeAt(errorNode) === undefined) { + if (noImplicitAny) { + error(errorNode, Diagnostics.JSX_element_implicitly_has_type_any_because_the_global_type_JSX_Element_does_not_exist); + } + } + } + + function checkJsxOpeningLikeElementOrOpeningFragment(node: JsxOpeningLikeElement | JsxOpeningFragment) { + const isNodeOpeningLikeElement = isJsxOpeningLikeElement(node); + + if (isNodeOpeningLikeElement) { + checkGrammarJsxElement(node); + } + + checkJsxPreconditions(node); + + markLinkedReferences(node, ReferenceHint.Jsx); + + if (isNodeOpeningLikeElement) { + const jsxOpeningLikeNode = node; + const sig = getResolvedSignature(jsxOpeningLikeNode); + checkDeprecatedSignature(sig, node); + + const elementTypeConstraint = getJsxElementTypeTypeAt(jsxOpeningLikeNode); + if (elementTypeConstraint !== undefined) { + const tagName = jsxOpeningLikeNode.tagName; + const tagType = isJsxIntrinsicTagName(tagName) + ? getStringLiteralType(intrinsicTagNameToString(tagName)) + : checkExpression(tagName); + checkTypeRelatedTo(tagType, elementTypeConstraint, assignableRelation, tagName, Diagnostics.Its_type_0_is_not_a_valid_JSX_element_type, () => { + const componentName = getTextOfNode(tagName); + return chainDiagnosticMessages(/*details*/ undefined, Diagnostics._0_cannot_be_used_as_a_JSX_component, componentName); + }); + } + else { + checkJsxReturnAssignableToAppropriateBound(getJsxReferenceKind(jsxOpeningLikeNode), getReturnTypeOfSignature(sig), jsxOpeningLikeNode); + } + } + } + + /** + * 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 (targetType.flags & TypeFlags.Substitution) { + return isKnownProperty((targetType as SubstitutionType).baseType, name, isComparingJsxAttributes); + } + 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 isExcessPropertyCheckTarget(type: Type): boolean { + return !!(type.flags & TypeFlags.Object && !(getObjectFlags(type) & ObjectFlags.ObjectLiteralPatternWithComputedProperties) || + type.flags & TypeFlags.NonPrimitive || + type.flags & TypeFlags.Substitution && isExcessPropertyCheckTarget((type as SubstitutionType).baseType) || + type.flags & TypeFlags.Union && some((type as UnionType).types, isExcessPropertyCheckTarget) || + type.flags & TypeFlags.Intersection && every((type as IntersectionType).types, isExcessPropertyCheckTarget)); + } + + 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; + } + } + + function getDeclarationNodeFlagsFromSymbol(s: Symbol): NodeFlags { + return s.valueDeclaration ? getCombinedNodeFlagsCached(s.valueDeclaration) : 0; + } + + /** + * 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 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); + } + + /** + * 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.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. + if (errorNode) { + error(errorNode, Diagnostics.Abstract_method_0_in_class_1_cannot_be_accessed_via_super_expression, symbolToString(prop), typeToString(getDeclaringClass(prop)!)); + } + return false; + } + // A class field cannot be accessed via super.* from a derived class. + // This is true for both [[Set]] (old) and [[Define]] (ES spec) semantics. + if (!(flags & ModifierFlags.Static) && prop.declarations?.some(isClassInstanceProperty)) { + if (errorNode) { + error(errorNode, Diagnostics.Class_field_0_defined_by_the_parent_class_is_not_accessible_in_the_child_class_via_super, symbolToString(prop)); + } + 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; + } + } + + // Public properties are otherwise accessible. + if (!(flags & ModifierFlags.NonPublicAccessibilityModifier)) { + return true; + } + + // 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 false; + } + return true; + } + + // 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; + } + + // 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(getSymbolOfDeclaration(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)) { + 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 false; + } + 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; + } + return undefined; + } + + function getThisParameterFromNodeContext(node: Node) { + const thisContainer = getThisContainer(node, /*includeArrowFunctions*/ false, /*includeClassComputedPropertyName*/ false); + return thisContainer && isFunctionLike(thisContainer) ? getThisParameter(thisContainer) : undefined; + } + + function symbolHasNonMethodDeclaration(symbol: Symbol) { + return !!forEachProperty(symbol, prop => !(prop.flags & SymbolFlags.Method)); + } + + function checkNonNullExpression(node: Expression | QualifiedName) { + return checkNonNullType(checkExpression(node), node); + } + + function isNullableType(type: Type) { + return hasTypeFacts(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; + } + 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, + ); + } + } + + 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, TypeFacts.IsUndefinedOrNull); + if (facts & TypeFacts.IsUndefinedOrNull) { + reportError(node, facts); + const t = getNonNullableType(type); + return t.flags & (TypeFlags.Nullable | TypeFlags.Never) ? errorType : t; + } + return type; + } + + 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; + } + + function checkPropertyAccessExpression(node: PropertyAccessExpression, checkMode: CheckMode | undefined, writeOnly?: boolean) { + return node.flags & NodeFlags.OptionalChain ? checkPropertyAccessChain(node as PropertyAccessChain, checkMode) : + checkPropertyAccessExpressionOrQualifiedName(node, node.expression, checkNonNullExpression(node.expression), node.name, checkMode, writeOnly); + } + + 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 isMethodAccessForCall(node: Node) { + while (node.parent.kind === SyntaxKind.ParenthesizedExpression) { + node = node.parent; + } + return isCallOrNewExpression(node.parent) && node.parent.expression === node; + } + + // Lookup the private identifier lexically. + function lookupSymbolForPrivateIdentifierDeclaration(propName: __String, location: Node): Symbol | undefined { + for (let containingClass = getContainingClassExcludingClassDecorators(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); + } + + 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)); + } + } + + return false; + } + + function checkPrivateIdentifierExpression(privId: PrivateIdentifier): Type { + checkGrammarPrivateIdentifierExpression(privId); + const symbol = getSymbolForPrivateIdentifierExpression(privId); + if (symbol) { + markPropertyAsReferenced(symbol, /*nodeForCheckWriteOnly*/ undefined, /*isSelfTypeAccess*/ false); + } + return anyType; + } + + function getSymbolForPrivateIdentifierExpression(privId: PrivateIdentifier): Symbol | undefined { + if (!isExpressionNode(privId)) { + return undefined; + } + + const links = getNodeLinks(privId); + if (links.resolvedSymbol === undefined) { + links.resolvedSymbol = lookupSymbolForPrivateIdentifierDeclaration(privId.escapedText, privId); + } + return links.resolvedSymbol; + } + + function getPrivateIdentifierPropertyOfType(leftType: Type, lexicallyScopedIdentifier: Symbol): Symbol | undefined { + return getPropertyOfType(leftType, lexicallyScopedIdentifier.escapedName); + } + + 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), + ); + + 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; + } + + function isThisPropertyAccessInConstructor(node: ElementAccessExpression | PropertyAccessExpression | QualifiedName, prop: Symbol) { + return (isConstructorDeclaredProperty(prop) || isThisProperty(node) && isAutoTypedProperty(prop)) + && getThisContainer(node, /*includeArrowFunctions*/ true, /*includeClassComputedPropertyName*/ false) === getDeclaringConstructor(prop); + } + + function checkPropertyAccessExpressionOrQualifiedName(node: PropertyAccessExpression | QualifiedName, left: Expression | QualifiedName, leftType: Type, right: Identifier | PrivateIdentifier, checkMode: CheckMode | undefined, writeOnly?: boolean) { + 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 < LanguageFeatureMinimumTarget.PrivateNamesAndClassStaticBlocks || + languageVersion < LanguageFeatureMinimumTarget.ClassAndClassElementDecorators || + !useDefineForClassFields + ) { + if (assignmentKind !== AssignmentKind.None) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.ClassPrivateFieldSet); + } + if (assignmentKind !== AssignmentKind.Definite) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.ClassPrivateFieldGet); + } + } + + 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)); + } + if (isAnyLike) { + if (lexicallyScopedSymbol) { + return isErrorType(apparentType) ? errorType : apparentType; + } + if (getContainingClassExcludingClassDecorators(right) === undefined) { + grammarErrorOnNode(right, Diagnostics.Private_identifiers_are_not_allowed_outside_class_bodies); + return anyType; + } + } + + prop = lexicallyScopedSymbol && getPrivateIdentifierPropertyOfType(leftType, lexicallyScopedSymbol); + if (prop === undefined) { + // Check for private-identifier-specific shadowing and lexical-scoping errors. + if (checkPrivateIdentifierPropertyAccess(leftType, right, lexicallyScopedSymbol)) { + return errorType; + } + const containingClass = getContainingClassExcludingClassDecorators(right); + if (containingClass && isPlainJsFile(getSourceFileOfNode(containingClass), compilerOptions.checkJs)) { + grammarErrorOnNode(right, Diagnostics.Private_field_0_must_be_declared_in_an_enclosing_class, idText(right)); + } + } + else { + const isSetonlyAccessor = prop.flags & SymbolFlags.SetAccessor && !(prop.flags & SymbolFlags.GetAccessor); + if (isSetonlyAccessor && assignmentKind !== AssignmentKind.Definite) { + error(node, Diagnostics.Private_accessor_was_defined_without_a_getter); + } + } + } + else { + if (isAnyLike) { + if (isIdentifier(left) && parentSymbol) { + markLinkedReferences(node, ReferenceHint.Property, /*propSymbol*/ undefined, leftType); + } + return isErrorType(apparentType) ? errorType : apparentType; + } + prop = getPropertyOfType(apparentType, right.escapedText, /*skipObjectFunctionPropertyAugment*/ isConstEnumObjectType(apparentType), /*includeTypeOnlyMembers*/ node.kind === SyntaxKind.QualifiedName); + } + markLinkedReferences(node, ReferenceHint.Property, prop, leftType); + + 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 = indexInfo.type; + if (compilerOptions.noUncheckedIndexedAccess && getAssignmentTargetKind(node) !== AssignmentKind.Definite) { + propType = getUnionType([propType, missingType]); + } + 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 (indexInfo.declaration && isDeprecatedDeclaration(indexInfo.declaration)) { + addDeprecatedSuggestion(right, [indexInfo.declaration], right.escapedText as string); + } + } + else { + const targetPropSymbol = resolveAliasWithDeprecationCheck(prop, right); + if (isDeprecatedSymbol(targetPropSymbol) && isUncalledFunctionReference(node, targetPropSymbol) && targetPropSymbol.declarations) { + addDeprecatedSuggestion(right, targetPropSymbol.declarations, right.escapedText as string); + } + checkPropertyNotUsedBeforeDeclaration(prop, node, right); + markPropertyAsReferenced(prop, node, isSelfTypeAccess(left, parentSymbol)); + getNodeLinks(node).resolvedSymbol = prop; + checkPropertyAccessibility(node, left.kind === SyntaxKind.SuperKeyword, isWriteAccess(node), 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; + } + + propType = isThisPropertyAccessInConstructor(node, prop) ? autoType : writeOnly || isWriteOnlyAccess(node) ? 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); + const suggestionHasNoExtendsOrDecorators = !suggestion?.valueDeclaration + || !isClassLike(suggestion.valueDeclaration) + || suggestion.valueDeclaration.heritageClauses?.length + || classOrConstructorParameterIsDecorated(/*useLegacyDecorators*/ false, suggestion.valueDeclaration); + return !(file !== declarationFile && !!declarationFile && isGlobalSourceFile(declarationFile)) + && !(excludeClasses && suggestion && suggestion.flags & SymbolFlags.Class && suggestionHasNoExtendsOrDecorators) + && !(!!node && excludeClasses && isPropertyAccessExpression(node) && node.expression.kind === SyntaxKind.ThisKeyword && suggestionHasNoExtendsOrDecorators); + } + } + 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; + } + + let diagnosticMessage; + const declarationName = idText(right); + if ( + isInPropertyInitializerOrClassStaticBlock(node) + && !isOptionalPropertyDeclaration(valueDeclaration) + && !(isAccessExpression(node) && isAccessExpression(node.expression)) + && !isBlockScopedNameDeclaredBeforeUse(valueDeclaration, right) + && !(isMethodDeclaration(valueDeclaration) && getCombinedModifierFlagsCached(valueDeclaration) & ModifierFlags.Static) + && (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); + } + + if (diagnosticMessage) { + addRelatedInfo(diagnosticMessage, createDiagnosticForNode(valueDeclaration, Diagnostics._0_is_declared_here, declarationName)); + } + } + + 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"; + } + }); + } + + /** + * 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; + } + } + } + + function getSuperClass(classType: InterfaceType): Type | undefined { + const x = getBaseTypes(classType); + if (x.length === 0) { + return undefined; + } + return getIntersectionType(x); + } + + 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); + } + 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); + } + } + } + } + const resultDiagnostic = createDiagnosticForNodeFromMessageChain(getSourceFileOfNode(propNode), 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 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 typeFeatures = allFeatures.get(missingName); + return typeFeatures && firstIterator(typeFeatures.keys()); + } + + function getSuggestedLibForNonExistentProperty(missingProperty: string, containingType: Type) { + const container = getApparentType(containingType).symbol; + if (!container) { + return undefined; + } + const containingTypeName = symbolName(container); + const allFeatures = getScriptTargetFeatures(); + const typeFeatures = allFeatures.get(containingTypeName); + if (typeFeatures) { + for (const [libTarget, featuresOfType] of typeFeatures) { + if (contains(featuresOfType, missingProperty)) { + return libTarget; + } + } + } + } + + function getSuggestedSymbolForNonexistentClassMember(name: string, baseType: Type): Symbol | undefined { + return getSpellingSuggestionForName(name, getPropertiesOfType(baseType), SymbolFlags.ClassMember); + } + + 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 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 getSuggestionForSymbolNameLookup(symbols: SymbolTable, name: __String, meaning: SymbolFlags) { + 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); + } + function getSuggestedSymbolForNonexistentSymbol(location: Node | undefined, outerName: __String, meaning: SymbolFlags): Symbol | undefined { + Debug.assert(outerName !== undefined, "outername should always be defined"); + const result = resolveNameForSymbolSuggestion(location, outerName, meaning, /*nameNotFoundMessage*/ undefined, /*isUse*/ false, /*excludeGlobals*/ false); + return result; + } + + function getSuggestedSymbolForNonexistentModule(name: Identifier, targetModule: Symbol): Symbol | undefined { + return targetModule.exports && getSpellingSuggestionForName(idText(name), getExportsOfModuleAsArray(targetModule), SymbolFlags.ModuleMember); + } + + 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; + } + + const suggestedMethod = isAssignmentTarget(expr) ? "set" : "get"; + if (!hasProp(suggestedMethod)) { + return undefined; + } + + let suggestion = tryGetPropertyAccessOrIdentifierToString(expr.expression); + if (suggestion === undefined) { + suggestion = suggestedMethod; + } + else { + suggestion += "." + suggestedMethod; + } + + return 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); + } + + /** + * 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; + } + + if (candidate.flags & meaning) { + return candidateName; + } + + if (candidate.flags & SymbolFlags.Alias) { + const alias = tryResolveAlias(candidate); + if (alias && alias.flags & meaning) { + return 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 (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; + } + } + + (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)); + } + } + + /** + * 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. + } + + function isValidPropertyAccessWithType( + node: PropertyAccessExpression | QualifiedName | ImportTypeNode, + isSuper: boolean, + propertyName: __String, + type: Type, + ): boolean { + // Short-circuiting for improved performance. + if (isTypeAny(type)) { + return true; + } + + const prop = getPropertyOfType(type, propertyName); + return !!prop && isPropertyAccessible(node, isSuper, /*isWrite*/ false, type, prop); + } + + /** + * 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; + } + + // 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 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 getSymbolOfDeclaration(variable); + } + } + else if (initializer.kind === SyntaxKind.Identifier) { + return getResolvedSymbol(initializer as Identifier); + } + 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); + } + + /** + * 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); + } + + 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); + + if (isErrorType(objectType) || objectType === silentNeverType) { + return objectType; + } + + if (isConstEnumObjectType(objectType) && !isStringLiteralLike(indexExpression)) { + error(indexExpression, Diagnostics.A_const_enum_member_can_only_be_accessed_using_a_string_literal); + return errorType; + } + + const effectiveIndexType = isForInVariableForNumericPropertyNames(indexExpression) ? numberType : indexType; + const assignmentTargetKind = getAssignmentTargetKind(node); + let accessFlags: AccessFlags; + if (assignmentTargetKind === AssignmentKind.None) { + accessFlags = AccessFlags.ExpressionPosition; + } + else { + accessFlags = AccessFlags.Writing | (isGenericObjectType(objectType) && !isThisTypeParameter(objectType) ? AccessFlags.NoIndexSignatures : 0); + if (assignmentTargetKind === AssignmentKind.Compound) { + accessFlags |= AccessFlags.ExpressionPosition; + } + } + const indexedAccessType = getIndexedAccessTypeOrUndefined(objectType, effectiveIndexType, accessFlags, node) || errorType; + return checkIndexedAccessIndexType(getFlowTypeOfAccessExpression(node, getNodeLinks(node).resolvedSymbol, indexedAccessType, indexExpression, checkMode), node); + } + + function callLikeExpressionMayHaveTypeArguments(node: CallLikeExpression): node is CallExpression | NewExpression | TaggedTemplateExpression | JsxOpeningElement { + return isCallOrNewExpression(node) || isTaggedTemplateExpression(node) || isJsxOpeningLikeElement(node); + } + + 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 (isBinaryExpression(node)) { + checkExpression(node.left); + } + else if (isCallOrNewExpression(node)) { + forEach(node.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 && getSymbolOfDeclaration(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; + } + + result.splice(spliceIndex, 0, callChainFlags ? getOptionalCallSignature(signature, callChainFlags) : signature); + } + } + + function isSpreadArgument(arg: Expression | undefined): arg is Expression { + return !!arg && (arg.kind === SyntaxKind.SpreadElement || arg.kind === SyntaxKind.SyntheticExpression && (arg as SyntheticExpression).isSpread); + } + + function getSpreadArgumentIndex(args: readonly Expression[]): number { + return findIndex(args, isSpreadArgument); + } + + 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; + } + 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 (node.kind === SyntaxKind.BinaryExpression) { + argCount = 1; + } + 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 + } + 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; + + // If we are missing the close parenthesis, the call is incomplete. + callIsIncomplete = node.arguments.end === node.end; + + // 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)); + } + } + + // Too many arguments implies incorrect arity. + if (!hasEffectiveRestParameter(signature) && argCount > effectiveParameterCount) { + return false; + } + + // 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; + } + } + 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); + } + + function isInstantiatedGenericParameter(signature: Signature, pos: number) { + let type; + return !!(signature.target && (type = tryGetTypeAtPosition(signature.target, pos)) && isGenericType(type)); + } + + // 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); + } + + function getSingleCallOrConstructSignature(type: Type): Signature | undefined { + return getSingleSignature(type, SignatureKind.Call, /*allowMembers*/ false) || + getSingleSignature(type, SignatureKind.Construct, /*allowMembers*/ false); + } + + 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; + } + + // 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); + } + + function getThisArgumentType(thisArgumentNode: Expression | undefined) { + if (!thisArgumentNode) { + return voidType; + } + const thisArgumentType = checkExpression(thisArgumentNode); + return isRightSideOfInstanceofExpression(thisArgumentNode) ? thisArgumentType : + isOptionalChainRoot(thisArgumentNode.parent) ? getNonNullableType(thisArgumentType) : + isOptionalChain(thisArgumentNode.parent) ? removeOptionalTypeMarker(thisArgumentType) : + thisArgumentType; + } + + function inferTypeArguments(node: CallLikeExpression, signature: Signature, args: readonly Expression[], checkMode: CheckMode, context: InferenceContext): Type[] { + if (isJsxOpeningLikeElement(node)) { + return inferJsxTypeArguments(node, signature, checkMode, context); + } + + // 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 && node.kind !== SyntaxKind.BinaryExpression) { + 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) { + 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(getElementTypes(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) { + const inConstContext = isConstTypeVariable(restType); + + 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. + const spreadType = arg.kind === SyntaxKind.SyntheticExpression ? (arg as SyntheticExpression).type : + checkExpressionWithContextualType((arg as SpreadElement).expression, restType, context, checkMode); + + if (isArrayLikeType(spreadType)) { + return getMutableArrayOrTupleType(spreadType); + } + + return createArrayType(checkIteratedTypeOrElementType(IterationUse.Spread, spreadType, undefinedType, arg.kind === SyntaxKind.SpreadElement ? (arg as SpreadElement).expression : arg), inConstContext); + } + } + 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); + } + } + else { + const contextualType = isTupleType(restType) ? + getContextualTypeForElementExpression(restType, i - index, argCount - index) || unknownType : + getIndexedAccessType(restType, getNumberLiteralType(i - index), AccessFlags.Contextual); + const argType = checkExpressionWithContextualType(arg, contextualType, context, checkMode); + const hasPrimitiveContextualType = inConstContext || 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!); + } + else { + names.push(undefined); + } + } + return createTupleType(types, flags, inConstContext && !someType(restType, isMutableArrayLikeType), names); + } + + 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 (isJsxIntrinsicTagName(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; + } + + /** + * 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: Map, + 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); + const checkAttributesType = checkMode & CheckMode.SkipContextSensitive ? getRegularTypeOfObjectLiteral(attributesType) : attributesType; + return checkTagNameDoesNotExpectTooManyArguments() && checkTypeRelatedToAndOptionallyElaborate( + checkAttributesType, + 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)) && !(isJsxIntrinsicTagName(node.tagName) || isJsxNamespacedName(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; + } + + 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; + } + } + } + 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); + } + } + return false; + } + } + + function getEffectiveCheckNode(argument: Expression): Expression { + argument = skipParentheses(argument); + return isSatisfiesExpression(argument) ? skipParentheses(argument.expression) : argument; + } + + function getSignatureApplicabilityError( + node: CallLikeExpression, + args: readonly Expression[], + signature: Signature, + relation: Map, + checkMode: CheckMode, + reportErrors: boolean, + containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, + inferenceContext: InferenceContext | 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; + } + const thisType = getThisTypeOfSignature(signature); + if (thisType && thisType !== voidType && !(isNewExpression(node) || isCallExpression(node) && isSuperProperty(node.expression))) { + // 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 or super call 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 regularArgType = checkMode & CheckMode.SkipContextSensitive ? getRegularTypeOfObjectLiteral(argType) : argType; + // If this was inferred under a given inference context, we may need to instantiate the expression type to finish resolving + // the type variables in the expression. + const checkArgType = inferenceContext ? instantiateType(regularArgType, inferenceContext.nonFixingMapper) : regularArgType; + const effectiveCheckArgumentNode = getEffectiveCheckNode(arg); + if (!checkTypeRelatedToAndOptionallyElaborate(checkArgType, paramType, relation, reportErrors ? effectiveCheckArgumentNode : undefined, effectiveCheckArgumentNode, 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 ? getEffectiveCheckNode(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; + + 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): Expression | undefined { + if (node.kind === SyntaxKind.BinaryExpression) { + return node.right; + } + + const expression = node.kind === SyntaxKind.CallExpression ? node.expression : + node.kind === SyntaxKind.TaggedTemplateExpression ? node.tag : + node.kind === SyntaxKind.Decorator && !legacyDecorators ? node.expression : + 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); + setTextRangeWorker(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); + }); + } + return args; + } + if (node.kind === SyntaxKind.Decorator) { + return getEffectiveDecoratorArguments(node); + } + if (node.kind === SyntaxKind.BinaryExpression) { + return [node.left]; + } + 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(getElementTypes(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); + } + } + return effectiveArgs; + } + return args; + } + + /** + * Returns the synthetic argument list for a decorator invocation. + */ + function getEffectiveDecoratorArguments(node: Decorator): readonly Expression[] { + const expr = node.expression; + const signature = getDecoratorCallSignature(node); + if (signature) { + const args: Expression[] = []; + for (const param of signature.parameters) { + const type = getTypeOfSymbol(param); + args.push(createSyntheticExpression(expr, type)); + } + return args; + } + return Debug.fail(); + } + + /** + * Returns the argument count for a decorator node that works like a function invocation. + */ + function getDecoratorArgumentCount(node: Decorator, signature: Signature) { + return compilerOptions.experimentalDecorators ? + getLegacyDecoratorArgumentCount(node, signature) : + // Allow the runtime to oversupply arguments to an ES decorator as long as there's at least one parameter. + Math.min(Math.max(getParameterCount(signature), 1), 2); + } + + /** + * Returns the argument count for a decorator node that works like a function invocation. + */ + function getLegacyDecoratorArgumentCount(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 decorators with only two parameters we supply only two arguments + return signature.parameters.length <= 2 ? 2 : 3; + case SyntaxKind.Parameter: + return 3; + default: + return Debug.fail(); + } + } + + function getDiagnosticSpanForCallNode(node: CallExpression) { + const sourceFile = getSourceFileOfNode(node); + const { start, length } = getErrorSpanForNode(sourceFile, isPropertyAccessExpression(node.expression) ? node.expression.name : node.expression); + return { start, length, sourceFile }; + } + + function getDiagnosticForCallNode(node: CallLikeExpression, message: DiagnosticMessage | DiagnosticMessageChain, ...args: DiagnosticArguments): DiagnosticWithLocation { + if (isCallExpression(node)) { + const { sourceFile, start, length } = getDiagnosticSpanForCallNode(node); + if ("message" in message) { // eslint-disable-line local/no-in-operator + return createFileDiagnostic(sourceFile, start, length, message, ...args); + } + return createDiagnosticForFileFromMessageChain(sourceFile, message); + } + else { + if ("message" in message) { // eslint-disable-line local/no-in-operator + return createDiagnosticForNode(node, message, ...args); + } + return createDiagnosticForNodeFromMessageChain(getSourceFileOfNode(node), node, message); + } + } + + function getErrorNodeForCallNode(callLike: CallLikeExpression): Node { + if (isCallOrNewExpression(callLike)) { + return isPropertyAccessExpression(callLike.expression) ? callLike.expression.name : callLike.expression; + } + if (isTaggedTemplateExpression(callLike)) { + return isPropertyAccessExpression(callLike.tag) ? callLike.tag.name : callLike.tag; + } + if (isJsxOpeningLikeElement(callLike)) { + return callLike.tagName; + } + return callLike; + } + + function isPromiseResolveArityError(node: CallLikeExpression) { + if (!isCallExpression(node) || !isIdentifier(node.expression)) return false; + + const symbol = resolveName(node.expression, node.expression.escapedText, SymbolFlags.Value, /*nameNotFoundMessage*/ undefined, /*isUse*/ 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[], headMessage?: DiagnosticMessage) { + 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 = isDecorator(node) ? + hasRestParameter ? Diagnostics.The_runtime_will_invoke_the_decorator_with_1_arguments_but_the_decorator_expects_at_least_0 : + Diagnostics.The_runtime_will_invoke_the_decorator_with_1_arguments_but_the_decorator_expects_0 : + 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 + if (headMessage) { + let chain = chainDiagnosticMessages(/*details*/ undefined, Diagnostics.No_overload_expects_0_arguments_but_overloads_do_exist_that_expect_either_1_or_2_arguments, args.length, maxBelow, minAbove); + chain = chainDiagnosticMessages(chain, headMessage); + return getDiagnosticForCallNode(node, chain); + } + 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 + let diagnostic: Diagnostic; + if (headMessage) { + let chain = chainDiagnosticMessages(/*details*/ undefined, error, parameterRange, args.length); + chain = chainDiagnosticMessages(chain, headMessage); + diagnostic = getDiagnosticForCallNode(node, chain); + } + else { + diagnostic = getDiagnosticForCallNode(node, error, parameterRange, args.length); + } + const parameter = closestSignature?.declaration?.parameters[closestSignature.thisParameter ? args.length + 1 : args.length]; + if (parameter) { + const messageAndArgs: DiagnosticAndArguments = 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, idText(getFirstIdentifier(parameter.name))] + : [Diagnostics.An_argument_for_0_was_not_provided, !parameter.name ? args.length : idText(getFirstIdentifier(parameter.name))]; + const parameterError = createDiagnosticForNode(parameter, ...messageAndArgs); + return addRelatedInfo(diagnostic, parameterError); + } + 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); + if (headMessage) { + let chain = chainDiagnosticMessages(/*details*/ undefined, error, parameterRange, args.length); + chain = chainDiagnosticMessages(chain, headMessage); + return createDiagnosticForNodeArrayFromMessageChain(getSourceFileOfNode(node), errorSpan, chain); + } + return createDiagnosticForNodeArray(getSourceFileOfNode(node), errorSpan, error, parameterRange, args.length); + } + } + + function getTypeArgumentArityError(node: Node, signatures: readonly Signature[], typeArguments: NodeArray, headMessage?: DiagnosticMessage) { + 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); + if (headMessage) { + let chain = chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Expected_0_type_arguments_but_got_1, min < max ? min + "-" + max : min, argCount); + chain = chainDiagnosticMessages(chain, headMessage); + return createDiagnosticForNodeArrayFromMessageChain(getSourceFileOfNode(node), typeArguments, chain); + } + 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); + } + } + if (belowArgCount !== -Infinity && aboveArgCount !== Infinity) { + if (headMessage) { + let chain = chainDiagnosticMessages(/*details*/ undefined, Diagnostics.No_overload_expects_0_type_arguments_but_overloads_do_exist_that_expect_either_1_or_2_type_arguments, argCount, belowArgCount, aboveArgCount); + chain = chainDiagnosticMessages(chain, headMessage); + return createDiagnosticForNodeArrayFromMessageChain(getSourceFileOfNode(node), typeArguments, chain); + } + 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); + } + if (headMessage) { + let chain = chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Expected_0_type_arguments_but_got_1, belowArgCount === -Infinity ? aboveArgCount : belowArgCount, argCount); + chain = chainDiagnosticMessages(chain, headMessage); + return createDiagnosticForNodeArrayFromMessageChain(getSourceFileOfNode(node), typeArguments, chain); + } + 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, headMessage?: DiagnosticMessage): Signature { + const isTaggedTemplate = node.kind === SyntaxKind.TaggedTemplateExpression; + const isDecorator = node.kind === SyntaxKind.Decorator; + const isJsxOpeningOrSelfClosingElement = isJsxOpeningLikeElement(node); + const isInstanceof = node.kind === SyntaxKind.BinaryExpression; + const reportErrors = !isInferencePartiallyBlocked && !candidatesOutArray; + + let typeArguments: NodeArray | undefined; + + if (!isDecorator && !isInstanceof && !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); + } + } + + const candidates = candidatesOutArray || []; + // reorderCandidates fills up the candidates array directly + reorderCandidates(signatures, candidates, callChainFlags); + Debug.assert(candidates.length, "Revert #54442 and add a testcase with whatever triggered this"); + + 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; + + // 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 the call expression is a synthetic call to a `[Symbol.hasInstance]` method then we will produce a head + // message when reporting diagnostics that explains how we got to `right[Symbol.hasInstance](left)` from + // `left instanceof right`, as it pertains to "Argument" related messages reported for the call. + if (!headMessage && isInstanceof) { + headMessage = Diagnostics.The_left_hand_side_of_an_instanceof_expression_must_be_assignable_to_the_first_argument_of_the_right_hand_side_s_Symbol_hasInstance_method; + } + 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); + } + if (headMessage) { + chain = chainDiagnosticMessages(chain, headMessage); + } + const diags = getSignatureApplicabilityError(node, args, last, assignableRelation, CheckMode.Normal, /*reportErrors*/ true, () => chain, /*inferenceContext*/ undefined); + 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); + } + } + else { + 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, /*inferenceContext*/ undefined); + 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"); + } + i++; + } + + const diags = max > 1 ? allDiagnostics[minIndex] : flatten(allDiagnostics); + Debug.assert(diags.length > 0, "No errors reported for 3 or fewer overload signatures"); + let chain = chainDiagnosticMessages( + map(diags, createDiagnosticMessageChainFromDiagnostic), + Diagnostics.No_overload_matches_this_call, + ); + if (headMessage) { + chain = chainDiagnosticMessages(chain, headMessage); + } + // 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(getSourceFileOfNode(node), getErrorNodeForCallNode(node), chain, related); + } + addImplementationSuccessElaboration(candidatesForArgumentError[0], diag); + diagnostics.add(diag); + } + } + else if (candidateForArgumentArityError) { + diagnostics.add(getArgumentArityError(node, [candidateForArgumentArityError], args, headMessage)); + } + else if (candidateForTypeArgumentError) { + checkTypeArguments(candidateForTypeArgumentError, (node as CallExpression | TaggedTemplateExpression | JsxOpeningLikeElement).typeArguments!, /*reportErrors*/ true, headMessage); + } + else { + const signaturesWithCorrectTypeArgumentArity = filter(signatures, s => hasCorrectTypeArgumentArity(s, typeArguments)); + if (signaturesWithCorrectTypeArgumentArity.length === 0) { + diagnostics.add(getTypeArgumentArityError(node, signatures, typeArguments!, headMessage)); + } + else { + diagnostics.add(getArgumentArityError(node, signaturesWithCorrectTypeArgumentArity, args, headMessage)); + } + } + } + + return result; + + 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)); + } + } + + candidatesForArgumentError = oldCandidatesForArgumentError; + candidateForArgumentArityError = oldCandidateForArgumentArityError; + candidateForTypeArgumentError = oldCandidateForTypeArgumentError; + } + + function chooseOverload(candidates: Signature[], relation: Map, 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, /*inferenceContext*/ undefined)) { + candidatesForArgumentError = [candidate]; + return undefined; + } + return candidate; + } + + for (let candidateIndex = 0; candidateIndex < candidates.length; candidateIndex++) { + let candidate = candidates[candidateIndex]; + if (!hasCorrectTypeArgumentArity(candidate, typeArguments) || !hasCorrectArity(node, args, candidate, signatureHelpTrailingComma)) { + continue; + } + + let checkCandidate: Signature; + let inferenceContext: InferenceContext | undefined; + + if (candidate.typeParameters) { + // If we are *inside the body of candidate*, we need to create a clone of `candidate` with differing type parameter identities, + // so our inference results for this call doesn't pollute expression types referencing the outer type parameter! + const paramLocation = candidate.typeParameters[0].symbol.declarations?.[0]?.parent; + const candidateParameterContext = paramLocation || (candidate.declaration && isConstructorDeclaration(candidate.declaration) ? candidate.declaration.parent : candidate.declaration); + if (candidateParameterContext && findAncestor(node, a => a === candidateParameterContext)) { + candidate = getImplementationSignature(candidate); + } + let typeArgumentTypes: readonly 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); + // The resulting type arguments are instantiated with the inference context mapper, as the inferred types may still contain references to the inference context's + // type variables via contextual projection. These are kept generic until all inferences are locked in, so the dependencies expressed can pass constraint checks. + typeArgumentTypes = instantiateTypes(inferTypeArguments(node, candidate, args, argCheckMode | CheckMode.SkipGenericFunctions, inferenceContext), inferenceContext.nonFixingMapper); + 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, inferenceContext)) { + // 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.Normal; + if (inferenceContext) { + const typeArgumentTypes = instantiateTypes(inferTypeArguments(node, candidate, args, argCheckMode, inferenceContext), inferenceContext.mapper); + 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, inferenceContext)) { + // 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; + } + } + + // 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 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.IsSignatureCandidateForOverloadFailure; + 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)), + /*resolvedTypePredicate*/ undefined, + minArgumentCount, + flags, + ); + } + + function getNumNonRestParameters(signature: Signature): number { + const numParams = signature.parameters.length; + return signatureHasRestParameter(signature) ? numParams - 1 : numParams; + } + + function createCombinedSymbolFromTypes(sources: readonly Symbol[], types: Type[]): Symbol { + return createCombinedSymbolForOverloadFailure(sources, getUnionType(types, UnionReduction.Subtype)); + } + + 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 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 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; + } + if (paramCount > maxParams) { + maxParams = paramCount; + maxParamsIndex = i; + } + } + + return maxParamsIndex; + } + + 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); + } + + 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; + } + + 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); + } + + // 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 { + 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); + } + + return resolveCall(node, callSignatures, candidatesOutArray, checkMode, callChainFlags); + } + + function isGenericFunctionReturningFunction(signature: Signature) { + return !!(signature.typeParameters && isFunctionType(getReturnTypeOfSignature(signature))); + } + + /** + * 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); + } + + function resolveNewExpression(node: NewExpression, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature { + let expressionType = checkNonNullExpression(node.expression); + if (expressionType === silentNeverType) { + return silentNeverSignature; + } + + // 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); + } + + // 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); + } + + // 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); + } + 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); + } + + 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); + } + 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); + } + + 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; + } + 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; + } + if (firstBase.symbol === target) { + return true; + } + return typeHasProtectedAccessibleBase(target, firstBase as InterfaceType); + } + + function isConstructorAccessible(node: NewExpression, signature: Signature) { + if (!signature || !signature.declaration) { + return true; + } + + 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; + } + + 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)); + } + return false; + } + + 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; + } + } + else { + // Error on the first non callable constituent only + if (!errorInfo) { + errorInfo = chainDiagnosticMessages( + errorInfo, + isCall ? + Diagnostics.Type_0_has_no_call_signatures : + Diagnostics.Type_0_has_no_construct_signatures, + typeToString(constituent), + ); + errorInfo = chainDiagnosticMessages( + errorInfo, + isCall ? + Diagnostics.Not_all_constituents_of_type_0_are_callable : + Diagnostics.Not_all_constituents_of_type_0_are_constructable, + typeToString(apparentType), + ); + } + if (hasSignatures) { + // Bail early if we already found a siganture, no chance of "No constituent of type is callable" + break; + } + } + } + if (!hasSignatures) { + errorInfo = chainDiagnosticMessages( + /*details*/ undefined, + isCall ? + Diagnostics.No_constituent_of_type_0_is_callable : + Diagnostics.No_constituent_of_type_0_is_constructable, + typeToString(apparentType), + ); + } + if (!errorInfo) { + errorInfo = chainDiagnosticMessages( + errorInfo, + isCall ? + Diagnostics.Each_member_of_the_union_type_0_has_signatures_but_none_of_those_signatures_are_compatible_with_each_other : + Diagnostics.Each_member_of_the_union_type_0_has_construct_signatures_but_none_of_those_signatures_are_compatible_with_each_other, + typeToString(apparentType), + ); + } + } + else { + errorInfo = chainDiagnosticMessages( + errorInfo, + isCall ? + Diagnostics.Type_0_has_no_call_signatures : + Diagnostics.Type_0_has_no_construct_signatures, + typeToString(apparentType), + ); + } + + 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; + } + } + + 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(getSourceFileOfNode(errorTarget), errorTarget, messageChain); + if (relatedInfo) { + addRelatedInfo(diagnostic, createDiagnosticForNode(errorTarget, relatedInfo)); + } + if (isCallExpression(errorTarget.parent)) { + const { start, length } = getDiagnosticSpanForCallNode(errorTarget.parent); + 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; + + 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 resolveTaggedTemplateExpression(node: TaggedTemplateExpression, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature { + const tagType = checkExpression(node.tag); + const apparentType = getApparentType(tagType); + + if (isErrorType(apparentType)) { + // Another error has already been reported + return resolveErrorCall(node); + } + + const callSignatures = getSignaturesOfType(apparentType, SignatureKind.Call); + const numConstructSignatures = getSignaturesOfType(apparentType, SignatureKind.Construct).length; + + if (isUntypedFunctionCall(tagType, apparentType, callSignatures.length, numConstructSignatures)) { + return resolveUntypedCall(node); + } + + 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); + } + + invocationError(node.tag, apparentType, SignatureKind.Call); + return resolveErrorCall(node); + } + + return resolveCall(node, callSignatures, candidatesOutArray, checkMode, SignatureFlags.None); + } + + /** + * 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; + + case SyntaxKind.PropertyDeclaration: + return Diagnostics.Unable_to_resolve_signature_of_property_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; + + default: + return Debug.fail(); + } + } + + /** + * 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 callSignatures = getSignaturesOfType(apparentType, SignatureKind.Call); + const numConstructSignatures = getSignaturesOfType(apparentType, SignatureKind.Construct).length; + if (isUntypedFunctionCall(funcType, apparentType, callSignatures.length, numConstructSignatures)) { + return resolveUntypedCall(node); + } + + if (isPotentiallyUncalledDecorator(node, callSignatures) && !isParenthesizedExpression(node.expression)) { + 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); + } + + const headMessage = getDiagnosticHeadMessageForDecoratorResolution(node); + if (!callSignatures.length) { + const errorDetails = invocationErrorDetails(node.expression, apparentType, SignatureKind.Call); + const messageChain = chainDiagnosticMessages(errorDetails.messageChain, headMessage); + const diag = createDiagnosticForNodeFromMessageChain(getSourceFileOfNode(node.expression), node.expression, messageChain); + if (errorDetails.relatedMessage) { + addRelatedInfo(diag, createDiagnosticForNode(node.expression, errorDetails.relatedMessage)); + } + diagnostics.add(diag); + invocationErrorRecovery(apparentType, SignatureKind.Call, diag); + return resolveErrorCall(node); + } + + return resolveCall(node, callSignatures, candidatesOutArray, checkMode, SignatureFlags.None, headMessage); + } + + 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, /*dotDotDotToken*/ undefined, "props", /*questionToken*/ undefined, nodeBuilder.typeToTypeNode(result, node))], returnNode ? factory.createTypeReferenceNode(returnNode, /*typeArguments*/ undefined) : factory.createKeywordTypeNode(SyntaxKind.AnyKeyword)); + const parameterSymbol = createSymbol(SymbolFlags.FunctionScopedVariable, "props" as __String); + parameterSymbol.links.type = result; + return createSignature( + declaration, + /*typeParameters*/ undefined, + /*thisParameter*/ undefined, + [parameterSymbol], + typeSymbol ? getDeclaredTypeOfSymbol(typeSymbol) : errorType, + /*resolvedTypePredicate*/ undefined, + 1, + SignatureFlags.None, + ); + } + + function resolveJsxOpeningLikeElement(node: JsxOpeningLikeElement, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature { + if (isJsxIntrinsicTagName(node.tagName)) { + const result = getIntrinsicAttributesTypeFromJsxOpeningLikeElement(node); + const fakeSignature = createSignatureForJSXIntrinsic(node, result); + checkTypeAssignableToAndOptionallyElaborate(checkExpressionWithContextualType(node.attributes, getEffectiveFirstArgumentForJsxSignature(fakeSignature, node), /*inferenceContext*/ 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); + } + + const signatures = getUninstantiatedJsxSignaturesOfType(exprTypes, node); + if (isUntypedFunctionCall(exprTypes, apparentType, signatures.length, /*constructSignatures*/ 0)) { + return resolveUntypedCall(node); + } + + 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); + } + + return resolveCall(node, signatures, candidatesOutArray, checkMode, SignatureFlags.None); + } + + function resolveInstanceofExpression(node: InstanceofExpression, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature { + // if rightType is an object type with a custom `[Symbol.hasInstance]` method, then it is potentially + // valid on the right-hand side of the `instanceof` operator. This allows normal `object` types to + // participate in `instanceof`, as per Step 2 of https://tc39.es/ecma262/#sec-instanceofoperator. + const rightType = checkExpression(node.right); + if (!isTypeAny(rightType)) { + const hasInstanceMethodType = getSymbolHasInstanceMethodOfObjectType(rightType); + if (hasInstanceMethodType) { + const apparentType = getApparentType(hasInstanceMethodType); + if (isErrorType(apparentType)) { + return resolveErrorCall(node); + } + + const callSignatures = getSignaturesOfType(apparentType, SignatureKind.Call); + const constructSignatures = getSignaturesOfType(apparentType, SignatureKind.Construct); + if (isUntypedFunctionCall(hasInstanceMethodType, apparentType, callSignatures.length, constructSignatures.length)) { + return resolveUntypedCall(node); + } + + if (callSignatures.length) { + return resolveCall(node, callSignatures, candidatesOutArray, checkMode, SignatureFlags.None); + } + } + // NOTE: do not raise error if right is unknown as related error was already reported + else if (!(typeHasCallOrConstructSignatures(rightType) || isTypeSubtypeOf(rightType, globalFunctionType))) { + error(node.right, Diagnostics.The_right_hand_side_of_an_instanceof_expression_must_be_either_of_type_any_a_class_function_or_other_type_assignable_to_the_Function_interface_type_or_an_object_type_with_a_Symbol_hasInstance_method); + return resolveErrorCall(node); + } + } + // fall back to a default signature + return anySignature; + } + + /** + * 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)); + } + + 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); + case SyntaxKind.BinaryExpression: + return resolveInstanceofExpression(node, candidatesOutArray, checkMode); + } + 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; + } + const saveResolutionStart = resolutionStart; + if (!cached) { + // If we haven't already done so, temporarily reset the resolution stack. This allows us to + // handle "inverted" situations where, for example, an API client asks for the type of a symbol + // containined in a function call argument whose contextual type depends on the symbol itself + // through resolution of the containing function call. By resetting the resolution stack we'll + // retry the symbol type resolution with the resolvingSignature marker in place to suppress + // the contextual type circularity. + resolutionStart = resolutionTargets.length; + } + links.resolvedSignature = resolvingSignature; + let result = resolveSignature(node, candidatesOutArray, checkMode || CheckMode.Normal); + resolutionStart = saveResolutionStart; + // When CheckMode.SkipGenericFunctions is set we use resolvingSignature to indicate that call + // resolution should be deferred. + if (result !== resolvingSignature) { + // if the signature resolution originated on a node that itself depends on the contextual type + // then it's possible that the resolved signature might not be the same as the one that would be computed in source order + // since resolving such signature leads to resolving the potential outer signature, its arguments and thus the very same signature + // it's possible that this inner resolution sets the resolvedSignature first. + // In such a case we ignore the local result and reuse the correct one that was cached. + if (links.resolvedSignature !== resolvingSignature) { + result = links.resolvedSignature; + } + // 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; + + // 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 = getSymbolOfDeclaration(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); + 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; + } + 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 ? getSymbolOfDeclaration(init) : undefined; + } + + 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) && !(isVarConstLike(node.parent) && isFunctionLikeDeclaration(node))) { + return undefined; + } + 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 (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; + } + } + } + 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; + } + } + + /** + * 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); + + 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); + + if (node.expression.kind === SyntaxKind.SuperKeyword) { + return voidType; + } + + if (node.kind === SyntaxKind.NewExpression) { + const declaration = signature.declaration; + + if ( + declaration && + declaration.kind !== SyntaxKind.Constructor && + declaration.kind !== SyntaxKind.ConstructSignature && + declaration.kind !== SyntaxKind.ConstructorType && + !(isJSDocSignature(declaration) && getJSDocRoot(declaration)?.parent?.kind === SyntaxKind.Constructor) && + !isJSDocConstructSignature(declaration) && + !isJSConstructor(declaration) + ) { + // 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; + } + } + + // 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); + } + + 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 (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 returnType; + } + + function checkDeprecatedSignature(signature: Signature, node: CallLikeExpression) { + if (signature.flags & SignatureFlags.IsSignatureCandidateForOverloadFailure) return; + 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 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; + } + } + + 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; + } + + // make sure `Symbol` is the global symbol + const globalESSymbol = getGlobalESSymbolConstructorSymbol(/*reportErrors*/ false); + if (!globalESSymbol) { + return false; + } + + return globalESSymbol === resolveName(left, "Symbol" as __String, SymbolFlags.Value, /*nameNotFoundMessage*/ undefined, /*isUse*/ false); + } + + function checkImportCallExpression(node: ImportCall): Type { + // Check grammar of dynamic import + checkGrammarImportCallExpression(node); + + if (node.arguments.length === 0) { + return createPromiseReturnType(node, anyType); + } + + 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]); + } + + 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)); + } + + if (optionsType) { + const importCallOptionsType = getGlobalImportCallOptionsType(/*reportErrors*/ true); + if (importCallOptionsType !== emptyObjectType) { + checkTypeAssignableTo(optionsType, getNullableType(importCallOptionsType, TypeFlags.Undefined), node.arguments[1]); + } + } + + // resolveExternalModuleName will return undefined if the moduleReferenceExpression is not a string literal + const moduleSymbol = resolveExternalModuleName(node, specifier); + if (moduleSymbol) { + const esModuleSymbol = resolveESModuleSymbol(moduleSymbol, specifier, /*dontResolveAlias*/ true, /*suppressInteropError*/ false); + if (esModuleSymbol) { + return createPromiseReturnType( + node, + getTypeWithSyntheticDefaultOnly(getTypeOfSymbol(esModuleSymbol), esModuleSymbol, moduleSymbol, specifier) || + getTypeWithSyntheticDefaultImportType(getTypeOfSymbol(esModuleSymbol), esModuleSymbol, moduleSymbol, specifier), + ); + } + } + return createPromiseReturnType(node, anyType); + } + + function createDefaultPropertyWrapperForModule(symbol: Symbol, originalSymbol: Symbol | undefined, anonymousSymbol?: Symbol | undefined) { + const memberTable = createSymbolTable(); + const newSymbol = createSymbol(SymbolFlags.Alias, InternalSymbolName.Default); + newSymbol.parent = originalSymbol; + newSymbol.links.nameType = getStringLiteralType("default"); + newSymbol.links.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; + } + return synthType.defaultOnlyType; + } + return undefined; + } + + 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.links.type = defaultContainingObject; + synthType.syntheticType = isValidSpreadType(type) ? getSpreadType(type, defaultContainingObject, anonymousSymbol, /*objectFlags*/ 0, /*readonly*/ false) : defaultContainingObject; + } + else { + synthType.syntheticType = type; + } + } + return synthType.syntheticType; + } + return type; + } + + function isCommonJsRequire(node: Node): boolean { + if (!isRequireCall(node, /*requireStringLiteralLikeArgument*/ 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, /*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); + } + return false; + } + + function checkTaggedTemplateExpression(node: TaggedTemplateExpression): Type { + if (!checkGrammarTaggedTemplateChain(node)) checkGrammarTypeArguments(node, node.typeArguments); + if (languageVersion < LanguageFeatureMinimumTarget.TaggedTemplates) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.MakeTemplateObject); + } + const signature = getResolvedSignature(node); + checkDeprecatedSignature(signature, node); + return getReturnTypeOfSignature(signature); + } + + function checkAssertion(node: AssertionExpression, checkMode: CheckMode | undefined) { + 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 checkAssertionWorker(node, checkMode); + } + + 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 = skipParentheses((node as PropertyAccessExpression | ElementAccessExpression).expression); + const symbol = isEntityNameExpression(expr) ? resolveEntityName(expr, SymbolFlags.Value, /*ignoreErrors*/ true) : undefined; + return !!(symbol && symbol.flags & SymbolFlags.Enum); + } + return false; + } + + function checkAssertionWorker(node: JSDocTypeAssertion | AssertionExpression, checkMode: CheckMode | undefined) { + const { type, expression } = getAssertionTypeAndExpression(node); + const 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); + } + const links = getNodeLinks(node); + links.assertionExpressionType = exprType; + checkSourceElement(type); + checkNodeDeferred(node); + return getTypeFromTypeNode(type); + } + + function getAssertionTypeAndExpression(node: JSDocTypeAssertion | AssertionExpression) { + let type: TypeNode; + let expression: Expression; + switch (node.kind) { + case SyntaxKind.AsExpression: + case SyntaxKind.TypeAssertionExpression: + type = node.type; + expression = node.expression; + break; + case SyntaxKind.ParenthesizedExpression: + type = getJSDocTypeAssertionType(node); + expression = node.expression; + break; + } + + return { type, expression }; + } + + function checkAssertionDeferred(node: JSDocTypeAssertion | AssertionExpression) { + const { type } = getAssertionTypeAndExpression(node); + const errNode = isParenthesizedExpression(node) ? type : node; + const links = getNodeLinks(node); + Debug.assertIsDefined(links.assertionExpressionType); + const exprType = getRegularTypeOfObjectLiteral(getBaseTypeOfLiteralType(links.assertionExpressionType)); + 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); + } + }); + } + } + + function checkNonNullChain(node: NonNullChain) { + const leftType = checkExpression(node.expression); + const nonOptionalType = getOptionalExpressionType(leftType, node.expression); + return propagateOptionalTypeMarker(getNonNullableType(nonOptionalType), node, nonOptionalType !== leftType); + } + + function checkNonNullAssertion(node: NonNullExpression) { + return node.flags & NodeFlags.OptionalChain ? checkNonNullChain(node as NonNullChain) : + getNonNullableType(checkExpression(node.expression)); + } + + function checkExpressionWithTypeArguments(node: ExpressionWithTypeArguments | TypeQueryNode) { + checkGrammarExpressionWithTypeArguments(node); + forEach(node.typeArguments, checkSourceElement); + if (node.kind === SyntaxKind.ExpressionWithTypeArguments) { + const parent = walkUpParenthesizedExpressions(node.parent); + if (parent.kind === SyntaxKind.BinaryExpression && (parent as BinaryExpression).operatorToken.kind === SyntaxKind.InstanceOfKeyword && isNodeDescendantOf(node, (parent as BinaryExpression).right)) { + error(node, Diagnostics.The_right_hand_side_of_an_instanceof_expression_must_not_be_an_instantiation_expression); + } + } + const exprType = node.kind === SyntaxKind.ExpressionWithTypeArguments ? checkExpression(node.expression) : + isThisIdentifier(node.exprName) ? checkThisExpression(node.exprName) : + checkExpression(node.exprName); + return getInstantiationExpressionType(exprType, node); + } + + function getInstantiationExpressionType(exprType: Type, node: NodeWithTypeArguments) { + 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 getInstantiatedType(type: Type): Type { + let hasSignatures = false; + let hasApplicableSignature = false; + const result = getInstantiatedTypePart(type); + hasSomeApplicableSignature ||= hasApplicableSignature; + if (hasSignatures && !hasApplicableSignature) { + nonApplicableType ??= type; + } + return result; + + 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(createSymbol(SymbolFlags.None, InternalSymbolName.InstantiationExpression), 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 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 checkSatisfiesExpression(node: SatisfiesExpression) { + checkSourceElement(node.type); + return checkSatisfiesExpressionWorker(node.expression, node.type); + } + + function checkSatisfiesExpressionWorker(expression: Expression, target: TypeNode, checkMode?: CheckMode) { + const exprType = checkExpression(expression, checkMode); + const targetType = getTypeFromTypeNode(target); + if (isErrorType(targetType)) { + return targetType; + } + const errorNode = findAncestor(target.parent, n => n.kind === SyntaxKind.SatisfiesExpression || n.kind === SyntaxKind.JSDocSatisfiesTag); + checkTypeAssignableToAndOptionallyElaborate(exprType, targetType, errorNode, 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); + } + + if (node.keywordToken === SyntaxKind.ImportKeyword) { + return checkImportMetaProperty(node); + } + + 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 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 = getSymbolOfDeclaration(container.parent); + return getTypeOfSymbol(symbol); + } + else { + const symbol = getSymbolOfDeclaration(container); + return getTypeOfSymbol(symbol); + } + } + + 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; + } + + function getTypeOfParameter(symbol: Symbol) { + const declaration = symbol.valueDeclaration; + return addOptionality( + getTypeOfSymbol(symbol), + /*isProperty*/ false, + /*isOptional*/ !!declaration && (hasInitializer(declaration) || isOptionalDeclaration(declaration)), + ); + } + + function getTupleElementLabel(d: ParameterDeclaration | NamedTupleMember): __String; + function getTupleElementLabel(d: ParameterDeclaration | NamedTupleMember | undefined, index: number, restParameterName?: __String): __String; + function getTupleElementLabel(d: ParameterDeclaration | NamedTupleMember | undefined, index?: number, restParameterName = "arg" as __String) { + if (!d) { + return `${restParameterName}_${index}` as __String; + } + Debug.assert(isIdentifier(d.name)); // Parameter declarations could be binding patterns, but we only allow identifier names + return d.name.escapedText; + } + + 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 getTupleElementLabel(associatedNames?.[index], index, restParameter.escapedName); + } + return restParameter.escapedName; + } + + function getParameterIdentifierInfoAtPosition(signature: Signature, pos: number): { parameter: Identifier; 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]; + const paramIdent = getParameterDeclarationIdentifier(param); + return paramIdent ? { + parameter: paramIdent, + parameterName: param.escapedName, + isRestParameter: false, + } : undefined; + } + + const restParameter = signature.parameters[paramCount] || unknownSymbol; + const restIdent = getParameterDeclarationIdentifier(restParameter); + if (!restIdent) { + return undefined; + } + + 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; + + if (associatedName) { + Debug.assert(isIdentifier(associatedName.name)); + return { parameter: associatedName.name, parameterName: associatedName.name.escapedText, isRestParameter: isRestTupleElement }; + } + + return undefined; + } + + if (pos === paramCount) { + return { parameter: restIdent, parameterName: restParameter.escapedName, isRestParameter: true }; + } + return undefined; + } + + function getParameterDeclarationIdentifier(symbol: Symbol) { + return symbol.valueDeclaration && isParameter(symbol.valueDeclaration) && isIdentifier(symbol.valueDeclaration.name) && 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 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 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.combinedFlags & ElementFlags.Variable || index < restType.target.fixedLength) { + return getIndexedAccessType(restType, getNumberLiteralType(index)); + } + } + return undefined; + } + + function getRestTypeAtPosition(source: Signature, pos: number, readonly?: boolean): 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); + } + else { + types.push(restType); + flags.push(ElementFlags.Variadic); + } + names.push(getNameableDeclarationAtPosition(source, i)); + } + return createTupleType(types, flags, readonly, names); + } + + // Return the rest type at the given position, transforming `any[]` into just `any`. We do this because + // in signatures we want `any[]` in a rest position to be compatible with anything, but `any[]` isn't + // assignable to tuple types with required elements. + function getRestOrAnyTypeAtPosition(source: Signature, pos: number): Type { + const restType = getRestTypeAtPosition(source, pos); + const elementType = restType && getElementTypeOfArrayType(restType); + return elementType && isTypeAny(elementType) ? anyType : restType; + } + + // 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.combinedFlags & ElementFlags.Variable ? 0 : 1); + } + } + 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; + } + 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; + } + minArgumentCount = i; + } + 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.combinedFlags & ElementFlags.Variable); + } + return false; + } + + function getEffectiveRestType(signature: Signature) { + if (signatureHasRestParameter(signature)) { + const restType = getTypeOfSymbol(signature.parameters[signature.parameters.length - 1]); + if (!isTupleType(restType)) { + return isTypeAny(restType) ? anyArrayType : restType; + } + if (restType.target.combinedFlags & ElementFlags.Variable) { + return sliceTupleType(restType, restType.target.fixedLength); + } + } + return undefined; + } + + function getNonArrayRestType(signature: Signature) { + const restType = getEffectiveRestType(signature); + return restType && !isArrayType(restType) && !isTypeAny(restType) ? restType : undefined; + } + + function getTypeOfFirstParameterOfSignature(signature: Signature) { + return getTypeOfFirstParameterOfSignatureWithFallback(signature, neverType); + } + + 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; + const typeNode = getEffectiveTypeAnnotationNode(declaration); + if (typeNode) { + const source = addOptionality(getTypeFromTypeNode(typeNode), /*isProperty*/ false, isOptionalDeclaration(declaration)); + const target = getTypeAtPosition(context, i); + inferTypes(inferenceContext.inferences, source, target); + } + } + } + + 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! + } + } + 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]; + const declaration = parameter.valueDeclaration as ParameterDeclaration; + if (!getEffectiveTypeAnnotationNode(declaration)) { + let type = tryGetTypeAtPosition(context, i); + if (type && declaration.initializer) { + let initializerType = checkDeclarationInitializer(declaration, CheckMode.Normal); + if (!isTypeAssignableTo(initializerType, type) && isTypeAssignableTo(type, initializerType = widenTypeInferredFromInitializer(declaration, initializerType))) { + type = initializerType; + } + } + assignParameterType(parameter, type); + } + } + 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 assignNonContextualParameterTypes(signature: Signature) { + if (signature.thisParameter) { + assignParameterType(signature.thisParameter); + } + for (const parameter of signature.parameters) { + assignParameterType(parameter); + } + } + + function assignParameterType(parameter: Symbol, contextualType?: Type) { + const links = getSymbolLinks(parameter); + if (!links.type) { + const declaration = parameter.valueDeclaration as ParameterDeclaration | undefined; + links.type = addOptionality( + contextualType || (declaration ? getWidenedTypeForVariableLikeDeclaration(declaration, /*reportErrors*/ true) : getTypeOfSymbol(parameter)), + /*isProperty*/ false, + /*isOptional*/ !!declaration && !declaration.initializer && isOptionalDeclaration(declaration), + ); + 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 (contextualType) { + Debug.assertEqual(links.type, contextualType, "Parameter symbol already has a cached type which differs from newly assigned type"); + } + } + + // 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, /*noTupleBoundsCheck*/ false); + if (element.name.kind === SyntaxKind.Identifier) { + getSymbolLinks(getSymbolOfDeclaration(element)).type = type; + } + else { + assignBindingElementTypes(element.name, type); + } + } + } + } + + function createClassDecoratorContextType(classType: Type) { + return tryCreateTypeReference(getGlobalClassDecoratorContextType(/*reportErrors*/ true), [classType]); + } + + function createClassMethodDecoratorContextType(thisType: Type, valueType: Type) { + return tryCreateTypeReference(getGlobalClassMethodDecoratorContextType(/*reportErrors*/ true), [thisType, valueType]); + } + + function createClassGetterDecoratorContextType(thisType: Type, valueType: Type) { + return tryCreateTypeReference(getGlobalClassGetterDecoratorContextType(/*reportErrors*/ true), [thisType, valueType]); + } + + function createClassSetterDecoratorContextType(thisType: Type, valueType: Type) { + return tryCreateTypeReference(getGlobalClassSetterDecoratorContextType(/*reportErrors*/ true), [thisType, valueType]); + } + + function createClassAccessorDecoratorContextType(thisType: Type, valueType: Type) { + return tryCreateTypeReference(getGlobalClassAccessorDecoratorContextType(/*reportErrors*/ true), [thisType, valueType]); + } + + function createClassFieldDecoratorContextType(thisType: Type, valueType: Type) { + return tryCreateTypeReference(getGlobalClassFieldDecoratorContextType(/*reportErrors*/ true), [thisType, valueType]); + } + + /** + * Gets a type like `{ name: "foo", private: false, static: true }` that is used to provided member-specific + * details that will be intersected with a decorator context type. + */ + function getClassMemberDecoratorContextOverrideType(nameType: Type, isPrivate: boolean, isStatic: boolean) { + const key = `${isPrivate ? "p" : "P"}${isStatic ? "s" : "S"}${nameType.id}` as const; + let overrideType = decoratorContextOverrideTypeCache.get(key); + if (!overrideType) { + const members = createSymbolTable(); + members.set("name" as __String, createProperty("name" as __String, nameType)); + members.set("private" as __String, createProperty("private" as __String, isPrivate ? trueType : falseType)); + members.set("static" as __String, createProperty("static" as __String, isStatic ? trueType : falseType)); + overrideType = createAnonymousType(/*symbol*/ undefined, members, emptyArray, emptyArray, emptyArray); + decoratorContextOverrideTypeCache.set(key, overrideType); + } + return overrideType; + } + + function createClassMemberDecoratorContextTypeForNode(node: MethodDeclaration | AccessorDeclaration | PropertyDeclaration, thisType: Type, valueType: Type) { + const isStatic = hasStaticModifier(node); + const isPrivate = isPrivateIdentifier(node.name); + const nameType = isPrivate ? getStringLiteralType(idText(node.name)) : getLiteralTypeFromPropertyName(node.name); + const contextType = isMethodDeclaration(node) ? createClassMethodDecoratorContextType(thisType, valueType) : + isGetAccessorDeclaration(node) ? createClassGetterDecoratorContextType(thisType, valueType) : + isSetAccessorDeclaration(node) ? createClassSetterDecoratorContextType(thisType, valueType) : + isAutoAccessorPropertyDeclaration(node) ? createClassAccessorDecoratorContextType(thisType, valueType) : + isPropertyDeclaration(node) ? createClassFieldDecoratorContextType(thisType, valueType) : + Debug.failBadSyntaxKind(node); + const overrideType = getClassMemberDecoratorContextOverrideType(nameType, isPrivate, isStatic); + return getIntersectionType([contextType, overrideType]); + } + + function createClassAccessorDecoratorTargetType(thisType: Type, valueType: Type) { + return tryCreateTypeReference(getGlobalClassAccessorDecoratorTargetType(/*reportErrors*/ true), [thisType, valueType]); + } + + function createClassAccessorDecoratorResultType(thisType: Type, valueType: Type) { + return tryCreateTypeReference(getGlobalClassAccessorDecoratorResultType(/*reportErrors*/ true), [thisType, valueType]); + } + + function createClassFieldDecoratorInitializerMutatorType(thisType: Type, valueType: Type) { + const thisParam = createParameter("this" as __String, thisType); + const valueParam = createParameter("value" as __String, valueType); + return createFunctionType(/*typeParameters*/ undefined, thisParam, [valueParam], valueType, /*typePredicate*/ undefined, 1); + } + + /** + * Creates a call signature for an ES Decorator. This method is used by the semantics of + * `getESDecoratorCallSignature`, which you should probably be using instead. + */ + function createESDecoratorCallSignature(targetType: Type, contextType: Type, nonOptionalReturnType: Type) { + const targetParam = createParameter("target" as __String, targetType); + const contextParam = createParameter("context" as __String, contextType); + const returnType = getUnionType([nonOptionalReturnType, voidType]); + return createCallSignature(/*typeParameters*/ undefined, /*thisParameter*/ undefined, [targetParam, contextParam], returnType); + } + + /** + * Gets a call signature that should be used when resolving `decorator` as a call. This does not use the value + * of the decorator itself, but instead uses the declaration on which it is placed along with its relative + * position amongst other decorators on the same declaration to determine the applicable signature. The + * resulting signature can be used for call resolution, inference, and contextual typing. + */ + function getESDecoratorCallSignature(decorator: Decorator) { + // We are considering a future change that would allow the type of a decorator to affect the type of the + // class and its members, such as a `@Stringify` decorator changing the type of a `number` field to `string`, or + // a `@Callable` decorator adding a call signature to a `class`. The type arguments for the various context + // types may eventually change to reflect such mutations. + // + // In some cases we describe such potential mutations as coming from a "prior decorator application". It is + // important to note that, while decorators are *evaluated* left to right, they are *applied* right to left + // to preserve f ৹ g -> f(g(x)) application order. In these cases, a "prior" decorator usually means the + // next decorator following this one in document order. + // + // The "original type" of a class or member is the type it was declared as, or the type we infer from + // initializers, before _any_ decorators are applied. + // + // The type of a class or member that is a result of a prior decorator application represents the + // "current type", i.e., the type for the declaration at the time the decorator is _applied_. + // + // The type of a class or member that is the result of the application of *all* relevant decorators is the + // "final type". + // + // Any decorator that allows mutation or replacement will also refer to an "input type" and an + // "output type". The "input type" corresponds to the "current type" of the declaration, while the + // "output type" will become either the "input type/current type" for a subsequent decorator application, + // or the "final type" for the decorated declaration. + // + // It is important to understand decorator application order as it relates to how the "current", "input", + // "output", and "final" types will be determined: + // + // @E2 @E1 class SomeClass { + // @A2 @A1 static f() {} + // @B2 @B1 g() {} + // @C2 @C1 static x; + // @D2 @D1 y; + // } + // + // Per [the specification][1], decorators are applied in the following order: + // + // 1. For each static method (incl. get/set methods and `accessor` fields), in document order: + // a. Apply each decorator for that method, in reverse order (`A1`, `A2`). + // 2. For each instance method (incl. get/set methods and `accessor` fields), in document order: + // a. Apply each decorator for that method, in reverse order (`B1`, `B2`). + // 3. For each static field (excl. auto-accessors), in document order: + // a. Apply each decorator for that field, in reverse order (`C1`, `C2`). + // 4. For each instance field (excl. auto-accessors), in document order: + // a. Apply each decorator for that field, in reverse order (`D1`, `D2`). + // 5. Apply each decorator for the class, in reverse order (`E1`, `E2`). + // + // As a result, "current" types at each decorator application are as follows: + // - For `A1`, the "current" types of the class and method are their "original" types. + // - For `A2`, the "current type" of the method is the "output type" of `A1`, and the "current type" of the + // class is the type of `SomeClass` where `f` is the "output type" of `A1`. This becomes the "final type" + // of `f`. + // - For `B1`, the "current type" of the method is its "original type", and the "current type" of the class + // is the type of `SomeClass` where `f` now has its "final type". + // - etc. + // + // [1]: https://arai-a.github.io/ecma262-compare/?pr=2417&id=sec-runtime-semantics-classdefinitionevaluation + // + // This seems complicated at first glance, but is not unlike our existing inference for functions: + // + // declare function pipe( + // original: Original, + // a1: (input: Original, context: Context) => A1, + // a2: (input: A1, context: Context) => A2, + // b1: (input: A2, context: Context) => B1, + // b2: (input: B1, context: Context) => B2, + // c1: (input: B2, context: Context) => C1, + // c2: (input: C1, context: Context) => C2, + // d1: (input: C2, context: Context) => D1, + // d2: (input: D1, context: Context) => D2, + // e1: (input: D2, context: Context) => E1, + // e2: (input: E1, context: Context) => E2, + // ): E2; + + // When a decorator is applied, it is passed two arguments: "target", which is a value representing the + // thing being decorated (constructors for classes, functions for methods/accessors, `undefined` for fields, + // and a `{ get, set }` object for auto-accessors), and "context", which is an object that provides + // reflection information about the decorated element, as well as the ability to add additional "extra" + // initializers. In most cases, the "target" argument corresponds to the "input type" in some way, and the + // return value similarly corresponds to the "output type" (though if the "output type" is `void` or + // `undefined` then the "output type" is the "input type"). + + const { parent } = decorator; + const links = getNodeLinks(parent); + if (!links.decoratorSignature) { + links.decoratorSignature = anySignature; + switch (parent.kind) { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: { + // Class decorators have a `context` of `ClassDecoratorContext`, where the `Class` type + // argument will be the "final type" of the class after all decorators are applied. + + const node = parent as ClassDeclaration | ClassExpression; + const targetType = getTypeOfSymbol(getSymbolOfDeclaration(node)); + const contextType = createClassDecoratorContextType(targetType); + links.decoratorSignature = createESDecoratorCallSignature(targetType, contextType, targetType); + break; + } + + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: { + const node = parent as MethodDeclaration | GetAccessorDeclaration | SetAccessorDeclaration; + if (!isClassLike(node.parent)) break; + + // Method decorators have a `context` of `ClassMethodDecoratorContext`, where the + // `Value` type argument corresponds to the "final type" of the method. + // + // Getter decorators have a `context` of `ClassGetterDecoratorContext`, where the + // `Value` type argument corresponds to the "final type" of the value returned by the getter. + // + // Setter decorators have a `context` of `ClassSetterDecoratorContext`, where the + // `Value` type argument corresponds to the "final type" of the parameter of the setter. + // + // In all three cases, the `This` type argument is the "final type" of either the class or + // instance, depending on whether the member was `static`. + + const valueType = isMethodDeclaration(node) ? getOrCreateTypeFromSignature(getSignatureFromDeclaration(node)) : + getTypeOfNode(node); + + const thisType = hasStaticModifier(node) ? + getTypeOfSymbol(getSymbolOfDeclaration(node.parent)) : + getDeclaredTypeOfClassOrInterface(getSymbolOfDeclaration(node.parent)); + + // We wrap the "input type", if necessary, to match the decoration target. For getters this is + // something like `() => inputType`, for setters it's `(value: inputType) => void` and for + // methods it is just the input type. + const targetType = isGetAccessorDeclaration(node) ? createGetterFunctionType(valueType) : + isSetAccessorDeclaration(node) ? createSetterFunctionType(valueType) : + valueType; + + const contextType = createClassMemberDecoratorContextTypeForNode(node, thisType, valueType); + + // We also wrap the "output type", as needed. + const returnType = isGetAccessorDeclaration(node) ? createGetterFunctionType(valueType) : + isSetAccessorDeclaration(node) ? createSetterFunctionType(valueType) : + valueType; + + links.decoratorSignature = createESDecoratorCallSignature(targetType, contextType, returnType); + break; + } + + case SyntaxKind.PropertyDeclaration: { + const node = parent as PropertyDeclaration; + if (!isClassLike(node.parent)) break; + + // Field decorators have a `context` of `ClassFieldDecoratorContext` and + // auto-accessor decorators have a `context` of `ClassAccessorDecoratorContext. In + // both cases, the `This` type argument is the "final type" of either the class or instance, + // depending on whether the member was `static`, and the `Value` type argument corresponds to + // the "final type" of the value stored in the field. + + const valueType = getTypeOfNode(node); + const thisType = hasStaticModifier(node) ? + getTypeOfSymbol(getSymbolOfDeclaration(node.parent)) : + getDeclaredTypeOfClassOrInterface(getSymbolOfDeclaration(node.parent)); + + // The `target` of an auto-accessor decorator is a `{ get, set }` object, representing the + // runtime-generated getter and setter that are added to the class/prototype. The `target` of a + // regular field decorator is always `undefined` as it isn't installed until it is initialized. + const targetType = hasAccessorModifier(node) ? createClassAccessorDecoratorTargetType(thisType, valueType) : + undefinedType; + + const contextType = createClassMemberDecoratorContextTypeForNode(node, thisType, valueType); + + // We wrap the "output type" depending on the declaration. For auto-accessors, we wrap the + // "output type" in a `ClassAccessorDecoratorResult` type, which allows for + // mutation of the runtime-generated getter and setter, as well as the injection of an + // initializer mutator. For regular fields, we wrap the "output type" in an initializer mutator. + const returnType = hasAccessorModifier(node) ? createClassAccessorDecoratorResultType(thisType, valueType) : + createClassFieldDecoratorInitializerMutatorType(thisType, valueType); + + links.decoratorSignature = createESDecoratorCallSignature(targetType, contextType, returnType); + break; + } + } + } + return links.decoratorSignature === anySignature ? undefined : links.decoratorSignature; + } + + function getLegacyDecoratorCallSignature(decorator: Decorator) { + const { parent } = decorator; + const links = getNodeLinks(parent); + if (!links.decoratorSignature) { + links.decoratorSignature = anySignature; + switch (parent.kind) { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: { + const node = parent as ClassDeclaration | ClassExpression; + // For a class decorator, the `target` is the type of the class (e.g. the + // "static" or "constructor" side of the class). + const targetType = getTypeOfSymbol(getSymbolOfDeclaration(node)); + const targetParam = createParameter("target" as __String, targetType); + links.decoratorSignature = createCallSignature( + /*typeParameters*/ undefined, + /*thisParameter*/ undefined, + [targetParam], + getUnionType([targetType, voidType]), + ); + break; + } + case SyntaxKind.Parameter: { + const node = parent as ParameterDeclaration; + if ( + !isConstructorDeclaration(node.parent) && + !(isMethodDeclaration(node.parent) || isSetAccessorDeclaration(node.parent) && isClassLike(node.parent.parent)) + ) { + break; + } + + if (getThisParameter(node.parent) === node) { + break; + } + + const index = getThisParameter(node.parent) ? + node.parent.parameters.indexOf(node) - 1 : + node.parent.parameters.indexOf(node); + Debug.assert(index >= 0); + + // A parameter declaration decorator will have three arguments (see `ParameterDecorator` in + // core.d.ts). + + const targetType = isConstructorDeclaration(node.parent) ? getTypeOfSymbol(getSymbolOfDeclaration(node.parent.parent)) : + getParentTypeOfClassElement(node.parent); + + const keyType = isConstructorDeclaration(node.parent) ? undefinedType : + getClassElementPropertyKeyType(node.parent); + + const indexType = getNumberLiteralType(index); + + const targetParam = createParameter("target" as __String, targetType); + const keyParam = createParameter("propertyKey" as __String, keyType); + const indexParam = createParameter("parameterIndex" as __String, indexType); + links.decoratorSignature = createCallSignature( + /*typeParameters*/ undefined, + /*thisParameter*/ undefined, + [targetParam, keyParam, indexParam], + voidType, + ); + break; + } + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.PropertyDeclaration: { + const node = parent as MethodDeclaration | AccessorDeclaration | PropertyDeclaration; + if (!isClassLike(node.parent)) break; + + // A method or accessor declaration decorator will have either two or three arguments (see + // `PropertyDecorator` and `MethodDecorator` in core.d.ts). + + const targetType = getParentTypeOfClassElement(node); + const targetParam = createParameter("target" as __String, targetType); + + const keyType = getClassElementPropertyKeyType(node); + const keyParam = createParameter("propertyKey" as __String, keyType); + + const returnType = isPropertyDeclaration(node) ? voidType : + createTypedPropertyDescriptorType(getTypeOfNode(node)); + + const hasPropDesc = !isPropertyDeclaration(parent) || hasAccessorModifier(parent); + if (hasPropDesc) { + const descriptorType = createTypedPropertyDescriptorType(getTypeOfNode(node)); + const descriptorParam = createParameter("descriptor" as __String, descriptorType); + links.decoratorSignature = createCallSignature( + /*typeParameters*/ undefined, + /*thisParameter*/ undefined, + [targetParam, keyParam, descriptorParam], + getUnionType([returnType, voidType]), + ); + } + else { + links.decoratorSignature = createCallSignature( + /*typeParameters*/ undefined, + /*thisParameter*/ undefined, + [targetParam, keyParam], + getUnionType([returnType, voidType]), + ); + } + break; + } + } + } + return links.decoratorSignature === anySignature ? undefined : links.decoratorSignature; + } + + function getDecoratorCallSignature(decorator: Decorator) { + return legacyDecorators ? getLegacyDecoratorCallSignature(decorator) : + getESDecoratorCallSignature(decorator); + } + + 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]); + } + + 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]); + } + + return unknownType; + } + + 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_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_requires_the_Promise_constructor_Make_sure_you_have_a_declaration_for_the_Promise_constructor_or_include_ES2015_in_your_lib_option, + ); + } + + return promiseType; + } + + 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.links.type = targetType; + + const members = createSymbolTable([targetPropertySymbol]); + symbol.members = members; + return createAnonymousType(symbol, members, emptyArray, emptyArray, emptyArray); + } + + function getReturnTypeFromBody(func: FunctionLikeDeclaration, checkMode?: CheckMode): Type { + if (!func.body) { + return errorType; + } + + const functionFlags = getFunctionFlags(func); + const isAsync = (functionFlags & FunctionFlags.Async) !== 0; + const isGenerator = (functionFlags & FunctionFlags.Generator) !== 0; + + 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/undefined, but rather a Promise for void/undefined. + const contextualReturnType = getContextualReturnType(func, /*contextFlags*/ undefined); + const returnType = contextualReturnType && (unwrapReturnType(contextualReturnType, functionFlags) || voidType).flags & TypeFlags.Undefined ? undefinedType : voidType; + return functionFlags & FunctionFlags.Async ? createPromiseReturnType(func, returnType) : // Async function + returnType; // 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); + } + } + + if (yieldType) yieldType = getWidenedType(yieldType); + if (returnType) returnType = getWidenedType(returnType); + if (nextType) nextType = getWidenedType(nextType); + } + + if (isGenerator) { + return createGeneratorType( + 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; + } + } + + function createGeneratorType(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; + } + + 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 { + nextType = getContextualType(yieldExpression, /*contextFlags*/ undefined); + } + 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 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; + } + } + 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 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); + } + 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); + } + + 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 => { + let expr = returnStatement.expression; + if (expr) { + expr = skipParentheses(expr, /*excludeJSDocTypeAssertions*/ true); + // Bare calls to this same function don't contribute to inference + // and `return await` is also safe to unwrap here + if (functionFlags & FunctionFlags.Async && expr.kind === SyntaxKind.AwaitExpression) { + expr = skipParentheses((expr as AwaitExpression).expression, /*excludeJSDocTypeAssertions*/ true); + } + if ( + expr.kind === SyntaxKind.CallExpression && + (expr as CallExpression).expression.kind === SyntaxKind.Identifier && + checkExpressionCached((expr as CallExpression).expression).symbol === getMergedSymbol(func.symbol) && + (!isFunctionExpressionOrArrowFunction(func.symbol.valueDeclaration!) || isConstantReference((expr as CallExpression).expression)) + ) { + hasReturnOfTypeNever = true; + return; + } + + 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); + } + else { + hasReturnWithNoExpression = true; + } + }); + if (aggregatedTypes.length === 0 && !hasReturnWithNoExpression && (hasReturnOfTypeNever || mayReturnNever(func))) { + return undefined; + } + 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; + } + } + + function getTypePredicateFromBody(func: FunctionLikeDeclaration): TypePredicate | undefined { + switch (func.kind) { + case SyntaxKind.Constructor: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + return undefined; + } + const functionFlags = getFunctionFlags(func); + if (functionFlags !== FunctionFlags.Normal) return undefined; + + // Only attempt to infer a type predicate if there's exactly one return. + let singleReturn: Expression | undefined; + if (func.body && func.body.kind !== SyntaxKind.Block) { + singleReturn = func.body; // arrow function + } + else { + const bailedEarly = forEachReturnStatement(func.body as Block, returnStatement => { + if (singleReturn || !returnStatement.expression) return true; + singleReturn = returnStatement.expression; + }); + if (bailedEarly || !singleReturn || functionHasImplicitReturn(func)) return undefined; + } + return checkIfExpressionRefinesAnyParameter(func, singleReturn); + } + + function checkIfExpressionRefinesAnyParameter(func: FunctionLikeDeclaration, expr: Expression): TypePredicate | undefined { + expr = skipParentheses(expr, /*excludeJSDocTypeAssertions*/ true); + const returnType = checkExpressionCached(expr); + if (!(returnType.flags & TypeFlags.Boolean)) return undefined; + + return forEach(func.parameters, (param, i) => { + const initType = getTypeOfSymbol(param.symbol); + if (!initType || initType.flags & TypeFlags.Boolean || !isIdentifier(param.name) || isSymbolAssigned(param.symbol) || isRestParameter(param)) { + // Refining "x: boolean" to "x is true" or "x is false" isn't useful. + return; + } + const trueType = checkIfExpressionRefinesParameter(func, expr, param, initType); + if (trueType) { + return createTypePredicate(TypePredicateKind.Identifier, unescapeLeadingUnderscores(param.name.escapedText), i, trueType); + } + }); + } + + function checkIfExpressionRefinesParameter(func: FunctionLikeDeclaration, expr: Expression, param: ParameterDeclaration, initType: Type): Type | undefined { + const antecedent = (expr as Expression & { flowNode?: FlowNode; }).flowNode || + expr.parent.kind === SyntaxKind.ReturnStatement && (expr.parent as ReturnStatement).flowNode || + createFlowNode(FlowFlags.Start, /*node*/ undefined, /*antecedent*/ undefined); + const trueCondition = createFlowNode(FlowFlags.TrueCondition, expr, antecedent); + + const trueType = getFlowTypeOfReference(param.name, initType, initType, func, trueCondition); + if (trueType === initType) return undefined; + + // "x is T" means that x is T if and only if it returns true. If it returns false then x is not T. + // This means that if the function is called with an argument of type trueType, there can't be anything left in the `else` branch. It must reduce to `never`. + const falseCondition = createFlowNode(FlowFlags.FalseCondition, expr, antecedent); + const falseSubtype = getFlowTypeOfReference(param.name, initType, trueType, func, falseCondition); + return falseSubtype.flags & TypeFlags.Never ? trueType : undefined; + } + + /** + * 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 an explicitly specified return type that includes `void` or is exactly `any` or `undefined` don't + // need any return statements. + if (type && (maybeTypeOfKind(type, TypeFlags.Void) || type.flags & (TypeFlags.Any | TypeFlags.Undefined))) { + 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; + + 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_undefined_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 (isUnwrappedReturnTypeUndefinedVoidOrAny(func, inferredReturnType)) { + return; + } + } + 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); + + 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(/*declaration*/ undefined, /*typeParameters*/ undefined, /*thisParameter*/ undefined, emptyArray, returnType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.IsNonInferrable); + const returnOnlyType = createAnonymousType(node.symbol, emptySymbols, [returnOnlySignature], emptyArray, emptyArray); + returnOnlyType.objectFlags |= ObjectFlags.NonInferrableType; + return links.contextFreeType = returnOnlyType; + } + } + return anyFunctionType; + } + + // Grammar checking + const hasGrammarError = checkGrammarFunctionLikeDeclaration(node); + if (!hasGrammarError && node.kind === SyntaxKind.FunctionExpression) { + checkGrammarForGenerator(node); + } + + contextuallyCheckFunctionExpressionOrObjectLiteralMethod(node, checkMode); + + return getTypeOfSymbol(getSymbolOfDeclaration(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. + 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(getSymbolOfDeclaration(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); + } + } + else if (contextualSignature && !node.typeParameters && contextualSignature.parameters.length > node.parameters.length) { + const inferenceContext = getInferenceContext(node); + if (checkMode && checkMode & CheckMode.Inferential) { + inferFromAnnotatedParameters(signature, contextualSignature, inferenceContext!); + } + } + if (contextualSignature && !getReturnTypeFromAnnotation(node) && !signature.resolvedReturnType) { + const returnType = getReturnTypeFromBody(node, checkMode); + if (!signature.resolvedReturnType) { + signature.resolvedReturnType = returnType; + } + } + checkSignatureDeclaration(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); + + 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) { + const effectiveCheckNode = getEffectiveCheckNode(node.body); + if ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.Async) { // Async function + const awaitedType = checkAwaitedType(exprType, /*withAlias*/ false, effectiveCheckNode, 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, effectiveCheckNode, effectiveCheckNode); + } + else { // Normal function + checkTypeAssignableToAndOptionallyElaborate(exprType, returnOrPromisedType, effectiveCheckNode, effectiveCheckNode); + } + } + } + } + } + + 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; + } + 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; + } + // 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.Constant || + 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 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 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); + } + else { + checkDeleteExpressionMustBeOptional(expr, symbol); + } + } + 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 : hasTypeFacts(type, TypeFacts.IsUndefined)) + ) { + error(expr, Diagnostics.The_operand_of_a_delete_operator_must_be_optional); + } + } + + function checkTypeOfExpression(node: TypeOfExpression): Type { + checkExpression(node.expression); + return typeofType; + } + + function checkVoidExpression(node: VoidExpression): Type { + checkNodeDeferred(node); + return undefinedWideningType; + } + + function checkAwaitGrammar(node: AwaitExpression | VariableDeclarationList): boolean { + // Grammar checking + let hasError = false; + const container = getContainingFunctionOrClassStaticBlock(node); + if (container && isClassStaticBlockDeclaration(container)) { + // NOTE: We report this regardless as to whether there are parse diagnostics. + const message = isAwaitExpression(node) ? Diagnostics.await_expression_cannot_be_used_inside_a_class_static_block : + Diagnostics.await_using_statements_cannot_be_used_inside_a_class_static_block; + error(node, message); + hasError = true; + } + 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 message = isAwaitExpression(node) ? 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.await_using_statements_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; + const diagnostic = createFileDiagnostic(sourceFile, span.start, span.length, message); + diagnostics.add(diagnostic); + hasError = true; + } + 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), + ); + hasError = true; + break; + } + // fallthrough + case ModuleKind.ES2022: + case ModuleKind.ESNext: + case ModuleKind.Preserve: + case ModuleKind.System: + if (languageVersion >= ScriptTarget.ES2017) { + break; + } + // fallthrough + default: + span ??= getSpanOfTokenAtPosition(sourceFile, node.pos); + const message = isAwaitExpression(node) ? Diagnostics.Top_level_await_expressions_are_only_allowed_when_the_module_option_is_set_to_es2022_esnext_system_node16_nodenext_or_preserve_and_the_target_option_is_set_to_es2017_or_higher : + Diagnostics.Top_level_await_using_statements_are_only_allowed_when_the_module_option_is_set_to_es2022_esnext_system_node16_nodenext_or_preserve_and_the_target_option_is_set_to_es2017_or_higher; + diagnostics.add(createFileDiagnostic(sourceFile, span.start, span.length, message)); + hasError = true; + break; + } + } + } + else { + // use of 'await' in non-async function + const sourceFile = getSourceFileOfNode(node); + if (!hasParseDiagnostics(sourceFile)) { + const span = getSpanOfTokenAtPosition(sourceFile, node.pos); + const message = isAwaitExpression(node) ? Diagnostics.await_expressions_are_only_allowed_within_async_functions_and_at_the_top_levels_of_modules : + Diagnostics.await_using_statements_are_only_allowed_within_async_functions_and_at_the_top_levels_of_modules; + const diagnostic = createFileDiagnostic(sourceFile, span.start, span.length, message); + 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); + hasError = true; + } + } + } + + if (isAwaitExpression(node) && isInParameterInitializerBeforeContainingFunction(node)) { + // NOTE: We report this regardless as to whether there are parse diagnostics. + error(node, Diagnostics.await_expressions_cannot_be_used_in_a_parameter_initializer); + hasError = true; + } + + return hasError; + } + + function checkAwaitExpression(node: AwaitExpression): Type { + addLazyDiagnostic(() => checkAwaitGrammar(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; + } + + 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))); + } + return numberType; + } + return getUnaryResultType(operandType); + case SyntaxKind.ExclamationToken: + checkTruthinessOfType(operandType, 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( + 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); + } + + 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); + } + + // 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; + } + + 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 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 isConstEnumObjectType(type: Type): boolean { + return !!(getObjectFlags(type) & ObjectFlags.Anonymous) && !!type.symbol && isConstEnumSymbol(type.symbol); + } + + function isConstEnumSymbol(symbol: Symbol): boolean { + return (symbol.flags & SymbolFlags.ConstEnum) !== 0; + } + + /** + * Get the type of the `[Symbol.hasInstance]` method of an object type. + */ + function getSymbolHasInstanceMethodOfObjectType(type: Type) { + const hasInstancePropertyName = getPropertyNameForKnownSymbolName("hasInstance"); + if (allTypesAssignableToKind(type, TypeFlags.NonPrimitive)) { + const hasInstanceProperty = getPropertyOfType(type, hasInstancePropertyName); + if (hasInstanceProperty) { + const hasInstancePropertyType = getTypeOfSymbol(hasInstanceProperty); + if (hasInstancePropertyType && getSignaturesOfType(hasInstancePropertyType, SignatureKind.Call).length !== 0) { + return hasInstancePropertyType; + } + } + } + } + + function checkInstanceOfExpression(left: Expression, right: Expression, leftType: Type, rightType: Type, checkMode?: CheckMode): 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); + } + + Debug.assert(isInstanceOfExpression(left.parent)); + const signature = getResolvedSignature(left.parent, /*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 rightType has a `[Symbol.hasInstance]` method that is not `(value: unknown) => boolean`, we + // must check the expression as if it were a call to `right[Symbol.hasInstance](left)`. The call to + // `getResolvedSignature`, below, will check that leftType is assignable to the type of the first + // parameter. + const returnType = getReturnTypeOfSignature(signature); + + // We also verify that the return type of the `[Symbol.hasInstance]` method is assignable to + // `boolean`. According to the spec, the runtime will actually perform `ToBoolean` on the result, + // but this is more type-safe. + checkTypeAssignableTo(returnType, booleanType, right, Diagnostics.An_object_s_Symbol_hasInstance_method_must_return_a_boolean_value_for_it_to_be_used_on_the_right_hand_side_of_an_instanceof_expression); + + return booleanType; + } + + function hasEmptyObjectIntersection(type: Type): boolean { + return someType(type, t => t === unknownEmptyObjectType || !!(t.flags & TypeFlags.Intersection) && isEmptyAnonymousObjectType(getBaseConstraintOrType(t))); + } + + function checkInExpression(left: Expression, right: Expression, leftType: Type, rightType: Type): Type { + if (leftType === silentNeverType || rightType === silentNeverType) { + return silentNeverType; + } + if (isPrivateIdentifier(left)) { + if ( + languageVersion < LanguageFeatureMinimumTarget.PrivateNamesAndClassStaticBlocks || + languageVersion < LanguageFeatureMinimumTarget.ClassAndClassElementDecorators || + !useDefineForClassFields + ) { + 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); + } + } + 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; + } + + 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); + } + } + 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 < LanguageFeatureMinimumTarget.ObjectSpreadRest) { + checkExternalEmitHelpers(property, ExternalEmitHelpers.Rest); + } + const nonRestNames: PropertyName[] = []; + if (allProperties) { + for (const otherProperty of allProperties) { + if (!isSpreadAssignment(otherProperty)) { + nonRestNames.push(otherProperty.name); + } + } + } + const type = getRestType(objectLiteralType, nonRestNames, objectLiteralType.symbol); + checkGrammarForDisallowedTrailingComma(allProperties, Diagnostics.A_rest_parameter_or_binding_pattern_may_not_have_a_trailing_comma); + return checkDestructuringAssignment(property.expression, type); + } + } + else { + error(property, Diagnostics.Property_assignment_expected); + } + } + + function checkArrayLiteralAssignment(node: ArrayLiteralExpression, sourceType: Type, checkMode?: CheckMode): Type { + const elements = node.elements; + if (languageVersion < LanguageFeatureMinimumTarget.DestructuringAssignment && 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 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 { + 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; + } + + 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 && + !(hasTypeFacts(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) { + sourceType = getTypeWithFacts(sourceType, TypeFacts.NEUndefined); + } + } + 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); + } + + 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)) { + // NOTE: we do not limit this to LanguageFeatureTargets.PrivateNames as some other feature downleveling still requires this. + checkExternalEmitHelpers(target.parent, ExternalEmitHelpers.ClassPrivateFieldSet); + } + return sourceType; + } + + /** + * 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); + + 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; + + // 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 isTypeEqualityComparableTo(source: Type, target: Type) { + return (target.flags & TypeFlags.Nullable) !== 0 || isTypeComparableTo(source, target); + } + + 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)[]; + } + + const trampoline = createBinaryExpressionTrampoline(onEnter, onLeft, onOperator, onRight, onExit, foldState); + + return (node: BinaryExpression, checkMode: CheckMode | undefined) => { + const result = trampoline(node, checkMode); + Debug.assertIsDefined(result); + return result; + }; + + 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], + }; + } + + if (isInJSFile(node) && getAssignedExpandoInitializer(node)) { + state.skip = true; + setLastResult(state, checkExpression(node.right, checkMode)); + 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; + } + + 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 (isLogicalOrCoalescingBinaryOperator(operator)) { + let parent = node.parent; + while (parent.kind === SyntaxKind.ParenthesizedExpression || isLogicalOrCoalescingBinaryExpression(parent)) { + parent = parent.parent; + } + if (operator === SyntaxKind.AmpersandAmpersandToken || isIfStatement(parent)) { + checkTestingKnownTruthyCallableOrAwaitableOrEnumMemberType(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 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); + + result = checkBinaryLikeExpressionWorker(node.left, node.operatorToken, node.right, leftType, rightType, state.checkMode, node); + } + + state.skip = false; + setLeftType(state, /*type*/ undefined); + setLastResult(state, /*type*/ undefined); + state.stackIndex--; + return result; + } + + function foldState(state: WorkArea, result: Type | undefined, _side: "left" | "right") { + setLastResult(state, result); + return state; + } + + function maybeCheckExpression(state: WorkArea, node: Expression): BinaryExpression | undefined { + if (isBinaryExpression(node)) { + return node; + } + setLastResult(state, checkExpression(node, state.checkMode)); + } + + function getLeftType(state: WorkArea) { + return state.typeStack[state.stackIndex]; + } + + function setLeftType(state: WorkArea, type: Type | undefined) { + state.typeStack[state.stackIndex] = type; + } + + function getLastResult(state: WorkArea) { + return state.typeStack[state.stackIndex + 1]; + } + + 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 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)); + } + } + } + + // 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: BinaryOperatorToken, 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 (isLogicalOrCoalescingBinaryOperator(operator)) { + leftType = checkTruthinessExpression(left, checkMode); + } + else { + leftType = checkExpression(left, checkMode); + } + + const rightType = checkExpression(right, checkMode); + return checkBinaryLikeExpressionWorker(left, operatorToken, right, leftType, rightType, checkMode, errorNode); + } + + function checkBinaryLikeExpressionWorker( + left: Expression, + operatorToken: BinaryOperatorToken, + right: Expression, + leftType: Type, + rightType: Type, + checkMode?: CheckMode, + 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: PunctuationSyntaxKind | 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; + } + + if (!isTypeAssignableToKind(leftType, TypeFlags.StringLike) && !isTypeAssignableToKind(rightType, TypeFlags.StringLike)) { + leftType = checkNonNullType(leftType, left); + rightType = checkNonNullType(rightType, right); + } + + 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; + } + + // Symbols are not allowed at all in arithmetic expressions + if (resultType && !checkForDisallowedESSymbolOperand(operator)) { + 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; + } + + if (operator === SyntaxKind.PlusEqualsToken) { + checkAssignmentOperator(resultType); + } + return resultType; + case SyntaxKind.LessThanToken: + case SyntaxKind.GreaterThanToken: + case SyntaxKind.LessThanEqualsToken: + case SyntaxKind.GreaterThanEqualsToken: + if (checkForDisallowedESSymbolOperand(operator)) { + leftType = getBaseTypeOfLiteralTypeForComparison(checkNonNullType(leftType, left)); + rightType = getBaseTypeOfLiteralTypeForComparison(checkNonNullType(rightType, right)); + reportOperatorErrorUnless((left, right) => { + if (isTypeAny(left) || isTypeAny(right)) { + return true; + } + const leftAssignableToNumber = isTypeAssignableTo(left, numberOrBigIntType); + const rightAssignableToNumber = isTypeAssignableTo(right, numberOrBigIntType); + return leftAssignableToNumber && rightAssignableToNumber || + !leftAssignableToNumber && !rightAssignableToNumber && areTypesComparable(left, right); + }); + } + return booleanType; + case SyntaxKind.EqualsEqualsToken: + case SyntaxKind.ExclamationEqualsToken: + case SyntaxKind.EqualsEqualsEqualsToken: + case SyntaxKind.ExclamationEqualsEqualsToken: + // We suppress errors in CheckMode.TypeOnly (meaning the invocation came from getTypeOfExpression). During + // control flow analysis it is possible for operands to temporarily have narrower types, and those narrower + // types may cause the operands to not be comparable. We don't want such errors reported (see #46475). + if (!(checkMode && checkMode & CheckMode.TypeOnly)) { + if ( + (isLiteralExpressionOfObject(left) || isLiteralExpressionOfObject(right)) && + // only report for === and !== in JS, not == or != + (!isInJSFile(left) || (operator === SyntaxKind.EqualsEqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken)) + ) { + 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, checkMode); + case SyntaxKind.InKeyword: + return checkInExpression(left, right, leftType, rightType); + case SyntaxKind.AmpersandAmpersandToken: + case SyntaxKind.AmpersandAmpersandEqualsToken: { + const resultType = hasTypeFacts(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 = hasTypeFacts(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 = hasTypeFacts(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 leftType; + } + else { + checkAssignmentOperator(rightType); + return rightType; + } + case SyntaxKind.CommaToken: + if (!compilerOptions.allowUnreachableCode && isSideEffectFree(left) && !isIndirectCall(left.parent as BinaryExpression)) { + 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(); + } + + 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, /*nameNotFoundMessage*/ undefined, /*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); + } + } + } + } + } + + // Return true for "indirect calls", (i.e. `(0, x.f)(...)` or `(0, eval)(...)`), which prevents passing `this`. + function isIndirectCall(node: BinaryExpression): boolean { + return node.parent.kind === SyntaxKind.ParenthesizedExpression && + isNumericLiteral(node.left) && + node.left.text === "0" && + (isCallExpression(node.parent.parent) && node.parent.parent.expression === node.parent || node.parent.parent.kind === SyntaxKind.TaggedTemplateExpression) && + // special-case for "eval" because it's the only non-access case where an indirect call actually affects behavior. + (isAccessExpression(node.right) || isIdentifier(node.right) && node.right.escapedText === "eval"); + } + + // Return true if there was no error, false if there was an error. + function checkForDisallowedESSymbolOperand(operator: PunctuationSyntaxKind): boolean { + const offendingSymbolOperand = maybeTypeOfKindConsideringBaseConstraint(leftType, TypeFlags.ESSymbolLike) ? left : + maybeTypeOfKindConsideringBaseConstraint(rightType, TypeFlags.ESSymbolLike) ? right : + undefined; + + if (offendingSymbolOperand) { + error(offendingSymbolOperand, Diagnostics.The_0_operator_cannot_be_applied_to_type_symbol, tokenToString(operator)); + return false; + } + + return true; + } + + function getSuggestedBooleanOperator(operator: SyntaxKind): PunctuationSyntaxKind | 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 checkAssignmentOperatorWorker() { + let assigneeType = leftType; + + // getters can be a subtype of setters, so to check for assignability we use the setter's type instead + if (isCompoundAssignment(operatorToken.kind) && left.kind === SyntaxKind.PropertyAccessExpression) { + assigneeType = checkPropertyAccessExpression(left as PropertyAccessExpression, /*checkMode*/ undefined, /*writeOnly*/ true); + } + + // 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)) { + 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, assigneeType, 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); + return true; + } + 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); + } + + 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 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 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 isGlobalNaN(expr: Expression): boolean { + if (isIdentifier(expr) && expr.escapedText === "NaN") { + const globalNaNSymbol = getGlobalNaNSymbol(); + return !!globalNaNSymbol && globalNaNSymbol === getResolvedSymbol(expr); + } + return false; + } + } + + 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]; + } + + function checkYieldExpression(node: YieldExpression): Type { + addLazyDiagnostic(checkYieldExpressionGrammar); + + const func = getContainingFunction(node); + if (!func) return anyType; + const functionFlags = getFunctionFlags(func); + + 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 isAsync = (functionFlags & FunctionFlags.Async) !== 0; + if (node.asteriskToken) { + // Async generator functions prior to ES2018 require the __await, __asyncDelegator, + // and __asyncValues helpers + if (isAsync && languageVersion < LanguageFeatureMinimumTarget.AsyncGenerators) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.AsyncDelegatorIncludes); + } + + // Generator functions prior to ES2015 require the __values helper + if (!isAsync && languageVersion < LanguageFeatureMinimumTarget.Generators && compilerOptions.downlevelIteration) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.Values); + } + } + + // 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. + let returnType = getReturnTypeFromAnnotation(func); + if (returnType && returnType.flags & TypeFlags.Union) { + returnType = filterType(returnType, t => checkGeneratorInstantiationAssignabilityToReturnType(t, functionFlags, /*errorNode*/ undefined)); + } + 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); + } + + 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; + + function checkYieldExpressionGrammar() { + if (!(node.flags & NodeFlags.YieldContext)) { + grammarErrorOnFirstToken(node, Diagnostics.A_yield_expression_is_only_allowed_in_a_generator_body); + } + + if (isInParameterInitializerBeforeContainingFunction(node)) { + error(node, Diagnostics.yield_expressions_cannot_be_used_in_a_parameter_initializer); + } + } + } + + function checkConditionalExpression(node: ConditionalExpression, checkMode?: CheckMode): Type { + const type = checkTruthinessExpression(node.condition, checkMode); + checkTestingKnownTruthyCallableOrAwaitableOrEnumMemberType(node.condition, type, node.whenTrue); + const type1 = checkExpression(node.whenTrue, checkMode); + const type2 = checkExpression(node.whenFalse, checkMode); + return getUnionType([type1, type2], UnionReduction.Subtype); + } + + 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); + } + texts.push(span.literal.text); + types.push(isTypeAssignableTo(type, templateConstraintType) ? type : stringType); + } + const evaluated = node.parent.kind !== SyntaxKind.TaggedTemplateExpression && evaluate(node).value; + if (evaluated) { + return getFreshTypeOfLiteralType(getStringLiteralType(evaluated)); + } + if (isConstContext(node) || isTemplateLiteralContext(node) || someType(getContextualType(node, /*contextFlags*/ undefined) || unknownType, isTemplateLiteralContextualType)) { + return getTemplateLiteralType(texts, types); + } + return stringType; + } + + 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): Expression { + if (isJsxAttributes(node) && !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 contextNode = getContextNode(node); + pushContextualType(contextNode, contextualType, /*isCache*/ false); + pushInferenceContext(contextNode, 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; + popInferenceContext(); + popContextualType(); + return result; + } + + 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)!; + if (isInJSFile(declaration)) { + const typeNode = tryGetJSDocSatisfiesTypeNode(declaration); + if (typeNode) { + return checkSatisfiesExpressionWorker(initializer, typeNode, checkMode); + } + } + 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.combinedFlags & ElementFlags.Variable) && getTypeReferenceArity(type) < declaration.name.elements.length ? + padTupleType(type, declaration.name) : type; + } + + function padTupleType(type: TupleTypeReference, pattern: ArrayBindingPattern) { + const patternElements = pattern.elements; + const elementTypes = getElementTypes(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 = getCombinedNodeFlagsCached(declaration) & NodeFlags.Constant || 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 widened; + } + + 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 isConstContext(node: Expression): boolean { + const parent = node.parent; + return isAssertionExpression(parent) && isConstTypeReference(parent.type) || + isJSDocTypeAssertion(parent) && isConstTypeReference(getJSDocTypeAssertionType(parent)) || + isValidConstAssertionArgument(node) && isConstTypeVariable(getContextualType(node, ContextFlags.None)) || + (isParenthesizedExpression(parent) || isArrayLiteralExpression(parent) || isSpreadElement(parent)) && isConstContext(parent) || + (isPropertyAssignment(parent) || isShorthandPropertyAssignment(parent) || isTemplateSpan(parent)) && isConstContext(parent.parent); + } + + function checkExpressionForMutableLocation(node: Expression, checkMode: CheckMode | undefined, forceTuple?: boolean): Type { + const type = checkExpression(node, checkMode, forceTuple); + return isConstContext(node) || isCommonJsExportedExpression(node) ? getRegularTypeOfLiteralType(type) : + isTypeAssertion(node) ? type : + getWidenedLiteralLikeTypeForContextualType(type, instantiateContextualType(getContextualType(node, /*contextFlags*/ undefined), node, /*contextFlags*/ undefined)); + } + + 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); + } + + return checkExpressionForMutableLocation(node.initializer, checkMode); + } + + function checkObjectLiteralMethod(node: MethodDeclaration, checkMode?: CheckMode): Type { + // Grammar checking + checkGrammarMethod(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.kind === SyntaxKind.ComputedPropertyName) { + checkComputedPropertyName(node.name); + } + + 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 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); + } + } + } + // TODO: The signature may reference any outer inference contexts, but we map pop off and then apply new inference contexts, and thus get different inferred types. + // That this is cached on the *first* such attempt is not currently an issue, since expression types *also* get cached on the first pass. If we ever properly speculate, though, + // the cached "isolatedSignatureType" signature field absolutely needs to be included in the list of speculative caches. + return getOrCreateTypeFromSignature(instantiateSignatureInContextOf(signature, contextualSignature, context), flatMap(inferenceContexts, c => c && map(c.inferences, i => i.typeParameter)).slice()); + } + } + } + } + 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 hasInferenceCandidates(info: InferenceInfo) { + return !!(info.candidates || info.contraCandidates); + } + + 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; + } + } + 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 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); + } + } + if (newTypeParameters) { + const mapper = createTypeMapper(oldTypeParameters!, newTypeParameters); + for (const tp of newTypeParameters) { + tp.mapper = mapper; + } + } + 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; + } + } + } + + function getReturnTypeOfSingleNonGenericCallSignature(funcType: Type) { + const signature = getSingleCallSignature(funcType); + if (signature && !signature.typeParameters) { + return getReturnTypeOfSignature(signature); + } + } + + 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); + } + + /** + * 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, CheckMode.TypeOnly); + // 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 getQuickTypeOfExpression(node: Expression): Type | undefined { + let expr = skipParentheses(node, /*excludeJSDocTypeAssertions*/ true); + if (isJSDocTypeAssertion(expr)) { + const type = getJSDocTypeAssertionType(expr); + if (!isConstTypeReference(type)) { + return getTypeFromTypeNode(type); + } + } + expr = skipParentheses(node); + if (isAwaitExpression(expr)) { + const type = getQuickTypeOfExpression(expr.expression); + return type ? getAwaitedType(type) : undefined; + } + // 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, /*requireStringLiteralLikeArgument*/ true) && !isSymbolOrSymbolForCall(expr)) { + return isCallChain(expr) ? getReturnTypeOfSingleNonGenericSignatureOfCallChain(expr) : + getReturnTypeOfSingleNonGenericCallSignature(checkNonNullExpression(expr.expression)); + } + else if (isAssertionExpression(expr) && !isConstTypeReference(expr.type)) { + return getTypeFromTypeNode((expr as TypeAssertion).type); + } + else if (isLiteralExpression(node) || isBooleanLiteral(node)) { + 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. + * 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; + } + pushContextualType(node, anyType, /*isCache*/ false); + const type = links.contextFreeType = checkExpression(node, CheckMode.SkipContextSensitive); + popContextualType(); + return type; + } + + 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; + } + + 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 (!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); + } + + if (getIsolatedModules(compilerOptions)) { + Debug.assert(!!(type.symbol.flags & SymbolFlags.ConstEnum)); + const constEnumDeclaration = type.symbol.valueDeclaration as EnumDeclaration; + const redirect = host.getRedirectReferenceForResolutionFromSourceOfProject(getSourceFileOfNode(constEnumDeclaration).resolvedPath); + if (constEnumDeclaration.flags & NodeFlags.Ambient && !isValidTypeOnlyAliasUseSite(node) && (!redirect || !shouldPreserveConstEnums(redirect.commandLine.options))) { + error(node, Diagnostics.Cannot_access_ambient_const_enums_when_0_is_enabled, isolatedModulesLikeFlagName); + } + } + } + + function checkParenthesizedExpression(node: ParenthesizedExpression, checkMode?: CheckMode): Type { + if (hasJSDocNodes(node)) { + if (isJSDocSatisfiesExpression(node)) { + return checkSatisfiesExpressionWorker(node.expression, getJSDocSatisfiesExpressionType(node), checkMode); + } + if (isJSDocTypeAssertion(node)) { + return checkAssertionWorker(node, 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(); + } + } + 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 hasSkipDirectInferenceFlag(node) ? + blockedStringType : + 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 checkRegularExpressionLiteral(node as RegularExpressionLiteral); + 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, checkMode); + 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 + + 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(getSymbolOfDeclaration(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(getSymbolOfDeclaration(node)); + const modifiers = getTypeParameterModifiers(typeParameter) & (ModifierFlags.In | ModifierFlags.Out); + if (modifiers) { + const symbol = getSymbolOfDeclaration(node.parent); + if (isTypeAliasDeclaration(node.parent) && !(getObjectFlags(getDeclaredTypeOfSymbol(symbol)) & (ObjectFlags.Reference | 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). + checkGrammarModifiers(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); + } + } + if (!node.initializer && isOptionalDeclaration(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); + } + 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); + } + } + + // 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); + } + } + + 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; + } + + const signature = getSignatureFromDeclaration(parent); + const typePredicate = getTypePredicateOfSignature(signature); + if (!typePredicate) { + return; + } + + 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 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); + 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); + } + + const functionFlags = getFunctionFlags(node as FunctionLikeDeclaration); + if (!(functionFlags & FunctionFlags.Invalid)) { + // Async generators prior to ES2018 require the __await and __asyncGenerator helpers + if ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.AsyncGenerator && languageVersion < LanguageFeatureMinimumTarget.AsyncGenerators) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.AsyncGeneratorIncludes); + } + + // Async functions prior to ES2017 require the __awaiter helper + if ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.Async && languageVersion < LanguageFeatureMinimumTarget.AsyncFunctions) { + 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 < LanguageFeatureMinimumTarget.Generators) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.Generator); + } + } + + checkTypeParameters(getEffectiveTypeParameterDeclarations(node)); + checkUnmatchedJSDocParameters(node); + + forEach(node.parameters, checkParameter); + + // TODO(rbuckton): Should we start checking JSDoc types? + if (node.type) { + checkSourceElement(node.type); + } + + addLazyDiagnostic(checkSignatureDeclarationDiagnostics); + + function checkSignatureDeclarationDiagnostics() { + checkCollisionWithArgumentsInGeneratedCode(node); + + let returnTypeNode = getEffectiveReturnTypeNode(node); + let returnTypeErrorLocation = returnTypeNode; + + if (isInJSFile(node)) { + const typeTag = getJSDocTypeTag(node); + if (typeTag && typeTag.typeExpression && isTypeReferenceNode(typeTag.typeExpression.type)) { + const signature = getSingleCallSignature(getTypeFromTypeNode(typeTag.typeExpression)); + if (signature && signature.declaration) { + returnTypeNode = getEffectiveReturnTypeNode(signature.declaration); + returnTypeErrorLocation = typeTag.typeExpression.type; + } + } + } + + 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 && returnTypeErrorLocation) { + const functionFlags = getFunctionFlags(node as FunctionDeclaration); + if ((functionFlags & (FunctionFlags.Invalid | FunctionFlags.Generator)) === FunctionFlags.Generator) { + const returnType = getTypeFromTypeNode(returnTypeNode); + if (returnType === voidType) { + error(returnTypeErrorLocation, Diagnostics.A_generator_cannot_have_a_void_type_annotation); + } + else { + checkGeneratorInstantiationAssignabilityToReturnType(returnType, functionFlags, returnTypeErrorLocation); + } + } + else if ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.Async) { + checkAsyncFunctionReturnType(node as FunctionLikeDeclaration, returnTypeNode, returnTypeErrorLocation); + } + } + if (node.kind !== SyntaxKind.IndexSignature && node.kind !== SyntaxKind.JSDocFunctionType) { + registerForUnusedIdentifiersCheck(node); + } + } + } + + function checkGeneratorInstantiationAssignabilityToReturnType(returnType: Type, functionFlags: FunctionFlags, errorNode?: TypeNode) { + // 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 = createGeneratorType(generatorYieldType, generatorReturnType, generatorNextType, !!(functionFlags & FunctionFlags.Async)); + + return checkTypeAssignableTo(generatorInstantiation, returnType, errorNode); + } + + 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; + + const memberName = name && getEffectivePropertyNameForPropertyNameNode(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.PropertyDeclaration: + addName(names, name, memberName, DeclarationMeaning.GetOrSetAccessor | privateStaticFlags); + break; + + case SyntaxKind.MethodDeclaration: + addName(names, name, memberName, DeclarationMeaning.Method | privateStaticFlags); + break; + } + } + } + } + + function addName(names: Map<__String, DeclarationMeaning>, 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) { + error(location, Diagnostics.Duplicate_identifier_0, getTextOfNode(location)); + } + else { + names.set(name, prev | 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 = getEffectivePropertyNameForPropertyNameNode(memberNameNode); + switch (memberName) { + case "name": + case "length": + case "caller": + case "arguments": + if (useDefineForClassFields) { + break; + } + // fall through + case "prototype": + const message = Diagnostics.Static_property_0_conflicts_with_built_in_property_Function_0_of_constructor_function_1; + const className = getNameOfSymbolAsWritten(getSymbolOfDeclaration(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; + } + + 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: ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode) { + if (node.kind === SyntaxKind.InterfaceDeclaration) { + const nodeSymbol = getSymbolOfDeclaration(node); + // 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(getSymbolOfDeclaration(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 (!checkGrammarModifiers(node) && !checkGrammarProperty(node)) checkGrammarComputedPropertyName(node.name); + checkVariableLikeDeclaration(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)); + } + } + + 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); + } + + // Grammar checking for modifiers is done inside the function checkGrammarFunctionLikeDeclaration + checkFunctionOrMethodDeclaration(node); + + // 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)); + } + + // 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); + } + + setNodeLinksForPrivateIdentifierScope(node); + } + + function setNodeLinksForPrivateIdentifierScope(node: PropertyDeclaration | PropertySignature | MethodDeclaration | MethodSignature | AccessorDeclaration) { + if (isPrivateIdentifier(node.name)) { + if ( + languageVersion < LanguageFeatureMinimumTarget.PrivateNamesAndClassStaticBlocks || + languageVersion < LanguageFeatureMinimumTarget.ClassAndClassElementDecorators || + !useDefineForClassFields + ) { + for (let lexicalScope = getEnclosingBlockScopeContainer(node); !!lexicalScope; lexicalScope = getEnclosingBlockScopeContainer(lexicalScope)) { + getNodeLinks(lexicalScope).flags |= NodeCheckFlags.ContainsClassWithPrivateIdentifiers; + } + + // 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 checkClassStaticBlockDeclaration(node: ClassStaticBlockDeclaration) { + checkGrammarModifiers(node); + + forEachChild(node, checkSourceElement); + } + + 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); + + checkSourceElement(node.body); + + const symbol = getSymbolOfDeclaration(node); + const firstDeclaration = getDeclarationOfKind(symbol, node.kind); + + // 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; + } + + addLazyDiagnostic(checkConstructorDeclarationDiagnostics); + + return; + + 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; + 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 = !emitStandardClassFields && + (some(node.parent.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; + + 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); + } + } + } + } + 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 nodeImmediatelyReferencesSuperOrThis(node: Node): boolean { + if (node.kind === SyntaxKind.SuperKeyword || node.kind === SyntaxKind.ThisKeyword) { + return true; + } + + if (isThisContainerOrFunctionBlock(node)) { + return false; + } + + return !!forEachChild(node, nodeImmediatelyReferencesSuperOrThis); + } + + function checkAccessorDeclaration(node: AccessorDeclaration) { + if (isIdentifier(node.name) && idText(node.name) === "constructor" && isClassLike(node.parent)) { + 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); + + 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); + } + + 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 = getSymbolOfDeclaration(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 returnType = getTypeOfAccessors(getSymbolOfDeclaration(node)); + if (node.kind === SyntaxKind.GetAccessor) { + checkAllCodePathsInNonVoidFunctionReturnOrThrow(node, returnType); + } + } + } + + 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 getEffectiveTypeArguments(node: TypeReferenceNode | ExpressionWithTypeArguments | NodeWithTypeArguments, typeParameters: readonly TypeParameter[]): Type[] { + return fillMissingTypeArguments(map(node.typeArguments!, getTypeFromTypeNode), typeParameters, getMinTypeArgumentCount(typeParameters), isInJSFile(node)); + } + + function checkTypeArgumentConstraints(node: TypeReferenceNode | ExpressionWithTypeArguments | NodeWithTypeArguments, 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 getTypeParametersForTypeAndSymbol(type: Type, symbol: Symbol) { + if (!isErrorType(type)) { + return symbol.flags & SymbolFlags.TypeAlias && getSymbolLinks(symbol).typeParameters || + (getObjectFlags(type) & ObjectFlags.Reference ? (type as TypeReference).target.localTypeParameters : undefined); + } + return undefined; + } + + function getTypeParametersForTypeReferenceOrImport(node: TypeReferenceNode | ExpressionWithTypeArguments | ImportTypeNode) { + const type = getTypeFromTypeNode(node); + if (!isErrorType(type)) { + const symbol = getNodeLinks(node).resolvedSymbol; + if (symbol) { + return getTypeParametersForTypeAndSymbol(type, symbol); + } + } + return undefined; + } + + function checkTypeReferenceNode(node: TypeReferenceNode | ExpressionWithTypeArguments) { + checkGrammarTypeArguments(node, node.typeArguments); + if (node.kind === SyntaxKind.TypeReference && !isInJSFile(node) && !isInJSDoc(node) && node.typeArguments && node.typeName.end !== node.typeArguments.pos) { + // If there was a token between the type name and the type arguments, check if it was a DotToken + const sourceFile = getSourceFileOfNode(node); + if (scanTokenAtPosition(sourceFile, node.typeName.end) === SyntaxKind.DotToken) { + grammarErrorAtPos(node, skipTrivia(sourceFile.text, node.typeName.end), 1, Diagnostics.JSDoc_types_can_only_be_used_inside_documentation_comments); + } + } + forEach(node.typeArguments, checkSourceElement); + checkTypeReferenceOrImport(node); + } + + function checkTypeReferenceOrImport(node: TypeReferenceNode | ExpressionWithTypeArguments | ImportTypeNode) { + const type = getTypeFromTypeNode(node); + if (!isErrorType(type)) { + if (node.typeArguments) { + addLazyDiagnostic(() => { + const typeParameters = getTypeParametersForTypeReferenceOrImport(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 = getTypeParametersForTypeReferenceOrImport(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 checkTypeLiteral(node: TypeLiteralNode) { + forEach(node.members, checkSourceElement); + addLazyDiagnostic(checkTypeLiteralDiagnostics); + + function checkTypeLiteralDiagnostics() { + const type = getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node); + checkIndexConstraints(type, type.symbol); + checkTypeForDuplicateIndexSignatures(node); + checkObjectTypeForDuplicateDeclarations(node); + } + } + + function checkArrayType(node: ArrayTypeNode) { + checkSourceElement(node.elementType); + } + + function checkTupleType(node: TupleTypeNode) { + let seenOptionalElement = false; + let seenRestElement = false; + for (const e of node.elements) { + let 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) { + flags |= ElementFlags.Rest; + } + } + if (flags & ElementFlags.Rest) { + if (seenRestElement) { + grammarErrorOnNode(e, Diagnostics.A_rest_element_cannot_follow_another_rest_element); + break; + } + 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 (flags & ElementFlags.Required && seenOptionalElement) { + grammarErrorOnNode(e, Diagnostics.A_required_element_cannot_follow_an_optional_element); + break; + } + } + 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; + // skip index type deferral on remapping mapped types + const objectIndexType = isGenericMappedType(objectType) && getMappedTypeNameTypeKind(objectType) === MappedTypeNameTypeKind.Remapping + ? getIndexTypeForMappedType(objectType, IndexFlags.None) + : getIndexType(objectType, IndexFlags.None); + const hasNumberIndexInfo = !!getIndexInfoOfType(objectType, numberType); + if (everyType(indexType, t => isTypeAssignableTo(t, objectIndexType) || hasNumberIndexInfo && isApplicableIndexType(t, numberType))) { + 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; + } + if (isGenericObjectType(objectType)) { + const propertyName = getPropertyNameFromIndex(indexType, accessNode); + if (propertyName) { + const propertySymbol = forEachType(getApparentType(objectType), t => getPropertyOfType(t, propertyName)); + if (propertySymbol && getDeclarationModifierFlagsFromSymbol(propertySymbol) & ModifierFlags.NonPublicAccessibilityModifier) { + error(accessNode, Diagnostics.Private_or_protected_member_0_cannot_be_accessed_on_a_type_parameter, unescapeLeadingUnderscores(propertyName)); + return errorType; + } + } + } + error(accessNode, Diagnostics.Type_0_cannot_be_used_to_index_type_1, typeToString(indexType), typeToString(objectType)); + return errorType; + } + + function checkIndexedAccessType(node: IndexedAccessTypeNode) { + checkSourceElement(node.objectType); + checkSourceElement(node.indexType); + checkIndexedAccessIndexType(getTypeFromIndexedAccessTypeNode(node), node); + } + + function checkMappedType(node: MappedTypeNode) { + checkGrammarMappedType(node); + checkSourceElement(node.typeParameter); + checkSourceElement(node.nameType); + checkSourceElement(node.type); + + if (!node.type) { + reportImplicitAny(node, anyType); + } + + const type = getTypeFromMappedTypeNode(node) as MappedType; + const nameType = getNameTypeFromMappedType(type); + if (nameType) { + checkTypeAssignableTo(nameType, stringNumberSymbolType, node.nameType); + } + else { + const constraintType = getConstraintTypeFromMappedType(type); + checkTypeAssignableTo(constraintType, stringNumberSymbolType, getEffectiveConstraintOfTypeParameter(node.typeParameter)); + } + } + + function checkGrammarMappedType(node: MappedTypeNode) { + if (node.members?.length) { + return grammarErrorOnNode(node.members[0], Diagnostics.A_mapped_type_may_not_declare_properties_or_methods); + } + } + + function checkThisType(node: ThisTypeNode) { + getTypeFromThisTypeNode(node); + } + + function checkTypeOperator(node: TypeOperatorNode) { + checkGrammarTypeOperatorNode(node); + checkSourceElement(node.type); + } + + function checkConditionalType(node: ConditionalTypeNode) { + forEachChild(node, checkSourceElement); + } + + 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 = getSymbolOfDeclaration(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); + } + + 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 checkImportType(node: ImportTypeNode) { + checkSourceElement(node.argument); + + if (node.attributes) { + getResolutionModeOverride(node.attributes, grammarErrorOnNode); + } + checkTypeReferenceOrImport(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); + } + + function isPrivateWithinAmbient(node: Node): boolean { + return (hasEffectiveModifier(node, ModifierFlags.Private) || isPrivateIdentifierClassElementDeclaration(node)) && !!(node.flags & NodeFlags.Ambient); + } + + function getEffectiveDeclarationFlags(n: Declaration, flagsToCheck: ModifierFlags): ModifierFlags { + let flags = getCombinedModifierFlagsCached(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 + ) { + const container = getEnclosingContainer(n); + if ((container && container.flags & NodeFlags.ExportContext) && !(flags & ModifierFlags.Ambient) && !(isModuleBlock(n.parent) && isModuleDeclaration(n.parent.parent) && isGlobalScopeAugmentation(n.parent.parent))) { + // It is nested in an ambient export context, which means it is automatically exported + flags |= ModifierFlags.Export; + } + flags |= ModifierFlags.Ambient; + } + + return flags & flagsToCheck; + } + + 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); + } + }); + } + } + + 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); + } + }); + } + } + + 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 reportImplementationExpectedError(node: SignatureDeclaration): void { + if (node.name && nodeIsMissing(node.name)) { + return; + } + + 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 + isComputedPropertyName(node.name) && isComputedPropertyName(subsequentName) && isTypeIdenticalTo(checkComputedPropertyName(node.name), checkComputedPropertyName(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); + } + 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); + } + } + } + + 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; + } + + previousDeclaration = node; + + if (!inAmbientContextOrInterface) { + lastSeenNonAmbientDeclaration = node as FunctionLikeDeclaration; + } + } + if (isInJSFile(current) && isFunctionLike(current) && current.jsDoc) { + hasOverloads = length(getJSDocOverloadTags(current)) > 0; + } + } + } + + if (multipleConstructorImplementation) { + forEach(functionDeclarations, declaration => { + error(declaration, Diagnostics.Multiple_constructor_implementations_are_not_allowed); + }); + } + + if (duplicateFunctionDeclaration) { + forEach(functionDeclarations, declaration => { + error(getNameOfDeclaration(declaration) || declaration, Diagnostics.Duplicate_function_implementation); + }); + } + + 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, + ); + } + }); + } + + // 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)) { + const errorNode = signature.declaration && isJSDocSignature(signature.declaration) + ? (signature.declaration.parent as JSDocOverloadTag | JSDocCallbackTag).tagName + : signature.declaration; + addRelatedInfo( + error(errorNode, Diagnostics.This_overload_signature_is_not_compatible_with_its_implementation_signature), + createDiagnosticForNode(bodyDeclaration, Diagnostics.The_implementation_signature_is_declared_here), + ); + break; + } + } + } + } + } + + function checkExportsOnMergedDeclarations(node: Declaration): void { + addLazyDiagnostic(() => checkExportsOnMergedDeclarationsWorker(node)); + } + + 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 = getSymbolOfDeclaration(node)!; + if (!symbol.exportSymbol) { + // this is a pure local symbol (all declarations are non-exported) - no need to check anything + return; + } + } + + // run the check only for the first declaration in the list + if (getDeclarationOfKind(symbol, node.kind) !== node) { + 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; + } + else { + 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); + + 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)); + } + } + } + + function getDeclarationSpaces(decl: Declaration): DeclarationSpaces { + let d = decl as Node; + switch (d.kind) { + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.TypeAliasDeclaration: + + // 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(getSymbolOfDeclaration(d as ImportEqualsDeclaration | NamespaceImport | ImportClause | ExportAssignment | BinaryExpression)); + 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; + case SyntaxKind.MethodSignature: + case SyntaxKind.PropertySignature: + return DeclarationSpaces.ExportType; + default: + return Debug.failBadSyntaxKind(d); + } + } + } + + function getAwaitedTypeOfPromise(type: Type, errorNode?: Node, diagnosticMessage?: DiagnosticMessage, ...args: DiagnosticArguments): Type | undefined { + const promisedType = getPromisedTypeOfPromise(type, errorNode); + return promisedType && getAwaitedType(promisedType, errorNode, diagnosticMessage, ...args); + } + + /** + * 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; + } + + const typeAsPromise = type as PromiseOrAwaitableType; + if (typeAsPromise.promisedTypeOfPromise) { + return typeAsPromise.promisedTypeOfPromise; + } + + if (isReferenceToType(type, getGlobalPromiseType(/*reportErrors*/ false))) { + return typeAsPromise.promisedTypeOfPromise = getTypeArguments(type as GenericType)[0]; + } + + // primitives with a `{ then() }` won't be unwrapped/adopted. + if (allTypesAssignableToKind(getBaseConstraintOrType(type), TypeFlags.Primitive | TypeFlags.Never)) { + return undefined; + } + + const thenFunction = getTypeOfPropertyOfType(type, "then" as __String)!; // TODO: GH#18217 + if (isTypeAny(thenFunction)) { + return undefined; + } + + 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 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); + } + } + + 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; + } + + 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, ...args: DiagnosticArguments): Type { + const awaitedType = withAlias ? + getAwaitedType(type, errorNode, diagnosticMessage, ...args) : + getAwaitedTypeNoAlias(type, errorNode, diagnosticMessage, ...args); + 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; + } + + // 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 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)]); + } + + 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)) { + return tryCreateAwaitedType(type) ?? type; + } + + Debug.assert(isAwaitedTypeInstantiation(type) || 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, ...args: DiagnosticArguments): Type | undefined { + const awaitedType = getAwaitedTypeNoAlias(type, errorNode, diagnosticMessage, ...args); + return awaitedType && createAwaitedTypeIfNeeded(awaitedType); + } + + /** + * Gets the "awaited type" of a type without introducing an `Awaited` wrapper. + * + * @see {@link getAwaitedType} + */ + function getAwaitedTypeNoAlias(type: Type, errorNode?: Node, diagnosticMessage?: DiagnosticMessage, ...args: DiagnosticArguments): Type | undefined { + if (isTypeAny(type)) { + return type; + } + + // If this is already an `Awaited`, just return it. This avoids `Awaited>` in higher-order + if (isAwaitedTypeInstantiation(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; + } + + // 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; + } + + const mapper = errorNode ? (constituentType: Type) => getAwaitedTypeNoAlias(constituentType, errorNode, diagnosticMessage, ...args) : getAwaitedTypeNoAlias; + + awaitedTypeStack.push(type.id); + const mapped = mapType(type, mapper); + awaitedTypeStack.pop(); + + 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; + } + + // 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, ...args); + awaitedTypeStack.pop(); + + if (!awaitedType) { + return undefined; + } + + return typeAsAwaitable.awaitedTypeOfType = awaitedType; + } + + // 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, ...args); + diagnostics.add(createDiagnosticForNodeFromMessageChain(getSourceFileOfNode(errorNode), errorNode, chain)); + } + return undefined; + } + + return typeAsAwaitable.awaitedTypeOfType = type; + } + + /** + * 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, returnTypeErrorLocation: 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. + reportErrorForInvalidReturnType(Diagnostics.The_return_type_of_an_async_function_or_method_must_be_the_global_Promise_T_type_Did_you_mean_to_write_Promise_0, returnTypeNode, returnTypeErrorLocation, typeToString(getAwaitedTypeNoAlias(returnType) || voidType)); + return; + } + } + else { + // Always mark the type node as referenced if it points to a value + markLinkedReferences(node, ReferenceHint.AsyncFunction); + if (isErrorType(returnType)) { + return; + } + + const promiseConstructorName = getEntityNameFromTypeNode(returnTypeNode); + if (promiseConstructorName === undefined) { + reportErrorForInvalidReturnType(Diagnostics.Type_0_is_not_a_valid_async_function_return_type_in_ES5_because_it_does_not_refer_to_a_Promise_compatible_constructor_value, returnTypeNode, returnTypeErrorLocation, 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(returnTypeErrorLocation, Diagnostics.An_async_function_or_method_in_ES5_requires_the_Promise_constructor_Make_sure_you_have_a_declaration_for_the_Promise_constructor_or_include_ES2015_in_your_lib_option); + } + else { + reportErrorForInvalidReturnType(Diagnostics.Type_0_is_not_a_valid_async_function_return_type_in_ES5_because_it_does_not_refer_to_a_Promise_compatible_constructor_value, returnTypeNode, returnTypeErrorLocation, entityNameToString(promiseConstructorName)); + } + return; + } + + const globalPromiseConstructorLikeType = getGlobalPromiseConstructorLikeType(/*reportErrors*/ true); + if (globalPromiseConstructorLikeType === emptyObjectType) { + // If we couldn't resolve the global PromiseConstructorLike type we cannot verify + // compatibility with __awaiter. + reportErrorForInvalidReturnType(Diagnostics.Type_0_is_not_a_valid_async_function_return_type_in_ES5_because_it_does_not_refer_to_a_Promise_compatible_constructor_value, returnTypeNode, returnTypeErrorLocation, entityNameToString(promiseConstructorName)); + return; + } + + const headMessage = Diagnostics.Type_0_is_not_a_valid_async_function_return_type_in_ES5_because_it_does_not_refer_to_a_Promise_compatible_constructor_value; + const errorInfo = () => returnTypeNode === returnTypeErrorLocation ? undefined : chainDiagnosticMessages(/*details*/ undefined, Diagnostics.The_return_type_of_an_async_function_or_method_must_be_the_global_Promise_T_type); + if (!checkTypeAssignableTo(promiseConstructorType, globalPromiseConstructorLikeType, returnTypeErrorLocation, headMessage, errorInfo)) { + 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; + } + } + + 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); + + function reportErrorForInvalidReturnType(message: DiagnosticMessage, returnTypeNode: TypeNode, returnTypeErrorLocation: TypeNode, typeName: string) { + if (returnTypeNode === returnTypeErrorLocation) { + error(returnTypeErrorLocation, message, typeName); + } + else { + const diag = error(returnTypeErrorLocation, Diagnostics.The_return_type_of_an_async_function_or_method_must_be_the_global_Promise_T_type); + addRelatedInfo(diag, createDiagnosticForNode(returnTypeNode, message, typeName)); + } + } + } + + function checkGrammarDecorator(decorator: Decorator): boolean { + const sourceFile = getSourceFileOfNode(decorator); + if (!hasParseDiagnostics(sourceFile)) { + let node: Expression = decorator.expression; + + // DecoratorParenthesizedExpression : + // `(` Expression `)` + + if (isParenthesizedExpression(node)) { + return false; + } + + let canHaveCallExpression = true; + let errorNode: Node | undefined; + while (true) { + // Allow TS syntax such as non-null assertions and instantiation expressions + if (isExpressionWithTypeArguments(node) || isNonNullExpression(node)) { + node = node.expression; + continue; + } + + // DecoratorCallExpression : + // DecoratorMemberExpression Arguments + + if (isCallExpression(node)) { + if (!canHaveCallExpression) { + errorNode = node; + } + if (node.questionDotToken) { + // Even if we already have an error node, error at the `?.` token since it appears earlier. + errorNode = node.questionDotToken; + } + node = node.expression; + canHaveCallExpression = false; + continue; + } + + // DecoratorMemberExpression : + // IdentifierReference + // DecoratorMemberExpression `.` IdentifierName + // DecoratorMemberExpression `.` PrivateIdentifier + + if (isPropertyAccessExpression(node)) { + if (node.questionDotToken) { + // Even if we already have an error node, error at the `?.` token since it appears earlier. + errorNode = node.questionDotToken; + } + node = node.expression; + canHaveCallExpression = false; + continue; + } + + if (!isIdentifier(node)) { + // Even if we already have an error node, error at this node since it appears earlier. + errorNode = node; + } + + break; + } + + if (errorNode) { + addRelatedInfo( + error(decorator.expression, Diagnostics.Expression_must_be_enclosed_in_parentheses_to_be_used_as_a_decorator), + createDiagnosticForNode(errorNode, Diagnostics.Invalid_syntax_in_decorator), + ); + return true; + } + } + + return false; + } + + /** Check a decorator */ + function checkDecorator(node: Decorator): void { + checkGrammarDecorator(node); + + const signature = getResolvedSignature(node); + checkDeprecatedSignature(signature, node); + const returnType = getReturnTypeOfSignature(signature); + if (returnType.flags & TypeFlags.Any) { + return; + } + + // if we fail to get a signature and return type here, we will have already reported a grammar error in `checkDecorators`. + const decoratorSignature = getDecoratorCallSignature(node); + if (!decoratorSignature?.resolvedReturnType) return; + + let headMessage: DiagnosticMessage; + const expectedReturnType = decoratorSignature.resolvedReturnType; + switch (node.parent.kind) { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + headMessage = Diagnostics.Decorator_function_return_type_0_is_not_assignable_to_type_1; + break; + + case SyntaxKind.PropertyDeclaration: + if (!legacyDecorators) { + headMessage = Diagnostics.Decorator_function_return_type_0_is_not_assignable_to_type_1; + break; + } + // falls through + + case SyntaxKind.Parameter: + headMessage = Diagnostics.Decorator_function_return_type_is_0_but_is_expected_to_be_void_or_any; + break; + + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + headMessage = Diagnostics.Decorator_function_return_type_0_is_not_assignable_to_type_1; + break; + + default: + return Debug.failBadSyntaxKind(node.parent); + } + + checkTypeAssignableTo(returnType, expectedReturnType, node.expression, headMessage); + } + + /** + * Creates a synthetic `Signature` corresponding to a call signature. + */ + function createCallSignature( + typeParameters: readonly TypeParameter[] | undefined, + thisParameter: Symbol | undefined, + parameters: readonly Symbol[], + returnType: Type, + typePredicate?: TypePredicate, + minArgumentCount: number = parameters.length, + flags: SignatureFlags = SignatureFlags.None, + ) { + const decl = factory.createFunctionTypeNode(/*typeParameters*/ undefined, emptyArray, factory.createKeywordTypeNode(SyntaxKind.AnyKeyword)); + return createSignature(decl, typeParameters, thisParameter, parameters, returnType, typePredicate, minArgumentCount, flags); + } + + /** + * Creates a synthetic `FunctionType` + */ + function createFunctionType( + typeParameters: readonly TypeParameter[] | undefined, + thisParameter: Symbol | undefined, + parameters: readonly Symbol[], + returnType: Type, + typePredicate?: TypePredicate, + minArgumentCount?: number, + flags?: SignatureFlags, + ) { + const signature = createCallSignature(typeParameters, thisParameter, parameters, returnType, typePredicate, minArgumentCount, flags); + return getOrCreateTypeFromSignature(signature); + } + + function createGetterFunctionType(type: Type) { + return createFunctionType(/*typeParameters*/ undefined, /*thisParameter*/ undefined, emptyArray, type); + } + + function createSetterFunctionType(type: Type) { + const valueParam = createParameter("value" as __String, type); + return createFunctionType(/*typeParameters*/ undefined, /*thisParameter*/ undefined, [valueParam], voidType); + } + + 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.ParenthesizedType: + case SyntaxKind.NamedTupleMember: + return getEntityNameForDecoratorMetadata((node as ParenthesizedTypeNode).type); + + 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; + } + + 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; + } + } + return commonEntityName; + } + + function getParameterTypeNodeForDecoratorCheck(node: ParameterDeclaration): TypeNode | undefined { + const typeNode = getEffectiveTypeAnnotationNode(node); + return isRestParameter(node) ? getRestParameterElementType(typeNode) : typeNode; + } + + /** 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 + // checkGrammarModifiers. + if (!canHaveDecorators(node) || !hasDecorators(node) || !node.modifiers || !nodeCanBeDecorated(legacyDecorators, node, node.parent, node.parent.parent)) { + return; + } + + const firstDecorator = find(node.modifiers, isDecorator); + if (!firstDecorator) { + return; + } + + if (legacyDecorators) { + checkExternalEmitHelpers(firstDecorator, ExternalEmitHelpers.Decorate); + if (node.kind === SyntaxKind.Parameter) { + checkExternalEmitHelpers(firstDecorator, ExternalEmitHelpers.Param); + } + } + else if (languageVersion < LanguageFeatureMinimumTarget.ClassAndClassElementDecorators) { + checkExternalEmitHelpers(firstDecorator, ExternalEmitHelpers.ESDecorateAndRunInitializers); + if (isClassDeclaration(node)) { + if (!node.name) { + checkExternalEmitHelpers(firstDecorator, ExternalEmitHelpers.SetFunctionName); + } + else { + const member = getFirstTransformableStaticClassElement(node); + if (member) { + checkExternalEmitHelpers(firstDecorator, ExternalEmitHelpers.SetFunctionName); + } + } + } + else if (!isClassExpression(node)) { + if (isPrivateIdentifier(node.name) && (isMethodDeclaration(node) || isAccessor(node) || isAutoAccessorPropertyDeclaration(node))) { + checkExternalEmitHelpers(firstDecorator, ExternalEmitHelpers.SetFunctionName); + } + if (isComputedPropertyName(node.name)) { + checkExternalEmitHelpers(firstDecorator, ExternalEmitHelpers.PropKey); + } + } + } + + markLinkedReferences(node, ReferenceHint.Decorator); + + for (const modifier of node.modifiers) { + if (isDecorator(modifier)) { + checkDecorator(modifier); + } + } + } + + function checkFunctionDeclaration(node: FunctionDeclaration): void { + addLazyDiagnostic(checkFunctionDeclarationDiagnostics); + + function checkFunctionDeclarationDiagnostics() { + checkFunctionOrMethodDeclaration(node); + checkGrammarForGenerator(node); + checkCollisionsForDeclarationName(node, node.name); + } + } + + 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); + } + + if (node.name) { + checkTypeNameIsReserved(node.name, Diagnostics.Type_alias_name_cannot_be_0); + } + checkSourceElement(node.typeExpression); + checkTypeParameters(getEffectiveTypeParameterDeclarations(node)); + } + + function checkJSDocTemplateTag(node: JSDocTemplateTag): void { + checkSourceElement(node.constraint); + for (const tp of node.typeParameters) { + checkSourceElement(tp); + } + } + + function checkJSDocTypeTag(node: JSDocTypeTag) { + checkSourceElement(node.typeExpression); + } + + function checkJSDocSatisfiesTag(node: JSDocSatisfiesTag) { + checkSourceElement(node.typeExpression); + const host = getEffectiveJSDocHost(node); + if (host) { + const tags = getAllJSDocTags(host, isJSDocSatisfiesTag); + if (length(tags) > 1) { + for (let i = 1; i < length(tags); i++) { + const tagName = tags[i].tagName; + error(tagName, Diagnostics._0_tag_already_specified, idText(tagName)); + } + } + } + } + + function checkJSDocLinkLikeTag(node: JSDocLink | JSDocLinkCode | JSDocLinkPlain) { + if (node.name) { + resolveJSDocMemberName(node.name, /*ignoreErrors*/ true); + } + } + + function checkJSDocParameterTag(node: JSDocParameterTag) { + checkSourceElement(node.typeExpression); + } + + function checkJSDocPropertyTag(node: JSDocPropertyTag) { + checkSourceElement(node.typeExpression); + } + + function checkJSDocFunctionType(node: JSDocFunctionType): void { + addLazyDiagnostic(checkJSDocFunctionTypeImplicitAny); + checkSignatureDeclaration(node); + + function checkJSDocFunctionTypeImplicitAny() { + if (!node.type && !isJSDocConstructSignature(node)) { + reportImplicitAny(node, anyType); + } + } + } + + function checkJSDocThisTag(node: JSDocThisTag) { + const host = getEffectiveJSDocHost(node); + if (host && isArrowFunction(host)) { + error(node.tagName, Diagnostics.An_arrow_function_cannot_have_a_this_parameter); + } + } + + function checkJSDocImportTag(node: JSDocImportTag) { + checkImportAttributes(node); + } + + 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; + } + + 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)); + } + } + } + + 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 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); + } + + 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 = getSymbolOfDeclaration(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), + ); + + // 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); + } + } + + const body = node.kind === SyntaxKind.MethodSignature ? undefined : node.body; + checkSourceElement(body); + checkAllCodePathsInNonVoidFunctionReturnOrThrow(node, getReturnTypeFromAnnotation(node)); + + 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); + } + } + + 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)); + } + } + } + } + + 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); + } + } + + 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: + 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"); + } + } + } + + 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 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 = getSymbolOfDeclaration(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"); + } + } + } + + 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 = getSymbolOfDeclaration(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 messageAndArg: DiagnosticAndArguments = only + ? [Diagnostics._0_is_declared_but_its_value_is_never_read, name] + : [Diagnostics.All_type_parameters_are_unused]; + addDiagnostic(typeParameter, UnusedKind.Parameter, createFileDiagnostic(sourceFile, range.pos, range.end - range.pos, ...messageAndArg)); + } + } + else { + // 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 addToGroup(map: Map, 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 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 isIdentifierThatStartsWithUnderscore(declaration.name); + } + return isAmbientModule(declaration) || + (isVariableDeclaration(declaration) && isForInOrOfStatement(declaration.parent.parent) || isImportedDeclaration(declaration)) && isIdentifierThatStartsWithUnderscore(declaration.name!); + } + + function checkUnusedLocalsAndParameters(nodeWithLocals: HasLocals, 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 (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 (isVariableDeclaration(declaration)) { + const blockScopeKind = getCombinedNodeFlagsCached(declaration) & NodeFlags.BlockScoped; + const name = getNameOfDeclaration(declaration); + if (blockScopeKind !== NodeFlags.Using && blockScopeKind !== NodeFlags.AwaitUsing || !name || !isIdentifierThatStartsWithUnderscore(name)) { + 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); + } + } + } + } + }); + 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), + ); + } + } + else { + for (const e of bindingElements) { + addDiagnostic(e, kind, createDiagnosticForNode(e, Diagnostics._0_is_declared_but_its_value_is_never_read, bindingNameText(e.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 (!getSymbolOfDeclaration(node)?.isReferenced) { + const wrappingDeclaration = walkUpBindingElementsAndPatterns(node); + Debug.assert(isPartOfParameterDeclaration(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); + } + } + + 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; + } + + 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); + } + } + + 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); + } + }); + } + + /** + * 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.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 (node.flags & NodeFlags.Ambient) { + // ambient context - no codegen impact + 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; + } + } + + const root = getRootDeclaration(node); + if (isParameter(root) && nodeIsMissing((root.parent as FunctionLikeDeclaration).body)) { + // just an overload - no codegen impact + 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; + }); + } + + 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; + }); + } + + 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; + } + + if (!name || !needCollisionCheckForIdentifier(node, name, "require") && !needCollisionCheckForIdentifier(node, name, "exports")) { + 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)); + } + } + + 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; + } + + // 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 recordPotentialCollisionWithWeakMapSetInGeneratedCode(node: Node, name: Identifier): void { + if ( + languageVersion <= ScriptTarget.ES2021 + && (needCollisionCheckForIdentifier(node, name, "WeakMap") || needCollisionCheckForIdentifier(node, name, "WeakSet")) + ) { + potentialWeakMapSetCollisions.push(node); + } + } + + 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 if (isFunctionExpression(node)) { + // FunctionExpression names don't contribute to their containers, but do matter for their contents + if (getNodeCheckFlags(node) & NodeCheckFlags.ContainsSuperPropertyInStaticInitializer) { + hasCollision = true; + } + } + 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 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 ((getCombinedNodeFlagsCached(node) & NodeFlags.BlockScoped) !== 0 || isPartOfParameterDeclaration(node)) { + return; + } + + // NOTE: in ES6 spec initializer is required in variable declarations where name is binding pattern + // so we'll always treat binding elements as initialized + + const symbol = getSymbolOfDeclaration(node); + if (symbol.flags & SymbolFlags.FunctionScopedVariable) { + if (!isIdentifier(node.name)) return Debug.fail(); + const localDeclarationSymbol = resolveName(node, node.name.escapedText, SymbolFlags.Variable, /*nameNotFoundMessage*/ 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); + + // here we know that function scoped variable is "shadowed" by block scoped one + // a var declatation can't hoist past a lexical declaration and it results in a SyntaxError at runtime + 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; + } + + // 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; + } + + // 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) && + isPartOfParameterDeclaration(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 < LanguageFeatureMinimumTarget.ObjectSpreadRest) { + 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); + } + } + } + } + + // For a binding pattern, check contained binding elements + if (isBindingPattern(node.name)) { + if (node.name.kind === SyntaxKind.ArrayBindingPattern && languageVersion < LanguageFeatureMinimumTarget.BindingPatterns && 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 (node.initializer && isPartOfParameterDeclaration(node) && 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)) { + if (isInAmbientOrTypeNode(node)) { + return; + } + 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); + } + } + } + return; + } + // For a commonjs `const x = require`, validate the alias and exit + const symbol = getSymbolOfDeclaration(node); + if (symbol.flags & SymbolFlags.Alias && (isVariableDeclarationInitializedToBareOrAccessedRequire(node) || isBindingElementOfBareOrAccessedRequire(node))) { + checkAliasSymbol(node); + return; + } + + 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) { + const initializerType = checkExpressionCached(initializer); + checkTypeAssignableToAndOptionallyElaborate(initializerType, type, node, initializer, /*headMessage*/ undefined); + const blockScopeKind = getCombinedNodeFlagsCached(node) & NodeFlags.BlockScoped; + if (blockScopeKind === NodeFlags.AwaitUsing) { + const globalAsyncDisposableType = getGlobalAsyncDisposableType(/*reportErrors*/ true); + const globalDisposableType = getGlobalDisposableType(/*reportErrors*/ true); + if (globalAsyncDisposableType !== emptyObjectType && globalDisposableType !== emptyObjectType) { + const optionalDisposableType = getUnionType([globalAsyncDisposableType, globalDisposableType, nullType, undefinedType]); + checkTypeAssignableTo(initializerType, optionalDisposableType, initializer, Diagnostics.The_initializer_of_an_await_using_declaration_must_be_either_an_object_with_a_Symbol_asyncDispose_or_Symbol_dispose_method_or_be_null_or_undefined); + } + } + else if (blockScopeKind === NodeFlags.Using) { + const globalDisposableType = getGlobalDisposableType(/*reportErrors*/ true); + if (globalDisposableType !== emptyObjectType) { + const optionalDisposableType = getUnionType([globalDisposableType, nullType, undefinedType]); + checkTypeAssignableTo(initializerType, optionalDisposableType, initializer, Diagnostics.The_initializer_of_a_using_declaration_must_be_either_an_object_with_a_Symbol_dispose_method_or_be_null_or_undefined); + } + } + } + } + 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)); + } + } + } + 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)); + + 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)); + } + } + 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 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 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 (hasQuestionToken(left) !== hasQuestionToken(right)) { + return false; + } + + const interestingFlags = ModifierFlags.Private | + ModifierFlags.Protected | + ModifierFlags.Async | + ModifierFlags.Abstract | + ModifierFlags.Readonly | + ModifierFlags.Static; + + return getSelectedEffectiveModifierFlags(left, interestingFlags) === getSelectedEffectiveModifierFlags(right, interestingFlags); + } + + 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 checkBindingElement(node: BindingElement) { + checkGrammarBindingElement(node); + return checkVariableLikeDeclaration(node); + } + + function checkVariableDeclarationList(node: VariableDeclarationList) { + const blockScopeKind = getCombinedNodeFlags(node) & NodeFlags.BlockScoped; + if ((blockScopeKind === NodeFlags.Using || blockScopeKind === NodeFlags.AwaitUsing) && languageVersion < LanguageFeatureMinimumTarget.UsingAndAwaitUsing) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.AddDisposableResourceAndDisposeResources); + } + + forEach(node.declarations, checkSourceElement); + } + + function checkVariableStatement(node: VariableStatement) { + // Grammar checking + if (!checkGrammarModifiers(node) && !checkGrammarVariableDeclarationList(node.declarationList)) checkGrammarForDisallowedBlockScopedVariableStatement(node); + checkVariableDeclarationList(node.declarationList); + } + + function checkExpressionStatement(node: ExpressionStatement) { + // Grammar checking + checkGrammarStatementInAmbientContext(node); + + checkExpression(node.expression); + } + + function checkIfStatement(node: IfStatement) { + // Grammar checking + checkGrammarStatementInAmbientContext(node); + const type = checkTruthinessExpression(node.expression); + checkTestingKnownTruthyCallableOrAwaitableOrEnumMemberType(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); + } + + checkSourceElement(node.elseStatement); + } + + function checkTestingKnownTruthyCallableOrAwaitableOrEnumMemberType(condExpr: Expression, condType: Type, body?: Statement | Expression) { + if (!strictNullChecks) return; + bothHelper(condExpr, body); + + function bothHelper(condExpr: Expression, body: Expression | Statement | undefined) { + condExpr = skipParentheses(condExpr); + + helper(condExpr, body); + + while (isBinaryExpression(condExpr) && (condExpr.operatorToken.kind === SyntaxKind.BarBarToken || condExpr.operatorToken.kind === SyntaxKind.QuestionQuestionToken)) { + condExpr = skipParentheses(condExpr.left); + helper(condExpr, body); + } + } + + function helper(condExpr: Expression, body: Expression | Statement | undefined) { + const location = isLogicalOrCoalescingBinaryExpression(condExpr) ? skipParentheses(condExpr.right) : condExpr; + if (isModuleExportsAccessExpression(location)) { + return; + } + if (isLogicalOrCoalescingBinaryExpression(location)) { + bothHelper(location, body); + return; + } + const type = location === condExpr ? condType : checkTruthinessExpression(location); + if (type.flags & TypeFlags.EnumLiteral && isPropertyAccessExpression(location) && (getNodeLinks(location.expression).resolvedSymbol ?? unknownSymbol).flags & SymbolFlags.Enum) { + // EnumLiteral type at condition with known value is always truthy or always falsy, likely an error + error(location, Diagnostics.This_condition_will_always_return_0, !!(type as LiteralType).value ? "true" : "false"); + return; + } + const isPropertyExpressionCast = isPropertyAccessExpression(location) && isTypeAssertion(location.expression); + if (!hasTypeFacts(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 + : 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; + } + // 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 { + return false; + } + } + } + } + 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; + } + } + return forEachChild(child, visit); + }); + if (isUsed) { + return true; + } + node = node.parent; + } + return false; + } + + function checkDoStatement(node: DoStatement) { + // Grammar checking + checkGrammarStatementInAmbientContext(node); + + checkSourceElement(node.statement); + checkTruthinessExpression(node.expression); + } + + function checkWhileStatement(node: WhileStatement) { + // Grammar checking + checkGrammarStatementInAmbientContext(node); + + 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 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); + } + } + + if (node.initializer) { + if (node.initializer.kind === SyntaxKind.VariableDeclarationList) { + checkVariableDeclarationList(node.initializer as VariableDeclarationList); + } + else { + checkExpression(node.initializer); + } + } + + if (node.condition) checkTruthinessExpression(node.condition); + if (node.incrementor) checkExpression(node.incrementor); + checkSourceElement(node.statement); + if (node.locals) { + registerForUnusedIdentifiersCheck(node); + } + } + + 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 { + const functionFlags = getFunctionFlags(container); + if ((functionFlags & (FunctionFlags.Invalid | FunctionFlags.Async)) === FunctionFlags.Async && languageVersion < LanguageFeatureMinimumTarget.ForAwaitOf) { + // 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 < LanguageFeatureMinimumTarget.ForOf) { + // 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) { + checkVariableDeclarationList(node.initializer as VariableDeclarationList); + } + 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, + ); + + // 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); + } + } + + function checkForInStatement(node: ForInStatement) { + // Grammar checking + checkGrammarForInOrForOfStatement(node); + + 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); + } + checkVariableDeclarationList(node.initializer as VariableDeclarationList); + } + 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, + ); + } + } + + // 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)); + } + + 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 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) { + if (errorNode) { + reportTypeNotIterableError(errorNode, inputType, allowAsyncIterables); + } + return undefined; + } + + 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); + } + } + } + if (iterationTypes || uplevelIteration) { + return possibleOutOfBounds ? includeUndefinedInIndexSignature(iterationTypes && iterationTypes.yieldType) : (iterationTypes && iterationTypes.yieldType); + } + } + + let arrayType = inputType; + 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); + } + } + else if (arrayType.flags & TypeFlags.StringLike) { + arrayType = neverType; + } + + hasStringConstituent = arrayType !== inputType; + if (hasStringConstituent) { + // 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; + } + } + } + + if (!isArrayLikeType(arrayType)) { + if (errorNode) { + // 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; + } + + 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); + } + + return (use & IterationUse.PossiblyOutOfBounds) ? includeUndefinedInIndexSignature(arrayElementType) : arrayElementType; + + 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]; + } + + const yieldType = getIterationTypeOfIterable(use, IterationTypeKind.Yield, inputType, /*errorNode*/ undefined); + + 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]; + } + + 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]; + } + + 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; + } + 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; + } + + const iterationTypes = getIterationTypesOfIterable(inputType, use, errorNode); + return iterationTypes && iterationTypes[getIterationTypesKeyFromIterationTypeKind(typeKind)]; + } + + 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 }; + } + + /** + * 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; + } + + 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 iterationTypes; + } + + 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); + } + } + 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, + ); + } + + /** + * 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; + } + else { + return use & IterationUse.ForOfFlag ? + getAsyncFromSyncIterationTypes(iterationTypes, errorNode) : + 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; + } + 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; + } + } + } + } + + if (use & IterationUse.AllowsAsyncIterablesFlag) { + const iterationTypes = getIterationTypesOfIterableSlow(type, asyncIterationTypesResolver, errorNode, errorOutputContainer, noCache); + if (iterationTypes !== noIterationTypes) { + return iterationTypes; + } + } + + 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; + } + } + } + + 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); + } + + 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)); + } + } + + 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; + } + + /** + * 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 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 *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; + } + + let iterationTypes = getIterationTypesOfIteratorCached(type, resolver) || + getIterationTypesOfIteratorFast(type, resolver); + + if (iterationTypes === noIterationTypes && errorNode) { + iterationTypes = undefined; + noCache = true; + } + + 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); + } + + /** + * 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)); + } + } + + 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); + } + + function isYieldIteratorResult(type: Type) { + return isIteratorResult(type, IterationTypeKind.Yield); + } + + function isReturnIteratorResult(type: Type) { + return isIteratorResult(type, IterationTypeKind.Return); + } + + /** + * 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 cachedTypes = getCachedIterationTypes(type, "iterationTypesOfIteratorResult"); + if (cachedTypes) { + return cachedTypes; + } + + // 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; + + if (!yieldType && !returnType) { + return setCachedIterationTypes(type, "iterationTypesOfIteratorResult", noIterationTypes); + } + + // 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)); + } + + /** + * 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; + + 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); + } + } + 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, + ); + } + } + + // 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 *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); + } + } + + // 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); + } + + 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); + } + + /** + * 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; + } + + const iterationTypes = getIterationTypesOfGeneratorFunctionReturnType(returnType, isAsyncGenerator); + return iterationTypes && iterationTypes[getIterationTypesKeyFromIterationTypeKind(kind)]; + } + + function getIterationTypesOfGeneratorFunctionReturnType(type: Type, isAsyncGenerator: boolean) { + if (isTypeAny(type)) { + return anyIterationTypes; + } + + 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 checkBreakOrContinueStatement(node: BreakOrContinueStatement) { + // Grammar checking + if (!checkGrammarStatementInAmbientContext(node)) checkGrammarBreakOrContinueStatement(node); + + // 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(unwrapAwaitedType(returnIterationType)) : returnIterationType; + } + return isAsync ? getAwaitedTypeNoAlias(returnType) || errorType : returnType; + } + + function isUnwrappedReturnTypeUndefinedVoidOrAny(func: SignatureDeclaration, returnType: Type): boolean { + const type = unwrapReturnType(returnType, getFunctionFlags(func)); + return !!(type && (maybeTypeOfKind(type, TypeFlags.Void) || type.flags & (TypeFlags.Any | TypeFlags.Undefined))); + } + + 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; + } + + 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); + } + } + 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 && compilerOptions.noImplicitReturns && !isUnwrappedReturnTypeUndefinedVoidOrAny(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); + } + } + + 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); + } + } + + function checkSwitchStatement(node: SwitchStatement) { + // Grammar checking + checkGrammarStatementInAmbientContext(node); + + let firstDefaultClause: CaseOrDefaultClause; + let hasDuplicateDefaultClause = false; + + const expressionType = checkExpression(node.expression); + + 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; + } + } + + 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 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. + const caseType = checkExpression(clause.expression); + + if (!isTypeEqualityComparableTo(expressionType, caseType)) { + // expressionType is not comparable to caseType, try the reversed check and report errors if it fails + checkTypeComparableTo(caseType, expressionType, clause.expression, /*headMessage*/ undefined); + } + }; + } + }); + if (node.caseBlock.locals) { + registerForUnusedIdentifiersCheck(node.caseBlock); + } + } + + 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); + } + + function checkThrowStatement(node: ThrowStatement) { + // Grammar checking + if (!checkGrammarStatementInAmbientContext(node)) { + if (isIdentifier(node.expression) && !node.expression.escapedText) { + grammarErrorAfterFirstToken(node, Diagnostics.Line_break_not_permitted_here); + } + } + + if (node.expression) { + checkExpression(node.expression); + } + } + + 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; + checkVariableLikeDeclaration(declaration); + const typeNode = getEffectiveTypeAnnotationNode(declaration); + if (typeNode) { + const type = getTypeFromTypeNode(typeNode); + 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); + } + 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, unescapeLeadingUnderscores(caughtName)); + } + }); + } + } + } + + checkBlock(catchClause.block); + } + + 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 = getSymbolOfDeclaration(member); + checkIndexConstraintForProperty(type, symbol, getTypeOfExpression((member as DynamicNamedDeclaration).name.expression), getNonMissingTypeOfSymbol(symbol)); + } + } + } + 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(getSymbolOfDeclaration(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(getSymbolOfDeclaration(declaration)) === type.symbol ? declaration : undefined; + for (const info of indexInfos) { + if (info === checkInfo) continue; + const localIndexDeclaration = info.declaration && getParentOfSymbol(getSymbolOfDeclaration(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": + case "undefined": + error(name, message, name.escapedText as string); + } + } + + /** + * 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 checkUnmatchedJSDocParameters(node: SignatureDeclaration) { + const jsdocParameters = filter(getJSDocTags(node), isJSDocParameterTag); + if (!length(jsdocParameters)) return; + + 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); + } + }); + + const containsArguments = containsArgumentsReference(node); + if (containsArguments) { + const lastJSDocParamIndex = jsdocParameters.length - 1; + const lastJSDocParam = jsdocParameters[lastJSDocParamIndex]; + if ( + isJs && lastJSDocParam && isIdentifier(lastJSDocParam.name) && lastJSDocParam.typeExpression && + lastJSDocParam.typeExpression.type && !parameters.has(lastJSDocParam.name.escapedText) && !excludedParameters.has(lastJSDocParamIndex) && !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)); + } + } + }); + } + } + + /** + * 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)); + } + } + + 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 === getSymbolOfDeclaration(typeParameters[i])) { + error(node, Diagnostics.Type_parameter_defaults_can_only_reference_previously_declared_type_parameters); + } + } + } + } + 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; + } + + 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); + } + } + } + } + + 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; + } + + for (let i = 0; i < numTypeParameters; i++) { + const source = sourceParameters[i]; + const target = targetParameters[i]; + + // If the type parameter node does not have the same as the resolved type + // parameter at this position, we report an error. + if (source.name.escapedText !== target.symbol.escapedName) { + 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 getFirstTransformableStaticClassElement(node: ClassLikeDeclaration) { + const willTransformStaticElementsOfDecoratedClass = !legacyDecorators && languageVersion < LanguageFeatureMinimumTarget.ClassAndClassElementDecorators && + classOrConstructorParameterIsDecorated(/*useLegacyDecorators*/ false, node); + const willTransformPrivateElementsOrClassStaticBlocks = languageVersion < LanguageFeatureMinimumTarget.PrivateNamesAndClassStaticBlocks || + languageVersion < LanguageFeatureMinimumTarget.ClassAndClassElementDecorators; + const willTransformInitializers = !emitStandardClassFields; + if (willTransformStaticElementsOfDecoratedClass || willTransformPrivateElementsOrClassStaticBlocks) { + for (const member of node.members) { + if (willTransformStaticElementsOfDecoratedClass && classElementOrClassElementParameterIsDecorated(/*useLegacyDecorators*/ false, member, node)) { + return firstOrUndefined(getDecorators(node)) ?? node; + } + else if (willTransformPrivateElementsOrClassStaticBlocks) { + if (isClassStaticBlockDeclaration(member)) { + return member; + } + else if (isStatic(member)) { + if ( + isPrivateIdentifierClassElementDeclaration(member) || + willTransformInitializers && isInitializedProperty(member) + ) { + return member; + } + } + } + } + } + } + + function checkClassExpressionExternalHelpers(node: ClassExpression) { + if (node.name) return; + + const parent = walkUpOuterExpressions(node); + if (!isNamedEvaluationSource(parent)) return; + + const willTransformESDecorators = !legacyDecorators && languageVersion < LanguageFeatureMinimumTarget.ClassAndClassElementDecorators; + let location: Node | undefined; + if (willTransformESDecorators && classOrConstructorParameterIsDecorated(/*useLegacyDecorators*/ false, node)) { + location = firstOrUndefined(getDecorators(node)) ?? node; + } + else { + location = getFirstTransformableStaticClassElement(node); + } + + if (location) { + checkExternalEmitHelpers(location, ExternalEmitHelpers.SetFunctionName); + if ((isPropertyAssignment(parent) || isPropertyDeclaration(parent) || isBindingElement(parent)) && isComputedPropertyName(parent.name)) { + checkExternalEmitHelpers(location, ExternalEmitHelpers.PropKey); + } + } + } + + function checkClassExpression(node: ClassExpression): Type { + checkClassLikeDeclaration(node); + checkNodeDeferred(node); + checkClassExpressionExternalHelpers(node); + return getTypeOfSymbol(getSymbolOfDeclaration(node)); + } + + function checkClassExpressionDeferred(node: ClassExpression) { + forEach(node.members, checkSourceElement); + registerForUnusedIdentifiersCheck(node); + } + + function checkClassDeclaration(node: ClassDeclaration) { + const firstDecorator = find(node.modifiers, isDecorator); + if (legacyDecorators && 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); + + registerForUnusedIdentifiersCheck(node); + } + + function checkClassLikeDeclaration(node: ClassLikeDeclaration) { + checkGrammarClassLikeDeclaration(node); + checkDecorators(node); + checkCollisionsForDeclarationName(node, node.name); + checkTypeParameters(getEffectiveTypeParameterDeclarations(node)); + checkExportsOnMergedDeclarations(node); + const symbol = getSymbolOfDeclaration(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 < LanguageFeatureMinimumTarget.Classes) { + 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 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 { + 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); + } + } + checkKindsOfPropertyMemberOverrides(type, baseType); + }); + } + } + + 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)); + } + } + + 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 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; + } + + 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, + ); + } + } + + /** + * @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( + node, + staticType, + baseStaticType, + baseWithThis, + type, + typeWithThis, + hasOverrideModifier(member), + hasAbstractModifier(member), + isStatic(member), + memberIsParameterProperty, + 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, + member: Symbol, + errorNode?: Node, + ): MemberOverrideStatus { + const isJs = isInJSFile(node); + const nodeInAmbientContext = !!(node.flags & NodeFlags.Ambient); + if (baseWithThis && (memberHasOverrideModifier || compilerOptions.noImplicitOverride)) { + const thisType = memberIsStatic ? staticType : typeWithThis; + const baseType = memberIsStatic ? baseStaticType : baseWithThis; + const prop = getPropertyOfType(thisType, member.escapedName); + const baseProp = getPropertyOfType(baseType, member.escapedName); + + const baseClassName = typeToString(baseWithThis); + if (prop && !baseProp && memberHasOverrideModifier) { + if (errorNode) { + const suggestion = getSuggestedSymbolForNonexistentClassMember(symbolName(member), 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; + } + + 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) { + 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; + } + + 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, /*headMessage*/ undefined, rootChain)) { + issuedMemberError = true; + } + } + } + } + if (!issuedMemberError) { + // check again with diagnostics to generate a less-specific error + checkTypeAssignableTo(typeWithThis, baseWithThis, node.name || node, broadDiag); + } + } + + 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)); + } + } + } + } + + /** + * 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. + * @param memberSymbol Member symbol. + * Note: `member` can be a synthetic node without a parent. + */ + function getMemberOverrideModifierStatus(node: ClassLikeDeclaration, member: ClassElement, memberSymbol: Symbol): MemberOverrideStatus { + if (!member.name) { + return MemberOverrideStatus.Ok; + } + + const classSymbol = getSymbolOfDeclaration(node); + const type = getDeclaredTypeOfSymbol(classSymbol) as InterfaceType; + const typeWithThis = getTypeWithThisArgument(type); + const staticType = getTypeOfSymbol(classSymbol) 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); + + return checkMemberForOverrideModifier( + node, + staticType, + baseStaticType, + baseWithThis, + type, + typeWithThis, + memberHasOverrideModifier, + hasAbstractModifier(member), + isStatic(member), + /*memberIsParameterProperty*/ false, + memberSymbol, + ); + } + + 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 + // NOTE: cast to TransientSymbol should be safe because only TransientSymbols have CheckFlags.Instantiated + return getCheckFlags(s) & CheckFlags.Instantiated ? (s as TransientSymbol).links.target! : s; + } + + function getClassOrInterfaceDeclarationsOfSymbol(symbol: Symbol) { + return filter(symbol.declarations, (d: Declaration): d is ClassDeclaration | InterfaceDeclaration => d.kind === SyntaxKind.ClassDeclaration || d.kind === SyntaxKind.InterfaceDeclaration); + } + + 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); + + interface MemberInfo { + missedProperties: string[]; + baseTypeName: string; + typeName: string; + } + const notImplementedInfo = new Map(); + + 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; + } + } + const baseTypeName = typeToString(baseType); + const typeName = typeToString(type); + const basePropertyName = symbolToString(baseProperty); + const missedProperties = append(notImplementedInfo.get(derivedClassDecl)?.missedProperties, basePropertyName); + notImplementedInfo.set(derivedClassDecl, { baseTypeName, typeName, missedProperties }); + } + } + 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; + } + + 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)); + } + } + } + + // 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; + } + + error(getNameOfDeclaration(derived.valueDeclaration) || derived.valueDeclaration, errorMessage, typeToString(baseType), symbolToString(base), typeToString(type)); + } + } + + for (const [errorNode, memberInfo] of notImplementedInfo) { + if (length(memberInfo.missedProperties) === 1) { + if (isClassExpression(errorNode)) { + error(errorNode, Diagnostics.Non_abstract_class_expression_does_not_implement_inherited_abstract_member_0_from_class_1, first(memberInfo.missedProperties), memberInfo.baseTypeName); + } + else { + error(errorNode, Diagnostics.Non_abstract_class_0_does_not_implement_inherited_abstract_member_1_from_class_2, memberInfo.typeName, first(memberInfo.missedProperties), memberInfo.baseTypeName); + } + } + else if (length(memberInfo.missedProperties) > 5) { + const missedProperties = map(memberInfo.missedProperties.slice(0, 4), prop => `'${prop}'`).join(", "); + const remainingMissedProperties = length(memberInfo.missedProperties) - 4; + if (isClassExpression(errorNode)) { + error(errorNode, Diagnostics.Non_abstract_class_expression_is_missing_implementations_for_the_following_members_of_0_Colon_1_and_2_more, memberInfo.baseTypeName, missedProperties, remainingMissedProperties); + } + else { + error(errorNode, Diagnostics.Non_abstract_class_0_is_missing_implementations_for_the_following_members_of_1_Colon_2_and_3_more, memberInfo.typeName, memberInfo.baseTypeName, missedProperties, remainingMissedProperties); + } + } + else { + const missedProperties = map(memberInfo.missedProperties, prop => `'${prop}'`).join(", "); + if (isClassExpression(errorNode)) { + error(errorNode, Diagnostics.Non_abstract_class_expression_is_missing_implementations_for_the_following_members_of_0_Colon_1, memberInfo.baseTypeName, missedProperties); + } + else { + error(errorNode, Diagnostics.Non_abstract_class_0_is_missing_implementations_for_the_following_members_of_1_Colon_2, memberInfo.typeName, memberInfo.baseTypeName, missedProperties); + } + } + } + } + + 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); + }); + + 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; + } + + 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; + + 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; + + 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(getSourceFileOfNode(typeNode), typeNode, errorInfo)); + } + } + } + } + + 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; + } + if (!isStatic(member) && isPropertyWithoutInitializer(member)) { + const propName = (member as PropertyDeclaration).name; + if (isIdentifier(propName) || isPrivateIdentifier(propName) || isComputedPropertyName(propName)) { + const type = getTypeOfSymbol(getSymbolOfDeclaration(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 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); + } + + function checkInterfaceDeclaration(node: InterfaceDeclaration) { + // Grammar checking + if (!checkGrammarModifiers(node)) checkGrammarInterfaceDeclaration(node); + + checkTypeParameters(node.typeParameters); + addLazyDiagnostic(() => { + checkTypeNameIsReserved(node.name, Diagnostics.Interface_name_cannot_be_0); + + checkExportsOnMergedDeclarations(node); + const symbol = getSymbolOfDeclaration(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); + + addLazyDiagnostic(() => { + checkTypeForDuplicateIndexSignatures(node); + registerForUnusedIdentifiersCheck(node); + }); + } + + function checkTypeAliasDeclaration(node: TypeAliasDeclaration) { + // Grammar checking + checkGrammarModifiers(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 computeEnumMemberValues(node: EnumDeclaration) { + const nodeLinks = getNodeLinks(node); + if (!(nodeLinks.flags & NodeCheckFlags.EnumValuesComputed)) { + nodeLinks.flags |= NodeCheckFlags.EnumValuesComputed; + let autoValue: number | undefined = 0; + let previous: EnumMember | undefined; + for (const member of node.members) { + const result = computeEnumMemberValue(member, autoValue, previous); + getNodeLinks(member).enumMemberValue = result; + autoValue = typeof result.value === "number" ? result.value + 1 : undefined; + previous = member; + } + } + } + + function computeEnumMemberValue(member: EnumMember, autoValue: number | undefined, previous: EnumMember | undefined): EvaluatorResult { + 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 computeConstantEnumMemberValue(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 evaluatorResult(/*value*/ 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) { + error(member.name, Diagnostics.Enum_member_must_have_initializer); + return evaluatorResult(/*value*/ undefined); + } + if (getIsolatedModules(compilerOptions) && previous?.initializer) { + const prevValue = getEnumMemberValue(previous); + if (!(typeof prevValue.value === "number" && !prevValue.resolvedOtherFiles)) { + error( + member.name, + Diagnostics.Enum_member_following_a_non_literal_numeric_member_must_have_an_initializer_when_isolatedModules_is_enabled, + ); + } + } + return evaluatorResult(autoValue); + } + + function computeConstantEnumMemberValue(member: EnumMember): EvaluatorResult { + const isConstEnum = isEnumConst(member.parent); + const initializer = member.initializer!; + const result = evaluate(initializer, member); + if (result.value !== undefined) { + if (isConstEnum && typeof result.value === "number" && !isFinite(result.value)) { + error( + initializer, + isNaN(result.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 (getIsolatedModules(compilerOptions) && typeof result.value === "string" && !result.isSyntacticallyString) { + error( + initializer, + Diagnostics._0_has_a_string_type_but_must_have_syntactically_recognizable_string_syntax_when_isolatedModules_is_enabled, + `${idText(member.parent.name)}.${getTextOfPropertyName(member.name)}`, + ); + } + } + 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 result; + } + + function evaluateEntityNameExpression(expr: EntityNameExpression, location?: Declaration) { + const symbol = resolveEntityName(expr, SymbolFlags.Value, /*ignoreErrors*/ true); + if (!symbol) return evaluatorResult(/*value*/ undefined); + + if (expr.kind === SyntaxKind.Identifier) { + const identifier = expr; + if (isInfinityOrNaNString(identifier.escapedText) && (symbol === getGlobalSymbol(identifier.escapedText, SymbolFlags.Value, /*diagnostic*/ undefined))) { + // Technically we resolved a global lib file here, but the decision to treat this as numeric + // is more predicated on the fact that the single-file resolution *didn't* resolve to a + // different meaning of `Infinity` or `NaN`. Transpilers handle this no problem. + return evaluatorResult(+(identifier.escapedText), /*isSyntacticallyString*/ false); + } + } + + if (symbol.flags & SymbolFlags.EnumMember) { + return location ? evaluateEnumMember(expr, symbol, location) : getEnumMemberValue(symbol.valueDeclaration as EnumMember); + } + if (isConstantVariable(symbol)) { + const declaration = symbol.valueDeclaration; + if (declaration && isVariableDeclaration(declaration) && !declaration.type && declaration.initializer && (!location || declaration !== location && isBlockScopedNameDeclaredBeforeUse(declaration, location))) { + const result = evaluate(declaration.initializer, declaration); + if (location && getSourceFileOfNode(location) !== getSourceFileOfNode(declaration)) { + return evaluatorResult( + result.value, + /*isSyntacticallyString*/ false, + /*resolvedOtherFiles*/ true, + /*hasExternalReferences*/ true, + ); + } + return evaluatorResult(result.value, result.isSyntacticallyString, result.resolvedOtherFiles, /*hasExternalReferences*/ true); + } + } + return evaluatorResult(/*value*/ undefined); + } + + function evaluateElementAccessExpression(expr: ElementAccessExpression, location?: Declaration) { + const root = expr.expression; + if (isEntityNameExpression(root) && isStringLiteralLike(expr.argumentExpression)) { + const rootSymbol = resolveEntityName(root, SymbolFlags.Value, /*ignoreErrors*/ true); + if (rootSymbol && rootSymbol.flags & SymbolFlags.Enum) { + const name = escapeLeadingUnderscores(expr.argumentExpression.text); + const member = rootSymbol.exports!.get(name); + if (member) { + Debug.assert(getSourceFileOfNode(member.valueDeclaration) === getSourceFileOfNode(rootSymbol.valueDeclaration)); + return location ? evaluateEnumMember(expr, member, location) : getEnumMemberValue(member.valueDeclaration as EnumMember); + } + } + } + return evaluatorResult(/*value*/ 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 evaluatorResult(/*value*/ 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 evaluatorResult(/*value*/ 0); + } + const value = getEnumMemberValue(declaration as EnumMember); + if (location.parent !== declaration.parent) { + return evaluatorResult(value.value, value.isSyntacticallyString, value.resolvedOtherFiles, /*hasExternalReferences*/ true); + } + return value; + } + + function checkEnumDeclaration(node: EnumDeclaration) { + addLazyDiagnostic(() => checkEnumDeclarationWorker(node)); + } + + function checkEnumDeclarationWorker(node: EnumDeclaration) { + // Grammar checking + checkGrammarModifiers(node); + + checkCollisionsForDeclarationName(node, node.name); + checkExportsOnMergedDeclarations(node); + node.members.forEach(checkEnumMember); + + 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 = getSymbolOfDeclaration(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; + } + + 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); + } + 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 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; + } + + 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); + } + } + + 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); + } + + 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 (!checkGrammarModifiers(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 = getSymbolOfDeclaration(node); + + // The following checks only apply on a non-ambient instantiated module declaration. + if ( + symbol.flags & SymbolFlags.ValueModule + && !inAmbientContext + && isInstantiatedModule(node, shouldPreserveConstEnums(compilerOptions)) + ) { + if (getIsolatedModules(compilerOptions) && !getSourceFileOfNode(node).externalModuleIndicator) { + // This could be loosened a little if needed. The only problem we are trying to avoid is unqualified + // references to namespace members declared in other files. But use of namespaces is discouraged anyway, + // so for now we will just not allow them in scripts, which is the only place they can merge cross-file. + error(node.name, Diagnostics.Namespaces_are_not_allowed_in_global_script_files_when_0_is_enabled_If_this_file_is_not_intended_to_be_a_global_script_set_moduleDetection_to_force_or_add_an_empty_export_statement, isolatedModulesLikeFlagName); + } + if (symbol.declarations?.length! > 1) { + 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 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 ( + compilerOptions.verbatimModuleSyntax && + node.parent.kind === SyntaxKind.SourceFile && + (moduleKind === ModuleKind.CommonJS || node.parent.impliedNodeFormat === ModuleKind.CommonJS) + ) { + const exportModifier = node.modifiers?.find(m => m.kind === SyntaxKind.ExportKeyword); + if (exportModifier) { + error(exportModifier, Diagnostics.A_top_level_export_modifier_cannot_be_used_on_value_declarations_in_a_CommonJS_module_when_verbatimModuleSyntax_is_enabled); + } + } + } + + 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 || (getSymbolOfDeclaration(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 (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 { + // 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; + } + 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 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 (!isImportEqualsDeclaration(node) && node.attributes) { + const diagnostic = node.attributes.token === SyntaxKind.WithKeyword ? Diagnostics.Import_attribute_values_must_be_string_literal_expressions : Diagnostics.Import_assertion_values_must_be_string_literal_expressions; + let hasError = false; + for (const attr of node.attributes.elements) { + if (!isStringLiteral(attr.value)) { + hasError = true; + error(attr.value, diagnostic); + } + } + return !hasError; + } + return true; + } + + function checkModuleExportName(name: ModuleExportName | undefined, allowStringLiteral = true) { + if (name === undefined || name.kind !== SyntaxKind.StringLiteral) { + return; + } + if (!allowStringLiteral) { + grammarErrorOnNode(name, Diagnostics.Identifier_expected); + } + else if (moduleKind === ModuleKind.ES2015 || moduleKind === ModuleKind.ES2020) { + grammarErrorOnNode(name, Diagnostics.String_literal_import_and_export_names_are_not_supported_when_the_module_flag_is_set_to_es2015_or_es2020); + } + } + + function checkAliasSymbol(node: AliasDeclarationNode) { + let symbol = getSymbolOfDeclaration(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(moduleExportNameTextEscaped(node.propertyName || node.name)); + 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; + } + + const targetFlags = getSymbolFlags(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)); + } + else if (node.kind !== SyntaxKind.ExportSpecifier) { + // Look at 'compilerOptions.isolatedModules' and not 'getIsolatedModules(...)' (which considers 'verbatimModuleSyntax') + // here because 'verbatimModuleSyntax' will already have an error for importing a type without 'import type'. + const appearsValueyToTranspiler = compilerOptions.isolatedModules && !findAncestor(node, isTypeOnlyImportOrExportDeclaration); + if (appearsValueyToTranspiler && symbol.flags & (SymbolFlags.Value | SymbolFlags.ExportValue)) { + error( + node, + Diagnostics.Import_0_conflicts_with_local_value_so_must_be_declared_with_a_type_only_import_when_isolatedModules_is_enabled, + symbolToString(symbol), + isolatedModulesLikeFlagName, + ); + } + } + + if ( + getIsolatedModules(compilerOptions) + && !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.verbatimModuleSyntax) { + Debug.assertIsDefined(node.name, "An ImportClause with a symbol should have a name"); + const message = compilerOptions.verbatimModuleSyntax && isInternalModuleImportEqualsDeclaration(node) + ? Diagnostics.An_import_alias_cannot_resolve_to_a_type_or_type_only_declaration_when_verbatimModuleSyntax_is_enabled + : isType + ? Diagnostics._0_is_a_type_and_must_be_imported_using_a_type_only_import_when_verbatimModuleSyntax_is_enabled + : Diagnostics._0_resolves_to_a_type_only_declaration_and_must_be_imported_using_a_type_only_import_when_verbatimModuleSyntax_is_enabled; + const name = moduleExportNameTextUnescaped(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_0_is_enabled, isolatedModulesLikeFlagName); + } + 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 (compilerOptions.verbatimModuleSyntax || getSourceFileOfNode(typeOnlyAlias) !== getSourceFileOfNode(node)) { + const name = moduleExportNameTextUnescaped(node.propertyName || node.name); + const diagnostic = isType + ? error(node, Diagnostics.Re_exporting_a_type_when_0_is_enabled_requires_using_export_type, isolatedModulesLikeFlagName) + : error(node, Diagnostics._0_resolves_to_a_type_only_declaration_and_must_be_re_exported_using_a_type_only_re_export_when_1_is_enabled, name, isolatedModulesLikeFlagName); + addTypeOnlyDeclarationRelatedInfo(diagnostic, isType ? undefined : typeOnlyAlias, name); + break; + } + } + } + } + + if ( + compilerOptions.verbatimModuleSyntax && + node.kind !== SyntaxKind.ImportEqualsDeclaration && + !isInJSFile(node) && + (moduleKind === ModuleKind.CommonJS || getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS) + ) { + error(node, Diagnostics.ESM_syntax_is_not_allowed_in_a_CommonJS_module_when_verbatimModuleSyntax_is_enabled); + } + } + + if (isImportSpecifier(node)) { + const targetSymbol = resolveAliasWithDeprecationCheck(symbol, node); + if (isDeprecatedSymbol(targetSymbol) && targetSymbol.declarations) { + addDeprecatedSuggestion(node, targetSymbol.declarations, targetSymbol.escapedName as string); + } + } + } + } + + function resolveAliasWithDeprecationCheck(symbol: Symbol, location: Node) { + if (!(symbol.flags & SymbolFlags.Alias) || isDeprecatedSymbol(symbol) || !getDeclarationOfAliasSymbol(symbol)) { + 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 (isDeprecatedSymbol(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) { + checkModuleExportName(node.propertyName); + if ( + moduleExportNameIsDefault(node.propertyName || node.name) && + getESModuleInterop(compilerOptions) && + moduleKind !== ModuleKind.System && (moduleKind < ModuleKind.ES2015 || getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS) + ) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.ImportDefault); + } + } + } + + function checkImportAttributes(declaration: ImportDeclaration | ExportDeclaration | JSDocImportTag) { + const node = declaration.attributes; + if (node) { + const importAttributesType = getGlobalImportAttributesType(/*reportErrors*/ true); + if (importAttributesType !== emptyObjectType) { + checkTypeAssignableTo(getTypeFromImportAttributes(node), getNullableType(importAttributesType, TypeFlags.Undefined), node); + } + + const validForTypeAttributes = isExclusivelyTypeOnlyImportOrExport(declaration); + const override = getResolutionModeOverride(node, validForTypeAttributes ? grammarErrorOnNode : undefined); + const isImportAttributes = declaration.attributes.token === SyntaxKind.WithKeyword; + if (validForTypeAttributes && override) { + return; // Other grammar checks do not apply to type-only imports with resolution mode assertions + } + + const mode = (moduleKind === ModuleKind.NodeNext) && declaration.moduleSpecifier && getUsageModeForExpression(declaration.moduleSpecifier); + if (mode !== ModuleKind.ESNext && moduleKind !== ModuleKind.ESNext && moduleKind !== ModuleKind.Preserve) { + const message = isImportAttributes + ? moduleKind === ModuleKind.NodeNext + ? Diagnostics.Import_attributes_are_not_allowed_on_statements_that_compile_to_CommonJS_require_calls + : Diagnostics.Import_attributes_are_only_supported_when_the_module_option_is_set_to_esnext_nodenext_or_preserve + : moduleKind === ModuleKind.NodeNext + ? Diagnostics.Import_assertions_are_not_allowed_on_statements_that_compile_to_CommonJS_require_calls + : Diagnostics.Import_assertions_are_only_supported_when_the_module_option_is_set_to_esnext_nodenext_or_preserve; + return grammarErrorOnNode(node, message); + } + + const isTypeOnly = isJSDocImportTag(declaration) || (isImportDeclaration(declaration) ? declaration.importClause?.isTypeOnly : declaration.isTypeOnly); + if (isTypeOnly) { + return grammarErrorOnNode(node, isImportAttributes ? Diagnostics.Import_attributes_cannot_be_used_with_type_only_imports_or_exports : Diagnostics.Import_assertions_cannot_be_used_with_type_only_imports_or_exports); + } + + if (override) { + return grammarErrorOnNode(node, Diagnostics.resolution_mode_can_only_be_set_for_type_only_imports); + } + } + } + + function checkImportAttribute(node: ImportAttribute) { + return getRegularTypeOfLiteralType(checkExpressionCached(node.value)); + } + + 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 (!checkGrammarModifiers(node) && node.modifiers) { + 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); + } + } + } + } + } + checkImportAttributes(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; + } + + checkGrammarModifiers(node); + if (isInternalModuleImportEqualsDeclaration(node) || checkExternalImportOrExportDeclaration(node)) { + checkImportBinding(node); + markLinkedReferences(node, ReferenceHint.ExportImportEquals); + if (node.moduleReference.kind !== SyntaxKind.ExternalModuleReference) { + const target = resolveAlias(getSymbolOfDeclaration(node)); + if (target !== unknownSymbol) { + const targetFlags = getSymbolFlags(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); + } + } + if (node.isTypeOnly) { + grammarErrorOnNode(node, Diagnostics.An_import_alias_cannot_use_import_type); + } + } + else { + if (moduleKind >= ModuleKind.ES2015 && moduleKind !== ModuleKind.Preserve && 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; + } + + if (!checkGrammarModifiers(node) && hasSyntacticModifiers(node)) { + grammarErrorOnFirstToken(node, Diagnostics.An_export_declaration_cannot_have_modifiers); + } + + 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); + checkModuleExportName(node.exportClause.name); + } + 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); + } + } + } + } + checkImportAttributes(node); + } + + function checkGrammarExportDeclaration(node: ExportDeclaration): boolean { + if (node.isTypeOnly && 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); + } + return !isInAppropriateContext; + } + + function checkExportSpecifier(node: ExportSpecifier) { + checkAliasSymbol(node); + const hasModuleSpecifier = node.parent.parent.moduleSpecifier !== undefined; + checkModuleExportName(node.propertyName, hasModuleSpecifier); + checkModuleExportName(node.name); + if (getEmitDeclarations(compilerOptions)) { + collectLinkedAliases(node.propertyName || node.name, /*setVisibility*/ true); + } + if (!hasModuleSpecifier) { + const exportedName = node.propertyName || node.name; + if (exportedName.kind === SyntaxKind.StringLiteral) { + return; // Skip for invalid syntax like this: export { "x" } + } + // 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, /*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 { + markLinkedReferences(node, ReferenceHint.ExportSpecifier); + } + } + else { + if ( + getESModuleInterop(compilerOptions) && + moduleKind !== ModuleKind.System && + (moduleKind < ModuleKind.ES2015 || getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS) && + moduleExportNameIsDefault(node.propertyName || node.name) + ) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.ImportDefault); + } + } + } + + 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; + } + + 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); + } + + return; + } + // Grammar checking + if (!checkGrammarModifiers(node) && hasEffectiveModifiers(node)) { + grammarErrorOnFirstToken(node, Diagnostics.An_export_assignment_cannot_have_modifiers); + } + + const typeAnnotationNode = getEffectiveTypeAnnotationNode(node); + if (typeAnnotationNode) { + checkTypeAssignableTo(checkExpressionCached(node.expression), getTypeFromTypeNode(typeAnnotationNode), node.expression); + } + + const isIllegalExportDefaultInCJS = !node.isExportEquals && + !(node.flags & NodeFlags.Ambient) && + compilerOptions.verbatimModuleSyntax && + (moduleKind === ModuleKind.CommonJS || getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS); + + if (node.expression.kind === SyntaxKind.Identifier) { + const id = node.expression as Identifier; + const sym = getExportSymbolOfValueSymbolIfExported(resolveEntityName(id, SymbolFlags.All, /*ignoreErrors*/ true, /*dontResolveAlias*/ true, node)); + if (sym) { + markLinkedReferences(node, ReferenceHint.ExportAssignment); + const typeOnlyDeclaration = getTypeOnlyAliasDeclaration(sym, SymbolFlags.Value); + // If not a value, we're interpreting the identifier as a type export, along the lines of (`export { Id as default }`) + if (getSymbolFlags(sym) & SymbolFlags.Value) { + // However if it is a value, we need to check it's being used correctly + checkExpressionCached(id); + if (!isIllegalExportDefaultInCJS && !(node.flags & NodeFlags.Ambient) && compilerOptions.verbatimModuleSyntax && typeOnlyDeclaration) { + error( + id, + node.isExportEquals + ? Diagnostics.An_export_declaration_must_reference_a_real_value_when_verbatimModuleSyntax_is_enabled_but_0_resolves_to_a_type_only_declaration + : Diagnostics.An_export_default_must_reference_a_real_value_when_verbatimModuleSyntax_is_enabled_but_0_resolves_to_a_type_only_declaration, + idText(id), + ); + } + } + else if (!isIllegalExportDefaultInCJS && !(node.flags & NodeFlags.Ambient) && compilerOptions.verbatimModuleSyntax) { + error( + id, + node.isExportEquals + ? Diagnostics.An_export_declaration_must_reference_a_value_when_verbatimModuleSyntax_is_enabled_but_0_only_refers_to_a_type + : Diagnostics.An_export_default_must_reference_a_value_when_verbatimModuleSyntax_is_enabled_but_0_only_refers_to_a_type, + idText(id), + ); + } + + if (!isIllegalExportDefaultInCJS && !(node.flags & NodeFlags.Ambient) && getIsolatedModules(compilerOptions) && !(sym.flags & SymbolFlags.Value)) { + const nonLocalMeanings = getSymbolFlags(sym, /*excludeTypeOnlyMeanings*/ false, /*excludeLocalMeanings*/ true); + if ( + sym.flags & SymbolFlags.Alias + && nonLocalMeanings & SymbolFlags.Type + && !(nonLocalMeanings & SymbolFlags.Value) + && (!typeOnlyDeclaration || getSourceFileOfNode(typeOnlyDeclaration) !== getSourceFileOfNode(node)) + ) { + // import { SomeType } from "./someModule"; + // export default SomeType; OR + // export = SomeType; + error( + id, + node.isExportEquals ? + Diagnostics._0_resolves_to_a_type_and_must_be_marked_type_only_in_this_file_before_re_exporting_when_1_is_enabled_Consider_using_import_type_where_0_is_imported + : Diagnostics._0_resolves_to_a_type_and_must_be_marked_type_only_in_this_file_before_re_exporting_when_1_is_enabled_Consider_using_export_type_0_as_default, + idText(id), + isolatedModulesLikeFlagName, + ); + } + else if (typeOnlyDeclaration && getSourceFileOfNode(typeOnlyDeclaration) !== getSourceFileOfNode(node)) { + // import { SomeTypeOnlyValue } from "./someModule"; + // export default SomeTypeOnlyValue; OR + // export = SomeTypeOnlyValue; + addTypeOnlyDeclarationRelatedInfo( + error( + id, + node.isExportEquals ? + Diagnostics._0_resolves_to_a_type_only_declaration_and_must_be_marked_type_only_in_this_file_before_re_exporting_when_1_is_enabled_Consider_using_import_type_where_0_is_imported + : Diagnostics._0_resolves_to_a_type_only_declaration_and_must_be_marked_type_only_in_this_file_before_re_exporting_when_1_is_enabled_Consider_using_export_type_0_as_default, + idText(id), + isolatedModulesLikeFlagName, + ), + typeOnlyDeclaration, + idText(id), + ); + } + } + } + else { + checkExpressionCached(id); // doesn't resolve, check as expression to mark as error + } + + if (getEmitDeclarations(compilerOptions)) { + collectLinkedAliases(id, /*setVisibility*/ true); + } + } + else { + checkExpressionCached(node.expression); + } + + if (isIllegalExportDefaultInCJS) { + error(node, Diagnostics.ESM_syntax_is_not_allowed_in_a_CommonJS_module_when_verbatimModuleSyntax_is_enabled); + } + + 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 (node.isExportEquals) { + // Forbid export= in esm implementation files, and esm mode declaration files + if ( + moduleKind >= ModuleKind.ES2015 && + moduleKind !== ModuleKind.Preserve && + ((node.flags & NodeFlags.Ambient && getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.ESNext) || + (!(node.flags & NodeFlags.Ambient) && 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 && !(node.flags & NodeFlags.Ambient)) { + // system modules does not support export assignment + grammarErrorOnNode(node, Diagnostics.Export_assignment_is_not_supported_when_module_flag_is_system); + } + } + } + + function hasExportedMembers(moduleSymbol: Symbol) { + return forEachEntry(moduleSymbol.exports!, (_, id) => id !== "export="); + } + + function checkExternalModuleExports(node: SourceFile | ModuleDeclaration) { + const moduleSymbol = getSymbolOfDeclaration(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))); + } + } + } + } + }); + } + 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 checkSourceElement(node: Node | undefined): void { + if (node) { + const saveCurrentNode = currentNode; + currentNode = node; + instantiationCount = 0; + checkSourceElementWorker(node); + currentNode = saveCurrentNode; + } + } + + function checkSourceElementWorker(node: Node): void { + if (getNodeCheckFlags(node) & NodeCheckFlags.PartiallyTypeChecked) { + return; + } + + if (canHaveJSDoc(node)) { + forEach(node.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(); + } + } + if (kind >= SyntaxKind.FirstStatement && kind <= SyntaxKind.LastStatement && canHaveFlowNode(node) && node.flowNode && !isReachableFlowNode(node.flowNode)) { + errorOrSuggestion(compilerOptions.allowUnreachableCode === false, node, Diagnostics.Unreachable_code_detected); + } + + // If editing this, keep `isSourceElement` in utilities up to date. + 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.JSDocSatisfiesTag: + return checkJSDocSatisfiesTag(node as JSDocSatisfiesTag); + case SyntaxKind.JSDocThisTag: + return checkJSDocThisTag(node as JSDocThisTag); + case SyntaxKind.JSDocImportTag: + return checkJSDocImportTag(node as JSDocImportTag); + 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 checkJSDocCommentWorker(node: string | readonly JSDocComment[] | undefined) { + if (isArray(node)) { + forEach(node, tag => { + if (isJSDocLinkLike(tag)) { + checkSourceElement(tag); + } + }); + } + } + + function checkJSDocTypeIsInJsFile(node: Node): void { + if (!isInJSFile(node)) { + if (isJSDocNonNullableType(node) || isJSDocNullableType(node)) { + const token = tokenToString(isJSDocNonNullableType(node) ? SyntaxKind.ExclamationToken : SyntaxKind.QuestionToken); + const diagnostic = node.postfix + ? Diagnostics._0_at_the_end_of_a_type_is_not_valid_TypeScript_syntax_Did_you_mean_to_write_1 + : Diagnostics._0_at_the_start_of_a_type_is_not_valid_TypeScript_syntax_Did_you_mean_to_write_1; + const typeNode = node.type; + const type = getTypeFromTypeNode(typeNode); + grammarErrorOnNode( + node, + diagnostic, + token, + typeToString( + isJSDocNullableType(node) && !(type === neverType || type === voidType) + ? getUnionType(append([type, undefinedType], node.postfix ? undefined : nullType)) : type, + ), + ); + } + else { + grammarErrorOnNode(node, Diagnostics.JSDoc_types_can_only_be_used_inside_documentation_comments); + } + } + } + + 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); + } + 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; + } + + const param = getParameterSymbolFromJSDoc(paramTag); + if (!param) { + // We will error in `checkJSDocParameterTag`. + return; + } + + 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 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); + } + else { + Debug.assert(!links.deferredNodes, "A type-checked file should have no deferred nodes."); + } + } + + function checkDeferredNodes(context: SourceFile) { + const links = getNodeLinks(context); + if (links.deferredNodes) { + links.deferredNodes.forEach(checkDeferredNode); + } + links.deferredNodes = undefined; + } + + 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; + case SyntaxKind.TypeAssertionExpression: + case SyntaxKind.AsExpression: + case SyntaxKind.ParenthesizedExpression: + checkAssertionDeferred(node as AssertionExpression | JSDocTypeAssertion); + break; + case SyntaxKind.VoidExpression: + checkExpression((node as VoidExpression).expression); + break; + case SyntaxKind.BinaryExpression: + if (isInstanceOfExpression(node)) { + resolveUntypedCall(node); + } + break; + } + currentNode = saveCurrentNode; + tracing?.pop(); + } + + function checkSourceFile(node: SourceFile, nodesToCheck: Node[] | undefined) { + tracing?.push(tracing.Phase.Check, nodesToCheck ? "checkSourceFileNodes" : "checkSourceFile", { path: node.path }, /*separateBeginAndEnd*/ true); + const beforeMark = nodesToCheck ? "beforeCheckNodes" : "beforeCheck"; + const afterMark = nodesToCheck ? "afterCheckNodes" : "afterCheck"; + performance.mark(beforeMark); + nodesToCheck ? checkSourceFileNodesWorker(node, nodesToCheck) : checkSourceFileWorker(node); + performance.mark(afterMark); + performance.measure("Check", beforeMark, afterMark); + tracing?.pop(); + } + + 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 getPotentiallyUnusedIdentifiers(sourceFile: SourceFile): readonly PotentiallyUnusedIdentifier[] { + return allPotentiallyUnusedIdentifiers.get(sourceFile.path) || emptyArray; + } + + // 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; + } + + // Grammar checking + checkGrammarSourceFile(node); + + clear(potentialThisCollisions); + clear(potentialNewTargetCollisions); + clear(potentialWeakMapSetCollisions); + clear(potentialReflectCollisions); + clear(potentialUnusedRenamedBindingElementsInTypes); + + if (links.flags & NodeCheckFlags.PartiallyTypeChecked) { + potentialThisCollisions = links.potentialThisCollisions!; + potentialNewTargetCollisions = links.potentialNewTargetCollisions!; + potentialWeakMapSetCollisions = links.potentialWeakMapSetCollisions!; + potentialReflectCollisions = links.potentialReflectCollisions!; + potentialUnusedRenamedBindingElementsInTypes = links.potentialUnusedRenamedBindingElementsInTypes!; + } + + forEach(node.statements, checkSourceElement); + checkSourceElement(node.endOfFileToken); + + checkDeferredNodes(node); + + if (isExternalOrCommonJsModule(node)) { + registerForUnusedIdentifiersCheck(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(); + } + }); + + if (isExternalOrCommonJsModule(node)) { + checkExternalModuleExports(node); + } + + if (potentialThisCollisions.length) { + forEach(potentialThisCollisions, checkIfThisIsCapturedInEnclosingScope); + clear(potentialThisCollisions); + } + + if (potentialNewTargetCollisions.length) { + forEach(potentialNewTargetCollisions, checkIfNewTargetIsCapturedInEnclosingScope); + clear(potentialNewTargetCollisions); + } + + if (potentialWeakMapSetCollisions.length) { + forEach(potentialWeakMapSetCollisions, checkWeakMapSetCollision); + clear(potentialWeakMapSetCollisions); + } + + if (potentialReflectCollisions.length) { + forEach(potentialReflectCollisions, checkReflectCollision); + clear(potentialReflectCollisions); + } + + links.flags |= NodeCheckFlags.TypeChecked; + } + } + + function checkSourceFileNodesWorker(file: SourceFile, nodes: readonly Node[]) { + const links = getNodeLinks(file); + if (!(links.flags & NodeCheckFlags.TypeChecked)) { + if (skipTypeChecking(file, compilerOptions, host)) { + return; + } + + // Grammar checking + checkGrammarSourceFile(file); + + clear(potentialThisCollisions); + clear(potentialNewTargetCollisions); + clear(potentialWeakMapSetCollisions); + clear(potentialReflectCollisions); + clear(potentialUnusedRenamedBindingElementsInTypes); + + forEach(nodes, checkSourceElement); + + checkDeferredNodes(file); + + (links.potentialThisCollisions || (links.potentialThisCollisions = [])).push(...potentialThisCollisions); + (links.potentialNewTargetCollisions || (links.potentialNewTargetCollisions = [])).push(...potentialNewTargetCollisions); + (links.potentialWeakMapSetCollisions || (links.potentialWeakMapSetCollisions = [])).push(...potentialWeakMapSetCollisions); + (links.potentialReflectCollisions || (links.potentialReflectCollisions = [])).push(...potentialReflectCollisions); + (links.potentialUnusedRenamedBindingElementsInTypes || (links.potentialUnusedRenamedBindingElementsInTypes = [])).push( + ...potentialUnusedRenamedBindingElementsInTypes, + ); + + links.flags |= NodeCheckFlags.PartiallyTypeChecked; + for (const node of nodes) { + const nodeLinks = getNodeLinks(node); + nodeLinks.flags |= NodeCheckFlags.PartiallyTypeChecked; + } + } + } + + function getDiagnostics(sourceFile: SourceFile, ct: CancellationToken, nodesToCheck?: Node[]): 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, nodesToCheck); + } + finally { + cancellationToken = undefined; + } + } + + function ensurePendingDiagnosticWorkComplete() { + // Invoke any existing lazy diagnostics to add them, clear the backlog of diagnostics + for (const cb of deferredDiagnosticsCallbacks) { + cb(); + } + deferredDiagnosticsCallbacks = []; + } + + function checkSourceFileWithEagerDiagnostics(sourceFile: SourceFile, nodesToCheck?: Node[]) { + 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, nodesToCheck); + addLazyDiagnostic = oldAddLazyDiagnostics; + } + + function getDiagnosticsWorker(sourceFile: SourceFile, nodesToCheck: Node[] | undefined): 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; + + checkSourceFileWithEagerDiagnostics(sourceFile, nodesToCheck); + const semanticDiagnostics = diagnostics.getDiagnostics(sourceFile.fileName); + if (nodesToCheck) { + // No need to get global diagnostics. + return semanticDiagnostics; + } + 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); + } + + return semanticDiagnostics; + } + + // Global diagnostics are always added when a file is not provided to + // getDiagnostics + forEach(host.getSourceFiles(), file => checkSourceFileWithEagerDiagnostics(file)); + return diagnostics.getDiagnostics(); + } + + function getGlobalDiagnostics(): Diagnostic[] { + ensurePendingDiagnosticWorkComplete(); + return diagnostics.getGlobalDiagnostics(); + } + + // Language service support + + 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 []; + } + + const symbols = createSymbolTable(); + let isStaticSymbol = false; + + populateSymbols(); + + symbols.delete(InternalSymbolName.This); // Not a symbol, a keyword + return symbolsToArray(symbols); + + function populateSymbols() { + while (location) { + if (canHaveLocals(location) && location.locals && !isGlobalSourceFile(location)) { + copySymbols(location.locals, meaning); + } + + switch (location.kind) { + case SyntaxKind.SourceFile: + if (!isExternalModule(location as SourceFile)) break; + // falls through + case SyntaxKind.ModuleDeclaration: + copyLocallyVisibleExportSymbols(getSymbolOfDeclaration(location as ModuleDeclaration | SourceFile).exports!, meaning & SymbolFlags.ModuleMember); + break; + case SyntaxKind.EnumDeclaration: + copySymbols(getSymbolOfDeclaration(location as EnumDeclaration).exports!, meaning & SymbolFlags.EnumMember); + break; + case SyntaxKind.ClassExpression: + const className = (location as ClassExpression).name; + if (className) { + copySymbol((location as ClassExpression).symbol, 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(getSymbolOfDeclaration(location as ClassDeclaration | InterfaceDeclaration)), meaning & SymbolFlags.Type); + } + break; + case SyntaxKind.FunctionExpression: + const funcName = (location as FunctionExpression).name; + if (funcName) { + copySymbol((location as FunctionExpression).symbol, meaning); + } + break; + } + + if (introducesArgumentsExoticObject(location)) { + copySymbol(argumentsSymbol, meaning); + } + + isStaticSymbol = isStatic(location); + location = location.parent; + } + + 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 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) && symbol.escapedName !== InternalSymbolName.Default) { + copySymbol(symbol, meaning); + } + }); + } + } + } + + 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; + } + + return node.parent.kind === SyntaxKind.TypeReference; + } + + function isInNameOfExpressionWithTypeArguments(node: Node): boolean { + while (node.parent.kind === SyntaxKind.PropertyAccessExpression) { + node = node.parent; + } + + return node.parent.kind === SyntaxKind.ExpressionWithTypeArguments; + } + + function forEachEnclosingClass(node: Node, callback: (node: ClassLikeDeclaration) => T | undefined): T | undefined { + let result: T | undefined; + let containingClass = getContainingClass(node); + while (containingClass) { + if (result = callback(containingClass)) break; + containingClass = getContainingClass(containingClass); + } + + return result; + } + + 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 false; + }); + } + + function isNodeWithinClass(node: Node, classDeclaration: ClassLikeDeclaration) { + return !!forEachEnclosingClass(node, n => n === classDeclaration); + } + + function getLeftSideOfImportEqualsOrExportAssignment(nodeOnRightSide: EntityName): ImportEqualsDeclaration | ExportAssignment | undefined { + while (nodeOnRightSide.parent.kind === SyntaxKind.QualifiedName) { + nodeOnRightSide = nodeOnRightSide.parent as QualifiedName; + } + + if (nodeOnRightSide.parent.kind === SyntaxKind.ImportEqualsDeclaration) { + return (nodeOnRightSide.parent as ImportEqualsDeclaration).moduleReference === nodeOnRightSide ? nodeOnRightSide.parent as ImportEqualsDeclaration : undefined; + } + + if (nodeOnRightSide.parent.kind === SyntaxKind.ExportAssignment) { + return (nodeOnRightSide.parent as ExportAssignment).expression === nodeOnRightSide as Node ? nodeOnRightSide.parent as ExportAssignment : undefined; + } + + return undefined; + } + + function isInRightSideOfImportOrExportAssignment(node: EntityName) { + return getLeftSideOfImportEqualsOrExportAssignment(node) !== 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.Property: + if (isPropertyAccessExpression(entityName.parent) && getLeftmostAccessExpression(entityName.parent) === entityName) { + return undefined; + } + // falls through + case AssignmentDeclarationKind.ThisProperty: + case AssignmentDeclarationKind.ModuleExports: + return getSymbolOfDeclaration(entityName.parent.parent as BinaryExpression); + } + } + + 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; + } + return undefined; + } + + function isThisPropertyAndThisTyped(node: PropertyAccessExpression) { + if (node.expression.kind === SyntaxKind.ThisKeyword) { + const container = getThisContainer(node, /*includeArrowFunctions*/ false, /*includeClassComputedPropertyName*/ false); + if (isFunctionLike(container)) { + const containingLiteral = getContainingObjectLiteral(container); + if (containingLiteral) { + const contextualType = getApparentTypeOfContextualType(containingLiteral, /*contextFlags*/ undefined); + const type = getThisTypeOfObjectLiteralFromContextualType(containingLiteral, contextualType); + return type && !isTypeAny(type); + } + } + } + } + + function getSymbolOfNameOrPropertyAccessExpression(name: EntityName | PrivateIdentifier | PropertyAccessExpression | JSDocMemberName): Symbol | undefined { + if (isDeclarationName(name)) { + return getSymbolOfNode(name.parent); + } + + 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) && !isThisPropertyAndThisTyped(name.parent as PropertyAccessExpression)) { + const specialPropertyAssignmentSymbol = getSpecialPropertyAssignmentSymbolFromEntityName(name); + if (specialPropertyAssignmentSymbol) { + return specialPropertyAssignmentSymbol; + } + } + } + + 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 (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 (isInNameOfExpressionWithTypeArguments(name)) { + let meaning = SymbolFlags.None; + if (name.parent.kind === SyntaxKind.ExpressionWithTypeArguments) { + // An 'ExpressionWithTypeArguments' may appear in type space (interface Foo extends Bar), + // value space (return foo), or both(class Foo extends Bar); ensure the meaning matches. + meaning = isPartOfTypeNode(name) ? SymbolFlags.Type : SymbolFlags.Value; + + // In a class 'extends' clause we are also looking for a value. + if (isExpressionWithTypeArgumentsInClassExtendsClause(name.parent)) { + meaning |= SymbolFlags.Value; + } + } + else { + meaning = SymbolFlags.Namespace; + } + + meaning |= SymbolFlags.Alias; + const entityNameSymbol = isEntityNameExpression(name) ? resolveEntityName(name, meaning, /*ignoreErrors*/ true) : undefined; + if (entityNameSymbol) { + return entityNameSymbol; + } + } + + 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; + } + + 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) && isJsxIntrinsicTagName(name)) { + const symbol = getIntrinsicTagSymbol(name.parent as JsxOpeningLikeElement); + return symbol === unknownSymbol ? undefined : symbol; + } + const result = resolveEntityName(name, meaning, /*ignoreErrors*/ true, /*dontResolveAlias*/ true, getHostSignatureFromJSDoc(name)); + if (!result && isJSDoc) { + const container = findAncestor(name, or(isClassLike, isInterfaceDeclaration)); + if (container) { + return resolveJSDocMemberName(name, /*ignoreErrors*/ true, getSymbolOfDeclaration(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) { + links.resolvedSymbol = getApplicableIndexSymbol(checkExpressionCached(name.expression), getLiteralTypeFromPropertyName(name.name)); + } + } + else { + checkQualifiedName(name, CheckMode.Normal); + } + if (!links.resolvedSymbol && isJSDoc && isQualifiedName(name)) { + return resolveJSDocMemberName(name); + } + return links.resolvedSymbol; + } + else if (isJSDocMemberName(name)) { + return resolveJSDocMemberName(name); + } + } + 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); + } + + return undefined; + } + + function getApplicableIndexSymbol(type: Type, keyType: Type) { + const infos = getApplicableIndexInfos(type, keyType); + if (infos.length && (type as ObjectType).members) { + const symbol = getIndexSymbolFromSymbolTable(resolveStructuredTypeMembers(type as ObjectType).members); + if (infos === getIndexInfosOfType(type)) { + return 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(); + } + if (symbolLinks.filteredIndexSymbolCache.has(nodeListId)) { + return symbolLinks.filteredIndexSymbolCache.get(nodeListId)!; + } + else { + const copy = createSymbol(SymbolFlags.Signature, InternalSymbolName.Index); + copy.declarations = mapDefined(infos, i => i.declaration); + copy.parent = type.aliasSymbol ? type.aliasSymbol : type.symbol ? type.symbol : getSymbolAtLocation(copy.declarations[0].parent); + symbolLinks.filteredIndexSymbolCache.set(nodeListId, copy); + return copy; + } + } + } + } + + /** + * 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; + } + } + 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 (isSourceFile(node)) { + return isExternalModule(node) ? 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 (isDeclarationNameOrImportPropertyName(node)) { + // This is a declaration, call getSymbolOfNode + const parentSymbol = getSymbolOfDeclaration(parent as Declaration); + return isImportOrExportSpecifier(node.parent) && node.parent.propertyName === node + ? getImmediateAliasedSymbol(parentSymbol) + : parentSymbol; + } + else if (isLiteralComputedPropertyDeclarationName(node)) { + return getSymbolOfDeclaration(parent.parent as Declaration); + } + + 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 (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; + } + // 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 + + case SyntaxKind.ThisKeyword: + const container = getThisContainer(node, /*includeArrowFunctions*/ false, /*includeClassComputedPropertyName*/ false); + if (isFunctionLike(container)) { + const sig = getSignatureFromDeclaration(container); + if (sig.thisParameter) { + return sig.thisParameter; + } + } + if (isInExpressionContext(node)) { + return checkExpression(node as Expression).symbol; + } + // falls through + + case SyntaxKind.ThisType: + return getTypeFromThisTypeNode(node as ThisExpression | ThisTypeNode).symbol; + + case SyntaxKind.SuperKeyword: + return checkExpression(node as Expression).symbol; + + 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) && isJSDocImportTag(node.parent) && node.parent.moduleSpecifier === node) || + ((isInJSFile(node) && isRequireCall(node.parent, /*requireStringLiteralLikeArgument*/ 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 getSymbolOfDeclaration(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.InstanceOfKeyword: + if (isBinaryExpression(node.parent)) { + const type = getTypeOfExpression(node.parent.right); + const hasInstanceMethodType = getSymbolHasInstanceMethodOfObjectType(type); + return hasInstanceMethodType?.symbol ?? type.symbol; + } + return undefined; + case SyntaxKind.MetaProperty: + return checkExpression(node as Expression).symbol; + case SyntaxKind.JsxNamespacedName: + if (isJSXTagName(node) && isJsxIntrinsicTagName(node)) { + const symbol = getIntrinsicTagSymbol(node.parent as JsxOpeningLikeElement); + return symbol === unknownSymbol ? undefined : symbol; + } + // falls through + + 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 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)) { + const name = node.propertyName || node.name; + return node.parent.parent.moduleSpecifier ? + getExternalModuleMember(node.parent.parent, node) : + name.kind === SyntaxKind.StringLiteral ? undefined : // Skip for invalid syntax like this: export { "x" } + resolveEntityName(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; + } + + const classDecl = tryGetClassImplementingOrExtendingExpressionWithTypeArguments(node); + const classType = classDecl && getDeclaredTypeOfClassOrInterface(getSymbolOfDeclaration(classDecl.class)); + if (isPartOfTypeNode(node)) { + const typeFromTypeNode = getTypeFromTypeNode(node as TypeNode); + return classType ? getTypeWithThisArgument(typeFromTypeNode, classType.thisType) : typeFromTypeNode; + } + + if (isExpressionNode(node)) { + return getRegularTypeOfExpression(node as Expression); + } + + 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 (isTypeDeclaration(node)) { + // In this case, we call getSymbolOfNode instead of getSymbolAtLocation because it is a declaration + const symbol = getSymbolOfDeclaration(node); + return getDeclaredTypeOfSymbol(symbol); + } + + if (isTypeDeclarationName(node)) { + const symbol = getSymbolAtLocation(node); + return symbol ? getDeclaredTypeOfSymbol(symbol) : errorType; + } + + if (isBindingElement(node)) { + return getTypeForVariableLikeDeclaration(node, /*includeOptionality*/ true, CheckMode.Normal) || errorType; + } + + if (isDeclaration(node)) { + // In this case, we call getSymbolOfNode instead of getSymbolAtLocation because it is a declaration + const symbol = getSymbolOfDeclaration(node); + return symbol ? getTypeOfSymbol(symbol) : errorType; + } + + if (isDeclarationNameOrImportPropertyName(node)) { + const symbol = getSymbolAtLocation(node); + if (symbol) { + return getTypeOfSymbol(symbol); + } + return errorType; + } + + if (isBindingPattern(node)) { + return getTypeForVariableLikeDeclaration(node.parent, /*includeOptionality*/ true, CheckMode.Normal) || errorType; + } + + if (isInRightSideOfImportOrExportAssignment(node as Identifier)) { + const symbol = getSymbolAtLocation(node); + if (symbol) { + const declaredType = getDeclaredTypeOfSymbol(symbol); + return !isErrorType(declaredType) ? declaredType : getTypeOfSymbol(symbol); + } + } + + if (isMetaProperty(node.parent) && node.parent.keywordToken === node.kind) { + return checkMetaPropertyKeyword(node.parent); + } + + if (isImportAttributes(node)) { + return getGlobalImportAttributesType(/*reportErrors*/ false); + } + + 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) { + // } + 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); + } + + function getRegularTypeOfExpression(expr: Expression): Type { + if (isRightSideOfQualifiedNameOrPropertyAccess(expr)) { + expr = expr.parent as Expression; + } + return getRegularTypeOfLiteralType(getTypeOfExpression(expr)); + } + + /** + * 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 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."); + } + } + + // 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 typeHasCallOrConstructSignatures(type: Type): boolean { + return getSignaturesOfType(type, SignatureKind.Call).length !== 0 || getSignaturesOfType(type, SignatureKind.Construct).length !== 0; + } + + 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)); + } + else if (symbol.flags & SymbolFlags.Transient) { + const { links: { leftSpread, rightSpread, syntheticOrigin } } = symbol as TransientSymbol; + return leftSpread ? [leftSpread, rightSpread!] + : syntheticOrigin ? [syntheticOrigin] + : singleElementArray(tryGetTarget(symbol)); + } + 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 + + 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 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) && getSymbolOfDeclaration(n) === parentSymbol); + } + } + } + } + + // 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 { + const specifier = getIdentifierGeneratedImportReference(nodeIn); + if (specifier) { + return specifier; + } + 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); + } + } + + 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)) { + if (resolveName(container.parent, symbol.escapedName, SymbolFlags.Value, /*nameNotFoundMessage*/ undefined, /*isUse*/ false)) { + // redeclaration - always should be renamed + links.isDeclarationWithCollidingName = true; + } + else if (hasNodeCheckFlag(symbol.valueDeclaration, 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 = hasNodeCheckFlag(symbol.valueDeclaration, 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!; + } + 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; + } + + // 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 = getSymbolOfDeclaration(node); + if (symbol) { + return isSymbolOfDeclarationWithCollidingName(symbol); + } + } + + return false; + } + + function isValueAliasDeclaration(node: Node): boolean { + Debug.assert(canCollectSymbolAliasAccessabilityData); + switch (node.kind) { + case SyntaxKind.ImportEqualsDeclaration: + return isAliasResolvedToValue(getSymbolOfDeclaration(node as ImportEqualsDeclaration)); + case SyntaxKind.ImportClause: + case SyntaxKind.NamespaceImport: + case SyntaxKind.ImportSpecifier: + case SyntaxKind.ExportSpecifier: + const symbol = getSymbolOfDeclaration(node as ImportClause | NamespaceImport | ImportSpecifier | ExportSpecifier); + return !!symbol && isAliasResolvedToValue(symbol, /*excludeTypeOnlyValues*/ true); + 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(getSymbolOfDeclaration(node as ExportAssignment), /*excludeTypeOnlyValues*/ true) : + 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(getSymbolOfDeclaration(node)); + return isValue && node.moduleReference && !nodeIsMissing(node.moduleReference); + } + + function isAliasResolvedToValue(symbol: Symbol | undefined, excludeTypeOnlyValues?: boolean): boolean { + if (!symbol) { + return false; + } + const container = getSourceFileOfNode(symbol.valueDeclaration); + const fileSymbol = container && getSymbolOfDeclaration(container); + // Ensures cjs export assignment is setup, since this symbol may point at, and merge with, the file itself. + // If we don't, the merge may not have yet occured, and the flags check below will be missing flags that + // are added as a result of the merge. + void resolveExternalModuleSymbol(fileSymbol); + const target = getExportSymbolOfValueSymbolIfExported(resolveAlias(symbol)); + if (target === unknownSymbol) { + return !excludeTypeOnlyValues || !getTypeOnlyAliasDeclaration(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 !!(getSymbolFlags(symbol, excludeTypeOnlyValues, /*excludeLocalMeanings*/ true) & SymbolFlags.Value) && + (shouldPreserveConstEnums(compilerOptions) || !isConstEnumOrConstEnumOnlyModule(target)); + } + + function isConstEnumOrConstEnumOnlyModule(s: Symbol): boolean { + return isConstEnumSymbol(s) || !!s.constEnumOnlyModule; + } + + function isReferencedAliasDeclaration(node: Node, checkChildren?: boolean): boolean { + Debug.assert(canCollectSymbolAliasAccessabilityData); + if (isAliasSymbolDeclaration(node)) { + const symbol = getSymbolOfDeclaration(node as Declaration); + const links = symbol && getSymbolLinks(symbol); + if (links?.referenced) { + return true; + } + const target = getSymbolLinks(symbol).aliasTarget; + if ( + target && getEffectiveModifierFlags(node) & ModifierFlags.Export && + getSymbolFlags(target) & SymbolFlags.Value && + (shouldPreserveConstEnums(compilerOptions) || !isConstEnumOrConstEnumOnlyModule(target)) + ) { + // An `export import ... =` of a value symbol is always considered referenced + return true; + } + } + + if (checkChildren) { + return !!forEachChild(node, node => isReferencedAliasDeclaration(node, checkChildren)); + } + 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 = getSymbolOfDeclaration(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 declaredParameterTypeContainsUndefined(parameter: ParameterDeclaration | JSDocParameterTag) { + const typeNode = getNonlocalEffectiveTypeAnnotationNode(parameter); + if (!typeNode) return false; + const type = getTypeFromTypeNode(typeNode); + return containsUndefinedType(type); + } + function requiresAddingImplicitUndefined(parameter: ParameterDeclaration | JSDocParameterTag) { + return (isRequiredInitializedParameter(parameter) || isOptionalUninitializedParameterProperty(parameter)) && !declaredParameterTypeContainsUndefined(parameter); + } + + function isRequiredInitializedParameter(parameter: ParameterDeclaration | JSDocParameterTag): boolean { + return !!strictNullChecks && + !isOptionalParameter(parameter) && + !isJSDocParameterTag(parameter) && + !!parameter.initializer && + !hasSyntacticModifier(parameter, ModifierFlags.ParameterPropertyModifier); + } + + function isOptionalUninitializedParameterProperty(parameter: ParameterDeclaration | JSDocParameterTag) { + return strictNullChecks && + isOptionalParameter(parameter) && + (isJSDocParameterTag(parameter) || !parameter.initializer) && + hasSyntacticModifier(parameter, ModifierFlags.ParameterPropertyModifier); + } + + function isExpandoFunctionDeclaration(node: Declaration): boolean { + const declaration = getParseTreeNode(node, (n): n is FunctionDeclaration | VariableDeclaration => isFunctionDeclaration(n) || isVariableDeclaration(n)); + if (!declaration) { + return false; + } + let symbol: Symbol | undefined; + if (isVariableDeclaration(declaration)) { + if (declaration.type || (!isInJSFile(declaration) && !isVarConstLike(declaration))) { + return false; + } + const initializer = getDeclaredExpandoInitializer(declaration); + if (!initializer || !canHaveSymbol(initializer)) { + return false; + } + symbol = getSymbolOfDeclaration(initializer); + } + else { + symbol = getSymbolOfDeclaration(declaration); + } + if (!symbol || !(symbol.flags & SymbolFlags.Function | SymbolFlags.Variable)) { + return false; + } + return !!forEachEntry(getExportsOfSymbol(symbol), p => p.flags & SymbolFlags.Value && isExpandoPropertyDeclaration(p.valueDeclaration)); + } + + function getPropertiesOfContainerFunction(node: Declaration): Symbol[] { + const declaration = getParseTreeNode(node, isFunctionDeclaration); + if (!declaration) { + return emptyArray; + } + const symbol = getSymbolOfDeclaration(declaration); + return symbol && getPropertiesOfType(getTypeOfSymbol(symbol)) || emptyArray; + } + + function getNodeCheckFlags(node: Node): NodeCheckFlags { + const nodeId = node.id || 0; + if (nodeId < 0 || nodeId >= nodeLinks.length) return 0; + return nodeLinks[nodeId]?.flags || 0; + } + + function hasNodeCheckFlag(node: Node, flag: LazyNodeCheckFlags) { + calculateNodeCheckFlagWorker(node, flag); + return !!(getNodeCheckFlags(node) & flag); + } + + function calculateNodeCheckFlagWorker(node: Node, flag: LazyNodeCheckFlags) { + if (!compilerOptions.noCheck && canIncludeBindAndCheckDiagnostics(getSourceFileOfNode(node), compilerOptions)) { + // Unless noCheck is passed, assume calculation of node check flags has been done eagerly. + // This saves needing to mark up where in the eager traversal certain results are "done", + // just to reconcile the eager and lazy results. This wouldn't be hard if an eager typecheck + // was actually an in-order traversal, but it isn't - some nodes are deferred, and so don't + // have these node check flags calculated until that deferral is completed. As an example, + // in concept, we could consider a class that we've called `checkSourceElement` on as having had + // these flags calculated, but since the method bodies are deferred, we actually can't set the + // flags as having been calculated until that deferral is completed. + // The downside to this either/or approach to eager or lazy calculation is that we can't combine + // a partial eager traversal and lazy calculation for the missing bits, and there's a bit of + // overlap in functionality. This isn't a huge loss for any usecases today, but would be nice + // alongside language service partial file checking and editor-triggered emit. + return; + } + const links = getNodeLinks(node); + if (links.calculatedFlags & flag) { + return; + } + // This is only the set of `NodeCheckFlags` our emitter actually looks for, not all of them + switch (flag) { + case NodeCheckFlags.SuperInstance: + case NodeCheckFlags.SuperStatic: + return checkSingleSuperExpression(node); + case NodeCheckFlags.MethodWithSuperPropertyAccessInAsync: + case NodeCheckFlags.MethodWithSuperPropertyAssignmentInAsync: + case NodeCheckFlags.ContainsSuperPropertyInStaticInitializer: + return checkChildSuperExpressions(node); + case NodeCheckFlags.CaptureArguments: + case NodeCheckFlags.ContainsCapturedBlockScopeBinding: + case NodeCheckFlags.NeedsLoopOutParameter: + case NodeCheckFlags.ContainsConstructorReference: + return checkChildIdentifiers(node); + case NodeCheckFlags.ConstructorReference: + return checkSingleIdentifier(node); + case NodeCheckFlags.LoopWithCapturedBlockScopedBinding: + case NodeCheckFlags.BlockScopedBindingInLoop: + case NodeCheckFlags.CapturedBlockScopedBinding: + return checkContainingBlockScopeBindingUses(node); + default: + return Debug.assertNever(flag, `Unhandled node check flag calculation: ${Debug.formatNodeCheckFlags(flag)}`); + } + + function forEachNodeRecursively(root: Node, cb: (node: Node, parent: Node) => T | "skip" | undefined): T | undefined { + const rootResult = cb(root, root.parent); + if (rootResult === "skip") return undefined; + if (rootResult) return rootResult; + return forEachChildRecursively(root, cb); + } + + function checkSuperExpressions(node: Node) { + const links = getNodeLinks(node); + if (links.calculatedFlags & flag) return "skip"; + links.calculatedFlags |= NodeCheckFlags.MethodWithSuperPropertyAccessInAsync | NodeCheckFlags.MethodWithSuperPropertyAssignmentInAsync | NodeCheckFlags.ContainsSuperPropertyInStaticInitializer; + checkSingleSuperExpression(node); + return undefined; + } + + function checkChildSuperExpressions(node: Node) { + forEachNodeRecursively(node, checkSuperExpressions); + } + + function checkSingleSuperExpression(node: Node) { + const nodeLinks = getNodeLinks(node); // This is called on sub-nodes of the original input, make sure we set `calculatedFlags` on the correct node + nodeLinks.calculatedFlags |= NodeCheckFlags.SuperInstance | NodeCheckFlags.SuperStatic; // Yes, we set this on non-applicable nodes, so we can entirely skip the traversal on future calls + if (node.kind === SyntaxKind.SuperKeyword) { + checkSuperExpression(node); + } + } + + function checkIdentifiers(node: Node) { + const links = getNodeLinks(node); + if (links.calculatedFlags & flag) return "skip"; + links.calculatedFlags |= NodeCheckFlags.CaptureArguments | NodeCheckFlags.ContainsCapturedBlockScopeBinding | NodeCheckFlags.NeedsLoopOutParameter | NodeCheckFlags.ContainsConstructorReference; + checkSingleIdentifier(node); + return undefined; + } + + function checkChildIdentifiers(node: Node) { + forEachNodeRecursively(node, checkIdentifiers); + } + + function checkSingleIdentifier(node: Node) { + const nodeLinks = getNodeLinks(node); + nodeLinks.calculatedFlags |= NodeCheckFlags.ConstructorReference | NodeCheckFlags.CapturedBlockScopedBinding | NodeCheckFlags.BlockScopedBindingInLoop; + if (isIdentifier(node) && isExpressionNode(node) && !(isPropertyAccessExpression(node.parent) && node.parent.name === node)) { + const s = getSymbolAtLocation(node, /*ignoreErrors*/ true); + if (s && s !== unknownSymbol) { + checkIdentifierCalculateNodeCheckFlags(node, s); + } + } + } + + function checkBlockScopeBindings(node: Node) { + const links = getNodeLinks(node); + if (links.calculatedFlags & flag) return "skip"; + links.calculatedFlags |= NodeCheckFlags.LoopWithCapturedBlockScopedBinding | NodeCheckFlags.BlockScopedBindingInLoop | NodeCheckFlags.CapturedBlockScopedBinding; + checkSingleBlockScopeBinding(node); + return undefined; + } + + function checkContainingBlockScopeBindingUses(node: Node) { + const scope = getEnclosingBlockScopeContainer(isDeclarationName(node) ? node.parent : node); + forEachNodeRecursively(scope, checkBlockScopeBindings); + } + + function checkSingleBlockScopeBinding(node: Node) { + checkSingleIdentifier(node); + if (isComputedPropertyName(node)) { + checkComputedPropertyName(node); + } + if (isPrivateIdentifier(node) && isClassElement(node.parent)) { + setNodeLinksForPrivateIdentifierScope(node.parent as PropertyDeclaration | PropertySignature | MethodDeclaration | MethodSignature | AccessorDeclaration); + } + } + } + + function getEnumMemberValue(node: EnumMember): EvaluatorResult { + computeEnumMemberValues(node.parent); + return getNodeLinks(node).enumMemberValue ?? evaluatorResult(/*value*/ undefined); + } + + 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 getConstantValue(node: EnumMember | AccessExpression): string | number | undefined { + if (node.kind === SyntaxKind.EnumMember) { + return getEnumMemberValue(node).value; + } + + if (!getNodeLinks(node).resolvedSymbol) { + void checkExpressionCached(node); // ensure cached resolved symbol is set + } + const symbol = getNodeLinks(node).resolvedSymbol || (isEntityNameExpression(node) ? resolveEntityName(node, SymbolFlags.Value, /*ignoreErrors*/ true) : undefined); + 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).value; + } + } + + return undefined; + } + + function isFunctionType(type: Type): boolean { + return !!(type.flags & TypeFlags.Object) && getSignaturesOfType(type, SignatureKind.Call).length > 0; + } + + 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; + + if (location) { + location = getParseTreeNode(location); + if (!location) return TypeReferenceSerializationKind.Unknown; + } + + // 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 resolvedValueSymbol = valueSymbol && valueSymbol.flags & SymbolFlags.Alias ? resolveAlias(valueSymbol) : valueSymbol; + isTypeOnly ||= !!(valueSymbol && getTypeOnlyAliasDeclaration(valueSymbol, SymbolFlags.Value)); + + // 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*/ true, location); + const resolvedTypeSymbol = typeSymbol && typeSymbol.flags & SymbolFlags.Alias ? resolveAlias(typeSymbol) : typeSymbol; + + // In case the value symbol can't be resolved (e.g. because of missing declarations), use type symbol for reachability check. + if (!valueSymbol) { + isTypeOnly ||= !!(typeSymbol && getTypeOnlyAliasDeclaration(typeSymbol, SymbolFlags.Type)); + } + + if (resolvedValueSymbol && resolvedValueSymbol === resolvedTypeSymbol) { + const globalPromiseSymbol = getGlobalPromiseConstructorSymbol(/*reportErrors*/ false); + if (globalPromiseSymbol && resolvedValueSymbol === globalPromiseSymbol) { + return TypeReferenceSerializationKind.Promise; + } + + const constructorType = getTypeOfSymbol(resolvedValueSymbol); + if (constructorType && isConstructorType(constructorType)) { + return isTypeOnly ? TypeReferenceSerializationKind.TypeWithCallSignature : TypeReferenceSerializationKind.TypeWithConstructSignatureAndValue; + } + } + + // We might not be able to resolve type symbol so use unknown type in that case (eg error case) + if (!resolvedTypeSymbol) { + return isTypeOnly ? TypeReferenceSerializationKind.ObjectType : TypeReferenceSerializationKind.Unknown; + } + const type = getDeclaredTypeOfSymbol(resolvedTypeSymbol); + 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 createTypeOfDeclaration(declarationIn: AccessorDeclaration | VariableLikeDeclaration | PropertyAccessExpression, enclosingDeclaration: Node, flags: NodeBuilderFlags, tracker: SymbolTracker) { + 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 = getSymbolOfDeclaration(declaration); + const type = symbol && !(symbol.flags & (SymbolFlags.TypeLiteral | SymbolFlags.Signature)) + ? getWidenedLiteralType(getTypeOfSymbol(symbol)) + : errorType; + + return nodeBuilder.serializeTypeForDeclaration(declaration, type, symbol, enclosingDeclaration, flags | NodeBuilderFlags.MultilineObjectLiterals, tracker); + } + + type DeclarationWithPotentialInnerNodeReuse = + | SignatureDeclaration + | JSDocSignature + | AccessorDeclaration + | VariableLikeDeclaration + | PropertyAccessExpression + | ExportAssignment; + + function isDeclarationWithPossibleInnerTypeNodeReuse(declaration: Declaration): declaration is DeclarationWithPotentialInnerNodeReuse { + return isFunctionLike(declaration) || isExportAssignment(declaration) || isVariableLike(declaration); + } + + function getAllAccessorDeclarationsForDeclaration(accessor: AccessorDeclaration): AllAccessorDeclarations { + accessor = getParseTreeNode(accessor, isGetOrSetAccessorDeclaration)!; // TODO: GH#18217 + const otherKind = accessor.kind === SyntaxKind.SetAccessor ? SyntaxKind.GetAccessor : SyntaxKind.SetAccessor; + const otherAccessor = getDeclarationOfKind(getSymbolOfDeclaration(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, + }; + } + + function getPossibleTypeNodeReuseExpression(declaration: DeclarationWithPotentialInnerNodeReuse) { + return isFunctionLike(declaration) && !isSetAccessor(declaration) + ? getSingleReturnExpression(declaration) + : isExportAssignment(declaration) + ? declaration.expression + : !!(declaration as HasInitializer).initializer + ? (declaration as HasInitializer & typeof declaration).initializer + : isParameter(declaration) && isSetAccessor(declaration.parent) + ? getSingleReturnExpression(getAllAccessorDeclarationsForDeclaration(declaration.parent).getAccessor) + : undefined; + } + + function getSingleReturnExpression(declaration: SignatureDeclaration | undefined): Expression | undefined { + let candidateExpr: Expression | undefined; + if (declaration && !nodeIsMissing((declaration as FunctionLikeDeclaration).body)) { + if (getFunctionFlags(declaration) & FunctionFlags.AsyncGenerator) return undefined; + const body = (declaration as FunctionLikeDeclaration).body; + if (body && isBlock(body)) { + forEachReturnStatement(body, s => { + if (!candidateExpr) { + candidateExpr = s.expression; + } + else { + candidateExpr = undefined; + return true; + } + }); + } + else { + candidateExpr = body; + } + } + return candidateExpr; + } + + function createReturnTypeOfSignatureDeclaration(signatureDeclarationIn: SignatureDeclaration, enclosingDeclaration: Node, flags: NodeBuilderFlags, tracker: SymbolTracker) { + const signatureDeclaration = getParseTreeNode(signatureDeclarationIn, isFunctionLike); + if (!signatureDeclaration) { + return factory.createToken(SyntaxKind.AnyKeyword) as KeywordTypeNode; + } + return nodeBuilder.serializeReturnTypeForSignature(getSignatureFromDeclaration(signatureDeclaration), 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 type = getWidenedType(getRegularTypeOfExpression(expr)); + return nodeBuilder.expressionOrTypeToTypeNode(expr, type, /*addUndefined*/ undefined, enclosingDeclaration, flags | NodeBuilderFlags.MultilineObjectLiterals, tracker); + } + + function hasGlobalName(name: string): boolean { + return globals.has(escapeLeadingUnderscores(name)); + } + + function getReferencedValueSymbol(reference: Identifier, startInDeclarationContainer?: boolean): Symbol | undefined { + const resolvedSymbol = getNodeLinks(reference).resolvedSymbol; + if (resolvedSymbol) { + return resolvedSymbol; + } + + 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 resolveName(location, reference.escapedText, SymbolFlags.Value | SymbolFlags.ExportValue | SymbolFlags.Alias, /*nameNotFoundMessage*/ undefined, /*isUse*/ true); + } + + /** + * 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, + /*nameNotFoundMessage*/ undefined, + /*isUse*/ true, + /*excludeGlobals*/ 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; + } + } + } + + return undefined; + } + + function getReferencedValueDeclarations(referenceIn: Identifier): Declaration[] | undefined { + if (!isGeneratedIdentifier(referenceIn)) { + const reference = getParseTreeNode(referenceIn, isIdentifier); + if (reference) { + const symbol = getReferencedValueSymbol(reference); + if (symbol) { + return filter(getExportSymbolOfValueSymbolIfExported(symbol).declarations, declaration => { + switch (declaration.kind) { + case SyntaxKind.VariableDeclaration: + case SyntaxKind.Parameter: + case SyntaxKind.BindingElement: + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.PropertyAssignment: + case SyntaxKind.ShorthandPropertyAssignment: + case SyntaxKind.EnumMember: + case SyntaxKind.ObjectLiteralExpression: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.ModuleDeclaration: + return true; + } + return false; + }); + } + } + } + + return undefined; + } + + function isLiteralConstDeclaration(node: VariableDeclaration | PropertyDeclaration | PropertySignature | ParameterDeclaration): boolean { + if (isDeclarationReadonly(node) || isVariableDeclaration(node) && isVarConstLike(node)) { + return isFreshLiteralType(getTypeOfSymbol(getSymbolOfDeclaration(node))); + } + return false; + } + + function literalTypeToNode(type: FreshableType, enclosing: Node, tracker: SymbolTracker): Expression { + const enumResult = type.flags & TypeFlags.EnumLike ? 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 === "string" ? factory.createStringLiteral(literalValue) : + literalValue < 0 ? factory.createPrefixUnaryExpression(SyntaxKind.MinusToken, factory.createNumericLiteral(-literalValue)) : + factory.createNumericLiteral(literalValue); + } + + function createLiteralConstValue(node: VariableDeclaration | PropertyDeclaration | PropertySignature | ParameterDeclaration, tracker: SymbolTracker) { + const type = getTypeOfSymbol(getSymbolOfDeclaration(node)); + return literalTypeToNode(type as FreshableType, node, tracker); + } + + 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; + } + } + } + + if (compilerOptions.jsxFragmentFactory) { + return parseIsolatedEntityName(compilerOptions.jsxFragmentFactory, languageVersion); + } + } + + function getNonlocalEffectiveTypeAnnotationNode(node: Node) { + const direct = getEffectiveTypeAnnotationNode(node); + if (direct) { + return direct; + } + if (node.kind === SyntaxKind.Parameter && node.parent.kind === SyntaxKind.SetAccessor) { + const other = getAllAccessorDeclarationsForDeclaration(node.parent as SetAccessorDeclaration).getAccessor; + if (other) { + return getEffectiveReturnTypeNode(other); + } + } + return undefined; + } + + function getNonlocalEffectiveReturnTypeAnnotationNode(node: SignatureDeclaration | JSDocSignature) { + const direct = getEffectiveReturnTypeNode(node); + if (direct) { + return direct; + } + if (node.kind === SyntaxKind.GetAccessor) { + const other = getAllAccessorDeclarationsForDeclaration(node).setAccessor; + if (other) { + const param = getSetAccessorValueParameter(other); + if (param) { + return getEffectiveTypeAnnotationNode(param); + } + } + } + return undefined; + } + + function createResolver(): EmitResolver { + return { + getReferencedExportContainer, + getReferencedImportDeclaration, + getReferencedDeclarationWithCollidingName, + isDeclarationWithCollidingName, + isValueAliasDeclaration: nodeIn => { + const node = getParseTreeNode(nodeIn); + // Synthesized nodes are always treated like values. + return node && canCollectSymbolAliasAccessabilityData ? isValueAliasDeclaration(node) : true; + }, + hasGlobalName, + isReferencedAliasDeclaration: (nodeIn, checkChildren?) => { + const node = getParseTreeNode(nodeIn); + // Synthesized nodes are always treated as referenced. + return node && canCollectSymbolAliasAccessabilityData ? isReferencedAliasDeclaration(node, checkChildren) : true; + }, + hasNodeCheckFlag: (nodeIn, flag) => { + const node = getParseTreeNode(nodeIn); + if (!node) return false; + return hasNodeCheckFlag(node, flag); + }, + isTopLevelValueImportEqualsWithEntityName, + isDeclarationVisible, + isImplementationOfOverload, + requiresAddingImplicitUndefined, + isExpandoFunctionDeclaration, + getPropertiesOfContainerFunction, + createTypeOfDeclaration, + createReturnTypeOfSignatureDeclaration, + createTypeOfExpression, + createLiteralConstValue, + isSymbolAccessible, + isEntityNameVisible, + getConstantValue: nodeIn => { + const node = getParseTreeNode(nodeIn, canHaveConstantValue); + return node ? getConstantValue(node) : undefined; + }, + getEnumMemberValue: nodeIn => { + const node = getParseTreeNode(nodeIn, isEnumMember); + return node ? getEnumMemberValue(node) : undefined; + }, + collectLinkedAliases, + markLinkedReferences: nodeIn => { + const node = getParseTreeNode(nodeIn); + return node && markLinkedReferences(node, ReferenceHint.Unspecified); + }, + getReferencedValueDeclaration, + getReferencedValueDeclarations, + getTypeReferenceSerializationKind, + isOptionalParameter, + isArgumentsLocalBinding, + getExternalModuleFileFromDeclaration: nodeIn => { + const node = getParseTreeNode(nodeIn, hasPossibleExternalModuleReference); + return node && getExternalModuleFileFromDeclaration(node); + }, + isLiteralConstDeclaration, + isLateBound: (nodeIn: Declaration): nodeIn is LateBoundDeclaration => { + const node = getParseTreeNode(nodeIn, isDeclaration); + const symbol = node && getSymbolOfDeclaration(node); + return !!(symbol && getCheckFlags(symbol) & CheckFlags.Late); + }, + getJsxFactoryEntity, + getJsxFragmentFactoryEntity, + 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) => { + const n = getParseTreeNode(node) as SourceFile; + Debug.assert(n && n.kind === SyntaxKind.SourceFile, "Non-sourcefile node passed into getDeclarationsForSourceFile"); + const sym = getSymbolOfDeclaration(node); + if (!sym) { + return !node.locals ? [] : nodeBuilder.symbolTableToDeclarationStatements(node.locals, node, flags, tracker); + } + resolveExternalModuleSymbol(sym); // ensures cjs export assignment is setup + return !sym.exports ? [] : nodeBuilder.symbolTableToDeclarationStatements(sym.exports, node, flags, tracker); + }, + isImportRequiredByAugmentation, + isDefinitelyReferenceToGlobalSymbolObject, + }; + + 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 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; + } + 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(); + + // 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`. + 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); + } + }); + } + } + + // 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); + } + } + } + + addUndefinedToGlobalsOrErrorOnRedeclaration(); + + 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); + + 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(/*symbol*/ 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; + + 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 (compilerOptions.importHelpers) { + const sourceFile = getSourceFileOfNode(location); + if (isEffectiveExternalModule(sourceFile, compilerOptions) && !(location.flags & NodeFlags.Ambient)) { + const helpersModule = resolveHelpersModule(sourceFile, location); + if (helpersModule !== unknownSymbol) { + const links = getSymbolLinks(helpersModule); + links.requestedExternalEmitHelpers ??= 0 as ExternalEmitHelpers; + if ((links.requestedExternalEmitHelpers & helpers) !== helpers) { + const uncheckedHelpers = helpers & ~links.requestedExternalEmitHelpers; + for (let helper = ExternalEmitHelpers.FirstEmitHelper; helper <= ExternalEmitHelpers.LastEmitHelper; helper <<= 1) { + if (uncheckedHelpers & helper) { + for (const name of getHelperNames(helper)) { + const symbol = resolveSymbol(getSymbol(getExportsOfModule(helpersModule), 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.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); + } + } + } + } + } + } + links.requestedExternalEmitHelpers |= helpers; + } + } + } + } + + function getHelperNames(helper: ExternalEmitHelpers) { + switch (helper) { + case ExternalEmitHelpers.Extends: + return ["__extends"]; + case ExternalEmitHelpers.Assign: + return ["__assign"]; + case ExternalEmitHelpers.Rest: + return ["__rest"]; + case ExternalEmitHelpers.Decorate: + return legacyDecorators ? ["__decorate"] : ["__esDecorate", "__runInitializers"]; + 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.SetFunctionName: + return ["__setFunctionName"]; + case ExternalEmitHelpers.PropKey: + return ["__propKey"]; + case ExternalEmitHelpers.AddDisposableResourceAndDisposeResources: + return ["__addDisposableResource", "__disposeResources"]; + default: + return Debug.fail("Unrecognized helper"); + } + } + + function resolveHelpersModule(file: SourceFile, errorNode: Node) { + const links = getNodeLinks(file); + if (!links.externalHelpersModule) { + links.externalHelpersModule = resolveExternalModule(getImportHelpersImportSpecifier(file), externalHelpersModuleNameText, Diagnostics.This_syntax_requires_an_imported_helper_but_module_0_cannot_be_found, errorNode) || unknownSymbol; + } + return links.externalHelpersModule; + } + + // GRAMMAR CHECKING + + function checkGrammarModifiers(node: HasModifiers | HasDecorators | HasIllegalModifiers | HasIllegalDecorators): boolean { + const quickResult = reportObviousDecoratorErrors(node) || reportObviousModifierErrors(node); + if (quickResult !== undefined) { + return quickResult; + } + + if (isParameter(node) && parameterIsThisKeyword(node)) { + return grammarErrorOnFirstToken(node, Diagnostics.Neither_decorators_nor_modifiers_may_be_applied_to_this_parameters); + } + + const blockScopeKind = isVariableStatement(node) ? node.declarationList.flags & NodeFlags.BlockScoped : NodeFlags.None; + let lastStatic: Node | undefined, lastDeclare: Node | undefined, lastAsync: Node | undefined, lastOverride: Node | undefined, firstDecorator: Decorator | undefined; + let flags = ModifierFlags.None; + let sawExportBeforeDecorators = false; + // We parse decorators and modifiers in four contiguous chunks: + // [...leadingDecorators, ...leadingModifiers, ...trailingDecorators, ...trailingModifiers]. It is an error to + // have both leading and trailing decorators. + let hasLeadingDecorators = false; + for (const modifier of (node as HasModifiers).modifiers!) { + if (isDecorator(modifier)) { + if (!nodeCanBeDecorated(legacyDecorators, 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); + } + } + else if (legacyDecorators && (node.kind === SyntaxKind.GetAccessor || node.kind === SyntaxKind.SetAccessor)) { + const accessors = getAllAccessorDeclarationsForDeclaration(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 we've seen any modifiers aside from `export`, `default`, or another decorator, then this is an invalid position + if (flags & ~(ModifierFlags.ExportDefault | ModifierFlags.Decorator)) { + return grammarErrorOnNode(modifier, Diagnostics.Decorators_are_not_valid_here); + } + + // if we've already seen leading decorators and leading modifiers, then trailing decorators are an invalid position + if (hasLeadingDecorators && flags & ModifierFlags.Modifier) { + Debug.assertIsDefined(firstDecorator); + const sourceFile = getSourceFileOfNode(modifier); + if (!hasParseDiagnostics(sourceFile)) { + addRelatedInfo( + error(modifier, Diagnostics.Decorators_may_not_appear_after_export_or_export_default_if_they_also_appear_before_export), + createDiagnosticForNode(firstDecorator, Diagnostics.Decorator_used_before_export_here), + ); + return true; + } + return false; + } + + flags |= ModifierFlags.Decorator; + + // if we have not yet seen a modifier, then these are leading decorators + if (!(flags & ModifierFlags.Modifier)) { + hasLeadingDecorators = true; + } + else if (flags & ModifierFlags.Export) { + sawExportBeforeDecorators = true; + } + + firstDecorator ??= modifier; + } + else { + 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)); + } + 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)); + } + } + if (modifier.kind !== SyntaxKind.InKeyword && modifier.kind !== SyntaxKind.OutKeyword && modifier.kind !== SyntaxKind.ConstKeyword) { + if (node.kind === SyntaxKind.TypeParameter) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_type_parameter, tokenToString(modifier.kind)); + } + } + switch (modifier.kind) { + case SyntaxKind.ConstKeyword: { + if (node.kind !== SyntaxKind.EnumDeclaration && node.kind !== SyntaxKind.TypeParameter) { + return grammarErrorOnNode(node, Diagnostics.A_class_member_cannot_have_the_0_keyword, tokenToString(SyntaxKind.ConstKeyword)); + } + const parent = (isJSDocTemplateTag(node.parent) && getEffectiveJSDocHost(node.parent)) || node.parent; + if ( + node.kind === SyntaxKind.TypeParameter && !(isFunctionLikeDeclaration(parent) || isClassLike(parent) || isFunctionTypeNode(parent) || + isConstructorTypeNode(parent) || isCallSignatureDeclaration(parent) || isConstructSignatureDeclaration(parent) || isMethodSignature(parent)) + ) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_can_only_appear_on_a_type_parameter_of_a_function_method_or_class, 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"); + } + 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; + + 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.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); + } + + flags |= ModifierFlags.Accessor; + 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); + } + else if (flags & ModifierFlags.Accessor) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "readonly", "accessor"); + } + flags |= ModifierFlags.Readonly; + break; + + case SyntaxKind.ExportKeyword: + if ( + compilerOptions.verbatimModuleSyntax && + !(node.flags & NodeFlags.Ambient) && + node.kind !== SyntaxKind.TypeAliasDeclaration && + node.kind !== SyntaxKind.InterfaceDeclaration && + // ModuleDeclaration needs to be checked that it is uninstantiated later + node.kind !== SyntaxKind.ModuleDeclaration && + node.parent.kind === SyntaxKind.SourceFile && + (moduleKind === ModuleKind.CommonJS || getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS) + ) { + return grammarErrorOnNode(modifier, Diagnostics.A_top_level_export_modifier_cannot_be_used_on_value_declarations_in_a_CommonJS_module_when_verbatimModuleSyntax_is_enabled); + } + 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"); + } + else if (blockScopeKind === NodeFlags.Using) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_using_declaration, "export"); + } + else if (blockScopeKind === NodeFlags.AwaitUsing) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_an_await_using_declaration, "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 (blockScopeKind === NodeFlags.Using) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_using_declaration, "default"); + } + else if (blockScopeKind === NodeFlags.AwaitUsing) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_an_await_using_declaration, "default"); + } + else if (!(flags & ModifierFlags.Export)) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "export", "default"); + } + else if (sawExportBeforeDecorators) { + return grammarErrorOnNode(firstDecorator!, Diagnostics.Decorators_are_not_valid_here); + } + + 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 (blockScopeKind === NodeFlags.Using) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_using_declaration, "declare"); + } + else if (blockScopeKind === NodeFlags.AwaitUsing) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_an_await_using_declaration, "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"); + } + else if (flags & ModifierFlags.Accessor) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "declare", "accessor"); + } + flags |= ModifierFlags.Ambient; + lastDeclare = modifier; + 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))) { + const message = node.kind === SyntaxKind.PropertyDeclaration + ? Diagnostics.Abstract_properties_can_only_appear_within_an_abstract_class + : Diagnostics.Abstract_methods_can_only_appear_within_an_abstract_class; + return grammarErrorOnNode(modifier, message); + } + 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"); + } + + flags |= ModifierFlags.Abstract; + break; + + 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; + + case SyntaxKind.InKeyword: + case SyntaxKind.OutKeyword: { + const inOutFlag = modifier.kind === SyntaxKind.InKeyword ? ModifierFlags.In : ModifierFlags.Out; + const inOutText = modifier.kind === SyntaxKind.InKeyword ? "in" : "out"; + const parent = isJSDocTemplateTag(node.parent) && (getEffectiveJSDocHost(node.parent) || find(getJSDocRoot(node.parent)?.tags, isJSDocTypedefTag)) || node.parent; + if (node.kind !== SyntaxKind.TypeParameter || parent && !(isInterfaceDeclaration(parent) || isClassLike(parent) || isTypeAliasDeclaration(parent) || isJSDocTypedefTag(parent))) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_can_only_appear_on_a_type_parameter_of_a_class_interface_or_type_alias, inOutText); + } + 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; + } + } + } + } + + 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; + } + + /** + * true | false: Early return this value from checkGrammarModifiers. + * undefined: Need to do full checking on the modifiers. + */ + function reportObviousModifierErrors(node: HasModifiers | HasIllegalModifiers): boolean | undefined { + if (!node.modifiers) return false; + + const modifier = findFirstIllegalModifier(node); + return modifier && grammarErrorOnFirstToken(modifier, Diagnostics.Modifiers_cannot_appear_here); + } + + function findFirstModifierExcept(node: HasModifiers, allowedModifier: SyntaxKind): Modifier | undefined { + const modifier = find(node.modifiers, isModifier); + return modifier && modifier.kind !== allowedModifier ? modifier : undefined; + } + + function findFirstIllegalModifier(node: HasModifiers | HasIllegalModifiers): Modifier | undefined { + 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 undefined; + case SyntaxKind.ClassStaticBlockDeclaration: + case SyntaxKind.PropertyAssignment: + case SyntaxKind.ShorthandPropertyAssignment: + case SyntaxKind.NamespaceExportDeclaration: + case SyntaxKind.MissingDeclaration: + return find(node.modifiers, isModifier); + default: + if (node.parent.kind === SyntaxKind.ModuleBlock || node.parent.kind === SyntaxKind.SourceFile) { + return undefined; + } + switch (node.kind) { + case SyntaxKind.FunctionDeclaration: + return findFirstModifierExcept(node, SyntaxKind.AsyncKeyword); + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ConstructorType: + return findFirstModifierExcept(node, SyntaxKind.AbstractKeyword); + case SyntaxKind.ClassExpression: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.TypeAliasDeclaration: + return find(node.modifiers, isModifier); + case SyntaxKind.VariableStatement: + return node.declarationList.flags & NodeFlags.Using ? + findFirstModifierExcept(node, SyntaxKind.AwaitKeyword) : + find(node.modifiers, isModifier); + case SyntaxKind.EnumDeclaration: + return findFirstModifierExcept(node, SyntaxKind.ConstKeyword); + default: + Debug.assertNever(node); + } + } + } + + function reportObviousDecoratorErrors(node: HasModifiers | HasDecorators | HasIllegalModifiers | HasIllegalDecorators) { + const decorator = findFirstIllegalDecorator(node); + return decorator && grammarErrorOnFirstToken(decorator, Diagnostics.Decorators_are_not_valid_here); + } + + function findFirstIllegalDecorator(node: HasModifiers | HasDecorators | HasIllegalModifiers | HasIllegalDecorators): Decorator | undefined { + return canHaveIllegalDecorators(node) ? find(node.modifiers, isDecorator) : undefined; + } + + function checkGrammarAsyncModifier(node: Node, asyncModifier: Node): boolean { + switch (node.kind) { + case SyntaxKind.MethodDeclaration: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + 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 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; + } + + function checkGrammarParameterList(parameters: NodeArray) { + let seenOptionalParameter = false; + const parameterCount = parameters.length; + + 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.questionToken) { + return grammarErrorOnNode(parameter.questionToken, Diagnostics.A_rest_parameter_cannot_be_optional); + } + + if (parameter.initializer) { + return grammarErrorOnNode(parameter.name, Diagnostics.A_rest_parameter_cannot_have_an_initializer); + } + } + else if (hasEffectiveQuestionToken(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 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; + } + } + } + return false; + } + + function checkGrammarFunctionLikeDeclaration(node: FunctionLikeDeclaration | MethodSignature): boolean { + // Prevent cascading error by short-circuit + const file = getSourceFileOfNode(node); + return checkGrammarModifiers(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 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); + } + } + + 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); + } + if (!node.type) { + return grammarErrorOnNode(node, Diagnostics.An_index_signature_must_have_a_type_annotation); + } + return false; + } + + function checkGrammarIndexSignature(node: IndexSignatureDeclaration) { + // Prevent cascading error by short-circuit + return checkGrammarModifiers(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 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 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 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 checkGrammarClassDeclarationHeritageClauses(node: ClassLikeDeclaration) { + let seenExtendsClause = false; + let seenImplementsClause = false; + + if (!checkGrammarModifiers(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 (seenImplementsClause) { + return grammarErrorOnFirstToken(heritageClause, Diagnostics.extends_clause_must_precede_implements_clause); + } + + if (heritageClause.types.length > 1) { + return grammarErrorOnFirstToken(heritageClause.types[1], Diagnostics.Classes_can_only_extend_a_single_class); + } + + seenExtendsClause = true; + } + else { + Debug.assert(heritageClause.token === SyntaxKind.ImplementsKeyword); + if (seenImplementsClause) { + return grammarErrorOnFirstToken(heritageClause, Diagnostics.implements_clause_already_seen); + } + + 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); + } + + seenExtendsClause = true; + } + else { + Debug.assert(heritageClause.token === SyntaxKind.ImplementsKeyword); + return grammarErrorOnFirstToken(heritageClause, Diagnostics.Interface_declaration_cannot_have_implements_clause); + } + + // Grammar checking heritageClause inside class declaration + checkGrammarHeritageClause(heritageClause); + } + } + 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; + } + + 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 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 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>(); + + 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); + } + + 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); + } + + // 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) { + if (isModifier(mod)) { + 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: + Debug.assertNever(prop, "Unexpected syntax kind:" + (prop as Node).kind); + } + + if (!inDestructuring) { + const effectiveName = getEffectivePropertyNameForPropertyNameNode(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)); + } + 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 { + 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>(); + + for (const attr of node.attributes.properties) { + if (attr.kind === SyntaxKind.JsxSpreadAttribute) { + continue; + } + + const { name, initializer } = attr; + const escapedText = getEscapedTextOfJsxAttributeName(name); + if (!seen.get(escapedText)) { + seen.set(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); + } + } + } + + function checkGrammarJsxName(node: JsxTagNameExpression) { + if (isPropertyAccessExpression(node) && isJsxNamespacedName(node.expression)) { + return grammarErrorOnNode(node.expression, Diagnostics.JSX_property_access_expressions_cannot_include_JSX_namespace_names); + } + if (isJsxNamespacedName(node) && getJSXTransformEnabled(compilerOptions) && !isIntrinsicJsxName(node.namespace.escapedText)) { + return grammarErrorOnNode(node, Diagnostics.React_components_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 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: + 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_nodenext_or_preserve_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; + } + } + } + } + + 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 (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 (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); + } + } + } + + 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.ES2015 && isPrivateIdentifier(accessor.name)) { + return grammarErrorOnNode(accessor.name, Diagnostics.Private_identifiers_are_only_available_when_targeting_ECMAScript_2015_and_higher); + } + if (accessor.body === undefined && !hasSyntacticModifier(accessor, ModifierFlags.Abstract)) { + return grammarErrorAtPos(accessor, accessor.end - 1, ";".length, Diagnostics._0_expected, "{"); + } + } + 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); + } + } + if (accessor.typeParameters) { + return grammarErrorOnNode(accessor.name, Diagnostics.An_accessor_cannot_have_type_parameters); + } + 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)); + } + 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); + } + 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.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); + } + } + 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 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 (checkGrammarForGenerator(node)) { + 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 (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); + } + } + + function checkGrammarBreakOrContinueStatement(node: BreakOrContinueStatement): boolean { + let current: Node = node; + while (current) { + if (isFunctionLikeOrClassStaticBlockDeclaration(current)) { + return grammarErrorOnNode(node, Diagnostics.Jump_target_cannot_cross_function_boundary); + } + + 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, /*lookInLabeledStatements*/ true); + + if (isMisplacedContinueLabel) { + return grammarErrorOnNode(node, Diagnostics.A_continue_statement_can_only_jump_to_a_label_of_an_enclosing_iteration_statement); + } + + 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, /*lookInLabeledStatements*/ false) && !node.label) { + // unlabeled break or continue within iteration statement - ok + return false; + } + break; + } + + current = current.parent; + } + + 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); + } + } + + 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.propertyName) { + return grammarErrorOnNode(node.name, Diagnostics.A_rest_element_cannot_have_a_property_name); + } + } + + 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 isStringOrNumberLiteralExpression(expr: Expression) { + return isStringOrNumericLiteralLike(expr) || + expr.kind === SyntaxKind.PrefixUnaryExpression && (expr as PrefixUnaryExpression).operator === SyntaxKind.MinusToken && + (expr as PrefixUnaryExpression).operand.kind === SyntaxKind.NumericLiteral; + } + + 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.EnumLike); + } + } + + 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) && (isVarConstLike(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) { + const nodeFlags = getCombinedNodeFlagsCached(node); + const blockScopeKind = nodeFlags & NodeFlags.BlockScoped; + if (isBindingPattern(node.name)) { + switch (blockScopeKind) { + case NodeFlags.AwaitUsing: + return grammarErrorOnNode(node, Diagnostics._0_declarations_may_not_have_binding_patterns, "await using"); + case NodeFlags.Using: + return grammarErrorOnNode(node, Diagnostics._0_declarations_may_not_have_binding_patterns, "using"); + } + } + + if (node.parent.parent.kind !== SyntaxKind.ForInStatement && node.parent.parent.kind !== SyntaxKind.ForOfStatement) { + if (nodeFlags & 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); + } + switch (blockScopeKind) { + case NodeFlags.AwaitUsing: + return grammarErrorOnNode(node, Diagnostics._0_declarations_must_be_initialized, "await using"); + case NodeFlags.Using: + return grammarErrorOnNode(node, Diagnostics._0_declarations_must_be_initialized, "using"); + case NodeFlags.Const: + return grammarErrorOnNode(node, Diagnostics._0_declarations_must_be_initialized, "const"); + } + } + } + + if (node.exclamationToken && (node.parent.parent.kind !== SyntaxKind.VariableStatement || !node.type || node.initializer || nodeFlags & 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); + } + + // 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 !!blockScopeKind && 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); + } + } + else { + const elements = name.elements; + for (const element of elements) { + if (!isOmittedExpression(element)) { + return checkESModuleMarker(element.name); + } + } + } + return false; + } + + function checkGrammarNameInLetOrConstDeclarations(name: Identifier | BindingPattern): boolean { + if (name.kind === SyntaxKind.Identifier) { + if (name.escapedText === "let") { + 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); + } + } + } + return false; + } + + 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); + } + + const blockScopeFlags = declarationList.flags & NodeFlags.BlockScoped; + if ((blockScopeFlags === NodeFlags.Using || blockScopeFlags === NodeFlags.AwaitUsing) && isForInStatement(declarationList.parent)) { + return grammarErrorOnNode( + declarationList, + blockScopeFlags === NodeFlags.Using ? + Diagnostics.The_left_hand_side_of_a_for_in_statement_cannot_be_a_using_declaration : + Diagnostics.The_left_hand_side_of_a_for_in_statement_cannot_be_an_await_using_declaration, + ); + } + + if (blockScopeFlags === NodeFlags.AwaitUsing) { + return checkAwaitGrammar(declarationList); + } + + 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); + } + + return true; + } + + function checkGrammarForDisallowedBlockScopedVariableStatement(node: VariableStatement) { + if (!allowLetAndConstDeclarations(node.parent)) { + const blockScopeKind = getCombinedNodeFlagsCached(node.declarationList) & NodeFlags.BlockScoped; + if (blockScopeKind) { + const keyword = blockScopeKind === NodeFlags.Let ? "let" : + blockScopeKind === NodeFlags.Const ? "const" : + blockScopeKind === NodeFlags.Using ? "using" : + blockScopeKind === NodeFlags.AwaitUsing ? "await using" : + Debug.fail("Unknown BlockScope flag"); + return grammarErrorOnNode(node, Diagnostics._0_declarations_can_only_be_declared_inside_a_block, keyword); + } + } + } + + 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, unescapeLeadingUnderscores(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, unescapeLeadingUnderscores(node.name.escapedText), tokenToString(node.keywordToken), "meta"); + } + break; + } + } + + function hasParseDiagnostics(sourceFile: SourceFile): boolean { + return sourceFile.parseDiagnostics.length > 0; + } + + function grammarErrorOnFirstToken(node: Node, message: DiagnosticMessage, ...args: DiagnosticArguments): boolean { + const sourceFile = getSourceFileOfNode(node); + if (!hasParseDiagnostics(sourceFile)) { + const span = getSpanOfTokenAtPosition(sourceFile, node.pos); + diagnostics.add(createFileDiagnostic(sourceFile, span.start, span.length, message, ...args)); + return true; + } + return false; + } + + function grammarErrorAtPos(nodeForSourceFile: Node, start: number, length: number, message: DiagnosticMessage, ...args: DiagnosticArguments): boolean { + const sourceFile = getSourceFileOfNode(nodeForSourceFile); + if (!hasParseDiagnostics(sourceFile)) { + diagnostics.add(createFileDiagnostic(sourceFile, start, length, message, ...args)); + return true; + } + return false; + } + + function grammarErrorOnNodeSkippedOn(key: keyof CompilerOptions, node: Node, message: DiagnosticMessage, ...args: DiagnosticArguments): boolean { + const sourceFile = getSourceFileOfNode(node); + if (!hasParseDiagnostics(sourceFile)) { + errorSkippedOn(key, node, message, ...args); + return true; + } + return false; + } + + function grammarErrorOnNode(node: Node, message: DiagnosticMessage, ...args: DiagnosticArguments): boolean { + const sourceFile = getSourceFileOfNode(node); + if (!hasParseDiagnostics(sourceFile)) { + diagnostics.add(createDiagnosticForNode(node, message, ...args)); + 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 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.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 (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; + } + } + 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); + } + } + 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 (node.flags & NodeFlags.Ambient) { + checkAmbientInitializer(node); + } + + 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 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; + } + + return grammarErrorOnFirstToken(node, Diagnostics.Top_level_declarations_in_d_ts_files_must_start_with_either_a_declare_or_export_modifier); + } + + 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; + } + + 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); + } + + // 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; + } + + function checkGrammarNumericLiteral(node: NumericLiteral) { + // Realism (size) checking + // 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).includes("."); + const isScientific = node.numericLiteralFlags & TokenFlags.Scientific; + + // 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; + } + + // 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; + } + + 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 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; + } + + function grammarErrorAfterFirstToken(node: Node, message: DiagnosticMessage, ...args: DiagnosticArguments): boolean { + const sourceFile = getSourceFileOfNode(node); + if (!hasParseDiagnostics(sourceFile)) { + const span = getSpanOfTokenAtPosition(sourceFile, node.pos); + diagnostics.add(createFileDiagnostic(sourceFile, textSpanEnd(span), /*length*/ 0, message, ...args)); + return true; + } + return false; + } + + 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 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 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 checkGrammarImportCallExpression(node: ImportCall): boolean { + if (compilerOptions.verbatimModuleSyntax && moduleKind === ModuleKind.CommonJS) { + return grammarErrorOnNode(node, Diagnostics.ESM_syntax_is_not_allowed_in_a_CommonJS_module_when_verbatimModuleSyntax_is_enabled); + } + + 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 (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); + } + + 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); + + if (nodeArguments.length > 1) { + const importAttributesArgument = nodeArguments[1]; + return grammarErrorOnNode(importAttributesArgument, Diagnostics.Dynamic_imports_only_support_a_second_argument_when_the_module_option_is_set_to_esnext_node16_or_nodenext); + } + } + + if (nodeArguments.length === 0 || nodeArguments.length > 2) { + return grammarErrorOnNode(node, Diagnostics.Dynamic_imports_can_only_accept_a_module_specifier_and_an_optional_set_of_attributes_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; + } + 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 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; + } + } + } + } + } + return bestMatch; + } + + function filterPrimitivesIfContainsNonPrimitive(type: UnionType) { + if (maybeTypeOfKind(type, TypeFlags.NonPrimitive)) { + const result = filterType(type, t => !(t.flags & TypeFlags.Primitive)); + if (!(result.flags & TypeFlags.Never)) { + return result; + } + } + return type; + } + + // Keep this up-to-date with the same logic within `getApparentTypeOfContextualType`, since they should behave similarly + function findMatchingDiscriminantType(source: Type, target: Type, isRelatedTo: (source: Type, target: Type) => Ternary) { + 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) { + const discriminated = discriminateTypeByDiscriminableItems(target as UnionType, map(sourcePropertiesFiltered, p => ([() => getTypeOfSymbol(p), p.escapedName] as [() => Type, __String])), isRelatedTo); + if (discriminated !== target) { + return discriminated; + } + } + } + } + return undefined; + } + + function getEffectivePropertyNameForPropertyNameNode(node: PropertyName) { + const name = getPropertyNameForPropertyNameNode(node); + return name ? name : + isComputedPropertyName(node) ? tryGetNameFromType(getTypeOfExpression(node.expression)) : undefined; + } + + function getCombinedModifierFlagsCached(node: Declaration) { + // we hold onto the last node and result to speed up repeated lookups against the same node. + if (lastGetCombinedModifierFlagsNode === node) { + return lastGetCombinedModifierFlagsResult; + } + + lastGetCombinedModifierFlagsNode = node; + lastGetCombinedModifierFlagsResult = getCombinedModifierFlags(node); + return lastGetCombinedModifierFlagsResult; + } + + function getCombinedNodeFlagsCached(node: Node) { + // we hold onto the last node and result to speed up repeated lookups against the same node. + if (lastGetCombinedNodeFlagsNode === node) { + return lastGetCombinedNodeFlagsResult; + } + lastGetCombinedNodeFlagsNode = node; + lastGetCombinedNodeFlagsResult = getCombinedNodeFlags(node); + return lastGetCombinedNodeFlagsResult; + } + + function isVarConstLike(node: VariableDeclaration | VariableDeclarationList) { + const blockScopeKind = getCombinedNodeFlagsCached(node) & NodeFlags.BlockScoped; + return blockScopeKind === NodeFlags.Const || + blockScopeKind === NodeFlags.Using || + blockScopeKind === NodeFlags.AwaitUsing; + } + + function getJSXRuntimeImportSpecifier(file: SourceFile | undefined, specifierText: string) { + // Synthesized JSX import is either first or after tslib + const jsxImportIndex = compilerOptions.importHelpers ? 1 : 0; + const specifier = file?.imports[jsxImportIndex]; + if (specifier) { + Debug.assert(nodeIsSynthesized(specifier) && specifier.text === specifierText, `Expected sourceFile.imports[${jsxImportIndex}] to be the synthesized JSX runtime import`); + } + return specifier; + } + + function getImportHelpersImportSpecifier(file: SourceFile) { + Debug.assert(compilerOptions.importHelpers, "Expected importHelpers to be enabled"); + const specifier = file.imports[0]; + Debug.assert(specifier && nodeIsSynthesized(specifier) && specifier.text === "tslib", `Expected sourceFile.imports[0] to be the synthesized tslib import`); + return specifier; + } +} + +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; +} + +/** 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) || name.kind === SyntaxKind.StringLiteral; + 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 ElementType = "ElementType" 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"; + } +} + +/** @internal */ +export function signatureHasRestParameter(s: Signature) { + return !!(s.flags & SignatureFlags.HasRestParameter); +} + +/** @internal */ +export function signatureHasLiteralTypes(s: Signature) { + return !!(s.flags & SignatureFlags.HasLiteralTypes); +} + +function createBasicNodeBuilderModuleSpecifierResolutionHost(host: TypeCheckerHost): ModuleSpecifierResolutionHost { + return { + 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, + }; +} + +interface NodeBuilderContext { + enclosingDeclaration: Node | undefined; + /** + * `enclosingFile` is generated from the initial `enclosingDeclaration` and + * is used to ensure text ranges for generated nodes are not set based on nodes from outside + * the original input's containing file. Checking the `enclosingDeclaration` at the time of + * `setTextRange` is not sufficient, as the `enclosingDeclaration` is modified by the node builder + * as it decends into some types as a shortcut to making certain scopes visible, and may be modified + * into a declaration in a different file from the original input `enclosingDeclaration`! + */ + enclosingFile: SourceFile | undefined; + flags: NodeBuilderFlags; + tracker: SymbolTrackerImpl; + + // State + encounteredError: boolean; + reportedDiagnostic: boolean; + trackedSymbols: TrackedSymbol[] | undefined; + visitedTypes: Set | undefined; + symbolDepth: Map | undefined; + inferTypeParameters: TypeParameter[] | undefined; + approximateLength: number; + truncating: boolean; + mustCreateTypeParameterSymbolList: boolean; + typeParameterSymbolList: Set | undefined; + mustCreateTypeParametersNamesLookups: boolean; + typeParameterNames: Map | undefined; + typeParameterNamesByText: Set | undefined; + typeParameterNamesByTextNextNameCount: Map | undefined; + usedSymbolNames: Set | undefined; + remappedSymbolNames: Map | undefined; + remappedSymbolReferences: Map | undefined; + reverseMappedStack: ReverseMappedSymbol[] | undefined; + bundled: boolean; + mapper: TypeMapper | undefined; +} + +class SymbolTrackerImpl implements SymbolTracker { + moduleResolverHost: ModuleSpecifierResolutionHost & { getCommonSourceDirectory(): string; } | undefined = undefined; + context: NodeBuilderContext; + + readonly inner: SymbolTracker | undefined = undefined; + readonly canTrackSymbol: boolean; + disableTrackSymbol = false; + + constructor(context: NodeBuilderContext, tracker: SymbolTracker | undefined, moduleResolverHost: ModuleSpecifierResolutionHost & { getCommonSourceDirectory(): string; } | undefined) { + while (tracker instanceof SymbolTrackerImpl) { + tracker = tracker.inner; + } + + this.inner = tracker; + this.moduleResolverHost = moduleResolverHost; + this.context = context; + this.canTrackSymbol = !!this.inner?.trackSymbol; + } + + trackSymbol(symbol: Symbol, enclosingDeclaration: Node | undefined, meaning: SymbolFlags): boolean { + if (this.inner?.trackSymbol && !this.disableTrackSymbol) { + if (this.inner.trackSymbol(symbol, enclosingDeclaration, meaning)) { + this.onDiagnosticReported(); + return true; + } + // Skip recording type parameters as they dont contribute to late painted statements + if (!(symbol.flags & SymbolFlags.TypeParameter)) (this.context.trackedSymbols ??= []).push([symbol, enclosingDeclaration, meaning]); + } + return false; + } + + reportInaccessibleThisError(): void { + if (this.inner?.reportInaccessibleThisError) { + this.onDiagnosticReported(); + this.inner.reportInaccessibleThisError(); + } + } + + reportPrivateInBaseOfClassExpression(propertyName: string): void { + if (this.inner?.reportPrivateInBaseOfClassExpression) { + this.onDiagnosticReported(); + this.inner.reportPrivateInBaseOfClassExpression(propertyName); + } + } + + reportInaccessibleUniqueSymbolError(): void { + if (this.inner?.reportInaccessibleUniqueSymbolError) { + this.onDiagnosticReported(); + this.inner.reportInaccessibleUniqueSymbolError(); + } + } + + reportCyclicStructureError(): void { + if (this.inner?.reportCyclicStructureError) { + this.onDiagnosticReported(); + this.inner.reportCyclicStructureError(); + } + } + + reportLikelyUnsafeImportRequiredError(specifier: string): void { + if (this.inner?.reportLikelyUnsafeImportRequiredError) { + this.onDiagnosticReported(); + this.inner.reportLikelyUnsafeImportRequiredError(specifier); + } + } + + reportTruncationError(): void { + if (this.inner?.reportTruncationError) { + this.onDiagnosticReported(); + this.inner.reportTruncationError(); + } + } + + reportNonlocalAugmentation(containingFile: SourceFile, parentSymbol: Symbol, augmentingSymbol: Symbol): void { + if (this.inner?.reportNonlocalAugmentation) { + this.onDiagnosticReported(); + this.inner.reportNonlocalAugmentation(containingFile, parentSymbol, augmentingSymbol); + } + } + + reportNonSerializableProperty(propertyName: string): void { + if (this.inner?.reportNonSerializableProperty) { + this.onDiagnosticReported(); + this.inner.reportNonSerializableProperty(propertyName); + } + } + + private onDiagnosticReported() { + this.context.reportedDiagnostic = true; + } + + reportInferenceFallback(node: Node): void { + if (this.inner?.reportInferenceFallback) { + this.inner.reportInferenceFallback(node); + } + } +} +import { + __String, + AccessExpression, + AccessFlags, + AccessorDeclaration, + addRange, + addRelatedInfo, + addSyntheticLeadingComment, + AliasDeclarationNode, + AllAccessorDeclarations, + AmbientModuleDeclaration, + and, + AnonymousType, + AnyImportOrJsDocImport, + AnyImportOrReExport, + append, + appendIfUnique, + ArrayBindingPattern, + arrayFrom, + arrayIsHomogeneous, + ArrayLiteralExpression, + arrayOf, + arraysEqual, + arrayToMultiMap, + ArrayTypeNode, + ArrowFunction, + AsExpression, + AssertionExpression, + AssignmentDeclarationKind, + AssignmentKind, + AssignmentPattern, + AwaitExpression, + BaseType, + BigIntLiteral, + BigIntLiteralType, + BinaryExpression, + BinaryOperator, + BinaryOperatorToken, + binarySearch, + BindableObjectDefinePropertyCall, + BindableStaticNameExpression, + BindingElement, + BindingElementGrandparent, + BindingName, + BindingPattern, + bindSourceFile, + Block, + BooleanLiteral, + BreakOrContinueStatement, + CallChain, + CallExpression, + CallLikeExpression, + CallSignatureDeclaration, + CancellationToken, + canHaveDecorators, + canHaveExportModifier, + canHaveFlowNode, + canHaveIllegalDecorators, + canHaveIllegalModifiers, + canHaveJSDoc, + canHaveLocals, + canHaveModifiers, + canHaveSymbol, + canIncludeBindAndCheckDiagnostics, + canUsePropertyAccess, + cartesianProduct, + CaseBlock, + CaseClause, + CaseOrDefaultClause, + cast, + chainDiagnosticMessages, + CharacterCodes, + CheckFlags, + ClassDeclaration, + ClassElement, + classElementOrClassElementParameterIsDecorated, + ClassExpression, + ClassLikeDeclaration, + classOrConstructorParameterIsDecorated, + 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, + createDetachedDiagnostic, + createDiagnosticCollection, + createDiagnosticForFileFromMessageChain, + createDiagnosticForNode, + createDiagnosticForNodeArray, + createDiagnosticForNodeArrayFromMessageChain, + createDiagnosticForNodeFromMessageChain, + createDiagnosticMessageChainFromDiagnostic, + createEmptyExports, + createEvaluator, + createFileDiagnostic, + createFlowNode, + createGetSymbolWalker, + createModeAwareCacheKey, + createModuleNotFoundChain, + createMultiMap, + createNameResolver, + createPrinterWithDefaults, + createPrinterWithRemoveComments, + createPrinterWithRemoveCommentsNeverAsciiEscape, + createPrinterWithRemoveCommentsOmitTrailingSemicolon, + createPropertyNameNodeForIdentifierOrLiteral, + createScanner, + createSymbolTable, + createSyntacticTypeNodeBuilder, + createTextWriter, + Debug, + Declaration, + DeclarationName, + declarationNameToString, + DeclarationStatement, + DeclarationWithTypeParameterChildren, + DeclarationWithTypeParameters, + Decorator, + deduplicate, + DefaultClause, + defaultMaximumTruncationLength, + DeferredTypeReference, + DeleteExpression, + Diagnostic, + DiagnosticAndArguments, + DiagnosticArguments, + DiagnosticCategory, + DiagnosticMessage, + DiagnosticMessageChain, + DiagnosticRelatedInformation, + Diagnostics, + DiagnosticWithLocation, + DoStatement, + DynamicNamedDeclaration, + ElementAccessChain, + ElementAccessExpression, + ElementFlags, + EmitFlags, + EmitHint, + emitModuleKindIsNonNodeESM, + EmitResolver, + EmitTextWriter, + emptyArray, + endsWith, + EntityName, + EntityNameExpression, + EntityNameOrEntityNameExpression, + entityNameToString, + EnumDeclaration, + EnumMember, + EnumType, + equateValues, + escapeLeadingUnderscores, + escapeString, + EvaluatorResult, + evaluatorResult, + every, + EvolvingArrayType, + ExclamationToken, + ExportAssignment, + exportAssignmentIsAlias, + ExportDeclaration, + ExportSpecifier, + Expression, + expressionResultIsUnused, + ExpressionStatement, + ExpressionWithTypeArguments, + Extension, + ExternalEmitHelpers, + externalHelpersModuleNameText, + factory, + fileExtensionIs, + fileExtensionIsOneOf, + filter, + find, + findAncestor, + findBestPatternMatch, + findConstructorDeclaration, + findIndex, + findLast, + findLastIndex, + findUseStrictPrologue, + first, + firstDefined, + firstIterator, + firstOrUndefined, + firstOrUndefinedIterator, + flatMap, + flatten, + FlowArrayMutation, + FlowAssignment, + FlowCall, + FlowCondition, + FlowFlags, + FlowLabel, + FlowNode, + FlowReduceLabel, + FlowStart, + FlowSwitchClause, + FlowSwitchClauseData, + FlowType, + forEach, + forEachChild, + forEachChildRecursively, + forEachEnclosingBlockScopeContainer, + forEachEntry, + forEachKey, + forEachReturnStatement, + forEachYieldExpression, + ForInOrOfStatement, + ForInStatement, + formatMessage, + ForOfStatement, + ForStatement, + FreshableIntrinsicType, + FreshableType, + FreshObjectLiteralType, + FunctionDeclaration, + FunctionExpression, + FunctionFlags, + FunctionLikeDeclaration, + FunctionOrConstructorTypeNode, + FunctionTypeNode, + GenericType, + GetAccessorDeclaration, + getAliasDeclarationFromName, + getAllJSDocTags, + getAllowSyntheticDefaultImports, + getAncestor, + getAssignedExpandoInitializer, + getAssignmentDeclarationKind, + getAssignmentDeclarationPropertyAccessKind, + getAssignmentTargetKind, + getCanonicalDiagnostic, + getCheckFlags, + getClassExtendsHeritageElement, + getClassLikeDeclarationOfSymbol, + getCombinedLocalAndExportSymbolFlags, + getCombinedModifierFlags, + getCombinedNodeFlags, + getContainingClass, + getContainingClassExcludingClassDecorators, + getContainingClassStaticBlock, + getContainingFunction, + getContainingFunctionOrClassStaticBlock, + getDeclarationModifierFlagsFromSymbol, + getDeclarationOfKind, + getDeclarationsOfKind, + getDeclaredExpandoInitializer, + getDecorators, + getDirectoryPath, + getEffectiveBaseTypeNode, + getEffectiveConstraintOfTypeParameter, + getEffectiveContainerForJSDocTemplateTag, + getEffectiveImplementsTypeNodes, + getEffectiveInitializer, + getEffectiveJSDocHost, + getEffectiveModifierFlags, + getEffectiveReturnTypeNode, + getEffectiveSetAccessorTypeAnnotationNode, + getEffectiveTypeAnnotationNode, + getEffectiveTypeParameterDeclarations, + getElementOrPropertyAccessName, + getEmitDeclarations, + getEmitFlags, + getEmitModuleKind, + getEmitModuleResolutionKind, + getEmitScriptTarget, + getEmitStandardClassFields, + getEnclosingBlockScopeContainer, + getEnclosingContainer, + getEntityNameFromTypeNode, + getErrorSpanForNode, + getEscapedTextOfIdentifierOrLiteral, + getEscapedTextOfJsxAttributeName, + getEscapedTextOfJsxNamespacedName, + getESModuleInterop, + getExpandoInitializer, + getExportAssignmentExpression, + getExternalModuleImportEqualsDeclarationExpression, + getExternalModuleName, + getExternalModuleRequireArgument, + getFirstConstructorWithBody, + getFirstIdentifier, + getFunctionFlags, + getHostSignatureFromJSDoc, + getIdentifierGeneratedImportReference, + getIdentifierTypeArguments, + getImmediatelyInvokedFunctionExpression, + getInitializerOfBinaryExpression, + getInterfaceBaseTypeNodes, + getInvokedExpression, + getIsolatedModules, + getJSDocClassTag, + getJSDocDeprecatedTag, + getJSDocEnumTag, + getJSDocHost, + getJSDocOverloadTags, + getJSDocParameterTags, + getJSDocRoot, + getJSDocSatisfiesExpressionType, + getJSDocTags, + getJSDocThisTag, + getJSDocType, + getJSDocTypeAssertionType, + getJSDocTypeParameterDeclarations, + getJSDocTypeTag, + getJSXImplicitImportBase, + getJSXRuntimeImport, + getJSXTransformEnabled, + getLeftmostAccessExpression, + getLineAndCharacterOfPosition, + getMembersOfDeclaration, + getModifiers, + getModuleInstanceState, + getNameFromImportAttribute, + getNameFromIndexInfo, + getNameOfDeclaration, + getNameOfExpando, + getNamespaceDeclarationNode, + getNewTargetContainer, + getNonAugmentationDeclaration, + getNormalizedAbsolutePath, + getObjectFlags, + getOriginalNode, + getOrUpdate, + getParameterSymbolFromJSDoc, + getParseTreeNode, + getPropertyAssignmentAliasLikeExpression, + getPropertyNameForPropertyNameNode, + getPropertyNameFromType, + getResolutionDiagnostic, + getResolutionModeOverride, + getResolveJsonModule, + getRestParameterElementType, + getRootDeclaration, + getScriptTargetFeatures, + getSelectedEffectiveModifierFlags, + getSemanticJsxChildren, + getSetAccessorValueParameter, + getSingleVariableOfVariableStatement, + getSourceFileOfModule, + getSourceFileOfNode, + getSpanOfTokenAtPosition, + getSpellingSuggestion, + getStrictOptionValue, + getSuperContainer, + getSymbolNameForPrivateIdentifier, + getTextOfIdentifierOrLiteral, + getTextOfJSDocComment, + getTextOfJsxAttributeName, + getTextOfNode, + getTextOfPropertyName, + getThisContainer, + getThisParameter, + getTrailingSemicolonDeferringWriter, + getTypeParameterFromJsDoc, + getUseDefineForClassFields, + group, + hasAbstractModifier, + hasAccessorModifier, + hasAmbientModifier, + hasContextSensitiveParameters, + HasDecorators, + hasDecorators, + hasDynamicName, + hasEffectiveModifier, + hasEffectiveModifiers, + hasEffectiveReadonlyModifier, + HasExpressionInitializer, + hasExtension, + HasIllegalDecorators, + HasIllegalModifiers, + hasInferredType, + HasInitializer, + hasInitializer, + hasJSDocNodes, + hasJSDocParameterTags, + hasJsonModuleEmitEnabled, + HasLocals, + HasModifiers, + hasOnlyExpressionInitializer, + hasOverrideModifier, + hasPossibleExternalModuleReference, + hasQuestionToken, + hasResolutionModeOverride, + hasRestParameter, + hasScopeMarker, + hasStaticModifier, + hasSyntacticModifier, + hasSyntacticModifiers, + hasType, + HeritageClause, + Identifier, + identifierToKeywordKind, + IdentifierTypePredicate, + idText, + IfStatement, + ImportAttribute, + ImportAttributes, + ImportCall, + ImportClause, + ImportDeclaration, + ImportEqualsDeclaration, + ImportOrExportSpecifier, + ImportSpecifier, + ImportTypeNode, + IndexedAccessType, + IndexedAccessTypeNode, + IndexFlags, + IndexInfo, + IndexKind, + indexOfNode, + IndexSignatureDeclaration, + IndexType, + indicesOf, + InferenceContext, + InferenceFlags, + InferenceInfo, + InferencePriority, + InferTypeNode, + InstanceofExpression, + InstantiableType, + InstantiationExpressionType, + InterfaceDeclaration, + InterfaceType, + InterfaceTypeWithDeclaredMembers, + InternalSymbolName, + IntersectionFlags, + IntersectionType, + IntersectionTypeNode, + intrinsicTagNameToString, + IntrinsicType, + introducesArgumentsExoticObject, + isAccessExpression, + isAccessor, + isAliasableExpression, + isAmbientModule, + isArray, + isArrayBindingPattern, + isArrayLiteralExpression, + isArrowFunction, + isAssertionExpression, + isAssignmentDeclaration, + isAssignmentExpression, + isAssignmentOperator, + isAssignmentPattern, + isAssignmentTarget, + isAutoAccessorPropertyDeclaration, + isAwaitExpression, + isBinaryExpression, + isBindableObjectDefinePropertyCall, + isBindableStaticElementAccessExpression, + isBindableStaticNameExpression, + isBindingElement, + isBindingElementOfBareOrAccessedRequire, + isBindingPattern, + isBlock, + isBlockOrCatchScoped, + isBlockScopedContainerTopLevel, + isBooleanLiteral, + isCallChain, + isCallExpression, + isCallLikeExpression, + isCallLikeOrFunctionLikeExpression, + isCallOrNewExpression, + isCallSignatureDeclaration, + isCatchClause, + isCatchClauseVariableDeclaration, + isCatchClauseVariableDeclarationOrBindingElement, + isCheckJsEnabledForFile, + isClassDeclaration, + isClassElement, + isClassExpression, + isClassInstanceProperty, + isClassLike, + isClassStaticBlockDeclaration, + isCommaSequence, + isCommonJsExportedExpression, + isCommonJsExportPropertyAssignment, + isCompoundAssignment, + isComputedNonLiteralName, + isComputedPropertyName, + isConditionalTypeNode, + isConstAssertion, + isConstructorDeclaration, + isConstructorTypeNode, + isConstructSignatureDeclaration, + isConstTypeReference, + isDeclaration, + isDeclarationFileName, + isDeclarationName, + isDeclarationReadonly, + isDecorator, + isDefaultedExpandoInitializer, + isDeleteTarget, + isDottedName, + isDynamicName, + isEffectiveExternalModule, + isElementAccessExpression, + isEntityName, + isEntityNameExpression, + isEnumConst, + isEnumDeclaration, + isEnumMember, + isExclusivelyTypeOnlyImportOrExport, + isExpandoPropertyDeclaration, + isExportAssignment, + isExportDeclaration, + isExportsIdentifier, + isExportSpecifier, + isExpression, + isExpressionNode, + isExpressionOfOptionalChainRoot, + isExpressionStatement, + isExpressionWithTypeArguments, + isExpressionWithTypeArgumentsInClassExtendsClause, + isExternalModule, + isExternalModuleAugmentation, + isExternalModuleImportEqualsDeclaration, + isExternalModuleIndicator, + isExternalModuleNameRelative, + isExternalModuleReference, + isExternalModuleSymbol, + isExternalOrCommonJsModule, + isForInOrOfStatement, + isForInStatement, + isForOfStatement, + isForStatement, + isFunctionDeclaration, + isFunctionExpression, + isFunctionExpressionOrArrowFunction, + isFunctionLike, + isFunctionLikeDeclaration, + isFunctionLikeOrClassStaticBlockDeclaration, + isFunctionOrModuleBlock, + isFunctionTypeNode, + isGeneratedIdentifier, + isGetAccessor, + isGetAccessorDeclaration, + isGetOrSetAccessorDeclaration, + isGlobalScopeAugmentation, + isGlobalSourceFile, + isHeritageClause, + isIdentifier, + isIdentifierText, + isIdentifierTypePredicate, + isIdentifierTypeReference, + isIfStatement, + isImportAttributes, + isImportCall, + isImportClause, + isImportDeclaration, + isImportEqualsDeclaration, + isImportKeyword, + isImportOrExportSpecifier, + isImportSpecifier, + isImportTypeNode, + isInCompoundLikeAssignment, + isIndexedAccessTypeNode, + isInExpressionContext, + isInfinityOrNaNString, + isInitializedProperty, + isInJSDoc, + isInJSFile, + isInJsonFile, + isInstanceOfExpression, + isInterfaceDeclaration, + isInternalModuleImportEqualsDeclaration, + isInTopLevelContext, + isIntrinsicJsxName, + isInTypeQuery, + isIterationStatement, + isJSDocAllType, + isJSDocAugmentsTag, + isJSDocCallbackTag, + isJSDocConstructSignature, + isJSDocFunctionType, + isJSDocImportTag, + isJSDocIndexSignature, + isJSDocLinkLike, + isJSDocMemberName, + isJSDocNameReference, + isJSDocNode, + isJSDocNonNullableType, + isJSDocNullableType, + isJSDocOptionalParameter, + isJSDocOptionalType, + isJSDocOverloadTag, + isJSDocParameterTag, + isJSDocPropertyLikeTag, + isJSDocPropertyTag, + isJSDocSatisfiesExpression, + isJSDocSatisfiesTag, + isJSDocSignature, + isJSDocTemplateTag, + isJSDocThisTag, + isJSDocTypeAlias, + isJSDocTypeAssertion, + isJSDocTypedefTag, + isJSDocTypeExpression, + isJSDocTypeLiteral, + isJSDocUnknownType, + isJSDocVariadicType, + isJsonSourceFile, + isJsxAttribute, + isJsxAttributeLike, + isJsxAttributes, + isJsxElement, + isJsxNamespacedName, + isJsxOpeningElement, + isJsxOpeningFragment, + isJsxOpeningLikeElement, + isJsxSelfClosingElement, + isJsxSpreadAttribute, + isJSXTagName, + isKnownSymbol, + isLateVisibilityPaintedStatement, + isLeftHandSideExpression, + isLineBreak, + isLiteralComputedPropertyDeclarationName, + isLiteralExpression, + isLiteralExpressionOfObject, + isLiteralImportTypeNode, + isLiteralTypeNode, + isLogicalOrCoalescingBinaryExpression, + isLogicalOrCoalescingBinaryOperator, + isMappedTypeNode, + isMetaProperty, + isMethodDeclaration, + isMethodSignature, + isModifier, + isModuleBlock, + isModuleDeclaration, + isModuleExportsAccessExpression, + isModuleIdentifier, + isModuleOrEnumDeclaration, + isModuleWithStringLiteralName, + isNamedDeclaration, + isNamedEvaluationSource, + isNamedExports, + isNamedTupleMember, + isNamespaceExport, + isNamespaceExportDeclaration, + isNamespaceReexportDeclaration, + isNewExpression, + isNodeDescendantOf, + isNonNullAccess, + isNonNullExpression, + isNumericLiteral, + isNumericLiteralName, + isObjectBindingPattern, + isObjectLiteralElementLike, + isObjectLiteralExpression, + isObjectLiteralMethod, + isObjectLiteralOrClassExpressionMethodOrAccessor, + isOmittedExpression, + isOptionalChain, + isOptionalChainRoot, + isOptionalDeclaration, + isOptionalJSDocPropertyLikeTag, + isOptionalTypeNode, + isOutermostOptionalChain, + isParameter, + isParameterPropertyDeclaration, + isParenthesizedExpression, + isParenthesizedTypeNode, + isPartOfParameterDeclaration, + isPartOfTypeNode, + isPartOfTypeQuery, + isPlainJsFile, + isPrefixUnaryExpression, + isPrivateIdentifier, + isPrivateIdentifierClassElementDeclaration, + isPrivateIdentifierPropertyAccessExpression, + isPropertyAccessEntityNameExpression, + isPropertyAccessExpression, + isPropertyAccessOrQualifiedName, + isPropertyAccessOrQualifiedNameOrImportTypeNode, + isPropertyAssignment, + isPropertyDeclaration, + isPropertyName, + isPropertyNameLiteral, + isPropertySignature, + isPrototypeAccess, + isPrototypePropertyAssignment, + isPushOrUnshiftIdentifier, + isQualifiedName, + isRequireCall, + isRestParameter, + isRestTypeNode, + isRightSideOfAccessExpression, + isRightSideOfInstanceofExpression, + isRightSideOfQualifiedNameOrPropertyAccess, + isRightSideOfQualifiedNameOrPropertyAccessOrJSDocMemberName, + isSameEntityName, + isSatisfiesExpression, + isSetAccessor, + isSetAccessorDeclaration, + 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, + isThisTypeNode, + isThisTypeParameter, + isThisTypePredicate, + isTransientSymbol, + isTupleTypeNode, + isTypeAlias, + isTypeAliasDeclaration, + isTypeDeclaration, + isTypeLiteralNode, + isTypeNode, + isTypeNodeKind, + isTypeOfExpression, + isTypeOnlyImportDeclaration, + isTypeOnlyImportOrExportDeclaration, + isTypeOperatorNode, + isTypeParameterDeclaration, + isTypePredicateNode, + isTypeQueryNode, + isTypeReferenceNode, + isTypeReferenceType, + isTypeUsableAsPropertyName, + isUMDExportSymbol, + isValidBigIntString, + isValidESSymbolDeclaration, + isValidTypeOnlyAliasUseSite, + isValueSignatureDeclaration, + isVariableDeclaration, + isVariableDeclarationInitializedToBareOrAccessedRequire, + isVariableDeclarationInVariableStatement, + isVariableDeclarationList, + isVariableLike, + isVariableLikeOrAccessor, + isVariableStatement, + isWriteAccess, + isWriteOnlyAccess, + IterableOrIteratorType, + IterationTypes, + JSDoc, + JSDocAugmentsTag, + JSDocCallbackTag, + JSDocComment, + JSDocFunctionType, + JSDocImplementsTag, + JSDocImportTag, + JSDocLink, + JSDocLinkCode, + JSDocLinkPlain, + JSDocMemberName, + JSDocNullableType, + JSDocOptionalType, + JSDocOverloadTag, + JSDocParameterTag, + JSDocPrivateTag, + JSDocPropertyLikeTag, + JSDocPropertyTag, + JSDocProtectedTag, + JSDocPublicTag, + JSDocSatisfiesTag, + JSDocSignature, + JSDocTemplateTag, + JSDocThisTag, + JSDocTypeAssertion, + JSDocTypedefTag, + JSDocTypeExpression, + JSDocTypeLiteral, + JSDocTypeReferencingNode, + JSDocTypeTag, + JSDocVariadicType, + JsxAttribute, + JsxAttributeLike, + JsxAttributeName, + JsxAttributes, + JsxAttributeValue, + JsxChild, + JsxClosingElement, + JsxElement, + JsxEmit, + JsxExpression, + JsxFlags, + JsxFragment, + JsxNamespacedName, + JsxOpeningElement, + JsxOpeningFragment, + JsxOpeningLikeElement, + JsxReferenceKind, + JsxSelfClosingElement, + JsxSpreadAttribute, + JsxTagNameExpression, + KeywordTypeNode, + LabeledStatement, + LanguageFeatureMinimumTarget, + last, + lastOrUndefined, + LateBoundBinaryExpressionDeclaration, + LateBoundDeclaration, + LateBoundName, + LateVisibilityPaintedStatement, + LazyNodeCheckFlags, + length, + LiteralExpression, + LiteralType, + LiteralTypeNode, + map, + mapDefined, + MappedSymbol, + MappedType, + MappedTypeNode, + MatchingKeys, + maybeBind, + MemberOverrideStatus, + MetaProperty, + MethodDeclaration, + MethodSignature, + minAndMax, + MinusToken, + Modifier, + ModifierFlags, + modifiersToFlags, + modifierToFlag, + ModuleBlock, + ModuleDeclaration, + ModuleExportName, + moduleExportNameIsDefault, + moduleExportNameTextEscaped, + moduleExportNameTextUnescaped, + ModuleInstanceState, + ModuleKind, + ModuleResolutionKind, + ModuleSpecifierResolutionHost, + Mutable, + NamedDeclaration, + NamedExports, + NamedImportsOrExports, + NamedTupleMember, + NamespaceDeclaration, + NamespaceExport, + NamespaceExportDeclaration, + NamespaceImport, + needsScopeMarker, + NewExpression, + Node, + NodeArray, + NodeBuilderFlags, + nodeCanBeDecorated, + NodeCheckFlags, + NodeFlags, + nodeHasName, + nodeIsMissing, + nodeIsPresent, + nodeIsSynthesized, + NodeLinks, + nodeStartsNewLexicalEnvironment, + NodeWithTypeArguments, + NonNullChain, + NonNullExpression, + not, + noTruncationMaximumTruncationLength, + NumberLiteralType, + NumericLiteral, + objectAllocator, + ObjectBindingPattern, + ObjectFlags, + ObjectFlagsType, + ObjectLiteralElementLike, + ObjectLiteralExpression, + ObjectType, + OptionalChain, + OptionalTypeNode, + or, + orderedRemoveItemAt, + OuterExpressionKinds, + ParameterDeclaration, + parameterIsThisKeyword, + ParameterPropertyDeclaration, + ParenthesizedExpression, + ParenthesizedTypeNode, + parseIsolatedEntityName, + parseNodeFactory, + parsePseudoBigInt, + parseValidBigInt, + Path, + pathIsRelative, + PatternAmbientModule, + PlusToken, + PostfixUnaryExpression, + PrefixUnaryExpression, + PrivateIdentifier, + Program, + PromiseOrAwaitableType, + PropertyAccessChain, + PropertyAccessEntityNameExpression, + PropertyAccessExpression, + PropertyAssignment, + PropertyDeclaration, + PropertyName, + PropertySignature, + PseudoBigInt, + pseudoBigIntToString, + PunctuationSyntaxKind, + pushIfUnique, + QualifiedName, + QuestionToken, + rangeEquals, + rangeOfNode, + rangeOfTypeParameters, + ReadonlyKeyword, + reduceLeft, + RegularExpressionLiteral, + RelationComparisonResult, + relativeComplement, + removeExtension, + removePrefix, + replaceElement, + resolutionExtensionIsTSOrJson, + ResolutionMode, + ResolvedModuleFull, + ResolvedType, + resolvingEmptyArray, + RestTypeNode, + ReturnStatement, + ReverseMappedSymbol, + ReverseMappedType, + sameMap, + SatisfiesExpression, + Scanner, + scanTokenAtPosition, + ScriptKind, + ScriptTarget, + SetAccessorDeclaration, + setCommentRange as setCommentRangeWorker, + setEmitFlags, + setIdentifierTypeArguments, + setNodeFlags, + setOriginalNode, + setParent, + setSyntheticLeadingComments, + setTextRange as setTextRangeWorker, + setTextRangePosEnd, + setValueDeclaration, + ShorthandPropertyAssignment, + shouldAllowImportingTsExtension, + shouldPreserveConstEnums, + Signature, + SignatureDeclaration, + SignatureFlags, + SignatureKind, + singleElementArray, + SingleSignatureType, + skipOuterExpressions, + skipParentheses, + skipTrivia, + skipTypeChecking, + skipTypeParentheses, + some, + SourceFile, + SpreadAssignment, + SpreadElement, + startsWith, + Statement, + StringLiteral, + StringLiteralLike, + StringLiteralType, + StringMappingType, + stripQuotes, + StructuredType, + SubstitutionType, + 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, + TrackedSymbol, + TransientSymbol, + TransientSymbolLinks, + tryAddToSet, + tryCast, + tryExtractTSExtension, + tryGetClassImplementingOrExtendingExpressionWithTypeArguments, + tryGetExtensionFromPath, + tryGetJSDocSatisfiesTypeNode, + 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, + unescapeLeadingUnderscores, + UnionOrIntersectionType, + UnionOrIntersectionTypeNode, + UnionReduction, + UnionType, + UnionTypeNode, + UniqueESSymbolType, + usingSingleLineStringWriter, + VariableDeclaration, + VariableDeclarationList, + VariableLikeDeclaration, + VariableStatement, + VarianceFlags, + visitEachChild as visitEachChildWorker, + visitNode, + visitNodes, + Visitor, + VisitResult, + VoidExpression, + walkUpBindingElementsAndPatterns, + walkUpOuterExpressions, + walkUpParenthesizedExpressions, + walkUpParenthesizedTypes, + walkUpParenthesizedTypesAndGetParentAndChild, + WhileStatement, + WideningContext, + WithStatement, + YieldExpression, +} from "./_namespaces/ts.js"; +import * as moduleSpecifiers from "./_namespaces/ts.moduleSpecifiers.js"; +import * as performance from "./_namespaces/ts.performance.js"; + +const ambientModuleSymbolRegex = /^".+"$/; +const anon = "(anonymous)" as __String & string; + +const enum ReferenceHint { + Unspecified, + Identifier, + Property, + ExportAssignment, + Jsx, + AsyncFunction, + ExportImportEquals, + ExportSpecifier, + Decorator, +} + +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, +} + +// dprint-ignore +/** @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 typeofNEFacts: ReadonlyMap = new Map(Object.entries({ + 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, + ResolvedTypeArguments, + ResolvedBaseTypes, + WriteType, + ParameterInitializerContainsUndefined, +} + +// dprint-ignore +/** @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 + RestBindingElement = 1 << 5, // 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 + TypeOnly = 1 << 6, // Called from getTypeOfExpression, diagnostics may be omitted +} + +/** @internal */ +export const enum SignatureCheckMode { + None = 0, + BivariantCallback = 1 << 0, + StrictCallback = 1 << 1, + IgnoreReturnTypes = 1 << 2, + StrictArity = 1 << 3, + StrictTopSignature = 1 << 4, + Callback = BivariantCallback | StrictCallback, +} + +const enum IntersectionState { + None = 0, + Source = 1 << 0, // Source type is a constituent of an outer intersection + Target = 1 << 1, // Target type is a constituent of an outer intersection +} + +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 MappedTypeNameTypeKind { + None, + Filtering, + Remapping, +} + +const enum ExpandingFlags { + None = 0, + Source = 1, + Target = 1 << 1, + Both = Source | Target, +} + +const enum MembersOrExportsResolutionKind { + resolvedExports = "resolvedExports", + resolvedMembers = "resolvedMembers", +} + +const enum UnusedKind { + Local, + Parameter, +} + +/** @param containingNode Node to check for parse error */ +type AddUnusedDiagnostic = (containingNode: Node, type: UnusedKind, diagnostic: DiagnosticWithLocation) => void; + +const isNotOverloadAndNotAccessor = and(isNotOverload, isNotAccessor); + +const enum DeclarationMeaning { + GetAccessor = 1, + SetAccessor = 2, + PropertyAssignment = 4, + Method = 8, + PrivateStatic = 16, + GetOrSetAccessor = GetAccessor | SetAccessor, + PropertyAssignmentOrMethod = PropertyAssignment | Method, +} + +const enum DeclarationSpaces { + None = 0, + ExportValue = 1 << 0, + ExportType = 1 << 1, + ExportNamespace = 1 << 2, +} + +const enum MinArgumentCountFlags { + None = 0, + StrongArityForUntypedJS = 1 << 0, + VoidIsNonOptional = 1 << 1, +} + +const enum IntrinsicTypeKind { + Uppercase, + Lowercase, + Capitalize, + Uncapitalize, + NoInfer, +} + +const intrinsicTypeKinds: ReadonlyMap = new Map(Object.entries({ + Uppercase: IntrinsicTypeKind.Uppercase, + Lowercase: IntrinsicTypeKind.Lowercase, + Capitalize: IntrinsicTypeKind.Capitalize, + Uncapitalize: IntrinsicTypeKind.Uncapitalize, + NoInfer: IntrinsicTypeKind.NoInfer, +})); + +const SymbolLinks = class implements SymbolLinks { + declare _symbolLinksBrand: any; +}; + +function NodeLinks(this: NodeLinks) { + this.flags = NodeCheckFlags.None; +} + +/** @internal */ +export function getNodeId(node: Node): number { + if (!node.id) { + node.id = nextNodeId; + nextNodeId++; + } + return node.id; +} + +/** @internal */ +export function getSymbolId(symbol: Symbol): SymbolId { + if (!symbol.id) { + symbol.id = nextSymbolId; + nextSymbolId++; + } + + return symbol.id; +} + +/** @internal */ +export function isInstantiatedModule(node: ModuleDeclaration, preserveConstEnums: boolean) { + const moduleState = getModuleInstanceState(node); + return moduleState === ModuleInstanceState.Instantiated || + (preserveConstEnums && moduleState === ModuleInstanceState.ConstEnumOnly); +} + +/** @internal */ +export function createTypeChecker(host: TypeCheckerHost): TypeChecker { + // Why var? It avoids TDZ checks in the runtime which can be costly. + // See: https://github.com/microsoft/TypeScript/issues/52924 + /* eslint-disable no-var */ + var deferredDiagnosticsCallbacks: (() => void)[] = []; + + var 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). + var cancellationToken: CancellationToken | undefined; + + var scanner: Scanner | undefined; + + var Symbol = objectAllocator.getSymbolConstructor(); + var Type = objectAllocator.getTypeConstructor(); + var Signature = objectAllocator.getSignatureConstructor(); + + var typeCount = 0; + var symbolCount = 0; + var totalInstantiationCount = 0; + var instantiationCount = 0; + var instantiationDepth = 0; + var inlineLevel = 0; + var currentNode: Node | undefined; + var varianceTypeParameter: TypeParameter | undefined; + var isInferencePartiallyBlocked = false; + + var emptySymbols = createSymbolTable(); + var arrayVariances = [VarianceFlags.Covariant]; + + var compilerOptions = host.getCompilerOptions(); + var languageVersion = getEmitScriptTarget(compilerOptions); + var moduleKind = getEmitModuleKind(compilerOptions); + var legacyDecorators = !!compilerOptions.experimentalDecorators; + var useDefineForClassFields = getUseDefineForClassFields(compilerOptions); + var emitStandardClassFields = getEmitStandardClassFields(compilerOptions); + var allowSyntheticDefaultImports = getAllowSyntheticDefaultImports(compilerOptions); + var strictNullChecks = getStrictOptionValue(compilerOptions, "strictNullChecks"); + var strictFunctionTypes = getStrictOptionValue(compilerOptions, "strictFunctionTypes"); + var strictBindCallApply = getStrictOptionValue(compilerOptions, "strictBindCallApply"); + var strictPropertyInitialization = getStrictOptionValue(compilerOptions, "strictPropertyInitialization"); + var noImplicitAny = getStrictOptionValue(compilerOptions, "noImplicitAny"); + var noImplicitThis = getStrictOptionValue(compilerOptions, "noImplicitThis"); + var useUnknownInCatchVariables = getStrictOptionValue(compilerOptions, "useUnknownInCatchVariables"); + var exactOptionalPropertyTypes = compilerOptions.exactOptionalPropertyTypes; + + var checkBinaryExpression = createCheckBinaryExpression(); + var emitResolver = createResolver(); + var nodeBuilder = createNodeBuilder(); + var syntacticNodeBuilder = createSyntacticTypeNodeBuilder(compilerOptions, { + isEntityNameVisible, + isExpandoFunctionDeclaration, + getAllAccessorDeclarations: getAllAccessorDeclarationsForDeclaration, + requiresAddingImplicitUndefined, + isUndefinedIdentifierExpression(node: Identifier) { + Debug.assert(isExpressionNode(node)); + return getSymbolAtLocation(node) === undefinedSymbol; + }, + isDefinitelyReferenceToGlobalSymbolObject, + }); + var evaluate = createEvaluator({ + evaluateElementAccessExpression, + evaluateEntityNameExpression, + }); + + var globals = createSymbolTable(); + var undefinedSymbol = createSymbol(SymbolFlags.Property, "undefined" as __String); + undefinedSymbol.declarations = []; + + var globalThisSymbol = createSymbol(SymbolFlags.Module, "globalThis" as __String, CheckFlags.Readonly); + globalThisSymbol.exports = globals; + globalThisSymbol.declarations = []; + globals.set(globalThisSymbol.escapedName, globalThisSymbol); + + var argumentsSymbol = createSymbol(SymbolFlags.Property, "arguments" as __String); + var requireSymbol = createSymbol(SymbolFlags.Property, "require" as __String); + var isolatedModulesLikeFlagName = compilerOptions.verbatimModuleSyntax ? "verbatimModuleSyntax" : "isolatedModules"; + var canCollectSymbolAliasAccessabilityData = !compilerOptions.verbatimModuleSyntax; + + /** This will be set during calls to `getResolvedSignature` where services determines an apparent number of arguments greater than what is actually provided. */ + var apparentArgumentCount: number | undefined; + + var lastGetCombinedNodeFlagsNode: Node | undefined; + var lastGetCombinedNodeFlagsResult = NodeFlags.None; + var lastGetCombinedModifierFlagsNode: Declaration | undefined; + var lastGetCombinedModifierFlagsResult = ModifierFlags.None; + var resolveName = createNameResolver({ + compilerOptions, + requireSymbol, + argumentsSymbol, + globals, + getSymbolOfDeclaration, + error, + getRequiresScopeChangeCache, + setRequiresScopeChangeCache, + lookup: getSymbol, + onPropertyWithInvalidInitializer: checkAndReportErrorForInvalidInitializer, + onFailedToResolveSymbol, + onSuccessfullyResolvedSymbol, + }); + + var resolveNameForSymbolSuggestion = createNameResolver({ + compilerOptions, + requireSymbol, + argumentsSymbol, + globals, + getSymbolOfDeclaration, + error, + getRequiresScopeChangeCache, + setRequiresScopeChangeCache, + lookup: getSuggestionForSymbolNameLookup, + }); + // 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: () => reduceLeft(host.getSourceFiles(), (n, s) => n + s.nodeCount, 0), + getIdentifierCount: () => reduceLeft(host.getSourceFiles(), (n, s) => n + s.identifierCount, 0), + getSymbolCount: () => reduceLeft(host.getSourceFiles(), (n, s) => n + s.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, + symbolIsValue, + 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."); + Debug.assert(isParameterPropertyDeclaration(parameter, parameter.parent)); + 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, + getWidenedLiteralType, + getTypeFromTypeNode: nodeIn => { + const node = getParseTreeNode(nodeIn, isTypeNode); + return node ? getTypeFromTypeNode(node) : errorType; + }, + getParameterType: getTypeAtPosition, + getParameterIdentifierInfoAtPosition, + 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), + getCandidateSignaturesForStringLiteralCompletions, + getResolvedSignatureForSignatureHelp: (node, candidatesOutArray, argumentCount) => runWithoutResolvedSignatureCaching(node, () => 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, + requiresAddingImplicitUndefined, + 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, + getStringLiteralType, + getNumberType: () => numberType, + getNumberLiteralType, + getBigIntType: () => bigintType, + getBigIntLiteralType, + 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, + isEmptyAnonymousObjectType, + isTypeInvalidDueToUnionDiscriminant, + getExactOptionalProperties, + getAllPossiblePropertiesOfTypes, + getSuggestedSymbolForNonexistentProperty, + getSuggestedSymbolForNonexistentJSXAttribute, + getSuggestedSymbolForNonexistentSymbol: (location, name, meaning) => getSuggestedSymbolForNonexistentSymbol(location, escapeLeadingUnderscores(name), meaning), + getSuggestedSymbolForNonexistentModule, + 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, /*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; + } + + 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; + + // 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)); + + 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 }); + } + }); + + return diagnostics || emptyArray; + } + finally { + cancellationToken = undefined; + } + }, + + runWithCancellationToken: (token, callback) => { + try { + cancellationToken = token; + return callback(checker); + } + finally { + cancellationToken = undefined; + } + }, + + getLocalTypeParametersOfClassOrInterfaceOrTypeAlias, + isDeclarationVisible, + isPropertyAccessible, + getTypeOnlyAliasDeclaration, + getMemberOverrideModifierStatus, + isTypeParameterPossiblyReferenced, + typeHasCallOrConstructSignatures, + getSymbolFlags, + }; + + function getCandidateSignaturesForStringLiteralCompletions(call: CallLikeExpression, editingArgument: Node) { + const candidatesSet = new Set(); + const candidates: Signature[] = []; + + // first, get candidates when inference is blocked from the source node. + runWithInferenceBlockedFromSourceNode(editingArgument, () => getResolvedSignatureWorker(call, candidates, /*argumentCount*/ undefined, CheckMode.Normal)); + for (const candidate of candidates) { + candidatesSet.add(candidate); + } + + // reset candidates for second pass + candidates.length = 0; + + // next, get candidates where the source node is considered for inference. + runWithoutResolvedSignatureCaching(editingArgument, () => getResolvedSignatureWorker(call, candidates, /*argumentCount*/ undefined, CheckMode.Normal)); + for (const candidate of candidates) { + candidatesSet.add(candidate); + } + + return arrayFrom(candidatesSet); + } + + function runWithoutResolvedSignatureCaching(node: Node | undefined, fn: () => T): T { + node = findAncestor(node, isCallLikeOrFunctionLikeExpression); + if (node) { + const cachedResolvedSignatures = []; + const cachedTypes = []; + while (node) { + const nodeLinks = getNodeLinks(node); + cachedResolvedSignatures.push([nodeLinks, nodeLinks.resolvedSignature] as const); + nodeLinks.resolvedSignature = undefined; + if (isFunctionExpressionOrArrowFunction(node)) { + const symbolLinks = getSymbolLinks(getSymbolOfDeclaration(node)); + const type = symbolLinks.type; + cachedTypes.push([symbolLinks, type] as const); + symbolLinks.type = undefined; + } + node = findAncestor(node.parent, isCallLikeOrFunctionLikeExpression); + } + const result = fn(); + for (const [nodeLinks, resolvedSignature] of cachedResolvedSignatures) { + nodeLinks.resolvedSignature = resolvedSignature; + } + for (const [symbolLinks, type] of cachedTypes) { + symbolLinks.type = type; + } + return result; + } + return fn(); + } + + function runWithInferenceBlockedFromSourceNode(node: Node | undefined, fn: () => T): T { + const containingCall = findAncestor(node, isCallLikeExpression); + if (containingCall) { + let toMarkSkip = node!; + do { + getNodeLinks(toMarkSkip).skipDirectInference = true; + toMarkSkip = toMarkSkip.parent; + } + while (toMarkSkip && toMarkSkip !== containingCall); + } + + isInferencePartiallyBlocked = true; + const result = runWithoutResolvedSignatureCaching(node, fn); + isInferencePartiallyBlocked = false; + + if (containingCall) { + let toMarkSkip = node!; + do { + getNodeLinks(toMarkSkip).skipDirectInference = undefined; + toMarkSkip = toMarkSkip.parent; + } + while (toMarkSkip && toMarkSkip !== containingCall); + } + return result; + } + + function getResolvedSignatureWorker(nodeIn: CallLikeExpression, candidatesOutArray: Signature[] | undefined, argumentCount: number | undefined, checkMode: CheckMode): Signature | undefined { + const node = getParseTreeNode(nodeIn, isCallLikeExpression); + apparentArgumentCount = argumentCount; + const res = !node ? undefined : getResolvedSignature(node, candidatesOutArray, checkMode); + apparentArgumentCount = undefined; + return res; + } + + var tupleTypes = new Map(); + var unionTypes = new Map(); + var unionOfUnionTypes = new Map(); + var intersectionTypes = new Map(); + var stringLiteralTypes = new Map(); + var numberLiteralTypes = new Map(); + var bigIntLiteralTypes = new Map(); + var enumLiteralTypes = new Map(); + var indexedAccessTypes = new Map(); + var templateLiteralTypes = new Map(); + var stringMappingTypes = new Map(); + var substitutionTypes = new Map(); + var subtypeReductionCache = new Map(); + var decoratorContextOverrideTypeCache = new Map(); + var cachedTypes = new Map(); + var evolvingArrayTypes: EvolvingArrayType[] = []; + var undefinedProperties: SymbolTable = new Map(); + var markerTypes = new Set(); + + var unknownSymbol = createSymbol(SymbolFlags.Property, "unknown" as __String); + var resolvingSymbol = createSymbol(0, InternalSymbolName.Resolving); + var unresolvedSymbols = new Map(); + var errorTypes = new Map(); + + // We specifically create the `undefined` and `null` types before any other types that can occur in + // unions such that they are given low type IDs and occur first in the sorted list of union constituents. + // We can then just examine the first constituent(s) of a union to check for their presence. + + var seenIntrinsicNames = new Set(); + + var anyType = createIntrinsicType(TypeFlags.Any, "any"); + var autoType = createIntrinsicType(TypeFlags.Any, "any", ObjectFlags.NonInferrableType, "auto"); + var wildcardType = createIntrinsicType(TypeFlags.Any, "any", /*objectFlags*/ undefined, "wildcard"); + var blockedStringType = createIntrinsicType(TypeFlags.Any, "any", /*objectFlags*/ undefined, "blocked string"); + var errorType = createIntrinsicType(TypeFlags.Any, "error"); + var unresolvedType = createIntrinsicType(TypeFlags.Any, "unresolved"); + var nonInferrableAnyType = createIntrinsicType(TypeFlags.Any, "any", ObjectFlags.ContainsWideningType, "non-inferrable"); + var intrinsicMarkerType = createIntrinsicType(TypeFlags.Any, "intrinsic"); + var unknownType = createIntrinsicType(TypeFlags.Unknown, "unknown"); + var undefinedType = createIntrinsicType(TypeFlags.Undefined, "undefined"); + var undefinedWideningType = strictNullChecks ? undefinedType : createIntrinsicType(TypeFlags.Undefined, "undefined", ObjectFlags.ContainsWideningType, "widening"); + var missingType = createIntrinsicType(TypeFlags.Undefined, "undefined", /*objectFlags*/ undefined, "missing"); + var undefinedOrMissingType = exactOptionalPropertyTypes ? missingType : undefinedType; + var optionalType = createIntrinsicType(TypeFlags.Undefined, "undefined", /*objectFlags*/ undefined, "optional"); + var nullType = createIntrinsicType(TypeFlags.Null, "null"); + var nullWideningType = strictNullChecks ? nullType : createIntrinsicType(TypeFlags.Null, "null", ObjectFlags.ContainsWideningType, "widening"); + var stringType = createIntrinsicType(TypeFlags.String, "string"); + var numberType = createIntrinsicType(TypeFlags.Number, "number"); + var bigintType = createIntrinsicType(TypeFlags.BigInt, "bigint"); + var falseType = createIntrinsicType(TypeFlags.BooleanLiteral, "false", /*objectFlags*/ undefined, "fresh") as FreshableIntrinsicType; + var regularFalseType = createIntrinsicType(TypeFlags.BooleanLiteral, "false") as FreshableIntrinsicType; + var trueType = createIntrinsicType(TypeFlags.BooleanLiteral, "true", /*objectFlags*/ undefined, "fresh") as FreshableIntrinsicType; + var 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; + var booleanType = getUnionType([regularFalseType, regularTrueType]); + var esSymbolType = createIntrinsicType(TypeFlags.ESSymbol, "symbol"); + var voidType = createIntrinsicType(TypeFlags.Void, "void"); + var neverType = createIntrinsicType(TypeFlags.Never, "never"); + var silentNeverType = createIntrinsicType(TypeFlags.Never, "never", ObjectFlags.NonInferrableType, "silent"); + var implicitNeverType = createIntrinsicType(TypeFlags.Never, "never", /*objectFlags*/ undefined, "implicit"); + var unreachableNeverType = createIntrinsicType(TypeFlags.Never, "never", /*objectFlags*/ undefined, "unreachable"); + var nonPrimitiveType = createIntrinsicType(TypeFlags.NonPrimitive, "object"); + var stringOrNumberType = getUnionType([stringType, numberType]); + var stringNumberSymbolType = getUnionType([stringType, numberType, esSymbolType]); + var numberOrBigIntType = getUnionType([numberType, bigintType]); + var templateConstraintType = getUnionType([stringType, numberType, booleanType, bigintType, nullType, undefinedType]) as UnionType; + var numericStringType = getTemplateLiteralType(["", ""], [numberType]); // The `${number}` type + + var restrictiveMapper: TypeMapper = makeFunctionTypeMapper(t => t.flags & TypeFlags.TypeParameter ? getRestrictiveTypeParameter(t as TypeParameter) : t, () => "(restrictive mapper)"); + var permissiveMapper: TypeMapper = makeFunctionTypeMapper(t => t.flags & TypeFlags.TypeParameter ? wildcardType : t, () => "(permissive mapper)"); + var uniqueLiteralType = createIntrinsicType(TypeFlags.Never, "never", /*objectFlags*/ undefined, "unique literal"); // `uniqueLiteralType` is a special `never` flagged by union reduction to behave as a literal + var uniqueLiteralMapper: TypeMapper = makeFunctionTypeMapper(t => t.flags & TypeFlags.TypeParameter ? uniqueLiteralType : t, () => "(unique literal mapper)"); // replace all type parameters with the unique literal type (disregarding constraints) + var outofbandVarianceMarkerHandler: ((onlyUnreliable: boolean) => void) | undefined; + var reportUnreliableMapper = makeFunctionTypeMapper(t => { + if (outofbandVarianceMarkerHandler && (t === markerSuperType || t === markerSubType || t === markerOtherType)) { + outofbandVarianceMarkerHandler(/*onlyUnreliable*/ true); + } + return t; + }, () => "(unmeasurable reporter)"); + var reportUnmeasurableMapper = makeFunctionTypeMapper(t => { + if (outofbandVarianceMarkerHandler && (t === markerSuperType || t === markerSubType || t === markerOtherType)) { + outofbandVarianceMarkerHandler(/*onlyUnreliable*/ false); + } + return t; + }, () => "(unreliable reporter)"); + + var emptyObjectType = createAnonymousType(/*symbol*/ undefined, emptySymbols, emptyArray, emptyArray, emptyArray); + var emptyJsxObjectType = createAnonymousType(/*symbol*/ undefined, emptySymbols, emptyArray, emptyArray, emptyArray); + emptyJsxObjectType.objectFlags |= ObjectFlags.JsxAttributes; + + var emptyTypeLiteralSymbol = createSymbol(SymbolFlags.TypeLiteral, InternalSymbolName.Type); + emptyTypeLiteralSymbol.members = createSymbolTable(); + var emptyTypeLiteralType = createAnonymousType(emptyTypeLiteralSymbol, emptySymbols, emptyArray, emptyArray, emptyArray); + + var unknownEmptyObjectType = createAnonymousType(/*symbol*/ undefined, emptySymbols, emptyArray, emptyArray, emptyArray); + var unknownUnionType = strictNullChecks ? getUnionType([undefinedType, nullType, unknownEmptyObjectType]) : unknownType; + + var emptyGenericType = createAnonymousType(/*symbol*/ undefined, emptySymbols, emptyArray, emptyArray, emptyArray) as ObjectType as GenericType; + emptyGenericType.instantiations = new Map(); + + var anyFunctionType = createAnonymousType(/*symbol*/ 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; + + var noConstraintType = createAnonymousType(/*symbol*/ undefined, emptySymbols, emptyArray, emptyArray, emptyArray); + var circularConstraintType = createAnonymousType(/*symbol*/ undefined, emptySymbols, emptyArray, emptyArray, emptyArray); + var resolvingDefaultType = createAnonymousType(/*symbol*/ undefined, emptySymbols, emptyArray, emptyArray, emptyArray); + + var markerSuperType = createTypeParameter(); + var markerSubType = createTypeParameter(); + markerSubType.constraint = markerSuperType; + var markerOtherType = createTypeParameter(); + + var markerSuperTypeForCheck = createTypeParameter(); + var markerSubTypeForCheck = createTypeParameter(); + markerSubTypeForCheck.constraint = markerSuperTypeForCheck; + + var noTypePredicate = createTypePredicate(TypePredicateKind.Identifier, "<>", 0, anyType); + + var anySignature = createSignature(/*declaration*/ undefined, /*typeParameters*/ undefined, /*thisParameter*/ undefined, emptyArray, anyType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); + var unknownSignature = createSignature(/*declaration*/ undefined, /*typeParameters*/ undefined, /*thisParameter*/ undefined, emptyArray, errorType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); + var resolvingSignature = createSignature(/*declaration*/ undefined, /*typeParameters*/ undefined, /*thisParameter*/ undefined, emptyArray, anyType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); + var silentNeverSignature = createSignature(/*declaration*/ undefined, /*typeParameters*/ undefined, /*thisParameter*/ undefined, emptyArray, silentNeverType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); + + var enumNumberIndexInfo = createIndexInfo(numberType, stringType, /*isReadonly*/ true); + + var iterationTypesCache = new Map(); // cache for common IterationTypes instances + var 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"); + }, + }; + + var anyIterationTypes = createIterationTypes(anyType, anyType, anyType); + var anyIterationTypesExceptNext = createIterationTypes(anyType, anyType, unknownType); + var defaultIterationTypes = createIterationTypes(neverType, anyType, undefinedType); // default iteration types for `Iterator`. + + var asyncIterationTypesResolver: IterationTypesResolver = { + iterableCacheKey: "iterationTypesOfAsyncIterable", + iteratorCacheKey: "iterationTypesOfAsyncIterator", + iteratorSymbolName: "asyncIterator", + getGlobalIteratorType: getGlobalAsyncIteratorType, + getGlobalIterableType: getGlobalAsyncIterableType, + getGlobalIterableIteratorType: getGlobalAsyncIterableIteratorType, + getGlobalGeneratorType: getGlobalAsyncGeneratorType, + resolveIterationType: (type, errorNode) => getAwaitedType(type, errorNode, Diagnostics.Type_of_await_operand_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member), + 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, + }; + + var 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: Map; + } + /** Key is "/path/to/a.ts|/path/to/b.ts". */ + var amalgamatedDuplicates: Map | undefined; + var reverseMappedCache = new Map(); + var 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. + */ + var patternAmbientModules: PatternAmbientModule[]; + var patternAmbientModuleAugmentations: Map | undefined; + + var globalObjectType: ObjectType; + var globalFunctionType: ObjectType; + var globalCallableFunctionType: ObjectType; + var globalNewableFunctionType: ObjectType; + var globalArrayType: GenericType; + var globalReadonlyArrayType: GenericType; + var globalStringType: ObjectType; + var globalNumberType: ObjectType; + var globalBooleanType: ObjectType; + var globalRegExpType: ObjectType; + var globalThisType: GenericType; + var anyArrayType: Type; + var autoArrayType: Type; + var anyReadonlyArrayType: Type; + var 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 + var deferredGlobalESSymbolConstructorSymbol: Symbol | undefined; + var deferredGlobalESSymbolConstructorTypeSymbol: Symbol | undefined; + var deferredGlobalESSymbolType: ObjectType | undefined; + var deferredGlobalTypedPropertyDescriptorType: GenericType; + var deferredGlobalPromiseType: GenericType | undefined; + var deferredGlobalPromiseLikeType: GenericType | undefined; + var deferredGlobalPromiseConstructorSymbol: Symbol | undefined; + var deferredGlobalPromiseConstructorLikeType: ObjectType | undefined; + var deferredGlobalIterableType: GenericType | undefined; + var deferredGlobalIteratorType: GenericType | undefined; + var deferredGlobalIterableIteratorType: GenericType | undefined; + var deferredGlobalGeneratorType: GenericType | undefined; + var deferredGlobalIteratorYieldResultType: GenericType | undefined; + var deferredGlobalIteratorReturnResultType: GenericType | undefined; + var deferredGlobalAsyncIterableType: GenericType | undefined; + var deferredGlobalAsyncIteratorType: GenericType | undefined; + var deferredGlobalAsyncIterableIteratorType: GenericType | undefined; + var deferredGlobalAsyncGeneratorType: GenericType | undefined; + var deferredGlobalTemplateStringsArrayType: ObjectType | undefined; + var deferredGlobalImportMetaType: ObjectType; + var deferredGlobalImportMetaExpressionType: ObjectType; + var deferredGlobalImportCallOptionsType: ObjectType | undefined; + var deferredGlobalImportAttributesType: ObjectType | undefined; + var deferredGlobalDisposableType: ObjectType | undefined; + var deferredGlobalAsyncDisposableType: ObjectType | undefined; + var deferredGlobalExtractSymbol: Symbol | undefined; + var deferredGlobalOmitSymbol: Symbol | undefined; + var deferredGlobalAwaitedSymbol: Symbol | undefined; + var deferredGlobalBigIntType: ObjectType | undefined; + var deferredGlobalNaNSymbol: Symbol | undefined; + var deferredGlobalRecordSymbol: Symbol | undefined; + var deferredGlobalClassDecoratorContextType: GenericType | undefined; + var deferredGlobalClassMethodDecoratorContextType: GenericType | undefined; + var deferredGlobalClassGetterDecoratorContextType: GenericType | undefined; + var deferredGlobalClassSetterDecoratorContextType: GenericType | undefined; + var deferredGlobalClassAccessorDecoratorContextType: GenericType | undefined; + var deferredGlobalClassAccessorDecoratorTargetType: GenericType | undefined; + var deferredGlobalClassAccessorDecoratorResultType: GenericType | undefined; + var deferredGlobalClassFieldDecoratorContextType: GenericType | undefined; + + var allPotentiallyUnusedIdentifiers = new Map(); // key is file name + + var flowLoopStart = 0; + var flowLoopCount = 0; + var sharedFlowCount = 0; + var flowAnalysisDisabled = false; + var flowInvocationCount = 0; + var lastFlowNode: FlowNode | undefined; + var lastFlowNodeReachable: boolean; + var flowTypeCache: Type[] | undefined; + + var contextualTypeNodes: Node[] = []; + var contextualTypes: (Type | undefined)[] = []; + var contextualIsCache: boolean[] = []; + var contextualTypeCount = 0; + + var inferenceContextNodes: Node[] = []; + var inferenceContexts: (InferenceContext | undefined)[] = []; + var inferenceContextCount = 0; + + var emptyStringType = getStringLiteralType(""); + var zeroType = getNumberLiteralType(0); + var zeroBigIntType = getBigIntLiteralType({ negative: false, base10Value: "0" }); + + var resolutionTargets: TypeSystemEntity[] = []; + var resolutionResults: boolean[] = []; + var resolutionPropertyNames: TypeSystemPropertyName[] = []; + var resolutionStart = 0; + var inVarianceComputation = false; + + var suggestionCount = 0; + var maximumSuggestionCount = 10; + var mergedSymbols: Symbol[] = []; + var symbolLinks: SymbolLinks[] = []; + var nodeLinks: NodeLinks[] = []; + var flowLoopCaches: Map[] = []; + var flowLoopNodes: FlowNode[] = []; + var flowLoopKeys: string[] = []; + var flowLoopTypes: Type[][] = []; + var sharedFlowNodes: FlowNode[] = []; + var sharedFlowTypes: FlowType[] = []; + var flowNodeReachable: (boolean | undefined)[] = []; + var flowNodePostSuper: (boolean | undefined)[] = []; + var potentialThisCollisions: Node[] = []; + var potentialNewTargetCollisions: Node[] = []; + var potentialWeakMapSetCollisions: Node[] = []; + var potentialReflectCollisions: Node[] = []; + var potentialUnusedRenamedBindingElementsInTypes: BindingElement[] = []; + var awaitedTypeStack: number[] = []; + var reverseMappedSourceStack: Type[] = []; + var reverseMappedTargetStack: Type[] = []; + var reverseExpandingFlags = ExpandingFlags.None; + + var diagnostics = createDiagnosticCollection(); + var suggestionDiagnostics = createDiagnosticCollection(); + + var typeofType = createTypeofType(); + + var _jsxNamespace: __String; + var _jsxFactoryEntity: EntityName | undefined; + + var subtypeRelation = new Map(); + var strictSubtypeRelation = new Map(); + var assignableRelation = new Map(); + var comparableRelation = new Map(); + var identityRelation = new Map(); + var enumRelation = new Map(); + + // 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". + var 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"], + ]; + /* eslint-enable no-var */ + + initializeTypeChecker(); + + return checker; + + function isDefinitelyReferenceToGlobalSymbolObject(node: Node): boolean { + if (!isPropertyAccessExpression(node)) return false; + if (!isIdentifier(node.name)) return false; + if (!isPropertyAccessExpression(node.expression) && !isIdentifier(node.expression)) return false; + if (isIdentifier(node.expression)) { + // Exactly `Symbol.something` and `Symbol` either does not resolve or definitely resolves to the global Symbol + return idText(node.expression) === "Symbol" && getResolvedSymbol(node.expression) === (getGlobalSymbol("Symbol" as __String, SymbolFlags.Value | SymbolFlags.ExportValue, /*diagnostic*/ undefined) || unknownSymbol); + } + if (!isIdentifier(node.expression.expression)) return false; + // Exactly `globalThis.Symbol.something` and `globalThis` resolves to the global `globalThis` + return idText(node.expression.name) === "Symbol" && idText(node.expression.expression) === "globalThis" && getResolvedSymbol(node.expression.expression) === globalThisSymbol; + } + + 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 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, isEntityName); + 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; + } + } + else { + const localJsxNamespace = getLocalJsxNamespace(file); + if (localJsxNamespace) { + return file.localJsxNamespace = localJsxNamespace; + } + } + } + } + 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); + } + } + 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, isEntityName); + if (file.localJsxFactory) { + return file.localJsxNamespace = getFirstIdentifier(file.localJsxFactory).escapedText; + } + } + } + + function markAsSynthetic(node: T): VisitResult { + setTextRangePosEnd(node, -1, -1); + return visitEachChildWorker(node, markAsSynthetic, /*context*/ undefined); + } + + function getEmitResolver(sourceFile: SourceFile, cancellationToken: CancellationToken, skipDiagnostics?: boolean) { + // 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. + if (!skipDiagnostics) getDiagnostics(sourceFile, cancellationToken); + return emitResolver; + } + + function lookupOrIssueError(location: Node | undefined, message: DiagnosticMessage, ...args: DiagnosticArguments): Diagnostic { + const diagnostic = location + ? createDiagnosticForNode(location, message, ...args) + : createCompilerDiagnostic(message, ...args); + const existing = diagnostics.lookup(diagnostic); + if (existing) { + return existing; + } + else { + diagnostics.add(diagnostic); + return diagnostic; + } + } + + function errorSkippedOn(key: keyof CompilerOptions, location: Node | undefined, message: DiagnosticMessage, ...args: DiagnosticArguments): Diagnostic { + const diagnostic = error(location, message, ...args); + diagnostic.skippedOn = key; + return diagnostic; + } + + function createError(location: Node | undefined, message: DiagnosticMessage, ...args: DiagnosticArguments): Diagnostic { + return location + ? createDiagnosticForNode(location, message, ...args) + : createCompilerDiagnostic(message, ...args); + } + + function error(location: Node | undefined, message: DiagnosticMessage, ...args: DiagnosticArguments): Diagnostic { + const diagnostic = createError(location, message, ...args); + diagnostics.add(diagnostic); + return diagnostic; + } + + function addErrorOrSuggestion(isError: boolean, diagnostic: Diagnostic) { + if (isError) { + diagnostics.add(diagnostic); + } + else { + suggestionDiagnostics.add({ ...diagnostic, category: DiagnosticCategory.Suggestion }); + } + } + function errorOrSuggestion(isError: boolean, location: Node, message: DiagnosticMessage | DiagnosticMessageChain, ...args: DiagnosticArguments): 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, ...args) : createDiagnosticForFileFromMessageChain(file, message)); // eslint-disable-line local/no-in-operator + return; + } + addErrorOrSuggestion(isError, "message" in message ? createDiagnosticForNode(location, message, ...args) : createDiagnosticForNodeFromMessageChain(getSourceFileOfNode(location), location, message)); // eslint-disable-line local/no-in-operator + } + + function errorAndMaybeSuggestAwait( + location: Node, + maybeMissingAwait: boolean, + message: DiagnosticMessage, + ...args: DiagnosticArguments + ): Diagnostic { + const diagnostic = error(location, message, ...args); + if (maybeMissingAwait) { + const related = createDiagnosticForNode(location, Diagnostics.Did_you_forget_to_use_await); + addRelatedInfo(diagnostic, related); + } + return diagnostic; + } + + 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 isDeprecatedSymbol(symbol: Symbol) { + const parentSymbol = getParentOfSymbol(symbol); + if (parentSymbol && length(symbol.declarations) > 1) { + return parentSymbol.flags & SymbolFlags.Interface ? some(symbol.declarations, isDeprecatedDeclaration) : every(symbol.declarations, isDeprecatedDeclaration); + } + return !!symbol.valueDeclaration && isDeprecatedDeclaration(symbol.valueDeclaration) + || length(symbol.declarations) && every(symbol.declarations, isDeprecatedDeclaration); + } + + function isDeprecatedDeclaration(declaration: Declaration) { + return !!(getCombinedNodeFlagsCached(declaration) & NodeFlags.Deprecated); + } + + function addDeprecatedSuggestion(location: Node, declarations: Node[], deprecatedEntity: string) { + const diagnostic = createDiagnosticForNode(location, Diagnostics._0_is_deprecated, deprecatedEntity); + return addDeprecatedSuggestionWorker(declarations, diagnostic); + } + + 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 createSymbol(flags: SymbolFlags, name: __String, checkFlags?: CheckFlags) { + symbolCount++; + const symbol = new Symbol(flags | SymbolFlags.Transient, name) as TransientSymbol; + symbol.links = new SymbolLinks() as TransientSymbolLinks; + symbol.links.checkFlags = checkFlags || CheckFlags.None; + return symbol; + } + + function createParameter(name: __String, type: Type) { + const symbol = createSymbol(SymbolFlags.FunctionScopedVariable, name); + symbol.links.type = type; + return symbol; + } + + function createProperty(name: __String, type: Type) { + const symbol = createSymbol(SymbolFlags.Property, name); + symbol.links.type = type; + return symbol; + } + + 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 recordMergedSymbol(target: Symbol, source: Symbol) { + if (!source.mergeId) { + source.mergeId = nextMergeId; + nextMergeId++; + } + mergedSymbols[source.mergeId] = target; + } + + function cloneSymbol(symbol: Symbol): TransientSymbol { + 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; + } + + /** + * 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; + } + if ( + !(resolvedTarget.flags & getExcludedSymbolFlags(source.flags)) || + (source.flags | resolvedTarget.flags) & SymbolFlags.Assignment + ) { + target = cloneSymbol(resolvedTarget); + } + else { + reportMergeSymbolError(target, source); + return source; + } + } + // 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 { + reportMergeSymbolError(target, source); + } + return target; + + function reportMergeSymbolError(target: Symbol, source: Symbol) { + 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}`, (): DuplicateInfoForFiles => ({ firstFile, secondFile, conflictingSymbols: new Map() })); + const conflictingSymbolInfo = getOrUpdate(filesDuplicates.conflictingSymbols, symbolName, (): DuplicateInfoForSymbol => ({ isBlockScoped: isEitherBlockScoped, firstFileLocations: [], secondFileLocations: [] })); + 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); + } + } + + 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 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 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; + } + + 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); + } + 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); + } + } + 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 addUndefinedToGlobalsOrErrorOnRedeclaration() { + const name = undefinedSymbol.escapedName; + const targetSymbol = globals.get(name); + if (targetSymbol) { + forEach(targetSymbol.declarations, declaration => { + // checkTypeNameIsReserved will have added better diagnostics for type declarations. + if (!isTypeDeclaration(declaration)) { + diagnostics.add(createDiagnosticForNode(declaration, Diagnostics.Declaration_name_conflicts_with_built_in_global_identifier_0, unescapeLeadingUnderscores(name))); + } + }); + } + else { + globals.set(name, undefinedSymbol); + } + } + + function getSymbolLinks(symbol: Symbol): SymbolLinks { + if (symbol.flags & SymbolFlags.Transient) return (symbol as TransientSymbol).links; + const id = getSymbolId(symbol); + return symbolLinks[id] ??= new SymbolLinks(); + } + + 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) { + if (symbol.flags & meaning) { + return symbol; + } + if (symbol.flags & SymbolFlags.Alias) { + const targetFlags = getSymbolFlags(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. + } + + /** + * 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: ParameterPropertyDeclaration, 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); + + if (parameterSymbol && propertySymbol) { + return [parameterSymbol, propertySymbol]; + } + + return Debug.fail("There should exist two symbols, one as property declaration and one as parameter declaration"); + } + + 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)) || + (!compilerOptions.outFile) || + 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); + } + + // deferred usage in a type context is always OK regardless of the usage position: + if (!!(usage.flags & NodeFlags.JSDoc) || isInTypeQuery(usage) || isInAmbientOrTypeNode(usage)) { + 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); + } + 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 (isClassLike(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]() {} }) + // or when used within a decorator in the class (e.g. `@dec(A.x) class A { static x = "x" }`), + // except when used in a function that is not an IIFE (e.g., `@dec(() => A.x) class A { ... }`) + const container = findAncestor(usage, n => + n === declaration ? "quit" : + isComputedPropertyName(n) ? n.parent.parent === declaration : + !legacyDecorators && isDecorator(n) && (n.parent === declaration || + isMethodDeclaration(n.parent) && n.parent.parent === declaration || + isGetOrSetAccessorDeclaration(n.parent) && n.parent.parent === declaration || + isPropertyDeclaration(n.parent) && n.parent.parent === declaration || + isParameter(n.parent) && n.parent.parent.parent === declaration)); + if (!container) { + return true; + } + if (!legacyDecorators && isDecorator(container)) { + return !!findAncestor(usage, n => n === container ? "quit" : isFunctionLike(n) && !getImmediatelyInvokedFunctionExpression(n)); + } + return false; + } + 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 emitStandardClassFields when bar is a parameter property + return !(emitStandardClassFields + && getContainingClass(declaration) === getContainingClass(usage) + && isUsedInFunctionOrInstanceProperty(usage, declaration)); + } + return true; + } + + // 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 emitStandardClassFields: 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) + 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 (isUsedInFunctionOrInstanceProperty(usage, declaration)) { + if ( + emitStandardClassFields + && getContainingClass(declaration) + && (isPropertyDeclaration(declaration) || isParameterPropertyDeclaration(declaration, declaration.parent)) + ) { + return !isPropertyImmediatelyReferencedWithinDeclaration(declaration, usage, /*stopAtAnyPropertyDeclaration*/ true); + } + else { + return true; + } + } + return false; + + 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); + } + + 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(getSymbolOfDeclaration(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; + } + } + } + } + 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; + } + } + + function getRequiresScopeChangeCache(node: FunctionLikeDeclaration) { + return getNodeLinks(node).declarationRequiresScopeChange; + } + function setRequiresScopeChangeCache(node: FunctionLikeDeclaration, value: boolean) { + getNodeLinks(node).declarationRequiresScopeChange = value; + } + // 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(errorLocation: Node | undefined, name: __String, propertyWithInvalidInitializer: PropertyDeclaration, result: Symbol | undefined) { + if (!emitStandardClassFields) { + if (errorLocation && !result && checkAndReportErrorForMissingPrefix(errorLocation, name, name)) { + return true; + } + // 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 emitStandardClassFields 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(name), + ); + return true; + } + return false; + } + function onFailedToResolveSymbol( + errorLocation: Node | undefined, + nameArg: __String | Identifier, + meaning: SymbolFlags, + nameNotFoundMessage: DiagnosticMessage, + ) { + const name = isString(nameArg) ? nameArg : (nameArg as Identifier).escapedText; + addLazyDiagnostic(() => { + if ( + !errorLocation || + errorLocation.parent.kind !== SyntaxKind.JSDocLink && + !checkAndReportErrorForMissingPrefix(errorLocation, name, nameArg) && + !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 && suggestionCount < maximumSuggestionCount) { + suggestion = getSuggestedSymbolForNonexistentSymbol(errorLocation, 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(errorLocation, 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); + diagnostic.canonicalHead = getCanonicalDiagnostic(nameNotFoundMessage, diagnosticName(nameArg)); + 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++; + } + }); + } + + function onSuccessfullyResolvedSymbol( + errorLocation: Node | undefined, + result: Symbol, + meaning: SymbolFlags, + lastLocation: Node | undefined, + associatedDeclarationForContainingInitializerOrBindingName: ParameterDeclaration | BindingElement | undefined, + withinDeferredContext: boolean, + ) { + addLazyDiagnostic(() => { + const name = result.escapedName; + const isInExternalModule = lastLocation && isSourceFile(lastLocation) && isExternalOrCommonJsModule(lastLocation); + // 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 (isInExternalModule && (meaning & SymbolFlags.Value) === SymbolFlags.Value && !(errorLocation!.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 (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 === getSymbolOfDeclaration(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 && getSymbol(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 (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 || typeOnlyDeclaration.kind === SyntaxKind.ExportDeclaration || typeOnlyDeclaration.kind === SyntaxKind.NamespaceExport + ? 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, + ); + } + } + + // Look at 'compilerOptions.isolatedModules' and not 'getIsolatedModules(...)' (which considers 'verbatimModuleSyntax') + // here because 'verbatimModuleSyntax' will already have an error for importing a type without 'import type'. + if (compilerOptions.isolatedModules && result && isInExternalModule && (meaning & SymbolFlags.Value) === SymbolFlags.Value) { + const isGlobal = getSymbol(globals, name, meaning) === result; + const nonValueSymbol = isGlobal && isSourceFile(lastLocation) && lastLocation.locals && getSymbol(lastLocation.locals, name, ~SymbolFlags.Value); + if (nonValueSymbol) { + const importDecl = nonValueSymbol.declarations?.find(d => d.kind === SyntaxKind.ImportSpecifier || d.kind === SyntaxKind.ImportClause || d.kind === SyntaxKind.NamespaceImport || d.kind === SyntaxKind.ImportEqualsDeclaration); + if (importDecl && !isTypeOnlyImportDeclaration(importDecl)) { + error(importDecl, Diagnostics.Import_0_conflicts_with_global_value_used_in_this_file_so_must_be_declared_with_a_type_only_import_when_isolatedModules_is_enabled, unescapeLeadingUnderscores(name)); + } + } + } + }); + } + + function addTypeOnlyDeclarationRelatedInfo(diagnostic: Diagnostic, typeOnlyDeclaration: TypeOnlyCompatibleAliasDeclaration | undefined, unescapedName: string) { + if (!typeOnlyDeclaration) return diagnostic; + return addRelatedInfo( + diagnostic, + createDiagnosticForNode( + typeOnlyDeclaration, + typeOnlyDeclaration.kind === SyntaxKind.ExportSpecifier || typeOnlyDeclaration.kind === SyntaxKind.ExportDeclaration || typeOnlyDeclaration.kind === SyntaxKind.NamespaceExport + ? Diagnostics._0_was_exported_here + : Diagnostics._0_was_imported_here, + unescapedName, + ), + ); + } + + function diagnosticName(nameArg: __String | Identifier | PrivateIdentifier) { + return isString(nameArg) ? unescapeLeadingUnderscores(nameArg as __String) : declarationNameToString(nameArg as Identifier); + } + + 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, /*includeClassComputedPropertyName*/ false); + let location: Node = container; + while (location) { + if (isClassLike(location.parent)) { + const classSymbol = getSymbolOfDeclaration(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; + } + + // 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; + } + + 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; + } + /** + * 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, /*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; + } + } + + 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, /*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 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 checkAndReportErrorForUsingTypeAsValue(errorLocation: Node, name: __String, meaning: SymbolFlags): boolean { + if (meaning & SymbolFlags.Value) { + if (isPrimitiveTypeName(name)) { + const grandparent = errorLocation.parent.parent; + if (grandparent && grandparent.parent && isHeritageClause(grandparent)) { + const heritageKind = grandparent.token; + const containerKind = grandparent.parent.kind; + if (containerKind === SyntaxKind.InterfaceDeclaration && heritageKind === SyntaxKind.ExtendsKeyword) { + error(errorLocation, Diagnostics.An_interface_cannot_extend_a_primitive_type_like_0_It_can_only_extend_other_named_object_types, unescapeLeadingUnderscores(name)); + } + else if (containerKind === SyntaxKind.ClassDeclaration && heritageKind === SyntaxKind.ExtendsKeyword) { + error(errorLocation, Diagnostics.A_class_cannot_extend_a_primitive_type_like_0_Classes_can_only_extend_constructable_values, unescapeLeadingUnderscores(name)); + } + else if (containerKind === SyntaxKind.ClassDeclaration && heritageKind === SyntaxKind.ImplementsKeyword) { + error(errorLocation, Diagnostics.A_class_cannot_implement_a_primitive_type_like_0_It_can_only_implement_other_named_object_types, 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, /*isUse*/ false)); + const allFlags = symbol && getSymbolFlags(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; + } + } + 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 checkAndReportErrorForUsingNamespaceAsTypeOrValue(errorLocation: Node, name: __String, meaning: SymbolFlags): boolean { + if (meaning & (SymbolFlags.Value & ~SymbolFlags.Type)) { + const symbol = resolveSymbol(resolveName(errorLocation, name, SymbolFlags.NamespaceModule, /*nameNotFoundMessage*/ 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, /*isUse*/ false)); + if (symbol) { + error(errorLocation, Diagnostics.Cannot_use_namespace_0_as_a_type, unescapeLeadingUnderscores(name)); + return true; + } + } + 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), + ); + + 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) { + diagnosticMessage = error(errorLocation, Diagnostics.Enum_0_used_before_its_declaration, declarationName); + } + else { + Debug.assert(!!(result.flags & SymbolFlags.ConstEnum)); + if (getIsolatedModules(compilerOptions)) { + diagnosticMessage = error(errorLocation, Diagnostics.Enum_0_used_before_its_declaration, 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) || (getFunctionFlags(n) & FunctionFlags.AsyncGenerator)) ? "quit" : false)); + } + + function getAnyImportSyntax(node: Node): AnyImportOrJsDocImport | 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); + } + + /** + * 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; + } + + function checkAndReportErrorForResolvingImportAliasToTypeOnlySymbol(node: ImportEqualsDeclaration, resolved: Symbol | undefined) { + if (markSymbolOfAliasDeclarationIfTypeOnly(node, /*immediateTarget*/ undefined, resolved, /*overwriteEmpty*/ false) && !node.isTypeOnly) { + const typeOnlyDeclaration = getTypeOnlyAliasDeclaration(getSymbolOfDeclaration(node))!; + const isExport = typeOnlyDeclaration.kind === SyntaxKind.ExportSpecifier || typeOnlyDeclaration.kind === SyntaxKind.ExportDeclaration; + 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; + + // TODO: how to get name for export *? + const name = typeOnlyDeclaration.kind === SyntaxKind.ExportDeclaration ? "*" : moduleExportNameTextUnescaped(typeOnlyDeclaration.name); + addRelatedInfo(error(node.moduleReference, message), createDiagnosticForNode(typeOnlyDeclaration, relatedMessage, name)); + } + } + + 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, /*skipObjectFunctionPropertyAugment*/ true) + : moduleSymbol.exports!.get(name); + const resolved = resolveSymbol(exportSymbol, dontResolveAlias); + markSymbolOfAliasDeclarationIfTypeOnly(sourceNode, exportSymbol, resolved, /*overwriteEmpty*/ false); + return resolved; + } + + function isSyntacticDefault(node: Node) { + return ((isExportAssignment(node) && !node.isExportEquals) + || hasSyntacticModifier(node, ModifierFlags.Default) + || isExportSpecifier(node) + || isNamespaceExport(node)); + } + + function getUsageModeForExpression(usage: Expression) { + return isStringLiteralLike(usage) ? host.getModeForUsageLocation(getSourceFileOfNode(usage), usage) : undefined; + } + + function isESMFormatImportImportingCommonjsFormatFile(usageMode: ResolutionMode, targetMode: ResolutionMode) { + 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 && ModuleKind.Node16 <= moduleKind && moduleKind <= ModuleKind.NodeNext) { + 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 + } + 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; + } + // 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); + } + // 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 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) && !allowSyntheticDefaultImports) { + 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, /*overwriteEmpty*/ false); + return resolved; + } + markSymbolOfAliasDeclarationIfTypeOnly(node, exportDefaultSymbol, /*finalTarget*/ undefined, /*overwriteEmpty*/ false); + 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); + } + } + + 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 { + 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 getTargetOfNamespaceImport(node: NamespaceImport, dontResolveAlias: boolean): Symbol | undefined { + const moduleSpecifier = node.parent.parent.moduleSpecifier; + const immediate = resolveExternalModuleName(node, moduleSpecifier); + const resolved = resolveESModuleSymbol(immediate, moduleSpecifier, dontResolveAlias, /*suppressInteropError*/ 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, /*suppressInteropError*/ false); + markSymbolOfAliasDeclarationIfTypeOnly(node, immediate, resolved, /*overwriteEmpty*/ false); + return resolved; + } + + // 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); + Debug.assert(valueSymbol.declarations || typeSymbol.declarations); + 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 getExportOfModule(symbol: Symbol, nameText: __String, specifier: Declaration, dontResolveAlias: boolean): Symbol | undefined { + if (symbol.flags & SymbolFlags.Module) { + const exportSymbol = getExportsOfSymbol(symbol).get(nameText); + const resolved = resolveSymbol(exportSymbol, dontResolveAlias); + const exportStarDeclaration = getSymbolLinks(symbol).typeOnlyExportStarMap?.get(nameText); + markSymbolOfAliasDeclarationIfTypeOnly(specifier, exportSymbol, resolved, /*overwriteEmpty*/ false, exportStarDeclaration, nameText); + 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 getExternalModuleMember(node: ImportDeclaration | ExportDeclaration | VariableDeclaration | JSDocImportTag, specifier: ImportOrExportSpecifier | BindingElement | PropertyAccessExpression, dontResolveAlias = false): Symbol | undefined { + const moduleSpecifier = getExternalModuleRequireArgument(node) || (node as ImportDeclaration | ExportDeclaration | JSDocImportTag).moduleSpecifier!; + const moduleSymbol = resolveExternalModuleName(node, moduleSpecifier)!; // TODO: GH#18217 + const name = !isPropertyAccessExpression(specifier) && specifier.propertyName || specifier.name; + if (!isIdentifier(name) && name.kind !== SyntaxKind.StringLiteral) { + return undefined; + } + const nameText = moduleExportNameTextEscaped(name); + const suppressInteropError = nameText === InternalSymbolName.Default && allowSyntheticDefaultImports; + const targetSymbol = resolveESModuleSymbol(moduleSymbol, moduleSpecifier, /*dontResolveAlias*/ false, suppressInteropError); + if (targetSymbol) { + // Note: The empty string is a valid module export name: + // + // import { "" as foo } from "./foo"; + // export { foo as "" }; + // + if (nameText || name.kind === SyntaxKind.StringLiteral) { + if (isShorthandAmbientModuleSymbol(moduleSymbol)) { + return moduleSymbol; + } + + 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), nameText, /*skipObjectFunctionPropertyAugment*/ true); + } + else { + symbolFromVariable = getPropertyOfVariable(targetSymbol, nameText); + } + // if symbolFromVariable is export - get its final target + symbolFromVariable = resolveSymbol(symbolFromVariable, dontResolveAlias); + + let symbolFromModule = getExportOfModule(targetSymbol, nameText, specifier, dontResolveAlias); + if (symbolFromModule === undefined && nameText === 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: ModuleExportName) { + const moduleName = getFullyQualifiedName(moduleSymbol, node); + const declarationName = declarationNameToString(name); + const suggestion = isIdentifier(name) ? getSuggestedSymbolForNonexistentModule(name, targetSymbol) : undefined; + 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 { + reportNonExportedMember(node, name, declarationName, moduleSymbol, moduleName); + } + } + } + + function reportNonExportedMember(node: Node, name: ModuleExportName, declarationName: string, moduleSymbol: Symbol, moduleName: string): void { + const localSymbol = tryCast(moduleSymbol.valueDeclaration, canHaveLocals)?.locals?.get(moduleExportNameTextEscaped(name)); + 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))); + } + } + } + else { + error(name, Diagnostics.Module_0_has_no_exported_member_1, moduleName, declarationName); + } + } + + function reportInvalidImportEqualsExportMember(node: Node, name: ModuleExportName, 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); + } + 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); + } + } + } + + function getTargetOfImportSpecifier(node: ImportSpecifier | BindingElement, dontResolveAlias: boolean): Symbol | undefined { + if (isImportSpecifier(node) && moduleExportNameIsDefault(node.propertyName || node.name)) { + const specifier = getModuleSpecifierForImportOrExport(node); + const moduleSymbol = specifier && resolveExternalModuleName(node, specifier); + if (moduleSymbol) { + return getTargetofModuleDefault(moduleSymbol, node, dontResolveAlias); + } + } + 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 getCommonJSPropertyAccess(node: Node) { + if (isVariableDeclaration(node) && node.initializer && isPropertyAccessExpression(node.initializer)) { + return node.initializer; + } + } + + function getTargetOfNamespaceExportDeclaration(node: NamespaceExportDeclaration, dontResolveAlias: boolean): Symbol | undefined { + if (canHaveSymbol(node.parent)) { + const resolved = resolveExternalModuleSymbol(node.parent.symbol, dontResolveAlias); + markSymbolOfAliasDeclarationIfTypeOnly(node, /*immediateTarget*/ undefined, resolved, /*overwriteEmpty*/ false); + return resolved; + } + } + + function getTargetOfExportSpecifier(node: ExportSpecifier, meaning: SymbolFlags, dontResolveAlias?: boolean) { + const name = node.propertyName || node.name; + if (moduleExportNameIsDefault(name)) { + 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) : + name.kind === SyntaxKind.StringLiteral ? undefined : // Skip for invalid syntax like this: export { "x" } + resolveEntityName(name, meaning, /*ignoreErrors*/ false, dontResolveAlias); + markSymbolOfAliasDeclarationIfTypeOnly(node, /*immediateTarget*/ undefined, resolved, /*overwriteEmpty*/ false); + return resolved; + } + + 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 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; + } + checkExpressionCached(expression); + return getNodeLinks(expression).resolvedSymbol; + } + + function getTargetOfAccessExpression(node: AccessExpression, dontRecursivelyResolve: boolean): Symbol | undefined { + if (!(isBinaryExpression(node.parent) && node.parent.left === node && node.parent.operatorToken.kind === SyntaxKind.EqualsToken)) { + return undefined; + } + + 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(); + } + } + + /** + * 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 { + error(node, Diagnostics.Circular_definition_of_import_alias_0, symbolToString(symbol)); + } + } + 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; + } + + /** + * 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 getSymbolFlags(symbol: Symbol, excludeTypeOnlyMeanings?: boolean, excludeLocalMeanings?: boolean): SymbolFlags { + const typeOnlyDeclaration = excludeTypeOnlyMeanings && getTypeOnlyAliasDeclaration(symbol); + const typeOnlyDeclarationIsExportStar = typeOnlyDeclaration && isExportDeclaration(typeOnlyDeclaration); + const typeOnlyResolution = typeOnlyDeclaration && ( + typeOnlyDeclarationIsExportStar + ? resolveExternalModuleName(typeOnlyDeclaration.moduleSpecifier, typeOnlyDeclaration.moduleSpecifier, /*ignoreErrors*/ true) + : resolveAlias(typeOnlyDeclaration.symbol) + ); + const typeOnlyExportStarTargets = typeOnlyDeclarationIsExportStar && typeOnlyResolution ? getExportsOfModule(typeOnlyResolution) : undefined; + let flags = excludeLocalMeanings ? SymbolFlags.None : symbol.flags; + let seenSymbols; + while (symbol.flags & SymbolFlags.Alias) { + const target = getExportSymbolOfValueSymbolIfExported(resolveAlias(symbol)); + if ( + !typeOnlyDeclarationIsExportStar && target === typeOnlyResolution || + typeOnlyExportStarTargets?.get(target.escapedName) === target + ) { + break; + } + if (target === unknownSymbol) { + return SymbolFlags.All; + } + + // 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); + } + 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, + exportStarDeclaration?: ExportDeclaration & { readonly isTypeOnly: true; readonly moduleSpecifier: Expression; }, + exportStarName?: __String, + ): 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 = getSymbolOfDeclaration(aliasDeclaration); + if (isTypeOnlyImportOrExportDeclaration(aliasDeclaration)) { + const links = getSymbolLinks(sourceSymbol); + links.typeOnlyDeclaration = aliasDeclaration; + return true; + } + if (exportStarDeclaration) { + const links = getSymbolLinks(sourceSymbol); + links.typeOnlyDeclaration = exportStarDeclaration; + if (sourceSymbol.escapedName !== exportStarName) { + links.typeOnlyExportStarName = exportStarName; + } + return true; + } + + 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 (links.typeOnlyDeclaration === undefined) { + // We need to set a WIP value here to prevent reentrancy during `getImmediateAliasedSymbol` which, paradoxically, can depend on this + links.typeOnlyDeclaration = false; + const resolved = resolveSymbol(symbol); // do this before the `resolveImmediate` below, as it uses a different circularity cache and we might hide a circularity error if we blindly get the immediate alias first + // While usually the alias will have been marked during the pass by the full typecheck, we may still need to calculate the alias declaration now + markSymbolOfAliasDeclarationIfTypeOnly(symbol.declarations?.[0], getDeclarationOfAliasSymbol(symbol) && getImmediateAliasedSymbol(symbol), resolved, /*overwriteEmpty*/ true); + } + if (include === undefined) { + return links.typeOnlyDeclaration || undefined; + } + if (links.typeOnlyDeclaration) { + const resolved = links.typeOnlyDeclaration.kind === SyntaxKind.ExportDeclaration + ? resolveSymbol(getExportsOfModule(links.typeOnlyDeclaration.symbol.parent!).get(links.typeOnlyExportStarName || symbol.escapedName))! + : resolveAlias(links.typeOnlyDeclaration.symbol); + return getSymbolFlags(resolved) & include ? links.typeOnlyDeclaration : undefined; + } + return undefined; + } + + // 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 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, SymbolFlags.Value, /*nameNotFoundMessage*/ undefined, /*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; + } + 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)) { + 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, meaning, ignoreErrors || symbolFromJSPrototype ? undefined : message, /*isUse*/ true, /*excludeGlobals*/ 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; + } + else if (namespace === unknownSymbol) { + return namespace; + } + if ( + namespace.valueDeclaration && + isInJSFile(namespace.valueDeclaration) && + getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.Bundler && + 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 && (namespace.flags & SymbolFlags.Alias)) { + // `namespace` can be resolved further if there was a symbol merge with a re-export + symbol = getMergedSymbol(getSymbol(getExportsOfSymbol(resolveAlias(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; + } + + 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; + } + } + else { + Debug.assertNever(name, "Unknown entity name kind."); + } + 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, meaning, /*nameNotFoundMessage*/ undefined, /*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 = getSymbolOfDeclaration(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 = getSymbolOfDeclaration(host.parent.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 = getSymbolOfDeclaration(host.parent.parent.left as BindableStaticNameExpression); + if (symbol) { + return getDeclarationOfJSPrototypeContainer(symbol); + } + } + const sig = getEffectiveJSDocHost(node); + if (sig && isFunctionLike(sig)) { + const symbol = getSymbolOfDeclaration(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; + } + + /** + * 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_nodenext_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 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 + : (isModuleDeclaration(location) ? location : location.parent && isModuleDeclaration(location.parent) && location.parent.name === location ? location.parent : undefined)?.name || + (isLiteralImportTypeNode(location) ? location : undefined)?.argument.literal || + (isInJSFile(location) && isJSDocImportTag(location) ? location.moduleSpecifier : undefined) || + (isVariableDeclaration(location) && location.initializer && isRequireCall(location.initializer, /*requireStringLiteralLikeArgument*/ true) ? location.initializer.arguments[0] : undefined) || + findAncestor(location, isImportCall)?.arguments[0] || + findAncestor(location, isImportDeclaration)?.moduleSpecifier || + findAncestor(location, isExternalModuleImportEqualsDeclaration)?.moduleReference.expression || + findAncestor(location, isExportDeclaration)?.moduleSpecifier; + const mode = contextSpecifier && isStringLiteralLike(contextSpecifier) ? host.getModeForUsageLocation(currentSourceFile, contextSpecifier) : currentSourceFile.impliedNodeFormat; + const moduleResolutionKind = getEmitModuleResolutionKind(compilerOptions); + const resolvedModule = host.getResolvedModule(currentSourceFile, moduleReference, mode)?.resolvedModule; + const resolutionDiagnostic = resolvedModule && getResolutionDiagnostic(compilerOptions, resolvedModule, currentSourceFile); + 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 (resolvedModule.resolvedUsingTsExtension && isDeclarationFileName(moduleReference)) { + const importOrExport = findAncestor(location, isImportDeclaration)?.importClause || + findAncestor(location, or(isImportEqualsDeclaration, isExportDeclaration)); + if (importOrExport && !importOrExport.isTypeOnly || findAncestor(location, isImportCall)) { + error( + errorNode, + Diagnostics.A_declaration_file_cannot_be_imported_without_import_type_Did_you_mean_to_import_an_implementation_file_0_instead, + getSuggestedImportSource(Debug.checkDefined(tryExtractTSExtension(moduleReference))), + ); + } + } + else if (resolvedModule.resolvedUsingTsExtension && !shouldAllowImportingTsExtension(compilerOptions, currentSourceFile.fileName)) { + const importOrExport = findAncestor(location, isImportDeclaration)?.importClause || + findAncestor(location, or(isImportEqualsDeclaration, isExportDeclaration)); + if (!(importOrExport?.isTypeOnly || findAncestor(location, isImportTypeNode))) { + const tsExtension = Debug.checkDefined(tryExtractTSExtension(moduleReference)); + error(errorNode, Diagnostics.An_import_path_can_only_end_with_a_0_extension_when_allowImportingTsExtensions_is_enabled, tsExtension); + } + } + + if (sourceFile.symbol) { + if (resolvedModule.isExternalLibraryImport && !resolutionExtensionIsTSOrJson(resolvedModule.extension)) { + errorOnImplicitAnyModule(/*isError*/ false, errorNode, currentSourceFile, mode, resolvedModule, moduleReference); + } + if (moduleResolutionKind === ModuleResolutionKind.Node16 || moduleResolutionKind === ModuleResolutionKind.NodeNext) { + const isSyncImport = (currentSourceFile.impliedNodeFormat === ModuleKind.CommonJS && !findAncestor(location, isImportCall)) || !!findAncestor(location, isImportEqualsDeclaration); + const overrideHost = findAncestor(location, l => isImportTypeNode(l) || isExportDeclaration(l) || isImportDeclaration(l)) as ImportTypeNode | ImportDeclaration | ExportDeclaration | undefined; + // 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 && !hasResolutionModeOverride(overrideHost)) { + 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 { + 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( + getSourceFileOfNode(errorNode), + 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; + } + + 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, currentSourceFile, mode, 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; + } + } + + if (resolutionDiagnostic) { + error(errorNode, resolutionDiagnostic, moduleReference, resolvedModule.resolvedFileName); + } + else { + const isExtensionlessRelativePathImport = pathIsRelative(moduleReference) && !hasExtension(moduleReference); + const resolutionIsNode16OrNext = moduleResolutionKind === ModuleResolutionKind.Node16 || + moduleResolutionKind === ModuleResolutionKind.NodeNext; + if ( + !getResolveJsonModule(compilerOptions) && + fileExtensionIs(moduleReference, Extension.Json) && + moduleResolutionKind !== 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 (host.getResolvedModule(currentSourceFile, moduleReference, mode)?.alternateResult) { + const errorInfo = createModuleNotFoundChain(currentSourceFile, host, moduleReference, mode, moduleReference); + errorOrSuggestion(/*isError*/ true, errorNode, chainDiagnosticMessages(errorInfo, moduleNotFoundError, moduleReference)); + } + else { + error(errorNode, moduleNotFoundError, moduleReference); + } + } + } + } + return undefined; + + function getSuggestedImportSource(tsExtension: string) { + const importSourceWithoutExtension = removeExtension(moduleReference, tsExtension); + /** + * Direct users to import source with .js extension if outputting an ES module. + * @see https://github.com/microsoft/TypeScript/issues/42151 + */ + if (emitModuleKindIsNonNodeESM(moduleKind) || mode === ModuleKind.ESNext) { + const preferTs = isDeclarationFileName(moduleReference) && shouldAllowImportingTsExtension(compilerOptions); + const ext = tsExtension === Extension.Mts || tsExtension === Extension.Dmts ? preferTs ? ".mts" : ".mjs" : + tsExtension === Extension.Cts || tsExtension === Extension.Dmts ? preferTs ? ".cts" : ".cjs" : + preferTs ? ".ts" : ".js"; + return importSourceWithoutExtension + ext; + } + return importSourceWithoutExtension; + } + } + + function errorOnImplicitAnyModule(isError: boolean, errorNode: Node, sourceFile: SourceFile, mode: ResolutionMode, { packageId, resolvedFileName }: ResolvedModuleFull, moduleReference: string): void { + let errorInfo: DiagnosticMessageChain | undefined; + if (!isExternalModuleNameRelative(moduleReference) && packageId) { + errorInfo = createModuleNotFoundChain(sourceFile, host, moduleReference, mode, packageId.name); + } + errorOrSuggestion( + isError, + errorNode, + chainDiagnosticMessages( + errorInfo, + Diagnostics.Could_not_find_a_declaration_file_for_module_0_1_implicitly_has_an_any_type, + moduleReference, + resolvedFileName, + ), + ); + } + + function resolveExternalModuleSymbol(moduleSymbol: Symbol, dontResolveAlias?: boolean): Symbol; + function resolveExternalModuleSymbol(moduleSymbol: Symbol | undefined, dontResolveAlias?: boolean): Symbol | undefined; + function resolveExternalModuleSymbol(moduleSymbol: Symbol | undefined, 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); + }); + if (merged === exported) { + // We just mutated a symbol, reset any cached links we may have already set + // (Notably required to make late bound members appear) + getSymbolLinks(merged).resolvedExports = undefined; + getSymbolLinks(merged).resolvedMembers = undefined; + } + getSymbolLinks(merged).cjsExportMerged = merged; + return links.cjsExportMerged = merged; + } + + // 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 = type.flags & TypeFlags.StructuredType + ? getTypeWithSyntheticDefaultImportType(type, symbol, moduleSymbol!, reference) + : createDefaultPropertyWrapperForModule(symbol, symbol.parent); + return cloneTypeAsModuleType(symbol, moduleType, referenceParent); + } + } + } + } + return symbol; + } + + /** + * 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.links.target = symbol; + result.links.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.links.type = createAnonymousType(result, resolvedModuleType.members, emptyArray, emptyArray, resolvedModuleType.indexInfos); + return result; + } + + function hasExportAssignmentSymbol(moduleSymbol: Symbol): boolean { + return moduleSymbol.exports!.get(InternalSymbolName.ExportEquals) !== 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); + if (shouldTreatPropertiesOfExternalModuleAsExports(type)) { + addRange(exports, getPropertiesOfType(type)); + } + } + 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 exportEquals = resolveExternalModuleSymbol(moduleSymbol); + if (exportEquals !== moduleSymbol) { + const type = getTypeOfSymbol(exportEquals); + if (shouldTreatPropertiesOfExternalModuleAsExports(type)) { + forEachPropertyOfType(type, (symbol, escapedName) => { + cb(symbol, escapedName); + }); + } + } + } + + function tryGetMemberInModuleExports(memberName: __String, moduleSymbol: Symbol): Symbol | undefined { + const symbolTable = getExportsOfModule(moduleSymbol); + if (symbolTable) { + return symbolTable.get(memberName); + } + } + + function tryGetMemberInModuleExportsAndProperties(memberName: __String, moduleSymbol: Symbol): Symbol | undefined { + const symbol = tryGetMemberInModuleExports(memberName, moduleSymbol); + if (symbol) { + return symbol; + } + + const exportEquals = resolveExternalModuleSymbol(moduleSymbol); + if (exportEquals === moduleSymbol) { + return undefined; + } + + const type = getTypeOfSymbol(exportEquals); + return shouldTreatPropertiesOfExternalModuleAsExports(type) ? getPropertyOfType(type, memberName) : undefined; + } + + 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 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); + if (!links.resolvedExports) { + const { exports, typeOnlyExportStarMap } = getExportsOfModuleWorker(moduleSymbol); + links.resolvedExports = exports; + links.typeOnlyExportStarMap = typeOnlyExportStarMap; + } + return links.resolvedExports; + } + + interface ExportCollisionTracker { + specifierText: string; + exportsWithDuplicate?: ExportDeclaration[]; + } + + type ExportCollisionTrackerTable = Map<__String, ExportCollisionTracker>; + + /** + * 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; + + const targetSymbol = target.get(id); + if (!targetSymbol) { + target.set(id, sourceSymbol); + if (lookupTable && exportNode) { + lookupTable.set(id, { + specifierText: getTextOfNode(exportNode.moduleSpecifier!), + }); + } + } + 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 getExportsOfModuleWorker(moduleSymbol: Symbol) { + const visitedSymbols: Symbol[] = []; + let typeOnlyExportStarMap: Map<__String, ExportDeclaration & { readonly isTypeOnly: true; readonly moduleSpecifier: Expression; }> | undefined; + const nonTypeOnlyNames = new Set<__String>(); + + // A module defined by an 'export=' consists of one export that needs to be resolved + moduleSymbol = resolveExternalModuleSymbol(moduleSymbol); + const exports = visit(moduleSymbol) || emptySymbols; + + if (typeOnlyExportStarMap) { + nonTypeOnlyNames.forEach(name => typeOnlyExportStarMap!.delete(name)); + } + + return { + exports, + typeOnlyExportStarMap, + }; + + // 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, exportStar?: ExportDeclaration, isTypeOnly?: boolean): SymbolTable | undefined { + if (!isTypeOnly && symbol?.exports) { + // Add non-type-only names before checking if we've visited this module, + // because we might have visited it via an 'export type *', and visiting + // again with 'export *' will override the type-onlyness of its exports. + symbol.exports.forEach((_, name) => nonTypeOnlyNames.add(name)); + } + if (!(symbol && symbol.exports && pushIfUnique(visitedSymbols, symbol))) { + return; + } + 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, node as ExportDeclaration, isTypeOnly || (node as ExportDeclaration).isTypeOnly); + 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); + } + if (exportStar?.isTypeOnly) { + typeOnlyExportStarMap ??= new Map(); + symbols.forEach((_, escapedName) => + typeOnlyExportStarMap!.set( + escapedName, + exportStar as ExportDeclaration & { readonly isTypeOnly: true; readonly moduleSpecifier: Expression; }, + ) + ); + } + 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 getSymbolOfDeclaration(node: Declaration): Symbol { + return getMergedSymbol(node.symbol && getLateBoundSymbol(node.symbol)); + } + + /** + * Get the merged symbol for a node. If you know the node is a `Declaration`, it is faster and more type safe to + * use use `getSymbolOfDeclaration` instead. + */ + function getSymbolOfNode(node: Node): Symbol | undefined { + return canHaveSymbol(node) ? getSymbolOfDeclaration(node) : undefined; + } + + function getParentOfSymbol(symbol: Symbol): Symbol | undefined { + return getMergedSymbol(symbol.parent && getLateBoundSymbol(symbol.parent)); + } + + function getFunctionExpressionParentSymbolOrSymbol(symbol: Symbol) { + return symbol.valueDeclaration?.kind === SyntaxKind.ArrowFunction || symbol.valueDeclaration?.kind === SyntaxKind.FunctionExpression + ? getSymbolOfNode(symbol.valueDeclaration.parent) || symbol + : symbol; + } + + 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 = getSymbolOfDeclaration(file); + const ref = getAliasForSymbolInContainer(sym, symbol); + if (!ref) continue; + results = append(results, sym); + } + return links.extendedContainers = results || emptyArray; + } + + /** + * 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)) { + return getWithAlternativeContainers(container); + } + const candidates = mapDefined(symbol.declarations, d => { + if (!isAmbientModule(d) && d.parent) { + // direct children of a module + if (hasNonGlobalAugmentationExternalModuleSymbol(d.parent)) { + return getSymbolOfDeclaration(d.parent as Declaration); + } + // export ='d member of an ambient module + if (isModuleBlock(d.parent) && d.parent.parent && resolveExternalModuleSymbol(getSymbolOfDeclaration(d.parent.parent)) === symbol) { + return getSymbolOfDeclaration(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 getSymbolOfDeclaration(getSourceFileOfNode(d)); + } + checkExpressionCached(d.parent.left.expression); + return getNodeLinks(d.parent.left.expression).resolvedSymbol; + } + }); + if (!length(candidates)) { + return undefined; + } + const containers = mapDefined(candidates, candidate => getAliasForSymbolInContainer(candidate, symbol) ? candidate : undefined); + + let bestContainers: Symbol[] = []; + let alternativeContainers: Symbol[] = []; + + for (const container of containers) { + const [bestMatch, ...rest] = getWithAlternativeContainers(container); + bestContainers = append(bestContainers, bestMatch); + alternativeContainers = addRange(alternativeContainers, rest); + } + + return concatenate(bestContainers, alternativeContainers); + + function getWithAlternativeContainers(container: Symbol) { + 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, /*useOnlyExternalAliasing*/ 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 fileSymbolIfFileSymbolExportEqualsContainer(d: Declaration) { + return container && getFileSymbolIfFileSymbolExportEqualsContainer(d, container); + } + } + + 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 getSymbolOfDeclaration(firstDecl.parent); + } + } + } + + 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 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; + } + 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; + } + }); + } + + /** + * 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; + } + } + + 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 symbolIsValue(symbol: Symbol, includeTypeOnlyMembers?: boolean): boolean { + return !!( + symbol.flags & SymbolFlags.Value || + symbol.flags & SymbolFlags.Alias && getSymbolFlags(symbol, !includeTypeOnlyMembers) & SymbolFlags.Value + ); + } + + function createType(flags: TypeFlags): Type { + const result = new Type(checker, flags); + typeCount++; + result.id = typeCount; + tracing?.recordType(result); + return result; + } + + function createTypeWithSymbol(flags: TypeFlags, symbol: Symbol): Type { + const result = createType(flags); + result.symbol = symbol; + return result; + } + + function createOriginType(flags: TypeFlags): Type { + return new Type(checker, flags); + } + + function createIntrinsicType(kind: TypeFlags, intrinsicName: string, objectFlags = ObjectFlags.None, debugIntrinsicName?: string): IntrinsicType { + checkIntrinsicName(intrinsicName, debugIntrinsicName); + const type = createType(kind) as IntrinsicType; + type.intrinsicName = intrinsicName; + type.debugIntrinsicName = debugIntrinsicName; + type.objectFlags = objectFlags | ObjectFlags.CouldContainTypeVariablesComputed | ObjectFlags.IsGenericTypeComputed | ObjectFlags.IsUnknownLikeUnionComputed | ObjectFlags.IsNeverIntersectionComputed; + return type; + } + + function checkIntrinsicName(name: string, debug: string | undefined) { + const key = `${name},${debug ?? ""}`; + if (seenIntrinsicNames.has(key)) { + Debug.fail(`Duplicate intrinsic type name ${name}${debug ? ` (${debug})` : ""}; you may need to pass a name to createIntrinsicType.`); + } + seenIntrinsicNames.add(key); + } + + 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; + } + + function createTypeofType() { + return getUnionType(arrayFrom(typeofNEFacts.keys(), getStringLiteralType)); + } + + function createTypeParameter(symbol?: Symbol) { + return createTypeWithSymbol(TypeFlags.TypeParameter, symbol!) as TypeParameter; + } + + // 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 getNamedMembers(members: SymbolTable): Symbol[] { + let result: Symbol[] | undefined; + members.forEach((symbol, id) => { + if (isNamedMember(symbol, id)) { + (result || (result = [])).push(symbol); + } + }); + return result || emptyArray; + } + + function isNamedMember(member: Symbol, escapedName: __String) { + return !isReservedMemberName(escapedName) && symbolIsValue(member); + } + + function getNamedOrIndexSignatureMembers(members: SymbolTable): Symbol[] { + const result = getNamedMembers(members); + const index = getIndexSymbolFromSymbolTable(members); + return index ? concatenate(result, [index]) : 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 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 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 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 (canHaveLocals(location) && 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 = getSymbolOfDeclaration(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: Map<__String, Symbol> | undefined; + // TODO: Should this filtered table be cached in some way? + (getSymbolOfDeclaration(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; + } + } + + return callback(globals, /*ignoreQualification*/ undefined, /*isLocalNameLookup*/ true); + } + + 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 getAccessibleSymbolChain(symbol: Symbol | undefined, enclosingDeclaration: Node | undefined, meaning: SymbolFlags, useOnlyExternalAliasing: boolean, visitedSymbolTablesMap = 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); + } + + 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; + + /** + * @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; + } + + const result = trySymbolTable(symbols, ignoreQualification, isLocalNameLookup); + visitedSymbolTables!.pop(); + return result; + } + + 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)) + ) { + 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), /*resolvedAliasSymbol*/ undefined, ignoreQualification)) { + return [symbol!]; + } + } + }); + + // 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 getCandidateListForSymbol(symbolFromSymbolTable: Symbol, resolvedImportedSymbol: Symbol, ignoreQualification: boolean | undefined) { + if (isAccessible(symbolFromSymbolTable, resolvedImportedSymbol, ignoreQualification)) { + return [symbolFromSymbolTable]; + } + + // 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 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 ? getSymbolFlags(symbolFromSymbolTable) : symbolFromSymbolTable.flags; + if (flags & meaning) { + qualify = true; + return true; + } + + // 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; + } + + 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, + }; + } + } + + // 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 + + const containers = getContainersOfSymbol(symbol, enclosingDeclaration, meaning); + const parentResult = isAnySymbolAccessible(containers, enclosingDeclaration, initialSymbol, initialSymbol === symbol ? getQualifiedLeftMeaning(meaning) : meaning, shouldComputeAliasesToMakeVisible, allowModules); + if (parentResult) { + return parentResult; + } + } + + if (earlyModuleBail) { + return { + accessibility: SymbolAccessibility.Accessible, + }; + } + + if (hadAccessibleChain) { + return { + accessibility: SymbolAccessibility.NotAccessible, + errorSymbolName: symbolToString(initialSymbol, enclosingDeclaration, meaning), + errorModuleName: hadAccessibleChain !== initialSymbol ? symbolToString(hadAccessibleChain, enclosingDeclaration, SymbolFlags.Namespace) : undefined, + }; + } + } + + /** + * 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); + } + + 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; + } + + // 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, + }; + } + } + + // Just a local name that is not accessible + return { + accessibility: SymbolAccessibility.NotAccessible, + errorSymbolName: symbolToString(symbol, enclosingDeclaration, meaning), + }; + } + + return { accessibility: SymbolAccessibility.Accessible }; + } + + function getExternalModuleContainer(declaration: Node) { + const node = findAncestor(declaration, hasExternalModuleSymbol); + return node && getSymbolOfDeclaration(node as AmbientModuleDeclaration | SourceFile); + } + + 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); + } + } + + // Declaration is not visible + return false; + } + + 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 getMeaningOfEntityNameReference(entityName: EntityNameOrEntityNameExpression): SymbolFlags { + // 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 || + entityName.parent.kind === SyntaxKind.TypePredicate && (entityName.parent as TypePredicateNode).parameterName === entityName + ) { + // Typeof value + meaning = SymbolFlags.Value | SymbolFlags.ExportValue; + } + else if ( + entityName.kind === SyntaxKind.QualifiedName || entityName.kind === SyntaxKind.PropertyAccessExpression || + entityName.parent.kind === SyntaxKind.ImportEqualsDeclaration || + (entityName.parent.kind === SyntaxKind.QualifiedName && (entityName.parent as QualifiedName).left === entityName) || + (entityName.parent.kind === SyntaxKind.PropertyAccessExpression && (entityName.parent as PropertyAccessExpression).expression === entityName) || + (entityName.parent.kind === SyntaxKind.ElementAccessExpression && (entityName.parent as ElementAccessExpression).expression === entityName) + ) { + // 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 meaning; + } + + function isEntityNameVisible(entityName: EntityNameOrEntityNameExpression, enclosingDeclaration: Node, shouldComputeAliasToMakeVisible = true): SymbolVisibilityResult { + const meaning = getMeaningOfEntityNameReference(entityName); + const firstIdentifier = getFirstIdentifier(entityName); + const symbol = resolveName(enclosingDeclaration, firstIdentifier.escapedText, meaning, /*nameNotFoundMessage*/ undefined, /*isUse*/ false); + if (symbol && symbol.flags & SymbolFlags.TypeParameter && meaning & SymbolFlags.Type) { + return { accessibility: SymbolAccessibility.Accessible }; + } + if (!symbol && isThisIdentifier(firstIdentifier) && isSymbolAccessible(getSymbolOfDeclaration(getThisContainer(firstIdentifier, /*includeArrowFunctions*/ false, /*includeClassComputedPropertyName*/ false)), firstIdentifier, meaning, /*shouldComputeAliasesToMakeVisible*/ false).accessibility === SymbolAccessibility.Accessible) { + return { accessibility: SymbolAccessibility.Accessible }; + } + + if (!symbol) { + return { + accessibility: SymbolAccessibility.NotResolved, + errorSymbolName: getTextOfNode(firstIdentifier), + errorNode: firstIdentifier, + }; + } + // Verify if the symbol is accessible + return hasVisibleDeclarations(symbol, shouldComputeAliasToMakeVisible) || { + 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 symbolToStringWorker(writer: EmitTextWriter) { + const entity = builder(symbol, meaning!, enclosingDeclaration, nodeFlags)!; // TODO: GH#18217 + // add neverAsciiEscape for GH#39027 + const printer = enclosingDeclaration?.kind === SyntaxKind.SourceFile + ? createPrinterWithRemoveCommentsNeverAsciiEscape() + : createPrinterWithRemoveComments(); + const sourceFile = enclosingDeclaration && getSourceFileOfNode(enclosingDeclaration); + printer.writeNode(EmitHint.Unspecified, entity, /*sourceFile*/ sourceFile, writer); + return writer; + } + } + + 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; + } + const sig = nodeBuilder.signatureToSignatureDeclaration(signature, sigOutput, enclosingDeclaration, toNodeBuilderFlags(flags) | NodeBuilderFlags.IgnoreErrors | NodeBuilderFlags.WriteTypeParametersInQualifiedName); + const printer = createPrinterWithRemoveCommentsOmitTrailingSemicolon(); + const sourceFile = enclosingDeclaration && getSourceFileOfNode(enclosingDeclaration); + printer.writeNode(EmitHint.Unspecified, sig!, /*sourceFile*/ sourceFile, getTrailingSemicolonDeferringWriter(writer)); // TODO: GH#18217 + return writer; + } + } + + 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)); + 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 printer = type !== unresolvedType ? createPrinterWithRemoveComments() : createPrinterWithDefaults(); + 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 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 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 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))); + } + /** + * Same as getTypeFromTypeNode, but for use in createNodeBuilder + * Inside createNodeBuilder we shadow getTypeFromTypeNode to make sure anyone using this function will call the local version that does type mapping if appropriate + * This function is used to still be able to call the original getTypeFromTypeNode from the local scope version of getTypeFromTypeNode + */ + function getTypeFromTypeNodeWithoutContext(node: TypeNode) { + return getTypeFromTypeNode(node); + } + function createNodeBuilder() { + return { + typeToTypeNode: (type: Type, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => withContext(enclosingDeclaration, flags, tracker, context => typeToTypeNodeHelper(type, context)), + typePredicateToTypePredicateNode: (typePredicate: TypePredicate, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => withContext(enclosingDeclaration, flags, tracker, context => typePredicateToTypePredicateNodeHelper(typePredicate, context)), + expressionOrTypeToTypeNode: (expr: Expression | JsxAttributeValue | undefined, type: Type, addUndefined?: boolean, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => withContext(enclosingDeclaration, flags, tracker, context => expressionOrTypeToTypeNode(context, expr, type, addUndefined)), + serializeTypeForDeclaration: (declaration: Declaration, type: Type, symbol: Symbol, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => withContext(enclosingDeclaration, flags, tracker, context => serializeTypeForDeclaration(context, declaration, type, symbol)), + serializeReturnTypeForSignature: (signature: Signature, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => withContext(enclosingDeclaration, flags, tracker, context => serializeReturnTypeForSignature(context, signature)), + 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) => withContext(enclosingDeclaration, flags, tracker, context => symbolTableToDeclarationStatements(symbolTable, context)), + symbolToNode: (symbol: Symbol, meaning: SymbolFlags, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => withContext(enclosingDeclaration, flags, tracker, context => symbolToNode(symbol, context, meaning)), + }; + + function getTypeFromTypeNode(context: NodeBuilderContext, node: TypeNode, noMappedTypes?: false): Type; + function getTypeFromTypeNode(context: NodeBuilderContext, node: TypeNode, noMappedTypes: true): Type | undefined; + function getTypeFromTypeNode(context: NodeBuilderContext, node: TypeNode, noMappedTypes?: boolean): Type | undefined { + const type = getTypeFromTypeNodeWithoutContext(node); + if (!context.mapper) return type; + + const mappedType = instantiateType(type, context.mapper); + return noMappedTypes && mappedType !== type ? undefined : mappedType; + } + + /** + * Unlike the utilities `setTextRange`, this checks if the `location` we're trying to set on `range` is within the + * same file as the active context. If not, the range is not applied. This prevents us from copying ranges across files, + * which will confuse the node printer (as it assumes all node ranges are within the current file). + * Additionally, if `range` _isn't synthetic_, or isn't in the current file, it will _copy_ it to _remove_ its' position + * information. + * + * It also calls `setOriginalNode` to setup a `.original` pointer, since you basically *always* want these in the node builder. + */ + function setTextRange(context: NodeBuilderContext, range: T, location: Node | undefined): T { + if (!nodeIsSynthesized(range) || !(range.flags & NodeFlags.Synthesized) || !context.enclosingFile || context.enclosingFile !== getSourceFileOfNode(getOriginalNode(range))) { + range = factory.cloneNode(range); // if `range` is synthesized or originates in another file, copy it so it definitely has synthetic positions + } + if (range === location) return range; + if (!location) { + return range; + } + if (!context.enclosingFile || context.enclosingFile !== getSourceFileOfNode(getOriginalNode(location))) { + return setOriginalNode(range, location); // if `location` is from another file, only set/update original pointer, and not positions, since copying text across files isn't supported by the emitter + } + return setTextRangeWorker(setOriginalNode(range, location), location); + } + + /** + * Same as expressionOrTypeToTypeNodeHelper, but also checks if the expression can be syntactically typed. + */ + function expressionOrTypeToTypeNode(context: NodeBuilderContext, expr: Expression | JsxAttributeValue | undefined, type: Type, addUndefined?: boolean) { + const oldFlags = context.flags; + if (expr && !(context.flags & NodeBuilderFlags.NoSyntacticPrinter)) { + syntacticNodeBuilder.serializeTypeOfExpression(expr, context, addUndefined); + } + context.flags |= NodeBuilderFlags.NoSyntacticPrinter; + const result = expressionOrTypeToTypeNodeHelper(context, expr, type, addUndefined); + context.flags = oldFlags; + return result; + } + function expressionOrTypeToTypeNodeHelper(context: NodeBuilderContext, expr: Expression | JsxAttributeValue | undefined, type: Type, addUndefined?: boolean) { + if (expr) { + const typeNode = isAssertionExpression(expr) ? expr.type + : isJSDocTypeAssertion(expr) ? getJSDocTypeAssertionType(expr) + : undefined; + if (typeNode && !isConstTypeReference(typeNode)) { + const result = tryReuseExistingTypeNode(context, typeNode, type, expr.parent, addUndefined); + if (result) { + return result; + } + } + } + + if (addUndefined) { + type = getOptionalType(type); + } + + return typeToTypeNodeHelper(type, context); + } + + function tryReuseExistingTypeNode( + context: NodeBuilderContext, + typeNode: TypeNode, + type: Type, + host: Node, + addUndefined?: boolean, + ) { + const originalType = type; + if (addUndefined) { + type = getOptionalType(type); + } + const clone = tryReuseExistingNonParameterTypeNode(context, typeNode, type, host); + if (clone) { + if (addUndefined && !someType(getTypeFromTypeNode(context, typeNode), t => !!(t.flags & TypeFlags.Undefined))) { + return factory.createUnionTypeNode([clone, factory.createKeywordTypeNode(SyntaxKind.UndefinedKeyword)]); + } + return clone; + } + if (addUndefined && originalType !== type) { + const cloneMissingUndefined = tryReuseExistingNonParameterTypeNode(context, typeNode, originalType, host); + if (cloneMissingUndefined) { + return factory.createUnionTypeNode([cloneMissingUndefined, factory.createKeywordTypeNode(SyntaxKind.UndefinedKeyword)]); + } + } + return undefined; + } + + function tryReuseExistingNonParameterTypeNode( + context: NodeBuilderContext, + existing: TypeNode, + type: Type, + host = context.enclosingDeclaration, + annotationType = getTypeFromTypeNode(context, existing, /*noMappedTypes*/ true), + ) { + if (annotationType && typeNodeIsEquivalentToType(host, type, annotationType) && existingTypeNodeIsNotReferenceOrIsReferenceWithCompatibleTypeArgumentCount(existing, type)) { + const result = tryReuseExistingTypeNodeHelper(context, existing); + if (result) { + return result; + } + } + return undefined; + } + + 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 { + const moduleResolverHost = tracker?.trackSymbol ? tracker.moduleResolverHost : + flags! & NodeBuilderFlags.DoNotIncludeSymbolChain ? createBasicNodeBuilderModuleSpecifierResolutionHost(host) : + undefined; + const context: NodeBuilderContext = { + enclosingDeclaration, + enclosingFile: enclosingDeclaration && getSourceFileOfNode(enclosingDeclaration), + flags: flags || NodeBuilderFlags.None, + tracker: undefined!, + encounteredError: false, + reportedDiagnostic: false, + visitedTypes: undefined, + symbolDepth: undefined, + inferTypeParameters: undefined, + approximateLength: 0, + trackedSymbols: undefined, + bundled: !!compilerOptions.outFile && !!enclosingDeclaration && isExternalOrCommonJsModule(getSourceFileOfNode(enclosingDeclaration)), + truncating: false, + usedSymbolNames: undefined, + remappedSymbolNames: undefined, + remappedSymbolReferences: undefined, + reverseMappedStack: undefined, + mustCreateTypeParameterSymbolList: true, + typeParameterSymbolList: undefined, + mustCreateTypeParametersNamesLookups: true, + typeParameterNames: undefined, + typeParameterNamesByText: undefined, + typeParameterNamesByTextNextNameCount: undefined, + mapper: undefined, + }; + context.tracker = new SymbolTrackerImpl(context, tracker, moduleResolverHost); + const resultingNode = cb(context); + if (context.truncating && context.flags & NodeBuilderFlags.NoTruncation) { + context.tracker.reportTruncationError(); + } + return context.encounteredError ? undefined : resultingNode; + } + + function checkTruncationLength(context: NodeBuilderContext): boolean { + if (context.truncating) return context.truncating; + return context.truncating = context.approximateLength > ((context.flags & NodeBuilderFlags.NoTruncation) ? noTruncationMaximumTruncationLength : defaultMaximumTruncationLength); + } + + function typeToTypeNodeHelper(type: Type, context: NodeBuilderContext): TypeNode { + const savedFlags = context.flags; + const typeNode = typeToTypeNodeWorker(type, context); + context.flags = savedFlags; + return typeNode; + } + + function typeToTypeNodeWorker(type: Type, context: NodeBuilderContext): TypeNode { + if (cancellationToken && cancellationToken.throwIfCancellationRequested) { + cancellationToken.throwIfCancellationRequested(); + } + const inTypeAlias = context.flags & NodeBuilderFlags.InTypeAlias; + context.flags &= ~NodeBuilderFlags.InTypeAlias; + + if (!type) { + if (!(context.flags & NodeBuilderFlags.AllowEmptyUnionOrIntersection)) { + context.encounteredError = true; + return undefined!; // TODO: GH#18217 + } + context.approximateLength += 3; + return factory.createKeywordTypeNode(SyntaxKind.AnyKeyword); + } + + if (!(context.flags & NodeBuilderFlags.NoTypeReduction)) { + type = getReducedType(type); + } + + if (type.flags & TypeFlags.Any) { + if (type.aliasSymbol) { + return factory.createTypeReferenceNode(symbolToEntityNameNode(type.aliasSymbol), mapToTypeNodes(type.aliasTypeArguments, context)); + } + if (type === unresolvedType) { + return addSyntheticLeadingComment(factory.createKeywordTypeNode(SyntaxKind.AnyKeyword), SyntaxKind.MultiLineCommentTrivia, "unresolved"); + } + 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.ES5)) { + 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`."); + } + } + 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(); + } + } + 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; + } + context.tracker.reportInaccessibleThisError?.(); + } + 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); + } + + const objectFlags = getObjectFlags(type); + + if (objectFlags & ObjectFlags.Reference) { + Debug.assert(!!(type.flags & TypeFlags.Object)); + return (type as TypeReference).node ? visitAndTransformType(type as TypeReference, 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 + ) { + 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); + } + 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); + } + 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 + } + } + 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) { + const typeNode = typeToTypeNodeHelper((type as SubstitutionType).baseType, context); + const noInferSymbol = isNoInferType(type) && getGlobalTypeSymbol("NoInfer" as __String, /*reportErrors*/ false); + return noInferSymbol ? symbolToTypeNode(noInferSymbol, context, SymbolFlags.Type, [typeNode]) : typeNode; + } + + 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(context, type.root.node.trueType), newMapper)); + const falseTypeNode = typeToTypeNodeOrCircularityElision(instantiateType(getTypeFromTypeNode(context, 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); + } + return visitAndTransformType(type, type => typeToTypeNodeHelper(type, context)); + } + return typeToTypeNodeHelper(type, context); + } + + function isMappedTypeHomomorphic(type: MappedType) { + return !!getHomomorphicTypeVariable(type); + } + + function isHomomorphicMappedTypeWithNonHomomorphicInstantiation(type: MappedType) { + return !!type.target && isMappedTypeHomomorphic(type.target as MappedType) && !isMappedTypeHomomorphic(type); + } + + 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 the mapped type isn't `keyof` constraint-declared, _but_ still has modifiers preserved, and its naive instantiation won't preserve modifiers because its constraint isn't `keyof` constrained, we have work to do + const needsModifierPreservingWrapper = !isMappedTypeWithKeyofConstraintDeclaration(type) + && !(getModifiersTypeFromMappedType(type).flags & TypeFlags.Unknown) + && context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams + && !(getConstraintTypeFromMappedType(type).flags & TypeFlags.TypeParameter && getConstraintOfTypeParameter(getConstraintTypeFromMappedType(type))?.flags! & TypeFlags.Index); + 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 if (needsModifierPreservingWrapper) { + // So, step 1: new type variable + const newParam = createTypeParameter(createSymbol(SymbolFlags.TypeParameter, "T" as __String)); + const name = typeParameterToName(newParam, context); + newTypeVariable = factory.createTypeReferenceNode(name); + // step 2: make that new type variable itself the constraint node, making the mapped type `{[K in T_1]: Template}` + appropriateConstraintTypeNode = newTypeVariable; + } + 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(context, (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), + ); + } + else if (needsModifierPreservingWrapper) { + // and step 3: once the mapped type is reconstructed, create a `ConstraintType extends infer T_1 extends keyof ModifiersType ? {[K in T_1]: Template} : never` + // subtly different from the `keyof` constraint case, by including the `keyof` constraint on the `infer` type parameter, it doesn't rely on the constraint type being itself + // constrained to a `keyof` type to preserve its modifier-preserving behavior. This is all basically because we preserve modifiers for a wider set of mapped types than + // just homomorphic ones. + return factory.createConditionalTypeNode( + typeToTypeNodeHelper(getConstraintTypeFromMappedType(type), context), + factory.createInferTypeNode(factory.createTypeParameterDeclaration(/*modifiers*/ undefined, factory.cloneNode(newTypeVariable!.typeName) as Identifier, factory.createTypeOperatorNode(SyntaxKind.KeyOfKeyword, typeToTypeNodeHelper(getModifiersTypeFromMappedType(type), context)))), + result, + factory.createKeywordTypeNode(SyntaxKind.NeverKeyword), + ); + } + return result; + } + + function createAnonymousTypeNode(type: ObjectType): TypeNode { + const typeId = type.id; + const symbol = type.symbol; + if (symbol) { + const isInstantiationExpressionType = !!(getObjectFlags(type) & ObjectFlags.InstantiationExpressionType); + if (isInstantiationExpressionType) { + const instantiationExpressionType = type as InstantiationExpressionType; + const existing = instantiationExpressionType.node; + if (isTypeQueryNode(existing)) { + const typeNode = tryReuseExistingNonParameterTypeNode(context, existing, type); + if (typeNode) { + return typeNode; + } + } + if (context.visitedTypes?.has(typeId)) { + return createElidedInformationPlaceholder(context); + } + return visitAndTransformType(type, createTypeNodeFromObjectType); + } + 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, /*shouldComputeAliasesToMakeVisible*/ 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); + } + } + else { + 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: T, transform: (type: T) => TypeNode) { + 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 & T).node ? "N" + getNodeId((type as TypeReference & T).node!) : + type.flags & TypeFlags.Conditional ? "N" + getNodeId((type as ConditionalType & T).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) { + // TODO:: check if we instead store late painted statements associated with this? + cachedResult.trackedSymbols?.forEach( + ([symbol, enclosingDeclaration, meaning]) => + context.tracker.trackSymbol( + symbol, + enclosingDeclaration, + meaning, + ), + ); + if (cachedResult.truncating) { + context.truncating = true; + } + context.approximateLength += cachedResult.addedLength; + return deepCloneOrReuseNode(cachedResult.node); + } + + let depth: number | undefined; + if (id) { + depth = context.symbolDepth!.get(id) || 0; + if (depth > 10) { + return createElidedInformationPlaceholder(context); + } + context.symbolDepth!.set(id, depth + 1); + } + context.visitedTypes.add(typeId); + const prevTrackedSymbols = context.trackedSymbols; + context.trackedSymbols = undefined; + const startLength = context.approximateLength; + const result = transform(type); + const addedLength = context.approximateLength - startLength; + if (!context.reportedDiagnostic && !context.encounteredError) { + links?.serializedTypes?.set(key, { + node: result, + truncating: context.truncating, + addedLength, + trackedSymbols: context.trackedSymbols, + }); + } + context.visitedTypes.delete(typeId); + if (id) { + context.symbolDepth!.set(id, depth!); + } + context.trackedSymbols = prevTrackedSymbols; + return result; + + function deepCloneOrReuseNode(node: T): T { + if (!nodeIsSynthesized(node) && getParseTreeNode(node) === node) { + return node; + } + return setTextRange(context, factory.cloneNode(visitEachChildWorker(node, deepCloneOrReuseNode, /*context*/ undefined, deepCloneOrReuseNodes, deepCloneOrReuseNode)), node); + } + + function deepCloneOrReuseNodes( + nodes: NodeArray | undefined, + visitor: Visitor, + 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 setTextRangeWorker(factory.createNodeArray(/*elements*/ 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, s => getOrCreateTypeFromSignature(s)); + // 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) { + const { labeledElementDeclarations } = type.target as TupleType; + for (let i = 0; i < tupleConstituentNodes.length; i++) { + const flags = (type.target as TupleType).elementFlags[i]; + const labeledElementDeclaration = labeledElementDeclarations?.[i]; + + if (labeledElementDeclaration) { + tupleConstituentNodes[i] = factory.createNamedTupleMember( + flags & ElementFlags.Variable ? factory.createToken(SyntaxKind.DotDotDotToken) : undefined, + factory.createIdentifier(unescapeLeadingUnderscores(getTupleElementLabel(labeledElementDeclaration))), + flags & ElementFlags.Optional ? factory.createToken(SyntaxKind.QuestionToken) : undefined, + flags & ElementFlags.Rest ? factory.createArrayTypeNode(tupleConstituentNodes[i]) : + tupleConstituentNodes[i], + ); + } + else { + 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 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)) { + if (typeArguments !== getIdentifierTypeArguments(qualifier)) { + qualifier = setIdentifierTypeArguments(factory.cloneNode(qualifier), typeArguments); + } + } + else { + if (typeArguments !== getIdentifierTypeArguments(qualifier.right)) { + qualifier = factory.updateQualifiedName(qualifier, qualifier.left, setIdentifierTypeArguments(factory.cloneNode(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.attributes, + qualifier, + typeArguments, + root.isTypeOf, + ); + } + else { + // first shift type arguments + let typeArguments = root.typeArguments; + let typeName = root.typeName; + if (isIdentifier(typeName)) { + if (typeArguments !== getIdentifierTypeArguments(typeName)) { + typeName = setIdentifierTypeArguments(factory.cloneNode(typeName), typeArguments); + } + } + else { + if (typeArguments !== getIdentifierTypeArguments(typeName.right)) { + typeName = factory.updateQualifiedName(typeName, typeName.left, setIdentifierTypeArguments(factory.cloneNode(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 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; + } + 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); + } + return typeElements.length ? typeElements : undefined; + } + } + + 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 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).links.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.canTrackSymbol && 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); + } + } + } + else { + context.tracker.reportNonSerializableProperty(symbolToString(propertySymbol)); + } + } + context.enclosingDeclaration = propertySymbol.valueDeclaration || propertySymbol.declarations?.[0] || saveEnclosingDeclaration; + const propertyName = getPropertyNameNodeForSymbol(propertySymbol, context); + context.enclosingDeclaration = saveEnclosingDeclaration; + context.approximateLength += symbolName(propertySymbol).length + 1; + + if (propertySymbol.flags & SymbolFlags.Accessor) { + const writeType = getWriteTypeOfSymbol(propertySymbol); + if (propertyType !== writeType && !isErrorType(propertyType) && !isErrorType(writeType)) { + const getterDeclaration = getDeclarationOfKind(propertySymbol, SyntaxKind.GetAccessor)!; + const getterSignature = getSignatureFromDeclaration(getterDeclaration); + typeElements.push( + setCommentRange( + context, + signatureToSignatureDeclarationHelper(getterSignature, SyntaxKind.GetAccessor, context, { name: propertyName }) as GetAccessorDeclaration, + getterDeclaration, + ), + ); + const setterDeclaration = getDeclarationOfKind(propertySymbol, SyntaxKind.SetAccessor)!; + const setterSignature = getSignatureFromDeclaration(setterDeclaration); + typeElements.push( + setCommentRange( + context, + signatureToSignatureDeclarationHelper(setterSignature, SyntaxKind.SetAccessor, context, { name: propertyName }) as SetAccessorDeclaration, + setterDeclaration, + ), + ); + return; + } + } + + 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)); + } + if (signatures.length || !optionalToken) { + return; + } + } + let propertyTypeNode: TypeNode; + if (shouldUsePlaceholderForProperty(propertySymbol, context)) { + propertyTypeNode = createElidedInformationPlaceholder(context); + } + else { + if (propertyIsReverseMapped) { + context.reverseMappedStack ||= []; + context.reverseMappedStack.push(propertySymbol as ReverseMappedSymbol); + } + propertyTypeNode = propertyType ? serializeTypeForDeclaration(context, /*declaration*/ undefined, propertyType, propertySymbol) : factory.createKeywordTypeNode(SyntaxKind.AnyKeyword); + if (propertyIsReverseMapped) { + context.reverseMappedStack!.pop(); + } + } + + 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) { + const jsdocPropertyTag = propertySymbol.declarations?.find((d): d is JSDocPropertyTag => d.kind === SyntaxKind.JSDocPropertyTag); + if (jsdocPropertyTag) { + const commentText = getTextOfJSDocComment(jsdocPropertyTag.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(context, node, propertySymbol.valueDeclaration); + } + return node; + } + } + + function setCommentRange(context: NodeBuilderContext, node: T, range: Node): T { + if (context.enclosingFile && context.enclosingFile === getSourceFileOfNode(range)) { + // Copy comments to node for declaration emit + return setCommentRangeWorker(node, range); + } + return node; + } + + 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 ? createMultiMap<__String, [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; + } + 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 (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; + } + + 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); + + 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, + ); + } + + interface SignatureToSignatureDeclarationOptions { + modifiers?: readonly Modifier[]; + name?: PropertyName; + questionToken?: QuestionToken; + } + + function signatureToSignatureDeclarationHelper(signature: Signature, kind: SignatureDeclaration["kind"], context: NodeBuilderContext, options?: SignatureToSignatureDeclarationOptions): SignatureDeclaration { + let typeParameters: TypeParameterDeclaration[] | undefined; + let typeArguments: TypeNode[] | undefined; + + const expandedParams = getExpandedParameters(signature, /*skipUnionExpanding*/ true)[0]; + const cleanup = enterNewScope(context, signature.declaration, expandedParams, signature.typeParameters, signature.parameters, signature.mapper); + context.approximateLength += 3; // Usually a signature contributes a few more characters than this, but 3 is the minimum + + 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 flags = context.flags; + context.flags &= ~NodeBuilderFlags.SuppressAnyReturnType; // SuppressAnyReturnType should only apply to the signature `return` position + // 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)); + const thisParameter = context.flags & NodeBuilderFlags.OmitThisParameter ? undefined : tryGetThisParameterDeclaration(signature, context); + if (thisParameter) { + parameters.unshift(thisParameter); + } + context.flags = flags; + + const returnTypeNode = serializeReturnTypeForSignature(context, signature); + + let modifiers = options?.modifiers; + if ((kind === SyntaxKind.ConstructorType) && signature.flags & SignatureFlags.Abstract) { + const flags = modifiersToFlags(modifiers); + modifiers = factory.createModifiersFromModifierFlags(flags | ModifierFlags.Abstract); + } + + 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); + } + if (signature.declaration?.kind === SyntaxKind.JSDocSignature && signature.declaration.parent.kind === SyntaxKind.JSDocOverloadTag) { + const comment = getTextOfNode(signature.declaration.parent.parent, /*includeTrivia*/ true).slice(2, -2).split(/\r\n|\n|\r/).map(line => line.replace(/^\s+/, " ")).join("\n"); + addSyntheticLeadingComment(node, SyntaxKind.MultiLineCommentTrivia, comment, /*hasTrailingNewLine*/ true); + } + + cleanup?.(); + return node; + } + + type IntroducesNewScopeNode = SignatureDeclaration | JSDocSignature | MappedTypeNode; + + function isNewScopeNode(node: Node): node is IntroducesNewScopeNode { + return isFunctionLike(node) + || isJSDocSignature(node) + || isMappedTypeNode(node); + } + + function getTypeParametersInScope(node: IntroducesNewScopeNode | ConditionalTypeNode) { + return isFunctionLike(node) || isJSDocSignature(node) ? getSignatureFromDeclaration(node).typeParameters : + isConditionalTypeNode(node) ? getInferTypeParameters(node) : + [getDeclaredTypeOfTypeParameter(getSymbolOfDeclaration(node.typeParameter))]; + } + + function getParametersInScope(node: IntroducesNewScopeNode | ConditionalTypeNode) { + return isFunctionLike(node) || isJSDocSignature(node) ? getSignatureFromDeclaration(node).parameters : undefined; + } + + function enterNewScope( + context: NodeBuilderContext, + declaration: IntroducesNewScopeNode | ConditionalTypeNode | undefined, + expandedParams: readonly Symbol[] | undefined, + typeParameters: readonly TypeParameter[] | undefined, + originalParameters?: readonly Symbol[] | undefined, + mapper?: TypeMapper, + ) { + const cleanupContext = cloneNodeBuilderContext(context); + // For regular function/method declarations, the enclosing declaration will already be signature.declaration, + // so this is a no-op, but for arrow functions and function expressions, the enclosing declaration will be + // the declaration that the arrow function / function expression is assigned to. + // + // If the parameters or return type include "typeof globalThis.paramName", using the wrong scope will lead + // us to believe that we can emit "typeof paramName" instead, even though that would refer to the parameter, + // not the global. Make sure we are in the right scope by changing the enclosingDeclaration to the function. + // + // We can't use the declaration directly; it may be in another file and so we may lose access to symbols + // accessible to the current enclosing declaration, or gain access to symbols not accessible to the current + // enclosing declaration. To keep this chain accurate, insert a fake scope into the chain which makes the + // function's parameters visible. + let cleanupParams: (() => void) | undefined; + let cleanupTypeParams: (() => void) | undefined; + const oldEnclosingDecl = context.enclosingDeclaration; + const oldMapper = context.mapper; + if (mapper) { + context.mapper = mapper; + } + if (context.enclosingDeclaration && declaration) { + // As a performance optimization, reuse the same fake scope within this chain. + // This is especially needed when we are working on an excessively deep type; + // if we don't do this, then we spend all of our time adding more and more + // scopes that need to be searched in isSymbolAccessible later. Since all we + // really want to do is to mark certain names as unavailable, we can just keep + // all of the names we're introducing in one large table and push/pop from it as + // needed; isSymbolAccessible will walk upward and find the closest "fake" scope, + // which will conveniently report on any and all faked scopes in the chain. + // + // It'd likely be better to store this somewhere else for isSymbolAccessible, but + // since that API _only_ uses the enclosing declaration (and its parents), this is + // seems like the best way to inject names into that search process. + // + // Note that we only check the most immediate enclosingDeclaration; the only place we + // could potentially add another fake scope into the chain is right here, so we don't + // traverse all ancestors. + cleanupParams = !some(expandedParams) ? undefined : pushFakeScope( + "params", + add => { + if (!expandedParams) return; + for (let pIndex = 0; pIndex < expandedParams.length; pIndex++) { + const param = expandedParams[pIndex]; + const originalParam = originalParameters?.[pIndex]; + if (originalParameters && originalParam !== param) { + // Can't reference parameters that come from an expansion + add(param.escapedName, unknownSymbol); + // Can't reference the original expanded parameter either + if (originalParam) { + add(originalParam.escapedName, unknownSymbol); + } + } + else if ( + !forEach(param.declarations, d => { + if (isParameter(d) && isBindingPattern(d.name)) { + bindPattern(d.name); + return true; + } + return undefined; + function bindPattern(p: BindingPattern): void { + forEach(p.elements, e => { + switch (e.kind) { + case SyntaxKind.OmittedExpression: + return; + case SyntaxKind.BindingElement: + return bindElement(e); + default: + return Debug.assertNever(e); + } + }); + } + function bindElement(e: BindingElement): void { + if (isBindingPattern(e.name)) { + return bindPattern(e.name); + } + const symbol = getSymbolOfDeclaration(e); + add(symbol.escapedName, symbol); + } + }) + ) { + add(param.escapedName, param); + } + } + }, + ); + + if (context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams && some(typeParameters)) { + cleanupTypeParams = pushFakeScope( + "typeParams", + add => { + for (const typeParam of typeParameters ?? emptyArray) { + const typeParamName = typeParameterToName(typeParam, context).escapedText; + add(typeParamName, typeParam.symbol); + } + }, + ); + } + + function pushFakeScope(kind: "params" | "typeParams", addAll: (addSymbol: (name: __String, symbol: Symbol) => void) => void) { + // We only ever need to look two declarations upward. + Debug.assert(context.enclosingDeclaration); + let existingFakeScope: Node | undefined; + if (getNodeLinks(context.enclosingDeclaration).fakeScopeForSignatureDeclaration === kind) { + existingFakeScope = context.enclosingDeclaration; + } + else if (context.enclosingDeclaration.parent && getNodeLinks(context.enclosingDeclaration.parent).fakeScopeForSignatureDeclaration === kind) { + existingFakeScope = context.enclosingDeclaration.parent; + } + Debug.assertOptionalNode(existingFakeScope, isBlock); + + const locals = existingFakeScope?.locals ?? createSymbolTable(); + let newLocals: __String[] | undefined; + let oldLocals: { name: __String; oldSymbol: Symbol; }[] | undefined; + addAll((name, symbol) => { + // Add cleanup information only if we don't own the fake scope + if (existingFakeScope) { + const oldSymbol = locals.get(name); + if (!oldSymbol) { + newLocals = append(newLocals, name); + } + else { + oldLocals = append(oldLocals, { name, oldSymbol }); + } + } + locals.set(name, symbol); + }); + + if (!existingFakeScope) { + // Use a Block for this; the type of the node doesn't matter so long as it + // has locals, and this is cheaper/easier than using a function-ish Node. + const fakeScope = factory.createBlock(emptyArray); + getNodeLinks(fakeScope).fakeScopeForSignatureDeclaration = kind; + fakeScope.locals = locals; + + setParent(fakeScope, context.enclosingDeclaration); + context.enclosingDeclaration = fakeScope; + } + else { + // We did not create the current scope, so we have to clean it up + return function undo() { + forEach(newLocals, s => locals.delete(s)); + forEach(oldLocals, s => locals.set(s.name, s.oldSymbol)); + }; + } + } + } + + return () => { + cleanupParams?.(); + cleanupTypeParams?.(); + cleanupContext(); + context.enclosingDeclaration = oldEnclosingDecl; + context.mapper = oldMapper; + }; + } + + function tryGetThisParameterDeclaration(signature: Signature, context: NodeBuilderContext) { + if (signature.thisParameter) { + return symbolToParameterDeclaration(signature.thisParameter, context); + } + if (signature.declaration && isInJSFile(signature.declaration)) { + const thisTag = getJSDocThisTag(signature.declaration); + if (thisTag && thisTag.typeExpression) { + return factory.createParameterDeclaration( + /*modifiers*/ undefined, + /*dotDotDotToken*/ undefined, + "this", + /*questionToken*/ undefined, + typeToTypeNodeHelper(getTypeFromTypeNode(context, thisTag.typeExpression), context), + ); + } + } + } + + 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(getTypeParameterModifiers(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 typeToTypeNodeHelperWithPossibleReusableTypeNode(type: Type, typeNode: TypeNode | undefined, context: NodeBuilderContext) { + return typeNode && tryReuseExistingNonParameterTypeNode(context, typeNode, type) || typeToTypeNodeHelper(type, context); + } + + function typeParameterToDeclaration(type: TypeParameter, context: NodeBuilderContext, constraint = getConstraintOfTypeParameter(type)): TypeParameterDeclaration { + const constraintNode = constraint && typeToTypeNodeHelperWithPossibleReusableTypeNode(constraint, getConstraintDeclaration(type), context); + return typeParameterToDeclarationWithConstraint(type, context, constraintNode); + } + + function typePredicateToTypePredicateNodeHelper(typePredicate: TypePredicate, context: NodeBuilderContext): TypePredicateNode { + 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); + return factory.createTypePredicateNode(assertsModifier, parameterName, typeNode); + } + + function getEffectiveParameterDeclaration(parameterSymbol: Symbol): ParameterDeclaration | JSDocParameterTag | undefined { + const parameterDeclaration: ParameterDeclaration | JSDocParameterTag | undefined = getDeclarationOfKind(parameterSymbol, SyntaxKind.Parameter); + if (parameterDeclaration) { + return parameterDeclaration; + } + if (!isTransientSymbol(parameterSymbol)) { + return getDeclarationOfKind(parameterSymbol, SyntaxKind.JSDocParameterTag); + } + } + + function symbolToParameterDeclaration(parameterSymbol: Symbol, context: NodeBuilderContext, preserveModifierFlags?: boolean): ParameterDeclaration { + const parameterDeclaration = getEffectiveParameterDeclaration(parameterSymbol); + + const parameterType = getTypeOfSymbol(parameterSymbol); + const parameterTypeNode = serializeTypeForDeclaration(context, parameterDeclaration, parameterType, parameterSymbol); + + 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 = parameterToParameterDeclarationName(parameterSymbol, parameterDeclaration, context); + 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 parameterToParameterDeclarationName(parameterSymbol: Symbol, parameterDeclaration: ParameterDeclaration | JSDocParameterTag | undefined, context: NodeBuilderContext) { + return 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); + + function cloneBindingName(node: BindingName): BindingName { + return elideInitializerAndSetEmitFlags(node) as BindingName; + function elideInitializerAndSetEmitFlags(node: Node): Node { + if (context.tracker.canTrackSymbol && isComputedPropertyName(node) && isLateBindableName(node)) { + trackComputedName(node.expression, context.enclosingDeclaration, context); + } + let visited = visitEachChildWorker(node, elideInitializerAndSetEmitFlags, /*context*/ undefined, /*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 trackComputedName(accessExpression: EntityNameOrEntityNameExpression, enclosingDeclaration: Node | undefined, context: NodeBuilderContext) { + if (!context.tracker.canTrackSymbol) return; + // get symbol of the first identifier of the entityName + const firstIdentifier = getFirstIdentifier(accessExpression); + const name = resolveName(firstIdentifier, firstIdentifier.escapedText, SymbolFlags.Value | SymbolFlags.ExportValue, /*nameNotFoundMessage*/ undefined, /*isUse*/ true); + if (name) { + context.tracker.trackSymbol(name, enclosingDeclaration, SymbolFlags.Value); + } + } + + function lookupSymbolChain(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags, yieldModuleSymbol?: boolean) { + context.tracker.trackSymbol(symbol, context.enclosingDeclaration, meaning); + 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]; + } + + 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; + } + 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 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; + } + if (context.mustCreateTypeParameterSymbolList) { + context.mustCreateTypeParameterSymbolList = false; + context.typeParameterSymbolList = new Set(context.typeParameterSymbolList); + } + context.typeParameterSymbolList!.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, + ); + // NOTE: cast to TransientSymbol should be safe because only TransientSymbol can have CheckFlags.Instantiated + typeParameterNodes = mapToTypeNodes(map(params, t => getMappedType(t, (nextSymbol as TransientSymbol).links.mapper!)), context); + } + else { + typeParameterNodes = typeParametersToTypeParameterDeclarations(symbol, context); + } + } + 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; + } + + function getSpecifierForModuleSymbol(symbol: Symbol, context: NodeBuilderContext, overrideImportMode?: ResolutionMode) { + 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 (ambientModuleSymbolRegex.test(symbol.escapedName as string)) { + return (symbol.escapedName as string).substring(1, (symbol.escapedName as string).length - 1); + } + } + if (!context.enclosingFile || !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 = context.enclosingFile; + const resolutionMode = overrideImportMode || contextFile?.impliedNodeFormat; + const cacheKey = createModeAwareCacheKey(contextFile.path, resolutionMode); + const links = getSymbolLinks(symbol); + let specifier = links.specifierCache && links.specifierCache.get(cacheKey); + if (!specifier) { + const isBundle = !!compilerOptions.outFile; + // 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 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 attributes: ImportAttributes | 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); + attributes = factory.createImportAttributes( + factory.createNodeArray([ + factory.createImportAttribute( + factory.createStringLiteral("resolution-mode"), + factory.createStringLiteral("import"), + ), + ]), + ); + } + } + if (!specifier) { + specifier = getSpecifierForModuleSymbol(chain[0], context); + } + if (!(context.flags & NodeBuilderFlags.AllowNodeModulesRelativePaths) && getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.Classic && specifier.includes("/node_modules/")) { + 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.includes("/node_modules/")) { + // Still unreachable :( + specifier = oldSpecifier; + } + else { + attributes = factory.createImportAttributes( + factory.createNodeArray([ + factory.createImportAttribute( + factory.createStringLiteral("resolution-mode"), + factory.createStringLiteral(swappedMode === ModuleKind.ESNext ? "import" : "require"), + ), + ]), + ); + } + } + + if (!attributes) { + // 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)); + context.approximateLength += specifier.length + 10; // specifier + import("") + if (!nonRootParts || isEntityName(nonRootParts)) { + if (nonRootParts) { + const lastId = isIdentifier(nonRootParts) ? nonRootParts : nonRootParts.right; + setIdentifierTypeArguments(lastId, /*typeArguments*/ undefined); + } + return factory.createImportTypeNode(lit, attributes, 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, attributes, 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); + } + else { + const lastId = isIdentifier(entityName) ? entityName : entityName.right; + const lastTypeArgs = getIdentifierTypeArguments(lastId); + setIdentifierTypeArguments(lastId, /*typeArguments*/ undefined); + return factory.createTypeReferenceNode(entityName, lastTypeArgs as NodeArray); + } + + 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]; + + 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; + } + }); + } + } + + 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)); + } + 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 + 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))); + } + } + + const identifier = setEmitFlags(factory.createIdentifier(symbolName), EmitFlags.NoAsciiEscaping); + if (typeParameterNodes) setIdentifierTypeArguments(identifier, factory.createNodeArray(typeParameterNodes)); + 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 factory.createQualifiedName(LHS, identifier); + } + return identifier; + } + } + + function typeParameterShadowsOtherTypeParameterInScope(escapedName: __String, context: NodeBuilderContext, type: TypeParameter) { + const result = resolveName(context.enclosingDeclaration, escapedName, SymbolFlags.Type, /*nameNotFoundMessage*/ undefined, /*isUse*/ false); + if (result && result.flags & SymbolFlags.TypeParameter) { + return result !== type.symbol; + } + 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; + } + } + let result = symbolToName(type.symbol, context, SymbolFlags.Type, /*expectsIdentifier*/ true); + if (!(result.kind & SyntaxKind.Identifier)) { + return factory.createIdentifier("(Missing type parameter)"); + } + const decl = type.symbol?.declarations?.[0]; + if (decl && isTypeParameterDeclaration(decl)) { + result = setTextRange(context, result, decl.name); + } + 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) || typeParameterShadowsOtherTypeParameterInScope(text as __String, context, type)) { + i++; + text = `${rawtext}_${i}`; + } + if (text !== rawtext) { + const typeArguments = getIdentifierTypeArguments(result); + result = factory.createIdentifier(text); + setIdentifierTypeArguments(result, typeArguments); + } + if (context.mustCreateTypeParametersNamesLookups) { + context.mustCreateTypeParametersNamesLookups = false; + context.typeParameterNames = new Map(context.typeParameterNames); + context.typeParameterNamesByTextNextNameCount = new Map(context.typeParameterNamesByTextNextNameCount); + context.typeParameterNamesByText = new Set(context.typeParameterNamesByText); + } + // 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!.set(rawtext, i); + context.typeParameterNames!.set(getTypeId(type), result); + context.typeParameterNamesByText!.add(text); + } + 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); + + 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; + } + + const identifier = setEmitFlags(factory.createIdentifier(symbolName), EmitFlags.NoAsciiEscaping); + if (typeParameterNodes) setIdentifierTypeArguments(identifier, factory.createNodeArray(typeParameterNodes)); + identifier.symbol = symbol; + + 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)); + } + if (index === 0 || canUsePropertyAccess(symbolName, languageVersion)) { + const identifier = setEmitFlags(factory.createIdentifier(symbolName), EmitFlags.NoAsciiEscaping); + if (typeParameterNodes) setIdentifierTypeArguments(identifier, factory.createNodeArray(typeParameterNodes)); + identifier.symbol = symbol; + + 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) { + const identifier = setEmitFlags(factory.createIdentifier(symbolName), EmitFlags.NoAsciiEscaping); + if (typeParameterNodes) setIdentifierTypeArguments(identifier, factory.createNodeArray(typeParameterNodes)); + identifier.symbol = symbol; + expression = identifier; + } + return factory.createElementAccessExpression(createExpressionFromSymbolChain(chain, index - 1), expression); + } + } + } + + function isStringNamed(d: Declaration) { + const name = getNameOfDeclaration(d); + if (!name) { + return false; + } + if (isComputedPropertyName(name)) { + const type = checkExpression(name.expression); + return !!(type.flags & TypeFlags.StringLike); + } + if (isElementAccessExpression(name)) { + const type = checkExpression(name.argumentExpression); + return !!(type.flags & TypeFlags.StringLike); + } + return isStringLiteral(name); + } + + function isSingleQuotedStringNamed(d: Declaration) { + const name = getNameOfDeclaration(d); + return !!(name && isStringLiteral(name) && (name.singleQuote || !nodeIsSynthesized(name) && startsWith(getTextOfNode(name, /*includeTrivia*/ false), "'"))); + } + + function getPropertyNameNodeForSymbol(symbol: Symbol, context: NodeBuilderContext) { + const stringNamed = !!length(symbol.declarations) && every(symbol.declarations, isStringNamed); + const singleQuote = !!length(symbol.declarations) && every(symbol.declarations, isSingleQuotedStringNamed); + const isMethod = !!(symbol.flags & SymbolFlags.Method); + const fromNameType = getPropertyNameNodeForSymbolFromNameType(symbol, context, singleQuote, stringNamed, isMethod); + if (fromNameType) { + return fromNameType; + } + const rawName = unescapeLeadingUnderscores(symbol.escapedName); + return createPropertyNameNodeForIdentifierOrLiteral(rawName, getEmitScriptTarget(compilerOptions), singleQuote, stringNamed, isMethod); + } + + // See getNameForSymbolFromNameType for a stringy equivalent + function getPropertyNameNodeForSymbolFromNameType(symbol: Symbol, context: NodeBuilderContext, singleQuote: boolean, stringNamed: boolean, isMethod: 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)) && (stringNamed || !isNumericLiteralName(name))) { + return factory.createStringLiteral(name, !!singleQuote); + } + if (isNumericLiteralName(name) && startsWith(name, "-")) { + return factory.createComputedPropertyName(factory.createPrefixUnaryExpression(SyntaxKind.MinusToken, factory.createNumericLiteral(-name))); + } + return createPropertyNameNodeForIdentifierOrLiteral(name, getEmitScriptTarget(compilerOptions), singleQuote, stringNamed, isMethod); + } + if (nameType.flags & TypeFlags.UniqueESSymbol) { + return factory.createComputedPropertyName(symbolToExpression((nameType as UniqueESSymbolType).symbol, context, SymbolFlags.Value)); + } + } + } + + function cloneNodeBuilderContext(context: NodeBuilderContext) { + // 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 + const oldMustCreateTypeParameterSymbolList = context.mustCreateTypeParameterSymbolList; + const oldMustCreateTypeParametersNamesLookups = context.mustCreateTypeParametersNamesLookups; + context.mustCreateTypeParameterSymbolList = true; + context.mustCreateTypeParametersNamesLookups = true; + const oldTypeParameterNames = context.typeParameterNames; + const oldTypeParameterNamesByText = context.typeParameterNamesByText; + const oldTypeParameterNamesByTextNextNameCount = context.typeParameterNamesByTextNextNameCount; + const oldTypeParameterSymbolList = context.typeParameterSymbolList; + return () => { + context.typeParameterNames = oldTypeParameterNames; + context.typeParameterNamesByText = oldTypeParameterNamesByText; + context.typeParameterNamesByTextNextNameCount = oldTypeParameterNamesByTextNextNameCount; + context.typeParameterSymbolList = oldTypeParameterSymbolList; + context.mustCreateTypeParameterSymbolList = oldMustCreateTypeParameterSymbolList; + context.mustCreateTypeParametersNamesLookups = oldMustCreateTypeParametersNamesLookups; + }; + } + + function getDeclarationWithTypeAnnotation(symbol: Symbol, enclosingDeclaration?: Node | undefined) { + return symbol.declarations && find(symbol.declarations, s => !!getNonlocalEffectiveTypeAnnotationNode(s) && (!enclosingDeclaration || !!findAncestor(s, n => n === enclosingDeclaration))); + } + + function existingTypeNodeIsNotReferenceOrIsReferenceWithCompatibleTypeArgumentCount(existing: TypeNode, type: Type) { + // In JS, you can say something like `Foo` and get a `Foo` implicitly - we don't want to preserve that original `Foo` in these cases, though. + if (!(getObjectFlags(type) & ObjectFlags.Reference)) return true; + if (!isTypeReferenceNode(existing)) return true; + // `type` is a reference type, and `existing` is a type reference node, but we still need to make sure they refer to the _same_ target type + // before we go comparing their type argument counts. + void getTypeFromTypeReference(existing); // call to ensure symbol is resolved + const symbol = getNodeLinks(existing).resolvedSymbol; + const existingTarget = symbol && getDeclaredTypeOfSymbol(symbol); + if (!existingTarget || existingTarget !== (type as TypeReference).target) return true; + return length(existing.typeArguments) >= getMinTypeArgumentCount((type as TypeReference).target.typeParameters); + } + + function getEnclosingDeclarationIgnoringFakeScope(enclosingDeclaration: Node) { + while (getNodeLinks(enclosingDeclaration).fakeScopeForSignatureDeclaration) { + enclosingDeclaration = enclosingDeclaration.parent; + } + return enclosingDeclaration; + } + + /** + * 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` + * @param context - The node builder context. Any reused nodes are checked to be pulled from within the scope of the context's enclosingDeclaration. + * @param declaration - The preferred declaration to pull existing type nodes from (the symbol will be used as a fallback to find any annotated declaration) + * @param type - The type to write; an existing annotation must match this type if it's used, otherwise this is the type serialized as a new type node + * @param symbol - The symbol is used both to find an existing annotation if declaration is not provided, and to determine if `unique symbol` should be printed + */ + function serializeTypeForDeclaration(context: NodeBuilderContext, declaration: Declaration | undefined, type: Type, symbol: Symbol) { + const addUndefined = declaration && (isParameter(declaration) || isJSDocParameterTag(declaration)) && requiresAddingImplicitUndefined(declaration); + const enclosingDeclaration = context.enclosingDeclaration; + const oldFlags = context.flags; + if (declaration && hasInferredType(declaration) && !(context.flags & NodeBuilderFlags.NoSyntacticPrinter)) { + syntacticNodeBuilder.serializeTypeOfDeclaration(declaration, context); + } + context.flags |= NodeBuilderFlags.NoSyntacticPrinter; + if (enclosingDeclaration && (!isErrorType(type) || (context.flags & NodeBuilderFlags.AllowUnresolvedNames))) { + const declWithExistingAnnotation = declaration && getNonlocalEffectiveTypeAnnotationNode(declaration) + ? declaration + : getDeclarationWithTypeAnnotation(symbol); + if (declWithExistingAnnotation && !isFunctionLikeDeclaration(declWithExistingAnnotation) && !isGetAccessorDeclaration(declWithExistingAnnotation)) { + // try to reuse the existing annotation + const existing = getNonlocalEffectiveTypeAnnotationNode(declWithExistingAnnotation)!; + const result = !isTypePredicateNode(existing) && tryReuseExistingTypeNode(context, existing, type, declWithExistingAnnotation, addUndefined); + if (result) { + context.flags = oldFlags; + return result; + } + } + } + if ( + type.flags & TypeFlags.UniqueESSymbol && + type.symbol === symbol && (!context.enclosingDeclaration || some(symbol.declarations, d => getSourceFileOfNode(d) === getSourceFileOfNode(context.enclosingDeclaration!))) + ) { + context.flags |= NodeBuilderFlags.AllowUniqueESSymbolType; + } + + const decl = declaration ?? symbol.valueDeclaration ?? symbol.declarations?.[0]; + const expr = decl && isDeclarationWithPossibleInnerTypeNodeReuse(decl) ? getPossibleTypeNodeReuseExpression(decl) : undefined; + + const result = expressionOrTypeToTypeNode(context, expr, type, addUndefined); + context.flags = oldFlags; + return result; + } + + function typeNodeIsEquivalentToType(annotatedDeclaration: Node | undefined, type: Type, typeFromTypeNode: Type) { + if (typeFromTypeNode === type) { + return true; + } + if (annotatedDeclaration && (isParameter(annotatedDeclaration) || isPropertySignature(annotatedDeclaration) || isPropertyDeclaration(annotatedDeclaration)) && annotatedDeclaration.questionToken) { + return getTypeWithFacts(type, TypeFacts.NEUndefined) === typeFromTypeNode; + } + return false; + } + + function serializeReturnTypeForSignature(context: NodeBuilderContext, signature: Signature) { + const suppressAny = context.flags & NodeBuilderFlags.SuppressAnyReturnType; + const flags = context.flags; + if (suppressAny) context.flags &= ~NodeBuilderFlags.SuppressAnyReturnType; // suppress only toplevel `any`s + let returnTypeNode: TypeNode | undefined; + const returnType = getReturnTypeOfSignature(signature); + if (returnType && !(suppressAny && isTypeAny(returnType))) { + if (signature.declaration && !(context.flags & NodeBuilderFlags.NoSyntacticPrinter)) { + syntacticNodeBuilder.serializeReturnTypeForSignature(signature.declaration, context); + } + context.flags |= NodeBuilderFlags.NoSyntacticPrinter; + returnTypeNode = serializeReturnTypeForSignatureWorker(context, signature); + } + else if (!suppressAny) { + returnTypeNode = factory.createKeywordTypeNode(SyntaxKind.AnyKeyword); + } + context.flags = flags; + return returnTypeNode; + } + + function serializeReturnTypeForSignatureWorker(context: NodeBuilderContext, signature: Signature) { + const typePredicate = getTypePredicateOfSignature(signature); + const type = getReturnTypeOfSignature(signature); + if (context.enclosingDeclaration && (!isErrorType(type) || (context.flags & NodeBuilderFlags.AllowUnresolvedNames)) && signature.declaration && !nodeIsSynthesized(signature.declaration)) { + const annotation = signature.declaration && getNonlocalEffectiveReturnTypeAnnotationNode(signature.declaration); + // Default constructor signatures inherited from base classes return the derived class but have the base class declaration + // To ensure we don't serialize the wrong type we check that that return type of the signature corresponds to the declaration return type signature + if (annotation && getTypeFromTypeNode(context, annotation) === type) { + const result = tryReuseExistingTypeNodeHelper(context, annotation); + if (result) { + return result; + } + } + } + if (typePredicate) { + return typePredicateToTypePredicateNodeHelper(typePredicate, context); + } + const expr = signature.declaration && getPossibleTypeNodeReuseExpression(signature.declaration); + return expressionOrTypeToTypeNode(context, expr, type); + } + + function trackExistingEntityName(node: T, context: NodeBuilderContext) { + 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 meaning = getMeaningOfEntityNameReference(node); + let sym: Symbol | undefined; + if (isThisIdentifier(leftmost)) { + // `this` isn't a bindable identifier - skip resolution, find a relevant `this` symbol directly and avoid exhaustive scope traversal + sym = getSymbolOfDeclaration(getThisContainer(leftmost, /*includeArrowFunctions*/ false, /*includeClassComputedPropertyName*/ false)); + if (isSymbolAccessible(sym, leftmost, meaning, /*shouldComputeAliasesToMakeVisible*/ false).accessibility !== SymbolAccessibility.Accessible) { + introducesError = true; + context.tracker.reportInaccessibleThisError(); + } + return { introducesError, node: attachSymbolToLeftmostIdentifier(node) as T }; + } + sym = resolveEntityName(leftmost, meaning, /*ignoreErrors*/ true, /*dontResolveAlias*/ true); + if ( + context.enclosingDeclaration && + !(sym && sym.flags & SymbolFlags.TypeParameter) + ) { + sym = getExportSymbolOfValueSymbolIfExported(sym); + // Some declarations may be transplanted to a new location. + // When this happens we need to make sure that the name has the same meaning at both locations + // We also check for the unknownSymbol because when we create a fake scope some parameters may actually not be usable + // either because they are the expanded rest parameter, + // or because they are the newly added parameters from the tuple, which might have different meanings in the original context + const symAtLocation = resolveEntityName(leftmost, meaning, /*ignoreErrors*/ true, /*dontResolveAlias*/ true, context.enclosingDeclaration); + if ( + // Check for unusable parameters symbols + symAtLocation === unknownSymbol || + // If the symbol is not found, but was not found in the original scope either we probably have an error, don't reuse the node + (symAtLocation === undefined && sym !== undefined) || + // If the symbol is found both in declaration scope and in current scope then it shoudl point to the same reference + (symAtLocation && sym && !getSymbolIfSameReference(getExportSymbolOfValueSymbolIfExported(symAtLocation), sym)) + ) { + // In isolated declaration we will not do rest parameter expansion so there is no need to report on these. + if (symAtLocation !== unknownSymbol) { + context.tracker.reportInferenceFallback(node); + } + introducesError = true; + return { introducesError, node, sym }; + } + } + + if (sym) { + // If a parameter is resolvable in the current context it is also visible, so no need to go to symbol accesibility + if ( + sym.flags & SymbolFlags.FunctionScopedVariable + && sym.valueDeclaration + ) { + if (isPartOfParameterDeclaration(sym.valueDeclaration) || isJSDocParameterTag(sym.valueDeclaration)) { + return { introducesError, node: attachSymbolToLeftmostIdentifier(node) as T }; + } + } + if ( + !(sym.flags & SymbolFlags.TypeParameter) && // Type parameters are visible in the current context if they are are resolvable + !isDeclarationName(node) && + isSymbolAccessible(sym, context.enclosingDeclaration, meaning, /*shouldComputeAliasesToMakeVisible*/ false).accessibility !== SymbolAccessibility.Accessible + ) { + context.tracker.reportInferenceFallback(node); + introducesError = true; + } + else { + context.tracker.trackSymbol(sym, context.enclosingDeclaration, meaning); + } + return { introducesError, node: attachSymbolToLeftmostIdentifier(node) as T }; + } + + return { introducesError, node }; + + /** + * Attaches a `.symbol` member to an identifier, cloning it to do so, so symbol information + * is smuggled out for symbol display information. + */ + function attachSymbolToLeftmostIdentifier(node: Node): Node { + if (node === leftmost) { + const type = getDeclaredTypeOfSymbol(sym!); + const name = sym!.flags & SymbolFlags.TypeParameter ? typeParameterToName(type, context) : factory.cloneNode(node as Identifier); + name.symbol = sym!; // for quickinfo, which uses identifier symbol information + return setTextRange(context, setEmitFlags(name, EmitFlags.NoAsciiEscaping), node); + } + const updated = visitEachChildWorker(node, c => attachSymbolToLeftmostIdentifier(c), /*context*/ undefined); + if (updated !== node) { + setTextRange(context, updated, node); + } + return updated; + } + } + + function serializeTypeName(context: NodeBuilderContext, node: EntityName, isTypeOf?: boolean, typeArguments?: readonly TypeNode[]) { + const meaning = isTypeOf ? SymbolFlags.Value : SymbolFlags.Type; + const symbol = resolveEntityName(node, meaning, /*ignoreErrors*/ true); + if (!symbol) return undefined; + const resolvedSymbol = symbol.flags & SymbolFlags.Alias ? resolveAlias(symbol) : symbol; + if (isSymbolAccessible(symbol, context.enclosingDeclaration, meaning, /*shouldComputeAliasesToMakeVisible*/ false).accessibility !== SymbolAccessibility.Accessible) return undefined; + return symbolToTypeNode(resolvedSymbol, context, meaning, typeArguments); + } + + function canReuseTypeNode(context: NodeBuilderContext, existing: TypeNode) { + if (isInJSFile(existing)) { + if (isLiteralImportTypeNode(existing)) { + // Ensure resolvedSymbol is present + void getTypeFromImportTypeNode(existing); + const nodeSymbol = getNodeLinks(existing).resolvedSymbol; + return ( + !nodeSymbol || + !( + // The import type resolved using jsdoc fallback logic + (!existing.isTypeOf && !(nodeSymbol.flags & SymbolFlags.Type)) || + // The import type had type arguments autofilled by js fallback logic + !(length(existing.typeArguments) >= getMinTypeArgumentCount(getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(nodeSymbol))) + ) + ); + } + } + if (isThisTypeNode(existing)) { + if (context.mapper === undefined) return true; + const type = getTypeFromTypeNode(context, existing, /*noMappedTypes*/ true); + return !!type; + } + if (isTypeReferenceNode(existing)) { + if (isConstTypeReference(existing)) return false; + const type = getTypeFromTypeReference(existing); + const symbol = getNodeLinks(existing).resolvedSymbol; + if (!symbol) return false; + if (symbol.flags & SymbolFlags.TypeParameter) { + const type = getDeclaredTypeOfSymbol(symbol); + if (context.mapper && getMappedType(type, context.mapper) !== type) { + return false; + } + } + if (isInJSDoc(existing)) { + return existingTypeNodeIsNotReferenceOrIsReferenceWithCompatibleTypeArgumentCount(existing, type) + && !getIntendedTypeFromJSDocTypeReference(existing) // We should probably allow the reuse of JSDoc reference types such as String Number etc + && (symbol.flags & SymbolFlags.Type); // JSDoc type annotations can reference values (meaning typeof value) as well as types. We only reuse type nodes + } + } + if ( + isTypeOperatorNode(existing) && + existing.operator === SyntaxKind.UniqueKeyword && + existing.type.kind === SyntaxKind.SymbolKeyword + ) { + const effectiveEnclosingContext = context.enclosingDeclaration && getEnclosingDeclarationIgnoringFakeScope(context.enclosingDeclaration); + return !!findAncestor(existing, n => n === effectiveEnclosingContext); + } + return true; + } + + function serializeExistingTypeNode(context: NodeBuilderContext, typeNode: TypeNode) { + const type = getTypeFromTypeNode(context, typeNode); + return typeToTypeNodeHelper(type, context); + } + + /** + * Do you mean to call this directly? You probably should use `tryReuseExistingTypeNode` instead, + * which performs sanity checking on the type before doing this. + */ + function tryReuseExistingTypeNodeHelper(context: NodeBuilderContext, existing: TypeNode) { + if (cancellationToken && cancellationToken.throwIfCancellationRequested) { + cancellationToken.throwIfCancellationRequested(); + } + let hadError = false; + const { finalizeBoundary, startRecoveryScope } = createRecoveryBoundary(); + const transformed = visitNode(existing, visitExistingNodeTreeSymbols, isTypeNode); + if (!finalizeBoundary()) { + return undefined; + } + context.approximateLength += existing.end - existing.pos; + return transformed; + + function visitExistingNodeTreeSymbols(node: Node): Node | undefined { + // If there was an error in a sibling node bail early, the result will be discarded anyway + if (hadError) return node; + const recover = startRecoveryScope(); + const onExitNewScope = isNewScopeNode(node) ? onEnterNewScope(node) : undefined; + const result = visitExistingNodeTreeSymbolsWorker(node); + onExitNewScope?.(); + + // If there was an error, maybe we can recover by serializing the actual type of the node + if (hadError) { + if (isTypeNode(node) && !isTypePredicateNode(node)) { + recover(); + return serializeExistingTypeNode(context, node); + } + return node; + } + // We want to clone the subtree, so when we mark it up with __pos and __end in quickfixes, + // we don't get odd behavior because of reused nodes. We also need to clone to _remove_ + // the position information if the node comes from a different file than the one the node builder + // is set to build for (even though we are reusing the node structure, the position information + // would make the printer print invalid spans for literals and identifiers, and the formatter would + // choke on the mismatched positonal spans between a parent and an injected child from another file). + return result ? setTextRange(context, result, node) : undefined; + } + + function createRecoveryBoundary() { + let trackedSymbols: TrackedSymbol[]; + let unreportedErrors: (() => void)[]; + const oldTracker = context.tracker; + const oldTrackedSymbols = context.trackedSymbols; + context.trackedSymbols = undefined; + const oldEncounteredError = context.encounteredError; + context.tracker = new SymbolTrackerImpl(context, { + ...oldTracker.inner, + reportCyclicStructureError() { + markError(() => oldTracker.reportCyclicStructureError()); + }, + reportInaccessibleThisError() { + markError(() => oldTracker.reportInaccessibleThisError()); + }, + reportInaccessibleUniqueSymbolError() { + markError(() => oldTracker.reportInaccessibleUniqueSymbolError()); + }, + reportLikelyUnsafeImportRequiredError(specifier) { + markError(() => oldTracker.reportLikelyUnsafeImportRequiredError(specifier)); + }, + reportNonSerializableProperty(name) { + markError(() => oldTracker.reportNonSerializableProperty(name)); + }, + trackSymbol(sym, decl, meaning) { + (trackedSymbols ??= []).push([sym, decl, meaning]); + return false; + }, + moduleResolverHost: context.tracker.moduleResolverHost, + }, context.tracker.moduleResolverHost); + + return { + startRecoveryScope, + finalizeBoundary, + }; + + function markError(unreportedError: () => void) { + hadError = true; + (unreportedErrors ??= []).push(unreportedError); + } + + function startRecoveryScope() { + const trackedSymbolsTop = trackedSymbols?.length ?? 0; + const unreportedErrorsTop = unreportedErrors?.length ?? 0; + return () => { + hadError = false; + // Reset the tracked symbols to before the error + if (trackedSymbols) { + trackedSymbols.length = trackedSymbolsTop; + } + if (unreportedErrors) { + unreportedErrors.length = unreportedErrorsTop; + } + }; + } + + function finalizeBoundary() { + context.tracker = oldTracker; + context.trackedSymbols = oldTrackedSymbols; + context.encounteredError = oldEncounteredError; + + unreportedErrors?.forEach(fn => fn()); + if (hadError) { + return false; + } + trackedSymbols?.forEach( + ([symbol, enclosingDeclaration, meaning]) => + context.tracker.trackSymbol( + symbol, + enclosingDeclaration, + meaning, + ), + ); + return true; + } + } + function onEnterNewScope(node: IntroducesNewScopeNode | ConditionalTypeNode) { + return enterNewScope(context, node, getParametersInScope(node), getTypeParametersInScope(node)); + } + + function tryVisitSimpleTypeNode(node: TypeNode): TypeNode | undefined { + const innerNode = skipTypeParentheses(node); + switch (innerNode.kind) { + case SyntaxKind.TypeReference: + return tryVisitTypeReference(innerNode as TypeReferenceNode); + case SyntaxKind.TypeQuery: + return tryVisitTypeQuery(innerNode as TypeQueryNode); + case SyntaxKind.IndexedAccessType: + return tryVisitIndexedAccess(innerNode as IndexedAccessTypeNode); + case SyntaxKind.TypeOperator: + const typeOperatorNode = innerNode as TypeOperatorNode; + if (typeOperatorNode.operator === SyntaxKind.KeyOfKeyword) { + return tryVisitKeyOf(typeOperatorNode); + } + } + return visitNode(node, visitExistingNodeTreeSymbols, isTypeNode); + } + + function tryVisitIndexedAccess(node: IndexedAccessTypeNode): TypeNode | undefined { + const resultObjectType = tryVisitSimpleTypeNode(node.objectType); + if (resultObjectType === undefined) { + return undefined; + } + return factory.updateIndexedAccessTypeNode(node, resultObjectType, visitNode(node.indexType, visitExistingNodeTreeSymbols, isTypeNode)!); + } + + function tryVisitKeyOf(node: TypeOperatorNode): TypeNode | undefined { + Debug.assertEqual(node.operator, SyntaxKind.KeyOfKeyword); + const type = tryVisitSimpleTypeNode(node.type); + if (type === undefined) { + return undefined; + } + return factory.updateTypeOperatorNode(node, type); + } + + function tryVisitTypeQuery(node: TypeQueryNode): TypeNode | undefined { + const { introducesError, node: exprName } = trackExistingEntityName(node.exprName, context); + if (!introducesError) { + return factory.updateTypeQueryNode( + node, + exprName, + visitNodes(node.typeArguments, visitExistingNodeTreeSymbols, isTypeNode), + ); + } + + const serializedName = serializeTypeName(context, node.exprName, /*isTypeOf*/ true); + if (serializedName) { + return setTextRange(context, serializedName, node.exprName); + } + } + + function tryVisitTypeReference(node: TypeReferenceNode): TypeNode | undefined { + if (canReuseTypeNode(context, node)) { + const { introducesError, node: newName } = trackExistingEntityName(node.typeName, context); + const typeArguments = visitNodes(node.typeArguments, visitExistingNodeTreeSymbols, isTypeNode); + + if (!introducesError) { + const updated = factory.updateTypeReferenceNode( + node, + newName, + typeArguments, + ); + return setTextRange(context, updated, node); + } + else { + const serializedName = serializeTypeName(context, node.typeName, /*isTypeOf*/ false, typeArguments); + if (serializedName) { + return setTextRange(context, serializedName, node.typeName); + } + } + } + } + + function visitExistingNodeTreeSymbolsWorker(node: Node): Node | undefined { + if (isJSDocTypeExpression(node)) { + // Unwrap JSDocTypeExpressions + return visitNode(node.type, visitExistingNodeTreeSymbols, isTypeNode); + } + // 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, isTypeNode)!, factory.createLiteralTypeNode(factory.createNull())]); + } + if (isJSDocOptionalType(node)) { + return factory.createUnionTypeNode([visitNode(node.type, visitExistingNodeTreeSymbols, isTypeNode)!, factory.createKeywordTypeNode(SyntaxKind.UndefinedKeyword)]); + } + if (isJSDocNonNullableType(node)) { + return visitNode(node.type, visitExistingNodeTreeSymbols); + } + if (isJSDocVariadicType(node)) { + return factory.createArrayTypeNode(visitNode(node.type, visitExistingNodeTreeSymbols, isTypeNode)!); + } + if (isJSDocTypeLiteral(node)) { + return factory.createTypeLiteralNode(map(node.jsDocPropertyTags, t => { + const name = visitNode(isIdentifier(t.name) ? t.name : t.name.right, visitExistingNodeTreeSymbols, isIdentifier)!; + const typeViaParent = getTypeOfPropertyOfType(getTypeFromTypeNode(context, node), name.escapedText); + const overrideTypeNode = typeViaParent && t.typeExpression && getTypeFromTypeNode(context, t.typeExpression.type) !== typeViaParent ? typeToTypeNodeHelper(typeViaParent, context) : undefined; + + 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, isTypeNode)) || 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, isTypeNode), + )], + visitNode(node.typeArguments![1], visitExistingNodeTreeSymbols, isTypeNode), + )]); + } + if (isJSDocFunctionType(node)) { + if (isJSDocConstructSignature(node)) { + let newTypeNode: TypeNode | undefined; + return factory.createConstructorTypeNode( + /*modifiers*/ undefined, + visitNodes(node.typeParameters, visitExistingNodeTreeSymbols, isTypeParameterDeclaration), + mapDefined(node.parameters, (p, i) => + p.name && isIdentifier(p.name) && p.name.escapedText === "new" ? (newTypeNode = p.type, undefined) : factory.createParameterDeclaration( + /*modifiers*/ undefined, + getEffectiveDotDotDotForParameter(p), + setTextRange(context, factory.createIdentifier(getNameForJSDocFunctionParameter(p, i)), p), + factory.cloneNode(p.questionToken), + visitNode(p.type, visitExistingNodeTreeSymbols, isTypeNode), + /*initializer*/ undefined, + )), + visitNode(newTypeNode || node.type, visitExistingNodeTreeSymbols, isTypeNode) || factory.createKeywordTypeNode(SyntaxKind.AnyKeyword), + ); + } + else { + return factory.createFunctionTypeNode( + visitNodes(node.typeParameters, visitExistingNodeTreeSymbols, isTypeParameterDeclaration), + map(node.parameters, (p, i) => + factory.createParameterDeclaration( + /*modifiers*/ undefined, + getEffectiveDotDotDotForParameter(p), + setTextRange(context, factory.createIdentifier(getNameForJSDocFunctionParameter(p, i)), p), + factory.cloneNode(p.questionToken), + visitNode(p.type, visitExistingNodeTreeSymbols, isTypeNode), + /*initializer*/ undefined, + )), + visitNode(node.type, visitExistingNodeTreeSymbols, isTypeNode) || factory.createKeywordTypeNode(SyntaxKind.AnyKeyword), + ); + } + } + if (isThisTypeNode(node)) { + if (canReuseTypeNode(context, node)) { + return node; + } + hadError = true; + return node; + } + if (isTypeParameterDeclaration(node)) { + return factory.updateTypeParameterDeclaration( + node, + visitNodes(node.modifiers, visitExistingNodeTreeSymbols, isModifier), + setTextRange(context, typeParameterToName(getDeclaredTypeOfSymbol(getSymbolOfDeclaration(node)), context), node), + visitNode(node.constraint, visitExistingNodeTreeSymbols, isTypeNode), + visitNode(node.default, visitExistingNodeTreeSymbols, isTypeNode), + ); + } + + if (isIndexedAccessTypeNode(node)) { + const result = tryVisitIndexedAccess(node); + if (!result) { + hadError = true; + return node; + } + return result; + } + + if (isTypeReferenceNode(node)) { + const result = tryVisitTypeReference(node); + if (result) { + return result; + } + hadError = true; + return 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 setTextRange(context, typeToTypeNodeHelper(getTypeFromTypeNode(context, node), context), node); + } + return factory.updateImportTypeNode( + node, + factory.updateLiteralTypeNode(node.argument, rewriteModuleSpecifier(node, node.argument.literal)), + visitNode(node.attributes, visitExistingNodeTreeSymbols, isImportAttributes), + visitNode(node.qualifier, visitExistingNodeTreeSymbols, isEntityName), + visitNodes(node.typeArguments, visitExistingNodeTreeSymbols, isTypeNode), + node.isTypeOf, + ); + } + if (isNamedDeclaration(node) && node.name.kind === SyntaxKind.ComputedPropertyName && !isLateBindableName(node.name)) { + if (!(context.flags & NodeBuilderFlags.AllowUnresolvedNames && hasDynamicName(node) && isEntityNameExpression(node.name.expression) && checkComputedPropertyName(node.name).flags & TypeFlags.Any)) { + return undefined; + } + } + if ( + (isFunctionLike(node) && !node.type) + || (isPropertyDeclaration(node) && !node.type && !node.initializer) + || (isPropertySignature(node) && !node.type && !node.initializer) + || (isParameter(node) && !node.type && !node.initializer) + ) { + let visited = visitEachChild(node, visitExistingNodeTreeSymbols); + if (visited === node) { + visited = setTextRange(context, factory.cloneNode(node), node); + } + (visited as Mutable).type = factory.createKeywordTypeNode(SyntaxKind.AnyKeyword); + if (isParameter(node)) { + (visited as Mutable).modifiers = undefined; + } + return visited; + } + if (isTypeQueryNode(node)) { + const result = tryVisitTypeQuery(node); + if (!result) { + hadError = true; + return node; + } + return result; + } + if (isComputedPropertyName(node) && isEntityNameExpression(node.expression)) { + const { node: result, introducesError } = trackExistingEntityName(node.expression, context); + if (!introducesError) { + return factory.updateComputedPropertyName(node, result); + } + else { + const type = getWidenedType(getRegularTypeOfExpression(node.expression)); + const computedPropertyNameType = typeToTypeNodeHelper(type, context); + let literal; + if (isLiteralTypeNode(computedPropertyNameType)) { + literal = computedPropertyNameType.literal; + } + else { + const evaluated = evaluateEntityNameExpression(node.expression); + const literalNode = typeof evaluated.value === "string" ? factory.createStringLiteral(evaluated.value, /*isSingleQuote*/ undefined) : + typeof evaluated.value === "number" ? factory.createNumericLiteral(evaluated.value, /*numericLiteralFlags*/ 0) : + undefined; + if (!literalNode) { + if (isImportTypeNode(computedPropertyNameType)) { + trackComputedName(node.expression, context.enclosingDeclaration, context); + } + return node; + } + literal = literalNode; + } + if (literal.kind === SyntaxKind.StringLiteral && isIdentifierText(literal.text, getEmitScriptTarget(compilerOptions))) { + return factory.createIdentifier(literal.text); + } + if (literal.kind === SyntaxKind.NumericLiteral && !literal.text.startsWith("-")) { + return literal; + } + return factory.updateComputedPropertyName(node, literal); + } + } + if (isTypePredicateNode(node)) { + let parameterName; + if (isIdentifier(node.parameterName)) { + const { node: result, introducesError } = trackExistingEntityName(node.parameterName, context); + // Should not usually happen the only case is when a type predicate comes from a JSDoc type annotation with it's own parameter symbol definition. + // /** @type {(v: unknown) => v is undefined} */ + // const isUndef = v => v === undefined; + hadError = hadError || introducesError; + parameterName = result; + } + else { + parameterName = factory.cloneNode(node.parameterName); + } + return factory.updateTypePredicateNode(node, factory.cloneNode(node.assertsModifier), parameterName, visitNode(node.type, visitExistingNodeTreeSymbols, isTypeNode)); + } + + if (isTupleTypeNode(node) || isTypeLiteralNode(node) || isMappedTypeNode(node)) { + const visited = visitEachChild(node, visitExistingNodeTreeSymbols); + const clone = setTextRange(context, visited === node ? factory.cloneNode(node) : visited, node); + const flags = getEmitFlags(clone); + setEmitFlags(clone, flags | (context.flags & NodeBuilderFlags.MultilineObjectLiterals && isTypeLiteralNode(node) ? 0 : EmitFlags.SingleLine)); + return clone; + } + if (isStringLiteral(node) && !!(context.flags & NodeBuilderFlags.UseSingleQuotesForStringLiteralType) && !node.singleQuote) { + const clone = factory.cloneNode(node); + (clone as Mutable).singleQuote = true; + return clone; + } + if (isConditionalTypeNode(node)) { + const checkType = visitNode(node.checkType, visitExistingNodeTreeSymbols, isTypeNode)!; + + const disposeScope = onEnterNewScope(node); + const extendType = visitNode(node.extendsType, visitExistingNodeTreeSymbols, isTypeNode)!; + const trueType = visitNode(node.trueType, visitExistingNodeTreeSymbols, isTypeNode)!; + disposeScope(); + const falseType = visitNode(node.falseType, visitExistingNodeTreeSymbols, isTypeNode)!; + return factory.updateConditionalTypeNode( + node, + checkType, + extendType, + trueType, + falseType, + ); + } + + if (isTypeOperatorNode(node)) { + if (node.operator === SyntaxKind.UniqueKeyword && node.type.kind === SyntaxKind.SymbolKeyword) { + if (!canReuseTypeNode(context, node)) { + hadError = true; + return node; + } + } + else if (node.operator === SyntaxKind.KeyOfKeyword) { + const result = tryVisitKeyOf(node); + if (!result) { + hadError = true; + return node; + } + return result; + } + } + + return visitEachChild(node, visitExistingNodeTreeSymbols); + + function visitEachChild(node: T, visitor: Visitor): T; + function visitEachChild(node: T | undefined, visitor: Visitor): T | undefined; + function visitEachChild(node: T | undefined, visitor: Visitor): T | undefined { + const nonlocalNode = !context.enclosingFile || context.enclosingFile !== getSourceFileOfNode(node); + return visitEachChildWorker(node, visitor, /*context*/ undefined, nonlocalNode ? visitNodesWithoutCopyingPositions : undefined); + } + + function visitNodesWithoutCopyingPositions( + nodes: NodeArray | undefined, + visitor: Visitor, + test?: (node: Node) => boolean, + start?: number, + count?: number, + ): NodeArray | undefined { + let result = visitNodes(nodes, visitor, test, start, count); + if (result) { + if (result.pos !== -1 || result.end !== -1) { + if (result === nodes) { + result = factory.createNodeArray(nodes, nodes.hasTrailingComma); + } + setTextRangePosEnd(result, -1, -1); + } + } + return result; + } + + function getEffectiveDotDotDotForParameter(p: ParameterDeclaration) { + return p.dotDotDotToken || (p.type && isJSDocVariadicType(p.type) ? factory.createToken(SyntaxKind.DotDotDotToken) : 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}`; + } + + function rewriteModuleSpecifier(parent: ImportTypeNode, lit: StringLiteral) { + if (context.bundled || context.enclosingFile !== getSourceFileOfNode(lit)) { + let name = lit.text; + const nodeSymbol = getNodeLinks(node).resolvedSymbol; + const meaning = parent.isTypeOf ? SymbolFlags.Value : SymbolFlags.Type; + const parentSymbol = nodeSymbol + && isSymbolAccessible(nodeSymbol, context.enclosingDeclaration, meaning, /*shouldComputeAliasesToMakeVisible*/ false).accessibility === SymbolAccessibility.Accessible + && lookupSymbolChain(nodeSymbol, context, meaning, /*yieldModuleSymbol*/ true)[0]; + if (parentSymbol && isExternalModuleSymbol(parentSymbol)) { + name = getSpecifierForModuleSymbol(parentSymbol, context); + } + else { + const targetFile = getExternalModuleFileFromDeclaration(parent); + if (targetFile) { + name = getSpecifierForModuleSymbol(targetFile.symbol, context); + } + } + if (name.includes("/node_modules/")) { + context.encounteredError = true; + if (context.tracker.reportLikelyUnsafeImportRequiredError) { + context.tracker.reportLikelyUnsafeImportRequiredError(name); + } + } + if (name !== lit.text) { + return setOriginalNode(factory.createStringLiteral(name), lit); + } + } + return visitNode(lit, visitExistingNodeTreeSymbols, isStringLiteral)!; + } + } + } + + function symbolTableToDeclarationStatements(symbolTable: SymbolTable, context: NodeBuilderContext): Statement[] { + const serializePropertySymbolForClass = makeSerializePropertySymbol(factory.createPropertyDeclaration, SyntaxKind.MethodDeclaration, /*useAccessors*/ true); + const serializePropertySymbolForInterfaceWorker = makeSerializePropertySymbol((mods, name, question, type) => factory.createPropertySignature(mods, name, question, type), SyntaxKind.MethodSignature, /*useAccessors*/ 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: Map[] = []; + const oldcontext = context; + context = { + ...oldcontext, + usedSymbolNames: new Set(oldcontext.usedSymbolNames), + remappedSymbolNames: new Map(), + remappedSymbolReferences: new Map(oldcontext.remappedSymbolReferences?.entries()), + tracker: undefined!, + }; + const tracker: SymbolTracker = { + ...oldcontext.tracker.inner, + trackSymbol: (sym, decl, meaning) => { + if (context.remappedSymbolNames?.has(getSymbolId(sym))) return false; // If the context has a remapped name for the symbol, it *should* mean it's been made visible + const accessibleResult = isSymbolAccessible(sym, decl, meaning, /*shouldComputeAliasesToMakeVisible*/ 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)) { + // Only include referenced privates in the same file. Weird JS aliases may expose privates + // from other files - assume JS transforms will make those available via expected means + const root = chain[0]; + const contextFile = getSourceFileOfNode(oldcontext.enclosingDeclaration); + if (some(root.declarations, d => getSourceFileOfNode(d) === contextFile)) { + includePrivateSymbol(root); + } + } + } + else if (oldcontext.tracker.inner?.trackSymbol) { + return oldcontext.tracker.inner.trackSymbol(sym, decl, meaning); + } + return false; + }, + }; + context.tracker = new SymbolTrackerImpl(context, tracker, oldcontext.tracker.moduleResolverHost); + forEachEntry(symbolTable, (symbol, name) => { + const baseName = unescapeLeadingUnderscores(name); + void getInternalSymbolName(symbol, baseName); // Called to cache values into `usedSymbolNames` and `remappedSymbolNames` + }); + let addingDeclare = !context.bundled; + const exportEquals = symbolTable.get(InternalSymbolName.ExportEquals); + if (exportEquals && symbolTable.size > 1 && exportEquals.flags & (SymbolFlags.Alias | SymbolFlags.Module)) { + 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, /*propertyName*/ 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]; + } + } + 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.includes(s as ExportDeclaration)), + factory.createExportDeclaration( + /*modifiers*/ undefined, + /*isTypeOnly*/ false, + factory.createNamedExports(flatMap(group, e => cast(e.exportClause, isNamedExports).elements)), + group[0].moduleSpecifier, + ), + ]; + } + } + } + } + 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.attributes && !!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 && e.name.kind !== SyntaxKind.StringLiteral) { + // export {name} - look thru `statements` for `name`, and if all results can take an `export` modifier, do so and filter it + const name = e.name; + const indices = indicesOf(statements); + const associatedIndices = filter(indices, i => nodeHasName(statements[i], 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.attributes, + ); + } + } + return 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; + } + + function addExportModifier(node: Extract) { + const flags = (getEffectiveModifierFlags(node) | ModifierFlags.Export) & ~ModifierFlags.Ambient; + return factory.replaceModifiers(node, flags); + } + + function removeExportModifier(node: Extract) { + const flags = getEffectiveModifierFlags(node) & ~ModifierFlags.Export; + return factory.replaceModifiers(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); + }); + 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 { + void getPropertiesOfType(getTypeOfSymbol(symbol)); // resolve symbol's type and properties, which should trigger any required merges + // 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 scopeCleanup = cloneNodeBuilderContext(context); + serializeSymbolWorker(symbol, isPrivate, propertyAsAlias); + scopeCleanup(); + } + } + + // 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, escapedSymbolName = symbol.escapedName): void { + const symbolName = unescapeLeadingUnderscores(escapedSymbolName); + const isDefault = escapedSymbolName === 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) && + escapedSymbolName !== 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 | SymbolFlags.Accessor) + && escapedSymbolName !== 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 (type.symbol && type.symbol !== symbol && type.symbol.flags & SymbolFlags.Function && some(type.symbol.declarations, isFunctionExpressionOrArrowFunction) && (type.symbol.members?.size || type.symbol.exports?.size)) { + // assignment of a anonymous expando/class-like function, the func/ns/merge branch below won't trigger, + // and the assignment form has to reference the unreachable anonymous type so will error. + // Instead, serialize the type's symbol, but with the current symbol's name, rather than the anonymous one. + if (!context.remappedSymbolReferences) { + context.remappedSymbolReferences = new Map(); + } + context.remappedSymbolReferences.set(getSymbolId(type.symbol), symbol); // save name remapping as local name for target symbol + serializeSymbolWorker(type.symbol, isPrivate, propertyAsAlias, escapedSymbolName); + context.remappedSymbolReferences.delete(getSymbolId(type.symbol)); + } + else 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 { + // 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 + : isConstantVariable(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 { + const statement = setTextRange( + context, + factory.createVariableStatement( + /*modifiers*/ undefined, + factory.createVariableDeclarationList([ + factory.createVariableDeclaration(name, /*exclamationToken*/ undefined, serializeTypeForDeclaration(context, /*declaration*/ undefined, type, symbol)), + ], 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; + } + } + } + } + } + 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) { + 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*/ (node as ExportDeclaration).isTypeOnly, /*exportClause*/ undefined, factory.createStringLiteral(getSpecifierForModuleSymbol(resolvedModule, context))), ModifierFlags.None); + } + } + } + if (needsPostExportDefault) { + addResult(factory.createExportAssignment(/*modifiers*/ undefined, /*isExportEquals*/ 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, isPartOfParameterDeclaration)) 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.replaceModifiers(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) + && tryReuseExistingNonParameterTypeNode(context, jsdocAliasDecl.typeExpression.type, aliasType, /*host*/ undefined) + || 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) { + let exports = arrayFrom(getExportsOfSymbol(symbol).values()); + const merged = getMergedSymbol(symbol); + if (merged !== symbol) { + const membersSet = new Set(exports); + for (const exported of getExportsOfSymbol(merged).values()) { + if (!(getSymbolFlags(resolveSymbol(exported)) & SymbolFlags.Value)) { + membersSet.add(exported); + } + } + exports = arrayFrom(membersSet); + } + return filter(exports, m => isNamespaceMember(m) && isIdentifierText(m.escapedName as string, ScriptTarget.ESNext)); + } + + function isTypeOnlyNamespace(symbol: Symbol) { + return every(getNamespaceMembersForSerialization(symbol), m => !(getSymbolFlags(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, + factory.createIdentifier(localName), + nsBody, + NodeFlags.Namespace, + ), + ModifierFlags.None, + ); + } + } + + 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 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) }) as FunctionDeclaration; + addResult(setTextRange(context, 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 getSignatureTextRangeLocation(signature: Signature) { + if (signature.declaration && signature.declaration.parent) { + if (isBinaryExpression(signature.declaration.parent) && getAssignmentDeclarationKind(signature.declaration.parent) === AssignmentDeclarationKind.Property) { + return signature.declaration.parent; + } + // 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 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 as Extract[], 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)); + if (introducesError) { + return cleanup(/*result*/ undefined); + } + } + return cleanup(factory.createExpressionWithTypeArguments( + expr, + map(e.typeArguments, a => + tryReuseExistingNonParameterTypeNode(context, a, getTypeFromTypeNode(context, a)) + || typeToTypeNodeHelper(getTypeFromTypeNode(context, a), context)), + )); + + function cleanup(result: T): T { + context.enclosingDeclaration = oldEnclosing; + return result; + } + }); + 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 = getTypeWithThisArgument(getDeclaredTypeOfClassOrInterface(symbol)) as InterfaceType; + 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( + context, + 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 getSomeTargetNameFromDeclarations(declarations: Declaration[] | undefined) { + return firstDefined(declarations, d => { + if (isImportSpecifier(d) || isExportSpecifier(d)) { + return moduleExportNameTextUnescaped(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; + }); + } + + 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 && 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( + /*isTypeOnly*/ false, + /*name*/ undefined, + factory.createNamedImports([factory.createImportSpecifier( + /*isTypeOnly*/ false, + propertyName && isIdentifier(propertyName) ? factory.createIdentifier(idText(propertyName)) : undefined, + factory.createIdentifier(localName), + )]), + ), + factory.createStringLiteral(specifier), + /*attributes*/ 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, d => isSourceFile(d) && isJsonSourceFile(d))) { + 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: { + const generatedSpecifier = getSpecifierForModuleSymbol(target.parent || target, context); // generate specifier (even though we're reusing and existing one) for ambient module reference include side effects + const specifier = context.bundled ? factory.createStringLiteral(generatedSpecifier) : (node as ImportClause).parent.moduleSpecifier; + const attributes = isImportDeclaration(node.parent) ? node.parent.attributes : undefined; + const isTypeOnly = isJSDocImportTag((node as ImportClause).parent); + addResult( + factory.createImportDeclaration( + /*modifiers*/ undefined, + factory.createImportClause(isTypeOnly, factory.createIdentifier(localName), /*namedBindings*/ undefined), + specifier, + attributes, + ), + ModifierFlags.None, + ); + break; + } + case SyntaxKind.NamespaceImport: { + const generatedSpecifier = getSpecifierForModuleSymbol(target.parent || target, context); // generate specifier (even though we're reusing and existing one) for ambient module reference include side effects + const specifier = context.bundled ? factory.createStringLiteral(generatedSpecifier) : (node as NamespaceImport).parent.parent.moduleSpecifier; + const isTypeOnly = isJSDocImportTag((node as NamespaceImport).parent.parent); + addResult( + factory.createImportDeclaration( + /*modifiers*/ undefined, + factory.createImportClause(isTypeOnly, /*name*/ undefined, factory.createNamespaceImport(factory.createIdentifier(localName))), + specifier, + (node as ImportClause).parent.attributes, + ), + 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: { + const generatedSpecifier = getSpecifierForModuleSymbol(target.parent || target, context); // generate specifier (even though we're reusing and existing one) for ambient module reference include side effects + const specifier = context.bundled ? factory.createStringLiteral(generatedSpecifier) : (node as ImportSpecifier).parent.parent.parent.moduleSpecifier; + const isTypeOnly = isJSDocImportTag((node as ImportSpecifier).parent.parent.parent); + addResult( + factory.createImportDeclaration( + /*modifiers*/ undefined, + factory.createImportClause( + isTypeOnly, + /*name*/ undefined, + factory.createNamedImports([ + factory.createImportSpecifier( + /*isTypeOnly*/ false, + localName !== verbatimTargetName ? factory.createIdentifier(verbatimTargetName) : undefined, + factory.createIdentifier(localName), + ), + ]), + ), + specifier, + (node as ImportSpecifier).parent.parent.parent.attributes, + ), + 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; + if (specifier) { + const propertyName = (node as ExportSpecifier).propertyName; + if (propertyName && moduleExportNameIsDefault(propertyName)) { + verbatimTargetName = InternalSymbolName.Default; + } + } + // 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 { + 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 prevDisableTrackSymbol = context.tracker.disableTrackSymbol; + context.tracker.disableTrackSymbol = true; + if (isExportAssignmentCompatibleSymbolName) { + results.push(factory.createExportAssignment( + /*modifiers*/ undefined, + isExportEquals, + symbolToExpression(target, context, SymbolFlags.All), + )); + } + 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); + } + } + context.tracker.disableTrackSymbol = prevDisableTrackSymbol; + 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 flags = context.enclosingDeclaration?.kind === SyntaxKind.ModuleDeclaration && (!(symbol.flags & SymbolFlags.Accessor) || symbol.flags & SymbolFlags.SetAccessor) ? NodeFlags.Let : NodeFlags.Const; + const statement = factory.createVariableStatement( + /*modifiers*/ undefined, + factory.createVariableDeclarationList([ + factory.createVariableDeclaration(varName, /*exclamationToken*/ undefined, serializeTypeForDeclaration(context, /*declaration*/ undefined, typeToSerialize, symbol)), + ], flags), + ); + // 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) && + !some(typeToSerialize.symbol?.declarations, isTypeNode) && // If the type comes straight from a type node, we shouldn't try to break it up + !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 => { + if (!isIdentifierText(symbolName(p), languageVersion)) { + return false; + } + if (!(p.flags & SymbolFlags.Accessor)) { + return true; + } + return getNonMissingTypeOfSymbol(p) === getWriteTypeOfSymbol(p); + }); + } + + 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 || p.escapedName === "constructor" || + (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) { + const setter = p.declarations && forEach(p.declarations, d => { + if (d.kind === SyntaxKind.SetAccessor) { + return d as SetAccessorDeclaration; + } + if (isCallExpression(d) && isBindableObjectDefinePropertyCall(d)) { + return forEach(d.arguments[2].properties, propDecl => { + const id = getNameOfDeclaration(propDecl); + if (!!id && isIdentifier(id) && idText(id) === "set") { + return propDecl; + } + }); + } + }); + + Debug.assert(!!setter); + const paramSymbol = isFunctionLikeDeclaration(setter) ? getSignatureFromDeclaration(setter).parameters[0] : undefined; + + result.push(setTextRange( + context, + factory.createSetAccessorDeclaration( + factory.createModifiersFromModifierFlags(flag), + name, + [factory.createParameterDeclaration( + /*modifiers*/ undefined, + /*dotDotDotToken*/ undefined, + paramSymbol ? parameterToParameterDeclarationName(paramSymbol, getEffectiveParameterDeclaration(paramSymbol), context) : "value", + /*questionToken*/ undefined, + isPrivate ? undefined : serializeTypeForDeclaration(context, /*declaration*/ undefined, getWriteTypeOfSymbol(p), p), + )], + /*body*/ undefined, + ), + p.declarations?.find(isSetAccessor) || firstPropertyLikeDecl, + )); + } + if (p.flags & SymbolFlags.GetAccessor) { + const isPrivate = modifierFlags & ModifierFlags.Private; + result.push(setTextRange( + context, + factory.createGetAccessorDeclaration( + factory.createModifiersFromModifierFlags(flag), + name, + [], + isPrivate ? undefined : serializeTypeForDeclaration(context, /*declaration*/ undefined, getTypeOfSymbol(p), p), + /*body*/ undefined, + ), + p.declarations?.find(isGetAccessor) || firstPropertyLikeDecl, + )); + } + 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( + context, + createProperty( + factory.createModifiersFromModifierFlags((isReadonlySymbol(p) ? ModifierFlags.Readonly : 0) | flag), + name, + p.flags & SymbolFlags.Optional ? factory.createToken(SyntaxKind.QuestionToken) : undefined, + isPrivate ? undefined : serializeTypeForDeclaration(context, /*declaration*/ undefined, getWriteTypeOfSymbol(p), p), + // 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( + context, + 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], + ); + } + + 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(context, decl, location)); + } + 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 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 (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 + } + } + } + let privateProtected: ModifierFlags = 0; + for (const s of signatures) { + if (s.declaration) { + privateProtected |= getSelectedEffectiveModifierFlags(s.declaration, ModifierFlags.Private | ModifierFlags.Protected); + } + } + if (privateProtected) { + return [setTextRange( + context, + factory.createConstructorDeclaration( + factory.createModifiersFromModifierFlags(privateProtected), + /*parameters*/ [], + /*body*/ undefined, + ), + signatures[0].declaration, + )]; + } + } + + 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(context, 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; + } + + 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), /*typeArguments*/ 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); + } + } + + 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), /*typeArguments*/ undefined); + } + } + + 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; + } + + 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 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 typePredicateToString(typePredicate: TypePredicate, enclosingDeclaration?: Node, flags: TypeFormatFlags = TypeFormatFlags.UseAliasDefinedOutsideCurrentScope, writer?: EmitTextWriter): string { + return writer ? typePredicateToStringWorker(writer).getText() : usingSingleLineStringWriter(typePredicateToStringWorker); + + function typePredicateToStringWorker(writer: EmitTextWriter) { + const nodeBuilderFlags = toNodeBuilderFlags(flags) | NodeBuilderFlags.IgnoreErrors | NodeBuilderFlags.WriteTypeParametersInQualifiedName; + const predicate = nodeBuilder.typePredicateToTypePredicateNode(typePredicate, enclosingDeclaration, nodeBuilderFlags)!; // TODO: GH#18217 + const printer = createPrinterWithRemoveComments(); + 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 = 0 as TypeFlags; + 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.EnumLike)) { + const baseType = t.flags & TypeFlags.BooleanLiteral ? booleanType : getBaseTypeOfEnumLikeType(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); + } + } + if (flags & TypeFlags.Null) result.push(nullType); + if (flags & TypeFlags.Undefined) result.push(undefinedType); + return result || types; + } + + function visibilityToString(flags: ModifierFlags): string { + if (flags === ModifierFlags.Private) { + return "private"; + } + if (flags === ModifierFlags.Protected) { + return "protected"; + } + return "public"; + } + + 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 (isTypeAliasDeclaration(node)) { + return getSymbolOfDeclaration(node); + } + } + return undefined; + } + + function isTopLevelInExternalModuleAugmentation(node: Node): boolean { + return node && node.parent && + node.parent.kind === SyntaxKind.ModuleBlock && + isExternalModuleAugmentation(node.parent.parent); + } + + 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; + } + 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?.remappedSymbolReferences?.has(getSymbolId(symbol))) { + symbol = context.remappedSymbolReferences.get(getSymbolId(symbol))!; + } + 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)"; + } + } + 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; + } + + 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 ( + !(getCombinedModifierFlagsCached(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 + + 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: ModuleExportName, setVisibility?: boolean): Node[] | undefined { + let exportSymbol: Symbol | undefined; + if (node.kind !== SyntaxKind.StringLiteral && node.parent && node.parent.kind === SyntaxKind.ExportAssignment) { + exportSymbol = resolveName(node, node, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias, /*nameNotFoundMessage*/ undefined, /*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, /*nameNotFoundMessage*/ 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; + } + + function findResolutionCycleStartIndex(target: TypeSystemEntity, propertyName: TypeSystemPropertyName): number { + for (let i = resolutionTargets.length - 1; i >= resolutionStart; i--) { + if (resolutionTargetHasProperty(resolutionTargets[i], resolutionPropertyNames[i])) { + return -1; + } + if (resolutionTargets[i] === target && resolutionPropertyNames[i] === propertyName) { + return i; + } + } + return -1; + } + + function resolutionTargetHasProperty(target: TypeSystemEntity, propertyName: TypeSystemPropertyName): boolean { + switch (propertyName) { + case TypeSystemPropertyName.Type: + return !!getSymbolLinks(target as Symbol).type; + 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; + case TypeSystemPropertyName.ParameterInitializerContainsUndefined: + return getNodeLinks(target as ParameterDeclaration).parameterInitializerContainsUndefined !== undefined; + } + 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; + } + + // 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; + } + + /** + * Return the type of the matching property or index signature in the given type, or undefined + * if no matching property or index signature exists. Add optionality to index signature types. + */ + function getTypeOfPropertyOrIndexSignatureOfType(type: Type, name: __String): Type | undefined { + let propType; + return getTypeOfPropertyOfType(type, name) || + (propType = getApplicableIndexInfoForName(type, name)?.type) && + addOptionality(propType, /*isProperty*/ true, /*isOptional*/ true); + } + + function isTypeAny(type: Type | undefined) { + return type && (type.flags & TypeFlags.Any) !== 0; + } + + 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); + } + + // 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 = getSymbolOfDeclaration(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)); + } + + let omitKeyType = getUnionType(map(properties, getLiteralTypeFromPropertyName)); + + const spreadableProperties: Symbol[] = []; + const unspreadableToRestKeys: Type[] = []; + + 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); + } + } + + 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]); + } + + if (omitKeyType.flags & TypeFlags.Never) { + return source; + } + + 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; + } + + function isGenericTypeWithUndefinedConstraint(type: Type) { + return !!(type.flags & TypeFlags.Instantiable) && maybeTypeOfKind(getBaseConstraintOfType(type) || unknownType, TypeFlags.Undefined); + } + + 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); + } + + // 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 && canHaveFlowNode(parentAccess) && parentAccess.flowNode) { + const propName = getDestructuringPropertyName(node); + if (propName) { + const literal = setTextRangeWorker(parseNodeFactory.createStringLiteral(propName), node); + const lhsExpr = isLeftHandSideExpression(parentAccess) ? parentAccess : parseNodeFactory.createParenthesizedExpression(parentAccess); + const result = setTextRangeWorker(parseNodeFactory.createElementAccessExpression(lhsExpr, literal), node); + setParent(literal, result); + setParent(result, node); + if (lhsExpr !== parentAccess) { + setParent(lhsExpr, result); + } + result.flowNode = parentAccess.flowNode; + return result; + } + } + } + + 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 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 getLiteralPropertyNameText(name: PropertyName) { + const type = getLiteralTypeFromPropertyName(name); + return type.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral) ? "" + (type as StringLiteralType | NumberLiteralType).value : undefined; + } + + /** 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, /*noTupleBoundsCheck*/ false); + } + + function getBindingElementTypeFromParentType(declaration: BindingElement, parentType: Type, noTupleBoundsCheck: boolean): 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 && isPartOfParameterDeclaration(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 && !(hasTypeFacts(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); + } + 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); + } + } + 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. + const baseConstraint = mapType(parentType, t => t.flags & TypeFlags.InstantiableNonPrimitive ? getBaseConstraintOrType(t) : t); + type = everyType(baseConstraint, isTupleType) ? + mapType(baseConstraint, t => sliceTupleType(t as TupleTypeReference, index)) : + createArrayType(elementType); + } + else if (isArrayLikeType(parentType)) { + const indexType = getNumberLiteralType(index); + const accessFlags = AccessFlags.ExpressionPosition | (noTupleBoundsCheck || 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 && !(hasTypeFacts(checkDeclarationInitializer(declaration, CheckMode.Normal), TypeFacts.IsUndefined)) ? getNonUndefinedType(type) : type; + } + 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); + } + 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; + } + + function isEmptyArrayLiteral(node: Expression) { + const expr = skipParentheses(node, /*excludeJSDocTypeAssertions*/ true); + return expr.kind === SyntaxKind.ArrayLiteralExpression && (expr as ArrayLiteralExpression).elements.length === 0; + } + + function addOptionality(type: Type, isProperty = false, isOptional = true): Type { + return strictNullChecks && isOptional ? getOptionalType(type, isProperty) : type; + } + + // 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) || isJSDocPropertyTag(declaration); + const isOptional = includeOptionality && isOptionalDeclaration(declaration); + + // Use type from type annotation if one is present + const declaredType = tryGetTypeFromEffectiveTypeNode(declaration); + if (isCatchClauseVariableDeclarationOrBindingElement(declaration)) { + if (declaredType) { + // If the catch clause is explicitly annotated with any or unknown, accept it, otherwise error. + return isTypeAny(declaredType) || declaredType === unknownType ? declaredType : errorType; + } + // If the catch clause is not explicitly annotated, treat it as though it were explicitly + // annotated with unknown or any, depending on useUnknownInCatchVariables. + return useUnknownInCatchVariables ? unknownType : anyType; + } + if (declaredType) { + return addOptionality(declaredType, isProperty, isOptional); + } + + if ( + (noImplicitAny || isInJSFile(declaration)) && + isVariableDeclaration(declaration) && !isBindingPattern(declaration.name) && + !(getCombinedModifierFlagsCached(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 (!(getCombinedNodeFlagsCached(declaration) & NodeFlags.Constant) && (!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)) { + if (!declaration.symbol) { + // parameters of function types defined in JSDoc in TS files don't have symbols + return; + } + 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(getSymbolOfDeclaration(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); + } + } + + // 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, getSymbolOfDeclaration(declaration), getDeclaredExpandoInitializer(declaration)); + if (containerObjectType) { + return containerObjectType; + } + } + const type = widenTypeInferredFromInitializer(declaration, checkDeclarationInitializer(declaration, checkMode)); + return addOptionality(type, isProperty, 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); + } + } + + 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 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); + } + + // 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; + } + + 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, /*includeClassComputedPropertyName*/ 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 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 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 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 = isInJSFile(container) ? getJSDocTypeTag(container) : undefined; + 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); + } + } + 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); + definedInConstructor = true; + } + } + 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 && isInJSFile(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); + } + 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 (symbol.parent?.valueDeclaration) { + const possiblyAnnotatedSymbol = getFunctionExpressionParentSymbolOrSymbol(symbol.parent); + if (possiblyAnnotatedSymbol.valueDeclaration) { + const typeNode = getEffectiveTypeAnnotationNode(possiblyAnnotatedSymbol.valueDeclaration); + if (typeNode) { + const annotationSymbol = getPropertyOfType(getTypeFromTypeNode(typeNode), symbol.escapedName); + if (annotationSymbol) { + return getNonMissingTypeOfSymbol(annotationSymbol); + } + } + } + } + + 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; + } + 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.links.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 |= getPropagatingFlagsOfTypes([type]) | getObjectFlags(type) & (ObjectFlags.JSLiteral | ObjectFlags.ArrayLiteral | ObjectFlags.ObjectLiteral); + 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, /*includeClassComputedPropertyName*/ 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, reportErrors ? CheckMode.Normal : CheckMode.Contextual, 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 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 exprType = getLiteralTypeFromPropertyName(name); + if (!isTypeUsableAsPropertyName(exprType)) { + // do not include computed properties in the implied type + objectFlags |= ObjectFlags.ObjectLiteralPatternWithComputedProperties; + return; + } + const text = getPropertyNameFromType(exprType); + const flags = SymbolFlags.Property | (e.initializer ? SymbolFlags.Optional : 0); + const symbol = createSymbol(flags, text); + symbol.links.type = getTypeFromBindingElement(e, includePatternInType, reportErrors); + symbol.links.bindingElement = e; + members.set(symbol.escapedName, symbol); + }); + const result = createAnonymousType(/*symbol*/ 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 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 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 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 getTypeFromImportAttributes(node: ImportAttributes): Type { + const links = getNodeLinks(node); + if (!links.resolvedType) { + const symbol = createSymbol(SymbolFlags.ObjectLiteral, InternalSymbolName.ImportAttributes); + const members = createSymbolTable(); + forEach(node.elements, attr => { + const member = createSymbol(SymbolFlags.Property, getNameFromImportAttribute(attr)); + member.parent = symbol; + member.links.type = checkImportAttribute(attr); + member.links.target = member; + members.set(member.escapedName, member); + }); + const type = createAnonymousType(symbol, members, emptyArray, emptyArray, emptyArray); + type.objectFlags |= ObjectFlags.ObjectLiteral | ObjectFlags.NonInferrableType; + links.resolvedType = type; + } + return links.resolvedType; + } + + function isGlobalSymbolConstructor(node: Node) { + const symbol = getSymbolOfNode(node); + const globalSymbol = getGlobalESSymbolConstructorTypeSymbol(/*reportErrors*/ false); + return globalSymbol && symbol && symbol === globalSymbol; + } + + 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); + } + + // 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 !== getSymbolOfDeclaration(declaration)) { + type = esSymbolType; + } + + 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); + } + } + return type; + } + + 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 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, checkMode?: CheckMode): Type { + const links = getSymbolLinks(symbol); + if (!links.type) { + const type = getTypeOfVariableOrParameterOrPropertyWorker(symbol, checkMode); + // 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) && !checkMode) { + links.type = type; + } + return type; + } + return links.type; + } + + function getTypeOfVariableOrParameterOrPropertyWorker(symbol: Symbol, checkMode?: CheckMode): 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 = getSymbolOfDeclaration(getSourceFileOfNode(symbol.valueDeclaration)); + const result = createSymbol(fileSymbol.flags, "exports" as __String); + result.declarations = fileSymbol.declarations ? fileSymbol.declarations.slice() : []; + result.parent = symbol; + result.links.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); + } + Debug.assertIsDefined(symbol.valueDeclaration); + const declaration = symbol.valueDeclaration; + // 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); + } + + // When trying to get the *contextual* type of a binding element, it's possible to fall in a loop and therefore + // end up in a circularity-like situation. This is not a true circularity so we should not report such an error. + // For example, here the looping could happen when trying to get the type of `a` (binding element): + // + // const { a, b = a } = { a: 0 } + // + if (isBindingElement(declaration) && checkMode === CheckMode.Contextual) { + return errorType; + } + 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, /*reportErrors*/ 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); + } + + // When trying to get the *contextual* type of a binding element, it's possible to fall in a loop and therefore + // end up in a circularity-like situation. This is not a true circularity so we should not report such an error. + // For example, here the looping could happen when trying to get the type of `a` (binding element): + // + // const { a, b = a } = { a: 0 } + // + if (isBindingElement(declaration) && checkMode === CheckMode.Contextual) { + 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; + } + } + return undefined; + } + + 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 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; + } + 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, /*reportErrors*/ 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; + } + 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; + } + links.type ??= type; + } + return links.type; + } + + function getWriteTypeOfAccessors(symbol: Symbol): Type { + const links = getSymbolLinks(symbol); + if (!links.writeType) { + if (!pushTypeResolution(symbol, TypeSystemPropertyName.WriteType)) { + return errorType; + } + + 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; + } + // Absent an explicit setter type annotation we use the read type of the accessor. + links.writeType ??= writeType || getTypeOfAccessors(symbol); + } + return links.writeType; + } + + 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 = merged; + links = merged.links; + } + } + originalLinks.type = links.type = getTypeOfFuncClassEnumModuleWorker(symbol); + } + return links.type; + } + + 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; + } + } + 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, /*isProperty*/ true) : type; + } + } + + 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) { + if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) { + return errorType; + } + const targetSymbol = resolveAlias(symbol); + const exportSymbol = symbol.declarations && getTargetOfAliasDeclaration(getDeclarationOfAliasSymbol(symbol)!, /*dontRecursivelyResolve*/ 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 + : getSymbolFlags(targetSymbol) & SymbolFlags.Value ? getTypeOfSymbol(targetSymbol) + : errorType; + + if (!popTypeResolution()) { + reportCircularityError(exportSymbol ?? symbol); + return links.type ??= 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 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; + // Check if variable has type annotation that circularly references the variable itself + if (declaration) { + 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.Alias) { + const node = getDeclarationOfAliasSymbol(symbol); + if (node) { + error(node, Diagnostics.Circular_definition_of_import_alias_0, 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 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 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) : + // NOTE: cast to TransientSymbol should be safe because only TransientSymbols can have CheckFlags.SyntheticProperty + (symbol as TransientSymbol).links.writeType || (symbol as TransientSymbol).links.type! : + removeMissingType(getTypeOfSymbol(symbol), !!(symbol.flags & SymbolFlags.Optional)); + } + if (symbol.flags & SymbolFlags.Accessor) { + return checkFlags & CheckFlags.Instantiated ? + getWriteTypeOfInstantiatedSymbol(symbol) : + getWriteTypeOfAccessors(symbol); + } + return getTypeOfSymbol(symbol); + } + + function getTypeOfSymbol(symbol: Symbol, checkMode?: CheckMode): 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, checkMode); + } + 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; + } + + 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(getSymbolOfDeclaration(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 = getSymbolOfDeclaration(node.left as BindableStaticNameExpression | PropertyAssignment); + if (symbol && symbol.parent && !findAncestor(symbol.parent.valueDeclaration, d => node === d)) { + node = symbol.parent.valueDeclaration!; + } + } + } + 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(getSymbolOfDeclaration((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(getSymbolOfDeclaration(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; + } + } + } + } + + // 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.flags & SymbolFlags.Function) + ? symbol.valueDeclaration + : symbol.declarations?.find(decl => { + if (decl.kind === SyntaxKind.InterfaceDeclaration) { + return true; + } + if (decl.kind !== SyntaxKind.VariableDeclaration) { + return false; + } + const initializer = (decl as VariableDeclaration).initializer; + return !!initializer && (initializer.kind === SyntaxKind.FunctionExpression || initializer.kind === SyntaxKind.ArrowFunction); + })!; + 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)); + } + } + 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)); + } + + // 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; + } + + 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 getBaseTypeNodeOfClass(type: InterfaceType): ExpressionWithTypeArguments | undefined { + const decl = getClassLikeDeclarationOfSymbol(type.symbol); + return decl && getEffectiveBaseTypeNode(decl); + } + + 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 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, + * * errorType 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))); + } + } + return type.resolvedBaseConstructorType ??= errorType; + } + 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); + } + } + } + } + } + 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 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); + } + } + 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; + } + + 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))) { + 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]); + } + + 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(getSourceFileOfNode(baseTypeNode.expression), 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; + } + + // 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); + } + } + else { + 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; + } + } + } + } + } + } + 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 = merged; + links = merged.links; + } + + 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; + } + + 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); + } + } + 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)); + } + } + links.declaredType ??= type; + } + return links.declaredType; + } + + function getBaseTypeOfEnumLikeType(type: Type) { + return type.flags & TypeFlags.EnumLike && type.symbol.flags & SymbolFlags.EnumMember ? 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 = getSymbolOfDeclaration(member); + const value = getEnumMemberValue(member).value; + const memberType = getFreshTypeOfLiteralType( + value !== undefined ? + getEnumLiteralType(value, getSymbolId(symbol), memberSymbol) : + createComputedEnumType(memberSymbol), + ); + getSymbolLinks(memberSymbol).declaredType = memberType; + memberTypeList.push(getRegularTypeOfLiteralType(memberType)); + } + } + } + } + } + const enumType = memberTypeList.length ? + getUnionType(memberTypeList, UnionReduction.Literal, symbol, /*aliasTypeArguments*/ undefined) : + createComputedEnumType(symbol); + if (enumType.flags & TypeFlags.Union) { + enumType.flags |= TypeFlags.EnumLiteral; + enumType.symbol = symbol; + } + links.declaredType = enumType; + } + return links.declaredType; + } + + function createComputedEnumType(symbol: Symbol) { + const regularType = createTypeWithSymbol(TypeFlags.Enum, symbol) as EnumType; + const freshType = createTypeWithSymbol(TypeFlags.Enum, symbol) as EnumType; + regularType.regularType = regularType; + regularType.freshType = freshType; + freshType.regularType = regularType; + freshType.freshType = freshType; + return regularType; + } + + function getDeclaredTypeOfEnumMember(symbol: Symbol): Type { + const links = getSymbolLinks(symbol); + if (!links.declaredType) { + const enumType = getDeclaredTypeOfEnum(getParentOfSymbol(symbol)!); + if (!links.declaredType) { + links.declaredType = enumType; + } + } + return links.declaredType; + } + + 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 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; + } + + /** + * 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; + } + + /** 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; + } + + // 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 base of baseSymbols) { + if (isStaticPrivateIdentifierProperty(base)) { + continue; + } + const derived = symbols.get(base.escapedName); + if ( + !derived + // non-constructor/static-block assignment declarations are ignored here; they're not treated as overrides + || derived.valueDeclaration + && isBinaryExpression(derived.valueDeclaration) + && !isConstructorDeclaredProperty(derived) + && !getContainingClassStaticBlock(derived.valueDeclaration) + ) { + symbols.set(base.escapedName, base); + symbols.set(base.escapedName, base); + } + } + } + + 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; + + (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 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; + } + + /** + * 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 name is a dynamic name that cannot be late-bound. + */ + function isNonBindableDynamicName(node: DeclarationName) { + return isDynamicName(node) && !isLateBindableName(node); + } + + /** + * 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: Map<__String, TransientSymbol>, 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 there's a symbol declaration with the same name and conflicting flags. + const earlySymbol = earlySymbols && earlySymbols.get(memberName); + // Duplicate property declarations of classes are checked in checkClassForDuplicateDeclarations. + if (!(parent.flags & SymbolFlags.Class) && lateSymbol.flags & getExcludedSymbolFlags(symbolFlags)) { + // 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.links.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; + } + + function getResolvedMembersOrExportsOfSymbol(symbol: Symbol, resolutionKind: MembersOrExportsResolutionKind): Map<__String, Symbol> { + const links = getSymbolLinks(symbol); + if (!links[resolutionKind]) { + const isStatic = resolutionKind === MembersOrExportsResolutionKind.resolvedExports; + const earlySymbols = !isStatic ? symbol.members : + symbol.flags & SymbolFlags.Module ? getExportsOfModuleWorker(symbol).exports : + 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 Map<__String, TransientSymbol>; + 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 = getFunctionExpressionParentSymbolOrSymbol(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); + } + } + } + } + + let resolved = combineSymbolTables(earlySymbols, lateSymbols); + if (symbol.flags & SymbolFlags.Transient && links.cjsExportMerged && symbol.declarations) { + for (const decl of symbol.declarations) { + const original = getSymbolLinks(decl.symbol)[resolutionKind]; + if (!resolved) { + resolved = original; + continue; + } + if (!original) continue; + original.forEach((s, name) => { + const existing = resolved!.get(name); + if (!existing) resolved!.set(name, s); + else if (existing === s) return; + else resolved!.set(name, mergeSymbol(existing, s)); + }); + } + } + links[resolutionKind] = resolved || emptySymbols; + } + + return links[resolutionKind]!; + } + + /** + * 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; + } + + 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); + return length(target.typeParameters) === length(typeArguments) ? createTypeReference(target, concatenate(typeArguments, [thisArgument || target.thisType!])) : 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)) { + const symbolTable = createSymbolTable(source.declaredProperties); + // copy index signature symbol as well (for quickinfo) + const sourceIndex = getIndexSymbol(source.symbol); + if (sourceIndex) { + symbolTable.set(InternalSymbolName.Index, sourceIndex); + } + members = symbolTable; + } + 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 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 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 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 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 restName = sig.parameters[restIndex].escapedName; + const restType = getTypeOfSymbol(sig.parameters[restIndex]); + if (isTupleType(restType)) { + return [expandSignatureParametersWithTupleMembers(restType, restIndex, restName)]; + } + 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, restName)); + } + } + return [sig.parameters]; + + function expandSignatureParametersWithTupleMembers(restType: TupleTypeReference, restIndex: number, restName: __String) { + const elementTypes = getTypeArguments(restType); + const associatedNames = getUniqAssociatedNamesFromTupleType(restType, restName); + 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 name = associatedNames && associatedNames[i] ? associatedNames[i] : + 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.links.type = flags & ElementFlags.Rest ? createArrayType(t) : t; + return symbol; + }); + return concatenate(sig.parameters.slice(0, restIndex), restParams); + } + + function getUniqAssociatedNamesFromTupleType(type: TupleTypeReference, restName: __String) { + const associatedNamesMap = new Map<__String, number>(); + return map(type.target.labeledElementDeclarations, (labeledElement, i) => { + const name = getTupleElementLabel(labeledElement, i, restName); + const prevCounter = associatedNamesMap.get(name); + if (prevCounter === undefined) { + associatedNamesMap.set(name, 1); + return name; + } + else { + associatedNamesMap.set(name, prevCounter + 1); + return `${name}_${prevCounter}` as __String; + } + }); + } + } + + 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(/*declaration*/ undefined, classType.localTypeParameters, /*thisParameter*/ 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]; + } + let result: Signature[] | undefined; + for (let i = 0; i < signatureLists.length; i++) { + // Allow matching non-generic signatures to have excess parameters (as a fallback if exact parameter match is not found) and different return types. + // Prefer matching this types if possible. + const match = i === listIndex + ? signature + : findMatchingSignature(signatureLists[i], signature, /*partialMatch*/ false, /*ignoreThisTypes*/ false, /*ignoreReturnTypes*/ true) + || findMatchingSignature(signatureLists[i], signature, /*partialMatch*/ true, /*ignoreThisTypes*/ false, /*ignoreReturnTypes*/ true); + if (!match) { + return undefined; + } + 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; + } + + 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 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, + isRestParam ? CheckFlags.RestParameter : isOptional ? CheckFlags.OptionalParameter : 0, + ); + paramSymbol.links.type = isRestParam ? createArrayType(unionParamType) : unionParamType; + params[i] = paramSymbol; + } + if (needsExtraRestElement) { + const restParamSymbol = createSymbol(SymbolFlags.FunctionScopedVariable, "args" as __String, CheckFlags.RestParameter); + restParamSymbol.links.type = createArrayType(getTypeAtPosition(shorter, longestCount)); + if (shorter === right) { + restParamSymbol.links.type = instantiateType(restParamSymbol.links.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; + } + else if (left.compositeKind !== TypeFlags.Intersection && left.mapper && left.compositeSignatures) { + result.mapper = left.mapper; + } + return result; + } + + 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; + } + + 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); + } + + 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 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); + } + else if (mixinFlags[i]) { + mixedTypes.push(getReturnTypeOfSignature(getSignaturesOfType(types[i], SignatureKind.Construct)[0])); + } + } + 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; + }); + } + 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 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 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); + } + + /** + * 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 = getExportsOfSymbol(symbol); + let indexInfos: IndexInfo[] | undefined; + if (symbol === globalThisSymbol) { + const varsOnly = new Map<__String, Symbol>(); + 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); + } + } + + 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); + } + } + 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; + } + } + + 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])])); + } + + // If the original mapped type had an intersection constraint we extract its components, + // and we make an attempt to do so even if the intersection has been reduced to a union. + // This entire process allows us to possibly retrieve the filtering type literals. + // e.g. { [K in keyof U & ("a" | "b") ] } -> "a" | "b" + function getLimitedConstraint(type: ReverseMappedType) { + const constraint = getConstraintTypeFromMappedType(type.mappedType); + if (!(constraint.flags & TypeFlags.Union || constraint.flags & TypeFlags.Intersection)) { + return; + } + const origin = (constraint.flags & TypeFlags.Union) ? (constraint as UnionType).origin : (constraint as IntersectionType); + if (!origin || !(origin.flags & TypeFlags.Intersection)) { + return; + } + const limitedConstraint = getIntersectionType((origin as IntersectionType).types.filter(t => t !== type.constraintType)); + return limitedConstraint !== neverType ? limitedConstraint : undefined; + } + + 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) || unknownType, readonlyMask && indexInfo.isReadonly)] : emptyArray; + const members = createSymbolTable(); + const limitedConstraint = getLimitedConstraint(type); + for (const prop of getPropertiesOfType(type.source)) { + // In case of a reverse mapped type with an intersection constraint, if we were able to + // extract the filtering type literals we skip those properties that are not assignable to them, + // because the extra properties wouldn't get through the application of the mapped type anyway + if (limitedConstraint) { + const propertyNameType = getLiteralTypeFromProperty(prop, TypeFlags.StringOrNumberLiteralOrUnique); + if (!isTypeAssignableTo(propertyNameType, limitedConstraint)) { + continue; + } + } + 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.links.nameType = getSymbolLinks(prop).nameType; + inferredProp.links.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.links.mappedType = newMappedType as MappedType; + inferredProp.links.constraintType = getIndexType(newTypeParam) as IndexType; + } + else { + inferredProp.links.mappedType = type.mappedType; + inferredProp.links.constraintType = type.constraintType; + } + members.set(prop.escapedName, inferredProp); + } + setStructuredTypeMembers(type, members, emptyArray, emptyArray, indexInfos); + } + + // 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), /*forConstraint*/ false); + } + } + return type; + } + if (type.flags & TypeFlags.Union) { + return mapType(type as UnionType, getLowerBoundOfKeyType, /*noReductions*/ true); + } + 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 getIsLateCheckFlag(s: Symbol): CheckFlags { + return getCheckFlags(s) & CheckFlags.Late; + } + + function forEachMappedTypePropertyKeyTypeAndIndexSignatureKeyType(type: Type, include: TypeFlags, stringsOnly: boolean, cb: (keyType: Type) => void) { + for (const prop of getPropertiesOfType(type)) { + cb(getLiteralTypeFromProperty(prop, include)); + } + if (type.flags & TypeFlags.Any) { + cb(stringType); + } + else { + for (const info of getIndexInfosOfType(type)) { + if (!stringsOnly || info.keyType.flags & (TypeFlags.String | TypeFlags.TemplateLiteral)) { + cb(info.keyType); + } + } + } + } + + /** 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 mappedType = (type.target as MappedType) || type; + const nameType = getNameTypeFromMappedType(mappedType); + const shouldLinkPropDeclarations = getMappedTypeNameTypeKind(mappedType) !== MappedTypeNameTypeKind.Remapping; + const templateType = getTemplateTypeFromMappedType(mappedType); + const modifiersType = getApparentType(getModifiersTypeFromMappedType(type)); // The 'T' in 'keyof T' + const templateModifiers = getMappedTypeModifiers(type); + const include = TypeFlags.StringOrNumberLiteralOrUnique; + if (isMappedTypeWithKeyofConstraintDeclaration(type)) { + // We have a { [P in keyof T]: X } + forEachMappedTypePropertyKeyTypeAndIndexSignatureKeyType(modifiersType, include, /*stringsOnly*/ false, 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.links.nameType = getUnionType([existingProp.links.nameType!, propNameType]); + existingProp.links.keyType = getUnionType([existingProp.links.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.links.mappedType = type; + prop.links.nameType = propNameType; + prop.links.keyType = keyType; + if (modifiersProp) { + prop.links.syntheticOrigin = modifiersProp; + prop.declarations = shouldLinkPropDeclarations ? modifiersProp.declarations : undefined; + } + 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 modifiersIndexInfo = getApplicableIndexInfo(modifiersType, propNameType); + const isReadonly = !!(templateModifiers & MappedTypeModifiers.IncludeReadonly || + !(templateModifiers & MappedTypeModifiers.ExcludeReadonly) && modifiersIndexInfo?.isReadonly); + const indexInfo = createIndexInfo(indexKeyType, propType, isReadonly); + indexInfos = appendIndexInfo(indexInfos, indexInfo, /*union*/ true); + } + } + } + + function getTypeOfMappedSymbol(symbol: MappedSymbol) { + if (!symbol.links.type) { + const mappedType = symbol.links.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.links.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.links.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.links.type ??= type; + } + return symbol.links.type; + } + + function getTypeParameterFromMappedType(type: MappedType) { + return type.typeParameter || + (type.typeParameter = getDeclaredTypeOfTypeParameter(getSymbolOfDeclaration(type.declaration.typeParameter))); + } + + 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 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 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); + } + + // Return -1, 0, or 1, where -1 means optionality is stripped (i.e. -?), 0 means optionality is unchanged, and 1 means + // optionality is added (i.e. +?). + function getMappedTypeOptionality(type: MappedType): number { + const modifiers = getMappedTypeModifiers(type); + return modifiers & MappedTypeModifiers.ExcludeOptional ? -1 : modifiers & MappedTypeModifiers.IncludeOptional ? 1 : 0; + } + + // Return -1, 0, or 1, for stripped, unchanged, or added optionality respectively. When a homomorphic mapped type doesn't + // modify optionality, recursively consult the optionality of the type being mapped over to see if it strips or adds optionality. + // For intersections, return -1 or 1 when all constituents strip or add optionality, otherwise return 0. + function getCombinedMappedTypeOptionality(type: Type): number { + if (getObjectFlags(type) & ObjectFlags.Mapped) { + return getMappedTypeOptionality(type as MappedType) || getCombinedMappedTypeOptionality(getModifiersTypeFromMappedType(type as MappedType)); + } + if (type.flags & TypeFlags.Intersection) { + const optionality = getCombinedMappedTypeOptionality((type as IntersectionType).types[0]); + return every((type as IntersectionType).types, (t, i) => i === 0 || getCombinedMappedTypeOptionality(t) === optionality) ? optionality : 0; + } + return 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; + } + + function getMappedTypeNameTypeKind(type: MappedType): MappedTypeNameTypeKind { + const nameType = getNameTypeFromMappedType(type); + if (!nameType) { + return MappedTypeNameTypeKind.None; + } + return isTypeAssignableTo(nameType, getTypeParameterFromMappedType(type)) ? MappedTypeNameTypeKind.Filtering : MappedTypeNameTypeKind.Remapping; + } + + 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)); + } + } + 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; + } + + /** 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, /*skipObjectFunctionPropertyAugment*/ !!(type.flags & TypeFlags.Intersection)); + 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); + } + return type.resolvedProperties; + } + + function getPropertiesOfType(type: Type): Symbol[] { + type = getReducedApparentType(type); + return type.flags & TypeFlags.UnionOrIntersection ? + getPropertiesOfUnionOrIntersectionType(type as UnionType) : + getPropertiesOfObjectType(type); + } + + 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 isTypeInvalidDueToUnionDiscriminant(contextualType: Type, obj: ObjectLiteralExpression | JsxAttributes): boolean { + const list = obj.properties as NodeArray; + return list.some(property => { + const nameType = property.name && (isJsxNamespacedName(property.name) ? getStringLiteralType(getTextOfJsxAttributeName(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 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()); + } + + 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 isConstMappedType(type: MappedType, depth: number): boolean { + const typeVariable = getHomomorphicTypeVariable(type); + return !!typeVariable && isConstTypeVariable(typeVariable, depth); + } + + function isConstTypeVariable(type: Type | undefined, depth = 0): boolean { + return depth < 5 && !!(type && ( + type.flags & TypeFlags.TypeParameter && some((type as TypeParameter).symbol?.declarations, d => hasSyntacticModifier(d, ModifierFlags.Const)) || + type.flags & TypeFlags.UnionOrIntersection && some((type as UnionOrIntersectionType).types, t => isConstTypeVariable(t, depth)) || + type.flags & TypeFlags.IndexedAccess && isConstTypeVariable((type as IndexedAccessType).objectType, depth + 1) || + type.flags & TypeFlags.Conditional && isConstTypeVariable(getConstraintOfConditionalType(type as ConditionalType), depth + 1) || + type.flags & TypeFlags.Substitution && isConstTypeVariable((type as SubstitutionType).baseType, depth) || + getObjectFlags(type) & ObjectFlags.Mapped && isConstMappedType(type as MappedType, depth) || + isGenericTupleType(type) && findIndex(getElementTypes(type), (t, i) => !!(type.target.elementFlags[i] & ElementFlags.Variadic) && isConstTypeVariable(t, depth)) >= 0 + )); + } + + 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 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 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 { + if (type.resolvedConstraintOfDistributive !== undefined) { + return type.resolvedConstraintOfDistributive || 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), /*forConstraint*/ true); + if (!(instantiated.flags & TypeFlags.Never)) { + type.resolvedConstraintOfDistributive = instantiated; + return instantiated; + } + } + } + type.resolvedConstraintOfDistributive = false; + 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); + } + if (constraint) { + constraints = append(constraints, constraint); + if (targetIsUnion) { + 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, IntersectionFlags.NoConstraintReduction), /*writing*/ false); + } + return undefined; + } + + function getBaseConstraintOfType(type: Type): Type | undefined { + if (type.flags & (TypeFlags.InstantiableNonPrimitive | TypeFlags.UnionOrIntersection | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) || isGenericTupleType(type)) { + const constraint = getResolvedBaseConstraint(type as InstantiableType | UnionOrIntersectionType); + return constraint !== noConstraintType && constraint !== circularConstraintType ? constraint : undefined; + } + return type.flags & TypeFlags.Index ? stringNumberSymbolType : 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; + } + + 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 = getImmediateBaseConstraint(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; + } + return t.immediateBaseConstraint; + } + + 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 { + different = true; + } + } + 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 stringNumberSymbolType; + } + 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)); + } + 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)); + } + if (isGenericTupleType(t)) { + // We substitute constraints for variadic elements only when the constraints are array types or + // non-variadic tuple types as we want to avoid further (possibly unbounded) recursion. + const newElements = map(getElementTypes(t), (v, i) => { + const constraint = v.flags & TypeFlags.TypeParameter && t.target.elementFlags[i] & ElementFlags.Variadic && getBaseConstraint(v) || v; + return constraint !== v && everyType(constraint, c => isArrayOrTupleType(c) && !isGenericTupleType(c)) ? constraint : v; + }); + return createTupleType(newElements, t.target.elementFlags, t.target.readonly, t.target.labeledElementDeclarations); + } + return t; + } + } + + function getApparentTypeOfIntersectionType(type: IntersectionType, thisArgument: Type) { + if (type === thisArgument) { + return type.resolvedApparentType || (type.resolvedApparentType = getTypeWithThisArgument(type, thisArgument, /*needApparentType*/ true)); + } + const key = `I${getTypeId(type)},${getTypeId(thisArgument)}`; + return getCachedType(key) ?? setCachedType(key, getTypeWithThisArgument(type, thisArgument, /*needApparentType*/ 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; + } + } + } + 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; + } + + /** + * 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; + } + + function hasNonCircularTypeParameterDefault(typeParameter: TypeParameter) { + return getResolvedTypeParameterDefault(typeParameter) !== circularConstraintType; + } + + /** + * 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 getApparentTypeOfMappedType(type: MappedType) { + return type.resolvedApparentType || (type.resolvedApparentType = getResolvedApparentTypeOfMappedType(type)); + } + + function getResolvedApparentTypeOfMappedType(type: MappedType): Type { + const target = (type.target ?? type) as MappedType; + const typeVariable = getHomomorphicTypeVariable(target); + if (typeVariable && !target.declaration.nameType) { + // We have a homomorphic mapped type or an instantiation of a homomorphic mapped type, i.e. a type + // of the form { [P in keyof T]: X }. Obtain the modifiers type (the T of the keyof T), and if it is + // another generic mapped type, recursively obtain its apparent type. Otherwise, obtain its base + // constraint. Then, if every constituent of the base constraint is an array or tuple type, apply + // this mapped type to the base constraint. It is safe to recurse when the modifiers type is a + // mapped type because we protect again circular constraints in getTypeFromMappedTypeNode. + const modifiersType = getModifiersTypeFromMappedType(type); + const baseConstraint = isGenericMappedType(modifiersType) ? getApparentTypeOfMappedType(modifiersType) : getBaseConstraintOfType(modifiersType); + if (baseConstraint && everyType(baseConstraint, t => isArrayOrTupleType(t) || isArrayOrTupleOrIntersection(t))) { + return instantiateType(target, prependTypeMapping(typeVariable, baseConstraint, type.mapper)); + } + } + return type; + } + + function isArrayOrTupleOrIntersection(type: Type) { + return !!(type.flags & TypeFlags.Intersection) && every((type as IntersectionType).types, isArrayOrTupleType); + } + + 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 ? getBaseConstraintOfType(type) || unknownType : type; + const objectFlags = getObjectFlags(t); + return objectFlags & ObjectFlags.Mapped ? getApparentTypeOfMappedType(t as MappedType) : + objectFlags & ObjectFlags.Reference && t !== type ? getTypeWithThisArgument(t, type) : + t.flags & TypeFlags.Intersection ? getApparentTypeOfIntersectionType(t as IntersectionType, type) : + 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 ? stringNumberSymbolType : + 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: Map | 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; + } + 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(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 links = tryCast(singleProp, isTransientSymbol)?.links; + const clone = createSymbolWithType(singleProp, links?.type); + clone.parent = singleProp.valueDeclaration?.symbol?.parent; + clone.links.containingType = containingType; + clone.links.mapper = links?.mapper; + clone.links.writeType = getWriteTypeOfSymbol(singleProp); + return clone; + } + 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 writeType = getWriteTypeOfSymbol(prop); + if (writeTypes || writeType !== type) { + writeTypes = append(!writeTypes ? propTypes.slice() : writeTypes, writeType); + } + if (type !== firstType) { + checkFlags |= CheckFlags.HasNonUniformType; + } + if (isLiteralType(type) || isPatternLiteralType(type)) { + checkFlags |= CheckFlags.HasLiteralType; + } + if (type.flags & TypeFlags.Never && type !== uniqueLiteralType) { + checkFlags |= CheckFlags.HasNeverType; + } + propTypes.push(type); + } + addRange(propTypes, indexTypes); + const result = createSymbol(SymbolFlags.Property | (optionalFlag ?? 0), name, syntheticFlag | checkFlags); + result.links.containingType = containingType; + if (!hasNonUniformValueDeclaration && firstValueDeclaration) { + result.valueDeclaration = firstValueDeclaration; + + // Inherit information about parent type. + if (firstValueDeclaration.symbol.parent) { + result.parent = firstValueDeclaration.symbol.parent; + } + } + + result.declarations = declarations; + result.links.nameType = nameType; + if (propTypes.length > 2) { + // When `propTypes` has the potential to explode in size when normalized, defer normalization until absolutely needed + result.links.checkFlags |= CheckFlags.DeferredType; + result.links.deferralParent = containingType; + result.links.deferralConstituents = propTypes; + result.links.deferralWriteConstituents = writeTypes; + } + else { + result.links.type = isUnion ? getUnionType(propTypes) : getIntersectionType(propTypes); + if (writeTypes) { + result.links.writeType = isUnion ? getUnionType(writeTypes) : getIntersectionType(writeTypes); + } + } + return result; + } + + // 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 = skipObjectFunctionPropertyAugment ? + type.propertyCacheWithoutObjectFunctionPropertyAugment?.get(name) : + type.propertyCache?.get(name); + if (!property) { + property = createUnionOrIntersectionProperty(type, name, skipObjectFunctionPropertyAugment); + if (property) { + const properties = skipObjectFunctionPropertyAugment ? + type.propertyCacheWithoutObjectFunctionPropertyAugment ||= createSymbolTable() : + type.propertyCache ||= createSymbolTable(); + properties.set(name, property); + // Propagate an entry from the non-augmented cache to the augmented cache unless the property is partial. + if (skipObjectFunctionPropertyAugment && !(getCheckFlags(property) & CheckFlags.Partial) && !type.propertyCache?.get(name)) { + const properties = type.propertyCache ||= createSymbolTable(); + properties.set(name, property); + } + } + } + return property; + } + + function getCommonDeclarationsOfSymbols(symbols: Iterable) { + 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; + } + } + return commonDeclarations; + } + + 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 (type as IntersectionType).objectFlags & ObjectFlags.IsNeverIntersection ? neverType : type; + } + return type; + } + + 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; + } + return reduced; + } + + function isNeverReducedProperty(prop: Symbol) { + return isDiscriminantWithNeverType(prop) || isConflictingPrivateProperty(prop); + } + + 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); + } + + /** + * 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 isGenericReducibleType(type: Type): boolean { + return !!(type.flags & TypeFlags.Union && (type as UnionType).objectFlags & ObjectFlags.ContainsIntersections && some((type as UnionType).types, isGenericReducibleType) || + type.flags & TypeFlags.Intersection && isReducibleIntersection(type as IntersectionType)); + } + + function isReducibleIntersection(type: IntersectionType) { + const uniqueFilled = type.uniqueLiteralFilledInstantiation || (type.uniqueLiteralFilledInstantiation = instantiateType(type, uniqueLiteralMapper)); + return getReducedType(uniqueFilled) !== uniqueFilled; + } + + 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 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 && !includeTypeOnlyMembers && type.symbol?.flags & SymbolFlags.ValueModule && getSymbolLinks(type.symbol).typeOnlyExportStarMap?.has(name)) { + // If this is the type of a module, `resolved.members.get(name)` might have effectively skipped over + // an `export type * from './foo'`, leaving `symbolIsValue` unable to see that the symbol is being + // viewed through a type-only export. + return undefined; + } + 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; + } + } + return getPropertyOfObjectType(globalObjectType, name); + } + if (type.flags & TypeFlags.Intersection) { + const prop = getPropertyOfUnionOrIntersectionType(type as UnionOrIntersectionType, name, /*skipObjectFunctionPropertyAugment*/ true); + if (prop) { + return prop; + } + if (!skipObjectFunctionPropertyAugment) { + return getPropertyOfUnionOrIntersectionType(type as UnionOrIntersectionType, name, skipObjectFunctionPropertyAugment); + } + return undefined; + } + if (type.flags & TypeFlags.Union) { + return getPropertyOfUnionOrIntersectionType(type as UnionOrIntersectionType, name, skipObjectFunctionPropertyAugment); + } + return undefined; + } + + 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; + } + + /** + * 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[] { + const result = getSignaturesOfStructuredType(getReducedApparentType(type), kind); + if (kind === SignatureKind.Call && !length(result) && type.flags & TypeFlags.Union) { + if ((type as UnionType).arrayFallbackSignatures) { + return (type as UnionType).arrayFallbackSignatures!; + } + // If the union is all different instantiations of a member of the global array type... + let memberName: __String; + if (everyType(type, t => !!t.symbol?.parent && isArrayOrTupleSymbol(t.symbol.parent) && (!memberName ? (memberName = t.symbol.escapedName, true) : memberName === t.symbol.escapedName))) { + // Transform the type from `(A[] | B[])["member"]` to `(A | B)[]["member"]` (since we pretend array is covariant anyway) + const arrayArg = mapType(type, t => getMappedType((isReadonlyArraySymbol(t.symbol.parent) ? globalReadonlyArrayType : globalArrayType).typeParameters![0], (t as AnonymousType).mapper!)); + const arrayType = createArrayType(arrayArg, someType(type, t => isReadonlyArraySymbol(t.symbol.parent))); + return (type as UnionType).arrayFallbackSignatures = getSignaturesOfType(getTypeOfPropertyOfType(arrayType, memberName!)!, kind); + } + (type as UnionType).arrayFallbackSignatures = result; + } + return result; + } + + function isArrayOrTupleSymbol(symbol: Symbol | undefined) { + if (!symbol || !globalArrayType.symbol || !globalReadonlyArrayType.symbol) { + return false; + } + return !!getSymbolIfSameReference(symbol, globalArrayType.symbol) || !!getSymbolIfSameReference(symbol, globalReadonlyArrayType.symbol); + } + + function isReadonlyArraySymbol(symbol: Symbol | undefined) { + if (!symbol || !globalReadonlyArrayType.symbol) { + return false; + } + return !!getSymbolIfSameReference(symbol, globalReadonlyArrayType.symbol); + } + + 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 { + (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; + } + + 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 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 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 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))); + } + + // 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; + } + + function symbolsToArray(symbols: SymbolTable): Symbol[] { + const result: Symbol[] = []; + symbols.forEach((symbol, id) => { + if (!isReservedMemberName(id)) { + result.push(symbol); + } + }); + return result; + } + + 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 hasEffectiveQuestionToken(node: ParameterDeclaration | JSDocParameterTag | JSDocPropertyTag) { + return hasQuestionToken(node) || isOptionalJSDocPropertyLikeTag(node) || isParameter(node) && isJSDocOptionalParameter(node); + } + + function isOptionalParameter(node: ParameterDeclaration | JSDocParameterTag | JSDocPropertyTag) { + if (hasEffectiveQuestionToken(node)) { + return true; + } + if (!isParameter(node)) { + 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); + } + const iife = getImmediatelyInvokedFunctionExpression(node.parent); + if (iife) { + return !node.type && + !node.dotDotDotToken && + node.parent.parameters.indexOf(node) >= getEffectiveCallArguments(iife).length; + } + + return false; + } + + 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; + } + + /** + * 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 thisTag: JSDocThisTag | undefined = isInJSFile(declaration) ? getJSDocThisTag(declaration) : 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]; + if (isInJSFile(param) && isJSDocThisTag(param)) { + thisTag = param; + continue; + } + + 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, /*nameNotFoundMessage*/ 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; + } + + // Record a new minimum argument count if this is not an optional parameter + const isOptionalParameter = hasEffectiveQuestionToken(param) || + isParameter(param) && param.initializer || isRestParameter(param) || + iife && parameters.length > iife.arguments.length && !type; + if (!isOptionalParameter) { + minArgumentCount = parameters.length; + } + } + + // 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(getSymbolOfDeclaration(declaration), otherKind); + if (other) { + thisParameter = getAnnotatedAccessorThisParameter(other); + } + } + + if (thisTag && thisTag.typeExpression) { + thisParameter = createSymbolWithType(createSymbol(SymbolFlags.FunctionScopedVariable, InternalSymbolName.This), getTypeFromTypeNode(thisTag.typeExpression)); + } + + const hostDeclaration = isJSDocSignature(declaration) ? getEffectiveJSDocHost(declaration) : declaration; + const classType = hostDeclaration && isConstructorDeclaration(hostDeclaration) ? + getDeclaredTypeOfClassOrInterface(getMergedSymbol((hostDeclaration.parent as ClassDeclaration).symbol)) + : undefined; + const typeParameters = classType ? classType.localTypeParameters : getTypeParametersFromDeclaration(declaration); + if (hasRestParameter(declaration) || isInJSFile(declaration) && maybeAddJsSyntheticRestParameter(declaration, parameters)) { + flags |= SignatureFlags.HasRestParameter; + } + 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; + } + + /** + * 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.links.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.links.checkFlags |= CheckFlags.DeferredType; + syntheticArgsSymbol.links.deferralParent = neverType; + syntheticArgsSymbol.links.deferralConstituents = [anyArrayType]; + syntheticArgsSymbol.links.deferralWriteConstituents = [anyArrayType]; + } + if (lastParamVariadicType) { + // Replace the last parameter with a rest parameter. + parameters.pop(); + } + parameters.push(syntheticArgsSymbol); + return true; + } + + 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 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!); + } + } + 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; + + 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.PropertyAssignment: + return traverse((node as PropertyAssignment).initializer); + + 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; + } + } + if (isInJSFile(decl) && decl.jsDoc) { + const tags = getJSDocOverloadTags(decl); + if (length(tags)) { + for (const tag of tags) { + const jsDocSignature = tag.typeExpression; + if (jsDocSignature.type === undefined && !isConstructorDeclaration(decl)) { + reportImplicitAny(jsDocSignature, anyType); + } + result.push(getSignatureFromDeclaration(jsDocSignature)); + } + 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; + } + + 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); + } + } + + 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); + } + } + if (type || jsdocPredicate) { + signature.resolvedTypePredicate = type && isTypePredicateNode(type) ? + createTypePredicateFromTypePredicateNode(type, signature) : + jsdocPredicate || noTypePredicate; + } + else if (signature.declaration && isFunctionLikeDeclaration(signature.declaration) && (!signature.resolvedReturnType || signature.resolvedReturnType.flags & TypeFlags.Boolean) && getParameterCount(signature) > 0) { + const { declaration } = signature; + signature.resolvedTypePredicate = noTypePredicate; // avoid infinite loop + signature.resolvedTypePredicate = getTypePredicateFromBody(declaration) || noTypePredicate; + } + else { + signature.resolvedTypePredicate = noTypePredicate; + } + } + 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 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); + } + 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); + } + } + } + type = anyType; + } + signature.resolvedReturnType ??= type; + } + return signature.resolvedReturnType; + } + + function getReturnTypeFromAnnotation(declaration: SignatureDeclaration | JSDocSignature) { + if (declaration.kind === SyntaxKind.Constructor) { + return getDeclaredTypeOfClassOrInterface(getMergedSymbol((declaration.parent as ClassDeclaration).symbol)); + } + const typeNode = getEffectiveReturnTypeNode(declaration); + if (isJSDocSignature(declaration)) { + const root = getJSDocRoot(declaration); + if (root && isConstructorDeclaration(root.parent) && !typeNode) { + return getDeclaredTypeOfClassOrInterface(getMergedSymbol((root.parent.parent as ClassDeclaration).symbol)); + } + } + if (isJSDocConstructSignature(declaration)) { + return getTypeFromTypeNode((declaration.parameters[0] as ParameterDeclaration).type!); // TODO: GH#18217 + } + 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(getSymbolOfDeclaration(declaration), SyntaxKind.SetAccessor); + const setterType = getAnnotatedAccessorType(setter); + if (setterType) { + return setterType; + } + } + return getReturnTypeOfTypeTag(declaration); + } + + function isResolvingReturnTypeOfSignature(signature: Signature): boolean { + return signature.compositeSignatures && some(signature.compositeSignatures, isResolvingReturnTypeOfSignature) || + !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 getSignatureInstantiation(signature: Signature, typeArguments: readonly 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 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 createSignatureInstantiation(signature: Signature, typeArguments: readonly Type[] | undefined): Signature { + return instantiateSignature(signature, createSignatureTypeMapper(signature, typeArguments), /*eraseTypeParameters*/ true); + } + + function createSignatureTypeMapper(signature: Signature, typeArguments: readonly Type[] | undefined): TypeMapper { + return createTypeMapper(signature.typeParameters!, typeArguments); + } + + function getErasedSignature(signature: Signature): Signature { + return signature.typeParameters ? + signature.erasedSignatureCache || (signature.erasedSignatureCache = createErasedSignature(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 getCanonicalSignature(signature: Signature): Signature { + return signature.typeParameters ? + signature.canonicalSignatureCache || (signature.canonicalSignatureCache = createCanonicalSignature(signature)) : + 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 getImplementationSignature(signature: Signature) { + return signature.typeParameters ? + signature.implementationSignatureCache ||= createImplementationSignature(signature) : + signature; + } + + function createImplementationSignature(signature: Signature) { + return signature.typeParameters ? instantiateSignature(signature, createTypeMapper([], [])) : 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 getOrCreateTypeFromSignature(signature: Signature, outerTypeParameters?: TypeParameter[]): 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; + + // 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; + + // The type must have a symbol with a `Function` flag and a declaration in order to be correctly flagged as possibly containing + // type variables by `couldContainTypeVariables` + const type = createObjectType(ObjectFlags.Anonymous | ObjectFlags.SingleSignatureType, createSymbol(SymbolFlags.Function, InternalSymbolName.Function)) as SingleSignatureType; + if (signature.declaration && !nodeIsSynthesized(signature.declaration)) { // skip synthetic declarations - keeping those around could be bad, since they lack a parent pointer + type.symbol.declarations = [signature.declaration]; + type.symbol.valueDeclaration = signature.declaration; + } + outerTypeParameters ||= signature.declaration && getOuterTypeParameters(signature.declaration, /*includeThisTypes*/ true); + type.outerTypeParameters = outerTypeParameters; + + type.members = emptySymbols; + type.properties = emptyArray; + type.callSignatures = !isConstructor ? [signature] : emptyArray; + type.constructSignatures = isConstructor ? [signature] : emptyArray; + type.indexInfos = emptyArray; + signature.isolatedSignatureType = type; + } + + return signature.isolatedSignatureType; + } + + function getIndexSymbol(symbol: Symbol): Symbol | undefined { + return symbol.members ? getIndexSymbolFromSymbolTable(symbol.members) : undefined; + } + + function getIndexSymbolFromSymbolTable(symbolTable: SymbolTable): Symbol | undefined { + return symbolTable.get(InternalSymbolName.Index); + } + + function createIndexInfo(keyType: Type, type: Type, isReadonly: boolean, declaration?: IndexSignatureDeclaration): IndexInfo { + return { keyType, type, isReadonly, 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; + } + + 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 = getTypeParametersForTypeReferenceOrImport(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, stringNumberSymbolType); + } + // 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(getSymbolOfDeclaration(checkMappedType.typeParameter)), checkMappedType.typeParameter.constraint ? getTypeFromTypeNode(checkMappedType.typeParameter.constraint) : stringNumberSymbolType))); + } + } + } + } + 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; + } + 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 stringNumberSymbolType 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 ? stringNumberSymbolType : unknownType; + } + typeParameter.constraint = type; + } + } + } + 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 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; + } + } + 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); + } + } + return result & ObjectFlags.PropagatingFlags; + } + + function tryCreateTypeReference(target: GenericType, typeArguments: readonly Type[] | undefined): Type { + if (some(typeArguments) && target === emptyGenericType) { + return unknownType; + } + + return createTypeReference(target, typeArguments); + } + + 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 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; + } + + 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 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; + } + } + 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); + } + 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) { + const typeKind = intrinsicTypeKinds.get(symbol.escapedName as string); + if (typeKind !== undefined && typeArguments && typeArguments.length === 1) { + return typeKind === IntrinsicTypeKind.NoInfer ? getNoInferType(typeArguments[0]) : 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; + } + + /** + * 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", /*objectFlags*/ undefined, `alias ${id}`); + 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, + ); + 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); + let newAliasSymbol = aliasSymbol && (isLocalTypeAlias(symbol) || !isLocalTypeAlias(aliasSymbol)) ? aliasSymbol : undefined; + let aliasTypeArguments: Type[] | undefined; + if (newAliasSymbol) { + aliasTypeArguments = getTypeArgumentsForAliasSymbol(newAliasSymbol); + } + else if (isTypeReferenceType(node)) { + const aliasSymbol = resolveTypeReferenceName(node, SymbolFlags.Alias, /*ignoreErrors*/ true); + // refers to an alias import/export/reexport - by making sure we use the target as an aliasSymbol, + // we ensure the exported symbol is used to refer to the type when it's reserialized later + if (aliasSymbol && aliasSymbol !== unknownSymbol) { + const resolved = resolveAlias(aliasSymbol); + if (resolved && resolved.flags & SymbolFlags.TypeAlias) { + newAliasSymbol = resolved; + aliasTypeArguments = typeArgumentsFromTypeReferenceNode(node) || (typeParameters ? [] : undefined); + } + } + } + return getTypeAliasInstantiation(symbol, typeArgumentsFromTypeReferenceNode(node), newAliasSymbol, aliasTypeArguments); + } + 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; + } + + 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.links.declaredType = unresolvedType; + } + return result; + } + return unknownSymbol; + } + + 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 getTypeReferenceType(node: NodeWithTypeArguments, symbol: Symbol): Type { + if (symbol === unknownSymbol) { + 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; + } + + /** + * 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 getNoInferType(type: Type) { + return isNoInferTargetType(type) ? getOrCreateSubstitutionType(type, unknownType) : type; + } + + function isNoInferTargetType(type: Type): boolean { + // This is effectively a more conservative and predictable form of couldContainTypeVariables. We want to + // preserve NoInfer only for types that could contain type variables, but we don't want to exhaustively + // examine all object type members. + return !!(type.flags & TypeFlags.UnionOrIntersection && some((type as UnionOrIntersectionType).types, isNoInferTargetType) || + type.flags & TypeFlags.Substitution && !isNoInferType(type) && isNoInferTargetType((type as SubstitutionType).baseType) || + type.flags & TypeFlags.Object && !isEmptyAnonymousObjectType(type) || + type.flags & (TypeFlags.Instantiable & ~TypeFlags.Substitution) && !isPatternLiteralType(type)); + } + + function isNoInferType(type: Type) { + // A NoInfer type is represented as a substitution type with a TypeFlags.Unknown constraint. + return !!(type.flags & TypeFlags.Substitution && (type as SubstitutionType).constraint.flags & TypeFlags.Unknown); + } + + function getSubstitutionType(baseType: Type, constraint: Type) { + return constraint.flags & TypeFlags.AnyOrUnknown || constraint === baseType || baseType.flags & TypeFlags.Any ? + baseType : + getOrCreateSubstitutionType(baseType, constraint); + } + + function getOrCreateSubstitutionType(baseType: Type, constraint: Type) { + 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 getSubstitutionIntersection(substitutionType: SubstitutionType) { + return isNoInferType(substitutionType) ? substitutionType.baseType : 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); + } + } + // 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 && !(parent as MappedTypeNode).nameType && 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; + } + + function isJSDocTypeReference(node: Node): node is TypeReferenceNode { + return !!(node.flags & NodeFlags.JSDoc) && (node.kind === SyntaxKind.TypeReference || node.kind === SyntaxKind.ImportType); + } + + 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 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(/*symbol*/ undefined, emptySymbols, emptyArray, emptyArray, indexInfo); + } + return anyType; + } + checkNoTypeArguments(node); + return !noImplicitAny ? anyType : undefined; + } + } + } + + 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); + } + 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 (!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; + } + + function typeArgumentsFromTypeReferenceNode(node: NodeWithTypeArguments): Type[] | undefined { + return map(node.typeArguments, getTypeFromTypeNode); + } + + 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 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; + } + } + } + } + + if (!symbol) { + return arity ? emptyGenericType : emptyObjectType; + } + 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; + } + 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 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 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 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(/*location*/ undefined, name, meaning, diagnostic, /*isUse*/ false, /*excludeGlobals*/ false); + } + + 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; + } + + 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; + } + + 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 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 getGlobalImportMetaExpressionType() { + if (!deferredGlobalImportMetaExpressionType) { + // Create a synthetic type `ImportMetaExpression { meta: MetaProperty }` + const symbol = createSymbol(SymbolFlags.None, "ImportMetaExpression" as __String); + const importMetaType = getGlobalImportMetaType(); + + const metaPropertySymbol = createSymbol(SymbolFlags.Property, "meta" as __String, CheckFlags.Readonly); + metaPropertySymbol.parent = symbol; + metaPropertySymbol.links.type = importMetaType; + + const members = createSymbolTable([metaPropertySymbol]); + symbol.members = members; + + deferredGlobalImportMetaExpressionType = createAnonymousType(symbol, members, emptyArray, emptyArray, emptyArray); + } + return deferredGlobalImportMetaExpressionType; + } + + function getGlobalImportCallOptionsType(reportErrors: boolean) { + return (deferredGlobalImportCallOptionsType ||= getGlobalType("ImportCallOptions" as __String, /*arity*/ 0, reportErrors)) || emptyObjectType; + } + + function getGlobalImportAttributesType(reportErrors: boolean) { + return (deferredGlobalImportAttributesType ||= getGlobalType("ImportAttributes" as __String, /*arity*/ 0, reportErrors)) || emptyObjectType; + } + + function getGlobalESSymbolConstructorSymbol(reportErrors: boolean): Symbol | undefined { + return deferredGlobalESSymbolConstructorSymbol ||= getGlobalValueSymbol("Symbol" as __String, reportErrors); + } + + function getGlobalESSymbolConstructorTypeSymbol(reportErrors: boolean): Symbol | undefined { + return deferredGlobalESSymbolConstructorTypeSymbol ||= getGlobalTypeSymbol("SymbolConstructor" as __String, reportErrors); + } + + function getGlobalESSymbolType() { + return (deferredGlobalESSymbolType ||= getGlobalType("Symbol" as __String, /*arity*/ 0, /*reportErrors*/ false)) || emptyObjectType; + } + + function getGlobalPromiseType(reportErrors: boolean) { + return (deferredGlobalPromiseType ||= getGlobalType("Promise" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; + } + + function getGlobalPromiseLikeType(reportErrors: boolean) { + return (deferredGlobalPromiseLikeType ||= getGlobalType("PromiseLike" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; + } + + function getGlobalPromiseConstructorSymbol(reportErrors: boolean): Symbol | undefined { + return deferredGlobalPromiseConstructorSymbol ||= getGlobalValueSymbol("Promise" as __String, reportErrors); + } + + function getGlobalPromiseConstructorLikeType(reportErrors: boolean) { + return (deferredGlobalPromiseConstructorLikeType ||= getGlobalType("PromiseConstructorLike" as __String, /*arity*/ 0, reportErrors)) || emptyObjectType; + } + + function getGlobalAsyncIterableType(reportErrors: boolean) { + return (deferredGlobalAsyncIterableType ||= getGlobalType("AsyncIterable" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; + } + + function getGlobalAsyncIteratorType(reportErrors: boolean) { + return (deferredGlobalAsyncIteratorType ||= getGlobalType("AsyncIterator" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType; + } + + function getGlobalAsyncIterableIteratorType(reportErrors: boolean) { + return (deferredGlobalAsyncIterableIteratorType ||= getGlobalType("AsyncIterableIterator" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; + } + + function getGlobalAsyncGeneratorType(reportErrors: boolean) { + return (deferredGlobalAsyncGeneratorType ||= getGlobalType("AsyncGenerator" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType; + } + + function getGlobalIterableType(reportErrors: boolean) { + return (deferredGlobalIterableType ||= getGlobalType("Iterable" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; + } + + function getGlobalIteratorType(reportErrors: boolean) { + return (deferredGlobalIteratorType ||= getGlobalType("Iterator" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType; + } + + function getGlobalIterableIteratorType(reportErrors: boolean) { + return (deferredGlobalIterableIteratorType ||= getGlobalType("IterableIterator" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; + } + + function getGlobalGeneratorType(reportErrors: boolean) { + return (deferredGlobalGeneratorType ||= getGlobalType("Generator" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType; + } + + function getGlobalIteratorYieldResultType(reportErrors: boolean) { + return (deferredGlobalIteratorYieldResultType ||= getGlobalType("IteratorYieldResult" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; + } + + function getGlobalIteratorReturnResultType(reportErrors: boolean) { + return (deferredGlobalIteratorReturnResultType ||= getGlobalType("IteratorReturnResult" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; + } + + function getGlobalDisposableType(reportErrors: boolean) { + return (deferredGlobalDisposableType ||= getGlobalType("Disposable" as __String, /*arity*/ 0, reportErrors)) || emptyObjectType; + } + + function getGlobalAsyncDisposableType(reportErrors: boolean) { + return (deferredGlobalAsyncDisposableType ||= getGlobalType("AsyncDisposable" as __String, /*arity*/ 0, reportErrors)) || emptyObjectType; + } + + function getGlobalTypeOrUndefined(name: __String, arity = 0): ObjectType | undefined { + const symbol = getGlobalSymbol(name, SymbolFlags.Type, /*diagnostic*/ undefined); + return symbol && getTypeOfGlobalSymbol(symbol, arity) as GenericType; + } + + 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 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 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 getGlobalBigIntType() { + return (deferredGlobalBigIntType ||= getGlobalType("BigInt" as __String, /*arity*/ 0, /*reportErrors*/ false)) || emptyObjectType; + } + + function getGlobalClassDecoratorContextType(reportErrors: boolean) { + return (deferredGlobalClassDecoratorContextType ??= getGlobalType("ClassDecoratorContext" as __String, /*arity*/ 1, reportErrors)) ?? emptyGenericType; + } + + function getGlobalClassMethodDecoratorContextType(reportErrors: boolean) { + return (deferredGlobalClassMethodDecoratorContextType ??= getGlobalType("ClassMethodDecoratorContext" as __String, /*arity*/ 2, reportErrors)) ?? emptyGenericType; + } + + function getGlobalClassGetterDecoratorContextType(reportErrors: boolean) { + return (deferredGlobalClassGetterDecoratorContextType ??= getGlobalType("ClassGetterDecoratorContext" as __String, /*arity*/ 2, reportErrors)) ?? emptyGenericType; + } + + function getGlobalClassSetterDecoratorContextType(reportErrors: boolean) { + return (deferredGlobalClassSetterDecoratorContextType ??= getGlobalType("ClassSetterDecoratorContext" as __String, /*arity*/ 2, reportErrors)) ?? emptyGenericType; + } + + function getGlobalClassAccessorDecoratorContextType(reportErrors: boolean) { + return (deferredGlobalClassAccessorDecoratorContextType ??= getGlobalType("ClassAccessorDecoratorContext" as __String, /*arity*/ 2, reportErrors)) ?? emptyGenericType; + } + + function getGlobalClassAccessorDecoratorTargetType(reportErrors: boolean) { + return (deferredGlobalClassAccessorDecoratorTargetType ??= getGlobalType("ClassAccessorDecoratorTarget" as __String, /*arity*/ 2, reportErrors)) ?? emptyGenericType; + } + + function getGlobalClassAccessorDecoratorResultType(reportErrors: boolean) { + return (deferredGlobalClassAccessorDecoratorResultType ??= getGlobalType("ClassAccessorDecoratorResult" as __String, /*arity*/ 2, reportErrors)) ?? emptyGenericType; + } + + function getGlobalClassFieldDecoratorContextType(reportErrors: boolean) { + return (deferredGlobalClassFieldDecoratorContextType ??= getGlobalType("ClassFieldDecoratorContext" as __String, /*arity*/ 2, reportErrors)) ?? emptyGenericType; + } + + function getGlobalNaNSymbol(): Symbol | undefined { + return (deferredGlobalNaNSymbol ||= getGlobalValueSymbol("NaN" as __String, /*reportErrors*/ false)); + } + + function getGlobalRecordSymbol(): Symbol | undefined { + deferredGlobalRecordSymbol ||= getGlobalTypeAliasSymbol("Record" as __String, /*arity*/ 2, /*reportErrors*/ true) || unknownSymbol; + return deferredGlobalRecordSymbol === unknownSymbol ? undefined : deferredGlobalRecordSymbol; + } + + /** + * 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 createTypedPropertyDescriptorType(propertyType: Type): Type { + return createTypeFromGenericGlobalType(getGlobalTypedPropertyDescriptorType(), [propertyType]); + } + + function createIterableType(iteratedType: Type): Type { + return createTypeFromGenericGlobalType(getGlobalIterableType(/*reportErrors*/ true), [iteratedType]); + } + + 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; + } + } + + 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); + return getTupleTargetType(elementFlags, readonly, map((node as TupleTypeNode).elements, memberIfLabeledElementDeclaration)); + } + + function memberIfLabeledElementDeclaration(member: Node): NamedTupleMember | ParameterDeclaration | undefined { + return isNamedTupleMember(member) || isParameter(member) ? member : undefined; + } + + // 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; + } + + // 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; + } + + 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 | undefined)[] = []) { + 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 | undefined)[]): 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" : "") + + (some(namedMemberDeclarations, node => !!node) ? "," + map(namedMemberDeclarations, node => node ? getNodeId(node) : "_").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 = 0 as ElementFlags; + 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.links.tupleLabelDeclaration = namedMemberDeclarations?.[i]; + property.links.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.links.type = numberType; + } + else { + const literalTypes = []; + for (let i = minLength; i <= arity; i++) literalTypes.push(getNumberLiteralType(i)); + lengthSymbol.links.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[] = []; + const 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.Any) { + addElement(type, ElementFlags.Rest, target.labeledElementDeclarations?.[i]); + } + else 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 = getElementTypes(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]); + } + } + else { + // Copy other element kinds with no change. + addElement(type, flags, 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); + } + 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); + expandedDeclarations.push(declaration); + } + } + + 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))); + } + + // 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; + } + + function getTotalFixedElementCount(type: TupleType) { + return type.fixedLength + getEndElementCount(type, ElementFlags.Fixed); + } + + function getElementTypes(type: TupleTypeReference): readonly Type[] { + const typeArguments = getTypeArguments(type); + const arity = getTypeReferenceArity(type); + return typeArguments.length === arity ? typeArguments : typeArguments.slice(0, arity); + } + + function getTypeFromOptionalTypeNode(node: OptionalTypeNode): Type { + return addOptionality(getTypeFromTypeNode(node.type), /*isProperty*/ true); + } + + function getTypeId(type: Type): TypeId { + return type.id; + } + + 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 addTypeToUnion(typeSet: Type[], includes: TypeFlags, type: Type) { + const flags = type.flags; + // We ignore 'never' types in unions + if (!(flags & TypeFlags.Never)) { + includes |= flags & TypeFlags.IncludesMask; + if (flags & TypeFlags.Instantiable) includes |= TypeFlags.IncludesInstantiable; + if (flags & TypeFlags.Intersection && getObjectFlags(type) & ObjectFlags.IsConstrainedTypeVariable) includes |= TypeFlags.IncludesConstrainedTypeVariable; + if (type === wildcardType) includes |= TypeFlags.IncludesWildcard; + if (isErrorType(type)) includes |= TypeFlags.IncludesError; + 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); + } + } + } + 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 { + let lastType: Type | undefined; + for (const type of types) { + // We skip the type if it is the same as the last type we processed. This simple test particularly + // saves a lot of work for large lists of the same union type, such as when resolving `Record[A]`, + // where A and B are large union types. + if (type !== lastType) { + includes = type.flags & TypeFlags.Union ? + addTypesToUnion(typeSet, includes | (isNamedUnionType(type) ? TypeFlags.Union : 0), (type as UnionType).types) : + addTypeToUnion(typeSet, includes, type); + lastType = 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; + } + + // 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) { + // A type parameter with a union constraint may be a subtype of some union, but not a subtype of the + // individual constituents of that union. For example, `T extends A | B` is a subtype of `A | B`, but not + // a subtype of just `A` or just `B`. When we encounter such a type parameter, we therefore check if the + // type parameter is a subtype of a union of all the other types. + if (source.flags & TypeFlags.TypeParameter && getBaseConstraintOrType(source).flags & TypeFlags.Union) { + if (isTypeRelatedTo(source, getUnionType(map(types, t => t === source ? neverType : t)), strictSubtypeRelation)) { + orderedRemoveItemAt(types, i); + } + continue; + } + // 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; + } + } + 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; + } + + 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 removeStringLiteralsMatchedByTemplateLiterals(types: Type[]) { + const templates = filter(types, isPatternLiteralType) as (TemplateLiteralType | StringMappingType)[]; + if (templates.length) { + let i = types.length; + while (i > 0) { + i--; + const t = types[i]; + if (t.flags & TypeFlags.StringLiteral && some(templates, template => isTypeMatchedByTemplateLiteralOrStringMapping(t, template))) { + orderedRemoveItemAt(types, i); + } + } + } + } + + function isTypeMatchedByTemplateLiteralOrStringMapping(type: Type, template: TemplateLiteralType | StringMappingType) { + return template.flags & TypeFlags.TemplateLiteral ? + isTypeMatchedByTemplateLiteralType(type, template as TemplateLiteralType) : + isMemberOfStringMapping(type, template); + } + + function removeConstrainedTypeVariables(types: Type[]) { + const typeVariables: TypeVariable[] = []; + // First collect a list of the type variables occurring in constraining intersections. + for (const type of types) { + if (type.flags & TypeFlags.Intersection && getObjectFlags(type) & ObjectFlags.IsConstrainedTypeVariable) { + const index = (type as IntersectionType).types[0].flags & TypeFlags.TypeVariable ? 0 : 1; + pushIfUnique(typeVariables, (type as IntersectionType).types[index]); + } + } + // For each type variable, check if the constraining intersections for that type variable fully + // cover the constraint of the type variable; if so, remove the constraining intersections and + // substitute the type variable. + for (const typeVariable of typeVariables) { + const primitives: Type[] = []; + // First collect the primitive types from the constraining intersections. + for (const type of types) { + if (type.flags & TypeFlags.Intersection && getObjectFlags(type) & ObjectFlags.IsConstrainedTypeVariable) { + const index = (type as IntersectionType).types[0].flags & TypeFlags.TypeVariable ? 0 : 1; + if ((type as IntersectionType).types[index] === typeVariable) { + insertType(primitives, (type as IntersectionType).types[1 - index]); + } + } + } + // If every constituent in the type variable's constraint is covered by an intersection of the type + // variable and that constituent, remove those intersections and substitute the type variable. + const constraint = getBaseConstraintOfType(typeVariable)!; + if (everyType(constraint, t => containsType(primitives, t))) { + let i = types.length; + while (i > 0) { + i--; + const type = types[i]; + if (type.flags & TypeFlags.Intersection && getObjectFlags(type) & ObjectFlags.IsConstrainedTypeVariable) { + const index = (type as IntersectionType).types[0].flags & TypeFlags.TypeVariable ? 0 : 1; + if ((type as IntersectionType).types[index] === typeVariable && containsType(primitives, (type as IntersectionType).types[1 - index])) { + orderedRemoveItemAt(types, i); + } + } + } + insertType(types, typeVariable); + } + } + } + + 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 createOriginUnionOrIntersectionType(flags: TypeFlags, types: Type[]) { + const result = createOriginType(flags) as UnionOrIntersectionType; + result.types = types; + return result; + } + + // 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]; + } + // We optimize for the common case of unioning a union type with some other type (such as `undefined`). + if (types.length === 2 && !origin && (types[0].flags & TypeFlags.Union || types[1].flags & TypeFlags.Union)) { + const infix = unionReduction === UnionReduction.None ? "N" : unionReduction === UnionReduction.Subtype ? "S" : "L"; + const index = types[0].id < types[1].id ? 0 : 1; + const id = types[index].id + infix + types[1 - index].id + getAliasId(aliasSymbol, aliasTypeArguments); + let type = unionOfUnionTypes.get(id); + if (!type) { + type = getUnionTypeWorker(types, unionReduction, aliasSymbol, aliasTypeArguments, /*origin*/ undefined); + unionOfUnionTypes.set(id, type); + } + return type; + } + return getUnionTypeWorker(types, unionReduction, aliasSymbol, aliasTypeArguments, origin); + } + + function getUnionTypeWorker(types: readonly Type[], unionReduction: UnionReduction, aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined, origin: Type | undefined): Type { + let typeSet: Type[] | undefined = []; + const includes = addTypesToUnion(typeSet, 0 as TypeFlags, types); + if (unionReduction !== UnionReduction.None) { + if (includes & TypeFlags.AnyOrUnknown) { + return includes & TypeFlags.Any ? + includes & TypeFlags.IncludesWildcard ? wildcardType : + includes & TypeFlags.IncludesError ? errorType : anyType : + unknownType; + } + if (includes & TypeFlags.Undefined) { + // If type set contains both undefinedType and missingType, remove missingType + if (typeSet.length >= 2 && typeSet[0] === undefinedType && typeSet[1] === missingType) { + orderedRemoveItemAt(typeSet, 1); + } + } + if (includes & (TypeFlags.Enum | 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 | TypeFlags.StringMapping)) { + removeStringLiteralsMatchedByTemplateLiterals(typeSet); + } + if (includes & TypeFlags.IncludesConstrainedTypeVariable) { + removeConstrainedTypeVariables(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 (!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); + } + } + 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 last: TypePredicate | undefined; + const types: Type[] = []; + for (const sig of signatures) { + const pred = getTypePredicateOfSignature(sig); + if (pred) { + // Constituent type predicates must all have matching kinds. We don't create composite type predicates for assertions. + if (pred.kind !== TypePredicateKind.This && pred.kind !== TypePredicateKind.Identifier || last && !typePredicateKindsMatch(last, pred)) { + return undefined; + } + last = pred; + types.push(pred.type); + } + else { + // In composite union signatures we permit and ignore signatures with a return type `false`. + const returnType = kind !== TypeFlags.Intersection ? getReturnTypeOfSignature(sig) : undefined; + if (returnType !== falseType && returnType !== regularFalseType) { + return undefined; + } + } + } + if (!last) { + return undefined; + } + const compositeType = getUnionOrIntersectionType(types, kind); + return createTypePredicate(last.kind, last.parameterName, last.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[], precomputedObjectFlags: ObjectFlags, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[], origin?: Type): Type { + if (types.length === 0) { + return neverType; + } + 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 = precomputedObjectFlags | 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"; + } + unionTypes.set(id, type); + } + return type; + } + + 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 addTypeToIntersection(typeSet: Map, 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); + } + } + else { + if (flags & TypeFlags.AnyOrUnknown) { + if (type === wildcardType) includes |= TypeFlags.IncludesWildcard; + if (isErrorType(type)) includes |= TypeFlags.IncludesError; + } + else if (strictNullChecks || !(flags & TypeFlags.Nullable)) { + if (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; + } + return includes; + } + + // 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: Map, includes: TypeFlags, types: readonly Type[]) { + for (const type of types) { + includes = addTypeToIntersection(typeSet, includes, getRegularTypeOfLiteralType(type)); + } + return includes; + } + + 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); + } + } + } + + // 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.Enum | TypeFlags.NumberLiteral) ? numberType : + type.flags & TypeFlags.BigIntLiteral ? bigintType : + type.flags & TypeFlags.UniqueESSymbol ? esSymbolType : + undefined; + if (!primitive || !containsType(u.types, primitive)) { + return false; + } + } + } + return true; + } + + /** + * Returns true if the intersection of the template literals and string literals is the empty set, + * for example `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 | TypeFlags.StringMapping))) continue; + for (const t2 of literals) { + if (isTypeSubtypeOf(t2, t)) { + // For example, `get${T}` & "getX" is just "getX", and Lowercase & "foo" is just "foo" + orderedRemoveItemAt(types, i); + break; + } + else if (isPatternLiteralType(t)) { + return true; + } + } + } + return false; + } + + function removeFromEach(types: Type[], flag: TypeFlags) { + for (let i = 0; i < types.length; i++) { + types[i] = filterType(types[i], t => !(t.flags & flag)); + } + } + + // 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 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); + } + } + } + } + // Finally replace the first union with the result + types[index] = getUnionTypeFromSortedList(result, ObjectFlags.PrimitiveUnion); + return true; + } + + function createIntersectionType(types: Type[], objectFlags: ObjectFlags, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]) { + const result = createType(TypeFlags.Intersection) as IntersectionType; + result.objectFlags = objectFlags | getPropagatingFlagsOfTypes(types, /*excludeKinds*/ TypeFlags.Nullable); + result.types = types; + result.aliasSymbol = aliasSymbol; + result.aliasTypeArguments = aliasTypeArguments; + return result; + } + + // We normalize combinations of intersection and union types based on the distributive property of the '&' + // operator. Specifically, because X & (A | B) is equivalent to X & A | X & B, we can transform intersection + // types with union type constituents into equivalent union types with intersection type constituents and + // 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[], flags = IntersectionFlags.None, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type { + const typeMembershipMap = new Map(); + const includes = addTypesToIntersection(typeMembershipMap, 0 as TypeFlags, types); + const typeSet: Type[] = arrayFrom(typeMembershipMap.values()); + let objectFlags = ObjectFlags.None; + // 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 | TypeFlags.StringMapping) && includes & TypeFlags.StringLiteral && extractRedundantTemplateLiterals(typeSet)) { + return neverType; + } + if (includes & TypeFlags.Any) { + return includes & TypeFlags.IncludesWildcard ? wildcardType : includes & TypeFlags.IncludesError ? errorType : 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 (!(flags & IntersectionFlags.NoSupertypeReduction)) removeRedundantSupertypes(typeSet, includes); + } + if (includes & TypeFlags.IncludesMissingType) { + typeSet[typeSet.indexOf(undefinedType)] = missingType; + } + if (typeSet.length === 0) { + return unknownType; + } + if (typeSet.length === 1) { + return typeSet[0]; + } + if (typeSet.length === 2 && !(flags & IntersectionFlags.NoConstraintReduction)) { + const typeVarIndex = typeSet[0].flags & TypeFlags.TypeVariable ? 0 : 1; + const typeVariable = typeSet[typeVarIndex]; + const primitiveType = typeSet[1 - typeVarIndex]; + if ( + typeVariable.flags & TypeFlags.TypeVariable && + (primitiveType.flags & (TypeFlags.Primitive | TypeFlags.NonPrimitive) && !isGenericStringLikeType(primitiveType) || includes & TypeFlags.IncludesEmptyObject) + ) { + // We have an intersection T & P or P & T, where T is a type variable and P is a primitive type, the object type, or {}. + const constraint = getBaseConstraintOfType(typeVariable); + // Check that T's constraint is similarly composed of primitive types, the object type, or {}. + if (constraint && everyType(constraint, t => !!(t.flags & (TypeFlags.Primitive | TypeFlags.NonPrimitive)) || isEmptyAnonymousObjectType(t))) { + // If T's constraint is a subtype of P, simply return T. For example, given `T extends "a" | "b"`, + // the intersection `T & string` reduces to just T. + if (isTypeStrictSubtypeOf(constraint, primitiveType)) { + return typeVariable; + } + if (!(constraint.flags & TypeFlags.Union && someType(constraint, c => isTypeStrictSubtypeOf(c, primitiveType)))) { + // No constituent of T's constraint is a subtype of P. If P is also not a subtype of T's constraint, + // then the constraint and P are unrelated, and the intersection reduces to never. For example, given + // `T extends "a" | "b"`, the intersection `T & number` reduces to never. + if (!isTypeStrictSubtypeOf(primitiveType, constraint)) { + return neverType; + } + } + // Some constituent of T's constraint is a subtype of P, or P is a subtype of T's constraint. Thus, + // the intersection further constrains the type variable. For example, given `T extends string | number`, + // the intersection `T & "a"` is marked as a constrained type variable. Likewise, given `T extends "a" | 1`, + // the intersection `T & number` is marked as a constrained type variable. + objectFlags = ObjectFlags.IsConstrainedTypeVariable; + } + } + } + const id = getTypeListId(typeSet) + (flags & IntersectionFlags.NoConstraintReduction ? "*" : 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, flags, aliasSymbol, aliasTypeArguments); + } + else if (every(typeSet, t => !!(t.flags & TypeFlags.Union && (t as UnionType).types[0].flags & TypeFlags.Undefined))) { + const containedUndefinedType = some(typeSet, containsMissingType) ? missingType : undefinedType; + removeFromEach(typeSet, TypeFlags.Undefined); + result = getUnionType([getIntersectionType(typeSet, flags), containedUndefinedType], UnionReduction.Literal, aliasSymbol, aliasTypeArguments); + } + else if (every(typeSet, t => !!(t.flags & TypeFlags.Union && ((t as UnionType).types[0].flags & TypeFlags.Null || (t as UnionType).types[1].flags & TypeFlags.Null)))) { + removeFromEach(typeSet, TypeFlags.Null); + result = getUnionType([getIntersectionType(typeSet, flags), nullType], UnionReduction.Literal, aliasSymbol, aliasTypeArguments); + } + else if (typeSet.length >= 4) { + // When we have four or more constituents, some of which are unions, we employ a "divide and conquer" strategy + // where A & B & C & D is processed as (A & B) & (C & D). Since intersections of unions often produce far smaller + // unions of intersections than the full cartesian product (due to some intersections becoming `never`), this can + // dramatically reduce the overall work. + const middle = Math.floor(typeSet.length / 2); + result = getIntersectionType([getIntersectionType(typeSet.slice(0, middle), flags), getIntersectionType(typeSet.slice(middle), flags)], flags, 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, flags); + // 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 { + result = createIntersectionType(typeSet, objectFlags, aliasSymbol, aliasTypeArguments); + } + intersectionTypes.set(id, result); + } + return result; + } + + 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 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 getCrossProductIntersections(types: readonly Type[], flags: IntersectionFlags) { + 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); + } + } + const t = getIntersectionType(constituents, flags); + if (!(t.flags & TypeFlags.Never)) intersections.push(t); + } + return intersections; + } + + 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 getTypeFromIntersectionTypeNode(node: IntersectionTypeNode): Type { + const links = getNodeLinks(node); + if (!links.resolvedType) { + const aliasSymbol = getAliasSymbolForTypeNode(node); + const types = map(node.types, getTypeFromTypeNode); + // We perform no supertype reduction for X & {} or {} & X, where X is one of string, number, bigint, + // or a pattern literal template type. This enables union types like "a" | "b" | string & {} or + // "aa" | "ab" | `a${string}` which preserve the literal types for purposes of statement completion. + const emptyIndex = types.length === 2 ? types.indexOf(emptyTypeLiteralType) : -1; + const t = emptyIndex >= 0 ? types[1 - emptyIndex] : unknownType; + const noSupertypeReduction = !!(t.flags & (TypeFlags.String | TypeFlags.Number | TypeFlags.BigInt) || t.flags & TypeFlags.TemplateLiteral && isPatternLiteralType(t)); + links.resolvedType = getIntersectionType(types, noSupertypeReduction ? IntersectionFlags.NoSupertypeReduction : 0, aliasSymbol, getTypeArgumentsForAliasSymbol(aliasSymbol)); + } + return links.resolvedType; + } + + function createIndexType(type: InstantiableType | UnionOrIntersectionType, indexFlags: IndexFlags) { + const result = createType(TypeFlags.Index) as IndexType; + result.type = type; + result.indexFlags = indexFlags; + return result; + } + + function createOriginIndexType(type: InstantiableType | UnionOrIntersectionType) { + const result = createOriginType(TypeFlags.Index) as IndexType; + result.type = type; + return result; + } + + function getIndexTypeForGenericType(type: InstantiableType | UnionOrIntersectionType, indexFlags: IndexFlags) { + return indexFlags & IndexFlags.StringsOnly ? + type.resolvedStringIndexType || (type.resolvedStringIndexType = createIndexType(type, IndexFlags.StringsOnly)) : + type.resolvedIndexType || (type.resolvedIndexType = createIndexType(type, IndexFlags.None)); + } + + /** + * 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, indexFlags: IndexFlags) { + const typeParameter = getTypeParameterFromMappedType(type); + const constraintType = getConstraintTypeFromMappedType(type); + const nameType = getNameTypeFromMappedType(type.target as MappedType || type); + if (!nameType && !(indexFlags & IndexFlags.NoIndexSignatures)) { + // no mapping and no filtering required, just quickly bail to returning the constraint in the common case + return constraintType; + } + const keyTypes: Type[] = []; + // Calling getApparentType on the `T` of a `keyof T` in the constraint type of a generic mapped type can + // trigger a circularity. For example, `T extends { [P in keyof T & string as Captitalize

]: any }` is + // a circular definition. For this reason, we only eagerly manifest the keys if the constraint is non-generic. + if (isGenericIndexType(constraintType)) { + if (isMappedTypeWithKeyofConstraintDeclaration(type)) { + // 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, indexFlags); + } + // Include the generic component in the resulting type. + forEachType(constraintType, addMemberForKeyType); + } + else if (isMappedTypeWithKeyofConstraintDeclaration(type)) { + const modifiersType = getApparentType(getModifiersTypeFromMappedType(type)); // The 'T' in 'keyof T' + forEachMappedTypePropertyKeyTypeAndIndexSignatureKeyType(modifiersType, TypeFlags.StringOrNumberLiteralOrUnique, !!(indexFlags & IndexFlags.StringsOnly), addMemberForKeyType); + } + else { + forEachType(getLowerBoundOfKeyType(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 = indexFlags & IndexFlags.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); + } + } + + // Ordinarily we reduce a keyof M, where M is a mapped type { [P in K as N

) + 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) { + if (node.typeParameters || getEffectiveReturnTypeNode(node) || !node.body) { + return false; + } + if (node.body.kind !== SyntaxKind.Block) { + return isContextSensitive(node.body); + } + return !!forEachReturnStatement(node.body as Block, statement => !!statement.expression && isContextSensitive(statement.expression)); + } + + 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; + } + } + else if (type.flags & TypeFlags.Intersection) { + return getIntersectionType(map((type as IntersectionType).types, getTypeWithoutSignatures)); + } + return type; + } + + // TYPE CHECKING + + 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 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 isTypeSubtypeOf(source: Type, target: Type): boolean { + return isTypeRelatedTo(source, target, subtypeRelation); + } + + function isTypeStrictSubtypeOf(source: Type, target: Type): boolean { + return isTypeRelatedTo(source, target, strictSubtypeRelation); + } + + 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 an intersection type and some constituent of S is derived from T, or + // S is a type variable with a base constraint that is derived from T, or + // T is {} and S is an object-like type (ensuring {} is less derived than Object), or + // 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.Intersection ? some((source as IntersectionType).types, t => isTypeDerivedFrom(t, target)) : + source.flags & TypeFlags.InstantiableNonPrimitive ? isTypeDerivedFrom(getBaseConstraintOfType(source) || unknownType, target) : + isEmptyAnonymousObjectType(target) ? !!(source.flags & (TypeFlags.Object | TypeFlags.NonPrimitive)) : + target === globalObjectType ? !!(source.flags & (TypeFlags.Object | TypeFlags.NonPrimitive)) && !isEmptyAnonymousObjectType(source) : + 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); + } + + 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); + } + + /** + * 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: Map, + 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 elaborateError( + node: Expression | undefined, + source: Type, + target: Type, + relation: Map, + 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.AsExpression: + if (!isConstAssertion(node)) { + break; + } + // fallthrough + case SyntaxKind.JsxExpression: + case SyntaxKind.ParenthesizedExpression: + return elaborateError((node as AsExpression | 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 elaborateDidYouMeanToCallOrConstruct( + node: Expression, + source: Type, + target: Type, + relation: Map, + 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; + } + } + return false; + } + + function elaborateArrowFunction( + node: ArrowFunction, + source: Type, + target: Type, + relation: Map, + 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, 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, /*headMessage*/ 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 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); + } + } + } + + function checkExpressionForMutableLocationWithContextualType(next: Expression, sourcePropType: Type) { + pushContextualType(next, sourcePropType, /*isCache*/ false); + const result = checkExpressionForMutableLocation(next, CheckMode.Contextual); + popContextualType(); + return result; + } + + 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: Map, + 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 (const value of iterator) { + const { errorNode: prop, innerExpression: next, nameType, errorMessage } = 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), + ), + ); + } + } + } + } + } + } + return reportedError; + } + + /** + * Assumes `target` type is assignable to the `Iterable` type, if `Iterable` is defined, + * or that it's an array or tuple-like type, if `Iterable` is not defined. + */ + function elaborateIterableOrArrayLikeTargetElementwise( + iterator: ElaborationIterator, + source: Type, + target: Type, + relation: Map, + containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, + errorOutputContainer: { errors?: Diagnostic[]; skipLogging?: boolean; } | undefined, + ) { + const tupleOrArrayLikeTargetParts = filterType(target, isArrayOrTupleLikeType); + const nonTupleOrArrayLikeTargetParts = filterType(target, t => !isArrayOrTupleLikeType(t)); + // If `nonTupleOrArrayLikeTargetParts` is not `never`, then that should mean `Iterable` is defined. + const iterationType = nonTupleOrArrayLikeTargetParts !== neverType + ? getIterationTypeOfIterable(IterationUse.ForOf, IterationTypeKind.Yield, nonTupleOrArrayLikeTargetParts, /*errorNode*/ undefined) + : undefined; + + let reportedError = false; + for (let status = iterator.next(); !status.done; status = iterator.next()) { + const { errorNode: prop, innerExpression: next, nameType, errorMessage } = status.value; + let targetPropType = iterationType; + const targetIndexedPropType = tupleOrArrayLikeTargetParts !== neverType ? getBestMatchIndexedAccessTypeOrUndefined(source, tupleOrArrayLikeTargetParts, nameType) : undefined; + if (targetIndexedPropType && !(targetIndexedPropType.flags & TypeFlags.IndexedAccess)) { // Don't elaborate on indexes on generic variables + targetPropType = iterationType ? getUnionType([iterationType, targetIndexedPropType]) : targetIndexedPropType; + } + if (!targetPropType) continue; + 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(tupleOrArrayLikeTargetParts, 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); + } + } + } + } + } + return reportedError; + } + + function* generateJsxAttributes(node: JsxAttributes): ElaborationIterator { + if (!length(node.properties)) return; + for (const prop of node.properties) { + if (isJsxSpreadAttribute(prop) || isHyphenatedJsxName(getTextOfJsxAttributeName(prop.name))) continue; + yield { errorNode: prop.name, innerExpression: prop.initializer, nameType: getStringLiteralType(getTextOfJsxAttributeName(prop.name)) }; + } + } + + 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++; + } + } + } + + 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: Map, + 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; + let arrayLikeTargetParts: Type; + let nonArrayLikeTargetParts: Type; + const iterableType = getGlobalIterableType(/*reportErrors*/ false); + if (iterableType !== emptyGenericType) { + const anyIterable = createIterableType(anyType); + arrayLikeTargetParts = filterType(childrenTargetType, t => isTypeAssignableTo(t, anyIterable)); + nonArrayLikeTargetParts = filterType(childrenTargetType, t => !isTypeAssignableTo(t, anyIterable)); + } + else { + arrayLikeTargetParts = filterType(childrenTargetType, isArrayOrTupleLikeType); + nonArrayLikeTargetParts = filterType(childrenTargetType, t => !isArrayOrTupleLikeType(t)); + } + if (moreThanOneRealChildren) { + if (arrayLikeTargetParts !== neverType) { + const realSource = createTupleType(checkJsxChildren(containingElement, CheckMode.Normal)); + const children = generateJsxChildren(containingElement, getInvalidTextualChildDiagnostic); + result = elaborateIterableOrArrayLikeTargetElementwise(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); + } + } + } + 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 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(diagnostic, tagNameText, childrenPropName, typeToString(childrenTargetType)) }; + } + return invalidTextDiagnostic; + } + } + + 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); + const checkNode = getEffectiveCheckNode(elem); + yield { errorNode: checkNode, innerExpression: checkNode, nameType }; + } + } + + function elaborateArrayLiteral( + node: ArrayLiteralExpression, + source: Type, + target: Type, + relation: Map, + 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 + pushContextualType(node, target, /*isCache*/ false); + const tupleizedType = checkArrayLiteral(node, CheckMode.Contextual, /*forceTuple*/ true); + popContextualType(); + if (isTupleLikeType(tupleizedType)) { + return elaborateElementwise(generateLimitedTupleElements(node, target), tupleizedType, target, relation, containingMessageChain, errorOutputContainer); + } + return false; + } + + function* generateObjectLiteralElements(node: ObjectLiteralExpression): ElaborationIterator { + if (!length(node.properties)) return; + for (const prop of node.properties) { + if (isSpreadAssignment(prop)) continue; + const type = getLiteralTypeFromProperty(getSymbolOfDeclaration(prop), TypeFlags.StringOrNumberLiteralOrUnique); + if (!type || (type.flags & TypeFlags.Never)) { + continue; + } + 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); + } + } + } + + function elaborateObjectLiteral( + node: ObjectLiteralExpression, + source: Type, + target: Type, + relation: Map, + 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); + } + + /** + * 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 isSignatureAssignableTo(source: Signature, target: Signature, ignoreReturnTypes: boolean): boolean { + return compareSignaturesRelated(source, target, ignoreReturnTypes ? SignatureCheckMode.IgnoreReturnTypes : SignatureCheckMode.None, /*reportErrors*/ false, /*errorReporter*/ undefined, /*incompatibleErrorReporter*/ undefined, compareTypesAssignable, /*reportUnreliableMarkers*/ undefined) !== Ternary.False; + } + + type ErrorReporter = (message: DiagnosticMessage, ...args: DiagnosticArguments) => void; + + /** + * Returns true if `s` is `(...args: A) => R` where `A` is `any`, `any[]`, `never`, or `never[]`, and `R` is `any` or `unknown`. + */ + function isTopSignature(s: Signature) { + if (!s.typeParameters && (!s.thisParameter || isTypeAny(getTypeOfParameter(s.thisParameter))) && s.parameters.length === 1 && signatureHasRestParameter(s)) { + const paramType = getTypeOfParameter(s.parameters[0]); + const restType = isArrayType(paramType) ? getTypeArguments(paramType)[0] : paramType; + return !!(restType.flags & (TypeFlags.Any | TypeFlags.Never) && getReturnTypeOfSignature(s).flags & TypeFlags.AnyOrUnknown); + } + return 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 (!(checkMode & SignatureCheckMode.StrictTopSignature && isTopSignature(source)) && isTopSignature(target)) { + return Ternary.True; + } + if (checkMode & SignatureCheckMode.StrictTopSignature && isTopSignature(source) && !isTopSignature(target)) { + return Ternary.False; + } + + const targetCount = getParameterCount(target); + const sourceHasMoreParameters = !hasEffectiveRestParameter(target) && + (checkMode & SignatureCheckMode.StrictArity ? hasEffectiveRestParameter(source) || getParameterCount(source) > targetCount : getMinArgumentCount(source) > targetCount); + if (sourceHasMoreParameters) { + if (reportErrors && !(checkMode & SignatureCheckMode.StrictArity)) { + // the second condition should be redundant, because there is no error reporting when comparing signatures by strict arity + // since it is only done for subtype reduction + errorReporter!(Diagnostics.Target_signature_provides_too_few_arguments_Expected_0_or_more_but_got_1, getMinArgumentCount(source), targetCount); + } + 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); + } + return Ternary.False; + } + result &= related; + } + } + + 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 ? getRestOrAnyTypeAtPosition(source, i) : tryGetTypeAtPosition(source, i); + const targetType = i === restIndex ? getRestOrAnyTypeAtPosition(target, i) : tryGetTypeAtPosition(target, i); + if (sourceType && targetType && (sourceType !== targetType || checkMode & SignatureCheckMode.StrictArity)) { + // 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 || isInstantiatedGenericParameter(source, i) ? undefined : getSingleCallSignature(getNonNullableType(sourceType)); + const targetSig = checkMode & SignatureCheckMode.Callback || isInstantiatedGenericParameter(target, i) ? 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; + } + 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) || isThisTypePredicate(targetTypePredicate)) { + if (reportErrors) { + errorReporter!(Diagnostics.Signature_0_must_be_a_type_predicate, signatureToString(source)); + } + return 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); + } + } + } + + 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 (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; + } + } + + 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 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); + } + + 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 !!((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 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 sourceProperty of getPropertiesOfType(getTypeOfSymbol(sourceSymbol))) { + if (sourceProperty.flags & SymbolFlags.EnumMember) { + const targetProperty = getPropertyOfType(targetEnumType, sourceProperty.escapedName); + if (!targetProperty || !(targetProperty.flags & SymbolFlags.EnumMember)) { + if (errorReporter) { + errorReporter(Diagnostics.Property_0_is_missing_in_type_1, symbolName(sourceProperty), typeToString(getDeclaredTypeOfSymbol(targetSymbol), /*enclosingDeclaration*/ undefined, TypeFormatFlags.UseFullyQualifiedType)); + enumRelation.set(id, RelationComparisonResult.Failed | RelationComparisonResult.Reported); + } + else { + enumRelation.set(id, RelationComparisonResult.Failed); + } + return false; + } + const sourceValue = getEnumMemberValue(getDeclarationOfKind(sourceProperty, SyntaxKind.EnumMember)!).value; + const targetValue = getEnumMemberValue(getDeclarationOfKind(targetProperty, SyntaxKind.EnumMember)!).value; + if (sourceValue !== targetValue) { + const sourceIsString = typeof sourceValue === "string"; + const targetIsString = typeof targetValue === "string"; + + // If we have 2 enums with *known* values that differ, they are incompatible. + if (sourceValue !== undefined && targetValue !== undefined) { + if (!errorReporter) { + enumRelation.set(id, RelationComparisonResult.Failed); + } + else { + const escapedSource = sourceIsString ? `"${escapeString(sourceValue)}"` : sourceValue; + const escapedTarget = targetIsString ? `"${escapeString(targetValue)}"` : targetValue; + errorReporter(Diagnostics.Each_declaration_of_0_1_differs_in_its_value_where_2_was_expected_but_3_was_given, symbolName(targetSymbol), symbolName(targetProperty), escapedTarget, escapedSource); + enumRelation.set(id, RelationComparisonResult.Failed | RelationComparisonResult.Reported); + } + return false; + } + + // At this point we know that at least one of the values is 'undefined'. + // This may mean that we have an opaque member from an ambient enum declaration, + // or that we were not able to calculate it (which is basically an error). + // + // Either way, we can assume that it's numeric. + // If the other is a string, we have a mismatch in types. + if (sourceIsString || targetIsString) { + if (!errorReporter) { + enumRelation.set(id, RelationComparisonResult.Failed); + } + else { + const knownStringValue = sourceValue ?? targetValue; + Debug.assert(typeof knownStringValue === "string"); + const escapedValue = `"${escapeString(knownStringValue)}"`; + errorReporter(Diagnostics.One_value_of_0_1_is_the_string_2_and_the_other_is_assumed_to_be_an_unknown_numeric_value, symbolName(targetSymbol), symbolName(targetProperty), escapedValue); + enumRelation.set(id, RelationComparisonResult.Failed | RelationComparisonResult.Reported); + } + return false; + } + } + } + } + enumRelation.set(id, RelationComparisonResult.Succeeded); + return true; + } + + function isSimpleTypeRelatedTo(source: Type, target: Type, relation: Map, errorReporter?: ErrorReporter) { + const s = source.flags; + const t = target.flags; + if (t & TypeFlags.Any || s & TypeFlags.Never || source === wildcardType) return true; + if (t & TypeFlags.Unknown && !(relation === strictSubtypeRelation && s & TypeFlags.Any)) 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; + } + // 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 is assignable to any computed numeric enum type or any numeric enum literal type, and + // a numeric literal type is assignable any computed numeric enum type or any numeric enum literal type + // with a matching value. These rules exist such that enums can be used for bit-flag purposes. + if (s & TypeFlags.Number && (t & TypeFlags.Enum || t & TypeFlags.NumberLiteral && t & TypeFlags.EnumLiteral)) return true; + if ( + s & TypeFlags.NumberLiteral && !(s & TypeFlags.EnumLiteral) && (t & TypeFlags.Enum || + t & TypeFlags.NumberLiteral && t & TypeFlags.EnumLiteral && + (source as NumberLiteralType).value === (target as NumberLiteralType).value) + ) 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: Map) { + 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; + } + } + 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); + } + + function getNormalizedType(type: Type, writing: boolean): Type { + while (true) { + const t = isFreshLiteralType(type) ? (type as FreshableType).regularType : + isGenericTupleType(type) ? getNormalizedTupleType(type, writing) : + 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; + } + } + + function getNormalizedUnionOrIntersectionType(type: UnionOrIntersectionType, writing: boolean) { + const reduced = getReducedType(type); + if (reduced !== type) { + return reduced; + } + if (type.flags & TypeFlags.Intersection && shouldNormalizeIntersection(type as IntersectionType)) { + // Normalization handles cases like + // Partial[K] & ({} | null) ==> + // Partial[K] & {} | Partial[K} & null ==> + // (T[K] | undefined) & {} | (T[K] | undefined) & null ==> + // T[K] & {} | undefined & {} | T[K] & null | undefined & null ==> + // T[K] & {} | T[K] & null + const normalizedTypes = sameMap(type.types, t => getNormalizedType(t, writing)); + if (normalizedTypes !== type.types) { + return getIntersectionType(normalizedTypes); + } + } + return type; + } + + function shouldNormalizeIntersection(type: IntersectionType) { + let hasInstantiable = false; + let hasNullableOrEmpty = false; + for (const t of type.types) { + hasInstantiable ||= !!(t.flags & TypeFlags.Instantiable); + hasNullableOrEmpty ||= !!(t.flags & TypeFlags.Nullable) || isEmptyAnonymousObjectType(t); + if (hasInstantiable && hasNullableOrEmpty) return true; + } + return false; + } + + function getNormalizedTupleType(type: TupleTypeReference, writing: boolean): Type { + const elements = getElementTypes(type); + const normalizedElements = sameMap(elements, t => t.flags & TypeFlags.Simplifiable ? getSimplifiedType(t, writing) : t); + return elements !== normalizedElements ? createNormalizedTupleType(type.target, normalizedElements) : type; + } + + /** + * 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: Map, + 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 maybeKeysSet: Set; + 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 skipParentCounter = 0; // How many errors should be skipped 'above' in the elaboration pyramid + let lastSkippedInfo: [Type, Type] | undefined; + let incompatibleStack: DiagnosticAndArguments[] | undefined; + // In Node.js, the maximum number of elements in a map is 2^24. We limit the number of entries an invocation + // of checkTypeRelatedTo can add to a relation to 1/8th of its remaining capacity. + let relationCount = (16_000_000 - relation.size) >> 3; + + 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) { + // Record this relation as having failed such that we don't attempt the overflowing operation again. + const id = getRelationKey(source, target, /*intersectionState*/ IntersectionState.None, relation, /*ignoreConstraints*/ false); + relation.set(id, RelationComparisonResult.Reported | RelationComparisonResult.Failed); + tracing?.instant(tracing.Phase.CheckTypes, "checkTypeRelatedTo_DepthLimit", { sourceId: source.id, targetId: target.id, depth: sourceDepth, targetDepth }); + const message = relationCount <= 0 ? + Diagnostics.Excessive_complexity_comparing_types_0_and_1 : + Diagnostics.Excessive_stack_depth_comparing_types_0_and_1; + const diag = error(errorNode || currentNode, message, 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(getSourceFileOfNode(errorNode!), errorNode!, errorInfo, relatedInformation); + if (relatedInfo) { + addRelatedInfo(diag, ...relatedInfo); + } + 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; + + function resetErrorInfo(saved: ReturnType) { + errorInfo = saved.errorInfo; + lastSkippedInfo = saved.lastSkippedInfo; + incompatibleStack = saved.incompatibleStack; + overrideNextErrorInfo = saved.overrideNextErrorInfo; + skipParentCounter = saved.skipParentCounter; + relatedInfo = saved.relatedInfo; + } + + function captureErrorCalculationState() { + return { + errorInfo, + lastSkippedInfo, + incompatibleStack: incompatibleStack?.slice(), + overrideNextErrorInfo, + skipParentCounter, + relatedInfo: relatedInfo?.slice() as [DiagnosticRelatedInformation, ...DiagnosticRelatedInformation[]] | undefined, + }; + } + + function reportIncompatibleError(message: DiagnosticMessage, ...args: DiagnosticArguments) { + overrideNextErrorInfo++; // Suppress the next relation error + lastSkippedInfo = undefined; // Reset skipped info cache + (incompatibleStack ||= []).push([message, ...args]); + } + + 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(/*message*/ undefined, ...info); + } + 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: DiagnosticAndArguments[] = []; + 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; + } + 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]]); + } + 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})`; + } + 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(/*message*/ undefined, ...info); + } + } + + function reportError(message: DiagnosticMessage, ...args: DiagnosticArguments): void { + Debug.assert(!!errorNode); + if (incompatibleStack) reportIncompatibleStack(); + if (message.elidedInCompatabilityPyramid) return; + if (skipParentCounter === 0) { + errorInfo = chainDiagnosticMessages(errorInfo, message, ...args); + } + else { + skipParentCounter--; + } + } + + function reportParentSkippedError(message: DiagnosticMessage, ...args: DiagnosticArguments): void { + reportError(message, ...args); + skipParentCounter++; + } + + 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; + + if (isLiteralType(source) && !typeCouldHaveTopLevelSingletonTypes(target)) { + generalizedSource = getBaseTypeOfLiteralType(source); + Debug.assert(!isTypeAssignableTo(generalizedSource, target), "generalized source shouldn't be assignable"); + generalizedSourceType = getTypeNameForErrorDisplay(generalizedSource); + } + + // If `target` is of indexed access type (And `source` it is not), we use the object type of `target` for better error reporting + const targetFlags = target.flags & TypeFlags.IndexedAccess && !(source.flags & TypeFlags.IndexedAccess) ? + (target as IndexedAccessType).objectType.flags : + target.flags; + + if (targetFlags & 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, + ); + } + } + + 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; + } + } + 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); + } + + 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); + } + } + + /** + * 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 { + /** + * 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 (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 true; + } + + 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 { + if (originalSource === originalTarget) return Ternary.True; + + // 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); + + 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); + } + + // 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; + } + } + + 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; + } + } + + 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; + } + + 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; + } + } + + 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 (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); + } + // Used by, eg, missing property checking to replace the top-level message with a more informative one. + if (!headMessage && maybeSuppress) { + // We suppress a call to `reportRelationError` or not depending on the state of the type checker, so + // we call `reportRelationError` here and then undo its effects to figure out what would be the diagnostic + // if we hadn't supress it, and save that as a canonical diagnostic for deduplication purposes. + const savedErrorState = captureErrorCalculationState(); + reportRelationError(headMessage, source, target); + let canonical; + if (errorInfo && errorInfo !== savedErrorState.errorInfo) { + canonical = { code: errorInfo.code, messageText: errorInfo.messageText }; + } + resetErrorInfo(savedErrorState); + if (canonical && errorInfo) { + errorInfo.canonicalHead = canonical; + } + + lastSkippedInfo = [source, target]; + 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; + } + + 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; + } + + 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 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)); + } + } + 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); + + const name = propDeclaration.name!; + errorNode = name; + + if (isIdentifier(name)) { + suggestion = getSuggestionForNonexistentProperty(name, errorTarget); + } + } + if (suggestion !== undefined) { + reportParentSkippedError(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 { + reportParentSkippedError(Diagnostics.Object_literal_may_only_specify_known_properties_and_0_does_not_exist_in_type_1, symbolToString(prop), 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)); + } + return true; + } + } + } + return false; + } + + 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) { + if (target.flags & TypeFlags.Union) { + // Intersections of union types are normalized into unions of intersection types, and such normalized + // unions can get very large and expensive to relate. The following fast path checks if the source union + // originated in an intersection. If so, and if that intersection contains the target type, then we know + // the result to be true (for any two types A and B, A & B is related to both A and B). + const sourceOrigin = (source as UnionType).origin; + if (sourceOrigin && sourceOrigin.flags & TypeFlags.Intersection && target.aliasSymbol && contains((sourceOrigin as IntersectionType).types, target)) { + return Ternary.True; + } + // Similarly, in unions of unions the we preserve the original list of unions. This original list is often + // much shorter than the normalized result, so we scan it in the following fast path. + const targetOrigin = (target as UnionType).origin; + if (targetOrigin && targetOrigin.flags & TypeFlags.Union && source.aliasSymbol && contains((targetOrigin as UnionType).types, source)) { + return Ternary.True; + } + } + 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), intersectionState); + } + 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); + } + + 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, IntersectionState.None); + if (!related) { + return Ternary.False; + } + result &= related; + } + return result; + } + + function typeRelatedToSomeType(source: Type, target: UnionOrIntersectionType, reportErrors: boolean, intersectionState: IntersectionState): Ternary { + const targetTypes = target.types; + if (target.flags & TypeFlags.Union) { + if (containsType(targetTypes, source)) { + return Ternary.True; + } + if ( + relation !== comparableRelation && getObjectFlags(target) & ObjectFlags.PrimitiveUnion && !(source.flags & TypeFlags.EnumLiteral) && ( + source.flags & (TypeFlags.StringLiteral | TypeFlags.BooleanLiteral | TypeFlags.BigIntLiteral) || + (relation === subtypeRelation || relation === strictSubtypeRelation) && source.flags & TypeFlags.NumberLiteral + ) + ) { + // When relating a literal type to a union of primitive types, we know the relation is false unless + // the union contains the base primitive type or the literal type in one of its fresh/regular forms. + // We exclude numeric literals for non-subtype relations because numeric literals are assignable to + // numeric enum literals with the same value. Similarly, we exclude enum literal types because + // identically named enum types are related (see isEnumTypeRelatedTo). We exclude the comparable + // relation in entirety because it needs to be checked in both directions. + const alternateForm = source === (source as StringLiteralType).regularType ? (source as StringLiteralType).freshType : (source as StringLiteralType).regularType; + const primitive = source.flags & TypeFlags.StringLiteral ? stringType : + source.flags & TypeFlags.NumberLiteral ? numberType : + source.flags & TypeFlags.BigIntLiteral ? bigintType : + undefined; + return primitive && containsType(targetTypes, primitive) || alternateForm && containsType(targetTypes, alternateForm) ? Ternary.True : Ternary.False; + } + const match = getMatchingUnionConstituentForType(target as UnionType, source); + if (match) { + const related = isRelatedTo(source, match, RecursionFlags.Target, /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState); + if (related) { + return related; + } + } + } + for (const type of targetTypes) { + const related = isRelatedTo(source, type, RecursionFlags.Target, /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState); + 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, /*headMessage*/ undefined, intersectionState); + } + } + 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; + } + 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; + } + 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; + } + } + return Ternary.False; + } + + function getUndefinedStrippedTargetIfNeeded(source: Type, target: Type) { + 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) { + result &= related; + continue; + } + } + const related = isRelatedTo(sourceType, target, RecursionFlags.Source, reportErrors, /*headMessage*/ undefined, intersectionState); + if (!related) { + return Ternary.False; + } + result &= related; + } + 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; + } + 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); + } + } + if (!related) { + return Ternary.False; + } + result &= related; + } + } + 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, /*ignoreConstraints*/ 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 (relationCount <= 0) { + overflow = true; + return Ternary.False; + } + if (!maybeKeys) { + maybeKeys = []; + maybeKeysSet = new Set(); + sourceStack = []; + targetStack = []; + } + else { + // If source and target are already being compared, consider them related with assumptions + if (maybeKeysSet.has(id)) { + return Ternary.Maybe; + } + + // 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; + if (broadestEquivalentId && maybeKeysSet.has(broadestEquivalentId)) { + return Ternary.Maybe; + } + + if (sourceDepth === 100 || targetDepth === 100) { + overflow = true; + return Ternary.False; + } + } + const maybeStart = maybeCount; + maybeKeys[maybeCount] = id; + maybeKeysSet.add(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 = 0 as RelationComparisonResult; + 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. + resetMaybeStack(/*markAllAsSucceeded*/ true); + } + else { + resetMaybeStack(/*markAllAsSucceeded*/ false); + } + } + // Note: it's intentional that we don't reset in the else case; + // we leave them on the stack such that when we hit depth zero + // above, we can report all of them as successful. + } + 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); + relationCount--; + resetMaybeStack(/*markAllAsSucceeded*/ false); + } + return result; + + function resetMaybeStack(markAllAsSucceeded: boolean) { + for (let i = maybeStart; i < maybeCount; i++) { + maybeKeysSet.delete(maybeKeys[i]); + if (markAllAsSucceeded) { + relation.set(maybeKeys[i], RelationComparisonResult.Succeeded | propagatingVarianceFlags); + relationCount--; + } + } + maybeCount = maybeStart; + } + } + + 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); + } + } + // When the target is an intersection we need an extra property check in order to detect nested excess + // properties and nested weak types. 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 + // + if ( + result && !(intersectionState & IntersectionState.Target) && target.flags & TypeFlags.Intersection && + !isGenericObjectType(target) && source.flags & (TypeFlags.Object | TypeFlags.Intersection) + ) { + result &= propertiesRelatedTo(source, target, reportErrors, /*excludedProperties*/ undefined, /*optionalsOnly*/ false, IntersectionState.None); + if (result && isObjectLiteralType(source) && getObjectFlags(source) & ObjectFlags.FreshLiteral) { + result &= indexSignaturesRelatedTo(source, target, /*sourceIsPrimitive*/ false, reportErrors, IntersectionState.None); + } + } + // When the source is an intersection we need an extra check of any optional properties in the target to + // detect possible mismatched property types. For example: + // + // function foo(x: { a?: string }, y: T & { a: boolean }) { + // x = y; // Mismatched property in source intersection + // } + // + else if ( + result && isNonGenericObjectType(target) && !isArrayOrTupleType(target) && + source.flags & TypeFlags.Intersection && getApparentType(source).flags & TypeFlags.StructuredType && + !some((source as IntersectionType).types, t => t === target || !!(getObjectFlags(t) & ObjectFlags.NonInferrableType)) + ) { + result &= propertiesRelatedTo(source, target, reportErrors, /*excludedProperties*/ undefined, /*optionalsOnly*/ true, intersectionState); + } + } + if (result) { + resetErrorInfo(saveErrorInfo); + } + return result; + } + + function getApparentMappedTypeKeys(nameType: Type, targetType: MappedType) { + 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))), + ); + return getUnionType(mappedKeys); + } + + 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 (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; + } + } + + // 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 params = getSymbolLinks(source.aliasSymbol).typeParameters!; + const minParams = getMinTypeArgumentCount(params); + const sourceTypes = fillMissingTypeArguments(source.aliasTypeArguments, params, minParams, isInJSFile(source.aliasSymbol.valueDeclaration)); + const targetTypes = fillMissingTypeArguments(target.aliasTypeArguments, params, minParams, isInJSFile(source.aliasSymbol.valueDeclaration)); + const varianceResult = relateVariances(sourceTypes, targetTypes, 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; + } + + 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 (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) { + while (constraint && someType(constraint, c => !!(c.flags & TypeFlags.TypeParameter))) { + if (result = isRelatedTo(constraint, target, RecursionFlags.Source, /*reportErrors*/ false)) { + return result; + } + constraint = getConstraintOfTypeParameter(constraint); + } + } + 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; + } + } + 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; + } + } + 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).indexFlags | IndexFlags.NoReducibleCheck), 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 mappedKeys = getApparentMappedTypeKeys(nameType, targetType); + // 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; + } + } + } + } + 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)) { + 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, IndexFlags.NoIndexSignatures); + 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; + } + } + } + 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, is not distributive or is distributive but doesn't reference the check type + // parameter in either of the result types, and the source isn't an instantiation of the same + // conditional type (as happens when computing variance). + if (!c.root.inferTypeParameters && !isDistributionDependent(c.root) && !(source.flags & TypeFlags.Conditional && (source as ConditionalType).root === 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 (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; + } + } + } + } + } + else if (sourceFlags & TypeFlags.Index) { + const isDeferredMappedIndex = shouldDeferIndexType((source as IndexType).type, (source as IndexType).indexFlags) && getObjectFlags((source as IndexType).type) & ObjectFlags.Mapped; + if (result = isRelatedTo(stringNumberSymbolType, target, RecursionFlags.Source, reportErrors && !isDeferredMappedIndex)) { + return result; + } + if (isDeferredMappedIndex) { + const mappedType = (source as IndexType).type as MappedType; + const nameType = getNameTypeFromMappedType(mappedType); + // Unlike on the target side, on the source side we do *not* include the generic part of the `nameType`, since that comes from a + // (potentially anonymous) mapped type local type parameter, so that'd never assign outside the mapped type body, but we still want to + // allow assignments of index types of identical (or similar enough) mapped types. + // eg, `keyof {[X in keyof A]: Obj[X]}` should be assignable to `keyof {[Y in keyof A]: Tup[Y]}` because both map over the same set of keys (`keyof A`). + // Without this source-side breakdown, a `keyof {[X in keyof A]: Obj[X]}` style type won't be assignable to anything except itself, which is much too strict. + const sourceMappedKeys = nameType && isMappedTypeWithKeyofConstraintDeclaration(mappedType) ? getApparentMappedTypeKeys(nameType, mappedType) : (nameType || getConstraintTypeFromMappedType(mappedType)); + if (result = isRelatedTo(sourceMappedKeys, 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 { + 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) { + 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)) { + return result; + } + } + // 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 = !(targetFlags & TypeFlags.Conditional) && hasNonCircularBaseConstraint(source) ? getConstraintOfDistributiveConditionalType(source as ConditionalType) : undefined; + if (distributiveConstraint) { + resetErrorInfo(saveErrorInfo); + if (result = isRelatedTo(distributiveConstraint, 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; + } + 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; + } + // 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; + } + } + else if (isReadonlyArrayType(target) ? everyType(source, isArrayOrTupleType) : isArrayType(target) && everyType(source, t => isTupleType(t) && !t.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; + } + } + else if (isGenericTupleType(source) && isTupleType(target) && !isGenericTupleType(target)) { + const constraint = getBaseConstraintOrType(source); + if (constraint !== source) { + return isRelatedTo(constraint, target, RecursionFlags.Source, reportErrors); + } + } + // A fresh empty object type is never a subtype of a non-empty object type. This ensures fresh({}) <: { [x: string]: xxx } + // but not vice-versa. Without this rule, those types would be mutual subtypes. + 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, /*optionalsOnly*/ false, intersectionState); + if (result) { + result &= signaturesRelatedTo(source, target, SignatureKind.Call, reportStructuralErrors, intersectionState); + if (result) { + result &= signaturesRelatedTo(source, target, SignatureKind.Construct, reportStructuralErrors, intersectionState); + 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; + + 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); + } + } + } + + // 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; + } + + 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; + } + } + 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, /*optionalsOnly*/ false, IntersectionState.None); + if (result) { + result &= signaturesRelatedTo(source, type, SignatureKind.Call, /*reportErrors*/ false, IntersectionState.None); + if (result) { + result &= signaturesRelatedTo(source, type, SignatureKind.Construct, /*reportErrors*/ false, IntersectionState.None); + 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, /*reportErrors*/ false, IntersectionState.None); + } + } + } + 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]); + } + } + else if (!result) { + result = properties.slice(0, i); + } + } + return result || properties; + } + + 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)); + } + } + 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++; + } + } + // No array like or unmatched property error - just issue top level error (errorInfo = undefined) + } + + function propertiesRelatedTo(source: Type, target: Type, reportErrors: boolean, excludedProperties: Set<__String> | undefined, optionalsOnly: boolean, 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 targetHasRestElement = !!(target.target.combinedFlags & ElementFlags.Variable); + const sourceMinLength = isTupleType(source) ? source.target.minLength : 0; + const targetMinLength = target.target.minLength; + if (!sourceRestFlag && sourceArity < targetMinLength) { + if (reportErrors) { + reportError(Diagnostics.Source_has_0_element_s_but_target_requires_1, sourceArity, targetMinLength); + } + return Ternary.False; + } + if (!targetHasRestElement && targetArity < sourceMinLength) { + if (reportErrors) { + reportError(Diagnostics.Source_has_0_element_s_but_target_allows_only_1, sourceMinLength, targetArity); + } + return Ternary.False; + } + if (!targetHasRestElement && (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; + } + const sourceTypeArguments = getTypeArguments(source); + const targetTypeArguments = getTypeArguments(target); + const targetStartCount = getStartElementCount(target.target, ElementFlags.NonRest); + const targetEndCount = getEndElementCount(target.target, ElementFlags.NonRest); + 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; + } + 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; + } + } + + 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); + } + } + return Ternary.False; + } + result &= related; + } + return result; + } + if (target.target.combinedFlags & ElementFlags.Variable) { + 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") && (!optionalsOnly || targetProp.flags & SymbolFlags.Optional)) { + 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; + } + + 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 related = compareProperties(sourceProp, targetProp, isRelatedTo); + if (!related) { + return Ternary.False; + } + result &= related; + } + return result; + } + + function signaturesRelatedTo(source: Type, target: Type, kind: SignatureKind, reportErrors: boolean, intersectionState: IntersectionState): 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 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; + } + } + + 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. + Debug.assertEqual(sourceSignatures.length, targetSignatures.length); + for (let i = 0; i < targetSignatures.length; i++) { + const related = signatureRelatedTo(sourceSignatures[i], targetSignatures[i], /*erase*/ true, reportErrors, intersectionState, 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; + const sourceSignature = first(sourceSignatures); + const targetSignature = first(targetSignatures); + result = signatureRelatedTo(sourceSignature, targetSignature, eraseGenerics, reportErrors, intersectionState, 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, intersectionState, 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)); + } + return Ternary.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 + } + return false; + } + return true; + } + + 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)); + } + + 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, intersectionState: IntersectionState, incompatibleReporter: (source: Type, target: Type) => void): Ternary { + const checkMode = relation === subtypeRelation ? SignatureCheckMode.StrictTopSignature : + relation === strictSubtypeRelation ? SignatureCheckMode.StrictTopSignature | SignatureCheckMode.StrictArity : + SignatureCheckMode.None; + return compareSignaturesRelated(erase ? getErasedSignature(source) : source, erase ? getErasedSignature(target) : target, checkMode, reportErrors, reportError, incompatibleReporter, isRelatedToWorker, reportUnreliableMapper); + function isRelatedToWorker(source: Type, target: Type, reportErrors?: boolean) { + return isRelatedTo(source, target, RecursionFlags.Both, reportErrors, /*headMessage*/ undefined, intersectionState); + } + } + + 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; + } + result &= related; + } + return result; + } + + function membersRelatedToIndexInfo(source: Type, targetInfo: IndexInfo, reportErrors: boolean, intersectionState: IntersectionState): 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, /*headMessage*/ undefined, intersectionState); + if (!related) { + if (reportErrors) { + reportError(Diagnostics.Property_0_is_incompatible_with_index_signature, symbolToString(prop)); + } + return Ternary.False; + } + result &= related; + } + } + for (const info of getIndexInfosOfType(source)) { + if (isApplicableIndexType(info.keyType, keyType)) { + const related = indexInfoRelatedTo(info, targetInfo, reportErrors, intersectionState); + if (!related) { + return Ternary.False; + } + result &= related; + } + } + return result; + } + + function indexInfoRelatedTo(sourceInfo: IndexInfo, targetInfo: IndexInfo, reportErrors: boolean, intersectionState: IntersectionState) { + const related = isRelatedTo(sourceInfo.type, targetInfo.type, RecursionFlags.Both, reportErrors, /*headMessage*/ undefined, intersectionState); + 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 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 = relation !== strictSubtypeRelation && !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; + } + result &= related; + } + 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, intersectionState); + } + // Intersection constituents are never considered to have an inferred index signature. Also, in the strict subtype relation, + // only fresh object literals are considered to have inferred index signatures. This ensures { [x: string]: xxx } <: {} but + // not vice-versa. Without this rule, those types would be mutual strict subtypes. + if (!(intersectionState & IntersectionState.Source) && (relation !== strictSubtypeRelation || getObjectFlags(source) & ObjectFlags.FreshLiteral) && isObjectTypeWithInferableIndex(source)) { + return membersRelatedToIndexInfo(source, targetInfo, reportErrors, intersectionState); + } + if (reportErrors) { + reportError(Diagnostics.Index_signature_for_type_0_is_missing_in_type_1, typeToString(targetInfo.keyType), typeToString(source)); + } + return Ternary.False; + } + + 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; + } + + 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; + } + + // Only a public signature is assignable to public signature. + if (targetAccessibility !== ModifierFlags.Protected && !sourceAccessibility) { + return true; + } + + if (reportErrors) { + reportError(Diagnostics.Cannot_assign_a_0_constructor_type_to_a_1_constructor_type, visibilityToString(sourceAccessibility), visibilityToString(targetAccessibility)); + } + + return false; + } + } + + 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; + } + + if (type.flags & TypeFlags.UnionOrIntersection) { + return !!forEach((type as IntersectionType).types, typeCouldHaveTopLevelSingletonTypes); + } + + if (type.flags & TypeFlags.Instantiable) { + const constraint = getConstraintOfType(type); + if (constraint && constraint !== type) { + return typeCouldHaveTopLevelSingletonTypes(constraint); + } + } + + return isUnitType(type) || !!(type.flags & TypeFlags.TemplateLiteral) || !!(type.flags & TypeFlags.StringMapping); + } + + 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) || + findMatchingTypeReferenceOrTypeAliasReference(source, target) || + findBestTypeForObjectLiteral(source, target) || + findBestTypeForInvokable(source, target) || + findMostOverlappyType(source, target); + } + + function discriminateTypeByDiscriminableItems(target: UnionType, discriminators: (readonly [() => Type, __String])[], related: (source: Type, target: Type) => boolean | Ternary) { + const types = target.types; + const include: Ternary[] = types.map(t => t.flags & TypeFlags.Primitive ? Ternary.False : Ternary.True); + for (const [getDiscriminatingType, propertyName] of discriminators) { + // If the remaining target types include at least one with a matching discriminant, eliminate those that + // have non-matching discriminants. This ensures that we ignore erroneous discriminators and gradually + // refine the target set without eliminating every constituent (which would lead to `never`). + let matched = false; + for (let i = 0; i < types.length; i++) { + if (include[i]) { + const targetType = getTypeOfPropertyOrIndexSignatureOfType(types[i], propertyName); + if (targetType && related(getDiscriminatingType(), targetType)) { + matched = true; + } + else { + include[i] = Ternary.Maybe; + } + } + } + // Turn each Ternary.Maybe into Ternary.False if there was a match. Otherwise, revert to Ternary.True. + for (let i = 0; i < types.length; i++) { + if (include[i] === Ternary.Maybe) { + include[i] = matched ? Ternary.False : Ternary.True; + } + } + } + const filtered = contains(include, Ternary.False) ? getUnionType(types.filter((_, i) => include[i]), UnionReduction.None) : target; + return filtered.flags & TypeFlags.Never ? target : filtered; + } + + /** + * 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.Substitution) { + return isWeakType((type as SubstitutionType).baseType); + } + 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; + } + } + 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 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)) }); + const oldVarianceComputation = inVarianceComputation; + const saveResolutionStart = resolutionStart; + if (!inVarianceComputation) { + inVarianceComputation = true; + resolutionStart = resolutionTargets.length; + } + links.variances = emptyArray; + const variances = []; + for (const tp of typeParameters) { + const modifiers = getTypeParameterModifiers(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); + } + if (!oldVarianceComputation) { + inVarianceComputation = false; + resolutionStart = saveResolutionStart; + } + 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 isMarkerType(type: Type) { + return markerTypes.has(getTypeId(type)); + } + + function getTypeParameterModifiers(tp: TypeParameter): ModifierFlags { + return reduceLeft(tp.symbol?.declarations, (modifiers, d) => modifiers | getEffectiveModifierFlags(d), ModifierFlags.None) & (ModifierFlags.In | ModifierFlags.Out | ModifierFlags.Const); + } + + // 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; + } + + 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 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; + } + // 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) + ">"; + 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: Map, 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) { + // NOTE: cast to TransientSymbol should be safe because only TransientSymbols can have CheckFlags.Synthetic + for (const t of (prop as TransientSymbol).links.containingType!.types) { + const p = getPropertyOfType(t, prop.escapedName); + const result = p && forEachProperty(p, callback); + if (result) { + return result; + } + } + 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 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 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 the given stack contains + // maxDepth or more occurrences of types with the same recursion identity as the given type. The recursion identity + // provides a shared identity for type instantiations that repeat in some (possibly infinite) pattern. For example, + // in `type Deep = { next: Deep> }`, repeatedly referencing the `next` property leads to an infinite + // sequence of ever deeper instantiations with the same recursion identity (in this case the symbol associated with + // the object type literal). + // A homomorphic mapped type is considered deeply nested if its target type is deeply nested, and an intersection is + // considered deeply nested if any constituent of the intersection is deeply nested. + // It is possible, though highly unlikely, for the deeply nested check 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. + function isDeeplyNestedType(type: Type, stack: Type[], depth: number, maxDepth = 3): boolean { + if (depth >= maxDepth) { + if ((getObjectFlags(type) & ObjectFlags.InstantiatedMapped) === ObjectFlags.InstantiatedMapped) { + type = getMappedTargetWithSymbol(type); + } + if (type.flags & TypeFlags.Intersection) { + return some((type as IntersectionType).types, t => isDeeplyNestedType(t, stack, depth, maxDepth)); + } + const identity = getRecursionIdentity(type); + let count = 0; + let lastTypeId = 0; + for (let i = 0; i < depth; i++) { + const t = stack[i]; + if (hasMatchingRecursionIdentity(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; + } + } + } + return false; + } + + // Unwrap nested homomorphic mapped types and return the deepest target type that has a symbol. This better + // preserves unique type identities for mapped types applied to explicitly written object literals. For example + // in `Mapped<{ x: Mapped<{ x: Mapped<{ x: string }>}>}>`, each of the mapped type applications will have a + // unique recursion identity (that of their target object type literal) and thus avoid appearing deeply nested. + function getMappedTargetWithSymbol(type: Type) { + let target; + while ( + (getObjectFlags(type) & ObjectFlags.InstantiatedMapped) === ObjectFlags.InstantiatedMapped && + (target = getModifiersTypeFromMappedType(type as MappedType)) && + (target.symbol || target.flags & TypeFlags.Intersection && some((target as IntersectionType).types, t => !!t.symbol)) + ) { + type = target; + } + return type; + } + + function hasMatchingRecursionIdentity(type: Type, identity: object): boolean { + if ((getObjectFlags(type) & ObjectFlags.InstantiatedMapped) === ObjectFlags.InstantiatedMapped) { + type = getMappedTargetWithSymbol(type); + } + if (type.flags & TypeFlags.Intersection) { + return some((type as IntersectionType).types, t => hasMatchingRecursionIdentity(t, identity)); + } + return getRecursionIdentity(type) === identity; + } + + // 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 object types that have a symbol by that symbol (representing the origin of the type), but + // exclude the static side of a class since it shares its symbol with the instance side. + return type.symbol; + } + if (isTupleType(type)) { + return type.target; + } + } + if (type.flags & TypeFlags.TypeParameter) { + // We use the symbol of the type parameter such that all "fresh" instantiations of that type parameter + // have the same recursion identity. + return type.symbol; + } + if (type.flags & TypeFlags.IndexedAccess) { + // Identity is the leftmost object type in a chain of indexed accesses, eg, in A[P1][P2][P3] 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; + } + return type; + } + + 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; + } + } + else { + if ((sourceProp.flags & SymbolFlags.Optional) !== (targetProp.flags & SymbolFlags.Optional)) { + return Ternary.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; + } + } + 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)); + } + return result; + } + + 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; + } + + function getCombinedTypeFlags(types: Type[]): TypeFlags { + return reduceLeft(types, (flags, t) => flags | (t.flags & TypeFlags.Union ? getCombinedTypeFlags((t as UnionType).types) : t.flags), 0 as TypeFlags); + } + + 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)!; + } + + 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 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 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 isMutableArrayLikeType(type: Type): boolean { + // A type is mutable-array-like if it is a reference to the global Array type, or if it is not the + // any, undefined or null type and if it is assignable to Array + return isMutableArrayOrTuple(type) || !(type.flags & (TypeFlags.Any | TypeFlags.Nullable)) && isTypeAssignableTo(type, anyArrayType); + } + + 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) { + 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 { + let lengthType; + return isTupleType(type) || + !!getPropertyOfType(type, "0" as __String) || + isArrayLikeType(type) && !!(lengthType = getTypeOfPropertyOfType(type, "length" as __String)) && everyType(lengthType, t => !!(t.flags & TypeFlags.NumberLiteral)); + } + + function isArrayOrTupleLikeType(type: Type): boolean { + return isArrayLikeType(type) || isTupleLikeType(type); + } + + function getTupleElementType(type: Type, index: number) { + const propType = getTypeOfPropertyOfType(type, "" + index as __String); + if (propType) { + return propType; + } + if (everyType(type, isTupleType)) { + return getTupleElementTypeOutOfStartCount(type, index, compilerOptions.noUncheckedIndexedAccess ? undefinedType : undefined); + } + return undefined; + } + + function isNeitherUnitTypeNorNever(type: Type): boolean { + return !(type.flags & (TypeFlags.Unit | TypeFlags.Never)); + } + + function isUnitType(type: Type): boolean { + return !!(type.flags & TypeFlags.Unit); + } + + function isUnitLikeType(type: Type): boolean { + // Intersections that reduce to 'never' (e.g. 'T & null' where 'T extends {}') are not unit types. + const t = getBaseConstraintOrType(type); + // Scan intersections such that tagged literal types are considered unit types. + return t.flags & TypeFlags.Intersection ? some((t as IntersectionType).types, isUnitType) : isUnitType(t); + } + + function extractUnitType(type: Type) { + return type.flags & TypeFlags.Intersection ? find((type as IntersectionType).types, isUnitType) || type : 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 getBaseTypeOfLiteralType(type: Type): Type { + return type.flags & TypeFlags.EnumLike ? getBaseTypeOfEnumLikeType(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 getBaseTypeOfLiteralTypeUnion(type: UnionType) { + const key = `B${getTypeId(type)}`; + return getCachedType(key) ?? setCachedType(key, mapType(type, getBaseTypeOfLiteralType)); + } + + // This like getBaseTypeOfLiteralType, but instead treats enum literals as strings/numbers instead + // of returning their enum base type (which depends on the types of other literals in the enum). + function getBaseTypeOfLiteralTypeForComparison(type: Type): Type { + return type.flags & (TypeFlags.StringLiteral | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) ? stringType : + type.flags & (TypeFlags.NumberLiteral | TypeFlags.Enum) ? numberType : + type.flags & TypeFlags.BigIntLiteral ? bigintType : + type.flags & TypeFlags.BooleanLiteral ? booleanType : + type.flags & TypeFlags.Union ? mapType(type, getBaseTypeOfLiteralTypeForComparison) : + type; + } + + function getWidenedLiteralType(type: Type): Type { + return type.flags & TypeFlags.EnumLike && isFreshLiteralType(type) ? getBaseTypeOfEnumLikeType(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 getWidenedUniqueESSymbolType(type: Type): Type { + return type.flags & TypeFlags.UniqueESSymbol ? esSymbolType : + type.flags & TypeFlags.Union ? mapType(type as UnionType, getWidenedUniqueESSymbolType) : + type; + } + + function getWidenedLiteralLikeTypeForContextualType(type: Type, contextualType: Type | undefined) { + if (!isLiteralOfContextualType(type, contextualType)) { + type = getWidenedUniqueESSymbolType(getWidenedLiteralType(type)); + } + return getRegularTypeOfLiteralType(type); + } + + 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 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; + } + + /** + * 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 isGenericTupleType(type: Type): type is TupleTypeReference { + return isTupleType(type) && !!(type.target.combinedFlags & ElementFlags.Variadic); + } + + function isSingleElementGenericTupleType(type: Type): type is TupleTypeReference { + return isGenericTupleType(type) && type.target.elementFlags.length === 1; + } + + function getRestTypeOfTupleType(type: TupleTypeReference) { + return getElementTypeOfSliceOfTupleType(type, type.target.fixedLength); + } + + function getTupleElementTypeOutOfStartCount(type: Type, index: number, undefinedOrMissingType: Type | undefined) { + return mapType(type, t => { + const tupleType = t as TupleTypeReference; + const restType = getRestTypeOfTupleType(tupleType); + if (!restType) { + return undefinedType; + } + if (undefinedOrMissingType && index >= getTotalFixedElementCount(tupleType.target)) { + return getUnionType([restType, undefinedOrMissingType]); + } + return restType; + }); + } + + function getRestArrayTypeOfTupleType(type: TupleTypeReference) { + const restType = getRestTypeOfTupleType(type); + return restType && createArrayType(restType); + } + + function getElementTypeOfSliceOfTupleType(type: TupleTypeReference, index: number, endSkipCount = 0, writing = false, noReductions = 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, noReductions ? UnionReduction.None : UnionReduction.Literal); + } + return undefined; + } + + 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 isZeroBigInt({ value }: BigIntLiteralType) { + return value.base10Value === "0"; + } + + function removeDefinitelyFalsyTypes(type: Type): Type { + return filterType(type, t => hasTypeFacts(t, TypeFacts.Truthy)); + } + + function extractDefinitelyFalsyTypes(type: Type): Type { + return mapType(type, getDefinitelyFalsyPartOfType); + } + + 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; + } + + /** + * 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 getOptionalType(type: Type, isProperty = false): Type { + Debug.assert(strictNullChecks); + const missingOrUndefined = isProperty ? undefinedOrMissingType : undefinedType; + return type === missingOrUndefined || type.flags & TypeFlags.Union && (type as UnionType).types[0] === missingOrUndefined ? type : getUnionType([type, missingOrUndefined]); + } + + 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 getNonNullableType(type: Type): Type { + return strictNullChecks ? getAdjustedTypeWithFacts(type, TypeFacts.NEUndefinedOrNull) : type; + } + + function addOptionalTypeMarker(type: Type) { + return strictNullChecks ? getUnionType([type, optionalType]) : type; + } + + function removeOptionalTypeMarker(type: Type): Type { + return strictNullChecks ? removeType(type, optionalType) : type; + } + + function propagateOptionalTypeMarker(type: Type, node: OptionalChain, wasOptional: boolean) { + return wasOptional ? isOutermostOptionalChain(node) ? getOptionalType(type) : addOptionalTypeMarker(type) : type; + } + + function getOptionalExpressionType(exprType: Type, expression: Expression) { + return isExpressionOfOptionalChainRoot(expression) ? getNonNullableType(exprType) : + isOptionalChain(expression) ? removeOptionalTypeMarker(exprType) : + exprType; + } + + function removeMissingType(type: Type, isOptional: boolean) { + return exactOptionalPropertyTypes && isOptional ? removeType(type, missingType) : type; + } + + function containsMissingType(type: Type) { + return type === missingType || !!(type.flags & TypeFlags.Union) && (type as UnionType).types[0] === missingType; + } + + function removeMissingOrUndefinedType(type: Type): Type { + return exactOptionalPropertyTypes ? removeType(type, missingType) : getTypeWithFacts(type, TypeFacts.NEUndefined); + } + + /** + * 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); + } + + /** + * 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.links.type = type; + symbol.links.target = source; + if (source.valueDeclaration) { + symbol.valueDeclaration = source.valueDeclaration; + } + const nameType = getSymbolLinks(source).nameType; + if (nameType) { + symbol.links.nameType = nameType; + } + return symbol; + } + + 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; + } + + /** + * 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; + } + + 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); + }); + } + } + } + context.siblings = siblings; + } + return context.siblings; + } + + function getPropertiesOfContext(context: WideningContext): Symbol[] { + if (!context.resolvedProperties) { + const names = new Map<__String, Symbol>(); + 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; + } + + 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, undefinedOrMissingType); + 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)); + } + } + } + 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 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; + } + 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) { + errorReported ||= reportWideningErrorsInType(t); + } + } + } + else if (isArrayOrTupleType(type)) { + for (const t of getTypeArguments(type)) { + errorReported ||= reportWideningErrorsInType(t); + } + } + else if (isObjectLiteralType(type)) { + for (const p of getPropertiesOfObjectType(type)) { + const t = getTypeOfSymbol(p); + if (getObjectFlags(t) & ObjectFlags.ContainsWideningType) { + errorReported = reportWideningErrorsInType(t); + if (!errorReported) { + // we need to account for property types coming from object literal type normalization in unions + const valueDeclaration = p.declarations?.find(d => d.symbol.valueDeclaration?.parent === type.symbol.valueDeclaration); + if (valueDeclaration) { + error(valueDeclaration, Diagnostics.Object_literal_s_property_0_implicitly_has_an_1_type, symbolToString(p), typeToString(getWidenedType(t))); + errorReported = true; + } + } + } + } + } + } + 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)) { + const originalKeywordKind = identifierToKeywordKind(param.name); + if ( + (isCallSignatureDeclaration(param.parent) || isMethodSignature(param.parent) || isFunctionTypeNode(param.parent)) && + param.parent.parameters.includes(param) && + (resolveName(param, param.name.escapedText, SymbolFlags.Type, /*nameNotFoundMessage*/ undefined, /*isUse*/ true) || + originalKeywordKind && isTypeNodeKind(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); + return; + case SyntaxKind.JSDocSignature: + if (noImplicitAny && isJSDocOverloadTag(declaration.parent)) { + error(declaration.parent.tagName, Diagnostics.This_overload_implicitly_returns_the_type_0_because_it_lacks_a_return_type_annotation, 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); + } + else { + error(declaration, Diagnostics.Function_expression_which_lacks_return_type_annotation_implicitly_has_an_0_return_type, typeAsString); + } + return; + } + 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 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); + } + } + }); + } + + 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, /*readonly*/ isConstTypeVariable(targetRestType) && !someType(targetRestType, isMutableArrayLikeType)), targetRestType); + } + } + + function applyToReturnTypes(source: Signature, target: Signature, callback: (s: Type, t: Type) => void) { + const targetTypePredicate = getTypePredicateOfSignature(target); + if (targetTypePredicate) { + const sourceTypePredicate = getTypePredicateOfSignature(source); + if (sourceTypePredicate && typePredicateKindsMatch(sourceTypePredicate, targetTypePredicate) && sourceTypePredicate.type && targetTypePredicate.type) { + callback(sourceTypePredicate.type, targetTypePredicate.type); + return; + } + } + const targetReturnType = getReturnTypeOfSignature(target); + if (couldContainTypeVariables(targetReturnType)) { + callback(getReturnTypeOfSignature(source), targetReturnType); + } + } + + 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 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 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 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); + } + } + 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 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 getMapperFromContext(context: T): TypeMapper | T & undefined { + return context && context.mapper; + } + + // 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 || some(getTypeArguments(type as TypeReference), couldContainTypeVariables)) || + objectFlags & ObjectFlags.SingleSignatureType && !!length((type as SingleSignatureType).outerTypeParameters) || + 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 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; + } + + function isTypeParameterAtTopLevel(type: Type, tp: TypeParameter, depth = 0): boolean { + return !!(type === tp || + type.flags & TypeFlags.UnionOrIntersection && some((type as UnionOrIntersectionType).types, t => isTypeParameterAtTopLevel(t, tp, depth)) || + depth < 3 && type.flags & TypeFlags.Conditional && ( + isTypeParameterAtTopLevel(getTrueTypeFromConditionalType(type as ConditionalType), tp, depth + 1) || + isTypeParameterAtTopLevel(getFalseTypeFromConditionalType(type as ConditionalType), tp, depth + 1) + )); + } + + function isTypeParameterAtTopLevelInReturnType(signature: Signature, typeParameter: TypeParameter) { + const typePredicate = getTypePredicateOfSignature(signature); + return typePredicate ? !!typePredicate.type && isTypeParameterAtTopLevel(typePredicate.type, typeParameter) : + isTypeParameterAtTopLevel(getReturnTypeOfSignature(signature), typeParameter); + } + + /** 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.links.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(/*symbol*/ undefined, members, emptyArray, emptyArray, indexInfos); + } + + /** + * 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 { + const cacheKey = source.id + "," + target.id + "," + constraint.id; + if (reverseMappedCache.has(cacheKey)) { + return reverseMappedCache.get(cacheKey); + } + const type = createReverseMappedType(source, target, constraint); + reverseMappedCache.set(cacheKey, type); + return type; + } + + // We consider a type to be partially inferable if it isn't marked non-inferable or if it is + // an object literal type with at least one property of an inferable type. For example, an object + // literal { a: 123, b: x => true } is marked non-inferable because it contains a context sensitive + // arrow function, but is considered partially inferable because property 'a' has an inferable type. + function isPartiallyInferableType(type: Type): boolean { + return !(getObjectFlags(type) & ObjectFlags.NonInferrableType) || + isObjectLiteralType(type) && some(getPropertiesOfType(type), prop => isPartiallyInferableType(getTypeOfSymbol(prop))) || + isTupleType(type) && some(getElementTypes(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)) { + const elementType = inferReverseMappedType(getTypeArguments(source)[0], target, constraint); + if (!elementType) { + return undefined; + } + return createArrayType(elementType, isReadonlyArrayType(source)); + } + if (isTupleType(source)) { + const elementTypes = map(getElementTypes(source), t => inferReverseMappedType(t, target, constraint)); + if (!every(elementTypes, (t): t is Type => !!t)) { + return undefined; + } + 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): Type { + const links = getSymbolLinks(symbol); + if (!links.type) { + links.type = inferReverseMappedType(symbol.links.propertyType, symbol.links.mappedType, symbol.links.constraintType) || unknownType; + } + return links.type; + } + + function inferReverseMappedTypeWorker(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 inferReverseMappedType(source: Type, target: MappedType, constraint: IndexType): Type | undefined { + const cacheKey = source.id + "," + target.id + "," + constraint.id; + if (reverseMappedCache.has(cacheKey)) { + return reverseMappedCache.get(cacheKey) || unknownType; + } + reverseMappedSourceStack.push(source); + reverseMappedTargetStack.push(target); + const saveExpandingFlags = reverseExpandingFlags; + if (isDeeplyNestedType(source, reverseMappedSourceStack, reverseMappedSourceStack.length, 2)) reverseExpandingFlags |= ExpandingFlags.Source; + if (isDeeplyNestedType(target, reverseMappedTargetStack, reverseMappedTargetStack.length, 2)) reverseExpandingFlags |= ExpandingFlags.Target; + let type; + if (reverseExpandingFlags !== ExpandingFlags.Both) { + type = inferReverseMappedTypeWorker(source, target, constraint); + } + reverseMappedSourceStack.pop(); + reverseMappedTargetStack.pop(); + reverseExpandingFlags = saveExpandingFlags; + reverseMappedCache.set(cacheKey, type); + return 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; + } + 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; + } + } + } + } + } + } + + function getUnmatchedProperty(source: Type, target: Type, requireOptionalProperties: boolean, matchDiscriminantProperties: boolean): Symbol | undefined { + return firstOrUndefinedIterator(getUnmatchedProperties(source, target, requireOptionalProperties, matchDiscriminantProperties)); + } + + function tupleTypesDefinitelyUnrelated(source: TupleTypeReference, target: TupleTypeReference) { + return !(target.target.combinedFlags & ElementFlags.Variadic) && target.target.minLength > source.target.minLength || + !(target.target.combinedFlags & ElementFlags.Variable) && (!!(source.target.combinedFlags & ElementFlags.Variable) || 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 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 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); + } + + /** + * 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) { + return getBigIntLiteralType(parseValidBigInt(text)); + } + + function isMemberOfStringMapping(source: Type, target: Type): boolean { + if (target.flags & TypeFlags.Any) { + return true; + } + if (target.flags & (TypeFlags.String | 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; + } + + function isValidTypeForTemplateLiteralPlaceholder(source: Type, target: Type): boolean { + if (target.flags & TypeFlags.Intersection) { + return every((target as IntersectionType).types, t => t === emptyTypeLiteralType || isValidTypeForTemplateLiteralPlaceholder(source, t)); + } + if (target.flags & TypeFlags.String || isTypeAssignableTo(source, target)) { + 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) || + target.flags & TypeFlags.TemplateLiteral && isTypeMatchedByTemplateLiteralType(source, target as TemplateLiteralType)); + } + 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 false; + } + + 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, (s, i) => { + return isTypeAssignableTo(getBaseConstraintOrType(s), getBaseConstraintOrType(target.types[i])) ? s : getStringLikeTypeForType(s); + }) : + 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; + } + 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; + } + } + 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; + } + } + + /** + * @returns `true` if `type` has the shape `[T[0]]` where `T` is `typeParameter` + */ + function isTupleOfSelf(typeParameter: TypeParameter, type: Type) { + return isTupleType(type) && getTupleElementType(type, 0) === getIndexedAccessType(typeParameter, getNumberLiteralType(0)) && !getTypeOfPropertyOfType(type, "1" as __String); + } + + function inferTypes(inferences: InferenceInfo[], originalSource: Type, originalTarget: Type, priority = InferencePriority.None, contravariant = false) { + let bivariant = false; + let propagationType: Type; + let inferencePriority: number = InferencePriority.MaxValue; + let visited: Map; + let sourceStack: Type[]; + let targetStack: Type[]; + let expandingFlags = ExpandingFlags.None; + inferFromTypes(originalSource, originalTarget); + + function inferFromTypes(source: Type, target: Type): void { + if (!couldContainTypeVariables(target) || isNoInferType(target)) { + return; + } + if (source === wildcardType || source === blockedStringType) { + // 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, with defaults applied. + const params = getSymbolLinks(source.aliasSymbol).typeParameters!; + const minParams = getMinTypeArgumentCount(params); + const sourceTypes = fillMissingTypeArguments(source.aliasTypeArguments, params, minParams, isInJSFile(source.aliasSymbol.valueDeclaration)); + const targetTypes = fillMissingTypeArguments(target.aliasTypeArguments, params, minParams, isInJSFile(source.aliasSymbol.valueDeclaration)); + inferFromTypeArguments(sourceTypes, targetTypes!, 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 (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); + } + 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); + 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); + } + } + if (target.flags & (TypeFlags.IndexedAccess | TypeFlags.Substitution)) { + if (isNoInferType(target)) { + return; + } + 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; + } + 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) { + const candidate = propagationType || source; + if (candidate === blockedStringType) { + return; + } + if (inference.priority === undefined || priority < inference.priority) { + inference.candidates = undefined; + inference.contraCandidates = undefined; + inference.topLevel = true; + inference.priority = priority; + } + if (priority === inference.priority) { + // Inferring A to [A[0]] is a zero information inference (it guarantees A becomes its constraint), but oft arises from generic argument list inferences + // By discarding it early, we can allow more fruitful results to be used instead. + if (isTupleOfSelf(inference.typeParameter, candidate)) { + return; + } + // 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); + clearCachedInferences(inferences); + } + } + 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; + } + // 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 ( + 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 (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 as ConditionalType, 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 (target.flags & TypeFlags.TemplateLiteral) { + inferToTemplateLiteralType(source, target as TemplateLiteralType); + } + else { + source = getReducedType(source); + if (isGenericMappedType(source) && isGenericMappedType(target)) { + invokeOnce(source, target, inferFromGenericMappedTypes); + } + 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 && !(apparentSource.flags & (TypeFlags.Object | TypeFlags.Intersection))) { + 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 inferToMultipleTypesWithPriority(source: Type, targets: Type[], targetFlags: TypeFlags, newPriority: InferencePriority) { + const savePriority = priority; + priority |= newPriority; + inferToMultipleTypes(source, targets, targetFlags); + priority = savePriority; + } + + // Ensure an inference action is performed only once for the given source and target types. + // This includes two things: + // Avoiding inferring between the same pair of source and target types, + // and avoiding circularly inferring between source and target types. + // For an example of the last, consider if we are inferring between source type + // `type Deep = { next: Deep> }` and target type `type Loop = { next: Loop }`. + // We would then infer between the types of the `next` property: `Deep>` = `{ next: Deep>> }` and `Loop` = `{ next: Loop }`. + // We will then infer again between the types of the `next` property: + // `Deep>>` and `Loop`, and so on, such that we would be forever inferring + // between instantiations of the same types `Deep` and `Loop`. + // In particular, we would be inferring from increasingly deep instantiations of `Deep` to `Loop`, + // such that we would go on inferring forever, even though we would never infer + // between the same pair of types. + function invokeOnce(source: Source, target: Target, action: (source: Source, target: Target) => 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; + (sourceStack ??= []).push(source); + (targetStack ??= []).push(target); + if (isDeeplyNestedType(source, sourceStack, sourceStack.length, 2)) expandingFlags |= ExpandingFlags.Source; + if (isDeeplyNestedType(target, targetStack, targetStack.length, 2)) expandingFlags |= ExpandingFlags.Target; + if (expandingFlags !== ExpandingFlags.Both) { + action(source, target); + } + else { + inferencePriority = InferencePriority.Circularity; + } + targetStack.pop(); + sourceStack.pop(); + expandingFlags = saveExpandingFlags; + visited.set(key, inferencePriority); + inferencePriority = Math.min(inferencePriority, saveInferencePriority); + } + + 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 inferFromTypeArguments(sourceTypes: readonly Type[], targetTypes: readonly Type[], variances: readonly VarianceFlags[]) { + const count = sourceTypes.length < targetTypes.length ? sourceTypes.length : targetTypes.length; + for (let i = 0; i < count; i++) { + if (i < variances.length && (variances[i] & VarianceFlags.VarianceMask) === VarianceFlags.Contravariant) { + inferFromContravariantTypes(sourceTypes[i], targetTypes[i]); + } + else { + inferFromTypes(sourceTypes[i], targetTypes[i]); + } + } + } + + function inferFromContravariantTypes(source: Type, target: Type) { + 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; + } + + 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; + } + 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; + } + // 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 { + // 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); + } + } + } + // 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); + } + } + } + } + + function inferToMappedType(source: Type, target: MappedType, constraintType: Type): boolean { + if ((constraintType.flags & TypeFlags.Union) || (constraintType.flags & TypeFlags.Intersection)) { + let result = false; + for (const type of (constraintType as (UnionType | IntersectionType)).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, + ); + } + } + 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, /*indexFlags*/ !!source.pattern ? IndexFlags.NoIndexSignatures : IndexFlags.None), 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; + } + // 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; + } + + // 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); + } + } + } + + function inferFromGenericMappedTypes(source: MappedType, target: MappedType) { + // 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); + } + + 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)) { + inferFromGenericMappedTypes(source, target); + } + 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.combinedFlags & ElementFlags.Variable ? 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) { + if (elementFlags[startLength] & elementFlags[startLength + 1] & ElementFlags.Variadic) { + // 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 (elementFlags[startLength] & ElementFlags.Variadic && elementFlags[startLength + 1] & ElementFlags.Rest) { + // Middle of target is [...T, ...rest] and source is tuple type + // if T is constrained by a fixed-size tuple we might be able to use its arity to infer T + const param = getInferenceInfoForType(elementTypes[startLength])?.typeParameter; + const constraint = param && getBaseConstraintOfType(param); + if (constraint && isTupleType(constraint) && !(constraint.target.combinedFlags & ElementFlags.Variable)) { + const impliedArity = constraint.target.fixedLength; + inferFromTypes(sliceTupleType(source, startLength, sourceArity - (startLength + impliedArity)), elementTypes[startLength]); + inferFromTypes(getElementTypeOfSliceOfTupleType(source, startLength + impliedArity, endLength)!, elementTypes[startLength + 1]); + } + } + else if (elementFlags[startLength] & ElementFlags.Rest && elementFlags[startLength + 1] & ElementFlags.Variadic) { + // Middle of target is [...rest, ...T] and source is tuple type + // if T is constrained by a fixed-size tuple we might be able to use its arity to infer T + const param = getInferenceInfoForType(elementTypes[startLength + 1])?.typeParameter; + const constraint = param && getBaseConstraintOfType(param); + if (constraint && isTupleType(constraint) && !(constraint.target.combinedFlags & ElementFlags.Variable)) { + const impliedArity = constraint.target.fixedLength; + const endIndex = sourceArity - getEndElementCount(target.target, ElementFlags.Fixed); + const startIndex = endIndex - impliedArity; + const trailingSlice = createTupleType(getTypeArguments(source).slice(startIndex, endIndex), source.target.elementFlags.slice(startIndex, endIndex), /*readonly*/ false, source.target.labeledElementDeclarations && source.target.labeledElementDeclarations.slice(startIndex, endIndex)); + + inferFromTypes(getElementTypeOfSliceOfTupleType(source, startLength, endLength + impliedArity)!, elementTypes[startLength]); + inferFromTypes(trailingSlice, 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 = sliceTupleType(source, startLength, endLength); + 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 = getElementTypeOfSliceOfTupleType(source, startLength, endLength); + if (restType) { + inferFromTypes(restType, elementTypes[startLength]); + } + } + } + // Infer between ending fixed elements + for (let i = 0; i < endLength; i++) { + inferFromTypes(getTypeArguments(source)[sourceArity - i - 1], elementTypes[targetArity - i - 1]); + } + return; + } + if (isArrayType(target)) { + inferFromIndexTypes(source, target); + 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( + removeMissingType(getTypeOfSymbol(sourceProp), !!(sourceProp.flags & SymbolFlags.Optional)), + removeMissingType(getTypeOfSymbol(targetProp), !!(targetProp.flags & SymbolFlags.Optional)), + ); + } + } + } + + function inferFromSignatures(source: Type, target: Type, kind: SignatureKind) { + const sourceSignatures = getSignaturesOfType(source, kind); + const sourceLen = sourceSignatures.length; + if (sourceLen > 0) { + // We match source and target signatures from the bottom up, and if the source has fewer signatures + // than the target, we infer from the first source signature to the excess target signatures. + const targetSignatures = getSignaturesOfType(target, kind); + const targetLen = targetSignatures.length; + for (let i = 0; i < targetLen; i++) { + const sourceIndex = Math.max(sourceLen - targetLen + i, 0); + inferFromSignature(getBaseSignature(sourceSignatures[sourceIndex]), getErasedSignature(targetSignatures[i])); + } + } + } + + function inferFromSignature(source: Signature, target: Signature) { + if (!(source.flags & SignatureFlags.IsNonInferrable)) { + 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); + } + } + for (const info of getIndexInfosOfType(source)) { + if (isApplicableIndexType(info.keyType, targetInfo.keyType)) { + propTypes.push(info.type); + } + } + 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 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 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 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]); + } + } + return candidates; + } + + 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) || isConstTypeVariable(inference.typeParameter); + const widenLiteralTypes = !primitiveConstraint && inference.topLevel && + (inference.isFixed || !isTypeParameterAtTopLevelInReturnType(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; + let fallbackType: Type | undefined; + if (context.signature) { + const inferredCovariantType = inference.candidates ? getCovariantInference(inference, context.signature) : undefined; + const inferredContravariantType = inference.contraCandidates ? getContravariantInference(inference) : undefined; + if (inferredCovariantType || inferredContravariantType) { + // If we have both co- and contra-variant inferences, we prefer the co-variant inference if it is not 'never', + // all co-variant inferences are assignable to it (i.e. it isn't one of a conflicting set of candidates), it is + // assignable to some contra-variant inference, and no other type parameter is constrained to this type parameter + // and has inferences that would conflict. Otherwise, we prefer the contra-variant inference. + // Similarly ignore co-variant `any` inference when both are available as almost everything is assignable to it + // and it would spoil the overall inference. + const preferCovariantType = inferredCovariantType && (!inferredContravariantType || + !(inferredCovariantType.flags & (TypeFlags.Never | TypeFlags.Any)) && + some(inference.contraCandidates, t => isTypeAssignableTo(inferredCovariantType, t)) && + every(context.inferences, other => + other !== inference && getConstraintOfTypeParameter(other.typeParameter) !== inference.typeParameter || + every(other.candidates, t => isTypeAssignableTo(t, inferredCovariantType)))); + inferredType = preferCovariantType ? inferredCovariantType : inferredContravariantType; + fallbackType = preferCovariantType ? inferredContravariantType : 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)); + } + } + } + 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))) { + // If the fallback type satisfies the constraint, we pick it. Otherwise, we pick the constraint. + inference.inferredType = fallbackType && context.compareTypes(fallbackType, getTypeWithThisArgument(instantiatedConstraint, fallbackType)) ? fallbackType : instantiatedConstraint; + } + } + } + + 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 "Bun": + return compilerOptions.types + ? Diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_Bun_Try_npm_i_save_dev_types_Slashbun_and_then_add_bun_to_the_types_field_in_your_tsconfig + : Diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_Bun_Try_npm_i_save_dev_types_Slashbun; + 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, + SymbolFlags.Value | SymbolFlags.ExportValue, + getCannotFindNameDiagnosticForName(node), + !isWriteOnlyAccess(node), + /*excludeGlobals*/ false, + ) || unknownSymbol; + } + return links.resolvedSymbol; + } + + function isInAmbientOrTypeNode(node: Node): boolean { + return !!(node.flags & NodeFlags.Ambient || findAncestor(node, n => isInterfaceDeclaration(n) || isTypeAliasDeclaration(n) || isTypeLiteralNode(n))); + } + + // 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}`; + } + if (isElementAccessExpression(node) && isIdentifier(node.argumentExpression)) { + const symbol = getResolvedSymbol(node.argumentExpression); + if (isConstantVariable(symbol) || isParameterOrMutableLocalVariable(symbol) && !isSymbolAssigned(symbol)) { + const key = getFlowCacheKey((node as AccessExpression).expression, declaredType, initialType, flowContainer); + return key && `${key}.@${getSymbolId(symbol)}`; + } + } + 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 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) || + (isVariableDeclaration(target) || isBindingElement(target)) && + getExportSymbolOfValueSymbolIfExported(getResolvedSymbol(source as Identifier)) === getSymbolOfDeclaration(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); + if (sourcePropertyName !== undefined) { + const targetPropertyName = isAccessExpression(target) ? getAccessedPropertyName(target) : undefined; + if (targetPropertyName !== undefined) { + return targetPropertyName === sourcePropertyName && isMatchingReference((source as AccessExpression).expression, (target as AccessExpression).expression); + } + } + if (isElementAccessExpression(source) && isElementAccessExpression(target) && isIdentifier(source.argumentExpression) && isIdentifier(target.argumentExpression)) { + const symbol = getResolvedSymbol(source.argumentExpression); + if (symbol === getResolvedSymbol(target.argumentExpression) && (isConstantVariable(symbol) || isParameterOrMutableLocalVariable(symbol) && !isSymbolAssigned(symbol))) { + return isMatchingReference(source.expression, target.expression); + } + } + break; + 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; + } + + 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 tryGetNameFromType(type: Type) { + return type.flags & TypeFlags.UniqueESSymbol ? (type as UniqueESSymbolType).escapedName : + type.flags & TypeFlags.StringOrNumberLiteral ? escapeLeadingUnderscores("" + (type as StringLiteralType | NumberLiteralType).value) : undefined; + } + + function tryGetElementAccessExpressionName(node: ElementAccessExpression) { + return isStringOrNumericLiteralLike(node.argumentExpression) ? escapeLeadingUnderscores(node.argumentExpression.text) : + isEntityNameExpression(node.argumentExpression) ? tryGetNameFromEntityNameExpression(node.argumentExpression) : undefined; + } + + function tryGetNameFromEntityNameExpression(node: EntityNameOrEntityNameExpression) { + const symbol = resolveEntityName(node, SymbolFlags.Value, /*ignoreErrors*/ true); + if (!symbol || !(isConstantVariable(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; + } + } + if (hasOnlyExpressionInitializer(declaration) && isBlockScopedNameDeclaredBeforeUse(declaration, node)) { + const initializer = getEffectiveInitializer(declaration); + if (initializer) { + const initializerType = isBindingPattern(declaration.parent) ? getTypeForBindingElement(declaration as BindingElement) : getTypeOfExpression(initializer); + return initializerType && tryGetNameFromType(initializerType); + } + if (isEnumMember(declaration)) { + return getTextOfPropertyName(declaration.name); + } + } + return undefined; + } + + function containsMatchingReference(source: Node, target: Node) { + while (isAccessExpression(source)) { + source = source.expression; + if (isMatchingReference(source, target)) { + return true; + } + } + return false; + } + + function optionalChainContainsReference(source: Node, target: Node) { + while (isOptionalChain(source)) { + source = source.expression; + if (isMatchingReference(source, 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) { + // NOTE: cast to TransientSymbol should be safe because only TransientSymbols can have CheckFlags.SyntheticProperty + if ((prop as TransientSymbol).links.isDiscriminantProperty === undefined) { + (prop as TransientSymbol).links.isDiscriminantProperty = ((prop as TransientSymbol).links.checkFlags & CheckFlags.Discriminant) === CheckFlags.Discriminant && + !isGenericType(getTypeOfSymbol(prop)); + } + return !!(prop as TransientSymbol).links.isDiscriminantProperty; + } + } + return false; + } + + 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; + } + + // 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; + } + + // 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; + } + + // 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 hasMatchingArgument(expression: CallExpression | NewExpression, reference: Node) { + if (expression.arguments) { + for (const argument of expression.arguments) { + if (isOrContainsMatchingReference(reference, argument) || optionalChainContainsReference(argument, reference)) { + return true; + } + } + } + if ( + expression.expression.kind === SyntaxKind.PropertyAccessExpression && + isOrContainsMatchingReference(reference, (expression.expression as PropertyAccessExpression).expression) + ) { + return true; + } + return false; + } + + function getFlowNodeId(flow: FlowNode): number { + if (flow.id <= 0) { + flow.id = nextFlowId; + nextFlowId++; + } + return flow.id; + } + + 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 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; + } + const key = `A${getTypeId(declaredType)},${getTypeId(assignedType)}`; + return getCachedType(key) ?? setCachedType(key, getAssignmentReducedTypeWorker(declaredType, assignedType)); + } + + 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 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 getTypeFacts(type: Type, mask: TypeFacts): TypeFacts { + return getTypeFactsWorker(type, mask) & mask; + } + + function hasTypeFacts(type: Type, mask: TypeFacts): boolean { + return getTypeFacts(type, mask) !== 0; + } + + function getTypeFactsWorker(type: Type, callerOnlyNeeds: TypeFacts): 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) { + const possibleFacts = strictNullChecks + ? TypeFacts.EmptyObjectStrictFacts | TypeFacts.FunctionStrictFacts | TypeFacts.ObjectStrictFacts + : TypeFacts.EmptyObjectFacts | TypeFacts.FunctionFacts | TypeFacts.ObjectFacts; + + if ((callerOnlyNeeds & possibleFacts) === 0) { + // If the caller doesn't care about any of the facts that we could possibly produce, + // return zero so we can skip resolving members. + return 0; + } + + 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 | getTypeFactsWorker(t, callerOnlyNeeds), TypeFacts.None); + } + if (flags & TypeFlags.Intersection) { + return getIntersectionTypeFacts(type as IntersectionType, callerOnlyNeeds); + } + return TypeFacts.UnknownFacts; + } + + function getIntersectionTypeFacts(type: IntersectionType, callerOnlyNeeds: TypeFacts): 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 = getTypeFactsWorker(t, callerOnlyNeeds); + oredFacts |= f; + andedFacts &= f; + } + } + return oredFacts & TypeFacts.OrFactsMask | andedFacts & TypeFacts.AndFactsMask; + } + + function getTypeWithFacts(type: Type, include: TypeFacts) { + return filterType(type, t => hasTypeFacts(t, include)); + } + + // 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 removeNullableByIntersection(reduced, TypeFacts.EQUndefined, TypeFacts.EQNull, TypeFacts.IsNull, nullType); + case TypeFacts.NENull: + return removeNullableByIntersection(reduced, TypeFacts.EQNull, TypeFacts.EQUndefined, TypeFacts.IsUndefined, undefinedType); + case TypeFacts.NEUndefinedOrNull: + case TypeFacts.Truthy: + return mapType(reduced, t => hasTypeFacts(t, TypeFacts.EQUndefinedOrNull) ? getGlobalNonNullableTypeInstantiation(t) : t); + } + } + return reduced; + } + + function removeNullableByIntersection(type: Type, targetFacts: TypeFacts, otherFacts: TypeFacts, otherIncludesFacts: TypeFacts, otherType: Type) { + const facts = getTypeFacts(type, TypeFacts.EQUndefined | TypeFacts.EQNull | TypeFacts.IsUndefined | TypeFacts.IsNull); + // Simply return the type if it never compares equal to the target nullable. + if (!(facts & targetFacts)) { + return type; + } + // By default we intersect with a union of {} and the opposite nullable. + const emptyAndOtherUnion = getUnionType([emptyObjectType, otherType]); + // For each constituent type that can compare equal to the target nullable, intersect with the above union + // if the type doesn't already include the opppsite nullable and the constituent can compare equal to the + // opposite nullable; otherwise, just intersect with {}. + return mapType(type, t => hasTypeFacts(t, targetFacts) ? getIntersectionType([t, !(facts & otherIncludesFacts) && hasTypeFacts(t, otherFacts) ? emptyAndOtherUnion : emptyObjectType]) : t); + } + + 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, missingType]) : + 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; + 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 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 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 getInitialTypeOfVariableDeclaration(node: VariableDeclaration) { + if (node.initializer) { + return getTypeOfInitializer(node.initializer); + } + if (node.parent.parent.kind === SyntaxKind.ForInStatement) { + return stringType; + } + if (node.parent.parent.kind === SyntaxKind.ForOfStatement) { + return checkRightHandSideOfForOf(node.parent.parent) || errorType; + } + return errorType; + } + + function getInitialType(node: VariableDeclaration | BindingElement) { + return node.kind === SyntaxKind.VariableDeclaration ? + getInitialTypeOfVariableDeclaration(node) : + getInitialTypeOfBindingElement(node); + } + + 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 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 getTypeOfSwitchClause(clause: CaseClause | DefaultClause) { + if (clause.kind === SyntaxKind.CaseClause) { + return getRegularTypeOfLiteralType(getTypeOfExpression(clause.expression)); + } + return neverType; + } + + 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; + } + + // 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 eachTypeContainedIn(source: Type, types: Type[]) { + return source.flags & TypeFlags.Union ? !forEach((source as UnionType).types, t => !contains(types, t)) : contains(types, source); + } + + function isTypeSubsetOf(source: Type, target: Type) { + return !!(source === target || source.flags & TypeFlags.Never || target.flags & TypeFlags.Union && isTypeSubsetOfUnion(source, target as UnionType)); + } + + 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.EnumLike && getBaseTypeOfEnumLikeType(source as LiteralType) === target) { + return true; + } + return containsType(target.types, source); + } + + function forEachType(type: Type, f: (t: Type) => T | undefined): T | undefined { + return type.flags & TypeFlags.Union ? forEach((type as UnionType).types, f) : f(type); + } + + 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; + } + 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); + } + } + // filtering could remove intersections so `ContainsIntersections` might be forwarded "incorrectly" + // it is purely an optimization hint so there is no harm in accidentally forwarding it + return getUnionTypeFromSortedList(filtered, (type as UnionType).objectFlags & (ObjectFlags.PrimitiveUnion | ObjectFlags.ContainsIntersections), /*aliasSymbol*/ undefined, /*aliasTypeArguments*/ undefined, newOrigin); + } + return type.flags & TypeFlags.Never || f(type) ? type : neverType; + } + + 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; + } + + // 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; + } + + function isIncomplete(flowType: FlowType) { + return flowType.flags === 0; + } + + function getTypeFromFlowType(flowType: FlowType) { + return flowType.flags === 0 ? flowType.type : flowType as 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; + } + + 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])); + } + + 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)); + } + + 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 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 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 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).links.syntheticOrigin; + if (origin && getExplicitTypeOfSymbol(origin)) { + 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))); + } + } + } + } + + // 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); + } + return prop && getExplicitTypeOfSymbol(prop, diagnostic); + } + return undefined; + } + case SyntaxKind.ParenthesizedExpression: + return getTypeOfDottedName((node as ParenthesizedExpression).expression, diagnostic); + } + } + } + + function getEffectsSignature(node: CallExpression | InstanceofExpression) { + 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 (isBinaryExpression(node)) { + const rightType = checkNonNullExpression(node.right); + funcType = getSymbolHasInstanceMethodOfObjectType(rightType); + } + else 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; + } + + 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 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 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; + } + 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) { + 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).antecedent, f => isReachableFlowNodeWorker(f, /*noCacheCheck*/ false)); + } + else if (flags & FlowFlags.LoopLabel) { + const antecedents = (flow as FlowLabel).antecedent; + if (antecedents === undefined || antecedents.length === 0) { + 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. + const data = (flow as FlowSwitchClause).node; + if (data.clauseStart === data.clauseEnd && isExhaustiveSwitchStatement(data.switchStatement)) { + return false; + } + 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).node.target; + const saveAntecedents = target.antecedent; + target.antecedent = (flow as FlowReduceLabel).node.antecedents; + const result = isReachableFlowNodeWorker((flow as FlowReduceLabel).antecedent, /*noCacheCheck*/ false); + target.antecedent = 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).antecedent, 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).antecedent![0]; + } + else if (flags & FlowFlags.ReduceLabel) { + const target = (flow as FlowReduceLabel).node.target; + const saveAntecedents = target.antecedent; + target.antecedent = (flow as FlowReduceLabel).node.antecedents; + const result = isPostSuperFlowNode((flow as FlowReduceLabel).antecedent, /*noCacheCheck*/ false); + target.antecedent = 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.ThisKeyword: + return true; + case SyntaxKind.Identifier: + if (!isThisInTypeQuery(node)) { + const symbol = getResolvedSymbol(node as Identifier); + return isConstantVariable(symbol) + || isParameterOrMutableLocalVariable(symbol) && !isSymbolAssigned(symbol) + || !!symbol.valueDeclaration && isFunctionExpression(symbol.valueDeclaration); + } + break; + 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); + case SyntaxKind.ObjectBindingPattern: + case SyntaxKind.ArrayBindingPattern: + const rootDeclaration = getRootDeclaration(node.parent); + return isParameter(rootDeclaration) || isCatchClauseVariableDeclaration(rootDeclaration) + ? !isSomeSymbolAssigned(rootDeclaration) + : isVariableDeclaration(rootDeclaration) && isVarConstLike(rootDeclaration); + } + return false; + } + + function getFlowTypeOfReference(reference: Node, declaredType: Type, initialType = declaredType, flowContainer?: Node, flowNode = tryCast(reference, canHaveFlowNode)?.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; + } + return resultType; + + function getOrSetCacheKey() { + if (isKeySet) { + return key; + } + 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; + } + 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).antecedent!.length === 1) { + flow = (flow as FlowLabel).antecedent![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).node.target; + const saveAntecedents = target.antecedent; + target.antecedent = (flow as FlowReduceLabel).node.antecedents; + type = getTypeAtFlowNode((flow as FlowReduceLabel).antecedent); + target.antecedent = 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 && container.kind !== SyntaxKind.ArrowFunction) + ) { + 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++; + } + flowDepth--; + return type; + } + } + + 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; + } + const t = isInCompoundLikeAssignment(node) ? getBaseTypeOfLiteralType(declaredType) : declaredType; + if (t.flags & TypeFlags.Union) { + return getAssignmentReducedType(t as UnionType, getInitialOrAssignedType(flow)); + } + return t; + } + // 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) || isVarConstLike(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) || optionalChainContainsReference(node.parent.parent.expression, reference)) + ) { + 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; + } + 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)]); + } + } + 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; + } + + 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); + } + } + return evolvedType === type ? flowType : createFlowType(evolvedType, isIncomplete(flowType)); + } + return flowType; + } + } + return undefined; + } + + 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 = skipParentheses(flow.node.switchStatement.expression); + const flowType = getTypeAtFlowNode(flow.antecedent); + let type = getTypeFromFlowType(flowType); + if (isMatchingReference(reference, expr)) { + type = narrowTypeBySwitchOnDiscriminant(type, flow.node); + } + else if (expr.kind === SyntaxKind.TypeOfExpression && isMatchingReference(reference, (expr as TypeOfExpression).expression)) { + type = narrowTypeBySwitchOnTypeOf(type, flow.node); + } + else if (expr.kind === SyntaxKind.TrueKeyword) { + type = narrowTypeBySwitchOnTrue(type, flow.node); + } + else { + if (strictNullChecks) { + if (optionalChainContainsReference(expr, reference)) { + type = narrowTypeBySwitchOptionalChainContainment(type, flow.node, t => !(t.flags & (TypeFlags.Undefined | TypeFlags.Never))); + } + else if (expr.kind === SyntaxKind.TypeOfExpression && optionalChainContainsReference((expr as TypeOfExpression).expression, reference)) { + type = narrowTypeBySwitchOptionalChainContainment(type, flow.node, 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.node); + } + } + 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.antecedent!) { + if (!bypassFlow && antecedent.flags & FlowFlags.SwitchClause && (antecedent as FlowSwitchClause).node.clauseStart === (antecedent as FlowSwitchClause).node.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 (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. + if (!isTypeSubsetOf(type, initialType)) { + subtypeReduction = true; + } + if (isIncomplete(flowType)) { + 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 (!(type.flags & TypeFlags.Never) && !contains(antecedentTypes, type) && !isExhaustiveSwitchStatement(bypassFlow.node.switchStatement)) { + if (type === declaredType && declaredType === initialType) { + return type; + } + antecedentTypes.push(type); + if (!isTypeSubsetOf(type, initialType)) { + subtypeReduction = true; + } + if (isIncomplete(flowType)) { + seenIncomplete = true; + } + } + } + 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; + } + // 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.antecedent!) { + 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, initialType)) { + 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; + } + } + // 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; + } + + 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 (isIdentifier(expr)) { + const symbol = getResolvedSymbol(expr); + if (isConstantVariable(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; + } + + function getDiscriminantPropertyAccess(expr: Expression, computedType: Type) { + // As long as the computed type is a subset of the declared type, we use the full declared type to detect + // a discriminant property. In cases where the computed type isn't a subset, e.g because of a preceding type + // predicate narrowing, we use the actual computed type. + if (declaredType.flags & TypeFlags.Union || computedType.flags & TypeFlags.Union) { + const access = getCandidateDiscriminantPropertyAccess(expr); + if (access) { + const name = getAccessedPropertyName(access); + if (name) { + const type = declaredType.flags & TypeFlags.Union && isTypeSubsetOf(computedType, declaredType) ? declaredType : computedType; + if (isDiscriminantProperty(type, name)) { + return access; + } + } + } + } + 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 optionalChain = isOptionalChain(access); + const removeNullable = strictNullChecks && (optionalChain || isNonNullAccess(access)) && maybeTypeOfKind(type, TypeFlags.Nullable); + let propType = getTypeOfPropertyOfType(removeNullable ? getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull) : type, propName); + if (!propType) { + return type; + } + propType = removeNullable && optionalChain ? getOptionalType(propType) : propType; + const narrowedPropType = narrowType(propType); + return filterType(type, t => { + const discriminantType = getTypeOfPropertyOrIndexSignatureOfType(t, propName) || unknownType; + return !(discriminantType.flags & TypeFlags.Never) && !(narrowedPropType.flags & TypeFlags.Never) && areTypesComparable(narrowedPropType, discriminantType); + }); + } + + 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)); + } + + function narrowTypeBySwitchOnDiscriminantProperty(type: Type, access: AccessExpression | BindingElement | ParameterDeclaration, data: FlowSwitchClauseData) { + if (data.clauseStart < data.clauseEnd && type.flags & TypeFlags.Union && getKeyPropertyName(type as UnionType) === getAccessedPropertyName(access)) { + const clauseTypes = getSwitchClauseTypes(data.switchStatement).slice(data.clauseStart, data.clauseEnd); + const candidate = getUnionType(map(clauseTypes, t => getConstituentTypeForKeyType(type as UnionType, t) || unknownType)); + if (candidate !== unknownType) { + return candidate; + } + } + return narrowTypeByDiscriminant(type, access, t => narrowTypeBySwitchOnDiscriminant(t, data)); + } + + 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 isTypePresencePossible(type: Type, propName: __String, assumeTrue: boolean) { + const prop = getPropertyOfType(type, propName); + return prop ? + !!(prop.flags & SymbolFlags.Optional || getCheckFlags(prop) & CheckFlags.Partial) || 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; + } + + function narrowTypeByBooleanComparison(type: Type, expr: Expression, bool: BooleanLiteral, operator: BinaryOperator, assumeTrue: boolean): Type { + assumeTrue = (assumeTrue !== (bool.kind === SyntaxKind.TrueKeyword)) !== (operator !== SyntaxKind.ExclamationEqualsEqualsToken && operator !== SyntaxKind.ExclamationEqualsToken); + return narrowType(type, expr, 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); + } + 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); + } + if (isMatchingConstructorReference(left)) { + return narrowTypeByConstructor(type, operator, right, assumeTrue); + } + if (isMatchingConstructorReference(right)) { + return narrowTypeByConstructor(type, operator, left, assumeTrue); + } + if (isBooleanLiteral(right) && !isAccessExpression(left)) { + return narrowTypeByBooleanComparison(type, left, right, operator, assumeTrue); + } + if (isBooleanLiteral(left) && !isAccessExpression(right)) { + return narrowTypeByBooleanComparison(type, right, left, operator, assumeTrue); + } + break; + case SyntaxKind.InstanceOfKeyword: + return narrowTypeByInstanceof(type, expr as InstanceofExpression, assumeTrue); + case SyntaxKind.InKeyword: + if (isPrivateIdentifier(expr.left)) { + return narrowTypeByPrivateIdentifierInInExpression(type, expr, assumeTrue); + } + const target = getReferenceCandidate(expr.right); + if (containsMissingType(type) && isAccessExpression(reference) && isMatchingReference(reference.expression, target)) { + const leftType = getTypeOfExpression(expr.left); + if (isTypeUsableAsPropertyName(leftType) && getAccessedPropertyName(reference) === getPropertyNameFromType(leftType)) { + return getTypeWithFacts(type, assumeTrue ? TypeFacts.NEUndefined : TypeFacts.EQUndefined); + } + } + if (isMatchingReference(reference, target)) { + const leftType = getTypeOfExpression(expr.left); + if (isTypeUsableAsPropertyName(leftType)) { + return narrowTypeByInKeyword(type, leftType, 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; + } + + 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) { + 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; + } + 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))); + } + 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)) { + if (strictNullChecks && optionalChainContainsReference(target, reference) && assumeTrue === (literal.text !== "undefined")) { + type = getAdjustedTypeWithFacts(type, TypeFacts.NEUndefinedOrNull); + } + const propertyAccess = getDiscriminantPropertyAccess(target, type); + if (propertyAccess) { + return narrowTypeByDiscriminant(type, propertyAccess, t => narrowTypeByLiteralExpression(t, literal, assumeTrue)); + } + return type; + } + return narrowTypeByLiteralExpression(type, literal, assumeTrue); + } + + function narrowTypeByLiteralExpression(type: Type, literal: LiteralExpression, assumeTrue: boolean) { + return assumeTrue ? + narrowTypeByTypeName(type, literal.text) : + getAdjustedTypeWithFacts(type, typeofNEFacts.get(literal.text) || TypeFacts.TypeofNEHostObject); + } + + function narrowTypeBySwitchOptionalChainContainment(type: Type, { switchStatement, clauseStart, clauseEnd }: FlowSwitchClauseData, 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, clauseStart, clauseEnd }: FlowSwitchClauseData) { + // 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, t.flags & TypeFlags.Undefined ? undefinedType : 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) ? hasTypeFacts(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. + hasTypeFacts(t, facts) ? getIntersectionType([t, impliedType]) : + neverType); + } + + function narrowTypeBySwitchOnTypeOf(type: Type, { switchStatement, clauseStart, clauseEnd }: FlowSwitchClauseData): 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 narrowTypeBySwitchOnTrue(type: Type, { switchStatement, clauseStart, clauseEnd }: FlowSwitchClauseData): Type { + const defaultIndex = findIndex(switchStatement.caseBlock.clauses, clause => clause.kind === SyntaxKind.DefaultClause); + const hasDefaultClause = clauseStart === clauseEnd || (defaultIndex >= clauseStart && defaultIndex < clauseEnd); + + // First, narrow away all of the cases that preceded this set of cases. + for (let i = 0; i < clauseStart; i++) { + const clause = switchStatement.caseBlock.clauses[i]; + if (clause.kind === SyntaxKind.CaseClause) { + type = narrowType(type, clause.expression, /*assumeTrue*/ false); + } + } + + // If our current set has a default, then none the other cases were hit either. + // There's no point in narrowing by the the other cases in the set, since we can + // get here through other paths. + if (hasDefaultClause) { + for (let i = clauseEnd; i < switchStatement.caseBlock.clauses.length; i++) { + const clause = switchStatement.caseBlock.clauses[i]; + if (clause.kind === SyntaxKind.CaseClause) { + type = narrowType(type, clause.expression, /*assumeTrue*/ false); + } + } + return type; + } + + // Now, narrow based on the cases in this set. + const clauses = switchStatement.caseBlock.clauses.slice(clauseStart, clauseEnd); + return getUnionType(map(clauses, clause => clause.kind === SyntaxKind.CaseClause ? narrowType(type, clause.expression, /*assumeTrue*/ true) : 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 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; + } + + // 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; + } + + // 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; + } + + // 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; + } + + // 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; + } + + // 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); + } + } + + function narrowTypeByInstanceof(type: Type, expr: InstanceofExpression, 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; + } + const right = expr.right; + const rightType = getTypeOfExpression(right); + if (!isTypeDerivedFrom(rightType, globalObjectType)) { + return type; + } + + // if the right-hand side has an object type with a custom `[Symbol.hasInstance]` method, and that method + // has a type predicate, use the type predicate to perform narrowing. This allows normal `object` types to + // participate in `instanceof`, as per Step 2 of https://tc39.es/ecma262/#sec-instanceofoperator. + const signature = getEffectsSignature(expr); + const predicate = signature && getTypePredicateOfSignature(signature); + if (predicate && predicate.kind === TypePredicateKind.Identifier && predicate.parameterIndex === 0) { + return getNarrowedType(type, predicate.type, assumeTrue, /*checkDerived*/ true); + } + if (!isTypeDerivedFrom(rightType, globalFunctionType)) { + return type; + } + const instanceType = mapType(rightType, getInstanceType); + // Don't narrow from `any` if the target type is exactly `Object` or `Function`, and narrow + // in the false branch only if the target is a non-empty object type. + if ( + isTypeAny(type) && (instanceType === globalObjectType || instanceType === globalFunctionType) || + !assumeTrue && !(instanceType.flags & TypeFlags.Object && !isEmptyAnonymousObjectType(instanceType)) + ) { + return type; + } + return getNarrowedType(type, instanceType, assumeTrue, /*checkDerived*/ true); + } + + function getInstanceType(constructorType: Type) { + const prototypePropertyType = getTypeOfPropertyOfType(constructorType, "prototype" as __String); + if (prototypePropertyType && !isTypeAny(prototypePropertyType)) { + return prototypePropertyType; + } + const constructSignatures = getSignaturesOfType(constructorType, SignatureKind.Construct); + if (constructSignatures.length) { + return getUnionType(map(constructSignatures, signature => getReturnTypeOfSignature(getErasedSignature(signature)))); + } + // We use the empty object type to indicate we don't know the type of objects created by + // this constructor function. + return emptyObjectType; + } + + function getNarrowedType(type: Type, candidate: Type, assumeTrue: boolean, checkDerived: boolean): Type { + 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)); + } + + function getNarrowedTypeWorker(type: Type, candidate: Type, assumeTrue: boolean, checkDerived: boolean) { + if (!assumeTrue) { + if (type === candidate) { + return neverType; + } + if (checkDerived) { + return filterType(type, t => !isTypeDerivedFrom(t, candidate)); + } + const trueType = getNarrowedType(type, candidate, /*assumeTrue*/ true, /*checkDerived*/ false); + return filterType(type, t => !isTypeSubsetOf(t, trueType)); + } + if (type.flags & TypeFlags.AnyOrUnknown) { + return candidate; + } + if (type === candidate) { + return candidate; + } + + // We first attempt to filter the current type, narrowing constituents as appropriate and removing + // constituents that are unrelated to the candidate. + const isRelated = checkDerived ? isTypeDerivedFrom : isTypeSubtypeOf; + 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 => isTypeStrictSubtypeOf(t, c) ? t : isTypeStrictSubtypeOf(c, t) ? c : isTypeSubtypeOf(t, c) ? t : isTypeSubtypeOf(c, t) ? c : 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); + } + } + } + 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 && optionalChainContainsReference(predicateArgument, reference) && + ( + assumeTrue && !(hasTypeFacts(predicate.type, TypeFacts.EQUndefined)) || + !assumeTrue && everyType(predicate.type, isNullableType) + ) + ) { + 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; + } + + // 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.operatorToken.kind === SyntaxKind.QuestionQuestionEqualsToken) && 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 (isConstantVariable(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; + } + } + + function getTypeOfSymbolAtLocation(symbol: Symbol, location: Node) { + symbol = getExportSymbolOfValueSymbolIfExported(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 = removeOptionalTypeMarker( + isWriteAccess(location) && location.kind === SyntaxKind.PropertyAccessExpression ? + checkPropertyAccessExpression(location as PropertyAccessExpression, /*checkMode*/ undefined, /*writeOnly*/ true) : + getTypeOfExpression(location as Expression), + ); + if (getExportSymbolOfValueSymbolIfExported(getNodeLinks(location).resolvedSymbol) === symbol) { + return type; + } + } + } + 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 isRightSideOfAccessExpression(location) && isWriteAccess(location.parent) ? getWriteTypeOfSymbol(symbol) : getNonMissingTypeOfSymbol(symbol); + } + + 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) { + return !isPastLastAssignment(symbol, /*location*/ undefined); + } + + // Return true if there are no assignments to the given symbol or if the given location + // is past the last assignment to the symbol. + function isPastLastAssignment(symbol: Symbol, location: Node | undefined) { + const parent = findAncestor(symbol.valueDeclaration, isFunctionOrSourceFile); + if (!parent) { + return false; + } + const links = getNodeLinks(parent); + if (!(links.flags & NodeCheckFlags.AssignmentsMarked)) { + links.flags |= NodeCheckFlags.AssignmentsMarked; + if (!hasParentWithAssignmentsMarked(parent)) { + markNodeAssignments(parent); + } + } + return !symbol.lastAssignmentPos || location && symbol.lastAssignmentPos < location.pos; + } + + // Check if a parameter or catch variable (or their bindings elements) is assigned anywhere + function isSomeSymbolAssigned(rootDeclaration: Node) { + Debug.assert(isVariableDeclaration(rootDeclaration) || isParameter(rootDeclaration)); + return isSomeSymbolAssignedWorker(rootDeclaration.name); + } + + function isSomeSymbolAssignedWorker(node: BindingName): boolean { + if (node.kind === SyntaxKind.Identifier) { + return isSymbolAssigned(getSymbolOfDeclaration(node.parent as Declaration)); + } + + return some(node.elements, e => e.kind !== SyntaxKind.OmittedExpression && isSomeSymbolAssignedWorker(e.name)); + } + + function hasParentWithAssignmentsMarked(node: Node) { + return !!findAncestor(node.parent, node => isFunctionOrSourceFile(node) && !!(getNodeLinks(node).flags & NodeCheckFlags.AssignmentsMarked)); + } + + function isFunctionOrSourceFile(node: Node) { + return isFunctionLikeDeclaration(node) || isSourceFile(node); + } + + // For all assignments within the given root node, record the last assignment source position for all + // referenced parameters and mutable local variables. When assignments occur in nested functions or + // references occur in export specifiers, record Number.MAX_VALUE as the assignment position. When + // assignments occur in compound statements, record the ending source position of the compound statement + // as the assignment position (this is more conservative than full control flow analysis, but requires + // only a single walk over the AST). + function markNodeAssignments(node: Node) { + switch (node.kind) { + case SyntaxKind.Identifier: + if (isAssignmentTarget(node)) { + const symbol = getResolvedSymbol(node as Identifier); + if (isParameterOrMutableLocalVariable(symbol) && symbol.lastAssignmentPos !== Number.MAX_VALUE) { + const referencingFunction = findAncestor(node, isFunctionOrSourceFile); + const declaringFunction = findAncestor(symbol.valueDeclaration, isFunctionOrSourceFile); + symbol.lastAssignmentPos = referencingFunction === declaringFunction ? extendAssignmentPosition(node, symbol.valueDeclaration!) : Number.MAX_VALUE; + } + } + return; + case SyntaxKind.ExportSpecifier: + const exportDeclaration = (node as ExportSpecifier).parent.parent; + const name = (node as ExportSpecifier).propertyName || (node as ExportSpecifier).name; + if (!(node as ExportSpecifier).isTypeOnly && !exportDeclaration.isTypeOnly && !exportDeclaration.moduleSpecifier && name.kind !== SyntaxKind.StringLiteral) { + const symbol = resolveEntityName(name, SymbolFlags.Value, /*ignoreErrors*/ true, /*dontResolveAlias*/ true); + if (symbol && isParameterOrMutableLocalVariable(symbol)) { + symbol.lastAssignmentPos = Number.MAX_VALUE; + } + } + return; + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.EnumDeclaration: + return; + } + if (isTypeNode(node)) { + return; + } + forEachChild(node, markNodeAssignments); + } + + // Extend the position of the given assignment target node to the end of any intervening variable statement, + // expression statement, compound statement, or class declaration occurring between the node and the given + // declaration node. + function extendAssignmentPosition(node: Node, declaration: Declaration) { + let pos = node.pos; + while (node && node.pos > declaration.pos) { + switch (node.kind) { + case SyntaxKind.VariableStatement: + case SyntaxKind.ExpressionStatement: + case SyntaxKind.IfStatement: + case SyntaxKind.DoStatement: + case SyntaxKind.WhileStatement: + case SyntaxKind.ForStatement: + case SyntaxKind.ForInStatement: + case SyntaxKind.ForOfStatement: + case SyntaxKind.WithStatement: + case SyntaxKind.SwitchStatement: + case SyntaxKind.TryStatement: + case SyntaxKind.ClassDeclaration: + pos = node.end; + } + node = node.parent; + } + return pos; + } + + function isConstantVariable(symbol: Symbol) { + return symbol.flags & SymbolFlags.Variable && (getDeclarationNodeFlagsFromSymbol(symbol) & NodeFlags.Constant) !== 0; + } + + function isParameterOrMutableLocalVariable(symbol: Symbol) { + // Return true if symbol is a parameter, a catch clause variable, or a mutable local variable + const declaration = symbol.valueDeclaration && getRootDeclaration(symbol.valueDeclaration); + return !!declaration && ( + isParameter(declaration) || + isVariableDeclaration(declaration) && (isCatchClause(declaration.parent) || isMutableLocalVariableDeclaration(declaration)) + ); + } + + function isMutableLocalVariableDeclaration(declaration: VariableDeclaration) { + // Return true if symbol is a non-exported and non-global `let` variable + return !!(declaration.parent.flags & NodeFlags.Let) && !( + getCombinedModifierFlags(declaration) & ModifierFlags.Export || + declaration.parent.parent.kind === SyntaxKind.VariableStatement && isGlobalSourceFile(declaration.parent.parent.parent) + ); + } + + function parameterInitializerContainsUndefined(declaration: ParameterDeclaration): boolean { + const links = getNodeLinks(declaration); + + if (links.parameterInitializerContainsUndefined === undefined) { + if (!pushTypeResolution(declaration, TypeSystemPropertyName.ParameterInitializerContainsUndefined)) { + reportCircularityError(declaration.symbol); + return true; + } + + const containsUndefined = !!(hasTypeFacts(checkDeclarationInitializer(declaration, CheckMode.Normal), TypeFacts.IsUndefined)); + + if (!popTypeResolution()) { + reportCircularityError(declaration.symbol); + return true; + } + + links.parameterInitializerContainsUndefined ??= containsUndefined; + } + + return links.parameterInitializerContainsUndefined; + } + + /** 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 { + const removeUndefined = strictNullChecks && + declaration.kind === SyntaxKind.Parameter && + declaration.initializer && + hasTypeFacts(declaredType, TypeFacts.IsUndefined) && + !parameterInitializerContainsUndefined(declaration); + + return removeUndefined ? getTypeWithFacts(declaredType, TypeFacts.NEUndefined) : 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.NewExpression && (parent as NewExpression).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) { + if (isNoInferType(type)) { + type = (type as SubstitutionType).baseType; + } + // 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; + } + return false; + }); + } + + /** + * This function marks all the imports the given location refers to as `.referenced` in `NodeLinks` (transitively through local import aliases). + * (This corresponds to not getting elided in JS emit.) + * It can be called on *most* nodes in the AST with `ReferenceHint.Unspecified` and will filter its inputs, but care should be taken to avoid calling it on the RHS of an `import =` or specifiers in a `import {} from "..."`, + * unless you *really* want to *definitely* mark those as referenced. + * These shouldn't be directly marked, and should only get marked transitively by the internals of this function. + * + * @param location The location to mark js import refernces for + * @param hint The kind of reference `location` has already been checked to be + * @param propSymbol The optional symbol of the property we're looking up - this is used for property accesses when `const enum`s do not count as references (no `isolatedModules`, no `preserveConstEnums` + export). It will be calculated if not provided. + * @param parentType The optional type of the parent of the LHS of the property access - this will be recalculated if not provided (but is costly). + */ + function markLinkedReferences(location: PropertyAccessExpression | QualifiedName, hint: ReferenceHint.Property, propSymbol: Symbol | undefined, parentType: Type): void; + function markLinkedReferences(location: Identifier, hint: ReferenceHint.Identifier): void; + function markLinkedReferences(location: ExportAssignment, hint: ReferenceHint.ExportAssignment): void; + function markLinkedReferences(location: JsxOpeningLikeElement | JsxOpeningFragment, hint: ReferenceHint.Jsx): void; + function markLinkedReferences(location: FunctionLikeDeclaration | MethodSignature, hint: ReferenceHint.AsyncFunction): void; + function markLinkedReferences(location: ImportEqualsDeclaration, hint: ReferenceHint.ExportImportEquals): void; + function markLinkedReferences(location: ExportSpecifier, hint: ReferenceHint.ExportSpecifier): void; + function markLinkedReferences(location: HasDecorators, hint: ReferenceHint.Decorator): void; + function markLinkedReferences(location: Node, hint: ReferenceHint.Unspecified, propSymbol?: Symbol, parentType?: Type): void; + function markLinkedReferences(location: Node, hint: ReferenceHint, propSymbol?: Symbol, parentType?: Type) { + if (!canCollectSymbolAliasAccessabilityData) { + return; + } + if (location.flags & NodeFlags.Ambient) { + return; // References within types and declaration files are never going to contribute to retaining a JS import + } + switch (hint) { + case ReferenceHint.Identifier: + return markIdentifierAliasReferenced(location as Identifier); + case ReferenceHint.Property: + return markPropertyAliasReferenced(location as PropertyAccessExpression | QualifiedName, propSymbol, parentType); + case ReferenceHint.ExportAssignment: + return markExportAssignmentAliasReferenced(location as ExportAssignment); + case ReferenceHint.Jsx: + return markJsxAliasReferenced(location as JsxOpeningLikeElement | JsxOpeningFragment); + case ReferenceHint.AsyncFunction: + return markAsyncFunctionAliasReferenced(location as FunctionLikeDeclaration | MethodSignature); + case ReferenceHint.ExportImportEquals: + return markImportEqualsAliasReferenced(location as ImportEqualsDeclaration); + case ReferenceHint.ExportSpecifier: + return markExportSpecifierAliasReferenced(location as ExportSpecifier); + case ReferenceHint.Decorator: + return markDecoratorAliasReferenced(location as HasDecorators); + case ReferenceHint.Unspecified: { + // Identifiers in expression contexts are emitted, so we need to follow their referenced aliases and mark them as used + // Some non-expression identifiers are also treated as expression identifiers for this purpose, eg, `a` in `b = {a}` or `q` in `import r = q` + // This is the exception, rather than the rule - most non-expression identifiers are declaration names. + if (isIdentifier(location) && (isExpressionNode(location) || isShorthandPropertyAssignment(location.parent) || (isImportEqualsDeclaration(location.parent) && location.parent.moduleReference === location)) && shouldMarkIdentifierAliasReferenced(location)) { + if (isPropertyAccessOrQualifiedName(location.parent)) { + const left = isPropertyAccessExpression(location.parent) ? location.parent.expression : location.parent.left; + if (left !== location) return; // Only mark the LHS (the RHS is a property lookup) + } + markIdentifierAliasReferenced(location); + return; + } + if (isPropertyAccessOrQualifiedName(location)) { + let topProp: Node | undefined = location; + while (isPropertyAccessOrQualifiedName(topProp)) { + if (isPartOfTypeNode(topProp)) return; + topProp = topProp.parent; + } + return markPropertyAliasReferenced(location); + } + if (isExportAssignment(location)) { + return markExportAssignmentAliasReferenced(location); + } + if (isJsxOpeningLikeElement(location) || isJsxOpeningFragment(location)) { + return markJsxAliasReferenced(location); + } + if (isImportEqualsDeclaration(location)) { + if (isInternalModuleImportEqualsDeclaration(location) || checkExternalImportOrExportDeclaration(location)) { + return markImportEqualsAliasReferenced(location); + } + return; + } + if (isExportSpecifier(location)) { + return markExportSpecifierAliasReferenced(location); + } + if (isFunctionLikeDeclaration(location) || isMethodSignature(location)) { + markAsyncFunctionAliasReferenced(location); + // Might be decorated, fall through to decorator final case + } + if (!compilerOptions.emitDecoratorMetadata) { + return; + } + if (!canHaveDecorators(location) || !hasDecorators(location) || !location.modifiers || !nodeCanBeDecorated(legacyDecorators, location, location.parent, location.parent.parent)) { + return; + } + + return markDecoratorAliasReferenced(location); + } + default: + Debug.assertNever(hint, `Unhandled reference hint: ${hint}`); + } + } + + function markIdentifierAliasReferenced(location: Identifier) { + const symbol = getResolvedSymbol(location); + if (symbol && symbol !== argumentsSymbol && symbol !== unknownSymbol && !isThisInTypeQuery(location)) { + markAliasReferenced(symbol, location); + } + } + + function markPropertyAliasReferenced(location: PropertyAccessExpression | QualifiedName, propSymbol?: Symbol, parentType?: Type) { + const left = isPropertyAccessExpression(location) ? location.expression : location.left; + if (isThisIdentifier(left) || !isIdentifier(left)) { + return; + } + const parentSymbol = getResolvedSymbol(left); + if (!parentSymbol || parentSymbol === unknownSymbol) { + return; + } + // 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`. + // + // The property lookup is deferred as much as possible, in as many situations as possible, to avoid alias marking + // pulling on types/symbols it doesn't strictly need to. + if (getIsolatedModules(compilerOptions) || (shouldPreserveConstEnums(compilerOptions) && isExportOrExportExpression(location))) { + markAliasReferenced(parentSymbol, location); + return; + } + // Hereafter, this relies on type checking - but every check prior to this only used symbol information + const leftType = parentType || checkExpressionCached(left); + if (isTypeAny(leftType) || leftType === silentNeverType) { + markAliasReferenced(parentSymbol, location); + return; + } + let prop = propSymbol; + if (!prop && !parentType) { + const right = isPropertyAccessExpression(location) ? location.name : location.right; + const lexicallyScopedSymbol = isPrivateIdentifier(right) && lookupSymbolForPrivateIdentifierDeclaration(right.escapedText, right); + const assignmentKind = getAssignmentTargetKind(location); + const apparentType = getApparentType(assignmentKind !== AssignmentKind.None || isMethodAccessForCall(location) ? getWidenedType(leftType) : leftType); + prop = isPrivateIdentifier(right) ? lexicallyScopedSymbol && getPrivateIdentifierPropertyOfType(apparentType, lexicallyScopedSymbol) || undefined : getPropertyOfType(apparentType, right.escapedText); + } + if ( + !(prop && (isConstEnumOrConstEnumOnlyModule(prop) || prop.flags & SymbolFlags.EnumMember && location.parent.kind === SyntaxKind.EnumMember)) + ) { + markAliasReferenced(parentSymbol, location); + } + return; + } + + function markExportAssignmentAliasReferenced(location: ExportAssignment) { + if (isIdentifier(location.expression)) { + const id = location.expression; + const sym = getExportSymbolOfValueSymbolIfExported(resolveEntityName(id, SymbolFlags.All, /*ignoreErrors*/ true, /*dontResolveAlias*/ true, location)); + if (sym) { + markAliasReferenced(sym, id); + } + } + } + + function markJsxAliasReferenced(node: JsxOpeningLikeElement | JsxOpeningFragment) { + 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 = isJsxOpeningLikeElement(node) ? node.tagName : node; + + // allow null as jsxFragmentFactory + let jsxFactorySym: Symbol | undefined; + if (!(isJsxOpeningFragment(node) && jsxFactoryNamespace === "null")) { + jsxFactorySym = resolveName(jsxFactoryLocation, jsxFactoryNamespace, SymbolFlags.Value, jsxFactoryRefErr, /*isUse*/ true); + } + + 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; + + // If react/jsxFactory symbol is alias, mark it as refereced + if (canCollectSymbolAliasAccessabilityData && jsxFactorySym.flags & SymbolFlags.Alias && !getTypeOnlyAliasDeclaration(jsxFactorySym)) { + markAliasSymbolAsReferenced(jsxFactorySym); + } + } + + // 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, /*isUse*/ true); + } + } + } + return; + } + + function markAsyncFunctionAliasReferenced(location: FunctionLikeDeclaration | MethodSignature) { + if (languageVersion < ScriptTarget.ES2015) { + if (getFunctionFlags(location) & FunctionFlags.Async) { + const returnTypeNode = getEffectiveReturnTypeNode(location); + markTypeNodeAsReferenced(returnTypeNode); + } + } + } + + function markImportEqualsAliasReferenced(location: ImportEqualsDeclaration) { + if (hasSyntacticModifier(location, ModifierFlags.Export)) { + markExportAsReferenced(location); + } + } + + function markExportSpecifierAliasReferenced(location: ExportSpecifier) { + if (!location.parent.parent.moduleSpecifier && !location.isTypeOnly && !location.parent.parent.isTypeOnly) { + const exportedName = location.propertyName || location.name; + if (exportedName.kind === SyntaxKind.StringLiteral) { + return; // Skip for invalid syntax like this: export { "x" } + } + const symbol = resolveName(exportedName, exportedName.escapedText, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias, /*nameNotFoundMessage*/ undefined, /*isUse*/ true); + if (symbol && (symbol === undefinedSymbol || symbol === globalThisSymbol || symbol.declarations && isGlobalSourceFile(getDeclarationContainer(symbol.declarations[0])))) { + // Do nothing, non-local symbol + } + else { + const target = symbol && (symbol.flags & SymbolFlags.Alias ? resolveAlias(symbol) : symbol); + if (!target || getSymbolFlags(target) & SymbolFlags.Value) { + markExportAsReferenced(location); // marks export as used + markIdentifierAliasReferenced(exportedName); // marks target of export as used + } + } + return; + } + } + + function markDecoratorAliasReferenced(node: HasDecorators) { + if (compilerOptions.emitDecoratorMetadata) { + const firstDecorator = find(node.modifiers, isDecorator); + if (!firstDecorator) { + return; + } + + checkExternalEmitHelpers(firstDecorator, ExternalEmitHelpers.Metadata); + + // 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; + + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + const otherKind = node.kind === SyntaxKind.GetAccessor ? SyntaxKind.SetAccessor : SyntaxKind.GetAccessor; + const otherAccessor = getDeclarationOfKind(getSymbolOfDeclaration(node), otherKind); + markDecoratorMedataDataTypeNodeAsReferenced(getAnnotatedAccessorTypeNode(node) || otherAccessor && getAnnotatedAccessorTypeNode(otherAccessor)); + break; + case SyntaxKind.MethodDeclaration: + for (const parameter of node.parameters) { + markDecoratorMedataDataTypeNodeAsReferenced(getParameterTypeNodeForDecoratorCheck(parameter)); + } + + markDecoratorMedataDataTypeNodeAsReferenced(getEffectiveReturnTypeNode(node)); + break; + + case SyntaxKind.PropertyDeclaration: + markDecoratorMedataDataTypeNodeAsReferenced(getEffectiveTypeAnnotationNode(node)); + break; + + case SyntaxKind.Parameter: + markDecoratorMedataDataTypeNodeAsReferenced(getParameterTypeNodeForDecoratorCheck(node)); + const containingSignature = node.parent; + for (const parameter of containingSignature.parameters) { + markDecoratorMedataDataTypeNodeAsReferenced(getParameterTypeNodeForDecoratorCheck(parameter)); + } + markDecoratorMedataDataTypeNodeAsReferenced(getEffectiveReturnTypeNode(containingSignature)); + break; + } + } + } + + function markAliasReferenced(symbol: Symbol, location: Node) { + if (!canCollectSymbolAliasAccessabilityData) { + return; + } + if (isNonLocalAlias(symbol, /*excludes*/ SymbolFlags.Value) && !isInTypeQuery(location)) { + const target = resolveAlias(symbol); + if (getSymbolFlags(symbol, /*excludeTypeOnlyMeanings*/ true) & (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 ( + getIsolatedModules(compilerOptions) || + shouldPreserveConstEnums(compilerOptions) && isExportOrExportExpression(location) || + !isConstEnumOrConstEnumOnlyModule(getExportSymbolOfValueSymbolIfExported(target)) + ) { + 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) { + Debug.assert(canCollectSymbolAliasAccessabilityData); + 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 (getSymbolFlags(resolveSymbol(symbol)) & SymbolFlags.Value) { + // import foo = + const left = getFirstIdentifier(node.moduleReference as EntityNameExpression); + markIdentifierAliasReferenced(left); + } + } + } + } + + function markExportAsReferenced(node: ImportEqualsDeclaration | ExportSpecifier) { + const symbol = getSymbolOfDeclaration(node); + const target = resolveAlias(symbol); + if (target) { + const markAlias = target === unknownSymbol || + ((getSymbolFlags(symbol, /*excludeTypeOnlyMeanings*/ true) & SymbolFlags.Value) && !isConstEnumOrConstEnumOnlyModule(target)); + + if (markAlias) { + markAliasSymbolAsReferenced(symbol); + } + } + } + + 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, /*isUse*/ true); + if (rootSymbol && rootSymbol.flags & SymbolFlags.Alias) { + if ( + canCollectSymbolAliasAccessabilityData + && symbolIsValue(rootSymbol) + && !isConstEnumOrConstEnumOnlyModule(resolveAlias(rootSymbol)) + && !getTypeOnlyAliasDeclaration(rootSymbol) + ) { + markAliasSymbolAsReferenced(rootSymbol); + } + else if ( + forDecoratorMetadata + && getIsolatedModules(compilerOptions) + && 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))); + } + } + } + } + + /** + * 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 | undefined) { + markEntityNameOrEntityExpressionAsReference(node && getEntityNameFromTypeNode(node), /*forDecoratorMetadata*/ false); + } + + /** + * 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 getNarrowedTypeOfSymbol(symbol: Symbol, location: Identifier, checkMode?: CheckMode) { + const type = getTypeOfSymbol(symbol, checkMode); + 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; + const rootDeclaration = getRootDeclaration(parent); + if (rootDeclaration.kind === SyntaxKind.VariableDeclaration && getCombinedNodeFlagsCached(rootDeclaration) & NodeFlags.Constant || rootDeclaration.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 && !(rootDeclaration.kind === SyntaxKind.Parameter && isSomeSymbolAssigned(rootDeclaration))) { + const pattern = declaration.parent; + const narrowedType = getFlowTypeOfReference(pattern, parentTypeConstraint, parentTypeConstraint, /*flowContainer*/ undefined, location.flowNode); + if (narrowedType.flags & TypeFlags.Never) { + return neverType; + } + // Destructurings are validated against the parent type elsewhere. Here we disable tuple bounds + // checks because the narrowed type may have lower arity than the full parent type. For example, + // for the declaration [x, y]: [1, 2] | [3], we may have narrowed the parent type to just [3]. + return getBindingElementTypeFromParentType(declaration, narrowedType, /*noTupleBoundsCheck*/ true); + } + } + } + } + // 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(instantiateType(getTypeOfSymbol(contextualSignature.parameters[0]), getInferenceContext(func)?.nonFixingMapper)); + if (restType.flags & TypeFlags.Union && everyType(restType, isTupleType) && !some(func.parameters, isSomeSymbolAssigned)) { + 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 type; + } + + /** + * This part of `checkIdentifier` is kept seperate from the rest, so `NodeCheckFlags` (and related diagnostics) can be lazily calculated + * without calculating the flow type of the identifier. + */ + function checkIdentifierCalculateNodeCheckFlags(node: Identifier, symbol: Symbol) { + if (isThisInTypeQuery(node)) return; + + // 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; + } + + let container = getContainingFunction(node); + if (container) { + if (languageVersion < ScriptTarget.ES2015) { + if (container.kind === SyntaxKind.ArrowFunction) { + error(node, Diagnostics.The_arguments_object_cannot_be_referenced_in_an_arrow_function_in_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_ES5_Consider_using_a_standard_function_or_method); + } + } + + getNodeLinks(container).flags |= NodeCheckFlags.CaptureArguments; + while (container && isArrowFunction(container)) { + container = getContainingFunction(container); + if (container) { + getNodeLinks(container).flags |= NodeCheckFlags.CaptureArguments; + } + } + } + return; + } + + const localOrExportSymbol = getExportSymbolOfValueSymbolIfExported(symbol); + const targetSymbol = resolveAliasWithDeprecationCheck(localOrExportSymbol, node); + if (isDeprecatedSymbol(targetSymbol) && isUncalledFunctionReference(node, targetSymbol) && targetSymbol.declarations) { + addDeprecatedSuggestion(node, targetSymbol.declarations, node.escapedText as string); + } + + const declaration = localOrExportSymbol.valueDeclaration; + if (declaration && localOrExportSymbol.flags & SymbolFlags.Class) { + // When we downlevel classes we may emit some code outside of the class body. Due to the fact the + // class name is double-bound, we must ensure we mark references to the class name so that we can + // emit an alias to the class later. + if (isClassLike(declaration) && declaration.name !== node) { + let container = getThisContainer(node, /*includeArrowFunctions*/ false, /*includeClassComputedPropertyName*/ false); + while (container.kind !== SyntaxKind.SourceFile && container.parent !== declaration) { + container = getThisContainer(container, /*includeArrowFunctions*/ false, /*includeClassComputedPropertyName*/ false); + } + + if (container.kind !== SyntaxKind.SourceFile) { + getNodeLinks(declaration).flags |= NodeCheckFlags.ContainsConstructorReference; + getNodeLinks(container).flags |= NodeCheckFlags.ContainsConstructorReference; + getNodeLinks(node).flags |= NodeCheckFlags.ConstructorReference; + } + } + } + + checkNestedBlockScopedBinding(node, symbol); + } + + function checkIdentifier(node: Identifier, checkMode: CheckMode | undefined): Type { + if (isThisInTypeQuery(node)) { + return checkThisExpression(node); + } + + const symbol = getResolvedSymbol(node); + if (symbol === unknownSymbol) { + return errorType; + } + + checkIdentifierCalculateNodeCheckFlags(node, symbol); + + if (symbol === argumentsSymbol) { + if (isInPropertyInitializerOrClassStaticBlock(node)) { + return errorType; + } + return getTypeOfSymbol(symbol); + } + + if (shouldMarkIdentifierAliasReferenced(node)) { + markLinkedReferences(node, ReferenceHint.Identifier); + } + + const localOrExportSymbol = getExportSymbolOfValueSymbolIfExported(symbol); + let declaration = localOrExportSymbol.valueDeclaration; + + let type = getNarrowedTypeOfSymbol(localOrExportSymbol, node, checkMode); + 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; + + 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; + } + } + + 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 isInCompoundLikeAssignment(node) ? getBaseTypeOfLiteralType(type) : type; + } + } + else if (isAlias) { + declaration = getDeclarationOfAliasSymbol(symbol); + } + else { + return type; + } + + if (!declaration) { + return type; + } + + 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; + const typeIsAutomatic = type === autoType || type === autoArrayType; + const isAutomaticTypeInNonNull = typeIsAutomatic && node.parent.kind === SyntaxKind.NonNullExpression; + // When the control flow originates in a function expression, arrow function, method, or accessor, and + // we are referencing a closed-over const variable or parameter or mutable local variable past its last + // assignment, we extend the origin of the control flow analysis to include the immediately enclosing + // control flow container. + while ( + flowContainer !== declarationContainer && ( + flowContainer.kind === SyntaxKind.FunctionExpression || + flowContainer.kind === SyntaxKind.ArrowFunction || + isObjectLiteralOrClassExpressionMethodOrAccessor(flowContainer) + ) && ( + isConstantVariable(localOrExportSymbol) && type !== autoArrayType || + isParameterOrMutableLocalVariable(localOrExportSymbol) && isPastLastAssignment(localOrExportSymbol, node) + ) + ) { + 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) || isInAmbientOrTypeNode(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 = isAutomaticTypeInNonNull ? undefinedType : + assumeInitialized ? (isParameter ? removeOptionalityFromDeclaredType(type, declaration as VariableLikeDeclaration) : type) : + typeIsAutomatic ? undefinedType : getOptionalType(type); + const flowType = isAutomaticTypeInNonNull ? getNonNullableType(getFlowTypeOfReference(node, type, initialType, flowContainer)) : + 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 + return type; + } + return assignmentKind ? getBaseTypeOfLiteralType(flowType) : flowType; + } + + function isSameScopedBindingElement(node: Identifier, declaration: Declaration) { + if (isBindingElement(declaration)) { + const bindingElement = findAncestor(node, isBindingElement); + return bindingElement && getRootDeclaration(bindingElement) === getRootDeclaration(declaration); + } + } + + 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 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; + } + + // 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; + } + } + + // 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; + } + } + + // set 'declared inside loop' bit on the block-scoped binding + getNodeLinks(symbol.valueDeclaration).flags |= NodeCheckFlags.BlockScopedBindingInLoop; + } + + if (isCaptured) { + getNodeLinks(symbol.valueDeclaration).flags |= NodeCheckFlags.CapturedBlockScopedBinding; + } + } + + function isBindingCapturedByNode(node: Node, decl: VariableDeclaration | BindingElement) { + const links = getNodeLinks(node); + return !!links && contains(links.capturedBlockScopeBindings, getSymbolOfDeclaration(decl)); + } + + 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 (!isAssigned) { + return false; + } + + // 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 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: ClassLikeDeclaration): boolean { + const classSymbol = getSymbolOfDeclaration(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); + + // 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 (canHaveFlowNode(node) && node.flowNode && !isPostSuperFlowNode(node.flowNode, /*noCacheCheck*/ false)) { + error(node, diagnosticMessage); + } + } + } + + function checkThisInStaticClassFieldInitializerInDecoratedClass(thisExpression: Node, container: Node) { + if ( + isPropertyDeclaration(container) && hasStaticModifier(container) && legacyDecorators && + 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, /*includeClassComputedPropertyName*/ true); + let capturedByArrowFunction = false; + let thisInComputedPropertyName = false; + + if (container.kind === SyntaxKind.Constructor) { + checkThisBeforeSuper(node, container, Diagnostics.super_must_be_called_before_accessing_this_in_the_constructor_of_a_derived_class); + } + + while (true) { + // Now skip arrow functions to get the "real" owner of 'this'. + if (container.kind === SyntaxKind.ArrowFunction) { + container = getThisContainer(container, /*includeArrowFunctions*/ false, !thisInComputedPropertyName); + capturedByArrowFunction = true; + } + + if (container.kind === SyntaxKind.ComputedPropertyName) { + container = getThisContainer(container, !capturedByArrowFunction, /*includeClassComputedPropertyName*/ false); + thisInComputedPropertyName = true; + continue; + } + + break; + } + + checkThisInStaticClassFieldInitializerInDecoratedClass(node, container); + if (thisInComputedPropertyName) { + error(node, Diagnostics.this_cannot_be_referenced_in_a_computed_property_name); + } + else { + 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; + } + } + + // 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)); + } + } + } + } + return type || anyType; + } + + function tryGetThisTypeAt(node: Node, includeGlobalThis = true, container = getThisContainer(node, /*includeArrowFunctions*/ false, /*includeClassComputedPropertyName*/ 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; + } + thisType ||= getContextualThisParameterType(container); + } + + if (thisType) { + return getFlowTypeOfReference(node, thisType); + } + } + + if (isClassLike(container.parent)) { + const symbol = getSymbolOfDeclaration(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 = getSymbolOfDeclaration(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); + } + } + } + + function getExplicitThisType(node: Expression) { + const container = getThisContainer(node, /*includeArrowFunctions*/ false, /*includeClassComputedPropertyName*/ false); + if (isFunctionLike(container)) { + const signature = getSignatureFromDeclaration(container); + if (signature.thisParameter) { + return getExplicitTypeOfSymbol(signature.thisParameter); + } + } + if (isClassLike(container.parent)) { + const symbol = getSymbolOfDeclaration(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 getTypeForThisExpressionFromJSDoc(node: SignatureDeclaration) { + const thisTag = getJSDocThisTag(node); + if (thisTag && thisTag.typeExpression) { + return getTypeFromTypeNode(thisTag.typeExpression); + } + const signature = getSignatureOfTypeTag(node); + if (signature) { + return getThisTypeOfSignature(signature); + } + } + + function isInConstructorArgumentInitializer(node: Node, constructorDecl: Node): boolean { + return !!findAncestor(node, n => isFunctionLikeDeclaration(n) ? "quit" : n.kind === SyntaxKind.Parameter && n.parent === constructorDecl); + } + + function checkSuperExpression(node: Node): Type { + const isCallExpression = node.parent.kind === SyntaxKind.CallExpression && (node.parent as CallExpression).expression === node; + + const immediateContainer = getSuperContainer(node, /*stopOnFunctions*/ true); + let container = immediateContainer; + let needToCaptureLexicalThis = false; + let inAsyncFunction = false; + + // 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; + } + + let nodeCheckFlag: NodeCheckFlags = 0; + + if (!container || !isLegalUsageOfSuperExpression(container)) { + // 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; + } + + 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); + } + + 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; + } + + getNodeLinks(node).flags |= nodeCheckFlag; + + // 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 (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.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; + } + } + + // 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; + } + + if (classDeclarationExtendsNull(classLikeDeclaration)) { + return isCallExpression ? errorType : nullWideningType; + } + + const classType = getDeclaredTypeOfSymbol(getSymbolOfDeclaration(classLikeDeclaration)) as InterfaceType; + const baseClassType = classType && getBaseTypes(classType)[0]; + if (!baseClassType) { + return errorType; + } + + 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; + } + + return nodeCheckFlag === NodeCheckFlags.SuperStatic + ? getBaseConstructorTypeOfClass(classType) + : getTypeWithThisArgument(baseClassType, classType.thisType); + + function isLegalUsageOfSuperExpression(container: Node): boolean { + 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; + } + } + + 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 getThisTypeOfObjectLiteralFromContextualType(containingLiteral: ObjectLiteralExpression, contextualType: Type | undefined) { + let literal = containingLiteral; + let type = contextualType; + while (type) { + const thisType = getThisTypeFromContextualType(type); + if (thisType) { + return thisType; + } + if (literal.parent.kind !== SyntaxKind.PropertyAssignment) { + break; + } + literal = literal.parent.parent as ObjectLiteralExpression; + type = getApparentTypeOfContextualType(literal, /*contextFlags*/ undefined); + } + } + + function getContextualThisParameterType(func: SignatureDeclaration): Type | undefined { + if (func.kind === SyntaxKind.ArrowFunction) { + return undefined; + } + 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); + const thisType = getThisTypeOfObjectLiteralFromContextualType(containingLiteral, contextualType); + if (thisType) { + return instantiateType(thisType, getMapperFromContext(getInferenceContext(containingLiteral))); + } + // 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 (isAssignmentExpression(parent)) { + const target = parent.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)); + } + } + } + return undefined; + } + + // 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; + } + 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) || (isInJSFile(declaration) ? tryGetJSDocSatisfiesTypeNode(declaration) : undefined); + 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); + } + // By default, do nothing and return undefined - only the above cases have context implied by a parent + } + } + + 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); + } + } + + 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, getSymbolOfDeclaration(declaration).escapedName); + } + + // 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); + } + } + return undefined; + } + + 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 + } + + 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)]); + } + + return contextualReturnType; // Regular function or Generator function + } + } + 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)]); + } + return 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) { + const isAsyncGenerator = (functionFlags & FunctionFlags.Async) !== 0; + if (!node.asteriskToken && contextualReturnType.flags & TypeFlags.Union) { + contextualReturnType = filterType(contextualReturnType, type => !!getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Return, type, isAsyncGenerator)); + } + if (node.asteriskToken) { + const iterationTypes = getIterationTypesOfGeneratorFunctionReturnType(contextualReturnType, isAsyncGenerator); + const yieldType = iterationTypes?.yieldType ?? silentNeverType; + const returnType = getContextualType(node, contextFlags) ?? silentNeverType; + const nextType = iterationTypes?.nextType ?? unknownType; + const generatorType = createGeneratorType(yieldType, returnType, nextType, /*isAsyncGenerator*/ false); + if (isAsyncGenerator) { + const asyncGeneratorType = createGeneratorType(yieldType, returnType, nextType, /*isAsyncGenerator*/ true); + return getUnionType([generatorType, asyncGeneratorType]); + } + return generatorType; + } + return getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Yield, contextualReturnType, isAsyncGenerator); + } + } + + 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; + } + + node = node.parent; + } + + return false; + } + + 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; + } + + 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)) { + const returnType = getReturnTypeOfSignature(signature); + const functionFlags = getFunctionFlags(functionDecl); + if (functionFlags & FunctionFlags.Generator) { + return filterType(returnType, t => { + return !!(t.flags & (TypeFlags.AnyOrUnknown | TypeFlags.Void | TypeFlags.InstantiableNonPrimitive)) || checkGeneratorInstantiationAssignabilityToReturnType(t, functionFlags, /*errorNode*/ undefined); + }); + } + if (functionFlags & FunctionFlags.Async) { + return filterType(returnType, t => { + return !!(t.flags & (TypeFlags.AnyOrUnknown | TypeFlags.Void | TypeFlags.InstantiableNonPrimitive)) || !!getAwaitedTypeOfPromise(t); + }); + } + return returnType; + } + const iife = getImmediatelyInvokedFunctionExpression(functionDecl); + if (iife) { + return getContextualType(iife, contextFlags); + } + 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; + } + + // 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); + + 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 getContextualTypeForDecorator(decorator: Decorator): Type | undefined { + const signature = getDecoratorCallSignature(decorator); + return signature ? getOrCreateTypeFromSignature(signature) : undefined; + } + + function getContextualTypeForSubstitutionExpression(template: TemplateExpression, substitutionExpression: Expression) { + if (template.parent.kind === SyntaxKind.TaggedTemplateExpression) { + return getContextualTypeForArgument(template.parent as TaggedTemplateExpression, substitutionExpression); + } + + 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; + } + } + + /** + * 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 (canHaveSymbol(e) && 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; + + function tryGetPrivateIdentifierPropertyOfType(type: Type, id: PrivateIdentifier) { + const lexicallyScopedSymbol = lookupSymbolForPrivateIdentifierDeclaration(id.escapedText, id); + return lexicallyScopedSymbol && getPrivateIdentifierPropertyOfType(type, lexicallyScopedSymbol); + } + } + + // 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 (!canHaveSymbol(binaryExpression.left) || !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, /*nameNotFoundMessage*/ undefined, /*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) || decl === binaryExpression.left ? 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 = canHaveSymbol(binaryExpression.left) ? binaryExpression.left.symbol?.valueDeclaration : undefined; + } + 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 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, /*nameNotFoundMessage*/ undefined, /*isUse*/ true, /*excludeGlobals*/ true); + return isThisInitializedDeclaration(symbol?.valueDeclaration); + } + + 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, /*includeClassComputedPropertyName*/ false))) { + return undefined; + } + const thisType = checkThisExpression(thisAccess.expression); + const nameStr = getElementOrPropertyAccessName(thisAccess); + return nameStr !== undefined && getTypeOfPropertyOfContextualType(thisType, nameStr) || undefined; + } + + function isCircularMappedProperty(symbol: Symbol) { + return !!(getCheckFlags(symbol) & CheckFlags.Mapped && !(symbol as MappedSymbol).links.type && findResolutionCycleStartIndex(symbol, TypeSystemPropertyName.Type) >= 0); + } + + 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); + } + } + else if (t.flags & TypeFlags.StructuredType) { + const prop = getPropertyOfType(t, name); + if (prop) { + return isCircularMappedProperty(prop) ? undefined : removeMissingType(getTypeOfSymbol(prop), !!(prop.flags & SymbolFlags.Optional)); + } + if (isTupleType(t) && isNumericLiteralName(name) && +name >= 0) { + const restType = getElementTypeOfSliceOfTupleType(t, t.target.fixedLength, /*endSkipCount*/ 0, /*writing*/ false, /*noReductions*/ true); + if (restType) { + return restType; + } + } + 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 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 = getSymbolOfDeclaration(element); + return getTypeOfPropertyOfContextualType(type, symbol.escapedName, getSymbolLinks(symbol).nameType); + } + if (hasDynamicName(element)) { + const name = getNameOfDeclaration(element); + if (name && isComputedPropertyName(name)) { + const exprType = checkExpression(name.expression); + const propType = isTypeUsableAsPropertyName(exprType) && getTypeOfPropertyOfContextualType(type, getPropertyNameFromType(exprType)); + if (propType) { + return propType; + } + } + } + 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 getSpreadIndices(elements: readonly Node[]) { + let first, last; + for (let i = 0; i < elements.length; i++) { + if (isSpreadElement(elements[i])) { + first ??= i; + last = i; + } + } + return { first, last }; + } + + function getContextualTypeForElementExpression(type: Type | undefined, index: number, length?: number, firstSpreadIndex?: number, lastSpreadIndex?: number): Type | undefined { + return type && mapType(type, t => { + if (isTupleType(t)) { + // If index is before any spread element and within the fixed part of the contextual tuple type, return + // the type of the contextual tuple element. + if ((firstSpreadIndex === undefined || index < firstSpreadIndex) && index < t.target.fixedLength) { + return removeMissingType(getTypeArguments(t)[index], !!(t.target.elementFlags[index] && ElementFlags.Optional)); + } + // When the length is known and the index is after all spread elements we compute the offset from the element + // to the end and the number of ending fixed elements in the contextual tuple type. + const offset = length !== undefined && (lastSpreadIndex === undefined || index > lastSpreadIndex) ? length - index : 0; + const fixedEndLength = offset > 0 && (t.target.combinedFlags & ElementFlags.Variable) ? getEndElementCount(t.target, ElementFlags.Fixed) : 0; + // If the offset is within the ending fixed part of the contextual tuple type, return the type of the contextual + // tuple element. + if (offset > 0 && offset <= fixedEndLength) { + return getTypeArguments(t)[getTypeReferenceArity(t) - offset]; + } + // Return a union of the possible contextual element types with no subtype reduction. + return getElementTypeOfSliceOfTupleType(t, firstSpreadIndex === undefined ? t.target.fixedLength : Math.min(t.target.fixedLength, firstSpreadIndex), length === undefined || lastSpreadIndex === undefined ? fixedEndLength : Math.min(fixedEndLength, length - lastSpreadIndex), /*writing*/ false, /*noReductions*/ true); + } + // If element index is known and a contextual property with that name exists, return it. Otherwise return the + // iterated or element type of the contextual type. + return (!firstSpreadIndex || index < firstSpreadIndex) && getTypeOfPropertyOfContextualType(t, "" + index as __String) || + 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.attributes, 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 t; + } + }, /*noReductions*/ true)); + } + + 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 getTypeOfPropertyOfContextualType(attributesType, getEscapedTextOfJsxAttributeName(attribute.name)); + } + else { + return getContextualType(attribute.parent, contextFlags); + } + } + + // 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.TemplateExpression: + 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 discriminateContextualTypeByObjectMembers(node: ObjectLiteralExpression, contextualType: UnionType) { + const key = `D${getNodeId(node)},${getTypeId(contextualType)}`; + return getCachedType(key) ?? setCachedType( + key, + getMatchingUnionConstituentForObjectLiteral(contextualType, node) ?? discriminateTypeByDiscriminableItems( + contextualType, + concatenate( + map( + filter(node.properties, (p): p is PropertyAssignment | ShorthandPropertyAssignment => { + if (!p.symbol) { + return false; + } + if (p.kind === SyntaxKind.PropertyAssignment) { + return isPossiblyDiscriminantValue(p.initializer) && isDiscriminantProperty(contextualType, p.symbol.escapedName); + } + if (p.kind === SyntaxKind.ShorthandPropertyAssignment) { + return isDiscriminantProperty(contextualType, p.symbol.escapedName); + } + return false; + }), + prop => ([() => getContextFreeTypeOfExpression(prop.kind === SyntaxKind.PropertyAssignment ? prop.initializer : prop.name), prop.symbol.escapedName] as const), + ), + 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 const, + ), + ), + isTypeAssignableTo, + ), + ); + } + + function discriminateContextualTypeByJSXAttributes(node: JsxAttributes, contextualType: UnionType) { + const key = `D${getNodeId(node)},${getTypeId(contextualType)}`; + const cached = getCachedType(key); + if (cached) return cached; + const jsxChildrenPropertyName = getJsxElementChildrenPropertyName(getJsxNamespaceAt(node)); + return setCachedType( + key, + 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 const), + ), + map( + filter(getPropertiesOfType(contextualType), s => { + if (!(s.flags & SymbolFlags.Optional) || !node?.symbol?.members) { + return false; + } + const element = node.parent.parent; + if (s.escapedName === jsxChildrenPropertyName && isJsxElement(element) && getSemanticJsxChildren(element.children).length) { + return false; + } + return !node.symbol.members.has(s.escapedName) && isDiscriminantProperty(contextualType, s.escapedName); + }), + s => [() => undefinedType, s.escapedName] as const, + ), + ), + isTypeAssignableTo, + ), + ); + } + + // 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, + // When obtaining apparent type of *contextual* type we don't want to get apparent type of mapped types. + // That would evaluate mapped types with array or tuple type constraints too eagerly + // and thus it would prevent `getTypeOfPropertyOfContextualType` from obtaining per-position contextual type for elements of array literal expressions. + // Apparent type of other mapped types is already the mapped type itself so we can just avoid calling `getApparentType` here for all mapped types. + t => getObjectFlags(t) & ObjectFlags.Mapped ? t : getApparentType(t), + /*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; + } + } + 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; + } + + /** + * 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; + } + // Cached contextual types are obtained with no ContextFlags, so we can only consult them for + // requests with no ContextFlags. + const index = findContextualNode(node, /*includeCaches*/ !contextFlags); + if (index >= 0) { + return contextualTypes[index]; + } + 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 | Decorator, node); + case SyntaxKind.Decorator: + return getContextualTypeForDecorator(parent as Decorator); + case SyntaxKind.TypeAssertionExpression: + case SyntaxKind.AsExpression: + return isConstTypeReference((parent as AssertionExpression).type) ? getContextualType(parent as AssertionExpression, contextFlags) : 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); + const elementIndex = indexOfNode(arrayLiteral.elements, node); + const spreadIndices = getNodeLinks(arrayLiteral).spreadIndices ??= getSpreadIndices(arrayLiteral.elements); + return getContextualTypeForElementExpression(type, elementIndex, arrayLiteral.elements.length, spreadIndices.first, spreadIndices.last); + } + 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: { + if (isInJSFile(parent)) { + if (isJSDocSatisfiesExpression(parent)) { + return getTypeFromTypeNode(getJSDocSatisfiesExpressionType(parent)); + } + // Like in `checkParenthesizedExpression`, an `/** @type {xyz} */` comment before a parenthesized expression acts as a type cast. + const typeTag = getJSDocTypeTag(parent); + if (typeTag && !isConstTypeReference(typeTag.typeExpression.type)) { + return getTypeFromTypeNode(typeTag.typeExpression.type); + } + } + return getContextualType(parent as ParenthesizedExpression, contextFlags); + } + 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); + case SyntaxKind.ImportAttribute: + return getContextualImportAttributeType(parent as ImportAttribute); + } + return undefined; + } + + function pushCachedContextualType(node: Expression) { + pushContextualType(node, getContextualType(node, /*contextFlags*/ undefined), /*isCache*/ true); + } + + function pushContextualType(node: Expression, type: Type | undefined, isCache: boolean) { + contextualTypeNodes[contextualTypeCount] = node; + contextualTypes[contextualTypeCount] = type; + contextualIsCache[contextualTypeCount] = isCache; + contextualTypeCount++; + } + + function popContextualType() { + contextualTypeCount--; + } + + function findContextualNode(node: Node, includeCaches: boolean) { + for (let i = contextualTypeCount - 1; i >= 0; i--) { + if (node === contextualTypeNodes[i] && (includeCaches || !contextualIsCache[i])) { + return i; + } + } + return -1; + } + + function pushInferenceContext(node: Node, inferenceContext: InferenceContext | undefined) { + inferenceContextNodes[inferenceContextCount] = node; + inferenceContexts[inferenceContextCount] = inferenceContext; + inferenceContextCount++; + } + + function popInferenceContext() { + inferenceContextCount--; + } + + function getInferenceContext(node: Node) { + for (let i = inferenceContextCount - 1; i >= 0; i--) { + if (isNodeDescendantOf(node, inferenceContextNodes[i])) { + return inferenceContexts[i]; + } + } + } + + function getContextualImportAttributeType(node: ImportAttribute) { + return getTypeOfPropertyOfContextualType(getGlobalImportAttributesType(/*reportErrors*/ false), getNameFromImportAttribute(node)); + } + + function getContextualJsxElementAttributesType(node: JsxOpeningLikeElement, contextFlags: ContextFlags | undefined) { + if (isJsxOpeningElement(node) && contextFlags !== ContextFlags.Completions) { + const index = findContextualNode(node.parent, /*includeCaches*/ !contextFlags); + if (index >= 0) { + // 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 contextualTypes[index]; + } + } + return getContextualTypeForArgumentAtIndex(node, 0); + } + + function getEffectiveFirstArgumentForJsxSignature(signature: Signature, node: JsxOpeningLikeElement) { + return getJsxReferenceKind(node) !== JsxReferenceKind.Component + ? getJsxPropsTypeFromCallSignature(signature, node) + : getJsxPropsTypeFromClassType(signature, node); + } + + 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 instanceType = getReturnTypeOfSignature(sig); + return isTypeAny(instanceType) ? instanceType : getTypeOfPropertyOfType(instanceType, forcedLookupLocation); + } + + function getStaticTypeOfReferencedJsxConstructor(context: JsxOpeningLikeElement) { + if (isJsxIntrinsicTagName(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; + } + const fakeSignature = createSignatureForJSXIntrinsic(context, result); + return getOrCreateTypeFromSignature(fakeSignature); + } + return tagType; + } + + function getJsxManagedAttributesFromLocatedAttributes(context: JsxOpeningLikeElement, ns: Symbol, attributesType: Type) { + const managedSym = getJsxLibraryManagedAttributes(ns); + if (managedSym) { + const ctorType = getStaticTypeOfReferencedJsxConstructor(context); + const result = instantiateAliasOrInterfaceWithDefaults(managedSym, isInJSFile(context), ctorType, attributesType); + if (result) { + return result; + } + } + return attributesType; + } + + 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 unknownType; + } + + attributesType = getJsxManagedAttributesFromLocatedAttributes(context, ns, attributesType); + + 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)); + } + // 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; + } + } + + function getIntersectedSignatures(signatures: readonly Signature[]) { + return getStrictOptionValue(compilerOptions, "noImplicitAny") + ? reduceLeft( + signatures, + (left: Signature | undefined, 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 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.links.type = isRestParam ? createArrayType(unionParamType) : unionParamType; + params[i] = paramSymbol; + } + if (needsExtraRestElement) { + const restParamSymbol = createSymbol(SymbolFlags.FunctionScopedVariable, "args" as __String); + restParamSymbol.links.type = createArrayType(getTypeAtPosition(shorter, longestCount)); + if (shorter === right) { + restParamSymbol.links.type = instantiateType(restParamSymbol.links.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, + ); + 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 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; + } + } + if (target.parameters.length && parameterIsThisKeyword(target.parameters[0])) { + targetParameterCount--; + } + 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; + } + + // 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 checkGrammarRegularExpressionLiteral(node: RegularExpressionLiteral) { + const sourceFile = getSourceFileOfNode(node); + if (!hasParseDiagnostics(sourceFile) && !node.isUnterminated) { + let lastError: DiagnosticWithLocation | undefined; + scanner ??= createScanner(ScriptTarget.ESNext, /*skipTrivia*/ true); + scanner.setScriptTarget(sourceFile.languageVersion); + scanner.setLanguageVariant(sourceFile.languageVariant); + scanner.setOnError((message, length, arg0) => { + // For providing spelling suggestions + const start = scanner!.getTokenEnd(); + if (message.category === DiagnosticCategory.Message && lastError && start === lastError.start && length === lastError.length) { + const error = createDetachedDiagnostic(sourceFile.fileName, sourceFile.text, start, length, message, arg0); + addRelatedInfo(lastError, error); + } + else if (!lastError || start !== lastError.start) { + lastError = createFileDiagnostic(sourceFile, start, length, message, arg0); + diagnostics.add(lastError); + } + }); + scanner.setText(sourceFile.text, node.pos, node.end - node.pos); + try { + scanner.scan(); + Debug.assert(scanner.reScanSlashToken(/*reportErrors*/ true) === SyntaxKind.RegularExpressionLiteral, "Expected scanner to rescan RegularExpressionLiteral"); + return !!lastError; + } + finally { + scanner.setText(""); + scanner.setOnError(/*onError*/ undefined); + } + } + return false; + } + + function checkRegularExpressionLiteral(node: RegularExpressionLiteral) { + const nodeLinks = getNodeLinks(node); + if (!(nodeLinks.flags & NodeCheckFlags.TypeChecked)) { + nodeLinks.flags |= NodeCheckFlags.TypeChecked; + addLazyDiagnostic(() => checkGrammarRegularExpressionLiteral(node)); + } + return globalRegExpType; + } + + function checkSpreadExpression(node: SpreadElement, checkMode?: CheckMode): Type { + if (languageVersion < LanguageFeatureMinimumTarget.SpreadElements) { + checkExternalEmitHelpers(node, compilerOptions.downlevelIteration ? ExternalEmitHelpers.SpreadIncludes : ExternalEmitHelpers.SpreadArray); + } + + const arrayOrIterableType = checkExpression(node.expression, checkMode); + return checkIteratedTypeOrElementType(IterationUse.Spread, arrayOrIterableType, undefinedType, node.expression); + } + + 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 isSpreadIntoCallOrNew(node: ArrayLiteralExpression) { + const parent = walkUpParenthesizedExpressions(node.parent); + return isSpreadElement(parent) && isCallOrNewExpression(parent.parent); + } + + 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[] = []; + pushCachedContextualType(node); + const inDestructuringPattern = isAssignmentTarget(node); + const inConstContext = isConstContext(node); + const contextualType = getApparentTypeOfContextualType(node, /*contextFlags*/ undefined); + const inTupleContext = isSpreadIntoCallOrNew(node) || !!contextualType && someType(contextualType, t => isTupleLikeType(t) || isGenericMappedType(t) && !t.nameType && !!getHomomorphicTypeVariable(t.target as MappedType || t)); + + let hasOmittedExpression = false; + for (let i = 0; i < elementCount; i++) { + const e = elements[i]; + if (e.kind === SyntaxKind.SpreadElement) { + if (languageVersion < LanguageFeatureMinimumTarget.SpreadElements) { + 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); + } + } + else if (exactOptionalPropertyTypes && e.kind === SyntaxKind.OmittedExpression) { + hasOmittedExpression = true; + elementTypes.push(undefinedOrMissingType); + elementFlags.push(ElementFlags.Optional); + } + else { + const type = checkExpressionForMutableLocation(e, checkMode, forceTuple); + elementTypes.push(addOptionality(type, /*isProperty*/ true, hasOmittedExpression)); + elementFlags.push(hasOmittedExpression ? ElementFlags.Optional : ElementFlags.Required); + if (inTupleContext && 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); + } + } + } + popContextualType(); + if (inDestructuringPattern) { + return createTupleType(elementTypes, elementFlags); + } + if (forceTuple || inConstContext || inTupleContext) { + return createArrayLiteralType(createTupleType(elementTypes, elementFlags, /*readonly*/ inConstContext && !(contextualType && someType(contextualType, isMutableArrayLikeType)))); + } + 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; + } + + 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 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 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 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])); + } + } + 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 checkObjectLiteral(node: ObjectLiteralExpression, checkMode: CheckMode = CheckMode.Normal): 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; + + pushCachedContextualType(node); + 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 = isInJavascript ? getJSDocEnumTag(node) : undefined; + const isJSObjectLiteral = !contextualType && isInJavascript && !enumTag; + let objectFlags: ObjectFlags = ObjectFlags.FreshLiteral; + 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 = getSymbolOfDeclaration(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.links.nameType = nameType; + } + + 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; + } + else if (!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)); + } + } + + prop.declarations = member.declarations; + prop.parent = member.parent; + if (member.valueDeclaration) { + prop.valueDeclaration = member.valueDeclaration; + } + + prop.links.type = type; + prop.links.target = member; + member = prop; + allPropertiesTable?.set(prop.escapedName, prop); + + if ( + contextualType && 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 < LanguageFeatureMinimumTarget.ObjectAssign) { + 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, checkMode & CheckMode.Inferential)); + 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; + } + 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); + } + popContextualType(); + + // 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 || tryCast(prop, isTransientSymbol)?.links.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); + } + } + } + } + + if (isErrorType(spread)) { + return errorType; + } + + 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); + } + + 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; + } + if (patternWithComputedProperties) { + result.objectFlags |= ObjectFlags.ObjectLiteralPatternWithComputedProperties; + } + if (inDestructuringPattern) { + result.pattern = node; + } + return result; + } + } + + 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 checkJsxSelfClosingElementDeferred(node: JsxSelfClosingElement) { + checkJsxOpeningLikeElementOrOpeningFragment(node); + } + + function checkJsxSelfClosingElement(node: JsxSelfClosingElement, _checkMode: CheckMode | undefined): Type { + checkNodeDeferred(node); + return getJsxElementTypeAt(node) || anyType; + } + + function checkJsxElementDeferred(node: JsxElement) { + // Check attributes + checkJsxOpeningLikeElementOrOpeningFragment(node.openingElement); + + // Perform resolution on the closing tag so that rename/go to definition/etc work + if (isJsxIntrinsicTagName(node.closingElement.tagName)) { + getIntrinsicTagSymbol(node.closingElement); + } + else { + checkExpression(node.closingElement.tagName); + } + + checkJsxChildren(node); + } + + function checkJsxElement(node: JsxElement, _checkMode: CheckMode | undefined): Type { + checkNodeDeferred(node); + + return getJsxElementTypeAt(node) || anyType; + } + + function checkJsxFragment(node: JsxFragment): Type { + checkJsxOpeningLikeElementOrOpeningFragment(node.openingFragment); + + // 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, + ); + } + + checkJsxChildren(node); + return getJsxElementTypeAt(node) || anyType; + } + + function isHyphenatedJsxName(name: string | __String) { + return (name as string).includes("-"); + } + + /** + * Returns true iff React would emit this tag name as a string rather than an identifier or qualified name + */ + function isJsxIntrinsicTagName(tagName: Node): tagName is Identifier | JsxNamespacedName { + return isIdentifier(tagName) && isIntrinsicJsxName(tagName.escapedText) || isJsxNamespacedName(tagName); + } + + function checkJsxAttribute(node: JsxAttribute, checkMode?: CheckMode) { + return node.initializer + ? checkExpressionForMutableLocation(node.initializer, checkMode) + : trueType; // is sugar for + } + + /** + * 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 = CheckMode.Normal) { + const attributes = openingLikeElement.attributes; + const contextualType = 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.links.type = exprType; + attributeSymbol.links.target = member; + attributesTable.set(attributeSymbol.escapedName, attributeSymbol); + allAttributesTable?.set(attributeSymbol.escapedName, attributeSymbol); + if (getEscapedTextOfJsxAttributeName(attributeDecl.name) === jsxChildrenPropertyName) { + explicitlySpecifyChildrenAttribute = true; + } + if (contextualType) { + const prop = getPropertyOfType(contextualType, member.escapedName); + if (prop && prop.declarations && isDeprecatedSymbol(prop) && isIdentifier(attributeDecl.name)) { + addDeprecatedSuggestion(attributeDecl.name, prop.declarations, attributeDecl.name.escapedText as string); + } + } + if (contextualType && checkMode & CheckMode.Inferential && !(checkMode & CheckMode.SkipContextSensitive) && isContextSensitive(attributeDecl)) { + const inferenceContext = getInferenceContext(attributes); + Debug.assert(inferenceContext); // In CheckMode.Inferential we should always have an inference context + const inferenceNode = (attributeDecl.initializer as JsxExpression).expression!; + addIntraExpressionInferenceSite(inferenceContext, inferenceNode, exprType); + } + } + 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(checkExpression(attributeDecl.expression, checkMode & CheckMode.Inferential)); + 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; + } + } + } + + if (!hasSpreadAnyType) { + if (attributesTable.size > 0) { + spread = getSpreadType(spread, createJsxAttributesType(), attributes.symbol, objectFlags, /*readonly*/ false); + } + } + + // 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 && getSemanticJsxChildren(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.links.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); + } + } + + if (hasSpreadAnyType) { + return anyType; + } + if (typeToIntersect && spread !== emptyJsxObjectType) { + return getIntersectionType([typeToIntersect, spread]); + } + return typeToIntersect || (spread === emptyJsxObjectType ? createJsxAttributesType() : spread); + + /** + * 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 |= ObjectFlags.FreshLiteral; + const result = createAnonymousType(attributes.symbol, attributesTable, emptyArray, emptyArray, emptyArray); + result.objectFlags |= objectFlags | ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral; + return result; + } + } + + 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); + } + } + else if (child.kind === SyntaxKind.JsxExpression && !child.expression) { + continue; // empty jsx expressions don't *really* count as present children + } + else { + childrenTypes.push(checkExpressionForMutableLocation(child, checkMode)); + } + } + 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)); + } + } + } + } + + /** + * 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); + } + + 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) && !isJsxNamespacedName(node.tagName)) return Debug.fail(); + const propName = isJsxNamespacedName(node.tagName) ? getEscapedTextOfJsxNamespacedName(node.tagName) : node.tagName.escapedText; + const intrinsicProp = getPropertyOfType(intrinsicElementsType, propName); + if (intrinsicProp) { + links.jsxFlags |= JsxFlags.IntrinsicNamedElement; + return links.resolvedSymbol = intrinsicProp; + } + + // Intrinsic string indexer case + const indexSymbol = getApplicableIndexSymbol(intrinsicElementsType, getStringLiteralType(unescapeLeadingUnderscores(propName))); + if (indexSymbol) { + links.jsxFlags |= JsxFlags.IntrinsicIndexedElement; + return links.resolvedSymbol = indexSymbol; + } + + if (getTypeOfPropertyOrIndexSignatureOfType(intrinsicElementsType, propName)) { + links.jsxFlags |= JsxFlags.IntrinsicIndexedElement; + return links.resolvedSymbol = intrinsicElementsType.symbol; + } + + // Wasn't found + error(node, Diagnostics.Property_0_does_not_exist_on_type_1, intrinsicTagNameToString(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 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; + } + 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_nodenext_or_to_add_aliases_to_the_paths_option + : Diagnostics.Cannot_find_module_0_or_its_corresponding_type_declarations; + const specifier = getJSXRuntimeImportSpecifier(file, runtimeImportSpecifier); + const mod = resolveExternalModule(specifier || location!, runtimeImportSpecifier, errorMessage, location!); + const result = mod && mod !== unknownSymbol ? getMergedSymbol(resolveSymbol(mod)) : undefined; + if (links) { + links.jsxImplicitImportContainer = result || false; + } + return result; + } + + 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); + + if (!resolvedNamespace || resolvedNamespace === unknownSymbol) { + const namespaceName = getJsxNamespace(location); + resolvedNamespace = resolveName(location, namespaceName, SymbolFlags.Namespace, /*nameNotFoundMessage*/ undefined, /*isUse*/ false); + } + + if (resolvedNamespace) { + const candidate = resolveSymbol(getSymbol(getExportsOfSymbol(resolveSymbol(resolvedNamespace)), JsxNames.JSX, SymbolFlags.Namespace)); + if (candidate && candidate !== unknownSymbol) { + if (links) { + links.jsxNamespace = candidate; + } + return candidate; + } + } + if (links) { + links.jsxNamespace = false; + } + } + // JSX global fallback + const s = resolveSymbol(getGlobalSymbol(JsxNames.JSX, SymbolFlags.Namespace, /*diagnostic*/ undefined)); + if (s === unknownSymbol) { + return undefined!; // TODO: GH#18217 + } + return s!; // TODO: GH#18217 + } + + /** + * 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); + } + + function getJsxElementTypeSymbol(jsxNamespace: Symbol) { + // JSX.ElementType [symbol] + return jsxNamespace && getSymbol(jsxNamespace.exports!, JsxNames.ElementType, 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 { + 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 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; + } + + 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); + } + } + 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 generateInitialErrorChain(): DiagnosticMessageChain { + const componentName = getTextOfNode(openingLikeElement.tagName); + return chainDiagnosticMessages(/*details*/ undefined, Diagnostics._0_cannot_be_used_as_a_JSX_component, componentName); + } + } + + /** + * 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(isJsxIntrinsicTagName(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) { + const propName = isJsxNamespacedName(node.tagName) ? getEscapedTextOfJsxNamespacedName(node.tagName) : node.tagName.escapedText; + return links.resolvedJsxElementAttributesType = getApplicableIndexInfoForName(getJsxType(JsxNames.IntrinsicElements, node), propName)?.type || errorType; + } + else { + return links.resolvedJsxElementAttributesType = errorType; + } + } + return links.resolvedJsxElementAttributesType; + } + + function getJsxElementClassTypeAt(location: Node): Type | undefined { + const type = getJsxType(JsxNames.ElementClass, location); + if (isErrorType(type)) return undefined; + return type; + } + + function getJsxElementTypeAt(location: Node): Type { + return getJsxType(JsxNames.Element, location); + } + + function getJsxStatelessElementTypeAt(location: Node): Type | undefined { + const jsxElementType = getJsxElementTypeAt(location); + if (jsxElementType) { + return getUnionType([jsxElementType, nullType]); + } + } + + function getJsxElementTypeTypeAt(location: Node): Type | undefined { + const ns = getJsxNamespaceAt(location); + if (!ns) return undefined; + const sym = getJsxElementTypeSymbol(ns); + if (!sym) return undefined; + const type = instantiateAliasOrInterfaceWithDefaults(sym, isInJSFile(location)); + if (!type || isErrorType(type)) return undefined; + return type; + } + + function instantiateAliasOrInterfaceWithDefaults(managedSym: Symbol, inJs: boolean, ...typeArguments: Type[]) { + const declaredManagedType = getDeclaredTypeOfSymbol(managedSym); // fetches interface type, or initializes symbol links type parmaeters + if (managedSym.flags & SymbolFlags.TypeAlias) { + const params = getSymbolLinks(managedSym).typeParameters; + if (length(params) >= typeArguments.length) { + const args = fillMissingTypeArguments(typeArguments, params, typeArguments.length, inJs); + return length(args) === 0 ? declaredManagedType : getTypeAliasInstantiation(managedSym, args); + } + } + if (length((declaredManagedType as GenericType).typeParameters) >= typeArguments.length) { + const args = fillMissingTypeArguments(typeArguments, (declaredManagedType as GenericType).typeParameters, typeArguments.length, inJs); + return createTypeReference(declaredManagedType as GenericType, args); + } + return undefined; + } + + /** + * 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 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 (getJsxElementTypeAt(errorNode) === undefined) { + if (noImplicitAny) { + error(errorNode, Diagnostics.JSX_element_implicitly_has_type_any_because_the_global_type_JSX_Element_does_not_exist); + } + } + } + + function checkJsxOpeningLikeElementOrOpeningFragment(node: JsxOpeningLikeElement | JsxOpeningFragment) { + const isNodeOpeningLikeElement = isJsxOpeningLikeElement(node); + + if (isNodeOpeningLikeElement) { + checkGrammarJsxElement(node); + } + + checkJsxPreconditions(node); + + markLinkedReferences(node, ReferenceHint.Jsx); + + if (isNodeOpeningLikeElement) { + const jsxOpeningLikeNode = node; + const sig = getResolvedSignature(jsxOpeningLikeNode); + checkDeprecatedSignature(sig, node); + + const elementTypeConstraint = getJsxElementTypeTypeAt(jsxOpeningLikeNode); + if (elementTypeConstraint !== undefined) { + const tagName = jsxOpeningLikeNode.tagName; + const tagType = isJsxIntrinsicTagName(tagName) + ? getStringLiteralType(intrinsicTagNameToString(tagName)) + : checkExpression(tagName); + checkTypeRelatedTo(tagType, elementTypeConstraint, assignableRelation, tagName, Diagnostics.Its_type_0_is_not_a_valid_JSX_element_type, () => { + const componentName = getTextOfNode(tagName); + return chainDiagnosticMessages(/*details*/ undefined, Diagnostics._0_cannot_be_used_as_a_JSX_component, componentName); + }); + } + else { + checkJsxReturnAssignableToAppropriateBound(getJsxReferenceKind(jsxOpeningLikeNode), getReturnTypeOfSignature(sig), jsxOpeningLikeNode); + } + } + } + + /** + * 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 (targetType.flags & TypeFlags.Substitution) { + return isKnownProperty((targetType as SubstitutionType).baseType, name, isComparingJsxAttributes); + } + 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 isExcessPropertyCheckTarget(type: Type): boolean { + return !!(type.flags & TypeFlags.Object && !(getObjectFlags(type) & ObjectFlags.ObjectLiteralPatternWithComputedProperties) || + type.flags & TypeFlags.NonPrimitive || + type.flags & TypeFlags.Substitution && isExcessPropertyCheckTarget((type as SubstitutionType).baseType) || + type.flags & TypeFlags.Union && some((type as UnionType).types, isExcessPropertyCheckTarget) || + type.flags & TypeFlags.Intersection && every((type as IntersectionType).types, isExcessPropertyCheckTarget)); + } + + 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; + } + } + + function getDeclarationNodeFlagsFromSymbol(s: Symbol): NodeFlags { + return s.valueDeclaration ? getCombinedNodeFlagsCached(s.valueDeclaration) : 0; + } + + /** + * 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 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); + } + + /** + * 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.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. + if (errorNode) { + error(errorNode, Diagnostics.Abstract_method_0_in_class_1_cannot_be_accessed_via_super_expression, symbolToString(prop), typeToString(getDeclaringClass(prop)!)); + } + return false; + } + // A class field cannot be accessed via super.* from a derived class. + // This is true for both [[Set]] (old) and [[Define]] (ES spec) semantics. + if (!(flags & ModifierFlags.Static) && prop.declarations?.some(isClassInstanceProperty)) { + if (errorNode) { + error(errorNode, Diagnostics.Class_field_0_defined_by_the_parent_class_is_not_accessible_in_the_child_class_via_super, symbolToString(prop)); + } + 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; + } + } + + // Public properties are otherwise accessible. + if (!(flags & ModifierFlags.NonPublicAccessibilityModifier)) { + return true; + } + + // 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 false; + } + return true; + } + + // 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; + } + + // 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(getSymbolOfDeclaration(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)) { + 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 false; + } + 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; + } + return undefined; + } + + function getThisParameterFromNodeContext(node: Node) { + const thisContainer = getThisContainer(node, /*includeArrowFunctions*/ false, /*includeClassComputedPropertyName*/ false); + return thisContainer && isFunctionLike(thisContainer) ? getThisParameter(thisContainer) : undefined; + } + + function symbolHasNonMethodDeclaration(symbol: Symbol) { + return !!forEachProperty(symbol, prop => !(prop.flags & SymbolFlags.Method)); + } + + function checkNonNullExpression(node: Expression | QualifiedName) { + return checkNonNullType(checkExpression(node), node); + } + + function isNullableType(type: Type) { + return hasTypeFacts(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; + } + 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, + ); + } + } + + 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, TypeFacts.IsUndefinedOrNull); + if (facts & TypeFacts.IsUndefinedOrNull) { + reportError(node, facts); + const t = getNonNullableType(type); + return t.flags & (TypeFlags.Nullable | TypeFlags.Never) ? errorType : t; + } + return type; + } + + 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; + } + + function checkPropertyAccessExpression(node: PropertyAccessExpression, checkMode: CheckMode | undefined, writeOnly?: boolean) { + return node.flags & NodeFlags.OptionalChain ? checkPropertyAccessChain(node as PropertyAccessChain, checkMode) : + checkPropertyAccessExpressionOrQualifiedName(node, node.expression, checkNonNullExpression(node.expression), node.name, checkMode, writeOnly); + } + + 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 isMethodAccessForCall(node: Node) { + while (node.parent.kind === SyntaxKind.ParenthesizedExpression) { + node = node.parent; + } + return isCallOrNewExpression(node.parent) && node.parent.expression === node; + } + + // Lookup the private identifier lexically. + function lookupSymbolForPrivateIdentifierDeclaration(propName: __String, location: Node): Symbol | undefined { + for (let containingClass = getContainingClassExcludingClassDecorators(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); + } + + 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)); + } + } + + return false; + } + + function checkPrivateIdentifierExpression(privId: PrivateIdentifier): Type { + checkGrammarPrivateIdentifierExpression(privId); + const symbol = getSymbolForPrivateIdentifierExpression(privId); + if (symbol) { + markPropertyAsReferenced(symbol, /*nodeForCheckWriteOnly*/ undefined, /*isSelfTypeAccess*/ false); + } + return anyType; + } + + function getSymbolForPrivateIdentifierExpression(privId: PrivateIdentifier): Symbol | undefined { + if (!isExpressionNode(privId)) { + return undefined; + } + + const links = getNodeLinks(privId); + if (links.resolvedSymbol === undefined) { + links.resolvedSymbol = lookupSymbolForPrivateIdentifierDeclaration(privId.escapedText, privId); + } + return links.resolvedSymbol; + } + + function getPrivateIdentifierPropertyOfType(leftType: Type, lexicallyScopedIdentifier: Symbol): Symbol | undefined { + return getPropertyOfType(leftType, lexicallyScopedIdentifier.escapedName); + } + + 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), + ); + + 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; + } + + function isThisPropertyAccessInConstructor(node: ElementAccessExpression | PropertyAccessExpression | QualifiedName, prop: Symbol) { + return (isConstructorDeclaredProperty(prop) || isThisProperty(node) && isAutoTypedProperty(prop)) + && getThisContainer(node, /*includeArrowFunctions*/ true, /*includeClassComputedPropertyName*/ false) === getDeclaringConstructor(prop); + } + + function checkPropertyAccessExpressionOrQualifiedName(node: PropertyAccessExpression | QualifiedName, left: Expression | QualifiedName, leftType: Type, right: Identifier | PrivateIdentifier, checkMode: CheckMode | undefined, writeOnly?: boolean) { + 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 < LanguageFeatureMinimumTarget.PrivateNamesAndClassStaticBlocks || + languageVersion < LanguageFeatureMinimumTarget.ClassAndClassElementDecorators || + !useDefineForClassFields + ) { + if (assignmentKind !== AssignmentKind.None) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.ClassPrivateFieldSet); + } + if (assignmentKind !== AssignmentKind.Definite) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.ClassPrivateFieldGet); + } + } + + 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)); + } + if (isAnyLike) { + if (lexicallyScopedSymbol) { + return isErrorType(apparentType) ? errorType : apparentType; + } + if (getContainingClassExcludingClassDecorators(right) === undefined) { + grammarErrorOnNode(right, Diagnostics.Private_identifiers_are_not_allowed_outside_class_bodies); + return anyType; + } + } + + prop = lexicallyScopedSymbol && getPrivateIdentifierPropertyOfType(leftType, lexicallyScopedSymbol); + if (prop === undefined) { + // Check for private-identifier-specific shadowing and lexical-scoping errors. + if (checkPrivateIdentifierPropertyAccess(leftType, right, lexicallyScopedSymbol)) { + return errorType; + } + const containingClass = getContainingClassExcludingClassDecorators(right); + if (containingClass && isPlainJsFile(getSourceFileOfNode(containingClass), compilerOptions.checkJs)) { + grammarErrorOnNode(right, Diagnostics.Private_field_0_must_be_declared_in_an_enclosing_class, idText(right)); + } + } + else { + const isSetonlyAccessor = prop.flags & SymbolFlags.SetAccessor && !(prop.flags & SymbolFlags.GetAccessor); + if (isSetonlyAccessor && assignmentKind !== AssignmentKind.Definite) { + error(node, Diagnostics.Private_accessor_was_defined_without_a_getter); + } + } + } + else { + if (isAnyLike) { + if (isIdentifier(left) && parentSymbol) { + markLinkedReferences(node, ReferenceHint.Property, /*propSymbol*/ undefined, leftType); + } + return isErrorType(apparentType) ? errorType : apparentType; + } + prop = getPropertyOfType(apparentType, right.escapedText, /*skipObjectFunctionPropertyAugment*/ isConstEnumObjectType(apparentType), /*includeTypeOnlyMembers*/ node.kind === SyntaxKind.QualifiedName); + } + markLinkedReferences(node, ReferenceHint.Property, prop, leftType); + + 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 = indexInfo.type; + if (compilerOptions.noUncheckedIndexedAccess && getAssignmentTargetKind(node) !== AssignmentKind.Definite) { + propType = getUnionType([propType, missingType]); + } + 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 (indexInfo.declaration && isDeprecatedDeclaration(indexInfo.declaration)) { + addDeprecatedSuggestion(right, [indexInfo.declaration], right.escapedText as string); + } + } + else { + const targetPropSymbol = resolveAliasWithDeprecationCheck(prop, right); + if (isDeprecatedSymbol(targetPropSymbol) && isUncalledFunctionReference(node, targetPropSymbol) && targetPropSymbol.declarations) { + addDeprecatedSuggestion(right, targetPropSymbol.declarations, right.escapedText as string); + } + checkPropertyNotUsedBeforeDeclaration(prop, node, right); + markPropertyAsReferenced(prop, node, isSelfTypeAccess(left, parentSymbol)); + getNodeLinks(node).resolvedSymbol = prop; + checkPropertyAccessibility(node, left.kind === SyntaxKind.SuperKeyword, isWriteAccess(node), 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; + } + + propType = isThisPropertyAccessInConstructor(node, prop) ? autoType : writeOnly || isWriteOnlyAccess(node) ? 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); + const suggestionHasNoExtendsOrDecorators = !suggestion?.valueDeclaration + || !isClassLike(suggestion.valueDeclaration) + || suggestion.valueDeclaration.heritageClauses?.length + || classOrConstructorParameterIsDecorated(/*useLegacyDecorators*/ false, suggestion.valueDeclaration); + return !(file !== declarationFile && !!declarationFile && isGlobalSourceFile(declarationFile)) + && !(excludeClasses && suggestion && suggestion.flags & SymbolFlags.Class && suggestionHasNoExtendsOrDecorators) + && !(!!node && excludeClasses && isPropertyAccessExpression(node) && node.expression.kind === SyntaxKind.ThisKeyword && suggestionHasNoExtendsOrDecorators); + } + } + 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; + } + + let diagnosticMessage; + const declarationName = idText(right); + if ( + isInPropertyInitializerOrClassStaticBlock(node) + && !isOptionalPropertyDeclaration(valueDeclaration) + && !(isAccessExpression(node) && isAccessExpression(node.expression)) + && !isBlockScopedNameDeclaredBeforeUse(valueDeclaration, right) + && !(isMethodDeclaration(valueDeclaration) && getCombinedModifierFlagsCached(valueDeclaration) & ModifierFlags.Static) + && (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); + } + + if (diagnosticMessage) { + addRelatedInfo(diagnosticMessage, createDiagnosticForNode(valueDeclaration, Diagnostics._0_is_declared_here, declarationName)); + } + } + + 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"; + } + }); + } + + /** + * 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; + } + } + } + + function getSuperClass(classType: InterfaceType): Type | undefined { + const x = getBaseTypes(classType); + if (x.length === 0) { + return undefined; + } + return getIntersectionType(x); + } + + 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); + } + 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); + } + } + } + } + const resultDiagnostic = createDiagnosticForNodeFromMessageChain(getSourceFileOfNode(propNode), 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 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 typeFeatures = allFeatures.get(missingName); + return typeFeatures && firstIterator(typeFeatures.keys()); + } + + function getSuggestedLibForNonExistentProperty(missingProperty: string, containingType: Type) { + const container = getApparentType(containingType).symbol; + if (!container) { + return undefined; + } + const containingTypeName = symbolName(container); + const allFeatures = getScriptTargetFeatures(); + const typeFeatures = allFeatures.get(containingTypeName); + if (typeFeatures) { + for (const [libTarget, featuresOfType] of typeFeatures) { + if (contains(featuresOfType, missingProperty)) { + return libTarget; + } + } + } + } + + function getSuggestedSymbolForNonexistentClassMember(name: string, baseType: Type): Symbol | undefined { + return getSpellingSuggestionForName(name, getPropertiesOfType(baseType), SymbolFlags.ClassMember); + } + + 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 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 getSuggestionForSymbolNameLookup(symbols: SymbolTable, name: __String, meaning: SymbolFlags) { + 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); + } + function getSuggestedSymbolForNonexistentSymbol(location: Node | undefined, outerName: __String, meaning: SymbolFlags): Symbol | undefined { + Debug.assert(outerName !== undefined, "outername should always be defined"); + const result = resolveNameForSymbolSuggestion(location, outerName, meaning, /*nameNotFoundMessage*/ undefined, /*isUse*/ false, /*excludeGlobals*/ false); + return result; + } + + function getSuggestedSymbolForNonexistentModule(name: Identifier, targetModule: Symbol): Symbol | undefined { + return targetModule.exports && getSpellingSuggestionForName(idText(name), getExportsOfModuleAsArray(targetModule), SymbolFlags.ModuleMember); + } + + 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; + } + + const suggestedMethod = isAssignmentTarget(expr) ? "set" : "get"; + if (!hasProp(suggestedMethod)) { + return undefined; + } + + let suggestion = tryGetPropertyAccessOrIdentifierToString(expr.expression); + if (suggestion === undefined) { + suggestion = suggestedMethod; + } + else { + suggestion += "." + suggestedMethod; + } + + return 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); + } + + /** + * 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; + } + + if (candidate.flags & meaning) { + return candidateName; + } + + if (candidate.flags & SymbolFlags.Alias) { + const alias = tryResolveAlias(candidate); + if (alias && alias.flags & meaning) { + return 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 (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; + } + } + + (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)); + } + } + + /** + * 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. + } + + function isValidPropertyAccessWithType( + node: PropertyAccessExpression | QualifiedName | ImportTypeNode, + isSuper: boolean, + propertyName: __String, + type: Type, + ): boolean { + // Short-circuiting for improved performance. + if (isTypeAny(type)) { + return true; + } + + const prop = getPropertyOfType(type, propertyName); + return !!prop && isPropertyAccessible(node, isSuper, /*isWrite*/ false, type, prop); + } + + /** + * 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; + } + + // 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 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 getSymbolOfDeclaration(variable); + } + } + else if (initializer.kind === SyntaxKind.Identifier) { + return getResolvedSymbol(initializer as Identifier); + } + 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); + } + + /** + * 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); + } + + 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); + + if (isErrorType(objectType) || objectType === silentNeverType) { + return objectType; + } + + if (isConstEnumObjectType(objectType) && !isStringLiteralLike(indexExpression)) { + error(indexExpression, Diagnostics.A_const_enum_member_can_only_be_accessed_using_a_string_literal); + return errorType; + } + + const effectiveIndexType = isForInVariableForNumericPropertyNames(indexExpression) ? numberType : indexType; + const assignmentTargetKind = getAssignmentTargetKind(node); + let accessFlags: AccessFlags; + if (assignmentTargetKind === AssignmentKind.None) { + accessFlags = AccessFlags.ExpressionPosition; + } + else { + accessFlags = AccessFlags.Writing | (isGenericObjectType(objectType) && !isThisTypeParameter(objectType) ? AccessFlags.NoIndexSignatures : 0); + if (assignmentTargetKind === AssignmentKind.Compound) { + accessFlags |= AccessFlags.ExpressionPosition; + } + } + const indexedAccessType = getIndexedAccessTypeOrUndefined(objectType, effectiveIndexType, accessFlags, node) || errorType; + return checkIndexedAccessIndexType(getFlowTypeOfAccessExpression(node, getNodeLinks(node).resolvedSymbol, indexedAccessType, indexExpression, checkMode), node); + } + + function callLikeExpressionMayHaveTypeArguments(node: CallLikeExpression): node is CallExpression | NewExpression | TaggedTemplateExpression | JsxOpeningElement { + return isCallOrNewExpression(node) || isTaggedTemplateExpression(node) || isJsxOpeningLikeElement(node); + } + + 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 (isBinaryExpression(node)) { + checkExpression(node.left); + } + else if (isCallOrNewExpression(node)) { + forEach(node.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 && getSymbolOfDeclaration(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; + } + + result.splice(spliceIndex, 0, callChainFlags ? getOptionalCallSignature(signature, callChainFlags) : signature); + } + } + + function isSpreadArgument(arg: Expression | undefined): arg is Expression { + return !!arg && (arg.kind === SyntaxKind.SpreadElement || arg.kind === SyntaxKind.SyntheticExpression && (arg as SyntheticExpression).isSpread); + } + + function getSpreadArgumentIndex(args: readonly Expression[]): number { + return findIndex(args, isSpreadArgument); + } + + 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; + } + 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 (node.kind === SyntaxKind.BinaryExpression) { + argCount = 1; + } + 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 + } + 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; + + // If we are missing the close parenthesis, the call is incomplete. + callIsIncomplete = node.arguments.end === node.end; + + // 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)); + } + } + + // Too many arguments implies incorrect arity. + if (!hasEffectiveRestParameter(signature) && argCount > effectiveParameterCount) { + return false; + } + + // 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; + } + } + 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); + } + + function isInstantiatedGenericParameter(signature: Signature, pos: number) { + let type; + return !!(signature.target && (type = tryGetTypeAtPosition(signature.target, pos)) && isGenericType(type)); + } + + // 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); + } + + function getSingleCallOrConstructSignature(type: Type): Signature | undefined { + return getSingleSignature(type, SignatureKind.Call, /*allowMembers*/ false) || + getSingleSignature(type, SignatureKind.Construct, /*allowMembers*/ false); + } + + 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; + } + + // 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); + } + + function getThisArgumentType(thisArgumentNode: Expression | undefined) { + if (!thisArgumentNode) { + return voidType; + } + const thisArgumentType = checkExpression(thisArgumentNode); + return isRightSideOfInstanceofExpression(thisArgumentNode) ? thisArgumentType : + isOptionalChainRoot(thisArgumentNode.parent) ? getNonNullableType(thisArgumentType) : + isOptionalChain(thisArgumentNode.parent) ? removeOptionalTypeMarker(thisArgumentType) : + thisArgumentType; + } + + function inferTypeArguments(node: CallLikeExpression, signature: Signature, args: readonly Expression[], checkMode: CheckMode, context: InferenceContext): Type[] { + if (isJsxOpeningLikeElement(node)) { + return inferJsxTypeArguments(node, signature, checkMode, context); + } + + // 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 && node.kind !== SyntaxKind.BinaryExpression) { + 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) { + 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(getElementTypes(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) { + const inConstContext = isConstTypeVariable(restType); + + 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. + const spreadType = arg.kind === SyntaxKind.SyntheticExpression ? (arg as SyntheticExpression).type : + checkExpressionWithContextualType((arg as SpreadElement).expression, restType, context, checkMode); + + if (isArrayLikeType(spreadType)) { + return getMutableArrayOrTupleType(spreadType); + } + + return createArrayType(checkIteratedTypeOrElementType(IterationUse.Spread, spreadType, undefinedType, arg.kind === SyntaxKind.SpreadElement ? (arg as SpreadElement).expression : arg), inConstContext); + } + } + 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); + } + } + else { + const contextualType = isTupleType(restType) ? + getContextualTypeForElementExpression(restType, i - index, argCount - index) || unknownType : + getIndexedAccessType(restType, getNumberLiteralType(i - index), AccessFlags.Contextual); + const argType = checkExpressionWithContextualType(arg, contextualType, context, checkMode); + const hasPrimitiveContextualType = inConstContext || 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!); + } + else { + names.push(undefined); + } + } + return createTupleType(types, flags, inConstContext && !someType(restType, isMutableArrayLikeType), names); + } + + 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 (isJsxIntrinsicTagName(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; + } + + /** + * 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: Map, + 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); + const checkAttributesType = checkMode & CheckMode.SkipContextSensitive ? getRegularTypeOfObjectLiteral(attributesType) : attributesType; + return checkTagNameDoesNotExpectTooManyArguments() && checkTypeRelatedToAndOptionallyElaborate( + checkAttributesType, + 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)) && !(isJsxIntrinsicTagName(node.tagName) || isJsxNamespacedName(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; + } + + 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; + } + } + } + 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); + } + } + return false; + } + } + + function getEffectiveCheckNode(argument: Expression): Expression { + argument = skipParentheses(argument); + return isSatisfiesExpression(argument) ? skipParentheses(argument.expression) : argument; + } + + function getSignatureApplicabilityError( + node: CallLikeExpression, + args: readonly Expression[], + signature: Signature, + relation: Map, + checkMode: CheckMode, + reportErrors: boolean, + containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, + inferenceContext: InferenceContext | 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; + } + const thisType = getThisTypeOfSignature(signature); + if (thisType && thisType !== voidType && !(isNewExpression(node) || isCallExpression(node) && isSuperProperty(node.expression))) { + // 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 or super call 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 regularArgType = checkMode & CheckMode.SkipContextSensitive ? getRegularTypeOfObjectLiteral(argType) : argType; + // If this was inferred under a given inference context, we may need to instantiate the expression type to finish resolving + // the type variables in the expression. + const checkArgType = inferenceContext ? instantiateType(regularArgType, inferenceContext.nonFixingMapper) : regularArgType; + const effectiveCheckArgumentNode = getEffectiveCheckNode(arg); + if (!checkTypeRelatedToAndOptionallyElaborate(checkArgType, paramType, relation, reportErrors ? effectiveCheckArgumentNode : undefined, effectiveCheckArgumentNode, 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 ? getEffectiveCheckNode(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; + + 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): Expression | undefined { + if (node.kind === SyntaxKind.BinaryExpression) { + return node.right; + } + + const expression = node.kind === SyntaxKind.CallExpression ? node.expression : + node.kind === SyntaxKind.TaggedTemplateExpression ? node.tag : + node.kind === SyntaxKind.Decorator && !legacyDecorators ? node.expression : + 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); + setTextRangeWorker(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); + }); + } + return args; + } + if (node.kind === SyntaxKind.Decorator) { + return getEffectiveDecoratorArguments(node); + } + if (node.kind === SyntaxKind.BinaryExpression) { + return [node.left]; + } + 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(getElementTypes(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); + } + } + return effectiveArgs; + } + return args; + } + + /** + * Returns the synthetic argument list for a decorator invocation. + */ + function getEffectiveDecoratorArguments(node: Decorator): readonly Expression[] { + const expr = node.expression; + const signature = getDecoratorCallSignature(node); + if (signature) { + const args: Expression[] = []; + for (const param of signature.parameters) { + const type = getTypeOfSymbol(param); + args.push(createSyntheticExpression(expr, type)); + } + return args; + } + return Debug.fail(); + } + + /** + * Returns the argument count for a decorator node that works like a function invocation. + */ + function getDecoratorArgumentCount(node: Decorator, signature: Signature) { + return compilerOptions.experimentalDecorators ? + getLegacyDecoratorArgumentCount(node, signature) : + // Allow the runtime to oversupply arguments to an ES decorator as long as there's at least one parameter. + Math.min(Math.max(getParameterCount(signature), 1), 2); + } + + /** + * Returns the argument count for a decorator node that works like a function invocation. + */ + function getLegacyDecoratorArgumentCount(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 decorators with only two parameters we supply only two arguments + return signature.parameters.length <= 2 ? 2 : 3; + case SyntaxKind.Parameter: + return 3; + default: + return Debug.fail(); + } + } + + function getDiagnosticSpanForCallNode(node: CallExpression) { + const sourceFile = getSourceFileOfNode(node); + const { start, length } = getErrorSpanForNode(sourceFile, isPropertyAccessExpression(node.expression) ? node.expression.name : node.expression); + return { start, length, sourceFile }; + } + + function getDiagnosticForCallNode(node: CallLikeExpression, message: DiagnosticMessage | DiagnosticMessageChain, ...args: DiagnosticArguments): DiagnosticWithLocation { + if (isCallExpression(node)) { + const { sourceFile, start, length } = getDiagnosticSpanForCallNode(node); + if ("message" in message) { // eslint-disable-line local/no-in-operator + return createFileDiagnostic(sourceFile, start, length, message, ...args); + } + return createDiagnosticForFileFromMessageChain(sourceFile, message); + } + else { + if ("message" in message) { // eslint-disable-line local/no-in-operator + return createDiagnosticForNode(node, message, ...args); + } + return createDiagnosticForNodeFromMessageChain(getSourceFileOfNode(node), node, message); + } + } + + function getErrorNodeForCallNode(callLike: CallLikeExpression): Node { + if (isCallOrNewExpression(callLike)) { + return isPropertyAccessExpression(callLike.expression) ? callLike.expression.name : callLike.expression; + } + if (isTaggedTemplateExpression(callLike)) { + return isPropertyAccessExpression(callLike.tag) ? callLike.tag.name : callLike.tag; + } + if (isJsxOpeningLikeElement(callLike)) { + return callLike.tagName; + } + return callLike; + } + + function isPromiseResolveArityError(node: CallLikeExpression) { + if (!isCallExpression(node) || !isIdentifier(node.expression)) return false; + + const symbol = resolveName(node.expression, node.expression.escapedText, SymbolFlags.Value, /*nameNotFoundMessage*/ undefined, /*isUse*/ 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[], headMessage?: DiagnosticMessage) { + 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 = isDecorator(node) ? + hasRestParameter ? Diagnostics.The_runtime_will_invoke_the_decorator_with_1_arguments_but_the_decorator_expects_at_least_0 : + Diagnostics.The_runtime_will_invoke_the_decorator_with_1_arguments_but_the_decorator_expects_0 : + 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 + if (headMessage) { + let chain = chainDiagnosticMessages(/*details*/ undefined, Diagnostics.No_overload_expects_0_arguments_but_overloads_do_exist_that_expect_either_1_or_2_arguments, args.length, maxBelow, minAbove); + chain = chainDiagnosticMessages(chain, headMessage); + return getDiagnosticForCallNode(node, chain); + } + 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 + let diagnostic: Diagnostic; + if (headMessage) { + let chain = chainDiagnosticMessages(/*details*/ undefined, error, parameterRange, args.length); + chain = chainDiagnosticMessages(chain, headMessage); + diagnostic = getDiagnosticForCallNode(node, chain); + } + else { + diagnostic = getDiagnosticForCallNode(node, error, parameterRange, args.length); + } + const parameter = closestSignature?.declaration?.parameters[closestSignature.thisParameter ? args.length + 1 : args.length]; + if (parameter) { + const messageAndArgs: DiagnosticAndArguments = 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, idText(getFirstIdentifier(parameter.name))] + : [Diagnostics.An_argument_for_0_was_not_provided, !parameter.name ? args.length : idText(getFirstIdentifier(parameter.name))]; + const parameterError = createDiagnosticForNode(parameter, ...messageAndArgs); + return addRelatedInfo(diagnostic, parameterError); + } + 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); + if (headMessage) { + let chain = chainDiagnosticMessages(/*details*/ undefined, error, parameterRange, args.length); + chain = chainDiagnosticMessages(chain, headMessage); + return createDiagnosticForNodeArrayFromMessageChain(getSourceFileOfNode(node), errorSpan, chain); + } + return createDiagnosticForNodeArray(getSourceFileOfNode(node), errorSpan, error, parameterRange, args.length); + } + } + + function getTypeArgumentArityError(node: Node, signatures: readonly Signature[], typeArguments: NodeArray, headMessage?: DiagnosticMessage) { + 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); + if (headMessage) { + let chain = chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Expected_0_type_arguments_but_got_1, min < max ? min + "-" + max : min, argCount); + chain = chainDiagnosticMessages(chain, headMessage); + return createDiagnosticForNodeArrayFromMessageChain(getSourceFileOfNode(node), typeArguments, chain); + } + 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); + } + } + if (belowArgCount !== -Infinity && aboveArgCount !== Infinity) { + if (headMessage) { + let chain = chainDiagnosticMessages(/*details*/ undefined, Diagnostics.No_overload_expects_0_type_arguments_but_overloads_do_exist_that_expect_either_1_or_2_type_arguments, argCount, belowArgCount, aboveArgCount); + chain = chainDiagnosticMessages(chain, headMessage); + return createDiagnosticForNodeArrayFromMessageChain(getSourceFileOfNode(node), typeArguments, chain); + } + 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); + } + if (headMessage) { + let chain = chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Expected_0_type_arguments_but_got_1, belowArgCount === -Infinity ? aboveArgCount : belowArgCount, argCount); + chain = chainDiagnosticMessages(chain, headMessage); + return createDiagnosticForNodeArrayFromMessageChain(getSourceFileOfNode(node), typeArguments, chain); + } + 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, headMessage?: DiagnosticMessage): Signature { + const isTaggedTemplate = node.kind === SyntaxKind.TaggedTemplateExpression; + const isDecorator = node.kind === SyntaxKind.Decorator; + const isJsxOpeningOrSelfClosingElement = isJsxOpeningLikeElement(node); + const isInstanceof = node.kind === SyntaxKind.BinaryExpression; + const reportErrors = !isInferencePartiallyBlocked && !candidatesOutArray; + + let typeArguments: NodeArray | undefined; + + if (!isDecorator && !isInstanceof && !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); + } + } + + const candidates = candidatesOutArray || []; + // reorderCandidates fills up the candidates array directly + reorderCandidates(signatures, candidates, callChainFlags); + Debug.assert(candidates.length, "Revert #54442 and add a testcase with whatever triggered this"); + + 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; + + // 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 the call expression is a synthetic call to a `[Symbol.hasInstance]` method then we will produce a head + // message when reporting diagnostics that explains how we got to `right[Symbol.hasInstance](left)` from + // `left instanceof right`, as it pertains to "Argument" related messages reported for the call. + if (!headMessage && isInstanceof) { + headMessage = Diagnostics.The_left_hand_side_of_an_instanceof_expression_must_be_assignable_to_the_first_argument_of_the_right_hand_side_s_Symbol_hasInstance_method; + } + 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); + } + if (headMessage) { + chain = chainDiagnosticMessages(chain, headMessage); + } + const diags = getSignatureApplicabilityError(node, args, last, assignableRelation, CheckMode.Normal, /*reportErrors*/ true, () => chain, /*inferenceContext*/ undefined); + 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); + } + } + else { + 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, /*inferenceContext*/ undefined); + 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"); + } + i++; + } + + const diags = max > 1 ? allDiagnostics[minIndex] : flatten(allDiagnostics); + Debug.assert(diags.length > 0, "No errors reported for 3 or fewer overload signatures"); + let chain = chainDiagnosticMessages( + map(diags, createDiagnosticMessageChainFromDiagnostic), + Diagnostics.No_overload_matches_this_call, + ); + if (headMessage) { + chain = chainDiagnosticMessages(chain, headMessage); + } + // 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(getSourceFileOfNode(node), getErrorNodeForCallNode(node), chain, related); + } + addImplementationSuccessElaboration(candidatesForArgumentError[0], diag); + diagnostics.add(diag); + } + } + else if (candidateForArgumentArityError) { + diagnostics.add(getArgumentArityError(node, [candidateForArgumentArityError], args, headMessage)); + } + else if (candidateForTypeArgumentError) { + checkTypeArguments(candidateForTypeArgumentError, (node as CallExpression | TaggedTemplateExpression | JsxOpeningLikeElement).typeArguments!, /*reportErrors*/ true, headMessage); + } + else { + const signaturesWithCorrectTypeArgumentArity = filter(signatures, s => hasCorrectTypeArgumentArity(s, typeArguments)); + if (signaturesWithCorrectTypeArgumentArity.length === 0) { + diagnostics.add(getTypeArgumentArityError(node, signatures, typeArguments!, headMessage)); + } + else { + diagnostics.add(getArgumentArityError(node, signaturesWithCorrectTypeArgumentArity, args, headMessage)); + } + } + } + + return result; + + 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)); + } + } + + candidatesForArgumentError = oldCandidatesForArgumentError; + candidateForArgumentArityError = oldCandidateForArgumentArityError; + candidateForTypeArgumentError = oldCandidateForTypeArgumentError; + } + + function chooseOverload(candidates: Signature[], relation: Map, 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, /*inferenceContext*/ undefined)) { + candidatesForArgumentError = [candidate]; + return undefined; + } + return candidate; + } + + for (let candidateIndex = 0; candidateIndex < candidates.length; candidateIndex++) { + let candidate = candidates[candidateIndex]; + if (!hasCorrectTypeArgumentArity(candidate, typeArguments) || !hasCorrectArity(node, args, candidate, signatureHelpTrailingComma)) { + continue; + } + + let checkCandidate: Signature; + let inferenceContext: InferenceContext | undefined; + + if (candidate.typeParameters) { + // If we are *inside the body of candidate*, we need to create a clone of `candidate` with differing type parameter identities, + // so our inference results for this call doesn't pollute expression types referencing the outer type parameter! + const paramLocation = candidate.typeParameters[0].symbol.declarations?.[0]?.parent; + const candidateParameterContext = paramLocation || (candidate.declaration && isConstructorDeclaration(candidate.declaration) ? candidate.declaration.parent : candidate.declaration); + if (candidateParameterContext && findAncestor(node, a => a === candidateParameterContext)) { + candidate = getImplementationSignature(candidate); + } + let typeArgumentTypes: readonly 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); + // The resulting type arguments are instantiated with the inference context mapper, as the inferred types may still contain references to the inference context's + // type variables via contextual projection. These are kept generic until all inferences are locked in, so the dependencies expressed can pass constraint checks. + typeArgumentTypes = instantiateTypes(inferTypeArguments(node, candidate, args, argCheckMode | CheckMode.SkipGenericFunctions, inferenceContext), inferenceContext.nonFixingMapper); + 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, inferenceContext)) { + // 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.Normal; + if (inferenceContext) { + const typeArgumentTypes = instantiateTypes(inferTypeArguments(node, candidate, args, argCheckMode, inferenceContext), inferenceContext.mapper); + 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, inferenceContext)) { + // 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; + } + } + + // 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 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.IsSignatureCandidateForOverloadFailure; + 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)), + /*resolvedTypePredicate*/ undefined, + minArgumentCount, + flags, + ); + } + + function getNumNonRestParameters(signature: Signature): number { + const numParams = signature.parameters.length; + return signatureHasRestParameter(signature) ? numParams - 1 : numParams; + } + + function createCombinedSymbolFromTypes(sources: readonly Symbol[], types: Type[]): Symbol { + return createCombinedSymbolForOverloadFailure(sources, getUnionType(types, UnionReduction.Subtype)); + } + + 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 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 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; + } + if (paramCount > maxParams) { + maxParams = paramCount; + maxParamsIndex = i; + } + } + + return maxParamsIndex; + } + + 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); + } + + 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; + } + + 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); + } + + // 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 { + 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); + } + + return resolveCall(node, callSignatures, candidatesOutArray, checkMode, callChainFlags); + } + + function isGenericFunctionReturningFunction(signature: Signature) { + return !!(signature.typeParameters && isFunctionType(getReturnTypeOfSignature(signature))); + } + + /** + * 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); + } + + function resolveNewExpression(node: NewExpression, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature { + let expressionType = checkNonNullExpression(node.expression); + if (expressionType === silentNeverType) { + return silentNeverSignature; + } + + // 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); + } + + // 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); + } + + // 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); + } + 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); + } + + 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); + } + 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); + } + + 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; + } + 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; + } + if (firstBase.symbol === target) { + return true; + } + return typeHasProtectedAccessibleBase(target, firstBase as InterfaceType); + } + + function isConstructorAccessible(node: NewExpression, signature: Signature) { + if (!signature || !signature.declaration) { + return true; + } + + 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; + } + + 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)); + } + return false; + } + + 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; + } + } + else { + // Error on the first non callable constituent only + if (!errorInfo) { + errorInfo = chainDiagnosticMessages( + errorInfo, + isCall ? + Diagnostics.Type_0_has_no_call_signatures : + Diagnostics.Type_0_has_no_construct_signatures, + typeToString(constituent), + ); + errorInfo = chainDiagnosticMessages( + errorInfo, + isCall ? + Diagnostics.Not_all_constituents_of_type_0_are_callable : + Diagnostics.Not_all_constituents_of_type_0_are_constructable, + typeToString(apparentType), + ); + } + if (hasSignatures) { + // Bail early if we already found a siganture, no chance of "No constituent of type is callable" + break; + } + } + } + if (!hasSignatures) { + errorInfo = chainDiagnosticMessages( + /*details*/ undefined, + isCall ? + Diagnostics.No_constituent_of_type_0_is_callable : + Diagnostics.No_constituent_of_type_0_is_constructable, + typeToString(apparentType), + ); + } + if (!errorInfo) { + errorInfo = chainDiagnosticMessages( + errorInfo, + isCall ? + Diagnostics.Each_member_of_the_union_type_0_has_signatures_but_none_of_those_signatures_are_compatible_with_each_other : + Diagnostics.Each_member_of_the_union_type_0_has_construct_signatures_but_none_of_those_signatures_are_compatible_with_each_other, + typeToString(apparentType), + ); + } + } + else { + errorInfo = chainDiagnosticMessages( + errorInfo, + isCall ? + Diagnostics.Type_0_has_no_call_signatures : + Diagnostics.Type_0_has_no_construct_signatures, + typeToString(apparentType), + ); + } + + 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; + } + } + + 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(getSourceFileOfNode(errorTarget), errorTarget, messageChain); + if (relatedInfo) { + addRelatedInfo(diagnostic, createDiagnosticForNode(errorTarget, relatedInfo)); + } + if (isCallExpression(errorTarget.parent)) { + const { start, length } = getDiagnosticSpanForCallNode(errorTarget.parent); + 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; + + 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 resolveTaggedTemplateExpression(node: TaggedTemplateExpression, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature { + const tagType = checkExpression(node.tag); + const apparentType = getApparentType(tagType); + + if (isErrorType(apparentType)) { + // Another error has already been reported + return resolveErrorCall(node); + } + + const callSignatures = getSignaturesOfType(apparentType, SignatureKind.Call); + const numConstructSignatures = getSignaturesOfType(apparentType, SignatureKind.Construct).length; + + if (isUntypedFunctionCall(tagType, apparentType, callSignatures.length, numConstructSignatures)) { + return resolveUntypedCall(node); + } + + 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); + } + + invocationError(node.tag, apparentType, SignatureKind.Call); + return resolveErrorCall(node); + } + + return resolveCall(node, callSignatures, candidatesOutArray, checkMode, SignatureFlags.None); + } + + /** + * 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; + + case SyntaxKind.PropertyDeclaration: + return Diagnostics.Unable_to_resolve_signature_of_property_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; + + default: + return Debug.fail(); + } + } + + /** + * 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 callSignatures = getSignaturesOfType(apparentType, SignatureKind.Call); + const numConstructSignatures = getSignaturesOfType(apparentType, SignatureKind.Construct).length; + if (isUntypedFunctionCall(funcType, apparentType, callSignatures.length, numConstructSignatures)) { + return resolveUntypedCall(node); + } + + if (isPotentiallyUncalledDecorator(node, callSignatures) && !isParenthesizedExpression(node.expression)) { + 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); + } + + const headMessage = getDiagnosticHeadMessageForDecoratorResolution(node); + if (!callSignatures.length) { + const errorDetails = invocationErrorDetails(node.expression, apparentType, SignatureKind.Call); + const messageChain = chainDiagnosticMessages(errorDetails.messageChain, headMessage); + const diag = createDiagnosticForNodeFromMessageChain(getSourceFileOfNode(node.expression), node.expression, messageChain); + if (errorDetails.relatedMessage) { + addRelatedInfo(diag, createDiagnosticForNode(node.expression, errorDetails.relatedMessage)); + } + diagnostics.add(diag); + invocationErrorRecovery(apparentType, SignatureKind.Call, diag); + return resolveErrorCall(node); + } + + return resolveCall(node, callSignatures, candidatesOutArray, checkMode, SignatureFlags.None, headMessage); + } + + 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, /*dotDotDotToken*/ undefined, "props", /*questionToken*/ undefined, nodeBuilder.typeToTypeNode(result, node))], returnNode ? factory.createTypeReferenceNode(returnNode, /*typeArguments*/ undefined) : factory.createKeywordTypeNode(SyntaxKind.AnyKeyword)); + const parameterSymbol = createSymbol(SymbolFlags.FunctionScopedVariable, "props" as __String); + parameterSymbol.links.type = result; + return createSignature( + declaration, + /*typeParameters*/ undefined, + /*thisParameter*/ undefined, + [parameterSymbol], + typeSymbol ? getDeclaredTypeOfSymbol(typeSymbol) : errorType, + /*resolvedTypePredicate*/ undefined, + 1, + SignatureFlags.None, + ); + } + + function resolveJsxOpeningLikeElement(node: JsxOpeningLikeElement, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature { + if (isJsxIntrinsicTagName(node.tagName)) { + const result = getIntrinsicAttributesTypeFromJsxOpeningLikeElement(node); + const fakeSignature = createSignatureForJSXIntrinsic(node, result); + checkTypeAssignableToAndOptionallyElaborate(checkExpressionWithContextualType(node.attributes, getEffectiveFirstArgumentForJsxSignature(fakeSignature, node), /*inferenceContext*/ 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); + } + + const signatures = getUninstantiatedJsxSignaturesOfType(exprTypes, node); + if (isUntypedFunctionCall(exprTypes, apparentType, signatures.length, /*constructSignatures*/ 0)) { + return resolveUntypedCall(node); + } + + 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); + } + + return resolveCall(node, signatures, candidatesOutArray, checkMode, SignatureFlags.None); + } + + function resolveInstanceofExpression(node: InstanceofExpression, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature { + // if rightType is an object type with a custom `[Symbol.hasInstance]` method, then it is potentially + // valid on the right-hand side of the `instanceof` operator. This allows normal `object` types to + // participate in `instanceof`, as per Step 2 of https://tc39.es/ecma262/#sec-instanceofoperator. + const rightType = checkExpression(node.right); + if (!isTypeAny(rightType)) { + const hasInstanceMethodType = getSymbolHasInstanceMethodOfObjectType(rightType); + if (hasInstanceMethodType) { + const apparentType = getApparentType(hasInstanceMethodType); + if (isErrorType(apparentType)) { + return resolveErrorCall(node); + } + + const callSignatures = getSignaturesOfType(apparentType, SignatureKind.Call); + const constructSignatures = getSignaturesOfType(apparentType, SignatureKind.Construct); + if (isUntypedFunctionCall(hasInstanceMethodType, apparentType, callSignatures.length, constructSignatures.length)) { + return resolveUntypedCall(node); + } + + if (callSignatures.length) { + return resolveCall(node, callSignatures, candidatesOutArray, checkMode, SignatureFlags.None); + } + } + // NOTE: do not raise error if right is unknown as related error was already reported + else if (!(typeHasCallOrConstructSignatures(rightType) || isTypeSubtypeOf(rightType, globalFunctionType))) { + error(node.right, Diagnostics.The_right_hand_side_of_an_instanceof_expression_must_be_either_of_type_any_a_class_function_or_other_type_assignable_to_the_Function_interface_type_or_an_object_type_with_a_Symbol_hasInstance_method); + return resolveErrorCall(node); + } + } + // fall back to a default signature + return anySignature; + } + + /** + * 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)); + } + + 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); + case SyntaxKind.BinaryExpression: + return resolveInstanceofExpression(node, candidatesOutArray, checkMode); + } + 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; + } + const saveResolutionStart = resolutionStart; + if (!cached) { + // If we haven't already done so, temporarily reset the resolution stack. This allows us to + // handle "inverted" situations where, for example, an API client asks for the type of a symbol + // containined in a function call argument whose contextual type depends on the symbol itself + // through resolution of the containing function call. By resetting the resolution stack we'll + // retry the symbol type resolution with the resolvingSignature marker in place to suppress + // the contextual type circularity. + resolutionStart = resolutionTargets.length; + } + links.resolvedSignature = resolvingSignature; + let result = resolveSignature(node, candidatesOutArray, checkMode || CheckMode.Normal); + resolutionStart = saveResolutionStart; + // When CheckMode.SkipGenericFunctions is set we use resolvingSignature to indicate that call + // resolution should be deferred. + if (result !== resolvingSignature) { + // if the signature resolution originated on a node that itself depends on the contextual type + // then it's possible that the resolved signature might not be the same as the one that would be computed in source order + // since resolving such signature leads to resolving the potential outer signature, its arguments and thus the very same signature + // it's possible that this inner resolution sets the resolvedSignature first. + // In such a case we ignore the local result and reuse the correct one that was cached. + if (links.resolvedSignature !== resolvingSignature) { + result = links.resolvedSignature; + } + // 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; + + // 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 = getSymbolOfDeclaration(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); + 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; + } + 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 ? getSymbolOfDeclaration(init) : undefined; + } + + 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) && !(isVarConstLike(node.parent) && isFunctionLikeDeclaration(node))) { + return undefined; + } + 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 (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; + } + } + } + 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; + } + } + + /** + * 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); + + 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); + + if (node.expression.kind === SyntaxKind.SuperKeyword) { + return voidType; + } + + if (node.kind === SyntaxKind.NewExpression) { + const declaration = signature.declaration; + + if ( + declaration && + declaration.kind !== SyntaxKind.Constructor && + declaration.kind !== SyntaxKind.ConstructSignature && + declaration.kind !== SyntaxKind.ConstructorType && + !(isJSDocSignature(declaration) && getJSDocRoot(declaration)?.parent?.kind === SyntaxKind.Constructor) && + !isJSDocConstructSignature(declaration) && + !isJSConstructor(declaration) + ) { + // 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; + } + } + + // 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); + } + + 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 (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 returnType; + } + + function checkDeprecatedSignature(signature: Signature, node: CallLikeExpression) { + if (signature.flags & SignatureFlags.IsSignatureCandidateForOverloadFailure) return; + 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 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; + } + } + + 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; + } + + // make sure `Symbol` is the global symbol + const globalESSymbol = getGlobalESSymbolConstructorSymbol(/*reportErrors*/ false); + if (!globalESSymbol) { + return false; + } + + return globalESSymbol === resolveName(left, "Symbol" as __String, SymbolFlags.Value, /*nameNotFoundMessage*/ undefined, /*isUse*/ false); + } + + function checkImportCallExpression(node: ImportCall): Type { + // Check grammar of dynamic import + checkGrammarImportCallExpression(node); + + if (node.arguments.length === 0) { + return createPromiseReturnType(node, anyType); + } + + 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]); + } + + 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)); + } + + if (optionsType) { + const importCallOptionsType = getGlobalImportCallOptionsType(/*reportErrors*/ true); + if (importCallOptionsType !== emptyObjectType) { + checkTypeAssignableTo(optionsType, getNullableType(importCallOptionsType, TypeFlags.Undefined), node.arguments[1]); + } + } + + // resolveExternalModuleName will return undefined if the moduleReferenceExpression is not a string literal + const moduleSymbol = resolveExternalModuleName(node, specifier); + if (moduleSymbol) { + const esModuleSymbol = resolveESModuleSymbol(moduleSymbol, specifier, /*dontResolveAlias*/ true, /*suppressInteropError*/ false); + if (esModuleSymbol) { + return createPromiseReturnType( + node, + getTypeWithSyntheticDefaultOnly(getTypeOfSymbol(esModuleSymbol), esModuleSymbol, moduleSymbol, specifier) || + getTypeWithSyntheticDefaultImportType(getTypeOfSymbol(esModuleSymbol), esModuleSymbol, moduleSymbol, specifier), + ); + } + } + return createPromiseReturnType(node, anyType); + } + + function createDefaultPropertyWrapperForModule(symbol: Symbol, originalSymbol: Symbol | undefined, anonymousSymbol?: Symbol | undefined) { + const memberTable = createSymbolTable(); + const newSymbol = createSymbol(SymbolFlags.Alias, InternalSymbolName.Default); + newSymbol.parent = originalSymbol; + newSymbol.links.nameType = getStringLiteralType("default"); + newSymbol.links.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; + } + return synthType.defaultOnlyType; + } + return undefined; + } + + 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.links.type = defaultContainingObject; + synthType.syntheticType = isValidSpreadType(type) ? getSpreadType(type, defaultContainingObject, anonymousSymbol, /*objectFlags*/ 0, /*readonly*/ false) : defaultContainingObject; + } + else { + synthType.syntheticType = type; + } + } + return synthType.syntheticType; + } + return type; + } + + function isCommonJsRequire(node: Node): boolean { + if (!isRequireCall(node, /*requireStringLiteralLikeArgument*/ 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, /*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); + } + return false; + } + + function checkTaggedTemplateExpression(node: TaggedTemplateExpression): Type { + if (!checkGrammarTaggedTemplateChain(node)) checkGrammarTypeArguments(node, node.typeArguments); + if (languageVersion < LanguageFeatureMinimumTarget.TaggedTemplates) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.MakeTemplateObject); + } + const signature = getResolvedSignature(node); + checkDeprecatedSignature(signature, node); + return getReturnTypeOfSignature(signature); + } + + function checkAssertion(node: AssertionExpression, checkMode: CheckMode | undefined) { + 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 checkAssertionWorker(node, checkMode); + } + + 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 = skipParentheses((node as PropertyAccessExpression | ElementAccessExpression).expression); + const symbol = isEntityNameExpression(expr) ? resolveEntityName(expr, SymbolFlags.Value, /*ignoreErrors*/ true) : undefined; + return !!(symbol && symbol.flags & SymbolFlags.Enum); + } + return false; + } + + function checkAssertionWorker(node: JSDocTypeAssertion | AssertionExpression, checkMode: CheckMode | undefined) { + const { type, expression } = getAssertionTypeAndExpression(node); + const 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); + } + const links = getNodeLinks(node); + links.assertionExpressionType = exprType; + checkSourceElement(type); + checkNodeDeferred(node); + return getTypeFromTypeNode(type); + } + + function getAssertionTypeAndExpression(node: JSDocTypeAssertion | AssertionExpression) { + let type: TypeNode; + let expression: Expression; + switch (node.kind) { + case SyntaxKind.AsExpression: + case SyntaxKind.TypeAssertionExpression: + type = node.type; + expression = node.expression; + break; + case SyntaxKind.ParenthesizedExpression: + type = getJSDocTypeAssertionType(node); + expression = node.expression; + break; + } + + return { type, expression }; + } + + function checkAssertionDeferred(node: JSDocTypeAssertion | AssertionExpression) { + const { type } = getAssertionTypeAndExpression(node); + const errNode = isParenthesizedExpression(node) ? type : node; + const links = getNodeLinks(node); + Debug.assertIsDefined(links.assertionExpressionType); + const exprType = getRegularTypeOfObjectLiteral(getBaseTypeOfLiteralType(links.assertionExpressionType)); + 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); + } + }); + } + } + + function checkNonNullChain(node: NonNullChain) { + const leftType = checkExpression(node.expression); + const nonOptionalType = getOptionalExpressionType(leftType, node.expression); + return propagateOptionalTypeMarker(getNonNullableType(nonOptionalType), node, nonOptionalType !== leftType); + } + + function checkNonNullAssertion(node: NonNullExpression) { + return node.flags & NodeFlags.OptionalChain ? checkNonNullChain(node as NonNullChain) : + getNonNullableType(checkExpression(node.expression)); + } + + function checkExpressionWithTypeArguments(node: ExpressionWithTypeArguments | TypeQueryNode) { + checkGrammarExpressionWithTypeArguments(node); + forEach(node.typeArguments, checkSourceElement); + if (node.kind === SyntaxKind.ExpressionWithTypeArguments) { + const parent = walkUpParenthesizedExpressions(node.parent); + if (parent.kind === SyntaxKind.BinaryExpression && (parent as BinaryExpression).operatorToken.kind === SyntaxKind.InstanceOfKeyword && isNodeDescendantOf(node, (parent as BinaryExpression).right)) { + error(node, Diagnostics.The_right_hand_side_of_an_instanceof_expression_must_not_be_an_instantiation_expression); + } + } + const exprType = node.kind === SyntaxKind.ExpressionWithTypeArguments ? checkExpression(node.expression) : + isThisIdentifier(node.exprName) ? checkThisExpression(node.exprName) : + checkExpression(node.exprName); + return getInstantiationExpressionType(exprType, node); + } + + function getInstantiationExpressionType(exprType: Type, node: NodeWithTypeArguments) { + 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 getInstantiatedType(type: Type): Type { + let hasSignatures = false; + let hasApplicableSignature = false; + const result = getInstantiatedTypePart(type); + hasSomeApplicableSignature ||= hasApplicableSignature; + if (hasSignatures && !hasApplicableSignature) { + nonApplicableType ??= type; + } + return result; + + 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(createSymbol(SymbolFlags.None, InternalSymbolName.InstantiationExpression), 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 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 checkSatisfiesExpression(node: SatisfiesExpression) { + checkSourceElement(node.type); + return checkSatisfiesExpressionWorker(node.expression, node.type); + } + + function checkSatisfiesExpressionWorker(expression: Expression, target: TypeNode, checkMode?: CheckMode) { + const exprType = checkExpression(expression, checkMode); + const targetType = getTypeFromTypeNode(target); + if (isErrorType(targetType)) { + return targetType; + } + const errorNode = findAncestor(target.parent, n => n.kind === SyntaxKind.SatisfiesExpression || n.kind === SyntaxKind.JSDocSatisfiesTag); + checkTypeAssignableToAndOptionallyElaborate(exprType, targetType, errorNode, 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); + } + + if (node.keywordToken === SyntaxKind.ImportKeyword) { + return checkImportMetaProperty(node); + } + + 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 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 = getSymbolOfDeclaration(container.parent); + return getTypeOfSymbol(symbol); + } + else { + const symbol = getSymbolOfDeclaration(container); + return getTypeOfSymbol(symbol); + } + } + + 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; + } + + function getTypeOfParameter(symbol: Symbol) { + const declaration = symbol.valueDeclaration; + return addOptionality( + getTypeOfSymbol(symbol), + /*isProperty*/ false, + /*isOptional*/ !!declaration && (hasInitializer(declaration) || isOptionalDeclaration(declaration)), + ); + } + + function getTupleElementLabel(d: ParameterDeclaration | NamedTupleMember): __String; + function getTupleElementLabel(d: ParameterDeclaration | NamedTupleMember | undefined, index: number, restParameterName?: __String): __String; + function getTupleElementLabel(d: ParameterDeclaration | NamedTupleMember | undefined, index?: number, restParameterName = "arg" as __String) { + if (!d) { + return `${restParameterName}_${index}` as __String; + } + Debug.assert(isIdentifier(d.name)); // Parameter declarations could be binding patterns, but we only allow identifier names + return d.name.escapedText; + } + + 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 getTupleElementLabel(associatedNames?.[index], index, restParameter.escapedName); + } + return restParameter.escapedName; + } + + function getParameterIdentifierInfoAtPosition(signature: Signature, pos: number): { parameter: Identifier; 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]; + const paramIdent = getParameterDeclarationIdentifier(param); + return paramIdent ? { + parameter: paramIdent, + parameterName: param.escapedName, + isRestParameter: false, + } : undefined; + } + + const restParameter = signature.parameters[paramCount] || unknownSymbol; + const restIdent = getParameterDeclarationIdentifier(restParameter); + if (!restIdent) { + return undefined; + } + + 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; + + if (associatedName) { + Debug.assert(isIdentifier(associatedName.name)); + return { parameter: associatedName.name, parameterName: associatedName.name.escapedText, isRestParameter: isRestTupleElement }; + } + + return undefined; + } + + if (pos === paramCount) { + return { parameter: restIdent, parameterName: restParameter.escapedName, isRestParameter: true }; + } + return undefined; + } + + function getParameterDeclarationIdentifier(symbol: Symbol) { + return symbol.valueDeclaration && isParameter(symbol.valueDeclaration) && isIdentifier(symbol.valueDeclaration.name) && 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 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 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.combinedFlags & ElementFlags.Variable || index < restType.target.fixedLength) { + return getIndexedAccessType(restType, getNumberLiteralType(index)); + } + } + return undefined; + } + + function getRestTypeAtPosition(source: Signature, pos: number, readonly?: boolean): 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); + } + else { + types.push(restType); + flags.push(ElementFlags.Variadic); + } + names.push(getNameableDeclarationAtPosition(source, i)); + } + return createTupleType(types, flags, readonly, names); + } + + // Return the rest type at the given position, transforming `any[]` into just `any`. We do this because + // in signatures we want `any[]` in a rest position to be compatible with anything, but `any[]` isn't + // assignable to tuple types with required elements. + function getRestOrAnyTypeAtPosition(source: Signature, pos: number): Type { + const restType = getRestTypeAtPosition(source, pos); + const elementType = restType && getElementTypeOfArrayType(restType); + return elementType && isTypeAny(elementType) ? anyType : restType; + } + + // 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.combinedFlags & ElementFlags.Variable ? 0 : 1); + } + } + 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; + } + 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; + } + minArgumentCount = i; + } + 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.combinedFlags & ElementFlags.Variable); + } + return false; + } + + function getEffectiveRestType(signature: Signature) { + if (signatureHasRestParameter(signature)) { + const restType = getTypeOfSymbol(signature.parameters[signature.parameters.length - 1]); + if (!isTupleType(restType)) { + return isTypeAny(restType) ? anyArrayType : restType; + } + if (restType.target.combinedFlags & ElementFlags.Variable) { + return sliceTupleType(restType, restType.target.fixedLength); + } + } + return undefined; + } + + function getNonArrayRestType(signature: Signature) { + const restType = getEffectiveRestType(signature); + return restType && !isArrayType(restType) && !isTypeAny(restType) ? restType : undefined; + } + + function getTypeOfFirstParameterOfSignature(signature: Signature) { + return getTypeOfFirstParameterOfSignatureWithFallback(signature, neverType); + } + + 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; + const typeNode = getEffectiveTypeAnnotationNode(declaration); + if (typeNode) { + const source = addOptionality(getTypeFromTypeNode(typeNode), /*isProperty*/ false, isOptionalDeclaration(declaration)); + const target = getTypeAtPosition(context, i); + inferTypes(inferenceContext.inferences, source, target); + } + } + } + + 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! + } + } + 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]; + const declaration = parameter.valueDeclaration as ParameterDeclaration; + if (!getEffectiveTypeAnnotationNode(declaration)) { + let type = tryGetTypeAtPosition(context, i); + if (type && declaration.initializer) { + let initializerType = checkDeclarationInitializer(declaration, CheckMode.Normal); + if (!isTypeAssignableTo(initializerType, type) && isTypeAssignableTo(type, initializerType = widenTypeInferredFromInitializer(declaration, initializerType))) { + type = initializerType; + } + } + assignParameterType(parameter, type); + } + } + 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 assignNonContextualParameterTypes(signature: Signature) { + if (signature.thisParameter) { + assignParameterType(signature.thisParameter); + } + for (const parameter of signature.parameters) { + assignParameterType(parameter); + } + } + + function assignParameterType(parameter: Symbol, contextualType?: Type) { + const links = getSymbolLinks(parameter); + if (!links.type) { + const declaration = parameter.valueDeclaration as ParameterDeclaration | undefined; + links.type = addOptionality( + contextualType || (declaration ? getWidenedTypeForVariableLikeDeclaration(declaration, /*reportErrors*/ true) : getTypeOfSymbol(parameter)), + /*isProperty*/ false, + /*isOptional*/ !!declaration && !declaration.initializer && isOptionalDeclaration(declaration), + ); + 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 (contextualType) { + Debug.assertEqual(links.type, contextualType, "Parameter symbol already has a cached type which differs from newly assigned type"); + } + } + + // 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, /*noTupleBoundsCheck*/ false); + if (element.name.kind === SyntaxKind.Identifier) { + getSymbolLinks(getSymbolOfDeclaration(element)).type = type; + } + else { + assignBindingElementTypes(element.name, type); + } + } + } + } + + function createClassDecoratorContextType(classType: Type) { + return tryCreateTypeReference(getGlobalClassDecoratorContextType(/*reportErrors*/ true), [classType]); + } + + function createClassMethodDecoratorContextType(thisType: Type, valueType: Type) { + return tryCreateTypeReference(getGlobalClassMethodDecoratorContextType(/*reportErrors*/ true), [thisType, valueType]); + } + + function createClassGetterDecoratorContextType(thisType: Type, valueType: Type) { + return tryCreateTypeReference(getGlobalClassGetterDecoratorContextType(/*reportErrors*/ true), [thisType, valueType]); + } + + function createClassSetterDecoratorContextType(thisType: Type, valueType: Type) { + return tryCreateTypeReference(getGlobalClassSetterDecoratorContextType(/*reportErrors*/ true), [thisType, valueType]); + } + + function createClassAccessorDecoratorContextType(thisType: Type, valueType: Type) { + return tryCreateTypeReference(getGlobalClassAccessorDecoratorContextType(/*reportErrors*/ true), [thisType, valueType]); + } + + function createClassFieldDecoratorContextType(thisType: Type, valueType: Type) { + return tryCreateTypeReference(getGlobalClassFieldDecoratorContextType(/*reportErrors*/ true), [thisType, valueType]); + } + + /** + * Gets a type like `{ name: "foo", private: false, static: true }` that is used to provided member-specific + * details that will be intersected with a decorator context type. + */ + function getClassMemberDecoratorContextOverrideType(nameType: Type, isPrivate: boolean, isStatic: boolean) { + const key = `${isPrivate ? "p" : "P"}${isStatic ? "s" : "S"}${nameType.id}` as const; + let overrideType = decoratorContextOverrideTypeCache.get(key); + if (!overrideType) { + const members = createSymbolTable(); + members.set("name" as __String, createProperty("name" as __String, nameType)); + members.set("private" as __String, createProperty("private" as __String, isPrivate ? trueType : falseType)); + members.set("static" as __String, createProperty("static" as __String, isStatic ? trueType : falseType)); + overrideType = createAnonymousType(/*symbol*/ undefined, members, emptyArray, emptyArray, emptyArray); + decoratorContextOverrideTypeCache.set(key, overrideType); + } + return overrideType; + } + + function createClassMemberDecoratorContextTypeForNode(node: MethodDeclaration | AccessorDeclaration | PropertyDeclaration, thisType: Type, valueType: Type) { + const isStatic = hasStaticModifier(node); + const isPrivate = isPrivateIdentifier(node.name); + const nameType = isPrivate ? getStringLiteralType(idText(node.name)) : getLiteralTypeFromPropertyName(node.name); + const contextType = isMethodDeclaration(node) ? createClassMethodDecoratorContextType(thisType, valueType) : + isGetAccessorDeclaration(node) ? createClassGetterDecoratorContextType(thisType, valueType) : + isSetAccessorDeclaration(node) ? createClassSetterDecoratorContextType(thisType, valueType) : + isAutoAccessorPropertyDeclaration(node) ? createClassAccessorDecoratorContextType(thisType, valueType) : + isPropertyDeclaration(node) ? createClassFieldDecoratorContextType(thisType, valueType) : + Debug.failBadSyntaxKind(node); + const overrideType = getClassMemberDecoratorContextOverrideType(nameType, isPrivate, isStatic); + return getIntersectionType([contextType, overrideType]); + } + + function createClassAccessorDecoratorTargetType(thisType: Type, valueType: Type) { + return tryCreateTypeReference(getGlobalClassAccessorDecoratorTargetType(/*reportErrors*/ true), [thisType, valueType]); + } + + function createClassAccessorDecoratorResultType(thisType: Type, valueType: Type) { + return tryCreateTypeReference(getGlobalClassAccessorDecoratorResultType(/*reportErrors*/ true), [thisType, valueType]); + } + + function createClassFieldDecoratorInitializerMutatorType(thisType: Type, valueType: Type) { + const thisParam = createParameter("this" as __String, thisType); + const valueParam = createParameter("value" as __String, valueType); + return createFunctionType(/*typeParameters*/ undefined, thisParam, [valueParam], valueType, /*typePredicate*/ undefined, 1); + } + + /** + * Creates a call signature for an ES Decorator. This method is used by the semantics of + * `getESDecoratorCallSignature`, which you should probably be using instead. + */ + function createESDecoratorCallSignature(targetType: Type, contextType: Type, nonOptionalReturnType: Type) { + const targetParam = createParameter("target" as __String, targetType); + const contextParam = createParameter("context" as __String, contextType); + const returnType = getUnionType([nonOptionalReturnType, voidType]); + return createCallSignature(/*typeParameters*/ undefined, /*thisParameter*/ undefined, [targetParam, contextParam], returnType); + } + + /** + * Gets a call signature that should be used when resolving `decorator` as a call. This does not use the value + * of the decorator itself, but instead uses the declaration on which it is placed along with its relative + * position amongst other decorators on the same declaration to determine the applicable signature. The + * resulting signature can be used for call resolution, inference, and contextual typing. + */ + function getESDecoratorCallSignature(decorator: Decorator) { + // We are considering a future change that would allow the type of a decorator to affect the type of the + // class and its members, such as a `@Stringify` decorator changing the type of a `number` field to `string`, or + // a `@Callable` decorator adding a call signature to a `class`. The type arguments for the various context + // types may eventually change to reflect such mutations. + // + // In some cases we describe such potential mutations as coming from a "prior decorator application". It is + // important to note that, while decorators are *evaluated* left to right, they are *applied* right to left + // to preserve f ৹ g -> f(g(x)) application order. In these cases, a "prior" decorator usually means the + // next decorator following this one in document order. + // + // The "original type" of a class or member is the type it was declared as, or the type we infer from + // initializers, before _any_ decorators are applied. + // + // The type of a class or member that is a result of a prior decorator application represents the + // "current type", i.e., the type for the declaration at the time the decorator is _applied_. + // + // The type of a class or member that is the result of the application of *all* relevant decorators is the + // "final type". + // + // Any decorator that allows mutation or replacement will also refer to an "input type" and an + // "output type". The "input type" corresponds to the "current type" of the declaration, while the + // "output type" will become either the "input type/current type" for a subsequent decorator application, + // or the "final type" for the decorated declaration. + // + // It is important to understand decorator application order as it relates to how the "current", "input", + // "output", and "final" types will be determined: + // + // @E2 @E1 class SomeClass { + // @A2 @A1 static f() {} + // @B2 @B1 g() {} + // @C2 @C1 static x; + // @D2 @D1 y; + // } + // + // Per [the specification][1], decorators are applied in the following order: + // + // 1. For each static method (incl. get/set methods and `accessor` fields), in document order: + // a. Apply each decorator for that method, in reverse order (`A1`, `A2`). + // 2. For each instance method (incl. get/set methods and `accessor` fields), in document order: + // a. Apply each decorator for that method, in reverse order (`B1`, `B2`). + // 3. For each static field (excl. auto-accessors), in document order: + // a. Apply each decorator for that field, in reverse order (`C1`, `C2`). + // 4. For each instance field (excl. auto-accessors), in document order: + // a. Apply each decorator for that field, in reverse order (`D1`, `D2`). + // 5. Apply each decorator for the class, in reverse order (`E1`, `E2`). + // + // As a result, "current" types at each decorator application are as follows: + // - For `A1`, the "current" types of the class and method are their "original" types. + // - For `A2`, the "current type" of the method is the "output type" of `A1`, and the "current type" of the + // class is the type of `SomeClass` where `f` is the "output type" of `A1`. This becomes the "final type" + // of `f`. + // - For `B1`, the "current type" of the method is its "original type", and the "current type" of the class + // is the type of `SomeClass` where `f` now has its "final type". + // - etc. + // + // [1]: https://arai-a.github.io/ecma262-compare/?pr=2417&id=sec-runtime-semantics-classdefinitionevaluation + // + // This seems complicated at first glance, but is not unlike our existing inference for functions: + // + // declare function pipe( + // original: Original, + // a1: (input: Original, context: Context) => A1, + // a2: (input: A1, context: Context) => A2, + // b1: (input: A2, context: Context) => B1, + // b2: (input: B1, context: Context) => B2, + // c1: (input: B2, context: Context) => C1, + // c2: (input: C1, context: Context) => C2, + // d1: (input: C2, context: Context) => D1, + // d2: (input: D1, context: Context) => D2, + // e1: (input: D2, context: Context) => E1, + // e2: (input: E1, context: Context) => E2, + // ): E2; + + // When a decorator is applied, it is passed two arguments: "target", which is a value representing the + // thing being decorated (constructors for classes, functions for methods/accessors, `undefined` for fields, + // and a `{ get, set }` object for auto-accessors), and "context", which is an object that provides + // reflection information about the decorated element, as well as the ability to add additional "extra" + // initializers. In most cases, the "target" argument corresponds to the "input type" in some way, and the + // return value similarly corresponds to the "output type" (though if the "output type" is `void` or + // `undefined` then the "output type" is the "input type"). + + const { parent } = decorator; + const links = getNodeLinks(parent); + if (!links.decoratorSignature) { + links.decoratorSignature = anySignature; + switch (parent.kind) { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: { + // Class decorators have a `context` of `ClassDecoratorContext`, where the `Class` type + // argument will be the "final type" of the class after all decorators are applied. + + const node = parent as ClassDeclaration | ClassExpression; + const targetType = getTypeOfSymbol(getSymbolOfDeclaration(node)); + const contextType = createClassDecoratorContextType(targetType); + links.decoratorSignature = createESDecoratorCallSignature(targetType, contextType, targetType); + break; + } + + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: { + const node = parent as MethodDeclaration | GetAccessorDeclaration | SetAccessorDeclaration; + if (!isClassLike(node.parent)) break; + + // Method decorators have a `context` of `ClassMethodDecoratorContext`, where the + // `Value` type argument corresponds to the "final type" of the method. + // + // Getter decorators have a `context` of `ClassGetterDecoratorContext`, where the + // `Value` type argument corresponds to the "final type" of the value returned by the getter. + // + // Setter decorators have a `context` of `ClassSetterDecoratorContext`, where the + // `Value` type argument corresponds to the "final type" of the parameter of the setter. + // + // In all three cases, the `This` type argument is the "final type" of either the class or + // instance, depending on whether the member was `static`. + + const valueType = isMethodDeclaration(node) ? getOrCreateTypeFromSignature(getSignatureFromDeclaration(node)) : + getTypeOfNode(node); + + const thisType = hasStaticModifier(node) ? + getTypeOfSymbol(getSymbolOfDeclaration(node.parent)) : + getDeclaredTypeOfClassOrInterface(getSymbolOfDeclaration(node.parent)); + + // We wrap the "input type", if necessary, to match the decoration target. For getters this is + // something like `() => inputType`, for setters it's `(value: inputType) => void` and for + // methods it is just the input type. + const targetType = isGetAccessorDeclaration(node) ? createGetterFunctionType(valueType) : + isSetAccessorDeclaration(node) ? createSetterFunctionType(valueType) : + valueType; + + const contextType = createClassMemberDecoratorContextTypeForNode(node, thisType, valueType); + + // We also wrap the "output type", as needed. + const returnType = isGetAccessorDeclaration(node) ? createGetterFunctionType(valueType) : + isSetAccessorDeclaration(node) ? createSetterFunctionType(valueType) : + valueType; + + links.decoratorSignature = createESDecoratorCallSignature(targetType, contextType, returnType); + break; + } + + case SyntaxKind.PropertyDeclaration: { + const node = parent as PropertyDeclaration; + if (!isClassLike(node.parent)) break; + + // Field decorators have a `context` of `ClassFieldDecoratorContext` and + // auto-accessor decorators have a `context` of `ClassAccessorDecoratorContext. In + // both cases, the `This` type argument is the "final type" of either the class or instance, + // depending on whether the member was `static`, and the `Value` type argument corresponds to + // the "final type" of the value stored in the field. + + const valueType = getTypeOfNode(node); + const thisType = hasStaticModifier(node) ? + getTypeOfSymbol(getSymbolOfDeclaration(node.parent)) : + getDeclaredTypeOfClassOrInterface(getSymbolOfDeclaration(node.parent)); + + // The `target` of an auto-accessor decorator is a `{ get, set }` object, representing the + // runtime-generated getter and setter that are added to the class/prototype. The `target` of a + // regular field decorator is always `undefined` as it isn't installed until it is initialized. + const targetType = hasAccessorModifier(node) ? createClassAccessorDecoratorTargetType(thisType, valueType) : + undefinedType; + + const contextType = createClassMemberDecoratorContextTypeForNode(node, thisType, valueType); + + // We wrap the "output type" depending on the declaration. For auto-accessors, we wrap the + // "output type" in a `ClassAccessorDecoratorResult` type, which allows for + // mutation of the runtime-generated getter and setter, as well as the injection of an + // initializer mutator. For regular fields, we wrap the "output type" in an initializer mutator. + const returnType = hasAccessorModifier(node) ? createClassAccessorDecoratorResultType(thisType, valueType) : + createClassFieldDecoratorInitializerMutatorType(thisType, valueType); + + links.decoratorSignature = createESDecoratorCallSignature(targetType, contextType, returnType); + break; + } + } + } + return links.decoratorSignature === anySignature ? undefined : links.decoratorSignature; + } + + function getLegacyDecoratorCallSignature(decorator: Decorator) { + const { parent } = decorator; + const links = getNodeLinks(parent); + if (!links.decoratorSignature) { + links.decoratorSignature = anySignature; + switch (parent.kind) { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: { + const node = parent as ClassDeclaration | ClassExpression; + // For a class decorator, the `target` is the type of the class (e.g. the + // "static" or "constructor" side of the class). + const targetType = getTypeOfSymbol(getSymbolOfDeclaration(node)); + const targetParam = createParameter("target" as __String, targetType); + links.decoratorSignature = createCallSignature( + /*typeParameters*/ undefined, + /*thisParameter*/ undefined, + [targetParam], + getUnionType([targetType, voidType]), + ); + break; + } + case SyntaxKind.Parameter: { + const node = parent as ParameterDeclaration; + if ( + !isConstructorDeclaration(node.parent) && + !(isMethodDeclaration(node.parent) || isSetAccessorDeclaration(node.parent) && isClassLike(node.parent.parent)) + ) { + break; + } + + if (getThisParameter(node.parent) === node) { + break; + } + + const index = getThisParameter(node.parent) ? + node.parent.parameters.indexOf(node) - 1 : + node.parent.parameters.indexOf(node); + Debug.assert(index >= 0); + + // A parameter declaration decorator will have three arguments (see `ParameterDecorator` in + // core.d.ts). + + const targetType = isConstructorDeclaration(node.parent) ? getTypeOfSymbol(getSymbolOfDeclaration(node.parent.parent)) : + getParentTypeOfClassElement(node.parent); + + const keyType = isConstructorDeclaration(node.parent) ? undefinedType : + getClassElementPropertyKeyType(node.parent); + + const indexType = getNumberLiteralType(index); + + const targetParam = createParameter("target" as __String, targetType); + const keyParam = createParameter("propertyKey" as __String, keyType); + const indexParam = createParameter("parameterIndex" as __String, indexType); + links.decoratorSignature = createCallSignature( + /*typeParameters*/ undefined, + /*thisParameter*/ undefined, + [targetParam, keyParam, indexParam], + voidType, + ); + break; + } + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.PropertyDeclaration: { + const node = parent as MethodDeclaration | AccessorDeclaration | PropertyDeclaration; + if (!isClassLike(node.parent)) break; + + // A method or accessor declaration decorator will have either two or three arguments (see + // `PropertyDecorator` and `MethodDecorator` in core.d.ts). + + const targetType = getParentTypeOfClassElement(node); + const targetParam = createParameter("target" as __String, targetType); + + const keyType = getClassElementPropertyKeyType(node); + const keyParam = createParameter("propertyKey" as __String, keyType); + + const returnType = isPropertyDeclaration(node) ? voidType : + createTypedPropertyDescriptorType(getTypeOfNode(node)); + + const hasPropDesc = !isPropertyDeclaration(parent) || hasAccessorModifier(parent); + if (hasPropDesc) { + const descriptorType = createTypedPropertyDescriptorType(getTypeOfNode(node)); + const descriptorParam = createParameter("descriptor" as __String, descriptorType); + links.decoratorSignature = createCallSignature( + /*typeParameters*/ undefined, + /*thisParameter*/ undefined, + [targetParam, keyParam, descriptorParam], + getUnionType([returnType, voidType]), + ); + } + else { + links.decoratorSignature = createCallSignature( + /*typeParameters*/ undefined, + /*thisParameter*/ undefined, + [targetParam, keyParam], + getUnionType([returnType, voidType]), + ); + } + break; + } + } + } + return links.decoratorSignature === anySignature ? undefined : links.decoratorSignature; + } + + function getDecoratorCallSignature(decorator: Decorator) { + return legacyDecorators ? getLegacyDecoratorCallSignature(decorator) : + getESDecoratorCallSignature(decorator); + } + + 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]); + } + + 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]); + } + + return unknownType; + } + + 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_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_requires_the_Promise_constructor_Make_sure_you_have_a_declaration_for_the_Promise_constructor_or_include_ES2015_in_your_lib_option, + ); + } + + return promiseType; + } + + 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.links.type = targetType; + + const members = createSymbolTable([targetPropertySymbol]); + symbol.members = members; + return createAnonymousType(symbol, members, emptyArray, emptyArray, emptyArray); + } + + function getReturnTypeFromBody(func: FunctionLikeDeclaration, checkMode?: CheckMode): Type { + if (!func.body) { + return errorType; + } + + const functionFlags = getFunctionFlags(func); + const isAsync = (functionFlags & FunctionFlags.Async) !== 0; + const isGenerator = (functionFlags & FunctionFlags.Generator) !== 0; + + 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/undefined, but rather a Promise for void/undefined. + const contextualReturnType = getContextualReturnType(func, /*contextFlags*/ undefined); + const returnType = contextualReturnType && (unwrapReturnType(contextualReturnType, functionFlags) || voidType).flags & TypeFlags.Undefined ? undefinedType : voidType; + return functionFlags & FunctionFlags.Async ? createPromiseReturnType(func, returnType) : // Async function + returnType; // 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); + } + } + + if (yieldType) yieldType = getWidenedType(yieldType); + if (returnType) returnType = getWidenedType(returnType); + if (nextType) nextType = getWidenedType(nextType); + } + + if (isGenerator) { + return createGeneratorType( + 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; + } + } + + function createGeneratorType(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; + } + + 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 { + nextType = getContextualType(yieldExpression, /*contextFlags*/ undefined); + } + 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 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; + } + } + 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 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); + } + 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); + } + + 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 => { + let expr = returnStatement.expression; + if (expr) { + expr = skipParentheses(expr, /*excludeJSDocTypeAssertions*/ true); + // Bare calls to this same function don't contribute to inference + // and `return await` is also safe to unwrap here + if (functionFlags & FunctionFlags.Async && expr.kind === SyntaxKind.AwaitExpression) { + expr = skipParentheses((expr as AwaitExpression).expression, /*excludeJSDocTypeAssertions*/ true); + } + if ( + expr.kind === SyntaxKind.CallExpression && + (expr as CallExpression).expression.kind === SyntaxKind.Identifier && + checkExpressionCached((expr as CallExpression).expression).symbol === getMergedSymbol(func.symbol) && + (!isFunctionExpressionOrArrowFunction(func.symbol.valueDeclaration!) || isConstantReference((expr as CallExpression).expression)) + ) { + hasReturnOfTypeNever = true; + return; + } + + 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); + } + else { + hasReturnWithNoExpression = true; + } + }); + if (aggregatedTypes.length === 0 && !hasReturnWithNoExpression && (hasReturnOfTypeNever || mayReturnNever(func))) { + return undefined; + } + 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; + } + } + + function getTypePredicateFromBody(func: FunctionLikeDeclaration): TypePredicate | undefined { + switch (func.kind) { + case SyntaxKind.Constructor: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + return undefined; + } + const functionFlags = getFunctionFlags(func); + if (functionFlags !== FunctionFlags.Normal) return undefined; + + // Only attempt to infer a type predicate if there's exactly one return. + let singleReturn: Expression | undefined; + if (func.body && func.body.kind !== SyntaxKind.Block) { + singleReturn = func.body; // arrow function + } + else { + const bailedEarly = forEachReturnStatement(func.body as Block, returnStatement => { + if (singleReturn || !returnStatement.expression) return true; + singleReturn = returnStatement.expression; + }); + if (bailedEarly || !singleReturn || functionHasImplicitReturn(func)) return undefined; + } + return checkIfExpressionRefinesAnyParameter(func, singleReturn); + } + + function checkIfExpressionRefinesAnyParameter(func: FunctionLikeDeclaration, expr: Expression): TypePredicate | undefined { + expr = skipParentheses(expr, /*excludeJSDocTypeAssertions*/ true); + const returnType = checkExpressionCached(expr); + if (!(returnType.flags & TypeFlags.Boolean)) return undefined; + + return forEach(func.parameters, (param, i) => { + const initType = getTypeOfSymbol(param.symbol); + if (!initType || initType.flags & TypeFlags.Boolean || !isIdentifier(param.name) || isSymbolAssigned(param.symbol) || isRestParameter(param)) { + // Refining "x: boolean" to "x is true" or "x is false" isn't useful. + return; + } + const trueType = checkIfExpressionRefinesParameter(func, expr, param, initType); + if (trueType) { + return createTypePredicate(TypePredicateKind.Identifier, unescapeLeadingUnderscores(param.name.escapedText), i, trueType); + } + }); + } + + function checkIfExpressionRefinesParameter(func: FunctionLikeDeclaration, expr: Expression, param: ParameterDeclaration, initType: Type): Type | undefined { + const antecedent = (expr as Expression & { flowNode?: FlowNode; }).flowNode || + expr.parent.kind === SyntaxKind.ReturnStatement && (expr.parent as ReturnStatement).flowNode || + createFlowNode(FlowFlags.Start, /*node*/ undefined, /*antecedent*/ undefined); + const trueCondition = createFlowNode(FlowFlags.TrueCondition, expr, antecedent); + + const trueType = getFlowTypeOfReference(param.name, initType, initType, func, trueCondition); + if (trueType === initType) return undefined; + + // "x is T" means that x is T if and only if it returns true. If it returns false then x is not T. + // This means that if the function is called with an argument of type trueType, there can't be anything left in the `else` branch. It must reduce to `never`. + const falseCondition = createFlowNode(FlowFlags.FalseCondition, expr, antecedent); + const falseSubtype = getFlowTypeOfReference(param.name, initType, trueType, func, falseCondition); + return falseSubtype.flags & TypeFlags.Never ? trueType : undefined; + } + + /** + * 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 an explicitly specified return type that includes `void` or is exactly `any` or `undefined` don't + // need any return statements. + if (type && (maybeTypeOfKind(type, TypeFlags.Void) || type.flags & (TypeFlags.Any | TypeFlags.Undefined))) { + 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; + + 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_undefined_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 (isUnwrappedReturnTypeUndefinedVoidOrAny(func, inferredReturnType)) { + return; + } + } + 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); + + 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(/*declaration*/ undefined, /*typeParameters*/ undefined, /*thisParameter*/ undefined, emptyArray, returnType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.IsNonInferrable); + const returnOnlyType = createAnonymousType(node.symbol, emptySymbols, [returnOnlySignature], emptyArray, emptyArray); + returnOnlyType.objectFlags |= ObjectFlags.NonInferrableType; + return links.contextFreeType = returnOnlyType; + } + } + return anyFunctionType; + } + + // Grammar checking + const hasGrammarError = checkGrammarFunctionLikeDeclaration(node); + if (!hasGrammarError && node.kind === SyntaxKind.FunctionExpression) { + checkGrammarForGenerator(node); + } + + contextuallyCheckFunctionExpressionOrObjectLiteralMethod(node, checkMode); + + return getTypeOfSymbol(getSymbolOfDeclaration(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. + 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(getSymbolOfDeclaration(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); + } + } + else if (contextualSignature && !node.typeParameters && contextualSignature.parameters.length > node.parameters.length) { + const inferenceContext = getInferenceContext(node); + if (checkMode && checkMode & CheckMode.Inferential) { + inferFromAnnotatedParameters(signature, contextualSignature, inferenceContext!); + } + } + if (contextualSignature && !getReturnTypeFromAnnotation(node) && !signature.resolvedReturnType) { + const returnType = getReturnTypeFromBody(node, checkMode); + if (!signature.resolvedReturnType) { + signature.resolvedReturnType = returnType; + } + } + checkSignatureDeclaration(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); + + 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) { + const effectiveCheckNode = getEffectiveCheckNode(node.body); + if ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.Async) { // Async function + const awaitedType = checkAwaitedType(exprType, /*withAlias*/ false, effectiveCheckNode, 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, effectiveCheckNode, effectiveCheckNode); + } + else { // Normal function + checkTypeAssignableToAndOptionallyElaborate(exprType, returnOrPromisedType, effectiveCheckNode, effectiveCheckNode); + } + } + } + } + } + + 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; + } + 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; + } + // 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.Constant || + 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 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 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); + } + else { + checkDeleteExpressionMustBeOptional(expr, symbol); + } + } + 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 : hasTypeFacts(type, TypeFacts.IsUndefined)) + ) { + error(expr, Diagnostics.The_operand_of_a_delete_operator_must_be_optional); + } + } + + function checkTypeOfExpression(node: TypeOfExpression): Type { + checkExpression(node.expression); + return typeofType; + } + + function checkVoidExpression(node: VoidExpression): Type { + checkNodeDeferred(node); + return undefinedWideningType; + } + + function checkAwaitGrammar(node: AwaitExpression | VariableDeclarationList): boolean { + // Grammar checking + let hasError = false; + const container = getContainingFunctionOrClassStaticBlock(node); + if (container && isClassStaticBlockDeclaration(container)) { + // NOTE: We report this regardless as to whether there are parse diagnostics. + const message = isAwaitExpression(node) ? Diagnostics.await_expression_cannot_be_used_inside_a_class_static_block : + Diagnostics.await_using_statements_cannot_be_used_inside_a_class_static_block; + error(node, message); + hasError = true; + } + 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 message = isAwaitExpression(node) ? 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.await_using_statements_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; + const diagnostic = createFileDiagnostic(sourceFile, span.start, span.length, message); + diagnostics.add(diagnostic); + hasError = true; + } + 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), + ); + hasError = true; + break; + } + // fallthrough + case ModuleKind.ES2022: + case ModuleKind.ESNext: + case ModuleKind.Preserve: + case ModuleKind.System: + if (languageVersion >= ScriptTarget.ES2017) { + break; + } + // fallthrough + default: + span ??= getSpanOfTokenAtPosition(sourceFile, node.pos); + const message = isAwaitExpression(node) ? Diagnostics.Top_level_await_expressions_are_only_allowed_when_the_module_option_is_set_to_es2022_esnext_system_node16_nodenext_or_preserve_and_the_target_option_is_set_to_es2017_or_higher : + Diagnostics.Top_level_await_using_statements_are_only_allowed_when_the_module_option_is_set_to_es2022_esnext_system_node16_nodenext_or_preserve_and_the_target_option_is_set_to_es2017_or_higher; + diagnostics.add(createFileDiagnostic(sourceFile, span.start, span.length, message)); + hasError = true; + break; + } + } + } + else { + // use of 'await' in non-async function + const sourceFile = getSourceFileOfNode(node); + if (!hasParseDiagnostics(sourceFile)) { + const span = getSpanOfTokenAtPosition(sourceFile, node.pos); + const message = isAwaitExpression(node) ? Diagnostics.await_expressions_are_only_allowed_within_async_functions_and_at_the_top_levels_of_modules : + Diagnostics.await_using_statements_are_only_allowed_within_async_functions_and_at_the_top_levels_of_modules; + const diagnostic = createFileDiagnostic(sourceFile, span.start, span.length, message); + 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); + hasError = true; + } + } + } + + if (isAwaitExpression(node) && isInParameterInitializerBeforeContainingFunction(node)) { + // NOTE: We report this regardless as to whether there are parse diagnostics. + error(node, Diagnostics.await_expressions_cannot_be_used_in_a_parameter_initializer); + hasError = true; + } + + return hasError; + } + + function checkAwaitExpression(node: AwaitExpression): Type { + addLazyDiagnostic(() => checkAwaitGrammar(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; + } + + 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))); + } + return numberType; + } + return getUnaryResultType(operandType); + case SyntaxKind.ExclamationToken: + checkTruthinessOfType(operandType, 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( + 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); + } + + 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); + } + + // 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; + } + + 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 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 isConstEnumObjectType(type: Type): boolean { + return !!(getObjectFlags(type) & ObjectFlags.Anonymous) && !!type.symbol && isConstEnumSymbol(type.symbol); + } + + function isConstEnumSymbol(symbol: Symbol): boolean { + return (symbol.flags & SymbolFlags.ConstEnum) !== 0; + } + + /** + * Get the type of the `[Symbol.hasInstance]` method of an object type. + */ + function getSymbolHasInstanceMethodOfObjectType(type: Type) { + const hasInstancePropertyName = getPropertyNameForKnownSymbolName("hasInstance"); + if (allTypesAssignableToKind(type, TypeFlags.NonPrimitive)) { + const hasInstanceProperty = getPropertyOfType(type, hasInstancePropertyName); + if (hasInstanceProperty) { + const hasInstancePropertyType = getTypeOfSymbol(hasInstanceProperty); + if (hasInstancePropertyType && getSignaturesOfType(hasInstancePropertyType, SignatureKind.Call).length !== 0) { + return hasInstancePropertyType; + } + } + } + } + + function checkInstanceOfExpression(left: Expression, right: Expression, leftType: Type, rightType: Type, checkMode?: CheckMode): 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); + } + + Debug.assert(isInstanceOfExpression(left.parent)); + const signature = getResolvedSignature(left.parent, /*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 rightType has a `[Symbol.hasInstance]` method that is not `(value: unknown) => boolean`, we + // must check the expression as if it were a call to `right[Symbol.hasInstance](left)`. The call to + // `getResolvedSignature`, below, will check that leftType is assignable to the type of the first + // parameter. + const returnType = getReturnTypeOfSignature(signature); + + // We also verify that the return type of the `[Symbol.hasInstance]` method is assignable to + // `boolean`. According to the spec, the runtime will actually perform `ToBoolean` on the result, + // but this is more type-safe. + checkTypeAssignableTo(returnType, booleanType, right, Diagnostics.An_object_s_Symbol_hasInstance_method_must_return_a_boolean_value_for_it_to_be_used_on_the_right_hand_side_of_an_instanceof_expression); + + return booleanType; + } + + function hasEmptyObjectIntersection(type: Type): boolean { + return someType(type, t => t === unknownEmptyObjectType || !!(t.flags & TypeFlags.Intersection) && isEmptyAnonymousObjectType(getBaseConstraintOrType(t))); + } + + function checkInExpression(left: Expression, right: Expression, leftType: Type, rightType: Type): Type { + if (leftType === silentNeverType || rightType === silentNeverType) { + return silentNeverType; + } + if (isPrivateIdentifier(left)) { + if ( + languageVersion < LanguageFeatureMinimumTarget.PrivateNamesAndClassStaticBlocks || + languageVersion < LanguageFeatureMinimumTarget.ClassAndClassElementDecorators || + !useDefineForClassFields + ) { + 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); + } + } + 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; + } + + 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); + } + } + 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 < LanguageFeatureMinimumTarget.ObjectSpreadRest) { + checkExternalEmitHelpers(property, ExternalEmitHelpers.Rest); + } + const nonRestNames: PropertyName[] = []; + if (allProperties) { + for (const otherProperty of allProperties) { + if (!isSpreadAssignment(otherProperty)) { + nonRestNames.push(otherProperty.name); + } + } + } + const type = getRestType(objectLiteralType, nonRestNames, objectLiteralType.symbol); + checkGrammarForDisallowedTrailingComma(allProperties, Diagnostics.A_rest_parameter_or_binding_pattern_may_not_have_a_trailing_comma); + return checkDestructuringAssignment(property.expression, type); + } + } + else { + error(property, Diagnostics.Property_assignment_expected); + } + } + + function checkArrayLiteralAssignment(node: ArrayLiteralExpression, sourceType: Type, checkMode?: CheckMode): Type { + const elements = node.elements; + if (languageVersion < LanguageFeatureMinimumTarget.DestructuringAssignment && 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 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 { + 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; + } + + 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 && + !(hasTypeFacts(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) { + sourceType = getTypeWithFacts(sourceType, TypeFacts.NEUndefined); + } + } + 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); + } + + 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)) { + // NOTE: we do not limit this to LanguageFeatureTargets.PrivateNames as some other feature downleveling still requires this. + checkExternalEmitHelpers(target.parent, ExternalEmitHelpers.ClassPrivateFieldSet); + } + return sourceType; + } + + /** + * 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); + + 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; + + // 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 isTypeEqualityComparableTo(source: Type, target: Type) { + return (target.flags & TypeFlags.Nullable) !== 0 || isTypeComparableTo(source, target); + } + + 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)[]; + } + + const trampoline = createBinaryExpressionTrampoline(onEnter, onLeft, onOperator, onRight, onExit, foldState); + + return (node: BinaryExpression, checkMode: CheckMode | undefined) => { + const result = trampoline(node, checkMode); + Debug.assertIsDefined(result); + return result; + }; + + 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], + }; + } + + if (isInJSFile(node) && getAssignedExpandoInitializer(node)) { + state.skip = true; + setLastResult(state, checkExpression(node.right, checkMode)); + 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; + } + + 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 (isLogicalOrCoalescingBinaryOperator(operator)) { + let parent = node.parent; + while (parent.kind === SyntaxKind.ParenthesizedExpression || isLogicalOrCoalescingBinaryExpression(parent)) { + parent = parent.parent; + } + if (operator === SyntaxKind.AmpersandAmpersandToken || isIfStatement(parent)) { + checkTestingKnownTruthyCallableOrAwaitableOrEnumMemberType(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 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); + + result = checkBinaryLikeExpressionWorker(node.left, node.operatorToken, node.right, leftType, rightType, state.checkMode, node); + } + + state.skip = false; + setLeftType(state, /*type*/ undefined); + setLastResult(state, /*type*/ undefined); + state.stackIndex--; + return result; + } + + function foldState(state: WorkArea, result: Type | undefined, _side: "left" | "right") { + setLastResult(state, result); + return state; + } + + function maybeCheckExpression(state: WorkArea, node: Expression): BinaryExpression | undefined { + if (isBinaryExpression(node)) { + return node; + } + setLastResult(state, checkExpression(node, state.checkMode)); + } + + function getLeftType(state: WorkArea) { + return state.typeStack[state.stackIndex]; + } + + function setLeftType(state: WorkArea, type: Type | undefined) { + state.typeStack[state.stackIndex] = type; + } + + function getLastResult(state: WorkArea) { + return state.typeStack[state.stackIndex + 1]; + } + + 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 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)); + } + } + } + + // 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: BinaryOperatorToken, 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 (isLogicalOrCoalescingBinaryOperator(operator)) { + leftType = checkTruthinessExpression(left, checkMode); + } + else { + leftType = checkExpression(left, checkMode); + } + + const rightType = checkExpression(right, checkMode); + return checkBinaryLikeExpressionWorker(left, operatorToken, right, leftType, rightType, checkMode, errorNode); + } + + function checkBinaryLikeExpressionWorker( + left: Expression, + operatorToken: BinaryOperatorToken, + right: Expression, + leftType: Type, + rightType: Type, + checkMode?: CheckMode, + 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: PunctuationSyntaxKind | 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; + } + + if (!isTypeAssignableToKind(leftType, TypeFlags.StringLike) && !isTypeAssignableToKind(rightType, TypeFlags.StringLike)) { + leftType = checkNonNullType(leftType, left); + rightType = checkNonNullType(rightType, right); + } + + 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; + } + + // Symbols are not allowed at all in arithmetic expressions + if (resultType && !checkForDisallowedESSymbolOperand(operator)) { + 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; + } + + if (operator === SyntaxKind.PlusEqualsToken) { + checkAssignmentOperator(resultType); + } + return resultType; + case SyntaxKind.LessThanToken: + case SyntaxKind.GreaterThanToken: + case SyntaxKind.LessThanEqualsToken: + case SyntaxKind.GreaterThanEqualsToken: + if (checkForDisallowedESSymbolOperand(operator)) { + leftType = getBaseTypeOfLiteralTypeForComparison(checkNonNullType(leftType, left)); + rightType = getBaseTypeOfLiteralTypeForComparison(checkNonNullType(rightType, right)); + reportOperatorErrorUnless((left, right) => { + if (isTypeAny(left) || isTypeAny(right)) { + return true; + } + const leftAssignableToNumber = isTypeAssignableTo(left, numberOrBigIntType); + const rightAssignableToNumber = isTypeAssignableTo(right, numberOrBigIntType); + return leftAssignableToNumber && rightAssignableToNumber || + !leftAssignableToNumber && !rightAssignableToNumber && areTypesComparable(left, right); + }); + } + return booleanType; + case SyntaxKind.EqualsEqualsToken: + case SyntaxKind.ExclamationEqualsToken: + case SyntaxKind.EqualsEqualsEqualsToken: + case SyntaxKind.ExclamationEqualsEqualsToken: + // We suppress errors in CheckMode.TypeOnly (meaning the invocation came from getTypeOfExpression). During + // control flow analysis it is possible for operands to temporarily have narrower types, and those narrower + // types may cause the operands to not be comparable. We don't want such errors reported (see #46475). + if (!(checkMode && checkMode & CheckMode.TypeOnly)) { + if ( + (isLiteralExpressionOfObject(left) || isLiteralExpressionOfObject(right)) && + // only report for === and !== in JS, not == or != + (!isInJSFile(left) || (operator === SyntaxKind.EqualsEqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken)) + ) { + 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, checkMode); + case SyntaxKind.InKeyword: + return checkInExpression(left, right, leftType, rightType); + case SyntaxKind.AmpersandAmpersandToken: + case SyntaxKind.AmpersandAmpersandEqualsToken: { + const resultType = hasTypeFacts(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 = hasTypeFacts(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 = hasTypeFacts(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 leftType; + } + else { + checkAssignmentOperator(rightType); + return rightType; + } + case SyntaxKind.CommaToken: + if (!compilerOptions.allowUnreachableCode && isSideEffectFree(left) && !isIndirectCall(left.parent as BinaryExpression)) { + 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(); + } + + 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, /*nameNotFoundMessage*/ undefined, /*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); + } + } + } + } + } + + // Return true for "indirect calls", (i.e. `(0, x.f)(...)` or `(0, eval)(...)`), which prevents passing `this`. + function isIndirectCall(node: BinaryExpression): boolean { + return node.parent.kind === SyntaxKind.ParenthesizedExpression && + isNumericLiteral(node.left) && + node.left.text === "0" && + (isCallExpression(node.parent.parent) && node.parent.parent.expression === node.parent || node.parent.parent.kind === SyntaxKind.TaggedTemplateExpression) && + // special-case for "eval" because it's the only non-access case where an indirect call actually affects behavior. + (isAccessExpression(node.right) || isIdentifier(node.right) && node.right.escapedText === "eval"); + } + + // Return true if there was no error, false if there was an error. + function checkForDisallowedESSymbolOperand(operator: PunctuationSyntaxKind): boolean { + const offendingSymbolOperand = maybeTypeOfKindConsideringBaseConstraint(leftType, TypeFlags.ESSymbolLike) ? left : + maybeTypeOfKindConsideringBaseConstraint(rightType, TypeFlags.ESSymbolLike) ? right : + undefined; + + if (offendingSymbolOperand) { + error(offendingSymbolOperand, Diagnostics.The_0_operator_cannot_be_applied_to_type_symbol, tokenToString(operator)); + return false; + } + + return true; + } + + function getSuggestedBooleanOperator(operator: SyntaxKind): PunctuationSyntaxKind | 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 checkAssignmentOperatorWorker() { + let assigneeType = leftType; + + // getters can be a subtype of setters, so to check for assignability we use the setter's type instead + if (isCompoundAssignment(operatorToken.kind) && left.kind === SyntaxKind.PropertyAccessExpression) { + assigneeType = checkPropertyAccessExpression(left as PropertyAccessExpression, /*checkMode*/ undefined, /*writeOnly*/ true); + } + + // 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)) { + 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, assigneeType, 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); + return true; + } + 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); + } + + 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 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 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 isGlobalNaN(expr: Expression): boolean { + if (isIdentifier(expr) && expr.escapedText === "NaN") { + const globalNaNSymbol = getGlobalNaNSymbol(); + return !!globalNaNSymbol && globalNaNSymbol === getResolvedSymbol(expr); + } + return false; + } + } + + 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]; + } + + function checkYieldExpression(node: YieldExpression): Type { + addLazyDiagnostic(checkYieldExpressionGrammar); + + const func = getContainingFunction(node); + if (!func) return anyType; + const functionFlags = getFunctionFlags(func); + + 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 isAsync = (functionFlags & FunctionFlags.Async) !== 0; + if (node.asteriskToken) { + // Async generator functions prior to ES2018 require the __await, __asyncDelegator, + // and __asyncValues helpers + if (isAsync && languageVersion < LanguageFeatureMinimumTarget.AsyncGenerators) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.AsyncDelegatorIncludes); + } + + // Generator functions prior to ES2015 require the __values helper + if (!isAsync && languageVersion < LanguageFeatureMinimumTarget.Generators && compilerOptions.downlevelIteration) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.Values); + } + } + + // 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. + let returnType = getReturnTypeFromAnnotation(func); + if (returnType && returnType.flags & TypeFlags.Union) { + returnType = filterType(returnType, t => checkGeneratorInstantiationAssignabilityToReturnType(t, functionFlags, /*errorNode*/ undefined)); + } + 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); + } + + 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; + + function checkYieldExpressionGrammar() { + if (!(node.flags & NodeFlags.YieldContext)) { + grammarErrorOnFirstToken(node, Diagnostics.A_yield_expression_is_only_allowed_in_a_generator_body); + } + + if (isInParameterInitializerBeforeContainingFunction(node)) { + error(node, Diagnostics.yield_expressions_cannot_be_used_in_a_parameter_initializer); + } + } + } + + function checkConditionalExpression(node: ConditionalExpression, checkMode?: CheckMode): Type { + const type = checkTruthinessExpression(node.condition, checkMode); + checkTestingKnownTruthyCallableOrAwaitableOrEnumMemberType(node.condition, type, node.whenTrue); + const type1 = checkExpression(node.whenTrue, checkMode); + const type2 = checkExpression(node.whenFalse, checkMode); + return getUnionType([type1, type2], UnionReduction.Subtype); + } + + 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); + } + texts.push(span.literal.text); + types.push(isTypeAssignableTo(type, templateConstraintType) ? type : stringType); + } + const evaluated = node.parent.kind !== SyntaxKind.TaggedTemplateExpression && evaluate(node).value; + if (evaluated) { + return getFreshTypeOfLiteralType(getStringLiteralType(evaluated)); + } + if (isConstContext(node) || isTemplateLiteralContext(node) || someType(getContextualType(node, /*contextFlags*/ undefined) || unknownType, isTemplateLiteralContextualType)) { + return getTemplateLiteralType(texts, types); + } + return stringType; + } + + 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): Expression { + if (isJsxAttributes(node) && !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 contextNode = getContextNode(node); + pushContextualType(contextNode, contextualType, /*isCache*/ false); + pushInferenceContext(contextNode, 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; + popInferenceContext(); + popContextualType(); + return result; + } + + 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)!; + if (isInJSFile(declaration)) { + const typeNode = tryGetJSDocSatisfiesTypeNode(declaration); + if (typeNode) { + return checkSatisfiesExpressionWorker(initializer, typeNode, checkMode); + } + } + 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.combinedFlags & ElementFlags.Variable) && getTypeReferenceArity(type) < declaration.name.elements.length ? + padTupleType(type, declaration.name) : type; + } + + function padTupleType(type: TupleTypeReference, pattern: ArrayBindingPattern) { + const patternElements = pattern.elements; + const elementTypes = getElementTypes(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 = getCombinedNodeFlagsCached(declaration) & NodeFlags.Constant || 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 widened; + } + + 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 isConstContext(node: Expression): boolean { + const parent = node.parent; + return isAssertionExpression(parent) && isConstTypeReference(parent.type) || + isJSDocTypeAssertion(parent) && isConstTypeReference(getJSDocTypeAssertionType(parent)) || + isValidConstAssertionArgument(node) && isConstTypeVariable(getContextualType(node, ContextFlags.None)) || + (isParenthesizedExpression(parent) || isArrayLiteralExpression(parent) || isSpreadElement(parent)) && isConstContext(parent) || + (isPropertyAssignment(parent) || isShorthandPropertyAssignment(parent) || isTemplateSpan(parent)) && isConstContext(parent.parent); + } + + function checkExpressionForMutableLocation(node: Expression, checkMode: CheckMode | undefined, forceTuple?: boolean): Type { + const type = checkExpression(node, checkMode, forceTuple); + return isConstContext(node) || isCommonJsExportedExpression(node) ? getRegularTypeOfLiteralType(type) : + isTypeAssertion(node) ? type : + getWidenedLiteralLikeTypeForContextualType(type, instantiateContextualType(getContextualType(node, /*contextFlags*/ undefined), node, /*contextFlags*/ undefined)); + } + + 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); + } + + return checkExpressionForMutableLocation(node.initializer, checkMode); + } + + function checkObjectLiteralMethod(node: MethodDeclaration, checkMode?: CheckMode): Type { + // Grammar checking + checkGrammarMethod(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.kind === SyntaxKind.ComputedPropertyName) { + checkComputedPropertyName(node.name); + } + + 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 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); + } + } + } + // TODO: The signature may reference any outer inference contexts, but we map pop off and then apply new inference contexts, and thus get different inferred types. + // That this is cached on the *first* such attempt is not currently an issue, since expression types *also* get cached on the first pass. If we ever properly speculate, though, + // the cached "isolatedSignatureType" signature field absolutely needs to be included in the list of speculative caches. + return getOrCreateTypeFromSignature(instantiateSignatureInContextOf(signature, contextualSignature, context), flatMap(inferenceContexts, c => c && map(c.inferences, i => i.typeParameter)).slice()); + } + } + } + } + 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 hasInferenceCandidates(info: InferenceInfo) { + return !!(info.candidates || info.contraCandidates); + } + + 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; + } + } + 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 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); + } + } + if (newTypeParameters) { + const mapper = createTypeMapper(oldTypeParameters!, newTypeParameters); + for (const tp of newTypeParameters) { + tp.mapper = mapper; + } + } + 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; + } + } + } + + function getReturnTypeOfSingleNonGenericCallSignature(funcType: Type) { + const signature = getSingleCallSignature(funcType); + if (signature && !signature.typeParameters) { + return getReturnTypeOfSignature(signature); + } + } + + 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); + } + + /** + * 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, CheckMode.TypeOnly); + // 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 getQuickTypeOfExpression(node: Expression): Type | undefined { + let expr = skipParentheses(node, /*excludeJSDocTypeAssertions*/ true); + if (isJSDocTypeAssertion(expr)) { + const type = getJSDocTypeAssertionType(expr); + if (!isConstTypeReference(type)) { + return getTypeFromTypeNode(type); + } + } + expr = skipParentheses(node); + if (isAwaitExpression(expr)) { + const type = getQuickTypeOfExpression(expr.expression); + return type ? getAwaitedType(type) : undefined; + } + // 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, /*requireStringLiteralLikeArgument*/ true) && !isSymbolOrSymbolForCall(expr)) { + return isCallChain(expr) ? getReturnTypeOfSingleNonGenericSignatureOfCallChain(expr) : + getReturnTypeOfSingleNonGenericCallSignature(checkNonNullExpression(expr.expression)); + } + else if (isAssertionExpression(expr) && !isConstTypeReference(expr.type)) { + return getTypeFromTypeNode((expr as TypeAssertion).type); + } + else if (isLiteralExpression(node) || isBooleanLiteral(node)) { + 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. + * 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; + } + pushContextualType(node, anyType, /*isCache*/ false); + const type = links.contextFreeType = checkExpression(node, CheckMode.SkipContextSensitive); + popContextualType(); + return type; + } + + 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; + } + + 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 (!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); + } + + if (getIsolatedModules(compilerOptions)) { + Debug.assert(!!(type.symbol.flags & SymbolFlags.ConstEnum)); + const constEnumDeclaration = type.symbol.valueDeclaration as EnumDeclaration; + const redirect = host.getRedirectReferenceForResolutionFromSourceOfProject(getSourceFileOfNode(constEnumDeclaration).resolvedPath); + if (constEnumDeclaration.flags & NodeFlags.Ambient && !isValidTypeOnlyAliasUseSite(node) && (!redirect || !shouldPreserveConstEnums(redirect.commandLine.options))) { + error(node, Diagnostics.Cannot_access_ambient_const_enums_when_0_is_enabled, isolatedModulesLikeFlagName); + } + } + } + + function checkParenthesizedExpression(node: ParenthesizedExpression, checkMode?: CheckMode): Type { + if (hasJSDocNodes(node)) { + if (isJSDocSatisfiesExpression(node)) { + return checkSatisfiesExpressionWorker(node.expression, getJSDocSatisfiesExpressionType(node), checkMode); + } + if (isJSDocTypeAssertion(node)) { + return checkAssertionWorker(node, 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(); + } + } + 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 hasSkipDirectInferenceFlag(node) ? + blockedStringType : + 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 checkRegularExpressionLiteral(node as RegularExpressionLiteral); + 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, checkMode); + 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 + + 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(getSymbolOfDeclaration(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(getSymbolOfDeclaration(node)); + const modifiers = getTypeParameterModifiers(typeParameter) & (ModifierFlags.In | ModifierFlags.Out); + if (modifiers) { + const symbol = getSymbolOfDeclaration(node.parent); + if (isTypeAliasDeclaration(node.parent) && !(getObjectFlags(getDeclaredTypeOfSymbol(symbol)) & (ObjectFlags.Reference | 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). + checkGrammarModifiers(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); + } + } + if (!node.initializer && isOptionalDeclaration(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); + } + 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); + } + } + + // 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); + } + } + + 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; + } + + const signature = getSignatureFromDeclaration(parent); + const typePredicate = getTypePredicateOfSignature(signature); + if (!typePredicate) { + return; + } + + 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 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); + 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); + } + + const functionFlags = getFunctionFlags(node as FunctionLikeDeclaration); + if (!(functionFlags & FunctionFlags.Invalid)) { + // Async generators prior to ES2018 require the __await and __asyncGenerator helpers + if ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.AsyncGenerator && languageVersion < LanguageFeatureMinimumTarget.AsyncGenerators) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.AsyncGeneratorIncludes); + } + + // Async functions prior to ES2017 require the __awaiter helper + if ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.Async && languageVersion < LanguageFeatureMinimumTarget.AsyncFunctions) { + 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 < LanguageFeatureMinimumTarget.Generators) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.Generator); + } + } + + checkTypeParameters(getEffectiveTypeParameterDeclarations(node)); + checkUnmatchedJSDocParameters(node); + + forEach(node.parameters, checkParameter); + + // TODO(rbuckton): Should we start checking JSDoc types? + if (node.type) { + checkSourceElement(node.type); + } + + addLazyDiagnostic(checkSignatureDeclarationDiagnostics); + + function checkSignatureDeclarationDiagnostics() { + checkCollisionWithArgumentsInGeneratedCode(node); + + let returnTypeNode = getEffectiveReturnTypeNode(node); + let returnTypeErrorLocation = returnTypeNode; + + if (isInJSFile(node)) { + const typeTag = getJSDocTypeTag(node); + if (typeTag && typeTag.typeExpression && isTypeReferenceNode(typeTag.typeExpression.type)) { + const signature = getSingleCallSignature(getTypeFromTypeNode(typeTag.typeExpression)); + if (signature && signature.declaration) { + returnTypeNode = getEffectiveReturnTypeNode(signature.declaration); + returnTypeErrorLocation = typeTag.typeExpression.type; + } + } + } + + 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 && returnTypeErrorLocation) { + const functionFlags = getFunctionFlags(node as FunctionDeclaration); + if ((functionFlags & (FunctionFlags.Invalid | FunctionFlags.Generator)) === FunctionFlags.Generator) { + const returnType = getTypeFromTypeNode(returnTypeNode); + if (returnType === voidType) { + error(returnTypeErrorLocation, Diagnostics.A_generator_cannot_have_a_void_type_annotation); + } + else { + checkGeneratorInstantiationAssignabilityToReturnType(returnType, functionFlags, returnTypeErrorLocation); + } + } + else if ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.Async) { + checkAsyncFunctionReturnType(node as FunctionLikeDeclaration, returnTypeNode, returnTypeErrorLocation); + } + } + if (node.kind !== SyntaxKind.IndexSignature && node.kind !== SyntaxKind.JSDocFunctionType) { + registerForUnusedIdentifiersCheck(node); + } + } + } + + function checkGeneratorInstantiationAssignabilityToReturnType(returnType: Type, functionFlags: FunctionFlags, errorNode?: TypeNode) { + // 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 = createGeneratorType(generatorYieldType, generatorReturnType, generatorNextType, !!(functionFlags & FunctionFlags.Async)); + + return checkTypeAssignableTo(generatorInstantiation, returnType, errorNode); + } + + 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; + + const memberName = name && getEffectivePropertyNameForPropertyNameNode(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.PropertyDeclaration: + addName(names, name, memberName, DeclarationMeaning.GetOrSetAccessor | privateStaticFlags); + break; + + case SyntaxKind.MethodDeclaration: + addName(names, name, memberName, DeclarationMeaning.Method | privateStaticFlags); + break; + } + } + } + } + + function addName(names: Map<__String, DeclarationMeaning>, 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) { + error(location, Diagnostics.Duplicate_identifier_0, getTextOfNode(location)); + } + else { + names.set(name, prev | 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 = getEffectivePropertyNameForPropertyNameNode(memberNameNode); + switch (memberName) { + case "name": + case "length": + case "caller": + case "arguments": + if (useDefineForClassFields) { + break; + } + // fall through + case "prototype": + const message = Diagnostics.Static_property_0_conflicts_with_built_in_property_Function_0_of_constructor_function_1; + const className = getNameOfSymbolAsWritten(getSymbolOfDeclaration(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; + } + + 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: ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode) { + if (node.kind === SyntaxKind.InterfaceDeclaration) { + const nodeSymbol = getSymbolOfDeclaration(node); + // 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(getSymbolOfDeclaration(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 (!checkGrammarModifiers(node) && !checkGrammarProperty(node)) checkGrammarComputedPropertyName(node.name); + checkVariableLikeDeclaration(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)); + } + } + + 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); + } + + // Grammar checking for modifiers is done inside the function checkGrammarFunctionLikeDeclaration + checkFunctionOrMethodDeclaration(node); + + // 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)); + } + + // 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); + } + + setNodeLinksForPrivateIdentifierScope(node); + } + + function setNodeLinksForPrivateIdentifierScope(node: PropertyDeclaration | PropertySignature | MethodDeclaration | MethodSignature | AccessorDeclaration) { + if (isPrivateIdentifier(node.name)) { + if ( + languageVersion < LanguageFeatureMinimumTarget.PrivateNamesAndClassStaticBlocks || + languageVersion < LanguageFeatureMinimumTarget.ClassAndClassElementDecorators || + !useDefineForClassFields + ) { + for (let lexicalScope = getEnclosingBlockScopeContainer(node); !!lexicalScope; lexicalScope = getEnclosingBlockScopeContainer(lexicalScope)) { + getNodeLinks(lexicalScope).flags |= NodeCheckFlags.ContainsClassWithPrivateIdentifiers; + } + + // 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 checkClassStaticBlockDeclaration(node: ClassStaticBlockDeclaration) { + checkGrammarModifiers(node); + + forEachChild(node, checkSourceElement); + } + + 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); + + checkSourceElement(node.body); + + const symbol = getSymbolOfDeclaration(node); + const firstDeclaration = getDeclarationOfKind(symbol, node.kind); + + // 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; + } + + addLazyDiagnostic(checkConstructorDeclarationDiagnostics); + + return; + + 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; + 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 = !emitStandardClassFields && + (some(node.parent.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; + + 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); + } + } + } + } + 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 nodeImmediatelyReferencesSuperOrThis(node: Node): boolean { + if (node.kind === SyntaxKind.SuperKeyword || node.kind === SyntaxKind.ThisKeyword) { + return true; + } + + if (isThisContainerOrFunctionBlock(node)) { + return false; + } + + return !!forEachChild(node, nodeImmediatelyReferencesSuperOrThis); + } + + function checkAccessorDeclaration(node: AccessorDeclaration) { + if (isIdentifier(node.name) && idText(node.name) === "constructor" && isClassLike(node.parent)) { + 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); + + 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); + } + + 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 = getSymbolOfDeclaration(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 returnType = getTypeOfAccessors(getSymbolOfDeclaration(node)); + if (node.kind === SyntaxKind.GetAccessor) { + checkAllCodePathsInNonVoidFunctionReturnOrThrow(node, returnType); + } + } + } + + 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 getEffectiveTypeArguments(node: TypeReferenceNode | ExpressionWithTypeArguments | NodeWithTypeArguments, typeParameters: readonly TypeParameter[]): Type[] { + return fillMissingTypeArguments(map(node.typeArguments!, getTypeFromTypeNode), typeParameters, getMinTypeArgumentCount(typeParameters), isInJSFile(node)); + } + + function checkTypeArgumentConstraints(node: TypeReferenceNode | ExpressionWithTypeArguments | NodeWithTypeArguments, 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 getTypeParametersForTypeAndSymbol(type: Type, symbol: Symbol) { + if (!isErrorType(type)) { + return symbol.flags & SymbolFlags.TypeAlias && getSymbolLinks(symbol).typeParameters || + (getObjectFlags(type) & ObjectFlags.Reference ? (type as TypeReference).target.localTypeParameters : undefined); + } + return undefined; + } + + function getTypeParametersForTypeReferenceOrImport(node: TypeReferenceNode | ExpressionWithTypeArguments | ImportTypeNode) { + const type = getTypeFromTypeNode(node); + if (!isErrorType(type)) { + const symbol = getNodeLinks(node).resolvedSymbol; + if (symbol) { + return getTypeParametersForTypeAndSymbol(type, symbol); + } + } + return undefined; + } + + function checkTypeReferenceNode(node: TypeReferenceNode | ExpressionWithTypeArguments) { + checkGrammarTypeArguments(node, node.typeArguments); + if (node.kind === SyntaxKind.TypeReference && !isInJSFile(node) && !isInJSDoc(node) && node.typeArguments && node.typeName.end !== node.typeArguments.pos) { + // If there was a token between the type name and the type arguments, check if it was a DotToken + const sourceFile = getSourceFileOfNode(node); + if (scanTokenAtPosition(sourceFile, node.typeName.end) === SyntaxKind.DotToken) { + grammarErrorAtPos(node, skipTrivia(sourceFile.text, node.typeName.end), 1, Diagnostics.JSDoc_types_can_only_be_used_inside_documentation_comments); + } + } + forEach(node.typeArguments, checkSourceElement); + checkTypeReferenceOrImport(node); + } + + function checkTypeReferenceOrImport(node: TypeReferenceNode | ExpressionWithTypeArguments | ImportTypeNode) { + const type = getTypeFromTypeNode(node); + if (!isErrorType(type)) { + if (node.typeArguments) { + addLazyDiagnostic(() => { + const typeParameters = getTypeParametersForTypeReferenceOrImport(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 = getTypeParametersForTypeReferenceOrImport(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 checkTypeLiteral(node: TypeLiteralNode) { + forEach(node.members, checkSourceElement); + addLazyDiagnostic(checkTypeLiteralDiagnostics); + + function checkTypeLiteralDiagnostics() { + const type = getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node); + checkIndexConstraints(type, type.symbol); + checkTypeForDuplicateIndexSignatures(node); + checkObjectTypeForDuplicateDeclarations(node); + } + } + + function checkArrayType(node: ArrayTypeNode) { + checkSourceElement(node.elementType); + } + + function checkTupleType(node: TupleTypeNode) { + let seenOptionalElement = false; + let seenRestElement = false; + for (const e of node.elements) { + let 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) { + flags |= ElementFlags.Rest; + } + } + if (flags & ElementFlags.Rest) { + if (seenRestElement) { + grammarErrorOnNode(e, Diagnostics.A_rest_element_cannot_follow_another_rest_element); + break; + } + 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 (flags & ElementFlags.Required && seenOptionalElement) { + grammarErrorOnNode(e, Diagnostics.A_required_element_cannot_follow_an_optional_element); + break; + } + } + 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; + // skip index type deferral on remapping mapped types + const objectIndexType = isGenericMappedType(objectType) && getMappedTypeNameTypeKind(objectType) === MappedTypeNameTypeKind.Remapping + ? getIndexTypeForMappedType(objectType, IndexFlags.None) + : getIndexType(objectType, IndexFlags.None); + const hasNumberIndexInfo = !!getIndexInfoOfType(objectType, numberType); + if (everyType(indexType, t => isTypeAssignableTo(t, objectIndexType) || hasNumberIndexInfo && isApplicableIndexType(t, numberType))) { + 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; + } + if (isGenericObjectType(objectType)) { + const propertyName = getPropertyNameFromIndex(indexType, accessNode); + if (propertyName) { + const propertySymbol = forEachType(getApparentType(objectType), t => getPropertyOfType(t, propertyName)); + if (propertySymbol && getDeclarationModifierFlagsFromSymbol(propertySymbol) & ModifierFlags.NonPublicAccessibilityModifier) { + error(accessNode, Diagnostics.Private_or_protected_member_0_cannot_be_accessed_on_a_type_parameter, unescapeLeadingUnderscores(propertyName)); + return errorType; + } + } + } + error(accessNode, Diagnostics.Type_0_cannot_be_used_to_index_type_1, typeToString(indexType), typeToString(objectType)); + return errorType; + } + + function checkIndexedAccessType(node: IndexedAccessTypeNode) { + checkSourceElement(node.objectType); + checkSourceElement(node.indexType); + checkIndexedAccessIndexType(getTypeFromIndexedAccessTypeNode(node), node); + } + + function checkMappedType(node: MappedTypeNode) { + checkGrammarMappedType(node); + checkSourceElement(node.typeParameter); + checkSourceElement(node.nameType); + checkSourceElement(node.type); + + if (!node.type) { + reportImplicitAny(node, anyType); + } + + const type = getTypeFromMappedTypeNode(node) as MappedType; + const nameType = getNameTypeFromMappedType(type); + if (nameType) { + checkTypeAssignableTo(nameType, stringNumberSymbolType, node.nameType); + } + else { + const constraintType = getConstraintTypeFromMappedType(type); + checkTypeAssignableTo(constraintType, stringNumberSymbolType, getEffectiveConstraintOfTypeParameter(node.typeParameter)); + } + } + + function checkGrammarMappedType(node: MappedTypeNode) { + if (node.members?.length) { + return grammarErrorOnNode(node.members[0], Diagnostics.A_mapped_type_may_not_declare_properties_or_methods); + } + } + + function checkThisType(node: ThisTypeNode) { + getTypeFromThisTypeNode(node); + } + + function checkTypeOperator(node: TypeOperatorNode) { + checkGrammarTypeOperatorNode(node); + checkSourceElement(node.type); + } + + function checkConditionalType(node: ConditionalTypeNode) { + forEachChild(node, checkSourceElement); + } + + 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 = getSymbolOfDeclaration(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); + } + + 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 checkImportType(node: ImportTypeNode) { + checkSourceElement(node.argument); + + if (node.attributes) { + getResolutionModeOverride(node.attributes, grammarErrorOnNode); + } + checkTypeReferenceOrImport(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); + } + + function isPrivateWithinAmbient(node: Node): boolean { + return (hasEffectiveModifier(node, ModifierFlags.Private) || isPrivateIdentifierClassElementDeclaration(node)) && !!(node.flags & NodeFlags.Ambient); + } + + function getEffectiveDeclarationFlags(n: Declaration, flagsToCheck: ModifierFlags): ModifierFlags { + let flags = getCombinedModifierFlagsCached(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 + ) { + const container = getEnclosingContainer(n); + if ((container && container.flags & NodeFlags.ExportContext) && !(flags & ModifierFlags.Ambient) && !(isModuleBlock(n.parent) && isModuleDeclaration(n.parent.parent) && isGlobalScopeAugmentation(n.parent.parent))) { + // It is nested in an ambient export context, which means it is automatically exported + flags |= ModifierFlags.Export; + } + flags |= ModifierFlags.Ambient; + } + + return flags & flagsToCheck; + } + + 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); + } + }); + } + } + + 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); + } + }); + } + } + + 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 reportImplementationExpectedError(node: SignatureDeclaration): void { + if (node.name && nodeIsMissing(node.name)) { + return; + } + + 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 + isComputedPropertyName(node.name) && isComputedPropertyName(subsequentName) && isTypeIdenticalTo(checkComputedPropertyName(node.name), checkComputedPropertyName(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); + } + 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); + } + } + } + + 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; + } + + previousDeclaration = node; + + if (!inAmbientContextOrInterface) { + lastSeenNonAmbientDeclaration = node as FunctionLikeDeclaration; + } + } + if (isInJSFile(current) && isFunctionLike(current) && current.jsDoc) { + hasOverloads = length(getJSDocOverloadTags(current)) > 0; + } + } + } + + if (multipleConstructorImplementation) { + forEach(functionDeclarations, declaration => { + error(declaration, Diagnostics.Multiple_constructor_implementations_are_not_allowed); + }); + } + + if (duplicateFunctionDeclaration) { + forEach(functionDeclarations, declaration => { + error(getNameOfDeclaration(declaration) || declaration, Diagnostics.Duplicate_function_implementation); + }); + } + + 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, + ); + } + }); + } + + // 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)) { + const errorNode = signature.declaration && isJSDocSignature(signature.declaration) + ? (signature.declaration.parent as JSDocOverloadTag | JSDocCallbackTag).tagName + : signature.declaration; + addRelatedInfo( + error(errorNode, Diagnostics.This_overload_signature_is_not_compatible_with_its_implementation_signature), + createDiagnosticForNode(bodyDeclaration, Diagnostics.The_implementation_signature_is_declared_here), + ); + break; + } + } + } + } + } + + function checkExportsOnMergedDeclarations(node: Declaration): void { + addLazyDiagnostic(() => checkExportsOnMergedDeclarationsWorker(node)); + } + + 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 = getSymbolOfDeclaration(node)!; + if (!symbol.exportSymbol) { + // this is a pure local symbol (all declarations are non-exported) - no need to check anything + return; + } + } + + // run the check only for the first declaration in the list + if (getDeclarationOfKind(symbol, node.kind) !== node) { + 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; + } + else { + 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); + + 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)); + } + } + } + + function getDeclarationSpaces(decl: Declaration): DeclarationSpaces { + let d = decl as Node; + switch (d.kind) { + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.TypeAliasDeclaration: + + // 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(getSymbolOfDeclaration(d as ImportEqualsDeclaration | NamespaceImport | ImportClause | ExportAssignment | BinaryExpression)); + 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; + case SyntaxKind.MethodSignature: + case SyntaxKind.PropertySignature: + return DeclarationSpaces.ExportType; + default: + return Debug.failBadSyntaxKind(d); + } + } + } + + function getAwaitedTypeOfPromise(type: Type, errorNode?: Node, diagnosticMessage?: DiagnosticMessage, ...args: DiagnosticArguments): Type | undefined { + const promisedType = getPromisedTypeOfPromise(type, errorNode); + return promisedType && getAwaitedType(promisedType, errorNode, diagnosticMessage, ...args); + } + + /** + * 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; + } + + const typeAsPromise = type as PromiseOrAwaitableType; + if (typeAsPromise.promisedTypeOfPromise) { + return typeAsPromise.promisedTypeOfPromise; + } + + if (isReferenceToType(type, getGlobalPromiseType(/*reportErrors*/ false))) { + return typeAsPromise.promisedTypeOfPromise = getTypeArguments(type as GenericType)[0]; + } + + // primitives with a `{ then() }` won't be unwrapped/adopted. + if (allTypesAssignableToKind(getBaseConstraintOrType(type), TypeFlags.Primitive | TypeFlags.Never)) { + return undefined; + } + + const thenFunction = getTypeOfPropertyOfType(type, "then" as __String)!; // TODO: GH#18217 + if (isTypeAny(thenFunction)) { + return undefined; + } + + 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 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); + } + } + + 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; + } + + 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, ...args: DiagnosticArguments): Type { + const awaitedType = withAlias ? + getAwaitedType(type, errorNode, diagnosticMessage, ...args) : + getAwaitedTypeNoAlias(type, errorNode, diagnosticMessage, ...args); + 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; + } + + // 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 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)]); + } + + 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)) { + return tryCreateAwaitedType(type) ?? type; + } + + Debug.assert(isAwaitedTypeInstantiation(type) || 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, ...args: DiagnosticArguments): Type | undefined { + const awaitedType = getAwaitedTypeNoAlias(type, errorNode, diagnosticMessage, ...args); + return awaitedType && createAwaitedTypeIfNeeded(awaitedType); + } + + /** + * Gets the "awaited type" of a type without introducing an `Awaited` wrapper. + * + * @see {@link getAwaitedType} + */ + function getAwaitedTypeNoAlias(type: Type, errorNode?: Node, diagnosticMessage?: DiagnosticMessage, ...args: DiagnosticArguments): Type | undefined { + if (isTypeAny(type)) { + return type; + } + + // If this is already an `Awaited`, just return it. This avoids `Awaited>` in higher-order + if (isAwaitedTypeInstantiation(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; + } + + // 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; + } + + const mapper = errorNode ? (constituentType: Type) => getAwaitedTypeNoAlias(constituentType, errorNode, diagnosticMessage, ...args) : getAwaitedTypeNoAlias; + + awaitedTypeStack.push(type.id); + const mapped = mapType(type, mapper); + awaitedTypeStack.pop(); + + 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; + } + + // 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, ...args); + awaitedTypeStack.pop(); + + if (!awaitedType) { + return undefined; + } + + return typeAsAwaitable.awaitedTypeOfType = awaitedType; + } + + // 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, ...args); + diagnostics.add(createDiagnosticForNodeFromMessageChain(getSourceFileOfNode(errorNode), errorNode, chain)); + } + return undefined; + } + + return typeAsAwaitable.awaitedTypeOfType = type; + } + + /** + * 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, returnTypeErrorLocation: 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. + reportErrorForInvalidReturnType(Diagnostics.The_return_type_of_an_async_function_or_method_must_be_the_global_Promise_T_type_Did_you_mean_to_write_Promise_0, returnTypeNode, returnTypeErrorLocation, typeToString(getAwaitedTypeNoAlias(returnType) || voidType)); + return; + } + } + else { + // Always mark the type node as referenced if it points to a value + markLinkedReferences(node, ReferenceHint.AsyncFunction); + if (isErrorType(returnType)) { + return; + } + + const promiseConstructorName = getEntityNameFromTypeNode(returnTypeNode); + if (promiseConstructorName === undefined) { + reportErrorForInvalidReturnType(Diagnostics.Type_0_is_not_a_valid_async_function_return_type_in_ES5_because_it_does_not_refer_to_a_Promise_compatible_constructor_value, returnTypeNode, returnTypeErrorLocation, 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(returnTypeErrorLocation, Diagnostics.An_async_function_or_method_in_ES5_requires_the_Promise_constructor_Make_sure_you_have_a_declaration_for_the_Promise_constructor_or_include_ES2015_in_your_lib_option); + } + else { + reportErrorForInvalidReturnType(Diagnostics.Type_0_is_not_a_valid_async_function_return_type_in_ES5_because_it_does_not_refer_to_a_Promise_compatible_constructor_value, returnTypeNode, returnTypeErrorLocation, entityNameToString(promiseConstructorName)); + } + return; + } + + const globalPromiseConstructorLikeType = getGlobalPromiseConstructorLikeType(/*reportErrors*/ true); + if (globalPromiseConstructorLikeType === emptyObjectType) { + // If we couldn't resolve the global PromiseConstructorLike type we cannot verify + // compatibility with __awaiter. + reportErrorForInvalidReturnType(Diagnostics.Type_0_is_not_a_valid_async_function_return_type_in_ES5_because_it_does_not_refer_to_a_Promise_compatible_constructor_value, returnTypeNode, returnTypeErrorLocation, entityNameToString(promiseConstructorName)); + return; + } + + const headMessage = Diagnostics.Type_0_is_not_a_valid_async_function_return_type_in_ES5_because_it_does_not_refer_to_a_Promise_compatible_constructor_value; + const errorInfo = () => returnTypeNode === returnTypeErrorLocation ? undefined : chainDiagnosticMessages(/*details*/ undefined, Diagnostics.The_return_type_of_an_async_function_or_method_must_be_the_global_Promise_T_type); + if (!checkTypeAssignableTo(promiseConstructorType, globalPromiseConstructorLikeType, returnTypeErrorLocation, headMessage, errorInfo)) { + 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; + } + } + + 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); + + function reportErrorForInvalidReturnType(message: DiagnosticMessage, returnTypeNode: TypeNode, returnTypeErrorLocation: TypeNode, typeName: string) { + if (returnTypeNode === returnTypeErrorLocation) { + error(returnTypeErrorLocation, message, typeName); + } + else { + const diag = error(returnTypeErrorLocation, Diagnostics.The_return_type_of_an_async_function_or_method_must_be_the_global_Promise_T_type); + addRelatedInfo(diag, createDiagnosticForNode(returnTypeNode, message, typeName)); + } + } + } + + function checkGrammarDecorator(decorator: Decorator): boolean { + const sourceFile = getSourceFileOfNode(decorator); + if (!hasParseDiagnostics(sourceFile)) { + let node: Expression = decorator.expression; + + // DecoratorParenthesizedExpression : + // `(` Expression `)` + + if (isParenthesizedExpression(node)) { + return false; + } + + let canHaveCallExpression = true; + let errorNode: Node | undefined; + while (true) { + // Allow TS syntax such as non-null assertions and instantiation expressions + if (isExpressionWithTypeArguments(node) || isNonNullExpression(node)) { + node = node.expression; + continue; + } + + // DecoratorCallExpression : + // DecoratorMemberExpression Arguments + + if (isCallExpression(node)) { + if (!canHaveCallExpression) { + errorNode = node; + } + if (node.questionDotToken) { + // Even if we already have an error node, error at the `?.` token since it appears earlier. + errorNode = node.questionDotToken; + } + node = node.expression; + canHaveCallExpression = false; + continue; + } + + // DecoratorMemberExpression : + // IdentifierReference + // DecoratorMemberExpression `.` IdentifierName + // DecoratorMemberExpression `.` PrivateIdentifier + + if (isPropertyAccessExpression(node)) { + if (node.questionDotToken) { + // Even if we already have an error node, error at the `?.` token since it appears earlier. + errorNode = node.questionDotToken; + } + node = node.expression; + canHaveCallExpression = false; + continue; + } + + if (!isIdentifier(node)) { + // Even if we already have an error node, error at this node since it appears earlier. + errorNode = node; + } + + break; + } + + if (errorNode) { + addRelatedInfo( + error(decorator.expression, Diagnostics.Expression_must_be_enclosed_in_parentheses_to_be_used_as_a_decorator), + createDiagnosticForNode(errorNode, Diagnostics.Invalid_syntax_in_decorator), + ); + return true; + } + } + + return false; + } + + /** Check a decorator */ + function checkDecorator(node: Decorator): void { + checkGrammarDecorator(node); + + const signature = getResolvedSignature(node); + checkDeprecatedSignature(signature, node); + const returnType = getReturnTypeOfSignature(signature); + if (returnType.flags & TypeFlags.Any) { + return; + } + + // if we fail to get a signature and return type here, we will have already reported a grammar error in `checkDecorators`. + const decoratorSignature = getDecoratorCallSignature(node); + if (!decoratorSignature?.resolvedReturnType) return; + + let headMessage: DiagnosticMessage; + const expectedReturnType = decoratorSignature.resolvedReturnType; + switch (node.parent.kind) { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + headMessage = Diagnostics.Decorator_function_return_type_0_is_not_assignable_to_type_1; + break; + + case SyntaxKind.PropertyDeclaration: + if (!legacyDecorators) { + headMessage = Diagnostics.Decorator_function_return_type_0_is_not_assignable_to_type_1; + break; + } + // falls through + + case SyntaxKind.Parameter: + headMessage = Diagnostics.Decorator_function_return_type_is_0_but_is_expected_to_be_void_or_any; + break; + + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + headMessage = Diagnostics.Decorator_function_return_type_0_is_not_assignable_to_type_1; + break; + + default: + return Debug.failBadSyntaxKind(node.parent); + } + + checkTypeAssignableTo(returnType, expectedReturnType, node.expression, headMessage); + } + + /** + * Creates a synthetic `Signature` corresponding to a call signature. + */ + function createCallSignature( + typeParameters: readonly TypeParameter[] | undefined, + thisParameter: Symbol | undefined, + parameters: readonly Symbol[], + returnType: Type, + typePredicate?: TypePredicate, + minArgumentCount: number = parameters.length, + flags: SignatureFlags = SignatureFlags.None, + ) { + const decl = factory.createFunctionTypeNode(/*typeParameters*/ undefined, emptyArray, factory.createKeywordTypeNode(SyntaxKind.AnyKeyword)); + return createSignature(decl, typeParameters, thisParameter, parameters, returnType, typePredicate, minArgumentCount, flags); + } + + /** + * Creates a synthetic `FunctionType` + */ + function createFunctionType( + typeParameters: readonly TypeParameter[] | undefined, + thisParameter: Symbol | undefined, + parameters: readonly Symbol[], + returnType: Type, + typePredicate?: TypePredicate, + minArgumentCount?: number, + flags?: SignatureFlags, + ) { + const signature = createCallSignature(typeParameters, thisParameter, parameters, returnType, typePredicate, minArgumentCount, flags); + return getOrCreateTypeFromSignature(signature); + } + + function createGetterFunctionType(type: Type) { + return createFunctionType(/*typeParameters*/ undefined, /*thisParameter*/ undefined, emptyArray, type); + } + + function createSetterFunctionType(type: Type) { + const valueParam = createParameter("value" as __String, type); + return createFunctionType(/*typeParameters*/ undefined, /*thisParameter*/ undefined, [valueParam], voidType); + } + + 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.ParenthesizedType: + case SyntaxKind.NamedTupleMember: + return getEntityNameForDecoratorMetadata((node as ParenthesizedTypeNode).type); + + 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; + } + + 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; + } + } + return commonEntityName; + } + + function getParameterTypeNodeForDecoratorCheck(node: ParameterDeclaration): TypeNode | undefined { + const typeNode = getEffectiveTypeAnnotationNode(node); + return isRestParameter(node) ? getRestParameterElementType(typeNode) : typeNode; + } + + /** 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 + // checkGrammarModifiers. + if (!canHaveDecorators(node) || !hasDecorators(node) || !node.modifiers || !nodeCanBeDecorated(legacyDecorators, node, node.parent, node.parent.parent)) { + return; + } + + const firstDecorator = find(node.modifiers, isDecorator); + if (!firstDecorator) { + return; + } + + if (legacyDecorators) { + checkExternalEmitHelpers(firstDecorator, ExternalEmitHelpers.Decorate); + if (node.kind === SyntaxKind.Parameter) { + checkExternalEmitHelpers(firstDecorator, ExternalEmitHelpers.Param); + } + } + else if (languageVersion < LanguageFeatureMinimumTarget.ClassAndClassElementDecorators) { + checkExternalEmitHelpers(firstDecorator, ExternalEmitHelpers.ESDecorateAndRunInitializers); + if (isClassDeclaration(node)) { + if (!node.name) { + checkExternalEmitHelpers(firstDecorator, ExternalEmitHelpers.SetFunctionName); + } + else { + const member = getFirstTransformableStaticClassElement(node); + if (member) { + checkExternalEmitHelpers(firstDecorator, ExternalEmitHelpers.SetFunctionName); + } + } + } + else if (!isClassExpression(node)) { + if (isPrivateIdentifier(node.name) && (isMethodDeclaration(node) || isAccessor(node) || isAutoAccessorPropertyDeclaration(node))) { + checkExternalEmitHelpers(firstDecorator, ExternalEmitHelpers.SetFunctionName); + } + if (isComputedPropertyName(node.name)) { + checkExternalEmitHelpers(firstDecorator, ExternalEmitHelpers.PropKey); + } + } + } + + markLinkedReferences(node, ReferenceHint.Decorator); + + for (const modifier of node.modifiers) { + if (isDecorator(modifier)) { + checkDecorator(modifier); + } + } + } + + function checkFunctionDeclaration(node: FunctionDeclaration): void { + addLazyDiagnostic(checkFunctionDeclarationDiagnostics); + + function checkFunctionDeclarationDiagnostics() { + checkFunctionOrMethodDeclaration(node); + checkGrammarForGenerator(node); + checkCollisionsForDeclarationName(node, node.name); + } + } + + 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); + } + + if (node.name) { + checkTypeNameIsReserved(node.name, Diagnostics.Type_alias_name_cannot_be_0); + } + checkSourceElement(node.typeExpression); + checkTypeParameters(getEffectiveTypeParameterDeclarations(node)); + } + + function checkJSDocTemplateTag(node: JSDocTemplateTag): void { + checkSourceElement(node.constraint); + for (const tp of node.typeParameters) { + checkSourceElement(tp); + } + } + + function checkJSDocTypeTag(node: JSDocTypeTag) { + checkSourceElement(node.typeExpression); + } + + function checkJSDocSatisfiesTag(node: JSDocSatisfiesTag) { + checkSourceElement(node.typeExpression); + const host = getEffectiveJSDocHost(node); + if (host) { + const tags = getAllJSDocTags(host, isJSDocSatisfiesTag); + if (length(tags) > 1) { + for (let i = 1; i < length(tags); i++) { + const tagName = tags[i].tagName; + error(tagName, Diagnostics._0_tag_already_specified, idText(tagName)); + } + } + } + } + + function checkJSDocLinkLikeTag(node: JSDocLink | JSDocLinkCode | JSDocLinkPlain) { + if (node.name) { + resolveJSDocMemberName(node.name, /*ignoreErrors*/ true); + } + } + + function checkJSDocParameterTag(node: JSDocParameterTag) { + checkSourceElement(node.typeExpression); + } + + function checkJSDocPropertyTag(node: JSDocPropertyTag) { + checkSourceElement(node.typeExpression); + } + + function checkJSDocFunctionType(node: JSDocFunctionType): void { + addLazyDiagnostic(checkJSDocFunctionTypeImplicitAny); + checkSignatureDeclaration(node); + + function checkJSDocFunctionTypeImplicitAny() { + if (!node.type && !isJSDocConstructSignature(node)) { + reportImplicitAny(node, anyType); + } + } + } + + function checkJSDocThisTag(node: JSDocThisTag) { + const host = getEffectiveJSDocHost(node); + if (host && isArrowFunction(host)) { + error(node.tagName, Diagnostics.An_arrow_function_cannot_have_a_this_parameter); + } + } + + function checkJSDocImportTag(node: JSDocImportTag) { + checkImportAttributes(node); + } + + 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; + } + + 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)); + } + } + } + + 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 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); + } + + 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 = getSymbolOfDeclaration(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), + ); + + // 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); + } + } + + const body = node.kind === SyntaxKind.MethodSignature ? undefined : node.body; + checkSourceElement(body); + checkAllCodePathsInNonVoidFunctionReturnOrThrow(node, getReturnTypeFromAnnotation(node)); + + 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); + } + } + + 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)); + } + } + } + } + + 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); + } + } + + 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: + 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"); + } + } + } + + 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 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 = getSymbolOfDeclaration(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"); + } + } + } + + 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 = getSymbolOfDeclaration(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 messageAndArg: DiagnosticAndArguments = only + ? [Diagnostics._0_is_declared_but_its_value_is_never_read, name] + : [Diagnostics.All_type_parameters_are_unused]; + addDiagnostic(typeParameter, UnusedKind.Parameter, createFileDiagnostic(sourceFile, range.pos, range.end - range.pos, ...messageAndArg)); + } + } + else { + // 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 addToGroup(map: Map, 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 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 isIdentifierThatStartsWithUnderscore(declaration.name); + } + return isAmbientModule(declaration) || + (isVariableDeclaration(declaration) && isForInOrOfStatement(declaration.parent.parent) || isImportedDeclaration(declaration)) && isIdentifierThatStartsWithUnderscore(declaration.name!); + } + + function checkUnusedLocalsAndParameters(nodeWithLocals: HasLocals, 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 (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 (isVariableDeclaration(declaration)) { + const blockScopeKind = getCombinedNodeFlagsCached(declaration) & NodeFlags.BlockScoped; + const name = getNameOfDeclaration(declaration); + if (blockScopeKind !== NodeFlags.Using && blockScopeKind !== NodeFlags.AwaitUsing || !name || !isIdentifierThatStartsWithUnderscore(name)) { + 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); + } + } + } + } + }); + 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), + ); + } + } + else { + for (const e of bindingElements) { + addDiagnostic(e, kind, createDiagnosticForNode(e, Diagnostics._0_is_declared_but_its_value_is_never_read, bindingNameText(e.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 (!getSymbolOfDeclaration(node)?.isReferenced) { + const wrappingDeclaration = walkUpBindingElementsAndPatterns(node); + Debug.assert(isPartOfParameterDeclaration(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); + } + } + + 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; + } + + 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); + } + } + + 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); + } + }); + } + + /** + * 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.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 (node.flags & NodeFlags.Ambient) { + // ambient context - no codegen impact + 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; + } + } + + const root = getRootDeclaration(node); + if (isParameter(root) && nodeIsMissing((root.parent as FunctionLikeDeclaration).body)) { + // just an overload - no codegen impact + 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; + }); + } + + 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; + }); + } + + 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; + } + + if (!name || !needCollisionCheckForIdentifier(node, name, "require") && !needCollisionCheckForIdentifier(node, name, "exports")) { + 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)); + } + } + + 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; + } + + // 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 recordPotentialCollisionWithWeakMapSetInGeneratedCode(node: Node, name: Identifier): void { + if ( + languageVersion <= ScriptTarget.ES2021 + && (needCollisionCheckForIdentifier(node, name, "WeakMap") || needCollisionCheckForIdentifier(node, name, "WeakSet")) + ) { + potentialWeakMapSetCollisions.push(node); + } + } + + 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 if (isFunctionExpression(node)) { + // FunctionExpression names don't contribute to their containers, but do matter for their contents + if (getNodeCheckFlags(node) & NodeCheckFlags.ContainsSuperPropertyInStaticInitializer) { + hasCollision = true; + } + } + 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 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 ((getCombinedNodeFlagsCached(node) & NodeFlags.BlockScoped) !== 0 || isPartOfParameterDeclaration(node)) { + return; + } + + // NOTE: in ES6 spec initializer is required in variable declarations where name is binding pattern + // so we'll always treat binding elements as initialized + + const symbol = getSymbolOfDeclaration(node); + if (symbol.flags & SymbolFlags.FunctionScopedVariable) { + if (!isIdentifier(node.name)) return Debug.fail(); + const localDeclarationSymbol = resolveName(node, node.name.escapedText, SymbolFlags.Variable, /*nameNotFoundMessage*/ 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); + + // here we know that function scoped variable is "shadowed" by block scoped one + // a var declatation can't hoist past a lexical declaration and it results in a SyntaxError at runtime + 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; + } + + // 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; + } + + // 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) && + isPartOfParameterDeclaration(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 < LanguageFeatureMinimumTarget.ObjectSpreadRest) { + 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); + } + } + } + } + + // For a binding pattern, check contained binding elements + if (isBindingPattern(node.name)) { + if (node.name.kind === SyntaxKind.ArrayBindingPattern && languageVersion < LanguageFeatureMinimumTarget.BindingPatterns && 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 (node.initializer && isPartOfParameterDeclaration(node) && 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)) { + if (isInAmbientOrTypeNode(node)) { + return; + } + 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); + } + } + } + return; + } + // For a commonjs `const x = require`, validate the alias and exit + const symbol = getSymbolOfDeclaration(node); + if (symbol.flags & SymbolFlags.Alias && (isVariableDeclarationInitializedToBareOrAccessedRequire(node) || isBindingElementOfBareOrAccessedRequire(node))) { + checkAliasSymbol(node); + return; + } + + 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) { + const initializerType = checkExpressionCached(initializer); + checkTypeAssignableToAndOptionallyElaborate(initializerType, type, node, initializer, /*headMessage*/ undefined); + const blockScopeKind = getCombinedNodeFlagsCached(node) & NodeFlags.BlockScoped; + if (blockScopeKind === NodeFlags.AwaitUsing) { + const globalAsyncDisposableType = getGlobalAsyncDisposableType(/*reportErrors*/ true); + const globalDisposableType = getGlobalDisposableType(/*reportErrors*/ true); + if (globalAsyncDisposableType !== emptyObjectType && globalDisposableType !== emptyObjectType) { + const optionalDisposableType = getUnionType([globalAsyncDisposableType, globalDisposableType, nullType, undefinedType]); + checkTypeAssignableTo(initializerType, optionalDisposableType, initializer, Diagnostics.The_initializer_of_an_await_using_declaration_must_be_either_an_object_with_a_Symbol_asyncDispose_or_Symbol_dispose_method_or_be_null_or_undefined); + } + } + else if (blockScopeKind === NodeFlags.Using) { + const globalDisposableType = getGlobalDisposableType(/*reportErrors*/ true); + if (globalDisposableType !== emptyObjectType) { + const optionalDisposableType = getUnionType([globalDisposableType, nullType, undefinedType]); + checkTypeAssignableTo(initializerType, optionalDisposableType, initializer, Diagnostics.The_initializer_of_a_using_declaration_must_be_either_an_object_with_a_Symbol_dispose_method_or_be_null_or_undefined); + } + } + } + } + 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)); + } + } + } + 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)); + + 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)); + } + } + 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 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 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 (hasQuestionToken(left) !== hasQuestionToken(right)) { + return false; + } + + const interestingFlags = ModifierFlags.Private | + ModifierFlags.Protected | + ModifierFlags.Async | + ModifierFlags.Abstract | + ModifierFlags.Readonly | + ModifierFlags.Static; + + return getSelectedEffectiveModifierFlags(left, interestingFlags) === getSelectedEffectiveModifierFlags(right, interestingFlags); + } + + 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 checkBindingElement(node: BindingElement) { + checkGrammarBindingElement(node); + return checkVariableLikeDeclaration(node); + } + + function checkVariableDeclarationList(node: VariableDeclarationList) { + const blockScopeKind = getCombinedNodeFlags(node) & NodeFlags.BlockScoped; + if ((blockScopeKind === NodeFlags.Using || blockScopeKind === NodeFlags.AwaitUsing) && languageVersion < LanguageFeatureMinimumTarget.UsingAndAwaitUsing) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.AddDisposableResourceAndDisposeResources); + } + + forEach(node.declarations, checkSourceElement); + } + + function checkVariableStatement(node: VariableStatement) { + // Grammar checking + if (!checkGrammarModifiers(node) && !checkGrammarVariableDeclarationList(node.declarationList)) checkGrammarForDisallowedBlockScopedVariableStatement(node); + checkVariableDeclarationList(node.declarationList); + } + + function checkExpressionStatement(node: ExpressionStatement) { + // Grammar checking + checkGrammarStatementInAmbientContext(node); + + checkExpression(node.expression); + } + + function checkIfStatement(node: IfStatement) { + // Grammar checking + checkGrammarStatementInAmbientContext(node); + const type = checkTruthinessExpression(node.expression); + checkTestingKnownTruthyCallableOrAwaitableOrEnumMemberType(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); + } + + checkSourceElement(node.elseStatement); + } + + function checkTestingKnownTruthyCallableOrAwaitableOrEnumMemberType(condExpr: Expression, condType: Type, body?: Statement | Expression) { + if (!strictNullChecks) return; + bothHelper(condExpr, body); + + function bothHelper(condExpr: Expression, body: Expression | Statement | undefined) { + condExpr = skipParentheses(condExpr); + + helper(condExpr, body); + + while (isBinaryExpression(condExpr) && (condExpr.operatorToken.kind === SyntaxKind.BarBarToken || condExpr.operatorToken.kind === SyntaxKind.QuestionQuestionToken)) { + condExpr = skipParentheses(condExpr.left); + helper(condExpr, body); + } + } + + function helper(condExpr: Expression, body: Expression | Statement | undefined) { + const location = isLogicalOrCoalescingBinaryExpression(condExpr) ? skipParentheses(condExpr.right) : condExpr; + if (isModuleExportsAccessExpression(location)) { + return; + } + if (isLogicalOrCoalescingBinaryExpression(location)) { + bothHelper(location, body); + return; + } + const type = location === condExpr ? condType : checkTruthinessExpression(location); + if (type.flags & TypeFlags.EnumLiteral && isPropertyAccessExpression(location) && (getNodeLinks(location.expression).resolvedSymbol ?? unknownSymbol).flags & SymbolFlags.Enum) { + // EnumLiteral type at condition with known value is always truthy or always falsy, likely an error + error(location, Diagnostics.This_condition_will_always_return_0, !!(type as LiteralType).value ? "true" : "false"); + return; + } + const isPropertyExpressionCast = isPropertyAccessExpression(location) && isTypeAssertion(location.expression); + if (!hasTypeFacts(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 + : 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; + } + // 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 { + return false; + } + } + } + } + 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; + } + } + return forEachChild(child, visit); + }); + if (isUsed) { + return true; + } + node = node.parent; + } + return false; + } + + function checkDoStatement(node: DoStatement) { + // Grammar checking + checkGrammarStatementInAmbientContext(node); + + checkSourceElement(node.statement); + checkTruthinessExpression(node.expression); + } + + function checkWhileStatement(node: WhileStatement) { + // Grammar checking + checkGrammarStatementInAmbientContext(node); + + 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 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); + } + } + + if (node.initializer) { + if (node.initializer.kind === SyntaxKind.VariableDeclarationList) { + checkVariableDeclarationList(node.initializer as VariableDeclarationList); + } + else { + checkExpression(node.initializer); + } + } + + if (node.condition) checkTruthinessExpression(node.condition); + if (node.incrementor) checkExpression(node.incrementor); + checkSourceElement(node.statement); + if (node.locals) { + registerForUnusedIdentifiersCheck(node); + } + } + + 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 { + const functionFlags = getFunctionFlags(container); + if ((functionFlags & (FunctionFlags.Invalid | FunctionFlags.Async)) === FunctionFlags.Async && languageVersion < LanguageFeatureMinimumTarget.ForAwaitOf) { + // 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 < LanguageFeatureMinimumTarget.ForOf) { + // 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) { + checkVariableDeclarationList(node.initializer as VariableDeclarationList); + } + 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, + ); + + // 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); + } + } + + function checkForInStatement(node: ForInStatement) { + // Grammar checking + checkGrammarForInOrForOfStatement(node); + + 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); + } + checkVariableDeclarationList(node.initializer as VariableDeclarationList); + } + 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, + ); + } + } + + // 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)); + } + + 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 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) { + if (errorNode) { + reportTypeNotIterableError(errorNode, inputType, allowAsyncIterables); + } + return undefined; + } + + 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); + } + } + } + if (iterationTypes || uplevelIteration) { + return possibleOutOfBounds ? includeUndefinedInIndexSignature(iterationTypes && iterationTypes.yieldType) : (iterationTypes && iterationTypes.yieldType); + } + } + + let arrayType = inputType; + 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); + } + } + else if (arrayType.flags & TypeFlags.StringLike) { + arrayType = neverType; + } + + hasStringConstituent = arrayType !== inputType; + if (hasStringConstituent) { + // 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; + } + } + } + + if (!isArrayLikeType(arrayType)) { + if (errorNode) { + // 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; + } + + 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); + } + + return (use & IterationUse.PossiblyOutOfBounds) ? includeUndefinedInIndexSignature(arrayElementType) : arrayElementType; + + 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]; + } + + const yieldType = getIterationTypeOfIterable(use, IterationTypeKind.Yield, inputType, /*errorNode*/ undefined); + + 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]; + } + + 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]; + } + + 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; + } + 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; + } + + const iterationTypes = getIterationTypesOfIterable(inputType, use, errorNode); + return iterationTypes && iterationTypes[getIterationTypesKeyFromIterationTypeKind(typeKind)]; + } + + 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 }; + } + + /** + * 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; + } + + 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 iterationTypes; + } + + 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); + } + } + 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, + ); + } + + /** + * 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; + } + else { + return use & IterationUse.ForOfFlag ? + getAsyncFromSyncIterationTypes(iterationTypes, errorNode) : + 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; + } + 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; + } + } + } + } + + if (use & IterationUse.AllowsAsyncIterablesFlag) { + const iterationTypes = getIterationTypesOfIterableSlow(type, asyncIterationTypesResolver, errorNode, errorOutputContainer, noCache); + if (iterationTypes !== noIterationTypes) { + return iterationTypes; + } + } + + 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; + } + } + } + + 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); + } + + 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)); + } + } + + 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; + } + + /** + * 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 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 *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; + } + + let iterationTypes = getIterationTypesOfIteratorCached(type, resolver) || + getIterationTypesOfIteratorFast(type, resolver); + + if (iterationTypes === noIterationTypes && errorNode) { + iterationTypes = undefined; + noCache = true; + } + + 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); + } + + /** + * 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)); + } + } + + 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); + } + + function isYieldIteratorResult(type: Type) { + return isIteratorResult(type, IterationTypeKind.Yield); + } + + function isReturnIteratorResult(type: Type) { + return isIteratorResult(type, IterationTypeKind.Return); + } + + /** + * 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 cachedTypes = getCachedIterationTypes(type, "iterationTypesOfIteratorResult"); + if (cachedTypes) { + return cachedTypes; + } + + // 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; + + if (!yieldType && !returnType) { + return setCachedIterationTypes(type, "iterationTypesOfIteratorResult", noIterationTypes); + } + + // 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)); + } + + /** + * 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; + + 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); + } + } + 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, + ); + } + } + + // 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 *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); + } + } + + // 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); + } + + 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); + } + + /** + * 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; + } + + const iterationTypes = getIterationTypesOfGeneratorFunctionReturnType(returnType, isAsyncGenerator); + return iterationTypes && iterationTypes[getIterationTypesKeyFromIterationTypeKind(kind)]; + } + + function getIterationTypesOfGeneratorFunctionReturnType(type: Type, isAsyncGenerator: boolean) { + if (isTypeAny(type)) { + return anyIterationTypes; + } + + 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 checkBreakOrContinueStatement(node: BreakOrContinueStatement) { + // Grammar checking + if (!checkGrammarStatementInAmbientContext(node)) checkGrammarBreakOrContinueStatement(node); + + // 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(unwrapAwaitedType(returnIterationType)) : returnIterationType; + } + return isAsync ? getAwaitedTypeNoAlias(returnType) || errorType : returnType; + } + + function isUnwrappedReturnTypeUndefinedVoidOrAny(func: SignatureDeclaration, returnType: Type): boolean { + const type = unwrapReturnType(returnType, getFunctionFlags(func)); + return !!(type && (maybeTypeOfKind(type, TypeFlags.Void) || type.flags & (TypeFlags.Any | TypeFlags.Undefined))); + } + + 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; + } + + 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); + } + } + 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 && compilerOptions.noImplicitReturns && !isUnwrappedReturnTypeUndefinedVoidOrAny(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); + } + } + + 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); + } + } + + function checkSwitchStatement(node: SwitchStatement) { + // Grammar checking + checkGrammarStatementInAmbientContext(node); + + let firstDefaultClause: CaseOrDefaultClause; + let hasDuplicateDefaultClause = false; + + const expressionType = checkExpression(node.expression); + + 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; + } + } + + 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 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. + const caseType = checkExpression(clause.expression); + + if (!isTypeEqualityComparableTo(expressionType, caseType)) { + // expressionType is not comparable to caseType, try the reversed check and report errors if it fails + checkTypeComparableTo(caseType, expressionType, clause.expression, /*headMessage*/ undefined); + } + }; + } + }); + if (node.caseBlock.locals) { + registerForUnusedIdentifiersCheck(node.caseBlock); + } + } + + 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); + } + + function checkThrowStatement(node: ThrowStatement) { + // Grammar checking + if (!checkGrammarStatementInAmbientContext(node)) { + if (isIdentifier(node.expression) && !node.expression.escapedText) { + grammarErrorAfterFirstToken(node, Diagnostics.Line_break_not_permitted_here); + } + } + + if (node.expression) { + checkExpression(node.expression); + } + } + + 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; + checkVariableLikeDeclaration(declaration); + const typeNode = getEffectiveTypeAnnotationNode(declaration); + if (typeNode) { + const type = getTypeFromTypeNode(typeNode); + 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); + } + 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, unescapeLeadingUnderscores(caughtName)); + } + }); + } + } + } + + checkBlock(catchClause.block); + } + + 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 = getSymbolOfDeclaration(member); + checkIndexConstraintForProperty(type, symbol, getTypeOfExpression((member as DynamicNamedDeclaration).name.expression), getNonMissingTypeOfSymbol(symbol)); + } + } + } + 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(getSymbolOfDeclaration(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(getSymbolOfDeclaration(declaration)) === type.symbol ? declaration : undefined; + for (const info of indexInfos) { + if (info === checkInfo) continue; + const localIndexDeclaration = info.declaration && getParentOfSymbol(getSymbolOfDeclaration(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": + case "undefined": + error(name, message, name.escapedText as string); + } + } + + /** + * 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 checkUnmatchedJSDocParameters(node: SignatureDeclaration) { + const jsdocParameters = filter(getJSDocTags(node), isJSDocParameterTag); + if (!length(jsdocParameters)) return; + + 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); + } + }); + + const containsArguments = containsArgumentsReference(node); + if (containsArguments) { + const lastJSDocParamIndex = jsdocParameters.length - 1; + const lastJSDocParam = jsdocParameters[lastJSDocParamIndex]; + if ( + isJs && lastJSDocParam && isIdentifier(lastJSDocParam.name) && lastJSDocParam.typeExpression && + lastJSDocParam.typeExpression.type && !parameters.has(lastJSDocParam.name.escapedText) && !excludedParameters.has(lastJSDocParamIndex) && !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)); + } + } + }); + } + } + + /** + * 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)); + } + } + + 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 === getSymbolOfDeclaration(typeParameters[i])) { + error(node, Diagnostics.Type_parameter_defaults_can_only_reference_previously_declared_type_parameters); + } + } + } + } + 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; + } + + 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); + } + } + } + } + + 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; + } + + for (let i = 0; i < numTypeParameters; i++) { + const source = sourceParameters[i]; + const target = targetParameters[i]; + + // If the type parameter node does not have the same as the resolved type + // parameter at this position, we report an error. + if (source.name.escapedText !== target.symbol.escapedName) { + 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 getFirstTransformableStaticClassElement(node: ClassLikeDeclaration) { + const willTransformStaticElementsOfDecoratedClass = !legacyDecorators && languageVersion < LanguageFeatureMinimumTarget.ClassAndClassElementDecorators && + classOrConstructorParameterIsDecorated(/*useLegacyDecorators*/ false, node); + const willTransformPrivateElementsOrClassStaticBlocks = languageVersion < LanguageFeatureMinimumTarget.PrivateNamesAndClassStaticBlocks || + languageVersion < LanguageFeatureMinimumTarget.ClassAndClassElementDecorators; + const willTransformInitializers = !emitStandardClassFields; + if (willTransformStaticElementsOfDecoratedClass || willTransformPrivateElementsOrClassStaticBlocks) { + for (const member of node.members) { + if (willTransformStaticElementsOfDecoratedClass && classElementOrClassElementParameterIsDecorated(/*useLegacyDecorators*/ false, member, node)) { + return firstOrUndefined(getDecorators(node)) ?? node; + } + else if (willTransformPrivateElementsOrClassStaticBlocks) { + if (isClassStaticBlockDeclaration(member)) { + return member; + } + else if (isStatic(member)) { + if ( + isPrivateIdentifierClassElementDeclaration(member) || + willTransformInitializers && isInitializedProperty(member) + ) { + return member; + } + } + } + } + } + } + + function checkClassExpressionExternalHelpers(node: ClassExpression) { + if (node.name) return; + + const parent = walkUpOuterExpressions(node); + if (!isNamedEvaluationSource(parent)) return; + + const willTransformESDecorators = !legacyDecorators && languageVersion < LanguageFeatureMinimumTarget.ClassAndClassElementDecorators; + let location: Node | undefined; + if (willTransformESDecorators && classOrConstructorParameterIsDecorated(/*useLegacyDecorators*/ false, node)) { + location = firstOrUndefined(getDecorators(node)) ?? node; + } + else { + location = getFirstTransformableStaticClassElement(node); + } + + if (location) { + checkExternalEmitHelpers(location, ExternalEmitHelpers.SetFunctionName); + if ((isPropertyAssignment(parent) || isPropertyDeclaration(parent) || isBindingElement(parent)) && isComputedPropertyName(parent.name)) { + checkExternalEmitHelpers(location, ExternalEmitHelpers.PropKey); + } + } + } + + function checkClassExpression(node: ClassExpression): Type { + checkClassLikeDeclaration(node); + checkNodeDeferred(node); + checkClassExpressionExternalHelpers(node); + return getTypeOfSymbol(getSymbolOfDeclaration(node)); + } + + function checkClassExpressionDeferred(node: ClassExpression) { + forEach(node.members, checkSourceElement); + registerForUnusedIdentifiersCheck(node); + } + + function checkClassDeclaration(node: ClassDeclaration) { + const firstDecorator = find(node.modifiers, isDecorator); + if (legacyDecorators && 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); + + registerForUnusedIdentifiersCheck(node); + } + + function checkClassLikeDeclaration(node: ClassLikeDeclaration) { + checkGrammarClassLikeDeclaration(node); + checkDecorators(node); + checkCollisionsForDeclarationName(node, node.name); + checkTypeParameters(getEffectiveTypeParameterDeclarations(node)); + checkExportsOnMergedDeclarations(node); + const symbol = getSymbolOfDeclaration(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 < LanguageFeatureMinimumTarget.Classes) { + 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 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 { + 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); + } + } + checkKindsOfPropertyMemberOverrides(type, baseType); + }); + } + } + + 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)); + } + } + + 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 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; + } + + 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, + ); + } + } + + /** + * @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( + node, + staticType, + baseStaticType, + baseWithThis, + type, + typeWithThis, + hasOverrideModifier(member), + hasAbstractModifier(member), + isStatic(member), + memberIsParameterProperty, + 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, + member: Symbol, + errorNode?: Node, + ): MemberOverrideStatus { + const isJs = isInJSFile(node); + const nodeInAmbientContext = !!(node.flags & NodeFlags.Ambient); + if (baseWithThis && (memberHasOverrideModifier || compilerOptions.noImplicitOverride)) { + const thisType = memberIsStatic ? staticType : typeWithThis; + const baseType = memberIsStatic ? baseStaticType : baseWithThis; + const prop = getPropertyOfType(thisType, member.escapedName); + const baseProp = getPropertyOfType(baseType, member.escapedName); + + const baseClassName = typeToString(baseWithThis); + if (prop && !baseProp && memberHasOverrideModifier) { + if (errorNode) { + const suggestion = getSuggestedSymbolForNonexistentClassMember(symbolName(member), 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; + } + + 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) { + 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; + } + + 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, /*headMessage*/ undefined, rootChain)) { + issuedMemberError = true; + } + } + } + } + if (!issuedMemberError) { + // check again with diagnostics to generate a less-specific error + checkTypeAssignableTo(typeWithThis, baseWithThis, node.name || node, broadDiag); + } + } + + 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)); + } + } + } + } + + /** + * 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. + * @param memberSymbol Member symbol. + * Note: `member` can be a synthetic node without a parent. + */ + function getMemberOverrideModifierStatus(node: ClassLikeDeclaration, member: ClassElement, memberSymbol: Symbol): MemberOverrideStatus { + if (!member.name) { + return MemberOverrideStatus.Ok; + } + + const classSymbol = getSymbolOfDeclaration(node); + const type = getDeclaredTypeOfSymbol(classSymbol) as InterfaceType; + const typeWithThis = getTypeWithThisArgument(type); + const staticType = getTypeOfSymbol(classSymbol) 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); + + return checkMemberForOverrideModifier( + node, + staticType, + baseStaticType, + baseWithThis, + type, + typeWithThis, + memberHasOverrideModifier, + hasAbstractModifier(member), + isStatic(member), + /*memberIsParameterProperty*/ false, + memberSymbol, + ); + } + + 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 + // NOTE: cast to TransientSymbol should be safe because only TransientSymbols have CheckFlags.Instantiated + return getCheckFlags(s) & CheckFlags.Instantiated ? (s as TransientSymbol).links.target! : s; + } + + function getClassOrInterfaceDeclarationsOfSymbol(symbol: Symbol) { + return filter(symbol.declarations, (d: Declaration): d is ClassDeclaration | InterfaceDeclaration => d.kind === SyntaxKind.ClassDeclaration || d.kind === SyntaxKind.InterfaceDeclaration); + } + + 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); + + interface MemberInfo { + missedProperties: string[]; + baseTypeName: string; + typeName: string; + } + const notImplementedInfo = new Map(); + + 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; + } + } + const baseTypeName = typeToString(baseType); + const typeName = typeToString(type); + const basePropertyName = symbolToString(baseProperty); + const missedProperties = append(notImplementedInfo.get(derivedClassDecl)?.missedProperties, basePropertyName); + notImplementedInfo.set(derivedClassDecl, { baseTypeName, typeName, missedProperties }); + } + } + 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; + } + + 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)); + } + } + } + + // 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; + } + + error(getNameOfDeclaration(derived.valueDeclaration) || derived.valueDeclaration, errorMessage, typeToString(baseType), symbolToString(base), typeToString(type)); + } + } + + for (const [errorNode, memberInfo] of notImplementedInfo) { + if (length(memberInfo.missedProperties) === 1) { + if (isClassExpression(errorNode)) { + error(errorNode, Diagnostics.Non_abstract_class_expression_does_not_implement_inherited_abstract_member_0_from_class_1, first(memberInfo.missedProperties), memberInfo.baseTypeName); + } + else { + error(errorNode, Diagnostics.Non_abstract_class_0_does_not_implement_inherited_abstract_member_1_from_class_2, memberInfo.typeName, first(memberInfo.missedProperties), memberInfo.baseTypeName); + } + } + else if (length(memberInfo.missedProperties) > 5) { + const missedProperties = map(memberInfo.missedProperties.slice(0, 4), prop => `'${prop}'`).join(", "); + const remainingMissedProperties = length(memberInfo.missedProperties) - 4; + if (isClassExpression(errorNode)) { + error(errorNode, Diagnostics.Non_abstract_class_expression_is_missing_implementations_for_the_following_members_of_0_Colon_1_and_2_more, memberInfo.baseTypeName, missedProperties, remainingMissedProperties); + } + else { + error(errorNode, Diagnostics.Non_abstract_class_0_is_missing_implementations_for_the_following_members_of_1_Colon_2_and_3_more, memberInfo.typeName, memberInfo.baseTypeName, missedProperties, remainingMissedProperties); + } + } + else { + const missedProperties = map(memberInfo.missedProperties, prop => `'${prop}'`).join(", "); + if (isClassExpression(errorNode)) { + error(errorNode, Diagnostics.Non_abstract_class_expression_is_missing_implementations_for_the_following_members_of_0_Colon_1, memberInfo.baseTypeName, missedProperties); + } + else { + error(errorNode, Diagnostics.Non_abstract_class_0_is_missing_implementations_for_the_following_members_of_1_Colon_2, memberInfo.typeName, memberInfo.baseTypeName, missedProperties); + } + } + } + } + + 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); + }); + + 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; + } + + 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; + + 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; + + 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(getSourceFileOfNode(typeNode), typeNode, errorInfo)); + } + } + } + } + + 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; + } + if (!isStatic(member) && isPropertyWithoutInitializer(member)) { + const propName = (member as PropertyDeclaration).name; + if (isIdentifier(propName) || isPrivateIdentifier(propName) || isComputedPropertyName(propName)) { + const type = getTypeOfSymbol(getSymbolOfDeclaration(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 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); + } + + function checkInterfaceDeclaration(node: InterfaceDeclaration) { + // Grammar checking + if (!checkGrammarModifiers(node)) checkGrammarInterfaceDeclaration(node); + + checkTypeParameters(node.typeParameters); + addLazyDiagnostic(() => { + checkTypeNameIsReserved(node.name, Diagnostics.Interface_name_cannot_be_0); + + checkExportsOnMergedDeclarations(node); + const symbol = getSymbolOfDeclaration(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); + + addLazyDiagnostic(() => { + checkTypeForDuplicateIndexSignatures(node); + registerForUnusedIdentifiersCheck(node); + }); + } + + function checkTypeAliasDeclaration(node: TypeAliasDeclaration) { + // Grammar checking + checkGrammarModifiers(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 computeEnumMemberValues(node: EnumDeclaration) { + const nodeLinks = getNodeLinks(node); + if (!(nodeLinks.flags & NodeCheckFlags.EnumValuesComputed)) { + nodeLinks.flags |= NodeCheckFlags.EnumValuesComputed; + let autoValue: number | undefined = 0; + let previous: EnumMember | undefined; + for (const member of node.members) { + const result = computeEnumMemberValue(member, autoValue, previous); + getNodeLinks(member).enumMemberValue = result; + autoValue = typeof result.value === "number" ? result.value + 1 : undefined; + previous = member; + } + } + } + + function computeEnumMemberValue(member: EnumMember, autoValue: number | undefined, previous: EnumMember | undefined): EvaluatorResult { + 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 computeConstantEnumMemberValue(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 evaluatorResult(/*value*/ 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) { + error(member.name, Diagnostics.Enum_member_must_have_initializer); + return evaluatorResult(/*value*/ undefined); + } + if (getIsolatedModules(compilerOptions) && previous?.initializer) { + const prevValue = getEnumMemberValue(previous); + if (!(typeof prevValue.value === "number" && !prevValue.resolvedOtherFiles)) { + error( + member.name, + Diagnostics.Enum_member_following_a_non_literal_numeric_member_must_have_an_initializer_when_isolatedModules_is_enabled, + ); + } + } + return evaluatorResult(autoValue); + } + + function computeConstantEnumMemberValue(member: EnumMember): EvaluatorResult { + const isConstEnum = isEnumConst(member.parent); + const initializer = member.initializer!; + const result = evaluate(initializer, member); + if (result.value !== undefined) { + if (isConstEnum && typeof result.value === "number" && !isFinite(result.value)) { + error( + initializer, + isNaN(result.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 (getIsolatedModules(compilerOptions) && typeof result.value === "string" && !result.isSyntacticallyString) { + error( + initializer, + Diagnostics._0_has_a_string_type_but_must_have_syntactically_recognizable_string_syntax_when_isolatedModules_is_enabled, + `${idText(member.parent.name)}.${getTextOfPropertyName(member.name)}`, + ); + } + } + 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 result; + } + + function evaluateEntityNameExpression(expr: EntityNameExpression, location?: Declaration) { + const symbol = resolveEntityName(expr, SymbolFlags.Value, /*ignoreErrors*/ true); + if (!symbol) return evaluatorResult(/*value*/ undefined); + + if (expr.kind === SyntaxKind.Identifier) { + const identifier = expr; + if (isInfinityOrNaNString(identifier.escapedText) && (symbol === getGlobalSymbol(identifier.escapedText, SymbolFlags.Value, /*diagnostic*/ undefined))) { + // Technically we resolved a global lib file here, but the decision to treat this as numeric + // is more predicated on the fact that the single-file resolution *didn't* resolve to a + // different meaning of `Infinity` or `NaN`. Transpilers handle this no problem. + return evaluatorResult(+(identifier.escapedText), /*isSyntacticallyString*/ false); + } + } + + if (symbol.flags & SymbolFlags.EnumMember) { + return location ? evaluateEnumMember(expr, symbol, location) : getEnumMemberValue(symbol.valueDeclaration as EnumMember); + } + if (isConstantVariable(symbol)) { + const declaration = symbol.valueDeclaration; + if (declaration && isVariableDeclaration(declaration) && !declaration.type && declaration.initializer && (!location || declaration !== location && isBlockScopedNameDeclaredBeforeUse(declaration, location))) { + const result = evaluate(declaration.initializer, declaration); + if (location && getSourceFileOfNode(location) !== getSourceFileOfNode(declaration)) { + return evaluatorResult( + result.value, + /*isSyntacticallyString*/ false, + /*resolvedOtherFiles*/ true, + /*hasExternalReferences*/ true, + ); + } + return evaluatorResult(result.value, result.isSyntacticallyString, result.resolvedOtherFiles, /*hasExternalReferences*/ true); + } + } + return evaluatorResult(/*value*/ undefined); + } + + function evaluateElementAccessExpression(expr: ElementAccessExpression, location?: Declaration) { + const root = expr.expression; + if (isEntityNameExpression(root) && isStringLiteralLike(expr.argumentExpression)) { + const rootSymbol = resolveEntityName(root, SymbolFlags.Value, /*ignoreErrors*/ true); + if (rootSymbol && rootSymbol.flags & SymbolFlags.Enum) { + const name = escapeLeadingUnderscores(expr.argumentExpression.text); + const member = rootSymbol.exports!.get(name); + if (member) { + Debug.assert(getSourceFileOfNode(member.valueDeclaration) === getSourceFileOfNode(rootSymbol.valueDeclaration)); + return location ? evaluateEnumMember(expr, member, location) : getEnumMemberValue(member.valueDeclaration as EnumMember); + } + } + } + return evaluatorResult(/*value*/ 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 evaluatorResult(/*value*/ 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 evaluatorResult(/*value*/ 0); + } + const value = getEnumMemberValue(declaration as EnumMember); + if (location.parent !== declaration.parent) { + return evaluatorResult(value.value, value.isSyntacticallyString, value.resolvedOtherFiles, /*hasExternalReferences*/ true); + } + return value; + } + + function checkEnumDeclaration(node: EnumDeclaration) { + addLazyDiagnostic(() => checkEnumDeclarationWorker(node)); + } + + function checkEnumDeclarationWorker(node: EnumDeclaration) { + // Grammar checking + checkGrammarModifiers(node); + + checkCollisionsForDeclarationName(node, node.name); + checkExportsOnMergedDeclarations(node); + node.members.forEach(checkEnumMember); + + 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 = getSymbolOfDeclaration(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; + } + + 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); + } + 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 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; + } + + 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); + } + } + + 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); + } + + 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 (!checkGrammarModifiers(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 = getSymbolOfDeclaration(node); + + // The following checks only apply on a non-ambient instantiated module declaration. + if ( + symbol.flags & SymbolFlags.ValueModule + && !inAmbientContext + && isInstantiatedModule(node, shouldPreserveConstEnums(compilerOptions)) + ) { + if (getIsolatedModules(compilerOptions) && !getSourceFileOfNode(node).externalModuleIndicator) { + // This could be loosened a little if needed. The only problem we are trying to avoid is unqualified + // references to namespace members declared in other files. But use of namespaces is discouraged anyway, + // so for now we will just not allow them in scripts, which is the only place they can merge cross-file. + error(node.name, Diagnostics.Namespaces_are_not_allowed_in_global_script_files_when_0_is_enabled_If_this_file_is_not_intended_to_be_a_global_script_set_moduleDetection_to_force_or_add_an_empty_export_statement, isolatedModulesLikeFlagName); + } + if (symbol.declarations?.length! > 1) { + 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 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 ( + compilerOptions.verbatimModuleSyntax && + node.parent.kind === SyntaxKind.SourceFile && + (moduleKind === ModuleKind.CommonJS || node.parent.impliedNodeFormat === ModuleKind.CommonJS) + ) { + const exportModifier = node.modifiers?.find(m => m.kind === SyntaxKind.ExportKeyword); + if (exportModifier) { + error(exportModifier, Diagnostics.A_top_level_export_modifier_cannot_be_used_on_value_declarations_in_a_CommonJS_module_when_verbatimModuleSyntax_is_enabled); + } + } + } + + 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 || (getSymbolOfDeclaration(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 (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 { + // 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; + } + 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 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 (!isImportEqualsDeclaration(node) && node.attributes) { + const diagnostic = node.attributes.token === SyntaxKind.WithKeyword ? Diagnostics.Import_attribute_values_must_be_string_literal_expressions : Diagnostics.Import_assertion_values_must_be_string_literal_expressions; + let hasError = false; + for (const attr of node.attributes.elements) { + if (!isStringLiteral(attr.value)) { + hasError = true; + error(attr.value, diagnostic); + } + } + return !hasError; + } + return true; + } + + function checkModuleExportName(name: ModuleExportName | undefined, allowStringLiteral = true) { + if (name === undefined || name.kind !== SyntaxKind.StringLiteral) { + return; + } + if (!allowStringLiteral) { + grammarErrorOnNode(name, Diagnostics.Identifier_expected); + } + else if (moduleKind === ModuleKind.ES2015 || moduleKind === ModuleKind.ES2020) { + grammarErrorOnNode(name, Diagnostics.String_literal_import_and_export_names_are_not_supported_when_the_module_flag_is_set_to_es2015_or_es2020); + } + } + + function checkAliasSymbol(node: AliasDeclarationNode) { + let symbol = getSymbolOfDeclaration(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(moduleExportNameTextEscaped(node.propertyName || node.name)); + 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; + } + + const targetFlags = getSymbolFlags(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)); + } + else if (node.kind !== SyntaxKind.ExportSpecifier) { + // Look at 'compilerOptions.isolatedModules' and not 'getIsolatedModules(...)' (which considers 'verbatimModuleSyntax') + // here because 'verbatimModuleSyntax' will already have an error for importing a type without 'import type'. + const appearsValueyToTranspiler = compilerOptions.isolatedModules && !findAncestor(node, isTypeOnlyImportOrExportDeclaration); + if (appearsValueyToTranspiler && symbol.flags & (SymbolFlags.Value | SymbolFlags.ExportValue)) { + error( + node, + Diagnostics.Import_0_conflicts_with_local_value_so_must_be_declared_with_a_type_only_import_when_isolatedModules_is_enabled, + symbolToString(symbol), + isolatedModulesLikeFlagName, + ); + } + } + + if ( + getIsolatedModules(compilerOptions) + && !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.verbatimModuleSyntax) { + Debug.assertIsDefined(node.name, "An ImportClause with a symbol should have a name"); + const message = compilerOptions.verbatimModuleSyntax && isInternalModuleImportEqualsDeclaration(node) + ? Diagnostics.An_import_alias_cannot_resolve_to_a_type_or_type_only_declaration_when_verbatimModuleSyntax_is_enabled + : isType + ? Diagnostics._0_is_a_type_and_must_be_imported_using_a_type_only_import_when_verbatimModuleSyntax_is_enabled + : Diagnostics._0_resolves_to_a_type_only_declaration_and_must_be_imported_using_a_type_only_import_when_verbatimModuleSyntax_is_enabled; + const name = moduleExportNameTextUnescaped(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_0_is_enabled, isolatedModulesLikeFlagName); + } + 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 (compilerOptions.verbatimModuleSyntax || getSourceFileOfNode(typeOnlyAlias) !== getSourceFileOfNode(node)) { + const name = moduleExportNameTextUnescaped(node.propertyName || node.name); + const diagnostic = isType + ? error(node, Diagnostics.Re_exporting_a_type_when_0_is_enabled_requires_using_export_type, isolatedModulesLikeFlagName) + : error(node, Diagnostics._0_resolves_to_a_type_only_declaration_and_must_be_re_exported_using_a_type_only_re_export_when_1_is_enabled, name, isolatedModulesLikeFlagName); + addTypeOnlyDeclarationRelatedInfo(diagnostic, isType ? undefined : typeOnlyAlias, name); + break; + } + } + } + } + + if ( + compilerOptions.verbatimModuleSyntax && + node.kind !== SyntaxKind.ImportEqualsDeclaration && + !isInJSFile(node) && + (moduleKind === ModuleKind.CommonJS || getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS) + ) { + error(node, Diagnostics.ESM_syntax_is_not_allowed_in_a_CommonJS_module_when_verbatimModuleSyntax_is_enabled); + } + } + + if (isImportSpecifier(node)) { + const targetSymbol = resolveAliasWithDeprecationCheck(symbol, node); + if (isDeprecatedSymbol(targetSymbol) && targetSymbol.declarations) { + addDeprecatedSuggestion(node, targetSymbol.declarations, targetSymbol.escapedName as string); + } + } + } + } + + function resolveAliasWithDeprecationCheck(symbol: Symbol, location: Node) { + if (!(symbol.flags & SymbolFlags.Alias) || isDeprecatedSymbol(symbol) || !getDeclarationOfAliasSymbol(symbol)) { + 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 (isDeprecatedSymbol(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) { + checkModuleExportName(node.propertyName); + if ( + moduleExportNameIsDefault(node.propertyName || node.name) && + getESModuleInterop(compilerOptions) && + moduleKind !== ModuleKind.System && (moduleKind < ModuleKind.ES2015 || getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS) + ) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.ImportDefault); + } + } + } + + function checkImportAttributes(declaration: ImportDeclaration | ExportDeclaration | JSDocImportTag) { + const node = declaration.attributes; + if (node) { + const importAttributesType = getGlobalImportAttributesType(/*reportErrors*/ true); + if (importAttributesType !== emptyObjectType) { + checkTypeAssignableTo(getTypeFromImportAttributes(node), getNullableType(importAttributesType, TypeFlags.Undefined), node); + } + + const validForTypeAttributes = isExclusivelyTypeOnlyImportOrExport(declaration); + const override = getResolutionModeOverride(node, validForTypeAttributes ? grammarErrorOnNode : undefined); + const isImportAttributes = declaration.attributes.token === SyntaxKind.WithKeyword; + if (validForTypeAttributes && override) { + return; // Other grammar checks do not apply to type-only imports with resolution mode assertions + } + + const mode = (moduleKind === ModuleKind.NodeNext) && declaration.moduleSpecifier && getUsageModeForExpression(declaration.moduleSpecifier); + if (mode !== ModuleKind.ESNext && moduleKind !== ModuleKind.ESNext && moduleKind !== ModuleKind.Preserve) { + const message = isImportAttributes + ? moduleKind === ModuleKind.NodeNext + ? Diagnostics.Import_attributes_are_not_allowed_on_statements_that_compile_to_CommonJS_require_calls + : Diagnostics.Import_attributes_are_only_supported_when_the_module_option_is_set_to_esnext_nodenext_or_preserve + : moduleKind === ModuleKind.NodeNext + ? Diagnostics.Import_assertions_are_not_allowed_on_statements_that_compile_to_CommonJS_require_calls + : Diagnostics.Import_assertions_are_only_supported_when_the_module_option_is_set_to_esnext_nodenext_or_preserve; + return grammarErrorOnNode(node, message); + } + + const isTypeOnly = isJSDocImportTag(declaration) || (isImportDeclaration(declaration) ? declaration.importClause?.isTypeOnly : declaration.isTypeOnly); + if (isTypeOnly) { + return grammarErrorOnNode(node, isImportAttributes ? Diagnostics.Import_attributes_cannot_be_used_with_type_only_imports_or_exports : Diagnostics.Import_assertions_cannot_be_used_with_type_only_imports_or_exports); + } + + if (override) { + return grammarErrorOnNode(node, Diagnostics.resolution_mode_can_only_be_set_for_type_only_imports); + } + } + } + + function checkImportAttribute(node: ImportAttribute) { + return getRegularTypeOfLiteralType(checkExpressionCached(node.value)); + } + + 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 (!checkGrammarModifiers(node) && node.modifiers) { + 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); + } + } + } + } + } + checkImportAttributes(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; + } + + checkGrammarModifiers(node); + if (isInternalModuleImportEqualsDeclaration(node) || checkExternalImportOrExportDeclaration(node)) { + checkImportBinding(node); + markLinkedReferences(node, ReferenceHint.ExportImportEquals); + if (node.moduleReference.kind !== SyntaxKind.ExternalModuleReference) { + const target = resolveAlias(getSymbolOfDeclaration(node)); + if (target !== unknownSymbol) { + const targetFlags = getSymbolFlags(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); + } + } + if (node.isTypeOnly) { + grammarErrorOnNode(node, Diagnostics.An_import_alias_cannot_use_import_type); + } + } + else { + if (moduleKind >= ModuleKind.ES2015 && moduleKind !== ModuleKind.Preserve && 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; + } + + if (!checkGrammarModifiers(node) && hasSyntacticModifiers(node)) { + grammarErrorOnFirstToken(node, Diagnostics.An_export_declaration_cannot_have_modifiers); + } + + 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); + checkModuleExportName(node.exportClause.name); + } + 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); + } + } + } + } + checkImportAttributes(node); + } + + function checkGrammarExportDeclaration(node: ExportDeclaration): boolean { + if (node.isTypeOnly && 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); + } + return !isInAppropriateContext; + } + + function checkExportSpecifier(node: ExportSpecifier) { + checkAliasSymbol(node); + const hasModuleSpecifier = node.parent.parent.moduleSpecifier !== undefined; + checkModuleExportName(node.propertyName, hasModuleSpecifier); + checkModuleExportName(node.name); + if (getEmitDeclarations(compilerOptions)) { + collectLinkedAliases(node.propertyName || node.name, /*setVisibility*/ true); + } + if (!hasModuleSpecifier) { + const exportedName = node.propertyName || node.name; + if (exportedName.kind === SyntaxKind.StringLiteral) { + return; // Skip for invalid syntax like this: export { "x" } + } + // 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, /*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 { + markLinkedReferences(node, ReferenceHint.ExportSpecifier); + } + } + else { + if ( + getESModuleInterop(compilerOptions) && + moduleKind !== ModuleKind.System && + (moduleKind < ModuleKind.ES2015 || getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS) && + moduleExportNameIsDefault(node.propertyName || node.name) + ) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.ImportDefault); + } + } + } + + 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; + } + + 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); + } + + return; + } + // Grammar checking + if (!checkGrammarModifiers(node) && hasEffectiveModifiers(node)) { + grammarErrorOnFirstToken(node, Diagnostics.An_export_assignment_cannot_have_modifiers); + } + + const typeAnnotationNode = getEffectiveTypeAnnotationNode(node); + if (typeAnnotationNode) { + checkTypeAssignableTo(checkExpressionCached(node.expression), getTypeFromTypeNode(typeAnnotationNode), node.expression); + } + + const isIllegalExportDefaultInCJS = !node.isExportEquals && + !(node.flags & NodeFlags.Ambient) && + compilerOptions.verbatimModuleSyntax && + (moduleKind === ModuleKind.CommonJS || getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS); + + if (node.expression.kind === SyntaxKind.Identifier) { + const id = node.expression as Identifier; + const sym = getExportSymbolOfValueSymbolIfExported(resolveEntityName(id, SymbolFlags.All, /*ignoreErrors*/ true, /*dontResolveAlias*/ true, node)); + if (sym) { + markLinkedReferences(node, ReferenceHint.ExportAssignment); + const typeOnlyDeclaration = getTypeOnlyAliasDeclaration(sym, SymbolFlags.Value); + // If not a value, we're interpreting the identifier as a type export, along the lines of (`export { Id as default }`) + if (getSymbolFlags(sym) & SymbolFlags.Value) { + // However if it is a value, we need to check it's being used correctly + checkExpressionCached(id); + if (!isIllegalExportDefaultInCJS && !(node.flags & NodeFlags.Ambient) && compilerOptions.verbatimModuleSyntax && typeOnlyDeclaration) { + error( + id, + node.isExportEquals + ? Diagnostics.An_export_declaration_must_reference_a_real_value_when_verbatimModuleSyntax_is_enabled_but_0_resolves_to_a_type_only_declaration + : Diagnostics.An_export_default_must_reference_a_real_value_when_verbatimModuleSyntax_is_enabled_but_0_resolves_to_a_type_only_declaration, + idText(id), + ); + } + } + else if (!isIllegalExportDefaultInCJS && !(node.flags & NodeFlags.Ambient) && compilerOptions.verbatimModuleSyntax) { + error( + id, + node.isExportEquals + ? Diagnostics.An_export_declaration_must_reference_a_value_when_verbatimModuleSyntax_is_enabled_but_0_only_refers_to_a_type + : Diagnostics.An_export_default_must_reference_a_value_when_verbatimModuleSyntax_is_enabled_but_0_only_refers_to_a_type, + idText(id), + ); + } + + if (!isIllegalExportDefaultInCJS && !(node.flags & NodeFlags.Ambient) && getIsolatedModules(compilerOptions) && !(sym.flags & SymbolFlags.Value)) { + const nonLocalMeanings = getSymbolFlags(sym, /*excludeTypeOnlyMeanings*/ false, /*excludeLocalMeanings*/ true); + if ( + sym.flags & SymbolFlags.Alias + && nonLocalMeanings & SymbolFlags.Type + && !(nonLocalMeanings & SymbolFlags.Value) + && (!typeOnlyDeclaration || getSourceFileOfNode(typeOnlyDeclaration) !== getSourceFileOfNode(node)) + ) { + // import { SomeType } from "./someModule"; + // export default SomeType; OR + // export = SomeType; + error( + id, + node.isExportEquals ? + Diagnostics._0_resolves_to_a_type_and_must_be_marked_type_only_in_this_file_before_re_exporting_when_1_is_enabled_Consider_using_import_type_where_0_is_imported + : Diagnostics._0_resolves_to_a_type_and_must_be_marked_type_only_in_this_file_before_re_exporting_when_1_is_enabled_Consider_using_export_type_0_as_default, + idText(id), + isolatedModulesLikeFlagName, + ); + } + else if (typeOnlyDeclaration && getSourceFileOfNode(typeOnlyDeclaration) !== getSourceFileOfNode(node)) { + // import { SomeTypeOnlyValue } from "./someModule"; + // export default SomeTypeOnlyValue; OR + // export = SomeTypeOnlyValue; + addTypeOnlyDeclarationRelatedInfo( + error( + id, + node.isExportEquals ? + Diagnostics._0_resolves_to_a_type_only_declaration_and_must_be_marked_type_only_in_this_file_before_re_exporting_when_1_is_enabled_Consider_using_import_type_where_0_is_imported + : Diagnostics._0_resolves_to_a_type_only_declaration_and_must_be_marked_type_only_in_this_file_before_re_exporting_when_1_is_enabled_Consider_using_export_type_0_as_default, + idText(id), + isolatedModulesLikeFlagName, + ), + typeOnlyDeclaration, + idText(id), + ); + } + } + } + else { + checkExpressionCached(id); // doesn't resolve, check as expression to mark as error + } + + if (getEmitDeclarations(compilerOptions)) { + collectLinkedAliases(id, /*setVisibility*/ true); + } + } + else { + checkExpressionCached(node.expression); + } + + if (isIllegalExportDefaultInCJS) { + error(node, Diagnostics.ESM_syntax_is_not_allowed_in_a_CommonJS_module_when_verbatimModuleSyntax_is_enabled); + } + + 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 (node.isExportEquals) { + // Forbid export= in esm implementation files, and esm mode declaration files + if ( + moduleKind >= ModuleKind.ES2015 && + moduleKind !== ModuleKind.Preserve && + ((node.flags & NodeFlags.Ambient && getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.ESNext) || + (!(node.flags & NodeFlags.Ambient) && 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 && !(node.flags & NodeFlags.Ambient)) { + // system modules does not support export assignment + grammarErrorOnNode(node, Diagnostics.Export_assignment_is_not_supported_when_module_flag_is_system); + } + } + } + + function hasExportedMembers(moduleSymbol: Symbol) { + return forEachEntry(moduleSymbol.exports!, (_, id) => id !== "export="); + } + + function checkExternalModuleExports(node: SourceFile | ModuleDeclaration) { + const moduleSymbol = getSymbolOfDeclaration(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))); + } + } + } + } + }); + } + 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 checkSourceElement(node: Node | undefined): void { + if (node) { + const saveCurrentNode = currentNode; + currentNode = node; + instantiationCount = 0; + checkSourceElementWorker(node); + currentNode = saveCurrentNode; + } + } + + function checkSourceElementWorker(node: Node): void { + if (getNodeCheckFlags(node) & NodeCheckFlags.PartiallyTypeChecked) { + return; + } + + if (canHaveJSDoc(node)) { + forEach(node.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(); + } + } + if (kind >= SyntaxKind.FirstStatement && kind <= SyntaxKind.LastStatement && canHaveFlowNode(node) && node.flowNode && !isReachableFlowNode(node.flowNode)) { + errorOrSuggestion(compilerOptions.allowUnreachableCode === false, node, Diagnostics.Unreachable_code_detected); + } + + // If editing this, keep `isSourceElement` in utilities up to date. + 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.JSDocSatisfiesTag: + return checkJSDocSatisfiesTag(node as JSDocSatisfiesTag); + case SyntaxKind.JSDocThisTag: + return checkJSDocThisTag(node as JSDocThisTag); + case SyntaxKind.JSDocImportTag: + return checkJSDocImportTag(node as JSDocImportTag); + 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 checkJSDocCommentWorker(node: string | readonly JSDocComment[] | undefined) { + if (isArray(node)) { + forEach(node, tag => { + if (isJSDocLinkLike(tag)) { + checkSourceElement(tag); + } + }); + } + } + + function checkJSDocTypeIsInJsFile(node: Node): void { + if (!isInJSFile(node)) { + if (isJSDocNonNullableType(node) || isJSDocNullableType(node)) { + const token = tokenToString(isJSDocNonNullableType(node) ? SyntaxKind.ExclamationToken : SyntaxKind.QuestionToken); + const diagnostic = node.postfix + ? Diagnostics._0_at_the_end_of_a_type_is_not_valid_TypeScript_syntax_Did_you_mean_to_write_1 + : Diagnostics._0_at_the_start_of_a_type_is_not_valid_TypeScript_syntax_Did_you_mean_to_write_1; + const typeNode = node.type; + const type = getTypeFromTypeNode(typeNode); + grammarErrorOnNode( + node, + diagnostic, + token, + typeToString( + isJSDocNullableType(node) && !(type === neverType || type === voidType) + ? getUnionType(append([type, undefinedType], node.postfix ? undefined : nullType)) : type, + ), + ); + } + else { + grammarErrorOnNode(node, Diagnostics.JSDoc_types_can_only_be_used_inside_documentation_comments); + } + } + } + + 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); + } + 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; + } + + const param = getParameterSymbolFromJSDoc(paramTag); + if (!param) { + // We will error in `checkJSDocParameterTag`. + return; + } + + 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 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); + } + else { + Debug.assert(!links.deferredNodes, "A type-checked file should have no deferred nodes."); + } + } + + function checkDeferredNodes(context: SourceFile) { + const links = getNodeLinks(context); + if (links.deferredNodes) { + links.deferredNodes.forEach(checkDeferredNode); + } + links.deferredNodes = undefined; + } + + 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; + case SyntaxKind.TypeAssertionExpression: + case SyntaxKind.AsExpression: + case SyntaxKind.ParenthesizedExpression: + checkAssertionDeferred(node as AssertionExpression | JSDocTypeAssertion); + break; + case SyntaxKind.VoidExpression: + checkExpression((node as VoidExpression).expression); + break; + case SyntaxKind.BinaryExpression: + if (isInstanceOfExpression(node)) { + resolveUntypedCall(node); + } + break; + } + currentNode = saveCurrentNode; + tracing?.pop(); + } + + function checkSourceFile(node: SourceFile, nodesToCheck: Node[] | undefined) { + tracing?.push(tracing.Phase.Check, nodesToCheck ? "checkSourceFileNodes" : "checkSourceFile", { path: node.path }, /*separateBeginAndEnd*/ true); + const beforeMark = nodesToCheck ? "beforeCheckNodes" : "beforeCheck"; + const afterMark = nodesToCheck ? "afterCheckNodes" : "afterCheck"; + performance.mark(beforeMark); + nodesToCheck ? checkSourceFileNodesWorker(node, nodesToCheck) : checkSourceFileWorker(node); + performance.mark(afterMark); + performance.measure("Check", beforeMark, afterMark); + tracing?.pop(); + } + + 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 getPotentiallyUnusedIdentifiers(sourceFile: SourceFile): readonly PotentiallyUnusedIdentifier[] { + return allPotentiallyUnusedIdentifiers.get(sourceFile.path) || emptyArray; + } + + // 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; + } + + // Grammar checking + checkGrammarSourceFile(node); + + clear(potentialThisCollisions); + clear(potentialNewTargetCollisions); + clear(potentialWeakMapSetCollisions); + clear(potentialReflectCollisions); + clear(potentialUnusedRenamedBindingElementsInTypes); + + if (links.flags & NodeCheckFlags.PartiallyTypeChecked) { + potentialThisCollisions = links.potentialThisCollisions!; + potentialNewTargetCollisions = links.potentialNewTargetCollisions!; + potentialWeakMapSetCollisions = links.potentialWeakMapSetCollisions!; + potentialReflectCollisions = links.potentialReflectCollisions!; + potentialUnusedRenamedBindingElementsInTypes = links.potentialUnusedRenamedBindingElementsInTypes!; + } + + forEach(node.statements, checkSourceElement); + checkSourceElement(node.endOfFileToken); + + checkDeferredNodes(node); + + if (isExternalOrCommonJsModule(node)) { + registerForUnusedIdentifiersCheck(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(); + } + }); + + if (isExternalOrCommonJsModule(node)) { + checkExternalModuleExports(node); + } + + if (potentialThisCollisions.length) { + forEach(potentialThisCollisions, checkIfThisIsCapturedInEnclosingScope); + clear(potentialThisCollisions); + } + + if (potentialNewTargetCollisions.length) { + forEach(potentialNewTargetCollisions, checkIfNewTargetIsCapturedInEnclosingScope); + clear(potentialNewTargetCollisions); + } + + if (potentialWeakMapSetCollisions.length) { + forEach(potentialWeakMapSetCollisions, checkWeakMapSetCollision); + clear(potentialWeakMapSetCollisions); + } + + if (potentialReflectCollisions.length) { + forEach(potentialReflectCollisions, checkReflectCollision); + clear(potentialReflectCollisions); + } + + links.flags |= NodeCheckFlags.TypeChecked; + } + } + + function checkSourceFileNodesWorker(file: SourceFile, nodes: readonly Node[]) { + const links = getNodeLinks(file); + if (!(links.flags & NodeCheckFlags.TypeChecked)) { + if (skipTypeChecking(file, compilerOptions, host)) { + return; + } + + // Grammar checking + checkGrammarSourceFile(file); + + clear(potentialThisCollisions); + clear(potentialNewTargetCollisions); + clear(potentialWeakMapSetCollisions); + clear(potentialReflectCollisions); + clear(potentialUnusedRenamedBindingElementsInTypes); + + forEach(nodes, checkSourceElement); + + checkDeferredNodes(file); + + (links.potentialThisCollisions || (links.potentialThisCollisions = [])).push(...potentialThisCollisions); + (links.potentialNewTargetCollisions || (links.potentialNewTargetCollisions = [])).push(...potentialNewTargetCollisions); + (links.potentialWeakMapSetCollisions || (links.potentialWeakMapSetCollisions = [])).push(...potentialWeakMapSetCollisions); + (links.potentialReflectCollisions || (links.potentialReflectCollisions = [])).push(...potentialReflectCollisions); + (links.potentialUnusedRenamedBindingElementsInTypes || (links.potentialUnusedRenamedBindingElementsInTypes = [])).push( + ...potentialUnusedRenamedBindingElementsInTypes, + ); + + links.flags |= NodeCheckFlags.PartiallyTypeChecked; + for (const node of nodes) { + const nodeLinks = getNodeLinks(node); + nodeLinks.flags |= NodeCheckFlags.PartiallyTypeChecked; + } + } + } + + function getDiagnostics(sourceFile: SourceFile, ct: CancellationToken, nodesToCheck?: Node[]): 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, nodesToCheck); + } + finally { + cancellationToken = undefined; + } + } + + function ensurePendingDiagnosticWorkComplete() { + // Invoke any existing lazy diagnostics to add them, clear the backlog of diagnostics + for (const cb of deferredDiagnosticsCallbacks) { + cb(); + } + deferredDiagnosticsCallbacks = []; + } + + function checkSourceFileWithEagerDiagnostics(sourceFile: SourceFile, nodesToCheck?: Node[]) { + 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, nodesToCheck); + addLazyDiagnostic = oldAddLazyDiagnostics; + } + + function getDiagnosticsWorker(sourceFile: SourceFile, nodesToCheck: Node[] | undefined): 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; + + checkSourceFileWithEagerDiagnostics(sourceFile, nodesToCheck); + const semanticDiagnostics = diagnostics.getDiagnostics(sourceFile.fileName); + if (nodesToCheck) { + // No need to get global diagnostics. + return semanticDiagnostics; + } + 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); + } + + return semanticDiagnostics; + } + + // Global diagnostics are always added when a file is not provided to + // getDiagnostics + forEach(host.getSourceFiles(), file => checkSourceFileWithEagerDiagnostics(file)); + return diagnostics.getDiagnostics(); + } + + function getGlobalDiagnostics(): Diagnostic[] { + ensurePendingDiagnosticWorkComplete(); + return diagnostics.getGlobalDiagnostics(); + } + + // Language service support + + 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 []; + } + + const symbols = createSymbolTable(); + let isStaticSymbol = false; + + populateSymbols(); + + symbols.delete(InternalSymbolName.This); // Not a symbol, a keyword + return symbolsToArray(symbols); + + function populateSymbols() { + while (location) { + if (canHaveLocals(location) && location.locals && !isGlobalSourceFile(location)) { + copySymbols(location.locals, meaning); + } + + switch (location.kind) { + case SyntaxKind.SourceFile: + if (!isExternalModule(location as SourceFile)) break; + // falls through + case SyntaxKind.ModuleDeclaration: + copyLocallyVisibleExportSymbols(getSymbolOfDeclaration(location as ModuleDeclaration | SourceFile).exports!, meaning & SymbolFlags.ModuleMember); + break; + case SyntaxKind.EnumDeclaration: + copySymbols(getSymbolOfDeclaration(location as EnumDeclaration).exports!, meaning & SymbolFlags.EnumMember); + break; + case SyntaxKind.ClassExpression: + const className = (location as ClassExpression).name; + if (className) { + copySymbol((location as ClassExpression).symbol, 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(getSymbolOfDeclaration(location as ClassDeclaration | InterfaceDeclaration)), meaning & SymbolFlags.Type); + } + break; + case SyntaxKind.FunctionExpression: + const funcName = (location as FunctionExpression).name; + if (funcName) { + copySymbol((location as FunctionExpression).symbol, meaning); + } + break; + } + + if (introducesArgumentsExoticObject(location)) { + copySymbol(argumentsSymbol, meaning); + } + + isStaticSymbol = isStatic(location); + location = location.parent; + } + + 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 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) && symbol.escapedName !== InternalSymbolName.Default) { + copySymbol(symbol, meaning); + } + }); + } + } + } + + 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; + } + + return node.parent.kind === SyntaxKind.TypeReference; + } + + function isInNameOfExpressionWithTypeArguments(node: Node): boolean { + while (node.parent.kind === SyntaxKind.PropertyAccessExpression) { + node = node.parent; + } + + return node.parent.kind === SyntaxKind.ExpressionWithTypeArguments; + } + + function forEachEnclosingClass(node: Node, callback: (node: ClassLikeDeclaration) => T | undefined): T | undefined { + let result: T | undefined; + let containingClass = getContainingClass(node); + while (containingClass) { + if (result = callback(containingClass)) break; + containingClass = getContainingClass(containingClass); + } + + return result; + } + + 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 false; + }); + } + + function isNodeWithinClass(node: Node, classDeclaration: ClassLikeDeclaration) { + return !!forEachEnclosingClass(node, n => n === classDeclaration); + } + + function getLeftSideOfImportEqualsOrExportAssignment(nodeOnRightSide: EntityName): ImportEqualsDeclaration | ExportAssignment | undefined { + while (nodeOnRightSide.parent.kind === SyntaxKind.QualifiedName) { + nodeOnRightSide = nodeOnRightSide.parent as QualifiedName; + } + + if (nodeOnRightSide.parent.kind === SyntaxKind.ImportEqualsDeclaration) { + return (nodeOnRightSide.parent as ImportEqualsDeclaration).moduleReference === nodeOnRightSide ? nodeOnRightSide.parent as ImportEqualsDeclaration : undefined; + } + + if (nodeOnRightSide.parent.kind === SyntaxKind.ExportAssignment) { + return (nodeOnRightSide.parent as ExportAssignment).expression === nodeOnRightSide as Node ? nodeOnRightSide.parent as ExportAssignment : undefined; + } + + return undefined; + } + + function isInRightSideOfImportOrExportAssignment(node: EntityName) { + return getLeftSideOfImportEqualsOrExportAssignment(node) !== 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.Property: + if (isPropertyAccessExpression(entityName.parent) && getLeftmostAccessExpression(entityName.parent) === entityName) { + return undefined; + } + // falls through + case AssignmentDeclarationKind.ThisProperty: + case AssignmentDeclarationKind.ModuleExports: + return getSymbolOfDeclaration(entityName.parent.parent as BinaryExpression); + } + } + + 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; + } + return undefined; + } + + function isThisPropertyAndThisTyped(node: PropertyAccessExpression) { + if (node.expression.kind === SyntaxKind.ThisKeyword) { + const container = getThisContainer(node, /*includeArrowFunctions*/ false, /*includeClassComputedPropertyName*/ false); + if (isFunctionLike(container)) { + const containingLiteral = getContainingObjectLiteral(container); + if (containingLiteral) { + const contextualType = getApparentTypeOfContextualType(containingLiteral, /*contextFlags*/ undefined); + const type = getThisTypeOfObjectLiteralFromContextualType(containingLiteral, contextualType); + return type && !isTypeAny(type); + } + } + } + } + + function getSymbolOfNameOrPropertyAccessExpression(name: EntityName | PrivateIdentifier | PropertyAccessExpression | JSDocMemberName): Symbol | undefined { + if (isDeclarationName(name)) { + return getSymbolOfNode(name.parent); + } + + 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) && !isThisPropertyAndThisTyped(name.parent as PropertyAccessExpression)) { + const specialPropertyAssignmentSymbol = getSpecialPropertyAssignmentSymbolFromEntityName(name); + if (specialPropertyAssignmentSymbol) { + return specialPropertyAssignmentSymbol; + } + } + } + + 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 (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 (isInNameOfExpressionWithTypeArguments(name)) { + let meaning = SymbolFlags.None; + if (name.parent.kind === SyntaxKind.ExpressionWithTypeArguments) { + // An 'ExpressionWithTypeArguments' may appear in type space (interface Foo extends Bar), + // value space (return foo), or both(class Foo extends Bar); ensure the meaning matches. + meaning = isPartOfTypeNode(name) ? SymbolFlags.Type : SymbolFlags.Value; + + // In a class 'extends' clause we are also looking for a value. + if (isExpressionWithTypeArgumentsInClassExtendsClause(name.parent)) { + meaning |= SymbolFlags.Value; + } + } + else { + meaning = SymbolFlags.Namespace; + } + + meaning |= SymbolFlags.Alias; + const entityNameSymbol = isEntityNameExpression(name) ? resolveEntityName(name, meaning, /*ignoreErrors*/ true) : undefined; + if (entityNameSymbol) { + return entityNameSymbol; + } + } + + 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; + } + + 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) && isJsxIntrinsicTagName(name)) { + const symbol = getIntrinsicTagSymbol(name.parent as JsxOpeningLikeElement); + return symbol === unknownSymbol ? undefined : symbol; + } + const result = resolveEntityName(name, meaning, /*ignoreErrors*/ true, /*dontResolveAlias*/ true, getHostSignatureFromJSDoc(name)); + if (!result && isJSDoc) { + const container = findAncestor(name, or(isClassLike, isInterfaceDeclaration)); + if (container) { + return resolveJSDocMemberName(name, /*ignoreErrors*/ true, getSymbolOfDeclaration(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) { + links.resolvedSymbol = getApplicableIndexSymbol(checkExpressionCached(name.expression), getLiteralTypeFromPropertyName(name.name)); + } + } + else { + checkQualifiedName(name, CheckMode.Normal); + } + if (!links.resolvedSymbol && isJSDoc && isQualifiedName(name)) { + return resolveJSDocMemberName(name); + } + return links.resolvedSymbol; + } + else if (isJSDocMemberName(name)) { + return resolveJSDocMemberName(name); + } + } + 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); + } + + return undefined; + } + + function getApplicableIndexSymbol(type: Type, keyType: Type) { + const infos = getApplicableIndexInfos(type, keyType); + if (infos.length && (type as ObjectType).members) { + const symbol = getIndexSymbolFromSymbolTable(resolveStructuredTypeMembers(type as ObjectType).members); + if (infos === getIndexInfosOfType(type)) { + return 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(); + } + if (symbolLinks.filteredIndexSymbolCache.has(nodeListId)) { + return symbolLinks.filteredIndexSymbolCache.get(nodeListId)!; + } + else { + const copy = createSymbol(SymbolFlags.Signature, InternalSymbolName.Index); + copy.declarations = mapDefined(infos, i => i.declaration); + copy.parent = type.aliasSymbol ? type.aliasSymbol : type.symbol ? type.symbol : getSymbolAtLocation(copy.declarations[0].parent); + symbolLinks.filteredIndexSymbolCache.set(nodeListId, copy); + return copy; + } + } + } + } + + /** + * 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; + } + } + 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 (isSourceFile(node)) { + return isExternalModule(node) ? 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 (isDeclarationNameOrImportPropertyName(node)) { + // This is a declaration, call getSymbolOfNode + const parentSymbol = getSymbolOfDeclaration(parent as Declaration); + return isImportOrExportSpecifier(node.parent) && node.parent.propertyName === node + ? getImmediateAliasedSymbol(parentSymbol) + : parentSymbol; + } + else if (isLiteralComputedPropertyDeclarationName(node)) { + return getSymbolOfDeclaration(parent.parent as Declaration); + } + + 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 (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; + } + // 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 + + case SyntaxKind.ThisKeyword: + const container = getThisContainer(node, /*includeArrowFunctions*/ false, /*includeClassComputedPropertyName*/ false); + if (isFunctionLike(container)) { + const sig = getSignatureFromDeclaration(container); + if (sig.thisParameter) { + return sig.thisParameter; + } + } + if (isInExpressionContext(node)) { + return checkExpression(node as Expression).symbol; + } + // falls through + + case SyntaxKind.ThisType: + return getTypeFromThisTypeNode(node as ThisExpression | ThisTypeNode).symbol; + + case SyntaxKind.SuperKeyword: + return checkExpression(node as Expression).symbol; + + 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) && isJSDocImportTag(node.parent) && node.parent.moduleSpecifier === node) || + ((isInJSFile(node) && isRequireCall(node.parent, /*requireStringLiteralLikeArgument*/ 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 getSymbolOfDeclaration(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.InstanceOfKeyword: + if (isBinaryExpression(node.parent)) { + const type = getTypeOfExpression(node.parent.right); + const hasInstanceMethodType = getSymbolHasInstanceMethodOfObjectType(type); + return hasInstanceMethodType?.symbol ?? type.symbol; + } + return undefined; + case SyntaxKind.MetaProperty: + return checkExpression(node as Expression).symbol; + case SyntaxKind.JsxNamespacedName: + if (isJSXTagName(node) && isJsxIntrinsicTagName(node)) { + const symbol = getIntrinsicTagSymbol(node.parent as JsxOpeningLikeElement); + return symbol === unknownSymbol ? undefined : symbol; + } + // falls through + + 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 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)) { + const name = node.propertyName || node.name; + return node.parent.parent.moduleSpecifier ? + getExternalModuleMember(node.parent.parent, node) : + name.kind === SyntaxKind.StringLiteral ? undefined : // Skip for invalid syntax like this: export { "x" } + resolveEntityName(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; + } + + const classDecl = tryGetClassImplementingOrExtendingExpressionWithTypeArguments(node); + const classType = classDecl && getDeclaredTypeOfClassOrInterface(getSymbolOfDeclaration(classDecl.class)); + if (isPartOfTypeNode(node)) { + const typeFromTypeNode = getTypeFromTypeNode(node as TypeNode); + return classType ? getTypeWithThisArgument(typeFromTypeNode, classType.thisType) : typeFromTypeNode; + } + + if (isExpressionNode(node)) { + return getRegularTypeOfExpression(node as Expression); + } + + 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 (isTypeDeclaration(node)) { + // In this case, we call getSymbolOfNode instead of getSymbolAtLocation because it is a declaration + const symbol = getSymbolOfDeclaration(node); + return getDeclaredTypeOfSymbol(symbol); + } + + if (isTypeDeclarationName(node)) { + const symbol = getSymbolAtLocation(node); + return symbol ? getDeclaredTypeOfSymbol(symbol) : errorType; + } + + if (isBindingElement(node)) { + return getTypeForVariableLikeDeclaration(node, /*includeOptionality*/ true, CheckMode.Normal) || errorType; + } + + if (isDeclaration(node)) { + // In this case, we call getSymbolOfNode instead of getSymbolAtLocation because it is a declaration + const symbol = getSymbolOfDeclaration(node); + return symbol ? getTypeOfSymbol(symbol) : errorType; + } + + if (isDeclarationNameOrImportPropertyName(node)) { + const symbol = getSymbolAtLocation(node); + if (symbol) { + return getTypeOfSymbol(symbol); + } + return errorType; + } + + if (isBindingPattern(node)) { + return getTypeForVariableLikeDeclaration(node.parent, /*includeOptionality*/ true, CheckMode.Normal) || errorType; + } + + if (isInRightSideOfImportOrExportAssignment(node as Identifier)) { + const symbol = getSymbolAtLocation(node); + if (symbol) { + const declaredType = getDeclaredTypeOfSymbol(symbol); + return !isErrorType(declaredType) ? declaredType : getTypeOfSymbol(symbol); + } + } + + if (isMetaProperty(node.parent) && node.parent.keywordToken === node.kind) { + return checkMetaPropertyKeyword(node.parent); + } + + if (isImportAttributes(node)) { + return getGlobalImportAttributesType(/*reportErrors*/ false); + } + + 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) { + // } + 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); + } + + function getRegularTypeOfExpression(expr: Expression): Type { + if (isRightSideOfQualifiedNameOrPropertyAccess(expr)) { + expr = expr.parent as Expression; + } + return getRegularTypeOfLiteralType(getTypeOfExpression(expr)); + } + + /** + * 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 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."); + } + } + + // 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 typeHasCallOrConstructSignatures(type: Type): boolean { + return getSignaturesOfType(type, SignatureKind.Call).length !== 0 || getSignaturesOfType(type, SignatureKind.Construct).length !== 0; + } + + 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)); + } + else if (symbol.flags & SymbolFlags.Transient) { + const { links: { leftSpread, rightSpread, syntheticOrigin } } = symbol as TransientSymbol; + return leftSpread ? [leftSpread, rightSpread!] + : syntheticOrigin ? [syntheticOrigin] + : singleElementArray(tryGetTarget(symbol)); + } + 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 + + 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 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) && getSymbolOfDeclaration(n) === parentSymbol); + } + } + } + } + + // 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 { + const specifier = getIdentifierGeneratedImportReference(nodeIn); + if (specifier) { + return specifier; + } + 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); + } + } + + 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)) { + if (resolveName(container.parent, symbol.escapedName, SymbolFlags.Value, /*nameNotFoundMessage*/ undefined, /*isUse*/ false)) { + // redeclaration - always should be renamed + links.isDeclarationWithCollidingName = true; + } + else if (hasNodeCheckFlag(symbol.valueDeclaration, 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 = hasNodeCheckFlag(symbol.valueDeclaration, 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!; + } + 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; + } + + // 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 = getSymbolOfDeclaration(node); + if (symbol) { + return isSymbolOfDeclarationWithCollidingName(symbol); + } + } + + return false; + } + + function isValueAliasDeclaration(node: Node): boolean { + Debug.assert(canCollectSymbolAliasAccessabilityData); + switch (node.kind) { + case SyntaxKind.ImportEqualsDeclaration: + return isAliasResolvedToValue(getSymbolOfDeclaration(node as ImportEqualsDeclaration)); + case SyntaxKind.ImportClause: + case SyntaxKind.NamespaceImport: + case SyntaxKind.ImportSpecifier: + case SyntaxKind.ExportSpecifier: + const symbol = getSymbolOfDeclaration(node as ImportClause | NamespaceImport | ImportSpecifier | ExportSpecifier); + return !!symbol && isAliasResolvedToValue(symbol, /*excludeTypeOnlyValues*/ true); + 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(getSymbolOfDeclaration(node as ExportAssignment), /*excludeTypeOnlyValues*/ true) : + 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(getSymbolOfDeclaration(node)); + return isValue && node.moduleReference && !nodeIsMissing(node.moduleReference); + } + + function isAliasResolvedToValue(symbol: Symbol | undefined, excludeTypeOnlyValues?: boolean): boolean { + if (!symbol) { + return false; + } + const container = getSourceFileOfNode(symbol.valueDeclaration); + const fileSymbol = container && getSymbolOfDeclaration(container); + // Ensures cjs export assignment is setup, since this symbol may point at, and merge with, the file itself. + // If we don't, the merge may not have yet occured, and the flags check below will be missing flags that + // are added as a result of the merge. + void resolveExternalModuleSymbol(fileSymbol); + const target = getExportSymbolOfValueSymbolIfExported(resolveAlias(symbol)); + if (target === unknownSymbol) { + return !excludeTypeOnlyValues || !getTypeOnlyAliasDeclaration(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 !!(getSymbolFlags(symbol, excludeTypeOnlyValues, /*excludeLocalMeanings*/ true) & SymbolFlags.Value) && + (shouldPreserveConstEnums(compilerOptions) || !isConstEnumOrConstEnumOnlyModule(target)); + } + + function isConstEnumOrConstEnumOnlyModule(s: Symbol): boolean { + return isConstEnumSymbol(s) || !!s.constEnumOnlyModule; + } + + function isReferencedAliasDeclaration(node: Node, checkChildren?: boolean): boolean { + Debug.assert(canCollectSymbolAliasAccessabilityData); + if (isAliasSymbolDeclaration(node)) { + const symbol = getSymbolOfDeclaration(node as Declaration); + const links = symbol && getSymbolLinks(symbol); + if (links?.referenced) { + return true; + } + const target = getSymbolLinks(symbol).aliasTarget; + if ( + target && getEffectiveModifierFlags(node) & ModifierFlags.Export && + getSymbolFlags(target) & SymbolFlags.Value && + (shouldPreserveConstEnums(compilerOptions) || !isConstEnumOrConstEnumOnlyModule(target)) + ) { + // An `export import ... =` of a value symbol is always considered referenced + return true; + } + } + + if (checkChildren) { + return !!forEachChild(node, node => isReferencedAliasDeclaration(node, checkChildren)); + } + 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 = getSymbolOfDeclaration(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 declaredParameterTypeContainsUndefined(parameter: ParameterDeclaration | JSDocParameterTag) { + const typeNode = getNonlocalEffectiveTypeAnnotationNode(parameter); + if (!typeNode) return false; + const type = getTypeFromTypeNode(typeNode); + return containsUndefinedType(type); + } + function requiresAddingImplicitUndefined(parameter: ParameterDeclaration | JSDocParameterTag) { + return (isRequiredInitializedParameter(parameter) || isOptionalUninitializedParameterProperty(parameter)) && !declaredParameterTypeContainsUndefined(parameter); + } + + function isRequiredInitializedParameter(parameter: ParameterDeclaration | JSDocParameterTag): boolean { + return !!strictNullChecks && + !isOptionalParameter(parameter) && + !isJSDocParameterTag(parameter) && + !!parameter.initializer && + !hasSyntacticModifier(parameter, ModifierFlags.ParameterPropertyModifier); + } + + function isOptionalUninitializedParameterProperty(parameter: ParameterDeclaration | JSDocParameterTag) { + return strictNullChecks && + isOptionalParameter(parameter) && + (isJSDocParameterTag(parameter) || !parameter.initializer) && + hasSyntacticModifier(parameter, ModifierFlags.ParameterPropertyModifier); + } + + function isExpandoFunctionDeclaration(node: Declaration): boolean { + const declaration = getParseTreeNode(node, (n): n is FunctionDeclaration | VariableDeclaration => isFunctionDeclaration(n) || isVariableDeclaration(n)); + if (!declaration) { + return false; + } + let symbol: Symbol | undefined; + if (isVariableDeclaration(declaration)) { + if (declaration.type || (!isInJSFile(declaration) && !isVarConstLike(declaration))) { + return false; + } + const initializer = getDeclaredExpandoInitializer(declaration); + if (!initializer || !canHaveSymbol(initializer)) { + return false; + } + symbol = getSymbolOfDeclaration(initializer); + } + else { + symbol = getSymbolOfDeclaration(declaration); + } + if (!symbol || !(symbol.flags & SymbolFlags.Function | SymbolFlags.Variable)) { + return false; + } + return !!forEachEntry(getExportsOfSymbol(symbol), p => p.flags & SymbolFlags.Value && isExpandoPropertyDeclaration(p.valueDeclaration)); + } + + function getPropertiesOfContainerFunction(node: Declaration): Symbol[] { + const declaration = getParseTreeNode(node, isFunctionDeclaration); + if (!declaration) { + return emptyArray; + } + const symbol = getSymbolOfDeclaration(declaration); + return symbol && getPropertiesOfType(getTypeOfSymbol(symbol)) || emptyArray; + } + + function getNodeCheckFlags(node: Node): NodeCheckFlags { + const nodeId = node.id || 0; + if (nodeId < 0 || nodeId >= nodeLinks.length) return 0; + return nodeLinks[nodeId]?.flags || 0; + } + + function hasNodeCheckFlag(node: Node, flag: LazyNodeCheckFlags) { + calculateNodeCheckFlagWorker(node, flag); + return !!(getNodeCheckFlags(node) & flag); + } + + function calculateNodeCheckFlagWorker(node: Node, flag: LazyNodeCheckFlags) { + if (!compilerOptions.noCheck && canIncludeBindAndCheckDiagnostics(getSourceFileOfNode(node), compilerOptions)) { + // Unless noCheck is passed, assume calculation of node check flags has been done eagerly. + // This saves needing to mark up where in the eager traversal certain results are "done", + // just to reconcile the eager and lazy results. This wouldn't be hard if an eager typecheck + // was actually an in-order traversal, but it isn't - some nodes are deferred, and so don't + // have these node check flags calculated until that deferral is completed. As an example, + // in concept, we could consider a class that we've called `checkSourceElement` on as having had + // these flags calculated, but since the method bodies are deferred, we actually can't set the + // flags as having been calculated until that deferral is completed. + // The downside to this either/or approach to eager or lazy calculation is that we can't combine + // a partial eager traversal and lazy calculation for the missing bits, and there's a bit of + // overlap in functionality. This isn't a huge loss for any usecases today, but would be nice + // alongside language service partial file checking and editor-triggered emit. + return; + } + const links = getNodeLinks(node); + if (links.calculatedFlags & flag) { + return; + } + // This is only the set of `NodeCheckFlags` our emitter actually looks for, not all of them + switch (flag) { + case NodeCheckFlags.SuperInstance: + case NodeCheckFlags.SuperStatic: + return checkSingleSuperExpression(node); + case NodeCheckFlags.MethodWithSuperPropertyAccessInAsync: + case NodeCheckFlags.MethodWithSuperPropertyAssignmentInAsync: + case NodeCheckFlags.ContainsSuperPropertyInStaticInitializer: + return checkChildSuperExpressions(node); + case NodeCheckFlags.CaptureArguments: + case NodeCheckFlags.ContainsCapturedBlockScopeBinding: + case NodeCheckFlags.NeedsLoopOutParameter: + case NodeCheckFlags.ContainsConstructorReference: + return checkChildIdentifiers(node); + case NodeCheckFlags.ConstructorReference: + return checkSingleIdentifier(node); + case NodeCheckFlags.LoopWithCapturedBlockScopedBinding: + case NodeCheckFlags.BlockScopedBindingInLoop: + case NodeCheckFlags.CapturedBlockScopedBinding: + return checkContainingBlockScopeBindingUses(node); + default: + return Debug.assertNever(flag, `Unhandled node check flag calculation: ${Debug.formatNodeCheckFlags(flag)}`); + } + + function forEachNodeRecursively(root: Node, cb: (node: Node, parent: Node) => T | "skip" | undefined): T | undefined { + const rootResult = cb(root, root.parent); + if (rootResult === "skip") return undefined; + if (rootResult) return rootResult; + return forEachChildRecursively(root, cb); + } + + function checkSuperExpressions(node: Node) { + const links = getNodeLinks(node); + if (links.calculatedFlags & flag) return "skip"; + links.calculatedFlags |= NodeCheckFlags.MethodWithSuperPropertyAccessInAsync | NodeCheckFlags.MethodWithSuperPropertyAssignmentInAsync | NodeCheckFlags.ContainsSuperPropertyInStaticInitializer; + checkSingleSuperExpression(node); + return undefined; + } + + function checkChildSuperExpressions(node: Node) { + forEachNodeRecursively(node, checkSuperExpressions); + } + + function checkSingleSuperExpression(node: Node) { + const nodeLinks = getNodeLinks(node); // This is called on sub-nodes of the original input, make sure we set `calculatedFlags` on the correct node + nodeLinks.calculatedFlags |= NodeCheckFlags.SuperInstance | NodeCheckFlags.SuperStatic; // Yes, we set this on non-applicable nodes, so we can entirely skip the traversal on future calls + if (node.kind === SyntaxKind.SuperKeyword) { + checkSuperExpression(node); + } + } + + function checkIdentifiers(node: Node) { + const links = getNodeLinks(node); + if (links.calculatedFlags & flag) return "skip"; + links.calculatedFlags |= NodeCheckFlags.CaptureArguments | NodeCheckFlags.ContainsCapturedBlockScopeBinding | NodeCheckFlags.NeedsLoopOutParameter | NodeCheckFlags.ContainsConstructorReference; + checkSingleIdentifier(node); + return undefined; + } + + function checkChildIdentifiers(node: Node) { + forEachNodeRecursively(node, checkIdentifiers); + } + + function checkSingleIdentifier(node: Node) { + const nodeLinks = getNodeLinks(node); + nodeLinks.calculatedFlags |= NodeCheckFlags.ConstructorReference | NodeCheckFlags.CapturedBlockScopedBinding | NodeCheckFlags.BlockScopedBindingInLoop; + if (isIdentifier(node) && isExpressionNode(node) && !(isPropertyAccessExpression(node.parent) && node.parent.name === node)) { + const s = getSymbolAtLocation(node, /*ignoreErrors*/ true); + if (s && s !== unknownSymbol) { + checkIdentifierCalculateNodeCheckFlags(node, s); + } + } + } + + function checkBlockScopeBindings(node: Node) { + const links = getNodeLinks(node); + if (links.calculatedFlags & flag) return "skip"; + links.calculatedFlags |= NodeCheckFlags.LoopWithCapturedBlockScopedBinding | NodeCheckFlags.BlockScopedBindingInLoop | NodeCheckFlags.CapturedBlockScopedBinding; + checkSingleBlockScopeBinding(node); + return undefined; + } + + function checkContainingBlockScopeBindingUses(node: Node) { + const scope = getEnclosingBlockScopeContainer(isDeclarationName(node) ? node.parent : node); + forEachNodeRecursively(scope, checkBlockScopeBindings); + } + + function checkSingleBlockScopeBinding(node: Node) { + checkSingleIdentifier(node); + if (isComputedPropertyName(node)) { + checkComputedPropertyName(node); + } + if (isPrivateIdentifier(node) && isClassElement(node.parent)) { + setNodeLinksForPrivateIdentifierScope(node.parent as PropertyDeclaration | PropertySignature | MethodDeclaration | MethodSignature | AccessorDeclaration); + } + } + } + + function getEnumMemberValue(node: EnumMember): EvaluatorResult { + computeEnumMemberValues(node.parent); + return getNodeLinks(node).enumMemberValue ?? evaluatorResult(/*value*/ undefined); + } + + 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 getConstantValue(node: EnumMember | AccessExpression): string | number | undefined { + if (node.kind === SyntaxKind.EnumMember) { + return getEnumMemberValue(node).value; + } + + if (!getNodeLinks(node).resolvedSymbol) { + void checkExpressionCached(node); // ensure cached resolved symbol is set + } + const symbol = getNodeLinks(node).resolvedSymbol || (isEntityNameExpression(node) ? resolveEntityName(node, SymbolFlags.Value, /*ignoreErrors*/ true) : undefined); + 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).value; + } + } + + return undefined; + } + + function isFunctionType(type: Type): boolean { + return !!(type.flags & TypeFlags.Object) && getSignaturesOfType(type, SignatureKind.Call).length > 0; + } + + 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; + + if (location) { + location = getParseTreeNode(location); + if (!location) return TypeReferenceSerializationKind.Unknown; + } + + // 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 resolvedValueSymbol = valueSymbol && valueSymbol.flags & SymbolFlags.Alias ? resolveAlias(valueSymbol) : valueSymbol; + isTypeOnly ||= !!(valueSymbol && getTypeOnlyAliasDeclaration(valueSymbol, SymbolFlags.Value)); + + // 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*/ true, location); + const resolvedTypeSymbol = typeSymbol && typeSymbol.flags & SymbolFlags.Alias ? resolveAlias(typeSymbol) : typeSymbol; + + // In case the value symbol can't be resolved (e.g. because of missing declarations), use type symbol for reachability check. + if (!valueSymbol) { + isTypeOnly ||= !!(typeSymbol && getTypeOnlyAliasDeclaration(typeSymbol, SymbolFlags.Type)); + } + + if (resolvedValueSymbol && resolvedValueSymbol === resolvedTypeSymbol) { + const globalPromiseSymbol = getGlobalPromiseConstructorSymbol(/*reportErrors*/ false); + if (globalPromiseSymbol && resolvedValueSymbol === globalPromiseSymbol) { + return TypeReferenceSerializationKind.Promise; + } + + const constructorType = getTypeOfSymbol(resolvedValueSymbol); + if (constructorType && isConstructorType(constructorType)) { + return isTypeOnly ? TypeReferenceSerializationKind.TypeWithCallSignature : TypeReferenceSerializationKind.TypeWithConstructSignatureAndValue; + } + } + + // We might not be able to resolve type symbol so use unknown type in that case (eg error case) + if (!resolvedTypeSymbol) { + return isTypeOnly ? TypeReferenceSerializationKind.ObjectType : TypeReferenceSerializationKind.Unknown; + } + const type = getDeclaredTypeOfSymbol(resolvedTypeSymbol); + 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 createTypeOfDeclaration(declarationIn: AccessorDeclaration | VariableLikeDeclaration | PropertyAccessExpression, enclosingDeclaration: Node, flags: NodeBuilderFlags, tracker: SymbolTracker) { + 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 = getSymbolOfDeclaration(declaration); + const type = symbol && !(symbol.flags & (SymbolFlags.TypeLiteral | SymbolFlags.Signature)) + ? getWidenedLiteralType(getTypeOfSymbol(symbol)) + : errorType; + + return nodeBuilder.serializeTypeForDeclaration(declaration, type, symbol, enclosingDeclaration, flags | NodeBuilderFlags.MultilineObjectLiterals, tracker); + } + + type DeclarationWithPotentialInnerNodeReuse = + | SignatureDeclaration + | JSDocSignature + | AccessorDeclaration + | VariableLikeDeclaration + | PropertyAccessExpression + | ExportAssignment; + + function isDeclarationWithPossibleInnerTypeNodeReuse(declaration: Declaration): declaration is DeclarationWithPotentialInnerNodeReuse { + return isFunctionLike(declaration) || isExportAssignment(declaration) || isVariableLike(declaration); + } + + function getAllAccessorDeclarationsForDeclaration(accessor: AccessorDeclaration): AllAccessorDeclarations { + accessor = getParseTreeNode(accessor, isGetOrSetAccessorDeclaration)!; // TODO: GH#18217 + const otherKind = accessor.kind === SyntaxKind.SetAccessor ? SyntaxKind.GetAccessor : SyntaxKind.SetAccessor; + const otherAccessor = getDeclarationOfKind(getSymbolOfDeclaration(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, + }; + } + + function getPossibleTypeNodeReuseExpression(declaration: DeclarationWithPotentialInnerNodeReuse) { + return isFunctionLike(declaration) && !isSetAccessor(declaration) + ? getSingleReturnExpression(declaration) + : isExportAssignment(declaration) + ? declaration.expression + : !!(declaration as HasInitializer).initializer + ? (declaration as HasInitializer & typeof declaration).initializer + : isParameter(declaration) && isSetAccessor(declaration.parent) + ? getSingleReturnExpression(getAllAccessorDeclarationsForDeclaration(declaration.parent).getAccessor) + : undefined; + } + + function getSingleReturnExpression(declaration: SignatureDeclaration | undefined): Expression | undefined { + let candidateExpr: Expression | undefined; + if (declaration && !nodeIsMissing((declaration as FunctionLikeDeclaration).body)) { + if (getFunctionFlags(declaration) & FunctionFlags.AsyncGenerator) return undefined; + const body = (declaration as FunctionLikeDeclaration).body; + if (body && isBlock(body)) { + forEachReturnStatement(body, s => { + if (!candidateExpr) { + candidateExpr = s.expression; + } + else { + candidateExpr = undefined; + return true; + } + }); + } + else { + candidateExpr = body; + } + } + return candidateExpr; + } + + function createReturnTypeOfSignatureDeclaration(signatureDeclarationIn: SignatureDeclaration, enclosingDeclaration: Node, flags: NodeBuilderFlags, tracker: SymbolTracker) { + const signatureDeclaration = getParseTreeNode(signatureDeclarationIn, isFunctionLike); + if (!signatureDeclaration) { + return factory.createToken(SyntaxKind.AnyKeyword) as KeywordTypeNode; + } + return nodeBuilder.serializeReturnTypeForSignature(getSignatureFromDeclaration(signatureDeclaration), 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 type = getWidenedType(getRegularTypeOfExpression(expr)); + return nodeBuilder.expressionOrTypeToTypeNode(expr, type, /*addUndefined*/ undefined, enclosingDeclaration, flags | NodeBuilderFlags.MultilineObjectLiterals, tracker); + } + + function hasGlobalName(name: string): boolean { + return globals.has(escapeLeadingUnderscores(name)); + } + + function getReferencedValueSymbol(reference: Identifier, startInDeclarationContainer?: boolean): Symbol | undefined { + const resolvedSymbol = getNodeLinks(reference).resolvedSymbol; + if (resolvedSymbol) { + return resolvedSymbol; + } + + 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 resolveName(location, reference.escapedText, SymbolFlags.Value | SymbolFlags.ExportValue | SymbolFlags.Alias, /*nameNotFoundMessage*/ undefined, /*isUse*/ true); + } + + /** + * 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, + /*nameNotFoundMessage*/ undefined, + /*isUse*/ true, + /*excludeGlobals*/ 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; + } + } + } + + return undefined; + } + + function getReferencedValueDeclarations(referenceIn: Identifier): Declaration[] | undefined { + if (!isGeneratedIdentifier(referenceIn)) { + const reference = getParseTreeNode(referenceIn, isIdentifier); + if (reference) { + const symbol = getReferencedValueSymbol(reference); + if (symbol) { + return filter(getExportSymbolOfValueSymbolIfExported(symbol).declarations, declaration => { + switch (declaration.kind) { + case SyntaxKind.VariableDeclaration: + case SyntaxKind.Parameter: + case SyntaxKind.BindingElement: + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.PropertyAssignment: + case SyntaxKind.ShorthandPropertyAssignment: + case SyntaxKind.EnumMember: + case SyntaxKind.ObjectLiteralExpression: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.ModuleDeclaration: + return true; + } + return false; + }); + } + } + } + + return undefined; + } + + function isLiteralConstDeclaration(node: VariableDeclaration | PropertyDeclaration | PropertySignature | ParameterDeclaration): boolean { + if (isDeclarationReadonly(node) || isVariableDeclaration(node) && isVarConstLike(node)) { + return isFreshLiteralType(getTypeOfSymbol(getSymbolOfDeclaration(node))); + } + return false; + } + + function literalTypeToNode(type: FreshableType, enclosing: Node, tracker: SymbolTracker): Expression { + const enumResult = type.flags & TypeFlags.EnumLike ? 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 === "string" ? factory.createStringLiteral(literalValue) : + literalValue < 0 ? factory.createPrefixUnaryExpression(SyntaxKind.MinusToken, factory.createNumericLiteral(-literalValue)) : + factory.createNumericLiteral(literalValue); + } + + function createLiteralConstValue(node: VariableDeclaration | PropertyDeclaration | PropertySignature | ParameterDeclaration, tracker: SymbolTracker) { + const type = getTypeOfSymbol(getSymbolOfDeclaration(node)); + return literalTypeToNode(type as FreshableType, node, tracker); + } + + 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; + } + } + } + + if (compilerOptions.jsxFragmentFactory) { + return parseIsolatedEntityName(compilerOptions.jsxFragmentFactory, languageVersion); + } + } + + function getNonlocalEffectiveTypeAnnotationNode(node: Node) { + const direct = getEffectiveTypeAnnotationNode(node); + if (direct) { + return direct; + } + if (node.kind === SyntaxKind.Parameter && node.parent.kind === SyntaxKind.SetAccessor) { + const other = getAllAccessorDeclarationsForDeclaration(node.parent as SetAccessorDeclaration).getAccessor; + if (other) { + return getEffectiveReturnTypeNode(other); + } + } + return undefined; + } + + function getNonlocalEffectiveReturnTypeAnnotationNode(node: SignatureDeclaration | JSDocSignature) { + const direct = getEffectiveReturnTypeNode(node); + if (direct) { + return direct; + } + if (node.kind === SyntaxKind.GetAccessor) { + const other = getAllAccessorDeclarationsForDeclaration(node).setAccessor; + if (other) { + const param = getSetAccessorValueParameter(other); + if (param) { + return getEffectiveTypeAnnotationNode(param); + } + } + } + return undefined; + } + + function createResolver(): EmitResolver { + return { + getReferencedExportContainer, + getReferencedImportDeclaration, + getReferencedDeclarationWithCollidingName, + isDeclarationWithCollidingName, + isValueAliasDeclaration: nodeIn => { + const node = getParseTreeNode(nodeIn); + // Synthesized nodes are always treated like values. + return node && canCollectSymbolAliasAccessabilityData ? isValueAliasDeclaration(node) : true; + }, + hasGlobalName, + isReferencedAliasDeclaration: (nodeIn, checkChildren?) => { + const node = getParseTreeNode(nodeIn); + // Synthesized nodes are always treated as referenced. + return node && canCollectSymbolAliasAccessabilityData ? isReferencedAliasDeclaration(node, checkChildren) : true; + }, + hasNodeCheckFlag: (nodeIn, flag) => { + const node = getParseTreeNode(nodeIn); + if (!node) return false; + return hasNodeCheckFlag(node, flag); + }, + isTopLevelValueImportEqualsWithEntityName, + isDeclarationVisible, + isImplementationOfOverload, + requiresAddingImplicitUndefined, + isExpandoFunctionDeclaration, + getPropertiesOfContainerFunction, + createTypeOfDeclaration, + createReturnTypeOfSignatureDeclaration, + createTypeOfExpression, + createLiteralConstValue, + isSymbolAccessible, + isEntityNameVisible, + getConstantValue: nodeIn => { + const node = getParseTreeNode(nodeIn, canHaveConstantValue); + return node ? getConstantValue(node) : undefined; + }, + getEnumMemberValue: nodeIn => { + const node = getParseTreeNode(nodeIn, isEnumMember); + return node ? getEnumMemberValue(node) : undefined; + }, + collectLinkedAliases, + markLinkedReferences: nodeIn => { + const node = getParseTreeNode(nodeIn); + return node && markLinkedReferences(node, ReferenceHint.Unspecified); + }, + getReferencedValueDeclaration, + getReferencedValueDeclarations, + getTypeReferenceSerializationKind, + isOptionalParameter, + isArgumentsLocalBinding, + getExternalModuleFileFromDeclaration: nodeIn => { + const node = getParseTreeNode(nodeIn, hasPossibleExternalModuleReference); + return node && getExternalModuleFileFromDeclaration(node); + }, + isLiteralConstDeclaration, + isLateBound: (nodeIn: Declaration): nodeIn is LateBoundDeclaration => { + const node = getParseTreeNode(nodeIn, isDeclaration); + const symbol = node && getSymbolOfDeclaration(node); + return !!(symbol && getCheckFlags(symbol) & CheckFlags.Late); + }, + getJsxFactoryEntity, + getJsxFragmentFactoryEntity, + 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) => { + const n = getParseTreeNode(node) as SourceFile; + Debug.assert(n && n.kind === SyntaxKind.SourceFile, "Non-sourcefile node passed into getDeclarationsForSourceFile"); + const sym = getSymbolOfDeclaration(node); + if (!sym) { + return !node.locals ? [] : nodeBuilder.symbolTableToDeclarationStatements(node.locals, node, flags, tracker); + } + resolveExternalModuleSymbol(sym); // ensures cjs export assignment is setup + return !sym.exports ? [] : nodeBuilder.symbolTableToDeclarationStatements(sym.exports, node, flags, tracker); + }, + isImportRequiredByAugmentation, + isDefinitelyReferenceToGlobalSymbolObject, + }; + + 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 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; + } + 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(); + + // 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`. + 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); + } + }); + } + } + + // 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); + } + } + } + + addUndefinedToGlobalsOrErrorOnRedeclaration(); + + 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); + + 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(/*symbol*/ 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; + + 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 (compilerOptions.importHelpers) { + const sourceFile = getSourceFileOfNode(location); + if (isEffectiveExternalModule(sourceFile, compilerOptions) && !(location.flags & NodeFlags.Ambient)) { + const helpersModule = resolveHelpersModule(sourceFile, location); + if (helpersModule !== unknownSymbol) { + const links = getSymbolLinks(helpersModule); + links.requestedExternalEmitHelpers ??= 0 as ExternalEmitHelpers; + if ((links.requestedExternalEmitHelpers & helpers) !== helpers) { + const uncheckedHelpers = helpers & ~links.requestedExternalEmitHelpers; + for (let helper = ExternalEmitHelpers.FirstEmitHelper; helper <= ExternalEmitHelpers.LastEmitHelper; helper <<= 1) { + if (uncheckedHelpers & helper) { + for (const name of getHelperNames(helper)) { + const symbol = resolveSymbol(getSymbol(getExportsOfModule(helpersModule), 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.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); + } + } + } + } + } + } + links.requestedExternalEmitHelpers |= helpers; + } + } + } + } + + function getHelperNames(helper: ExternalEmitHelpers) { + switch (helper) { + case ExternalEmitHelpers.Extends: + return ["__extends"]; + case ExternalEmitHelpers.Assign: + return ["__assign"]; + case ExternalEmitHelpers.Rest: + return ["__rest"]; + case ExternalEmitHelpers.Decorate: + return legacyDecorators ? ["__decorate"] : ["__esDecorate", "__runInitializers"]; + 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.SetFunctionName: + return ["__setFunctionName"]; + case ExternalEmitHelpers.PropKey: + return ["__propKey"]; + case ExternalEmitHelpers.AddDisposableResourceAndDisposeResources: + return ["__addDisposableResource", "__disposeResources"]; + default: + return Debug.fail("Unrecognized helper"); + } + } + + function resolveHelpersModule(file: SourceFile, errorNode: Node) { + const links = getNodeLinks(file); + if (!links.externalHelpersModule) { + links.externalHelpersModule = resolveExternalModule(getImportHelpersImportSpecifier(file), externalHelpersModuleNameText, Diagnostics.This_syntax_requires_an_imported_helper_but_module_0_cannot_be_found, errorNode) || unknownSymbol; + } + return links.externalHelpersModule; + } + + // GRAMMAR CHECKING + + function checkGrammarModifiers(node: HasModifiers | HasDecorators | HasIllegalModifiers | HasIllegalDecorators): boolean { + const quickResult = reportObviousDecoratorErrors(node) || reportObviousModifierErrors(node); + if (quickResult !== undefined) { + return quickResult; + } + + if (isParameter(node) && parameterIsThisKeyword(node)) { + return grammarErrorOnFirstToken(node, Diagnostics.Neither_decorators_nor_modifiers_may_be_applied_to_this_parameters); + } + + const blockScopeKind = isVariableStatement(node) ? node.declarationList.flags & NodeFlags.BlockScoped : NodeFlags.None; + let lastStatic: Node | undefined, lastDeclare: Node | undefined, lastAsync: Node | undefined, lastOverride: Node | undefined, firstDecorator: Decorator | undefined; + let flags = ModifierFlags.None; + let sawExportBeforeDecorators = false; + // We parse decorators and modifiers in four contiguous chunks: + // [...leadingDecorators, ...leadingModifiers, ...trailingDecorators, ...trailingModifiers]. It is an error to + // have both leading and trailing decorators. + let hasLeadingDecorators = false; + for (const modifier of (node as HasModifiers).modifiers!) { + if (isDecorator(modifier)) { + if (!nodeCanBeDecorated(legacyDecorators, 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); + } + } + else if (legacyDecorators && (node.kind === SyntaxKind.GetAccessor || node.kind === SyntaxKind.SetAccessor)) { + const accessors = getAllAccessorDeclarationsForDeclaration(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 we've seen any modifiers aside from `export`, `default`, or another decorator, then this is an invalid position + if (flags & ~(ModifierFlags.ExportDefault | ModifierFlags.Decorator)) { + return grammarErrorOnNode(modifier, Diagnostics.Decorators_are_not_valid_here); + } + + // if we've already seen leading decorators and leading modifiers, then trailing decorators are an invalid position + if (hasLeadingDecorators && flags & ModifierFlags.Modifier) { + Debug.assertIsDefined(firstDecorator); + const sourceFile = getSourceFileOfNode(modifier); + if (!hasParseDiagnostics(sourceFile)) { + addRelatedInfo( + error(modifier, Diagnostics.Decorators_may_not_appear_after_export_or_export_default_if_they_also_appear_before_export), + createDiagnosticForNode(firstDecorator, Diagnostics.Decorator_used_before_export_here), + ); + return true; + } + return false; + } + + flags |= ModifierFlags.Decorator; + + // if we have not yet seen a modifier, then these are leading decorators + if (!(flags & ModifierFlags.Modifier)) { + hasLeadingDecorators = true; + } + else if (flags & ModifierFlags.Export) { + sawExportBeforeDecorators = true; + } + + firstDecorator ??= modifier; + } + else { + 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)); + } + 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)); + } + } + if (modifier.kind !== SyntaxKind.InKeyword && modifier.kind !== SyntaxKind.OutKeyword && modifier.kind !== SyntaxKind.ConstKeyword) { + if (node.kind === SyntaxKind.TypeParameter) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_type_parameter, tokenToString(modifier.kind)); + } + } + switch (modifier.kind) { + case SyntaxKind.ConstKeyword: { + if (node.kind !== SyntaxKind.EnumDeclaration && node.kind !== SyntaxKind.TypeParameter) { + return grammarErrorOnNode(node, Diagnostics.A_class_member_cannot_have_the_0_keyword, tokenToString(SyntaxKind.ConstKeyword)); + } + const parent = (isJSDocTemplateTag(node.parent) && getEffectiveJSDocHost(node.parent)) || node.parent; + if ( + node.kind === SyntaxKind.TypeParameter && !(isFunctionLikeDeclaration(parent) || isClassLike(parent) || isFunctionTypeNode(parent) || + isConstructorTypeNode(parent) || isCallSignatureDeclaration(parent) || isConstructSignatureDeclaration(parent) || isMethodSignature(parent)) + ) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_can_only_appear_on_a_type_parameter_of_a_function_method_or_class, 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"); + } + 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; + + 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.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); + } + + flags |= ModifierFlags.Accessor; + 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); + } + else if (flags & ModifierFlags.Accessor) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "readonly", "accessor"); + } + flags |= ModifierFlags.Readonly; + break; + + case SyntaxKind.ExportKeyword: + if ( + compilerOptions.verbatimModuleSyntax && + !(node.flags & NodeFlags.Ambient) && + node.kind !== SyntaxKind.TypeAliasDeclaration && + node.kind !== SyntaxKind.InterfaceDeclaration && + // ModuleDeclaration needs to be checked that it is uninstantiated later + node.kind !== SyntaxKind.ModuleDeclaration && + node.parent.kind === SyntaxKind.SourceFile && + (moduleKind === ModuleKind.CommonJS || getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS) + ) { + return grammarErrorOnNode(modifier, Diagnostics.A_top_level_export_modifier_cannot_be_used_on_value_declarations_in_a_CommonJS_module_when_verbatimModuleSyntax_is_enabled); + } + 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"); + } + else if (blockScopeKind === NodeFlags.Using) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_using_declaration, "export"); + } + else if (blockScopeKind === NodeFlags.AwaitUsing) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_an_await_using_declaration, "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 (blockScopeKind === NodeFlags.Using) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_using_declaration, "default"); + } + else if (blockScopeKind === NodeFlags.AwaitUsing) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_an_await_using_declaration, "default"); + } + else if (!(flags & ModifierFlags.Export)) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "export", "default"); + } + else if (sawExportBeforeDecorators) { + return grammarErrorOnNode(firstDecorator!, Diagnostics.Decorators_are_not_valid_here); + } + + 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 (blockScopeKind === NodeFlags.Using) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_using_declaration, "declare"); + } + else if (blockScopeKind === NodeFlags.AwaitUsing) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_an_await_using_declaration, "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"); + } + else if (flags & ModifierFlags.Accessor) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "declare", "accessor"); + } + flags |= ModifierFlags.Ambient; + lastDeclare = modifier; + 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))) { + const message = node.kind === SyntaxKind.PropertyDeclaration + ? Diagnostics.Abstract_properties_can_only_appear_within_an_abstract_class + : Diagnostics.Abstract_methods_can_only_appear_within_an_abstract_class; + return grammarErrorOnNode(modifier, message); + } + 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"); + } + + flags |= ModifierFlags.Abstract; + break; + + 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; + + case SyntaxKind.InKeyword: + case SyntaxKind.OutKeyword: { + const inOutFlag = modifier.kind === SyntaxKind.InKeyword ? ModifierFlags.In : ModifierFlags.Out; + const inOutText = modifier.kind === SyntaxKind.InKeyword ? "in" : "out"; + const parent = isJSDocTemplateTag(node.parent) && (getEffectiveJSDocHost(node.parent) || find(getJSDocRoot(node.parent)?.tags, isJSDocTypedefTag)) || node.parent; + if (node.kind !== SyntaxKind.TypeParameter || parent && !(isInterfaceDeclaration(parent) || isClassLike(parent) || isTypeAliasDeclaration(parent) || isJSDocTypedefTag(parent))) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_can_only_appear_on_a_type_parameter_of_a_class_interface_or_type_alias, inOutText); + } + 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; + } + } + } + } + + 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; + } + + /** + * true | false: Early return this value from checkGrammarModifiers. + * undefined: Need to do full checking on the modifiers. + */ + function reportObviousModifierErrors(node: HasModifiers | HasIllegalModifiers): boolean | undefined { + if (!node.modifiers) return false; + + const modifier = findFirstIllegalModifier(node); + return modifier && grammarErrorOnFirstToken(modifier, Diagnostics.Modifiers_cannot_appear_here); + } + + function findFirstModifierExcept(node: HasModifiers, allowedModifier: SyntaxKind): Modifier | undefined { + const modifier = find(node.modifiers, isModifier); + return modifier && modifier.kind !== allowedModifier ? modifier : undefined; + } + + function findFirstIllegalModifier(node: HasModifiers | HasIllegalModifiers): Modifier | undefined { + 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 undefined; + case SyntaxKind.ClassStaticBlockDeclaration: + case SyntaxKind.PropertyAssignment: + case SyntaxKind.ShorthandPropertyAssignment: + case SyntaxKind.NamespaceExportDeclaration: + case SyntaxKind.MissingDeclaration: + return find(node.modifiers, isModifier); + default: + if (node.parent.kind === SyntaxKind.ModuleBlock || node.parent.kind === SyntaxKind.SourceFile) { + return undefined; + } + switch (node.kind) { + case SyntaxKind.FunctionDeclaration: + return findFirstModifierExcept(node, SyntaxKind.AsyncKeyword); + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ConstructorType: + return findFirstModifierExcept(node, SyntaxKind.AbstractKeyword); + case SyntaxKind.ClassExpression: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.TypeAliasDeclaration: + return find(node.modifiers, isModifier); + case SyntaxKind.VariableStatement: + return node.declarationList.flags & NodeFlags.Using ? + findFirstModifierExcept(node, SyntaxKind.AwaitKeyword) : + find(node.modifiers, isModifier); + case SyntaxKind.EnumDeclaration: + return findFirstModifierExcept(node, SyntaxKind.ConstKeyword); + default: + Debug.assertNever(node); + } + } + } + + function reportObviousDecoratorErrors(node: HasModifiers | HasDecorators | HasIllegalModifiers | HasIllegalDecorators) { + const decorator = findFirstIllegalDecorator(node); + return decorator && grammarErrorOnFirstToken(decorator, Diagnostics.Decorators_are_not_valid_here); + } + + function findFirstIllegalDecorator(node: HasModifiers | HasDecorators | HasIllegalModifiers | HasIllegalDecorators): Decorator | undefined { + return canHaveIllegalDecorators(node) ? find(node.modifiers, isDecorator) : undefined; + } + + function checkGrammarAsyncModifier(node: Node, asyncModifier: Node): boolean { + switch (node.kind) { + case SyntaxKind.MethodDeclaration: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + 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 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; + } + + function checkGrammarParameterList(parameters: NodeArray) { + let seenOptionalParameter = false; + const parameterCount = parameters.length; + + 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.questionToken) { + return grammarErrorOnNode(parameter.questionToken, Diagnostics.A_rest_parameter_cannot_be_optional); + } + + if (parameter.initializer) { + return grammarErrorOnNode(parameter.name, Diagnostics.A_rest_parameter_cannot_have_an_initializer); + } + } + else if (hasEffectiveQuestionToken(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 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; + } + } + } + return false; + } + + function checkGrammarFunctionLikeDeclaration(node: FunctionLikeDeclaration | MethodSignature): boolean { + // Prevent cascading error by short-circuit + const file = getSourceFileOfNode(node); + return checkGrammarModifiers(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 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); + } + } + + 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); + } + if (!node.type) { + return grammarErrorOnNode(node, Diagnostics.An_index_signature_must_have_a_type_annotation); + } + return false; + } + + function checkGrammarIndexSignature(node: IndexSignatureDeclaration) { + // Prevent cascading error by short-circuit + return checkGrammarModifiers(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 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 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 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 checkGrammarClassDeclarationHeritageClauses(node: ClassLikeDeclaration) { + let seenExtendsClause = false; + let seenImplementsClause = false; + + if (!checkGrammarModifiers(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 (seenImplementsClause) { + return grammarErrorOnFirstToken(heritageClause, Diagnostics.extends_clause_must_precede_implements_clause); + } + + if (heritageClause.types.length > 1) { + return grammarErrorOnFirstToken(heritageClause.types[1], Diagnostics.Classes_can_only_extend_a_single_class); + } + + seenExtendsClause = true; + } + else { + Debug.assert(heritageClause.token === SyntaxKind.ImplementsKeyword); + if (seenImplementsClause) { + return grammarErrorOnFirstToken(heritageClause, Diagnostics.implements_clause_already_seen); + } + + 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); + } + + seenExtendsClause = true; + } + else { + Debug.assert(heritageClause.token === SyntaxKind.ImplementsKeyword); + return grammarErrorOnFirstToken(heritageClause, Diagnostics.Interface_declaration_cannot_have_implements_clause); + } + + // Grammar checking heritageClause inside class declaration + checkGrammarHeritageClause(heritageClause); + } + } + 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; + } + + 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 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 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>(); + + 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); + } + + 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); + } + + // 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) { + if (isModifier(mod)) { + 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: + Debug.assertNever(prop, "Unexpected syntax kind:" + (prop as Node).kind); + } + + if (!inDestructuring) { + const effectiveName = getEffectivePropertyNameForPropertyNameNode(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)); + } + 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 { + 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>(); + + for (const attr of node.attributes.properties) { + if (attr.kind === SyntaxKind.JsxSpreadAttribute) { + continue; + } + + const { name, initializer } = attr; + const escapedText = getEscapedTextOfJsxAttributeName(name); + if (!seen.get(escapedText)) { + seen.set(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); + } + } + } + + function checkGrammarJsxName(node: JsxTagNameExpression) { + if (isPropertyAccessExpression(node) && isJsxNamespacedName(node.expression)) { + return grammarErrorOnNode(node.expression, Diagnostics.JSX_property_access_expressions_cannot_include_JSX_namespace_names); + } + if (isJsxNamespacedName(node) && getJSXTransformEnabled(compilerOptions) && !isIntrinsicJsxName(node.namespace.escapedText)) { + return grammarErrorOnNode(node, Diagnostics.React_components_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 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: + 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_nodenext_or_preserve_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; + } + } + } + } + + 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 (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 (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); + } + } + } + + 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.ES2015 && isPrivateIdentifier(accessor.name)) { + return grammarErrorOnNode(accessor.name, Diagnostics.Private_identifiers_are_only_available_when_targeting_ECMAScript_2015_and_higher); + } + if (accessor.body === undefined && !hasSyntacticModifier(accessor, ModifierFlags.Abstract)) { + return grammarErrorAtPos(accessor, accessor.end - 1, ";".length, Diagnostics._0_expected, "{"); + } + } + 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); + } + } + if (accessor.typeParameters) { + return grammarErrorOnNode(accessor.name, Diagnostics.An_accessor_cannot_have_type_parameters); + } + 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)); + } + 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); + } + 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.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); + } + } + 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 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 (checkGrammarForGenerator(node)) { + 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 (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); + } + } + + function checkGrammarBreakOrContinueStatement(node: BreakOrContinueStatement): boolean { + let current: Node = node; + while (current) { + if (isFunctionLikeOrClassStaticBlockDeclaration(current)) { + return grammarErrorOnNode(node, Diagnostics.Jump_target_cannot_cross_function_boundary); + } + + 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, /*lookInLabeledStatements*/ true); + + if (isMisplacedContinueLabel) { + return grammarErrorOnNode(node, Diagnostics.A_continue_statement_can_only_jump_to_a_label_of_an_enclosing_iteration_statement); + } + + 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, /*lookInLabeledStatements*/ false) && !node.label) { + // unlabeled break or continue within iteration statement - ok + return false; + } + break; + } + + current = current.parent; + } + + 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); + } + } + + 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.propertyName) { + return grammarErrorOnNode(node.name, Diagnostics.A_rest_element_cannot_have_a_property_name); + } + } + + 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 isStringOrNumberLiteralExpression(expr: Expression) { + return isStringOrNumericLiteralLike(expr) || + expr.kind === SyntaxKind.PrefixUnaryExpression && (expr as PrefixUnaryExpression).operator === SyntaxKind.MinusToken && + (expr as PrefixUnaryExpression).operand.kind === SyntaxKind.NumericLiteral; + } + + 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.EnumLike); + } + } + + 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) && (isVarConstLike(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) { + const nodeFlags = getCombinedNodeFlagsCached(node); + const blockScopeKind = nodeFlags & NodeFlags.BlockScoped; + if (isBindingPattern(node.name)) { + switch (blockScopeKind) { + case NodeFlags.AwaitUsing: + return grammarErrorOnNode(node, Diagnostics._0_declarations_may_not_have_binding_patterns, "await using"); + case NodeFlags.Using: + return grammarErrorOnNode(node, Diagnostics._0_declarations_may_not_have_binding_patterns, "using"); + } + } + + if (node.parent.parent.kind !== SyntaxKind.ForInStatement && node.parent.parent.kind !== SyntaxKind.ForOfStatement) { + if (nodeFlags & 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); + } + switch (blockScopeKind) { + case NodeFlags.AwaitUsing: + return grammarErrorOnNode(node, Diagnostics._0_declarations_must_be_initialized, "await using"); + case NodeFlags.Using: + return grammarErrorOnNode(node, Diagnostics._0_declarations_must_be_initialized, "using"); + case NodeFlags.Const: + return grammarErrorOnNode(node, Diagnostics._0_declarations_must_be_initialized, "const"); + } + } + } + + if (node.exclamationToken && (node.parent.parent.kind !== SyntaxKind.VariableStatement || !node.type || node.initializer || nodeFlags & 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); + } + + // 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 !!blockScopeKind && 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); + } + } + else { + const elements = name.elements; + for (const element of elements) { + if (!isOmittedExpression(element)) { + return checkESModuleMarker(element.name); + } + } + } + return false; + } + + function checkGrammarNameInLetOrConstDeclarations(name: Identifier | BindingPattern): boolean { + if (name.kind === SyntaxKind.Identifier) { + if (name.escapedText === "let") { + 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); + } + } + } + return false; + } + + 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); + } + + const blockScopeFlags = declarationList.flags & NodeFlags.BlockScoped; + if ((blockScopeFlags === NodeFlags.Using || blockScopeFlags === NodeFlags.AwaitUsing) && isForInStatement(declarationList.parent)) { + return grammarErrorOnNode( + declarationList, + blockScopeFlags === NodeFlags.Using ? + Diagnostics.The_left_hand_side_of_a_for_in_statement_cannot_be_a_using_declaration : + Diagnostics.The_left_hand_side_of_a_for_in_statement_cannot_be_an_await_using_declaration, + ); + } + + if (blockScopeFlags === NodeFlags.AwaitUsing) { + return checkAwaitGrammar(declarationList); + } + + 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); + } + + return true; + } + + function checkGrammarForDisallowedBlockScopedVariableStatement(node: VariableStatement) { + if (!allowLetAndConstDeclarations(node.parent)) { + const blockScopeKind = getCombinedNodeFlagsCached(node.declarationList) & NodeFlags.BlockScoped; + if (blockScopeKind) { + const keyword = blockScopeKind === NodeFlags.Let ? "let" : + blockScopeKind === NodeFlags.Const ? "const" : + blockScopeKind === NodeFlags.Using ? "using" : + blockScopeKind === NodeFlags.AwaitUsing ? "await using" : + Debug.fail("Unknown BlockScope flag"); + return grammarErrorOnNode(node, Diagnostics._0_declarations_can_only_be_declared_inside_a_block, keyword); + } + } + } + + 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, unescapeLeadingUnderscores(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, unescapeLeadingUnderscores(node.name.escapedText), tokenToString(node.keywordToken), "meta"); + } + break; + } + } + + function hasParseDiagnostics(sourceFile: SourceFile): boolean { + return sourceFile.parseDiagnostics.length > 0; + } + + function grammarErrorOnFirstToken(node: Node, message: DiagnosticMessage, ...args: DiagnosticArguments): boolean { + const sourceFile = getSourceFileOfNode(node); + if (!hasParseDiagnostics(sourceFile)) { + const span = getSpanOfTokenAtPosition(sourceFile, node.pos); + diagnostics.add(createFileDiagnostic(sourceFile, span.start, span.length, message, ...args)); + return true; + } + return false; + } + + function grammarErrorAtPos(nodeForSourceFile: Node, start: number, length: number, message: DiagnosticMessage, ...args: DiagnosticArguments): boolean { + const sourceFile = getSourceFileOfNode(nodeForSourceFile); + if (!hasParseDiagnostics(sourceFile)) { + diagnostics.add(createFileDiagnostic(sourceFile, start, length, message, ...args)); + return true; + } + return false; + } + + function grammarErrorOnNodeSkippedOn(key: keyof CompilerOptions, node: Node, message: DiagnosticMessage, ...args: DiagnosticArguments): boolean { + const sourceFile = getSourceFileOfNode(node); + if (!hasParseDiagnostics(sourceFile)) { + errorSkippedOn(key, node, message, ...args); + return true; + } + return false; + } + + function grammarErrorOnNode(node: Node, message: DiagnosticMessage, ...args: DiagnosticArguments): boolean { + const sourceFile = getSourceFileOfNode(node); + if (!hasParseDiagnostics(sourceFile)) { + diagnostics.add(createDiagnosticForNode(node, message, ...args)); + 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 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.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 (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; + } + } + 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); + } + } + 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 (node.flags & NodeFlags.Ambient) { + checkAmbientInitializer(node); + } + + 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 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; + } + + return grammarErrorOnFirstToken(node, Diagnostics.Top_level_declarations_in_d_ts_files_must_start_with_either_a_declare_or_export_modifier); + } + + 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; + } + + 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); + } + + // 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; + } + + function checkGrammarNumericLiteral(node: NumericLiteral) { + // Realism (size) checking + // 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).includes("."); + const isScientific = node.numericLiteralFlags & TokenFlags.Scientific; + + // 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; + } + + // 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; + } + + 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 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; + } + + function grammarErrorAfterFirstToken(node: Node, message: DiagnosticMessage, ...args: DiagnosticArguments): boolean { + const sourceFile = getSourceFileOfNode(node); + if (!hasParseDiagnostics(sourceFile)) { + const span = getSpanOfTokenAtPosition(sourceFile, node.pos); + diagnostics.add(createFileDiagnostic(sourceFile, textSpanEnd(span), /*length*/ 0, message, ...args)); + return true; + } + return false; + } + + 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 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 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 checkGrammarImportCallExpression(node: ImportCall): boolean { + if (compilerOptions.verbatimModuleSyntax && moduleKind === ModuleKind.CommonJS) { + return grammarErrorOnNode(node, Diagnostics.ESM_syntax_is_not_allowed_in_a_CommonJS_module_when_verbatimModuleSyntax_is_enabled); + } + + 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 (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); + } + + 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); + + if (nodeArguments.length > 1) { + const importAttributesArgument = nodeArguments[1]; + return grammarErrorOnNode(importAttributesArgument, Diagnostics.Dynamic_imports_only_support_a_second_argument_when_the_module_option_is_set_to_esnext_node16_or_nodenext); + } + } + + if (nodeArguments.length === 0 || nodeArguments.length > 2) { + return grammarErrorOnNode(node, Diagnostics.Dynamic_imports_can_only_accept_a_module_specifier_and_an_optional_set_of_attributes_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; + } + 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 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; + } + } + } + } + } + return bestMatch; + } + + function filterPrimitivesIfContainsNonPrimitive(type: UnionType) { + if (maybeTypeOfKind(type, TypeFlags.NonPrimitive)) { + const result = filterType(type, t => !(t.flags & TypeFlags.Primitive)); + if (!(result.flags & TypeFlags.Never)) { + return result; + } + } + return type; + } + + // Keep this up-to-date with the same logic within `getApparentTypeOfContextualType`, since they should behave similarly + function findMatchingDiscriminantType(source: Type, target: Type, isRelatedTo: (source: Type, target: Type) => Ternary) { + 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) { + const discriminated = discriminateTypeByDiscriminableItems(target as UnionType, map(sourcePropertiesFiltered, p => ([() => getTypeOfSymbol(p), p.escapedName] as [() => Type, __String])), isRelatedTo); + if (discriminated !== target) { + return discriminated; + } + } + } + } + return undefined; + } + + function getEffectivePropertyNameForPropertyNameNode(node: PropertyName) { + const name = getPropertyNameForPropertyNameNode(node); + return name ? name : + isComputedPropertyName(node) ? tryGetNameFromType(getTypeOfExpression(node.expression)) : undefined; + } + + function getCombinedModifierFlagsCached(node: Declaration) { + // we hold onto the last node and result to speed up repeated lookups against the same node. + if (lastGetCombinedModifierFlagsNode === node) { + return lastGetCombinedModifierFlagsResult; + } + + lastGetCombinedModifierFlagsNode = node; + lastGetCombinedModifierFlagsResult = getCombinedModifierFlags(node); + return lastGetCombinedModifierFlagsResult; + } + + function getCombinedNodeFlagsCached(node: Node) { + // we hold onto the last node and result to speed up repeated lookups against the same node. + if (lastGetCombinedNodeFlagsNode === node) { + return lastGetCombinedNodeFlagsResult; + } + lastGetCombinedNodeFlagsNode = node; + lastGetCombinedNodeFlagsResult = getCombinedNodeFlags(node); + return lastGetCombinedNodeFlagsResult; + } + + function isVarConstLike(node: VariableDeclaration | VariableDeclarationList) { + const blockScopeKind = getCombinedNodeFlagsCached(node) & NodeFlags.BlockScoped; + return blockScopeKind === NodeFlags.Const || + blockScopeKind === NodeFlags.Using || + blockScopeKind === NodeFlags.AwaitUsing; + } + + function getJSXRuntimeImportSpecifier(file: SourceFile | undefined, specifierText: string) { + // Synthesized JSX import is either first or after tslib + const jsxImportIndex = compilerOptions.importHelpers ? 1 : 0; + const specifier = file?.imports[jsxImportIndex]; + if (specifier) { + Debug.assert(nodeIsSynthesized(specifier) && specifier.text === specifierText, `Expected sourceFile.imports[${jsxImportIndex}] to be the synthesized JSX runtime import`); + } + return specifier; + } + + function getImportHelpersImportSpecifier(file: SourceFile) { + Debug.assert(compilerOptions.importHelpers, "Expected importHelpers to be enabled"); + const specifier = file.imports[0]; + Debug.assert(specifier && nodeIsSynthesized(specifier) && specifier.text === "tslib", `Expected sourceFile.imports[0] to be the synthesized tslib import`); + return specifier; + } +} + +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; +} + +/** 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) || name.kind === SyntaxKind.StringLiteral; + 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 ElementType = "ElementType" 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"; + } +} + +/** @internal */ +export function signatureHasRestParameter(s: Signature) { + return !!(s.flags & SignatureFlags.HasRestParameter); +} + +/** @internal */ +export function signatureHasLiteralTypes(s: Signature) { + return !!(s.flags & SignatureFlags.HasLiteralTypes); +} + +function createBasicNodeBuilderModuleSpecifierResolutionHost(host: TypeCheckerHost): ModuleSpecifierResolutionHost { + return { + 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, + }; +} + +interface NodeBuilderContext { + enclosingDeclaration: Node | undefined; + /** + * `enclosingFile` is generated from the initial `enclosingDeclaration` and + * is used to ensure text ranges for generated nodes are not set based on nodes from outside + * the original input's containing file. Checking the `enclosingDeclaration` at the time of + * `setTextRange` is not sufficient, as the `enclosingDeclaration` is modified by the node builder + * as it decends into some types as a shortcut to making certain scopes visible, and may be modified + * into a declaration in a different file from the original input `enclosingDeclaration`! + */ + enclosingFile: SourceFile | undefined; + flags: NodeBuilderFlags; + tracker: SymbolTrackerImpl; + + // State + encounteredError: boolean; + reportedDiagnostic: boolean; + trackedSymbols: TrackedSymbol[] | undefined; + visitedTypes: Set | undefined; + symbolDepth: Map | undefined; + inferTypeParameters: TypeParameter[] | undefined; + approximateLength: number; + truncating: boolean; + mustCreateTypeParameterSymbolList: boolean; + typeParameterSymbolList: Set | undefined; + mustCreateTypeParametersNamesLookups: boolean; + typeParameterNames: Map | undefined; + typeParameterNamesByText: Set | undefined; + typeParameterNamesByTextNextNameCount: Map | undefined; + usedSymbolNames: Set | undefined; + remappedSymbolNames: Map | undefined; + remappedSymbolReferences: Map | undefined; + reverseMappedStack: ReverseMappedSymbol[] | undefined; + bundled: boolean; + mapper: TypeMapper | undefined; +} + +class SymbolTrackerImpl implements SymbolTracker { + moduleResolverHost: ModuleSpecifierResolutionHost & { getCommonSourceDirectory(): string; } | undefined = undefined; + context: NodeBuilderContext; + + readonly inner: SymbolTracker | undefined = undefined; + readonly canTrackSymbol: boolean; + disableTrackSymbol = false; + + constructor(context: NodeBuilderContext, tracker: SymbolTracker | undefined, moduleResolverHost: ModuleSpecifierResolutionHost & { getCommonSourceDirectory(): string; } | undefined) { + while (tracker instanceof SymbolTrackerImpl) { + tracker = tracker.inner; + } + + this.inner = tracker; + this.moduleResolverHost = moduleResolverHost; + this.context = context; + this.canTrackSymbol = !!this.inner?.trackSymbol; + } + + trackSymbol(symbol: Symbol, enclosingDeclaration: Node | undefined, meaning: SymbolFlags): boolean { + if (this.inner?.trackSymbol && !this.disableTrackSymbol) { + if (this.inner.trackSymbol(symbol, enclosingDeclaration, meaning)) { + this.onDiagnosticReported(); + return true; + } + // Skip recording type parameters as they dont contribute to late painted statements + if (!(symbol.flags & SymbolFlags.TypeParameter)) (this.context.trackedSymbols ??= []).push([symbol, enclosingDeclaration, meaning]); + } + return false; + } + + reportInaccessibleThisError(): void { + if (this.inner?.reportInaccessibleThisError) { + this.onDiagnosticReported(); + this.inner.reportInaccessibleThisError(); + } + } + + reportPrivateInBaseOfClassExpression(propertyName: string): void { + if (this.inner?.reportPrivateInBaseOfClassExpression) { + this.onDiagnosticReported(); + this.inner.reportPrivateInBaseOfClassExpression(propertyName); + } + } + + reportInaccessibleUniqueSymbolError(): void { + if (this.inner?.reportInaccessibleUniqueSymbolError) { + this.onDiagnosticReported(); + this.inner.reportInaccessibleUniqueSymbolError(); + } + } + + reportCyclicStructureError(): void { + if (this.inner?.reportCyclicStructureError) { + this.onDiagnosticReported(); + this.inner.reportCyclicStructureError(); + } + } + + reportLikelyUnsafeImportRequiredError(specifier: string): void { + if (this.inner?.reportLikelyUnsafeImportRequiredError) { + this.onDiagnosticReported(); + this.inner.reportLikelyUnsafeImportRequiredError(specifier); + } + } + + reportTruncationError(): void { + if (this.inner?.reportTruncationError) { + this.onDiagnosticReported(); + this.inner.reportTruncationError(); + } + } + + reportNonlocalAugmentation(containingFile: SourceFile, parentSymbol: Symbol, augmentingSymbol: Symbol): void { + if (this.inner?.reportNonlocalAugmentation) { + this.onDiagnosticReported(); + this.inner.reportNonlocalAugmentation(containingFile, parentSymbol, augmentingSymbol); + } + } + + reportNonSerializableProperty(propertyName: string): void { + if (this.inner?.reportNonSerializableProperty) { + this.onDiagnosticReported(); + this.inner.reportNonSerializableProperty(propertyName); + } + } + + private onDiagnosticReported() { + this.context.reportedDiagnostic = true; + } + + reportInferenceFallback(node: Node): void { + if (this.inner?.reportInferenceFallback) { + this.inner.reportInferenceFallback(node); + } + } +} +import { + __String, + AccessExpression, + AccessFlags, + AccessorDeclaration, + addRange, + addRelatedInfo, + addSyntheticLeadingComment, + AliasDeclarationNode, + AllAccessorDeclarations, + AmbientModuleDeclaration, + and, + AnonymousType, + AnyImportOrJsDocImport, + AnyImportOrReExport, + append, + appendIfUnique, + ArrayBindingPattern, + arrayFrom, + arrayIsHomogeneous, + ArrayLiteralExpression, + arrayOf, + arraysEqual, + arrayToMultiMap, + ArrayTypeNode, + ArrowFunction, + AsExpression, + AssertionExpression, + AssignmentDeclarationKind, + AssignmentKind, + AssignmentPattern, + AwaitExpression, + BaseType, + BigIntLiteral, + BigIntLiteralType, + BinaryExpression, + BinaryOperator, + BinaryOperatorToken, + binarySearch, + BindableObjectDefinePropertyCall, + BindableStaticNameExpression, + BindingElement, + BindingElementGrandparent, + BindingName, + BindingPattern, + bindSourceFile, + Block, + BooleanLiteral, + BreakOrContinueStatement, + CallChain, + CallExpression, + CallLikeExpression, + CallSignatureDeclaration, + CancellationToken, + canHaveDecorators, + canHaveExportModifier, + canHaveFlowNode, + canHaveIllegalDecorators, + canHaveIllegalModifiers, + canHaveJSDoc, + canHaveLocals, + canHaveModifiers, + canHaveSymbol, + canIncludeBindAndCheckDiagnostics, + canUsePropertyAccess, + cartesianProduct, + CaseBlock, + CaseClause, + CaseOrDefaultClause, + cast, + chainDiagnosticMessages, + CharacterCodes, + CheckFlags, + ClassDeclaration, + ClassElement, + classElementOrClassElementParameterIsDecorated, + ClassExpression, + ClassLikeDeclaration, + classOrConstructorParameterIsDecorated, + 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, + createDetachedDiagnostic, + createDiagnosticCollection, + createDiagnosticForFileFromMessageChain, + createDiagnosticForNode, + createDiagnosticForNodeArray, + createDiagnosticForNodeArrayFromMessageChain, + createDiagnosticForNodeFromMessageChain, + createDiagnosticMessageChainFromDiagnostic, + createEmptyExports, + createEvaluator, + createFileDiagnostic, + createFlowNode, + createGetSymbolWalker, + createModeAwareCacheKey, + createModuleNotFoundChain, + createMultiMap, + createNameResolver, + createPrinterWithDefaults, + createPrinterWithRemoveComments, + createPrinterWithRemoveCommentsNeverAsciiEscape, + createPrinterWithRemoveCommentsOmitTrailingSemicolon, + createPropertyNameNodeForIdentifierOrLiteral, + createScanner, + createSymbolTable, + createSyntacticTypeNodeBuilder, + createTextWriter, + Debug, + Declaration, + DeclarationName, + declarationNameToString, + DeclarationStatement, + DeclarationWithTypeParameterChildren, + DeclarationWithTypeParameters, + Decorator, + deduplicate, + DefaultClause, + defaultMaximumTruncationLength, + DeferredTypeReference, + DeleteExpression, + Diagnostic, + DiagnosticAndArguments, + DiagnosticArguments, + DiagnosticCategory, + DiagnosticMessage, + DiagnosticMessageChain, + DiagnosticRelatedInformation, + Diagnostics, + DiagnosticWithLocation, + DoStatement, + DynamicNamedDeclaration, + ElementAccessChain, + ElementAccessExpression, + ElementFlags, + EmitFlags, + EmitHint, + emitModuleKindIsNonNodeESM, + EmitResolver, + EmitTextWriter, + emptyArray, + endsWith, + EntityName, + EntityNameExpression, + EntityNameOrEntityNameExpression, + entityNameToString, + EnumDeclaration, + EnumMember, + EnumType, + equateValues, + escapeLeadingUnderscores, + escapeString, + EvaluatorResult, + evaluatorResult, + every, + EvolvingArrayType, + ExclamationToken, + ExportAssignment, + exportAssignmentIsAlias, + ExportDeclaration, + ExportSpecifier, + Expression, + expressionResultIsUnused, + ExpressionStatement, + ExpressionWithTypeArguments, + Extension, + ExternalEmitHelpers, + externalHelpersModuleNameText, + factory, + fileExtensionIs, + fileExtensionIsOneOf, + filter, + find, + findAncestor, + findBestPatternMatch, + findConstructorDeclaration, + findIndex, + findLast, + findLastIndex, + findUseStrictPrologue, + first, + firstDefined, + firstIterator, + firstOrUndefined, + firstOrUndefinedIterator, + flatMap, + flatten, + FlowArrayMutation, + FlowAssignment, + FlowCall, + FlowCondition, + FlowFlags, + FlowLabel, + FlowNode, + FlowReduceLabel, + FlowStart, + FlowSwitchClause, + FlowSwitchClauseData, + FlowType, + forEach, + forEachChild, + forEachChildRecursively, + forEachEnclosingBlockScopeContainer, + forEachEntry, + forEachKey, + forEachReturnStatement, + forEachYieldExpression, + ForInOrOfStatement, + ForInStatement, + formatMessage, + ForOfStatement, + ForStatement, + FreshableIntrinsicType, + FreshableType, + FreshObjectLiteralType, + FunctionDeclaration, + FunctionExpression, + FunctionFlags, + FunctionLikeDeclaration, + FunctionOrConstructorTypeNode, + FunctionTypeNode, + GenericType, + GetAccessorDeclaration, + getAliasDeclarationFromName, + getAllJSDocTags, + getAllowSyntheticDefaultImports, + getAncestor, + getAssignedExpandoInitializer, + getAssignmentDeclarationKind, + getAssignmentDeclarationPropertyAccessKind, + getAssignmentTargetKind, + getCanonicalDiagnostic, + getCheckFlags, + getClassExtendsHeritageElement, + getClassLikeDeclarationOfSymbol, + getCombinedLocalAndExportSymbolFlags, + getCombinedModifierFlags, + getCombinedNodeFlags, + getContainingClass, + getContainingClassExcludingClassDecorators, + getContainingClassStaticBlock, + getContainingFunction, + getContainingFunctionOrClassStaticBlock, + getDeclarationModifierFlagsFromSymbol, + getDeclarationOfKind, + getDeclarationsOfKind, + getDeclaredExpandoInitializer, + getDecorators, + getDirectoryPath, + getEffectiveBaseTypeNode, + getEffectiveConstraintOfTypeParameter, + getEffectiveContainerForJSDocTemplateTag, + getEffectiveImplementsTypeNodes, + getEffectiveInitializer, + getEffectiveJSDocHost, + getEffectiveModifierFlags, + getEffectiveReturnTypeNode, + getEffectiveSetAccessorTypeAnnotationNode, + getEffectiveTypeAnnotationNode, + getEffectiveTypeParameterDeclarations, + getElementOrPropertyAccessName, + getEmitDeclarations, + getEmitFlags, + getEmitModuleKind, + getEmitModuleResolutionKind, + getEmitScriptTarget, + getEmitStandardClassFields, + getEnclosingBlockScopeContainer, + getEnclosingContainer, + getEntityNameFromTypeNode, + getErrorSpanForNode, + getEscapedTextOfIdentifierOrLiteral, + getEscapedTextOfJsxAttributeName, + getEscapedTextOfJsxNamespacedName, + getESModuleInterop, + getExpandoInitializer, + getExportAssignmentExpression, + getExternalModuleImportEqualsDeclarationExpression, + getExternalModuleName, + getExternalModuleRequireArgument, + getFirstConstructorWithBody, + getFirstIdentifier, + getFunctionFlags, + getHostSignatureFromJSDoc, + getIdentifierGeneratedImportReference, + getIdentifierTypeArguments, + getImmediatelyInvokedFunctionExpression, + getInitializerOfBinaryExpression, + getInterfaceBaseTypeNodes, + getInvokedExpression, + getIsolatedModules, + getJSDocClassTag, + getJSDocDeprecatedTag, + getJSDocEnumTag, + getJSDocHost, + getJSDocOverloadTags, + getJSDocParameterTags, + getJSDocRoot, + getJSDocSatisfiesExpressionType, + getJSDocTags, + getJSDocThisTag, + getJSDocType, + getJSDocTypeAssertionType, + getJSDocTypeParameterDeclarations, + getJSDocTypeTag, + getJSXImplicitImportBase, + getJSXRuntimeImport, + getJSXTransformEnabled, + getLeftmostAccessExpression, + getLineAndCharacterOfPosition, + getMembersOfDeclaration, + getModifiers, + getModuleInstanceState, + getNameFromImportAttribute, + getNameFromIndexInfo, + getNameOfDeclaration, + getNameOfExpando, + getNamespaceDeclarationNode, + getNewTargetContainer, + getNonAugmentationDeclaration, + getNormalizedAbsolutePath, + getObjectFlags, + getOriginalNode, + getOrUpdate, + getParameterSymbolFromJSDoc, + getParseTreeNode, + getPropertyAssignmentAliasLikeExpression, + getPropertyNameForPropertyNameNode, + getPropertyNameFromType, + getResolutionDiagnostic, + getResolutionModeOverride, + getResolveJsonModule, + getRestParameterElementType, + getRootDeclaration, + getScriptTargetFeatures, + getSelectedEffectiveModifierFlags, + getSemanticJsxChildren, + getSetAccessorValueParameter, + getSingleVariableOfVariableStatement, + getSourceFileOfModule, + getSourceFileOfNode, + getSpanOfTokenAtPosition, + getSpellingSuggestion, + getStrictOptionValue, + getSuperContainer, + getSymbolNameForPrivateIdentifier, + getTextOfIdentifierOrLiteral, + getTextOfJSDocComment, + getTextOfJsxAttributeName, + getTextOfNode, + getTextOfPropertyName, + getThisContainer, + getThisParameter, + getTrailingSemicolonDeferringWriter, + getTypeParameterFromJsDoc, + getUseDefineForClassFields, + group, + hasAbstractModifier, + hasAccessorModifier, + hasAmbientModifier, + hasContextSensitiveParameters, + HasDecorators, + hasDecorators, + hasDynamicName, + hasEffectiveModifier, + hasEffectiveModifiers, + hasEffectiveReadonlyModifier, + HasExpressionInitializer, + hasExtension, + HasIllegalDecorators, + HasIllegalModifiers, + hasInferredType, + HasInitializer, + hasInitializer, + hasJSDocNodes, + hasJSDocParameterTags, + hasJsonModuleEmitEnabled, + HasLocals, + HasModifiers, + hasOnlyExpressionInitializer, + hasOverrideModifier, + hasPossibleExternalModuleReference, + hasQuestionToken, + hasResolutionModeOverride, + hasRestParameter, + hasScopeMarker, + hasStaticModifier, + hasSyntacticModifier, + hasSyntacticModifiers, + hasType, + HeritageClause, + Identifier, + identifierToKeywordKind, + IdentifierTypePredicate, + idText, + IfStatement, + ImportAttribute, + ImportAttributes, + ImportCall, + ImportClause, + ImportDeclaration, + ImportEqualsDeclaration, + ImportOrExportSpecifier, + ImportSpecifier, + ImportTypeNode, + IndexedAccessType, + IndexedAccessTypeNode, + IndexFlags, + IndexInfo, + IndexKind, + indexOfNode, + IndexSignatureDeclaration, + IndexType, + indicesOf, + InferenceContext, + InferenceFlags, + InferenceInfo, + InferencePriority, + InferTypeNode, + InstanceofExpression, + InstantiableType, + InstantiationExpressionType, + InterfaceDeclaration, + InterfaceType, + InterfaceTypeWithDeclaredMembers, + InternalSymbolName, + IntersectionFlags, + IntersectionType, + IntersectionTypeNode, + intrinsicTagNameToString, + IntrinsicType, + introducesArgumentsExoticObject, + isAccessExpression, + isAccessor, + isAliasableExpression, + isAmbientModule, + isArray, + isArrayBindingPattern, + isArrayLiteralExpression, + isArrowFunction, + isAssertionExpression, + isAssignmentDeclaration, + isAssignmentExpression, + isAssignmentOperator, + isAssignmentPattern, + isAssignmentTarget, + isAutoAccessorPropertyDeclaration, + isAwaitExpression, + isBinaryExpression, + isBindableObjectDefinePropertyCall, + isBindableStaticElementAccessExpression, + isBindableStaticNameExpression, + isBindingElement, + isBindingElementOfBareOrAccessedRequire, + isBindingPattern, + isBlock, + isBlockOrCatchScoped, + isBlockScopedContainerTopLevel, + isBooleanLiteral, + isCallChain, + isCallExpression, + isCallLikeExpression, + isCallLikeOrFunctionLikeExpression, + isCallOrNewExpression, + isCallSignatureDeclaration, + isCatchClause, + isCatchClauseVariableDeclaration, + isCatchClauseVariableDeclarationOrBindingElement, + isCheckJsEnabledForFile, + isClassDeclaration, + isClassElement, + isClassExpression, + isClassInstanceProperty, + isClassLike, + isClassStaticBlockDeclaration, + isCommaSequence, + isCommonJsExportedExpression, + isCommonJsExportPropertyAssignment, + isCompoundAssignment, + isComputedNonLiteralName, + isComputedPropertyName, + isConditionalTypeNode, + isConstAssertion, + isConstructorDeclaration, + isConstructorTypeNode, + isConstructSignatureDeclaration, + isConstTypeReference, + isDeclaration, + isDeclarationFileName, + isDeclarationName, + isDeclarationReadonly, + isDecorator, + isDefaultedExpandoInitializer, + isDeleteTarget, + isDottedName, + isDynamicName, + isEffectiveExternalModule, + isElementAccessExpression, + isEntityName, + isEntityNameExpression, + isEnumConst, + isEnumDeclaration, + isEnumMember, + isExclusivelyTypeOnlyImportOrExport, + isExpandoPropertyDeclaration, + isExportAssignment, + isExportDeclaration, + isExportsIdentifier, + isExportSpecifier, + isExpression, + isExpressionNode, + isExpressionOfOptionalChainRoot, + isExpressionStatement, + isExpressionWithTypeArguments, + isExpressionWithTypeArgumentsInClassExtendsClause, + isExternalModule, + isExternalModuleAugmentation, + isExternalModuleImportEqualsDeclaration, + isExternalModuleIndicator, + isExternalModuleNameRelative, + isExternalModuleReference, + isExternalModuleSymbol, + isExternalOrCommonJsModule, + isForInOrOfStatement, + isForInStatement, + isForOfStatement, + isForStatement, + isFunctionDeclaration, + isFunctionExpression, + isFunctionExpressionOrArrowFunction, + isFunctionLike, + isFunctionLikeDeclaration, + isFunctionLikeOrClassStaticBlockDeclaration, + isFunctionOrModuleBlock, + isFunctionTypeNode, + isGeneratedIdentifier, + isGetAccessor, + isGetAccessorDeclaration, + isGetOrSetAccessorDeclaration, + isGlobalScopeAugmentation, + isGlobalSourceFile, + isHeritageClause, + isIdentifier, + isIdentifierText, + isIdentifierTypePredicate, + isIdentifierTypeReference, + isIfStatement, + isImportAttributes, + isImportCall, + isImportClause, + isImportDeclaration, + isImportEqualsDeclaration, + isImportKeyword, + isImportOrExportSpecifier, + isImportSpecifier, + isImportTypeNode, + isInCompoundLikeAssignment, + isIndexedAccessTypeNode, + isInExpressionContext, + isInfinityOrNaNString, + isInitializedProperty, + isInJSDoc, + isInJSFile, + isInJsonFile, + isInstanceOfExpression, + isInterfaceDeclaration, + isInternalModuleImportEqualsDeclaration, + isInTopLevelContext, + isIntrinsicJsxName, + isInTypeQuery, + isIterationStatement, + isJSDocAllType, + isJSDocAugmentsTag, + isJSDocCallbackTag, + isJSDocConstructSignature, + isJSDocFunctionType, + isJSDocImportTag, + isJSDocIndexSignature, + isJSDocLinkLike, + isJSDocMemberName, + isJSDocNameReference, + isJSDocNode, + isJSDocNonNullableType, + isJSDocNullableType, + isJSDocOptionalParameter, + isJSDocOptionalType, + isJSDocOverloadTag, + isJSDocParameterTag, + isJSDocPropertyLikeTag, + isJSDocPropertyTag, + isJSDocSatisfiesExpression, + isJSDocSatisfiesTag, + isJSDocSignature, + isJSDocTemplateTag, + isJSDocThisTag, + isJSDocTypeAlias, + isJSDocTypeAssertion, + isJSDocTypedefTag, + isJSDocTypeExpression, + isJSDocTypeLiteral, + isJSDocUnknownType, + isJSDocVariadicType, + isJsonSourceFile, + isJsxAttribute, + isJsxAttributeLike, + isJsxAttributes, + isJsxElement, + isJsxNamespacedName, + isJsxOpeningElement, + isJsxOpeningFragment, + isJsxOpeningLikeElement, + isJsxSelfClosingElement, + isJsxSpreadAttribute, + isJSXTagName, + isKnownSymbol, + isLateVisibilityPaintedStatement, + isLeftHandSideExpression, + isLineBreak, + isLiteralComputedPropertyDeclarationName, + isLiteralExpression, + isLiteralExpressionOfObject, + isLiteralImportTypeNode, + isLiteralTypeNode, + isLogicalOrCoalescingBinaryExpression, + isLogicalOrCoalescingBinaryOperator, + isMappedTypeNode, + isMetaProperty, + isMethodDeclaration, + isMethodSignature, + isModifier, + isModuleBlock, + isModuleDeclaration, + isModuleExportsAccessExpression, + isModuleIdentifier, + isModuleOrEnumDeclaration, + isModuleWithStringLiteralName, + isNamedDeclaration, + isNamedEvaluationSource, + isNamedExports, + isNamedTupleMember, + isNamespaceExport, + isNamespaceExportDeclaration, + isNamespaceReexportDeclaration, + isNewExpression, + isNodeDescendantOf, + isNonNullAccess, + isNonNullExpression, + isNumericLiteral, + isNumericLiteralName, + isObjectBindingPattern, + isObjectLiteralElementLike, + isObjectLiteralExpression, + isObjectLiteralMethod, + isObjectLiteralOrClassExpressionMethodOrAccessor, + isOmittedExpression, + isOptionalChain, + isOptionalChainRoot, + isOptionalDeclaration, + isOptionalJSDocPropertyLikeTag, + isOptionalTypeNode, + isOutermostOptionalChain, + isParameter, + isParameterPropertyDeclaration, + isParenthesizedExpression, + isParenthesizedTypeNode, + isPartOfParameterDeclaration, + isPartOfTypeNode, + isPartOfTypeQuery, + isPlainJsFile, + isPrefixUnaryExpression, + isPrivateIdentifier, + isPrivateIdentifierClassElementDeclaration, + isPrivateIdentifierPropertyAccessExpression, + isPropertyAccessEntityNameExpression, + isPropertyAccessExpression, + isPropertyAccessOrQualifiedName, + isPropertyAccessOrQualifiedNameOrImportTypeNode, + isPropertyAssignment, + isPropertyDeclaration, + isPropertyName, + isPropertyNameLiteral, + isPropertySignature, + isPrototypeAccess, + isPrototypePropertyAssignment, + isPushOrUnshiftIdentifier, + isQualifiedName, + isRequireCall, + isRestParameter, + isRestTypeNode, + isRightSideOfAccessExpression, + isRightSideOfInstanceofExpression, + isRightSideOfQualifiedNameOrPropertyAccess, + isRightSideOfQualifiedNameOrPropertyAccessOrJSDocMemberName, + isSameEntityName, + isSatisfiesExpression, + isSetAccessor, + isSetAccessorDeclaration, + 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, + isThisTypeNode, + isThisTypeParameter, + isThisTypePredicate, + isTransientSymbol, + isTupleTypeNode, + isTypeAlias, + isTypeAliasDeclaration, + isTypeDeclaration, + isTypeLiteralNode, + isTypeNode, + isTypeNodeKind, + isTypeOfExpression, + isTypeOnlyImportDeclaration, + isTypeOnlyImportOrExportDeclaration, + isTypeOperatorNode, + isTypeParameterDeclaration, + isTypePredicateNode, + isTypeQueryNode, + isTypeReferenceNode, + isTypeReferenceType, + isTypeUsableAsPropertyName, + isUMDExportSymbol, + isValidBigIntString, + isValidESSymbolDeclaration, + isValidTypeOnlyAliasUseSite, + isValueSignatureDeclaration, + isVariableDeclaration, + isVariableDeclarationInitializedToBareOrAccessedRequire, + isVariableDeclarationInVariableStatement, + isVariableDeclarationList, + isVariableLike, + isVariableLikeOrAccessor, + isVariableStatement, + isWriteAccess, + isWriteOnlyAccess, + IterableOrIteratorType, + IterationTypes, + JSDoc, + JSDocAugmentsTag, + JSDocCallbackTag, + JSDocComment, + JSDocFunctionType, + JSDocImplementsTag, + JSDocImportTag, + JSDocLink, + JSDocLinkCode, + JSDocLinkPlain, + JSDocMemberName, + JSDocNullableType, + JSDocOptionalType, + JSDocOverloadTag, + JSDocParameterTag, + JSDocPrivateTag, + JSDocPropertyLikeTag, + JSDocPropertyTag, + JSDocProtectedTag, + JSDocPublicTag, + JSDocSatisfiesTag, + JSDocSignature, + JSDocTemplateTag, + JSDocThisTag, + JSDocTypeAssertion, + JSDocTypedefTag, + JSDocTypeExpression, + JSDocTypeLiteral, + JSDocTypeReferencingNode, + JSDocTypeTag, + JSDocVariadicType, + JsxAttribute, + JsxAttributeLike, + JsxAttributeName, + JsxAttributes, + JsxAttributeValue, + JsxChild, + JsxClosingElement, + JsxElement, + JsxEmit, + JsxExpression, + JsxFlags, + JsxFragment, + JsxNamespacedName, + JsxOpeningElement, + JsxOpeningFragment, + JsxOpeningLikeElement, + JsxReferenceKind, + JsxSelfClosingElement, + JsxSpreadAttribute, + JsxTagNameExpression, + KeywordTypeNode, + LabeledStatement, + LanguageFeatureMinimumTarget, + last, + lastOrUndefined, + LateBoundBinaryExpressionDeclaration, + LateBoundDeclaration, + LateBoundName, + LateVisibilityPaintedStatement, + LazyNodeCheckFlags, + length, + LiteralExpression, + LiteralType, + LiteralTypeNode, + map, + mapDefined, + MappedSymbol, + MappedType, + MappedTypeNode, + MatchingKeys, + maybeBind, + MemberOverrideStatus, + MetaProperty, + MethodDeclaration, + MethodSignature, + minAndMax, + MinusToken, + Modifier, + ModifierFlags, + modifiersToFlags, + modifierToFlag, + ModuleBlock, + ModuleDeclaration, + ModuleExportName, + moduleExportNameIsDefault, + moduleExportNameTextEscaped, + moduleExportNameTextUnescaped, + ModuleInstanceState, + ModuleKind, + ModuleResolutionKind, + ModuleSpecifierResolutionHost, + Mutable, + NamedDeclaration, + NamedExports, + NamedImportsOrExports, + NamedTupleMember, + NamespaceDeclaration, + NamespaceExport, + NamespaceExportDeclaration, + NamespaceImport, + needsScopeMarker, + NewExpression, + Node, + NodeArray, + NodeBuilderFlags, + nodeCanBeDecorated, + NodeCheckFlags, + NodeFlags, + nodeHasName, + nodeIsMissing, + nodeIsPresent, + nodeIsSynthesized, + NodeLinks, + nodeStartsNewLexicalEnvironment, + NodeWithTypeArguments, + NonNullChain, + NonNullExpression, + not, + noTruncationMaximumTruncationLength, + NumberLiteralType, + NumericLiteral, + objectAllocator, + ObjectBindingPattern, + ObjectFlags, + ObjectFlagsType, + ObjectLiteralElementLike, + ObjectLiteralExpression, + ObjectType, + OptionalChain, + OptionalTypeNode, + or, + orderedRemoveItemAt, + OuterExpressionKinds, + ParameterDeclaration, + parameterIsThisKeyword, + ParameterPropertyDeclaration, + ParenthesizedExpression, + ParenthesizedTypeNode, + parseIsolatedEntityName, + parseNodeFactory, + parsePseudoBigInt, + parseValidBigInt, + Path, + pathIsRelative, + PatternAmbientModule, + PlusToken, + PostfixUnaryExpression, + PrefixUnaryExpression, + PrivateIdentifier, + Program, + PromiseOrAwaitableType, + PropertyAccessChain, + PropertyAccessEntityNameExpression, + PropertyAccessExpression, + PropertyAssignment, + PropertyDeclaration, + PropertyName, + PropertySignature, + PseudoBigInt, + pseudoBigIntToString, + PunctuationSyntaxKind, + pushIfUnique, + QualifiedName, + QuestionToken, + rangeEquals, + rangeOfNode, + rangeOfTypeParameters, + ReadonlyKeyword, + reduceLeft, + RegularExpressionLiteral, + RelationComparisonResult, + relativeComplement, + removeExtension, + removePrefix, + replaceElement, + resolutionExtensionIsTSOrJson, + ResolutionMode, + ResolvedModuleFull, + ResolvedType, + resolvingEmptyArray, + RestTypeNode, + ReturnStatement, + ReverseMappedSymbol, + ReverseMappedType, + sameMap, + SatisfiesExpression, + Scanner, + scanTokenAtPosition, + ScriptKind, + ScriptTarget, + SetAccessorDeclaration, + setCommentRange as setCommentRangeWorker, + setEmitFlags, + setIdentifierTypeArguments, + setNodeFlags, + setOriginalNode, + setParent, + setSyntheticLeadingComments, + setTextRange as setTextRangeWorker, + setTextRangePosEnd, + setValueDeclaration, + ShorthandPropertyAssignment, + shouldAllowImportingTsExtension, + shouldPreserveConstEnums, + Signature, + SignatureDeclaration, + SignatureFlags, + SignatureKind, + singleElementArray, + SingleSignatureType, + skipOuterExpressions, + skipParentheses, + skipTrivia, + skipTypeChecking, + skipTypeParentheses, + some, + SourceFile, + SpreadAssignment, + SpreadElement, + startsWith, + Statement, + StringLiteral, + StringLiteralLike, + StringLiteralType, + StringMappingType, + stripQuotes, + StructuredType, + SubstitutionType, + 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, + TrackedSymbol, + TransientSymbol, + TransientSymbolLinks, + tryAddToSet, + tryCast, + tryExtractTSExtension, + tryGetClassImplementingOrExtendingExpressionWithTypeArguments, + tryGetExtensionFromPath, + tryGetJSDocSatisfiesTypeNode, + 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, + unescapeLeadingUnderscores, + UnionOrIntersectionType, + UnionOrIntersectionTypeNode, + UnionReduction, + UnionType, + UnionTypeNode, + UniqueESSymbolType, + usingSingleLineStringWriter, + VariableDeclaration, + VariableDeclarationList, + VariableLikeDeclaration, + VariableStatement, + VarianceFlags, + visitEachChild as visitEachChildWorker, + visitNode, + visitNodes, + Visitor, + VisitResult, + VoidExpression, + walkUpBindingElementsAndPatterns, + walkUpOuterExpressions, + walkUpParenthesizedExpressions, + walkUpParenthesizedTypes, + walkUpParenthesizedTypesAndGetParentAndChild, + WhileStatement, + WideningContext, + WithStatement, + YieldExpression, +} from "./_namespaces/ts.js"; +import * as moduleSpecifiers from "./_namespaces/ts.moduleSpecifiers.js"; +import * as performance from "./_namespaces/ts.performance.js"; + +const ambientModuleSymbolRegex = /^".+"$/; +const anon = "(anonymous)" as __String & string; + +const enum ReferenceHint { + Unspecified, + Identifier, + Property, + ExportAssignment, + Jsx, + AsyncFunction, + ExportImportEquals, + ExportSpecifier, + Decorator, +} + +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, +} + +// dprint-ignore +/** @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 typeofNEFacts: ReadonlyMap = new Map(Object.entries({ + 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, + ResolvedTypeArguments, + ResolvedBaseTypes, + WriteType, + ParameterInitializerContainsUndefined, +} + +// dprint-ignore +/** @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 + RestBindingElement = 1 << 5, // 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 + TypeOnly = 1 << 6, // Called from getTypeOfExpression, diagnostics may be omitted +} + +/** @internal */ +export const enum SignatureCheckMode { + None = 0, + BivariantCallback = 1 << 0, + StrictCallback = 1 << 1, + IgnoreReturnTypes = 1 << 2, + StrictArity = 1 << 3, + StrictTopSignature = 1 << 4, + Callback = BivariantCallback | StrictCallback, +} + +const enum IntersectionState { + None = 0, + Source = 1 << 0, // Source type is a constituent of an outer intersection + Target = 1 << 1, // Target type is a constituent of an outer intersection +} + +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 MappedTypeNameTypeKind { + None, + Filtering, + Remapping, +} + +const enum ExpandingFlags { + None = 0, + Source = 1, + Target = 1 << 1, + Both = Source | Target, +} + +const enum MembersOrExportsResolutionKind { + resolvedExports = "resolvedExports", + resolvedMembers = "resolvedMembers", +} + +const enum UnusedKind { + Local, + Parameter, +} + +/** @param containingNode Node to check for parse error */ +type AddUnusedDiagnostic = (containingNode: Node, type: UnusedKind, diagnostic: DiagnosticWithLocation) => void; + +const isNotOverloadAndNotAccessor = and(isNotOverload, isNotAccessor); + +const enum DeclarationMeaning { + GetAccessor = 1, + SetAccessor = 2, + PropertyAssignment = 4, + Method = 8, + PrivateStatic = 16, + GetOrSetAccessor = GetAccessor | SetAccessor, + PropertyAssignmentOrMethod = PropertyAssignment | Method, +} + +const enum DeclarationSpaces { + None = 0, + ExportValue = 1 << 0, + ExportType = 1 << 1, + ExportNamespace = 1 << 2, +} + +const enum MinArgumentCountFlags { + None = 0, + StrongArityForUntypedJS = 1 << 0, + VoidIsNonOptional = 1 << 1, +} + +const enum IntrinsicTypeKind { + Uppercase, + Lowercase, + Capitalize, + Uncapitalize, + NoInfer, +} + +const intrinsicTypeKinds: ReadonlyMap = new Map(Object.entries({ + Uppercase: IntrinsicTypeKind.Uppercase, + Lowercase: IntrinsicTypeKind.Lowercase, + Capitalize: IntrinsicTypeKind.Capitalize, + Uncapitalize: IntrinsicTypeKind.Uncapitalize, + NoInfer: IntrinsicTypeKind.NoInfer, +})); + +const SymbolLinks = class implements SymbolLinks { + declare _symbolLinksBrand: any; +}; + +function NodeLinks(this: NodeLinks) { + this.flags = NodeCheckFlags.None; +} + +/** @internal */ +export function getNodeId(node: Node): number { + if (!node.id) { + node.id = nextNodeId; + nextNodeId++; + } + return node.id; +} + +/** @internal */ +export function getSymbolId(symbol: Symbol): SymbolId { + if (!symbol.id) { + symbol.id = nextSymbolId; + nextSymbolId++; + } + + return symbol.id; +} + +/** @internal */ +export function isInstantiatedModule(node: ModuleDeclaration, preserveConstEnums: boolean) { + const moduleState = getModuleInstanceState(node); + return moduleState === ModuleInstanceState.Instantiated || + (preserveConstEnums && moduleState === ModuleInstanceState.ConstEnumOnly); +} + +/** @internal */ +export function createTypeChecker(host: TypeCheckerHost): TypeChecker { + // Why var? It avoids TDZ checks in the runtime which can be costly. + // See: https://github.com/microsoft/TypeScript/issues/52924 + /* eslint-disable no-var */ + var deferredDiagnosticsCallbacks: (() => void)[] = []; + + var 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). + var cancellationToken: CancellationToken | undefined; + + var scanner: Scanner | undefined; + + var Symbol = objectAllocator.getSymbolConstructor(); + var Type = objectAllocator.getTypeConstructor(); + var Signature = objectAllocator.getSignatureConstructor(); + + var typeCount = 0; + var symbolCount = 0; + var totalInstantiationCount = 0; + var instantiationCount = 0; + var instantiationDepth = 0; + var inlineLevel = 0; + var currentNode: Node | undefined; + var varianceTypeParameter: TypeParameter | undefined; + var isInferencePartiallyBlocked = false; + + var emptySymbols = createSymbolTable(); + var arrayVariances = [VarianceFlags.Covariant]; + + var compilerOptions = host.getCompilerOptions(); + var languageVersion = getEmitScriptTarget(compilerOptions); + var moduleKind = getEmitModuleKind(compilerOptions); + var legacyDecorators = !!compilerOptions.experimentalDecorators; + var useDefineForClassFields = getUseDefineForClassFields(compilerOptions); + var emitStandardClassFields = getEmitStandardClassFields(compilerOptions); + var allowSyntheticDefaultImports = getAllowSyntheticDefaultImports(compilerOptions); + var strictNullChecks = getStrictOptionValue(compilerOptions, "strictNullChecks"); + var strictFunctionTypes = getStrictOptionValue(compilerOptions, "strictFunctionTypes"); + var strictBindCallApply = getStrictOptionValue(compilerOptions, "strictBindCallApply"); + var strictPropertyInitialization = getStrictOptionValue(compilerOptions, "strictPropertyInitialization"); + var noImplicitAny = getStrictOptionValue(compilerOptions, "noImplicitAny"); + var noImplicitThis = getStrictOptionValue(compilerOptions, "noImplicitThis"); + var useUnknownInCatchVariables = getStrictOptionValue(compilerOptions, "useUnknownInCatchVariables"); + var exactOptionalPropertyTypes = compilerOptions.exactOptionalPropertyTypes; + + var checkBinaryExpression = createCheckBinaryExpression(); + var emitResolver = createResolver(); + var nodeBuilder = createNodeBuilder(); + var syntacticNodeBuilder = createSyntacticTypeNodeBuilder(compilerOptions, { + isEntityNameVisible, + isExpandoFunctionDeclaration, + getAllAccessorDeclarations: getAllAccessorDeclarationsForDeclaration, + requiresAddingImplicitUndefined, + isUndefinedIdentifierExpression(node: Identifier) { + Debug.assert(isExpressionNode(node)); + return getSymbolAtLocation(node) === undefinedSymbol; + }, + isDefinitelyReferenceToGlobalSymbolObject, + }); + var evaluate = createEvaluator({ + evaluateElementAccessExpression, + evaluateEntityNameExpression, + }); + + var globals = createSymbolTable(); + var undefinedSymbol = createSymbol(SymbolFlags.Property, "undefined" as __String); + undefinedSymbol.declarations = []; + + var globalThisSymbol = createSymbol(SymbolFlags.Module, "globalThis" as __String, CheckFlags.Readonly); + globalThisSymbol.exports = globals; + globalThisSymbol.declarations = []; + globals.set(globalThisSymbol.escapedName, globalThisSymbol); + + var argumentsSymbol = createSymbol(SymbolFlags.Property, "arguments" as __String); + var requireSymbol = createSymbol(SymbolFlags.Property, "require" as __String); + var isolatedModulesLikeFlagName = compilerOptions.verbatimModuleSyntax ? "verbatimModuleSyntax" : "isolatedModules"; + var canCollectSymbolAliasAccessabilityData = !compilerOptions.verbatimModuleSyntax; + + /** This will be set during calls to `getResolvedSignature` where services determines an apparent number of arguments greater than what is actually provided. */ + var apparentArgumentCount: number | undefined; + + var lastGetCombinedNodeFlagsNode: Node | undefined; + var lastGetCombinedNodeFlagsResult = NodeFlags.None; + var lastGetCombinedModifierFlagsNode: Declaration | undefined; + var lastGetCombinedModifierFlagsResult = ModifierFlags.None; + var resolveName = createNameResolver({ + compilerOptions, + requireSymbol, + argumentsSymbol, + globals, + getSymbolOfDeclaration, + error, + getRequiresScopeChangeCache, + setRequiresScopeChangeCache, + lookup: getSymbol, + onPropertyWithInvalidInitializer: checkAndReportErrorForInvalidInitializer, + onFailedToResolveSymbol, + onSuccessfullyResolvedSymbol, + }); + + var resolveNameForSymbolSuggestion = createNameResolver({ + compilerOptions, + requireSymbol, + argumentsSymbol, + globals, + getSymbolOfDeclaration, + error, + getRequiresScopeChangeCache, + setRequiresScopeChangeCache, + lookup: getSuggestionForSymbolNameLookup, + }); + // 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: () => reduceLeft(host.getSourceFiles(), (n, s) => n + s.nodeCount, 0), + getIdentifierCount: () => reduceLeft(host.getSourceFiles(), (n, s) => n + s.identifierCount, 0), + getSymbolCount: () => reduceLeft(host.getSourceFiles(), (n, s) => n + s.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, + symbolIsValue, + 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."); + Debug.assert(isParameterPropertyDeclaration(parameter, parameter.parent)); + 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, + getWidenedLiteralType, + getTypeFromTypeNode: nodeIn => { + const node = getParseTreeNode(nodeIn, isTypeNode); + return node ? getTypeFromTypeNode(node) : errorType; + }, + getParameterType: getTypeAtPosition, + getParameterIdentifierInfoAtPosition, + 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), + getCandidateSignaturesForStringLiteralCompletions, + getResolvedSignatureForSignatureHelp: (node, candidatesOutArray, argumentCount) => runWithoutResolvedSignatureCaching(node, () => 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, + requiresAddingImplicitUndefined, + 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, + getStringLiteralType, + getNumberType: () => numberType, + getNumberLiteralType, + getBigIntType: () => bigintType, + getBigIntLiteralType, + 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, + isEmptyAnonymousObjectType, + isTypeInvalidDueToUnionDiscriminant, + getExactOptionalProperties, + getAllPossiblePropertiesOfTypes, + getSuggestedSymbolForNonexistentProperty, + getSuggestedSymbolForNonexistentJSXAttribute, + getSuggestedSymbolForNonexistentSymbol: (location, name, meaning) => getSuggestedSymbolForNonexistentSymbol(location, escapeLeadingUnderscores(name), meaning), + getSuggestedSymbolForNonexistentModule, + 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, /*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; + } + + 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; + + // 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)); + + 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 }); + } + }); + + return diagnostics || emptyArray; + } + finally { + cancellationToken = undefined; + } + }, + + runWithCancellationToken: (token, callback) => { + try { + cancellationToken = token; + return callback(checker); + } + finally { + cancellationToken = undefined; + } + }, + + getLocalTypeParametersOfClassOrInterfaceOrTypeAlias, + isDeclarationVisible, + isPropertyAccessible, + getTypeOnlyAliasDeclaration, + getMemberOverrideModifierStatus, + isTypeParameterPossiblyReferenced, + typeHasCallOrConstructSignatures, + getSymbolFlags, + }; + + function getCandidateSignaturesForStringLiteralCompletions(call: CallLikeExpression, editingArgument: Node) { + const candidatesSet = new Set(); + const candidates: Signature[] = []; + + // first, get candidates when inference is blocked from the source node. + runWithInferenceBlockedFromSourceNode(editingArgument, () => getResolvedSignatureWorker(call, candidates, /*argumentCount*/ undefined, CheckMode.Normal)); + for (const candidate of candidates) { + candidatesSet.add(candidate); + } + + // reset candidates for second pass + candidates.length = 0; + + // next, get candidates where the source node is considered for inference. + runWithoutResolvedSignatureCaching(editingArgument, () => getResolvedSignatureWorker(call, candidates, /*argumentCount*/ undefined, CheckMode.Normal)); + for (const candidate of candidates) { + candidatesSet.add(candidate); + } + + return arrayFrom(candidatesSet); + } + + function runWithoutResolvedSignatureCaching(node: Node | undefined, fn: () => T): T { + node = findAncestor(node, isCallLikeOrFunctionLikeExpression); + if (node) { + const cachedResolvedSignatures = []; + const cachedTypes = []; + while (node) { + const nodeLinks = getNodeLinks(node); + cachedResolvedSignatures.push([nodeLinks, nodeLinks.resolvedSignature] as const); + nodeLinks.resolvedSignature = undefined; + if (isFunctionExpressionOrArrowFunction(node)) { + const symbolLinks = getSymbolLinks(getSymbolOfDeclaration(node)); + const type = symbolLinks.type; + cachedTypes.push([symbolLinks, type] as const); + symbolLinks.type = undefined; + } + node = findAncestor(node.parent, isCallLikeOrFunctionLikeExpression); + } + const result = fn(); + for (const [nodeLinks, resolvedSignature] of cachedResolvedSignatures) { + nodeLinks.resolvedSignature = resolvedSignature; + } + for (const [symbolLinks, type] of cachedTypes) { + symbolLinks.type = type; + } + return result; + } + return fn(); + } + + function runWithInferenceBlockedFromSourceNode(node: Node | undefined, fn: () => T): T { + const containingCall = findAncestor(node, isCallLikeExpression); + if (containingCall) { + let toMarkSkip = node!; + do { + getNodeLinks(toMarkSkip).skipDirectInference = true; + toMarkSkip = toMarkSkip.parent; + } + while (toMarkSkip && toMarkSkip !== containingCall); + } + + isInferencePartiallyBlocked = true; + const result = runWithoutResolvedSignatureCaching(node, fn); + isInferencePartiallyBlocked = false; + + if (containingCall) { + let toMarkSkip = node!; + do { + getNodeLinks(toMarkSkip).skipDirectInference = undefined; + toMarkSkip = toMarkSkip.parent; + } + while (toMarkSkip && toMarkSkip !== containingCall); + } + return result; + } + + function getResolvedSignatureWorker(nodeIn: CallLikeExpression, candidatesOutArray: Signature[] | undefined, argumentCount: number | undefined, checkMode: CheckMode): Signature | undefined { + const node = getParseTreeNode(nodeIn, isCallLikeExpression); + apparentArgumentCount = argumentCount; + const res = !node ? undefined : getResolvedSignature(node, candidatesOutArray, checkMode); + apparentArgumentCount = undefined; + return res; + } + + var tupleTypes = new Map(); + var unionTypes = new Map(); + var unionOfUnionTypes = new Map(); + var intersectionTypes = new Map(); + var stringLiteralTypes = new Map(); + var numberLiteralTypes = new Map(); + var bigIntLiteralTypes = new Map(); + var enumLiteralTypes = new Map(); + var indexedAccessTypes = new Map(); + var templateLiteralTypes = new Map(); + var stringMappingTypes = new Map(); + var substitutionTypes = new Map(); + var subtypeReductionCache = new Map(); + var decoratorContextOverrideTypeCache = new Map(); + var cachedTypes = new Map(); + var evolvingArrayTypes: EvolvingArrayType[] = []; + var undefinedProperties: SymbolTable = new Map(); + var markerTypes = new Set(); + + var unknownSymbol = createSymbol(SymbolFlags.Property, "unknown" as __String); + var resolvingSymbol = createSymbol(0, InternalSymbolName.Resolving); + var unresolvedSymbols = new Map(); + var errorTypes = new Map(); + + // We specifically create the `undefined` and `null` types before any other types that can occur in + // unions such that they are given low type IDs and occur first in the sorted list of union constituents. + // We can then just examine the first constituent(s) of a union to check for their presence. + + var seenIntrinsicNames = new Set(); + + var anyType = createIntrinsicType(TypeFlags.Any, "any"); + var autoType = createIntrinsicType(TypeFlags.Any, "any", ObjectFlags.NonInferrableType, "auto"); + var wildcardType = createIntrinsicType(TypeFlags.Any, "any", /*objectFlags*/ undefined, "wildcard"); + var blockedStringType = createIntrinsicType(TypeFlags.Any, "any", /*objectFlags*/ undefined, "blocked string"); + var errorType = createIntrinsicType(TypeFlags.Any, "error"); + var unresolvedType = createIntrinsicType(TypeFlags.Any, "unresolved"); + var nonInferrableAnyType = createIntrinsicType(TypeFlags.Any, "any", ObjectFlags.ContainsWideningType, "non-inferrable"); + var intrinsicMarkerType = createIntrinsicType(TypeFlags.Any, "intrinsic"); + var unknownType = createIntrinsicType(TypeFlags.Unknown, "unknown"); + var undefinedType = createIntrinsicType(TypeFlags.Undefined, "undefined"); + var undefinedWideningType = strictNullChecks ? undefinedType : createIntrinsicType(TypeFlags.Undefined, "undefined", ObjectFlags.ContainsWideningType, "widening"); + var missingType = createIntrinsicType(TypeFlags.Undefined, "undefined", /*objectFlags*/ undefined, "missing"); + var undefinedOrMissingType = exactOptionalPropertyTypes ? missingType : undefinedType; + var optionalType = createIntrinsicType(TypeFlags.Undefined, "undefined", /*objectFlags*/ undefined, "optional"); + var nullType = createIntrinsicType(TypeFlags.Null, "null"); + var nullWideningType = strictNullChecks ? nullType : createIntrinsicType(TypeFlags.Null, "null", ObjectFlags.ContainsWideningType, "widening"); + var stringType = createIntrinsicType(TypeFlags.String, "string"); + var numberType = createIntrinsicType(TypeFlags.Number, "number"); + var bigintType = createIntrinsicType(TypeFlags.BigInt, "bigint"); + var falseType = createIntrinsicType(TypeFlags.BooleanLiteral, "false", /*objectFlags*/ undefined, "fresh") as FreshableIntrinsicType; + var regularFalseType = createIntrinsicType(TypeFlags.BooleanLiteral, "false") as FreshableIntrinsicType; + var trueType = createIntrinsicType(TypeFlags.BooleanLiteral, "true", /*objectFlags*/ undefined, "fresh") as FreshableIntrinsicType; + var 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; + var booleanType = getUnionType([regularFalseType, regularTrueType]); + var esSymbolType = createIntrinsicType(TypeFlags.ESSymbol, "symbol"); + var voidType = createIntrinsicType(TypeFlags.Void, "void"); + var neverType = createIntrinsicType(TypeFlags.Never, "never"); + var silentNeverType = createIntrinsicType(TypeFlags.Never, "never", ObjectFlags.NonInferrableType, "silent"); + var implicitNeverType = createIntrinsicType(TypeFlags.Never, "never", /*objectFlags*/ undefined, "implicit"); + var unreachableNeverType = createIntrinsicType(TypeFlags.Never, "never", /*objectFlags*/ undefined, "unreachable"); + var nonPrimitiveType = createIntrinsicType(TypeFlags.NonPrimitive, "object"); + var stringOrNumberType = getUnionType([stringType, numberType]); + var stringNumberSymbolType = getUnionType([stringType, numberType, esSymbolType]); + var numberOrBigIntType = getUnionType([numberType, bigintType]); + var templateConstraintType = getUnionType([stringType, numberType, booleanType, bigintType, nullType, undefinedType]) as UnionType; + var numericStringType = getTemplateLiteralType(["", ""], [numberType]); // The `${number}` type + + var restrictiveMapper: TypeMapper = makeFunctionTypeMapper(t => t.flags & TypeFlags.TypeParameter ? getRestrictiveTypeParameter(t as TypeParameter) : t, () => "(restrictive mapper)"); + var permissiveMapper: TypeMapper = makeFunctionTypeMapper(t => t.flags & TypeFlags.TypeParameter ? wildcardType : t, () => "(permissive mapper)"); + var uniqueLiteralType = createIntrinsicType(TypeFlags.Never, "never", /*objectFlags*/ undefined, "unique literal"); // `uniqueLiteralType` is a special `never` flagged by union reduction to behave as a literal + var uniqueLiteralMapper: TypeMapper = makeFunctionTypeMapper(t => t.flags & TypeFlags.TypeParameter ? uniqueLiteralType : t, () => "(unique literal mapper)"); // replace all type parameters with the unique literal type (disregarding constraints) + var outofbandVarianceMarkerHandler: ((onlyUnreliable: boolean) => void) | undefined; + var reportUnreliableMapper = makeFunctionTypeMapper(t => { + if (outofbandVarianceMarkerHandler && (t === markerSuperType || t === markerSubType || t === markerOtherType)) { + outofbandVarianceMarkerHandler(/*onlyUnreliable*/ true); + } + return t; + }, () => "(unmeasurable reporter)"); + var reportUnmeasurableMapper = makeFunctionTypeMapper(t => { + if (outofbandVarianceMarkerHandler && (t === markerSuperType || t === markerSubType || t === markerOtherType)) { + outofbandVarianceMarkerHandler(/*onlyUnreliable*/ false); + } + return t; + }, () => "(unreliable reporter)"); + + var emptyObjectType = createAnonymousType(/*symbol*/ undefined, emptySymbols, emptyArray, emptyArray, emptyArray); + var emptyJsxObjectType = createAnonymousType(/*symbol*/ undefined, emptySymbols, emptyArray, emptyArray, emptyArray); + emptyJsxObjectType.objectFlags |= ObjectFlags.JsxAttributes; + + var emptyTypeLiteralSymbol = createSymbol(SymbolFlags.TypeLiteral, InternalSymbolName.Type); + emptyTypeLiteralSymbol.members = createSymbolTable(); + var emptyTypeLiteralType = createAnonymousType(emptyTypeLiteralSymbol, emptySymbols, emptyArray, emptyArray, emptyArray); + + var unknownEmptyObjectType = createAnonymousType(/*symbol*/ undefined, emptySymbols, emptyArray, emptyArray, emptyArray); + var unknownUnionType = strictNullChecks ? getUnionType([undefinedType, nullType, unknownEmptyObjectType]) : unknownType; + + var emptyGenericType = createAnonymousType(/*symbol*/ undefined, emptySymbols, emptyArray, emptyArray, emptyArray) as ObjectType as GenericType; + emptyGenericType.instantiations = new Map(); + + var anyFunctionType = createAnonymousType(/*symbol*/ 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; + + var noConstraintType = createAnonymousType(/*symbol*/ undefined, emptySymbols, emptyArray, emptyArray, emptyArray); + var circularConstraintType = createAnonymousType(/*symbol*/ undefined, emptySymbols, emptyArray, emptyArray, emptyArray); + var resolvingDefaultType = createAnonymousType(/*symbol*/ undefined, emptySymbols, emptyArray, emptyArray, emptyArray); + + var markerSuperType = createTypeParameter(); + var markerSubType = createTypeParameter(); + markerSubType.constraint = markerSuperType; + var markerOtherType = createTypeParameter(); + + var markerSuperTypeForCheck = createTypeParameter(); + var markerSubTypeForCheck = createTypeParameter(); + markerSubTypeForCheck.constraint = markerSuperTypeForCheck; + + var noTypePredicate = createTypePredicate(TypePredicateKind.Identifier, "<>", 0, anyType); + + var anySignature = createSignature(/*declaration*/ undefined, /*typeParameters*/ undefined, /*thisParameter*/ undefined, emptyArray, anyType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); + var unknownSignature = createSignature(/*declaration*/ undefined, /*typeParameters*/ undefined, /*thisParameter*/ undefined, emptyArray, errorType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); + var resolvingSignature = createSignature(/*declaration*/ undefined, /*typeParameters*/ undefined, /*thisParameter*/ undefined, emptyArray, anyType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); + var silentNeverSignature = createSignature(/*declaration*/ undefined, /*typeParameters*/ undefined, /*thisParameter*/ undefined, emptyArray, silentNeverType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); + + var enumNumberIndexInfo = createIndexInfo(numberType, stringType, /*isReadonly*/ true); + + var iterationTypesCache = new Map(); // cache for common IterationTypes instances + var 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"); + }, + }; + + var anyIterationTypes = createIterationTypes(anyType, anyType, anyType); + var anyIterationTypesExceptNext = createIterationTypes(anyType, anyType, unknownType); + var defaultIterationTypes = createIterationTypes(neverType, anyType, undefinedType); // default iteration types for `Iterator`. + + var asyncIterationTypesResolver: IterationTypesResolver = { + iterableCacheKey: "iterationTypesOfAsyncIterable", + iteratorCacheKey: "iterationTypesOfAsyncIterator", + iteratorSymbolName: "asyncIterator", + getGlobalIteratorType: getGlobalAsyncIteratorType, + getGlobalIterableType: getGlobalAsyncIterableType, + getGlobalIterableIteratorType: getGlobalAsyncIterableIteratorType, + getGlobalGeneratorType: getGlobalAsyncGeneratorType, + resolveIterationType: (type, errorNode) => getAwaitedType(type, errorNode, Diagnostics.Type_of_await_operand_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member), + 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, + }; + + var 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: Map; + } + /** Key is "/path/to/a.ts|/path/to/b.ts". */ + var amalgamatedDuplicates: Map | undefined; + var reverseMappedCache = new Map(); + var 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. + */ + var patternAmbientModules: PatternAmbientModule[]; + var patternAmbientModuleAugmentations: Map | undefined; + + var globalObjectType: ObjectType; + var globalFunctionType: ObjectType; + var globalCallableFunctionType: ObjectType; + var globalNewableFunctionType: ObjectType; + var globalArrayType: GenericType; + var globalReadonlyArrayType: GenericType; + var globalStringType: ObjectType; + var globalNumberType: ObjectType; + var globalBooleanType: ObjectType; + var globalRegExpType: ObjectType; + var globalThisType: GenericType; + var anyArrayType: Type; + var autoArrayType: Type; + var anyReadonlyArrayType: Type; + var 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 + var deferredGlobalESSymbolConstructorSymbol: Symbol | undefined; + var deferredGlobalESSymbolConstructorTypeSymbol: Symbol | undefined; + var deferredGlobalESSymbolType: ObjectType | undefined; + var deferredGlobalTypedPropertyDescriptorType: GenericType; + var deferredGlobalPromiseType: GenericType | undefined; + var deferredGlobalPromiseLikeType: GenericType | undefined; + var deferredGlobalPromiseConstructorSymbol: Symbol | undefined; + var deferredGlobalPromiseConstructorLikeType: ObjectType | undefined; + var deferredGlobalIterableType: GenericType | undefined; + var deferredGlobalIteratorType: GenericType | undefined; + var deferredGlobalIterableIteratorType: GenericType | undefined; + var deferredGlobalGeneratorType: GenericType | undefined; + var deferredGlobalIteratorYieldResultType: GenericType | undefined; + var deferredGlobalIteratorReturnResultType: GenericType | undefined; + var deferredGlobalAsyncIterableType: GenericType | undefined; + var deferredGlobalAsyncIteratorType: GenericType | undefined; + var deferredGlobalAsyncIterableIteratorType: GenericType | undefined; + var deferredGlobalAsyncGeneratorType: GenericType | undefined; + var deferredGlobalTemplateStringsArrayType: ObjectType | undefined; + var deferredGlobalImportMetaType: ObjectType; + var deferredGlobalImportMetaExpressionType: ObjectType; + var deferredGlobalImportCallOptionsType: ObjectType | undefined; + var deferredGlobalImportAttributesType: ObjectType | undefined; + var deferredGlobalDisposableType: ObjectType | undefined; + var deferredGlobalAsyncDisposableType: ObjectType | undefined; + var deferredGlobalExtractSymbol: Symbol | undefined; + var deferredGlobalOmitSymbol: Symbol | undefined; + var deferredGlobalAwaitedSymbol: Symbol | undefined; + var deferredGlobalBigIntType: ObjectType | undefined; + var deferredGlobalNaNSymbol: Symbol | undefined; + var deferredGlobalRecordSymbol: Symbol | undefined; + var deferredGlobalClassDecoratorContextType: GenericType | undefined; + var deferredGlobalClassMethodDecoratorContextType: GenericType | undefined; + var deferredGlobalClassGetterDecoratorContextType: GenericType | undefined; + var deferredGlobalClassSetterDecoratorContextType: GenericType | undefined; + var deferredGlobalClassAccessorDecoratorContextType: GenericType | undefined; + var deferredGlobalClassAccessorDecoratorTargetType: GenericType | undefined; + var deferredGlobalClassAccessorDecoratorResultType: GenericType | undefined; + var deferredGlobalClassFieldDecoratorContextType: GenericType | undefined; + + var allPotentiallyUnusedIdentifiers = new Map(); // key is file name + + var flowLoopStart = 0; + var flowLoopCount = 0; + var sharedFlowCount = 0; + var flowAnalysisDisabled = false; + var flowInvocationCount = 0; + var lastFlowNode: FlowNode | undefined; + var lastFlowNodeReachable: boolean; + var flowTypeCache: Type[] | undefined; + + var contextualTypeNodes: Node[] = []; + var contextualTypes: (Type | undefined)[] = []; + var contextualIsCache: boolean[] = []; + var contextualTypeCount = 0; + + var inferenceContextNodes: Node[] = []; + var inferenceContexts: (InferenceContext | undefined)[] = []; + var inferenceContextCount = 0; + + var emptyStringType = getStringLiteralType(""); + var zeroType = getNumberLiteralType(0); + var zeroBigIntType = getBigIntLiteralType({ negative: false, base10Value: "0" }); + + var resolutionTargets: TypeSystemEntity[] = []; + var resolutionResults: boolean[] = []; + var resolutionPropertyNames: TypeSystemPropertyName[] = []; + var resolutionStart = 0; + var inVarianceComputation = false; + + var suggestionCount = 0; + var maximumSuggestionCount = 10; + var mergedSymbols: Symbol[] = []; + var symbolLinks: SymbolLinks[] = []; + var nodeLinks: NodeLinks[] = []; + var flowLoopCaches: Map[] = []; + var flowLoopNodes: FlowNode[] = []; + var flowLoopKeys: string[] = []; + var flowLoopTypes: Type[][] = []; + var sharedFlowNodes: FlowNode[] = []; + var sharedFlowTypes: FlowType[] = []; + var flowNodeReachable: (boolean | undefined)[] = []; + var flowNodePostSuper: (boolean | undefined)[] = []; + var potentialThisCollisions: Node[] = []; + var potentialNewTargetCollisions: Node[] = []; + var potentialWeakMapSetCollisions: Node[] = []; + var potentialReflectCollisions: Node[] = []; + var potentialUnusedRenamedBindingElementsInTypes: BindingElement[] = []; + var awaitedTypeStack: number[] = []; + var reverseMappedSourceStack: Type[] = []; + var reverseMappedTargetStack: Type[] = []; + var reverseExpandingFlags = ExpandingFlags.None; + + var diagnostics = createDiagnosticCollection(); + var suggestionDiagnostics = createDiagnosticCollection(); + + var typeofType = createTypeofType(); + + var _jsxNamespace: __String; + var _jsxFactoryEntity: EntityName | undefined; + + var subtypeRelation = new Map(); + var strictSubtypeRelation = new Map(); + var assignableRelation = new Map(); + var comparableRelation = new Map(); + var identityRelation = new Map(); + var enumRelation = new Map(); + + // 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". + var 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"], + ]; + /* eslint-enable no-var */ + + initializeTypeChecker(); + + return checker; + + function isDefinitelyReferenceToGlobalSymbolObject(node: Node): boolean { + if (!isPropertyAccessExpression(node)) return false; + if (!isIdentifier(node.name)) return false; + if (!isPropertyAccessExpression(node.expression) && !isIdentifier(node.expression)) return false; + if (isIdentifier(node.expression)) { + // Exactly `Symbol.something` and `Symbol` either does not resolve or definitely resolves to the global Symbol + return idText(node.expression) === "Symbol" && getResolvedSymbol(node.expression) === (getGlobalSymbol("Symbol" as __String, SymbolFlags.Value | SymbolFlags.ExportValue, /*diagnostic*/ undefined) || unknownSymbol); + } + if (!isIdentifier(node.expression.expression)) return false; + // Exactly `globalThis.Symbol.something` and `globalThis` resolves to the global `globalThis` + return idText(node.expression.name) === "Symbol" && idText(node.expression.expression) === "globalThis" && getResolvedSymbol(node.expression.expression) === globalThisSymbol; + } + + 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 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, isEntityName); + 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; + } + } + else { + const localJsxNamespace = getLocalJsxNamespace(file); + if (localJsxNamespace) { + return file.localJsxNamespace = localJsxNamespace; + } + } + } + } + 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); + } + } + 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, isEntityName); + if (file.localJsxFactory) { + return file.localJsxNamespace = getFirstIdentifier(file.localJsxFactory).escapedText; + } + } + } + + function markAsSynthetic(node: T): VisitResult { + setTextRangePosEnd(node, -1, -1); + return visitEachChildWorker(node, markAsSynthetic, /*context*/ undefined); + } + + function getEmitResolver(sourceFile: SourceFile, cancellationToken: CancellationToken, skipDiagnostics?: boolean) { + // 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. + if (!skipDiagnostics) getDiagnostics(sourceFile, cancellationToken); + return emitResolver; + } + + function lookupOrIssueError(location: Node | undefined, message: DiagnosticMessage, ...args: DiagnosticArguments): Diagnostic { + const diagnostic = location + ? createDiagnosticForNode(location, message, ...args) + : createCompilerDiagnostic(message, ...args); + const existing = diagnostics.lookup(diagnostic); + if (existing) { + return existing; + } + else { + diagnostics.add(diagnostic); + return diagnostic; + } + } + + function errorSkippedOn(key: keyof CompilerOptions, location: Node | undefined, message: DiagnosticMessage, ...args: DiagnosticArguments): Diagnostic { + const diagnostic = error(location, message, ...args); + diagnostic.skippedOn = key; + return diagnostic; + } + + function createError(location: Node | undefined, message: DiagnosticMessage, ...args: DiagnosticArguments): Diagnostic { + return location + ? createDiagnosticForNode(location, message, ...args) + : createCompilerDiagnostic(message, ...args); + } + + function error(location: Node | undefined, message: DiagnosticMessage, ...args: DiagnosticArguments): Diagnostic { + const diagnostic = createError(location, message, ...args); + diagnostics.add(diagnostic); + return diagnostic; + } + + function addErrorOrSuggestion(isError: boolean, diagnostic: Diagnostic) { + if (isError) { + diagnostics.add(diagnostic); + } + else { + suggestionDiagnostics.add({ ...diagnostic, category: DiagnosticCategory.Suggestion }); + } + } + function errorOrSuggestion(isError: boolean, location: Node, message: DiagnosticMessage | DiagnosticMessageChain, ...args: DiagnosticArguments): 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, ...args) : createDiagnosticForFileFromMessageChain(file, message)); // eslint-disable-line local/no-in-operator + return; + } + addErrorOrSuggestion(isError, "message" in message ? createDiagnosticForNode(location, message, ...args) : createDiagnosticForNodeFromMessageChain(getSourceFileOfNode(location), location, message)); // eslint-disable-line local/no-in-operator + } + + function errorAndMaybeSuggestAwait( + location: Node, + maybeMissingAwait: boolean, + message: DiagnosticMessage, + ...args: DiagnosticArguments + ): Diagnostic { + const diagnostic = error(location, message, ...args); + if (maybeMissingAwait) { + const related = createDiagnosticForNode(location, Diagnostics.Did_you_forget_to_use_await); + addRelatedInfo(diagnostic, related); + } + return diagnostic; + } + + 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 isDeprecatedSymbol(symbol: Symbol) { + const parentSymbol = getParentOfSymbol(symbol); + if (parentSymbol && length(symbol.declarations) > 1) { + return parentSymbol.flags & SymbolFlags.Interface ? some(symbol.declarations, isDeprecatedDeclaration) : every(symbol.declarations, isDeprecatedDeclaration); + } + return !!symbol.valueDeclaration && isDeprecatedDeclaration(symbol.valueDeclaration) + || length(symbol.declarations) && every(symbol.declarations, isDeprecatedDeclaration); + } + + function isDeprecatedDeclaration(declaration: Declaration) { + return !!(getCombinedNodeFlagsCached(declaration) & NodeFlags.Deprecated); + } + + function addDeprecatedSuggestion(location: Node, declarations: Node[], deprecatedEntity: string) { + const diagnostic = createDiagnosticForNode(location, Diagnostics._0_is_deprecated, deprecatedEntity); + return addDeprecatedSuggestionWorker(declarations, diagnostic); + } + + 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 createSymbol(flags: SymbolFlags, name: __String, checkFlags?: CheckFlags) { + symbolCount++; + const symbol = new Symbol(flags | SymbolFlags.Transient, name) as TransientSymbol; + symbol.links = new SymbolLinks() as TransientSymbolLinks; + symbol.links.checkFlags = checkFlags || CheckFlags.None; + return symbol; + } + + function createParameter(name: __String, type: Type) { + const symbol = createSymbol(SymbolFlags.FunctionScopedVariable, name); + symbol.links.type = type; + return symbol; + } + + function createProperty(name: __String, type: Type) { + const symbol = createSymbol(SymbolFlags.Property, name); + symbol.links.type = type; + return symbol; + } + + 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 recordMergedSymbol(target: Symbol, source: Symbol) { + if (!source.mergeId) { + source.mergeId = nextMergeId; + nextMergeId++; + } + mergedSymbols[source.mergeId] = target; + } + + function cloneSymbol(symbol: Symbol): TransientSymbol { + 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; + } + + /** + * 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; + } + if ( + !(resolvedTarget.flags & getExcludedSymbolFlags(source.flags)) || + (source.flags | resolvedTarget.flags) & SymbolFlags.Assignment + ) { + target = cloneSymbol(resolvedTarget); + } + else { + reportMergeSymbolError(target, source); + return source; + } + } + // 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 { + reportMergeSymbolError(target, source); + } + return target; + + function reportMergeSymbolError(target: Symbol, source: Symbol) { + 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}`, (): DuplicateInfoForFiles => ({ firstFile, secondFile, conflictingSymbols: new Map() })); + const conflictingSymbolInfo = getOrUpdate(filesDuplicates.conflictingSymbols, symbolName, (): DuplicateInfoForSymbol => ({ isBlockScoped: isEitherBlockScoped, firstFileLocations: [], secondFileLocations: [] })); + 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); + } + } + + 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 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 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; + } + + 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); + } + 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); + } + } + 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 addUndefinedToGlobalsOrErrorOnRedeclaration() { + const name = undefinedSymbol.escapedName; + const targetSymbol = globals.get(name); + if (targetSymbol) { + forEach(targetSymbol.declarations, declaration => { + // checkTypeNameIsReserved will have added better diagnostics for type declarations. + if (!isTypeDeclaration(declaration)) { + diagnostics.add(createDiagnosticForNode(declaration, Diagnostics.Declaration_name_conflicts_with_built_in_global_identifier_0, unescapeLeadingUnderscores(name))); + } + }); + } + else { + globals.set(name, undefinedSymbol); + } + } + + function getSymbolLinks(symbol: Symbol): SymbolLinks { + if (symbol.flags & SymbolFlags.Transient) return (symbol as TransientSymbol).links; + const id = getSymbolId(symbol); + return symbolLinks[id] ??= new SymbolLinks(); + } + + 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) { + if (symbol.flags & meaning) { + return symbol; + } + if (symbol.flags & SymbolFlags.Alias) { + const targetFlags = getSymbolFlags(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. + } + + /** + * 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: ParameterPropertyDeclaration, 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); + + if (parameterSymbol && propertySymbol) { + return [parameterSymbol, propertySymbol]; + } + + return Debug.fail("There should exist two symbols, one as property declaration and one as parameter declaration"); + } + + 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)) || + (!compilerOptions.outFile) || + 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); + } + + // deferred usage in a type context is always OK regardless of the usage position: + if (!!(usage.flags & NodeFlags.JSDoc) || isInTypeQuery(usage) || isInAmbientOrTypeNode(usage)) { + 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); + } + 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 (isClassLike(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]() {} }) + // or when used within a decorator in the class (e.g. `@dec(A.x) class A { static x = "x" }`), + // except when used in a function that is not an IIFE (e.g., `@dec(() => A.x) class A { ... }`) + const container = findAncestor(usage, n => + n === declaration ? "quit" : + isComputedPropertyName(n) ? n.parent.parent === declaration : + !legacyDecorators && isDecorator(n) && (n.parent === declaration || + isMethodDeclaration(n.parent) && n.parent.parent === declaration || + isGetOrSetAccessorDeclaration(n.parent) && n.parent.parent === declaration || + isPropertyDeclaration(n.parent) && n.parent.parent === declaration || + isParameter(n.parent) && n.parent.parent.parent === declaration)); + if (!container) { + return true; + } + if (!legacyDecorators && isDecorator(container)) { + return !!findAncestor(usage, n => n === container ? "quit" : isFunctionLike(n) && !getImmediatelyInvokedFunctionExpression(n)); + } + return false; + } + 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 emitStandardClassFields when bar is a parameter property + return !(emitStandardClassFields + && getContainingClass(declaration) === getContainingClass(usage) + && isUsedInFunctionOrInstanceProperty(usage, declaration)); + } + return true; + } + + // 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 emitStandardClassFields: 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) + 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 (isUsedInFunctionOrInstanceProperty(usage, declaration)) { + if ( + emitStandardClassFields + && getContainingClass(declaration) + && (isPropertyDeclaration(declaration) || isParameterPropertyDeclaration(declaration, declaration.parent)) + ) { + return !isPropertyImmediatelyReferencedWithinDeclaration(declaration, usage, /*stopAtAnyPropertyDeclaration*/ true); + } + else { + return true; + } + } + return false; + + 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); + } + + 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(getSymbolOfDeclaration(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; + } + } + } + } + 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; + } + } + + function getRequiresScopeChangeCache(node: FunctionLikeDeclaration) { + return getNodeLinks(node).declarationRequiresScopeChange; + } + function setRequiresScopeChangeCache(node: FunctionLikeDeclaration, value: boolean) { + getNodeLinks(node).declarationRequiresScopeChange = value; + } + // 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(errorLocation: Node | undefined, name: __String, propertyWithInvalidInitializer: PropertyDeclaration, result: Symbol | undefined) { + if (!emitStandardClassFields) { + if (errorLocation && !result && checkAndReportErrorForMissingPrefix(errorLocation, name, name)) { + return true; + } + // 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 emitStandardClassFields 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(name), + ); + return true; + } + return false; + } + function onFailedToResolveSymbol( + errorLocation: Node | undefined, + nameArg: __String | Identifier, + meaning: SymbolFlags, + nameNotFoundMessage: DiagnosticMessage, + ) { + const name = isString(nameArg) ? nameArg : (nameArg as Identifier).escapedText; + addLazyDiagnostic(() => { + if ( + !errorLocation || + errorLocation.parent.kind !== SyntaxKind.JSDocLink && + !checkAndReportErrorForMissingPrefix(errorLocation, name, nameArg) && + !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 && suggestionCount < maximumSuggestionCount) { + suggestion = getSuggestedSymbolForNonexistentSymbol(errorLocation, 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(errorLocation, 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); + diagnostic.canonicalHead = getCanonicalDiagnostic(nameNotFoundMessage, diagnosticName(nameArg)); + 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++; + } + }); + } + + function onSuccessfullyResolvedSymbol( + errorLocation: Node | undefined, + result: Symbol, + meaning: SymbolFlags, + lastLocation: Node | undefined, + associatedDeclarationForContainingInitializerOrBindingName: ParameterDeclaration | BindingElement | undefined, + withinDeferredContext: boolean, + ) { + addLazyDiagnostic(() => { + const name = result.escapedName; + const isInExternalModule = lastLocation && isSourceFile(lastLocation) && isExternalOrCommonJsModule(lastLocation); + // 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 (isInExternalModule && (meaning & SymbolFlags.Value) === SymbolFlags.Value && !(errorLocation!.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 (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 === getSymbolOfDeclaration(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 && getSymbol(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 (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 || typeOnlyDeclaration.kind === SyntaxKind.ExportDeclaration || typeOnlyDeclaration.kind === SyntaxKind.NamespaceExport + ? 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, + ); + } + } + + // Look at 'compilerOptions.isolatedModules' and not 'getIsolatedModules(...)' (which considers 'verbatimModuleSyntax') + // here because 'verbatimModuleSyntax' will already have an error for importing a type without 'import type'. + if (compilerOptions.isolatedModules && result && isInExternalModule && (meaning & SymbolFlags.Value) === SymbolFlags.Value) { + const isGlobal = getSymbol(globals, name, meaning) === result; + const nonValueSymbol = isGlobal && isSourceFile(lastLocation) && lastLocation.locals && getSymbol(lastLocation.locals, name, ~SymbolFlags.Value); + if (nonValueSymbol) { + const importDecl = nonValueSymbol.declarations?.find(d => d.kind === SyntaxKind.ImportSpecifier || d.kind === SyntaxKind.ImportClause || d.kind === SyntaxKind.NamespaceImport || d.kind === SyntaxKind.ImportEqualsDeclaration); + if (importDecl && !isTypeOnlyImportDeclaration(importDecl)) { + error(importDecl, Diagnostics.Import_0_conflicts_with_global_value_used_in_this_file_so_must_be_declared_with_a_type_only_import_when_isolatedModules_is_enabled, unescapeLeadingUnderscores(name)); + } + } + } + }); + } + + function addTypeOnlyDeclarationRelatedInfo(diagnostic: Diagnostic, typeOnlyDeclaration: TypeOnlyCompatibleAliasDeclaration | undefined, unescapedName: string) { + if (!typeOnlyDeclaration) return diagnostic; + return addRelatedInfo( + diagnostic, + createDiagnosticForNode( + typeOnlyDeclaration, + typeOnlyDeclaration.kind === SyntaxKind.ExportSpecifier || typeOnlyDeclaration.kind === SyntaxKind.ExportDeclaration || typeOnlyDeclaration.kind === SyntaxKind.NamespaceExport + ? Diagnostics._0_was_exported_here + : Diagnostics._0_was_imported_here, + unescapedName, + ), + ); + } + + function diagnosticName(nameArg: __String | Identifier | PrivateIdentifier) { + return isString(nameArg) ? unescapeLeadingUnderscores(nameArg as __String) : declarationNameToString(nameArg as Identifier); + } + + 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, /*includeClassComputedPropertyName*/ false); + let location: Node = container; + while (location) { + if (isClassLike(location.parent)) { + const classSymbol = getSymbolOfDeclaration(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; + } + + // 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; + } + + 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; + } + /** + * 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, /*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; + } + } + + 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, /*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 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 checkAndReportErrorForUsingTypeAsValue(errorLocation: Node, name: __String, meaning: SymbolFlags): boolean { + if (meaning & SymbolFlags.Value) { + if (isPrimitiveTypeName(name)) { + const grandparent = errorLocation.parent.parent; + if (grandparent && grandparent.parent && isHeritageClause(grandparent)) { + const heritageKind = grandparent.token; + const containerKind = grandparent.parent.kind; + if (containerKind === SyntaxKind.InterfaceDeclaration && heritageKind === SyntaxKind.ExtendsKeyword) { + error(errorLocation, Diagnostics.An_interface_cannot_extend_a_primitive_type_like_0_It_can_only_extend_other_named_object_types, unescapeLeadingUnderscores(name)); + } + else if (containerKind === SyntaxKind.ClassDeclaration && heritageKind === SyntaxKind.ExtendsKeyword) { + error(errorLocation, Diagnostics.A_class_cannot_extend_a_primitive_type_like_0_Classes_can_only_extend_constructable_values, unescapeLeadingUnderscores(name)); + } + else if (containerKind === SyntaxKind.ClassDeclaration && heritageKind === SyntaxKind.ImplementsKeyword) { + error(errorLocation, Diagnostics.A_class_cannot_implement_a_primitive_type_like_0_It_can_only_implement_other_named_object_types, 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, /*isUse*/ false)); + const allFlags = symbol && getSymbolFlags(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; + } + } + 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 checkAndReportErrorForUsingNamespaceAsTypeOrValue(errorLocation: Node, name: __String, meaning: SymbolFlags): boolean { + if (meaning & (SymbolFlags.Value & ~SymbolFlags.Type)) { + const symbol = resolveSymbol(resolveName(errorLocation, name, SymbolFlags.NamespaceModule, /*nameNotFoundMessage*/ 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, /*isUse*/ false)); + if (symbol) { + error(errorLocation, Diagnostics.Cannot_use_namespace_0_as_a_type, unescapeLeadingUnderscores(name)); + return true; + } + } + 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), + ); + + 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) { + diagnosticMessage = error(errorLocation, Diagnostics.Enum_0_used_before_its_declaration, declarationName); + } + else { + Debug.assert(!!(result.flags & SymbolFlags.ConstEnum)); + if (getIsolatedModules(compilerOptions)) { + diagnosticMessage = error(errorLocation, Diagnostics.Enum_0_used_before_its_declaration, 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) || (getFunctionFlags(n) & FunctionFlags.AsyncGenerator)) ? "quit" : false)); + } + + function getAnyImportSyntax(node: Node): AnyImportOrJsDocImport | 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); + } + + /** + * 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; + } + + function checkAndReportErrorForResolvingImportAliasToTypeOnlySymbol(node: ImportEqualsDeclaration, resolved: Symbol | undefined) { + if (markSymbolOfAliasDeclarationIfTypeOnly(node, /*immediateTarget*/ undefined, resolved, /*overwriteEmpty*/ false) && !node.isTypeOnly) { + const typeOnlyDeclaration = getTypeOnlyAliasDeclaration(getSymbolOfDeclaration(node))!; + const isExport = typeOnlyDeclaration.kind === SyntaxKind.ExportSpecifier || typeOnlyDeclaration.kind === SyntaxKind.ExportDeclaration; + 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; + + // TODO: how to get name for export *? + const name = typeOnlyDeclaration.kind === SyntaxKind.ExportDeclaration ? "*" : moduleExportNameTextUnescaped(typeOnlyDeclaration.name); + addRelatedInfo(error(node.moduleReference, message), createDiagnosticForNode(typeOnlyDeclaration, relatedMessage, name)); + } + } + + 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, /*skipObjectFunctionPropertyAugment*/ true) + : moduleSymbol.exports!.get(name); + const resolved = resolveSymbol(exportSymbol, dontResolveAlias); + markSymbolOfAliasDeclarationIfTypeOnly(sourceNode, exportSymbol, resolved, /*overwriteEmpty*/ false); + return resolved; + } + + function isSyntacticDefault(node: Node) { + return ((isExportAssignment(node) && !node.isExportEquals) + || hasSyntacticModifier(node, ModifierFlags.Default) + || isExportSpecifier(node) + || isNamespaceExport(node)); + } + + function getUsageModeForExpression(usage: Expression) { + return isStringLiteralLike(usage) ? host.getModeForUsageLocation(getSourceFileOfNode(usage), usage) : undefined; + } + + function isESMFormatImportImportingCommonjsFormatFile(usageMode: ResolutionMode, targetMode: ResolutionMode) { + 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 && ModuleKind.Node16 <= moduleKind && moduleKind <= ModuleKind.NodeNext) { + 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 + } + 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; + } + // 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); + } + // 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 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) && !allowSyntheticDefaultImports) { + 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, /*overwriteEmpty*/ false); + return resolved; + } + markSymbolOfAliasDeclarationIfTypeOnly(node, exportDefaultSymbol, /*finalTarget*/ undefined, /*overwriteEmpty*/ false); + 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); + } + } + + 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 { + 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 getTargetOfNamespaceImport(node: NamespaceImport, dontResolveAlias: boolean): Symbol | undefined { + const moduleSpecifier = node.parent.parent.moduleSpecifier; + const immediate = resolveExternalModuleName(node, moduleSpecifier); + const resolved = resolveESModuleSymbol(immediate, moduleSpecifier, dontResolveAlias, /*suppressInteropError*/ 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, /*suppressInteropError*/ false); + markSymbolOfAliasDeclarationIfTypeOnly(node, immediate, resolved, /*overwriteEmpty*/ false); + return resolved; + } + + // 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); + Debug.assert(valueSymbol.declarations || typeSymbol.declarations); + 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 getExportOfModule(symbol: Symbol, nameText: __String, specifier: Declaration, dontResolveAlias: boolean): Symbol | undefined { + if (symbol.flags & SymbolFlags.Module) { + const exportSymbol = getExportsOfSymbol(symbol).get(nameText); + const resolved = resolveSymbol(exportSymbol, dontResolveAlias); + const exportStarDeclaration = getSymbolLinks(symbol).typeOnlyExportStarMap?.get(nameText); + markSymbolOfAliasDeclarationIfTypeOnly(specifier, exportSymbol, resolved, /*overwriteEmpty*/ false, exportStarDeclaration, nameText); + 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 getExternalModuleMember(node: ImportDeclaration | ExportDeclaration | VariableDeclaration | JSDocImportTag, specifier: ImportOrExportSpecifier | BindingElement | PropertyAccessExpression, dontResolveAlias = false): Symbol | undefined { + const moduleSpecifier = getExternalModuleRequireArgument(node) || (node as ImportDeclaration | ExportDeclaration | JSDocImportTag).moduleSpecifier!; + const moduleSymbol = resolveExternalModuleName(node, moduleSpecifier)!; // TODO: GH#18217 + const name = !isPropertyAccessExpression(specifier) && specifier.propertyName || specifier.name; + if (!isIdentifier(name) && name.kind !== SyntaxKind.StringLiteral) { + return undefined; + } + const nameText = moduleExportNameTextEscaped(name); + const suppressInteropError = nameText === InternalSymbolName.Default && allowSyntheticDefaultImports; + const targetSymbol = resolveESModuleSymbol(moduleSymbol, moduleSpecifier, /*dontResolveAlias*/ false, suppressInteropError); + if (targetSymbol) { + // Note: The empty string is a valid module export name: + // + // import { "" as foo } from "./foo"; + // export { foo as "" }; + // + if (nameText || name.kind === SyntaxKind.StringLiteral) { + if (isShorthandAmbientModuleSymbol(moduleSymbol)) { + return moduleSymbol; + } + + 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), nameText, /*skipObjectFunctionPropertyAugment*/ true); + } + else { + symbolFromVariable = getPropertyOfVariable(targetSymbol, nameText); + } + // if symbolFromVariable is export - get its final target + symbolFromVariable = resolveSymbol(symbolFromVariable, dontResolveAlias); + + let symbolFromModule = getExportOfModule(targetSymbol, nameText, specifier, dontResolveAlias); + if (symbolFromModule === undefined && nameText === 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: ModuleExportName) { + const moduleName = getFullyQualifiedName(moduleSymbol, node); + const declarationName = declarationNameToString(name); + const suggestion = isIdentifier(name) ? getSuggestedSymbolForNonexistentModule(name, targetSymbol) : undefined; + 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 { + reportNonExportedMember(node, name, declarationName, moduleSymbol, moduleName); + } + } + } + + function reportNonExportedMember(node: Node, name: ModuleExportName, declarationName: string, moduleSymbol: Symbol, moduleName: string): void { + const localSymbol = tryCast(moduleSymbol.valueDeclaration, canHaveLocals)?.locals?.get(moduleExportNameTextEscaped(name)); + 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))); + } + } + } + else { + error(name, Diagnostics.Module_0_has_no_exported_member_1, moduleName, declarationName); + } + } + + function reportInvalidImportEqualsExportMember(node: Node, name: ModuleExportName, 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); + } + 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); + } + } + } + + function getTargetOfImportSpecifier(node: ImportSpecifier | BindingElement, dontResolveAlias: boolean): Symbol | undefined { + if (isImportSpecifier(node) && moduleExportNameIsDefault(node.propertyName || node.name)) { + const specifier = getModuleSpecifierForImportOrExport(node); + const moduleSymbol = specifier && resolveExternalModuleName(node, specifier); + if (moduleSymbol) { + return getTargetofModuleDefault(moduleSymbol, node, dontResolveAlias); + } + } + 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 getCommonJSPropertyAccess(node: Node) { + if (isVariableDeclaration(node) && node.initializer && isPropertyAccessExpression(node.initializer)) { + return node.initializer; + } + } + + function getTargetOfNamespaceExportDeclaration(node: NamespaceExportDeclaration, dontResolveAlias: boolean): Symbol | undefined { + if (canHaveSymbol(node.parent)) { + const resolved = resolveExternalModuleSymbol(node.parent.symbol, dontResolveAlias); + markSymbolOfAliasDeclarationIfTypeOnly(node, /*immediateTarget*/ undefined, resolved, /*overwriteEmpty*/ false); + return resolved; + } + } + + function getTargetOfExportSpecifier(node: ExportSpecifier, meaning: SymbolFlags, dontResolveAlias?: boolean) { + const name = node.propertyName || node.name; + if (moduleExportNameIsDefault(name)) { + 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) : + name.kind === SyntaxKind.StringLiteral ? undefined : // Skip for invalid syntax like this: export { "x" } + resolveEntityName(name, meaning, /*ignoreErrors*/ false, dontResolveAlias); + markSymbolOfAliasDeclarationIfTypeOnly(node, /*immediateTarget*/ undefined, resolved, /*overwriteEmpty*/ false); + return resolved; + } + + 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 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; + } + checkExpressionCached(expression); + return getNodeLinks(expression).resolvedSymbol; + } + + function getTargetOfAccessExpression(node: AccessExpression, dontRecursivelyResolve: boolean): Symbol | undefined { + if (!(isBinaryExpression(node.parent) && node.parent.left === node && node.parent.operatorToken.kind === SyntaxKind.EqualsToken)) { + return undefined; + } + + 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(); + } + } + + /** + * 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 { + error(node, Diagnostics.Circular_definition_of_import_alias_0, symbolToString(symbol)); + } + } + 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; + } + + /** + * 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 getSymbolFlags(symbol: Symbol, excludeTypeOnlyMeanings?: boolean, excludeLocalMeanings?: boolean): SymbolFlags { + const typeOnlyDeclaration = excludeTypeOnlyMeanings && getTypeOnlyAliasDeclaration(symbol); + const typeOnlyDeclarationIsExportStar = typeOnlyDeclaration && isExportDeclaration(typeOnlyDeclaration); + const typeOnlyResolution = typeOnlyDeclaration && ( + typeOnlyDeclarationIsExportStar + ? resolveExternalModuleName(typeOnlyDeclaration.moduleSpecifier, typeOnlyDeclaration.moduleSpecifier, /*ignoreErrors*/ true) + : resolveAlias(typeOnlyDeclaration.symbol) + ); + const typeOnlyExportStarTargets = typeOnlyDeclarationIsExportStar && typeOnlyResolution ? getExportsOfModule(typeOnlyResolution) : undefined; + let flags = excludeLocalMeanings ? SymbolFlags.None : symbol.flags; + let seenSymbols; + while (symbol.flags & SymbolFlags.Alias) { + const target = getExportSymbolOfValueSymbolIfExported(resolveAlias(symbol)); + if ( + !typeOnlyDeclarationIsExportStar && target === typeOnlyResolution || + typeOnlyExportStarTargets?.get(target.escapedName) === target + ) { + break; + } + if (target === unknownSymbol) { + return SymbolFlags.All; + } + + // 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); + } + 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, + exportStarDeclaration?: ExportDeclaration & { readonly isTypeOnly: true; readonly moduleSpecifier: Expression; }, + exportStarName?: __String, + ): 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 = getSymbolOfDeclaration(aliasDeclaration); + if (isTypeOnlyImportOrExportDeclaration(aliasDeclaration)) { + const links = getSymbolLinks(sourceSymbol); + links.typeOnlyDeclaration = aliasDeclaration; + return true; + } + if (exportStarDeclaration) { + const links = getSymbolLinks(sourceSymbol); + links.typeOnlyDeclaration = exportStarDeclaration; + if (sourceSymbol.escapedName !== exportStarName) { + links.typeOnlyExportStarName = exportStarName; + } + return true; + } + + 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 (links.typeOnlyDeclaration === undefined) { + // We need to set a WIP value here to prevent reentrancy during `getImmediateAliasedSymbol` which, paradoxically, can depend on this + links.typeOnlyDeclaration = false; + const resolved = resolveSymbol(symbol); // do this before the `resolveImmediate` below, as it uses a different circularity cache and we might hide a circularity error if we blindly get the immediate alias first + // While usually the alias will have been marked during the pass by the full typecheck, we may still need to calculate the alias declaration now + markSymbolOfAliasDeclarationIfTypeOnly(symbol.declarations?.[0], getDeclarationOfAliasSymbol(symbol) && getImmediateAliasedSymbol(symbol), resolved, /*overwriteEmpty*/ true); + } + if (include === undefined) { + return links.typeOnlyDeclaration || undefined; + } + if (links.typeOnlyDeclaration) { + const resolved = links.typeOnlyDeclaration.kind === SyntaxKind.ExportDeclaration + ? resolveSymbol(getExportsOfModule(links.typeOnlyDeclaration.symbol.parent!).get(links.typeOnlyExportStarName || symbol.escapedName))! + : resolveAlias(links.typeOnlyDeclaration.symbol); + return getSymbolFlags(resolved) & include ? links.typeOnlyDeclaration : undefined; + } + return undefined; + } + + // 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 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, SymbolFlags.Value, /*nameNotFoundMessage*/ undefined, /*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; + } + 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)) { + 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, meaning, ignoreErrors || symbolFromJSPrototype ? undefined : message, /*isUse*/ true, /*excludeGlobals*/ 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; + } + else if (namespace === unknownSymbol) { + return namespace; + } + if ( + namespace.valueDeclaration && + isInJSFile(namespace.valueDeclaration) && + getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.Bundler && + 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 && (namespace.flags & SymbolFlags.Alias)) { + // `namespace` can be resolved further if there was a symbol merge with a re-export + symbol = getMergedSymbol(getSymbol(getExportsOfSymbol(resolveAlias(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; + } + + 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; + } + } + else { + Debug.assertNever(name, "Unknown entity name kind."); + } + 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, meaning, /*nameNotFoundMessage*/ undefined, /*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 = getSymbolOfDeclaration(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 = getSymbolOfDeclaration(host.parent.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 = getSymbolOfDeclaration(host.parent.parent.left as BindableStaticNameExpression); + if (symbol) { + return getDeclarationOfJSPrototypeContainer(symbol); + } + } + const sig = getEffectiveJSDocHost(node); + if (sig && isFunctionLike(sig)) { + const symbol = getSymbolOfDeclaration(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; + } + + /** + * 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_nodenext_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 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 + : (isModuleDeclaration(location) ? location : location.parent && isModuleDeclaration(location.parent) && location.parent.name === location ? location.parent : undefined)?.name || + (isLiteralImportTypeNode(location) ? location : undefined)?.argument.literal || + (isInJSFile(location) && isJSDocImportTag(location) ? location.moduleSpecifier : undefined) || + (isVariableDeclaration(location) && location.initializer && isRequireCall(location.initializer, /*requireStringLiteralLikeArgument*/ true) ? location.initializer.arguments[0] : undefined) || + findAncestor(location, isImportCall)?.arguments[0] || + findAncestor(location, isImportDeclaration)?.moduleSpecifier || + findAncestor(location, isExternalModuleImportEqualsDeclaration)?.moduleReference.expression || + findAncestor(location, isExportDeclaration)?.moduleSpecifier; + const mode = contextSpecifier && isStringLiteralLike(contextSpecifier) ? host.getModeForUsageLocation(currentSourceFile, contextSpecifier) : currentSourceFile.impliedNodeFormat; + const moduleResolutionKind = getEmitModuleResolutionKind(compilerOptions); + const resolvedModule = host.getResolvedModule(currentSourceFile, moduleReference, mode)?.resolvedModule; + const resolutionDiagnostic = resolvedModule && getResolutionDiagnostic(compilerOptions, resolvedModule, currentSourceFile); + 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 (resolvedModule.resolvedUsingTsExtension && isDeclarationFileName(moduleReference)) { + const importOrExport = findAncestor(location, isImportDeclaration)?.importClause || + findAncestor(location, or(isImportEqualsDeclaration, isExportDeclaration)); + if (importOrExport && !importOrExport.isTypeOnly || findAncestor(location, isImportCall)) { + error( + errorNode, + Diagnostics.A_declaration_file_cannot_be_imported_without_import_type_Did_you_mean_to_import_an_implementation_file_0_instead, + getSuggestedImportSource(Debug.checkDefined(tryExtractTSExtension(moduleReference))), + ); + } + } + else if (resolvedModule.resolvedUsingTsExtension && !shouldAllowImportingTsExtension(compilerOptions, currentSourceFile.fileName)) { + const importOrExport = findAncestor(location, isImportDeclaration)?.importClause || + findAncestor(location, or(isImportEqualsDeclaration, isExportDeclaration)); + if (!(importOrExport?.isTypeOnly || findAncestor(location, isImportTypeNode))) { + const tsExtension = Debug.checkDefined(tryExtractTSExtension(moduleReference)); + error(errorNode, Diagnostics.An_import_path_can_only_end_with_a_0_extension_when_allowImportingTsExtensions_is_enabled, tsExtension); + } + } + + if (sourceFile.symbol) { + if (resolvedModule.isExternalLibraryImport && !resolutionExtensionIsTSOrJson(resolvedModule.extension)) { + errorOnImplicitAnyModule(/*isError*/ false, errorNode, currentSourceFile, mode, resolvedModule, moduleReference); + } + if (moduleResolutionKind === ModuleResolutionKind.Node16 || moduleResolutionKind === ModuleResolutionKind.NodeNext) { + const isSyncImport = (currentSourceFile.impliedNodeFormat === ModuleKind.CommonJS && !findAncestor(location, isImportCall)) || !!findAncestor(location, isImportEqualsDeclaration); + const overrideHost = findAncestor(location, l => isImportTypeNode(l) || isExportDeclaration(l) || isImportDeclaration(l)) as ImportTypeNode | ImportDeclaration | ExportDeclaration | undefined; + // 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 && !hasResolutionModeOverride(overrideHost)) { + 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 { + 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( + getSourceFileOfNode(errorNode), + 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; + } + + 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, currentSourceFile, mode, 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; + } + } + + if (resolutionDiagnostic) { + error(errorNode, resolutionDiagnostic, moduleReference, resolvedModule.resolvedFileName); + } + else { + const isExtensionlessRelativePathImport = pathIsRelative(moduleReference) && !hasExtension(moduleReference); + const resolutionIsNode16OrNext = moduleResolutionKind === ModuleResolutionKind.Node16 || + moduleResolutionKind === ModuleResolutionKind.NodeNext; + if ( + !getResolveJsonModule(compilerOptions) && + fileExtensionIs(moduleReference, Extension.Json) && + moduleResolutionKind !== 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 (host.getResolvedModule(currentSourceFile, moduleReference, mode)?.alternateResult) { + const errorInfo = createModuleNotFoundChain(currentSourceFile, host, moduleReference, mode, moduleReference); + errorOrSuggestion(/*isError*/ true, errorNode, chainDiagnosticMessages(errorInfo, moduleNotFoundError, moduleReference)); + } + else { + error(errorNode, moduleNotFoundError, moduleReference); + } + } + } + } + return undefined; + + function getSuggestedImportSource(tsExtension: string) { + const importSourceWithoutExtension = removeExtension(moduleReference, tsExtension); + /** + * Direct users to import source with .js extension if outputting an ES module. + * @see https://github.com/microsoft/TypeScript/issues/42151 + */ + if (emitModuleKindIsNonNodeESM(moduleKind) || mode === ModuleKind.ESNext) { + const preferTs = isDeclarationFileName(moduleReference) && shouldAllowImportingTsExtension(compilerOptions); + const ext = tsExtension === Extension.Mts || tsExtension === Extension.Dmts ? preferTs ? ".mts" : ".mjs" : + tsExtension === Extension.Cts || tsExtension === Extension.Dmts ? preferTs ? ".cts" : ".cjs" : + preferTs ? ".ts" : ".js"; + return importSourceWithoutExtension + ext; + } + return importSourceWithoutExtension; + } + } + + function errorOnImplicitAnyModule(isError: boolean, errorNode: Node, sourceFile: SourceFile, mode: ResolutionMode, { packageId, resolvedFileName }: ResolvedModuleFull, moduleReference: string): void { + let errorInfo: DiagnosticMessageChain | undefined; + if (!isExternalModuleNameRelative(moduleReference) && packageId) { + errorInfo = createModuleNotFoundChain(sourceFile, host, moduleReference, mode, packageId.name); + } + errorOrSuggestion( + isError, + errorNode, + chainDiagnosticMessages( + errorInfo, + Diagnostics.Could_not_find_a_declaration_file_for_module_0_1_implicitly_has_an_any_type, + moduleReference, + resolvedFileName, + ), + ); + } + + function resolveExternalModuleSymbol(moduleSymbol: Symbol, dontResolveAlias?: boolean): Symbol; + function resolveExternalModuleSymbol(moduleSymbol: Symbol | undefined, dontResolveAlias?: boolean): Symbol | undefined; + function resolveExternalModuleSymbol(moduleSymbol: Symbol | undefined, 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); + }); + if (merged === exported) { + // We just mutated a symbol, reset any cached links we may have already set + // (Notably required to make late bound members appear) + getSymbolLinks(merged).resolvedExports = undefined; + getSymbolLinks(merged).resolvedMembers = undefined; + } + getSymbolLinks(merged).cjsExportMerged = merged; + return links.cjsExportMerged = merged; + } + + // 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 = type.flags & TypeFlags.StructuredType + ? getTypeWithSyntheticDefaultImportType(type, symbol, moduleSymbol!, reference) + : createDefaultPropertyWrapperForModule(symbol, symbol.parent); + return cloneTypeAsModuleType(symbol, moduleType, referenceParent); + } + } + } + } + return symbol; + } + + /** + * 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.links.target = symbol; + result.links.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.links.type = createAnonymousType(result, resolvedModuleType.members, emptyArray, emptyArray, resolvedModuleType.indexInfos); + return result; + } + + function hasExportAssignmentSymbol(moduleSymbol: Symbol): boolean { + return moduleSymbol.exports!.get(InternalSymbolName.ExportEquals) !== 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); + if (shouldTreatPropertiesOfExternalModuleAsExports(type)) { + addRange(exports, getPropertiesOfType(type)); + } + } + 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 exportEquals = resolveExternalModuleSymbol(moduleSymbol); + if (exportEquals !== moduleSymbol) { + const type = getTypeOfSymbol(exportEquals); + if (shouldTreatPropertiesOfExternalModuleAsExports(type)) { + forEachPropertyOfType(type, (symbol, escapedName) => { + cb(symbol, escapedName); + }); + } + } + } + + function tryGetMemberInModuleExports(memberName: __String, moduleSymbol: Symbol): Symbol | undefined { + const symbolTable = getExportsOfModule(moduleSymbol); + if (symbolTable) { + return symbolTable.get(memberName); + } + } + + function tryGetMemberInModuleExportsAndProperties(memberName: __String, moduleSymbol: Symbol): Symbol | undefined { + const symbol = tryGetMemberInModuleExports(memberName, moduleSymbol); + if (symbol) { + return symbol; + } + + const exportEquals = resolveExternalModuleSymbol(moduleSymbol); + if (exportEquals === moduleSymbol) { + return undefined; + } + + const type = getTypeOfSymbol(exportEquals); + return shouldTreatPropertiesOfExternalModuleAsExports(type) ? getPropertyOfType(type, memberName) : undefined; + } + + 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 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); + if (!links.resolvedExports) { + const { exports, typeOnlyExportStarMap } = getExportsOfModuleWorker(moduleSymbol); + links.resolvedExports = exports; + links.typeOnlyExportStarMap = typeOnlyExportStarMap; + } + return links.resolvedExports; + } + + interface ExportCollisionTracker { + specifierText: string; + exportsWithDuplicate?: ExportDeclaration[]; + } + + type ExportCollisionTrackerTable = Map<__String, ExportCollisionTracker>; + + /** + * 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; + + const targetSymbol = target.get(id); + if (!targetSymbol) { + target.set(id, sourceSymbol); + if (lookupTable && exportNode) { + lookupTable.set(id, { + specifierText: getTextOfNode(exportNode.moduleSpecifier!), + }); + } + } + 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 getExportsOfModuleWorker(moduleSymbol: Symbol) { + const visitedSymbols: Symbol[] = []; + let typeOnlyExportStarMap: Map<__String, ExportDeclaration & { readonly isTypeOnly: true; readonly moduleSpecifier: Expression; }> | undefined; + const nonTypeOnlyNames = new Set<__String>(); + + // A module defined by an 'export=' consists of one export that needs to be resolved + moduleSymbol = resolveExternalModuleSymbol(moduleSymbol); + const exports = visit(moduleSymbol) || emptySymbols; + + if (typeOnlyExportStarMap) { + nonTypeOnlyNames.forEach(name => typeOnlyExportStarMap!.delete(name)); + } + + return { + exports, + typeOnlyExportStarMap, + }; + + // 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, exportStar?: ExportDeclaration, isTypeOnly?: boolean): SymbolTable | undefined { + if (!isTypeOnly && symbol?.exports) { + // Add non-type-only names before checking if we've visited this module, + // because we might have visited it via an 'export type *', and visiting + // again with 'export *' will override the type-onlyness of its exports. + symbol.exports.forEach((_, name) => nonTypeOnlyNames.add(name)); + } + if (!(symbol && symbol.exports && pushIfUnique(visitedSymbols, symbol))) { + return; + } + 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, node as ExportDeclaration, isTypeOnly || (node as ExportDeclaration).isTypeOnly); + 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); + } + if (exportStar?.isTypeOnly) { + typeOnlyExportStarMap ??= new Map(); + symbols.forEach((_, escapedName) => + typeOnlyExportStarMap!.set( + escapedName, + exportStar as ExportDeclaration & { readonly isTypeOnly: true; readonly moduleSpecifier: Expression; }, + ) + ); + } + 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 getSymbolOfDeclaration(node: Declaration): Symbol { + return getMergedSymbol(node.symbol && getLateBoundSymbol(node.symbol)); + } + + /** + * Get the merged symbol for a node. If you know the node is a `Declaration`, it is faster and more type safe to + * use use `getSymbolOfDeclaration` instead. + */ + function getSymbolOfNode(node: Node): Symbol | undefined { + return canHaveSymbol(node) ? getSymbolOfDeclaration(node) : undefined; + } + + function getParentOfSymbol(symbol: Symbol): Symbol | undefined { + return getMergedSymbol(symbol.parent && getLateBoundSymbol(symbol.parent)); + } + + function getFunctionExpressionParentSymbolOrSymbol(symbol: Symbol) { + return symbol.valueDeclaration?.kind === SyntaxKind.ArrowFunction || symbol.valueDeclaration?.kind === SyntaxKind.FunctionExpression + ? getSymbolOfNode(symbol.valueDeclaration.parent) || symbol + : symbol; + } + + 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 = getSymbolOfDeclaration(file); + const ref = getAliasForSymbolInContainer(sym, symbol); + if (!ref) continue; + results = append(results, sym); + } + return links.extendedContainers = results || emptyArray; + } + + /** + * 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)) { + return getWithAlternativeContainers(container); + } + const candidates = mapDefined(symbol.declarations, d => { + if (!isAmbientModule(d) && d.parent) { + // direct children of a module + if (hasNonGlobalAugmentationExternalModuleSymbol(d.parent)) { + return getSymbolOfDeclaration(d.parent as Declaration); + } + // export ='d member of an ambient module + if (isModuleBlock(d.parent) && d.parent.parent && resolveExternalModuleSymbol(getSymbolOfDeclaration(d.parent.parent)) === symbol) { + return getSymbolOfDeclaration(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 getSymbolOfDeclaration(getSourceFileOfNode(d)); + } + checkExpressionCached(d.parent.left.expression); + return getNodeLinks(d.parent.left.expression).resolvedSymbol; + } + }); + if (!length(candidates)) { + return undefined; + } + const containers = mapDefined(candidates, candidate => getAliasForSymbolInContainer(candidate, symbol) ? candidate : undefined); + + let bestContainers: Symbol[] = []; + let alternativeContainers: Symbol[] = []; + + for (const container of containers) { + const [bestMatch, ...rest] = getWithAlternativeContainers(container); + bestContainers = append(bestContainers, bestMatch); + alternativeContainers = addRange(alternativeContainers, rest); + } + + return concatenate(bestContainers, alternativeContainers); + + function getWithAlternativeContainers(container: Symbol) { + 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, /*useOnlyExternalAliasing*/ 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 fileSymbolIfFileSymbolExportEqualsContainer(d: Declaration) { + return container && getFileSymbolIfFileSymbolExportEqualsContainer(d, container); + } + } + + 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 getSymbolOfDeclaration(firstDecl.parent); + } + } + } + + 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 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; + } + 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; + } + }); + } + + /** + * 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; + } + } + + 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 symbolIsValue(symbol: Symbol, includeTypeOnlyMembers?: boolean): boolean { + return !!( + symbol.flags & SymbolFlags.Value || + symbol.flags & SymbolFlags.Alias && getSymbolFlags(symbol, !includeTypeOnlyMembers) & SymbolFlags.Value + ); + } + + function createType(flags: TypeFlags): Type { + const result = new Type(checker, flags); + typeCount++; + result.id = typeCount; + tracing?.recordType(result); + return result; + } + + function createTypeWithSymbol(flags: TypeFlags, symbol: Symbol): Type { + const result = createType(flags); + result.symbol = symbol; + return result; + } + + function createOriginType(flags: TypeFlags): Type { + return new Type(checker, flags); + } + + function createIntrinsicType(kind: TypeFlags, intrinsicName: string, objectFlags = ObjectFlags.None, debugIntrinsicName?: string): IntrinsicType { + checkIntrinsicName(intrinsicName, debugIntrinsicName); + const type = createType(kind) as IntrinsicType; + type.intrinsicName = intrinsicName; + type.debugIntrinsicName = debugIntrinsicName; + type.objectFlags = objectFlags | ObjectFlags.CouldContainTypeVariablesComputed | ObjectFlags.IsGenericTypeComputed | ObjectFlags.IsUnknownLikeUnionComputed | ObjectFlags.IsNeverIntersectionComputed; + return type; + } + + function checkIntrinsicName(name: string, debug: string | undefined) { + const key = `${name},${debug ?? ""}`; + if (seenIntrinsicNames.has(key)) { + Debug.fail(`Duplicate intrinsic type name ${name}${debug ? ` (${debug})` : ""}; you may need to pass a name to createIntrinsicType.`); + } + seenIntrinsicNames.add(key); + } + + 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; + } + + function createTypeofType() { + return getUnionType(arrayFrom(typeofNEFacts.keys(), getStringLiteralType)); + } + + function createTypeParameter(symbol?: Symbol) { + return createTypeWithSymbol(TypeFlags.TypeParameter, symbol!) as TypeParameter; + } + + // 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 getNamedMembers(members: SymbolTable): Symbol[] { + let result: Symbol[] | undefined; + members.forEach((symbol, id) => { + if (isNamedMember(symbol, id)) { + (result || (result = [])).push(symbol); + } + }); + return result || emptyArray; + } + + function isNamedMember(member: Symbol, escapedName: __String) { + return !isReservedMemberName(escapedName) && symbolIsValue(member); + } + + function getNamedOrIndexSignatureMembers(members: SymbolTable): Symbol[] { + const result = getNamedMembers(members); + const index = getIndexSymbolFromSymbolTable(members); + return index ? concatenate(result, [index]) : 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 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 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 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 (canHaveLocals(location) && 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 = getSymbolOfDeclaration(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: Map<__String, Symbol> | undefined; + // TODO: Should this filtered table be cached in some way? + (getSymbolOfDeclaration(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; + } + } + + return callback(globals, /*ignoreQualification*/ undefined, /*isLocalNameLookup*/ true); + } + + 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 getAccessibleSymbolChain(symbol: Symbol | undefined, enclosingDeclaration: Node | undefined, meaning: SymbolFlags, useOnlyExternalAliasing: boolean, visitedSymbolTablesMap = 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); + } + + 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; + + /** + * @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; + } + + const result = trySymbolTable(symbols, ignoreQualification, isLocalNameLookup); + visitedSymbolTables!.pop(); + return result; + } + + 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)) + ) { + 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), /*resolvedAliasSymbol*/ undefined, ignoreQualification)) { + return [symbol!]; + } + } + }); + + // 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 getCandidateListForSymbol(symbolFromSymbolTable: Symbol, resolvedImportedSymbol: Symbol, ignoreQualification: boolean | undefined) { + if (isAccessible(symbolFromSymbolTable, resolvedImportedSymbol, ignoreQualification)) { + return [symbolFromSymbolTable]; + } + + // 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 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 ? getSymbolFlags(symbolFromSymbolTable) : symbolFromSymbolTable.flags; + if (flags & meaning) { + qualify = true; + return true; + } + + // 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; + } + + 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, + }; + } + } + + // 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 + + const containers = getContainersOfSymbol(symbol, enclosingDeclaration, meaning); + const parentResult = isAnySymbolAccessible(containers, enclosingDeclaration, initialSymbol, initialSymbol === symbol ? getQualifiedLeftMeaning(meaning) : meaning, shouldComputeAliasesToMakeVisible, allowModules); + if (parentResult) { + return parentResult; + } + } + + if (earlyModuleBail) { + return { + accessibility: SymbolAccessibility.Accessible, + }; + } + + if (hadAccessibleChain) { + return { + accessibility: SymbolAccessibility.NotAccessible, + errorSymbolName: symbolToString(initialSymbol, enclosingDeclaration, meaning), + errorModuleName: hadAccessibleChain !== initialSymbol ? symbolToString(hadAccessibleChain, enclosingDeclaration, SymbolFlags.Namespace) : undefined, + }; + } + } + + /** + * 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); + } + + 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; + } + + // 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, + }; + } + } + + // Just a local name that is not accessible + return { + accessibility: SymbolAccessibility.NotAccessible, + errorSymbolName: symbolToString(symbol, enclosingDeclaration, meaning), + }; + } + + return { accessibility: SymbolAccessibility.Accessible }; + } + + function getExternalModuleContainer(declaration: Node) { + const node = findAncestor(declaration, hasExternalModuleSymbol); + return node && getSymbolOfDeclaration(node as AmbientModuleDeclaration | SourceFile); + } + + 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); + } + } + + // Declaration is not visible + return false; + } + + 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 getMeaningOfEntityNameReference(entityName: EntityNameOrEntityNameExpression): SymbolFlags { + // 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 || + entityName.parent.kind === SyntaxKind.TypePredicate && (entityName.parent as TypePredicateNode).parameterName === entityName + ) { + // Typeof value + meaning = SymbolFlags.Value | SymbolFlags.ExportValue; + } + else if ( + entityName.kind === SyntaxKind.QualifiedName || entityName.kind === SyntaxKind.PropertyAccessExpression || + entityName.parent.kind === SyntaxKind.ImportEqualsDeclaration || + (entityName.parent.kind === SyntaxKind.QualifiedName && (entityName.parent as QualifiedName).left === entityName) || + (entityName.parent.kind === SyntaxKind.PropertyAccessExpression && (entityName.parent as PropertyAccessExpression).expression === entityName) || + (entityName.parent.kind === SyntaxKind.ElementAccessExpression && (entityName.parent as ElementAccessExpression).expression === entityName) + ) { + // 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 meaning; + } + + function isEntityNameVisible(entityName: EntityNameOrEntityNameExpression, enclosingDeclaration: Node, shouldComputeAliasToMakeVisible = true): SymbolVisibilityResult { + const meaning = getMeaningOfEntityNameReference(entityName); + const firstIdentifier = getFirstIdentifier(entityName); + const symbol = resolveName(enclosingDeclaration, firstIdentifier.escapedText, meaning, /*nameNotFoundMessage*/ undefined, /*isUse*/ false); + if (symbol && symbol.flags & SymbolFlags.TypeParameter && meaning & SymbolFlags.Type) { + return { accessibility: SymbolAccessibility.Accessible }; + } + if (!symbol && isThisIdentifier(firstIdentifier) && isSymbolAccessible(getSymbolOfDeclaration(getThisContainer(firstIdentifier, /*includeArrowFunctions*/ false, /*includeClassComputedPropertyName*/ false)), firstIdentifier, meaning, /*shouldComputeAliasesToMakeVisible*/ false).accessibility === SymbolAccessibility.Accessible) { + return { accessibility: SymbolAccessibility.Accessible }; + } + + if (!symbol) { + return { + accessibility: SymbolAccessibility.NotResolved, + errorSymbolName: getTextOfNode(firstIdentifier), + errorNode: firstIdentifier, + }; + } + // Verify if the symbol is accessible + return hasVisibleDeclarations(symbol, shouldComputeAliasToMakeVisible) || { + 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 symbolToStringWorker(writer: EmitTextWriter) { + const entity = builder(symbol, meaning!, enclosingDeclaration, nodeFlags)!; // TODO: GH#18217 + // add neverAsciiEscape for GH#39027 + const printer = enclosingDeclaration?.kind === SyntaxKind.SourceFile + ? createPrinterWithRemoveCommentsNeverAsciiEscape() + : createPrinterWithRemoveComments(); + const sourceFile = enclosingDeclaration && getSourceFileOfNode(enclosingDeclaration); + printer.writeNode(EmitHint.Unspecified, entity, /*sourceFile*/ sourceFile, writer); + return writer; + } + } + + 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; + } + const sig = nodeBuilder.signatureToSignatureDeclaration(signature, sigOutput, enclosingDeclaration, toNodeBuilderFlags(flags) | NodeBuilderFlags.IgnoreErrors | NodeBuilderFlags.WriteTypeParametersInQualifiedName); + const printer = createPrinterWithRemoveCommentsOmitTrailingSemicolon(); + const sourceFile = enclosingDeclaration && getSourceFileOfNode(enclosingDeclaration); + printer.writeNode(EmitHint.Unspecified, sig!, /*sourceFile*/ sourceFile, getTrailingSemicolonDeferringWriter(writer)); // TODO: GH#18217 + return writer; + } + } + + 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)); + 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 printer = type !== unresolvedType ? createPrinterWithRemoveComments() : createPrinterWithDefaults(); + 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 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 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 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))); + } + /** + * Same as getTypeFromTypeNode, but for use in createNodeBuilder + * Inside createNodeBuilder we shadow getTypeFromTypeNode to make sure anyone using this function will call the local version that does type mapping if appropriate + * This function is used to still be able to call the original getTypeFromTypeNode from the local scope version of getTypeFromTypeNode + */ + function getTypeFromTypeNodeWithoutContext(node: TypeNode) { + return getTypeFromTypeNode(node); + } + function createNodeBuilder() { + return { + typeToTypeNode: (type: Type, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => withContext(enclosingDeclaration, flags, tracker, context => typeToTypeNodeHelper(type, context)), + typePredicateToTypePredicateNode: (typePredicate: TypePredicate, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => withContext(enclosingDeclaration, flags, tracker, context => typePredicateToTypePredicateNodeHelper(typePredicate, context)), + expressionOrTypeToTypeNode: (expr: Expression | JsxAttributeValue | undefined, type: Type, addUndefined?: boolean, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => withContext(enclosingDeclaration, flags, tracker, context => expressionOrTypeToTypeNode(context, expr, type, addUndefined)), + serializeTypeForDeclaration: (declaration: Declaration, type: Type, symbol: Symbol, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => withContext(enclosingDeclaration, flags, tracker, context => serializeTypeForDeclaration(context, declaration, type, symbol)), + serializeReturnTypeForSignature: (signature: Signature, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => withContext(enclosingDeclaration, flags, tracker, context => serializeReturnTypeForSignature(context, signature)), + 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) => withContext(enclosingDeclaration, flags, tracker, context => symbolTableToDeclarationStatements(symbolTable, context)), + symbolToNode: (symbol: Symbol, meaning: SymbolFlags, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => withContext(enclosingDeclaration, flags, tracker, context => symbolToNode(symbol, context, meaning)), + }; + + function getTypeFromTypeNode(context: NodeBuilderContext, node: TypeNode, noMappedTypes?: false): Type; + function getTypeFromTypeNode(context: NodeBuilderContext, node: TypeNode, noMappedTypes: true): Type | undefined; + function getTypeFromTypeNode(context: NodeBuilderContext, node: TypeNode, noMappedTypes?: boolean): Type | undefined { + const type = getTypeFromTypeNodeWithoutContext(node); + if (!context.mapper) return type; + + const mappedType = instantiateType(type, context.mapper); + return noMappedTypes && mappedType !== type ? undefined : mappedType; + } + + /** + * Unlike the utilities `setTextRange`, this checks if the `location` we're trying to set on `range` is within the + * same file as the active context. If not, the range is not applied. This prevents us from copying ranges across files, + * which will confuse the node printer (as it assumes all node ranges are within the current file). + * Additionally, if `range` _isn't synthetic_, or isn't in the current file, it will _copy_ it to _remove_ its' position + * information. + * + * It also calls `setOriginalNode` to setup a `.original` pointer, since you basically *always* want these in the node builder. + */ + function setTextRange(context: NodeBuilderContext, range: T, location: Node | undefined): T { + if (!nodeIsSynthesized(range) || !(range.flags & NodeFlags.Synthesized) || !context.enclosingFile || context.enclosingFile !== getSourceFileOfNode(getOriginalNode(range))) { + range = factory.cloneNode(range); // if `range` is synthesized or originates in another file, copy it so it definitely has synthetic positions + } + if (range === location) return range; + if (!location) { + return range; + } + if (!context.enclosingFile || context.enclosingFile !== getSourceFileOfNode(getOriginalNode(location))) { + return setOriginalNode(range, location); // if `location` is from another file, only set/update original pointer, and not positions, since copying text across files isn't supported by the emitter + } + return setTextRangeWorker(setOriginalNode(range, location), location); + } + + /** + * Same as expressionOrTypeToTypeNodeHelper, but also checks if the expression can be syntactically typed. + */ + function expressionOrTypeToTypeNode(context: NodeBuilderContext, expr: Expression | JsxAttributeValue | undefined, type: Type, addUndefined?: boolean) { + const oldFlags = context.flags; + if (expr && !(context.flags & NodeBuilderFlags.NoSyntacticPrinter)) { + syntacticNodeBuilder.serializeTypeOfExpression(expr, context, addUndefined); + } + context.flags |= NodeBuilderFlags.NoSyntacticPrinter; + const result = expressionOrTypeToTypeNodeHelper(context, expr, type, addUndefined); + context.flags = oldFlags; + return result; + } + function expressionOrTypeToTypeNodeHelper(context: NodeBuilderContext, expr: Expression | JsxAttributeValue | undefined, type: Type, addUndefined?: boolean) { + if (expr) { + const typeNode = isAssertionExpression(expr) ? expr.type + : isJSDocTypeAssertion(expr) ? getJSDocTypeAssertionType(expr) + : undefined; + if (typeNode && !isConstTypeReference(typeNode)) { + const result = tryReuseExistingTypeNode(context, typeNode, type, expr.parent, addUndefined); + if (result) { + return result; + } + } + } + + if (addUndefined) { + type = getOptionalType(type); + } + + return typeToTypeNodeHelper(type, context); + } + + function tryReuseExistingTypeNode( + context: NodeBuilderContext, + typeNode: TypeNode, + type: Type, + host: Node, + addUndefined?: boolean, + ) { + const originalType = type; + if (addUndefined) { + type = getOptionalType(type); + } + const clone = tryReuseExistingNonParameterTypeNode(context, typeNode, type, host); + if (clone) { + if (addUndefined && !someType(getTypeFromTypeNode(context, typeNode), t => !!(t.flags & TypeFlags.Undefined))) { + return factory.createUnionTypeNode([clone, factory.createKeywordTypeNode(SyntaxKind.UndefinedKeyword)]); + } + return clone; + } + if (addUndefined && originalType !== type) { + const cloneMissingUndefined = tryReuseExistingNonParameterTypeNode(context, typeNode, originalType, host); + if (cloneMissingUndefined) { + return factory.createUnionTypeNode([cloneMissingUndefined, factory.createKeywordTypeNode(SyntaxKind.UndefinedKeyword)]); + } + } + return undefined; + } + + function tryReuseExistingNonParameterTypeNode( + context: NodeBuilderContext, + existing: TypeNode, + type: Type, + host = context.enclosingDeclaration, + annotationType = getTypeFromTypeNode(context, existing, /*noMappedTypes*/ true), + ) { + if (annotationType && typeNodeIsEquivalentToType(host, type, annotationType) && existingTypeNodeIsNotReferenceOrIsReferenceWithCompatibleTypeArgumentCount(existing, type)) { + const result = tryReuseExistingTypeNodeHelper(context, existing); + if (result) { + return result; + } + } + return undefined; + } + + 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 { + const moduleResolverHost = tracker?.trackSymbol ? tracker.moduleResolverHost : + flags! & NodeBuilderFlags.DoNotIncludeSymbolChain ? createBasicNodeBuilderModuleSpecifierResolutionHost(host) : + undefined; + const context: NodeBuilderContext = { + enclosingDeclaration, + enclosingFile: enclosingDeclaration && getSourceFileOfNode(enclosingDeclaration), + flags: flags || NodeBuilderFlags.None, + tracker: undefined!, + encounteredError: false, + reportedDiagnostic: false, + visitedTypes: undefined, + symbolDepth: undefined, + inferTypeParameters: undefined, + approximateLength: 0, + trackedSymbols: undefined, + bundled: !!compilerOptions.outFile && !!enclosingDeclaration && isExternalOrCommonJsModule(getSourceFileOfNode(enclosingDeclaration)), + truncating: false, + usedSymbolNames: undefined, + remappedSymbolNames: undefined, + remappedSymbolReferences: undefined, + reverseMappedStack: undefined, + mustCreateTypeParameterSymbolList: true, + typeParameterSymbolList: undefined, + mustCreateTypeParametersNamesLookups: true, + typeParameterNames: undefined, + typeParameterNamesByText: undefined, + typeParameterNamesByTextNextNameCount: undefined, + mapper: undefined, + }; + context.tracker = new SymbolTrackerImpl(context, tracker, moduleResolverHost); + const resultingNode = cb(context); + if (context.truncating && context.flags & NodeBuilderFlags.NoTruncation) { + context.tracker.reportTruncationError(); + } + return context.encounteredError ? undefined : resultingNode; + } + + function checkTruncationLength(context: NodeBuilderContext): boolean { + if (context.truncating) return context.truncating; + return context.truncating = context.approximateLength > ((context.flags & NodeBuilderFlags.NoTruncation) ? noTruncationMaximumTruncationLength : defaultMaximumTruncationLength); + } + + function typeToTypeNodeHelper(type: Type, context: NodeBuilderContext): TypeNode { + const savedFlags = context.flags; + const typeNode = typeToTypeNodeWorker(type, context); + context.flags = savedFlags; + return typeNode; + } + + function typeToTypeNodeWorker(type: Type, context: NodeBuilderContext): TypeNode { + if (cancellationToken && cancellationToken.throwIfCancellationRequested) { + cancellationToken.throwIfCancellationRequested(); + } + const inTypeAlias = context.flags & NodeBuilderFlags.InTypeAlias; + context.flags &= ~NodeBuilderFlags.InTypeAlias; + + if (!type) { + if (!(context.flags & NodeBuilderFlags.AllowEmptyUnionOrIntersection)) { + context.encounteredError = true; + return undefined!; // TODO: GH#18217 + } + context.approximateLength += 3; + return factory.createKeywordTypeNode(SyntaxKind.AnyKeyword); + } + + if (!(context.flags & NodeBuilderFlags.NoTypeReduction)) { + type = getReducedType(type); + } + + if (type.flags & TypeFlags.Any) { + if (type.aliasSymbol) { + return factory.createTypeReferenceNode(symbolToEntityNameNode(type.aliasSymbol), mapToTypeNodes(type.aliasTypeArguments, context)); + } + if (type === unresolvedType) { + return addSyntheticLeadingComment(factory.createKeywordTypeNode(SyntaxKind.AnyKeyword), SyntaxKind.MultiLineCommentTrivia, "unresolved"); + } + 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.ES5)) { + 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`."); + } + } + 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(); + } + } + 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; + } + context.tracker.reportInaccessibleThisError?.(); + } + 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); + } + + const objectFlags = getObjectFlags(type); + + if (objectFlags & ObjectFlags.Reference) { + Debug.assert(!!(type.flags & TypeFlags.Object)); + return (type as TypeReference).node ? visitAndTransformType(type as TypeReference, 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 + ) { + 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); + } + 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); + } + 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 + } + } + 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) { + const typeNode = typeToTypeNodeHelper((type as SubstitutionType).baseType, context); + const noInferSymbol = isNoInferType(type) && getGlobalTypeSymbol("NoInfer" as __String, /*reportErrors*/ false); + return noInferSymbol ? symbolToTypeNode(noInferSymbol, context, SymbolFlags.Type, [typeNode]) : typeNode; + } + + 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(context, type.root.node.trueType), newMapper)); + const falseTypeNode = typeToTypeNodeOrCircularityElision(instantiateType(getTypeFromTypeNode(context, 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); + } + return visitAndTransformType(type, type => typeToTypeNodeHelper(type, context)); + } + return typeToTypeNodeHelper(type, context); + } + + function isMappedTypeHomomorphic(type: MappedType) { + return !!getHomomorphicTypeVariable(type); + } + + function isHomomorphicMappedTypeWithNonHomomorphicInstantiation(type: MappedType) { + return !!type.target && isMappedTypeHomomorphic(type.target as MappedType) && !isMappedTypeHomomorphic(type); + } + + 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 the mapped type isn't `keyof` constraint-declared, _but_ still has modifiers preserved, and its naive instantiation won't preserve modifiers because its constraint isn't `keyof` constrained, we have work to do + const needsModifierPreservingWrapper = !isMappedTypeWithKeyofConstraintDeclaration(type) + && !(getModifiersTypeFromMappedType(type).flags & TypeFlags.Unknown) + && context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams + && !(getConstraintTypeFromMappedType(type).flags & TypeFlags.TypeParameter && getConstraintOfTypeParameter(getConstraintTypeFromMappedType(type))?.flags! & TypeFlags.Index); + 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 if (needsModifierPreservingWrapper) { + // So, step 1: new type variable + const newParam = createTypeParameter(createSymbol(SymbolFlags.TypeParameter, "T" as __String)); + const name = typeParameterToName(newParam, context); + newTypeVariable = factory.createTypeReferenceNode(name); + // step 2: make that new type variable itself the constraint node, making the mapped type `{[K in T_1]: Template}` + appropriateConstraintTypeNode = newTypeVariable; + } + 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(context, (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), + ); + } + else if (needsModifierPreservingWrapper) { + // and step 3: once the mapped type is reconstructed, create a `ConstraintType extends infer T_1 extends keyof ModifiersType ? {[K in T_1]: Template} : never` + // subtly different from the `keyof` constraint case, by including the `keyof` constraint on the `infer` type parameter, it doesn't rely on the constraint type being itself + // constrained to a `keyof` type to preserve its modifier-preserving behavior. This is all basically because we preserve modifiers for a wider set of mapped types than + // just homomorphic ones. + return factory.createConditionalTypeNode( + typeToTypeNodeHelper(getConstraintTypeFromMappedType(type), context), + factory.createInferTypeNode(factory.createTypeParameterDeclaration(/*modifiers*/ undefined, factory.cloneNode(newTypeVariable!.typeName) as Identifier, factory.createTypeOperatorNode(SyntaxKind.KeyOfKeyword, typeToTypeNodeHelper(getModifiersTypeFromMappedType(type), context)))), + result, + factory.createKeywordTypeNode(SyntaxKind.NeverKeyword), + ); + } + return result; + } + + function createAnonymousTypeNode(type: ObjectType): TypeNode { + const typeId = type.id; + const symbol = type.symbol; + if (symbol) { + const isInstantiationExpressionType = !!(getObjectFlags(type) & ObjectFlags.InstantiationExpressionType); + if (isInstantiationExpressionType) { + const instantiationExpressionType = type as InstantiationExpressionType; + const existing = instantiationExpressionType.node; + if (isTypeQueryNode(existing)) { + const typeNode = tryReuseExistingNonParameterTypeNode(context, existing, type); + if (typeNode) { + return typeNode; + } + } + if (context.visitedTypes?.has(typeId)) { + return createElidedInformationPlaceholder(context); + } + return visitAndTransformType(type, createTypeNodeFromObjectType); + } + 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, /*shouldComputeAliasesToMakeVisible*/ 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); + } + } + else { + 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: T, transform: (type: T) => TypeNode) { + 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 & T).node ? "N" + getNodeId((type as TypeReference & T).node!) : + type.flags & TypeFlags.Conditional ? "N" + getNodeId((type as ConditionalType & T).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) { + // TODO:: check if we instead store late painted statements associated with this? + cachedResult.trackedSymbols?.forEach( + ([symbol, enclosingDeclaration, meaning]) => + context.tracker.trackSymbol( + symbol, + enclosingDeclaration, + meaning, + ), + ); + if (cachedResult.truncating) { + context.truncating = true; + } + context.approximateLength += cachedResult.addedLength; + return deepCloneOrReuseNode(cachedResult.node); + } + + let depth: number | undefined; + if (id) { + depth = context.symbolDepth!.get(id) || 0; + if (depth > 10) { + return createElidedInformationPlaceholder(context); + } + context.symbolDepth!.set(id, depth + 1); + } + context.visitedTypes.add(typeId); + const prevTrackedSymbols = context.trackedSymbols; + context.trackedSymbols = undefined; + const startLength = context.approximateLength; + const result = transform(type); + const addedLength = context.approximateLength - startLength; + if (!context.reportedDiagnostic && !context.encounteredError) { + links?.serializedTypes?.set(key, { + node: result, + truncating: context.truncating, + addedLength, + trackedSymbols: context.trackedSymbols, + }); + } + context.visitedTypes.delete(typeId); + if (id) { + context.symbolDepth!.set(id, depth!); + } + context.trackedSymbols = prevTrackedSymbols; + return result; + + function deepCloneOrReuseNode(node: T): T { + if (!nodeIsSynthesized(node) && getParseTreeNode(node) === node) { + return node; + } + return setTextRange(context, factory.cloneNode(visitEachChildWorker(node, deepCloneOrReuseNode, /*context*/ undefined, deepCloneOrReuseNodes, deepCloneOrReuseNode)), node); + } + + function deepCloneOrReuseNodes( + nodes: NodeArray | undefined, + visitor: Visitor, + 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 setTextRangeWorker(factory.createNodeArray(/*elements*/ 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, s => getOrCreateTypeFromSignature(s)); + // 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) { + const { labeledElementDeclarations } = type.target as TupleType; + for (let i = 0; i < tupleConstituentNodes.length; i++) { + const flags = (type.target as TupleType).elementFlags[i]; + const labeledElementDeclaration = labeledElementDeclarations?.[i]; + + if (labeledElementDeclaration) { + tupleConstituentNodes[i] = factory.createNamedTupleMember( + flags & ElementFlags.Variable ? factory.createToken(SyntaxKind.DotDotDotToken) : undefined, + factory.createIdentifier(unescapeLeadingUnderscores(getTupleElementLabel(labeledElementDeclaration))), + flags & ElementFlags.Optional ? factory.createToken(SyntaxKind.QuestionToken) : undefined, + flags & ElementFlags.Rest ? factory.createArrayTypeNode(tupleConstituentNodes[i]) : + tupleConstituentNodes[i], + ); + } + else { + 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 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)) { + if (typeArguments !== getIdentifierTypeArguments(qualifier)) { + qualifier = setIdentifierTypeArguments(factory.cloneNode(qualifier), typeArguments); + } + } + else { + if (typeArguments !== getIdentifierTypeArguments(qualifier.right)) { + qualifier = factory.updateQualifiedName(qualifier, qualifier.left, setIdentifierTypeArguments(factory.cloneNode(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.attributes, + qualifier, + typeArguments, + root.isTypeOf, + ); + } + else { + // first shift type arguments + let typeArguments = root.typeArguments; + let typeName = root.typeName; + if (isIdentifier(typeName)) { + if (typeArguments !== getIdentifierTypeArguments(typeName)) { + typeName = setIdentifierTypeArguments(factory.cloneNode(typeName), typeArguments); + } + } + else { + if (typeArguments !== getIdentifierTypeArguments(typeName.right)) { + typeName = factory.updateQualifiedName(typeName, typeName.left, setIdentifierTypeArguments(factory.cloneNode(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 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; + } + 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); + } + return typeElements.length ? typeElements : undefined; + } + } + + 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 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).links.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.canTrackSymbol && 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); + } + } + } + else { + context.tracker.reportNonSerializableProperty(symbolToString(propertySymbol)); + } + } + context.enclosingDeclaration = propertySymbol.valueDeclaration || propertySymbol.declarations?.[0] || saveEnclosingDeclaration; + const propertyName = getPropertyNameNodeForSymbol(propertySymbol, context); + context.enclosingDeclaration = saveEnclosingDeclaration; + context.approximateLength += symbolName(propertySymbol).length + 1; + + if (propertySymbol.flags & SymbolFlags.Accessor) { + const writeType = getWriteTypeOfSymbol(propertySymbol); + if (propertyType !== writeType && !isErrorType(propertyType) && !isErrorType(writeType)) { + const getterDeclaration = getDeclarationOfKind(propertySymbol, SyntaxKind.GetAccessor)!; + const getterSignature = getSignatureFromDeclaration(getterDeclaration); + typeElements.push( + setCommentRange( + context, + signatureToSignatureDeclarationHelper(getterSignature, SyntaxKind.GetAccessor, context, { name: propertyName }) as GetAccessorDeclaration, + getterDeclaration, + ), + ); + const setterDeclaration = getDeclarationOfKind(propertySymbol, SyntaxKind.SetAccessor)!; + const setterSignature = getSignatureFromDeclaration(setterDeclaration); + typeElements.push( + setCommentRange( + context, + signatureToSignatureDeclarationHelper(setterSignature, SyntaxKind.SetAccessor, context, { name: propertyName }) as SetAccessorDeclaration, + setterDeclaration, + ), + ); + return; + } + } + + 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)); + } + if (signatures.length || !optionalToken) { + return; + } + } + let propertyTypeNode: TypeNode; + if (shouldUsePlaceholderForProperty(propertySymbol, context)) { + propertyTypeNode = createElidedInformationPlaceholder(context); + } + else { + if (propertyIsReverseMapped) { + context.reverseMappedStack ||= []; + context.reverseMappedStack.push(propertySymbol as ReverseMappedSymbol); + } + propertyTypeNode = propertyType ? serializeTypeForDeclaration(context, /*declaration*/ undefined, propertyType, propertySymbol) : factory.createKeywordTypeNode(SyntaxKind.AnyKeyword); + if (propertyIsReverseMapped) { + context.reverseMappedStack!.pop(); + } + } + + 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) { + const jsdocPropertyTag = propertySymbol.declarations?.find((d): d is JSDocPropertyTag => d.kind === SyntaxKind.JSDocPropertyTag); + if (jsdocPropertyTag) { + const commentText = getTextOfJSDocComment(jsdocPropertyTag.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(context, node, propertySymbol.valueDeclaration); + } + return node; + } + } + + function setCommentRange(context: NodeBuilderContext, node: T, range: Node): T { + if (context.enclosingFile && context.enclosingFile === getSourceFileOfNode(range)) { + // Copy comments to node for declaration emit + return setCommentRangeWorker(node, range); + } + return node; + } + + 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 ? createMultiMap<__String, [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; + } + 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 (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; + } + + 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); + + 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, + ); + } + + interface SignatureToSignatureDeclarationOptions { + modifiers?: readonly Modifier[]; + name?: PropertyName; + questionToken?: QuestionToken; + } + + function signatureToSignatureDeclarationHelper(signature: Signature, kind: SignatureDeclaration["kind"], context: NodeBuilderContext, options?: SignatureToSignatureDeclarationOptions): SignatureDeclaration { + let typeParameters: TypeParameterDeclaration[] | undefined; + let typeArguments: TypeNode[] | undefined; + + const expandedParams = getExpandedParameters(signature, /*skipUnionExpanding*/ true)[0]; + const cleanup = enterNewScope(context, signature.declaration, expandedParams, signature.typeParameters, signature.parameters, signature.mapper); + context.approximateLength += 3; // Usually a signature contributes a few more characters than this, but 3 is the minimum + + 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 flags = context.flags; + context.flags &= ~NodeBuilderFlags.SuppressAnyReturnType; // SuppressAnyReturnType should only apply to the signature `return` position + // 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)); + const thisParameter = context.flags & NodeBuilderFlags.OmitThisParameter ? undefined : tryGetThisParameterDeclaration(signature, context); + if (thisParameter) { + parameters.unshift(thisParameter); + } + context.flags = flags; + + const returnTypeNode = serializeReturnTypeForSignature(context, signature); + + let modifiers = options?.modifiers; + if ((kind === SyntaxKind.ConstructorType) && signature.flags & SignatureFlags.Abstract) { + const flags = modifiersToFlags(modifiers); + modifiers = factory.createModifiersFromModifierFlags(flags | ModifierFlags.Abstract); + } + + 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); + } + if (signature.declaration?.kind === SyntaxKind.JSDocSignature && signature.declaration.parent.kind === SyntaxKind.JSDocOverloadTag) { + const comment = getTextOfNode(signature.declaration.parent.parent, /*includeTrivia*/ true).slice(2, -2).split(/\r\n|\n|\r/).map(line => line.replace(/^\s+/, " ")).join("\n"); + addSyntheticLeadingComment(node, SyntaxKind.MultiLineCommentTrivia, comment, /*hasTrailingNewLine*/ true); + } + + cleanup?.(); + return node; + } + + type IntroducesNewScopeNode = SignatureDeclaration | JSDocSignature | MappedTypeNode; + + function isNewScopeNode(node: Node): node is IntroducesNewScopeNode { + return isFunctionLike(node) + || isJSDocSignature(node) + || isMappedTypeNode(node); + } + + function getTypeParametersInScope(node: IntroducesNewScopeNode | ConditionalTypeNode) { + return isFunctionLike(node) || isJSDocSignature(node) ? getSignatureFromDeclaration(node).typeParameters : + isConditionalTypeNode(node) ? getInferTypeParameters(node) : + [getDeclaredTypeOfTypeParameter(getSymbolOfDeclaration(node.typeParameter))]; + } + + function getParametersInScope(node: IntroducesNewScopeNode | ConditionalTypeNode) { + return isFunctionLike(node) || isJSDocSignature(node) ? getSignatureFromDeclaration(node).parameters : undefined; + } + + function enterNewScope( + context: NodeBuilderContext, + declaration: IntroducesNewScopeNode | ConditionalTypeNode | undefined, + expandedParams: readonly Symbol[] | undefined, + typeParameters: readonly TypeParameter[] | undefined, + originalParameters?: readonly Symbol[] | undefined, + mapper?: TypeMapper, + ) { + const cleanupContext = cloneNodeBuilderContext(context); + // For regular function/method declarations, the enclosing declaration will already be signature.declaration, + // so this is a no-op, but for arrow functions and function expressions, the enclosing declaration will be + // the declaration that the arrow function / function expression is assigned to. + // + // If the parameters or return type include "typeof globalThis.paramName", using the wrong scope will lead + // us to believe that we can emit "typeof paramName" instead, even though that would refer to the parameter, + // not the global. Make sure we are in the right scope by changing the enclosingDeclaration to the function. + // + // We can't use the declaration directly; it may be in another file and so we may lose access to symbols + // accessible to the current enclosing declaration, or gain access to symbols not accessible to the current + // enclosing declaration. To keep this chain accurate, insert a fake scope into the chain which makes the + // function's parameters visible. + let cleanupParams: (() => void) | undefined; + let cleanupTypeParams: (() => void) | undefined; + const oldEnclosingDecl = context.enclosingDeclaration; + const oldMapper = context.mapper; + if (mapper) { + context.mapper = mapper; + } + if (context.enclosingDeclaration && declaration) { + // As a performance optimization, reuse the same fake scope within this chain. + // This is especially needed when we are working on an excessively deep type; + // if we don't do this, then we spend all of our time adding more and more + // scopes that need to be searched in isSymbolAccessible later. Since all we + // really want to do is to mark certain names as unavailable, we can just keep + // all of the names we're introducing in one large table and push/pop from it as + // needed; isSymbolAccessible will walk upward and find the closest "fake" scope, + // which will conveniently report on any and all faked scopes in the chain. + // + // It'd likely be better to store this somewhere else for isSymbolAccessible, but + // since that API _only_ uses the enclosing declaration (and its parents), this is + // seems like the best way to inject names into that search process. + // + // Note that we only check the most immediate enclosingDeclaration; the only place we + // could potentially add another fake scope into the chain is right here, so we don't + // traverse all ancestors. + cleanupParams = !some(expandedParams) ? undefined : pushFakeScope( + "params", + add => { + if (!expandedParams) return; + for (let pIndex = 0; pIndex < expandedParams.length; pIndex++) { + const param = expandedParams[pIndex]; + const originalParam = originalParameters?.[pIndex]; + if (originalParameters && originalParam !== param) { + // Can't reference parameters that come from an expansion + add(param.escapedName, unknownSymbol); + // Can't reference the original expanded parameter either + if (originalParam) { + add(originalParam.escapedName, unknownSymbol); + } + } + else if ( + !forEach(param.declarations, d => { + if (isParameter(d) && isBindingPattern(d.name)) { + bindPattern(d.name); + return true; + } + return undefined; + function bindPattern(p: BindingPattern): void { + forEach(p.elements, e => { + switch (e.kind) { + case SyntaxKind.OmittedExpression: + return; + case SyntaxKind.BindingElement: + return bindElement(e); + default: + return Debug.assertNever(e); + } + }); + } + function bindElement(e: BindingElement): void { + if (isBindingPattern(e.name)) { + return bindPattern(e.name); + } + const symbol = getSymbolOfDeclaration(e); + add(symbol.escapedName, symbol); + } + }) + ) { + add(param.escapedName, param); + } + } + }, + ); + + if (context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams && some(typeParameters)) { + cleanupTypeParams = pushFakeScope( + "typeParams", + add => { + for (const typeParam of typeParameters ?? emptyArray) { + const typeParamName = typeParameterToName(typeParam, context).escapedText; + add(typeParamName, typeParam.symbol); + } + }, + ); + } + + function pushFakeScope(kind: "params" | "typeParams", addAll: (addSymbol: (name: __String, symbol: Symbol) => void) => void) { + // We only ever need to look two declarations upward. + Debug.assert(context.enclosingDeclaration); + let existingFakeScope: Node | undefined; + if (getNodeLinks(context.enclosingDeclaration).fakeScopeForSignatureDeclaration === kind) { + existingFakeScope = context.enclosingDeclaration; + } + else if (context.enclosingDeclaration.parent && getNodeLinks(context.enclosingDeclaration.parent).fakeScopeForSignatureDeclaration === kind) { + existingFakeScope = context.enclosingDeclaration.parent; + } + Debug.assertOptionalNode(existingFakeScope, isBlock); + + const locals = existingFakeScope?.locals ?? createSymbolTable(); + let newLocals: __String[] | undefined; + let oldLocals: { name: __String; oldSymbol: Symbol; }[] | undefined; + addAll((name, symbol) => { + // Add cleanup information only if we don't own the fake scope + if (existingFakeScope) { + const oldSymbol = locals.get(name); + if (!oldSymbol) { + newLocals = append(newLocals, name); + } + else { + oldLocals = append(oldLocals, { name, oldSymbol }); + } + } + locals.set(name, symbol); + }); + + if (!existingFakeScope) { + // Use a Block for this; the type of the node doesn't matter so long as it + // has locals, and this is cheaper/easier than using a function-ish Node. + const fakeScope = factory.createBlock(emptyArray); + getNodeLinks(fakeScope).fakeScopeForSignatureDeclaration = kind; + fakeScope.locals = locals; + + setParent(fakeScope, context.enclosingDeclaration); + context.enclosingDeclaration = fakeScope; + } + else { + // We did not create the current scope, so we have to clean it up + return function undo() { + forEach(newLocals, s => locals.delete(s)); + forEach(oldLocals, s => locals.set(s.name, s.oldSymbol)); + }; + } + } + } + + return () => { + cleanupParams?.(); + cleanupTypeParams?.(); + cleanupContext(); + context.enclosingDeclaration = oldEnclosingDecl; + context.mapper = oldMapper; + }; + } + + function tryGetThisParameterDeclaration(signature: Signature, context: NodeBuilderContext) { + if (signature.thisParameter) { + return symbolToParameterDeclaration(signature.thisParameter, context); + } + if (signature.declaration && isInJSFile(signature.declaration)) { + const thisTag = getJSDocThisTag(signature.declaration); + if (thisTag && thisTag.typeExpression) { + return factory.createParameterDeclaration( + /*modifiers*/ undefined, + /*dotDotDotToken*/ undefined, + "this", + /*questionToken*/ undefined, + typeToTypeNodeHelper(getTypeFromTypeNode(context, thisTag.typeExpression), context), + ); + } + } + } + + 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(getTypeParameterModifiers(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 typeToTypeNodeHelperWithPossibleReusableTypeNode(type: Type, typeNode: TypeNode | undefined, context: NodeBuilderContext) { + return typeNode && tryReuseExistingNonParameterTypeNode(context, typeNode, type) || typeToTypeNodeHelper(type, context); + } + + function typeParameterToDeclaration(type: TypeParameter, context: NodeBuilderContext, constraint = getConstraintOfTypeParameter(type)): TypeParameterDeclaration { + const constraintNode = constraint && typeToTypeNodeHelperWithPossibleReusableTypeNode(constraint, getConstraintDeclaration(type), context); + return typeParameterToDeclarationWithConstraint(type, context, constraintNode); + } + + function typePredicateToTypePredicateNodeHelper(typePredicate: TypePredicate, context: NodeBuilderContext): TypePredicateNode { + 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); + return factory.createTypePredicateNode(assertsModifier, parameterName, typeNode); + } + + function getEffectiveParameterDeclaration(parameterSymbol: Symbol): ParameterDeclaration | JSDocParameterTag | undefined { + const parameterDeclaration: ParameterDeclaration | JSDocParameterTag | undefined = getDeclarationOfKind(parameterSymbol, SyntaxKind.Parameter); + if (parameterDeclaration) { + return parameterDeclaration; + } + if (!isTransientSymbol(parameterSymbol)) { + return getDeclarationOfKind(parameterSymbol, SyntaxKind.JSDocParameterTag); + } + } + + function symbolToParameterDeclaration(parameterSymbol: Symbol, context: NodeBuilderContext, preserveModifierFlags?: boolean): ParameterDeclaration { + const parameterDeclaration = getEffectiveParameterDeclaration(parameterSymbol); + + const parameterType = getTypeOfSymbol(parameterSymbol); + const parameterTypeNode = serializeTypeForDeclaration(context, parameterDeclaration, parameterType, parameterSymbol); + + 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 = parameterToParameterDeclarationName(parameterSymbol, parameterDeclaration, context); + 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 parameterToParameterDeclarationName(parameterSymbol: Symbol, parameterDeclaration: ParameterDeclaration | JSDocParameterTag | undefined, context: NodeBuilderContext) { + return 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); + + function cloneBindingName(node: BindingName): BindingName { + return elideInitializerAndSetEmitFlags(node) as BindingName; + function elideInitializerAndSetEmitFlags(node: Node): Node { + if (context.tracker.canTrackSymbol && isComputedPropertyName(node) && isLateBindableName(node)) { + trackComputedName(node.expression, context.enclosingDeclaration, context); + } + let visited = visitEachChildWorker(node, elideInitializerAndSetEmitFlags, /*context*/ undefined, /*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 trackComputedName(accessExpression: EntityNameOrEntityNameExpression, enclosingDeclaration: Node | undefined, context: NodeBuilderContext) { + if (!context.tracker.canTrackSymbol) return; + // get symbol of the first identifier of the entityName + const firstIdentifier = getFirstIdentifier(accessExpression); + const name = resolveName(firstIdentifier, firstIdentifier.escapedText, SymbolFlags.Value | SymbolFlags.ExportValue, /*nameNotFoundMessage*/ undefined, /*isUse*/ true); + if (name) { + context.tracker.trackSymbol(name, enclosingDeclaration, SymbolFlags.Value); + } + } + + function lookupSymbolChain(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags, yieldModuleSymbol?: boolean) { + context.tracker.trackSymbol(symbol, context.enclosingDeclaration, meaning); + 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]; + } + + 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; + } + 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 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; + } + if (context.mustCreateTypeParameterSymbolList) { + context.mustCreateTypeParameterSymbolList = false; + context.typeParameterSymbolList = new Set(context.typeParameterSymbolList); + } + context.typeParameterSymbolList!.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, + ); + // NOTE: cast to TransientSymbol should be safe because only TransientSymbol can have CheckFlags.Instantiated + typeParameterNodes = mapToTypeNodes(map(params, t => getMappedType(t, (nextSymbol as TransientSymbol).links.mapper!)), context); + } + else { + typeParameterNodes = typeParametersToTypeParameterDeclarations(symbol, context); + } + } + 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; + } + + function getSpecifierForModuleSymbol(symbol: Symbol, context: NodeBuilderContext, overrideImportMode?: ResolutionMode) { + 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 (ambientModuleSymbolRegex.test(symbol.escapedName as string)) { + return (symbol.escapedName as string).substring(1, (symbol.escapedName as string).length - 1); + } + } + if (!context.enclosingFile || !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 = context.enclosingFile; + const resolutionMode = overrideImportMode || contextFile?.impliedNodeFormat; + const cacheKey = createModeAwareCacheKey(contextFile.path, resolutionMode); + const links = getSymbolLinks(symbol); + let specifier = links.specifierCache && links.specifierCache.get(cacheKey); + if (!specifier) { + const isBundle = !!compilerOptions.outFile; + // 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 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 attributes: ImportAttributes | 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); + attributes = factory.createImportAttributes( + factory.createNodeArray([ + factory.createImportAttribute( + factory.createStringLiteral("resolution-mode"), + factory.createStringLiteral("import"), + ), + ]), + ); + } + } + if (!specifier) { + specifier = getSpecifierForModuleSymbol(chain[0], context); + } + if (!(context.flags & NodeBuilderFlags.AllowNodeModulesRelativePaths) && getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.Classic && specifier.includes("/node_modules/")) { + 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.includes("/node_modules/")) { + // Still unreachable :( + specifier = oldSpecifier; + } + else { + attributes = factory.createImportAttributes( + factory.createNodeArray([ + factory.createImportAttribute( + factory.createStringLiteral("resolution-mode"), + factory.createStringLiteral(swappedMode === ModuleKind.ESNext ? "import" : "require"), + ), + ]), + ); + } + } + + if (!attributes) { + // 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)); + context.approximateLength += specifier.length + 10; // specifier + import("") + if (!nonRootParts || isEntityName(nonRootParts)) { + if (nonRootParts) { + const lastId = isIdentifier(nonRootParts) ? nonRootParts : nonRootParts.right; + setIdentifierTypeArguments(lastId, /*typeArguments*/ undefined); + } + return factory.createImportTypeNode(lit, attributes, 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, attributes, 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); + } + else { + const lastId = isIdentifier(entityName) ? entityName : entityName.right; + const lastTypeArgs = getIdentifierTypeArguments(lastId); + setIdentifierTypeArguments(lastId, /*typeArguments*/ undefined); + return factory.createTypeReferenceNode(entityName, lastTypeArgs as NodeArray); + } + + 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]; + + 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; + } + }); + } + } + + 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)); + } + 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 + 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))); + } + } + + const identifier = setEmitFlags(factory.createIdentifier(symbolName), EmitFlags.NoAsciiEscaping); + if (typeParameterNodes) setIdentifierTypeArguments(identifier, factory.createNodeArray(typeParameterNodes)); + 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 factory.createQualifiedName(LHS, identifier); + } + return identifier; + } + } + + function typeParameterShadowsOtherTypeParameterInScope(escapedName: __String, context: NodeBuilderContext, type: TypeParameter) { + const result = resolveName(context.enclosingDeclaration, escapedName, SymbolFlags.Type, /*nameNotFoundMessage*/ undefined, /*isUse*/ false); + if (result && result.flags & SymbolFlags.TypeParameter) { + return result !== type.symbol; + } + 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; + } + } + let result = symbolToName(type.symbol, context, SymbolFlags.Type, /*expectsIdentifier*/ true); + if (!(result.kind & SyntaxKind.Identifier)) { + return factory.createIdentifier("(Missing type parameter)"); + } + const decl = type.symbol?.declarations?.[0]; + if (decl && isTypeParameterDeclaration(decl)) { + result = setTextRange(context, result, decl.name); + } + 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) || typeParameterShadowsOtherTypeParameterInScope(text as __String, context, type)) { + i++; + text = `${rawtext}_${i}`; + } + if (text !== rawtext) { + const typeArguments = getIdentifierTypeArguments(result); + result = factory.createIdentifier(text); + setIdentifierTypeArguments(result, typeArguments); + } + if (context.mustCreateTypeParametersNamesLookups) { + context.mustCreateTypeParametersNamesLookups = false; + context.typeParameterNames = new Map(context.typeParameterNames); + context.typeParameterNamesByTextNextNameCount = new Map(context.typeParameterNamesByTextNextNameCount); + context.typeParameterNamesByText = new Set(context.typeParameterNamesByText); + } + // 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!.set(rawtext, i); + context.typeParameterNames!.set(getTypeId(type), result); + context.typeParameterNamesByText!.add(text); + } + 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); + + 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; + } + + const identifier = setEmitFlags(factory.createIdentifier(symbolName), EmitFlags.NoAsciiEscaping); + if (typeParameterNodes) setIdentifierTypeArguments(identifier, factory.createNodeArray(typeParameterNodes)); + identifier.symbol = symbol; + + 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)); + } + if (index === 0 || canUsePropertyAccess(symbolName, languageVersion)) { + const identifier = setEmitFlags(factory.createIdentifier(symbolName), EmitFlags.NoAsciiEscaping); + if (typeParameterNodes) setIdentifierTypeArguments(identifier, factory.createNodeArray(typeParameterNodes)); + identifier.symbol = symbol; + + 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) { + const identifier = setEmitFlags(factory.createIdentifier(symbolName), EmitFlags.NoAsciiEscaping); + if (typeParameterNodes) setIdentifierTypeArguments(identifier, factory.createNodeArray(typeParameterNodes)); + identifier.symbol = symbol; + expression = identifier; + } + return factory.createElementAccessExpression(createExpressionFromSymbolChain(chain, index - 1), expression); + } + } + } + + function isStringNamed(d: Declaration) { + const name = getNameOfDeclaration(d); + if (!name) { + return false; + } + if (isComputedPropertyName(name)) { + const type = checkExpression(name.expression); + return !!(type.flags & TypeFlags.StringLike); + } + if (isElementAccessExpression(name)) { + const type = checkExpression(name.argumentExpression); + return !!(type.flags & TypeFlags.StringLike); + } + return isStringLiteral(name); + } + + function isSingleQuotedStringNamed(d: Declaration) { + const name = getNameOfDeclaration(d); + return !!(name && isStringLiteral(name) && (name.singleQuote || !nodeIsSynthesized(name) && startsWith(getTextOfNode(name, /*includeTrivia*/ false), "'"))); + } + + function getPropertyNameNodeForSymbol(symbol: Symbol, context: NodeBuilderContext) { + const stringNamed = !!length(symbol.declarations) && every(symbol.declarations, isStringNamed); + const singleQuote = !!length(symbol.declarations) && every(symbol.declarations, isSingleQuotedStringNamed); + const isMethod = !!(symbol.flags & SymbolFlags.Method); + const fromNameType = getPropertyNameNodeForSymbolFromNameType(symbol, context, singleQuote, stringNamed, isMethod); + if (fromNameType) { + return fromNameType; + } + const rawName = unescapeLeadingUnderscores(symbol.escapedName); + return createPropertyNameNodeForIdentifierOrLiteral(rawName, getEmitScriptTarget(compilerOptions), singleQuote, stringNamed, isMethod); + } + + // See getNameForSymbolFromNameType for a stringy equivalent + function getPropertyNameNodeForSymbolFromNameType(symbol: Symbol, context: NodeBuilderContext, singleQuote: boolean, stringNamed: boolean, isMethod: 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)) && (stringNamed || !isNumericLiteralName(name))) { + return factory.createStringLiteral(name, !!singleQuote); + } + if (isNumericLiteralName(name) && startsWith(name, "-")) { + return factory.createComputedPropertyName(factory.createPrefixUnaryExpression(SyntaxKind.MinusToken, factory.createNumericLiteral(-name))); + } + return createPropertyNameNodeForIdentifierOrLiteral(name, getEmitScriptTarget(compilerOptions), singleQuote, stringNamed, isMethod); + } + if (nameType.flags & TypeFlags.UniqueESSymbol) { + return factory.createComputedPropertyName(symbolToExpression((nameType as UniqueESSymbolType).symbol, context, SymbolFlags.Value)); + } + } + } + + function cloneNodeBuilderContext(context: NodeBuilderContext) { + // 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 + const oldMustCreateTypeParameterSymbolList = context.mustCreateTypeParameterSymbolList; + const oldMustCreateTypeParametersNamesLookups = context.mustCreateTypeParametersNamesLookups; + context.mustCreateTypeParameterSymbolList = true; + context.mustCreateTypeParametersNamesLookups = true; + const oldTypeParameterNames = context.typeParameterNames; + const oldTypeParameterNamesByText = context.typeParameterNamesByText; + const oldTypeParameterNamesByTextNextNameCount = context.typeParameterNamesByTextNextNameCount; + const oldTypeParameterSymbolList = context.typeParameterSymbolList; + return () => { + context.typeParameterNames = oldTypeParameterNames; + context.typeParameterNamesByText = oldTypeParameterNamesByText; + context.typeParameterNamesByTextNextNameCount = oldTypeParameterNamesByTextNextNameCount; + context.typeParameterSymbolList = oldTypeParameterSymbolList; + context.mustCreateTypeParameterSymbolList = oldMustCreateTypeParameterSymbolList; + context.mustCreateTypeParametersNamesLookups = oldMustCreateTypeParametersNamesLookups; + }; + } + + function getDeclarationWithTypeAnnotation(symbol: Symbol, enclosingDeclaration?: Node | undefined) { + return symbol.declarations && find(symbol.declarations, s => !!getNonlocalEffectiveTypeAnnotationNode(s) && (!enclosingDeclaration || !!findAncestor(s, n => n === enclosingDeclaration))); + } + + function existingTypeNodeIsNotReferenceOrIsReferenceWithCompatibleTypeArgumentCount(existing: TypeNode, type: Type) { + // In JS, you can say something like `Foo` and get a `Foo` implicitly - we don't want to preserve that original `Foo` in these cases, though. + if (!(getObjectFlags(type) & ObjectFlags.Reference)) return true; + if (!isTypeReferenceNode(existing)) return true; + // `type` is a reference type, and `existing` is a type reference node, but we still need to make sure they refer to the _same_ target type + // before we go comparing their type argument counts. + void getTypeFromTypeReference(existing); // call to ensure symbol is resolved + const symbol = getNodeLinks(existing).resolvedSymbol; + const existingTarget = symbol && getDeclaredTypeOfSymbol(symbol); + if (!existingTarget || existingTarget !== (type as TypeReference).target) return true; + return length(existing.typeArguments) >= getMinTypeArgumentCount((type as TypeReference).target.typeParameters); + } + + function getEnclosingDeclarationIgnoringFakeScope(enclosingDeclaration: Node) { + while (getNodeLinks(enclosingDeclaration).fakeScopeForSignatureDeclaration) { + enclosingDeclaration = enclosingDeclaration.parent; + } + return enclosingDeclaration; + } + + /** + * 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` + * @param context - The node builder context. Any reused nodes are checked to be pulled from within the scope of the context's enclosingDeclaration. + * @param declaration - The preferred declaration to pull existing type nodes from (the symbol will be used as a fallback to find any annotated declaration) + * @param type - The type to write; an existing annotation must match this type if it's used, otherwise this is the type serialized as a new type node + * @param symbol - The symbol is used both to find an existing annotation if declaration is not provided, and to determine if `unique symbol` should be printed + */ + function serializeTypeForDeclaration(context: NodeBuilderContext, declaration: Declaration | undefined, type: Type, symbol: Symbol) { + const addUndefined = declaration && (isParameter(declaration) || isJSDocParameterTag(declaration)) && requiresAddingImplicitUndefined(declaration); + const enclosingDeclaration = context.enclosingDeclaration; + const oldFlags = context.flags; + if (declaration && hasInferredType(declaration) && !(context.flags & NodeBuilderFlags.NoSyntacticPrinter)) { + syntacticNodeBuilder.serializeTypeOfDeclaration(declaration, context); + } + context.flags |= NodeBuilderFlags.NoSyntacticPrinter; + if (enclosingDeclaration && (!isErrorType(type) || (context.flags & NodeBuilderFlags.AllowUnresolvedNames))) { + const declWithExistingAnnotation = declaration && getNonlocalEffectiveTypeAnnotationNode(declaration) + ? declaration + : getDeclarationWithTypeAnnotation(symbol); + if (declWithExistingAnnotation && !isFunctionLikeDeclaration(declWithExistingAnnotation) && !isGetAccessorDeclaration(declWithExistingAnnotation)) { + // try to reuse the existing annotation + const existing = getNonlocalEffectiveTypeAnnotationNode(declWithExistingAnnotation)!; + const result = !isTypePredicateNode(existing) && tryReuseExistingTypeNode(context, existing, type, declWithExistingAnnotation, addUndefined); + if (result) { + context.flags = oldFlags; + return result; + } + } + } + if ( + type.flags & TypeFlags.UniqueESSymbol && + type.symbol === symbol && (!context.enclosingDeclaration || some(symbol.declarations, d => getSourceFileOfNode(d) === getSourceFileOfNode(context.enclosingDeclaration!))) + ) { + context.flags |= NodeBuilderFlags.AllowUniqueESSymbolType; + } + + const decl = declaration ?? symbol.valueDeclaration ?? symbol.declarations?.[0]; + const expr = decl && isDeclarationWithPossibleInnerTypeNodeReuse(decl) ? getPossibleTypeNodeReuseExpression(decl) : undefined; + + const result = expressionOrTypeToTypeNode(context, expr, type, addUndefined); + context.flags = oldFlags; + return result; + } + + function typeNodeIsEquivalentToType(annotatedDeclaration: Node | undefined, type: Type, typeFromTypeNode: Type) { + if (typeFromTypeNode === type) { + return true; + } + if (annotatedDeclaration && (isParameter(annotatedDeclaration) || isPropertySignature(annotatedDeclaration) || isPropertyDeclaration(annotatedDeclaration)) && annotatedDeclaration.questionToken) { + return getTypeWithFacts(type, TypeFacts.NEUndefined) === typeFromTypeNode; + } + return false; + } + + function serializeReturnTypeForSignature(context: NodeBuilderContext, signature: Signature) { + const suppressAny = context.flags & NodeBuilderFlags.SuppressAnyReturnType; + const flags = context.flags; + if (suppressAny) context.flags &= ~NodeBuilderFlags.SuppressAnyReturnType; // suppress only toplevel `any`s + let returnTypeNode: TypeNode | undefined; + const returnType = getReturnTypeOfSignature(signature); + if (returnType && !(suppressAny && isTypeAny(returnType))) { + if (signature.declaration && !(context.flags & NodeBuilderFlags.NoSyntacticPrinter)) { + syntacticNodeBuilder.serializeReturnTypeForSignature(signature.declaration, context); + } + context.flags |= NodeBuilderFlags.NoSyntacticPrinter; + returnTypeNode = serializeReturnTypeForSignatureWorker(context, signature); + } + else if (!suppressAny) { + returnTypeNode = factory.createKeywordTypeNode(SyntaxKind.AnyKeyword); + } + context.flags = flags; + return returnTypeNode; + } + + function serializeReturnTypeForSignatureWorker(context: NodeBuilderContext, signature: Signature) { + const typePredicate = getTypePredicateOfSignature(signature); + const type = getReturnTypeOfSignature(signature); + if (context.enclosingDeclaration && (!isErrorType(type) || (context.flags & NodeBuilderFlags.AllowUnresolvedNames)) && signature.declaration && !nodeIsSynthesized(signature.declaration)) { + const annotation = signature.declaration && getNonlocalEffectiveReturnTypeAnnotationNode(signature.declaration); + // Default constructor signatures inherited from base classes return the derived class but have the base class declaration + // To ensure we don't serialize the wrong type we check that that return type of the signature corresponds to the declaration return type signature + if (annotation && getTypeFromTypeNode(context, annotation) === type) { + const result = tryReuseExistingTypeNodeHelper(context, annotation); + if (result) { + return result; + } + } + } + if (typePredicate) { + return typePredicateToTypePredicateNodeHelper(typePredicate, context); + } + const expr = signature.declaration && getPossibleTypeNodeReuseExpression(signature.declaration); + return expressionOrTypeToTypeNode(context, expr, type); + } + + function trackExistingEntityName(node: T, context: NodeBuilderContext) { + 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 meaning = getMeaningOfEntityNameReference(node); + let sym: Symbol | undefined; + if (isThisIdentifier(leftmost)) { + // `this` isn't a bindable identifier - skip resolution, find a relevant `this` symbol directly and avoid exhaustive scope traversal + sym = getSymbolOfDeclaration(getThisContainer(leftmost, /*includeArrowFunctions*/ false, /*includeClassComputedPropertyName*/ false)); + if (isSymbolAccessible(sym, leftmost, meaning, /*shouldComputeAliasesToMakeVisible*/ false).accessibility !== SymbolAccessibility.Accessible) { + introducesError = true; + context.tracker.reportInaccessibleThisError(); + } + return { introducesError, node: attachSymbolToLeftmostIdentifier(node) as T }; + } + sym = resolveEntityName(leftmost, meaning, /*ignoreErrors*/ true, /*dontResolveAlias*/ true); + if ( + context.enclosingDeclaration && + !(sym && sym.flags & SymbolFlags.TypeParameter) + ) { + sym = getExportSymbolOfValueSymbolIfExported(sym); + // Some declarations may be transplanted to a new location. + // When this happens we need to make sure that the name has the same meaning at both locations + // We also check for the unknownSymbol because when we create a fake scope some parameters may actually not be usable + // either because they are the expanded rest parameter, + // or because they are the newly added parameters from the tuple, which might have different meanings in the original context + const symAtLocation = resolveEntityName(leftmost, meaning, /*ignoreErrors*/ true, /*dontResolveAlias*/ true, context.enclosingDeclaration); + if ( + // Check for unusable parameters symbols + symAtLocation === unknownSymbol || + // If the symbol is not found, but was not found in the original scope either we probably have an error, don't reuse the node + (symAtLocation === undefined && sym !== undefined) || + // If the symbol is found both in declaration scope and in current scope then it shoudl point to the same reference + (symAtLocation && sym && !getSymbolIfSameReference(getExportSymbolOfValueSymbolIfExported(symAtLocation), sym)) + ) { + // In isolated declaration we will not do rest parameter expansion so there is no need to report on these. + if (symAtLocation !== unknownSymbol) { + context.tracker.reportInferenceFallback(node); + } + introducesError = true; + return { introducesError, node, sym }; + } + } + + if (sym) { + // If a parameter is resolvable in the current context it is also visible, so no need to go to symbol accesibility + if ( + sym.flags & SymbolFlags.FunctionScopedVariable + && sym.valueDeclaration + ) { + if (isPartOfParameterDeclaration(sym.valueDeclaration) || isJSDocParameterTag(sym.valueDeclaration)) { + return { introducesError, node: attachSymbolToLeftmostIdentifier(node) as T }; + } + } + if ( + !(sym.flags & SymbolFlags.TypeParameter) && // Type parameters are visible in the current context if they are are resolvable + !isDeclarationName(node) && + isSymbolAccessible(sym, context.enclosingDeclaration, meaning, /*shouldComputeAliasesToMakeVisible*/ false).accessibility !== SymbolAccessibility.Accessible + ) { + context.tracker.reportInferenceFallback(node); + introducesError = true; + } + else { + context.tracker.trackSymbol(sym, context.enclosingDeclaration, meaning); + } + return { introducesError, node: attachSymbolToLeftmostIdentifier(node) as T }; + } + + return { introducesError, node }; + + /** + * Attaches a `.symbol` member to an identifier, cloning it to do so, so symbol information + * is smuggled out for symbol display information. + */ + function attachSymbolToLeftmostIdentifier(node: Node): Node { + if (node === leftmost) { + const type = getDeclaredTypeOfSymbol(sym!); + const name = sym!.flags & SymbolFlags.TypeParameter ? typeParameterToName(type, context) : factory.cloneNode(node as Identifier); + name.symbol = sym!; // for quickinfo, which uses identifier symbol information + return setTextRange(context, setEmitFlags(name, EmitFlags.NoAsciiEscaping), node); + } + const updated = visitEachChildWorker(node, c => attachSymbolToLeftmostIdentifier(c), /*context*/ undefined); + if (updated !== node) { + setTextRange(context, updated, node); + } + return updated; + } + } + + function serializeTypeName(context: NodeBuilderContext, node: EntityName, isTypeOf?: boolean, typeArguments?: readonly TypeNode[]) { + const meaning = isTypeOf ? SymbolFlags.Value : SymbolFlags.Type; + const symbol = resolveEntityName(node, meaning, /*ignoreErrors*/ true); + if (!symbol) return undefined; + const resolvedSymbol = symbol.flags & SymbolFlags.Alias ? resolveAlias(symbol) : symbol; + if (isSymbolAccessible(symbol, context.enclosingDeclaration, meaning, /*shouldComputeAliasesToMakeVisible*/ false).accessibility !== SymbolAccessibility.Accessible) return undefined; + return symbolToTypeNode(resolvedSymbol, context, meaning, typeArguments); + } + + function canReuseTypeNode(context: NodeBuilderContext, existing: TypeNode) { + if (isInJSFile(existing)) { + if (isLiteralImportTypeNode(existing)) { + // Ensure resolvedSymbol is present + void getTypeFromImportTypeNode(existing); + const nodeSymbol = getNodeLinks(existing).resolvedSymbol; + return ( + !nodeSymbol || + !( + // The import type resolved using jsdoc fallback logic + (!existing.isTypeOf && !(nodeSymbol.flags & SymbolFlags.Type)) || + // The import type had type arguments autofilled by js fallback logic + !(length(existing.typeArguments) >= getMinTypeArgumentCount(getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(nodeSymbol))) + ) + ); + } + } + if (isThisTypeNode(existing)) { + if (context.mapper === undefined) return true; + const type = getTypeFromTypeNode(context, existing, /*noMappedTypes*/ true); + return !!type; + } + if (isTypeReferenceNode(existing)) { + if (isConstTypeReference(existing)) return false; + const type = getTypeFromTypeReference(existing); + const symbol = getNodeLinks(existing).resolvedSymbol; + if (!symbol) return false; + if (symbol.flags & SymbolFlags.TypeParameter) { + const type = getDeclaredTypeOfSymbol(symbol); + if (context.mapper && getMappedType(type, context.mapper) !== type) { + return false; + } + } + if (isInJSDoc(existing)) { + return existingTypeNodeIsNotReferenceOrIsReferenceWithCompatibleTypeArgumentCount(existing, type) + && !getIntendedTypeFromJSDocTypeReference(existing) // We should probably allow the reuse of JSDoc reference types such as String Number etc + && (symbol.flags & SymbolFlags.Type); // JSDoc type annotations can reference values (meaning typeof value) as well as types. We only reuse type nodes + } + } + if ( + isTypeOperatorNode(existing) && + existing.operator === SyntaxKind.UniqueKeyword && + existing.type.kind === SyntaxKind.SymbolKeyword + ) { + const effectiveEnclosingContext = context.enclosingDeclaration && getEnclosingDeclarationIgnoringFakeScope(context.enclosingDeclaration); + return !!findAncestor(existing, n => n === effectiveEnclosingContext); + } + return true; + } + + function serializeExistingTypeNode(context: NodeBuilderContext, typeNode: TypeNode) { + const type = getTypeFromTypeNode(context, typeNode); + return typeToTypeNodeHelper(type, context); + } + + /** + * Do you mean to call this directly? You probably should use `tryReuseExistingTypeNode` instead, + * which performs sanity checking on the type before doing this. + */ + function tryReuseExistingTypeNodeHelper(context: NodeBuilderContext, existing: TypeNode) { + if (cancellationToken && cancellationToken.throwIfCancellationRequested) { + cancellationToken.throwIfCancellationRequested(); + } + let hadError = false; + const { finalizeBoundary, startRecoveryScope } = createRecoveryBoundary(); + const transformed = visitNode(existing, visitExistingNodeTreeSymbols, isTypeNode); + if (!finalizeBoundary()) { + return undefined; + } + context.approximateLength += existing.end - existing.pos; + return transformed; + + function visitExistingNodeTreeSymbols(node: Node): Node | undefined { + // If there was an error in a sibling node bail early, the result will be discarded anyway + if (hadError) return node; + const recover = startRecoveryScope(); + const onExitNewScope = isNewScopeNode(node) ? onEnterNewScope(node) : undefined; + const result = visitExistingNodeTreeSymbolsWorker(node); + onExitNewScope?.(); + + // If there was an error, maybe we can recover by serializing the actual type of the node + if (hadError) { + if (isTypeNode(node) && !isTypePredicateNode(node)) { + recover(); + return serializeExistingTypeNode(context, node); + } + return node; + } + // We want to clone the subtree, so when we mark it up with __pos and __end in quickfixes, + // we don't get odd behavior because of reused nodes. We also need to clone to _remove_ + // the position information if the node comes from a different file than the one the node builder + // is set to build for (even though we are reusing the node structure, the position information + // would make the printer print invalid spans for literals and identifiers, and the formatter would + // choke on the mismatched positonal spans between a parent and an injected child from another file). + return result ? setTextRange(context, result, node) : undefined; + } + + function createRecoveryBoundary() { + let trackedSymbols: TrackedSymbol[]; + let unreportedErrors: (() => void)[]; + const oldTracker = context.tracker; + const oldTrackedSymbols = context.trackedSymbols; + context.trackedSymbols = undefined; + const oldEncounteredError = context.encounteredError; + context.tracker = new SymbolTrackerImpl(context, { + ...oldTracker.inner, + reportCyclicStructureError() { + markError(() => oldTracker.reportCyclicStructureError()); + }, + reportInaccessibleThisError() { + markError(() => oldTracker.reportInaccessibleThisError()); + }, + reportInaccessibleUniqueSymbolError() { + markError(() => oldTracker.reportInaccessibleUniqueSymbolError()); + }, + reportLikelyUnsafeImportRequiredError(specifier) { + markError(() => oldTracker.reportLikelyUnsafeImportRequiredError(specifier)); + }, + reportNonSerializableProperty(name) { + markError(() => oldTracker.reportNonSerializableProperty(name)); + }, + trackSymbol(sym, decl, meaning) { + (trackedSymbols ??= []).push([sym, decl, meaning]); + return false; + }, + moduleResolverHost: context.tracker.moduleResolverHost, + }, context.tracker.moduleResolverHost); + + return { + startRecoveryScope, + finalizeBoundary, + }; + + function markError(unreportedError: () => void) { + hadError = true; + (unreportedErrors ??= []).push(unreportedError); + } + + function startRecoveryScope() { + const trackedSymbolsTop = trackedSymbols?.length ?? 0; + const unreportedErrorsTop = unreportedErrors?.length ?? 0; + return () => { + hadError = false; + // Reset the tracked symbols to before the error + if (trackedSymbols) { + trackedSymbols.length = trackedSymbolsTop; + } + if (unreportedErrors) { + unreportedErrors.length = unreportedErrorsTop; + } + }; + } + + function finalizeBoundary() { + context.tracker = oldTracker; + context.trackedSymbols = oldTrackedSymbols; + context.encounteredError = oldEncounteredError; + + unreportedErrors?.forEach(fn => fn()); + if (hadError) { + return false; + } + trackedSymbols?.forEach( + ([symbol, enclosingDeclaration, meaning]) => + context.tracker.trackSymbol( + symbol, + enclosingDeclaration, + meaning, + ), + ); + return true; + } + } + function onEnterNewScope(node: IntroducesNewScopeNode | ConditionalTypeNode) { + return enterNewScope(context, node, getParametersInScope(node), getTypeParametersInScope(node)); + } + + function tryVisitSimpleTypeNode(node: TypeNode): TypeNode | undefined { + const innerNode = skipTypeParentheses(node); + switch (innerNode.kind) { + case SyntaxKind.TypeReference: + return tryVisitTypeReference(innerNode as TypeReferenceNode); + case SyntaxKind.TypeQuery: + return tryVisitTypeQuery(innerNode as TypeQueryNode); + case SyntaxKind.IndexedAccessType: + return tryVisitIndexedAccess(innerNode as IndexedAccessTypeNode); + case SyntaxKind.TypeOperator: + const typeOperatorNode = innerNode as TypeOperatorNode; + if (typeOperatorNode.operator === SyntaxKind.KeyOfKeyword) { + return tryVisitKeyOf(typeOperatorNode); + } + } + return visitNode(node, visitExistingNodeTreeSymbols, isTypeNode); + } + + function tryVisitIndexedAccess(node: IndexedAccessTypeNode): TypeNode | undefined { + const resultObjectType = tryVisitSimpleTypeNode(node.objectType); + if (resultObjectType === undefined) { + return undefined; + } + return factory.updateIndexedAccessTypeNode(node, resultObjectType, visitNode(node.indexType, visitExistingNodeTreeSymbols, isTypeNode)!); + } + + function tryVisitKeyOf(node: TypeOperatorNode): TypeNode | undefined { + Debug.assertEqual(node.operator, SyntaxKind.KeyOfKeyword); + const type = tryVisitSimpleTypeNode(node.type); + if (type === undefined) { + return undefined; + } + return factory.updateTypeOperatorNode(node, type); + } + + function tryVisitTypeQuery(node: TypeQueryNode): TypeNode | undefined { + const { introducesError, node: exprName } = trackExistingEntityName(node.exprName, context); + if (!introducesError) { + return factory.updateTypeQueryNode( + node, + exprName, + visitNodes(node.typeArguments, visitExistingNodeTreeSymbols, isTypeNode), + ); + } + + const serializedName = serializeTypeName(context, node.exprName, /*isTypeOf*/ true); + if (serializedName) { + return setTextRange(context, serializedName, node.exprName); + } + } + + function tryVisitTypeReference(node: TypeReferenceNode): TypeNode | undefined { + if (canReuseTypeNode(context, node)) { + const { introducesError, node: newName } = trackExistingEntityName(node.typeName, context); + const typeArguments = visitNodes(node.typeArguments, visitExistingNodeTreeSymbols, isTypeNode); + + if (!introducesError) { + const updated = factory.updateTypeReferenceNode( + node, + newName, + typeArguments, + ); + return setTextRange(context, updated, node); + } + else { + const serializedName = serializeTypeName(context, node.typeName, /*isTypeOf*/ false, typeArguments); + if (serializedName) { + return setTextRange(context, serializedName, node.typeName); + } + } + } + } + + function visitExistingNodeTreeSymbolsWorker(node: Node): Node | undefined { + if (isJSDocTypeExpression(node)) { + // Unwrap JSDocTypeExpressions + return visitNode(node.type, visitExistingNodeTreeSymbols, isTypeNode); + } + // 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, isTypeNode)!, factory.createLiteralTypeNode(factory.createNull())]); + } + if (isJSDocOptionalType(node)) { + return factory.createUnionTypeNode([visitNode(node.type, visitExistingNodeTreeSymbols, isTypeNode)!, factory.createKeywordTypeNode(SyntaxKind.UndefinedKeyword)]); + } + if (isJSDocNonNullableType(node)) { + return visitNode(node.type, visitExistingNodeTreeSymbols); + } + if (isJSDocVariadicType(node)) { + return factory.createArrayTypeNode(visitNode(node.type, visitExistingNodeTreeSymbols, isTypeNode)!); + } + if (isJSDocTypeLiteral(node)) { + return factory.createTypeLiteralNode(map(node.jsDocPropertyTags, t => { + const name = visitNode(isIdentifier(t.name) ? t.name : t.name.right, visitExistingNodeTreeSymbols, isIdentifier)!; + const typeViaParent = getTypeOfPropertyOfType(getTypeFromTypeNode(context, node), name.escapedText); + const overrideTypeNode = typeViaParent && t.typeExpression && getTypeFromTypeNode(context, t.typeExpression.type) !== typeViaParent ? typeToTypeNodeHelper(typeViaParent, context) : undefined; + + 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, isTypeNode)) || 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, isTypeNode), + )], + visitNode(node.typeArguments![1], visitExistingNodeTreeSymbols, isTypeNode), + )]); + } + if (isJSDocFunctionType(node)) { + if (isJSDocConstructSignature(node)) { + let newTypeNode: TypeNode | undefined; + return factory.createConstructorTypeNode( + /*modifiers*/ undefined, + visitNodes(node.typeParameters, visitExistingNodeTreeSymbols, isTypeParameterDeclaration), + mapDefined(node.parameters, (p, i) => + p.name && isIdentifier(p.name) && p.name.escapedText === "new" ? (newTypeNode = p.type, undefined) : factory.createParameterDeclaration( + /*modifiers*/ undefined, + getEffectiveDotDotDotForParameter(p), + setTextRange(context, factory.createIdentifier(getNameForJSDocFunctionParameter(p, i)), p), + factory.cloneNode(p.questionToken), + visitNode(p.type, visitExistingNodeTreeSymbols, isTypeNode), + /*initializer*/ undefined, + )), + visitNode(newTypeNode || node.type, visitExistingNodeTreeSymbols, isTypeNode) || factory.createKeywordTypeNode(SyntaxKind.AnyKeyword), + ); + } + else { + return factory.createFunctionTypeNode( + visitNodes(node.typeParameters, visitExistingNodeTreeSymbols, isTypeParameterDeclaration), + map(node.parameters, (p, i) => + factory.createParameterDeclaration( + /*modifiers*/ undefined, + getEffectiveDotDotDotForParameter(p), + setTextRange(context, factory.createIdentifier(getNameForJSDocFunctionParameter(p, i)), p), + factory.cloneNode(p.questionToken), + visitNode(p.type, visitExistingNodeTreeSymbols, isTypeNode), + /*initializer*/ undefined, + )), + visitNode(node.type, visitExistingNodeTreeSymbols, isTypeNode) || factory.createKeywordTypeNode(SyntaxKind.AnyKeyword), + ); + } + } + if (isThisTypeNode(node)) { + if (canReuseTypeNode(context, node)) { + return node; + } + hadError = true; + return node; + } + if (isTypeParameterDeclaration(node)) { + return factory.updateTypeParameterDeclaration( + node, + visitNodes(node.modifiers, visitExistingNodeTreeSymbols, isModifier), + setTextRange(context, typeParameterToName(getDeclaredTypeOfSymbol(getSymbolOfDeclaration(node)), context), node), + visitNode(node.constraint, visitExistingNodeTreeSymbols, isTypeNode), + visitNode(node.default, visitExistingNodeTreeSymbols, isTypeNode), + ); + } + + if (isIndexedAccessTypeNode(node)) { + const result = tryVisitIndexedAccess(node); + if (!result) { + hadError = true; + return node; + } + return result; + } + + if (isTypeReferenceNode(node)) { + const result = tryVisitTypeReference(node); + if (result) { + return result; + } + hadError = true; + return 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 setTextRange(context, typeToTypeNodeHelper(getTypeFromTypeNode(context, node), context), node); + } + return factory.updateImportTypeNode( + node, + factory.updateLiteralTypeNode(node.argument, rewriteModuleSpecifier(node, node.argument.literal)), + visitNode(node.attributes, visitExistingNodeTreeSymbols, isImportAttributes), + visitNode(node.qualifier, visitExistingNodeTreeSymbols, isEntityName), + visitNodes(node.typeArguments, visitExistingNodeTreeSymbols, isTypeNode), + node.isTypeOf, + ); + } + if (isNamedDeclaration(node) && node.name.kind === SyntaxKind.ComputedPropertyName && !isLateBindableName(node.name)) { + if (!(context.flags & NodeBuilderFlags.AllowUnresolvedNames && hasDynamicName(node) && isEntityNameExpression(node.name.expression) && checkComputedPropertyName(node.name).flags & TypeFlags.Any)) { + return undefined; + } + } + if ( + (isFunctionLike(node) && !node.type) + || (isPropertyDeclaration(node) && !node.type && !node.initializer) + || (isPropertySignature(node) && !node.type && !node.initializer) + || (isParameter(node) && !node.type && !node.initializer) + ) { + let visited = visitEachChild(node, visitExistingNodeTreeSymbols); + if (visited === node) { + visited = setTextRange(context, factory.cloneNode(node), node); + } + (visited as Mutable).type = factory.createKeywordTypeNode(SyntaxKind.AnyKeyword); + if (isParameter(node)) { + (visited as Mutable).modifiers = undefined; + } + return visited; + } + if (isTypeQueryNode(node)) { + const result = tryVisitTypeQuery(node); + if (!result) { + hadError = true; + return node; + } + return result; + } + if (isComputedPropertyName(node) && isEntityNameExpression(node.expression)) { + const { node: result, introducesError } = trackExistingEntityName(node.expression, context); + if (!introducesError) { + return factory.updateComputedPropertyName(node, result); + } + else { + const type = getWidenedType(getRegularTypeOfExpression(node.expression)); + const computedPropertyNameType = typeToTypeNodeHelper(type, context); + let literal; + if (isLiteralTypeNode(computedPropertyNameType)) { + literal = computedPropertyNameType.literal; + } + else { + const evaluated = evaluateEntityNameExpression(node.expression); + const literalNode = typeof evaluated.value === "string" ? factory.createStringLiteral(evaluated.value, /*isSingleQuote*/ undefined) : + typeof evaluated.value === "number" ? factory.createNumericLiteral(evaluated.value, /*numericLiteralFlags*/ 0) : + undefined; + if (!literalNode) { + if (isImportTypeNode(computedPropertyNameType)) { + trackComputedName(node.expression, context.enclosingDeclaration, context); + } + return node; + } + literal = literalNode; + } + if (literal.kind === SyntaxKind.StringLiteral && isIdentifierText(literal.text, getEmitScriptTarget(compilerOptions))) { + return factory.createIdentifier(literal.text); + } + if (literal.kind === SyntaxKind.NumericLiteral && !literal.text.startsWith("-")) { + return literal; + } + return factory.updateComputedPropertyName(node, literal); + } + } + if (isTypePredicateNode(node)) { + let parameterName; + if (isIdentifier(node.parameterName)) { + const { node: result, introducesError } = trackExistingEntityName(node.parameterName, context); + // Should not usually happen the only case is when a type predicate comes from a JSDoc type annotation with it's own parameter symbol definition. + // /** @type {(v: unknown) => v is undefined} */ + // const isUndef = v => v === undefined; + hadError = hadError || introducesError; + parameterName = result; + } + else { + parameterName = factory.cloneNode(node.parameterName); + } + return factory.updateTypePredicateNode(node, factory.cloneNode(node.assertsModifier), parameterName, visitNode(node.type, visitExistingNodeTreeSymbols, isTypeNode)); + } + + if (isTupleTypeNode(node) || isTypeLiteralNode(node) || isMappedTypeNode(node)) { + const visited = visitEachChild(node, visitExistingNodeTreeSymbols); + const clone = setTextRange(context, visited === node ? factory.cloneNode(node) : visited, node); + const flags = getEmitFlags(clone); + setEmitFlags(clone, flags | (context.flags & NodeBuilderFlags.MultilineObjectLiterals && isTypeLiteralNode(node) ? 0 : EmitFlags.SingleLine)); + return clone; + } + if (isStringLiteral(node) && !!(context.flags & NodeBuilderFlags.UseSingleQuotesForStringLiteralType) && !node.singleQuote) { + const clone = factory.cloneNode(node); + (clone as Mutable).singleQuote = true; + return clone; + } + if (isConditionalTypeNode(node)) { + const checkType = visitNode(node.checkType, visitExistingNodeTreeSymbols, isTypeNode)!; + + const disposeScope = onEnterNewScope(node); + const extendType = visitNode(node.extendsType, visitExistingNodeTreeSymbols, isTypeNode)!; + const trueType = visitNode(node.trueType, visitExistingNodeTreeSymbols, isTypeNode)!; + disposeScope(); + const falseType = visitNode(node.falseType, visitExistingNodeTreeSymbols, isTypeNode)!; + return factory.updateConditionalTypeNode( + node, + checkType, + extendType, + trueType, + falseType, + ); + } + + if (isTypeOperatorNode(node)) { + if (node.operator === SyntaxKind.UniqueKeyword && node.type.kind === SyntaxKind.SymbolKeyword) { + if (!canReuseTypeNode(context, node)) { + hadError = true; + return node; + } + } + else if (node.operator === SyntaxKind.KeyOfKeyword) { + const result = tryVisitKeyOf(node); + if (!result) { + hadError = true; + return node; + } + return result; + } + } + + return visitEachChild(node, visitExistingNodeTreeSymbols); + + function visitEachChild(node: T, visitor: Visitor): T; + function visitEachChild(node: T | undefined, visitor: Visitor): T | undefined; + function visitEachChild(node: T | undefined, visitor: Visitor): T | undefined { + const nonlocalNode = !context.enclosingFile || context.enclosingFile !== getSourceFileOfNode(node); + return visitEachChildWorker(node, visitor, /*context*/ undefined, nonlocalNode ? visitNodesWithoutCopyingPositions : undefined); + } + + function visitNodesWithoutCopyingPositions( + nodes: NodeArray | undefined, + visitor: Visitor, + test?: (node: Node) => boolean, + start?: number, + count?: number, + ): NodeArray | undefined { + let result = visitNodes(nodes, visitor, test, start, count); + if (result) { + if (result.pos !== -1 || result.end !== -1) { + if (result === nodes) { + result = factory.createNodeArray(nodes, nodes.hasTrailingComma); + } + setTextRangePosEnd(result, -1, -1); + } + } + return result; + } + + function getEffectiveDotDotDotForParameter(p: ParameterDeclaration) { + return p.dotDotDotToken || (p.type && isJSDocVariadicType(p.type) ? factory.createToken(SyntaxKind.DotDotDotToken) : 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}`; + } + + function rewriteModuleSpecifier(parent: ImportTypeNode, lit: StringLiteral) { + if (context.bundled || context.enclosingFile !== getSourceFileOfNode(lit)) { + let name = lit.text; + const nodeSymbol = getNodeLinks(node).resolvedSymbol; + const meaning = parent.isTypeOf ? SymbolFlags.Value : SymbolFlags.Type; + const parentSymbol = nodeSymbol + && isSymbolAccessible(nodeSymbol, context.enclosingDeclaration, meaning, /*shouldComputeAliasesToMakeVisible*/ false).accessibility === SymbolAccessibility.Accessible + && lookupSymbolChain(nodeSymbol, context, meaning, /*yieldModuleSymbol*/ true)[0]; + if (parentSymbol && isExternalModuleSymbol(parentSymbol)) { + name = getSpecifierForModuleSymbol(parentSymbol, context); + } + else { + const targetFile = getExternalModuleFileFromDeclaration(parent); + if (targetFile) { + name = getSpecifierForModuleSymbol(targetFile.symbol, context); + } + } + if (name.includes("/node_modules/")) { + context.encounteredError = true; + if (context.tracker.reportLikelyUnsafeImportRequiredError) { + context.tracker.reportLikelyUnsafeImportRequiredError(name); + } + } + if (name !== lit.text) { + return setOriginalNode(factory.createStringLiteral(name), lit); + } + } + return visitNode(lit, visitExistingNodeTreeSymbols, isStringLiteral)!; + } + } + } + + function symbolTableToDeclarationStatements(symbolTable: SymbolTable, context: NodeBuilderContext): Statement[] { + const serializePropertySymbolForClass = makeSerializePropertySymbol(factory.createPropertyDeclaration, SyntaxKind.MethodDeclaration, /*useAccessors*/ true); + const serializePropertySymbolForInterfaceWorker = makeSerializePropertySymbol((mods, name, question, type) => factory.createPropertySignature(mods, name, question, type), SyntaxKind.MethodSignature, /*useAccessors*/ 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: Map[] = []; + const oldcontext = context; + context = { + ...oldcontext, + usedSymbolNames: new Set(oldcontext.usedSymbolNames), + remappedSymbolNames: new Map(), + remappedSymbolReferences: new Map(oldcontext.remappedSymbolReferences?.entries()), + tracker: undefined!, + }; + const tracker: SymbolTracker = { + ...oldcontext.tracker.inner, + trackSymbol: (sym, decl, meaning) => { + if (context.remappedSymbolNames?.has(getSymbolId(sym))) return false; // If the context has a remapped name for the symbol, it *should* mean it's been made visible + const accessibleResult = isSymbolAccessible(sym, decl, meaning, /*shouldComputeAliasesToMakeVisible*/ 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)) { + // Only include referenced privates in the same file. Weird JS aliases may expose privates + // from other files - assume JS transforms will make those available via expected means + const root = chain[0]; + const contextFile = getSourceFileOfNode(oldcontext.enclosingDeclaration); + if (some(root.declarations, d => getSourceFileOfNode(d) === contextFile)) { + includePrivateSymbol(root); + } + } + } + else if (oldcontext.tracker.inner?.trackSymbol) { + return oldcontext.tracker.inner.trackSymbol(sym, decl, meaning); + } + return false; + }, + }; + context.tracker = new SymbolTrackerImpl(context, tracker, oldcontext.tracker.moduleResolverHost); + forEachEntry(symbolTable, (symbol, name) => { + const baseName = unescapeLeadingUnderscores(name); + void getInternalSymbolName(symbol, baseName); // Called to cache values into `usedSymbolNames` and `remappedSymbolNames` + }); + let addingDeclare = !context.bundled; + const exportEquals = symbolTable.get(InternalSymbolName.ExportEquals); + if (exportEquals && symbolTable.size > 1 && exportEquals.flags & (SymbolFlags.Alias | SymbolFlags.Module)) { + 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, /*propertyName*/ 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]; + } + } + 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.includes(s as ExportDeclaration)), + factory.createExportDeclaration( + /*modifiers*/ undefined, + /*isTypeOnly*/ false, + factory.createNamedExports(flatMap(group, e => cast(e.exportClause, isNamedExports).elements)), + group[0].moduleSpecifier, + ), + ]; + } + } + } + } + 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.attributes && !!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 && e.name.kind !== SyntaxKind.StringLiteral) { + // export {name} - look thru `statements` for `name`, and if all results can take an `export` modifier, do so and filter it + const name = e.name; + const indices = indicesOf(statements); + const associatedIndices = filter(indices, i => nodeHasName(statements[i], 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.attributes, + ); + } + } + return 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; + } + + function addExportModifier(node: Extract) { + const flags = (getEffectiveModifierFlags(node) | ModifierFlags.Export) & ~ModifierFlags.Ambient; + return factory.replaceModifiers(node, flags); + } + + function removeExportModifier(node: Extract) { + const flags = getEffectiveModifierFlags(node) & ~ModifierFlags.Export; + return factory.replaceModifiers(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); + }); + 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 { + void getPropertiesOfType(getTypeOfSymbol(symbol)); // resolve symbol's type and properties, which should trigger any required merges + // 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 scopeCleanup = cloneNodeBuilderContext(context); + serializeSymbolWorker(symbol, isPrivate, propertyAsAlias); + scopeCleanup(); + } + } + + // 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, escapedSymbolName = symbol.escapedName): void { + const symbolName = unescapeLeadingUnderscores(escapedSymbolName); + const isDefault = escapedSymbolName === 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) && + escapedSymbolName !== 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 | SymbolFlags.Accessor) + && escapedSymbolName !== 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 (type.symbol && type.symbol !== symbol && type.symbol.flags & SymbolFlags.Function && some(type.symbol.declarations, isFunctionExpressionOrArrowFunction) && (type.symbol.members?.size || type.symbol.exports?.size)) { + // assignment of a anonymous expando/class-like function, the func/ns/merge branch below won't trigger, + // and the assignment form has to reference the unreachable anonymous type so will error. + // Instead, serialize the type's symbol, but with the current symbol's name, rather than the anonymous one. + if (!context.remappedSymbolReferences) { + context.remappedSymbolReferences = new Map(); + } + context.remappedSymbolReferences.set(getSymbolId(type.symbol), symbol); // save name remapping as local name for target symbol + serializeSymbolWorker(type.symbol, isPrivate, propertyAsAlias, escapedSymbolName); + context.remappedSymbolReferences.delete(getSymbolId(type.symbol)); + } + else 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 { + // 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 + : isConstantVariable(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 { + const statement = setTextRange( + context, + factory.createVariableStatement( + /*modifiers*/ undefined, + factory.createVariableDeclarationList([ + factory.createVariableDeclaration(name, /*exclamationToken*/ undefined, serializeTypeForDeclaration(context, /*declaration*/ undefined, type, symbol)), + ], 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; + } + } + } + } + } + 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) { + 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*/ (node as ExportDeclaration).isTypeOnly, /*exportClause*/ undefined, factory.createStringLiteral(getSpecifierForModuleSymbol(resolvedModule, context))), ModifierFlags.None); + } + } + } + if (needsPostExportDefault) { + addResult(factory.createExportAssignment(/*modifiers*/ undefined, /*isExportEquals*/ 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, isPartOfParameterDeclaration)) 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.replaceModifiers(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) + && tryReuseExistingNonParameterTypeNode(context, jsdocAliasDecl.typeExpression.type, aliasType, /*host*/ undefined) + || 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) { + let exports = arrayFrom(getExportsOfSymbol(symbol).values()); + const merged = getMergedSymbol(symbol); + if (merged !== symbol) { + const membersSet = new Set(exports); + for (const exported of getExportsOfSymbol(merged).values()) { + if (!(getSymbolFlags(resolveSymbol(exported)) & SymbolFlags.Value)) { + membersSet.add(exported); + } + } + exports = arrayFrom(membersSet); + } + return filter(exports, m => isNamespaceMember(m) && isIdentifierText(m.escapedName as string, ScriptTarget.ESNext)); + } + + function isTypeOnlyNamespace(symbol: Symbol) { + return every(getNamespaceMembersForSerialization(symbol), m => !(getSymbolFlags(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, + factory.createIdentifier(localName), + nsBody, + NodeFlags.Namespace, + ), + ModifierFlags.None, + ); + } + } + + 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 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) }) as FunctionDeclaration; + addResult(setTextRange(context, 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 getSignatureTextRangeLocation(signature: Signature) { + if (signature.declaration && signature.declaration.parent) { + if (isBinaryExpression(signature.declaration.parent) && getAssignmentDeclarationKind(signature.declaration.parent) === AssignmentDeclarationKind.Property) { + return signature.declaration.parent; + } + // 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 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 as Extract[], 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)); + if (introducesError) { + return cleanup(/*result*/ undefined); + } + } + return cleanup(factory.createExpressionWithTypeArguments( + expr, + map(e.typeArguments, a => + tryReuseExistingNonParameterTypeNode(context, a, getTypeFromTypeNode(context, a)) + || typeToTypeNodeHelper(getTypeFromTypeNode(context, a), context)), + )); + + function cleanup(result: T): T { + context.enclosingDeclaration = oldEnclosing; + return result; + } + }); + 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 = getTypeWithThisArgument(getDeclaredTypeOfClassOrInterface(symbol)) as InterfaceType; + 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( + context, + 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 getSomeTargetNameFromDeclarations(declarations: Declaration[] | undefined) { + return firstDefined(declarations, d => { + if (isImportSpecifier(d) || isExportSpecifier(d)) { + return moduleExportNameTextUnescaped(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; + }); + } + + 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 && 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( + /*isTypeOnly*/ false, + /*name*/ undefined, + factory.createNamedImports([factory.createImportSpecifier( + /*isTypeOnly*/ false, + propertyName && isIdentifier(propertyName) ? factory.createIdentifier(idText(propertyName)) : undefined, + factory.createIdentifier(localName), + )]), + ), + factory.createStringLiteral(specifier), + /*attributes*/ 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, d => isSourceFile(d) && isJsonSourceFile(d))) { + 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: { + const generatedSpecifier = getSpecifierForModuleSymbol(target.parent || target, context); // generate specifier (even though we're reusing and existing one) for ambient module reference include side effects + const specifier = context.bundled ? factory.createStringLiteral(generatedSpecifier) : (node as ImportClause).parent.moduleSpecifier; + const attributes = isImportDeclaration(node.parent) ? node.parent.attributes : undefined; + const isTypeOnly = isJSDocImportTag((node as ImportClause).parent); + addResult( + factory.createImportDeclaration( + /*modifiers*/ undefined, + factory.createImportClause(isTypeOnly, factory.createIdentifier(localName), /*namedBindings*/ undefined), + specifier, + attributes, + ), + ModifierFlags.None, + ); + break; + } + case SyntaxKind.NamespaceImport: { + const generatedSpecifier = getSpecifierForModuleSymbol(target.parent || target, context); // generate specifier (even though we're reusing and existing one) for ambient module reference include side effects + const specifier = context.bundled ? factory.createStringLiteral(generatedSpecifier) : (node as NamespaceImport).parent.parent.moduleSpecifier; + const isTypeOnly = isJSDocImportTag((node as NamespaceImport).parent.parent); + addResult( + factory.createImportDeclaration( + /*modifiers*/ undefined, + factory.createImportClause(isTypeOnly, /*name*/ undefined, factory.createNamespaceImport(factory.createIdentifier(localName))), + specifier, + (node as ImportClause).parent.attributes, + ), + 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: { + const generatedSpecifier = getSpecifierForModuleSymbol(target.parent || target, context); // generate specifier (even though we're reusing and existing one) for ambient module reference include side effects + const specifier = context.bundled ? factory.createStringLiteral(generatedSpecifier) : (node as ImportSpecifier).parent.parent.parent.moduleSpecifier; + const isTypeOnly = isJSDocImportTag((node as ImportSpecifier).parent.parent.parent); + addResult( + factory.createImportDeclaration( + /*modifiers*/ undefined, + factory.createImportClause( + isTypeOnly, + /*name*/ undefined, + factory.createNamedImports([ + factory.createImportSpecifier( + /*isTypeOnly*/ false, + localName !== verbatimTargetName ? factory.createIdentifier(verbatimTargetName) : undefined, + factory.createIdentifier(localName), + ), + ]), + ), + specifier, + (node as ImportSpecifier).parent.parent.parent.attributes, + ), + 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; + if (specifier) { + const propertyName = (node as ExportSpecifier).propertyName; + if (propertyName && moduleExportNameIsDefault(propertyName)) { + verbatimTargetName = InternalSymbolName.Default; + } + } + // 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 { + 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 prevDisableTrackSymbol = context.tracker.disableTrackSymbol; + context.tracker.disableTrackSymbol = true; + if (isExportAssignmentCompatibleSymbolName) { + results.push(factory.createExportAssignment( + /*modifiers*/ undefined, + isExportEquals, + symbolToExpression(target, context, SymbolFlags.All), + )); + } + 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); + } + } + context.tracker.disableTrackSymbol = prevDisableTrackSymbol; + 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 flags = context.enclosingDeclaration?.kind === SyntaxKind.ModuleDeclaration && (!(symbol.flags & SymbolFlags.Accessor) || symbol.flags & SymbolFlags.SetAccessor) ? NodeFlags.Let : NodeFlags.Const; + const statement = factory.createVariableStatement( + /*modifiers*/ undefined, + factory.createVariableDeclarationList([ + factory.createVariableDeclaration(varName, /*exclamationToken*/ undefined, serializeTypeForDeclaration(context, /*declaration*/ undefined, typeToSerialize, symbol)), + ], flags), + ); + // 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) && + !some(typeToSerialize.symbol?.declarations, isTypeNode) && // If the type comes straight from a type node, we shouldn't try to break it up + !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 => { + if (!isIdentifierText(symbolName(p), languageVersion)) { + return false; + } + if (!(p.flags & SymbolFlags.Accessor)) { + return true; + } + return getNonMissingTypeOfSymbol(p) === getWriteTypeOfSymbol(p); + }); + } + + 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 || p.escapedName === "constructor" || + (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) { + const setter = p.declarations && forEach(p.declarations, d => { + if (d.kind === SyntaxKind.SetAccessor) { + return d as SetAccessorDeclaration; + } + if (isCallExpression(d) && isBindableObjectDefinePropertyCall(d)) { + return forEach(d.arguments[2].properties, propDecl => { + const id = getNameOfDeclaration(propDecl); + if (!!id && isIdentifier(id) && idText(id) === "set") { + return propDecl; + } + }); + } + }); + + Debug.assert(!!setter); + const paramSymbol = isFunctionLikeDeclaration(setter) ? getSignatureFromDeclaration(setter).parameters[0] : undefined; + + result.push(setTextRange( + context, + factory.createSetAccessorDeclaration( + factory.createModifiersFromModifierFlags(flag), + name, + [factory.createParameterDeclaration( + /*modifiers*/ undefined, + /*dotDotDotToken*/ undefined, + paramSymbol ? parameterToParameterDeclarationName(paramSymbol, getEffectiveParameterDeclaration(paramSymbol), context) : "value", + /*questionToken*/ undefined, + isPrivate ? undefined : serializeTypeForDeclaration(context, /*declaration*/ undefined, getWriteTypeOfSymbol(p), p), + )], + /*body*/ undefined, + ), + p.declarations?.find(isSetAccessor) || firstPropertyLikeDecl, + )); + } + if (p.flags & SymbolFlags.GetAccessor) { + const isPrivate = modifierFlags & ModifierFlags.Private; + result.push(setTextRange( + context, + factory.createGetAccessorDeclaration( + factory.createModifiersFromModifierFlags(flag), + name, + [], + isPrivate ? undefined : serializeTypeForDeclaration(context, /*declaration*/ undefined, getTypeOfSymbol(p), p), + /*body*/ undefined, + ), + p.declarations?.find(isGetAccessor) || firstPropertyLikeDecl, + )); + } + 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( + context, + createProperty( + factory.createModifiersFromModifierFlags((isReadonlySymbol(p) ? ModifierFlags.Readonly : 0) | flag), + name, + p.flags & SymbolFlags.Optional ? factory.createToken(SyntaxKind.QuestionToken) : undefined, + isPrivate ? undefined : serializeTypeForDeclaration(context, /*declaration*/ undefined, getWriteTypeOfSymbol(p), p), + // 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( + context, + 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], + ); + } + + 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(context, decl, location)); + } + 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 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 (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 + } + } + } + let privateProtected: ModifierFlags = 0; + for (const s of signatures) { + if (s.declaration) { + privateProtected |= getSelectedEffectiveModifierFlags(s.declaration, ModifierFlags.Private | ModifierFlags.Protected); + } + } + if (privateProtected) { + return [setTextRange( + context, + factory.createConstructorDeclaration( + factory.createModifiersFromModifierFlags(privateProtected), + /*parameters*/ [], + /*body*/ undefined, + ), + signatures[0].declaration, + )]; + } + } + + 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(context, 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; + } + + 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), /*typeArguments*/ 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); + } + } + + 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), /*typeArguments*/ undefined); + } + } + + 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; + } + + 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 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 typePredicateToString(typePredicate: TypePredicate, enclosingDeclaration?: Node, flags: TypeFormatFlags = TypeFormatFlags.UseAliasDefinedOutsideCurrentScope, writer?: EmitTextWriter): string { + return writer ? typePredicateToStringWorker(writer).getText() : usingSingleLineStringWriter(typePredicateToStringWorker); + + function typePredicateToStringWorker(writer: EmitTextWriter) { + const nodeBuilderFlags = toNodeBuilderFlags(flags) | NodeBuilderFlags.IgnoreErrors | NodeBuilderFlags.WriteTypeParametersInQualifiedName; + const predicate = nodeBuilder.typePredicateToTypePredicateNode(typePredicate, enclosingDeclaration, nodeBuilderFlags)!; // TODO: GH#18217 + const printer = createPrinterWithRemoveComments(); + 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 = 0 as TypeFlags; + 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.EnumLike)) { + const baseType = t.flags & TypeFlags.BooleanLiteral ? booleanType : getBaseTypeOfEnumLikeType(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); + } + } + if (flags & TypeFlags.Null) result.push(nullType); + if (flags & TypeFlags.Undefined) result.push(undefinedType); + return result || types; + } + + function visibilityToString(flags: ModifierFlags): string { + if (flags === ModifierFlags.Private) { + return "private"; + } + if (flags === ModifierFlags.Protected) { + return "protected"; + } + return "public"; + } + + 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 (isTypeAliasDeclaration(node)) { + return getSymbolOfDeclaration(node); + } + } + return undefined; + } + + function isTopLevelInExternalModuleAugmentation(node: Node): boolean { + return node && node.parent && + node.parent.kind === SyntaxKind.ModuleBlock && + isExternalModuleAugmentation(node.parent.parent); + } + + 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; + } + 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?.remappedSymbolReferences?.has(getSymbolId(symbol))) { + symbol = context.remappedSymbolReferences.get(getSymbolId(symbol))!; + } + 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)"; + } + } + 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; + } + + 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 ( + !(getCombinedModifierFlagsCached(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 + + 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: ModuleExportName, setVisibility?: boolean): Node[] | undefined { + let exportSymbol: Symbol | undefined; + if (node.kind !== SyntaxKind.StringLiteral && node.parent && node.parent.kind === SyntaxKind.ExportAssignment) { + exportSymbol = resolveName(node, node, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias, /*nameNotFoundMessage*/ undefined, /*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, /*nameNotFoundMessage*/ 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; + } + + function findResolutionCycleStartIndex(target: TypeSystemEntity, propertyName: TypeSystemPropertyName): number { + for (let i = resolutionTargets.length - 1; i >= resolutionStart; i--) { + if (resolutionTargetHasProperty(resolutionTargets[i], resolutionPropertyNames[i])) { + return -1; + } + if (resolutionTargets[i] === target && resolutionPropertyNames[i] === propertyName) { + return i; + } + } + return -1; + } + + function resolutionTargetHasProperty(target: TypeSystemEntity, propertyName: TypeSystemPropertyName): boolean { + switch (propertyName) { + case TypeSystemPropertyName.Type: + return !!getSymbolLinks(target as Symbol).type; + 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; + case TypeSystemPropertyName.ParameterInitializerContainsUndefined: + return getNodeLinks(target as ParameterDeclaration).parameterInitializerContainsUndefined !== undefined; + } + 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; + } + + // 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; + } + + /** + * Return the type of the matching property or index signature in the given type, or undefined + * if no matching property or index signature exists. Add optionality to index signature types. + */ + function getTypeOfPropertyOrIndexSignatureOfType(type: Type, name: __String): Type | undefined { + let propType; + return getTypeOfPropertyOfType(type, name) || + (propType = getApplicableIndexInfoForName(type, name)?.type) && + addOptionality(propType, /*isProperty*/ true, /*isOptional*/ true); + } + + function isTypeAny(type: Type | undefined) { + return type && (type.flags & TypeFlags.Any) !== 0; + } + + 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); + } + + // 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 = getSymbolOfDeclaration(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)); + } + + let omitKeyType = getUnionType(map(properties, getLiteralTypeFromPropertyName)); + + const spreadableProperties: Symbol[] = []; + const unspreadableToRestKeys: Type[] = []; + + 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); + } + } + + 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]); + } + + if (omitKeyType.flags & TypeFlags.Never) { + return source; + } + + 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; + } + + function isGenericTypeWithUndefinedConstraint(type: Type) { + return !!(type.flags & TypeFlags.Instantiable) && maybeTypeOfKind(getBaseConstraintOfType(type) || unknownType, TypeFlags.Undefined); + } + + 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); + } + + // 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 && canHaveFlowNode(parentAccess) && parentAccess.flowNode) { + const propName = getDestructuringPropertyName(node); + if (propName) { + const literal = setTextRangeWorker(parseNodeFactory.createStringLiteral(propName), node); + const lhsExpr = isLeftHandSideExpression(parentAccess) ? parentAccess : parseNodeFactory.createParenthesizedExpression(parentAccess); + const result = setTextRangeWorker(parseNodeFactory.createElementAccessExpression(lhsExpr, literal), node); + setParent(literal, result); + setParent(result, node); + if (lhsExpr !== parentAccess) { + setParent(lhsExpr, result); + } + result.flowNode = parentAccess.flowNode; + return result; + } + } + } + + 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 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 getLiteralPropertyNameText(name: PropertyName) { + const type = getLiteralTypeFromPropertyName(name); + return type.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral) ? "" + (type as StringLiteralType | NumberLiteralType).value : undefined; + } + + /** 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, /*noTupleBoundsCheck*/ false); + } + + function getBindingElementTypeFromParentType(declaration: BindingElement, parentType: Type, noTupleBoundsCheck: boolean): 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 && isPartOfParameterDeclaration(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 && !(hasTypeFacts(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); + } + 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); + } + } + 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. + const baseConstraint = mapType(parentType, t => t.flags & TypeFlags.InstantiableNonPrimitive ? getBaseConstraintOrType(t) : t); + type = everyType(baseConstraint, isTupleType) ? + mapType(baseConstraint, t => sliceTupleType(t as TupleTypeReference, index)) : + createArrayType(elementType); + } + else if (isArrayLikeType(parentType)) { + const indexType = getNumberLiteralType(index); + const accessFlags = AccessFlags.ExpressionPosition | (noTupleBoundsCheck || 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 && !(hasTypeFacts(checkDeclarationInitializer(declaration, CheckMode.Normal), TypeFacts.IsUndefined)) ? getNonUndefinedType(type) : type; + } + 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); + } + 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; + } + + function isEmptyArrayLiteral(node: Expression) { + const expr = skipParentheses(node, /*excludeJSDocTypeAssertions*/ true); + return expr.kind === SyntaxKind.ArrayLiteralExpression && (expr as ArrayLiteralExpression).elements.length === 0; + } + + function addOptionality(type: Type, isProperty = false, isOptional = true): Type { + return strictNullChecks && isOptional ? getOptionalType(type, isProperty) : type; + } + + // 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) || isJSDocPropertyTag(declaration); + const isOptional = includeOptionality && isOptionalDeclaration(declaration); + + // Use type from type annotation if one is present + const declaredType = tryGetTypeFromEffectiveTypeNode(declaration); + if (isCatchClauseVariableDeclarationOrBindingElement(declaration)) { + if (declaredType) { + // If the catch clause is explicitly annotated with any or unknown, accept it, otherwise error. + return isTypeAny(declaredType) || declaredType === unknownType ? declaredType : errorType; + } + // If the catch clause is not explicitly annotated, treat it as though it were explicitly + // annotated with unknown or any, depending on useUnknownInCatchVariables. + return useUnknownInCatchVariables ? unknownType : anyType; + } + if (declaredType) { + return addOptionality(declaredType, isProperty, isOptional); + } + + if ( + (noImplicitAny || isInJSFile(declaration)) && + isVariableDeclaration(declaration) && !isBindingPattern(declaration.name) && + !(getCombinedModifierFlagsCached(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 (!(getCombinedNodeFlagsCached(declaration) & NodeFlags.Constant) && (!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)) { + if (!declaration.symbol) { + // parameters of function types defined in JSDoc in TS files don't have symbols + return; + } + 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(getSymbolOfDeclaration(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); + } + } + + // 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, getSymbolOfDeclaration(declaration), getDeclaredExpandoInitializer(declaration)); + if (containerObjectType) { + return containerObjectType; + } + } + const type = widenTypeInferredFromInitializer(declaration, checkDeclarationInitializer(declaration, checkMode)); + return addOptionality(type, isProperty, 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); + } + } + + 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 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); + } + + // 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; + } + + 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, /*includeClassComputedPropertyName*/ 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 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 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 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 = isInJSFile(container) ? getJSDocTypeTag(container) : undefined; + 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); + } + } + 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); + definedInConstructor = true; + } + } + 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 && isInJSFile(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); + } + 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 (symbol.parent?.valueDeclaration) { + const possiblyAnnotatedSymbol = getFunctionExpressionParentSymbolOrSymbol(symbol.parent); + if (possiblyAnnotatedSymbol.valueDeclaration) { + const typeNode = getEffectiveTypeAnnotationNode(possiblyAnnotatedSymbol.valueDeclaration); + if (typeNode) { + const annotationSymbol = getPropertyOfType(getTypeFromTypeNode(typeNode), symbol.escapedName); + if (annotationSymbol) { + return getNonMissingTypeOfSymbol(annotationSymbol); + } + } + } + } + + 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; + } + 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.links.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 |= getPropagatingFlagsOfTypes([type]) | getObjectFlags(type) & (ObjectFlags.JSLiteral | ObjectFlags.ArrayLiteral | ObjectFlags.ObjectLiteral); + 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, /*includeClassComputedPropertyName*/ 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, reportErrors ? CheckMode.Normal : CheckMode.Contextual, 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 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 exprType = getLiteralTypeFromPropertyName(name); + if (!isTypeUsableAsPropertyName(exprType)) { + // do not include computed properties in the implied type + objectFlags |= ObjectFlags.ObjectLiteralPatternWithComputedProperties; + return; + } + const text = getPropertyNameFromType(exprType); + const flags = SymbolFlags.Property | (e.initializer ? SymbolFlags.Optional : 0); + const symbol = createSymbol(flags, text); + symbol.links.type = getTypeFromBindingElement(e, includePatternInType, reportErrors); + symbol.links.bindingElement = e; + members.set(symbol.escapedName, symbol); + }); + const result = createAnonymousType(/*symbol*/ 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 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 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 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 getTypeFromImportAttributes(node: ImportAttributes): Type { + const links = getNodeLinks(node); + if (!links.resolvedType) { + const symbol = createSymbol(SymbolFlags.ObjectLiteral, InternalSymbolName.ImportAttributes); + const members = createSymbolTable(); + forEach(node.elements, attr => { + const member = createSymbol(SymbolFlags.Property, getNameFromImportAttribute(attr)); + member.parent = symbol; + member.links.type = checkImportAttribute(attr); + member.links.target = member; + members.set(member.escapedName, member); + }); + const type = createAnonymousType(symbol, members, emptyArray, emptyArray, emptyArray); + type.objectFlags |= ObjectFlags.ObjectLiteral | ObjectFlags.NonInferrableType; + links.resolvedType = type; + } + return links.resolvedType; + } + + function isGlobalSymbolConstructor(node: Node) { + const symbol = getSymbolOfNode(node); + const globalSymbol = getGlobalESSymbolConstructorTypeSymbol(/*reportErrors*/ false); + return globalSymbol && symbol && symbol === globalSymbol; + } + + 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); + } + + // 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 !== getSymbolOfDeclaration(declaration)) { + type = esSymbolType; + } + + 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); + } + } + return type; + } + + 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 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, checkMode?: CheckMode): Type { + const links = getSymbolLinks(symbol); + if (!links.type) { + const type = getTypeOfVariableOrParameterOrPropertyWorker(symbol, checkMode); + // 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) && !checkMode) { + links.type = type; + } + return type; + } + return links.type; + } + + function getTypeOfVariableOrParameterOrPropertyWorker(symbol: Symbol, checkMode?: CheckMode): 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 = getSymbolOfDeclaration(getSourceFileOfNode(symbol.valueDeclaration)); + const result = createSymbol(fileSymbol.flags, "exports" as __String); + result.declarations = fileSymbol.declarations ? fileSymbol.declarations.slice() : []; + result.parent = symbol; + result.links.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); + } + Debug.assertIsDefined(symbol.valueDeclaration); + const declaration = symbol.valueDeclaration; + // 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); + } + + // When trying to get the *contextual* type of a binding element, it's possible to fall in a loop and therefore + // end up in a circularity-like situation. This is not a true circularity so we should not report such an error. + // For example, here the looping could happen when trying to get the type of `a` (binding element): + // + // const { a, b = a } = { a: 0 } + // + if (isBindingElement(declaration) && checkMode === CheckMode.Contextual) { + return errorType; + } + 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, /*reportErrors*/ 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); + } + + // When trying to get the *contextual* type of a binding element, it's possible to fall in a loop and therefore + // end up in a circularity-like situation. This is not a true circularity so we should not report such an error. + // For example, here the looping could happen when trying to get the type of `a` (binding element): + // + // const { a, b = a } = { a: 0 } + // + if (isBindingElement(declaration) && checkMode === CheckMode.Contextual) { + 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; + } + } + return undefined; + } + + 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 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; + } + 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, /*reportErrors*/ 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; + } + 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; + } + links.type ??= type; + } + return links.type; + } + + function getWriteTypeOfAccessors(symbol: Symbol): Type { + const links = getSymbolLinks(symbol); + if (!links.writeType) { + if (!pushTypeResolution(symbol, TypeSystemPropertyName.WriteType)) { + return errorType; + } + + 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; + } + // Absent an explicit setter type annotation we use the read type of the accessor. + links.writeType ??= writeType || getTypeOfAccessors(symbol); + } + return links.writeType; + } + + 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 = merged; + links = merged.links; + } + } + originalLinks.type = links.type = getTypeOfFuncClassEnumModuleWorker(symbol); + } + return links.type; + } + + 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; + } + } + 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, /*isProperty*/ true) : type; + } + } + + 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) { + if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) { + return errorType; + } + const targetSymbol = resolveAlias(symbol); + const exportSymbol = symbol.declarations && getTargetOfAliasDeclaration(getDeclarationOfAliasSymbol(symbol)!, /*dontRecursivelyResolve*/ 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 + : getSymbolFlags(targetSymbol) & SymbolFlags.Value ? getTypeOfSymbol(targetSymbol) + : errorType; + + if (!popTypeResolution()) { + reportCircularityError(exportSymbol ?? symbol); + return links.type ??= 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 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; + // Check if variable has type annotation that circularly references the variable itself + if (declaration) { + 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.Alias) { + const node = getDeclarationOfAliasSymbol(symbol); + if (node) { + error(node, Diagnostics.Circular_definition_of_import_alias_0, 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 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 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) : + // NOTE: cast to TransientSymbol should be safe because only TransientSymbols can have CheckFlags.SyntheticProperty + (symbol as TransientSymbol).links.writeType || (symbol as TransientSymbol).links.type! : + removeMissingType(getTypeOfSymbol(symbol), !!(symbol.flags & SymbolFlags.Optional)); + } + if (symbol.flags & SymbolFlags.Accessor) { + return checkFlags & CheckFlags.Instantiated ? + getWriteTypeOfInstantiatedSymbol(symbol) : + getWriteTypeOfAccessors(symbol); + } + return getTypeOfSymbol(symbol); + } + + function getTypeOfSymbol(symbol: Symbol, checkMode?: CheckMode): 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, checkMode); + } + 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; + } + + 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(getSymbolOfDeclaration(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 = getSymbolOfDeclaration(node.left as BindableStaticNameExpression | PropertyAssignment); + if (symbol && symbol.parent && !findAncestor(symbol.parent.valueDeclaration, d => node === d)) { + node = symbol.parent.valueDeclaration!; + } + } + } + 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(getSymbolOfDeclaration((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(getSymbolOfDeclaration(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; + } + } + } + } + + // 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.flags & SymbolFlags.Function) + ? symbol.valueDeclaration + : symbol.declarations?.find(decl => { + if (decl.kind === SyntaxKind.InterfaceDeclaration) { + return true; + } + if (decl.kind !== SyntaxKind.VariableDeclaration) { + return false; + } + const initializer = (decl as VariableDeclaration).initializer; + return !!initializer && (initializer.kind === SyntaxKind.FunctionExpression || initializer.kind === SyntaxKind.ArrowFunction); + })!; + 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)); + } + } + 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)); + } + + // 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; + } + + 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 getBaseTypeNodeOfClass(type: InterfaceType): ExpressionWithTypeArguments | undefined { + const decl = getClassLikeDeclarationOfSymbol(type.symbol); + return decl && getEffectiveBaseTypeNode(decl); + } + + 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 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, + * * errorType 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))); + } + } + return type.resolvedBaseConstructorType ??= errorType; + } + 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); + } + } + } + } + } + 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 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); + } + } + 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; + } + + 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))) { + 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]); + } + + 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(getSourceFileOfNode(baseTypeNode.expression), 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; + } + + // 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); + } + } + else { + 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; + } + } + } + } + } + } + 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 = merged; + links = merged.links; + } + + 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; + } + + 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); + } + } + 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)); + } + } + links.declaredType ??= type; + } + return links.declaredType; + } + + function getBaseTypeOfEnumLikeType(type: Type) { + return type.flags & TypeFlags.EnumLike && type.symbol.flags & SymbolFlags.EnumMember ? 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 = getSymbolOfDeclaration(member); + const value = getEnumMemberValue(member).value; + const memberType = getFreshTypeOfLiteralType( + value !== undefined ? + getEnumLiteralType(value, getSymbolId(symbol), memberSymbol) : + createComputedEnumType(memberSymbol), + ); + getSymbolLinks(memberSymbol).declaredType = memberType; + memberTypeList.push(getRegularTypeOfLiteralType(memberType)); + } + } + } + } + } + const enumType = memberTypeList.length ? + getUnionType(memberTypeList, UnionReduction.Literal, symbol, /*aliasTypeArguments*/ undefined) : + createComputedEnumType(symbol); + if (enumType.flags & TypeFlags.Union) { + enumType.flags |= TypeFlags.EnumLiteral; + enumType.symbol = symbol; + } + links.declaredType = enumType; + } + return links.declaredType; + } + + function createComputedEnumType(symbol: Symbol) { + const regularType = createTypeWithSymbol(TypeFlags.Enum, symbol) as EnumType; + const freshType = createTypeWithSymbol(TypeFlags.Enum, symbol) as EnumType; + regularType.regularType = regularType; + regularType.freshType = freshType; + freshType.regularType = regularType; + freshType.freshType = freshType; + return regularType; + } + + function getDeclaredTypeOfEnumMember(symbol: Symbol): Type { + const links = getSymbolLinks(symbol); + if (!links.declaredType) { + const enumType = getDeclaredTypeOfEnum(getParentOfSymbol(symbol)!); + if (!links.declaredType) { + links.declaredType = enumType; + } + } + return links.declaredType; + } + + 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 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; + } + + /** + * 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; + } + + /** 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; + } + + // 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 base of baseSymbols) { + if (isStaticPrivateIdentifierProperty(base)) { + continue; + } + const derived = symbols.get(base.escapedName); + if ( + !derived + // non-constructor/static-block assignment declarations are ignored here; they're not treated as overrides + || derived.valueDeclaration + && isBinaryExpression(derived.valueDeclaration) + && !isConstructorDeclaredProperty(derived) + && !getContainingClassStaticBlock(derived.valueDeclaration) + ) { + symbols.set(base.escapedName, base); + symbols.set(base.escapedName, base); + } + } + } + + 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; + + (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 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; + } + + /** + * 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 name is a dynamic name that cannot be late-bound. + */ + function isNonBindableDynamicName(node: DeclarationName) { + return isDynamicName(node) && !isLateBindableName(node); + } + + /** + * 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: Map<__String, TransientSymbol>, 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 there's a symbol declaration with the same name and conflicting flags. + const earlySymbol = earlySymbols && earlySymbols.get(memberName); + // Duplicate property declarations of classes are checked in checkClassForDuplicateDeclarations. + if (!(parent.flags & SymbolFlags.Class) && lateSymbol.flags & getExcludedSymbolFlags(symbolFlags)) { + // 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.links.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; + } + + function getResolvedMembersOrExportsOfSymbol(symbol: Symbol, resolutionKind: MembersOrExportsResolutionKind): Map<__String, Symbol> { + const links = getSymbolLinks(symbol); + if (!links[resolutionKind]) { + const isStatic = resolutionKind === MembersOrExportsResolutionKind.resolvedExports; + const earlySymbols = !isStatic ? symbol.members : + symbol.flags & SymbolFlags.Module ? getExportsOfModuleWorker(symbol).exports : + 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 Map<__String, TransientSymbol>; + 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 = getFunctionExpressionParentSymbolOrSymbol(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); + } + } + } + } + + let resolved = combineSymbolTables(earlySymbols, lateSymbols); + if (symbol.flags & SymbolFlags.Transient && links.cjsExportMerged && symbol.declarations) { + for (const decl of symbol.declarations) { + const original = getSymbolLinks(decl.symbol)[resolutionKind]; + if (!resolved) { + resolved = original; + continue; + } + if (!original) continue; + original.forEach((s, name) => { + const existing = resolved!.get(name); + if (!existing) resolved!.set(name, s); + else if (existing === s) return; + else resolved!.set(name, mergeSymbol(existing, s)); + }); + } + } + links[resolutionKind] = resolved || emptySymbols; + } + + return links[resolutionKind]!; + } + + /** + * 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; + } + + 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); + return length(target.typeParameters) === length(typeArguments) ? createTypeReference(target, concatenate(typeArguments, [thisArgument || target.thisType!])) : 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)) { + const symbolTable = createSymbolTable(source.declaredProperties); + // copy index signature symbol as well (for quickinfo) + const sourceIndex = getIndexSymbol(source.symbol); + if (sourceIndex) { + symbolTable.set(InternalSymbolName.Index, sourceIndex); + } + members = symbolTable; + } + 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 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 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 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 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 restName = sig.parameters[restIndex].escapedName; + const restType = getTypeOfSymbol(sig.parameters[restIndex]); + if (isTupleType(restType)) { + return [expandSignatureParametersWithTupleMembers(restType, restIndex, restName)]; + } + 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, restName)); + } + } + return [sig.parameters]; + + function expandSignatureParametersWithTupleMembers(restType: TupleTypeReference, restIndex: number, restName: __String) { + const elementTypes = getTypeArguments(restType); + const associatedNames = getUniqAssociatedNamesFromTupleType(restType, restName); + 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 name = associatedNames && associatedNames[i] ? associatedNames[i] : + 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.links.type = flags & ElementFlags.Rest ? createArrayType(t) : t; + return symbol; + }); + return concatenate(sig.parameters.slice(0, restIndex), restParams); + } + + function getUniqAssociatedNamesFromTupleType(type: TupleTypeReference, restName: __String) { + const associatedNamesMap = new Map<__String, number>(); + return map(type.target.labeledElementDeclarations, (labeledElement, i) => { + const name = getTupleElementLabel(labeledElement, i, restName); + const prevCounter = associatedNamesMap.get(name); + if (prevCounter === undefined) { + associatedNamesMap.set(name, 1); + return name; + } + else { + associatedNamesMap.set(name, prevCounter + 1); + return `${name}_${prevCounter}` as __String; + } + }); + } + } + + 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(/*declaration*/ undefined, classType.localTypeParameters, /*thisParameter*/ 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]; + } + let result: Signature[] | undefined; + for (let i = 0; i < signatureLists.length; i++) { + // Allow matching non-generic signatures to have excess parameters (as a fallback if exact parameter match is not found) and different return types. + // Prefer matching this types if possible. + const match = i === listIndex + ? signature + : findMatchingSignature(signatureLists[i], signature, /*partialMatch*/ false, /*ignoreThisTypes*/ false, /*ignoreReturnTypes*/ true) + || findMatchingSignature(signatureLists[i], signature, /*partialMatch*/ true, /*ignoreThisTypes*/ false, /*ignoreReturnTypes*/ true); + if (!match) { + return undefined; + } + 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; + } + + 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 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, + isRestParam ? CheckFlags.RestParameter : isOptional ? CheckFlags.OptionalParameter : 0, + ); + paramSymbol.links.type = isRestParam ? createArrayType(unionParamType) : unionParamType; + params[i] = paramSymbol; + } + if (needsExtraRestElement) { + const restParamSymbol = createSymbol(SymbolFlags.FunctionScopedVariable, "args" as __String, CheckFlags.RestParameter); + restParamSymbol.links.type = createArrayType(getTypeAtPosition(shorter, longestCount)); + if (shorter === right) { + restParamSymbol.links.type = instantiateType(restParamSymbol.links.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; + } + else if (left.compositeKind !== TypeFlags.Intersection && left.mapper && left.compositeSignatures) { + result.mapper = left.mapper; + } + return result; + } + + 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; + } + + 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); + } + + 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 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); + } + else if (mixinFlags[i]) { + mixedTypes.push(getReturnTypeOfSignature(getSignaturesOfType(types[i], SignatureKind.Construct)[0])); + } + } + 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; + }); + } + 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 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 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); + } + + /** + * 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 = getExportsOfSymbol(symbol); + let indexInfos: IndexInfo[] | undefined; + if (symbol === globalThisSymbol) { + const varsOnly = new Map<__String, Symbol>(); + 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); + } + } + + 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); + } + } + 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; + } + } + + 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])])); + } + + // If the original mapped type had an intersection constraint we extract its components, + // and we make an attempt to do so even if the intersection has been reduced to a union. + // This entire process allows us to possibly retrieve the filtering type literals. + // e.g. { [K in keyof U & ("a" | "b") ] } -> "a" | "b" + function getLimitedConstraint(type: ReverseMappedType) { + const constraint = getConstraintTypeFromMappedType(type.mappedType); + if (!(constraint.flags & TypeFlags.Union || constraint.flags & TypeFlags.Intersection)) { + return; + } + const origin = (constraint.flags & TypeFlags.Union) ? (constraint as UnionType).origin : (constraint as IntersectionType); + if (!origin || !(origin.flags & TypeFlags.Intersection)) { + return; + } + const limitedConstraint = getIntersectionType((origin as IntersectionType).types.filter(t => t !== type.constraintType)); + return limitedConstraint !== neverType ? limitedConstraint : undefined; + } + + 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) || unknownType, readonlyMask && indexInfo.isReadonly)] : emptyArray; + const members = createSymbolTable(); + const limitedConstraint = getLimitedConstraint(type); + for (const prop of getPropertiesOfType(type.source)) { + // In case of a reverse mapped type with an intersection constraint, if we were able to + // extract the filtering type literals we skip those properties that are not assignable to them, + // because the extra properties wouldn't get through the application of the mapped type anyway + if (limitedConstraint) { + const propertyNameType = getLiteralTypeFromProperty(prop, TypeFlags.StringOrNumberLiteralOrUnique); + if (!isTypeAssignableTo(propertyNameType, limitedConstraint)) { + continue; + } + } + 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.links.nameType = getSymbolLinks(prop).nameType; + inferredProp.links.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.links.mappedType = newMappedType as MappedType; + inferredProp.links.constraintType = getIndexType(newTypeParam) as IndexType; + } + else { + inferredProp.links.mappedType = type.mappedType; + inferredProp.links.constraintType = type.constraintType; + } + members.set(prop.escapedName, inferredProp); + } + setStructuredTypeMembers(type, members, emptyArray, emptyArray, indexInfos); + } + + // 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), /*forConstraint*/ false); + } + } + return type; + } + if (type.flags & TypeFlags.Union) { + return mapType(type as UnionType, getLowerBoundOfKeyType, /*noReductions*/ true); + } + 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 getIsLateCheckFlag(s: Symbol): CheckFlags { + return getCheckFlags(s) & CheckFlags.Late; + } + + function forEachMappedTypePropertyKeyTypeAndIndexSignatureKeyType(type: Type, include: TypeFlags, stringsOnly: boolean, cb: (keyType: Type) => void) { + for (const prop of getPropertiesOfType(type)) { + cb(getLiteralTypeFromProperty(prop, include)); + } + if (type.flags & TypeFlags.Any) { + cb(stringType); + } + else { + for (const info of getIndexInfosOfType(type)) { + if (!stringsOnly || info.keyType.flags & (TypeFlags.String | TypeFlags.TemplateLiteral)) { + cb(info.keyType); + } + } + } + } + + /** 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 mappedType = (type.target as MappedType) || type; + const nameType = getNameTypeFromMappedType(mappedType); + const shouldLinkPropDeclarations = getMappedTypeNameTypeKind(mappedType) !== MappedTypeNameTypeKind.Remapping; + const templateType = getTemplateTypeFromMappedType(mappedType); + const modifiersType = getApparentType(getModifiersTypeFromMappedType(type)); // The 'T' in 'keyof T' + const templateModifiers = getMappedTypeModifiers(type); + const include = TypeFlags.StringOrNumberLiteralOrUnique; + if (isMappedTypeWithKeyofConstraintDeclaration(type)) { + // We have a { [P in keyof T]: X } + forEachMappedTypePropertyKeyTypeAndIndexSignatureKeyType(modifiersType, include, /*stringsOnly*/ false, 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.links.nameType = getUnionType([existingProp.links.nameType!, propNameType]); + existingProp.links.keyType = getUnionType([existingProp.links.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.links.mappedType = type; + prop.links.nameType = propNameType; + prop.links.keyType = keyType; + if (modifiersProp) { + prop.links.syntheticOrigin = modifiersProp; + prop.declarations = shouldLinkPropDeclarations ? modifiersProp.declarations : undefined; + } + 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 modifiersIndexInfo = getApplicableIndexInfo(modifiersType, propNameType); + const isReadonly = !!(templateModifiers & MappedTypeModifiers.IncludeReadonly || + !(templateModifiers & MappedTypeModifiers.ExcludeReadonly) && modifiersIndexInfo?.isReadonly); + const indexInfo = createIndexInfo(indexKeyType, propType, isReadonly); + indexInfos = appendIndexInfo(indexInfos, indexInfo, /*union*/ true); + } + } + } + + function getTypeOfMappedSymbol(symbol: MappedSymbol) { + if (!symbol.links.type) { + const mappedType = symbol.links.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.links.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.links.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.links.type ??= type; + } + return symbol.links.type; + } + + function getTypeParameterFromMappedType(type: MappedType) { + return type.typeParameter || + (type.typeParameter = getDeclaredTypeOfTypeParameter(getSymbolOfDeclaration(type.declaration.typeParameter))); + } + + 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 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 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); + } + + // Return -1, 0, or 1, where -1 means optionality is stripped (i.e. -?), 0 means optionality is unchanged, and 1 means + // optionality is added (i.e. +?). + function getMappedTypeOptionality(type: MappedType): number { + const modifiers = getMappedTypeModifiers(type); + return modifiers & MappedTypeModifiers.ExcludeOptional ? -1 : modifiers & MappedTypeModifiers.IncludeOptional ? 1 : 0; + } + + // Return -1, 0, or 1, for stripped, unchanged, or added optionality respectively. When a homomorphic mapped type doesn't + // modify optionality, recursively consult the optionality of the type being mapped over to see if it strips or adds optionality. + // For intersections, return -1 or 1 when all constituents strip or add optionality, otherwise return 0. + function getCombinedMappedTypeOptionality(type: Type): number { + if (getObjectFlags(type) & ObjectFlags.Mapped) { + return getMappedTypeOptionality(type as MappedType) || getCombinedMappedTypeOptionality(getModifiersTypeFromMappedType(type as MappedType)); + } + if (type.flags & TypeFlags.Intersection) { + const optionality = getCombinedMappedTypeOptionality((type as IntersectionType).types[0]); + return every((type as IntersectionType).types, (t, i) => i === 0 || getCombinedMappedTypeOptionality(t) === optionality) ? optionality : 0; + } + return 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; + } + + function getMappedTypeNameTypeKind(type: MappedType): MappedTypeNameTypeKind { + const nameType = getNameTypeFromMappedType(type); + if (!nameType) { + return MappedTypeNameTypeKind.None; + } + return isTypeAssignableTo(nameType, getTypeParameterFromMappedType(type)) ? MappedTypeNameTypeKind.Filtering : MappedTypeNameTypeKind.Remapping; + } + + 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)); + } + } + 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; + } + + /** 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, /*skipObjectFunctionPropertyAugment*/ !!(type.flags & TypeFlags.Intersection)); + 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); + } + return type.resolvedProperties; + } + + function getPropertiesOfType(type: Type): Symbol[] { + type = getReducedApparentType(type); + return type.flags & TypeFlags.UnionOrIntersection ? + getPropertiesOfUnionOrIntersectionType(type as UnionType) : + getPropertiesOfObjectType(type); + } + + 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 isTypeInvalidDueToUnionDiscriminant(contextualType: Type, obj: ObjectLiteralExpression | JsxAttributes): boolean { + const list = obj.properties as NodeArray; + return list.some(property => { + const nameType = property.name && (isJsxNamespacedName(property.name) ? getStringLiteralType(getTextOfJsxAttributeName(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 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()); + } + + 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 isConstMappedType(type: MappedType, depth: number): boolean { + const typeVariable = getHomomorphicTypeVariable(type); + return !!typeVariable && isConstTypeVariable(typeVariable, depth); + } + + function isConstTypeVariable(type: Type | undefined, depth = 0): boolean { + return depth < 5 && !!(type && ( + type.flags & TypeFlags.TypeParameter && some((type as TypeParameter).symbol?.declarations, d => hasSyntacticModifier(d, ModifierFlags.Const)) || + type.flags & TypeFlags.UnionOrIntersection && some((type as UnionOrIntersectionType).types, t => isConstTypeVariable(t, depth)) || + type.flags & TypeFlags.IndexedAccess && isConstTypeVariable((type as IndexedAccessType).objectType, depth + 1) || + type.flags & TypeFlags.Conditional && isConstTypeVariable(getConstraintOfConditionalType(type as ConditionalType), depth + 1) || + type.flags & TypeFlags.Substitution && isConstTypeVariable((type as SubstitutionType).baseType, depth) || + getObjectFlags(type) & ObjectFlags.Mapped && isConstMappedType(type as MappedType, depth) || + isGenericTupleType(type) && findIndex(getElementTypes(type), (t, i) => !!(type.target.elementFlags[i] & ElementFlags.Variadic) && isConstTypeVariable(t, depth)) >= 0 + )); + } + + 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 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 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 { + if (type.resolvedConstraintOfDistributive !== undefined) { + return type.resolvedConstraintOfDistributive || 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), /*forConstraint*/ true); + if (!(instantiated.flags & TypeFlags.Never)) { + type.resolvedConstraintOfDistributive = instantiated; + return instantiated; + } + } + } + type.resolvedConstraintOfDistributive = false; + 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); + } + if (constraint) { + constraints = append(constraints, constraint); + if (targetIsUnion) { + 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, IntersectionFlags.NoConstraintReduction), /*writing*/ false); + } + return undefined; + } + + function getBaseConstraintOfType(type: Type): Type | undefined { + if (type.flags & (TypeFlags.InstantiableNonPrimitive | TypeFlags.UnionOrIntersection | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) || isGenericTupleType(type)) { + const constraint = getResolvedBaseConstraint(type as InstantiableType | UnionOrIntersectionType); + return constraint !== noConstraintType && constraint !== circularConstraintType ? constraint : undefined; + } + return type.flags & TypeFlags.Index ? stringNumberSymbolType : 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; + } + + 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 = getImmediateBaseConstraint(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; + } + return t.immediateBaseConstraint; + } + + 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 { + different = true; + } + } + 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 stringNumberSymbolType; + } + 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)); + } + 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)); + } + if (isGenericTupleType(t)) { + // We substitute constraints for variadic elements only when the constraints are array types or + // non-variadic tuple types as we want to avoid further (possibly unbounded) recursion. + const newElements = map(getElementTypes(t), (v, i) => { + const constraint = v.flags & TypeFlags.TypeParameter && t.target.elementFlags[i] & ElementFlags.Variadic && getBaseConstraint(v) || v; + return constraint !== v && everyType(constraint, c => isArrayOrTupleType(c) && !isGenericTupleType(c)) ? constraint : v; + }); + return createTupleType(newElements, t.target.elementFlags, t.target.readonly, t.target.labeledElementDeclarations); + } + return t; + } + } + + function getApparentTypeOfIntersectionType(type: IntersectionType, thisArgument: Type) { + if (type === thisArgument) { + return type.resolvedApparentType || (type.resolvedApparentType = getTypeWithThisArgument(type, thisArgument, /*needApparentType*/ true)); + } + const key = `I${getTypeId(type)},${getTypeId(thisArgument)}`; + return getCachedType(key) ?? setCachedType(key, getTypeWithThisArgument(type, thisArgument, /*needApparentType*/ 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; + } + } + } + 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; + } + + /** + * 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; + } + + function hasNonCircularTypeParameterDefault(typeParameter: TypeParameter) { + return getResolvedTypeParameterDefault(typeParameter) !== circularConstraintType; + } + + /** + * 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 getApparentTypeOfMappedType(type: MappedType) { + return type.resolvedApparentType || (type.resolvedApparentType = getResolvedApparentTypeOfMappedType(type)); + } + + function getResolvedApparentTypeOfMappedType(type: MappedType): Type { + const target = (type.target ?? type) as MappedType; + const typeVariable = getHomomorphicTypeVariable(target); + if (typeVariable && !target.declaration.nameType) { + // We have a homomorphic mapped type or an instantiation of a homomorphic mapped type, i.e. a type + // of the form { [P in keyof T]: X }. Obtain the modifiers type (the T of the keyof T), and if it is + // another generic mapped type, recursively obtain its apparent type. Otherwise, obtain its base + // constraint. Then, if every constituent of the base constraint is an array or tuple type, apply + // this mapped type to the base constraint. It is safe to recurse when the modifiers type is a + // mapped type because we protect again circular constraints in getTypeFromMappedTypeNode. + const modifiersType = getModifiersTypeFromMappedType(type); + const baseConstraint = isGenericMappedType(modifiersType) ? getApparentTypeOfMappedType(modifiersType) : getBaseConstraintOfType(modifiersType); + if (baseConstraint && everyType(baseConstraint, t => isArrayOrTupleType(t) || isArrayOrTupleOrIntersection(t))) { + return instantiateType(target, prependTypeMapping(typeVariable, baseConstraint, type.mapper)); + } + } + return type; + } + + function isArrayOrTupleOrIntersection(type: Type) { + return !!(type.flags & TypeFlags.Intersection) && every((type as IntersectionType).types, isArrayOrTupleType); + } + + 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 ? getBaseConstraintOfType(type) || unknownType : type; + const objectFlags = getObjectFlags(t); + return objectFlags & ObjectFlags.Mapped ? getApparentTypeOfMappedType(t as MappedType) : + objectFlags & ObjectFlags.Reference && t !== type ? getTypeWithThisArgument(t, type) : + t.flags & TypeFlags.Intersection ? getApparentTypeOfIntersectionType(t as IntersectionType, type) : + 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 ? stringNumberSymbolType : + 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: Map | 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; + } + 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(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 links = tryCast(singleProp, isTransientSymbol)?.links; + const clone = createSymbolWithType(singleProp, links?.type); + clone.parent = singleProp.valueDeclaration?.symbol?.parent; + clone.links.containingType = containingType; + clone.links.mapper = links?.mapper; + clone.links.writeType = getWriteTypeOfSymbol(singleProp); + return clone; + } + 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 writeType = getWriteTypeOfSymbol(prop); + if (writeTypes || writeType !== type) { + writeTypes = append(!writeTypes ? propTypes.slice() : writeTypes, writeType); + } + if (type !== firstType) { + checkFlags |= CheckFlags.HasNonUniformType; + } + if (isLiteralType(type) || isPatternLiteralType(type)) { + checkFlags |= CheckFlags.HasLiteralType; + } + if (type.flags & TypeFlags.Never && type !== uniqueLiteralType) { + checkFlags |= CheckFlags.HasNeverType; + } + propTypes.push(type); + } + addRange(propTypes, indexTypes); + const result = createSymbol(SymbolFlags.Property | (optionalFlag ?? 0), name, syntheticFlag | checkFlags); + result.links.containingType = containingType; + if (!hasNonUniformValueDeclaration && firstValueDeclaration) { + result.valueDeclaration = firstValueDeclaration; + + // Inherit information about parent type. + if (firstValueDeclaration.symbol.parent) { + result.parent = firstValueDeclaration.symbol.parent; + } + } + + result.declarations = declarations; + result.links.nameType = nameType; + if (propTypes.length > 2) { + // When `propTypes` has the potential to explode in size when normalized, defer normalization until absolutely needed + result.links.checkFlags |= CheckFlags.DeferredType; + result.links.deferralParent = containingType; + result.links.deferralConstituents = propTypes; + result.links.deferralWriteConstituents = writeTypes; + } + else { + result.links.type = isUnion ? getUnionType(propTypes) : getIntersectionType(propTypes); + if (writeTypes) { + result.links.writeType = isUnion ? getUnionType(writeTypes) : getIntersectionType(writeTypes); + } + } + return result; + } + + // 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 = skipObjectFunctionPropertyAugment ? + type.propertyCacheWithoutObjectFunctionPropertyAugment?.get(name) : + type.propertyCache?.get(name); + if (!property) { + property = createUnionOrIntersectionProperty(type, name, skipObjectFunctionPropertyAugment); + if (property) { + const properties = skipObjectFunctionPropertyAugment ? + type.propertyCacheWithoutObjectFunctionPropertyAugment ||= createSymbolTable() : + type.propertyCache ||= createSymbolTable(); + properties.set(name, property); + // Propagate an entry from the non-augmented cache to the augmented cache unless the property is partial. + if (skipObjectFunctionPropertyAugment && !(getCheckFlags(property) & CheckFlags.Partial) && !type.propertyCache?.get(name)) { + const properties = type.propertyCache ||= createSymbolTable(); + properties.set(name, property); + } + } + } + return property; + } + + function getCommonDeclarationsOfSymbols(symbols: Iterable) { + 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; + } + } + return commonDeclarations; + } + + 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 (type as IntersectionType).objectFlags & ObjectFlags.IsNeverIntersection ? neverType : type; + } + return type; + } + + 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; + } + return reduced; + } + + function isNeverReducedProperty(prop: Symbol) { + return isDiscriminantWithNeverType(prop) || isConflictingPrivateProperty(prop); + } + + 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); + } + + /** + * 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 isGenericReducibleType(type: Type): boolean { + return !!(type.flags & TypeFlags.Union && (type as UnionType).objectFlags & ObjectFlags.ContainsIntersections && some((type as UnionType).types, isGenericReducibleType) || + type.flags & TypeFlags.Intersection && isReducibleIntersection(type as IntersectionType)); + } + + function isReducibleIntersection(type: IntersectionType) { + const uniqueFilled = type.uniqueLiteralFilledInstantiation || (type.uniqueLiteralFilledInstantiation = instantiateType(type, uniqueLiteralMapper)); + return getReducedType(uniqueFilled) !== uniqueFilled; + } + + 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 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 && !includeTypeOnlyMembers && type.symbol?.flags & SymbolFlags.ValueModule && getSymbolLinks(type.symbol).typeOnlyExportStarMap?.has(name)) { + // If this is the type of a module, `resolved.members.get(name)` might have effectively skipped over + // an `export type * from './foo'`, leaving `symbolIsValue` unable to see that the symbol is being + // viewed through a type-only export. + return undefined; + } + 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; + } + } + return getPropertyOfObjectType(globalObjectType, name); + } + if (type.flags & TypeFlags.Intersection) { + const prop = getPropertyOfUnionOrIntersectionType(type as UnionOrIntersectionType, name, /*skipObjectFunctionPropertyAugment*/ true); + if (prop) { + return prop; + } + if (!skipObjectFunctionPropertyAugment) { + return getPropertyOfUnionOrIntersectionType(type as UnionOrIntersectionType, name, skipObjectFunctionPropertyAugment); + } + return undefined; + } + if (type.flags & TypeFlags.Union) { + return getPropertyOfUnionOrIntersectionType(type as UnionOrIntersectionType, name, skipObjectFunctionPropertyAugment); + } + return undefined; + } + + 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; + } + + /** + * 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[] { + const result = getSignaturesOfStructuredType(getReducedApparentType(type), kind); + if (kind === SignatureKind.Call && !length(result) && type.flags & TypeFlags.Union) { + if ((type as UnionType).arrayFallbackSignatures) { + return (type as UnionType).arrayFallbackSignatures!; + } + // If the union is all different instantiations of a member of the global array type... + let memberName: __String; + if (everyType(type, t => !!t.symbol?.parent && isArrayOrTupleSymbol(t.symbol.parent) && (!memberName ? (memberName = t.symbol.escapedName, true) : memberName === t.symbol.escapedName))) { + // Transform the type from `(A[] | B[])["member"]` to `(A | B)[]["member"]` (since we pretend array is covariant anyway) + const arrayArg = mapType(type, t => getMappedType((isReadonlyArraySymbol(t.symbol.parent) ? globalReadonlyArrayType : globalArrayType).typeParameters![0], (t as AnonymousType).mapper!)); + const arrayType = createArrayType(arrayArg, someType(type, t => isReadonlyArraySymbol(t.symbol.parent))); + return (type as UnionType).arrayFallbackSignatures = getSignaturesOfType(getTypeOfPropertyOfType(arrayType, memberName!)!, kind); + } + (type as UnionType).arrayFallbackSignatures = result; + } + return result; + } + + function isArrayOrTupleSymbol(symbol: Symbol | undefined) { + if (!symbol || !globalArrayType.symbol || !globalReadonlyArrayType.symbol) { + return false; + } + return !!getSymbolIfSameReference(symbol, globalArrayType.symbol) || !!getSymbolIfSameReference(symbol, globalReadonlyArrayType.symbol); + } + + function isReadonlyArraySymbol(symbol: Symbol | undefined) { + if (!symbol || !globalReadonlyArrayType.symbol) { + return false; + } + return !!getSymbolIfSameReference(symbol, globalReadonlyArrayType.symbol); + } + + 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 { + (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; + } + + 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 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 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 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))); + } + + // 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; + } + + function symbolsToArray(symbols: SymbolTable): Symbol[] { + const result: Symbol[] = []; + symbols.forEach((symbol, id) => { + if (!isReservedMemberName(id)) { + result.push(symbol); + } + }); + return result; + } + + 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 hasEffectiveQuestionToken(node: ParameterDeclaration | JSDocParameterTag | JSDocPropertyTag) { + return hasQuestionToken(node) || isOptionalJSDocPropertyLikeTag(node) || isParameter(node) && isJSDocOptionalParameter(node); + } + + function isOptionalParameter(node: ParameterDeclaration | JSDocParameterTag | JSDocPropertyTag) { + if (hasEffectiveQuestionToken(node)) { + return true; + } + if (!isParameter(node)) { + 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); + } + const iife = getImmediatelyInvokedFunctionExpression(node.parent); + if (iife) { + return !node.type && + !node.dotDotDotToken && + node.parent.parameters.indexOf(node) >= getEffectiveCallArguments(iife).length; + } + + return false; + } + + 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; + } + + /** + * 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 thisTag: JSDocThisTag | undefined = isInJSFile(declaration) ? getJSDocThisTag(declaration) : 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]; + if (isInJSFile(param) && isJSDocThisTag(param)) { + thisTag = param; + continue; + } + + 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, /*nameNotFoundMessage*/ 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; + } + + // Record a new minimum argument count if this is not an optional parameter + const isOptionalParameter = hasEffectiveQuestionToken(param) || + isParameter(param) && param.initializer || isRestParameter(param) || + iife && parameters.length > iife.arguments.length && !type; + if (!isOptionalParameter) { + minArgumentCount = parameters.length; + } + } + + // 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(getSymbolOfDeclaration(declaration), otherKind); + if (other) { + thisParameter = getAnnotatedAccessorThisParameter(other); + } + } + + if (thisTag && thisTag.typeExpression) { + thisParameter = createSymbolWithType(createSymbol(SymbolFlags.FunctionScopedVariable, InternalSymbolName.This), getTypeFromTypeNode(thisTag.typeExpression)); + } + + const hostDeclaration = isJSDocSignature(declaration) ? getEffectiveJSDocHost(declaration) : declaration; + const classType = hostDeclaration && isConstructorDeclaration(hostDeclaration) ? + getDeclaredTypeOfClassOrInterface(getMergedSymbol((hostDeclaration.parent as ClassDeclaration).symbol)) + : undefined; + const typeParameters = classType ? classType.localTypeParameters : getTypeParametersFromDeclaration(declaration); + if (hasRestParameter(declaration) || isInJSFile(declaration) && maybeAddJsSyntheticRestParameter(declaration, parameters)) { + flags |= SignatureFlags.HasRestParameter; + } + 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; + } + + /** + * 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.links.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.links.checkFlags |= CheckFlags.DeferredType; + syntheticArgsSymbol.links.deferralParent = neverType; + syntheticArgsSymbol.links.deferralConstituents = [anyArrayType]; + syntheticArgsSymbol.links.deferralWriteConstituents = [anyArrayType]; + } + if (lastParamVariadicType) { + // Replace the last parameter with a rest parameter. + parameters.pop(); + } + parameters.push(syntheticArgsSymbol); + return true; + } + + 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 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!); + } + } + 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; + + 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.PropertyAssignment: + return traverse((node as PropertyAssignment).initializer); + + 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; + } + } + if (isInJSFile(decl) && decl.jsDoc) { + const tags = getJSDocOverloadTags(decl); + if (length(tags)) { + for (const tag of tags) { + const jsDocSignature = tag.typeExpression; + if (jsDocSignature.type === undefined && !isConstructorDeclaration(decl)) { + reportImplicitAny(jsDocSignature, anyType); + } + result.push(getSignatureFromDeclaration(jsDocSignature)); + } + 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; + } + + 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); + } + } + + 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); + } + } + if (type || jsdocPredicate) { + signature.resolvedTypePredicate = type && isTypePredicateNode(type) ? + createTypePredicateFromTypePredicateNode(type, signature) : + jsdocPredicate || noTypePredicate; + } + else if (signature.declaration && isFunctionLikeDeclaration(signature.declaration) && (!signature.resolvedReturnType || signature.resolvedReturnType.flags & TypeFlags.Boolean) && getParameterCount(signature) > 0) { + const { declaration } = signature; + signature.resolvedTypePredicate = noTypePredicate; // avoid infinite loop + signature.resolvedTypePredicate = getTypePredicateFromBody(declaration) || noTypePredicate; + } + else { + signature.resolvedTypePredicate = noTypePredicate; + } + } + 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 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); + } + 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); + } + } + } + type = anyType; + } + signature.resolvedReturnType ??= type; + } + return signature.resolvedReturnType; + } + + function getReturnTypeFromAnnotation(declaration: SignatureDeclaration | JSDocSignature) { + if (declaration.kind === SyntaxKind.Constructor) { + return getDeclaredTypeOfClassOrInterface(getMergedSymbol((declaration.parent as ClassDeclaration).symbol)); + } + const typeNode = getEffectiveReturnTypeNode(declaration); + if (isJSDocSignature(declaration)) { + const root = getJSDocRoot(declaration); + if (root && isConstructorDeclaration(root.parent) && !typeNode) { + return getDeclaredTypeOfClassOrInterface(getMergedSymbol((root.parent.parent as ClassDeclaration).symbol)); + } + } + if (isJSDocConstructSignature(declaration)) { + return getTypeFromTypeNode((declaration.parameters[0] as ParameterDeclaration).type!); // TODO: GH#18217 + } + 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(getSymbolOfDeclaration(declaration), SyntaxKind.SetAccessor); + const setterType = getAnnotatedAccessorType(setter); + if (setterType) { + return setterType; + } + } + return getReturnTypeOfTypeTag(declaration); + } + + function isResolvingReturnTypeOfSignature(signature: Signature): boolean { + return signature.compositeSignatures && some(signature.compositeSignatures, isResolvingReturnTypeOfSignature) || + !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 getSignatureInstantiation(signature: Signature, typeArguments: readonly 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 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 createSignatureInstantiation(signature: Signature, typeArguments: readonly Type[] | undefined): Signature { + return instantiateSignature(signature, createSignatureTypeMapper(signature, typeArguments), /*eraseTypeParameters*/ true); + } + + function createSignatureTypeMapper(signature: Signature, typeArguments: readonly Type[] | undefined): TypeMapper { + return createTypeMapper(signature.typeParameters!, typeArguments); + } + + function getErasedSignature(signature: Signature): Signature { + return signature.typeParameters ? + signature.erasedSignatureCache || (signature.erasedSignatureCache = createErasedSignature(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 getCanonicalSignature(signature: Signature): Signature { + return signature.typeParameters ? + signature.canonicalSignatureCache || (signature.canonicalSignatureCache = createCanonicalSignature(signature)) : + 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 getImplementationSignature(signature: Signature) { + return signature.typeParameters ? + signature.implementationSignatureCache ||= createImplementationSignature(signature) : + signature; + } + + function createImplementationSignature(signature: Signature) { + return signature.typeParameters ? instantiateSignature(signature, createTypeMapper([], [])) : 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 getOrCreateTypeFromSignature(signature: Signature, outerTypeParameters?: TypeParameter[]): 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; + + // 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; + + // The type must have a symbol with a `Function` flag and a declaration in order to be correctly flagged as possibly containing + // type variables by `couldContainTypeVariables` + const type = createObjectType(ObjectFlags.Anonymous | ObjectFlags.SingleSignatureType, createSymbol(SymbolFlags.Function, InternalSymbolName.Function)) as SingleSignatureType; + if (signature.declaration && !nodeIsSynthesized(signature.declaration)) { // skip synthetic declarations - keeping those around could be bad, since they lack a parent pointer + type.symbol.declarations = [signature.declaration]; + type.symbol.valueDeclaration = signature.declaration; + } + outerTypeParameters ||= signature.declaration && getOuterTypeParameters(signature.declaration, /*includeThisTypes*/ true); + type.outerTypeParameters = outerTypeParameters; + + type.members = emptySymbols; + type.properties = emptyArray; + type.callSignatures = !isConstructor ? [signature] : emptyArray; + type.constructSignatures = isConstructor ? [signature] : emptyArray; + type.indexInfos = emptyArray; + signature.isolatedSignatureType = type; + } + + return signature.isolatedSignatureType; + } + + function getIndexSymbol(symbol: Symbol): Symbol | undefined { + return symbol.members ? getIndexSymbolFromSymbolTable(symbol.members) : undefined; + } + + function getIndexSymbolFromSymbolTable(symbolTable: SymbolTable): Symbol | undefined { + return symbolTable.get(InternalSymbolName.Index); + } + + function createIndexInfo(keyType: Type, type: Type, isReadonly: boolean, declaration?: IndexSignatureDeclaration): IndexInfo { + return { keyType, type, isReadonly, 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; + } + + 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 = getTypeParametersForTypeReferenceOrImport(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, stringNumberSymbolType); + } + // 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(getSymbolOfDeclaration(checkMappedType.typeParameter)), checkMappedType.typeParameter.constraint ? getTypeFromTypeNode(checkMappedType.typeParameter.constraint) : stringNumberSymbolType))); + } + } + } + } + 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; + } + 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 stringNumberSymbolType 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 ? stringNumberSymbolType : unknownType; + } + typeParameter.constraint = type; + } + } + } + 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 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; + } + } + 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); + } + } + return result & ObjectFlags.PropagatingFlags; + } + + function tryCreateTypeReference(target: GenericType, typeArguments: readonly Type[] | undefined): Type { + if (some(typeArguments) && target === emptyGenericType) { + return unknownType; + } + + return createTypeReference(target, typeArguments); + } + + 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 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; + } + + 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 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; + } + } + 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); + } + 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) { + const typeKind = intrinsicTypeKinds.get(symbol.escapedName as string); + if (typeKind !== undefined && typeArguments && typeArguments.length === 1) { + return typeKind === IntrinsicTypeKind.NoInfer ? getNoInferType(typeArguments[0]) : 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; + } + + /** + * 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", /*objectFlags*/ undefined, `alias ${id}`); + 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, + ); + 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); + let newAliasSymbol = aliasSymbol && (isLocalTypeAlias(symbol) || !isLocalTypeAlias(aliasSymbol)) ? aliasSymbol : undefined; + let aliasTypeArguments: Type[] | undefined; + if (newAliasSymbol) { + aliasTypeArguments = getTypeArgumentsForAliasSymbol(newAliasSymbol); + } + else if (isTypeReferenceType(node)) { + const aliasSymbol = resolveTypeReferenceName(node, SymbolFlags.Alias, /*ignoreErrors*/ true); + // refers to an alias import/export/reexport - by making sure we use the target as an aliasSymbol, + // we ensure the exported symbol is used to refer to the type when it's reserialized later + if (aliasSymbol && aliasSymbol !== unknownSymbol) { + const resolved = resolveAlias(aliasSymbol); + if (resolved && resolved.flags & SymbolFlags.TypeAlias) { + newAliasSymbol = resolved; + aliasTypeArguments = typeArgumentsFromTypeReferenceNode(node) || (typeParameters ? [] : undefined); + } + } + } + return getTypeAliasInstantiation(symbol, typeArgumentsFromTypeReferenceNode(node), newAliasSymbol, aliasTypeArguments); + } + 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; + } + + 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.links.declaredType = unresolvedType; + } + return result; + } + return unknownSymbol; + } + + 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 getTypeReferenceType(node: NodeWithTypeArguments, symbol: Symbol): Type { + if (symbol === unknownSymbol) { + 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; + } + + /** + * 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 getNoInferType(type: Type) { + return isNoInferTargetType(type) ? getOrCreateSubstitutionType(type, unknownType) : type; + } + + function isNoInferTargetType(type: Type): boolean { + // This is effectively a more conservative and predictable form of couldContainTypeVariables. We want to + // preserve NoInfer only for types that could contain type variables, but we don't want to exhaustively + // examine all object type members. + return !!(type.flags & TypeFlags.UnionOrIntersection && some((type as UnionOrIntersectionType).types, isNoInferTargetType) || + type.flags & TypeFlags.Substitution && !isNoInferType(type) && isNoInferTargetType((type as SubstitutionType).baseType) || + type.flags & TypeFlags.Object && !isEmptyAnonymousObjectType(type) || + type.flags & (TypeFlags.Instantiable & ~TypeFlags.Substitution) && !isPatternLiteralType(type)); + } + + function isNoInferType(type: Type) { + // A NoInfer type is represented as a substitution type with a TypeFlags.Unknown constraint. + return !!(type.flags & TypeFlags.Substitution && (type as SubstitutionType).constraint.flags & TypeFlags.Unknown); + } + + function getSubstitutionType(baseType: Type, constraint: Type) { + return constraint.flags & TypeFlags.AnyOrUnknown || constraint === baseType || baseType.flags & TypeFlags.Any ? + baseType : + getOrCreateSubstitutionType(baseType, constraint); + } + + function getOrCreateSubstitutionType(baseType: Type, constraint: Type) { + 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 getSubstitutionIntersection(substitutionType: SubstitutionType) { + return isNoInferType(substitutionType) ? substitutionType.baseType : 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); + } + } + // 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 && !(parent as MappedTypeNode).nameType && 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; + } + + function isJSDocTypeReference(node: Node): node is TypeReferenceNode { + return !!(node.flags & NodeFlags.JSDoc) && (node.kind === SyntaxKind.TypeReference || node.kind === SyntaxKind.ImportType); + } + + 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 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(/*symbol*/ undefined, emptySymbols, emptyArray, emptyArray, indexInfo); + } + return anyType; + } + checkNoTypeArguments(node); + return !noImplicitAny ? anyType : undefined; + } + } + } + + 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); + } + 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 (!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; + } + + function typeArgumentsFromTypeReferenceNode(node: NodeWithTypeArguments): Type[] | undefined { + return map(node.typeArguments, getTypeFromTypeNode); + } + + 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 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; + } + } + } + } + + if (!symbol) { + return arity ? emptyGenericType : emptyObjectType; + } + 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; + } + 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 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 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 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(/*location*/ undefined, name, meaning, diagnostic, /*isUse*/ false, /*excludeGlobals*/ false); + } + + 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; + } + + 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; + } + + 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 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 getGlobalImportMetaExpressionType() { + if (!deferredGlobalImportMetaExpressionType) { + // Create a synthetic type `ImportMetaExpression { meta: MetaProperty }` + const symbol = createSymbol(SymbolFlags.None, "ImportMetaExpression" as __String); + const importMetaType = getGlobalImportMetaType(); + + const metaPropertySymbol = createSymbol(SymbolFlags.Property, "meta" as __String, CheckFlags.Readonly); + metaPropertySymbol.parent = symbol; + metaPropertySymbol.links.type = importMetaType; + + const members = createSymbolTable([metaPropertySymbol]); + symbol.members = members; + + deferredGlobalImportMetaExpressionType = createAnonymousType(symbol, members, emptyArray, emptyArray, emptyArray); + } + return deferredGlobalImportMetaExpressionType; + } + + function getGlobalImportCallOptionsType(reportErrors: boolean) { + return (deferredGlobalImportCallOptionsType ||= getGlobalType("ImportCallOptions" as __String, /*arity*/ 0, reportErrors)) || emptyObjectType; + } + + function getGlobalImportAttributesType(reportErrors: boolean) { + return (deferredGlobalImportAttributesType ||= getGlobalType("ImportAttributes" as __String, /*arity*/ 0, reportErrors)) || emptyObjectType; + } + + function getGlobalESSymbolConstructorSymbol(reportErrors: boolean): Symbol | undefined { + return deferredGlobalESSymbolConstructorSymbol ||= getGlobalValueSymbol("Symbol" as __String, reportErrors); + } + + function getGlobalESSymbolConstructorTypeSymbol(reportErrors: boolean): Symbol | undefined { + return deferredGlobalESSymbolConstructorTypeSymbol ||= getGlobalTypeSymbol("SymbolConstructor" as __String, reportErrors); + } + + function getGlobalESSymbolType() { + return (deferredGlobalESSymbolType ||= getGlobalType("Symbol" as __String, /*arity*/ 0, /*reportErrors*/ false)) || emptyObjectType; + } + + function getGlobalPromiseType(reportErrors: boolean) { + return (deferredGlobalPromiseType ||= getGlobalType("Promise" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; + } + + function getGlobalPromiseLikeType(reportErrors: boolean) { + return (deferredGlobalPromiseLikeType ||= getGlobalType("PromiseLike" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; + } + + function getGlobalPromiseConstructorSymbol(reportErrors: boolean): Symbol | undefined { + return deferredGlobalPromiseConstructorSymbol ||= getGlobalValueSymbol("Promise" as __String, reportErrors); + } + + function getGlobalPromiseConstructorLikeType(reportErrors: boolean) { + return (deferredGlobalPromiseConstructorLikeType ||= getGlobalType("PromiseConstructorLike" as __String, /*arity*/ 0, reportErrors)) || emptyObjectType; + } + + function getGlobalAsyncIterableType(reportErrors: boolean) { + return (deferredGlobalAsyncIterableType ||= getGlobalType("AsyncIterable" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; + } + + function getGlobalAsyncIteratorType(reportErrors: boolean) { + return (deferredGlobalAsyncIteratorType ||= getGlobalType("AsyncIterator" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType; + } + + function getGlobalAsyncIterableIteratorType(reportErrors: boolean) { + return (deferredGlobalAsyncIterableIteratorType ||= getGlobalType("AsyncIterableIterator" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; + } + + function getGlobalAsyncGeneratorType(reportErrors: boolean) { + return (deferredGlobalAsyncGeneratorType ||= getGlobalType("AsyncGenerator" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType; + } + + function getGlobalIterableType(reportErrors: boolean) { + return (deferredGlobalIterableType ||= getGlobalType("Iterable" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; + } + + function getGlobalIteratorType(reportErrors: boolean) { + return (deferredGlobalIteratorType ||= getGlobalType("Iterator" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType; + } + + function getGlobalIterableIteratorType(reportErrors: boolean) { + return (deferredGlobalIterableIteratorType ||= getGlobalType("IterableIterator" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; + } + + function getGlobalGeneratorType(reportErrors: boolean) { + return (deferredGlobalGeneratorType ||= getGlobalType("Generator" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType; + } + + function getGlobalIteratorYieldResultType(reportErrors: boolean) { + return (deferredGlobalIteratorYieldResultType ||= getGlobalType("IteratorYieldResult" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; + } + + function getGlobalIteratorReturnResultType(reportErrors: boolean) { + return (deferredGlobalIteratorReturnResultType ||= getGlobalType("IteratorReturnResult" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; + } + + function getGlobalDisposableType(reportErrors: boolean) { + return (deferredGlobalDisposableType ||= getGlobalType("Disposable" as __String, /*arity*/ 0, reportErrors)) || emptyObjectType; + } + + function getGlobalAsyncDisposableType(reportErrors: boolean) { + return (deferredGlobalAsyncDisposableType ||= getGlobalType("AsyncDisposable" as __String, /*arity*/ 0, reportErrors)) || emptyObjectType; + } + + function getGlobalTypeOrUndefined(name: __String, arity = 0): ObjectType | undefined { + const symbol = getGlobalSymbol(name, SymbolFlags.Type, /*diagnostic*/ undefined); + return symbol && getTypeOfGlobalSymbol(symbol, arity) as GenericType; + } + + 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 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 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 getGlobalBigIntType() { + return (deferredGlobalBigIntType ||= getGlobalType("BigInt" as __String, /*arity*/ 0, /*reportErrors*/ false)) || emptyObjectType; + } + + function getGlobalClassDecoratorContextType(reportErrors: boolean) { + return (deferredGlobalClassDecoratorContextType ??= getGlobalType("ClassDecoratorContext" as __String, /*arity*/ 1, reportErrors)) ?? emptyGenericType; + } + + function getGlobalClassMethodDecoratorContextType(reportErrors: boolean) { + return (deferredGlobalClassMethodDecoratorContextType ??= getGlobalType("ClassMethodDecoratorContext" as __String, /*arity*/ 2, reportErrors)) ?? emptyGenericType; + } + + function getGlobalClassGetterDecoratorContextType(reportErrors: boolean) { + return (deferredGlobalClassGetterDecoratorContextType ??= getGlobalType("ClassGetterDecoratorContext" as __String, /*arity*/ 2, reportErrors)) ?? emptyGenericType; + } + + function getGlobalClassSetterDecoratorContextType(reportErrors: boolean) { + return (deferredGlobalClassSetterDecoratorContextType ??= getGlobalType("ClassSetterDecoratorContext" as __String, /*arity*/ 2, reportErrors)) ?? emptyGenericType; + } + + function getGlobalClassAccessorDecoratorContextType(reportErrors: boolean) { + return (deferredGlobalClassAccessorDecoratorContextType ??= getGlobalType("ClassAccessorDecoratorContext" as __String, /*arity*/ 2, reportErrors)) ?? emptyGenericType; + } + + function getGlobalClassAccessorDecoratorTargetType(reportErrors: boolean) { + return (deferredGlobalClassAccessorDecoratorTargetType ??= getGlobalType("ClassAccessorDecoratorTarget" as __String, /*arity*/ 2, reportErrors)) ?? emptyGenericType; + } + + function getGlobalClassAccessorDecoratorResultType(reportErrors: boolean) { + return (deferredGlobalClassAccessorDecoratorResultType ??= getGlobalType("ClassAccessorDecoratorResult" as __String, /*arity*/ 2, reportErrors)) ?? emptyGenericType; + } + + function getGlobalClassFieldDecoratorContextType(reportErrors: boolean) { + return (deferredGlobalClassFieldDecoratorContextType ??= getGlobalType("ClassFieldDecoratorContext" as __String, /*arity*/ 2, reportErrors)) ?? emptyGenericType; + } + + function getGlobalNaNSymbol(): Symbol | undefined { + return (deferredGlobalNaNSymbol ||= getGlobalValueSymbol("NaN" as __String, /*reportErrors*/ false)); + } + + function getGlobalRecordSymbol(): Symbol | undefined { + deferredGlobalRecordSymbol ||= getGlobalTypeAliasSymbol("Record" as __String, /*arity*/ 2, /*reportErrors*/ true) || unknownSymbol; + return deferredGlobalRecordSymbol === unknownSymbol ? undefined : deferredGlobalRecordSymbol; + } + + /** + * 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 createTypedPropertyDescriptorType(propertyType: Type): Type { + return createTypeFromGenericGlobalType(getGlobalTypedPropertyDescriptorType(), [propertyType]); + } + + function createIterableType(iteratedType: Type): Type { + return createTypeFromGenericGlobalType(getGlobalIterableType(/*reportErrors*/ true), [iteratedType]); + } + + 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; + } + } + + 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); + return getTupleTargetType(elementFlags, readonly, map((node as TupleTypeNode).elements, memberIfLabeledElementDeclaration)); + } + + function memberIfLabeledElementDeclaration(member: Node): NamedTupleMember | ParameterDeclaration | undefined { + return isNamedTupleMember(member) || isParameter(member) ? member : undefined; + } + + // 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; + } + + // 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; + } + + 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 | undefined)[] = []) { + 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 | undefined)[]): 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" : "") + + (some(namedMemberDeclarations, node => !!node) ? "," + map(namedMemberDeclarations, node => node ? getNodeId(node) : "_").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 = 0 as ElementFlags; + 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.links.tupleLabelDeclaration = namedMemberDeclarations?.[i]; + property.links.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.links.type = numberType; + } + else { + const literalTypes = []; + for (let i = minLength; i <= arity; i++) literalTypes.push(getNumberLiteralType(i)); + lengthSymbol.links.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[] = []; + const 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.Any) { + addElement(type, ElementFlags.Rest, target.labeledElementDeclarations?.[i]); + } + else 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 = getElementTypes(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]); + } + } + else { + // Copy other element kinds with no change. + addElement(type, flags, 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); + } + 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); + expandedDeclarations.push(declaration); + } + } + + 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))); + } + + // 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; + } + + function getTotalFixedElementCount(type: TupleType) { + return type.fixedLength + getEndElementCount(type, ElementFlags.Fixed); + } + + function getElementTypes(type: TupleTypeReference): readonly Type[] { + const typeArguments = getTypeArguments(type); + const arity = getTypeReferenceArity(type); + return typeArguments.length === arity ? typeArguments : typeArguments.slice(0, arity); + } + + function getTypeFromOptionalTypeNode(node: OptionalTypeNode): Type { + return addOptionality(getTypeFromTypeNode(node.type), /*isProperty*/ true); + } + + function getTypeId(type: Type): TypeId { + return type.id; + } + + 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 addTypeToUnion(typeSet: Type[], includes: TypeFlags, type: Type) { + const flags = type.flags; + // We ignore 'never' types in unions + if (!(flags & TypeFlags.Never)) { + includes |= flags & TypeFlags.IncludesMask; + if (flags & TypeFlags.Instantiable) includes |= TypeFlags.IncludesInstantiable; + if (flags & TypeFlags.Intersection && getObjectFlags(type) & ObjectFlags.IsConstrainedTypeVariable) includes |= TypeFlags.IncludesConstrainedTypeVariable; + if (type === wildcardType) includes |= TypeFlags.IncludesWildcard; + if (isErrorType(type)) includes |= TypeFlags.IncludesError; + 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); + } + } + } + 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 { + let lastType: Type | undefined; + for (const type of types) { + // We skip the type if it is the same as the last type we processed. This simple test particularly + // saves a lot of work for large lists of the same union type, such as when resolving `Record[A]`, + // where A and B are large union types. + if (type !== lastType) { + includes = type.flags & TypeFlags.Union ? + addTypesToUnion(typeSet, includes | (isNamedUnionType(type) ? TypeFlags.Union : 0), (type as UnionType).types) : + addTypeToUnion(typeSet, includes, type); + lastType = 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; + } + + // 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) { + // A type parameter with a union constraint may be a subtype of some union, but not a subtype of the + // individual constituents of that union. For example, `T extends A | B` is a subtype of `A | B`, but not + // a subtype of just `A` or just `B`. When we encounter such a type parameter, we therefore check if the + // type parameter is a subtype of a union of all the other types. + if (source.flags & TypeFlags.TypeParameter && getBaseConstraintOrType(source).flags & TypeFlags.Union) { + if (isTypeRelatedTo(source, getUnionType(map(types, t => t === source ? neverType : t)), strictSubtypeRelation)) { + orderedRemoveItemAt(types, i); + } + continue; + } + // 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; + } + } + 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; + } + + 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 removeStringLiteralsMatchedByTemplateLiterals(types: Type[]) { + const templates = filter(types, isPatternLiteralType) as (TemplateLiteralType | StringMappingType)[]; + if (templates.length) { + let i = types.length; + while (i > 0) { + i--; + const t = types[i]; + if (t.flags & TypeFlags.StringLiteral && some(templates, template => isTypeMatchedByTemplateLiteralOrStringMapping(t, template))) { + orderedRemoveItemAt(types, i); + } + } + } + } + + function isTypeMatchedByTemplateLiteralOrStringMapping(type: Type, template: TemplateLiteralType | StringMappingType) { + return template.flags & TypeFlags.TemplateLiteral ? + isTypeMatchedByTemplateLiteralType(type, template as TemplateLiteralType) : + isMemberOfStringMapping(type, template); + } + + function removeConstrainedTypeVariables(types: Type[]) { + const typeVariables: TypeVariable[] = []; + // First collect a list of the type variables occurring in constraining intersections. + for (const type of types) { + if (type.flags & TypeFlags.Intersection && getObjectFlags(type) & ObjectFlags.IsConstrainedTypeVariable) { + const index = (type as IntersectionType).types[0].flags & TypeFlags.TypeVariable ? 0 : 1; + pushIfUnique(typeVariables, (type as IntersectionType).types[index]); + } + } + // For each type variable, check if the constraining intersections for that type variable fully + // cover the constraint of the type variable; if so, remove the constraining intersections and + // substitute the type variable. + for (const typeVariable of typeVariables) { + const primitives: Type[] = []; + // First collect the primitive types from the constraining intersections. + for (const type of types) { + if (type.flags & TypeFlags.Intersection && getObjectFlags(type) & ObjectFlags.IsConstrainedTypeVariable) { + const index = (type as IntersectionType).types[0].flags & TypeFlags.TypeVariable ? 0 : 1; + if ((type as IntersectionType).types[index] === typeVariable) { + insertType(primitives, (type as IntersectionType).types[1 - index]); + } + } + } + // If every constituent in the type variable's constraint is covered by an intersection of the type + // variable and that constituent, remove those intersections and substitute the type variable. + const constraint = getBaseConstraintOfType(typeVariable)!; + if (everyType(constraint, t => containsType(primitives, t))) { + let i = types.length; + while (i > 0) { + i--; + const type = types[i]; + if (type.flags & TypeFlags.Intersection && getObjectFlags(type) & ObjectFlags.IsConstrainedTypeVariable) { + const index = (type as IntersectionType).types[0].flags & TypeFlags.TypeVariable ? 0 : 1; + if ((type as IntersectionType).types[index] === typeVariable && containsType(primitives, (type as IntersectionType).types[1 - index])) { + orderedRemoveItemAt(types, i); + } + } + } + insertType(types, typeVariable); + } + } + } + + 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 createOriginUnionOrIntersectionType(flags: TypeFlags, types: Type[]) { + const result = createOriginType(flags) as UnionOrIntersectionType; + result.types = types; + return result; + } + + // 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]; + } + // We optimize for the common case of unioning a union type with some other type (such as `undefined`). + if (types.length === 2 && !origin && (types[0].flags & TypeFlags.Union || types[1].flags & TypeFlags.Union)) { + const infix = unionReduction === UnionReduction.None ? "N" : unionReduction === UnionReduction.Subtype ? "S" : "L"; + const index = types[0].id < types[1].id ? 0 : 1; + const id = types[index].id + infix + types[1 - index].id + getAliasId(aliasSymbol, aliasTypeArguments); + let type = unionOfUnionTypes.get(id); + if (!type) { + type = getUnionTypeWorker(types, unionReduction, aliasSymbol, aliasTypeArguments, /*origin*/ undefined); + unionOfUnionTypes.set(id, type); + } + return type; + } + return getUnionTypeWorker(types, unionReduction, aliasSymbol, aliasTypeArguments, origin); + } + + function getUnionTypeWorker(types: readonly Type[], unionReduction: UnionReduction, aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined, origin: Type | undefined): Type { + let typeSet: Type[] | undefined = []; + const includes = addTypesToUnion(typeSet, 0 as TypeFlags, types); + if (unionReduction !== UnionReduction.None) { + if (includes & TypeFlags.AnyOrUnknown) { + return includes & TypeFlags.Any ? + includes & TypeFlags.IncludesWildcard ? wildcardType : + includes & TypeFlags.IncludesError ? errorType : anyType : + unknownType; + } + if (includes & TypeFlags.Undefined) { + // If type set contains both undefinedType and missingType, remove missingType + if (typeSet.length >= 2 && typeSet[0] === undefinedType && typeSet[1] === missingType) { + orderedRemoveItemAt(typeSet, 1); + } + } + if (includes & (TypeFlags.Enum | 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 | TypeFlags.StringMapping)) { + removeStringLiteralsMatchedByTemplateLiterals(typeSet); + } + if (includes & TypeFlags.IncludesConstrainedTypeVariable) { + removeConstrainedTypeVariables(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 (!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); + } + } + 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 last: TypePredicate | undefined; + const types: Type[] = []; + for (const sig of signatures) { + const pred = getTypePredicateOfSignature(sig); + if (pred) { + // Constituent type predicates must all have matching kinds. We don't create composite type predicates for assertions. + if (pred.kind !== TypePredicateKind.This && pred.kind !== TypePredicateKind.Identifier || last && !typePredicateKindsMatch(last, pred)) { + return undefined; + } + last = pred; + types.push(pred.type); + } + else { + // In composite union signatures we permit and ignore signatures with a return type `false`. + const returnType = kind !== TypeFlags.Intersection ? getReturnTypeOfSignature(sig) : undefined; + if (returnType !== falseType && returnType !== regularFalseType) { + return undefined; + } + } + } + if (!last) { + return undefined; + } + const compositeType = getUnionOrIntersectionType(types, kind); + return createTypePredicate(last.kind, last.parameterName, last.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[], precomputedObjectFlags: ObjectFlags, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[], origin?: Type): Type { + if (types.length === 0) { + return neverType; + } + 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 = precomputedObjectFlags | 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"; + } + unionTypes.set(id, type); + } + return type; + } + + 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 addTypeToIntersection(typeSet: Map, 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); + } + } + else { + if (flags & TypeFlags.AnyOrUnknown) { + if (type === wildcardType) includes |= TypeFlags.IncludesWildcard; + if (isErrorType(type)) includes |= TypeFlags.IncludesError; + } + else if (strictNullChecks || !(flags & TypeFlags.Nullable)) { + if (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; + } + return includes; + } + + // 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: Map, includes: TypeFlags, types: readonly Type[]) { + for (const type of types) { + includes = addTypeToIntersection(typeSet, includes, getRegularTypeOfLiteralType(type)); + } + return includes; + } + + 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); + } + } + } + + // 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.Enum | TypeFlags.NumberLiteral) ? numberType : + type.flags & TypeFlags.BigIntLiteral ? bigintType : + type.flags & TypeFlags.UniqueESSymbol ? esSymbolType : + undefined; + if (!primitive || !containsType(u.types, primitive)) { + return false; + } + } + } + return true; + } + + /** + * Returns true if the intersection of the template literals and string literals is the empty set, + * for example `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 | TypeFlags.StringMapping))) continue; + for (const t2 of literals) { + if (isTypeSubtypeOf(t2, t)) { + // For example, `get${T}` & "getX" is just "getX", and Lowercase & "foo" is just "foo" + orderedRemoveItemAt(types, i); + break; + } + else if (isPatternLiteralType(t)) { + return true; + } + } + } + return false; + } + + function removeFromEach(types: Type[], flag: TypeFlags) { + for (let i = 0; i < types.length; i++) { + types[i] = filterType(types[i], t => !(t.flags & flag)); + } + } + + // 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 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); + } + } + } + } + // Finally replace the first union with the result + types[index] = getUnionTypeFromSortedList(result, ObjectFlags.PrimitiveUnion); + return true; + } + + function createIntersectionType(types: Type[], objectFlags: ObjectFlags, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]) { + const result = createType(TypeFlags.Intersection) as IntersectionType; + result.objectFlags = objectFlags | getPropagatingFlagsOfTypes(types, /*excludeKinds*/ TypeFlags.Nullable); + result.types = types; + result.aliasSymbol = aliasSymbol; + result.aliasTypeArguments = aliasTypeArguments; + return result; + } + + // We normalize combinations of intersection and union types based on the distributive property of the '&' + // operator. Specifically, because X & (A | B) is equivalent to X & A | X & B, we can transform intersection + // types with union type constituents into equivalent union types with intersection type constituents and + // 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[], flags = IntersectionFlags.None, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type { + const typeMembershipMap = new Map(); + const includes = addTypesToIntersection(typeMembershipMap, 0 as TypeFlags, types); + const typeSet: Type[] = arrayFrom(typeMembershipMap.values()); + let objectFlags = ObjectFlags.None; + // 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 | TypeFlags.StringMapping) && includes & TypeFlags.StringLiteral && extractRedundantTemplateLiterals(typeSet)) { + return neverType; + } + if (includes & TypeFlags.Any) { + return includes & TypeFlags.IncludesWildcard ? wildcardType : includes & TypeFlags.IncludesError ? errorType : 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 (!(flags & IntersectionFlags.NoSupertypeReduction)) removeRedundantSupertypes(typeSet, includes); + } + if (includes & TypeFlags.IncludesMissingType) { + typeSet[typeSet.indexOf(undefinedType)] = missingType; + } + if (typeSet.length === 0) { + return unknownType; + } + if (typeSet.length === 1) { + return typeSet[0]; + } + if (typeSet.length === 2 && !(flags & IntersectionFlags.NoConstraintReduction)) { + const typeVarIndex = typeSet[0].flags & TypeFlags.TypeVariable ? 0 : 1; + const typeVariable = typeSet[typeVarIndex]; + const primitiveType = typeSet[1 - typeVarIndex]; + if ( + typeVariable.flags & TypeFlags.TypeVariable && + (primitiveType.flags & (TypeFlags.Primitive | TypeFlags.NonPrimitive) && !isGenericStringLikeType(primitiveType) || includes & TypeFlags.IncludesEmptyObject) + ) { + // We have an intersection T & P or P & T, where T is a type variable and P is a primitive type, the object type, or {}. + const constraint = getBaseConstraintOfType(typeVariable); + // Check that T's constraint is similarly composed of primitive types, the object type, or {}. + if (constraint && everyType(constraint, t => !!(t.flags & (TypeFlags.Primitive | TypeFlags.NonPrimitive)) || isEmptyAnonymousObjectType(t))) { + // If T's constraint is a subtype of P, simply return T. For example, given `T extends "a" | "b"`, + // the intersection `T & string` reduces to just T. + if (isTypeStrictSubtypeOf(constraint, primitiveType)) { + return typeVariable; + } + if (!(constraint.flags & TypeFlags.Union && someType(constraint, c => isTypeStrictSubtypeOf(c, primitiveType)))) { + // No constituent of T's constraint is a subtype of P. If P is also not a subtype of T's constraint, + // then the constraint and P are unrelated, and the intersection reduces to never. For example, given + // `T extends "a" | "b"`, the intersection `T & number` reduces to never. + if (!isTypeStrictSubtypeOf(primitiveType, constraint)) { + return neverType; + } + } + // Some constituent of T's constraint is a subtype of P, or P is a subtype of T's constraint. Thus, + // the intersection further constrains the type variable. For example, given `T extends string | number`, + // the intersection `T & "a"` is marked as a constrained type variable. Likewise, given `T extends "a" | 1`, + // the intersection `T & number` is marked as a constrained type variable. + objectFlags = ObjectFlags.IsConstrainedTypeVariable; + } + } + } + const id = getTypeListId(typeSet) + (flags & IntersectionFlags.NoConstraintReduction ? "*" : 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, flags, aliasSymbol, aliasTypeArguments); + } + else if (every(typeSet, t => !!(t.flags & TypeFlags.Union && (t as UnionType).types[0].flags & TypeFlags.Undefined))) { + const containedUndefinedType = some(typeSet, containsMissingType) ? missingType : undefinedType; + removeFromEach(typeSet, TypeFlags.Undefined); + result = getUnionType([getIntersectionType(typeSet, flags), containedUndefinedType], UnionReduction.Literal, aliasSymbol, aliasTypeArguments); + } + else if (every(typeSet, t => !!(t.flags & TypeFlags.Union && ((t as UnionType).types[0].flags & TypeFlags.Null || (t as UnionType).types[1].flags & TypeFlags.Null)))) { + removeFromEach(typeSet, TypeFlags.Null); + result = getUnionType([getIntersectionType(typeSet, flags), nullType], UnionReduction.Literal, aliasSymbol, aliasTypeArguments); + } + else if (typeSet.length >= 4) { + // When we have four or more constituents, some of which are unions, we employ a "divide and conquer" strategy + // where A & B & C & D is processed as (A & B) & (C & D). Since intersections of unions often produce far smaller + // unions of intersections than the full cartesian product (due to some intersections becoming `never`), this can + // dramatically reduce the overall work. + const middle = Math.floor(typeSet.length / 2); + result = getIntersectionType([getIntersectionType(typeSet.slice(0, middle), flags), getIntersectionType(typeSet.slice(middle), flags)], flags, 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, flags); + // 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 { + result = createIntersectionType(typeSet, objectFlags, aliasSymbol, aliasTypeArguments); + } + intersectionTypes.set(id, result); + } + return result; + } + + 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 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 getCrossProductIntersections(types: readonly Type[], flags: IntersectionFlags) { + 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); + } + } + const t = getIntersectionType(constituents, flags); + if (!(t.flags & TypeFlags.Never)) intersections.push(t); + } + return intersections; + } + + 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 getTypeFromIntersectionTypeNode(node: IntersectionTypeNode): Type { + const links = getNodeLinks(node); + if (!links.resolvedType) { + const aliasSymbol = getAliasSymbolForTypeNode(node); + const types = map(node.types, getTypeFromTypeNode); + // We perform no supertype reduction for X & {} or {} & X, where X is one of string, number, bigint, + // or a pattern literal template type. This enables union types like "a" | "b" | string & {} or + // "aa" | "ab" | `a${string}` which preserve the literal types for purposes of statement completion. + const emptyIndex = types.length === 2 ? types.indexOf(emptyTypeLiteralType) : -1; + const t = emptyIndex >= 0 ? types[1 - emptyIndex] : unknownType; + const noSupertypeReduction = !!(t.flags & (TypeFlags.String | TypeFlags.Number | TypeFlags.BigInt) || t.flags & TypeFlags.TemplateLiteral && isPatternLiteralType(t)); + links.resolvedType = getIntersectionType(types, noSupertypeReduction ? IntersectionFlags.NoSupertypeReduction : 0, aliasSymbol, getTypeArgumentsForAliasSymbol(aliasSymbol)); + } + return links.resolvedType; + } + + function createIndexType(type: InstantiableType | UnionOrIntersectionType, indexFlags: IndexFlags) { + const result = createType(TypeFlags.Index) as IndexType; + result.type = type; + result.indexFlags = indexFlags; + return result; + } + + function createOriginIndexType(type: InstantiableType | UnionOrIntersectionType) { + const result = createOriginType(TypeFlags.Index) as IndexType; + result.type = type; + return result; + } + + function getIndexTypeForGenericType(type: InstantiableType | UnionOrIntersectionType, indexFlags: IndexFlags) { + return indexFlags & IndexFlags.StringsOnly ? + type.resolvedStringIndexType || (type.resolvedStringIndexType = createIndexType(type, IndexFlags.StringsOnly)) : + type.resolvedIndexType || (type.resolvedIndexType = createIndexType(type, IndexFlags.None)); + } + + /** + * 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, indexFlags: IndexFlags) { + const typeParameter = getTypeParameterFromMappedType(type); + const constraintType = getConstraintTypeFromMappedType(type); + const nameType = getNameTypeFromMappedType(type.target as MappedType || type); + if (!nameType && !(indexFlags & IndexFlags.NoIndexSignatures)) { + // no mapping and no filtering required, just quickly bail to returning the constraint in the common case + return constraintType; + } + const keyTypes: Type[] = []; + // Calling getApparentType on the `T` of a `keyof T` in the constraint type of a generic mapped type can + // trigger a circularity. For example, `T extends { [P in keyof T & string as Captitalize

]: any }` is + // a circular definition. For this reason, we only eagerly manifest the keys if the constraint is non-generic. + if (isGenericIndexType(constraintType)) { + if (isMappedTypeWithKeyofConstraintDeclaration(type)) { + // 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, indexFlags); + } + // Include the generic component in the resulting type. + forEachType(constraintType, addMemberForKeyType); + } + else if (isMappedTypeWithKeyofConstraintDeclaration(type)) { + const modifiersType = getApparentType(getModifiersTypeFromMappedType(type)); // The 'T' in 'keyof T' + forEachMappedTypePropertyKeyTypeAndIndexSignatureKeyType(modifiersType, TypeFlags.StringOrNumberLiteralOrUnique, !!(indexFlags & IndexFlags.StringsOnly), addMemberForKeyType); + } + else { + forEachType(getLowerBoundOfKeyType(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 = indexFlags & IndexFlags.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); + } + } + + // Ordinarily we reduce a keyof M, where M is a mapped type { [P in K as N

) + 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) { + if (node.typeParameters || getEffectiveReturnTypeNode(node) || !node.body) { + return false; + } + if (node.body.kind !== SyntaxKind.Block) { + return isContextSensitive(node.body); + } + return !!forEachReturnStatement(node.body as Block, statement => !!statement.expression && isContextSensitive(statement.expression)); + } + + 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; + } + } + else if (type.flags & TypeFlags.Intersection) { + return getIntersectionType(map((type as IntersectionType).types, getTypeWithoutSignatures)); + } + return type; + } + + // TYPE CHECKING + + 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 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 isTypeSubtypeOf(source: Type, target: Type): boolean { + return isTypeRelatedTo(source, target, subtypeRelation); + } + + function isTypeStrictSubtypeOf(source: Type, target: Type): boolean { + return isTypeRelatedTo(source, target, strictSubtypeRelation); + } + + 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 an intersection type and some constituent of S is derived from T, or + // S is a type variable with a base constraint that is derived from T, or + // T is {} and S is an object-like type (ensuring {} is less derived than Object), or + // 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.Intersection ? some((source as IntersectionType).types, t => isTypeDerivedFrom(t, target)) : + source.flags & TypeFlags.InstantiableNonPrimitive ? isTypeDerivedFrom(getBaseConstraintOfType(source) || unknownType, target) : + isEmptyAnonymousObjectType(target) ? !!(source.flags & (TypeFlags.Object | TypeFlags.NonPrimitive)) : + target === globalObjectType ? !!(source.flags & (TypeFlags.Object | TypeFlags.NonPrimitive)) && !isEmptyAnonymousObjectType(source) : + 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); + } + + 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); + } + + /** + * 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: Map, + 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 elaborateError( + node: Expression | undefined, + source: Type, + target: Type, + relation: Map, + 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.AsExpression: + if (!isConstAssertion(node)) { + break; + } + // fallthrough + case SyntaxKind.JsxExpression: + case SyntaxKind.ParenthesizedExpression: + return elaborateError((node as AsExpression | 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 elaborateDidYouMeanToCallOrConstruct( + node: Expression, + source: Type, + target: Type, + relation: Map, + 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; + } + } + return false; + } + + function elaborateArrowFunction( + node: ArrowFunction, + source: Type, + target: Type, + relation: Map, + 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, 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, /*headMessage*/ 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 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); + } + } + } + + function checkExpressionForMutableLocationWithContextualType(next: Expression, sourcePropType: Type) { + pushContextualType(next, sourcePropType, /*isCache*/ false); + const result = checkExpressionForMutableLocation(next, CheckMode.Contextual); + popContextualType(); + return result; + } + + 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: Map, + 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 (const value of iterator) { + const { errorNode: prop, innerExpression: next, nameType, errorMessage } = 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), + ), + ); + } + } + } + } + } + } + return reportedError; + } + + /** + * Assumes `target` type is assignable to the `Iterable` type, if `Iterable` is defined, + * or that it's an array or tuple-like type, if `Iterable` is not defined. + */ + function elaborateIterableOrArrayLikeTargetElementwise( + iterator: ElaborationIterator, + source: Type, + target: Type, + relation: Map, + containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, + errorOutputContainer: { errors?: Diagnostic[]; skipLogging?: boolean; } | undefined, + ) { + const tupleOrArrayLikeTargetParts = filterType(target, isArrayOrTupleLikeType); + const nonTupleOrArrayLikeTargetParts = filterType(target, t => !isArrayOrTupleLikeType(t)); + // If `nonTupleOrArrayLikeTargetParts` is not `never`, then that should mean `Iterable` is defined. + const iterationType = nonTupleOrArrayLikeTargetParts !== neverType + ? getIterationTypeOfIterable(IterationUse.ForOf, IterationTypeKind.Yield, nonTupleOrArrayLikeTargetParts, /*errorNode*/ undefined) + : undefined; + + let reportedError = false; + for (let status = iterator.next(); !status.done; status = iterator.next()) { + const { errorNode: prop, innerExpression: next, nameType, errorMessage } = status.value; + let targetPropType = iterationType; + const targetIndexedPropType = tupleOrArrayLikeTargetParts !== neverType ? getBestMatchIndexedAccessTypeOrUndefined(source, tupleOrArrayLikeTargetParts, nameType) : undefined; + if (targetIndexedPropType && !(targetIndexedPropType.flags & TypeFlags.IndexedAccess)) { // Don't elaborate on indexes on generic variables + targetPropType = iterationType ? getUnionType([iterationType, targetIndexedPropType]) : targetIndexedPropType; + } + if (!targetPropType) continue; + 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(tupleOrArrayLikeTargetParts, 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); + } + } + } + } + } + return reportedError; + } + + function* generateJsxAttributes(node: JsxAttributes): ElaborationIterator { + if (!length(node.properties)) return; + for (const prop of node.properties) { + if (isJsxSpreadAttribute(prop) || isHyphenatedJsxName(getTextOfJsxAttributeName(prop.name))) continue; + yield { errorNode: prop.name, innerExpression: prop.initializer, nameType: getStringLiteralType(getTextOfJsxAttributeName(prop.name)) }; + } + } + + 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++; + } + } + } + + 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: Map, + 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; + let arrayLikeTargetParts: Type; + let nonArrayLikeTargetParts: Type; + const iterableType = getGlobalIterableType(/*reportErrors*/ false); + if (iterableType !== emptyGenericType) { + const anyIterable = createIterableType(anyType); + arrayLikeTargetParts = filterType(childrenTargetType, t => isTypeAssignableTo(t, anyIterable)); + nonArrayLikeTargetParts = filterType(childrenTargetType, t => !isTypeAssignableTo(t, anyIterable)); + } + else { + arrayLikeTargetParts = filterType(childrenTargetType, isArrayOrTupleLikeType); + nonArrayLikeTargetParts = filterType(childrenTargetType, t => !isArrayOrTupleLikeType(t)); + } + if (moreThanOneRealChildren) { + if (arrayLikeTargetParts !== neverType) { + const realSource = createTupleType(checkJsxChildren(containingElement, CheckMode.Normal)); + const children = generateJsxChildren(containingElement, getInvalidTextualChildDiagnostic); + result = elaborateIterableOrArrayLikeTargetElementwise(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); + } + } + } + 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 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(diagnostic, tagNameText, childrenPropName, typeToString(childrenTargetType)) }; + } + return invalidTextDiagnostic; + } + } + + 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); + const checkNode = getEffectiveCheckNode(elem); + yield { errorNode: checkNode, innerExpression: checkNode, nameType }; + } + } + + function elaborateArrayLiteral( + node: ArrayLiteralExpression, + source: Type, + target: Type, + relation: Map, + 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 + pushContextualType(node, target, /*isCache*/ false); + const tupleizedType = checkArrayLiteral(node, CheckMode.Contextual, /*forceTuple*/ true); + popContextualType(); + if (isTupleLikeType(tupleizedType)) { + return elaborateElementwise(generateLimitedTupleElements(node, target), tupleizedType, target, relation, containingMessageChain, errorOutputContainer); + } + return false; + } + + function* generateObjectLiteralElements(node: ObjectLiteralExpression): ElaborationIterator { + if (!length(node.properties)) return; + for (const prop of node.properties) { + if (isSpreadAssignment(prop)) continue; + const type = getLiteralTypeFromProperty(getSymbolOfDeclaration(prop), TypeFlags.StringOrNumberLiteralOrUnique); + if (!type || (type.flags & TypeFlags.Never)) { + continue; + } + 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); + } + } + } + + function elaborateObjectLiteral( + node: ObjectLiteralExpression, + source: Type, + target: Type, + relation: Map, + 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); + } + + /** + * 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 isSignatureAssignableTo(source: Signature, target: Signature, ignoreReturnTypes: boolean): boolean { + return compareSignaturesRelated(source, target, ignoreReturnTypes ? SignatureCheckMode.IgnoreReturnTypes : SignatureCheckMode.None, /*reportErrors*/ false, /*errorReporter*/ undefined, /*incompatibleErrorReporter*/ undefined, compareTypesAssignable, /*reportUnreliableMarkers*/ undefined) !== Ternary.False; + } + + type ErrorReporter = (message: DiagnosticMessage, ...args: DiagnosticArguments) => void; + + /** + * Returns true if `s` is `(...args: A) => R` where `A` is `any`, `any[]`, `never`, or `never[]`, and `R` is `any` or `unknown`. + */ + function isTopSignature(s: Signature) { + if (!s.typeParameters && (!s.thisParameter || isTypeAny(getTypeOfParameter(s.thisParameter))) && s.parameters.length === 1 && signatureHasRestParameter(s)) { + const paramType = getTypeOfParameter(s.parameters[0]); + const restType = isArrayType(paramType) ? getTypeArguments(paramType)[0] : paramType; + return !!(restType.flags & (TypeFlags.Any | TypeFlags.Never) && getReturnTypeOfSignature(s).flags & TypeFlags.AnyOrUnknown); + } + return 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 (!(checkMode & SignatureCheckMode.StrictTopSignature && isTopSignature(source)) && isTopSignature(target)) { + return Ternary.True; + } + if (checkMode & SignatureCheckMode.StrictTopSignature && isTopSignature(source) && !isTopSignature(target)) { + return Ternary.False; + } + + const targetCount = getParameterCount(target); + const sourceHasMoreParameters = !hasEffectiveRestParameter(target) && + (checkMode & SignatureCheckMode.StrictArity ? hasEffectiveRestParameter(source) || getParameterCount(source) > targetCount : getMinArgumentCount(source) > targetCount); + if (sourceHasMoreParameters) { + if (reportErrors && !(checkMode & SignatureCheckMode.StrictArity)) { + // the second condition should be redundant, because there is no error reporting when comparing signatures by strict arity + // since it is only done for subtype reduction + errorReporter!(Diagnostics.Target_signature_provides_too_few_arguments_Expected_0_or_more_but_got_1, getMinArgumentCount(source), targetCount); + } + 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); + } + return Ternary.False; + } + result &= related; + } + } + + 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 ? getRestOrAnyTypeAtPosition(source, i) : tryGetTypeAtPosition(source, i); + const targetType = i === restIndex ? getRestOrAnyTypeAtPosition(target, i) : tryGetTypeAtPosition(target, i); + if (sourceType && targetType && (sourceType !== targetType || checkMode & SignatureCheckMode.StrictArity)) { + // 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 || isInstantiatedGenericParameter(source, i) ? undefined : getSingleCallSignature(getNonNullableType(sourceType)); + const targetSig = checkMode & SignatureCheckMode.Callback || isInstantiatedGenericParameter(target, i) ? 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; + } + 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) || isThisTypePredicate(targetTypePredicate)) { + if (reportErrors) { + errorReporter!(Diagnostics.Signature_0_must_be_a_type_predicate, signatureToString(source)); + } + return 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); + } + } + } + + 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 (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; + } + } + + 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 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); + } + + 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 !!((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 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 sourceProperty of getPropertiesOfType(getTypeOfSymbol(sourceSymbol))) { + if (sourceProperty.flags & SymbolFlags.EnumMember) { + const targetProperty = getPropertyOfType(targetEnumType, sourceProperty.escapedName); + if (!targetProperty || !(targetProperty.flags & SymbolFlags.EnumMember)) { + if (errorReporter) { + errorReporter(Diagnostics.Property_0_is_missing_in_type_1, symbolName(sourceProperty), typeToString(getDeclaredTypeOfSymbol(targetSymbol), /*enclosingDeclaration*/ undefined, TypeFormatFlags.UseFullyQualifiedType)); + enumRelation.set(id, RelationComparisonResult.Failed | RelationComparisonResult.Reported); + } + else { + enumRelation.set(id, RelationComparisonResult.Failed); + } + return false; + } + const sourceValue = getEnumMemberValue(getDeclarationOfKind(sourceProperty, SyntaxKind.EnumMember)!).value; + const targetValue = getEnumMemberValue(getDeclarationOfKind(targetProperty, SyntaxKind.EnumMember)!).value; + if (sourceValue !== targetValue) { + const sourceIsString = typeof sourceValue === "string"; + const targetIsString = typeof targetValue === "string"; + + // If we have 2 enums with *known* values that differ, they are incompatible. + if (sourceValue !== undefined && targetValue !== undefined) { + if (!errorReporter) { + enumRelation.set(id, RelationComparisonResult.Failed); + } + else { + const escapedSource = sourceIsString ? `"${escapeString(sourceValue)}"` : sourceValue; + const escapedTarget = targetIsString ? `"${escapeString(targetValue)}"` : targetValue; + errorReporter(Diagnostics.Each_declaration_of_0_1_differs_in_its_value_where_2_was_expected_but_3_was_given, symbolName(targetSymbol), symbolName(targetProperty), escapedTarget, escapedSource); + enumRelation.set(id, RelationComparisonResult.Failed | RelationComparisonResult.Reported); + } + return false; + } + + // At this point we know that at least one of the values is 'undefined'. + // This may mean that we have an opaque member from an ambient enum declaration, + // or that we were not able to calculate it (which is basically an error). + // + // Either way, we can assume that it's numeric. + // If the other is a string, we have a mismatch in types. + if (sourceIsString || targetIsString) { + if (!errorReporter) { + enumRelation.set(id, RelationComparisonResult.Failed); + } + else { + const knownStringValue = sourceValue ?? targetValue; + Debug.assert(typeof knownStringValue === "string"); + const escapedValue = `"${escapeString(knownStringValue)}"`; + errorReporter(Diagnostics.One_value_of_0_1_is_the_string_2_and_the_other_is_assumed_to_be_an_unknown_numeric_value, symbolName(targetSymbol), symbolName(targetProperty), escapedValue); + enumRelation.set(id, RelationComparisonResult.Failed | RelationComparisonResult.Reported); + } + return false; + } + } + } + } + enumRelation.set(id, RelationComparisonResult.Succeeded); + return true; + } + + function isSimpleTypeRelatedTo(source: Type, target: Type, relation: Map, errorReporter?: ErrorReporter) { + const s = source.flags; + const t = target.flags; + if (t & TypeFlags.Any || s & TypeFlags.Never || source === wildcardType) return true; + if (t & TypeFlags.Unknown && !(relation === strictSubtypeRelation && s & TypeFlags.Any)) 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; + } + // 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 is assignable to any computed numeric enum type or any numeric enum literal type, and + // a numeric literal type is assignable any computed numeric enum type or any numeric enum literal type + // with a matching value. These rules exist such that enums can be used for bit-flag purposes. + if (s & TypeFlags.Number && (t & TypeFlags.Enum || t & TypeFlags.NumberLiteral && t & TypeFlags.EnumLiteral)) return true; + if ( + s & TypeFlags.NumberLiteral && !(s & TypeFlags.EnumLiteral) && (t & TypeFlags.Enum || + t & TypeFlags.NumberLiteral && t & TypeFlags.EnumLiteral && + (source as NumberLiteralType).value === (target as NumberLiteralType).value) + ) 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: Map) { + 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; + } + } + 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); + } + + function getNormalizedType(type: Type, writing: boolean): Type { + while (true) { + const t = isFreshLiteralType(type) ? (type as FreshableType).regularType : + isGenericTupleType(type) ? getNormalizedTupleType(type, writing) : + 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; + } + } + + function getNormalizedUnionOrIntersectionType(type: UnionOrIntersectionType, writing: boolean) { + const reduced = getReducedType(type); + if (reduced !== type) { + return reduced; + } + if (type.flags & TypeFlags.Intersection && shouldNormalizeIntersection(type as IntersectionType)) { + // Normalization handles cases like + // Partial[K] & ({} | null) ==> + // Partial[K] & {} | Partial[K} & null ==> + // (T[K] | undefined) & {} | (T[K] | undefined) & null ==> + // T[K] & {} | undefined & {} | T[K] & null | undefined & null ==> + // T[K] & {} | T[K] & null + const normalizedTypes = sameMap(type.types, t => getNormalizedType(t, writing)); + if (normalizedTypes !== type.types) { + return getIntersectionType(normalizedTypes); + } + } + return type; + } + + function shouldNormalizeIntersection(type: IntersectionType) { + let hasInstantiable = false; + let hasNullableOrEmpty = false; + for (const t of type.types) { + hasInstantiable ||= !!(t.flags & TypeFlags.Instantiable); + hasNullableOrEmpty ||= !!(t.flags & TypeFlags.Nullable) || isEmptyAnonymousObjectType(t); + if (hasInstantiable && hasNullableOrEmpty) return true; + } + return false; + } + + function getNormalizedTupleType(type: TupleTypeReference, writing: boolean): Type { + const elements = getElementTypes(type); + const normalizedElements = sameMap(elements, t => t.flags & TypeFlags.Simplifiable ? getSimplifiedType(t, writing) : t); + return elements !== normalizedElements ? createNormalizedTupleType(type.target, normalizedElements) : type; + } + + /** + * 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: Map, + 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 maybeKeysSet: Set; + 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 skipParentCounter = 0; // How many errors should be skipped 'above' in the elaboration pyramid + let lastSkippedInfo: [Type, Type] | undefined; + let incompatibleStack: DiagnosticAndArguments[] | undefined; + // In Node.js, the maximum number of elements in a map is 2^24. We limit the number of entries an invocation + // of checkTypeRelatedTo can add to a relation to 1/8th of its remaining capacity. + let relationCount = (16_000_000 - relation.size) >> 3; + + 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) { + // Record this relation as having failed such that we don't attempt the overflowing operation again. + const id = getRelationKey(source, target, /*intersectionState*/ IntersectionState.None, relation, /*ignoreConstraints*/ false); + relation.set(id, RelationComparisonResult.Reported | RelationComparisonResult.Failed); + tracing?.instant(tracing.Phase.CheckTypes, "checkTypeRelatedTo_DepthLimit", { sourceId: source.id, targetId: target.id, depth: sourceDepth, targetDepth }); + const message = relationCount <= 0 ? + Diagnostics.Excessive_complexity_comparing_types_0_and_1 : + Diagnostics.Excessive_stack_depth_comparing_types_0_and_1; + const diag = error(errorNode || currentNode, message, 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(getSourceFileOfNode(errorNode!), errorNode!, errorInfo, relatedInformation); + if (relatedInfo) { + addRelatedInfo(diag, ...relatedInfo); + } + 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; + + function resetErrorInfo(saved: ReturnType) { + errorInfo = saved.errorInfo; + lastSkippedInfo = saved.lastSkippedInfo; + incompatibleStack = saved.incompatibleStack; + overrideNextErrorInfo = saved.overrideNextErrorInfo; + skipParentCounter = saved.skipParentCounter; + relatedInfo = saved.relatedInfo; + } + + function captureErrorCalculationState() { + return { + errorInfo, + lastSkippedInfo, + incompatibleStack: incompatibleStack?.slice(), + overrideNextErrorInfo, + skipParentCounter, + relatedInfo: relatedInfo?.slice() as [DiagnosticRelatedInformation, ...DiagnosticRelatedInformation[]] | undefined, + }; + } + + function reportIncompatibleError(message: DiagnosticMessage, ...args: DiagnosticArguments) { + overrideNextErrorInfo++; // Suppress the next relation error + lastSkippedInfo = undefined; // Reset skipped info cache + (incompatibleStack ||= []).push([message, ...args]); + } + + 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(/*message*/ undefined, ...info); + } + 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: DiagnosticAndArguments[] = []; + 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; + } + 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]]); + } + 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})`; + } + 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(/*message*/ undefined, ...info); + } + } + + function reportError(message: DiagnosticMessage, ...args: DiagnosticArguments): void { + Debug.assert(!!errorNode); + if (incompatibleStack) reportIncompatibleStack(); + if (message.elidedInCompatabilityPyramid) return; + if (skipParentCounter === 0) { + errorInfo = chainDiagnosticMessages(errorInfo, message, ...args); + } + else { + skipParentCounter--; + } + } + + function reportParentSkippedError(message: DiagnosticMessage, ...args: DiagnosticArguments): void { + reportError(message, ...args); + skipParentCounter++; + } + + 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; + + if (isLiteralType(source) && !typeCouldHaveTopLevelSingletonTypes(target)) { + generalizedSource = getBaseTypeOfLiteralType(source); + Debug.assert(!isTypeAssignableTo(generalizedSource, target), "generalized source shouldn't be assignable"); + generalizedSourceType = getTypeNameForErrorDisplay(generalizedSource); + } + + // If `target` is of indexed access type (And `source` it is not), we use the object type of `target` for better error reporting + const targetFlags = target.flags & TypeFlags.IndexedAccess && !(source.flags & TypeFlags.IndexedAccess) ? + (target as IndexedAccessType).objectType.flags : + target.flags; + + if (targetFlags & 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, + ); + } + } + + 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; + } + } + 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); + } + + 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); + } + } + + /** + * 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 { + /** + * 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 (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 true; + } + + 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 { + if (originalSource === originalTarget) return Ternary.True; + + // 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); + + 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); + } + + // 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; + } + } + + 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; + } + } + + 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; + } + + 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; + } + } + + 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 (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); + } + // Used by, eg, missing property checking to replace the top-level message with a more informative one. + if (!headMessage && maybeSuppress) { + // We suppress a call to `reportRelationError` or not depending on the state of the type checker, so + // we call `reportRelationError` here and then undo its effects to figure out what would be the diagnostic + // if we hadn't supress it, and save that as a canonical diagnostic for deduplication purposes. + const savedErrorState = captureErrorCalculationState(); + reportRelationError(headMessage, source, target); + let canonical; + if (errorInfo && errorInfo !== savedErrorState.errorInfo) { + canonical = { code: errorInfo.code, messageText: errorInfo.messageText }; + } + resetErrorInfo(savedErrorState); + if (canonical && errorInfo) { + errorInfo.canonicalHead = canonical; + } + + lastSkippedInfo = [source, target]; + 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; + } + + 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; + } + + 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 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)); + } + } + 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); + + const name = propDeclaration.name!; + errorNode = name; + + if (isIdentifier(name)) { + suggestion = getSuggestionForNonexistentProperty(name, errorTarget); + } + } + if (suggestion !== undefined) { + reportParentSkippedError(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 { + reportParentSkippedError(Diagnostics.Object_literal_may_only_specify_known_properties_and_0_does_not_exist_in_type_1, symbolToString(prop), 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)); + } + return true; + } + } + } + return false; + } + + 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) { + if (target.flags & TypeFlags.Union) { + // Intersections of union types are normalized into unions of intersection types, and such normalized + // unions can get very large and expensive to relate. The following fast path checks if the source union + // originated in an intersection. If so, and if that intersection contains the target type, then we know + // the result to be true (for any two types A and B, A & B is related to both A and B). + const sourceOrigin = (source as UnionType).origin; + if (sourceOrigin && sourceOrigin.flags & TypeFlags.Intersection && target.aliasSymbol && contains((sourceOrigin as IntersectionType).types, target)) { + return Ternary.True; + } + // Similarly, in unions of unions the we preserve the original list of unions. This original list is often + // much shorter than the normalized result, so we scan it in the following fast path. + const targetOrigin = (target as UnionType).origin; + if (targetOrigin && targetOrigin.flags & TypeFlags.Union && source.aliasSymbol && contains((targetOrigin as UnionType).types, source)) { + return Ternary.True; + } + } + 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), intersectionState); + } + 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); + } + + 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, IntersectionState.None); + if (!related) { + return Ternary.False; + } + result &= related; + } + return result; + } + + function typeRelatedToSomeType(source: Type, target: UnionOrIntersectionType, reportErrors: boolean, intersectionState: IntersectionState): Ternary { + const targetTypes = target.types; + if (target.flags & TypeFlags.Union) { + if (containsType(targetTypes, source)) { + return Ternary.True; + } + if ( + relation !== comparableRelation && getObjectFlags(target) & ObjectFlags.PrimitiveUnion && !(source.flags & TypeFlags.EnumLiteral) && ( + source.flags & (TypeFlags.StringLiteral | TypeFlags.BooleanLiteral | TypeFlags.BigIntLiteral) || + (relation === subtypeRelation || relation === strictSubtypeRelation) && source.flags & TypeFlags.NumberLiteral + ) + ) { + // When relating a literal type to a union of primitive types, we know the relation is false unless + // the union contains the base primitive type or the literal type in one of its fresh/regular forms. + // We exclude numeric literals for non-subtype relations because numeric literals are assignable to + // numeric enum literals with the same value. Similarly, we exclude enum literal types because + // identically named enum types are related (see isEnumTypeRelatedTo). We exclude the comparable + // relation in entirety because it needs to be checked in both directions. + const alternateForm = source === (source as StringLiteralType).regularType ? (source as StringLiteralType).freshType : (source as StringLiteralType).regularType; + const primitive = source.flags & TypeFlags.StringLiteral ? stringType : + source.flags & TypeFlags.NumberLiteral ? numberType : + source.flags & TypeFlags.BigIntLiteral ? bigintType : + undefined; + return primitive && containsType(targetTypes, primitive) || alternateForm && containsType(targetTypes, alternateForm) ? Ternary.True : Ternary.False; + } + const match = getMatchingUnionConstituentForType(target as UnionType, source); + if (match) { + const related = isRelatedTo(source, match, RecursionFlags.Target, /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState); + if (related) { + return related; + } + } + } + for (const type of targetTypes) { + const related = isRelatedTo(source, type, RecursionFlags.Target, /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState); + 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, /*headMessage*/ undefined, intersectionState); + } + } + 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; + } + 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; + } + 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; + } + } + return Ternary.False; + } + + function getUndefinedStrippedTargetIfNeeded(source: Type, target: Type) { + 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) { + result &= related; + continue; + } + } + const related = isRelatedTo(sourceType, target, RecursionFlags.Source, reportErrors, /*headMessage*/ undefined, intersectionState); + if (!related) { + return Ternary.False; + } + result &= related; + } + 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; + } + 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); + } + } + if (!related) { + return Ternary.False; + } + result &= related; + } + } + 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, /*ignoreConstraints*/ 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 (relationCount <= 0) { + overflow = true; + return Ternary.False; + } + if (!maybeKeys) { + maybeKeys = []; + maybeKeysSet = new Set(); + sourceStack = []; + targetStack = []; + } + else { + // If source and target are already being compared, consider them related with assumptions + if (maybeKeysSet.has(id)) { + return Ternary.Maybe; + } + + // 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; + if (broadestEquivalentId && maybeKeysSet.has(broadestEquivalentId)) { + return Ternary.Maybe; + } + + if (sourceDepth === 100 || targetDepth === 100) { + overflow = true; + return Ternary.False; + } + } + const maybeStart = maybeCount; + maybeKeys[maybeCount] = id; + maybeKeysSet.add(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 = 0 as RelationComparisonResult; + 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. + resetMaybeStack(/*markAllAsSucceeded*/ true); + } + else { + resetMaybeStack(/*markAllAsSucceeded*/ false); + } + } + // Note: it's intentional that we don't reset in the else case; + // we leave them on the stack such that when we hit depth zero + // above, we can report all of them as successful. + } + 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); + relationCount--; + resetMaybeStack(/*markAllAsSucceeded*/ false); + } + return result; + + function resetMaybeStack(markAllAsSucceeded: boolean) { + for (let i = maybeStart; i < maybeCount; i++) { + maybeKeysSet.delete(maybeKeys[i]); + if (markAllAsSucceeded) { + relation.set(maybeKeys[i], RelationComparisonResult.Succeeded | propagatingVarianceFlags); + relationCount--; + } + } + maybeCount = maybeStart; + } + } + + 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); + } + } + // When the target is an intersection we need an extra property check in order to detect nested excess + // properties and nested weak types. 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 + // + if ( + result && !(intersectionState & IntersectionState.Target) && target.flags & TypeFlags.Intersection && + !isGenericObjectType(target) && source.flags & (TypeFlags.Object | TypeFlags.Intersection) + ) { + result &= propertiesRelatedTo(source, target, reportErrors, /*excludedProperties*/ undefined, /*optionalsOnly*/ false, IntersectionState.None); + if (result && isObjectLiteralType(source) && getObjectFlags(source) & ObjectFlags.FreshLiteral) { + result &= indexSignaturesRelatedTo(source, target, /*sourceIsPrimitive*/ false, reportErrors, IntersectionState.None); + } + } + // When the source is an intersection we need an extra check of any optional properties in the target to + // detect possible mismatched property types. For example: + // + // function foo(x: { a?: string }, y: T & { a: boolean }) { + // x = y; // Mismatched property in source intersection + // } + // + else if ( + result && isNonGenericObjectType(target) && !isArrayOrTupleType(target) && + source.flags & TypeFlags.Intersection && getApparentType(source).flags & TypeFlags.StructuredType && + !some((source as IntersectionType).types, t => t === target || !!(getObjectFlags(t) & ObjectFlags.NonInferrableType)) + ) { + result &= propertiesRelatedTo(source, target, reportErrors, /*excludedProperties*/ undefined, /*optionalsOnly*/ true, intersectionState); + } + } + if (result) { + resetErrorInfo(saveErrorInfo); + } + return result; + } + + function getApparentMappedTypeKeys(nameType: Type, targetType: MappedType) { + 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))), + ); + return getUnionType(mappedKeys); + } + + 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 (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; + } + } + + // 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 params = getSymbolLinks(source.aliasSymbol).typeParameters!; + const minParams = getMinTypeArgumentCount(params); + const sourceTypes = fillMissingTypeArguments(source.aliasTypeArguments, params, minParams, isInJSFile(source.aliasSymbol.valueDeclaration)); + const targetTypes = fillMissingTypeArguments(target.aliasTypeArguments, params, minParams, isInJSFile(source.aliasSymbol.valueDeclaration)); + const varianceResult = relateVariances(sourceTypes, targetTypes, 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; + } + + 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 (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) { + while (constraint && someType(constraint, c => !!(c.flags & TypeFlags.TypeParameter))) { + if (result = isRelatedTo(constraint, target, RecursionFlags.Source, /*reportErrors*/ false)) { + return result; + } + constraint = getConstraintOfTypeParameter(constraint); + } + } + 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; + } + } + 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; + } + } + 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).indexFlags | IndexFlags.NoReducibleCheck), 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 mappedKeys = getApparentMappedTypeKeys(nameType, targetType); + // 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; + } + } + } + } + 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)) { + 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, IndexFlags.NoIndexSignatures); + 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; + } + } + } + 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, is not distributive or is distributive but doesn't reference the check type + // parameter in either of the result types, and the source isn't an instantiation of the same + // conditional type (as happens when computing variance). + if (!c.root.inferTypeParameters && !isDistributionDependent(c.root) && !(source.flags & TypeFlags.Conditional && (source as ConditionalType).root === 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 (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; + } + } + } + } + } + else if (sourceFlags & TypeFlags.Index) { + const isDeferredMappedIndex = shouldDeferIndexType((source as IndexType).type, (source as IndexType).indexFlags) && getObjectFlags((source as IndexType).type) & ObjectFlags.Mapped; + if (result = isRelatedTo(stringNumberSymbolType, target, RecursionFlags.Source, reportErrors && !isDeferredMappedIndex)) { + return result; + } + if (isDeferredMappedIndex) { + const mappedType = (source as IndexType).type as MappedType; + const nameType = getNameTypeFromMappedType(mappedType); + // Unlike on the target side, on the source side we do *not* include the generic part of the `nameType`, since that comes from a + // (potentially anonymous) mapped type local type parameter, so that'd never assign outside the mapped type body, but we still want to + // allow assignments of index types of identical (or similar enough) mapped types. + // eg, `keyof {[X in keyof A]: Obj[X]}` should be assignable to `keyof {[Y in keyof A]: Tup[Y]}` because both map over the same set of keys (`keyof A`). + // Without this source-side breakdown, a `keyof {[X in keyof A]: Obj[X]}` style type won't be assignable to anything except itself, which is much too strict. + const sourceMappedKeys = nameType && isMappedTypeWithKeyofConstraintDeclaration(mappedType) ? getApparentMappedTypeKeys(nameType, mappedType) : (nameType || getConstraintTypeFromMappedType(mappedType)); + if (result = isRelatedTo(sourceMappedKeys, 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 { + 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) { + 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)) { + return result; + } + } + // 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 = !(targetFlags & TypeFlags.Conditional) && hasNonCircularBaseConstraint(source) ? getConstraintOfDistributiveConditionalType(source as ConditionalType) : undefined; + if (distributiveConstraint) { + resetErrorInfo(saveErrorInfo); + if (result = isRelatedTo(distributiveConstraint, 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; + } + 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; + } + // 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; + } + } + else if (isReadonlyArrayType(target) ? everyType(source, isArrayOrTupleType) : isArrayType(target) && everyType(source, t => isTupleType(t) && !t.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; + } + } + else if (isGenericTupleType(source) && isTupleType(target) && !isGenericTupleType(target)) { + const constraint = getBaseConstraintOrType(source); + if (constraint !== source) { + return isRelatedTo(constraint, target, RecursionFlags.Source, reportErrors); + } + } + // A fresh empty object type is never a subtype of a non-empty object type. This ensures fresh({}) <: { [x: string]: xxx } + // but not vice-versa. Without this rule, those types would be mutual subtypes. + 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, /*optionalsOnly*/ false, intersectionState); + if (result) { + result &= signaturesRelatedTo(source, target, SignatureKind.Call, reportStructuralErrors, intersectionState); + if (result) { + result &= signaturesRelatedTo(source, target, SignatureKind.Construct, reportStructuralErrors, intersectionState); + 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; + + 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); + } + } + } + + // 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; + } + + 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; + } + } + 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, /*optionalsOnly*/ false, IntersectionState.None); + if (result) { + result &= signaturesRelatedTo(source, type, SignatureKind.Call, /*reportErrors*/ false, IntersectionState.None); + if (result) { + result &= signaturesRelatedTo(source, type, SignatureKind.Construct, /*reportErrors*/ false, IntersectionState.None); + 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, /*reportErrors*/ false, IntersectionState.None); + } + } + } + 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]); + } + } + else if (!result) { + result = properties.slice(0, i); + } + } + return result || properties; + } + + 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)); + } + } + 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++; + } + } + // No array like or unmatched property error - just issue top level error (errorInfo = undefined) + } + + function propertiesRelatedTo(source: Type, target: Type, reportErrors: boolean, excludedProperties: Set<__String> | undefined, optionalsOnly: boolean, 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 targetHasRestElement = !!(target.target.combinedFlags & ElementFlags.Variable); + const sourceMinLength = isTupleType(source) ? source.target.minLength : 0; + const targetMinLength = target.target.minLength; + if (!sourceRestFlag && sourceArity < targetMinLength) { + if (reportErrors) { + reportError(Diagnostics.Source_has_0_element_s_but_target_requires_1, sourceArity, targetMinLength); + } + return Ternary.False; + } + if (!targetHasRestElement && targetArity < sourceMinLength) { + if (reportErrors) { + reportError(Diagnostics.Source_has_0_element_s_but_target_allows_only_1, sourceMinLength, targetArity); + } + return Ternary.False; + } + if (!targetHasRestElement && (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; + } + const sourceTypeArguments = getTypeArguments(source); + const targetTypeArguments = getTypeArguments(target); + const targetStartCount = getStartElementCount(target.target, ElementFlags.NonRest); + const targetEndCount = getEndElementCount(target.target, ElementFlags.NonRest); + 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; + } + 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; + } + } + + 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); + } + } + return Ternary.False; + } + result &= related; + } + return result; + } + if (target.target.combinedFlags & ElementFlags.Variable) { + 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") && (!optionalsOnly || targetProp.flags & SymbolFlags.Optional)) { + 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; + } + + 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 related = compareProperties(sourceProp, targetProp, isRelatedTo); + if (!related) { + return Ternary.False; + } + result &= related; + } + return result; + } + + function signaturesRelatedTo(source: Type, target: Type, kind: SignatureKind, reportErrors: boolean, intersectionState: IntersectionState): 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 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; + } + } + + 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. + Debug.assertEqual(sourceSignatures.length, targetSignatures.length); + for (let i = 0; i < targetSignatures.length; i++) { + const related = signatureRelatedTo(sourceSignatures[i], targetSignatures[i], /*erase*/ true, reportErrors, intersectionState, 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; + const sourceSignature = first(sourceSignatures); + const targetSignature = first(targetSignatures); + result = signatureRelatedTo(sourceSignature, targetSignature, eraseGenerics, reportErrors, intersectionState, 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, intersectionState, 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)); + } + return Ternary.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 + } + return false; + } + return true; + } + + 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)); + } + + 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, intersectionState: IntersectionState, incompatibleReporter: (source: Type, target: Type) => void): Ternary { + const checkMode = relation === subtypeRelation ? SignatureCheckMode.StrictTopSignature : + relation === strictSubtypeRelation ? SignatureCheckMode.StrictTopSignature | SignatureCheckMode.StrictArity : + SignatureCheckMode.None; + return compareSignaturesRelated(erase ? getErasedSignature(source) : source, erase ? getErasedSignature(target) : target, checkMode, reportErrors, reportError, incompatibleReporter, isRelatedToWorker, reportUnreliableMapper); + function isRelatedToWorker(source: Type, target: Type, reportErrors?: boolean) { + return isRelatedTo(source, target, RecursionFlags.Both, reportErrors, /*headMessage*/ undefined, intersectionState); + } + } + + 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; + } + result &= related; + } + return result; + } + + function membersRelatedToIndexInfo(source: Type, targetInfo: IndexInfo, reportErrors: boolean, intersectionState: IntersectionState): 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, /*headMessage*/ undefined, intersectionState); + if (!related) { + if (reportErrors) { + reportError(Diagnostics.Property_0_is_incompatible_with_index_signature, symbolToString(prop)); + } + return Ternary.False; + } + result &= related; + } + } + for (const info of getIndexInfosOfType(source)) { + if (isApplicableIndexType(info.keyType, keyType)) { + const related = indexInfoRelatedTo(info, targetInfo, reportErrors, intersectionState); + if (!related) { + return Ternary.False; + } + result &= related; + } + } + return result; + } + + function indexInfoRelatedTo(sourceInfo: IndexInfo, targetInfo: IndexInfo, reportErrors: boolean, intersectionState: IntersectionState) { + const related = isRelatedTo(sourceInfo.type, targetInfo.type, RecursionFlags.Both, reportErrors, /*headMessage*/ undefined, intersectionState); + 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 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 = relation !== strictSubtypeRelation && !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; + } + result &= related; + } + 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, intersectionState); + } + // Intersection constituents are never considered to have an inferred index signature. Also, in the strict subtype relation, + // only fresh object literals are considered to have inferred index signatures. This ensures { [x: string]: xxx } <: {} but + // not vice-versa. Without this rule, those types would be mutual strict subtypes. + if (!(intersectionState & IntersectionState.Source) && (relation !== strictSubtypeRelation || getObjectFlags(source) & ObjectFlags.FreshLiteral) && isObjectTypeWithInferableIndex(source)) { + return membersRelatedToIndexInfo(source, targetInfo, reportErrors, intersectionState); + } + if (reportErrors) { + reportError(Diagnostics.Index_signature_for_type_0_is_missing_in_type_1, typeToString(targetInfo.keyType), typeToString(source)); + } + return Ternary.False; + } + + 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; + } + + 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; + } + + // Only a public signature is assignable to public signature. + if (targetAccessibility !== ModifierFlags.Protected && !sourceAccessibility) { + return true; + } + + if (reportErrors) { + reportError(Diagnostics.Cannot_assign_a_0_constructor_type_to_a_1_constructor_type, visibilityToString(sourceAccessibility), visibilityToString(targetAccessibility)); + } + + return false; + } + } + + 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; + } + + if (type.flags & TypeFlags.UnionOrIntersection) { + return !!forEach((type as IntersectionType).types, typeCouldHaveTopLevelSingletonTypes); + } + + if (type.flags & TypeFlags.Instantiable) { + const constraint = getConstraintOfType(type); + if (constraint && constraint !== type) { + return typeCouldHaveTopLevelSingletonTypes(constraint); + } + } + + return isUnitType(type) || !!(type.flags & TypeFlags.TemplateLiteral) || !!(type.flags & TypeFlags.StringMapping); + } + + 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) || + findMatchingTypeReferenceOrTypeAliasReference(source, target) || + findBestTypeForObjectLiteral(source, target) || + findBestTypeForInvokable(source, target) || + findMostOverlappyType(source, target); + } + + function discriminateTypeByDiscriminableItems(target: UnionType, discriminators: (readonly [() => Type, __String])[], related: (source: Type, target: Type) => boolean | Ternary) { + const types = target.types; + const include: Ternary[] = types.map(t => t.flags & TypeFlags.Primitive ? Ternary.False : Ternary.True); + for (const [getDiscriminatingType, propertyName] of discriminators) { + // If the remaining target types include at least one with a matching discriminant, eliminate those that + // have non-matching discriminants. This ensures that we ignore erroneous discriminators and gradually + // refine the target set without eliminating every constituent (which would lead to `never`). + let matched = false; + for (let i = 0; i < types.length; i++) { + if (include[i]) { + const targetType = getTypeOfPropertyOrIndexSignatureOfType(types[i], propertyName); + if (targetType && related(getDiscriminatingType(), targetType)) { + matched = true; + } + else { + include[i] = Ternary.Maybe; + } + } + } + // Turn each Ternary.Maybe into Ternary.False if there was a match. Otherwise, revert to Ternary.True. + for (let i = 0; i < types.length; i++) { + if (include[i] === Ternary.Maybe) { + include[i] = matched ? Ternary.False : Ternary.True; + } + } + } + const filtered = contains(include, Ternary.False) ? getUnionType(types.filter((_, i) => include[i]), UnionReduction.None) : target; + return filtered.flags & TypeFlags.Never ? target : filtered; + } + + /** + * 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.Substitution) { + return isWeakType((type as SubstitutionType).baseType); + } + 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; + } + } + 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 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)) }); + const oldVarianceComputation = inVarianceComputation; + const saveResolutionStart = resolutionStart; + if (!inVarianceComputation) { + inVarianceComputation = true; + resolutionStart = resolutionTargets.length; + } + links.variances = emptyArray; + const variances = []; + for (const tp of typeParameters) { + const modifiers = getTypeParameterModifiers(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); + } + if (!oldVarianceComputation) { + inVarianceComputation = false; + resolutionStart = saveResolutionStart; + } + 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 isMarkerType(type: Type) { + return markerTypes.has(getTypeId(type)); + } + + function getTypeParameterModifiers(tp: TypeParameter): ModifierFlags { + return reduceLeft(tp.symbol?.declarations, (modifiers, d) => modifiers | getEffectiveModifierFlags(d), ModifierFlags.None) & (ModifierFlags.In | ModifierFlags.Out | ModifierFlags.Const); + } + + // 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; + } + + 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 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; + } + // 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) + ">"; + 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: Map, 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) { + // NOTE: cast to TransientSymbol should be safe because only TransientSymbols can have CheckFlags.Synthetic + for (const t of (prop as TransientSymbol).links.containingType!.types) { + const p = getPropertyOfType(t, prop.escapedName); + const result = p && forEachProperty(p, callback); + if (result) { + return result; + } + } + 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 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 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 the given stack contains + // maxDepth or more occurrences of types with the same recursion identity as the given type. The recursion identity + // provides a shared identity for type instantiations that repeat in some (possibly infinite) pattern. For example, + // in `type Deep = { next: Deep> }`, repeatedly referencing the `next` property leads to an infinite + // sequence of ever deeper instantiations with the same recursion identity (in this case the symbol associated with + // the object type literal). + // A homomorphic mapped type is considered deeply nested if its target type is deeply nested, and an intersection is + // considered deeply nested if any constituent of the intersection is deeply nested. + // It is possible, though highly unlikely, for the deeply nested check 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. + function isDeeplyNestedType(type: Type, stack: Type[], depth: number, maxDepth = 3): boolean { + if (depth >= maxDepth) { + if ((getObjectFlags(type) & ObjectFlags.InstantiatedMapped) === ObjectFlags.InstantiatedMapped) { + type = getMappedTargetWithSymbol(type); + } + if (type.flags & TypeFlags.Intersection) { + return some((type as IntersectionType).types, t => isDeeplyNestedType(t, stack, depth, maxDepth)); + } + const identity = getRecursionIdentity(type); + let count = 0; + let lastTypeId = 0; + for (let i = 0; i < depth; i++) { + const t = stack[i]; + if (hasMatchingRecursionIdentity(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; + } + } + } + return false; + } + + // Unwrap nested homomorphic mapped types and return the deepest target type that has a symbol. This better + // preserves unique type identities for mapped types applied to explicitly written object literals. For example + // in `Mapped<{ x: Mapped<{ x: Mapped<{ x: string }>}>}>`, each of the mapped type applications will have a + // unique recursion identity (that of their target object type literal) and thus avoid appearing deeply nested. + function getMappedTargetWithSymbol(type: Type) { + let target; + while ( + (getObjectFlags(type) & ObjectFlags.InstantiatedMapped) === ObjectFlags.InstantiatedMapped && + (target = getModifiersTypeFromMappedType(type as MappedType)) && + (target.symbol || target.flags & TypeFlags.Intersection && some((target as IntersectionType).types, t => !!t.symbol)) + ) { + type = target; + } + return type; + } + + function hasMatchingRecursionIdentity(type: Type, identity: object): boolean { + if ((getObjectFlags(type) & ObjectFlags.InstantiatedMapped) === ObjectFlags.InstantiatedMapped) { + type = getMappedTargetWithSymbol(type); + } + if (type.flags & TypeFlags.Intersection) { + return some((type as IntersectionType).types, t => hasMatchingRecursionIdentity(t, identity)); + } + return getRecursionIdentity(type) === identity; + } + + // 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 object types that have a symbol by that symbol (representing the origin of the type), but + // exclude the static side of a class since it shares its symbol with the instance side. + return type.symbol; + } + if (isTupleType(type)) { + return type.target; + } + } + if (type.flags & TypeFlags.TypeParameter) { + // We use the symbol of the type parameter such that all "fresh" instantiations of that type parameter + // have the same recursion identity. + return type.symbol; + } + if (type.flags & TypeFlags.IndexedAccess) { + // Identity is the leftmost object type in a chain of indexed accesses, eg, in A[P1][P2][P3] 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; + } + return type; + } + + 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; + } + } + else { + if ((sourceProp.flags & SymbolFlags.Optional) !== (targetProp.flags & SymbolFlags.Optional)) { + return Ternary.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; + } + } + 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)); + } + return result; + } + + 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; + } + + function getCombinedTypeFlags(types: Type[]): TypeFlags { + return reduceLeft(types, (flags, t) => flags | (t.flags & TypeFlags.Union ? getCombinedTypeFlags((t as UnionType).types) : t.flags), 0 as TypeFlags); + } + + 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)!; + } + + 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 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 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 isMutableArrayLikeType(type: Type): boolean { + // A type is mutable-array-like if it is a reference to the global Array type, or if it is not the + // any, undefined or null type and if it is assignable to Array + return isMutableArrayOrTuple(type) || !(type.flags & (TypeFlags.Any | TypeFlags.Nullable)) && isTypeAssignableTo(type, anyArrayType); + } + + 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) { + 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 { + let lengthType; + return isTupleType(type) || + !!getPropertyOfType(type, "0" as __String) || + isArrayLikeType(type) && !!(lengthType = getTypeOfPropertyOfType(type, "length" as __String)) && everyType(lengthType, t => !!(t.flags & TypeFlags.NumberLiteral)); + } + + function isArrayOrTupleLikeType(type: Type): boolean { + return isArrayLikeType(type) || isTupleLikeType(type); + } + + function getTupleElementType(type: Type, index: number) { + const propType = getTypeOfPropertyOfType(type, "" + index as __String); + if (propType) { + return propType; + } + if (everyType(type, isTupleType)) { + return getTupleElementTypeOutOfStartCount(type, index, compilerOptions.noUncheckedIndexedAccess ? undefinedType : undefined); + } + return undefined; + } + + function isNeitherUnitTypeNorNever(type: Type): boolean { + return !(type.flags & (TypeFlags.Unit | TypeFlags.Never)); + } + + function isUnitType(type: Type): boolean { + return !!(type.flags & TypeFlags.Unit); + } + + function isUnitLikeType(type: Type): boolean { + // Intersections that reduce to 'never' (e.g. 'T & null' where 'T extends {}') are not unit types. + const t = getBaseConstraintOrType(type); + // Scan intersections such that tagged literal types are considered unit types. + return t.flags & TypeFlags.Intersection ? some((t as IntersectionType).types, isUnitType) : isUnitType(t); + } + + function extractUnitType(type: Type) { + return type.flags & TypeFlags.Intersection ? find((type as IntersectionType).types, isUnitType) || type : 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 getBaseTypeOfLiteralType(type: Type): Type { + return type.flags & TypeFlags.EnumLike ? getBaseTypeOfEnumLikeType(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 getBaseTypeOfLiteralTypeUnion(type: UnionType) { + const key = `B${getTypeId(type)}`; + return getCachedType(key) ?? setCachedType(key, mapType(type, getBaseTypeOfLiteralType)); + } + + // This like getBaseTypeOfLiteralType, but instead treats enum literals as strings/numbers instead + // of returning their enum base type (which depends on the types of other literals in the enum). + function getBaseTypeOfLiteralTypeForComparison(type: Type): Type { + return type.flags & (TypeFlags.StringLiteral | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) ? stringType : + type.flags & (TypeFlags.NumberLiteral | TypeFlags.Enum) ? numberType : + type.flags & TypeFlags.BigIntLiteral ? bigintType : + type.flags & TypeFlags.BooleanLiteral ? booleanType : + type.flags & TypeFlags.Union ? mapType(type, getBaseTypeOfLiteralTypeForComparison) : + type; + } + + function getWidenedLiteralType(type: Type): Type { + return type.flags & TypeFlags.EnumLike && isFreshLiteralType(type) ? getBaseTypeOfEnumLikeType(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 getWidenedUniqueESSymbolType(type: Type): Type { + return type.flags & TypeFlags.UniqueESSymbol ? esSymbolType : + type.flags & TypeFlags.Union ? mapType(type as UnionType, getWidenedUniqueESSymbolType) : + type; + } + + function getWidenedLiteralLikeTypeForContextualType(type: Type, contextualType: Type | undefined) { + if (!isLiteralOfContextualType(type, contextualType)) { + type = getWidenedUniqueESSymbolType(getWidenedLiteralType(type)); + } + return getRegularTypeOfLiteralType(type); + } + + 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 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; + } + + /** + * 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 isGenericTupleType(type: Type): type is TupleTypeReference { + return isTupleType(type) && !!(type.target.combinedFlags & ElementFlags.Variadic); + } + + function isSingleElementGenericTupleType(type: Type): type is TupleTypeReference { + return isGenericTupleType(type) && type.target.elementFlags.length === 1; + } + + function getRestTypeOfTupleType(type: TupleTypeReference) { + return getElementTypeOfSliceOfTupleType(type, type.target.fixedLength); + } + + function getTupleElementTypeOutOfStartCount(type: Type, index: number, undefinedOrMissingType: Type | undefined) { + return mapType(type, t => { + const tupleType = t as TupleTypeReference; + const restType = getRestTypeOfTupleType(tupleType); + if (!restType) { + return undefinedType; + } + if (undefinedOrMissingType && index >= getTotalFixedElementCount(tupleType.target)) { + return getUnionType([restType, undefinedOrMissingType]); + } + return restType; + }); + } + + function getRestArrayTypeOfTupleType(type: TupleTypeReference) { + const restType = getRestTypeOfTupleType(type); + return restType && createArrayType(restType); + } + + function getElementTypeOfSliceOfTupleType(type: TupleTypeReference, index: number, endSkipCount = 0, writing = false, noReductions = 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, noReductions ? UnionReduction.None : UnionReduction.Literal); + } + return undefined; + } + + 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 isZeroBigInt({ value }: BigIntLiteralType) { + return value.base10Value === "0"; + } + + function removeDefinitelyFalsyTypes(type: Type): Type { + return filterType(type, t => hasTypeFacts(t, TypeFacts.Truthy)); + } + + function extractDefinitelyFalsyTypes(type: Type): Type { + return mapType(type, getDefinitelyFalsyPartOfType); + } + + 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; + } + + /** + * 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 getOptionalType(type: Type, isProperty = false): Type { + Debug.assert(strictNullChecks); + const missingOrUndefined = isProperty ? undefinedOrMissingType : undefinedType; + return type === missingOrUndefined || type.flags & TypeFlags.Union && (type as UnionType).types[0] === missingOrUndefined ? type : getUnionType([type, missingOrUndefined]); + } + + 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 getNonNullableType(type: Type): Type { + return strictNullChecks ? getAdjustedTypeWithFacts(type, TypeFacts.NEUndefinedOrNull) : type; + } + + function addOptionalTypeMarker(type: Type) { + return strictNullChecks ? getUnionType([type, optionalType]) : type; + } + + function removeOptionalTypeMarker(type: Type): Type { + return strictNullChecks ? removeType(type, optionalType) : type; + } + + function propagateOptionalTypeMarker(type: Type, node: OptionalChain, wasOptional: boolean) { + return wasOptional ? isOutermostOptionalChain(node) ? getOptionalType(type) : addOptionalTypeMarker(type) : type; + } + + function getOptionalExpressionType(exprType: Type, expression: Expression) { + return isExpressionOfOptionalChainRoot(expression) ? getNonNullableType(exprType) : + isOptionalChain(expression) ? removeOptionalTypeMarker(exprType) : + exprType; + } + + function removeMissingType(type: Type, isOptional: boolean) { + return exactOptionalPropertyTypes && isOptional ? removeType(type, missingType) : type; + } + + function containsMissingType(type: Type) { + return type === missingType || !!(type.flags & TypeFlags.Union) && (type as UnionType).types[0] === missingType; + } + + function removeMissingOrUndefinedType(type: Type): Type { + return exactOptionalPropertyTypes ? removeType(type, missingType) : getTypeWithFacts(type, TypeFacts.NEUndefined); + } + + /** + * 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); + } + + /** + * 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.links.type = type; + symbol.links.target = source; + if (source.valueDeclaration) { + symbol.valueDeclaration = source.valueDeclaration; + } + const nameType = getSymbolLinks(source).nameType; + if (nameType) { + symbol.links.nameType = nameType; + } + return symbol; + } + + 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; + } + + /** + * 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; + } + + 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); + }); + } + } + } + context.siblings = siblings; + } + return context.siblings; + } + + function getPropertiesOfContext(context: WideningContext): Symbol[] { + if (!context.resolvedProperties) { + const names = new Map<__String, Symbol>(); + 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; + } + + 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, undefinedOrMissingType); + 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)); + } + } + } + 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 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; + } + 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) { + errorReported ||= reportWideningErrorsInType(t); + } + } + } + else if (isArrayOrTupleType(type)) { + for (const t of getTypeArguments(type)) { + errorReported ||= reportWideningErrorsInType(t); + } + } + else if (isObjectLiteralType(type)) { + for (const p of getPropertiesOfObjectType(type)) { + const t = getTypeOfSymbol(p); + if (getObjectFlags(t) & ObjectFlags.ContainsWideningType) { + errorReported = reportWideningErrorsInType(t); + if (!errorReported) { + // we need to account for property types coming from object literal type normalization in unions + const valueDeclaration = p.declarations?.find(d => d.symbol.valueDeclaration?.parent === type.symbol.valueDeclaration); + if (valueDeclaration) { + error(valueDeclaration, Diagnostics.Object_literal_s_property_0_implicitly_has_an_1_type, symbolToString(p), typeToString(getWidenedType(t))); + errorReported = true; + } + } + } + } + } + } + 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)) { + const originalKeywordKind = identifierToKeywordKind(param.name); + if ( + (isCallSignatureDeclaration(param.parent) || isMethodSignature(param.parent) || isFunctionTypeNode(param.parent)) && + param.parent.parameters.includes(param) && + (resolveName(param, param.name.escapedText, SymbolFlags.Type, /*nameNotFoundMessage*/ undefined, /*isUse*/ true) || + originalKeywordKind && isTypeNodeKind(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); + return; + case SyntaxKind.JSDocSignature: + if (noImplicitAny && isJSDocOverloadTag(declaration.parent)) { + error(declaration.parent.tagName, Diagnostics.This_overload_implicitly_returns_the_type_0_because_it_lacks_a_return_type_annotation, 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); + } + else { + error(declaration, Diagnostics.Function_expression_which_lacks_return_type_annotation_implicitly_has_an_0_return_type, typeAsString); + } + return; + } + 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 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); + } + } + }); + } + + 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, /*readonly*/ isConstTypeVariable(targetRestType) && !someType(targetRestType, isMutableArrayLikeType)), targetRestType); + } + } + + function applyToReturnTypes(source: Signature, target: Signature, callback: (s: Type, t: Type) => void) { + const targetTypePredicate = getTypePredicateOfSignature(target); + if (targetTypePredicate) { + const sourceTypePredicate = getTypePredicateOfSignature(source); + if (sourceTypePredicate && typePredicateKindsMatch(sourceTypePredicate, targetTypePredicate) && sourceTypePredicate.type && targetTypePredicate.type) { + callback(sourceTypePredicate.type, targetTypePredicate.type); + return; + } + } + const targetReturnType = getReturnTypeOfSignature(target); + if (couldContainTypeVariables(targetReturnType)) { + callback(getReturnTypeOfSignature(source), targetReturnType); + } + } + + 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 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 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 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); + } + } + 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 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 getMapperFromContext(context: T): TypeMapper | T & undefined { + return context && context.mapper; + } + + // 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 || some(getTypeArguments(type as TypeReference), couldContainTypeVariables)) || + objectFlags & ObjectFlags.SingleSignatureType && !!length((type as SingleSignatureType).outerTypeParameters) || + 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 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; + } + + function isTypeParameterAtTopLevel(type: Type, tp: TypeParameter, depth = 0): boolean { + return !!(type === tp || + type.flags & TypeFlags.UnionOrIntersection && some((type as UnionOrIntersectionType).types, t => isTypeParameterAtTopLevel(t, tp, depth)) || + depth < 3 && type.flags & TypeFlags.Conditional && ( + isTypeParameterAtTopLevel(getTrueTypeFromConditionalType(type as ConditionalType), tp, depth + 1) || + isTypeParameterAtTopLevel(getFalseTypeFromConditionalType(type as ConditionalType), tp, depth + 1) + )); + } + + function isTypeParameterAtTopLevelInReturnType(signature: Signature, typeParameter: TypeParameter) { + const typePredicate = getTypePredicateOfSignature(signature); + return typePredicate ? !!typePredicate.type && isTypeParameterAtTopLevel(typePredicate.type, typeParameter) : + isTypeParameterAtTopLevel(getReturnTypeOfSignature(signature), typeParameter); + } + + /** 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.links.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(/*symbol*/ undefined, members, emptyArray, emptyArray, indexInfos); + } + + /** + * 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 { + const cacheKey = source.id + "," + target.id + "," + constraint.id; + if (reverseMappedCache.has(cacheKey)) { + return reverseMappedCache.get(cacheKey); + } + const type = createReverseMappedType(source, target, constraint); + reverseMappedCache.set(cacheKey, type); + return type; + } + + // We consider a type to be partially inferable if it isn't marked non-inferable or if it is + // an object literal type with at least one property of an inferable type. For example, an object + // literal { a: 123, b: x => true } is marked non-inferable because it contains a context sensitive + // arrow function, but is considered partially inferable because property 'a' has an inferable type. + function isPartiallyInferableType(type: Type): boolean { + return !(getObjectFlags(type) & ObjectFlags.NonInferrableType) || + isObjectLiteralType(type) && some(getPropertiesOfType(type), prop => isPartiallyInferableType(getTypeOfSymbol(prop))) || + isTupleType(type) && some(getElementTypes(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)) { + const elementType = inferReverseMappedType(getTypeArguments(source)[0], target, constraint); + if (!elementType) { + return undefined; + } + return createArrayType(elementType, isReadonlyArrayType(source)); + } + if (isTupleType(source)) { + const elementTypes = map(getElementTypes(source), t => inferReverseMappedType(t, target, constraint)); + if (!every(elementTypes, (t): t is Type => !!t)) { + return undefined; + } + 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): Type { + const links = getSymbolLinks(symbol); + if (!links.type) { + links.type = inferReverseMappedType(symbol.links.propertyType, symbol.links.mappedType, symbol.links.constraintType) || unknownType; + } + return links.type; + } + + function inferReverseMappedTypeWorker(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 inferReverseMappedType(source: Type, target: MappedType, constraint: IndexType): Type | undefined { + const cacheKey = source.id + "," + target.id + "," + constraint.id; + if (reverseMappedCache.has(cacheKey)) { + return reverseMappedCache.get(cacheKey) || unknownType; + } + reverseMappedSourceStack.push(source); + reverseMappedTargetStack.push(target); + const saveExpandingFlags = reverseExpandingFlags; + if (isDeeplyNestedType(source, reverseMappedSourceStack, reverseMappedSourceStack.length, 2)) reverseExpandingFlags |= ExpandingFlags.Source; + if (isDeeplyNestedType(target, reverseMappedTargetStack, reverseMappedTargetStack.length, 2)) reverseExpandingFlags |= ExpandingFlags.Target; + let type; + if (reverseExpandingFlags !== ExpandingFlags.Both) { + type = inferReverseMappedTypeWorker(source, target, constraint); + } + reverseMappedSourceStack.pop(); + reverseMappedTargetStack.pop(); + reverseExpandingFlags = saveExpandingFlags; + reverseMappedCache.set(cacheKey, type); + return 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; + } + 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; + } + } + } + } + } + } + + function getUnmatchedProperty(source: Type, target: Type, requireOptionalProperties: boolean, matchDiscriminantProperties: boolean): Symbol | undefined { + return firstOrUndefinedIterator(getUnmatchedProperties(source, target, requireOptionalProperties, matchDiscriminantProperties)); + } + + function tupleTypesDefinitelyUnrelated(source: TupleTypeReference, target: TupleTypeReference) { + return !(target.target.combinedFlags & ElementFlags.Variadic) && target.target.minLength > source.target.minLength || + !(target.target.combinedFlags & ElementFlags.Variable) && (!!(source.target.combinedFlags & ElementFlags.Variable) || 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 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 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); + } + + /** + * 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) { + return getBigIntLiteralType(parseValidBigInt(text)); + } + + function isMemberOfStringMapping(source: Type, target: Type): boolean { + if (target.flags & TypeFlags.Any) { + return true; + } + if (target.flags & (TypeFlags.String | 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; + } + + function isValidTypeForTemplateLiteralPlaceholder(source: Type, target: Type): boolean { + if (target.flags & TypeFlags.Intersection) { + return every((target as IntersectionType).types, t => t === emptyTypeLiteralType || isValidTypeForTemplateLiteralPlaceholder(source, t)); + } + if (target.flags & TypeFlags.String || isTypeAssignableTo(source, target)) { + 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) || + target.flags & TypeFlags.TemplateLiteral && isTypeMatchedByTemplateLiteralType(source, target as TemplateLiteralType)); + } + 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 false; + } + + 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, (s, i) => { + return isTypeAssignableTo(getBaseConstraintOrType(s), getBaseConstraintOrType(target.types[i])) ? s : getStringLikeTypeForType(s); + }) : + 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; + } + 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; + } + } + 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; + } + } + + /** + * @returns `true` if `type` has the shape `[T[0]]` where `T` is `typeParameter` + */ + function isTupleOfSelf(typeParameter: TypeParameter, type: Type) { + return isTupleType(type) && getTupleElementType(type, 0) === getIndexedAccessType(typeParameter, getNumberLiteralType(0)) && !getTypeOfPropertyOfType(type, "1" as __String); + } + + function inferTypes(inferences: InferenceInfo[], originalSource: Type, originalTarget: Type, priority = InferencePriority.None, contravariant = false) { + let bivariant = false; + let propagationType: Type; + let inferencePriority: number = InferencePriority.MaxValue; + let visited: Map; + let sourceStack: Type[]; + let targetStack: Type[]; + let expandingFlags = ExpandingFlags.None; + inferFromTypes(originalSource, originalTarget); + + function inferFromTypes(source: Type, target: Type): void { + if (!couldContainTypeVariables(target) || isNoInferType(target)) { + return; + } + if (source === wildcardType || source === blockedStringType) { + // 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, with defaults applied. + const params = getSymbolLinks(source.aliasSymbol).typeParameters!; + const minParams = getMinTypeArgumentCount(params); + const sourceTypes = fillMissingTypeArguments(source.aliasTypeArguments, params, minParams, isInJSFile(source.aliasSymbol.valueDeclaration)); + const targetTypes = fillMissingTypeArguments(target.aliasTypeArguments, params, minParams, isInJSFile(source.aliasSymbol.valueDeclaration)); + inferFromTypeArguments(sourceTypes, targetTypes!, 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 (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); + } + 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); + 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); + } + } + if (target.flags & (TypeFlags.IndexedAccess | TypeFlags.Substitution)) { + if (isNoInferType(target)) { + return; + } + 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; + } + 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) { + const candidate = propagationType || source; + if (candidate === blockedStringType) { + return; + } + if (inference.priority === undefined || priority < inference.priority) { + inference.candidates = undefined; + inference.contraCandidates = undefined; + inference.topLevel = true; + inference.priority = priority; + } + if (priority === inference.priority) { + // Inferring A to [A[0]] is a zero information inference (it guarantees A becomes its constraint), but oft arises from generic argument list inferences + // By discarding it early, we can allow more fruitful results to be used instead. + if (isTupleOfSelf(inference.typeParameter, candidate)) { + return; + } + // 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); + clearCachedInferences(inferences); + } + } + 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; + } + // 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 ( + 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 (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 as ConditionalType, 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 (target.flags & TypeFlags.TemplateLiteral) { + inferToTemplateLiteralType(source, target as TemplateLiteralType); + } + else { + source = getReducedType(source); + if (isGenericMappedType(source) && isGenericMappedType(target)) { + invokeOnce(source, target, inferFromGenericMappedTypes); + } + 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 && !(apparentSource.flags & (TypeFlags.Object | TypeFlags.Intersection))) { + 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 inferToMultipleTypesWithPriority(source: Type, targets: Type[], targetFlags: TypeFlags, newPriority: InferencePriority) { + const savePriority = priority; + priority |= newPriority; + inferToMultipleTypes(source, targets, targetFlags); + priority = savePriority; + } + + // Ensure an inference action is performed only once for the given source and target types. + // This includes two things: + // Avoiding inferring between the same pair of source and target types, + // and avoiding circularly inferring between source and target types. + // For an example of the last, consider if we are inferring between source type + // `type Deep = { next: Deep> }` and target type `type Loop = { next: Loop }`. + // We would then infer between the types of the `next` property: `Deep>` = `{ next: Deep>> }` and `Loop` = `{ next: Loop }`. + // We will then infer again between the types of the `next` property: + // `Deep>>` and `Loop`, and so on, such that we would be forever inferring + // between instantiations of the same types `Deep` and `Loop`. + // In particular, we would be inferring from increasingly deep instantiations of `Deep` to `Loop`, + // such that we would go on inferring forever, even though we would never infer + // between the same pair of types. + function invokeOnce(source: Source, target: Target, action: (source: Source, target: Target) => 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; + (sourceStack ??= []).push(source); + (targetStack ??= []).push(target); + if (isDeeplyNestedType(source, sourceStack, sourceStack.length, 2)) expandingFlags |= ExpandingFlags.Source; + if (isDeeplyNestedType(target, targetStack, targetStack.length, 2)) expandingFlags |= ExpandingFlags.Target; + if (expandingFlags !== ExpandingFlags.Both) { + action(source, target); + } + else { + inferencePriority = InferencePriority.Circularity; + } + targetStack.pop(); + sourceStack.pop(); + expandingFlags = saveExpandingFlags; + visited.set(key, inferencePriority); + inferencePriority = Math.min(inferencePriority, saveInferencePriority); + } + + 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 inferFromTypeArguments(sourceTypes: readonly Type[], targetTypes: readonly Type[], variances: readonly VarianceFlags[]) { + const count = sourceTypes.length < targetTypes.length ? sourceTypes.length : targetTypes.length; + for (let i = 0; i < count; i++) { + if (i < variances.length && (variances[i] & VarianceFlags.VarianceMask) === VarianceFlags.Contravariant) { + inferFromContravariantTypes(sourceTypes[i], targetTypes[i]); + } + else { + inferFromTypes(sourceTypes[i], targetTypes[i]); + } + } + } + + function inferFromContravariantTypes(source: Type, target: Type) { + 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; + } + + 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; + } + 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; + } + // 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 { + // 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); + } + } + } + // 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); + } + } + } + } + + function inferToMappedType(source: Type, target: MappedType, constraintType: Type): boolean { + if ((constraintType.flags & TypeFlags.Union) || (constraintType.flags & TypeFlags.Intersection)) { + let result = false; + for (const type of (constraintType as (UnionType | IntersectionType)).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, + ); + } + } + 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, /*indexFlags*/ !!source.pattern ? IndexFlags.NoIndexSignatures : IndexFlags.None), 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; + } + // 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; + } + + // 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); + } + } + } + + function inferFromGenericMappedTypes(source: MappedType, target: MappedType) { + // 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); + } + + 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)) { + inferFromGenericMappedTypes(source, target); + } + 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.combinedFlags & ElementFlags.Variable ? 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) { + if (elementFlags[startLength] & elementFlags[startLength + 1] & ElementFlags.Variadic) { + // 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 (elementFlags[startLength] & ElementFlags.Variadic && elementFlags[startLength + 1] & ElementFlags.Rest) { + // Middle of target is [...T, ...rest] and source is tuple type + // if T is constrained by a fixed-size tuple we might be able to use its arity to infer T + const param = getInferenceInfoForType(elementTypes[startLength])?.typeParameter; + const constraint = param && getBaseConstraintOfType(param); + if (constraint && isTupleType(constraint) && !(constraint.target.combinedFlags & ElementFlags.Variable)) { + const impliedArity = constraint.target.fixedLength; + inferFromTypes(sliceTupleType(source, startLength, sourceArity - (startLength + impliedArity)), elementTypes[startLength]); + inferFromTypes(getElementTypeOfSliceOfTupleType(source, startLength + impliedArity, endLength)!, elementTypes[startLength + 1]); + } + } + else if (elementFlags[startLength] & ElementFlags.Rest && elementFlags[startLength + 1] & ElementFlags.Variadic) { + // Middle of target is [...rest, ...T] and source is tuple type + // if T is constrained by a fixed-size tuple we might be able to use its arity to infer T + const param = getInferenceInfoForType(elementTypes[startLength + 1])?.typeParameter; + const constraint = param && getBaseConstraintOfType(param); + if (constraint && isTupleType(constraint) && !(constraint.target.combinedFlags & ElementFlags.Variable)) { + const impliedArity = constraint.target.fixedLength; + const endIndex = sourceArity - getEndElementCount(target.target, ElementFlags.Fixed); + const startIndex = endIndex - impliedArity; + const trailingSlice = createTupleType(getTypeArguments(source).slice(startIndex, endIndex), source.target.elementFlags.slice(startIndex, endIndex), /*readonly*/ false, source.target.labeledElementDeclarations && source.target.labeledElementDeclarations.slice(startIndex, endIndex)); + + inferFromTypes(getElementTypeOfSliceOfTupleType(source, startLength, endLength + impliedArity)!, elementTypes[startLength]); + inferFromTypes(trailingSlice, 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 = sliceTupleType(source, startLength, endLength); + 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 = getElementTypeOfSliceOfTupleType(source, startLength, endLength); + if (restType) { + inferFromTypes(restType, elementTypes[startLength]); + } + } + } + // Infer between ending fixed elements + for (let i = 0; i < endLength; i++) { + inferFromTypes(getTypeArguments(source)[sourceArity - i - 1], elementTypes[targetArity - i - 1]); + } + return; + } + if (isArrayType(target)) { + inferFromIndexTypes(source, target); + 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( + removeMissingType(getTypeOfSymbol(sourceProp), !!(sourceProp.flags & SymbolFlags.Optional)), + removeMissingType(getTypeOfSymbol(targetProp), !!(targetProp.flags & SymbolFlags.Optional)), + ); + } + } + } + + function inferFromSignatures(source: Type, target: Type, kind: SignatureKind) { + const sourceSignatures = getSignaturesOfType(source, kind); + const sourceLen = sourceSignatures.length; + if (sourceLen > 0) { + // We match source and target signatures from the bottom up, and if the source has fewer signatures + // than the target, we infer from the first source signature to the excess target signatures. + const targetSignatures = getSignaturesOfType(target, kind); + const targetLen = targetSignatures.length; + for (let i = 0; i < targetLen; i++) { + const sourceIndex = Math.max(sourceLen - targetLen + i, 0); + inferFromSignature(getBaseSignature(sourceSignatures[sourceIndex]), getErasedSignature(targetSignatures[i])); + } + } + } + + function inferFromSignature(source: Signature, target: Signature) { + if (!(source.flags & SignatureFlags.IsNonInferrable)) { + 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); + } + } + for (const info of getIndexInfosOfType(source)) { + if (isApplicableIndexType(info.keyType, targetInfo.keyType)) { + propTypes.push(info.type); + } + } + 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 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 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 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]); + } + } + return candidates; + } + + 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) || isConstTypeVariable(inference.typeParameter); + const widenLiteralTypes = !primitiveConstraint && inference.topLevel && + (inference.isFixed || !isTypeParameterAtTopLevelInReturnType(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; + let fallbackType: Type | undefined; + if (context.signature) { + const inferredCovariantType = inference.candidates ? getCovariantInference(inference, context.signature) : undefined; + const inferredContravariantType = inference.contraCandidates ? getContravariantInference(inference) : undefined; + if (inferredCovariantType || inferredContravariantType) { + // If we have both co- and contra-variant inferences, we prefer the co-variant inference if it is not 'never', + // all co-variant inferences are assignable to it (i.e. it isn't one of a conflicting set of candidates), it is + // assignable to some contra-variant inference, and no other type parameter is constrained to this type parameter + // and has inferences that would conflict. Otherwise, we prefer the contra-variant inference. + // Similarly ignore co-variant `any` inference when both are available as almost everything is assignable to it + // and it would spoil the overall inference. + const preferCovariantType = inferredCovariantType && (!inferredContravariantType || + !(inferredCovariantType.flags & (TypeFlags.Never | TypeFlags.Any)) && + some(inference.contraCandidates, t => isTypeAssignableTo(inferredCovariantType, t)) && + every(context.inferences, other => + other !== inference && getConstraintOfTypeParameter(other.typeParameter) !== inference.typeParameter || + every(other.candidates, t => isTypeAssignableTo(t, inferredCovariantType)))); + inferredType = preferCovariantType ? inferredCovariantType : inferredContravariantType; + fallbackType = preferCovariantType ? inferredContravariantType : 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)); + } + } + } + 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))) { + // If the fallback type satisfies the constraint, we pick it. Otherwise, we pick the constraint. + inference.inferredType = fallbackType && context.compareTypes(fallbackType, getTypeWithThisArgument(instantiatedConstraint, fallbackType)) ? fallbackType : instantiatedConstraint; + } + } + } + + 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 "Bun": + return compilerOptions.types + ? Diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_Bun_Try_npm_i_save_dev_types_Slashbun_and_then_add_bun_to_the_types_field_in_your_tsconfig + : Diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_Bun_Try_npm_i_save_dev_types_Slashbun; + 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, + SymbolFlags.Value | SymbolFlags.ExportValue, + getCannotFindNameDiagnosticForName(node), + !isWriteOnlyAccess(node), + /*excludeGlobals*/ false, + ) || unknownSymbol; + } + return links.resolvedSymbol; + } + + function isInAmbientOrTypeNode(node: Node): boolean { + return !!(node.flags & NodeFlags.Ambient || findAncestor(node, n => isInterfaceDeclaration(n) || isTypeAliasDeclaration(n) || isTypeLiteralNode(n))); + } + + // 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}`; + } + if (isElementAccessExpression(node) && isIdentifier(node.argumentExpression)) { + const symbol = getResolvedSymbol(node.argumentExpression); + if (isConstantVariable(symbol) || isParameterOrMutableLocalVariable(symbol) && !isSymbolAssigned(symbol)) { + const key = getFlowCacheKey((node as AccessExpression).expression, declaredType, initialType, flowContainer); + return key && `${key}.@${getSymbolId(symbol)}`; + } + } + 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 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) || + (isVariableDeclaration(target) || isBindingElement(target)) && + getExportSymbolOfValueSymbolIfExported(getResolvedSymbol(source as Identifier)) === getSymbolOfDeclaration(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); + if (sourcePropertyName !== undefined) { + const targetPropertyName = isAccessExpression(target) ? getAccessedPropertyName(target) : undefined; + if (targetPropertyName !== undefined) { + return targetPropertyName === sourcePropertyName && isMatchingReference((source as AccessExpression).expression, (target as AccessExpression).expression); + } + } + if (isElementAccessExpression(source) && isElementAccessExpression(target) && isIdentifier(source.argumentExpression) && isIdentifier(target.argumentExpression)) { + const symbol = getResolvedSymbol(source.argumentExpression); + if (symbol === getResolvedSymbol(target.argumentExpression) && (isConstantVariable(symbol) || isParameterOrMutableLocalVariable(symbol) && !isSymbolAssigned(symbol))) { + return isMatchingReference(source.expression, target.expression); + } + } + break; + 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; + } + + 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 tryGetNameFromType(type: Type) { + return type.flags & TypeFlags.UniqueESSymbol ? (type as UniqueESSymbolType).escapedName : + type.flags & TypeFlags.StringOrNumberLiteral ? escapeLeadingUnderscores("" + (type as StringLiteralType | NumberLiteralType).value) : undefined; + } + + function tryGetElementAccessExpressionName(node: ElementAccessExpression) { + return isStringOrNumericLiteralLike(node.argumentExpression) ? escapeLeadingUnderscores(node.argumentExpression.text) : + isEntityNameExpression(node.argumentExpression) ? tryGetNameFromEntityNameExpression(node.argumentExpression) : undefined; + } + + function tryGetNameFromEntityNameExpression(node: EntityNameOrEntityNameExpression) { + const symbol = resolveEntityName(node, SymbolFlags.Value, /*ignoreErrors*/ true); + if (!symbol || !(isConstantVariable(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; + } + } + if (hasOnlyExpressionInitializer(declaration) && isBlockScopedNameDeclaredBeforeUse(declaration, node)) { + const initializer = getEffectiveInitializer(declaration); + if (initializer) { + const initializerType = isBindingPattern(declaration.parent) ? getTypeForBindingElement(declaration as BindingElement) : getTypeOfExpression(initializer); + return initializerType && tryGetNameFromType(initializerType); + } + if (isEnumMember(declaration)) { + return getTextOfPropertyName(declaration.name); + } + } + return undefined; + } + + function containsMatchingReference(source: Node, target: Node) { + while (isAccessExpression(source)) { + source = source.expression; + if (isMatchingReference(source, target)) { + return true; + } + } + return false; + } + + function optionalChainContainsReference(source: Node, target: Node) { + while (isOptionalChain(source)) { + source = source.expression; + if (isMatchingReference(source, 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) { + // NOTE: cast to TransientSymbol should be safe because only TransientSymbols can have CheckFlags.SyntheticProperty + if ((prop as TransientSymbol).links.isDiscriminantProperty === undefined) { + (prop as TransientSymbol).links.isDiscriminantProperty = ((prop as TransientSymbol).links.checkFlags & CheckFlags.Discriminant) === CheckFlags.Discriminant && + !isGenericType(getTypeOfSymbol(prop)); + } + return !!(prop as TransientSymbol).links.isDiscriminantProperty; + } + } + return false; + } + + 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; + } + + // 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; + } + + // 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; + } + + // 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 hasMatchingArgument(expression: CallExpression | NewExpression, reference: Node) { + if (expression.arguments) { + for (const argument of expression.arguments) { + if (isOrContainsMatchingReference(reference, argument) || optionalChainContainsReference(argument, reference)) { + return true; + } + } + } + if ( + expression.expression.kind === SyntaxKind.PropertyAccessExpression && + isOrContainsMatchingReference(reference, (expression.expression as PropertyAccessExpression).expression) + ) { + return true; + } + return false; + } + + function getFlowNodeId(flow: FlowNode): number { + if (flow.id <= 0) { + flow.id = nextFlowId; + nextFlowId++; + } + return flow.id; + } + + 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 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; + } + const key = `A${getTypeId(declaredType)},${getTypeId(assignedType)}`; + return getCachedType(key) ?? setCachedType(key, getAssignmentReducedTypeWorker(declaredType, assignedType)); + } + + 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 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 getTypeFacts(type: Type, mask: TypeFacts): TypeFacts { + return getTypeFactsWorker(type, mask) & mask; + } + + function hasTypeFacts(type: Type, mask: TypeFacts): boolean { + return getTypeFacts(type, mask) !== 0; + } + + function getTypeFactsWorker(type: Type, callerOnlyNeeds: TypeFacts): 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) { + const possibleFacts = strictNullChecks + ? TypeFacts.EmptyObjectStrictFacts | TypeFacts.FunctionStrictFacts | TypeFacts.ObjectStrictFacts + : TypeFacts.EmptyObjectFacts | TypeFacts.FunctionFacts | TypeFacts.ObjectFacts; + + if ((callerOnlyNeeds & possibleFacts) === 0) { + // If the caller doesn't care about any of the facts that we could possibly produce, + // return zero so we can skip resolving members. + return 0; + } + + 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 | getTypeFactsWorker(t, callerOnlyNeeds), TypeFacts.None); + } + if (flags & TypeFlags.Intersection) { + return getIntersectionTypeFacts(type as IntersectionType, callerOnlyNeeds); + } + return TypeFacts.UnknownFacts; + } + + function getIntersectionTypeFacts(type: IntersectionType, callerOnlyNeeds: TypeFacts): 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 = getTypeFactsWorker(t, callerOnlyNeeds); + oredFacts |= f; + andedFacts &= f; + } + } + return oredFacts & TypeFacts.OrFactsMask | andedFacts & TypeFacts.AndFactsMask; + } + + function getTypeWithFacts(type: Type, include: TypeFacts) { + return filterType(type, t => hasTypeFacts(t, include)); + } + + // 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 removeNullableByIntersection(reduced, TypeFacts.EQUndefined, TypeFacts.EQNull, TypeFacts.IsNull, nullType); + case TypeFacts.NENull: + return removeNullableByIntersection(reduced, TypeFacts.EQNull, TypeFacts.EQUndefined, TypeFacts.IsUndefined, undefinedType); + case TypeFacts.NEUndefinedOrNull: + case TypeFacts.Truthy: + return mapType(reduced, t => hasTypeFacts(t, TypeFacts.EQUndefinedOrNull) ? getGlobalNonNullableTypeInstantiation(t) : t); + } + } + return reduced; + } + + function removeNullableByIntersection(type: Type, targetFacts: TypeFacts, otherFacts: TypeFacts, otherIncludesFacts: TypeFacts, otherType: Type) { + const facts = getTypeFacts(type, TypeFacts.EQUndefined | TypeFacts.EQNull | TypeFacts.IsUndefined | TypeFacts.IsNull); + // Simply return the type if it never compares equal to the target nullable. + if (!(facts & targetFacts)) { + return type; + } + // By default we intersect with a union of {} and the opposite nullable. + const emptyAndOtherUnion = getUnionType([emptyObjectType, otherType]); + // For each constituent type that can compare equal to the target nullable, intersect with the above union + // if the type doesn't already include the opppsite nullable and the constituent can compare equal to the + // opposite nullable; otherwise, just intersect with {}. + return mapType(type, t => hasTypeFacts(t, targetFacts) ? getIntersectionType([t, !(facts & otherIncludesFacts) && hasTypeFacts(t, otherFacts) ? emptyAndOtherUnion : emptyObjectType]) : t); + } + + 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, missingType]) : + 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; + 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 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 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 getInitialTypeOfVariableDeclaration(node: VariableDeclaration) { + if (node.initializer) { + return getTypeOfInitializer(node.initializer); + } + if (node.parent.parent.kind === SyntaxKind.ForInStatement) { + return stringType; + } + if (node.parent.parent.kind === SyntaxKind.ForOfStatement) { + return checkRightHandSideOfForOf(node.parent.parent) || errorType; + } + return errorType; + } + + function getInitialType(node: VariableDeclaration | BindingElement) { + return node.kind === SyntaxKind.VariableDeclaration ? + getInitialTypeOfVariableDeclaration(node) : + getInitialTypeOfBindingElement(node); + } + + 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 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 getTypeOfSwitchClause(clause: CaseClause | DefaultClause) { + if (clause.kind === SyntaxKind.CaseClause) { + return getRegularTypeOfLiteralType(getTypeOfExpression(clause.expression)); + } + return neverType; + } + + 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; + } + + // 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 eachTypeContainedIn(source: Type, types: Type[]) { + return source.flags & TypeFlags.Union ? !forEach((source as UnionType).types, t => !contains(types, t)) : contains(types, source); + } + + function isTypeSubsetOf(source: Type, target: Type) { + return !!(source === target || source.flags & TypeFlags.Never || target.flags & TypeFlags.Union && isTypeSubsetOfUnion(source, target as UnionType)); + } + + 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.EnumLike && getBaseTypeOfEnumLikeType(source as LiteralType) === target) { + return true; + } + return containsType(target.types, source); + } + + function forEachType(type: Type, f: (t: Type) => T | undefined): T | undefined { + return type.flags & TypeFlags.Union ? forEach((type as UnionType).types, f) : f(type); + } + + 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; + } + 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); + } + } + // filtering could remove intersections so `ContainsIntersections` might be forwarded "incorrectly" + // it is purely an optimization hint so there is no harm in accidentally forwarding it + return getUnionTypeFromSortedList(filtered, (type as UnionType).objectFlags & (ObjectFlags.PrimitiveUnion | ObjectFlags.ContainsIntersections), /*aliasSymbol*/ undefined, /*aliasTypeArguments*/ undefined, newOrigin); + } + return type.flags & TypeFlags.Never || f(type) ? type : neverType; + } + + 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; + } + + // 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; + } + + function isIncomplete(flowType: FlowType) { + return flowType.flags === 0; + } + + function getTypeFromFlowType(flowType: FlowType) { + return flowType.flags === 0 ? flowType.type : flowType as 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; + } + + 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])); + } + + 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)); + } + + 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 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 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 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).links.syntheticOrigin; + if (origin && getExplicitTypeOfSymbol(origin)) { + 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))); + } + } + } + } + + // 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); + } + return prop && getExplicitTypeOfSymbol(prop, diagnostic); + } + return undefined; + } + case SyntaxKind.ParenthesizedExpression: + return getTypeOfDottedName((node as ParenthesizedExpression).expression, diagnostic); + } + } + } + + function getEffectsSignature(node: CallExpression | InstanceofExpression) { + 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 (isBinaryExpression(node)) { + const rightType = checkNonNullExpression(node.right); + funcType = getSymbolHasInstanceMethodOfObjectType(rightType); + } + else 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; + } + + 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 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 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; + } + 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) { + 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).antecedent, f => isReachableFlowNodeWorker(f, /*noCacheCheck*/ false)); + } + else if (flags & FlowFlags.LoopLabel) { + const antecedents = (flow as FlowLabel).antecedent; + if (antecedents === undefined || antecedents.length === 0) { + 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. + const data = (flow as FlowSwitchClause).node; + if (data.clauseStart === data.clauseEnd && isExhaustiveSwitchStatement(data.switchStatement)) { + return false; + } + 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).node.target; + const saveAntecedents = target.antecedent; + target.antecedent = (flow as FlowReduceLabel).node.antecedents; + const result = isReachableFlowNodeWorker((flow as FlowReduceLabel).antecedent, /*noCacheCheck*/ false); + target.antecedent = 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).antecedent, 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).antecedent![0]; + } + else if (flags & FlowFlags.ReduceLabel) { + const target = (flow as FlowReduceLabel).node.target; + const saveAntecedents = target.antecedent; + target.antecedent = (flow as FlowReduceLabel).node.antecedents; + const result = isPostSuperFlowNode((flow as FlowReduceLabel).antecedent, /*noCacheCheck*/ false); + target.antecedent = 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.ThisKeyword: + return true; + case SyntaxKind.Identifier: + if (!isThisInTypeQuery(node)) { + const symbol = getResolvedSymbol(node as Identifier); + return isConstantVariable(symbol) + || isParameterOrMutableLocalVariable(symbol) && !isSymbolAssigned(symbol) + || !!symbol.valueDeclaration && isFunctionExpression(symbol.valueDeclaration); + } + break; + 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); + case SyntaxKind.ObjectBindingPattern: + case SyntaxKind.ArrayBindingPattern: + const rootDeclaration = getRootDeclaration(node.parent); + return isParameter(rootDeclaration) || isCatchClauseVariableDeclaration(rootDeclaration) + ? !isSomeSymbolAssigned(rootDeclaration) + : isVariableDeclaration(rootDeclaration) && isVarConstLike(rootDeclaration); + } + return false; + } + + function getFlowTypeOfReference(reference: Node, declaredType: Type, initialType = declaredType, flowContainer?: Node, flowNode = tryCast(reference, canHaveFlowNode)?.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; + } + return resultType; + + function getOrSetCacheKey() { + if (isKeySet) { + return key; + } + 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; + } + 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).antecedent!.length === 1) { + flow = (flow as FlowLabel).antecedent![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).node.target; + const saveAntecedents = target.antecedent; + target.antecedent = (flow as FlowReduceLabel).node.antecedents; + type = getTypeAtFlowNode((flow as FlowReduceLabel).antecedent); + target.antecedent = 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 && container.kind !== SyntaxKind.ArrowFunction) + ) { + 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++; + } + flowDepth--; + return type; + } + } + + 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; + } + const t = isInCompoundLikeAssignment(node) ? getBaseTypeOfLiteralType(declaredType) : declaredType; + if (t.flags & TypeFlags.Union) { + return getAssignmentReducedType(t as UnionType, getInitialOrAssignedType(flow)); + } + return t; + } + // 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) || isVarConstLike(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) || optionalChainContainsReference(node.parent.parent.expression, reference)) + ) { + 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; + } + 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)]); + } + } + 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; + } + + 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); + } + } + return evolvedType === type ? flowType : createFlowType(evolvedType, isIncomplete(flowType)); + } + return flowType; + } + } + return undefined; + } + + 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 = skipParentheses(flow.node.switchStatement.expression); + const flowType = getTypeAtFlowNode(flow.antecedent); + let type = getTypeFromFlowType(flowType); + if (isMatchingReference(reference, expr)) { + type = narrowTypeBySwitchOnDiscriminant(type, flow.node); + } + else if (expr.kind === SyntaxKind.TypeOfExpression && isMatchingReference(reference, (expr as TypeOfExpression).expression)) { + type = narrowTypeBySwitchOnTypeOf(type, flow.node); + } + else if (expr.kind === SyntaxKind.TrueKeyword) { + type = narrowTypeBySwitchOnTrue(type, flow.node); + } + else { + if (strictNullChecks) { + if (optionalChainContainsReference(expr, reference)) { + type = narrowTypeBySwitchOptionalChainContainment(type, flow.node, t => !(t.flags & (TypeFlags.Undefined | TypeFlags.Never))); + } + else if (expr.kind === SyntaxKind.TypeOfExpression && optionalChainContainsReference((expr as TypeOfExpression).expression, reference)) { + type = narrowTypeBySwitchOptionalChainContainment(type, flow.node, 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.node); + } + } + 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.antecedent!) { + if (!bypassFlow && antecedent.flags & FlowFlags.SwitchClause && (antecedent as FlowSwitchClause).node.clauseStart === (antecedent as FlowSwitchClause).node.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 (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. + if (!isTypeSubsetOf(type, initialType)) { + subtypeReduction = true; + } + if (isIncomplete(flowType)) { + 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 (!(type.flags & TypeFlags.Never) && !contains(antecedentTypes, type) && !isExhaustiveSwitchStatement(bypassFlow.node.switchStatement)) { + if (type === declaredType && declaredType === initialType) { + return type; + } + antecedentTypes.push(type); + if (!isTypeSubsetOf(type, initialType)) { + subtypeReduction = true; + } + if (isIncomplete(flowType)) { + seenIncomplete = true; + } + } + } + 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; + } + // 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.antecedent!) { + 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, initialType)) { + 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; + } + } + // 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; + } + + 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 (isIdentifier(expr)) { + const symbol = getResolvedSymbol(expr); + if (isConstantVariable(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; + } + + function getDiscriminantPropertyAccess(expr: Expression, computedType: Type) { + // As long as the computed type is a subset of the declared type, we use the full declared type to detect + // a discriminant property. In cases where the computed type isn't a subset, e.g because of a preceding type + // predicate narrowing, we use the actual computed type. + if (declaredType.flags & TypeFlags.Union || computedType.flags & TypeFlags.Union) { + const access = getCandidateDiscriminantPropertyAccess(expr); + if (access) { + const name = getAccessedPropertyName(access); + if (name) { + const type = declaredType.flags & TypeFlags.Union && isTypeSubsetOf(computedType, declaredType) ? declaredType : computedType; + if (isDiscriminantProperty(type, name)) { + return access; + } + } + } + } + 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 optionalChain = isOptionalChain(access); + const removeNullable = strictNullChecks && (optionalChain || isNonNullAccess(access)) && maybeTypeOfKind(type, TypeFlags.Nullable); + let propType = getTypeOfPropertyOfType(removeNullable ? getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull) : type, propName); + if (!propType) { + return type; + } + propType = removeNullable && optionalChain ? getOptionalType(propType) : propType; + const narrowedPropType = narrowType(propType); + return filterType(type, t => { + const discriminantType = getTypeOfPropertyOrIndexSignatureOfType(t, propName) || unknownType; + return !(discriminantType.flags & TypeFlags.Never) && !(narrowedPropType.flags & TypeFlags.Never) && areTypesComparable(narrowedPropType, discriminantType); + }); + } + + 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)); + } + + function narrowTypeBySwitchOnDiscriminantProperty(type: Type, access: AccessExpression | BindingElement | ParameterDeclaration, data: FlowSwitchClauseData) { + if (data.clauseStart < data.clauseEnd && type.flags & TypeFlags.Union && getKeyPropertyName(type as UnionType) === getAccessedPropertyName(access)) { + const clauseTypes = getSwitchClauseTypes(data.switchStatement).slice(data.clauseStart, data.clauseEnd); + const candidate = getUnionType(map(clauseTypes, t => getConstituentTypeForKeyType(type as UnionType, t) || unknownType)); + if (candidate !== unknownType) { + return candidate; + } + } + return narrowTypeByDiscriminant(type, access, t => narrowTypeBySwitchOnDiscriminant(t, data)); + } + + 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 isTypePresencePossible(type: Type, propName: __String, assumeTrue: boolean) { + const prop = getPropertyOfType(type, propName); + return prop ? + !!(prop.flags & SymbolFlags.Optional || getCheckFlags(prop) & CheckFlags.Partial) || 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; + } + + function narrowTypeByBooleanComparison(type: Type, expr: Expression, bool: BooleanLiteral, operator: BinaryOperator, assumeTrue: boolean): Type { + assumeTrue = (assumeTrue !== (bool.kind === SyntaxKind.TrueKeyword)) !== (operator !== SyntaxKind.ExclamationEqualsEqualsToken && operator !== SyntaxKind.ExclamationEqualsToken); + return narrowType(type, expr, 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); + } + 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); + } + if (isMatchingConstructorReference(left)) { + return narrowTypeByConstructor(type, operator, right, assumeTrue); + } + if (isMatchingConstructorReference(right)) { + return narrowTypeByConstructor(type, operator, left, assumeTrue); + } + if (isBooleanLiteral(right) && !isAccessExpression(left)) { + return narrowTypeByBooleanComparison(type, left, right, operator, assumeTrue); + } + if (isBooleanLiteral(left) && !isAccessExpression(right)) { + return narrowTypeByBooleanComparison(type, right, left, operator, assumeTrue); + } + break; + case SyntaxKind.InstanceOfKeyword: + return narrowTypeByInstanceof(type, expr as InstanceofExpression, assumeTrue); + case SyntaxKind.InKeyword: + if (isPrivateIdentifier(expr.left)) { + return narrowTypeByPrivateIdentifierInInExpression(type, expr, assumeTrue); + } + const target = getReferenceCandidate(expr.right); + if (containsMissingType(type) && isAccessExpression(reference) && isMatchingReference(reference.expression, target)) { + const leftType = getTypeOfExpression(expr.left); + if (isTypeUsableAsPropertyName(leftType) && getAccessedPropertyName(reference) === getPropertyNameFromType(leftType)) { + return getTypeWithFacts(type, assumeTrue ? TypeFacts.NEUndefined : TypeFacts.EQUndefined); + } + } + if (isMatchingReference(reference, target)) { + const leftType = getTypeOfExpression(expr.left); + if (isTypeUsableAsPropertyName(leftType)) { + return narrowTypeByInKeyword(type, leftType, 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; + } + + 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) { + 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; + } + 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))); + } + 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)) { + if (strictNullChecks && optionalChainContainsReference(target, reference) && assumeTrue === (literal.text !== "undefined")) { + type = getAdjustedTypeWithFacts(type, TypeFacts.NEUndefinedOrNull); + } + const propertyAccess = getDiscriminantPropertyAccess(target, type); + if (propertyAccess) { + return narrowTypeByDiscriminant(type, propertyAccess, t => narrowTypeByLiteralExpression(t, literal, assumeTrue)); + } + return type; + } + return narrowTypeByLiteralExpression(type, literal, assumeTrue); + } + + function narrowTypeByLiteralExpression(type: Type, literal: LiteralExpression, assumeTrue: boolean) { + return assumeTrue ? + narrowTypeByTypeName(type, literal.text) : + getAdjustedTypeWithFacts(type, typeofNEFacts.get(literal.text) || TypeFacts.TypeofNEHostObject); + } + + function narrowTypeBySwitchOptionalChainContainment(type: Type, { switchStatement, clauseStart, clauseEnd }: FlowSwitchClauseData, 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, clauseStart, clauseEnd }: FlowSwitchClauseData) { + // 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, t.flags & TypeFlags.Undefined ? undefinedType : 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) ? hasTypeFacts(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. + hasTypeFacts(t, facts) ? getIntersectionType([t, impliedType]) : + neverType); + } + + function narrowTypeBySwitchOnTypeOf(type: Type, { switchStatement, clauseStart, clauseEnd }: FlowSwitchClauseData): 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 narrowTypeBySwitchOnTrue(type: Type, { switchStatement, clauseStart, clauseEnd }: FlowSwitchClauseData): Type { + const defaultIndex = findIndex(switchStatement.caseBlock.clauses, clause => clause.kind === SyntaxKind.DefaultClause); + const hasDefaultClause = clauseStart === clauseEnd || (defaultIndex >= clauseStart && defaultIndex < clauseEnd); + + // First, narrow away all of the cases that preceded this set of cases. + for (let i = 0; i < clauseStart; i++) { + const clause = switchStatement.caseBlock.clauses[i]; + if (clause.kind === SyntaxKind.CaseClause) { + type = narrowType(type, clause.expression, /*assumeTrue*/ false); + } + } + + // If our current set has a default, then none the other cases were hit either. + // There's no point in narrowing by the the other cases in the set, since we can + // get here through other paths. + if (hasDefaultClause) { + for (let i = clauseEnd; i < switchStatement.caseBlock.clauses.length; i++) { + const clause = switchStatement.caseBlock.clauses[i]; + if (clause.kind === SyntaxKind.CaseClause) { + type = narrowType(type, clause.expression, /*assumeTrue*/ false); + } + } + return type; + } + + // Now, narrow based on the cases in this set. + const clauses = switchStatement.caseBlock.clauses.slice(clauseStart, clauseEnd); + return getUnionType(map(clauses, clause => clause.kind === SyntaxKind.CaseClause ? narrowType(type, clause.expression, /*assumeTrue*/ true) : 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 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; + } + + // 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; + } + + // 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; + } + + // 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; + } + + // 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; + } + + // 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); + } + } + + function narrowTypeByInstanceof(type: Type, expr: InstanceofExpression, 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; + } + const right = expr.right; + const rightType = getTypeOfExpression(right); + if (!isTypeDerivedFrom(rightType, globalObjectType)) { + return type; + } + + // if the right-hand side has an object type with a custom `[Symbol.hasInstance]` method, and that method + // has a type predicate, use the type predicate to perform narrowing. This allows normal `object` types to + // participate in `instanceof`, as per Step 2 of https://tc39.es/ecma262/#sec-instanceofoperator. + const signature = getEffectsSignature(expr); + const predicate = signature && getTypePredicateOfSignature(signature); + if (predicate && predicate.kind === TypePredicateKind.Identifier && predicate.parameterIndex === 0) { + return getNarrowedType(type, predicate.type, assumeTrue, /*checkDerived*/ true); + } + if (!isTypeDerivedFrom(rightType, globalFunctionType)) { + return type; + } + const instanceType = mapType(rightType, getInstanceType); + // Don't narrow from `any` if the target type is exactly `Object` or `Function`, and narrow + // in the false branch only if the target is a non-empty object type. + if ( + isTypeAny(type) && (instanceType === globalObjectType || instanceType === globalFunctionType) || + !assumeTrue && !(instanceType.flags & TypeFlags.Object && !isEmptyAnonymousObjectType(instanceType)) + ) { + return type; + } + return getNarrowedType(type, instanceType, assumeTrue, /*checkDerived*/ true); + } + + function getInstanceType(constructorType: Type) { + const prototypePropertyType = getTypeOfPropertyOfType(constructorType, "prototype" as __String); + if (prototypePropertyType && !isTypeAny(prototypePropertyType)) { + return prototypePropertyType; + } + const constructSignatures = getSignaturesOfType(constructorType, SignatureKind.Construct); + if (constructSignatures.length) { + return getUnionType(map(constructSignatures, signature => getReturnTypeOfSignature(getErasedSignature(signature)))); + } + // We use the empty object type to indicate we don't know the type of objects created by + // this constructor function. + return emptyObjectType; + } + + function getNarrowedType(type: Type, candidate: Type, assumeTrue: boolean, checkDerived: boolean): Type { + 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)); + } + + function getNarrowedTypeWorker(type: Type, candidate: Type, assumeTrue: boolean, checkDerived: boolean) { + if (!assumeTrue) { + if (type === candidate) { + return neverType; + } + if (checkDerived) { + return filterType(type, t => !isTypeDerivedFrom(t, candidate)); + } + const trueType = getNarrowedType(type, candidate, /*assumeTrue*/ true, /*checkDerived*/ false); + return filterType(type, t => !isTypeSubsetOf(t, trueType)); + } + if (type.flags & TypeFlags.AnyOrUnknown) { + return candidate; + } + if (type === candidate) { + return candidate; + } + + // We first attempt to filter the current type, narrowing constituents as appropriate and removing + // constituents that are unrelated to the candidate. + const isRelated = checkDerived ? isTypeDerivedFrom : isTypeSubtypeOf; + 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 => isTypeStrictSubtypeOf(t, c) ? t : isTypeStrictSubtypeOf(c, t) ? c : isTypeSubtypeOf(t, c) ? t : isTypeSubtypeOf(c, t) ? c : 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); + } + } + } + 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 && optionalChainContainsReference(predicateArgument, reference) && + ( + assumeTrue && !(hasTypeFacts(predicate.type, TypeFacts.EQUndefined)) || + !assumeTrue && everyType(predicate.type, isNullableType) + ) + ) { + 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; + } + + // 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.operatorToken.kind === SyntaxKind.QuestionQuestionEqualsToken) && 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 (isConstantVariable(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; + } + } + + function getTypeOfSymbolAtLocation(symbol: Symbol, location: Node) { + symbol = getExportSymbolOfValueSymbolIfExported(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 = removeOptionalTypeMarker( + isWriteAccess(location) && location.kind === SyntaxKind.PropertyAccessExpression ? + checkPropertyAccessExpression(location as PropertyAccessExpression, /*checkMode*/ undefined, /*writeOnly*/ true) : + getTypeOfExpression(location as Expression), + ); + if (getExportSymbolOfValueSymbolIfExported(getNodeLinks(location).resolvedSymbol) === symbol) { + return type; + } + } + } + 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 isRightSideOfAccessExpression(location) && isWriteAccess(location.parent) ? getWriteTypeOfSymbol(symbol) : getNonMissingTypeOfSymbol(symbol); + } + + 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) { + return !isPastLastAssignment(symbol, /*location*/ undefined); + } + + // Return true if there are no assignments to the given symbol or if the given location + // is past the last assignment to the symbol. + function isPastLastAssignment(symbol: Symbol, location: Node | undefined) { + const parent = findAncestor(symbol.valueDeclaration, isFunctionOrSourceFile); + if (!parent) { + return false; + } + const links = getNodeLinks(parent); + if (!(links.flags & NodeCheckFlags.AssignmentsMarked)) { + links.flags |= NodeCheckFlags.AssignmentsMarked; + if (!hasParentWithAssignmentsMarked(parent)) { + markNodeAssignments(parent); + } + } + return !symbol.lastAssignmentPos || location && symbol.lastAssignmentPos < location.pos; + } + + // Check if a parameter or catch variable (or their bindings elements) is assigned anywhere + function isSomeSymbolAssigned(rootDeclaration: Node) { + Debug.assert(isVariableDeclaration(rootDeclaration) || isParameter(rootDeclaration)); + return isSomeSymbolAssignedWorker(rootDeclaration.name); + } + + function isSomeSymbolAssignedWorker(node: BindingName): boolean { + if (node.kind === SyntaxKind.Identifier) { + return isSymbolAssigned(getSymbolOfDeclaration(node.parent as Declaration)); + } + + return some(node.elements, e => e.kind !== SyntaxKind.OmittedExpression && isSomeSymbolAssignedWorker(e.name)); + } + + function hasParentWithAssignmentsMarked(node: Node) { + return !!findAncestor(node.parent, node => isFunctionOrSourceFile(node) && !!(getNodeLinks(node).flags & NodeCheckFlags.AssignmentsMarked)); + } + + function isFunctionOrSourceFile(node: Node) { + return isFunctionLikeDeclaration(node) || isSourceFile(node); + } + + // For all assignments within the given root node, record the last assignment source position for all + // referenced parameters and mutable local variables. When assignments occur in nested functions or + // references occur in export specifiers, record Number.MAX_VALUE as the assignment position. When + // assignments occur in compound statements, record the ending source position of the compound statement + // as the assignment position (this is more conservative than full control flow analysis, but requires + // only a single walk over the AST). + function markNodeAssignments(node: Node) { + switch (node.kind) { + case SyntaxKind.Identifier: + if (isAssignmentTarget(node)) { + const symbol = getResolvedSymbol(node as Identifier); + if (isParameterOrMutableLocalVariable(symbol) && symbol.lastAssignmentPos !== Number.MAX_VALUE) { + const referencingFunction = findAncestor(node, isFunctionOrSourceFile); + const declaringFunction = findAncestor(symbol.valueDeclaration, isFunctionOrSourceFile); + symbol.lastAssignmentPos = referencingFunction === declaringFunction ? extendAssignmentPosition(node, symbol.valueDeclaration!) : Number.MAX_VALUE; + } + } + return; + case SyntaxKind.ExportSpecifier: + const exportDeclaration = (node as ExportSpecifier).parent.parent; + const name = (node as ExportSpecifier).propertyName || (node as ExportSpecifier).name; + if (!(node as ExportSpecifier).isTypeOnly && !exportDeclaration.isTypeOnly && !exportDeclaration.moduleSpecifier && name.kind !== SyntaxKind.StringLiteral) { + const symbol = resolveEntityName(name, SymbolFlags.Value, /*ignoreErrors*/ true, /*dontResolveAlias*/ true); + if (symbol && isParameterOrMutableLocalVariable(symbol)) { + symbol.lastAssignmentPos = Number.MAX_VALUE; + } + } + return; + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.EnumDeclaration: + return; + } + if (isTypeNode(node)) { + return; + } + forEachChild(node, markNodeAssignments); + } + + // Extend the position of the given assignment target node to the end of any intervening variable statement, + // expression statement, compound statement, or class declaration occurring between the node and the given + // declaration node. + function extendAssignmentPosition(node: Node, declaration: Declaration) { + let pos = node.pos; + while (node && node.pos > declaration.pos) { + switch (node.kind) { + case SyntaxKind.VariableStatement: + case SyntaxKind.ExpressionStatement: + case SyntaxKind.IfStatement: + case SyntaxKind.DoStatement: + case SyntaxKind.WhileStatement: + case SyntaxKind.ForStatement: + case SyntaxKind.ForInStatement: + case SyntaxKind.ForOfStatement: + case SyntaxKind.WithStatement: + case SyntaxKind.SwitchStatement: + case SyntaxKind.TryStatement: + case SyntaxKind.ClassDeclaration: + pos = node.end; + } + node = node.parent; + } + return pos; + } + + function isConstantVariable(symbol: Symbol) { + return symbol.flags & SymbolFlags.Variable && (getDeclarationNodeFlagsFromSymbol(symbol) & NodeFlags.Constant) !== 0; + } + + function isParameterOrMutableLocalVariable(symbol: Symbol) { + // Return true if symbol is a parameter, a catch clause variable, or a mutable local variable + const declaration = symbol.valueDeclaration && getRootDeclaration(symbol.valueDeclaration); + return !!declaration && ( + isParameter(declaration) || + isVariableDeclaration(declaration) && (isCatchClause(declaration.parent) || isMutableLocalVariableDeclaration(declaration)) + ); + } + + function isMutableLocalVariableDeclaration(declaration: VariableDeclaration) { + // Return true if symbol is a non-exported and non-global `let` variable + return !!(declaration.parent.flags & NodeFlags.Let) && !( + getCombinedModifierFlags(declaration) & ModifierFlags.Export || + declaration.parent.parent.kind === SyntaxKind.VariableStatement && isGlobalSourceFile(declaration.parent.parent.parent) + ); + } + + function parameterInitializerContainsUndefined(declaration: ParameterDeclaration): boolean { + const links = getNodeLinks(declaration); + + if (links.parameterInitializerContainsUndefined === undefined) { + if (!pushTypeResolution(declaration, TypeSystemPropertyName.ParameterInitializerContainsUndefined)) { + reportCircularityError(declaration.symbol); + return true; + } + + const containsUndefined = !!(hasTypeFacts(checkDeclarationInitializer(declaration, CheckMode.Normal), TypeFacts.IsUndefined)); + + if (!popTypeResolution()) { + reportCircularityError(declaration.symbol); + return true; + } + + links.parameterInitializerContainsUndefined ??= containsUndefined; + } + + return links.parameterInitializerContainsUndefined; + } + + /** 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 { + const removeUndefined = strictNullChecks && + declaration.kind === SyntaxKind.Parameter && + declaration.initializer && + hasTypeFacts(declaredType, TypeFacts.IsUndefined) && + !parameterInitializerContainsUndefined(declaration); + + return removeUndefined ? getTypeWithFacts(declaredType, TypeFacts.NEUndefined) : 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.NewExpression && (parent as NewExpression).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) { + if (isNoInferType(type)) { + type = (type as SubstitutionType).baseType; + } + // 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; + } + return false; + }); + } + + /** + * This function marks all the imports the given location refers to as `.referenced` in `NodeLinks` (transitively through local import aliases). + * (This corresponds to not getting elided in JS emit.) + * It can be called on *most* nodes in the AST with `ReferenceHint.Unspecified` and will filter its inputs, but care should be taken to avoid calling it on the RHS of an `import =` or specifiers in a `import {} from "..."`, + * unless you *really* want to *definitely* mark those as referenced. + * These shouldn't be directly marked, and should only get marked transitively by the internals of this function. + * + * @param location The location to mark js import refernces for + * @param hint The kind of reference `location` has already been checked to be + * @param propSymbol The optional symbol of the property we're looking up - this is used for property accesses when `const enum`s do not count as references (no `isolatedModules`, no `preserveConstEnums` + export). It will be calculated if not provided. + * @param parentType The optional type of the parent of the LHS of the property access - this will be recalculated if not provided (but is costly). + */ + function markLinkedReferences(location: PropertyAccessExpression | QualifiedName, hint: ReferenceHint.Property, propSymbol: Symbol | undefined, parentType: Type): void; + function markLinkedReferences(location: Identifier, hint: ReferenceHint.Identifier): void; + function markLinkedReferences(location: ExportAssignment, hint: ReferenceHint.ExportAssignment): void; + function markLinkedReferences(location: JsxOpeningLikeElement | JsxOpeningFragment, hint: ReferenceHint.Jsx): void; + function markLinkedReferences(location: FunctionLikeDeclaration | MethodSignature, hint: ReferenceHint.AsyncFunction): void; + function markLinkedReferences(location: ImportEqualsDeclaration, hint: ReferenceHint.ExportImportEquals): void; + function markLinkedReferences(location: ExportSpecifier, hint: ReferenceHint.ExportSpecifier): void; + function markLinkedReferences(location: HasDecorators, hint: ReferenceHint.Decorator): void; + function markLinkedReferences(location: Node, hint: ReferenceHint.Unspecified, propSymbol?: Symbol, parentType?: Type): void; + function markLinkedReferences(location: Node, hint: ReferenceHint, propSymbol?: Symbol, parentType?: Type) { + if (!canCollectSymbolAliasAccessabilityData) { + return; + } + if (location.flags & NodeFlags.Ambient) { + return; // References within types and declaration files are never going to contribute to retaining a JS import + } + switch (hint) { + case ReferenceHint.Identifier: + return markIdentifierAliasReferenced(location as Identifier); + case ReferenceHint.Property: + return markPropertyAliasReferenced(location as PropertyAccessExpression | QualifiedName, propSymbol, parentType); + case ReferenceHint.ExportAssignment: + return markExportAssignmentAliasReferenced(location as ExportAssignment); + case ReferenceHint.Jsx: + return markJsxAliasReferenced(location as JsxOpeningLikeElement | JsxOpeningFragment); + case ReferenceHint.AsyncFunction: + return markAsyncFunctionAliasReferenced(location as FunctionLikeDeclaration | MethodSignature); + case ReferenceHint.ExportImportEquals: + return markImportEqualsAliasReferenced(location as ImportEqualsDeclaration); + case ReferenceHint.ExportSpecifier: + return markExportSpecifierAliasReferenced(location as ExportSpecifier); + case ReferenceHint.Decorator: + return markDecoratorAliasReferenced(location as HasDecorators); + case ReferenceHint.Unspecified: { + // Identifiers in expression contexts are emitted, so we need to follow their referenced aliases and mark them as used + // Some non-expression identifiers are also treated as expression identifiers for this purpose, eg, `a` in `b = {a}` or `q` in `import r = q` + // This is the exception, rather than the rule - most non-expression identifiers are declaration names. + if (isIdentifier(location) && (isExpressionNode(location) || isShorthandPropertyAssignment(location.parent) || (isImportEqualsDeclaration(location.parent) && location.parent.moduleReference === location)) && shouldMarkIdentifierAliasReferenced(location)) { + if (isPropertyAccessOrQualifiedName(location.parent)) { + const left = isPropertyAccessExpression(location.parent) ? location.parent.expression : location.parent.left; + if (left !== location) return; // Only mark the LHS (the RHS is a property lookup) + } + markIdentifierAliasReferenced(location); + return; + } + if (isPropertyAccessOrQualifiedName(location)) { + let topProp: Node | undefined = location; + while (isPropertyAccessOrQualifiedName(topProp)) { + if (isPartOfTypeNode(topProp)) return; + topProp = topProp.parent; + } + return markPropertyAliasReferenced(location); + } + if (isExportAssignment(location)) { + return markExportAssignmentAliasReferenced(location); + } + if (isJsxOpeningLikeElement(location) || isJsxOpeningFragment(location)) { + return markJsxAliasReferenced(location); + } + if (isImportEqualsDeclaration(location)) { + if (isInternalModuleImportEqualsDeclaration(location) || checkExternalImportOrExportDeclaration(location)) { + return markImportEqualsAliasReferenced(location); + } + return; + } + if (isExportSpecifier(location)) { + return markExportSpecifierAliasReferenced(location); + } + if (isFunctionLikeDeclaration(location) || isMethodSignature(location)) { + markAsyncFunctionAliasReferenced(location); + // Might be decorated, fall through to decorator final case + } + if (!compilerOptions.emitDecoratorMetadata) { + return; + } + if (!canHaveDecorators(location) || !hasDecorators(location) || !location.modifiers || !nodeCanBeDecorated(legacyDecorators, location, location.parent, location.parent.parent)) { + return; + } + + return markDecoratorAliasReferenced(location); + } + default: + Debug.assertNever(hint, `Unhandled reference hint: ${hint}`); + } + } + + function markIdentifierAliasReferenced(location: Identifier) { + const symbol = getResolvedSymbol(location); + if (symbol && symbol !== argumentsSymbol && symbol !== unknownSymbol && !isThisInTypeQuery(location)) { + markAliasReferenced(symbol, location); + } + } + + function markPropertyAliasReferenced(location: PropertyAccessExpression | QualifiedName, propSymbol?: Symbol, parentType?: Type) { + const left = isPropertyAccessExpression(location) ? location.expression : location.left; + if (isThisIdentifier(left) || !isIdentifier(left)) { + return; + } + const parentSymbol = getResolvedSymbol(left); + if (!parentSymbol || parentSymbol === unknownSymbol) { + return; + } + // 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`. + // + // The property lookup is deferred as much as possible, in as many situations as possible, to avoid alias marking + // pulling on types/symbols it doesn't strictly need to. + if (getIsolatedModules(compilerOptions) || (shouldPreserveConstEnums(compilerOptions) && isExportOrExportExpression(location))) { + markAliasReferenced(parentSymbol, location); + return; + } + // Hereafter, this relies on type checking - but every check prior to this only used symbol information + const leftType = parentType || checkExpressionCached(left); + if (isTypeAny(leftType) || leftType === silentNeverType) { + markAliasReferenced(parentSymbol, location); + return; + } + let prop = propSymbol; + if (!prop && !parentType) { + const right = isPropertyAccessExpression(location) ? location.name : location.right; + const lexicallyScopedSymbol = isPrivateIdentifier(right) && lookupSymbolForPrivateIdentifierDeclaration(right.escapedText, right); + const assignmentKind = getAssignmentTargetKind(location); + const apparentType = getApparentType(assignmentKind !== AssignmentKind.None || isMethodAccessForCall(location) ? getWidenedType(leftType) : leftType); + prop = isPrivateIdentifier(right) ? lexicallyScopedSymbol && getPrivateIdentifierPropertyOfType(apparentType, lexicallyScopedSymbol) || undefined : getPropertyOfType(apparentType, right.escapedText); + } + if ( + !(prop && (isConstEnumOrConstEnumOnlyModule(prop) || prop.flags & SymbolFlags.EnumMember && location.parent.kind === SyntaxKind.EnumMember)) + ) { + markAliasReferenced(parentSymbol, location); + } + return; + } + + function markExportAssignmentAliasReferenced(location: ExportAssignment) { + if (isIdentifier(location.expression)) { + const id = location.expression; + const sym = getExportSymbolOfValueSymbolIfExported(resolveEntityName(id, SymbolFlags.All, /*ignoreErrors*/ true, /*dontResolveAlias*/ true, location)); + if (sym) { + markAliasReferenced(sym, id); + } + } + } + + function markJsxAliasReferenced(node: JsxOpeningLikeElement | JsxOpeningFragment) { + 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 = isJsxOpeningLikeElement(node) ? node.tagName : node; + + // allow null as jsxFragmentFactory + let jsxFactorySym: Symbol | undefined; + if (!(isJsxOpeningFragment(node) && jsxFactoryNamespace === "null")) { + jsxFactorySym = resolveName(jsxFactoryLocation, jsxFactoryNamespace, SymbolFlags.Value, jsxFactoryRefErr, /*isUse*/ true); + } + + 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; + + // If react/jsxFactory symbol is alias, mark it as refereced + if (canCollectSymbolAliasAccessabilityData && jsxFactorySym.flags & SymbolFlags.Alias && !getTypeOnlyAliasDeclaration(jsxFactorySym)) { + markAliasSymbolAsReferenced(jsxFactorySym); + } + } + + // 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, /*isUse*/ true); + } + } + } + return; + } + + function markAsyncFunctionAliasReferenced(location: FunctionLikeDeclaration | MethodSignature) { + if (languageVersion < ScriptTarget.ES2015) { + if (getFunctionFlags(location) & FunctionFlags.Async) { + const returnTypeNode = getEffectiveReturnTypeNode(location); + markTypeNodeAsReferenced(returnTypeNode); + } + } + } + + function markImportEqualsAliasReferenced(location: ImportEqualsDeclaration) { + if (hasSyntacticModifier(location, ModifierFlags.Export)) { + markExportAsReferenced(location); + } + } + + function markExportSpecifierAliasReferenced(location: ExportSpecifier) { + if (!location.parent.parent.moduleSpecifier && !location.isTypeOnly && !location.parent.parent.isTypeOnly) { + const exportedName = location.propertyName || location.name; + if (exportedName.kind === SyntaxKind.StringLiteral) { + return; // Skip for invalid syntax like this: export { "x" } + } + const symbol = resolveName(exportedName, exportedName.escapedText, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias, /*nameNotFoundMessage*/ undefined, /*isUse*/ true); + if (symbol && (symbol === undefinedSymbol || symbol === globalThisSymbol || symbol.declarations && isGlobalSourceFile(getDeclarationContainer(symbol.declarations[0])))) { + // Do nothing, non-local symbol + } + else { + const target = symbol && (symbol.flags & SymbolFlags.Alias ? resolveAlias(symbol) : symbol); + if (!target || getSymbolFlags(target) & SymbolFlags.Value) { + markExportAsReferenced(location); // marks export as used + markIdentifierAliasReferenced(exportedName); // marks target of export as used + } + } + return; + } + } + + function markDecoratorAliasReferenced(node: HasDecorators) { + if (compilerOptions.emitDecoratorMetadata) { + const firstDecorator = find(node.modifiers, isDecorator); + if (!firstDecorator) { + return; + } + + checkExternalEmitHelpers(firstDecorator, ExternalEmitHelpers.Metadata); + + // 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; + + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + const otherKind = node.kind === SyntaxKind.GetAccessor ? SyntaxKind.SetAccessor : SyntaxKind.GetAccessor; + const otherAccessor = getDeclarationOfKind(getSymbolOfDeclaration(node), otherKind); + markDecoratorMedataDataTypeNodeAsReferenced(getAnnotatedAccessorTypeNode(node) || otherAccessor && getAnnotatedAccessorTypeNode(otherAccessor)); + break; + case SyntaxKind.MethodDeclaration: + for (const parameter of node.parameters) { + markDecoratorMedataDataTypeNodeAsReferenced(getParameterTypeNodeForDecoratorCheck(parameter)); + } + + markDecoratorMedataDataTypeNodeAsReferenced(getEffectiveReturnTypeNode(node)); + break; + + case SyntaxKind.PropertyDeclaration: + markDecoratorMedataDataTypeNodeAsReferenced(getEffectiveTypeAnnotationNode(node)); + break; + + case SyntaxKind.Parameter: + markDecoratorMedataDataTypeNodeAsReferenced(getParameterTypeNodeForDecoratorCheck(node)); + const containingSignature = node.parent; + for (const parameter of containingSignature.parameters) { + markDecoratorMedataDataTypeNodeAsReferenced(getParameterTypeNodeForDecoratorCheck(parameter)); + } + markDecoratorMedataDataTypeNodeAsReferenced(getEffectiveReturnTypeNode(containingSignature)); + break; + } + } + } + + function markAliasReferenced(symbol: Symbol, location: Node) { + if (!canCollectSymbolAliasAccessabilityData) { + return; + } + if (isNonLocalAlias(symbol, /*excludes*/ SymbolFlags.Value) && !isInTypeQuery(location)) { + const target = resolveAlias(symbol); + if (getSymbolFlags(symbol, /*excludeTypeOnlyMeanings*/ true) & (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 ( + getIsolatedModules(compilerOptions) || + shouldPreserveConstEnums(compilerOptions) && isExportOrExportExpression(location) || + !isConstEnumOrConstEnumOnlyModule(getExportSymbolOfValueSymbolIfExported(target)) + ) { + 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) { + Debug.assert(canCollectSymbolAliasAccessabilityData); + 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 (getSymbolFlags(resolveSymbol(symbol)) & SymbolFlags.Value) { + // import foo = + const left = getFirstIdentifier(node.moduleReference as EntityNameExpression); + markIdentifierAliasReferenced(left); + } + } + } + } + + function markExportAsReferenced(node: ImportEqualsDeclaration | ExportSpecifier) { + const symbol = getSymbolOfDeclaration(node); + const target = resolveAlias(symbol); + if (target) { + const markAlias = target === unknownSymbol || + ((getSymbolFlags(symbol, /*excludeTypeOnlyMeanings*/ true) & SymbolFlags.Value) && !isConstEnumOrConstEnumOnlyModule(target)); + + if (markAlias) { + markAliasSymbolAsReferenced(symbol); + } + } + } + + 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, /*isUse*/ true); + if (rootSymbol && rootSymbol.flags & SymbolFlags.Alias) { + if ( + canCollectSymbolAliasAccessabilityData + && symbolIsValue(rootSymbol) + && !isConstEnumOrConstEnumOnlyModule(resolveAlias(rootSymbol)) + && !getTypeOnlyAliasDeclaration(rootSymbol) + ) { + markAliasSymbolAsReferenced(rootSymbol); + } + else if ( + forDecoratorMetadata + && getIsolatedModules(compilerOptions) + && 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))); + } + } + } + } + + /** + * 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 | undefined) { + markEntityNameOrEntityExpressionAsReference(node && getEntityNameFromTypeNode(node), /*forDecoratorMetadata*/ false); + } + + /** + * 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 getNarrowedTypeOfSymbol(symbol: Symbol, location: Identifier, checkMode?: CheckMode) { + const type = getTypeOfSymbol(symbol, checkMode); + 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; + const rootDeclaration = getRootDeclaration(parent); + if (rootDeclaration.kind === SyntaxKind.VariableDeclaration && getCombinedNodeFlagsCached(rootDeclaration) & NodeFlags.Constant || rootDeclaration.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 && !(rootDeclaration.kind === SyntaxKind.Parameter && isSomeSymbolAssigned(rootDeclaration))) { + const pattern = declaration.parent; + const narrowedType = getFlowTypeOfReference(pattern, parentTypeConstraint, parentTypeConstraint, /*flowContainer*/ undefined, location.flowNode); + if (narrowedType.flags & TypeFlags.Never) { + return neverType; + } + // Destructurings are validated against the parent type elsewhere. Here we disable tuple bounds + // checks because the narrowed type may have lower arity than the full parent type. For example, + // for the declaration [x, y]: [1, 2] | [3], we may have narrowed the parent type to just [3]. + return getBindingElementTypeFromParentType(declaration, narrowedType, /*noTupleBoundsCheck*/ true); + } + } + } + } + // 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(instantiateType(getTypeOfSymbol(contextualSignature.parameters[0]), getInferenceContext(func)?.nonFixingMapper)); + if (restType.flags & TypeFlags.Union && everyType(restType, isTupleType) && !some(func.parameters, isSomeSymbolAssigned)) { + 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 type; + } + + /** + * This part of `checkIdentifier` is kept seperate from the rest, so `NodeCheckFlags` (and related diagnostics) can be lazily calculated + * without calculating the flow type of the identifier. + */ + function checkIdentifierCalculateNodeCheckFlags(node: Identifier, symbol: Symbol) { + if (isThisInTypeQuery(node)) return; + + // 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; + } + + let container = getContainingFunction(node); + if (container) { + if (languageVersion < ScriptTarget.ES2015) { + if (container.kind === SyntaxKind.ArrowFunction) { + error(node, Diagnostics.The_arguments_object_cannot_be_referenced_in_an_arrow_function_in_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_ES5_Consider_using_a_standard_function_or_method); + } + } + + getNodeLinks(container).flags |= NodeCheckFlags.CaptureArguments; + while (container && isArrowFunction(container)) { + container = getContainingFunction(container); + if (container) { + getNodeLinks(container).flags |= NodeCheckFlags.CaptureArguments; + } + } + } + return; + } + + const localOrExportSymbol = getExportSymbolOfValueSymbolIfExported(symbol); + const targetSymbol = resolveAliasWithDeprecationCheck(localOrExportSymbol, node); + if (isDeprecatedSymbol(targetSymbol) && isUncalledFunctionReference(node, targetSymbol) && targetSymbol.declarations) { + addDeprecatedSuggestion(node, targetSymbol.declarations, node.escapedText as string); + } + + const declaration = localOrExportSymbol.valueDeclaration; + if (declaration && localOrExportSymbol.flags & SymbolFlags.Class) { + // When we downlevel classes we may emit some code outside of the class body. Due to the fact the + // class name is double-bound, we must ensure we mark references to the class name so that we can + // emit an alias to the class later. + if (isClassLike(declaration) && declaration.name !== node) { + let container = getThisContainer(node, /*includeArrowFunctions*/ false, /*includeClassComputedPropertyName*/ false); + while (container.kind !== SyntaxKind.SourceFile && container.parent !== declaration) { + container = getThisContainer(container, /*includeArrowFunctions*/ false, /*includeClassComputedPropertyName*/ false); + } + + if (container.kind !== SyntaxKind.SourceFile) { + getNodeLinks(declaration).flags |= NodeCheckFlags.ContainsConstructorReference; + getNodeLinks(container).flags |= NodeCheckFlags.ContainsConstructorReference; + getNodeLinks(node).flags |= NodeCheckFlags.ConstructorReference; + } + } + } + + checkNestedBlockScopedBinding(node, symbol); + } + + function checkIdentifier(node: Identifier, checkMode: CheckMode | undefined): Type { + if (isThisInTypeQuery(node)) { + return checkThisExpression(node); + } + + const symbol = getResolvedSymbol(node); + if (symbol === unknownSymbol) { + return errorType; + } + + checkIdentifierCalculateNodeCheckFlags(node, symbol); + + if (symbol === argumentsSymbol) { + if (isInPropertyInitializerOrClassStaticBlock(node)) { + return errorType; + } + return getTypeOfSymbol(symbol); + } + + if (shouldMarkIdentifierAliasReferenced(node)) { + markLinkedReferences(node, ReferenceHint.Identifier); + } + + const localOrExportSymbol = getExportSymbolOfValueSymbolIfExported(symbol); + let declaration = localOrExportSymbol.valueDeclaration; + + let type = getNarrowedTypeOfSymbol(localOrExportSymbol, node, checkMode); + 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; + + 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; + } + } + + 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 isInCompoundLikeAssignment(node) ? getBaseTypeOfLiteralType(type) : type; + } + } + else if (isAlias) { + declaration = getDeclarationOfAliasSymbol(symbol); + } + else { + return type; + } + + if (!declaration) { + return type; + } + + 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; + const typeIsAutomatic = type === autoType || type === autoArrayType; + const isAutomaticTypeInNonNull = typeIsAutomatic && node.parent.kind === SyntaxKind.NonNullExpression; + // When the control flow originates in a function expression, arrow function, method, or accessor, and + // we are referencing a closed-over const variable or parameter or mutable local variable past its last + // assignment, we extend the origin of the control flow analysis to include the immediately enclosing + // control flow container. + while ( + flowContainer !== declarationContainer && ( + flowContainer.kind === SyntaxKind.FunctionExpression || + flowContainer.kind === SyntaxKind.ArrowFunction || + isObjectLiteralOrClassExpressionMethodOrAccessor(flowContainer) + ) && ( + isConstantVariable(localOrExportSymbol) && type !== autoArrayType || + isParameterOrMutableLocalVariable(localOrExportSymbol) && isPastLastAssignment(localOrExportSymbol, node) + ) + ) { + 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) || isInAmbientOrTypeNode(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 = isAutomaticTypeInNonNull ? undefinedType : + assumeInitialized ? (isParameter ? removeOptionalityFromDeclaredType(type, declaration as VariableLikeDeclaration) : type) : + typeIsAutomatic ? undefinedType : getOptionalType(type); + const flowType = isAutomaticTypeInNonNull ? getNonNullableType(getFlowTypeOfReference(node, type, initialType, flowContainer)) : + 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 + return type; + } + return assignmentKind ? getBaseTypeOfLiteralType(flowType) : flowType; + } + + function isSameScopedBindingElement(node: Identifier, declaration: Declaration) { + if (isBindingElement(declaration)) { + const bindingElement = findAncestor(node, isBindingElement); + return bindingElement && getRootDeclaration(bindingElement) === getRootDeclaration(declaration); + } + } + + 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 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; + } + + // 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; + } + } + + // 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; + } + } + + // set 'declared inside loop' bit on the block-scoped binding + getNodeLinks(symbol.valueDeclaration).flags |= NodeCheckFlags.BlockScopedBindingInLoop; + } + + if (isCaptured) { + getNodeLinks(symbol.valueDeclaration).flags |= NodeCheckFlags.CapturedBlockScopedBinding; + } + } + + function isBindingCapturedByNode(node: Node, decl: VariableDeclaration | BindingElement) { + const links = getNodeLinks(node); + return !!links && contains(links.capturedBlockScopeBindings, getSymbolOfDeclaration(decl)); + } + + 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 (!isAssigned) { + return false; + } + + // 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 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: ClassLikeDeclaration): boolean { + const classSymbol = getSymbolOfDeclaration(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); + + // 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 (canHaveFlowNode(node) && node.flowNode && !isPostSuperFlowNode(node.flowNode, /*noCacheCheck*/ false)) { + error(node, diagnosticMessage); + } + } + } + + function checkThisInStaticClassFieldInitializerInDecoratedClass(thisExpression: Node, container: Node) { + if ( + isPropertyDeclaration(container) && hasStaticModifier(container) && legacyDecorators && + 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, /*includeClassComputedPropertyName*/ true); + let capturedByArrowFunction = false; + let thisInComputedPropertyName = false; + + if (container.kind === SyntaxKind.Constructor) { + checkThisBeforeSuper(node, container, Diagnostics.super_must_be_called_before_accessing_this_in_the_constructor_of_a_derived_class); + } + + while (true) { + // Now skip arrow functions to get the "real" owner of 'this'. + if (container.kind === SyntaxKind.ArrowFunction) { + container = getThisContainer(container, /*includeArrowFunctions*/ false, !thisInComputedPropertyName); + capturedByArrowFunction = true; + } + + if (container.kind === SyntaxKind.ComputedPropertyName) { + container = getThisContainer(container, !capturedByArrowFunction, /*includeClassComputedPropertyName*/ false); + thisInComputedPropertyName = true; + continue; + } + + break; + } + + checkThisInStaticClassFieldInitializerInDecoratedClass(node, container); + if (thisInComputedPropertyName) { + error(node, Diagnostics.this_cannot_be_referenced_in_a_computed_property_name); + } + else { + 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; + } + } + + // 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)); + } + } + } + } + return type || anyType; + } + + function tryGetThisTypeAt(node: Node, includeGlobalThis = true, container = getThisContainer(node, /*includeArrowFunctions*/ false, /*includeClassComputedPropertyName*/ 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; + } + thisType ||= getContextualThisParameterType(container); + } + + if (thisType) { + return getFlowTypeOfReference(node, thisType); + } + } + + if (isClassLike(container.parent)) { + const symbol = getSymbolOfDeclaration(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 = getSymbolOfDeclaration(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); + } + } + } + + function getExplicitThisType(node: Expression) { + const container = getThisContainer(node, /*includeArrowFunctions*/ false, /*includeClassComputedPropertyName*/ false); + if (isFunctionLike(container)) { + const signature = getSignatureFromDeclaration(container); + if (signature.thisParameter) { + return getExplicitTypeOfSymbol(signature.thisParameter); + } + } + if (isClassLike(container.parent)) { + const symbol = getSymbolOfDeclaration(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 getTypeForThisExpressionFromJSDoc(node: SignatureDeclaration) { + const thisTag = getJSDocThisTag(node); + if (thisTag && thisTag.typeExpression) { + return getTypeFromTypeNode(thisTag.typeExpression); + } + const signature = getSignatureOfTypeTag(node); + if (signature) { + return getThisTypeOfSignature(signature); + } + } + + function isInConstructorArgumentInitializer(node: Node, constructorDecl: Node): boolean { + return !!findAncestor(node, n => isFunctionLikeDeclaration(n) ? "quit" : n.kind === SyntaxKind.Parameter && n.parent === constructorDecl); + } + + function checkSuperExpression(node: Node): Type { + const isCallExpression = node.parent.kind === SyntaxKind.CallExpression && (node.parent as CallExpression).expression === node; + + const immediateContainer = getSuperContainer(node, /*stopOnFunctions*/ true); + let container = immediateContainer; + let needToCaptureLexicalThis = false; + let inAsyncFunction = false; + + // 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; + } + + let nodeCheckFlag: NodeCheckFlags = 0; + + if (!container || !isLegalUsageOfSuperExpression(container)) { + // 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; + } + + 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); + } + + 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; + } + + getNodeLinks(node).flags |= nodeCheckFlag; + + // 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 (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.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; + } + } + + // 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; + } + + if (classDeclarationExtendsNull(classLikeDeclaration)) { + return isCallExpression ? errorType : nullWideningType; + } + + const classType = getDeclaredTypeOfSymbol(getSymbolOfDeclaration(classLikeDeclaration)) as InterfaceType; + const baseClassType = classType && getBaseTypes(classType)[0]; + if (!baseClassType) { + return errorType; + } + + 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; + } + + return nodeCheckFlag === NodeCheckFlags.SuperStatic + ? getBaseConstructorTypeOfClass(classType) + : getTypeWithThisArgument(baseClassType, classType.thisType); + + function isLegalUsageOfSuperExpression(container: Node): boolean { + 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; + } + } + + 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 getThisTypeOfObjectLiteralFromContextualType(containingLiteral: ObjectLiteralExpression, contextualType: Type | undefined) { + let literal = containingLiteral; + let type = contextualType; + while (type) { + const thisType = getThisTypeFromContextualType(type); + if (thisType) { + return thisType; + } + if (literal.parent.kind !== SyntaxKind.PropertyAssignment) { + break; + } + literal = literal.parent.parent as ObjectLiteralExpression; + type = getApparentTypeOfContextualType(literal, /*contextFlags*/ undefined); + } + } + + function getContextualThisParameterType(func: SignatureDeclaration): Type | undefined { + if (func.kind === SyntaxKind.ArrowFunction) { + return undefined; + } + 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); + const thisType = getThisTypeOfObjectLiteralFromContextualType(containingLiteral, contextualType); + if (thisType) { + return instantiateType(thisType, getMapperFromContext(getInferenceContext(containingLiteral))); + } + // 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 (isAssignmentExpression(parent)) { + const target = parent.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)); + } + } + } + return undefined; + } + + // 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; + } + 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) || (isInJSFile(declaration) ? tryGetJSDocSatisfiesTypeNode(declaration) : undefined); + 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); + } + // By default, do nothing and return undefined - only the above cases have context implied by a parent + } + } + + 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); + } + } + + 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, getSymbolOfDeclaration(declaration).escapedName); + } + + // 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); + } + } + return undefined; + } + + 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 + } + + 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)]); + } + + return contextualReturnType; // Regular function or Generator function + } + } + 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)]); + } + return 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) { + const isAsyncGenerator = (functionFlags & FunctionFlags.Async) !== 0; + if (!node.asteriskToken && contextualReturnType.flags & TypeFlags.Union) { + contextualReturnType = filterType(contextualReturnType, type => !!getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Return, type, isAsyncGenerator)); + } + if (node.asteriskToken) { + const iterationTypes = getIterationTypesOfGeneratorFunctionReturnType(contextualReturnType, isAsyncGenerator); + const yieldType = iterationTypes?.yieldType ?? silentNeverType; + const returnType = getContextualType(node, contextFlags) ?? silentNeverType; + const nextType = iterationTypes?.nextType ?? unknownType; + const generatorType = createGeneratorType(yieldType, returnType, nextType, /*isAsyncGenerator*/ false); + if (isAsyncGenerator) { + const asyncGeneratorType = createGeneratorType(yieldType, returnType, nextType, /*isAsyncGenerator*/ true); + return getUnionType([generatorType, asyncGeneratorType]); + } + return generatorType; + } + return getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Yield, contextualReturnType, isAsyncGenerator); + } + } + + 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; + } + + node = node.parent; + } + + return false; + } + + 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; + } + + 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)) { + const returnType = getReturnTypeOfSignature(signature); + const functionFlags = getFunctionFlags(functionDecl); + if (functionFlags & FunctionFlags.Generator) { + return filterType(returnType, t => { + return !!(t.flags & (TypeFlags.AnyOrUnknown | TypeFlags.Void | TypeFlags.InstantiableNonPrimitive)) || checkGeneratorInstantiationAssignabilityToReturnType(t, functionFlags, /*errorNode*/ undefined); + }); + } + if (functionFlags & FunctionFlags.Async) { + return filterType(returnType, t => { + return !!(t.flags & (TypeFlags.AnyOrUnknown | TypeFlags.Void | TypeFlags.InstantiableNonPrimitive)) || !!getAwaitedTypeOfPromise(t); + }); + } + return returnType; + } + const iife = getImmediatelyInvokedFunctionExpression(functionDecl); + if (iife) { + return getContextualType(iife, contextFlags); + } + 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; + } + + // 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); + + 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 getContextualTypeForDecorator(decorator: Decorator): Type | undefined { + const signature = getDecoratorCallSignature(decorator); + return signature ? getOrCreateTypeFromSignature(signature) : undefined; + } + + function getContextualTypeForSubstitutionExpression(template: TemplateExpression, substitutionExpression: Expression) { + if (template.parent.kind === SyntaxKind.TaggedTemplateExpression) { + return getContextualTypeForArgument(template.parent as TaggedTemplateExpression, substitutionExpression); + } + + 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; + } + } + + /** + * 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 (canHaveSymbol(e) && 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; + + function tryGetPrivateIdentifierPropertyOfType(type: Type, id: PrivateIdentifier) { + const lexicallyScopedSymbol = lookupSymbolForPrivateIdentifierDeclaration(id.escapedText, id); + return lexicallyScopedSymbol && getPrivateIdentifierPropertyOfType(type, lexicallyScopedSymbol); + } + } + + // 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 (!canHaveSymbol(binaryExpression.left) || !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, /*nameNotFoundMessage*/ undefined, /*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) || decl === binaryExpression.left ? 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 = canHaveSymbol(binaryExpression.left) ? binaryExpression.left.symbol?.valueDeclaration : undefined; + } + 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 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, /*nameNotFoundMessage*/ undefined, /*isUse*/ true, /*excludeGlobals*/ true); + return isThisInitializedDeclaration(symbol?.valueDeclaration); + } + + 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, /*includeClassComputedPropertyName*/ false))) { + return undefined; + } + const thisType = checkThisExpression(thisAccess.expression); + const nameStr = getElementOrPropertyAccessName(thisAccess); + return nameStr !== undefined && getTypeOfPropertyOfContextualType(thisType, nameStr) || undefined; + } + + function isCircularMappedProperty(symbol: Symbol) { + return !!(getCheckFlags(symbol) & CheckFlags.Mapped && !(symbol as MappedSymbol).links.type && findResolutionCycleStartIndex(symbol, TypeSystemPropertyName.Type) >= 0); + } + + 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); + } + } + else if (t.flags & TypeFlags.StructuredType) { + const prop = getPropertyOfType(t, name); + if (prop) { + return isCircularMappedProperty(prop) ? undefined : removeMissingType(getTypeOfSymbol(prop), !!(prop.flags & SymbolFlags.Optional)); + } + if (isTupleType(t) && isNumericLiteralName(name) && +name >= 0) { + const restType = getElementTypeOfSliceOfTupleType(t, t.target.fixedLength, /*endSkipCount*/ 0, /*writing*/ false, /*noReductions*/ true); + if (restType) { + return restType; + } + } + 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 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 = getSymbolOfDeclaration(element); + return getTypeOfPropertyOfContextualType(type, symbol.escapedName, getSymbolLinks(symbol).nameType); + } + if (hasDynamicName(element)) { + const name = getNameOfDeclaration(element); + if (name && isComputedPropertyName(name)) { + const exprType = checkExpression(name.expression); + const propType = isTypeUsableAsPropertyName(exprType) && getTypeOfPropertyOfContextualType(type, getPropertyNameFromType(exprType)); + if (propType) { + return propType; + } + } + } + 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 getSpreadIndices(elements: readonly Node[]) { + let first, last; + for (let i = 0; i < elements.length; i++) { + if (isSpreadElement(elements[i])) { + first ??= i; + last = i; + } + } + return { first, last }; + } + + function getContextualTypeForElementExpression(type: Type | undefined, index: number, length?: number, firstSpreadIndex?: number, lastSpreadIndex?: number): Type | undefined { + return type && mapType(type, t => { + if (isTupleType(t)) { + // If index is before any spread element and within the fixed part of the contextual tuple type, return + // the type of the contextual tuple element. + if ((firstSpreadIndex === undefined || index < firstSpreadIndex) && index < t.target.fixedLength) { + return removeMissingType(getTypeArguments(t)[index], !!(t.target.elementFlags[index] && ElementFlags.Optional)); + } + // When the length is known and the index is after all spread elements we compute the offset from the element + // to the end and the number of ending fixed elements in the contextual tuple type. + const offset = length !== undefined && (lastSpreadIndex === undefined || index > lastSpreadIndex) ? length - index : 0; + const fixedEndLength = offset > 0 && (t.target.combinedFlags & ElementFlags.Variable) ? getEndElementCount(t.target, ElementFlags.Fixed) : 0; + // If the offset is within the ending fixed part of the contextual tuple type, return the type of the contextual + // tuple element. + if (offset > 0 && offset <= fixedEndLength) { + return getTypeArguments(t)[getTypeReferenceArity(t) - offset]; + } + // Return a union of the possible contextual element types with no subtype reduction. + return getElementTypeOfSliceOfTupleType(t, firstSpreadIndex === undefined ? t.target.fixedLength : Math.min(t.target.fixedLength, firstSpreadIndex), length === undefined || lastSpreadIndex === undefined ? fixedEndLength : Math.min(fixedEndLength, length - lastSpreadIndex), /*writing*/ false, /*noReductions*/ true); + } + // If element index is known and a contextual property with that name exists, return it. Otherwise return the + // iterated or element type of the contextual type. + return (!firstSpreadIndex || index < firstSpreadIndex) && getTypeOfPropertyOfContextualType(t, "" + index as __String) || + 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.attributes, 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 t; + } + }, /*noReductions*/ true)); + } + + 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 getTypeOfPropertyOfContextualType(attributesType, getEscapedTextOfJsxAttributeName(attribute.name)); + } + else { + return getContextualType(attribute.parent, contextFlags); + } + } + + // 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.TemplateExpression: + 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 discriminateContextualTypeByObjectMembers(node: ObjectLiteralExpression, contextualType: UnionType) { + const key = `D${getNodeId(node)},${getTypeId(contextualType)}`; + return getCachedType(key) ?? setCachedType( + key, + getMatchingUnionConstituentForObjectLiteral(contextualType, node) ?? discriminateTypeByDiscriminableItems( + contextualType, + concatenate( + map( + filter(node.properties, (p): p is PropertyAssignment | ShorthandPropertyAssignment => { + if (!p.symbol) { + return false; + } + if (p.kind === SyntaxKind.PropertyAssignment) { + return isPossiblyDiscriminantValue(p.initializer) && isDiscriminantProperty(contextualType, p.symbol.escapedName); + } + if (p.kind === SyntaxKind.ShorthandPropertyAssignment) { + return isDiscriminantProperty(contextualType, p.symbol.escapedName); + } + return false; + }), + prop => ([() => getContextFreeTypeOfExpression(prop.kind === SyntaxKind.PropertyAssignment ? prop.initializer : prop.name), prop.symbol.escapedName] as const), + ), + 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 const, + ), + ), + isTypeAssignableTo, + ), + ); + } + + function discriminateContextualTypeByJSXAttributes(node: JsxAttributes, contextualType: UnionType) { + const key = `D${getNodeId(node)},${getTypeId(contextualType)}`; + const cached = getCachedType(key); + if (cached) return cached; + const jsxChildrenPropertyName = getJsxElementChildrenPropertyName(getJsxNamespaceAt(node)); + return setCachedType( + key, + 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 const), + ), + map( + filter(getPropertiesOfType(contextualType), s => { + if (!(s.flags & SymbolFlags.Optional) || !node?.symbol?.members) { + return false; + } + const element = node.parent.parent; + if (s.escapedName === jsxChildrenPropertyName && isJsxElement(element) && getSemanticJsxChildren(element.children).length) { + return false; + } + return !node.symbol.members.has(s.escapedName) && isDiscriminantProperty(contextualType, s.escapedName); + }), + s => [() => undefinedType, s.escapedName] as const, + ), + ), + isTypeAssignableTo, + ), + ); + } + + // 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, + // When obtaining apparent type of *contextual* type we don't want to get apparent type of mapped types. + // That would evaluate mapped types with array or tuple type constraints too eagerly + // and thus it would prevent `getTypeOfPropertyOfContextualType` from obtaining per-position contextual type for elements of array literal expressions. + // Apparent type of other mapped types is already the mapped type itself so we can just avoid calling `getApparentType` here for all mapped types. + t => getObjectFlags(t) & ObjectFlags.Mapped ? t : getApparentType(t), + /*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; + } + } + 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; + } + + /** + * 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; + } + // Cached contextual types are obtained with no ContextFlags, so we can only consult them for + // requests with no ContextFlags. + const index = findContextualNode(node, /*includeCaches*/ !contextFlags); + if (index >= 0) { + return contextualTypes[index]; + } + 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 | Decorator, node); + case SyntaxKind.Decorator: + return getContextualTypeForDecorator(parent as Decorator); + case SyntaxKind.TypeAssertionExpression: + case SyntaxKind.AsExpression: + return isConstTypeReference((parent as AssertionExpression).type) ? getContextualType(parent as AssertionExpression, contextFlags) : 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); + const elementIndex = indexOfNode(arrayLiteral.elements, node); + const spreadIndices = getNodeLinks(arrayLiteral).spreadIndices ??= getSpreadIndices(arrayLiteral.elements); + return getContextualTypeForElementExpression(type, elementIndex, arrayLiteral.elements.length, spreadIndices.first, spreadIndices.last); + } + 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: { + if (isInJSFile(parent)) { + if (isJSDocSatisfiesExpression(parent)) { + return getTypeFromTypeNode(getJSDocSatisfiesExpressionType(parent)); + } + // Like in `checkParenthesizedExpression`, an `/** @type {xyz} */` comment before a parenthesized expression acts as a type cast. + const typeTag = getJSDocTypeTag(parent); + if (typeTag && !isConstTypeReference(typeTag.typeExpression.type)) { + return getTypeFromTypeNode(typeTag.typeExpression.type); + } + } + return getContextualType(parent as ParenthesizedExpression, contextFlags); + } + 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); + case SyntaxKind.ImportAttribute: + return getContextualImportAttributeType(parent as ImportAttribute); + } + return undefined; + } + + function pushCachedContextualType(node: Expression) { + pushContextualType(node, getContextualType(node, /*contextFlags*/ undefined), /*isCache*/ true); + } + + function pushContextualType(node: Expression, type: Type | undefined, isCache: boolean) { + contextualTypeNodes[contextualTypeCount] = node; + contextualTypes[contextualTypeCount] = type; + contextualIsCache[contextualTypeCount] = isCache; + contextualTypeCount++; + } + + function popContextualType() { + contextualTypeCount--; + } + + function findContextualNode(node: Node, includeCaches: boolean) { + for (let i = contextualTypeCount - 1; i >= 0; i--) { + if (node === contextualTypeNodes[i] && (includeCaches || !contextualIsCache[i])) { + return i; + } + } + return -1; + } + + function pushInferenceContext(node: Node, inferenceContext: InferenceContext | undefined) { + inferenceContextNodes[inferenceContextCount] = node; + inferenceContexts[inferenceContextCount] = inferenceContext; + inferenceContextCount++; + } + + function popInferenceContext() { + inferenceContextCount--; + } + + function getInferenceContext(node: Node) { + for (let i = inferenceContextCount - 1; i >= 0; i--) { + if (isNodeDescendantOf(node, inferenceContextNodes[i])) { + return inferenceContexts[i]; + } + } + } + + function getContextualImportAttributeType(node: ImportAttribute) { + return getTypeOfPropertyOfContextualType(getGlobalImportAttributesType(/*reportErrors*/ false), getNameFromImportAttribute(node)); + } + + function getContextualJsxElementAttributesType(node: JsxOpeningLikeElement, contextFlags: ContextFlags | undefined) { + if (isJsxOpeningElement(node) && contextFlags !== ContextFlags.Completions) { + const index = findContextualNode(node.parent, /*includeCaches*/ !contextFlags); + if (index >= 0) { + // 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 contextualTypes[index]; + } + } + return getContextualTypeForArgumentAtIndex(node, 0); + } + + function getEffectiveFirstArgumentForJsxSignature(signature: Signature, node: JsxOpeningLikeElement) { + return getJsxReferenceKind(node) !== JsxReferenceKind.Component + ? getJsxPropsTypeFromCallSignature(signature, node) + : getJsxPropsTypeFromClassType(signature, node); + } + + 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 instanceType = getReturnTypeOfSignature(sig); + return isTypeAny(instanceType) ? instanceType : getTypeOfPropertyOfType(instanceType, forcedLookupLocation); + } + + function getStaticTypeOfReferencedJsxConstructor(context: JsxOpeningLikeElement) { + if (isJsxIntrinsicTagName(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; + } + const fakeSignature = createSignatureForJSXIntrinsic(context, result); + return getOrCreateTypeFromSignature(fakeSignature); + } + return tagType; + } + + function getJsxManagedAttributesFromLocatedAttributes(context: JsxOpeningLikeElement, ns: Symbol, attributesType: Type) { + const managedSym = getJsxLibraryManagedAttributes(ns); + if (managedSym) { + const ctorType = getStaticTypeOfReferencedJsxConstructor(context); + const result = instantiateAliasOrInterfaceWithDefaults(managedSym, isInJSFile(context), ctorType, attributesType); + if (result) { + return result; + } + } + return attributesType; + } + + 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 unknownType; + } + + attributesType = getJsxManagedAttributesFromLocatedAttributes(context, ns, attributesType); + + 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)); + } + // 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; + } + } + + function getIntersectedSignatures(signatures: readonly Signature[]) { + return getStrictOptionValue(compilerOptions, "noImplicitAny") + ? reduceLeft( + signatures, + (left: Signature | undefined, 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 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.links.type = isRestParam ? createArrayType(unionParamType) : unionParamType; + params[i] = paramSymbol; + } + if (needsExtraRestElement) { + const restParamSymbol = createSymbol(SymbolFlags.FunctionScopedVariable, "args" as __String); + restParamSymbol.links.type = createArrayType(getTypeAtPosition(shorter, longestCount)); + if (shorter === right) { + restParamSymbol.links.type = instantiateType(restParamSymbol.links.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, + ); + 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 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; + } + } + if (target.parameters.length && parameterIsThisKeyword(target.parameters[0])) { + targetParameterCount--; + } + 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; + } + + // 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 checkGrammarRegularExpressionLiteral(node: RegularExpressionLiteral) { + const sourceFile = getSourceFileOfNode(node); + if (!hasParseDiagnostics(sourceFile) && !node.isUnterminated) { + let lastError: DiagnosticWithLocation | undefined; + scanner ??= createScanner(ScriptTarget.ESNext, /*skipTrivia*/ true); + scanner.setScriptTarget(sourceFile.languageVersion); + scanner.setLanguageVariant(sourceFile.languageVariant); + scanner.setOnError((message, length, arg0) => { + // For providing spelling suggestions + const start = scanner!.getTokenEnd(); + if (message.category === DiagnosticCategory.Message && lastError && start === lastError.start && length === lastError.length) { + const error = createDetachedDiagnostic(sourceFile.fileName, sourceFile.text, start, length, message, arg0); + addRelatedInfo(lastError, error); + } + else if (!lastError || start !== lastError.start) { + lastError = createFileDiagnostic(sourceFile, start, length, message, arg0); + diagnostics.add(lastError); + } + }); + scanner.setText(sourceFile.text, node.pos, node.end - node.pos); + try { + scanner.scan(); + Debug.assert(scanner.reScanSlashToken(/*reportErrors*/ true) === SyntaxKind.RegularExpressionLiteral, "Expected scanner to rescan RegularExpressionLiteral"); + return !!lastError; + } + finally { + scanner.setText(""); + scanner.setOnError(/*onError*/ undefined); + } + } + return false; + } + + function checkRegularExpressionLiteral(node: RegularExpressionLiteral) { + const nodeLinks = getNodeLinks(node); + if (!(nodeLinks.flags & NodeCheckFlags.TypeChecked)) { + nodeLinks.flags |= NodeCheckFlags.TypeChecked; + addLazyDiagnostic(() => checkGrammarRegularExpressionLiteral(node)); + } + return globalRegExpType; + } + + function checkSpreadExpression(node: SpreadElement, checkMode?: CheckMode): Type { + if (languageVersion < LanguageFeatureMinimumTarget.SpreadElements) { + checkExternalEmitHelpers(node, compilerOptions.downlevelIteration ? ExternalEmitHelpers.SpreadIncludes : ExternalEmitHelpers.SpreadArray); + } + + const arrayOrIterableType = checkExpression(node.expression, checkMode); + return checkIteratedTypeOrElementType(IterationUse.Spread, arrayOrIterableType, undefinedType, node.expression); + } + + 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 isSpreadIntoCallOrNew(node: ArrayLiteralExpression) { + const parent = walkUpParenthesizedExpressions(node.parent); + return isSpreadElement(parent) && isCallOrNewExpression(parent.parent); + } + + 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[] = []; + pushCachedContextualType(node); + const inDestructuringPattern = isAssignmentTarget(node); + const inConstContext = isConstContext(node); + const contextualType = getApparentTypeOfContextualType(node, /*contextFlags*/ undefined); + const inTupleContext = isSpreadIntoCallOrNew(node) || !!contextualType && someType(contextualType, t => isTupleLikeType(t) || isGenericMappedType(t) && !t.nameType && !!getHomomorphicTypeVariable(t.target as MappedType || t)); + + let hasOmittedExpression = false; + for (let i = 0; i < elementCount; i++) { + const e = elements[i]; + if (e.kind === SyntaxKind.SpreadElement) { + if (languageVersion < LanguageFeatureMinimumTarget.SpreadElements) { + 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); + } + } + else if (exactOptionalPropertyTypes && e.kind === SyntaxKind.OmittedExpression) { + hasOmittedExpression = true; + elementTypes.push(undefinedOrMissingType); + elementFlags.push(ElementFlags.Optional); + } + else { + const type = checkExpressionForMutableLocation(e, checkMode, forceTuple); + elementTypes.push(addOptionality(type, /*isProperty*/ true, hasOmittedExpression)); + elementFlags.push(hasOmittedExpression ? ElementFlags.Optional : ElementFlags.Required); + if (inTupleContext && 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); + } + } + } + popContextualType(); + if (inDestructuringPattern) { + return createTupleType(elementTypes, elementFlags); + } + if (forceTuple || inConstContext || inTupleContext) { + return createArrayLiteralType(createTupleType(elementTypes, elementFlags, /*readonly*/ inConstContext && !(contextualType && someType(contextualType, isMutableArrayLikeType)))); + } + 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; + } + + 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 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 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 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])); + } + } + 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 checkObjectLiteral(node: ObjectLiteralExpression, checkMode: CheckMode = CheckMode.Normal): 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; + + pushCachedContextualType(node); + 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 = isInJavascript ? getJSDocEnumTag(node) : undefined; + const isJSObjectLiteral = !contextualType && isInJavascript && !enumTag; + let objectFlags: ObjectFlags = ObjectFlags.FreshLiteral; + 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 = getSymbolOfDeclaration(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.links.nameType = nameType; + } + + 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; + } + else if (!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)); + } + } + + prop.declarations = member.declarations; + prop.parent = member.parent; + if (member.valueDeclaration) { + prop.valueDeclaration = member.valueDeclaration; + } + + prop.links.type = type; + prop.links.target = member; + member = prop; + allPropertiesTable?.set(prop.escapedName, prop); + + if ( + contextualType && 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 < LanguageFeatureMinimumTarget.ObjectAssign) { + 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, checkMode & CheckMode.Inferential)); + 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; + } + 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); + } + popContextualType(); + + // 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 || tryCast(prop, isTransientSymbol)?.links.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); + } + } + } + } + + if (isErrorType(spread)) { + return errorType; + } + + 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); + } + + 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; + } + if (patternWithComputedProperties) { + result.objectFlags |= ObjectFlags.ObjectLiteralPatternWithComputedProperties; + } + if (inDestructuringPattern) { + result.pattern = node; + } + return result; + } + } + + 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 checkJsxSelfClosingElementDeferred(node: JsxSelfClosingElement) { + checkJsxOpeningLikeElementOrOpeningFragment(node); + } + + function checkJsxSelfClosingElement(node: JsxSelfClosingElement, _checkMode: CheckMode | undefined): Type { + checkNodeDeferred(node); + return getJsxElementTypeAt(node) || anyType; + } + + function checkJsxElementDeferred(node: JsxElement) { + // Check attributes + checkJsxOpeningLikeElementOrOpeningFragment(node.openingElement); + + // Perform resolution on the closing tag so that rename/go to definition/etc work + if (isJsxIntrinsicTagName(node.closingElement.tagName)) { + getIntrinsicTagSymbol(node.closingElement); + } + else { + checkExpression(node.closingElement.tagName); + } + + checkJsxChildren(node); + } + + function checkJsxElement(node: JsxElement, _checkMode: CheckMode | undefined): Type { + checkNodeDeferred(node); + + return getJsxElementTypeAt(node) || anyType; + } + + function checkJsxFragment(node: JsxFragment): Type { + checkJsxOpeningLikeElementOrOpeningFragment(node.openingFragment); + + // 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, + ); + } + + checkJsxChildren(node); + return getJsxElementTypeAt(node) || anyType; + } + + function isHyphenatedJsxName(name: string | __String) { + return (name as string).includes("-"); + } + + /** + * Returns true iff React would emit this tag name as a string rather than an identifier or qualified name + */ + function isJsxIntrinsicTagName(tagName: Node): tagName is Identifier | JsxNamespacedName { + return isIdentifier(tagName) && isIntrinsicJsxName(tagName.escapedText) || isJsxNamespacedName(tagName); + } + + function checkJsxAttribute(node: JsxAttribute, checkMode?: CheckMode) { + return node.initializer + ? checkExpressionForMutableLocation(node.initializer, checkMode) + : trueType; // is sugar for + } + + /** + * 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 = CheckMode.Normal) { + const attributes = openingLikeElement.attributes; + const contextualType = 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.links.type = exprType; + attributeSymbol.links.target = member; + attributesTable.set(attributeSymbol.escapedName, attributeSymbol); + allAttributesTable?.set(attributeSymbol.escapedName, attributeSymbol); + if (getEscapedTextOfJsxAttributeName(attributeDecl.name) === jsxChildrenPropertyName) { + explicitlySpecifyChildrenAttribute = true; + } + if (contextualType) { + const prop = getPropertyOfType(contextualType, member.escapedName); + if (prop && prop.declarations && isDeprecatedSymbol(prop) && isIdentifier(attributeDecl.name)) { + addDeprecatedSuggestion(attributeDecl.name, prop.declarations, attributeDecl.name.escapedText as string); + } + } + if (contextualType && checkMode & CheckMode.Inferential && !(checkMode & CheckMode.SkipContextSensitive) && isContextSensitive(attributeDecl)) { + const inferenceContext = getInferenceContext(attributes); + Debug.assert(inferenceContext); // In CheckMode.Inferential we should always have an inference context + const inferenceNode = (attributeDecl.initializer as JsxExpression).expression!; + addIntraExpressionInferenceSite(inferenceContext, inferenceNode, exprType); + } + } + 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(checkExpression(attributeDecl.expression, checkMode & CheckMode.Inferential)); + 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; + } + } + } + + if (!hasSpreadAnyType) { + if (attributesTable.size > 0) { + spread = getSpreadType(spread, createJsxAttributesType(), attributes.symbol, objectFlags, /*readonly*/ false); + } + } + + // 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 && getSemanticJsxChildren(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.links.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); + } + } + + if (hasSpreadAnyType) { + return anyType; + } + if (typeToIntersect && spread !== emptyJsxObjectType) { + return getIntersectionType([typeToIntersect, spread]); + } + return typeToIntersect || (spread === emptyJsxObjectType ? createJsxAttributesType() : spread); + + /** + * 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 |= ObjectFlags.FreshLiteral; + const result = createAnonymousType(attributes.symbol, attributesTable, emptyArray, emptyArray, emptyArray); + result.objectFlags |= objectFlags | ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral; + return result; + } + } + + 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); + } + } + else if (child.kind === SyntaxKind.JsxExpression && !child.expression) { + continue; // empty jsx expressions don't *really* count as present children + } + else { + childrenTypes.push(checkExpressionForMutableLocation(child, checkMode)); + } + } + 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)); + } + } + } + } + + /** + * 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); + } + + 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) && !isJsxNamespacedName(node.tagName)) return Debug.fail(); + const propName = isJsxNamespacedName(node.tagName) ? getEscapedTextOfJsxNamespacedName(node.tagName) : node.tagName.escapedText; + const intrinsicProp = getPropertyOfType(intrinsicElementsType, propName); + if (intrinsicProp) { + links.jsxFlags |= JsxFlags.IntrinsicNamedElement; + return links.resolvedSymbol = intrinsicProp; + } + + // Intrinsic string indexer case + const indexSymbol = getApplicableIndexSymbol(intrinsicElementsType, getStringLiteralType(unescapeLeadingUnderscores(propName))); + if (indexSymbol) { + links.jsxFlags |= JsxFlags.IntrinsicIndexedElement; + return links.resolvedSymbol = indexSymbol; + } + + if (getTypeOfPropertyOrIndexSignatureOfType(intrinsicElementsType, propName)) { + links.jsxFlags |= JsxFlags.IntrinsicIndexedElement; + return links.resolvedSymbol = intrinsicElementsType.symbol; + } + + // Wasn't found + error(node, Diagnostics.Property_0_does_not_exist_on_type_1, intrinsicTagNameToString(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 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; + } + 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_nodenext_or_to_add_aliases_to_the_paths_option + : Diagnostics.Cannot_find_module_0_or_its_corresponding_type_declarations; + const specifier = getJSXRuntimeImportSpecifier(file, runtimeImportSpecifier); + const mod = resolveExternalModule(specifier || location!, runtimeImportSpecifier, errorMessage, location!); + const result = mod && mod !== unknownSymbol ? getMergedSymbol(resolveSymbol(mod)) : undefined; + if (links) { + links.jsxImplicitImportContainer = result || false; + } + return result; + } + + 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); + + if (!resolvedNamespace || resolvedNamespace === unknownSymbol) { + const namespaceName = getJsxNamespace(location); + resolvedNamespace = resolveName(location, namespaceName, SymbolFlags.Namespace, /*nameNotFoundMessage*/ undefined, /*isUse*/ false); + } + + if (resolvedNamespace) { + const candidate = resolveSymbol(getSymbol(getExportsOfSymbol(resolveSymbol(resolvedNamespace)), JsxNames.JSX, SymbolFlags.Namespace)); + if (candidate && candidate !== unknownSymbol) { + if (links) { + links.jsxNamespace = candidate; + } + return candidate; + } + } + if (links) { + links.jsxNamespace = false; + } + } + // JSX global fallback + const s = resolveSymbol(getGlobalSymbol(JsxNames.JSX, SymbolFlags.Namespace, /*diagnostic*/ undefined)); + if (s === unknownSymbol) { + return undefined!; // TODO: GH#18217 + } + return s!; // TODO: GH#18217 + } + + /** + * 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); + } + + function getJsxElementTypeSymbol(jsxNamespace: Symbol) { + // JSX.ElementType [symbol] + return jsxNamespace && getSymbol(jsxNamespace.exports!, JsxNames.ElementType, 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 { + 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 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; + } + + 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); + } + } + 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 generateInitialErrorChain(): DiagnosticMessageChain { + const componentName = getTextOfNode(openingLikeElement.tagName); + return chainDiagnosticMessages(/*details*/ undefined, Diagnostics._0_cannot_be_used_as_a_JSX_component, componentName); + } + } + + /** + * 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(isJsxIntrinsicTagName(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) { + const propName = isJsxNamespacedName(node.tagName) ? getEscapedTextOfJsxNamespacedName(node.tagName) : node.tagName.escapedText; + return links.resolvedJsxElementAttributesType = getApplicableIndexInfoForName(getJsxType(JsxNames.IntrinsicElements, node), propName)?.type || errorType; + } + else { + return links.resolvedJsxElementAttributesType = errorType; + } + } + return links.resolvedJsxElementAttributesType; + } + + function getJsxElementClassTypeAt(location: Node): Type | undefined { + const type = getJsxType(JsxNames.ElementClass, location); + if (isErrorType(type)) return undefined; + return type; + } + + function getJsxElementTypeAt(location: Node): Type { + return getJsxType(JsxNames.Element, location); + } + + function getJsxStatelessElementTypeAt(location: Node): Type | undefined { + const jsxElementType = getJsxElementTypeAt(location); + if (jsxElementType) { + return getUnionType([jsxElementType, nullType]); + } + } + + function getJsxElementTypeTypeAt(location: Node): Type | undefined { + const ns = getJsxNamespaceAt(location); + if (!ns) return undefined; + const sym = getJsxElementTypeSymbol(ns); + if (!sym) return undefined; + const type = instantiateAliasOrInterfaceWithDefaults(sym, isInJSFile(location)); + if (!type || isErrorType(type)) return undefined; + return type; + } + + function instantiateAliasOrInterfaceWithDefaults(managedSym: Symbol, inJs: boolean, ...typeArguments: Type[]) { + const declaredManagedType = getDeclaredTypeOfSymbol(managedSym); // fetches interface type, or initializes symbol links type parmaeters + if (managedSym.flags & SymbolFlags.TypeAlias) { + const params = getSymbolLinks(managedSym).typeParameters; + if (length(params) >= typeArguments.length) { + const args = fillMissingTypeArguments(typeArguments, params, typeArguments.length, inJs); + return length(args) === 0 ? declaredManagedType : getTypeAliasInstantiation(managedSym, args); + } + } + if (length((declaredManagedType as GenericType).typeParameters) >= typeArguments.length) { + const args = fillMissingTypeArguments(typeArguments, (declaredManagedType as GenericType).typeParameters, typeArguments.length, inJs); + return createTypeReference(declaredManagedType as GenericType, args); + } + return undefined; + } + + /** + * 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 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 (getJsxElementTypeAt(errorNode) === undefined) { + if (noImplicitAny) { + error(errorNode, Diagnostics.JSX_element_implicitly_has_type_any_because_the_global_type_JSX_Element_does_not_exist); + } + } + } + + function checkJsxOpeningLikeElementOrOpeningFragment(node: JsxOpeningLikeElement | JsxOpeningFragment) { + const isNodeOpeningLikeElement = isJsxOpeningLikeElement(node); + + if (isNodeOpeningLikeElement) { + checkGrammarJsxElement(node); + } + + checkJsxPreconditions(node); + + markLinkedReferences(node, ReferenceHint.Jsx); + + if (isNodeOpeningLikeElement) { + const jsxOpeningLikeNode = node; + const sig = getResolvedSignature(jsxOpeningLikeNode); + checkDeprecatedSignature(sig, node); + + const elementTypeConstraint = getJsxElementTypeTypeAt(jsxOpeningLikeNode); + if (elementTypeConstraint !== undefined) { + const tagName = jsxOpeningLikeNode.tagName; + const tagType = isJsxIntrinsicTagName(tagName) + ? getStringLiteralType(intrinsicTagNameToString(tagName)) + : checkExpression(tagName); + checkTypeRelatedTo(tagType, elementTypeConstraint, assignableRelation, tagName, Diagnostics.Its_type_0_is_not_a_valid_JSX_element_type, () => { + const componentName = getTextOfNode(tagName); + return chainDiagnosticMessages(/*details*/ undefined, Diagnostics._0_cannot_be_used_as_a_JSX_component, componentName); + }); + } + else { + checkJsxReturnAssignableToAppropriateBound(getJsxReferenceKind(jsxOpeningLikeNode), getReturnTypeOfSignature(sig), jsxOpeningLikeNode); + } + } + } + + /** + * 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 (targetType.flags & TypeFlags.Substitution) { + return isKnownProperty((targetType as SubstitutionType).baseType, name, isComparingJsxAttributes); + } + 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 isExcessPropertyCheckTarget(type: Type): boolean { + return !!(type.flags & TypeFlags.Object && !(getObjectFlags(type) & ObjectFlags.ObjectLiteralPatternWithComputedProperties) || + type.flags & TypeFlags.NonPrimitive || + type.flags & TypeFlags.Substitution && isExcessPropertyCheckTarget((type as SubstitutionType).baseType) || + type.flags & TypeFlags.Union && some((type as UnionType).types, isExcessPropertyCheckTarget) || + type.flags & TypeFlags.Intersection && every((type as IntersectionType).types, isExcessPropertyCheckTarget)); + } + + 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; + } + } + + function getDeclarationNodeFlagsFromSymbol(s: Symbol): NodeFlags { + return s.valueDeclaration ? getCombinedNodeFlagsCached(s.valueDeclaration) : 0; + } + + /** + * 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 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); + } + + /** + * 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.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. + if (errorNode) { + error(errorNode, Diagnostics.Abstract_method_0_in_class_1_cannot_be_accessed_via_super_expression, symbolToString(prop), typeToString(getDeclaringClass(prop)!)); + } + return false; + } + // A class field cannot be accessed via super.* from a derived class. + // This is true for both [[Set]] (old) and [[Define]] (ES spec) semantics. + if (!(flags & ModifierFlags.Static) && prop.declarations?.some(isClassInstanceProperty)) { + if (errorNode) { + error(errorNode, Diagnostics.Class_field_0_defined_by_the_parent_class_is_not_accessible_in_the_child_class_via_super, symbolToString(prop)); + } + 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; + } + } + + // Public properties are otherwise accessible. + if (!(flags & ModifierFlags.NonPublicAccessibilityModifier)) { + return true; + } + + // 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 false; + } + return true; + } + + // 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; + } + + // 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(getSymbolOfDeclaration(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)) { + 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 false; + } + 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; + } + return undefined; + } + + function getThisParameterFromNodeContext(node: Node) { + const thisContainer = getThisContainer(node, /*includeArrowFunctions*/ false, /*includeClassComputedPropertyName*/ false); + return thisContainer && isFunctionLike(thisContainer) ? getThisParameter(thisContainer) : undefined; + } + + function symbolHasNonMethodDeclaration(symbol: Symbol) { + return !!forEachProperty(symbol, prop => !(prop.flags & SymbolFlags.Method)); + } + + function checkNonNullExpression(node: Expression | QualifiedName) { + return checkNonNullType(checkExpression(node), node); + } + + function isNullableType(type: Type) { + return hasTypeFacts(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; + } + 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, + ); + } + } + + 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, TypeFacts.IsUndefinedOrNull); + if (facts & TypeFacts.IsUndefinedOrNull) { + reportError(node, facts); + const t = getNonNullableType(type); + return t.flags & (TypeFlags.Nullable | TypeFlags.Never) ? errorType : t; + } + return type; + } + + 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; + } + + function checkPropertyAccessExpression(node: PropertyAccessExpression, checkMode: CheckMode | undefined, writeOnly?: boolean) { + return node.flags & NodeFlags.OptionalChain ? checkPropertyAccessChain(node as PropertyAccessChain, checkMode) : + checkPropertyAccessExpressionOrQualifiedName(node, node.expression, checkNonNullExpression(node.expression), node.name, checkMode, writeOnly); + } + + 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 isMethodAccessForCall(node: Node) { + while (node.parent.kind === SyntaxKind.ParenthesizedExpression) { + node = node.parent; + } + return isCallOrNewExpression(node.parent) && node.parent.expression === node; + } + + // Lookup the private identifier lexically. + function lookupSymbolForPrivateIdentifierDeclaration(propName: __String, location: Node): Symbol | undefined { + for (let containingClass = getContainingClassExcludingClassDecorators(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); + } + + 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)); + } + } + + return false; + } + + function checkPrivateIdentifierExpression(privId: PrivateIdentifier): Type { + checkGrammarPrivateIdentifierExpression(privId); + const symbol = getSymbolForPrivateIdentifierExpression(privId); + if (symbol) { + markPropertyAsReferenced(symbol, /*nodeForCheckWriteOnly*/ undefined, /*isSelfTypeAccess*/ false); + } + return anyType; + } + + function getSymbolForPrivateIdentifierExpression(privId: PrivateIdentifier): Symbol | undefined { + if (!isExpressionNode(privId)) { + return undefined; + } + + const links = getNodeLinks(privId); + if (links.resolvedSymbol === undefined) { + links.resolvedSymbol = lookupSymbolForPrivateIdentifierDeclaration(privId.escapedText, privId); + } + return links.resolvedSymbol; + } + + function getPrivateIdentifierPropertyOfType(leftType: Type, lexicallyScopedIdentifier: Symbol): Symbol | undefined { + return getPropertyOfType(leftType, lexicallyScopedIdentifier.escapedName); + } + + 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), + ); + + 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; + } + + function isThisPropertyAccessInConstructor(node: ElementAccessExpression | PropertyAccessExpression | QualifiedName, prop: Symbol) { + return (isConstructorDeclaredProperty(prop) || isThisProperty(node) && isAutoTypedProperty(prop)) + && getThisContainer(node, /*includeArrowFunctions*/ true, /*includeClassComputedPropertyName*/ false) === getDeclaringConstructor(prop); + } + + function checkPropertyAccessExpressionOrQualifiedName(node: PropertyAccessExpression | QualifiedName, left: Expression | QualifiedName, leftType: Type, right: Identifier | PrivateIdentifier, checkMode: CheckMode | undefined, writeOnly?: boolean) { + 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 < LanguageFeatureMinimumTarget.PrivateNamesAndClassStaticBlocks || + languageVersion < LanguageFeatureMinimumTarget.ClassAndClassElementDecorators || + !useDefineForClassFields + ) { + if (assignmentKind !== AssignmentKind.None) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.ClassPrivateFieldSet); + } + if (assignmentKind !== AssignmentKind.Definite) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.ClassPrivateFieldGet); + } + } + + 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)); + } + if (isAnyLike) { + if (lexicallyScopedSymbol) { + return isErrorType(apparentType) ? errorType : apparentType; + } + if (getContainingClassExcludingClassDecorators(right) === undefined) { + grammarErrorOnNode(right, Diagnostics.Private_identifiers_are_not_allowed_outside_class_bodies); + return anyType; + } + } + + prop = lexicallyScopedSymbol && getPrivateIdentifierPropertyOfType(leftType, lexicallyScopedSymbol); + if (prop === undefined) { + // Check for private-identifier-specific shadowing and lexical-scoping errors. + if (checkPrivateIdentifierPropertyAccess(leftType, right, lexicallyScopedSymbol)) { + return errorType; + } + const containingClass = getContainingClassExcludingClassDecorators(right); + if (containingClass && isPlainJsFile(getSourceFileOfNode(containingClass), compilerOptions.checkJs)) { + grammarErrorOnNode(right, Diagnostics.Private_field_0_must_be_declared_in_an_enclosing_class, idText(right)); + } + } + else { + const isSetonlyAccessor = prop.flags & SymbolFlags.SetAccessor && !(prop.flags & SymbolFlags.GetAccessor); + if (isSetonlyAccessor && assignmentKind !== AssignmentKind.Definite) { + error(node, Diagnostics.Private_accessor_was_defined_without_a_getter); + } + } + } + else { + if (isAnyLike) { + if (isIdentifier(left) && parentSymbol) { + markLinkedReferences(node, ReferenceHint.Property, /*propSymbol*/ undefined, leftType); + } + return isErrorType(apparentType) ? errorType : apparentType; + } + prop = getPropertyOfType(apparentType, right.escapedText, /*skipObjectFunctionPropertyAugment*/ isConstEnumObjectType(apparentType), /*includeTypeOnlyMembers*/ node.kind === SyntaxKind.QualifiedName); + } + markLinkedReferences(node, ReferenceHint.Property, prop, leftType); + + 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 = indexInfo.type; + if (compilerOptions.noUncheckedIndexedAccess && getAssignmentTargetKind(node) !== AssignmentKind.Definite) { + propType = getUnionType([propType, missingType]); + } + 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 (indexInfo.declaration && isDeprecatedDeclaration(indexInfo.declaration)) { + addDeprecatedSuggestion(right, [indexInfo.declaration], right.escapedText as string); + } + } + else { + const targetPropSymbol = resolveAliasWithDeprecationCheck(prop, right); + if (isDeprecatedSymbol(targetPropSymbol) && isUncalledFunctionReference(node, targetPropSymbol) && targetPropSymbol.declarations) { + addDeprecatedSuggestion(right, targetPropSymbol.declarations, right.escapedText as string); + } + checkPropertyNotUsedBeforeDeclaration(prop, node, right); + markPropertyAsReferenced(prop, node, isSelfTypeAccess(left, parentSymbol)); + getNodeLinks(node).resolvedSymbol = prop; + checkPropertyAccessibility(node, left.kind === SyntaxKind.SuperKeyword, isWriteAccess(node), 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; + } + + propType = isThisPropertyAccessInConstructor(node, prop) ? autoType : writeOnly || isWriteOnlyAccess(node) ? 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); + const suggestionHasNoExtendsOrDecorators = !suggestion?.valueDeclaration + || !isClassLike(suggestion.valueDeclaration) + || suggestion.valueDeclaration.heritageClauses?.length + || classOrConstructorParameterIsDecorated(/*useLegacyDecorators*/ false, suggestion.valueDeclaration); + return !(file !== declarationFile && !!declarationFile && isGlobalSourceFile(declarationFile)) + && !(excludeClasses && suggestion && suggestion.flags & SymbolFlags.Class && suggestionHasNoExtendsOrDecorators) + && !(!!node && excludeClasses && isPropertyAccessExpression(node) && node.expression.kind === SyntaxKind.ThisKeyword && suggestionHasNoExtendsOrDecorators); + } + } + 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; + } + + let diagnosticMessage; + const declarationName = idText(right); + if ( + isInPropertyInitializerOrClassStaticBlock(node) + && !isOptionalPropertyDeclaration(valueDeclaration) + && !(isAccessExpression(node) && isAccessExpression(node.expression)) + && !isBlockScopedNameDeclaredBeforeUse(valueDeclaration, right) + && !(isMethodDeclaration(valueDeclaration) && getCombinedModifierFlagsCached(valueDeclaration) & ModifierFlags.Static) + && (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); + } + + if (diagnosticMessage) { + addRelatedInfo(diagnosticMessage, createDiagnosticForNode(valueDeclaration, Diagnostics._0_is_declared_here, declarationName)); + } + } + + 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"; + } + }); + } + + /** + * 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; + } + } + } + + function getSuperClass(classType: InterfaceType): Type | undefined { + const x = getBaseTypes(classType); + if (x.length === 0) { + return undefined; + } + return getIntersectionType(x); + } + + 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); + } + 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); + } + } + } + } + const resultDiagnostic = createDiagnosticForNodeFromMessageChain(getSourceFileOfNode(propNode), 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 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 typeFeatures = allFeatures.get(missingName); + return typeFeatures && firstIterator(typeFeatures.keys()); + } + + function getSuggestedLibForNonExistentProperty(missingProperty: string, containingType: Type) { + const container = getApparentType(containingType).symbol; + if (!container) { + return undefined; + } + const containingTypeName = symbolName(container); + const allFeatures = getScriptTargetFeatures(); + const typeFeatures = allFeatures.get(containingTypeName); + if (typeFeatures) { + for (const [libTarget, featuresOfType] of typeFeatures) { + if (contains(featuresOfType, missingProperty)) { + return libTarget; + } + } + } + } + + function getSuggestedSymbolForNonexistentClassMember(name: string, baseType: Type): Symbol | undefined { + return getSpellingSuggestionForName(name, getPropertiesOfType(baseType), SymbolFlags.ClassMember); + } + + 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 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 getSuggestionForSymbolNameLookup(symbols: SymbolTable, name: __String, meaning: SymbolFlags) { + 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); + } + function getSuggestedSymbolForNonexistentSymbol(location: Node | undefined, outerName: __String, meaning: SymbolFlags): Symbol | undefined { + Debug.assert(outerName !== undefined, "outername should always be defined"); + const result = resolveNameForSymbolSuggestion(location, outerName, meaning, /*nameNotFoundMessage*/ undefined, /*isUse*/ false, /*excludeGlobals*/ false); + return result; + } + + function getSuggestedSymbolForNonexistentModule(name: Identifier, targetModule: Symbol): Symbol | undefined { + return targetModule.exports && getSpellingSuggestionForName(idText(name), getExportsOfModuleAsArray(targetModule), SymbolFlags.ModuleMember); + } + + 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; + } + + const suggestedMethod = isAssignmentTarget(expr) ? "set" : "get"; + if (!hasProp(suggestedMethod)) { + return undefined; + } + + let suggestion = tryGetPropertyAccessOrIdentifierToString(expr.expression); + if (suggestion === undefined) { + suggestion = suggestedMethod; + } + else { + suggestion += "." + suggestedMethod; + } + + return 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); + } + + /** + * 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; + } + + if (candidate.flags & meaning) { + return candidateName; + } + + if (candidate.flags & SymbolFlags.Alias) { + const alias = tryResolveAlias(candidate); + if (alias && alias.flags & meaning) { + return 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 (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; + } + } + + (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)); + } + } + + /** + * 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. + } + + function isValidPropertyAccessWithType( + node: PropertyAccessExpression | QualifiedName | ImportTypeNode, + isSuper: boolean, + propertyName: __String, + type: Type, + ): boolean { + // Short-circuiting for improved performance. + if (isTypeAny(type)) { + return true; + } + + const prop = getPropertyOfType(type, propertyName); + return !!prop && isPropertyAccessible(node, isSuper, /*isWrite*/ false, type, prop); + } + + /** + * 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; + } + + // 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 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 getSymbolOfDeclaration(variable); + } + } + else if (initializer.kind === SyntaxKind.Identifier) { + return getResolvedSymbol(initializer as Identifier); + } + 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); + } + + /** + * 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); + } + + 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); + + if (isErrorType(objectType) || objectType === silentNeverType) { + return objectType; + } + + if (isConstEnumObjectType(objectType) && !isStringLiteralLike(indexExpression)) { + error(indexExpression, Diagnostics.A_const_enum_member_can_only_be_accessed_using_a_string_literal); + return errorType; + } + + const effectiveIndexType = isForInVariableForNumericPropertyNames(indexExpression) ? numberType : indexType; + const assignmentTargetKind = getAssignmentTargetKind(node); + let accessFlags: AccessFlags; + if (assignmentTargetKind === AssignmentKind.None) { + accessFlags = AccessFlags.ExpressionPosition; + } + else { + accessFlags = AccessFlags.Writing | (isGenericObjectType(objectType) && !isThisTypeParameter(objectType) ? AccessFlags.NoIndexSignatures : 0); + if (assignmentTargetKind === AssignmentKind.Compound) { + accessFlags |= AccessFlags.ExpressionPosition; + } + } + const indexedAccessType = getIndexedAccessTypeOrUndefined(objectType, effectiveIndexType, accessFlags, node) || errorType; + return checkIndexedAccessIndexType(getFlowTypeOfAccessExpression(node, getNodeLinks(node).resolvedSymbol, indexedAccessType, indexExpression, checkMode), node); + } + + function callLikeExpressionMayHaveTypeArguments(node: CallLikeExpression): node is CallExpression | NewExpression | TaggedTemplateExpression | JsxOpeningElement { + return isCallOrNewExpression(node) || isTaggedTemplateExpression(node) || isJsxOpeningLikeElement(node); + } + + 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 (isBinaryExpression(node)) { + checkExpression(node.left); + } + else if (isCallOrNewExpression(node)) { + forEach(node.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 && getSymbolOfDeclaration(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; + } + + result.splice(spliceIndex, 0, callChainFlags ? getOptionalCallSignature(signature, callChainFlags) : signature); + } + } + + function isSpreadArgument(arg: Expression | undefined): arg is Expression { + return !!arg && (arg.kind === SyntaxKind.SpreadElement || arg.kind === SyntaxKind.SyntheticExpression && (arg as SyntheticExpression).isSpread); + } + + function getSpreadArgumentIndex(args: readonly Expression[]): number { + return findIndex(args, isSpreadArgument); + } + + 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; + } + 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 (node.kind === SyntaxKind.BinaryExpression) { + argCount = 1; + } + 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 + } + 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; + + // If we are missing the close parenthesis, the call is incomplete. + callIsIncomplete = node.arguments.end === node.end; + + // 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)); + } + } + + // Too many arguments implies incorrect arity. + if (!hasEffectiveRestParameter(signature) && argCount > effectiveParameterCount) { + return false; + } + + // 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; + } + } + 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); + } + + function isInstantiatedGenericParameter(signature: Signature, pos: number) { + let type; + return !!(signature.target && (type = tryGetTypeAtPosition(signature.target, pos)) && isGenericType(type)); + } + + // 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); + } + + function getSingleCallOrConstructSignature(type: Type): Signature | undefined { + return getSingleSignature(type, SignatureKind.Call, /*allowMembers*/ false) || + getSingleSignature(type, SignatureKind.Construct, /*allowMembers*/ false); + } + + 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; + } + + // 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); + } + + function getThisArgumentType(thisArgumentNode: Expression | undefined) { + if (!thisArgumentNode) { + return voidType; + } + const thisArgumentType = checkExpression(thisArgumentNode); + return isRightSideOfInstanceofExpression(thisArgumentNode) ? thisArgumentType : + isOptionalChainRoot(thisArgumentNode.parent) ? getNonNullableType(thisArgumentType) : + isOptionalChain(thisArgumentNode.parent) ? removeOptionalTypeMarker(thisArgumentType) : + thisArgumentType; + } + + function inferTypeArguments(node: CallLikeExpression, signature: Signature, args: readonly Expression[], checkMode: CheckMode, context: InferenceContext): Type[] { + if (isJsxOpeningLikeElement(node)) { + return inferJsxTypeArguments(node, signature, checkMode, context); + } + + // 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 && node.kind !== SyntaxKind.BinaryExpression) { + 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) { + 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(getElementTypes(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) { + const inConstContext = isConstTypeVariable(restType); + + 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. + const spreadType = arg.kind === SyntaxKind.SyntheticExpression ? (arg as SyntheticExpression).type : + checkExpressionWithContextualType((arg as SpreadElement).expression, restType, context, checkMode); + + if (isArrayLikeType(spreadType)) { + return getMutableArrayOrTupleType(spreadType); + } + + return createArrayType(checkIteratedTypeOrElementType(IterationUse.Spread, spreadType, undefinedType, arg.kind === SyntaxKind.SpreadElement ? (arg as SpreadElement).expression : arg), inConstContext); + } + } + 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); + } + } + else { + const contextualType = isTupleType(restType) ? + getContextualTypeForElementExpression(restType, i - index, argCount - index) || unknownType : + getIndexedAccessType(restType, getNumberLiteralType(i - index), AccessFlags.Contextual); + const argType = checkExpressionWithContextualType(arg, contextualType, context, checkMode); + const hasPrimitiveContextualType = inConstContext || 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!); + } + else { + names.push(undefined); + } + } + return createTupleType(types, flags, inConstContext && !someType(restType, isMutableArrayLikeType), names); + } + + 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 (isJsxIntrinsicTagName(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; + } + + /** + * 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: Map, + 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); + const checkAttributesType = checkMode & CheckMode.SkipContextSensitive ? getRegularTypeOfObjectLiteral(attributesType) : attributesType; + return checkTagNameDoesNotExpectTooManyArguments() && checkTypeRelatedToAndOptionallyElaborate( + checkAttributesType, + 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)) && !(isJsxIntrinsicTagName(node.tagName) || isJsxNamespacedName(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; + } + + 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; + } + } + } + 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); + } + } + return false; + } + } + + function getEffectiveCheckNode(argument: Expression): Expression { + argument = skipParentheses(argument); + return isSatisfiesExpression(argument) ? skipParentheses(argument.expression) : argument; + } + + function getSignatureApplicabilityError( + node: CallLikeExpression, + args: readonly Expression[], + signature: Signature, + relation: Map, + checkMode: CheckMode, + reportErrors: boolean, + containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, + inferenceContext: InferenceContext | 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; + } + const thisType = getThisTypeOfSignature(signature); + if (thisType && thisType !== voidType && !(isNewExpression(node) || isCallExpression(node) && isSuperProperty(node.expression))) { + // 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 or super call 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 regularArgType = checkMode & CheckMode.SkipContextSensitive ? getRegularTypeOfObjectLiteral(argType) : argType; + // If this was inferred under a given inference context, we may need to instantiate the expression type to finish resolving + // the type variables in the expression. + const checkArgType = inferenceContext ? instantiateType(regularArgType, inferenceContext.nonFixingMapper) : regularArgType; + const effectiveCheckArgumentNode = getEffectiveCheckNode(arg); + if (!checkTypeRelatedToAndOptionallyElaborate(checkArgType, paramType, relation, reportErrors ? effectiveCheckArgumentNode : undefined, effectiveCheckArgumentNode, 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 ? getEffectiveCheckNode(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; + + 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): Expression | undefined { + if (node.kind === SyntaxKind.BinaryExpression) { + return node.right; + } + + const expression = node.kind === SyntaxKind.CallExpression ? node.expression : + node.kind === SyntaxKind.TaggedTemplateExpression ? node.tag : + node.kind === SyntaxKind.Decorator && !legacyDecorators ? node.expression : + 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); + setTextRangeWorker(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); + }); + } + return args; + } + if (node.kind === SyntaxKind.Decorator) { + return getEffectiveDecoratorArguments(node); + } + if (node.kind === SyntaxKind.BinaryExpression) { + return [node.left]; + } + 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(getElementTypes(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); + } + } + return effectiveArgs; + } + return args; + } + + /** + * Returns the synthetic argument list for a decorator invocation. + */ + function getEffectiveDecoratorArguments(node: Decorator): readonly Expression[] { + const expr = node.expression; + const signature = getDecoratorCallSignature(node); + if (signature) { + const args: Expression[] = []; + for (const param of signature.parameters) { + const type = getTypeOfSymbol(param); + args.push(createSyntheticExpression(expr, type)); + } + return args; + } + return Debug.fail(); + } + + /** + * Returns the argument count for a decorator node that works like a function invocation. + */ + function getDecoratorArgumentCount(node: Decorator, signature: Signature) { + return compilerOptions.experimentalDecorators ? + getLegacyDecoratorArgumentCount(node, signature) : + // Allow the runtime to oversupply arguments to an ES decorator as long as there's at least one parameter. + Math.min(Math.max(getParameterCount(signature), 1), 2); + } + + /** + * Returns the argument count for a decorator node that works like a function invocation. + */ + function getLegacyDecoratorArgumentCount(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 decorators with only two parameters we supply only two arguments + return signature.parameters.length <= 2 ? 2 : 3; + case SyntaxKind.Parameter: + return 3; + default: + return Debug.fail(); + } + } + + function getDiagnosticSpanForCallNode(node: CallExpression) { + const sourceFile = getSourceFileOfNode(node); + const { start, length } = getErrorSpanForNode(sourceFile, isPropertyAccessExpression(node.expression) ? node.expression.name : node.expression); + return { start, length, sourceFile }; + } + + function getDiagnosticForCallNode(node: CallLikeExpression, message: DiagnosticMessage | DiagnosticMessageChain, ...args: DiagnosticArguments): DiagnosticWithLocation { + if (isCallExpression(node)) { + const { sourceFile, start, length } = getDiagnosticSpanForCallNode(node); + if ("message" in message) { // eslint-disable-line local/no-in-operator + return createFileDiagnostic(sourceFile, start, length, message, ...args); + } + return createDiagnosticForFileFromMessageChain(sourceFile, message); + } + else { + if ("message" in message) { // eslint-disable-line local/no-in-operator + return createDiagnosticForNode(node, message, ...args); + } + return createDiagnosticForNodeFromMessageChain(getSourceFileOfNode(node), node, message); + } + } + + function getErrorNodeForCallNode(callLike: CallLikeExpression): Node { + if (isCallOrNewExpression(callLike)) { + return isPropertyAccessExpression(callLike.expression) ? callLike.expression.name : callLike.expression; + } + if (isTaggedTemplateExpression(callLike)) { + return isPropertyAccessExpression(callLike.tag) ? callLike.tag.name : callLike.tag; + } + if (isJsxOpeningLikeElement(callLike)) { + return callLike.tagName; + } + return callLike; + } + + function isPromiseResolveArityError(node: CallLikeExpression) { + if (!isCallExpression(node) || !isIdentifier(node.expression)) return false; + + const symbol = resolveName(node.expression, node.expression.escapedText, SymbolFlags.Value, /*nameNotFoundMessage*/ undefined, /*isUse*/ 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[], headMessage?: DiagnosticMessage) { + 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 = isDecorator(node) ? + hasRestParameter ? Diagnostics.The_runtime_will_invoke_the_decorator_with_1_arguments_but_the_decorator_expects_at_least_0 : + Diagnostics.The_runtime_will_invoke_the_decorator_with_1_arguments_but_the_decorator_expects_0 : + 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 + if (headMessage) { + let chain = chainDiagnosticMessages(/*details*/ undefined, Diagnostics.No_overload_expects_0_arguments_but_overloads_do_exist_that_expect_either_1_or_2_arguments, args.length, maxBelow, minAbove); + chain = chainDiagnosticMessages(chain, headMessage); + return getDiagnosticForCallNode(node, chain); + } + 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 + let diagnostic: Diagnostic; + if (headMessage) { + let chain = chainDiagnosticMessages(/*details*/ undefined, error, parameterRange, args.length); + chain = chainDiagnosticMessages(chain, headMessage); + diagnostic = getDiagnosticForCallNode(node, chain); + } + else { + diagnostic = getDiagnosticForCallNode(node, error, parameterRange, args.length); + } + const parameter = closestSignature?.declaration?.parameters[closestSignature.thisParameter ? args.length + 1 : args.length]; + if (parameter) { + const messageAndArgs: DiagnosticAndArguments = 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, idText(getFirstIdentifier(parameter.name))] + : [Diagnostics.An_argument_for_0_was_not_provided, !parameter.name ? args.length : idText(getFirstIdentifier(parameter.name))]; + const parameterError = createDiagnosticForNode(parameter, ...messageAndArgs); + return addRelatedInfo(diagnostic, parameterError); + } + 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); + if (headMessage) { + let chain = chainDiagnosticMessages(/*details*/ undefined, error, parameterRange, args.length); + chain = chainDiagnosticMessages(chain, headMessage); + return createDiagnosticForNodeArrayFromMessageChain(getSourceFileOfNode(node), errorSpan, chain); + } + return createDiagnosticForNodeArray(getSourceFileOfNode(node), errorSpan, error, parameterRange, args.length); + } + } + + function getTypeArgumentArityError(node: Node, signatures: readonly Signature[], typeArguments: NodeArray, headMessage?: DiagnosticMessage) { + 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); + if (headMessage) { + let chain = chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Expected_0_type_arguments_but_got_1, min < max ? min + "-" + max : min, argCount); + chain = chainDiagnosticMessages(chain, headMessage); + return createDiagnosticForNodeArrayFromMessageChain(getSourceFileOfNode(node), typeArguments, chain); + } + 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); + } + } + if (belowArgCount !== -Infinity && aboveArgCount !== Infinity) { + if (headMessage) { + let chain = chainDiagnosticMessages(/*details*/ undefined, Diagnostics.No_overload_expects_0_type_arguments_but_overloads_do_exist_that_expect_either_1_or_2_type_arguments, argCount, belowArgCount, aboveArgCount); + chain = chainDiagnosticMessages(chain, headMessage); + return createDiagnosticForNodeArrayFromMessageChain(getSourceFileOfNode(node), typeArguments, chain); + } + 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); + } + if (headMessage) { + let chain = chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Expected_0_type_arguments_but_got_1, belowArgCount === -Infinity ? aboveArgCount : belowArgCount, argCount); + chain = chainDiagnosticMessages(chain, headMessage); + return createDiagnosticForNodeArrayFromMessageChain(getSourceFileOfNode(node), typeArguments, chain); + } + 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, headMessage?: DiagnosticMessage): Signature { + const isTaggedTemplate = node.kind === SyntaxKind.TaggedTemplateExpression; + const isDecorator = node.kind === SyntaxKind.Decorator; + const isJsxOpeningOrSelfClosingElement = isJsxOpeningLikeElement(node); + const isInstanceof = node.kind === SyntaxKind.BinaryExpression; + const reportErrors = !isInferencePartiallyBlocked && !candidatesOutArray; + + let typeArguments: NodeArray | undefined; + + if (!isDecorator && !isInstanceof && !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); + } + } + + const candidates = candidatesOutArray || []; + // reorderCandidates fills up the candidates array directly + reorderCandidates(signatures, candidates, callChainFlags); + Debug.assert(candidates.length, "Revert #54442 and add a testcase with whatever triggered this"); + + 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; + + // 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 the call expression is a synthetic call to a `[Symbol.hasInstance]` method then we will produce a head + // message when reporting diagnostics that explains how we got to `right[Symbol.hasInstance](left)` from + // `left instanceof right`, as it pertains to "Argument" related messages reported for the call. + if (!headMessage && isInstanceof) { + headMessage = Diagnostics.The_left_hand_side_of_an_instanceof_expression_must_be_assignable_to_the_first_argument_of_the_right_hand_side_s_Symbol_hasInstance_method; + } + 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); + } + if (headMessage) { + chain = chainDiagnosticMessages(chain, headMessage); + } + const diags = getSignatureApplicabilityError(node, args, last, assignableRelation, CheckMode.Normal, /*reportErrors*/ true, () => chain, /*inferenceContext*/ undefined); + 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); + } + } + else { + 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, /*inferenceContext*/ undefined); + 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"); + } + i++; + } + + const diags = max > 1 ? allDiagnostics[minIndex] : flatten(allDiagnostics); + Debug.assert(diags.length > 0, "No errors reported for 3 or fewer overload signatures"); + let chain = chainDiagnosticMessages( + map(diags, createDiagnosticMessageChainFromDiagnostic), + Diagnostics.No_overload_matches_this_call, + ); + if (headMessage) { + chain = chainDiagnosticMessages(chain, headMessage); + } + // 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(getSourceFileOfNode(node), getErrorNodeForCallNode(node), chain, related); + } + addImplementationSuccessElaboration(candidatesForArgumentError[0], diag); + diagnostics.add(diag); + } + } + else if (candidateForArgumentArityError) { + diagnostics.add(getArgumentArityError(node, [candidateForArgumentArityError], args, headMessage)); + } + else if (candidateForTypeArgumentError) { + checkTypeArguments(candidateForTypeArgumentError, (node as CallExpression | TaggedTemplateExpression | JsxOpeningLikeElement).typeArguments!, /*reportErrors*/ true, headMessage); + } + else { + const signaturesWithCorrectTypeArgumentArity = filter(signatures, s => hasCorrectTypeArgumentArity(s, typeArguments)); + if (signaturesWithCorrectTypeArgumentArity.length === 0) { + diagnostics.add(getTypeArgumentArityError(node, signatures, typeArguments!, headMessage)); + } + else { + diagnostics.add(getArgumentArityError(node, signaturesWithCorrectTypeArgumentArity, args, headMessage)); + } + } + } + + return result; + + 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)); + } + } + + candidatesForArgumentError = oldCandidatesForArgumentError; + candidateForArgumentArityError = oldCandidateForArgumentArityError; + candidateForTypeArgumentError = oldCandidateForTypeArgumentError; + } + + function chooseOverload(candidates: Signature[], relation: Map, 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, /*inferenceContext*/ undefined)) { + candidatesForArgumentError = [candidate]; + return undefined; + } + return candidate; + } + + for (let candidateIndex = 0; candidateIndex < candidates.length; candidateIndex++) { + let candidate = candidates[candidateIndex]; + if (!hasCorrectTypeArgumentArity(candidate, typeArguments) || !hasCorrectArity(node, args, candidate, signatureHelpTrailingComma)) { + continue; + } + + let checkCandidate: Signature; + let inferenceContext: InferenceContext | undefined; + + if (candidate.typeParameters) { + // If we are *inside the body of candidate*, we need to create a clone of `candidate` with differing type parameter identities, + // so our inference results for this call doesn't pollute expression types referencing the outer type parameter! + const paramLocation = candidate.typeParameters[0].symbol.declarations?.[0]?.parent; + const candidateParameterContext = paramLocation || (candidate.declaration && isConstructorDeclaration(candidate.declaration) ? candidate.declaration.parent : candidate.declaration); + if (candidateParameterContext && findAncestor(node, a => a === candidateParameterContext)) { + candidate = getImplementationSignature(candidate); + } + let typeArgumentTypes: readonly 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); + // The resulting type arguments are instantiated with the inference context mapper, as the inferred types may still contain references to the inference context's + // type variables via contextual projection. These are kept generic until all inferences are locked in, so the dependencies expressed can pass constraint checks. + typeArgumentTypes = instantiateTypes(inferTypeArguments(node, candidate, args, argCheckMode | CheckMode.SkipGenericFunctions, inferenceContext), inferenceContext.nonFixingMapper); + 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, inferenceContext)) { + // 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.Normal; + if (inferenceContext) { + const typeArgumentTypes = instantiateTypes(inferTypeArguments(node, candidate, args, argCheckMode, inferenceContext), inferenceContext.mapper); + 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, inferenceContext)) { + // 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; + } + } + + // 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 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.IsSignatureCandidateForOverloadFailure; + 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)), + /*resolvedTypePredicate*/ undefined, + minArgumentCount, + flags, + ); + } + + function getNumNonRestParameters(signature: Signature): number { + const numParams = signature.parameters.length; + return signatureHasRestParameter(signature) ? numParams - 1 : numParams; + } + + function createCombinedSymbolFromTypes(sources: readonly Symbol[], types: Type[]): Symbol { + return createCombinedSymbolForOverloadFailure(sources, getUnionType(types, UnionReduction.Subtype)); + } + + 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 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 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; + } + if (paramCount > maxParams) { + maxParams = paramCount; + maxParamsIndex = i; + } + } + + return maxParamsIndex; + } + + 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); + } + + 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; + } + + 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); + } + + // 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 { + 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); + } + + return resolveCall(node, callSignatures, candidatesOutArray, checkMode, callChainFlags); + } + + function isGenericFunctionReturningFunction(signature: Signature) { + return !!(signature.typeParameters && isFunctionType(getReturnTypeOfSignature(signature))); + } + + /** + * 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); + } + + function resolveNewExpression(node: NewExpression, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature { + let expressionType = checkNonNullExpression(node.expression); + if (expressionType === silentNeverType) { + return silentNeverSignature; + } + + // 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); + } + + // 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); + } + + // 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); + } + 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); + } + + 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); + } + 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); + } + + 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; + } + 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; + } + if (firstBase.symbol === target) { + return true; + } + return typeHasProtectedAccessibleBase(target, firstBase as InterfaceType); + } + + function isConstructorAccessible(node: NewExpression, signature: Signature) { + if (!signature || !signature.declaration) { + return true; + } + + 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; + } + + 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)); + } + return false; + } + + 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; + } + } + else { + // Error on the first non callable constituent only + if (!errorInfo) { + errorInfo = chainDiagnosticMessages( + errorInfo, + isCall ? + Diagnostics.Type_0_has_no_call_signatures : + Diagnostics.Type_0_has_no_construct_signatures, + typeToString(constituent), + ); + errorInfo = chainDiagnosticMessages( + errorInfo, + isCall ? + Diagnostics.Not_all_constituents_of_type_0_are_callable : + Diagnostics.Not_all_constituents_of_type_0_are_constructable, + typeToString(apparentType), + ); + } + if (hasSignatures) { + // Bail early if we already found a siganture, no chance of "No constituent of type is callable" + break; + } + } + } + if (!hasSignatures) { + errorInfo = chainDiagnosticMessages( + /*details*/ undefined, + isCall ? + Diagnostics.No_constituent_of_type_0_is_callable : + Diagnostics.No_constituent_of_type_0_is_constructable, + typeToString(apparentType), + ); + } + if (!errorInfo) { + errorInfo = chainDiagnosticMessages( + errorInfo, + isCall ? + Diagnostics.Each_member_of_the_union_type_0_has_signatures_but_none_of_those_signatures_are_compatible_with_each_other : + Diagnostics.Each_member_of_the_union_type_0_has_construct_signatures_but_none_of_those_signatures_are_compatible_with_each_other, + typeToString(apparentType), + ); + } + } + else { + errorInfo = chainDiagnosticMessages( + errorInfo, + isCall ? + Diagnostics.Type_0_has_no_call_signatures : + Diagnostics.Type_0_has_no_construct_signatures, + typeToString(apparentType), + ); + } + + 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; + } + } + + 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(getSourceFileOfNode(errorTarget), errorTarget, messageChain); + if (relatedInfo) { + addRelatedInfo(diagnostic, createDiagnosticForNode(errorTarget, relatedInfo)); + } + if (isCallExpression(errorTarget.parent)) { + const { start, length } = getDiagnosticSpanForCallNode(errorTarget.parent); + 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; + + 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 resolveTaggedTemplateExpression(node: TaggedTemplateExpression, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature { + const tagType = checkExpression(node.tag); + const apparentType = getApparentType(tagType); + + if (isErrorType(apparentType)) { + // Another error has already been reported + return resolveErrorCall(node); + } + + const callSignatures = getSignaturesOfType(apparentType, SignatureKind.Call); + const numConstructSignatures = getSignaturesOfType(apparentType, SignatureKind.Construct).length; + + if (isUntypedFunctionCall(tagType, apparentType, callSignatures.length, numConstructSignatures)) { + return resolveUntypedCall(node); + } + + 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); + } + + invocationError(node.tag, apparentType, SignatureKind.Call); + return resolveErrorCall(node); + } + + return resolveCall(node, callSignatures, candidatesOutArray, checkMode, SignatureFlags.None); + } + + /** + * 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; + + case SyntaxKind.PropertyDeclaration: + return Diagnostics.Unable_to_resolve_signature_of_property_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; + + default: + return Debug.fail(); + } + } + + /** + * 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 callSignatures = getSignaturesOfType(apparentType, SignatureKind.Call); + const numConstructSignatures = getSignaturesOfType(apparentType, SignatureKind.Construct).length; + if (isUntypedFunctionCall(funcType, apparentType, callSignatures.length, numConstructSignatures)) { + return resolveUntypedCall(node); + } + + if (isPotentiallyUncalledDecorator(node, callSignatures) && !isParenthesizedExpression(node.expression)) { + 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); + } + + const headMessage = getDiagnosticHeadMessageForDecoratorResolution(node); + if (!callSignatures.length) { + const errorDetails = invocationErrorDetails(node.expression, apparentType, SignatureKind.Call); + const messageChain = chainDiagnosticMessages(errorDetails.messageChain, headMessage); + const diag = createDiagnosticForNodeFromMessageChain(getSourceFileOfNode(node.expression), node.expression, messageChain); + if (errorDetails.relatedMessage) { + addRelatedInfo(diag, createDiagnosticForNode(node.expression, errorDetails.relatedMessage)); + } + diagnostics.add(diag); + invocationErrorRecovery(apparentType, SignatureKind.Call, diag); + return resolveErrorCall(node); + } + + return resolveCall(node, callSignatures, candidatesOutArray, checkMode, SignatureFlags.None, headMessage); + } + + 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, /*dotDotDotToken*/ undefined, "props", /*questionToken*/ undefined, nodeBuilder.typeToTypeNode(result, node))], returnNode ? factory.createTypeReferenceNode(returnNode, /*typeArguments*/ undefined) : factory.createKeywordTypeNode(SyntaxKind.AnyKeyword)); + const parameterSymbol = createSymbol(SymbolFlags.FunctionScopedVariable, "props" as __String); + parameterSymbol.links.type = result; + return createSignature( + declaration, + /*typeParameters*/ undefined, + /*thisParameter*/ undefined, + [parameterSymbol], + typeSymbol ? getDeclaredTypeOfSymbol(typeSymbol) : errorType, + /*resolvedTypePredicate*/ undefined, + 1, + SignatureFlags.None, + ); + } + + function resolveJsxOpeningLikeElement(node: JsxOpeningLikeElement, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature { + if (isJsxIntrinsicTagName(node.tagName)) { + const result = getIntrinsicAttributesTypeFromJsxOpeningLikeElement(node); + const fakeSignature = createSignatureForJSXIntrinsic(node, result); + checkTypeAssignableToAndOptionallyElaborate(checkExpressionWithContextualType(node.attributes, getEffectiveFirstArgumentForJsxSignature(fakeSignature, node), /*inferenceContext*/ 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); + } + + const signatures = getUninstantiatedJsxSignaturesOfType(exprTypes, node); + if (isUntypedFunctionCall(exprTypes, apparentType, signatures.length, /*constructSignatures*/ 0)) { + return resolveUntypedCall(node); + } + + 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); + } + + return resolveCall(node, signatures, candidatesOutArray, checkMode, SignatureFlags.None); + } + + function resolveInstanceofExpression(node: InstanceofExpression, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature { + // if rightType is an object type with a custom `[Symbol.hasInstance]` method, then it is potentially + // valid on the right-hand side of the `instanceof` operator. This allows normal `object` types to + // participate in `instanceof`, as per Step 2 of https://tc39.es/ecma262/#sec-instanceofoperator. + const rightType = checkExpression(node.right); + if (!isTypeAny(rightType)) { + const hasInstanceMethodType = getSymbolHasInstanceMethodOfObjectType(rightType); + if (hasInstanceMethodType) { + const apparentType = getApparentType(hasInstanceMethodType); + if (isErrorType(apparentType)) { + return resolveErrorCall(node); + } + + const callSignatures = getSignaturesOfType(apparentType, SignatureKind.Call); + const constructSignatures = getSignaturesOfType(apparentType, SignatureKind.Construct); + if (isUntypedFunctionCall(hasInstanceMethodType, apparentType, callSignatures.length, constructSignatures.length)) { + return resolveUntypedCall(node); + } + + if (callSignatures.length) { + return resolveCall(node, callSignatures, candidatesOutArray, checkMode, SignatureFlags.None); + } + } + // NOTE: do not raise error if right is unknown as related error was already reported + else if (!(typeHasCallOrConstructSignatures(rightType) || isTypeSubtypeOf(rightType, globalFunctionType))) { + error(node.right, Diagnostics.The_right_hand_side_of_an_instanceof_expression_must_be_either_of_type_any_a_class_function_or_other_type_assignable_to_the_Function_interface_type_or_an_object_type_with_a_Symbol_hasInstance_method); + return resolveErrorCall(node); + } + } + // fall back to a default signature + return anySignature; + } + + /** + * 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)); + } + + 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); + case SyntaxKind.BinaryExpression: + return resolveInstanceofExpression(node, candidatesOutArray, checkMode); + } + 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; + } + const saveResolutionStart = resolutionStart; + if (!cached) { + // If we haven't already done so, temporarily reset the resolution stack. This allows us to + // handle "inverted" situations where, for example, an API client asks for the type of a symbol + // containined in a function call argument whose contextual type depends on the symbol itself + // through resolution of the containing function call. By resetting the resolution stack we'll + // retry the symbol type resolution with the resolvingSignature marker in place to suppress + // the contextual type circularity. + resolutionStart = resolutionTargets.length; + } + links.resolvedSignature = resolvingSignature; + let result = resolveSignature(node, candidatesOutArray, checkMode || CheckMode.Normal); + resolutionStart = saveResolutionStart; + // When CheckMode.SkipGenericFunctions is set we use resolvingSignature to indicate that call + // resolution should be deferred. + if (result !== resolvingSignature) { + // if the signature resolution originated on a node that itself depends on the contextual type + // then it's possible that the resolved signature might not be the same as the one that would be computed in source order + // since resolving such signature leads to resolving the potential outer signature, its arguments and thus the very same signature + // it's possible that this inner resolution sets the resolvedSignature first. + // In such a case we ignore the local result and reuse the correct one that was cached. + if (links.resolvedSignature !== resolvingSignature) { + result = links.resolvedSignature; + } + // 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; + + // 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 = getSymbolOfDeclaration(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); + 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; + } + 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 ? getSymbolOfDeclaration(init) : undefined; + } + + 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) && !(isVarConstLike(node.parent) && isFunctionLikeDeclaration(node))) { + return undefined; + } + 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 (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; + } + } + } + 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; + } + } + + /** + * 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); + + 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); + + if (node.expression.kind === SyntaxKind.SuperKeyword) { + return voidType; + } + + if (node.kind === SyntaxKind.NewExpression) { + const declaration = signature.declaration; + + if ( + declaration && + declaration.kind !== SyntaxKind.Constructor && + declaration.kind !== SyntaxKind.ConstructSignature && + declaration.kind !== SyntaxKind.ConstructorType && + !(isJSDocSignature(declaration) && getJSDocRoot(declaration)?.parent?.kind === SyntaxKind.Constructor) && + !isJSDocConstructSignature(declaration) && + !isJSConstructor(declaration) + ) { + // 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; + } + } + + // 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); + } + + 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 (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 returnType; + } + + function checkDeprecatedSignature(signature: Signature, node: CallLikeExpression) { + if (signature.flags & SignatureFlags.IsSignatureCandidateForOverloadFailure) return; + 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 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; + } + } + + 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; + } + + // make sure `Symbol` is the global symbol + const globalESSymbol = getGlobalESSymbolConstructorSymbol(/*reportErrors*/ false); + if (!globalESSymbol) { + return false; + } + + return globalESSymbol === resolveName(left, "Symbol" as __String, SymbolFlags.Value, /*nameNotFoundMessage*/ undefined, /*isUse*/ false); + } + + function checkImportCallExpression(node: ImportCall): Type { + // Check grammar of dynamic import + checkGrammarImportCallExpression(node); + + if (node.arguments.length === 0) { + return createPromiseReturnType(node, anyType); + } + + 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]); + } + + 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)); + } + + if (optionsType) { + const importCallOptionsType = getGlobalImportCallOptionsType(/*reportErrors*/ true); + if (importCallOptionsType !== emptyObjectType) { + checkTypeAssignableTo(optionsType, getNullableType(importCallOptionsType, TypeFlags.Undefined), node.arguments[1]); + } + } + + // resolveExternalModuleName will return undefined if the moduleReferenceExpression is not a string literal + const moduleSymbol = resolveExternalModuleName(node, specifier); + if (moduleSymbol) { + const esModuleSymbol = resolveESModuleSymbol(moduleSymbol, specifier, /*dontResolveAlias*/ true, /*suppressInteropError*/ false); + if (esModuleSymbol) { + return createPromiseReturnType( + node, + getTypeWithSyntheticDefaultOnly(getTypeOfSymbol(esModuleSymbol), esModuleSymbol, moduleSymbol, specifier) || + getTypeWithSyntheticDefaultImportType(getTypeOfSymbol(esModuleSymbol), esModuleSymbol, moduleSymbol, specifier), + ); + } + } + return createPromiseReturnType(node, anyType); + } + + function createDefaultPropertyWrapperForModule(symbol: Symbol, originalSymbol: Symbol | undefined, anonymousSymbol?: Symbol | undefined) { + const memberTable = createSymbolTable(); + const newSymbol = createSymbol(SymbolFlags.Alias, InternalSymbolName.Default); + newSymbol.parent = originalSymbol; + newSymbol.links.nameType = getStringLiteralType("default"); + newSymbol.links.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; + } + return synthType.defaultOnlyType; + } + return undefined; + } + + 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.links.type = defaultContainingObject; + synthType.syntheticType = isValidSpreadType(type) ? getSpreadType(type, defaultContainingObject, anonymousSymbol, /*objectFlags*/ 0, /*readonly*/ false) : defaultContainingObject; + } + else { + synthType.syntheticType = type; + } + } + return synthType.syntheticType; + } + return type; + } + + function isCommonJsRequire(node: Node): boolean { + if (!isRequireCall(node, /*requireStringLiteralLikeArgument*/ 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, /*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); + } + return false; + } + + function checkTaggedTemplateExpression(node: TaggedTemplateExpression): Type { + if (!checkGrammarTaggedTemplateChain(node)) checkGrammarTypeArguments(node, node.typeArguments); + if (languageVersion < LanguageFeatureMinimumTarget.TaggedTemplates) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.MakeTemplateObject); + } + const signature = getResolvedSignature(node); + checkDeprecatedSignature(signature, node); + return getReturnTypeOfSignature(signature); + } + + function checkAssertion(node: AssertionExpression, checkMode: CheckMode | undefined) { + 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 checkAssertionWorker(node, checkMode); + } + + 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 = skipParentheses((node as PropertyAccessExpression | ElementAccessExpression).expression); + const symbol = isEntityNameExpression(expr) ? resolveEntityName(expr, SymbolFlags.Value, /*ignoreErrors*/ true) : undefined; + return !!(symbol && symbol.flags & SymbolFlags.Enum); + } + return false; + } + + function checkAssertionWorker(node: JSDocTypeAssertion | AssertionExpression, checkMode: CheckMode | undefined) { + const { type, expression } = getAssertionTypeAndExpression(node); + const 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); + } + const links = getNodeLinks(node); + links.assertionExpressionType = exprType; + checkSourceElement(type); + checkNodeDeferred(node); + return getTypeFromTypeNode(type); + } + + function getAssertionTypeAndExpression(node: JSDocTypeAssertion | AssertionExpression) { + let type: TypeNode; + let expression: Expression; + switch (node.kind) { + case SyntaxKind.AsExpression: + case SyntaxKind.TypeAssertionExpression: + type = node.type; + expression = node.expression; + break; + case SyntaxKind.ParenthesizedExpression: + type = getJSDocTypeAssertionType(node); + expression = node.expression; + break; + } + + return { type, expression }; + } + + function checkAssertionDeferred(node: JSDocTypeAssertion | AssertionExpression) { + const { type } = getAssertionTypeAndExpression(node); + const errNode = isParenthesizedExpression(node) ? type : node; + const links = getNodeLinks(node); + Debug.assertIsDefined(links.assertionExpressionType); + const exprType = getRegularTypeOfObjectLiteral(getBaseTypeOfLiteralType(links.assertionExpressionType)); + 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); + } + }); + } + } + + function checkNonNullChain(node: NonNullChain) { + const leftType = checkExpression(node.expression); + const nonOptionalType = getOptionalExpressionType(leftType, node.expression); + return propagateOptionalTypeMarker(getNonNullableType(nonOptionalType), node, nonOptionalType !== leftType); + } + + function checkNonNullAssertion(node: NonNullExpression) { + return node.flags & NodeFlags.OptionalChain ? checkNonNullChain(node as NonNullChain) : + getNonNullableType(checkExpression(node.expression)); + } + + function checkExpressionWithTypeArguments(node: ExpressionWithTypeArguments | TypeQueryNode) { + checkGrammarExpressionWithTypeArguments(node); + forEach(node.typeArguments, checkSourceElement); + if (node.kind === SyntaxKind.ExpressionWithTypeArguments) { + const parent = walkUpParenthesizedExpressions(node.parent); + if (parent.kind === SyntaxKind.BinaryExpression && (parent as BinaryExpression).operatorToken.kind === SyntaxKind.InstanceOfKeyword && isNodeDescendantOf(node, (parent as BinaryExpression).right)) { + error(node, Diagnostics.The_right_hand_side_of_an_instanceof_expression_must_not_be_an_instantiation_expression); + } + } + const exprType = node.kind === SyntaxKind.ExpressionWithTypeArguments ? checkExpression(node.expression) : + isThisIdentifier(node.exprName) ? checkThisExpression(node.exprName) : + checkExpression(node.exprName); + return getInstantiationExpressionType(exprType, node); + } + + function getInstantiationExpressionType(exprType: Type, node: NodeWithTypeArguments) { + 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 getInstantiatedType(type: Type): Type { + let hasSignatures = false; + let hasApplicableSignature = false; + const result = getInstantiatedTypePart(type); + hasSomeApplicableSignature ||= hasApplicableSignature; + if (hasSignatures && !hasApplicableSignature) { + nonApplicableType ??= type; + } + return result; + + 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(createSymbol(SymbolFlags.None, InternalSymbolName.InstantiationExpression), 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 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 checkSatisfiesExpression(node: SatisfiesExpression) { + checkSourceElement(node.type); + return checkSatisfiesExpressionWorker(node.expression, node.type); + } + + function checkSatisfiesExpressionWorker(expression: Expression, target: TypeNode, checkMode?: CheckMode) { + const exprType = checkExpression(expression, checkMode); + const targetType = getTypeFromTypeNode(target); + if (isErrorType(targetType)) { + return targetType; + } + const errorNode = findAncestor(target.parent, n => n.kind === SyntaxKind.SatisfiesExpression || n.kind === SyntaxKind.JSDocSatisfiesTag); + checkTypeAssignableToAndOptionallyElaborate(exprType, targetType, errorNode, 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); + } + + if (node.keywordToken === SyntaxKind.ImportKeyword) { + return checkImportMetaProperty(node); + } + + 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 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 = getSymbolOfDeclaration(container.parent); + return getTypeOfSymbol(symbol); + } + else { + const symbol = getSymbolOfDeclaration(container); + return getTypeOfSymbol(symbol); + } + } + + 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; + } + + function getTypeOfParameter(symbol: Symbol) { + const declaration = symbol.valueDeclaration; + return addOptionality( + getTypeOfSymbol(symbol), + /*isProperty*/ false, + /*isOptional*/ !!declaration && (hasInitializer(declaration) || isOptionalDeclaration(declaration)), + ); + } + + function getTupleElementLabel(d: ParameterDeclaration | NamedTupleMember): __String; + function getTupleElementLabel(d: ParameterDeclaration | NamedTupleMember | undefined, index: number, restParameterName?: __String): __String; + function getTupleElementLabel(d: ParameterDeclaration | NamedTupleMember | undefined, index?: number, restParameterName = "arg" as __String) { + if (!d) { + return `${restParameterName}_${index}` as __String; + } + Debug.assert(isIdentifier(d.name)); // Parameter declarations could be binding patterns, but we only allow identifier names + return d.name.escapedText; + } + + 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 getTupleElementLabel(associatedNames?.[index], index, restParameter.escapedName); + } + return restParameter.escapedName; + } + + function getParameterIdentifierInfoAtPosition(signature: Signature, pos: number): { parameter: Identifier; 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]; + const paramIdent = getParameterDeclarationIdentifier(param); + return paramIdent ? { + parameter: paramIdent, + parameterName: param.escapedName, + isRestParameter: false, + } : undefined; + } + + const restParameter = signature.parameters[paramCount] || unknownSymbol; + const restIdent = getParameterDeclarationIdentifier(restParameter); + if (!restIdent) { + return undefined; + } + + 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; + + if (associatedName) { + Debug.assert(isIdentifier(associatedName.name)); + return { parameter: associatedName.name, parameterName: associatedName.name.escapedText, isRestParameter: isRestTupleElement }; + } + + return undefined; + } + + if (pos === paramCount) { + return { parameter: restIdent, parameterName: restParameter.escapedName, isRestParameter: true }; + } + return undefined; + } + + function getParameterDeclarationIdentifier(symbol: Symbol) { + return symbol.valueDeclaration && isParameter(symbol.valueDeclaration) && isIdentifier(symbol.valueDeclaration.name) && 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 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 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.combinedFlags & ElementFlags.Variable || index < restType.target.fixedLength) { + return getIndexedAccessType(restType, getNumberLiteralType(index)); + } + } + return undefined; + } + + function getRestTypeAtPosition(source: Signature, pos: number, readonly?: boolean): 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); + } + else { + types.push(restType); + flags.push(ElementFlags.Variadic); + } + names.push(getNameableDeclarationAtPosition(source, i)); + } + return createTupleType(types, flags, readonly, names); + } + + // Return the rest type at the given position, transforming `any[]` into just `any`. We do this because + // in signatures we want `any[]` in a rest position to be compatible with anything, but `any[]` isn't + // assignable to tuple types with required elements. + function getRestOrAnyTypeAtPosition(source: Signature, pos: number): Type { + const restType = getRestTypeAtPosition(source, pos); + const elementType = restType && getElementTypeOfArrayType(restType); + return elementType && isTypeAny(elementType) ? anyType : restType; + } + + // 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.combinedFlags & ElementFlags.Variable ? 0 : 1); + } + } + 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; + } + 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; + } + minArgumentCount = i; + } + 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.combinedFlags & ElementFlags.Variable); + } + return false; + } + + function getEffectiveRestType(signature: Signature) { + if (signatureHasRestParameter(signature)) { + const restType = getTypeOfSymbol(signature.parameters[signature.parameters.length - 1]); + if (!isTupleType(restType)) { + return isTypeAny(restType) ? anyArrayType : restType; + } + if (restType.target.combinedFlags & ElementFlags.Variable) { + return sliceTupleType(restType, restType.target.fixedLength); + } + } + return undefined; + } + + function getNonArrayRestType(signature: Signature) { + const restType = getEffectiveRestType(signature); + return restType && !isArrayType(restType) && !isTypeAny(restType) ? restType : undefined; + } + + function getTypeOfFirstParameterOfSignature(signature: Signature) { + return getTypeOfFirstParameterOfSignatureWithFallback(signature, neverType); + } + + 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; + const typeNode = getEffectiveTypeAnnotationNode(declaration); + if (typeNode) { + const source = addOptionality(getTypeFromTypeNode(typeNode), /*isProperty*/ false, isOptionalDeclaration(declaration)); + const target = getTypeAtPosition(context, i); + inferTypes(inferenceContext.inferences, source, target); + } + } + } + + 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! + } + } + 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]; + const declaration = parameter.valueDeclaration as ParameterDeclaration; + if (!getEffectiveTypeAnnotationNode(declaration)) { + let type = tryGetTypeAtPosition(context, i); + if (type && declaration.initializer) { + let initializerType = checkDeclarationInitializer(declaration, CheckMode.Normal); + if (!isTypeAssignableTo(initializerType, type) && isTypeAssignableTo(type, initializerType = widenTypeInferredFromInitializer(declaration, initializerType))) { + type = initializerType; + } + } + assignParameterType(parameter, type); + } + } + 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 assignNonContextualParameterTypes(signature: Signature) { + if (signature.thisParameter) { + assignParameterType(signature.thisParameter); + } + for (const parameter of signature.parameters) { + assignParameterType(parameter); + } + } + + function assignParameterType(parameter: Symbol, contextualType?: Type) { + const links = getSymbolLinks(parameter); + if (!links.type) { + const declaration = parameter.valueDeclaration as ParameterDeclaration | undefined; + links.type = addOptionality( + contextualType || (declaration ? getWidenedTypeForVariableLikeDeclaration(declaration, /*reportErrors*/ true) : getTypeOfSymbol(parameter)), + /*isProperty*/ false, + /*isOptional*/ !!declaration && !declaration.initializer && isOptionalDeclaration(declaration), + ); + 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 (contextualType) { + Debug.assertEqual(links.type, contextualType, "Parameter symbol already has a cached type which differs from newly assigned type"); + } + } + + // 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, /*noTupleBoundsCheck*/ false); + if (element.name.kind === SyntaxKind.Identifier) { + getSymbolLinks(getSymbolOfDeclaration(element)).type = type; + } + else { + assignBindingElementTypes(element.name, type); + } + } + } + } + + function createClassDecoratorContextType(classType: Type) { + return tryCreateTypeReference(getGlobalClassDecoratorContextType(/*reportErrors*/ true), [classType]); + } + + function createClassMethodDecoratorContextType(thisType: Type, valueType: Type) { + return tryCreateTypeReference(getGlobalClassMethodDecoratorContextType(/*reportErrors*/ true), [thisType, valueType]); + } + + function createClassGetterDecoratorContextType(thisType: Type, valueType: Type) { + return tryCreateTypeReference(getGlobalClassGetterDecoratorContextType(/*reportErrors*/ true), [thisType, valueType]); + } + + function createClassSetterDecoratorContextType(thisType: Type, valueType: Type) { + return tryCreateTypeReference(getGlobalClassSetterDecoratorContextType(/*reportErrors*/ true), [thisType, valueType]); + } + + function createClassAccessorDecoratorContextType(thisType: Type, valueType: Type) { + return tryCreateTypeReference(getGlobalClassAccessorDecoratorContextType(/*reportErrors*/ true), [thisType, valueType]); + } + + function createClassFieldDecoratorContextType(thisType: Type, valueType: Type) { + return tryCreateTypeReference(getGlobalClassFieldDecoratorContextType(/*reportErrors*/ true), [thisType, valueType]); + } + + /** + * Gets a type like `{ name: "foo", private: false, static: true }` that is used to provided member-specific + * details that will be intersected with a decorator context type. + */ + function getClassMemberDecoratorContextOverrideType(nameType: Type, isPrivate: boolean, isStatic: boolean) { + const key = `${isPrivate ? "p" : "P"}${isStatic ? "s" : "S"}${nameType.id}` as const; + let overrideType = decoratorContextOverrideTypeCache.get(key); + if (!overrideType) { + const members = createSymbolTable(); + members.set("name" as __String, createProperty("name" as __String, nameType)); + members.set("private" as __String, createProperty("private" as __String, isPrivate ? trueType : falseType)); + members.set("static" as __String, createProperty("static" as __String, isStatic ? trueType : falseType)); + overrideType = createAnonymousType(/*symbol*/ undefined, members, emptyArray, emptyArray, emptyArray); + decoratorContextOverrideTypeCache.set(key, overrideType); + } + return overrideType; + } + + function createClassMemberDecoratorContextTypeForNode(node: MethodDeclaration | AccessorDeclaration | PropertyDeclaration, thisType: Type, valueType: Type) { + const isStatic = hasStaticModifier(node); + const isPrivate = isPrivateIdentifier(node.name); + const nameType = isPrivate ? getStringLiteralType(idText(node.name)) : getLiteralTypeFromPropertyName(node.name); + const contextType = isMethodDeclaration(node) ? createClassMethodDecoratorContextType(thisType, valueType) : + isGetAccessorDeclaration(node) ? createClassGetterDecoratorContextType(thisType, valueType) : + isSetAccessorDeclaration(node) ? createClassSetterDecoratorContextType(thisType, valueType) : + isAutoAccessorPropertyDeclaration(node) ? createClassAccessorDecoratorContextType(thisType, valueType) : + isPropertyDeclaration(node) ? createClassFieldDecoratorContextType(thisType, valueType) : + Debug.failBadSyntaxKind(node); + const overrideType = getClassMemberDecoratorContextOverrideType(nameType, isPrivate, isStatic); + return getIntersectionType([contextType, overrideType]); + } + + function createClassAccessorDecoratorTargetType(thisType: Type, valueType: Type) { + return tryCreateTypeReference(getGlobalClassAccessorDecoratorTargetType(/*reportErrors*/ true), [thisType, valueType]); + } + + function createClassAccessorDecoratorResultType(thisType: Type, valueType: Type) { + return tryCreateTypeReference(getGlobalClassAccessorDecoratorResultType(/*reportErrors*/ true), [thisType, valueType]); + } + + function createClassFieldDecoratorInitializerMutatorType(thisType: Type, valueType: Type) { + const thisParam = createParameter("this" as __String, thisType); + const valueParam = createParameter("value" as __String, valueType); + return createFunctionType(/*typeParameters*/ undefined, thisParam, [valueParam], valueType, /*typePredicate*/ undefined, 1); + } + + /** + * Creates a call signature for an ES Decorator. This method is used by the semantics of + * `getESDecoratorCallSignature`, which you should probably be using instead. + */ + function createESDecoratorCallSignature(targetType: Type, contextType: Type, nonOptionalReturnType: Type) { + const targetParam = createParameter("target" as __String, targetType); + const contextParam = createParameter("context" as __String, contextType); + const returnType = getUnionType([nonOptionalReturnType, voidType]); + return createCallSignature(/*typeParameters*/ undefined, /*thisParameter*/ undefined, [targetParam, contextParam], returnType); + } + + /** + * Gets a call signature that should be used when resolving `decorator` as a call. This does not use the value + * of the decorator itself, but instead uses the declaration on which it is placed along with its relative + * position amongst other decorators on the same declaration to determine the applicable signature. The + * resulting signature can be used for call resolution, inference, and contextual typing. + */ + function getESDecoratorCallSignature(decorator: Decorator) { + // We are considering a future change that would allow the type of a decorator to affect the type of the + // class and its members, such as a `@Stringify` decorator changing the type of a `number` field to `string`, or + // a `@Callable` decorator adding a call signature to a `class`. The type arguments for the various context + // types may eventually change to reflect such mutations. + // + // In some cases we describe such potential mutations as coming from a "prior decorator application". It is + // important to note that, while decorators are *evaluated* left to right, they are *applied* right to left + // to preserve f ৹ g -> f(g(x)) application order. In these cases, a "prior" decorator usually means the + // next decorator following this one in document order. + // + // The "original type" of a class or member is the type it was declared as, or the type we infer from + // initializers, before _any_ decorators are applied. + // + // The type of a class or member that is a result of a prior decorator application represents the + // "current type", i.e., the type for the declaration at the time the decorator is _applied_. + // + // The type of a class or member that is the result of the application of *all* relevant decorators is the + // "final type". + // + // Any decorator that allows mutation or replacement will also refer to an "input type" and an + // "output type". The "input type" corresponds to the "current type" of the declaration, while the + // "output type" will become either the "input type/current type" for a subsequent decorator application, + // or the "final type" for the decorated declaration. + // + // It is important to understand decorator application order as it relates to how the "current", "input", + // "output", and "final" types will be determined: + // + // @E2 @E1 class SomeClass { + // @A2 @A1 static f() {} + // @B2 @B1 g() {} + // @C2 @C1 static x; + // @D2 @D1 y; + // } + // + // Per [the specification][1], decorators are applied in the following order: + // + // 1. For each static method (incl. get/set methods and `accessor` fields), in document order: + // a. Apply each decorator for that method, in reverse order (`A1`, `A2`). + // 2. For each instance method (incl. get/set methods and `accessor` fields), in document order: + // a. Apply each decorator for that method, in reverse order (`B1`, `B2`). + // 3. For each static field (excl. auto-accessors), in document order: + // a. Apply each decorator for that field, in reverse order (`C1`, `C2`). + // 4. For each instance field (excl. auto-accessors), in document order: + // a. Apply each decorator for that field, in reverse order (`D1`, `D2`). + // 5. Apply each decorator for the class, in reverse order (`E1`, `E2`). + // + // As a result, "current" types at each decorator application are as follows: + // - For `A1`, the "current" types of the class and method are their "original" types. + // - For `A2`, the "current type" of the method is the "output type" of `A1`, and the "current type" of the + // class is the type of `SomeClass` where `f` is the "output type" of `A1`. This becomes the "final type" + // of `f`. + // - For `B1`, the "current type" of the method is its "original type", and the "current type" of the class + // is the type of `SomeClass` where `f` now has its "final type". + // - etc. + // + // [1]: https://arai-a.github.io/ecma262-compare/?pr=2417&id=sec-runtime-semantics-classdefinitionevaluation + // + // This seems complicated at first glance, but is not unlike our existing inference for functions: + // + // declare function pipe( + // original: Original, + // a1: (input: Original, context: Context) => A1, + // a2: (input: A1, context: Context) => A2, + // b1: (input: A2, context: Context) => B1, + // b2: (input: B1, context: Context) => B2, + // c1: (input: B2, context: Context) => C1, + // c2: (input: C1, context: Context) => C2, + // d1: (input: C2, context: Context) => D1, + // d2: (input: D1, context: Context) => D2, + // e1: (input: D2, context: Context) => E1, + // e2: (input: E1, context: Context) => E2, + // ): E2; + + // When a decorator is applied, it is passed two arguments: "target", which is a value representing the + // thing being decorated (constructors for classes, functions for methods/accessors, `undefined` for fields, + // and a `{ get, set }` object for auto-accessors), and "context", which is an object that provides + // reflection information about the decorated element, as well as the ability to add additional "extra" + // initializers. In most cases, the "target" argument corresponds to the "input type" in some way, and the + // return value similarly corresponds to the "output type" (though if the "output type" is `void` or + // `undefined` then the "output type" is the "input type"). + + const { parent } = decorator; + const links = getNodeLinks(parent); + if (!links.decoratorSignature) { + links.decoratorSignature = anySignature; + switch (parent.kind) { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: { + // Class decorators have a `context` of `ClassDecoratorContext`, where the `Class` type + // argument will be the "final type" of the class after all decorators are applied. + + const node = parent as ClassDeclaration | ClassExpression; + const targetType = getTypeOfSymbol(getSymbolOfDeclaration(node)); + const contextType = createClassDecoratorContextType(targetType); + links.decoratorSignature = createESDecoratorCallSignature(targetType, contextType, targetType); + break; + } + + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: { + const node = parent as MethodDeclaration | GetAccessorDeclaration | SetAccessorDeclaration; + if (!isClassLike(node.parent)) break; + + // Method decorators have a `context` of `ClassMethodDecoratorContext`, where the + // `Value` type argument corresponds to the "final type" of the method. + // + // Getter decorators have a `context` of `ClassGetterDecoratorContext`, where the + // `Value` type argument corresponds to the "final type" of the value returned by the getter. + // + // Setter decorators have a `context` of `ClassSetterDecoratorContext`, where the + // `Value` type argument corresponds to the "final type" of the parameter of the setter. + // + // In all three cases, the `This` type argument is the "final type" of either the class or + // instance, depending on whether the member was `static`. + + const valueType = isMethodDeclaration(node) ? getOrCreateTypeFromSignature(getSignatureFromDeclaration(node)) : + getTypeOfNode(node); + + const thisType = hasStaticModifier(node) ? + getTypeOfSymbol(getSymbolOfDeclaration(node.parent)) : + getDeclaredTypeOfClassOrInterface(getSymbolOfDeclaration(node.parent)); + + // We wrap the "input type", if necessary, to match the decoration target. For getters this is + // something like `() => inputType`, for setters it's `(value: inputType) => void` and for + // methods it is just the input type. + const targetType = isGetAccessorDeclaration(node) ? createGetterFunctionType(valueType) : + isSetAccessorDeclaration(node) ? createSetterFunctionType(valueType) : + valueType; + + const contextType = createClassMemberDecoratorContextTypeForNode(node, thisType, valueType); + + // We also wrap the "output type", as needed. + const returnType = isGetAccessorDeclaration(node) ? createGetterFunctionType(valueType) : + isSetAccessorDeclaration(node) ? createSetterFunctionType(valueType) : + valueType; + + links.decoratorSignature = createESDecoratorCallSignature(targetType, contextType, returnType); + break; + } + + case SyntaxKind.PropertyDeclaration: { + const node = parent as PropertyDeclaration; + if (!isClassLike(node.parent)) break; + + // Field decorators have a `context` of `ClassFieldDecoratorContext` and + // auto-accessor decorators have a `context` of `ClassAccessorDecoratorContext. In + // both cases, the `This` type argument is the "final type" of either the class or instance, + // depending on whether the member was `static`, and the `Value` type argument corresponds to + // the "final type" of the value stored in the field. + + const valueType = getTypeOfNode(node); + const thisType = hasStaticModifier(node) ? + getTypeOfSymbol(getSymbolOfDeclaration(node.parent)) : + getDeclaredTypeOfClassOrInterface(getSymbolOfDeclaration(node.parent)); + + // The `target` of an auto-accessor decorator is a `{ get, set }` object, representing the + // runtime-generated getter and setter that are added to the class/prototype. The `target` of a + // regular field decorator is always `undefined` as it isn't installed until it is initialized. + const targetType = hasAccessorModifier(node) ? createClassAccessorDecoratorTargetType(thisType, valueType) : + undefinedType; + + const contextType = createClassMemberDecoratorContextTypeForNode(node, thisType, valueType); + + // We wrap the "output type" depending on the declaration. For auto-accessors, we wrap the + // "output type" in a `ClassAccessorDecoratorResult` type, which allows for + // mutation of the runtime-generated getter and setter, as well as the injection of an + // initializer mutator. For regular fields, we wrap the "output type" in an initializer mutator. + const returnType = hasAccessorModifier(node) ? createClassAccessorDecoratorResultType(thisType, valueType) : + createClassFieldDecoratorInitializerMutatorType(thisType, valueType); + + links.decoratorSignature = createESDecoratorCallSignature(targetType, contextType, returnType); + break; + } + } + } + return links.decoratorSignature === anySignature ? undefined : links.decoratorSignature; + } + + function getLegacyDecoratorCallSignature(decorator: Decorator) { + const { parent } = decorator; + const links = getNodeLinks(parent); + if (!links.decoratorSignature) { + links.decoratorSignature = anySignature; + switch (parent.kind) { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: { + const node = parent as ClassDeclaration | ClassExpression; + // For a class decorator, the `target` is the type of the class (e.g. the + // "static" or "constructor" side of the class). + const targetType = getTypeOfSymbol(getSymbolOfDeclaration(node)); + const targetParam = createParameter("target" as __String, targetType); + links.decoratorSignature = createCallSignature( + /*typeParameters*/ undefined, + /*thisParameter*/ undefined, + [targetParam], + getUnionType([targetType, voidType]), + ); + break; + } + case SyntaxKind.Parameter: { + const node = parent as ParameterDeclaration; + if ( + !isConstructorDeclaration(node.parent) && + !(isMethodDeclaration(node.parent) || isSetAccessorDeclaration(node.parent) && isClassLike(node.parent.parent)) + ) { + break; + } + + if (getThisParameter(node.parent) === node) { + break; + } + + const index = getThisParameter(node.parent) ? + node.parent.parameters.indexOf(node) - 1 : + node.parent.parameters.indexOf(node); + Debug.assert(index >= 0); + + // A parameter declaration decorator will have three arguments (see `ParameterDecorator` in + // core.d.ts). + + const targetType = isConstructorDeclaration(node.parent) ? getTypeOfSymbol(getSymbolOfDeclaration(node.parent.parent)) : + getParentTypeOfClassElement(node.parent); + + const keyType = isConstructorDeclaration(node.parent) ? undefinedType : + getClassElementPropertyKeyType(node.parent); + + const indexType = getNumberLiteralType(index); + + const targetParam = createParameter("target" as __String, targetType); + const keyParam = createParameter("propertyKey" as __String, keyType); + const indexParam = createParameter("parameterIndex" as __String, indexType); + links.decoratorSignature = createCallSignature( + /*typeParameters*/ undefined, + /*thisParameter*/ undefined, + [targetParam, keyParam, indexParam], + voidType, + ); + break; + } + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.PropertyDeclaration: { + const node = parent as MethodDeclaration | AccessorDeclaration | PropertyDeclaration; + if (!isClassLike(node.parent)) break; + + // A method or accessor declaration decorator will have either two or three arguments (see + // `PropertyDecorator` and `MethodDecorator` in core.d.ts). + + const targetType = getParentTypeOfClassElement(node); + const targetParam = createParameter("target" as __String, targetType); + + const keyType = getClassElementPropertyKeyType(node); + const keyParam = createParameter("propertyKey" as __String, keyType); + + const returnType = isPropertyDeclaration(node) ? voidType : + createTypedPropertyDescriptorType(getTypeOfNode(node)); + + const hasPropDesc = !isPropertyDeclaration(parent) || hasAccessorModifier(parent); + if (hasPropDesc) { + const descriptorType = createTypedPropertyDescriptorType(getTypeOfNode(node)); + const descriptorParam = createParameter("descriptor" as __String, descriptorType); + links.decoratorSignature = createCallSignature( + /*typeParameters*/ undefined, + /*thisParameter*/ undefined, + [targetParam, keyParam, descriptorParam], + getUnionType([returnType, voidType]), + ); + } + else { + links.decoratorSignature = createCallSignature( + /*typeParameters*/ undefined, + /*thisParameter*/ undefined, + [targetParam, keyParam], + getUnionType([returnType, voidType]), + ); + } + break; + } + } + } + return links.decoratorSignature === anySignature ? undefined : links.decoratorSignature; + } + + function getDecoratorCallSignature(decorator: Decorator) { + return legacyDecorators ? getLegacyDecoratorCallSignature(decorator) : + getESDecoratorCallSignature(decorator); + } + + 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]); + } + + 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]); + } + + return unknownType; + } + + 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_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_requires_the_Promise_constructor_Make_sure_you_have_a_declaration_for_the_Promise_constructor_or_include_ES2015_in_your_lib_option, + ); + } + + return promiseType; + } + + 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.links.type = targetType; + + const members = createSymbolTable([targetPropertySymbol]); + symbol.members = members; + return createAnonymousType(symbol, members, emptyArray, emptyArray, emptyArray); + } + + function getReturnTypeFromBody(func: FunctionLikeDeclaration, checkMode?: CheckMode): Type { + if (!func.body) { + return errorType; + } + + const functionFlags = getFunctionFlags(func); + const isAsync = (functionFlags & FunctionFlags.Async) !== 0; + const isGenerator = (functionFlags & FunctionFlags.Generator) !== 0; + + 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/undefined, but rather a Promise for void/undefined. + const contextualReturnType = getContextualReturnType(func, /*contextFlags*/ undefined); + const returnType = contextualReturnType && (unwrapReturnType(contextualReturnType, functionFlags) || voidType).flags & TypeFlags.Undefined ? undefinedType : voidType; + return functionFlags & FunctionFlags.Async ? createPromiseReturnType(func, returnType) : // Async function + returnType; // 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); + } + } + + if (yieldType) yieldType = getWidenedType(yieldType); + if (returnType) returnType = getWidenedType(returnType); + if (nextType) nextType = getWidenedType(nextType); + } + + if (isGenerator) { + return createGeneratorType( + 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; + } + } + + function createGeneratorType(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; + } + + 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 { + nextType = getContextualType(yieldExpression, /*contextFlags*/ undefined); + } + 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 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; + } + } + 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 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); + } + 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); + } + + 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 => { + let expr = returnStatement.expression; + if (expr) { + expr = skipParentheses(expr, /*excludeJSDocTypeAssertions*/ true); + // Bare calls to this same function don't contribute to inference + // and `return await` is also safe to unwrap here + if (functionFlags & FunctionFlags.Async && expr.kind === SyntaxKind.AwaitExpression) { + expr = skipParentheses((expr as AwaitExpression).expression, /*excludeJSDocTypeAssertions*/ true); + } + if ( + expr.kind === SyntaxKind.CallExpression && + (expr as CallExpression).expression.kind === SyntaxKind.Identifier && + checkExpressionCached((expr as CallExpression).expression).symbol === getMergedSymbol(func.symbol) && + (!isFunctionExpressionOrArrowFunction(func.symbol.valueDeclaration!) || isConstantReference((expr as CallExpression).expression)) + ) { + hasReturnOfTypeNever = true; + return; + } + + 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); + } + else { + hasReturnWithNoExpression = true; + } + }); + if (aggregatedTypes.length === 0 && !hasReturnWithNoExpression && (hasReturnOfTypeNever || mayReturnNever(func))) { + return undefined; + } + 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; + } + } + + function getTypePredicateFromBody(func: FunctionLikeDeclaration): TypePredicate | undefined { + switch (func.kind) { + case SyntaxKind.Constructor: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + return undefined; + } + const functionFlags = getFunctionFlags(func); + if (functionFlags !== FunctionFlags.Normal) return undefined; + + // Only attempt to infer a type predicate if there's exactly one return. + let singleReturn: Expression | undefined; + if (func.body && func.body.kind !== SyntaxKind.Block) { + singleReturn = func.body; // arrow function + } + else { + const bailedEarly = forEachReturnStatement(func.body as Block, returnStatement => { + if (singleReturn || !returnStatement.expression) return true; + singleReturn = returnStatement.expression; + }); + if (bailedEarly || !singleReturn || functionHasImplicitReturn(func)) return undefined; + } + return checkIfExpressionRefinesAnyParameter(func, singleReturn); + } + + function checkIfExpressionRefinesAnyParameter(func: FunctionLikeDeclaration, expr: Expression): TypePredicate | undefined { + expr = skipParentheses(expr, /*excludeJSDocTypeAssertions*/ true); + const returnType = checkExpressionCached(expr); + if (!(returnType.flags & TypeFlags.Boolean)) return undefined; + + return forEach(func.parameters, (param, i) => { + const initType = getTypeOfSymbol(param.symbol); + if (!initType || initType.flags & TypeFlags.Boolean || !isIdentifier(param.name) || isSymbolAssigned(param.symbol) || isRestParameter(param)) { + // Refining "x: boolean" to "x is true" or "x is false" isn't useful. + return; + } + const trueType = checkIfExpressionRefinesParameter(func, expr, param, initType); + if (trueType) { + return createTypePredicate(TypePredicateKind.Identifier, unescapeLeadingUnderscores(param.name.escapedText), i, trueType); + } + }); + } + + function checkIfExpressionRefinesParameter(func: FunctionLikeDeclaration, expr: Expression, param: ParameterDeclaration, initType: Type): Type | undefined { + const antecedent = (expr as Expression & { flowNode?: FlowNode; }).flowNode || + expr.parent.kind === SyntaxKind.ReturnStatement && (expr.parent as ReturnStatement).flowNode || + createFlowNode(FlowFlags.Start, /*node*/ undefined, /*antecedent*/ undefined); + const trueCondition = createFlowNode(FlowFlags.TrueCondition, expr, antecedent); + + const trueType = getFlowTypeOfReference(param.name, initType, initType, func, trueCondition); + if (trueType === initType) return undefined; + + // "x is T" means that x is T if and only if it returns true. If it returns false then x is not T. + // This means that if the function is called with an argument of type trueType, there can't be anything left in the `else` branch. It must reduce to `never`. + const falseCondition = createFlowNode(FlowFlags.FalseCondition, expr, antecedent); + const falseSubtype = getFlowTypeOfReference(param.name, initType, trueType, func, falseCondition); + return falseSubtype.flags & TypeFlags.Never ? trueType : undefined; + } + + /** + * 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 an explicitly specified return type that includes `void` or is exactly `any` or `undefined` don't + // need any return statements. + if (type && (maybeTypeOfKind(type, TypeFlags.Void) || type.flags & (TypeFlags.Any | TypeFlags.Undefined))) { + 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; + + 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_undefined_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 (isUnwrappedReturnTypeUndefinedVoidOrAny(func, inferredReturnType)) { + return; + } + } + 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); + + 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(/*declaration*/ undefined, /*typeParameters*/ undefined, /*thisParameter*/ undefined, emptyArray, returnType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.IsNonInferrable); + const returnOnlyType = createAnonymousType(node.symbol, emptySymbols, [returnOnlySignature], emptyArray, emptyArray); + returnOnlyType.objectFlags |= ObjectFlags.NonInferrableType; + return links.contextFreeType = returnOnlyType; + } + } + return anyFunctionType; + } + + // Grammar checking + const hasGrammarError = checkGrammarFunctionLikeDeclaration(node); + if (!hasGrammarError && node.kind === SyntaxKind.FunctionExpression) { + checkGrammarForGenerator(node); + } + + contextuallyCheckFunctionExpressionOrObjectLiteralMethod(node, checkMode); + + return getTypeOfSymbol(getSymbolOfDeclaration(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. + 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(getSymbolOfDeclaration(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); + } + } + else if (contextualSignature && !node.typeParameters && contextualSignature.parameters.length > node.parameters.length) { + const inferenceContext = getInferenceContext(node); + if (checkMode && checkMode & CheckMode.Inferential) { + inferFromAnnotatedParameters(signature, contextualSignature, inferenceContext!); + } + } + if (contextualSignature && !getReturnTypeFromAnnotation(node) && !signature.resolvedReturnType) { + const returnType = getReturnTypeFromBody(node, checkMode); + if (!signature.resolvedReturnType) { + signature.resolvedReturnType = returnType; + } + } + checkSignatureDeclaration(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); + + 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) { + const effectiveCheckNode = getEffectiveCheckNode(node.body); + if ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.Async) { // Async function + const awaitedType = checkAwaitedType(exprType, /*withAlias*/ false, effectiveCheckNode, 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, effectiveCheckNode, effectiveCheckNode); + } + else { // Normal function + checkTypeAssignableToAndOptionallyElaborate(exprType, returnOrPromisedType, effectiveCheckNode, effectiveCheckNode); + } + } + } + } + } + + 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; + } + 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; + } + // 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.Constant || + 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 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 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); + } + else { + checkDeleteExpressionMustBeOptional(expr, symbol); + } + } + 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 : hasTypeFacts(type, TypeFacts.IsUndefined)) + ) { + error(expr, Diagnostics.The_operand_of_a_delete_operator_must_be_optional); + } + } + + function checkTypeOfExpression(node: TypeOfExpression): Type { + checkExpression(node.expression); + return typeofType; + } + + function checkVoidExpression(node: VoidExpression): Type { + checkNodeDeferred(node); + return undefinedWideningType; + } + + function checkAwaitGrammar(node: AwaitExpression | VariableDeclarationList): boolean { + // Grammar checking + let hasError = false; + const container = getContainingFunctionOrClassStaticBlock(node); + if (container && isClassStaticBlockDeclaration(container)) { + // NOTE: We report this regardless as to whether there are parse diagnostics. + const message = isAwaitExpression(node) ? Diagnostics.await_expression_cannot_be_used_inside_a_class_static_block : + Diagnostics.await_using_statements_cannot_be_used_inside_a_class_static_block; + error(node, message); + hasError = true; + } + 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 message = isAwaitExpression(node) ? 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.await_using_statements_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; + const diagnostic = createFileDiagnostic(sourceFile, span.start, span.length, message); + diagnostics.add(diagnostic); + hasError = true; + } + 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), + ); + hasError = true; + break; + } + // fallthrough + case ModuleKind.ES2022: + case ModuleKind.ESNext: + case ModuleKind.Preserve: + case ModuleKind.System: + if (languageVersion >= ScriptTarget.ES2017) { + break; + } + // fallthrough + default: + span ??= getSpanOfTokenAtPosition(sourceFile, node.pos); + const message = isAwaitExpression(node) ? Diagnostics.Top_level_await_expressions_are_only_allowed_when_the_module_option_is_set_to_es2022_esnext_system_node16_nodenext_or_preserve_and_the_target_option_is_set_to_es2017_or_higher : + Diagnostics.Top_level_await_using_statements_are_only_allowed_when_the_module_option_is_set_to_es2022_esnext_system_node16_nodenext_or_preserve_and_the_target_option_is_set_to_es2017_or_higher; + diagnostics.add(createFileDiagnostic(sourceFile, span.start, span.length, message)); + hasError = true; + break; + } + } + } + else { + // use of 'await' in non-async function + const sourceFile = getSourceFileOfNode(node); + if (!hasParseDiagnostics(sourceFile)) { + const span = getSpanOfTokenAtPosition(sourceFile, node.pos); + const message = isAwaitExpression(node) ? Diagnostics.await_expressions_are_only_allowed_within_async_functions_and_at_the_top_levels_of_modules : + Diagnostics.await_using_statements_are_only_allowed_within_async_functions_and_at_the_top_levels_of_modules; + const diagnostic = createFileDiagnostic(sourceFile, span.start, span.length, message); + 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); + hasError = true; + } + } + } + + if (isAwaitExpression(node) && isInParameterInitializerBeforeContainingFunction(node)) { + // NOTE: We report this regardless as to whether there are parse diagnostics. + error(node, Diagnostics.await_expressions_cannot_be_used_in_a_parameter_initializer); + hasError = true; + } + + return hasError; + } + + function checkAwaitExpression(node: AwaitExpression): Type { + addLazyDiagnostic(() => checkAwaitGrammar(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; + } + + 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))); + } + return numberType; + } + return getUnaryResultType(operandType); + case SyntaxKind.ExclamationToken: + checkTruthinessOfType(operandType, 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( + 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); + } + + 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); + } + + // 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; + } + + 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 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 isConstEnumObjectType(type: Type): boolean { + return !!(getObjectFlags(type) & ObjectFlags.Anonymous) && !!type.symbol && isConstEnumSymbol(type.symbol); + } + + function isConstEnumSymbol(symbol: Symbol): boolean { + return (symbol.flags & SymbolFlags.ConstEnum) !== 0; + } + + /** + * Get the type of the `[Symbol.hasInstance]` method of an object type. + */ + function getSymbolHasInstanceMethodOfObjectType(type: Type) { + const hasInstancePropertyName = getPropertyNameForKnownSymbolName("hasInstance"); + if (allTypesAssignableToKind(type, TypeFlags.NonPrimitive)) { + const hasInstanceProperty = getPropertyOfType(type, hasInstancePropertyName); + if (hasInstanceProperty) { + const hasInstancePropertyType = getTypeOfSymbol(hasInstanceProperty); + if (hasInstancePropertyType && getSignaturesOfType(hasInstancePropertyType, SignatureKind.Call).length !== 0) { + return hasInstancePropertyType; + } + } + } + } + + function checkInstanceOfExpression(left: Expression, right: Expression, leftType: Type, rightType: Type, checkMode?: CheckMode): 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); + } + + Debug.assert(isInstanceOfExpression(left.parent)); + const signature = getResolvedSignature(left.parent, /*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 rightType has a `[Symbol.hasInstance]` method that is not `(value: unknown) => boolean`, we + // must check the expression as if it were a call to `right[Symbol.hasInstance](left)`. The call to + // `getResolvedSignature`, below, will check that leftType is assignable to the type of the first + // parameter. + const returnType = getReturnTypeOfSignature(signature); + + // We also verify that the return type of the `[Symbol.hasInstance]` method is assignable to + // `boolean`. According to the spec, the runtime will actually perform `ToBoolean` on the result, + // but this is more type-safe. + checkTypeAssignableTo(returnType, booleanType, right, Diagnostics.An_object_s_Symbol_hasInstance_method_must_return_a_boolean_value_for_it_to_be_used_on_the_right_hand_side_of_an_instanceof_expression); + + return booleanType; + } + + function hasEmptyObjectIntersection(type: Type): boolean { + return someType(type, t => t === unknownEmptyObjectType || !!(t.flags & TypeFlags.Intersection) && isEmptyAnonymousObjectType(getBaseConstraintOrType(t))); + } + + function checkInExpression(left: Expression, right: Expression, leftType: Type, rightType: Type): Type { + if (leftType === silentNeverType || rightType === silentNeverType) { + return silentNeverType; + } + if (isPrivateIdentifier(left)) { + if ( + languageVersion < LanguageFeatureMinimumTarget.PrivateNamesAndClassStaticBlocks || + languageVersion < LanguageFeatureMinimumTarget.ClassAndClassElementDecorators || + !useDefineForClassFields + ) { + 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); + } + } + 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; + } + + 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); + } + } + 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 < LanguageFeatureMinimumTarget.ObjectSpreadRest) { + checkExternalEmitHelpers(property, ExternalEmitHelpers.Rest); + } + const nonRestNames: PropertyName[] = []; + if (allProperties) { + for (const otherProperty of allProperties) { + if (!isSpreadAssignment(otherProperty)) { + nonRestNames.push(otherProperty.name); + } + } + } + const type = getRestType(objectLiteralType, nonRestNames, objectLiteralType.symbol); + checkGrammarForDisallowedTrailingComma(allProperties, Diagnostics.A_rest_parameter_or_binding_pattern_may_not_have_a_trailing_comma); + return checkDestructuringAssignment(property.expression, type); + } + } + else { + error(property, Diagnostics.Property_assignment_expected); + } + } + + function checkArrayLiteralAssignment(node: ArrayLiteralExpression, sourceType: Type, checkMode?: CheckMode): Type { + const elements = node.elements; + if (languageVersion < LanguageFeatureMinimumTarget.DestructuringAssignment && 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 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 { + 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; + } + + 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 && + !(hasTypeFacts(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) { + sourceType = getTypeWithFacts(sourceType, TypeFacts.NEUndefined); + } + } + 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); + } + + 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)) { + // NOTE: we do not limit this to LanguageFeatureTargets.PrivateNames as some other feature downleveling still requires this. + checkExternalEmitHelpers(target.parent, ExternalEmitHelpers.ClassPrivateFieldSet); + } + return sourceType; + } + + /** + * 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); + + 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; + + // 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 isTypeEqualityComparableTo(source: Type, target: Type) { + return (target.flags & TypeFlags.Nullable) !== 0 || isTypeComparableTo(source, target); + } + + 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)[]; + } + + const trampoline = createBinaryExpressionTrampoline(onEnter, onLeft, onOperator, onRight, onExit, foldState); + + return (node: BinaryExpression, checkMode: CheckMode | undefined) => { + const result = trampoline(node, checkMode); + Debug.assertIsDefined(result); + return result; + }; + + 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], + }; + } + + if (isInJSFile(node) && getAssignedExpandoInitializer(node)) { + state.skip = true; + setLastResult(state, checkExpression(node.right, checkMode)); + 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; + } + + 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 (isLogicalOrCoalescingBinaryOperator(operator)) { + let parent = node.parent; + while (parent.kind === SyntaxKind.ParenthesizedExpression || isLogicalOrCoalescingBinaryExpression(parent)) { + parent = parent.parent; + } + if (operator === SyntaxKind.AmpersandAmpersandToken || isIfStatement(parent)) { + checkTestingKnownTruthyCallableOrAwaitableOrEnumMemberType(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 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); + + result = checkBinaryLikeExpressionWorker(node.left, node.operatorToken, node.right, leftType, rightType, state.checkMode, node); + } + + state.skip = false; + setLeftType(state, /*type*/ undefined); + setLastResult(state, /*type*/ undefined); + state.stackIndex--; + return result; + } + + function foldState(state: WorkArea, result: Type | undefined, _side: "left" | "right") { + setLastResult(state, result); + return state; + } + + function maybeCheckExpression(state: WorkArea, node: Expression): BinaryExpression | undefined { + if (isBinaryExpression(node)) { + return node; + } + setLastResult(state, checkExpression(node, state.checkMode)); + } + + function getLeftType(state: WorkArea) { + return state.typeStack[state.stackIndex]; + } + + function setLeftType(state: WorkArea, type: Type | undefined) { + state.typeStack[state.stackIndex] = type; + } + + function getLastResult(state: WorkArea) { + return state.typeStack[state.stackIndex + 1]; + } + + 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 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)); + } + } + } + + // 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: BinaryOperatorToken, 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 (isLogicalOrCoalescingBinaryOperator(operator)) { + leftType = checkTruthinessExpression(left, checkMode); + } + else { + leftType = checkExpression(left, checkMode); + } + + const rightType = checkExpression(right, checkMode); + return checkBinaryLikeExpressionWorker(left, operatorToken, right, leftType, rightType, checkMode, errorNode); + } + + function checkBinaryLikeExpressionWorker( + left: Expression, + operatorToken: BinaryOperatorToken, + right: Expression, + leftType: Type, + rightType: Type, + checkMode?: CheckMode, + 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: PunctuationSyntaxKind | 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; + } + + if (!isTypeAssignableToKind(leftType, TypeFlags.StringLike) && !isTypeAssignableToKind(rightType, TypeFlags.StringLike)) { + leftType = checkNonNullType(leftType, left); + rightType = checkNonNullType(rightType, right); + } + + 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; + } + + // Symbols are not allowed at all in arithmetic expressions + if (resultType && !checkForDisallowedESSymbolOperand(operator)) { + 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; + } + + if (operator === SyntaxKind.PlusEqualsToken) { + checkAssignmentOperator(resultType); + } + return resultType; + case SyntaxKind.LessThanToken: + case SyntaxKind.GreaterThanToken: + case SyntaxKind.LessThanEqualsToken: + case SyntaxKind.GreaterThanEqualsToken: + if (checkForDisallowedESSymbolOperand(operator)) { + leftType = getBaseTypeOfLiteralTypeForComparison(checkNonNullType(leftType, left)); + rightType = getBaseTypeOfLiteralTypeForComparison(checkNonNullType(rightType, right)); + reportOperatorErrorUnless((left, right) => { + if (isTypeAny(left) || isTypeAny(right)) { + return true; + } + const leftAssignableToNumber = isTypeAssignableTo(left, numberOrBigIntType); + const rightAssignableToNumber = isTypeAssignableTo(right, numberOrBigIntType); + return leftAssignableToNumber && rightAssignableToNumber || + !leftAssignableToNumber && !rightAssignableToNumber && areTypesComparable(left, right); + }); + } + return booleanType; + case SyntaxKind.EqualsEqualsToken: + case SyntaxKind.ExclamationEqualsToken: + case SyntaxKind.EqualsEqualsEqualsToken: + case SyntaxKind.ExclamationEqualsEqualsToken: + // We suppress errors in CheckMode.TypeOnly (meaning the invocation came from getTypeOfExpression). During + // control flow analysis it is possible for operands to temporarily have narrower types, and those narrower + // types may cause the operands to not be comparable. We don't want such errors reported (see #46475). + if (!(checkMode && checkMode & CheckMode.TypeOnly)) { + if ( + (isLiteralExpressionOfObject(left) || isLiteralExpressionOfObject(right)) && + // only report for === and !== in JS, not == or != + (!isInJSFile(left) || (operator === SyntaxKind.EqualsEqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken)) + ) { + 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, checkMode); + case SyntaxKind.InKeyword: + return checkInExpression(left, right, leftType, rightType); + case SyntaxKind.AmpersandAmpersandToken: + case SyntaxKind.AmpersandAmpersandEqualsToken: { + const resultType = hasTypeFacts(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 = hasTypeFacts(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 = hasTypeFacts(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 leftType; + } + else { + checkAssignmentOperator(rightType); + return rightType; + } + case SyntaxKind.CommaToken: + if (!compilerOptions.allowUnreachableCode && isSideEffectFree(left) && !isIndirectCall(left.parent as BinaryExpression)) { + 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(); + } + + 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, /*nameNotFoundMessage*/ undefined, /*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); + } + } + } + } + } + + // Return true for "indirect calls", (i.e. `(0, x.f)(...)` or `(0, eval)(...)`), which prevents passing `this`. + function isIndirectCall(node: BinaryExpression): boolean { + return node.parent.kind === SyntaxKind.ParenthesizedExpression && + isNumericLiteral(node.left) && + node.left.text === "0" && + (isCallExpression(node.parent.parent) && node.parent.parent.expression === node.parent || node.parent.parent.kind === SyntaxKind.TaggedTemplateExpression) && + // special-case for "eval" because it's the only non-access case where an indirect call actually affects behavior. + (isAccessExpression(node.right) || isIdentifier(node.right) && node.right.escapedText === "eval"); + } + + // Return true if there was no error, false if there was an error. + function checkForDisallowedESSymbolOperand(operator: PunctuationSyntaxKind): boolean { + const offendingSymbolOperand = maybeTypeOfKindConsideringBaseConstraint(leftType, TypeFlags.ESSymbolLike) ? left : + maybeTypeOfKindConsideringBaseConstraint(rightType, TypeFlags.ESSymbolLike) ? right : + undefined; + + if (offendingSymbolOperand) { + error(offendingSymbolOperand, Diagnostics.The_0_operator_cannot_be_applied_to_type_symbol, tokenToString(operator)); + return false; + } + + return true; + } + + function getSuggestedBooleanOperator(operator: SyntaxKind): PunctuationSyntaxKind | 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 checkAssignmentOperatorWorker() { + let assigneeType = leftType; + + // getters can be a subtype of setters, so to check for assignability we use the setter's type instead + if (isCompoundAssignment(operatorToken.kind) && left.kind === SyntaxKind.PropertyAccessExpression) { + assigneeType = checkPropertyAccessExpression(left as PropertyAccessExpression, /*checkMode*/ undefined, /*writeOnly*/ true); + } + + // 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)) { + 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, assigneeType, 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); + return true; + } + 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); + } + + 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 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 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 isGlobalNaN(expr: Expression): boolean { + if (isIdentifier(expr) && expr.escapedText === "NaN") { + const globalNaNSymbol = getGlobalNaNSymbol(); + return !!globalNaNSymbol && globalNaNSymbol === getResolvedSymbol(expr); + } + return false; + } + } + + 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]; + } + + function checkYieldExpression(node: YieldExpression): Type { + addLazyDiagnostic(checkYieldExpressionGrammar); + + const func = getContainingFunction(node); + if (!func) return anyType; + const functionFlags = getFunctionFlags(func); + + 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 isAsync = (functionFlags & FunctionFlags.Async) !== 0; + if (node.asteriskToken) { + // Async generator functions prior to ES2018 require the __await, __asyncDelegator, + // and __asyncValues helpers + if (isAsync && languageVersion < LanguageFeatureMinimumTarget.AsyncGenerators) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.AsyncDelegatorIncludes); + } + + // Generator functions prior to ES2015 require the __values helper + if (!isAsync && languageVersion < LanguageFeatureMinimumTarget.Generators && compilerOptions.downlevelIteration) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.Values); + } + } + + // 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. + let returnType = getReturnTypeFromAnnotation(func); + if (returnType && returnType.flags & TypeFlags.Union) { + returnType = filterType(returnType, t => checkGeneratorInstantiationAssignabilityToReturnType(t, functionFlags, /*errorNode*/ undefined)); + } + 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); + } + + 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; + + function checkYieldExpressionGrammar() { + if (!(node.flags & NodeFlags.YieldContext)) { + grammarErrorOnFirstToken(node, Diagnostics.A_yield_expression_is_only_allowed_in_a_generator_body); + } + + if (isInParameterInitializerBeforeContainingFunction(node)) { + error(node, Diagnostics.yield_expressions_cannot_be_used_in_a_parameter_initializer); + } + } + } + + function checkConditionalExpression(node: ConditionalExpression, checkMode?: CheckMode): Type { + const type = checkTruthinessExpression(node.condition, checkMode); + checkTestingKnownTruthyCallableOrAwaitableOrEnumMemberType(node.condition, type, node.whenTrue); + const type1 = checkExpression(node.whenTrue, checkMode); + const type2 = checkExpression(node.whenFalse, checkMode); + return getUnionType([type1, type2], UnionReduction.Subtype); + } + + 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); + } + texts.push(span.literal.text); + types.push(isTypeAssignableTo(type, templateConstraintType) ? type : stringType); + } + const evaluated = node.parent.kind !== SyntaxKind.TaggedTemplateExpression && evaluate(node).value; + if (evaluated) { + return getFreshTypeOfLiteralType(getStringLiteralType(evaluated)); + } + if (isConstContext(node) || isTemplateLiteralContext(node) || someType(getContextualType(node, /*contextFlags*/ undefined) || unknownType, isTemplateLiteralContextualType)) { + return getTemplateLiteralType(texts, types); + } + return stringType; + } + + 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): Expression { + if (isJsxAttributes(node) && !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 contextNode = getContextNode(node); + pushContextualType(contextNode, contextualType, /*isCache*/ false); + pushInferenceContext(contextNode, 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; + popInferenceContext(); + popContextualType(); + return result; + } + + 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)!; + if (isInJSFile(declaration)) { + const typeNode = tryGetJSDocSatisfiesTypeNode(declaration); + if (typeNode) { + return checkSatisfiesExpressionWorker(initializer, typeNode, checkMode); + } + } + 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.combinedFlags & ElementFlags.Variable) && getTypeReferenceArity(type) < declaration.name.elements.length ? + padTupleType(type, declaration.name) : type; + } + + function padTupleType(type: TupleTypeReference, pattern: ArrayBindingPattern) { + const patternElements = pattern.elements; + const elementTypes = getElementTypes(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 = getCombinedNodeFlagsCached(declaration) & NodeFlags.Constant || 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 widened; + } + + 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 isConstContext(node: Expression): boolean { + const parent = node.parent; + return isAssertionExpression(parent) && isConstTypeReference(parent.type) || + isJSDocTypeAssertion(parent) && isConstTypeReference(getJSDocTypeAssertionType(parent)) || + isValidConstAssertionArgument(node) && isConstTypeVariable(getContextualType(node, ContextFlags.None)) || + (isParenthesizedExpression(parent) || isArrayLiteralExpression(parent) || isSpreadElement(parent)) && isConstContext(parent) || + (isPropertyAssignment(parent) || isShorthandPropertyAssignment(parent) || isTemplateSpan(parent)) && isConstContext(parent.parent); + } + + function checkExpressionForMutableLocation(node: Expression, checkMode: CheckMode | undefined, forceTuple?: boolean): Type { + const type = checkExpression(node, checkMode, forceTuple); + return isConstContext(node) || isCommonJsExportedExpression(node) ? getRegularTypeOfLiteralType(type) : + isTypeAssertion(node) ? type : + getWidenedLiteralLikeTypeForContextualType(type, instantiateContextualType(getContextualType(node, /*contextFlags*/ undefined), node, /*contextFlags*/ undefined)); + } + + 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); + } + + return checkExpressionForMutableLocation(node.initializer, checkMode); + } + + function checkObjectLiteralMethod(node: MethodDeclaration, checkMode?: CheckMode): Type { + // Grammar checking + checkGrammarMethod(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.kind === SyntaxKind.ComputedPropertyName) { + checkComputedPropertyName(node.name); + } + + 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 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); + } + } + } + // TODO: The signature may reference any outer inference contexts, but we map pop off and then apply new inference contexts, and thus get different inferred types. + // That this is cached on the *first* such attempt is not currently an issue, since expression types *also* get cached on the first pass. If we ever properly speculate, though, + // the cached "isolatedSignatureType" signature field absolutely needs to be included in the list of speculative caches. + return getOrCreateTypeFromSignature(instantiateSignatureInContextOf(signature, contextualSignature, context), flatMap(inferenceContexts, c => c && map(c.inferences, i => i.typeParameter)).slice()); + } + } + } + } + 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 hasInferenceCandidates(info: InferenceInfo) { + return !!(info.candidates || info.contraCandidates); + } + + 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; + } + } + 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 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); + } + } + if (newTypeParameters) { + const mapper = createTypeMapper(oldTypeParameters!, newTypeParameters); + for (const tp of newTypeParameters) { + tp.mapper = mapper; + } + } + 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; + } + } + } + + function getReturnTypeOfSingleNonGenericCallSignature(funcType: Type) { + const signature = getSingleCallSignature(funcType); + if (signature && !signature.typeParameters) { + return getReturnTypeOfSignature(signature); + } + } + + 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); + } + + /** + * 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, CheckMode.TypeOnly); + // 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 getQuickTypeOfExpression(node: Expression): Type | undefined { + let expr = skipParentheses(node, /*excludeJSDocTypeAssertions*/ true); + if (isJSDocTypeAssertion(expr)) { + const type = getJSDocTypeAssertionType(expr); + if (!isConstTypeReference(type)) { + return getTypeFromTypeNode(type); + } + } + expr = skipParentheses(node); + if (isAwaitExpression(expr)) { + const type = getQuickTypeOfExpression(expr.expression); + return type ? getAwaitedType(type) : undefined; + } + // 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, /*requireStringLiteralLikeArgument*/ true) && !isSymbolOrSymbolForCall(expr)) { + return isCallChain(expr) ? getReturnTypeOfSingleNonGenericSignatureOfCallChain(expr) : + getReturnTypeOfSingleNonGenericCallSignature(checkNonNullExpression(expr.expression)); + } + else if (isAssertionExpression(expr) && !isConstTypeReference(expr.type)) { + return getTypeFromTypeNode((expr as TypeAssertion).type); + } + else if (isLiteralExpression(node) || isBooleanLiteral(node)) { + 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. + * 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; + } + pushContextualType(node, anyType, /*isCache*/ false); + const type = links.contextFreeType = checkExpression(node, CheckMode.SkipContextSensitive); + popContextualType(); + return type; + } + + 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; + } + + 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 (!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); + } + + if (getIsolatedModules(compilerOptions)) { + Debug.assert(!!(type.symbol.flags & SymbolFlags.ConstEnum)); + const constEnumDeclaration = type.symbol.valueDeclaration as EnumDeclaration; + const redirect = host.getRedirectReferenceForResolutionFromSourceOfProject(getSourceFileOfNode(constEnumDeclaration).resolvedPath); + if (constEnumDeclaration.flags & NodeFlags.Ambient && !isValidTypeOnlyAliasUseSite(node) && (!redirect || !shouldPreserveConstEnums(redirect.commandLine.options))) { + error(node, Diagnostics.Cannot_access_ambient_const_enums_when_0_is_enabled, isolatedModulesLikeFlagName); + } + } + } + + function checkParenthesizedExpression(node: ParenthesizedExpression, checkMode?: CheckMode): Type { + if (hasJSDocNodes(node)) { + if (isJSDocSatisfiesExpression(node)) { + return checkSatisfiesExpressionWorker(node.expression, getJSDocSatisfiesExpressionType(node), checkMode); + } + if (isJSDocTypeAssertion(node)) { + return checkAssertionWorker(node, 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(); + } + } + 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 hasSkipDirectInferenceFlag(node) ? + blockedStringType : + 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 checkRegularExpressionLiteral(node as RegularExpressionLiteral); + 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, checkMode); + 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 + + 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(getSymbolOfDeclaration(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(getSymbolOfDeclaration(node)); + const modifiers = getTypeParameterModifiers(typeParameter) & (ModifierFlags.In | ModifierFlags.Out); + if (modifiers) { + const symbol = getSymbolOfDeclaration(node.parent); + if (isTypeAliasDeclaration(node.parent) && !(getObjectFlags(getDeclaredTypeOfSymbol(symbol)) & (ObjectFlags.Reference | 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). + checkGrammarModifiers(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); + } + } + if (!node.initializer && isOptionalDeclaration(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); + } + 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); + } + } + + // 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); + } + } + + 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; + } + + const signature = getSignatureFromDeclaration(parent); + const typePredicate = getTypePredicateOfSignature(signature); + if (!typePredicate) { + return; + } + + 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 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); + 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); + } + + const functionFlags = getFunctionFlags(node as FunctionLikeDeclaration); + if (!(functionFlags & FunctionFlags.Invalid)) { + // Async generators prior to ES2018 require the __await and __asyncGenerator helpers + if ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.AsyncGenerator && languageVersion < LanguageFeatureMinimumTarget.AsyncGenerators) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.AsyncGeneratorIncludes); + } + + // Async functions prior to ES2017 require the __awaiter helper + if ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.Async && languageVersion < LanguageFeatureMinimumTarget.AsyncFunctions) { + 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 < LanguageFeatureMinimumTarget.Generators) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.Generator); + } + } + + checkTypeParameters(getEffectiveTypeParameterDeclarations(node)); + checkUnmatchedJSDocParameters(node); + + forEach(node.parameters, checkParameter); + + // TODO(rbuckton): Should we start checking JSDoc types? + if (node.type) { + checkSourceElement(node.type); + } + + addLazyDiagnostic(checkSignatureDeclarationDiagnostics); + + function checkSignatureDeclarationDiagnostics() { + checkCollisionWithArgumentsInGeneratedCode(node); + + let returnTypeNode = getEffectiveReturnTypeNode(node); + let returnTypeErrorLocation = returnTypeNode; + + if (isInJSFile(node)) { + const typeTag = getJSDocTypeTag(node); + if (typeTag && typeTag.typeExpression && isTypeReferenceNode(typeTag.typeExpression.type)) { + const signature = getSingleCallSignature(getTypeFromTypeNode(typeTag.typeExpression)); + if (signature && signature.declaration) { + returnTypeNode = getEffectiveReturnTypeNode(signature.declaration); + returnTypeErrorLocation = typeTag.typeExpression.type; + } + } + } + + 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 && returnTypeErrorLocation) { + const functionFlags = getFunctionFlags(node as FunctionDeclaration); + if ((functionFlags & (FunctionFlags.Invalid | FunctionFlags.Generator)) === FunctionFlags.Generator) { + const returnType = getTypeFromTypeNode(returnTypeNode); + if (returnType === voidType) { + error(returnTypeErrorLocation, Diagnostics.A_generator_cannot_have_a_void_type_annotation); + } + else { + checkGeneratorInstantiationAssignabilityToReturnType(returnType, functionFlags, returnTypeErrorLocation); + } + } + else if ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.Async) { + checkAsyncFunctionReturnType(node as FunctionLikeDeclaration, returnTypeNode, returnTypeErrorLocation); + } + } + if (node.kind !== SyntaxKind.IndexSignature && node.kind !== SyntaxKind.JSDocFunctionType) { + registerForUnusedIdentifiersCheck(node); + } + } + } + + function checkGeneratorInstantiationAssignabilityToReturnType(returnType: Type, functionFlags: FunctionFlags, errorNode?: TypeNode) { + // 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 = createGeneratorType(generatorYieldType, generatorReturnType, generatorNextType, !!(functionFlags & FunctionFlags.Async)); + + return checkTypeAssignableTo(generatorInstantiation, returnType, errorNode); + } + + 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; + + const memberName = name && getEffectivePropertyNameForPropertyNameNode(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.PropertyDeclaration: + addName(names, name, memberName, DeclarationMeaning.GetOrSetAccessor | privateStaticFlags); + break; + + case SyntaxKind.MethodDeclaration: + addName(names, name, memberName, DeclarationMeaning.Method | privateStaticFlags); + break; + } + } + } + } + + function addName(names: Map<__String, DeclarationMeaning>, 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) { + error(location, Diagnostics.Duplicate_identifier_0, getTextOfNode(location)); + } + else { + names.set(name, prev | 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 = getEffectivePropertyNameForPropertyNameNode(memberNameNode); + switch (memberName) { + case "name": + case "length": + case "caller": + case "arguments": + if (useDefineForClassFields) { + break; + } + // fall through + case "prototype": + const message = Diagnostics.Static_property_0_conflicts_with_built_in_property_Function_0_of_constructor_function_1; + const className = getNameOfSymbolAsWritten(getSymbolOfDeclaration(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; + } + + 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: ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode) { + if (node.kind === SyntaxKind.InterfaceDeclaration) { + const nodeSymbol = getSymbolOfDeclaration(node); + // 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(getSymbolOfDeclaration(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 (!checkGrammarModifiers(node) && !checkGrammarProperty(node)) checkGrammarComputedPropertyName(node.name); + checkVariableLikeDeclaration(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)); + } + } + + 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); + } + + // Grammar checking for modifiers is done inside the function checkGrammarFunctionLikeDeclaration + checkFunctionOrMethodDeclaration(node); + + // 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)); + } + + // 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); + } + + setNodeLinksForPrivateIdentifierScope(node); + } + + function setNodeLinksForPrivateIdentifierScope(node: PropertyDeclaration | PropertySignature | MethodDeclaration | MethodSignature | AccessorDeclaration) { + if (isPrivateIdentifier(node.name)) { + if ( + languageVersion < LanguageFeatureMinimumTarget.PrivateNamesAndClassStaticBlocks || + languageVersion < LanguageFeatureMinimumTarget.ClassAndClassElementDecorators || + !useDefineForClassFields + ) { + for (let lexicalScope = getEnclosingBlockScopeContainer(node); !!lexicalScope; lexicalScope = getEnclosingBlockScopeContainer(lexicalScope)) { + getNodeLinks(lexicalScope).flags |= NodeCheckFlags.ContainsClassWithPrivateIdentifiers; + } + + // 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 checkClassStaticBlockDeclaration(node: ClassStaticBlockDeclaration) { + checkGrammarModifiers(node); + + forEachChild(node, checkSourceElement); + } + + 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); + + checkSourceElement(node.body); + + const symbol = getSymbolOfDeclaration(node); + const firstDeclaration = getDeclarationOfKind(symbol, node.kind); + + // 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; + } + + addLazyDiagnostic(checkConstructorDeclarationDiagnostics); + + return; + + 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; + 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 = !emitStandardClassFields && + (some(node.parent.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; + + 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); + } + } + } + } + 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 nodeImmediatelyReferencesSuperOrThis(node: Node): boolean { + if (node.kind === SyntaxKind.SuperKeyword || node.kind === SyntaxKind.ThisKeyword) { + return true; + } + + if (isThisContainerOrFunctionBlock(node)) { + return false; + } + + return !!forEachChild(node, nodeImmediatelyReferencesSuperOrThis); + } + + function checkAccessorDeclaration(node: AccessorDeclaration) { + if (isIdentifier(node.name) && idText(node.name) === "constructor" && isClassLike(node.parent)) { + 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); + + 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); + } + + 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 = getSymbolOfDeclaration(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 returnType = getTypeOfAccessors(getSymbolOfDeclaration(node)); + if (node.kind === SyntaxKind.GetAccessor) { + checkAllCodePathsInNonVoidFunctionReturnOrThrow(node, returnType); + } + } + } + + 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 getEffectiveTypeArguments(node: TypeReferenceNode | ExpressionWithTypeArguments | NodeWithTypeArguments, typeParameters: readonly TypeParameter[]): Type[] { + return fillMissingTypeArguments(map(node.typeArguments!, getTypeFromTypeNode), typeParameters, getMinTypeArgumentCount(typeParameters), isInJSFile(node)); + } + + function checkTypeArgumentConstraints(node: TypeReferenceNode | ExpressionWithTypeArguments | NodeWithTypeArguments, 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 getTypeParametersForTypeAndSymbol(type: Type, symbol: Symbol) { + if (!isErrorType(type)) { + return symbol.flags & SymbolFlags.TypeAlias && getSymbolLinks(symbol).typeParameters || + (getObjectFlags(type) & ObjectFlags.Reference ? (type as TypeReference).target.localTypeParameters : undefined); + } + return undefined; + } + + function getTypeParametersForTypeReferenceOrImport(node: TypeReferenceNode | ExpressionWithTypeArguments | ImportTypeNode) { + const type = getTypeFromTypeNode(node); + if (!isErrorType(type)) { + const symbol = getNodeLinks(node).resolvedSymbol; + if (symbol) { + return getTypeParametersForTypeAndSymbol(type, symbol); + } + } + return undefined; + } + + function checkTypeReferenceNode(node: TypeReferenceNode | ExpressionWithTypeArguments) { + checkGrammarTypeArguments(node, node.typeArguments); + if (node.kind === SyntaxKind.TypeReference && !isInJSFile(node) && !isInJSDoc(node) && node.typeArguments && node.typeName.end !== node.typeArguments.pos) { + // If there was a token between the type name and the type arguments, check if it was a DotToken + const sourceFile = getSourceFileOfNode(node); + if (scanTokenAtPosition(sourceFile, node.typeName.end) === SyntaxKind.DotToken) { + grammarErrorAtPos(node, skipTrivia(sourceFile.text, node.typeName.end), 1, Diagnostics.JSDoc_types_can_only_be_used_inside_documentation_comments); + } + } + forEach(node.typeArguments, checkSourceElement); + checkTypeReferenceOrImport(node); + } + + function checkTypeReferenceOrImport(node: TypeReferenceNode | ExpressionWithTypeArguments | ImportTypeNode) { + const type = getTypeFromTypeNode(node); + if (!isErrorType(type)) { + if (node.typeArguments) { + addLazyDiagnostic(() => { + const typeParameters = getTypeParametersForTypeReferenceOrImport(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 = getTypeParametersForTypeReferenceOrImport(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 checkTypeLiteral(node: TypeLiteralNode) { + forEach(node.members, checkSourceElement); + addLazyDiagnostic(checkTypeLiteralDiagnostics); + + function checkTypeLiteralDiagnostics() { + const type = getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node); + checkIndexConstraints(type, type.symbol); + checkTypeForDuplicateIndexSignatures(node); + checkObjectTypeForDuplicateDeclarations(node); + } + } + + function checkArrayType(node: ArrayTypeNode) { + checkSourceElement(node.elementType); + } + + function checkTupleType(node: TupleTypeNode) { + let seenOptionalElement = false; + let seenRestElement = false; + for (const e of node.elements) { + let 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) { + flags |= ElementFlags.Rest; + } + } + if (flags & ElementFlags.Rest) { + if (seenRestElement) { + grammarErrorOnNode(e, Diagnostics.A_rest_element_cannot_follow_another_rest_element); + break; + } + 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 (flags & ElementFlags.Required && seenOptionalElement) { + grammarErrorOnNode(e, Diagnostics.A_required_element_cannot_follow_an_optional_element); + break; + } + } + 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; + // skip index type deferral on remapping mapped types + const objectIndexType = isGenericMappedType(objectType) && getMappedTypeNameTypeKind(objectType) === MappedTypeNameTypeKind.Remapping + ? getIndexTypeForMappedType(objectType, IndexFlags.None) + : getIndexType(objectType, IndexFlags.None); + const hasNumberIndexInfo = !!getIndexInfoOfType(objectType, numberType); + if (everyType(indexType, t => isTypeAssignableTo(t, objectIndexType) || hasNumberIndexInfo && isApplicableIndexType(t, numberType))) { + 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; + } + if (isGenericObjectType(objectType)) { + const propertyName = getPropertyNameFromIndex(indexType, accessNode); + if (propertyName) { + const propertySymbol = forEachType(getApparentType(objectType), t => getPropertyOfType(t, propertyName)); + if (propertySymbol && getDeclarationModifierFlagsFromSymbol(propertySymbol) & ModifierFlags.NonPublicAccessibilityModifier) { + error(accessNode, Diagnostics.Private_or_protected_member_0_cannot_be_accessed_on_a_type_parameter, unescapeLeadingUnderscores(propertyName)); + return errorType; + } + } + } + error(accessNode, Diagnostics.Type_0_cannot_be_used_to_index_type_1, typeToString(indexType), typeToString(objectType)); + return errorType; + } + + function checkIndexedAccessType(node: IndexedAccessTypeNode) { + checkSourceElement(node.objectType); + checkSourceElement(node.indexType); + checkIndexedAccessIndexType(getTypeFromIndexedAccessTypeNode(node), node); + } + + function checkMappedType(node: MappedTypeNode) { + checkGrammarMappedType(node); + checkSourceElement(node.typeParameter); + checkSourceElement(node.nameType); + checkSourceElement(node.type); + + if (!node.type) { + reportImplicitAny(node, anyType); + } + + const type = getTypeFromMappedTypeNode(node) as MappedType; + const nameType = getNameTypeFromMappedType(type); + if (nameType) { + checkTypeAssignableTo(nameType, stringNumberSymbolType, node.nameType); + } + else { + const constraintType = getConstraintTypeFromMappedType(type); + checkTypeAssignableTo(constraintType, stringNumberSymbolType, getEffectiveConstraintOfTypeParameter(node.typeParameter)); + } + } + + function checkGrammarMappedType(node: MappedTypeNode) { + if (node.members?.length) { + return grammarErrorOnNode(node.members[0], Diagnostics.A_mapped_type_may_not_declare_properties_or_methods); + } + } + + function checkThisType(node: ThisTypeNode) { + getTypeFromThisTypeNode(node); + } + + function checkTypeOperator(node: TypeOperatorNode) { + checkGrammarTypeOperatorNode(node); + checkSourceElement(node.type); + } + + function checkConditionalType(node: ConditionalTypeNode) { + forEachChild(node, checkSourceElement); + } + + 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 = getSymbolOfDeclaration(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); + } + + 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 checkImportType(node: ImportTypeNode) { + checkSourceElement(node.argument); + + if (node.attributes) { + getResolutionModeOverride(node.attributes, grammarErrorOnNode); + } + checkTypeReferenceOrImport(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); + } + + function isPrivateWithinAmbient(node: Node): boolean { + return (hasEffectiveModifier(node, ModifierFlags.Private) || isPrivateIdentifierClassElementDeclaration(node)) && !!(node.flags & NodeFlags.Ambient); + } + + function getEffectiveDeclarationFlags(n: Declaration, flagsToCheck: ModifierFlags): ModifierFlags { + let flags = getCombinedModifierFlagsCached(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 + ) { + const container = getEnclosingContainer(n); + if ((container && container.flags & NodeFlags.ExportContext) && !(flags & ModifierFlags.Ambient) && !(isModuleBlock(n.parent) && isModuleDeclaration(n.parent.parent) && isGlobalScopeAugmentation(n.parent.parent))) { + // It is nested in an ambient export context, which means it is automatically exported + flags |= ModifierFlags.Export; + } + flags |= ModifierFlags.Ambient; + } + + return flags & flagsToCheck; + } + + 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); + } + }); + } + } + + 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); + } + }); + } + } + + 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 reportImplementationExpectedError(node: SignatureDeclaration): void { + if (node.name && nodeIsMissing(node.name)) { + return; + } + + 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 + isComputedPropertyName(node.name) && isComputedPropertyName(subsequentName) && isTypeIdenticalTo(checkComputedPropertyName(node.name), checkComputedPropertyName(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); + } + 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); + } + } + } + + 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; + } + + previousDeclaration = node; + + if (!inAmbientContextOrInterface) { + lastSeenNonAmbientDeclaration = node as FunctionLikeDeclaration; + } + } + if (isInJSFile(current) && isFunctionLike(current) && current.jsDoc) { + hasOverloads = length(getJSDocOverloadTags(current)) > 0; + } + } + } + + if (multipleConstructorImplementation) { + forEach(functionDeclarations, declaration => { + error(declaration, Diagnostics.Multiple_constructor_implementations_are_not_allowed); + }); + } + + if (duplicateFunctionDeclaration) { + forEach(functionDeclarations, declaration => { + error(getNameOfDeclaration(declaration) || declaration, Diagnostics.Duplicate_function_implementation); + }); + } + + 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, + ); + } + }); + } + + // 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)) { + const errorNode = signature.declaration && isJSDocSignature(signature.declaration) + ? (signature.declaration.parent as JSDocOverloadTag | JSDocCallbackTag).tagName + : signature.declaration; + addRelatedInfo( + error(errorNode, Diagnostics.This_overload_signature_is_not_compatible_with_its_implementation_signature), + createDiagnosticForNode(bodyDeclaration, Diagnostics.The_implementation_signature_is_declared_here), + ); + break; + } + } + } + } + } + + function checkExportsOnMergedDeclarations(node: Declaration): void { + addLazyDiagnostic(() => checkExportsOnMergedDeclarationsWorker(node)); + } + + 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 = getSymbolOfDeclaration(node)!; + if (!symbol.exportSymbol) { + // this is a pure local symbol (all declarations are non-exported) - no need to check anything + return; + } + } + + // run the check only for the first declaration in the list + if (getDeclarationOfKind(symbol, node.kind) !== node) { + 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; + } + else { + 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); + + 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)); + } + } + } + + function getDeclarationSpaces(decl: Declaration): DeclarationSpaces { + let d = decl as Node; + switch (d.kind) { + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.TypeAliasDeclaration: + + // 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) ? nod diff --git a/extensions/vscode-colorize-perf-tests/test/colorize-fixtures/test-treeView.ts b/extensions/vscode-colorize-perf-tests/test/colorize-fixtures/test-treeView.ts new file mode 100644 index 0000000000000..08927639e7627 --- /dev/null +++ b/extensions/vscode-colorize-perf-tests/test/colorize-fixtures/test-treeView.ts @@ -0,0 +1,2067 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { DataTransfers, IDragAndDropData } from '../../../../base/browser/dnd.js'; +import * as DOM from '../../../../base/browser/dom.js'; +import * as cssJs from '../../../../base/browser/cssValue.js'; +import { renderMarkdownAsPlaintext } from '../../../../base/browser/markdownRenderer.js'; +import { ActionBar, IActionViewItemProvider } from '../../../../base/browser/ui/actionbar/actionbar.js'; +import { ActionViewItem } from '../../../../base/browser/ui/actionbar/actionViewItems.js'; +import { IHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegate.js'; +import { IIdentityProvider, IListVirtualDelegate } from '../../../../base/browser/ui/list/list.js'; +import { ElementsDragAndDropData, ListViewTargetSector } from '../../../../base/browser/ui/list/listView.js'; +import { IAsyncDataSource, ITreeContextMenuEvent, ITreeDragAndDrop, ITreeDragOverReaction, ITreeNode, ITreeRenderer, TreeDragOverBubble } from '../../../../base/browser/ui/tree/tree.js'; +import { CollapseAllAction } from '../../../../base/browser/ui/tree/treeDefaults.js'; +import { ActionRunner, IAction, Separator } from '../../../../base/common/actions.js'; +import { timeout } from '../../../../base/common/async.js'; +import { CancellationToken, CancellationTokenSource } from '../../../../base/common/cancellation.js'; +import { Codicon } from '../../../../base/common/codicons.js'; +import { isCancellationError } from '../../../../base/common/errors.js'; +import { Emitter, Event } from '../../../../base/common/event.js'; +import { createMatches, FuzzyScore } from '../../../../base/common/filters.js'; +import { IMarkdownString, isMarkdownString, MarkdownString } from '../../../../base/common/htmlContent.js'; +import { Disposable, DisposableStore, IDisposable, MutableDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; +import { Mimes } from '../../../../base/common/mime.js'; +import { Schemas } from '../../../../base/common/network.js'; +import { basename, dirname } from '../../../../base/common/resources.js'; +import { isFalsyOrWhitespace } from '../../../../base/common/strings.js'; +import { isString } from '../../../../base/common/types.js'; +import { URI } from '../../../../base/common/uri.js'; +import { generateUuid } from '../../../../base/common/uuid.js'; +import './media/views.css'; +import { VSDataTransfer } from '../../../../base/common/dataTransfer.js'; +import { localize } from '../../../../nls.js'; +import { createActionViewItem, getContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { Action2, IMenuService, MenuId, MenuRegistry, registerAction2 } from '../../../../platform/actions/common/actions.js'; +import { CommandsRegistry, ICommandService } from '../../../../platform/commands/common/commands.js'; +import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; +import { ContextKeyExpr, ContextKeyExpression, IContextKey, IContextKeyChangeEvent, IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js'; +import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js'; +import { FileKind } from '../../../../platform/files/common/files.js'; +import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; +import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; +import { ILabelService } from '../../../../platform/label/common/label.js'; +import { WorkbenchAsyncDataTree } from '../../../../platform/list/browser/listService.js'; +import { ILogService } from '../../../../platform/log/common/log.js'; +import { INotificationService } from '../../../../platform/notification/common/notification.js'; +import { IOpenerService } from '../../../../platform/opener/common/opener.js'; +import { IProgressService } from '../../../../platform/progress/common/progress.js'; +import { Registry } from '../../../../platform/registry/common/platform.js'; +import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; +import { ColorScheme } from '../../../../platform/theme/common/theme.js'; +import { FileThemeIcon, FolderThemeIcon, IThemeService } from '../../../../platform/theme/common/themeService.js'; +import { ThemeIcon } from '../../../../base/common/themables.js'; +import { fillEditorsDragData } from '../../dnd.js'; +import { IResourceLabel, ResourceLabels } from '../../labels.js'; +import { API_OPEN_DIFF_EDITOR_COMMAND_ID, API_OPEN_EDITOR_COMMAND_ID } from '../editor/editorCommands.js'; +import { getLocationBasedViewColors, IViewPaneOptions, ViewPane } from './viewPane.js'; +import { IViewletViewOptions } from './viewsViewlet.js'; +import { Extensions, ITreeItem, ITreeItemLabel, ITreeView, ITreeViewDataProvider, ITreeViewDescriptor, ITreeViewDragAndDropController, IViewBadge, IViewDescriptorService, IViewsRegistry, ResolvableTreeItem, TreeCommand, TreeItemCollapsibleState, TreeViewItemHandleArg, TreeViewPaneHandleArg, ViewContainer, ViewContainerLocation } from '../../../common/views.js'; +import { IActivityService, NumberBadge } from '../../../services/activity/common/activity.js'; +import { IExtensionService } from '../../../services/extensions/common/extensions.js'; +import { IHoverService, WorkbenchHoverDelegate } from '../../../../platform/hover/browser/hover.js'; +import { CodeDataTransfers, LocalSelectionTransfer } from '../../../../platform/dnd/browser/dnd.js'; +import { toExternalVSDataTransfer } from '../../../../editor/browser/dnd.js'; +import { CheckboxStateHandler, TreeItemCheckbox } from './checkbox.js'; +import { setTimeout0 } from '../../../../base/common/platform.js'; +import { AriaRole } from '../../../../base/browser/ui/aria/aria.js'; +import { TelemetryTrustedValue } from '../../../../platform/telemetry/common/telemetryUtils.js'; +import { ITreeViewsDnDService } from '../../../../editor/common/services/treeViewsDndService.js'; +import { DraggedTreeItemsIdentifier } from '../../../../editor/common/services/treeViewsDnd.js'; +import { IMarkdownRenderResult, MarkdownRenderer } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import type { IManagedHoverTooltipMarkdownString } from '../../../../base/browser/ui/hover/hover.js'; +import { parseLinkedText } from '../../../../base/common/linkedText.js'; +import { Button } from '../../../../base/browser/ui/button/button.js'; +import { defaultButtonStyles } from '../../../../platform/theme/browser/defaultStyles.js'; +import { IAccessibleViewInformationService } from '../../../services/accessibility/common/accessibleViewInformationService.js'; +import { Command } from '../../../../editor/common/languages.js'; + +export class TreeViewPane extends ViewPane { + + protected readonly treeView: ITreeView; + private _container: HTMLElement | undefined; + private _actionRunner: MultipleSelectionActionRunner; + + constructor( + options: IViewletViewOptions, + @IKeybindingService keybindingService: IKeybindingService, + @IContextMenuService contextMenuService: IContextMenuService, + @IConfigurationService configurationService: IConfigurationService, + @IContextKeyService contextKeyService: IContextKeyService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, + @IInstantiationService instantiationService: IInstantiationService, + @IOpenerService openerService: IOpenerService, + @IThemeService themeService: IThemeService, + @ITelemetryService telemetryService: ITelemetryService, + @INotificationService notificationService: INotificationService, + @IHoverService hoverService: IHoverService, + @IAccessibleViewInformationService accessibleViewService: IAccessibleViewInformationService, + ) { + super({ ...(options as IViewPaneOptions), titleMenuId: MenuId.ViewTitle, donotForwardArgs: false }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService, hoverService, accessibleViewService); + const { treeView } = (Registry.as(Extensions.ViewsRegistry).getView(options.id)); + this.treeView = treeView; + this._register(this.treeView.onDidChangeActions(() => this.updateActions(), this)); + this._register(this.treeView.onDidChangeTitle((newTitle) => this.updateTitle(newTitle))); + this._register(this.treeView.onDidChangeDescription((newDescription) => this.updateTitleDescription(newDescription))); + this._register(toDisposable(() => { + if (this._container && this.treeView.container && (this._container === this.treeView.container)) { + this.treeView.setVisibility(false); + } + })); + this._register(this.onDidChangeBodyVisibility(() => this.updateTreeVisibility())); + this._register(this.treeView.onDidChangeWelcomeState(() => this._onDidChangeViewWelcomeState.fire())); + if (options.title !== this.treeView.title) { + this.updateTitle(this.treeView.title); + } + if (options.titleDescription !== this.treeView.description) { + this.updateTitleDescription(this.treeView.description); + } + this._actionRunner = new MultipleSelectionActionRunner(notificationService, () => this.treeView.getSelection()); + + this.updateTreeVisibility(); + } + + override focus(): void { + super.focus(); + this.treeView.focus(); + } + + protected override renderBody(container: HTMLElement): void { + this._container = container; + super.renderBody(container); + this.renderTreeView(container); + } + + override shouldShowWelcome(): boolean { + return ((this.treeView.dataProvider === undefined) || !!this.treeView.dataProvider.isTreeEmpty) && ((this.treeView.message === undefined) || (this.treeView.message === '')); + } + + protected override layoutBody(height: number, width: number): void { + super.layoutBody(height, width); + this.layoutTreeView(height, width); + } + + override getOptimalWidth(): number { + return this.treeView.getOptimalWidth(); + } + + protected renderTreeView(container: HTMLElement): void { + this.treeView.show(container); + } + + protected layoutTreeView(height: number, width: number): void { + this.treeView.layout(height, width); + } + + private updateTreeVisibility(): void { + this.treeView.setVisibility(this.isBodyVisible()); + } + + override getActionRunner() { + return this._actionRunner; + } + + override getActionsContext(): TreeViewPaneHandleArg { + return { $treeViewId: this.id, $focusedTreeItem: true, $selectedTreeItems: true }; + } + +} + +class Root implements ITreeItem { + label = { label: 'root' }; + handle = '0'; + parentHandle: string | undefined = undefined; + collapsibleState = TreeItemCollapsibleState.Expanded; + children: ITreeItem[] | undefined = undefined; +} + +function commandPreconditions(commandId: string): ContextKeyExpression | undefined { + const command = CommandsRegistry.getCommand(commandId); + if (command) { + const commandAction = MenuRegistry.getCommand(command.id); + return commandAction && commandAction.precondition; + } + return undefined; +} + +function isTreeCommandEnabled(treeCommand: TreeCommand | Command, contextKeyService: IContextKeyService): boolean { + const commandId: string = (treeCommand as TreeCommand).originalId ? (treeCommand as TreeCommand).originalId! : treeCommand.id; + const precondition = commandPreconditions(commandId); + if (precondition) { + return contextKeyService.contextMatchesRules(precondition); + } + + return true; +} + +interface RenderedMessage { element: HTMLElement; disposables: DisposableStore } + +function isRenderedMessageValue(messageValue: string | RenderedMessage | undefined): messageValue is RenderedMessage { + return !!messageValue && typeof messageValue !== 'string' && 'element' in messageValue && 'disposables' in messageValue; +} + +const noDataProviderMessage = localize('no-dataprovider', "There is no data provider registered that can provide view data."); + +export const RawCustomTreeViewContextKey = new RawContextKey('customTreeView', false); + +class Tree extends WorkbenchAsyncDataTree { } + +abstract class AbstractTreeView extends Disposable implements ITreeView { + + private isVisible: boolean = false; + private _hasIconForParentNode = false; + private _hasIconForLeafNode = false; + + private collapseAllContextKey: RawContextKey | undefined; + private collapseAllContext: IContextKey | undefined; + private collapseAllToggleContextKey: RawContextKey | undefined; + private collapseAllToggleContext: IContextKey | undefined; + private refreshContextKey: RawContextKey | undefined; + private refreshContext: IContextKey | undefined; + + private focused: boolean = false; + private domNode!: HTMLElement; + private treeContainer: HTMLElement | undefined; + private _messageValue: string | { element: HTMLElement; disposables: DisposableStore } | undefined; + private _canSelectMany: boolean = false; + private _manuallyManageCheckboxes: boolean = false; + private messageElement: HTMLElement | undefined; + private tree: Tree | undefined; + private treeLabels: ResourceLabels | undefined; + private treeViewDnd: CustomTreeViewDragAndDrop | undefined; + private _container: HTMLElement | undefined; + + private root: ITreeItem; + private markdownRenderer: MarkdownRenderer | undefined; + private elementsToRefresh: ITreeItem[] = []; + private lastSelection: readonly ITreeItem[] = []; + private lastActive: ITreeItem; + + private readonly _onDidExpandItem: Emitter = this._register(new Emitter()); + readonly onDidExpandItem: Event = this._onDidExpandItem.event; + + private readonly _onDidCollapseItem: Emitter = this._register(new Emitter()); + readonly onDidCollapseItem: Event = this._onDidCollapseItem.event; + + private _onDidChangeSelectionAndFocus: Emitter<{ selection: readonly ITreeItem[]; focus: ITreeItem }> = this._register(new Emitter<{ selection: readonly ITreeItem[]; focus: ITreeItem }>()); + readonly onDidChangeSelectionAndFocus: Event<{ selection: readonly ITreeItem[]; focus: ITreeItem }> = this._onDidChangeSelectionAndFocus.event; + + private readonly _onDidChangeVisibility: Emitter = this._register(new Emitter()); + readonly onDidChangeVisibility: Event = this._onDidChangeVisibility.event; + + private readonly _onDidChangeActions: Emitter = this._register(new Emitter()); + readonly onDidChangeActions: Event = this._onDidChangeActions.event; + + private readonly _onDidChangeWelcomeState: Emitter = this._register(new Emitter()); + readonly onDidChangeWelcomeState: Event = this._onDidChangeWelcomeState.event; + + private readonly _onDidChangeTitle: Emitter = this._register(new Emitter()); + readonly onDidChangeTitle: Event = this._onDidChangeTitle.event; + + private readonly _onDidChangeDescription: Emitter = this._register(new Emitter()); + readonly onDidChangeDescription: Event = this._onDidChangeDescription.event; + + private readonly _onDidChangeCheckboxState: Emitter = this._register(new Emitter()); + readonly onDidChangeCheckboxState: Event = this._onDidChangeCheckboxState.event; + + private readonly _onDidCompleteRefresh: Emitter = this._register(new Emitter()); + + constructor( + readonly id: string, + private _title: string, + @IThemeService private readonly themeService: IThemeService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @ICommandService private readonly commandService: ICommandService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @IProgressService protected readonly progressService: IProgressService, + @IContextMenuService private readonly contextMenuService: IContextMenuService, + @IKeybindingService private readonly keybindingService: IKeybindingService, + @INotificationService private readonly notificationService: INotificationService, + @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService, + @IHoverService private readonly hoverService: IHoverService, + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IActivityService private readonly activityService: IActivityService, + @ILogService private readonly logService: ILogService, + @IOpenerService private readonly openerService: IOpenerService + ) { + super(); + this.root = new Root(); + this.lastActive = this.root; + // Try not to add anything that could be costly to this constructor. It gets called once per tree view + // during startup, and anything added here can affect performance. + } + + private _isInitialized: boolean = false; + private initialize() { + if (this._isInitialized) { + return; + } + this._isInitialized = true; + + // Remember when adding to this method that it isn't called until the the view is visible, meaning that + // properties could be set and events could be fired before we're initialized and that this needs to be handled. + + this.contextKeyService.bufferChangeEvents(() => { + this.initializeShowCollapseAllAction(); + this.initializeCollapseAllToggle(); + this.initializeShowRefreshAction(); + }); + + this.treeViewDnd = this.instantiationService.createInstance(CustomTreeViewDragAndDrop, this.id); + if (this._dragAndDropController) { + this.treeViewDnd.controller = this._dragAndDropController; + } + + this._register(this.configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('explorer.decorations')) { + this.doRefresh([this.root]); /** soft refresh **/ + } + })); + this._register(this.viewDescriptorService.onDidChangeLocation(({ views, from, to }) => { + if (views.some(v => v.id === this.id)) { + this.tree?.updateOptions({ overrideStyles: getLocationBasedViewColors(this.viewLocation).listOverrideStyles }); + } + })); + this.registerActions(); + + this.create(); + } + + get viewContainer(): ViewContainer { + return this.viewDescriptorService.getViewContainerByViewId(this.id)!; + } + + get viewLocation(): ViewContainerLocation { + return this.viewDescriptorService.getViewLocationById(this.id)!; + } + private _dragAndDropController: ITreeViewDragAndDropController | undefined; + get dragAndDropController(): ITreeViewDragAndDropController | undefined { + return this._dragAndDropController; + } + set dragAndDropController(dnd: ITreeViewDragAndDropController | undefined) { + this._dragAndDropController = dnd; + if (this.treeViewDnd) { + this.treeViewDnd.controller = dnd; + } + } + + private _dataProvider: ITreeViewDataProvider | undefined; + get dataProvider(): ITreeViewDataProvider | undefined { + return this._dataProvider; + } + + set dataProvider(dataProvider: ITreeViewDataProvider | undefined) { + if (dataProvider) { + if (this.visible) { + this.activate(); + } + const self = this; + this._dataProvider = new class implements ITreeViewDataProvider { + private _isEmpty: boolean = true; + private _onDidChangeEmpty: Emitter = new Emitter(); + public onDidChangeEmpty: Event = this._onDidChangeEmpty.event; + + get isTreeEmpty(): boolean { + return this._isEmpty; + } + + async getChildren(element?: ITreeItem): Promise { + const batches = await this.getChildrenBatch(element ? [element] : undefined); + return batches?.[0]; + } + + private updateEmptyState(nodes: ITreeItem[], childrenGroups: ITreeItem[][]): void { + if ((nodes.length === 1) && (nodes[0] instanceof Root)) { + const oldEmpty = this._isEmpty; + this._isEmpty = (childrenGroups.length === 0) || (childrenGroups[0].length === 0); + if (oldEmpty !== this._isEmpty) { + this._onDidChangeEmpty.fire(); + } + } + } + + private findCheckboxesUpdated(nodes: ITreeItem[], childrenGroups: ITreeItem[][]): ITreeItem[] { + if (childrenGroups.length === 0) { + return []; + } + const checkboxesUpdated: ITreeItem[] = []; + + for (let i = 0; i < nodes.length; i++) { + const node = nodes[i]; + const children = childrenGroups[i]; + for (const child of children) { + child.parent = node; + if (!self.manuallyManageCheckboxes && (node?.checkbox?.isChecked === true) && (child.checkbox?.isChecked === false)) { + child.checkbox.isChecked = true; + checkboxesUpdated.push(child); + } + } + } + return checkboxesUpdated; + } + + async getChildrenBatch(nodes?: ITreeItem[]): Promise { + let childrenGroups: ITreeItem[][]; + let checkboxesUpdated: ITreeItem[] = []; + if (nodes && nodes.every((node): node is Required => !!node.children)) { + childrenGroups = nodes.map(node => node.children); + } else { + nodes = nodes ?? [self.root]; + const batchedChildren = await (nodes.length === 1 && nodes[0] instanceof Root ? doGetChildrenOrBatch(dataProvider, undefined) : doGetChildrenOrBatch(dataProvider, nodes)); + for (let i = 0; i < nodes.length; i++) { + const node = nodes[i]; + node.children = batchedChildren ? batchedChildren[i] : undefined; + } + childrenGroups = batchedChildren ?? []; + checkboxesUpdated = this.findCheckboxesUpdated(nodes, childrenGroups); + } + + this.updateEmptyState(nodes, childrenGroups); + + if (checkboxesUpdated.length > 0) { + self._onDidChangeCheckboxState.fire(checkboxesUpdated); + } + return childrenGroups; + } + }; + if (this._dataProvider.onDidChangeEmpty) { + this._register(this._dataProvider.onDidChangeEmpty(() => { + this.updateCollapseAllToggle(); + this._onDidChangeWelcomeState.fire(); + })); + } + this.updateMessage(); + this.refresh(); + } else { + this._dataProvider = undefined; + this.treeDisposables.clear(); + this.activated = false; + this.updateMessage(); + } + + this._onDidChangeWelcomeState.fire(); + } + + private _message: string | IMarkdownString | undefined; + get message(): string | IMarkdownString | undefined { + return this._message; + } + + set message(message: string | IMarkdownString | undefined) { + this._message = message; + this.updateMessage(); + this._onDidChangeWelcomeState.fire(); + } + + get title(): string { + return this._title; + } + + set title(name: string) { + this._title = name; + this._onDidChangeTitle.fire(this._title); + } + + private _description: string | undefined; + get description(): string | undefined { + return this._description; + } + + set description(description: string | undefined) { + this._description = description; + this._onDidChangeDescription.fire(this._description); + } + + private _badge: IViewBadge | undefined; + private readonly _activity = this._register(new MutableDisposable()); + + get badge(): IViewBadge | undefined { + return this._badge; + } + + set badge(badge: IViewBadge | undefined) { + + if (this._badge?.value === badge?.value && + this._badge?.tooltip === badge?.tooltip) { + return; + } + + this._badge = badge; + if (badge) { + const activity = { + badge: new NumberBadge(badge.value, () => badge.tooltip), + priority: 50 + }; + this._activity.value = this.activityService.showViewActivity(this.id, activity); + } else { + this._activity.clear(); + } + } + + get canSelectMany(): boolean { + return this._canSelectMany; + } + + set canSelectMany(canSelectMany: boolean) { + const oldCanSelectMany = this._canSelectMany; + this._canSelectMany = canSelectMany; + if (this._canSelectMany !== oldCanSelectMany) { + this.tree?.updateOptions({ multipleSelectionSupport: this.canSelectMany }); + } + } + + get manuallyManageCheckboxes(): boolean { + return this._manuallyManageCheckboxes; + } + + set manuallyManageCheckboxes(manuallyManageCheckboxes: boolean) { + this._manuallyManageCheckboxes = manuallyManageCheckboxes; + } + + get hasIconForParentNode(): boolean { + return this._hasIconForParentNode; + } + + get hasIconForLeafNode(): boolean { + return this._hasIconForLeafNode; + } + + get visible(): boolean { + return this.isVisible; + } + + private initializeShowCollapseAllAction(startingValue: boolean = false) { + if (!this.collapseAllContext) { + this.collapseAllContextKey = new RawContextKey(`treeView.${this.id}.enableCollapseAll`, startingValue, localize('treeView.enableCollapseAll', "Whether the the tree view with id {0} enables collapse all.", this.id)); + this.collapseAllContext = this.collapseAllContextKey.bindTo(this.contextKeyService); + } + return true; + } + + get showCollapseAllAction(): boolean { + this.initializeShowCollapseAllAction(); + return !!this.collapseAllContext?.get(); + } + + set showCollapseAllAction(showCollapseAllAction: boolean) { + this.initializeShowCollapseAllAction(showCollapseAllAction); + this.collapseAllContext?.set(showCollapseAllAction); + } + + + private initializeShowRefreshAction(startingValue: boolean = false) { + if (!this.refreshContext) { + this.refreshContextKey = new RawContextKey(`treeView.${this.id}.enableRefresh`, startingValue, localize('treeView.enableRefresh', "Whether the tree view with id {0} enables refresh.", this.id)); + this.refreshContext = this.refreshContextKey.bindTo(this.contextKeyService); + } + } + + get showRefreshAction(): boolean { + this.initializeShowRefreshAction(); + return !!this.refreshContext?.get(); + } + + set showRefreshAction(showRefreshAction: boolean) { + this.initializeShowRefreshAction(showRefreshAction); + this.refreshContext?.set(showRefreshAction); + } + + private registerActions() { + const that = this; + this._register(registerAction2(class extends Action2 { + constructor() { + super({ + id: `workbench.actions.treeView.${that.id}.refresh`, + title: localize('refresh', "Refresh"), + menu: { + id: MenuId.ViewTitle, + when: ContextKeyExpr.and(ContextKeyExpr.equals('view', that.id), that.refreshContextKey), + group: 'navigation', + order: Number.MAX_SAFE_INTEGER - 1, + }, + icon: Codicon.refresh + }); + } + async run(): Promise { + return that.refresh(); + } + })); + this._register(registerAction2(class extends Action2 { + constructor() { + super({ + id: `workbench.actions.treeView.${that.id}.collapseAll`, + title: localize('collapseAll', "Collapse All"), + menu: { + id: MenuId.ViewTitle, + when: ContextKeyExpr.and(ContextKeyExpr.equals('view', that.id), that.collapseAllContextKey), + group: 'navigation', + order: Number.MAX_SAFE_INTEGER, + }, + precondition: that.collapseAllToggleContextKey, + icon: Codicon.collapseAll + }); + } + async run(): Promise { + if (that.tree) { + return new CollapseAllAction(that.tree, true).run(); + } + } + })); + } + + setVisibility(isVisible: boolean): void { + // Throughout setVisibility we need to check if the tree view's data provider still exists. + // This can happen because the `getChildren` call to the extension can return + // after the tree has been disposed. + + this.initialize(); + isVisible = !!isVisible; + if (this.isVisible === isVisible) { + return; + } + + this.isVisible = isVisible; + + if (this.tree) { + if (this.isVisible) { + DOM.show(this.tree.getHTMLElement()); + } else { + DOM.hide(this.tree.getHTMLElement()); // make sure the tree goes out of the tabindex world by hiding it + } + + if (this.isVisible && this.elementsToRefresh.length && this.dataProvider) { + this.doRefresh(this.elementsToRefresh); + this.elementsToRefresh = []; + } + } + + setTimeout0(() => { + if (this.dataProvider) { + this._onDidChangeVisibility.fire(this.isVisible); + } + }); + + if (this.visible) { + this.activate(); + } + } + + protected activated: boolean = false; + protected abstract activate(): void; + + focus(reveal: boolean = true, revealItem?: ITreeItem): void { + if (this.tree && this.root.children && this.root.children.length > 0) { + // Make sure the current selected element is revealed + const element = revealItem ?? this.tree.getSelection()[0]; + if (element && reveal) { + this.tree.reveal(element, 0.5); + } + + // Pass Focus to Viewer + this.tree.domFocus(); + } else if (this.tree && this.treeContainer && !this.treeContainer.classList.contains('hide')) { + this.tree.domFocus(); + } else { + this.domNode.focus(); + } + } + + show(container: HTMLElement): void { + this._container = container; + DOM.append(container, this.domNode); + } + + private create() { + this.domNode = DOM.$('.tree-explorer-viewlet-tree-view'); + this.messageElement = DOM.append(this.domNode, DOM.$('.message')); + this.updateMessage(); + this.treeContainer = DOM.append(this.domNode, DOM.$('.customview-tree')); + this.treeContainer.classList.add('file-icon-themable-tree', 'show-file-icons'); + const focusTracker = this._register(DOM.trackFocus(this.domNode)); + this._register(focusTracker.onDidFocus(() => this.focused = true)); + this._register(focusTracker.onDidBlur(() => this.focused = false)); + } + + private readonly treeDisposables: DisposableStore = this._register(new DisposableStore()); + protected createTree() { + this.treeDisposables.clear(); + const actionViewItemProvider = createActionViewItem.bind(undefined, this.instantiationService); + const treeMenus = this.treeDisposables.add(this.instantiationService.createInstance(TreeMenus, this.id)); + this.treeLabels = this.treeDisposables.add(this.instantiationService.createInstance(ResourceLabels, this)); + const dataSource = this.instantiationService.createInstance(TreeDataSource, this, (task: Promise) => this.progressService.withProgress({ location: this.id }, () => task)); + const aligner = this.treeDisposables.add(new Aligner(this.themeService)); + const checkboxStateHandler = this.treeDisposables.add(new CheckboxStateHandler()); + const renderer = this.treeDisposables.add(this.instantiationService.createInstance(TreeRenderer, this.id, treeMenus, this.treeLabels, actionViewItemProvider, aligner, checkboxStateHandler, () => this.manuallyManageCheckboxes)); + this.treeDisposables.add(renderer.onDidChangeCheckboxState(e => this._onDidChangeCheckboxState.fire(e))); + + const widgetAriaLabel = this._title; + + this.tree = this.treeDisposables.add(this.instantiationService.createInstance(Tree, this.id, this.treeContainer!, new TreeViewDelegate(), [renderer], + dataSource, { + identityProvider: new TreeViewIdentityProvider(), + accessibilityProvider: { + getAriaLabel(element: ITreeItem): string | null { + if (element.accessibilityInformation) { + return element.accessibilityInformation.label; + } + + if (isString(element.tooltip)) { + return element.tooltip; + } else { + if (element.resourceUri && !element.label) { + // The custom tree has no good information on what should be used for the aria label. + // Allow the tree widget's default aria label to be used. + return null; + } + let buildAriaLabel: string = ''; + if (element.label) { + buildAriaLabel += element.label.label + ' '; + } + if (element.description) { + buildAriaLabel += element.description; + } + return buildAriaLabel; + } + }, + getRole(element: ITreeItem): AriaRole | undefined { + return element.accessibilityInformation?.role ?? 'treeitem'; + }, + getWidgetAriaLabel(): string { + return widgetAriaLabel; + } + }, + keyboardNavigationLabelProvider: { + getKeyboardNavigationLabel: (item: ITreeItem) => { + return item.label ? item.label.label : (item.resourceUri ? basename(URI.revive(item.resourceUri)) : undefined); + } + }, + expandOnlyOnTwistieClick: (e: ITreeItem) => { + return !!e.command || !!e.checkbox || this.configurationService.getValue<'singleClick' | 'doubleClick'>('workbench.tree.expandMode') === 'doubleClick'; + }, + collapseByDefault: (e: ITreeItem): boolean => { + return e.collapsibleState !== TreeItemCollapsibleState.Expanded; + }, + multipleSelectionSupport: this.canSelectMany, + dnd: this.treeViewDnd, + overrideStyles: getLocationBasedViewColors(this.viewLocation).listOverrideStyles + }) as WorkbenchAsyncDataTree); + + this.treeDisposables.add(renderer.onDidChangeMenuContext(e => e.forEach(e => this.tree?.rerender(e)))); + + this.treeDisposables.add(this.tree); + treeMenus.setContextKeyService(this.tree.contextKeyService); + aligner.tree = this.tree; + const actionRunner = this.treeDisposables.add(new MultipleSelectionActionRunner(this.notificationService, () => this.tree!.getSelection())); + renderer.actionRunner = actionRunner; + + this.tree.contextKeyService.createKey(this.id, true); + const customTreeKey = RawCustomTreeViewContextKey.bindTo(this.tree.contextKeyService); + customTreeKey.set(true); + this.treeDisposables.add(this.tree.onContextMenu(e => this.onContextMenu(treeMenus, e, actionRunner))); + + this.treeDisposables.add(this.tree.onDidChangeSelection(e => { + this.lastSelection = e.elements; + this.lastActive = this.tree?.getFocus()[0] ?? this.lastActive; + this._onDidChangeSelectionAndFocus.fire({ selection: this.lastSelection, focus: this.lastActive }); + })); + this.treeDisposables.add(this.tree.onDidChangeFocus(e => { + if (e.elements.length && (e.elements[0] !== this.lastActive)) { + this.lastActive = e.elements[0]; + this.lastSelection = this.tree?.getSelection() ?? this.lastSelection; + this._onDidChangeSelectionAndFocus.fire({ selection: this.lastSelection, focus: this.lastActive }); + } + })); + this.treeDisposables.add(this.tree.onDidChangeCollapseState(e => { + if (!e.node.element) { + return; + } + + const element: ITreeItem = Array.isArray(e.node.element.element) ? e.node.element.element[0] : e.node.element.element; + if (e.node.collapsed) { + this._onDidCollapseItem.fire(element); + } else { + this._onDidExpandItem.fire(element); + } + })); + this.tree.setInput(this.root).then(() => this.updateContentAreas()); + + this.treeDisposables.add(this.tree.onDidOpen(async (e) => { + if (!e.browserEvent) { + return; + } + if (e.browserEvent.target && (e.browserEvent.target as HTMLElement).classList.contains(TreeItemCheckbox.checkboxClass)) { + return; + } + const selection = this.tree!.getSelection(); + const command = await this.resolveCommand(selection.length === 1 ? selection[0] : undefined); + + if (command && isTreeCommandEnabled(command, this.contextKeyService)) { + let args = command.arguments || []; + if (command.id === API_OPEN_EDITOR_COMMAND_ID || command.id === API_OPEN_DIFF_EDITOR_COMMAND_ID) { + // Some commands owned by us should receive the + // `IOpenEvent` as context to open properly + args = [...args, e]; + } + + try { + await this.commandService.executeCommand(command.id, ...args); + } catch (err) { + this.notificationService.error(err); + } + } + })); + + this.treeDisposables.add(treeMenus.onDidChange((changed) => { + if (this.tree?.hasNode(changed)) { + this.tree?.rerender(changed); + } + })); + } + + private async resolveCommand(element: ITreeItem | undefined): Promise { + let command = element?.command; + if (element && !command) { + if ((element instanceof ResolvableTreeItem) && element.hasResolve) { + await element.resolve(CancellationToken.None); + command = element.command; + } + } + return command; + } + + private onContextMenu(treeMenus: TreeMenus, treeEvent: ITreeContextMenuEvent, actionRunner: MultipleSelectionActionRunner): void { + this.hoverService.hideHover(); + const node: ITreeItem | null = treeEvent.element; + if (node === null) { + return; + } + const event: UIEvent = treeEvent.browserEvent; + + event.preventDefault(); + event.stopPropagation(); + + this.tree!.setFocus([node]); + let selected = this.canSelectMany ? this.getSelection() : []; + if (!selected.find(item => item.handle === node.handle)) { + selected = [node]; + } + + const actions = treeMenus.getResourceContextActions(selected); + if (!actions.length) { + return; + } + this.contextMenuService.showContextMenu({ + getAnchor: () => treeEvent.anchor, + + getActions: () => actions, + + getActionViewItem: (action) => { + const keybinding = this.keybindingService.lookupKeybinding(action.id); + if (keybinding) { + return new ActionViewItem(action, action, { label: true, keybinding: keybinding.getLabel() }); + } + return undefined; + }, + + onHide: (wasCancelled?: boolean) => { + if (wasCancelled) { + this.tree!.domFocus(); + } + }, + + getActionsContext: () => ({ $treeViewId: this.id, $treeItemHandle: node.handle } satisfies TreeViewItemHandleArg), + + actionRunner + }); + } + + protected updateMessage(): void { + if (this._message) { + this.showMessage(this._message); + } else if (!this.dataProvider) { + this.showMessage(noDataProviderMessage); + } else { + this.hideMessage(); + } + this.updateContentAreas(); + } + + private processMessage(message: IMarkdownString, disposables: DisposableStore): HTMLElement { + const lines = message.value.split('\n'); + const result: (IMarkdownRenderResult | HTMLElement)[] = []; + let hasFoundButton = false; + for (const line of lines) { + const linkedText = parseLinkedText(line); + + if (linkedText.nodes.length === 1 && typeof linkedText.nodes[0] !== 'string') { + const node = linkedText.nodes[0]; + const buttonContainer = document.createElement('div'); + buttonContainer.classList.add('button-container'); + const button = new Button(buttonContainer, { title: node.title, secondary: hasFoundButton, supportIcons: true, ...defaultButtonStyles }); + button.label = node.label; + button.onDidClick(_ => { + this.openerService.open(node.href, { allowCommands: true }); + }, null, disposables); + + const href = URI.parse(node.href); + if (href.scheme === Schemas.command) { + const preConditions = commandPreconditions(href.path); + if (preConditions) { + button.enabled = this.contextKeyService.contextMatchesRules(preConditions); + disposables.add(this.contextKeyService.onDidChangeContext(e => { + if (e.affectsSome(new Set(preConditions.keys()))) { + button.enabled = this.contextKeyService.contextMatchesRules(preConditions); + } + })); + } + } + + disposables.add(button); + hasFoundButton = true; + result.push(buttonContainer); + } else { + hasFoundButton = false; + const rendered = this.markdownRenderer!.render(new MarkdownString(line, { isTrusted: message.isTrusted, supportThemeIcons: message.supportThemeIcons, supportHtml: message.supportHtml })); + result.push(rendered.element); + disposables.add(rendered); + } + } + + const container = document.createElement('div'); + container.classList.add('rendered-message'); + for (const child of result) { + if (DOM.isHTMLElement(child)) { + container.appendChild(child); + } else { + container.appendChild(child.element); + } + } + return container; + } + + private showMessage(message: string | IMarkdownString): void { + if (isRenderedMessageValue(this._messageValue)) { + this._messageValue.disposables.dispose(); + } + if (isMarkdownString(message) && !this.markdownRenderer) { + this.markdownRenderer = this.instantiationService.createInstance(MarkdownRenderer, {}); + } + if (isMarkdownString(message)) { + const disposables = new DisposableStore(); + const renderedMessage = this.processMessage(message, disposables); + this._messageValue = { element: renderedMessage, disposables }; + } else { + this._messageValue = message; + } + if (!this.messageElement) { + return; + } + this.messageElement.classList.remove('hide'); + this.resetMessageElement(); + if (typeof this._messageValue === 'string' && !isFalsyOrWhitespace(this._messageValue)) { + this.messageElement.textContent = this._messageValue; + } else if (isRenderedMessageValue(this._messageValue)) { + this.messageElement.appendChild(this._messageValue.element); + } + this.layout(this._height, this._width); + } + + private hideMessage(): void { + this.resetMessageElement(); + this.messageElement?.classList.add('hide'); + this.layout(this._height, this._width); + } + + private resetMessageElement(): void { + if (this.messageElement) { + DOM.clearNode(this.messageElement); + } + } + + private _height: number = 0; + private _width: number = 0; + layout(height: number, width: number) { + if (height && width && this.messageElement && this.treeContainer) { + this._height = height; + this._width = width; + const treeHeight = height - DOM.getTotalHeight(this.messageElement); + this.treeContainer.style.height = treeHeight + 'px'; + this.tree?.layout(treeHeight, width); + } + } + + getOptimalWidth(): number { + if (this.tree) { + const parentNode = this.tree.getHTMLElement(); + const childNodes = ([] as HTMLElement[]).slice.call(parentNode.querySelectorAll('.outline-item-label > a')); + return DOM.getLargestChildWidth(parentNode, childNodes); + } + return 0; + } + + private updateCheckboxes(elements: readonly ITreeItem[]): ITreeItem[] { + return setCascadingCheckboxUpdates(elements); + } + + async refresh(elements?: readonly ITreeItem[], checkboxes?: readonly ITreeItem[]): Promise { + if (this.dataProvider && this.tree) { + if (this.refreshing) { + await Event.toPromise(this._onDidCompleteRefresh.event); + } + if (!elements) { + elements = [this.root]; + // remove all waiting elements to refresh if root is asked to refresh + this.elementsToRefresh = []; + } + for (const element of elements) { + element.children = undefined; // reset children + } + if (this.isVisible) { + const affectedElements = this.updateCheckboxes(checkboxes ?? []); + return this.doRefresh(elements.concat(affectedElements)); + } else { + if (this.elementsToRefresh.length) { + const seen: Set = new Set(); + this.elementsToRefresh.forEach(element => seen.add(element.handle)); + for (const element of elements) { + if (!seen.has(element.handle)) { + this.elementsToRefresh.push(element); + } + } + } else { + this.elementsToRefresh.push(...elements); + } + } + } + return undefined; + } + + async expand(itemOrItems: ITreeItem | ITreeItem[]): Promise { + const tree = this.tree; + if (!tree) { + return; + } + try { + itemOrItems = Array.isArray(itemOrItems) ? itemOrItems : [itemOrItems]; + for (const element of itemOrItems) { + await tree.expand(element, false); + } + } catch (e) { + // The extension could have changed the tree during the reveal. + // Because of that, we ignore errors. + } + } + + isCollapsed(item: ITreeItem): boolean { + return !!this.tree?.isCollapsed(item); + } + + setSelection(items: ITreeItem[]): void { + this.tree?.setSelection(items); + } + + getSelection(): ITreeItem[] { + return this.tree?.getSelection() ?? []; + } + + setFocus(item?: ITreeItem): void { + if (this.tree) { + if (item) { + this.focus(true, item); + this.tree.setFocus([item]); + } else if (this.tree.getFocus().length === 0) { + this.tree.setFocus([]); + } + } + } + + async reveal(item: ITreeItem): Promise { + if (this.tree) { + return this.tree.reveal(item); + } + } + + private refreshing: boolean = false; + private async doRefresh(elements: readonly ITreeItem[]): Promise { + const tree = this.tree; + if (tree && this.visible) { + this.refreshing = true; + const oldSelection = tree.getSelection(); + try { + await Promise.all(elements.map(element => tree.updateChildren(element, true, true))); + } catch (e) { + // When multiple calls are made to refresh the tree in quick succession, + // we can get a "Tree element not found" error. This is expected. + // Ideally this is fixable, so log instead of ignoring so the error is preserved. + this.logService.error(e); + } + const newSelection = tree.getSelection(); + if (oldSelection.length !== newSelection.length || oldSelection.some((value, index) => value.handle !== newSelection[index].handle)) { + this.lastSelection = newSelection; + this._onDidChangeSelectionAndFocus.fire({ selection: this.lastSelection, focus: this.lastActive }); + } + this.refreshing = false; + this._onDidCompleteRefresh.fire(); + this.updateContentAreas(); + if (this.focused) { + this.focus(false); + } + this.updateCollapseAllToggle(); + } + } + + private initializeCollapseAllToggle() { + if (!this.collapseAllToggleContext) { + this.collapseAllToggleContextKey = new RawContextKey(`treeView.${this.id}.toggleCollapseAll`, false, localize('treeView.toggleCollapseAll', "Whether collapse all is toggled for the tree view with id {0}.", this.id)); + this.collapseAllToggleContext = this.collapseAllToggleContextKey.bindTo(this.contextKeyService); + } + } + + private updateCollapseAllToggle() { + if (this.showCollapseAllAction) { + this.initializeCollapseAllToggle(); + this.collapseAllToggleContext?.set(!!this.root.children && (this.root.children.length > 0) && + this.root.children.some(value => value.collapsibleState !== TreeItemCollapsibleState.None)); + } + } + + private updateContentAreas(): void { + const isTreeEmpty = !this.root.children || this.root.children.length === 0; + // Hide tree container only when there is a message and tree is empty and not refreshing + if (this._messageValue && isTreeEmpty && !this.refreshing && this.treeContainer) { + // If there's a dnd controller then hiding the tree prevents it from being dragged into. + if (!this.dragAndDropController) { + this.treeContainer.classList.add('hide'); + } + this.domNode.setAttribute('tabindex', '0'); + } else if (this.treeContainer) { + this.treeContainer.classList.remove('hide'); + if (this.domNode === DOM.getActiveElement()) { + this.focus(); + } + this.domNode.removeAttribute('tabindex'); + } + } + + get container(): HTMLElement | undefined { + return this._container; + } +} + +class TreeViewIdentityProvider implements IIdentityProvider { + getId(element: ITreeItem): { toString(): string } { + return element.handle; + } +} + +class TreeViewDelegate implements IListVirtualDelegate { + + getHeight(element: ITreeItem): number { + return TreeRenderer.ITEM_HEIGHT; + } + + getTemplateId(element: ITreeItem): string { + return TreeRenderer.TREE_TEMPLATE_ID; + } +} + +async function doGetChildrenOrBatch(dataProvider: ITreeViewDataProvider, nodes: ITreeItem[] | undefined): Promise { + if (dataProvider.getChildrenBatch) { + return dataProvider.getChildrenBatch(nodes); + } else { + if (nodes) { + return Promise.all(nodes.map(node => dataProvider.getChildren(node).then(children => children ?? []))); + } else { + return [await dataProvider.getChildren()].filter(children => children !== undefined); + } + } +} + +class TreeDataSource implements IAsyncDataSource { + + constructor( + private treeView: ITreeView, + private withProgress: (task: Promise) => Promise + ) { + } + + hasChildren(element: ITreeItem): boolean { + return !!this.treeView.dataProvider && (element.collapsibleState !== TreeItemCollapsibleState.None); + } + + private batch: ITreeItem[] | undefined; + private batchPromise: Promise | undefined; + async getChildren(element: ITreeItem): Promise { + const dataProvider = this.treeView.dataProvider; + if (!dataProvider) { + return []; + } + if (this.batch === undefined) { + this.batch = [element]; + this.batchPromise = undefined; + } else { + this.batch.push(element); + } + const indexInBatch = this.batch.length - 1; + return new Promise((resolve, reject) => { + setTimeout(async () => { + const batch = this.batch; + this.batch = undefined; + if (!this.batchPromise) { + this.batchPromise = this.withProgress(doGetChildrenOrBatch(dataProvider, batch)); + } + try { + const result = await this.batchPromise; + resolve((result && (indexInBatch < result.length)) ? result[indexInBatch] : []); + } catch (e) { + if (!(e.message).startsWith('Bad progress location:')) { + reject(e); + } + } + }, 0); + }); + } +} + +interface ITreeExplorerTemplateData { + readonly container: HTMLElement; + readonly resourceLabel: IResourceLabel; + readonly icon: HTMLElement; + readonly checkboxContainer: HTMLElement; + checkbox?: TreeItemCheckbox; + readonly actionBar: ActionBar; +} + +class TreeRenderer extends Disposable implements ITreeRenderer { + static readonly ITEM_HEIGHT = 22; + static readonly TREE_TEMPLATE_ID = 'treeExplorer'; + + private readonly _onDidChangeCheckboxState: Emitter = this._register(new Emitter()); + readonly onDidChangeCheckboxState: Event = this._onDidChangeCheckboxState.event; + + private _onDidChangeMenuContext: Emitter = this._register(new Emitter()); + readonly onDidChangeMenuContext: Event = this._onDidChangeMenuContext.event; + + private _actionRunner: MultipleSelectionActionRunner | undefined; + private _hoverDelegate: IHoverDelegate; + private _hasCheckbox: boolean = false; + private _renderedElements = new Map; rendered: ITreeExplorerTemplateData }[]>(); // tree item handle to template data + + constructor( + private treeViewId: string, + private menus: TreeMenus, + private labels: ResourceLabels, + private actionViewItemProvider: IActionViewItemProvider, + private aligner: Aligner, + private checkboxStateHandler: CheckboxStateHandler, + private readonly manuallyManageCheckboxes: () => boolean, + @IThemeService private readonly themeService: IThemeService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @ILabelService private readonly labelService: ILabelService, + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IHoverService private readonly hoverService: IHoverService, + @IInstantiationService instantiationService: IInstantiationService, + ) { + super(); + this._hoverDelegate = this._register(instantiationService.createInstance(WorkbenchHoverDelegate, 'mouse', false, {})); + this._register(this.themeService.onDidFileIconThemeChange(() => this.rerender())); + this._register(this.themeService.onDidColorThemeChange(() => this.rerender())); + this._register(checkboxStateHandler.onDidChangeCheckboxState(items => { + this.updateCheckboxes(items); + })); + this._register(this.contextKeyService.onDidChangeContext(e => this.onDidChangeContext(e))); + } + + get templateId(): string { + return TreeRenderer.TREE_TEMPLATE_ID; + } + + set actionRunner(actionRunner: MultipleSelectionActionRunner) { + this._actionRunner = actionRunner; + } + + renderTemplate(container: HTMLElement): ITreeExplorerTemplateData { + container.classList.add('custom-view-tree-node-item'); + + const checkboxContainer = DOM.append(container, DOM.$('')); + const resourceLabel = this.labels.create(container, { supportHighlights: true, hoverDelegate: this._hoverDelegate }); + const icon = DOM.prepend(resourceLabel.element, DOM.$('.custom-view-tree-node-item-icon')); + const actionsContainer = DOM.append(resourceLabel.element, DOM.$('.actions')); + const actionBar = new ActionBar(actionsContainer, { + actionViewItemProvider: this.actionViewItemProvider + }); + + return { resourceLabel, icon, checkboxContainer, actionBar, container }; + } + + private getHover(label: string | undefined, resource: URI | null, node: ITreeItem): string | IManagedHoverTooltipMarkdownString | undefined { + if (!(node instanceof ResolvableTreeItem) || !node.hasResolve) { + if (resource && !node.tooltip) { + return undefined; + } else if (node.tooltip === undefined) { + return label; + } else if (!isString(node.tooltip)) { + return { markdown: node.tooltip, markdownNotSupportedFallback: resource ? undefined : renderMarkdownAsPlaintext(node.tooltip) }; // Passing undefined as the fallback for a resource falls back to the old native hover + } else if (node.tooltip !== '') { + return node.tooltip; + } else { + return undefined; + } + } + + return { + markdown: typeof node.tooltip === 'string' ? node.tooltip : + (token: CancellationToken): Promise => { + return new Promise((resolve) => { + node.resolve(token).then(() => resolve(node.tooltip)); + }); + }, + markdownNotSupportedFallback: resource ? undefined : (label ?? '') // Passing undefined as the fallback for a resource falls back to the old native hover + }; + } + + renderElement(element: ITreeNode, index: number, templateData: ITreeExplorerTemplateData): void { + const node = element.element; + const resource = node.resourceUri ? URI.revive(node.resourceUri) : null; + const treeItemLabel: ITreeItemLabel | undefined = node.label ? node.label : (resource ? { label: basename(resource) } : undefined); + const description = isString(node.description) ? node.description : resource && node.description === true ? this.labelService.getUriLabel(dirname(resource), { relative: true }) : undefined; + const label = treeItemLabel ? treeItemLabel.label : undefined; + const matches = (treeItemLabel && treeItemLabel.highlights && label) ? treeItemLabel.highlights.map(([start, end]) => { + if (start < 0) { + start = label.length + start; + } + if (end < 0) { + end = label.length + end; + } + if ((start >= label.length) || (end > label.length)) { + return ({ start: 0, end: 0 }); + } + if (start > end) { + const swap = start; + start = end; + end = swap; + } + return ({ start, end }); + }) : undefined; + const icon = this.themeService.getColorTheme().type === ColorScheme.LIGHT ? node.icon : node.iconDark; + const iconUrl = icon ? URI.revive(icon) : undefined; + const title = this.getHover(label, resource, node); + + // reset + templateData.actionBar.clear(); + templateData.icon.style.color = ''; + + let commandEnabled = true; + if (node.command) { + commandEnabled = isTreeCommandEnabled(node.command, this.contextKeyService); + } + + this.renderCheckbox(node, templateData); + + if (resource) { + const fileDecorations = this.configurationService.getValue<{ colors: boolean; badges: boolean }>('explorer.decorations'); + const labelResource = resource ? resource : URI.parse('missing:_icon_resource'); + templateData.resourceLabel.setResource({ name: label, description, resource: labelResource }, { + fileKind: this.getFileKind(node), + title, + hideIcon: this.shouldHideResourceLabelIcon(iconUrl, node.themeIcon), + fileDecorations, + extraClasses: ['custom-view-tree-node-item-resourceLabel'], + matches: matches ? matches : createMatches(element.filterData), + strikethrough: treeItemLabel?.strikethrough, + disabledCommand: !commandEnabled, + labelEscapeNewLines: true, + forceLabel: !!node.label + }); + } else { + templateData.resourceLabel.setResource({ name: label, description }, { + title, + hideIcon: true, + extraClasses: ['custom-view-tree-node-item-resourceLabel'], + matches: matches ? matches : createMatches(element.filterData), + strikethrough: treeItemLabel?.strikethrough, + disabledCommand: !commandEnabled, + labelEscapeNewLines: true + }); + } + + if (iconUrl) { + templateData.icon.className = 'custom-view-tree-node-item-icon'; + templateData.icon.style.backgroundImage = cssJs.asCSSUrl(iconUrl); + } else { + let iconClass: string | undefined; + if (this.shouldShowThemeIcon(!!resource, node.themeIcon)) { + iconClass = ThemeIcon.asClassName(node.themeIcon); + if (node.themeIcon.color) { + templateData.icon.style.color = this.themeService.getColorTheme().getColor(node.themeIcon.color.id)?.toString() ?? ''; + } + } + templateData.icon.className = iconClass ? `custom-view-tree-node-item-icon ${iconClass}` : ''; + templateData.icon.style.backgroundImage = ''; + } + + if (!commandEnabled) { + templateData.icon.className = templateData.icon.className + ' disabled'; + if (templateData.container.parentElement) { + templateData.container.parentElement.className = templateData.container.parentElement.className + ' disabled'; + } + } + + templateData.actionBar.context = { $treeViewId: this.treeViewId, $treeItemHandle: node.handle } satisfies TreeViewItemHandleArg; + + const menuActions = this.menus.getResourceActions([node]); + templateData.actionBar.push(menuActions, { icon: true, label: false }); + + if (this._actionRunner) { + templateData.actionBar.actionRunner = this._actionRunner; + } + this.setAlignment(templateData.container, node); + + // remember rendered element, an element can be rendered multiple times + const renderedItems = this._renderedElements.get(element.element.handle) ?? []; + this._renderedElements.set(element.element.handle, [...renderedItems, { original: element, rendered: templateData }]); + } + + private rerender() { + // As we add items to the map during this call we can't directly use the map in the for loop + // but have to create a copy of the keys first + const keys = new Set(this._renderedElements.keys()); + for (const key of keys) { + const values = this._renderedElements.get(key) ?? []; + for (const value of values) { + this.disposeElement(value.original, 0, value.rendered); + this.renderElement(value.original, 0, value.rendered); + } + } + } + + private renderCheckbox(node: ITreeItem, templateData: ITreeExplorerTemplateData) { + if (node.checkbox) { + // The first time we find a checkbox we want to rerender the visible tree to adapt the alignment + if (!this._hasCheckbox) { + this._hasCheckbox = true; + this.rerender(); + } + if (!templateData.checkbox) { + const checkbox = new TreeItemCheckbox(templateData.checkboxContainer, this.checkboxStateHandler, this._hoverDelegate, this.hoverService); + templateData.checkbox = checkbox; + } + templateData.checkbox.render(node); + } else if (templateData.checkbox) { + templateData.checkbox.dispose(); + templateData.checkbox = undefined; + } + } + + private setAlignment(container: HTMLElement, treeItem: ITreeItem) { + container.parentElement!.classList.toggle('align-icon-with-twisty', !this._hasCheckbox && this.aligner.alignIconWithTwisty(treeItem)); + } + + private shouldHideResourceLabelIcon(iconUrl: URI | undefined, icon: ThemeIcon | undefined): boolean { + // We always hide the resource label in favor of the iconUrl when it's provided. + // When `ThemeIcon` is provided, we hide the resource label icon in favor of it only if it's a not a file icon. + return (!!iconUrl || (!!icon && !this.isFileKindThemeIcon(icon))); + } + + private shouldShowThemeIcon(hasResource: boolean, icon: ThemeIcon | undefined): icon is ThemeIcon { + if (!icon) { + return false; + } + + // If there's a resource and the icon is a file icon, then the icon (or lack thereof) will already be coming from the + // icon theme and should use whatever the icon theme has provided. + return !(hasResource && this.isFileKindThemeIcon(icon)); + } + + private isFolderThemeIcon(icon: ThemeIcon | undefined): boolean { + return icon?.id === FolderThemeIcon.id; + } + + private isFileKindThemeIcon(icon: ThemeIcon | undefined): boolean { + if (icon) { + return icon.id === FileThemeIcon.id || this.isFolderThemeIcon(icon); + } else { + return false; + } + } + + private getFileKind(node: ITreeItem): FileKind { + if (node.themeIcon) { + switch (node.themeIcon.id) { + case FileThemeIcon.id: + return FileKind.FILE; + case FolderThemeIcon.id: + return FileKind.FOLDER; + } + } + return node.collapsibleState === TreeItemCollapsibleState.Collapsed || node.collapsibleState === TreeItemCollapsibleState.Expanded ? FileKind.FOLDER : FileKind.FILE; + } + + private onDidChangeContext(e: IContextKeyChangeEvent) { + const items: ITreeItem[] = []; + for (const [_, elements] of this._renderedElements) { + for (const element of elements) { + if (e.affectsSome(this.menus.getElementOverlayContexts(element.original.element)) || e.affectsSome(this.menus.getEntireMenuContexts())) { + items.push(element.original.element); + } + } + } + if (items.length) { + this._onDidChangeMenuContext.fire(items); + } + } + + private updateCheckboxes(items: ITreeItem[]) { + let allItems: ITreeItem[] = []; + + if (!this.manuallyManageCheckboxes()) { + allItems = setCascadingCheckboxUpdates(items); + } + + allItems.forEach(item => { + const renderedItems = this._renderedElements.get(item.handle); + if (renderedItems) { + renderedItems.forEach(renderedItems => renderedItems.rendered.checkbox?.render(item)); + } + }); + this._onDidChangeCheckboxState.fire(allItems); + } + + disposeElement(resource: ITreeNode, index: number, templateData: ITreeExplorerTemplateData): void { + const itemRenders = this._renderedElements.get(resource.element.handle) ?? []; + const renderedIndex = itemRenders.findIndex(renderedItem => templateData === renderedItem.rendered); + + if (itemRenders.length === 1) { + this._renderedElements.delete(resource.element.handle); + } else if (itemRenders.length > 0) { + itemRenders.splice(renderedIndex, 1); + } + + templateData.checkbox?.dispose(); + templateData.checkbox = undefined; + } + + disposeTemplate(templateData: ITreeExplorerTemplateData): void { + templateData.resourceLabel.dispose(); + templateData.actionBar.dispose(); + } +} + +class Aligner extends Disposable { + private _tree: WorkbenchAsyncDataTree | undefined; + + constructor(private themeService: IThemeService) { + super(); + } + + set tree(tree: WorkbenchAsyncDataTree) { + this._tree = tree; + } + + public alignIconWithTwisty(treeItem: ITreeItem): boolean { + if (treeItem.collapsibleState !== TreeItemCollapsibleState.None) { + return false; + } + if (!this.hasIcon(treeItem)) { + return false; + } + + if (this._tree) { + const parent: ITreeItem = this._tree.getParentElement(treeItem) || this._tree.getInput(); + if (this.hasIcon(parent)) { + return !!parent.children && parent.children.some(c => c.collapsibleState !== TreeItemCollapsibleState.None && !this.hasIcon(c)); + } + return !!parent.children && parent.children.every(c => c.collapsibleState === TreeItemCollapsibleState.None || !this.hasIcon(c)); + } else { + return false; + } + } + + private hasIcon(node: ITreeItem): boolean { + const icon = this.themeService.getColorTheme().type === ColorScheme.LIGHT ? node.icon : node.iconDark; + if (icon) { + return true; + } + if (node.resourceUri || node.themeIcon) { + const fileIconTheme = this.themeService.getFileIconTheme(); + const isFolder = node.themeIcon ? node.themeIcon.id === FolderThemeIcon.id : node.collapsibleState !== TreeItemCollapsibleState.None; + if (isFolder) { + return fileIconTheme.hasFileIcons && fileIconTheme.hasFolderIcons; + } + return fileIconTheme.hasFileIcons; + } + return false; + } +} + +class MultipleSelectionActionRunner extends ActionRunner { + + constructor(notificationService: INotificationService, private getSelectedResources: (() => ITreeItem[])) { + super(); + this._register(this.onDidRun(e => { + if (e.error && !isCancellationError(e.error)) { + notificationService.error(localize('command-error', 'Error running command {1}: {0}. This is likely caused by the extension that contributes {1}.', e.error.message, e.action.id)); + } + })); + } + + protected override async runAction(action: IAction, context: TreeViewItemHandleArg | TreeViewPaneHandleArg): Promise { + const selection = this.getSelectedResources(); + let selectionHandleArgs: TreeViewItemHandleArg[] | undefined = undefined; + let actionInSelected: boolean = false; + if (selection.length > 1) { + selectionHandleArgs = selection.map(selected => { + if ((selected.handle === (context as TreeViewItemHandleArg).$treeItemHandle) || (context as TreeViewPaneHandleArg).$selectedTreeItems) { + actionInSelected = true; + } + return { $treeViewId: context.$treeViewId, $treeItemHandle: selected.handle }; + }); + } + + if (!actionInSelected && selectionHandleArgs) { + selectionHandleArgs = undefined; + } + + await action.run(context, selectionHandleArgs); + } +} + +class TreeMenus implements IDisposable { + private contextKeyService: IContextKeyService | undefined; + private _onDidChange = new Emitter(); + public readonly onDidChange = this._onDidChange.event; + + constructor( + private id: string, + @IMenuService private readonly menuService: IMenuService + ) { } + + /** + * Gets only the actions that apply to all of the given elements. + */ + getResourceActions(elements: ITreeItem[]): IAction[] { + const actions = this.getActions(this.getMenuId(), elements); + return actions.primary; + } + + /** + * Gets only the actions that apply to all of the given elements. + */ + getResourceContextActions(elements: ITreeItem[]): IAction[] { + return this.getActions(this.getMenuId(), elements).secondary; + } + + public setContextKeyService(service: IContextKeyService) { + this.contextKeyService = service; + } + + private filterNonUniversalActions(groups: Map[], newActions: IAction[]) { + const newActionsSet: Set = new Set(newActions.map(a => a.id)); + for (const group of groups) { + const actions = group.keys(); + for (const action of actions) { + if (!newActionsSet.has(action)) { + group.delete(action); + } + } + } + } + + private buildMenu(groups: Map[]): IAction[] { + const result: IAction[] = []; + for (const group of groups) { + if (group.size > 0) { + if (result.length) { + result.push(new Separator()); + } + result.push(...group.values()); + } + } + return result; + } + + private createGroups(actions: IAction[]): Map[] { + const groups: Map[] = []; + let group: Map = new Map(); + for (const action of actions) { + if (action instanceof Separator) { + groups.push(group); + group = new Map(); + } else { + group.set(action.id, action); + } + } + groups.push(group); + return groups; + } + + public getElementOverlayContexts(element: ITreeItem): Map { + return new Map([ + ['view', this.id], + ['viewItem', element.contextValue] + ]); + } + + public getEntireMenuContexts(): ReadonlySet { + return this.menuService.getMenuContexts(this.getMenuId()); + } + + public getMenuId(): MenuId { + return MenuId.ViewItemContext; + } + + private getActions(menuId: MenuId, elements: ITreeItem[]): { primary: IAction[]; secondary: IAction[] } { + if (!this.contextKeyService) { + return { primary: [], secondary: [] }; + } + + let primaryGroups: Map[] = []; + let secondaryGroups: Map[] = []; + for (let i = 0; i < elements.length; i++) { + const element = elements[i]; + const contextKeyService = this.contextKeyService.createOverlay(this.getElementOverlayContexts(element)); + + const menuData = this.menuService.getMenuActions(menuId, contextKeyService, { shouldForwardArgs: true }); + + const result = getContextMenuActions(menuData, 'inline'); + if (i === 0) { + primaryGroups = this.createGroups(result.primary); + secondaryGroups = this.createGroups(result.secondary); + } else { + this.filterNonUniversalActions(primaryGroups, result.primary); + this.filterNonUniversalActions(secondaryGroups, result.secondary); + } + } + + return { primary: this.buildMenu(primaryGroups), secondary: this.buildMenu(secondaryGroups) }; + } + + dispose() { + this.contextKeyService = undefined; + } +} + +export class CustomTreeView extends AbstractTreeView { + + constructor( + id: string, + title: string, + private readonly extensionId: string, + @IThemeService themeService: IThemeService, + @IInstantiationService instantiationService: IInstantiationService, + @ICommandService commandService: ICommandService, + @IConfigurationService configurationService: IConfigurationService, + @IProgressService progressService: IProgressService, + @IContextMenuService contextMenuService: IContextMenuService, + @IKeybindingService keybindingService: IKeybindingService, + @INotificationService notificationService: INotificationService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, + @IContextKeyService contextKeyService: IContextKeyService, + @IHoverService hoverService: IHoverService, + @IExtensionService private readonly extensionService: IExtensionService, + @IActivityService activityService: IActivityService, + @ITelemetryService private readonly telemetryService: ITelemetryService, + @ILogService logService: ILogService, + @IOpenerService openerService: IOpenerService + ) { + super(id, title, themeService, instantiationService, commandService, configurationService, progressService, contextMenuService, keybindingService, notificationService, viewDescriptorService, hoverService, contextKeyService, activityService, logService, openerService); + } + + protected activate() { + if (!this.activated) { + type ExtensionViewTelemetry = { + extensionId: TelemetryTrustedValue; + id: string; + }; + type ExtensionViewTelemetryMeta = { + extensionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Id of the extension' }; + id: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Id of the view' }; + owner: 'digitarald'; + comment: 'Helps to gain insights on what extension contributed views are most popular'; + }; + this.telemetryService.publicLog2('Extension:ViewActivate', { + extensionId: new TelemetryTrustedValue(this.extensionId), + id: this.id, + }); + this.createTree(); + this.progressService.withProgress({ location: this.id }, () => this.extensionService.activateByEvent(`onView:${this.id}`)) + .then(() => timeout(2000)) + .then(() => { + this.updateMessage(); + }); + this.activated = true; + } + } +} + +export class TreeView extends AbstractTreeView { + + protected activate() { + if (!this.activated) { + this.createTree(); + this.activated = true; + } + } +} + +interface TreeDragSourceInfo { + id: string; + itemHandles: string[]; +} + +export class CustomTreeViewDragAndDrop implements ITreeDragAndDrop { + private readonly treeMimeType: string; + private readonly treeItemsTransfer = LocalSelectionTransfer.getInstance(); + private dragCancellationToken: CancellationTokenSource | undefined; + + constructor( + private readonly treeId: string, + @ILabelService private readonly labelService: ILabelService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @ITreeViewsDnDService private readonly treeViewsDragAndDropService: ITreeViewsDnDService, + @ILogService private readonly logService: ILogService) { + this.treeMimeType = `application/vnd.code.tree.${treeId.toLowerCase()}`; + } + + private dndController: ITreeViewDragAndDropController | undefined; + set controller(controller: ITreeViewDragAndDropController | undefined) { + this.dndController = controller; + } + + private handleDragAndLog(dndController: ITreeViewDragAndDropController, itemHandles: string[], uuid: string, dragCancellationToken: CancellationToken): Promise { + return dndController.handleDrag(itemHandles, uuid, dragCancellationToken).then(additionalDataTransfer => { + if (additionalDataTransfer) { + const unlistedTypes: string[] = []; + for (const item of additionalDataTransfer) { + if ((item[0] !== this.treeMimeType) && (dndController.dragMimeTypes.findIndex(value => value === item[0]) < 0)) { + unlistedTypes.push(item[0]); + } + } + if (unlistedTypes.length) { + this.logService.warn(`Drag and drop controller for tree ${this.treeId} adds the following data transfer types but does not declare them in dragMimeTypes: ${unlistedTypes.join(', ')}`); + } + } + return additionalDataTransfer; + }); + } + + private addExtensionProvidedTransferTypes(originalEvent: DragEvent, itemHandles: string[]) { + if (!originalEvent.dataTransfer || !this.dndController) { + return; + } + const uuid = generateUuid(); + + this.dragCancellationToken = new CancellationTokenSource(); + this.treeViewsDragAndDropService.addDragOperationTransfer(uuid, this.handleDragAndLog(this.dndController, itemHandles, uuid, this.dragCancellationToken.token)); + this.treeItemsTransfer.setData([new DraggedTreeItemsIdentifier(uuid)], DraggedTreeItemsIdentifier.prototype); + originalEvent.dataTransfer.clearData(Mimes.text); + if (this.dndController.dragMimeTypes.find((element) => element === Mimes.uriList)) { + // Add the type that the editor knows + originalEvent.dataTransfer?.setData(DataTransfers.RESOURCES, ''); + } + this.dndController.dragMimeTypes.forEach(supportedType => { + originalEvent.dataTransfer?.setData(supportedType, ''); + }); + } + + private addResourceInfoToTransfer(originalEvent: DragEvent, resources: URI[]) { + if (resources.length && originalEvent.dataTransfer) { + // Apply some datatransfer types to allow for dragging the element outside of the application + this.instantiationService.invokeFunction(accessor => fillEditorsDragData(accessor, resources, originalEvent)); + + // The only custom data transfer we set from the explorer is a file transfer + // to be able to DND between multiple code file explorers across windows + const fileResources = resources.filter(s => s.scheme === Schemas.file).map(r => r.fsPath); + if (fileResources.length) { + originalEvent.dataTransfer.setData(CodeDataTransfers.FILES, JSON.stringify(fileResources)); + } + } + } + + onDragStart(data: IDragAndDropData, originalEvent: DragEvent): void { + if (originalEvent.dataTransfer) { + const treeItemsData = (data as ElementsDragAndDropData).getData(); + const resources: URI[] = []; + const sourceInfo: TreeDragSourceInfo = { + id: this.treeId, + itemHandles: [] + }; + treeItemsData.forEach(item => { + sourceInfo.itemHandles.push(item.handle); + if (item.resourceUri) { + resources.push(URI.revive(item.resourceUri)); + } + }); + this.addResourceInfoToTransfer(originalEvent, resources); + this.addExtensionProvidedTransferTypes(originalEvent, sourceInfo.itemHandles); + originalEvent.dataTransfer.setData(this.treeMimeType, + JSON.stringify(sourceInfo)); + } + } + + private debugLog(types: Set) { + if (types.size) { + this.logService.debug(`TreeView dragged mime types: ${Array.from(types).join(', ')}`); + } else { + this.logService.debug(`TreeView dragged with no supported mime types.`); + } + } + + onDragOver(data: IDragAndDropData, targetElement: ITreeItem, targetIndex: number, targetSector: ListViewTargetSector | undefined, originalEvent: DragEvent): boolean | ITreeDragOverReaction { + const dataTransfer = toExternalVSDataTransfer(originalEvent.dataTransfer!); + + const types = new Set(Array.from(dataTransfer, x => x[0])); + + if (originalEvent.dataTransfer) { + // Also add uri-list if we have any files. At this stage we can't actually access the file itself though. + for (const item of originalEvent.dataTransfer.items) { + if (item.kind === 'file' || item.type === DataTransfers.RESOURCES.toLowerCase()) { + types.add(Mimes.uriList); + break; + } + } + } + + this.debugLog(types); + + const dndController = this.dndController; + if (!dndController || !originalEvent.dataTransfer || (dndController.dropMimeTypes.length === 0)) { + return false; + } + const dragContainersSupportedType = Array.from(types).some((value, index) => { + if (value === this.treeMimeType) { + return true; + } else { + return dndController.dropMimeTypes.indexOf(value) >= 0; + } + }); + if (dragContainersSupportedType) { + return { accept: true, bubble: TreeDragOverBubble.Down, autoExpand: true }; + } + return false; + } + + getDragURI(element: ITreeItem): string | null { + if (!this.dndController) { + return null; + } + return element.resourceUri ? URI.revive(element.resourceUri).toString() : element.handle; + } + + getDragLabel?(elements: ITreeItem[]): string | undefined { + if (!this.dndController) { + return undefined; + } + if (elements.length > 1) { + return String(elements.length); + } + const element = elements[0]; + return element.label ? element.label.label : (element.resourceUri ? this.labelService.getUriLabel(URI.revive(element.resourceUri)) : undefined); + } + + async drop(data: IDragAndDropData, targetNode: ITreeItem | undefined, targetIndex: number | undefined, targetSector: ListViewTargetSector | undefined, originalEvent: DragEvent): Promise { + const dndController = this.dndController; + if (!originalEvent.dataTransfer || !dndController) { + return; + } + + let treeSourceInfo: TreeDragSourceInfo | undefined; + let willDropUuid: string | undefined; + if (this.treeItemsTransfer.hasData(DraggedTreeItemsIdentifier.prototype)) { + willDropUuid = this.treeItemsTransfer.getData(DraggedTreeItemsIdentifier.prototype)![0].identifier; + } + + const originalDataTransfer = toExternalVSDataTransfer(originalEvent.dataTransfer, true); + + const outDataTransfer = new VSDataTransfer(); + for (const [type, item] of originalDataTransfer) { + if (type === this.treeMimeType || dndController.dropMimeTypes.includes(type) || (item.asFile() && dndController.dropMimeTypes.includes(DataTransfers.FILES.toLowerCase()))) { + outDataTransfer.append(type, item); + if (type === this.treeMimeType) { + try { + treeSourceInfo = JSON.parse(await item.asString()); + } catch { + // noop + } + } + } + } + + const additionalDataTransfer = await this.treeViewsDragAndDropService.removeDragOperationTransfer(willDropUuid); + if (additionalDataTransfer) { + for (const [type, item] of additionalDataTransfer) { + outDataTransfer.append(type, item); + } + } + return dndController.handleDrop(outDataTransfer, targetNode, CancellationToken.None, willDropUuid, treeSourceInfo?.id, treeSourceInfo?.itemHandles); + } + + onDragEnd(originalEvent: DragEvent): void { + // Check if the drag was cancelled. + if (originalEvent.dataTransfer?.dropEffect === 'none') { + this.dragCancellationToken?.cancel(); + } + } + + dispose(): void { } +} + +function setCascadingCheckboxUpdates(items: readonly ITreeItem[]) { + const additionalItems: ITreeItem[] = []; + + for (const item of items) { + if (item.checkbox !== undefined) { + + const checkChildren = (currentItem: ITreeItem) => { + for (const child of (currentItem.children ?? [])) { + if ((child.checkbox !== undefined) && (currentItem.checkbox !== undefined) && (child.checkbox.isChecked !== currentItem.checkbox.isChecked)) { + child.checkbox.isChecked = currentItem.checkbox.isChecked; + additionalItems.push(child); + checkChildren(child); + } + } + }; + checkChildren(item); + + const visitedParents: Set = new Set(); + const checkParents = (currentItem: ITreeItem) => { + if (currentItem.parent && (currentItem.parent.checkbox !== undefined) && currentItem.parent.children) { + if (visitedParents.has(currentItem.parent)) { + return; + } else { + visitedParents.add(currentItem.parent); + } + + let someUnchecked = false; + let someChecked = false; + for (const child of currentItem.parent.children) { + if (someUnchecked && someChecked) { + break; + } + if (child.checkbox !== undefined) { + if (child.checkbox.isChecked) { + someChecked = true; + } else { + someUnchecked = true; + } + } + } + if (someChecked && !someUnchecked && (currentItem.parent.checkbox.isChecked !== true)) { + currentItem.parent.checkbox.isChecked = true; + additionalItems.push(currentItem.parent); + checkParents(currentItem.parent); + } else if (someUnchecked && (currentItem.parent.checkbox.isChecked !== false)) { + currentItem.parent.checkbox.isChecked = false; + additionalItems.push(currentItem.parent); + checkParents(currentItem.parent); + } + } + }; + checkParents(item); + } + } + + return items.concat(additionalItems); +} diff --git a/extensions/vscode-colorize-perf-tests/test/colorize-fixtures/test.ts b/extensions/vscode-colorize-perf-tests/test/colorize-fixtures/test.ts new file mode 100644 index 0000000000000..b9c80efd386b5 --- /dev/null +++ b/extensions/vscode-colorize-perf-tests/test/colorize-fixtures/test.ts @@ -0,0 +1,111 @@ +/* Game of Life + * Implemented in TypeScript + * To learn more about TypeScript, please visit http://www.typescriptlang.org/ + */ + +module Conway { + + export class Cell { + public row: number; + public col: number; + public live: boolean; + + constructor(row: number, col: number, live: boolean) { + this.row = row; + this.col = col; + this.live = live + } + } + + export class GameOfLife { + private gridSize: number; + private canvasSize: number; + private lineColor: string; + private liveColor: string; + private deadColor: string; + private initialLifeProbability: number; + private animationRate: number; + private cellSize: number; + private world; + + + constructor() { + this.gridSize = 50; + this.canvasSize = 600; + this.lineColor = '#cdcdcd'; + this.liveColor = '#666'; + this.deadColor = '#eee'; + this.initialLifeProbability = 0.5; + this.animationRate = 60; + this.cellSize = 0; + this.world = this.createWorld(); + this.circleOfLife(); + } + + public createWorld() { + return this.travelWorld( (cell : Cell) => { + cell.live = Math.random() < this.initialLifeProbability; + return cell; + }); + } + + public circleOfLife() : void { + this.world = this.travelWorld( (cell: Cell) => { + cell = this.world[cell.row][cell.col]; + this.draw(cell); + return this.resolveNextGeneration(cell); + }); + setTimeout( () => {this.circleOfLife()}, this.animationRate); + } + + public resolveNextGeneration(cell : Cell) { + var count = this.countNeighbors(cell); + var newCell = new Cell(cell.row, cell.col, cell.live); + if(count < 2 || count > 3) newCell.live = false; + else if(count == 3) newCell.live = true; + return newCell; + } + + public countNeighbors(cell : Cell) { + var neighbors = 0; + for(var row = -1; row <=1; row++) { + for(var col = -1; col <= 1; col++) { + if(row == 0 && col == 0) continue; + if(this.isAlive(cell.row + row, cell.col + col)) { + neighbors++; + } + } + } + return neighbors; + } + + public isAlive(row : number, col : number) { + if(row < 0 || col < 0 || row >= this.gridSize || col >= this.gridSize) return false; + return this.world[row][col].live; + } + + public travelWorld(callback) { + var result = []; + for(var row = 0; row < this.gridSize; row++) { + var rowData = []; + for(var col = 0; col < this.gridSize; col++) { + rowData.push(callback(new Cell(row, col, false))); + } + result.push(rowData); + } + return result; + } + + public draw(cell : Cell) { + if(this.cellSize == 0) this.cellSize = this.canvasSize/this.gridSize; + + this.context.strokeStyle = this.lineColor; + this.context.strokeRect(cell.row * this.cellSize, cell.col*this.cellSize, this.cellSize, this.cellSize); + this.context.fillStyle = cell.live ? this.liveColor : this.deadColor; + this.context.fillRect(cell.row * this.cellSize, cell.col*this.cellSize, this.cellSize, this.cellSize); + } + + } +} + +var game = new Conway.GameOfLife(); diff --git a/extensions/vscode-colorize-perf-tests/tsconfig.json b/extensions/vscode-colorize-perf-tests/tsconfig.json new file mode 100644 index 0000000000000..7234fdfeb9757 --- /dev/null +++ b/extensions/vscode-colorize-perf-tests/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../tsconfig.base.json", + "compilerOptions": { + "outDir": "./out", + "types": [ + "node" + ] + }, + "include": [ + "src/**/*", + "../../src/vscode-dts/vscode.d.ts" + ] +} diff --git a/extensions/vscode-test-resolver/src/download.ts b/extensions/vscode-test-resolver/src/download.ts index fa001b5a17809..a351aa775cc39 100644 --- a/extensions/vscode-test-resolver/src/download.ts +++ b/extensions/vscode-test-resolver/src/download.ts @@ -36,33 +36,31 @@ async function downloadVSCodeServerArchive(updateUrl: string, commit: string, qu https.get(requestOptions, res => { if (res.statusCode !== 302) { reject('Failed to get VS Code server archive location'); + res.resume(); // read the rest of the response data and discard it + return; } const archiveUrl = res.headers.location; if (!archiveUrl) { reject('Failed to get VS Code server archive location'); + res.resume(); // read the rest of the response data and discard it return; } const archiveRequestOptions: https.RequestOptions = parseUrl(archiveUrl); - if (archiveUrl.endsWith('.zip')) { - const archivePath = path.resolve(destDir, `vscode-server-${commit}.zip`); - const outStream = fs.createWriteStream(archivePath); - outStream.on('close', () => { - resolve(archivePath); - }); - https.get(archiveRequestOptions, res => { - res.pipe(outStream); - }); - } else { - const zipPath = path.resolve(destDir, `vscode-server-${commit}.tgz`); - const outStream = fs.createWriteStream(zipPath); - https.get(archiveRequestOptions, res => { - res.pipe(outStream); + const archivePath = path.resolve(destDir, `vscode-server-${commit}.${archiveUrl.endsWith('.zip') ? 'zip' : 'tgz'}`); + const outStream = fs.createWriteStream(archivePath); + outStream.on('finish', () => { + resolve(archivePath); + }); + outStream.on('error', err => { + reject(err); + }); + https.get(archiveRequestOptions, res => { + res.pipe(outStream); + res.on('error', err => { + reject(err); }); - outStream.on('close', () => { - resolve(zipPath); - }); - } + }); }); }); } diff --git a/extensions/yaml/package.json b/extensions/yaml/package.json index c7cf9af06fccc..9362c55b5e1a0 100644 --- a/extensions/yaml/package.json +++ b/extensions/yaml/package.json @@ -67,7 +67,8 @@ "editor.insertSpaces": true, "editor.tabSize": 2, "editor.autoIndent": "advanced", - "diffEditor.ignoreTrimWhitespace": false + "diffEditor.ignoreTrimWhitespace": false, + "editor.defaultColorDecorators": false }, "[dockercompose]": { "editor.insertSpaces": true, diff --git a/package-lock.json b/package-lock.json index bbc6e5fc43c6c..8495295338e5c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,7 @@ "@vscode/deviceid": "^0.1.1", "@vscode/iconv-lite-umd": "0.7.0", "@vscode/policy-watcher": "^1.1.8", - "@vscode/proxy-agent": "^0.22.0", + "@vscode/proxy-agent": "^0.24.0", "@vscode/ripgrep": "^1.15.9", "@vscode/spdlog": "^0.15.0", "@vscode/sqlite3": "5.1.8-vscode", @@ -27,14 +27,15 @@ "@vscode/windows-mutex": "^0.5.0", "@vscode/windows-process-tree": "^0.6.0", "@vscode/windows-registry": "^1.1.0", - "@xterm/addon-clipboard": "^0.2.0-beta.48", - "@xterm/addon-image": "^0.9.0-beta.65", - "@xterm/addon-search": "^0.16.0-beta.65", - "@xterm/addon-serialize": "^0.14.0-beta.65", - "@xterm/addon-unicode11": "^0.9.0-beta.65", - "@xterm/addon-webgl": "^0.19.0-beta.65", - "@xterm/headless": "^5.6.0-beta.65", - "@xterm/xterm": "^5.6.0-beta.65", + "@xterm/addon-clipboard": "^0.2.0-beta.53", + "@xterm/addon-image": "^0.9.0-beta.70", + "@xterm/addon-ligatures": "^0.10.0-beta.70", + "@xterm/addon-search": "^0.16.0-beta.70", + "@xterm/addon-serialize": "^0.14.0-beta.70", + "@xterm/addon-unicode11": "^0.9.0-beta.70", + "@xterm/addon-webgl": "^0.19.0-beta.70", + "@xterm/headless": "^5.6.0-beta.70", + "@xterm/xterm": "^5.6.0-beta.70", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.2", "jschardet": "3.1.4", @@ -46,6 +47,7 @@ "node-pty": "^1.1.0-beta22", "open": "^8.4.2", "tas-client-umd": "0.2.0", + "undici": "^6.20.1", "v8-inspect-profiler": "^0.1.1", "vscode-oniguruma": "1.7.0", "vscode-regexpp": "^3.1.0", @@ -94,7 +96,7 @@ "cssnano": "^6.0.3", "debounce": "^1.0.0", "deemon": "^1.8.0", - "electron": "32.2.1", + "electron": "32.2.3", "eslint": "^9.11.1", "eslint-formatter-compact": "^8.40.0", "eslint-plugin-header": "3.1.1", @@ -152,7 +154,7 @@ "ts-node": "^10.9.1", "tsec": "0.2.7", "tslib": "^2.6.3", - "typescript": "^5.7.0-dev.20241021", + "typescript": "^5.8.0-dev.20241107", "typescript-eslint": "^8.8.0", "util": "^0.12.4", "webpack": "^5.94.0", @@ -2559,9 +2561,10 @@ } }, "node_modules/@vscode/proxy-agent": { - "version": "0.22.0", - "resolved": "https://registry.npmjs.org/@vscode/proxy-agent/-/proxy-agent-0.22.0.tgz", - "integrity": "sha512-TQrv456pbrjmD6G+iOoXE1Mflm+8Ic/Kny4QU7ioiYe2+0HisvqzJM/CUa3Am5SWrNjMbntTHISjgmSaSlorrA==", + "version": "0.24.0", + "resolved": "git+ssh://git@github.com/microsoft/vscode-proxy-agent.git#8b7032e91459a4bfe075115d333ea6bd1a78f807", + "integrity": "sha512-F8g5VtvGqeBKTi8azJyRJ3wrDIR+qliXDUSBiTvzVad/tR8tASRXD0wr7qFncSG/JyIHqRPr+1OUBaWh/bVMTA==", + "license": "MIT", "dependencies": { "@tootallnate/once": "^3.0.0", "agent-base": "^7.0.1", @@ -2595,10 +2598,11 @@ } }, "node_modules/@vscode/spdlog": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/@vscode/spdlog/-/spdlog-0.15.0.tgz", - "integrity": "sha512-5UFcQXM/G6bTRF49zJJJH3A3+47nxaXuKzT26vhTXVIiMFoV1oI9559mWOzapLEmvrntAdYtjE7Jh74lSAuMcA==", + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/@vscode/spdlog/-/spdlog-0.15.1.tgz", + "integrity": "sha512-tVPeXmyz3/4NKqtNfNQxqcrBSSEZVIbF4lVDuDh9Nik5xhuHVceCU6cTpwmJ6yVBs+jv51SGF622txOQv95sZA==", "hasInstallScript": true, + "license": "MIT", "dependencies": { "bindings": "^1.5.0", "mkdirp": "^1.0.4", @@ -3165,65 +3169,89 @@ } }, "node_modules/@xterm/addon-clipboard": { - "version": "0.2.0-beta.48", - "resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.2.0-beta.48.tgz", - "integrity": "sha512-oTrGoGMOQaW7PINAShhNR3duV8l2No/DbJ9rtfSBjfoQ9clWe0K+4fUHdJIuzW65AZsiLo9oh5IbHLWxlFP3Jg==", + "version": "0.2.0-beta.53", + "resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.2.0-beta.53.tgz", + "integrity": "sha512-kCcBuGvF8mwzExU+Tm9eylPvp1kXTkvm+kO0V4qP7HI3ZCw5vfKmnlRn41FvNIylsK2hnmrFtxauPHEGBy/dfA==", + "license": "MIT", "dependencies": { "js-base64": "^3.7.5" }, "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.65" + "@xterm/xterm": "^5.6.0-beta.70" } }, "node_modules/@xterm/addon-image": { - "version": "0.9.0-beta.65", - "resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.9.0-beta.65.tgz", - "integrity": "sha512-Ud5FI5cGtpbz8yYCHC4QINlbC1K04Mex1JD5oAWPFwjlOz/Si/DhqbY1QBDl6QrUmSUUTZc5DKT3v3nN5WbZxQ==", + "version": "0.9.0-beta.70", + "resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.9.0-beta.70.tgz", + "integrity": "sha512-QLhy77i0sjnffkLuxj1yB/mBUJI64bbL86eMW+1g5XEsZnSevY8YwU/cEJg02PAGK0ggwQNNfiRwoO6VBCdYFg==", + "license": "MIT", + "peerDependencies": { + "@xterm/xterm": "^5.6.0-beta.70" + } + }, + "node_modules/@xterm/addon-ligatures": { + "version": "0.10.0-beta.70", + "resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.10.0-beta.70.tgz", + "integrity": "sha512-BPDHHOybUWO6mjHf/RMDBjSKDl9QdyyGTyHvmlyhuI/2sma3lu98bA4U03F8nBnvj/6otzEFARuOeoN7rkfRng==", + "license": "MIT", + "dependencies": { + "font-finder": "^1.1.0", + "font-ligatures": "^1.4.1" + }, + "engines": { + "node": ">8.0.0" + }, "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.65" + "@xterm/xterm": "^5.6.0-beta.70" } }, "node_modules/@xterm/addon-search": { - "version": "0.16.0-beta.65", - "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.16.0-beta.65.tgz", - "integrity": "sha512-eHfbbgFCS3AcD8YSvGFIkW9NW4pfGHrtRDWIojM/zqmyi3JaCmHUgZmlxbaANwBlgLiNpJLDq7cmoz//86NgGw==", + "version": "0.16.0-beta.70", + "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.16.0-beta.70.tgz", + "integrity": "sha512-uaNBf77cr5Jikj69TDkTfe3V3wHA+4tDTFcv8DwJ/KGORPLUfBk8cn9HpbIJ+0jc1kOZr5xDrEjaYNwEVDkGeQ==", + "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.65" + "@xterm/xterm": "^5.6.0-beta.70" } }, "node_modules/@xterm/addon-serialize": { - "version": "0.14.0-beta.65", - "resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.14.0-beta.65.tgz", - "integrity": "sha512-NlvXVtFAHpLKEDzd+6/68yPFQ4Oe02jvQH6+a8p+gcxbSEyYj1l0sqqeABQ3gC5QDCmQepk6BQVB8Rpz/e4HgQ==", + "version": "0.14.0-beta.70", + "resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.14.0-beta.70.tgz", + "integrity": "sha512-4ijqHU7xDRcZ4Gm6yN/tKsZzovUwzttE9/pPfhxpWIgSdm9c8i5R4rGyOAlAZCnuU3DT5vPplQO7W/0zwAmJsQ==", + "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.65" + "@xterm/xterm": "^5.6.0-beta.70" } }, "node_modules/@xterm/addon-unicode11": { - "version": "0.9.0-beta.65", - "resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.9.0-beta.65.tgz", - "integrity": "sha512-sH9mu2GEIVuc6cs6HxsmCXn6KKzCa5D+Zyl1Pt9d2S4FKqSB17Fc4mVukCOMW7wUR9UChKTbxmByGJK+4xwdCA==", + "version": "0.9.0-beta.70", + "resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.9.0-beta.70.tgz", + "integrity": "sha512-9UE4v2SpWtqd85Hqvk8LW4QA9Phe93RMVltrNtt9jCUmkAok/QLFOEbLqoq2JUOvlmfsYfXmIl11Wg4CYIsvwA==", + "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.65" + "@xterm/xterm": "^5.6.0-beta.70" } }, "node_modules/@xterm/addon-webgl": { - "version": "0.19.0-beta.65", - "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.19.0-beta.65.tgz", - "integrity": "sha512-gPwP8ozqrLFWOlo9eh2I+acxq0Z6obG7GpL8HpJ+B+q6+vjfxoXHQ6vV04W90aoAvbLpScHcT/JtXA58GcH2vw==", + "version": "0.19.0-beta.70", + "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.19.0-beta.70.tgz", + "integrity": "sha512-3BkmF24i66SHCfAkcHy1VC6H715qyelfOIjd6n7sTqHon56J1bUO782Q1al/MK0vSvplElBRKVdR31SDvITzpg==", + "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.65" + "@xterm/xterm": "^5.6.0-beta.70" } }, "node_modules/@xterm/headless": { - "version": "5.6.0-beta.65", - "resolved": "https://registry.npmjs.org/@xterm/headless/-/headless-5.6.0-beta.65.tgz", - "integrity": "sha512-gQICZsBkFHQEb/K+dc+KN4u5aBwrDPgai0S7W8/KT7fqTPWz8Cgjq119WQLshnzfLeMMFPYGttNHFGUbpp/wmw==" + "version": "5.6.0-beta.70", + "resolved": "https://registry.npmjs.org/@xterm/headless/-/headless-5.6.0-beta.70.tgz", + "integrity": "sha512-npSJzmtJq8LLAzV3nwD9KkBQLNWSusi+cOBPyc3zlYYE63Vkqbtbp/iQS27zH2GxJ95rzCzDwnX6VOn0OoUPYQ==", + "license": "MIT" }, "node_modules/@xterm/xterm": { - "version": "5.6.0-beta.65", - "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.6.0-beta.65.tgz", - "integrity": "sha512-o6IIg4hqs994gtSQMbzQM6OUeLIXPG6RUxjoref0KJX4PIa/BqQjwQLPuSxnjOFeRQIvhZ2mSAPfGWJQ6jEGAg==" + "version": "5.6.0-beta.70", + "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.6.0-beta.70.tgz", + "integrity": "sha512-qviQMVWhRtgPn4z8PHNH6D/ffSKkNBHmUX1HyJxl325QM2xF8M8met83uFv7JZm7a5OQYScnLGsFAoTreSgdew==", + "license": "MIT" }, "node_modules/@xtuc/ieee754": { "version": "1.2.0", @@ -5787,9 +5815,9 @@ "dev": true }, "node_modules/electron": { - "version": "32.2.1", - "resolved": "https://registry.npmjs.org/electron/-/electron-32.2.1.tgz", - "integrity": "sha512-GCPI/5hU34pPcNltNpz+uylhhuTm9BM0N8RmrbVgaWBodLSmmcCkvpgN0BseKhO6IwQOPzWaovrcZ/nPIpfGaQ==", + "version": "32.2.3", + "resolved": "https://registry.npmjs.org/electron/-/electron-32.2.3.tgz", + "integrity": "sha512-ClTJrFuwBdZpDNEnVZSV1gTIYSq7c/TYoUv9AmOypL43/xtbfxXkz2vE67ehVoamFobWsIU2by087R5Av8cxJg==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -7086,6 +7114,51 @@ "util-deprecate": "~1.0.1" } }, + "node_modules/font-finder": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/font-finder/-/font-finder-1.1.0.tgz", + "integrity": "sha512-wpCL2uIbi6GurJbU7ZlQ3nGd61Ho+dSU6U83/xJT5UPFfN35EeCW/rOtS+5k+IuEZu2SYmHzDIPL9eA5tSYRAw==", + "license": "MIT", + "dependencies": { + "get-system-fonts": "^2.0.0", + "promise-stream-reader": "^1.0.1" + }, + "engines": { + "node": ">8.0.0" + } + }, + "node_modules/font-ligatures": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/font-ligatures/-/font-ligatures-1.4.1.tgz", + "integrity": "sha512-7W6zlfyhvCqShZ5ReUWqmSd9vBaUudW0Hxis+tqUjtHhsPU+L3Grf8mcZAtCiXHTzorhwdRTId2WeH/88gdFkw==", + "license": "MIT", + "dependencies": { + "font-finder": "^1.0.3", + "lru-cache": "^6.0.0", + "opentype.js": "^0.8.0" + }, + "engines": { + "node": ">8.0.0" + } + }, + "node_modules/font-ligatures/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==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/font-ligatures/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, "node_modules/for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -7384,6 +7457,15 @@ "once": "^1.3.1" } }, + "node_modules/get-system-fonts": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-system-fonts/-/get-system-fonts-2.0.2.tgz", + "integrity": "sha512-zzlgaYnHMIEgHRrfC7x0Qp0Ylhw/sHpM6MHXeVBTYIsvGf5GpbnClB+Q6rAPdn+0gd2oZZIo6Tj3EaWrt4VhDQ==", + "license": "MIT", + "engines": { + "node": ">8.0.0" + } + }, "node_modules/get-value": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", @@ -12825,6 +12907,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/opentype.js": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/opentype.js/-/opentype.js-0.8.0.tgz", + "integrity": "sha512-FQHR4oGP+a0m/f6yHoRpBOIbn/5ZWxKd4D/djHVJu8+KpBTYrJda0b7mLcgDEMWXE9xBCJm+qb0yv6FcvPjukg==", + "license": "MIT", + "dependencies": { + "tiny-inflate": "^1.0.2" + }, + "bin": { + "ot": "bin/ot" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -14339,6 +14433,15 @@ "node": ">=0.4.0" } }, + "node_modules/promise-stream-reader": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-stream-reader/-/promise-stream-reader-1.0.1.tgz", + "integrity": "sha512-Tnxit5trUjBAqqZCGWwjyxhmgMN4hGrtpW3Oc/tRI4bpm/O2+ej72BB08l6JBnGQgVDGCLvHFGjGgQS6vzhwXg==", + "license": "MIT", + "engines": { + "node": ">8.0.0" + } + }, "node_modules/proto-list": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", @@ -16822,6 +16925,12 @@ "next-tick": "1" } }, + "node_modules/tiny-inflate": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", + "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", + "license": "MIT" + }, "node_modules/to-absolute-glob": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz", @@ -17212,10 +17321,11 @@ "dev": true }, "node_modules/typescript": { - "version": "5.7.0-dev.20241021", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.0-dev.20241021.tgz", - "integrity": "sha512-nf5PGykGkdF2Palp0anP/jjLiqM7jdLaIyhpq1Y8bhHnClE1JR2eHXrame54dWeaX0ZMc3NF/TD59xtVhZiuMA==", + "version": "5.8.0-dev.20241107", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.0-dev.20241107.tgz", + "integrity": "sha512-AYCebTVMNbwq0Ec2P+mkALuJjg01l/dPtSOqTjovvcqqCrmqvXCgI13z4bb1pf9AuuEm8bnLQhpG9uAdCfIjqg==", "dev": true, + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -17392,6 +17502,15 @@ "integrity": "sha1-5qdUzI8V5YmHqpy9J69m/W9OWvk= sha512-Ia0sQNrMPXXkqVFt6w6M1n1oKo3NfKs+mvaV811Jwir7vAk9a6PVV9VPYf6X3BU97QiLEmuW3uXH9u87zDFfdw==", "dev": true }, + "node_modules/undici": { + "version": "6.20.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.20.1.tgz", + "integrity": "sha512-AjQF1QsmqfJys+LXfGTNum+qw4S88CojRInG/6t31W/1fk6G59s92bnAvGz5Cmur+kQv2SURXEvvudLmbrE8QA==", + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", diff --git a/package.json b/package.json index 6318e32f16716..d8b0e933fa68f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.96.0", - "distro": "2ac4fd6b98c3720a7f66b21121c105f2e9db964d", + "distro": "af82ec3330347ea13edd9b1501e3374a5dec8a86", "author": { "name": "Microsoft Corporation" }, @@ -75,7 +75,7 @@ "@vscode/deviceid": "^0.1.1", "@vscode/iconv-lite-umd": "0.7.0", "@vscode/policy-watcher": "^1.1.8", - "@vscode/proxy-agent": "^0.22.0", + "@vscode/proxy-agent": "^0.24.0", "@vscode/ripgrep": "^1.15.9", "@vscode/spdlog": "^0.15.0", "@vscode/sqlite3": "5.1.8-vscode", @@ -85,14 +85,15 @@ "@vscode/windows-mutex": "^0.5.0", "@vscode/windows-process-tree": "^0.6.0", "@vscode/windows-registry": "^1.1.0", - "@xterm/addon-clipboard": "^0.2.0-beta.48", - "@xterm/addon-image": "^0.9.0-beta.65", - "@xterm/addon-search": "^0.16.0-beta.65", - "@xterm/addon-serialize": "^0.14.0-beta.65", - "@xterm/addon-unicode11": "^0.9.0-beta.65", - "@xterm/addon-webgl": "^0.19.0-beta.65", - "@xterm/headless": "^5.6.0-beta.65", - "@xterm/xterm": "^5.6.0-beta.65", + "@xterm/addon-clipboard": "^0.2.0-beta.53", + "@xterm/addon-image": "^0.9.0-beta.70", + "@xterm/addon-ligatures": "^0.10.0-beta.70", + "@xterm/addon-search": "^0.16.0-beta.70", + "@xterm/addon-serialize": "^0.14.0-beta.70", + "@xterm/addon-unicode11": "^0.9.0-beta.70", + "@xterm/addon-webgl": "^0.19.0-beta.70", + "@xterm/headless": "^5.6.0-beta.70", + "@xterm/xterm": "^5.6.0-beta.70", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.2", "jschardet": "3.1.4", @@ -104,6 +105,7 @@ "node-pty": "^1.1.0-beta22", "open": "^8.4.2", "tas-client-umd": "0.2.0", + "undici": "^6.20.1", "v8-inspect-profiler": "^0.1.1", "vscode-oniguruma": "1.7.0", "vscode-regexpp": "^3.1.0", @@ -152,7 +154,7 @@ "cssnano": "^6.0.3", "debounce": "^1.0.0", "deemon": "^1.8.0", - "electron": "32.2.1", + "electron": "32.2.3", "eslint": "^9.11.1", "eslint-formatter-compact": "^8.40.0", "eslint-plugin-header": "3.1.1", @@ -210,7 +212,7 @@ "ts-node": "^10.9.1", "tsec": "0.2.7", "tslib": "^2.6.3", - "typescript": "^5.7.0-dev.20241021", + "typescript": "^5.8.0-dev.20241107", "typescript-eslint": "^8.8.0", "util": "^0.12.4", "webpack": "^5.94.0", diff --git a/product.json b/product.json index 204c49b9c9d2b..3ad6245b4a003 100644 --- a/product.json +++ b/product.json @@ -50,8 +50,8 @@ }, { "name": "ms-vscode.js-debug", - "version": "1.95.1", - "sha256": "0ac296e94c66fb31e8ca12af11cd5f1373627028c9e12e0dc6531f10bbe05f7e", + "version": "1.95.3", + "sha256": "77b61197558a46140534deb7d8dd3c9d5038b9cb7ea8195e272c6a3a882d8e73", "repo": "https://github.com/microsoft/vscode-js-debug", "metadata": { "id": "25629058-ddac-4e17-abba-74678e126c5d", diff --git a/remote/package-lock.json b/remote/package-lock.json index 934e61633b322..18c94b10e4eb7 100644 --- a/remote/package-lock.json +++ b/remote/package-lock.json @@ -13,21 +13,22 @@ "@parcel/watcher": "2.1.0", "@vscode/deviceid": "^0.1.1", "@vscode/iconv-lite-umd": "0.7.0", - "@vscode/proxy-agent": "^0.22.0", + "@vscode/proxy-agent": "^0.24.0", "@vscode/ripgrep": "^1.15.9", "@vscode/spdlog": "^0.15.0", "@vscode/tree-sitter-wasm": "^0.0.4", "@vscode/vscode-languagedetection": "1.0.21", "@vscode/windows-process-tree": "^0.6.0", "@vscode/windows-registry": "^1.1.0", - "@xterm/addon-clipboard": "^0.2.0-beta.48", - "@xterm/addon-image": "^0.9.0-beta.65", - "@xterm/addon-search": "^0.16.0-beta.65", - "@xterm/addon-serialize": "^0.14.0-beta.65", - "@xterm/addon-unicode11": "^0.9.0-beta.65", - "@xterm/addon-webgl": "^0.19.0-beta.65", - "@xterm/headless": "^5.6.0-beta.65", - "@xterm/xterm": "^5.6.0-beta.65", + "@xterm/addon-clipboard": "^0.2.0-beta.53", + "@xterm/addon-image": "^0.9.0-beta.70", + "@xterm/addon-ligatures": "^0.10.0-beta.70", + "@xterm/addon-search": "^0.16.0-beta.70", + "@xterm/addon-serialize": "^0.14.0-beta.70", + "@xterm/addon-unicode11": "^0.9.0-beta.70", + "@xterm/addon-webgl": "^0.19.0-beta.70", + "@xterm/headless": "^5.6.0-beta.70", + "@xterm/xterm": "^5.6.0-beta.70", "cookie": "^0.7.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.2", @@ -37,6 +38,7 @@ "native-watchdog": "^1.4.1", "node-pty": "^1.1.0-beta22", "tas-client-umd": "0.2.0", + "undici": "^6.20.1", "vscode-oniguruma": "1.7.0", "vscode-regexpp": "^3.1.0", "vscode-textmate": "9.1.0", @@ -129,9 +131,10 @@ "integrity": "sha512-bRRFxLfg5dtAyl5XyiVWz/ZBPahpOpPrNYnnHpOpUZvam4tKH35wdhP4Kj6PbM0+KdliOsPzbGWpkxcdpNB/sg==" }, "node_modules/@vscode/proxy-agent": { - "version": "0.22.0", - "resolved": "https://registry.npmjs.org/@vscode/proxy-agent/-/proxy-agent-0.22.0.tgz", - "integrity": "sha512-TQrv456pbrjmD6G+iOoXE1Mflm+8Ic/Kny4QU7ioiYe2+0HisvqzJM/CUa3Am5SWrNjMbntTHISjgmSaSlorrA==", + "version": "0.24.0", + "resolved": "git+ssh://git@github.com/microsoft/vscode-proxy-agent.git#8b7032e91459a4bfe075115d333ea6bd1a78f807", + "integrity": "sha512-F8g5VtvGqeBKTi8azJyRJ3wrDIR+qliXDUSBiTvzVad/tR8tASRXD0wr7qFncSG/JyIHqRPr+1OUBaWh/bVMTA==", + "license": "MIT", "dependencies": { "@tootallnate/once": "^3.0.0", "agent-base": "^7.0.1", @@ -165,10 +168,11 @@ } }, "node_modules/@vscode/spdlog": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/@vscode/spdlog/-/spdlog-0.15.0.tgz", - "integrity": "sha512-5UFcQXM/G6bTRF49zJJJH3A3+47nxaXuKzT26vhTXVIiMFoV1oI9559mWOzapLEmvrntAdYtjE7Jh74lSAuMcA==", + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/@vscode/spdlog/-/spdlog-0.15.1.tgz", + "integrity": "sha512-tVPeXmyz3/4NKqtNfNQxqcrBSSEZVIbF4lVDuDh9Nik5xhuHVceCU6cTpwmJ6yVBs+jv51SGF622txOQv95sZA==", "hasInstallScript": true, + "license": "MIT", "dependencies": { "bindings": "^1.5.0", "mkdirp": "^1.0.4", @@ -228,65 +232,89 @@ "hasInstallScript": true }, "node_modules/@xterm/addon-clipboard": { - "version": "0.2.0-beta.48", - "resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.2.0-beta.48.tgz", - "integrity": "sha512-oTrGoGMOQaW7PINAShhNR3duV8l2No/DbJ9rtfSBjfoQ9clWe0K+4fUHdJIuzW65AZsiLo9oh5IbHLWxlFP3Jg==", + "version": "0.2.0-beta.53", + "resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.2.0-beta.53.tgz", + "integrity": "sha512-kCcBuGvF8mwzExU+Tm9eylPvp1kXTkvm+kO0V4qP7HI3ZCw5vfKmnlRn41FvNIylsK2hnmrFtxauPHEGBy/dfA==", + "license": "MIT", "dependencies": { "js-base64": "^3.7.5" }, "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.65" + "@xterm/xterm": "^5.6.0-beta.70" } }, "node_modules/@xterm/addon-image": { - "version": "0.9.0-beta.65", - "resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.9.0-beta.65.tgz", - "integrity": "sha512-Ud5FI5cGtpbz8yYCHC4QINlbC1K04Mex1JD5oAWPFwjlOz/Si/DhqbY1QBDl6QrUmSUUTZc5DKT3v3nN5WbZxQ==", + "version": "0.9.0-beta.70", + "resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.9.0-beta.70.tgz", + "integrity": "sha512-QLhy77i0sjnffkLuxj1yB/mBUJI64bbL86eMW+1g5XEsZnSevY8YwU/cEJg02PAGK0ggwQNNfiRwoO6VBCdYFg==", + "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.65" + "@xterm/xterm": "^5.6.0-beta.70" + } + }, + "node_modules/@xterm/addon-ligatures": { + "version": "0.10.0-beta.70", + "resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.10.0-beta.70.tgz", + "integrity": "sha512-BPDHHOybUWO6mjHf/RMDBjSKDl9QdyyGTyHvmlyhuI/2sma3lu98bA4U03F8nBnvj/6otzEFARuOeoN7rkfRng==", + "license": "MIT", + "dependencies": { + "font-finder": "^1.1.0", + "font-ligatures": "^1.4.1" + }, + "engines": { + "node": ">8.0.0" + }, + "peerDependencies": { + "@xterm/xterm": "^5.6.0-beta.70" } }, "node_modules/@xterm/addon-search": { - "version": "0.16.0-beta.65", - "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.16.0-beta.65.tgz", - "integrity": "sha512-eHfbbgFCS3AcD8YSvGFIkW9NW4pfGHrtRDWIojM/zqmyi3JaCmHUgZmlxbaANwBlgLiNpJLDq7cmoz//86NgGw==", + "version": "0.16.0-beta.70", + "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.16.0-beta.70.tgz", + "integrity": "sha512-uaNBf77cr5Jikj69TDkTfe3V3wHA+4tDTFcv8DwJ/KGORPLUfBk8cn9HpbIJ+0jc1kOZr5xDrEjaYNwEVDkGeQ==", + "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.65" + "@xterm/xterm": "^5.6.0-beta.70" } }, "node_modules/@xterm/addon-serialize": { - "version": "0.14.0-beta.65", - "resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.14.0-beta.65.tgz", - "integrity": "sha512-NlvXVtFAHpLKEDzd+6/68yPFQ4Oe02jvQH6+a8p+gcxbSEyYj1l0sqqeABQ3gC5QDCmQepk6BQVB8Rpz/e4HgQ==", + "version": "0.14.0-beta.70", + "resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.14.0-beta.70.tgz", + "integrity": "sha512-4ijqHU7xDRcZ4Gm6yN/tKsZzovUwzttE9/pPfhxpWIgSdm9c8i5R4rGyOAlAZCnuU3DT5vPplQO7W/0zwAmJsQ==", + "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.65" + "@xterm/xterm": "^5.6.0-beta.70" } }, "node_modules/@xterm/addon-unicode11": { - "version": "0.9.0-beta.65", - "resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.9.0-beta.65.tgz", - "integrity": "sha512-sH9mu2GEIVuc6cs6HxsmCXn6KKzCa5D+Zyl1Pt9d2S4FKqSB17Fc4mVukCOMW7wUR9UChKTbxmByGJK+4xwdCA==", + "version": "0.9.0-beta.70", + "resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.9.0-beta.70.tgz", + "integrity": "sha512-9UE4v2SpWtqd85Hqvk8LW4QA9Phe93RMVltrNtt9jCUmkAok/QLFOEbLqoq2JUOvlmfsYfXmIl11Wg4CYIsvwA==", + "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.65" + "@xterm/xterm": "^5.6.0-beta.70" } }, "node_modules/@xterm/addon-webgl": { - "version": "0.19.0-beta.65", - "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.19.0-beta.65.tgz", - "integrity": "sha512-gPwP8ozqrLFWOlo9eh2I+acxq0Z6obG7GpL8HpJ+B+q6+vjfxoXHQ6vV04W90aoAvbLpScHcT/JtXA58GcH2vw==", + "version": "0.19.0-beta.70", + "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.19.0-beta.70.tgz", + "integrity": "sha512-3BkmF24i66SHCfAkcHy1VC6H715qyelfOIjd6n7sTqHon56J1bUO782Q1al/MK0vSvplElBRKVdR31SDvITzpg==", + "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.65" + "@xterm/xterm": "^5.6.0-beta.70" } }, "node_modules/@xterm/headless": { - "version": "5.6.0-beta.65", - "resolved": "https://registry.npmjs.org/@xterm/headless/-/headless-5.6.0-beta.65.tgz", - "integrity": "sha512-gQICZsBkFHQEb/K+dc+KN4u5aBwrDPgai0S7W8/KT7fqTPWz8Cgjq119WQLshnzfLeMMFPYGttNHFGUbpp/wmw==" + "version": "5.6.0-beta.70", + "resolved": "https://registry.npmjs.org/@xterm/headless/-/headless-5.6.0-beta.70.tgz", + "integrity": "sha512-npSJzmtJq8LLAzV3nwD9KkBQLNWSusi+cOBPyc3zlYYE63Vkqbtbp/iQS27zH2GxJ95rzCzDwnX6VOn0OoUPYQ==", + "license": "MIT" }, "node_modules/@xterm/xterm": { - "version": "5.6.0-beta.65", - "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.6.0-beta.65.tgz", - "integrity": "sha512-o6IIg4hqs994gtSQMbzQM6OUeLIXPG6RUxjoref0KJX4PIa/BqQjwQLPuSxnjOFeRQIvhZ2mSAPfGWJQ6jEGAg==" + "version": "5.6.0-beta.70", + "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.6.0-beta.70.tgz", + "integrity": "sha512-qviQMVWhRtgPn4z8PHNH6D/ffSKkNBHmUX1HyJxl325QM2xF8M8met83uFv7JZm7a5OQYScnLGsFAoTreSgdew==", + "license": "MIT" }, "node_modules/agent-base": { "version": "7.1.1", @@ -478,6 +506,33 @@ "node": ">=8" } }, + "node_modules/font-finder": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/font-finder/-/font-finder-1.1.0.tgz", + "integrity": "sha512-wpCL2uIbi6GurJbU7ZlQ3nGd61Ho+dSU6U83/xJT5UPFfN35EeCW/rOtS+5k+IuEZu2SYmHzDIPL9eA5tSYRAw==", + "license": "MIT", + "dependencies": { + "get-system-fonts": "^2.0.0", + "promise-stream-reader": "^1.0.1" + }, + "engines": { + "node": ">8.0.0" + } + }, + "node_modules/font-ligatures": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/font-ligatures/-/font-ligatures-1.4.1.tgz", + "integrity": "sha512-7W6zlfyhvCqShZ5ReUWqmSd9vBaUudW0Hxis+tqUjtHhsPU+L3Grf8mcZAtCiXHTzorhwdRTId2WeH/88gdFkw==", + "license": "MIT", + "dependencies": { + "font-finder": "^1.0.3", + "lru-cache": "^6.0.0", + "opentype.js": "^0.8.0" + }, + "engines": { + "node": ">8.0.0" + } + }, "node_modules/fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", @@ -496,6 +551,15 @@ "node": ">=14.14" } }, + "node_modules/get-system-fonts": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-system-fonts/-/get-system-fonts-2.0.2.tgz", + "integrity": "sha512-zzlgaYnHMIEgHRrfC7x0Qp0Ylhw/sHpM6MHXeVBTYIsvGf5GpbnClB+Q6rAPdn+0gd2oZZIo6Tj3EaWrt4VhDQ==", + "license": "MIT", + "engines": { + "node": ">8.0.0" + } + }, "node_modules/github-from-package": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", @@ -760,6 +824,18 @@ "wrappy": "1" } }, + "node_modules/opentype.js": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/opentype.js/-/opentype.js-0.8.0.tgz", + "integrity": "sha512-FQHR4oGP+a0m/f6yHoRpBOIbn/5ZWxKd4D/djHVJu8+KpBTYrJda0b7mLcgDEMWXE9xBCJm+qb0yv6FcvPjukg==", + "license": "MIT", + "dependencies": { + "tiny-inflate": "^1.0.2" + }, + "bin": { + "ot": "bin/ot" + } + }, "node_modules/pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", @@ -801,6 +877,15 @@ "node": ">=10" } }, + "node_modules/promise-stream-reader": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-stream-reader/-/promise-stream-reader-1.0.1.tgz", + "integrity": "sha512-Tnxit5trUjBAqqZCGWwjyxhmgMN4hGrtpW3Oc/tRI4bpm/O2+ej72BB08l6JBnGQgVDGCLvHFGjGgQS6vzhwXg==", + "license": "MIT", + "engines": { + "node": ">8.0.0" + } + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -1005,6 +1090,12 @@ "resolved": "https://registry.npmjs.org/tas-client-umd/-/tas-client-umd-0.2.0.tgz", "integrity": "sha512-oezN7mJVm5qZDVEby7OzxCLKUpUN5of0rY4dvOWaDF2JZBlGpd3BXceFN8B53qlTaIkVSzP65aAMT0Vc+/N25Q==" }, + "node_modules/tiny-inflate": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", + "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", + "license": "MIT" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -1027,6 +1118,15 @@ "node": "*" } }, + "node_modules/undici": { + "version": "6.20.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.20.1.tgz", + "integrity": "sha512-AjQF1QsmqfJys+LXfGTNum+qw4S88CojRInG/6t31W/1fk6G59s92bnAvGz5Cmur+kQv2SURXEvvudLmbrE8QA==", + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, "node_modules/universalify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", diff --git a/remote/package.json b/remote/package.json index b7a3d258c7906..a5396e27499f2 100644 --- a/remote/package.json +++ b/remote/package.json @@ -8,21 +8,22 @@ "@parcel/watcher": "2.1.0", "@vscode/deviceid": "^0.1.1", "@vscode/iconv-lite-umd": "0.7.0", - "@vscode/proxy-agent": "^0.22.0", + "@vscode/proxy-agent": "^0.24.0", "@vscode/ripgrep": "^1.15.9", "@vscode/spdlog": "^0.15.0", "@vscode/tree-sitter-wasm": "^0.0.4", "@vscode/vscode-languagedetection": "1.0.21", "@vscode/windows-process-tree": "^0.6.0", "@vscode/windows-registry": "^1.1.0", - "@xterm/addon-clipboard": "^0.2.0-beta.48", - "@xterm/addon-image": "^0.9.0-beta.65", - "@xterm/addon-search": "^0.16.0-beta.65", - "@xterm/addon-serialize": "^0.14.0-beta.65", - "@xterm/addon-unicode11": "^0.9.0-beta.65", - "@xterm/addon-webgl": "^0.19.0-beta.65", - "@xterm/headless": "^5.6.0-beta.65", - "@xterm/xterm": "^5.6.0-beta.65", + "@xterm/addon-clipboard": "^0.2.0-beta.53", + "@xterm/addon-image": "^0.9.0-beta.70", + "@xterm/addon-ligatures": "^0.10.0-beta.70", + "@xterm/addon-search": "^0.16.0-beta.70", + "@xterm/addon-serialize": "^0.14.0-beta.70", + "@xterm/addon-unicode11": "^0.9.0-beta.70", + "@xterm/addon-webgl": "^0.19.0-beta.70", + "@xterm/headless": "^5.6.0-beta.70", + "@xterm/xterm": "^5.6.0-beta.70", "cookie": "^0.7.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.2", @@ -32,6 +33,7 @@ "native-watchdog": "^1.4.1", "node-pty": "^1.1.0-beta22", "tas-client-umd": "0.2.0", + "undici": "^6.20.1", "vscode-oniguruma": "1.7.0", "vscode-regexpp": "^3.1.0", "vscode-textmate": "9.1.0", diff --git a/remote/web/package-lock.json b/remote/web/package-lock.json index 41bf944699658..3e45e62a14dc7 100644 --- a/remote/web/package-lock.json +++ b/remote/web/package-lock.json @@ -13,13 +13,14 @@ "@vscode/iconv-lite-umd": "0.7.0", "@vscode/tree-sitter-wasm": "^0.0.4", "@vscode/vscode-languagedetection": "1.0.21", - "@xterm/addon-clipboard": "^0.2.0-beta.48", - "@xterm/addon-image": "^0.9.0-beta.65", - "@xterm/addon-search": "^0.16.0-beta.65", - "@xterm/addon-serialize": "^0.14.0-beta.65", - "@xterm/addon-unicode11": "^0.9.0-beta.65", - "@xterm/addon-webgl": "^0.19.0-beta.65", - "@xterm/xterm": "^5.6.0-beta.65", + "@xterm/addon-clipboard": "^0.2.0-beta.53", + "@xterm/addon-image": "^0.9.0-beta.70", + "@xterm/addon-ligatures": "^0.10.0-beta.70", + "@xterm/addon-search": "^0.16.0-beta.70", + "@xterm/addon-serialize": "^0.14.0-beta.70", + "@xterm/addon-unicode11": "^0.9.0-beta.70", + "@xterm/addon-webgl": "^0.19.0-beta.70", + "@xterm/xterm": "^5.6.0-beta.70", "jschardet": "3.1.4", "tas-client-umd": "0.2.0", "vscode-oniguruma": "1.7.0", @@ -87,60 +88,119 @@ } }, "node_modules/@xterm/addon-clipboard": { - "version": "0.2.0-beta.48", - "resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.2.0-beta.48.tgz", - "integrity": "sha512-oTrGoGMOQaW7PINAShhNR3duV8l2No/DbJ9rtfSBjfoQ9clWe0K+4fUHdJIuzW65AZsiLo9oh5IbHLWxlFP3Jg==", + "version": "0.2.0-beta.53", + "resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.2.0-beta.53.tgz", + "integrity": "sha512-kCcBuGvF8mwzExU+Tm9eylPvp1kXTkvm+kO0V4qP7HI3ZCw5vfKmnlRn41FvNIylsK2hnmrFtxauPHEGBy/dfA==", + "license": "MIT", "dependencies": { "js-base64": "^3.7.5" }, "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.65" + "@xterm/xterm": "^5.6.0-beta.70" } }, "node_modules/@xterm/addon-image": { - "version": "0.9.0-beta.65", - "resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.9.0-beta.65.tgz", - "integrity": "sha512-Ud5FI5cGtpbz8yYCHC4QINlbC1K04Mex1JD5oAWPFwjlOz/Si/DhqbY1QBDl6QrUmSUUTZc5DKT3v3nN5WbZxQ==", + "version": "0.9.0-beta.70", + "resolved": "https://registry.npmjs.org/@xterm/addon-image/-/addon-image-0.9.0-beta.70.tgz", + "integrity": "sha512-QLhy77i0sjnffkLuxj1yB/mBUJI64bbL86eMW+1g5XEsZnSevY8YwU/cEJg02PAGK0ggwQNNfiRwoO6VBCdYFg==", + "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.65" + "@xterm/xterm": "^5.6.0-beta.70" + } + }, + "node_modules/@xterm/addon-ligatures": { + "version": "0.10.0-beta.70", + "resolved": "https://registry.npmjs.org/@xterm/addon-ligatures/-/addon-ligatures-0.10.0-beta.70.tgz", + "integrity": "sha512-BPDHHOybUWO6mjHf/RMDBjSKDl9QdyyGTyHvmlyhuI/2sma3lu98bA4U03F8nBnvj/6otzEFARuOeoN7rkfRng==", + "license": "MIT", + "dependencies": { + "font-finder": "^1.1.0", + "font-ligatures": "^1.4.1" + }, + "engines": { + "node": ">8.0.0" + }, + "peerDependencies": { + "@xterm/xterm": "^5.6.0-beta.70" } }, "node_modules/@xterm/addon-search": { - "version": "0.16.0-beta.65", - "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.16.0-beta.65.tgz", - "integrity": "sha512-eHfbbgFCS3AcD8YSvGFIkW9NW4pfGHrtRDWIojM/zqmyi3JaCmHUgZmlxbaANwBlgLiNpJLDq7cmoz//86NgGw==", + "version": "0.16.0-beta.70", + "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.16.0-beta.70.tgz", + "integrity": "sha512-uaNBf77cr5Jikj69TDkTfe3V3wHA+4tDTFcv8DwJ/KGORPLUfBk8cn9HpbIJ+0jc1kOZr5xDrEjaYNwEVDkGeQ==", + "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.65" + "@xterm/xterm": "^5.6.0-beta.70" } }, "node_modules/@xterm/addon-serialize": { - "version": "0.14.0-beta.65", - "resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.14.0-beta.65.tgz", - "integrity": "sha512-NlvXVtFAHpLKEDzd+6/68yPFQ4Oe02jvQH6+a8p+gcxbSEyYj1l0sqqeABQ3gC5QDCmQepk6BQVB8Rpz/e4HgQ==", + "version": "0.14.0-beta.70", + "resolved": "https://registry.npmjs.org/@xterm/addon-serialize/-/addon-serialize-0.14.0-beta.70.tgz", + "integrity": "sha512-4ijqHU7xDRcZ4Gm6yN/tKsZzovUwzttE9/pPfhxpWIgSdm9c8i5R4rGyOAlAZCnuU3DT5vPplQO7W/0zwAmJsQ==", + "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.65" + "@xterm/xterm": "^5.6.0-beta.70" } }, "node_modules/@xterm/addon-unicode11": { - "version": "0.9.0-beta.65", - "resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.9.0-beta.65.tgz", - "integrity": "sha512-sH9mu2GEIVuc6cs6HxsmCXn6KKzCa5D+Zyl1Pt9d2S4FKqSB17Fc4mVukCOMW7wUR9UChKTbxmByGJK+4xwdCA==", + "version": "0.9.0-beta.70", + "resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.9.0-beta.70.tgz", + "integrity": "sha512-9UE4v2SpWtqd85Hqvk8LW4QA9Phe93RMVltrNtt9jCUmkAok/QLFOEbLqoq2JUOvlmfsYfXmIl11Wg4CYIsvwA==", + "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.65" + "@xterm/xterm": "^5.6.0-beta.70" } }, "node_modules/@xterm/addon-webgl": { - "version": "0.19.0-beta.65", - "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.19.0-beta.65.tgz", - "integrity": "sha512-gPwP8ozqrLFWOlo9eh2I+acxq0Z6obG7GpL8HpJ+B+q6+vjfxoXHQ6vV04W90aoAvbLpScHcT/JtXA58GcH2vw==", + "version": "0.19.0-beta.70", + "resolved": "https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.19.0-beta.70.tgz", + "integrity": "sha512-3BkmF24i66SHCfAkcHy1VC6H715qyelfOIjd6n7sTqHon56J1bUO782Q1al/MK0vSvplElBRKVdR31SDvITzpg==", + "license": "MIT", "peerDependencies": { - "@xterm/xterm": "^5.6.0-beta.65" + "@xterm/xterm": "^5.6.0-beta.70" } }, "node_modules/@xterm/xterm": { - "version": "5.6.0-beta.65", - "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.6.0-beta.65.tgz", - "integrity": "sha512-o6IIg4hqs994gtSQMbzQM6OUeLIXPG6RUxjoref0KJX4PIa/BqQjwQLPuSxnjOFeRQIvhZ2mSAPfGWJQ6jEGAg==" + "version": "5.6.0-beta.70", + "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.6.0-beta.70.tgz", + "integrity": "sha512-qviQMVWhRtgPn4z8PHNH6D/ffSKkNBHmUX1HyJxl325QM2xF8M8met83uFv7JZm7a5OQYScnLGsFAoTreSgdew==", + "license": "MIT" + }, + "node_modules/font-finder": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/font-finder/-/font-finder-1.1.0.tgz", + "integrity": "sha512-wpCL2uIbi6GurJbU7ZlQ3nGd61Ho+dSU6U83/xJT5UPFfN35EeCW/rOtS+5k+IuEZu2SYmHzDIPL9eA5tSYRAw==", + "license": "MIT", + "dependencies": { + "get-system-fonts": "^2.0.0", + "promise-stream-reader": "^1.0.1" + }, + "engines": { + "node": ">8.0.0" + } + }, + "node_modules/font-ligatures": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/font-ligatures/-/font-ligatures-1.4.1.tgz", + "integrity": "sha512-7W6zlfyhvCqShZ5ReUWqmSd9vBaUudW0Hxis+tqUjtHhsPU+L3Grf8mcZAtCiXHTzorhwdRTId2WeH/88gdFkw==", + "license": "MIT", + "dependencies": { + "font-finder": "^1.0.3", + "lru-cache": "^6.0.0", + "opentype.js": "^0.8.0" + }, + "engines": { + "node": ">8.0.0" + } + }, + "node_modules/get-system-fonts": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-system-fonts/-/get-system-fonts-2.0.2.tgz", + "integrity": "sha512-zzlgaYnHMIEgHRrfC7x0Qp0Ylhw/sHpM6MHXeVBTYIsvGf5GpbnClB+Q6rAPdn+0gd2oZZIo6Tj3EaWrt4VhDQ==", + "license": "MIT", + "engines": { + "node": ">8.0.0" + } }, "node_modules/js-base64": { "version": "3.7.7", @@ -156,11 +216,50 @@ "node": ">=0.1.90" } }, + "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==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/opentype.js": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/opentype.js/-/opentype.js-0.8.0.tgz", + "integrity": "sha512-FQHR4oGP+a0m/f6yHoRpBOIbn/5ZWxKd4D/djHVJu8+KpBTYrJda0b7mLcgDEMWXE9xBCJm+qb0yv6FcvPjukg==", + "license": "MIT", + "dependencies": { + "tiny-inflate": "^1.0.2" + }, + "bin": { + "ot": "bin/ot" + } + }, + "node_modules/promise-stream-reader": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-stream-reader/-/promise-stream-reader-1.0.1.tgz", + "integrity": "sha512-Tnxit5trUjBAqqZCGWwjyxhmgMN4hGrtpW3Oc/tRI4bpm/O2+ej72BB08l6JBnGQgVDGCLvHFGjGgQS6vzhwXg==", + "license": "MIT", + "engines": { + "node": ">8.0.0" + } + }, "node_modules/tas-client-umd": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/tas-client-umd/-/tas-client-umd-0.2.0.tgz", "integrity": "sha512-oezN7mJVm5qZDVEby7OzxCLKUpUN5of0rY4dvOWaDF2JZBlGpd3BXceFN8B53qlTaIkVSzP65aAMT0Vc+/N25Q==" }, + "node_modules/tiny-inflate": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", + "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", + "license": "MIT" + }, "node_modules/vscode-oniguruma": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", @@ -170,6 +269,12 @@ "version": "9.1.0", "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-9.1.0.tgz", "integrity": "sha512-lxKSVp2DkFOx9RDAvpiYUrB9/KT1fAfi1aE8CBGstP8N7rLF+Seifj8kDA198X0mYj1CjQUC+81+nQf8CO0nVA==" + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" } } } diff --git a/remote/web/package.json b/remote/web/package.json index 5529301e4c429..8c1decd65bba7 100644 --- a/remote/web/package.json +++ b/remote/web/package.json @@ -8,13 +8,14 @@ "@vscode/iconv-lite-umd": "0.7.0", "@vscode/tree-sitter-wasm": "^0.0.4", "@vscode/vscode-languagedetection": "1.0.21", - "@xterm/addon-clipboard": "^0.2.0-beta.48", - "@xterm/addon-image": "^0.9.0-beta.65", - "@xterm/addon-search": "^0.16.0-beta.65", - "@xterm/addon-serialize": "^0.14.0-beta.65", - "@xterm/addon-unicode11": "^0.9.0-beta.65", - "@xterm/addon-webgl": "^0.19.0-beta.65", - "@xterm/xterm": "^5.6.0-beta.65", + "@xterm/addon-clipboard": "^0.2.0-beta.53", + "@xterm/addon-image": "^0.9.0-beta.70", + "@xterm/addon-ligatures": "^0.10.0-beta.70", + "@xterm/addon-search": "^0.16.0-beta.70", + "@xterm/addon-serialize": "^0.14.0-beta.70", + "@xterm/addon-unicode11": "^0.9.0-beta.70", + "@xterm/addon-webgl": "^0.19.0-beta.70", + "@xterm/xterm": "^5.6.0-beta.70", "jschardet": "3.1.4", "tas-client-umd": "0.2.0", "vscode-oniguruma": "1.7.0", diff --git a/resources/linux/debian/postinst.template b/resources/linux/debian/postinst.template index b292cff8b2921..b44aa361cb345 100755 --- a/resources/linux/debian/postinst.template +++ b/resources/linux/debian/postinst.template @@ -36,49 +36,60 @@ if [ "@@NAME@@" != "code-oss" ]; then eval $(apt-config shell APT_TRUSTED_PARTS Dir::Etc::trustedparts/d) CODE_TRUSTED_PART=${APT_TRUSTED_PARTS}microsoft.gpg - RET=true + # RET seems to be true by default even after db_get is called on a first install. + RET='true' if [ -e '/usr/share/debconf/confmodule' ]; then . /usr/share/debconf/confmodule db_get @@NAME@@/add-microsoft-repo || true fi - # Determine whether to install the repository source list - WRITE_SOURCE=0 - if [ "$RET" = false ]; then - # The user does not want to add the Microsoft repository - WRITE_SOURCE=0 + # Determine whether to write the Microsoft repository source list + WRITE_SOURCE='no' + if [ "$RET" = 'false' ]; then + # The user specified in debconf not to add the Microsoft repository + WRITE_SOURCE='no' elif [ -f "$CODE_SOURCE_PART_DEB822" ]; then # The user has migrated themselves to the DEB822 format - WRITE_SOURCE=0 + WRITE_SOURCE='no' elif [ -f "$CODE_SOURCE_PART" ] && (grep -q "http://packages.microsoft.com/repos/vscode" $CODE_SOURCE_PART); then # Migrate from old repository - WRITE_SOURCE=2 + WRITE_SOURCE='yes' elif [ -f "$CODE_SOURCE_PART" ] && (grep -q "http://packages.microsoft.com/repos/code" $CODE_SOURCE_PART); then # Migrate from old repository - WRITE_SOURCE=2 + WRITE_SOURCE='yes' elif apt-cache policy | grep -q "https://packages.microsoft.com/repos/code"; then # The user is already on the new repository - WRITE_SOURCE=0 + WRITE_SOURCE='no' elif [ ! -f $CODE_SOURCE_PART ] && [ ! -f /etc/rpi-issue ]; then - # Write source list if it does not exist and we're not running on Raspberry Pi OS - WRITE_SOURCE=1 + # Source list does not exist and we're not running on Raspberry Pi OS + WRITE_SOURCE='ask' elif grep -q "# disabled on upgrade to" $CODE_SOURCE_PART; then - # Write source list if it was disabled by OS upgrade - WRITE_SOURCE=1 + # Source list was disabled by OS upgrade + WRITE_SOURCE='ask' fi - if [ "$WRITE_SOURCE" -eq "1" ] && [ -e '/usr/share/debconf/confmodule' ]; then - # Ask the user whether to actually write the source list - db_input high @@NAME@@/add-microsoft-repo || true - db_go || true + if [ "$WRITE_SOURCE" = 'ask' ]; then + if ! [ -t 1 ]; then + # By default, write sources in a non-interactive terminal + # to match old behavior. + WRITE_SOURCE='yes' + elif [ -e '/usr/share/debconf/confmodule' ]; then + # Ask the user whether to actually write the source list + db_input high @@NAME@@/add-microsoft-repo || true + db_go || true - db_get @@NAME@@/add-microsoft-repo - if [ "$RET" = false ]; then - WRITE_SOURCE=0 + db_get @@NAME@@/add-microsoft-repo + if [ "$RET" = false ]; then + WRITE_SOURCE='no' + fi + else + # The terminal is interactive but there is no debconf. + # Write sources to match old behavior. + WRITE_SOURCE='yes' fi fi - if [ "$WRITE_SOURCE" -ne "0" ]; then + if [ "$WRITE_SOURCE" != 'no' ]; then echo "### THIS FILE IS AUTOMATICALLY CONFIGURED ### # You may comment out this entry, but any other modifications may be lost. deb [arch=amd64,arm64,armhf] https://packages.microsoft.com/repos/code stable main" > $CODE_SOURCE_PART diff --git a/resources/linux/snap/electron-launch b/resources/linux/snap/electron-launch index cf9e946360c2a..bbd8e76588e11 100755 --- a/resources/linux/snap/electron-launch +++ b/resources/linux/snap/electron-launch @@ -96,6 +96,7 @@ function can_open_file() { # Preserve system variables that get modified below copy_env_variable XDG_CONFIG_DIRS copy_env_variable XDG_DATA_DIRS +copy_env_variable XDG_DATA_HOME copy_env_variable LOCPATH copy_env_variable GIO_MODULE_DIR copy_env_variable GSETTINGS_SCHEMA_DIR @@ -167,6 +168,18 @@ fi # Keep an array of data dirs, for looping through them IFS=':' read -r -a data_dirs_array <<< "$XDG_DATA_DIRS" +# Font Config +export FONTCONFIG_PATH="/etc/fonts" +export FONTCONFIG_FILE="/etc/fonts/fonts.conf" + +if [ "$needs_update" = true ]; then + rm -rf "$XDG_DATA_HOME"/fonts + + if [ -d "$SNAP_REAL_HOME/.local/share/fonts" ]; then + ln -s "$SNAP_REAL_HOME/.local/share/fonts" "$XDG_DATA_HOME/fonts" + fi +fi + # Build mime.cache needed for gtk and qt icon # TODO(deepak1556): Re-enable this once we move to core22 # Refs https://github.com/microsoft/vscode/issues/230454#issuecomment-2418352959 diff --git a/scripts/xterm-update.js b/scripts/xterm-update.js index 15759069604f0..5a7db71abac12 100644 --- a/scripts/xterm-update.js +++ b/scripts/xterm-update.js @@ -10,6 +10,7 @@ const moduleNames = [ '@xterm/xterm', '@xterm/addon-clipboard', '@xterm/addon-image', + '@xterm/addon-ligatures', '@xterm/addon-search', '@xterm/addon-serialize', '@xterm/addon-unicode11', diff --git a/src/vs/base/browser/cssValue.ts b/src/vs/base/browser/cssValue.ts index c758b629a718c..f099ead8ce0e3 100644 --- a/src/vs/base/browser/cssValue.ts +++ b/src/vs/base/browser/cssValue.ts @@ -2,9 +2,16 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { Color } from '../common/color.js'; import { FileAccess } from '../common/network.js'; import { URI } from '../common/uri.js'; +export type CssFragment = string & { readonly __cssFragment: unique symbol }; + +function asFragment(raw: string): CssFragment { + return raw as CssFragment; +} + export function asCssValueWithDefault(cssPropertyValue: string | undefined, dflt: string): string { if (cssPropertyValue !== undefined) { const variableMatch = cssPropertyValue.match(/^\s*var\((.+)\)$/); @@ -20,16 +27,75 @@ export function asCssValueWithDefault(cssPropertyValue: string | undefined, dflt return dflt; } -export function asCSSPropertyValue(value: string) { - return `'${value.replace(/'/g, '%27')}'`; +export function sizeValue(value: string): CssFragment { + const out = value.replaceAll(/[^\w.%+-]/gi, ''); + if (out !== value) { + console.warn(`CSS size ${value} modified to ${out} to be safe for CSS`); + } + return asFragment(out); +} + +export function hexColorValue(value: string): CssFragment { + const out = value.replaceAll(/[^[0-9a-fA-F#]]/gi, ''); + if (out !== value) { + console.warn(`CSS hex color ${value} modified to ${out} to be safe for CSS`); + } + return asFragment(out); +} + +export function identValue(value: string): CssFragment { + const out = value.replaceAll(/[^_\-a-z0-9]/gi, ''); + if (out !== value) { + console.warn(`CSS ident value ${value} modified to ${out} to be safe for CSS`); + } + return asFragment(out); +} + +export function stringValue(value: string): CssFragment { + return asFragment(`'${value.replaceAll(/'/g, '\\000027')}'`); } /** * returns url('...') */ -export function asCSSUrl(uri: URI | null | undefined): string { +export function asCSSUrl(uri: URI | null | undefined): CssFragment { if (!uri) { - return `url('')`; + return asFragment(`url('')`); + } + return inline`url(${stringValue(FileAccess.uriToBrowserUri(uri).toString(true))})`; +} + +export function className(value: string): CssFragment { + const out = CSS.escape(value); + if (out !== value) { + console.warn(`CSS class name ${value} modified to ${out} to be safe for CSS`); + } + return asFragment(out); +} + +type InlineCssTemplateValue = CssFragment | Color; + +/** + * Template string tag that that constructs a CSS fragment. + * + * All expressions in the template must be css safe values. + */ +export function inline(strings: TemplateStringsArray, ...values: InlineCssTemplateValue[]): CssFragment { + return asFragment(strings.reduce((result, str, i) => { + const value = values[i] || ''; + return result + str + value; + }, '')); +} + + +export class Builder { + private readonly _parts: CssFragment[] = []; + + push(...parts: CssFragment[]): void { + this._parts.push(...parts); + } + + join(joiner = '\n'): CssFragment { + return asFragment(this._parts.join(joiner)); } - return `url('${FileAccess.uriToBrowserUri(uri).toString(true).replace(/'/g, '%27')}')`; } diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index 55b161d6be6e6..ecf1d194cdcd5 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -922,105 +922,6 @@ export function getActiveWindow(): CodeWindow { return (document.defaultView?.window ?? mainWindow) as CodeWindow; } -const globalStylesheets = new Map>(); - -export function isGlobalStylesheet(node: Node): boolean { - return globalStylesheets.has(node as HTMLStyleElement); -} - -/** - * A version of createStyleSheet which has a unified API to initialize/set the style content. - */ -export function createStyleSheet2(): WrappedStyleElement { - return new WrappedStyleElement(); -} - -class WrappedStyleElement { - private _currentCssStyle = ''; - private _styleSheet: HTMLStyleElement | undefined = undefined; - - public setStyle(cssStyle: string): void { - if (cssStyle === this._currentCssStyle) { - return; - } - this._currentCssStyle = cssStyle; - - if (!this._styleSheet) { - this._styleSheet = createStyleSheet(mainWindow.document.head, (s) => s.innerText = cssStyle); - } else { - this._styleSheet.innerText = cssStyle; - } - } - - public dispose(): void { - if (this._styleSheet) { - this._styleSheet.remove(); - this._styleSheet = undefined; - } - } -} - -export function createStyleSheet(container: HTMLElement = mainWindow.document.head, beforeAppend?: (style: HTMLStyleElement) => void, disposableStore?: DisposableStore): HTMLStyleElement { - const style = document.createElement('style'); - style.type = 'text/css'; - style.media = 'screen'; - beforeAppend?.(style); - container.appendChild(style); - - if (disposableStore) { - disposableStore.add(toDisposable(() => style.remove())); - } - - // With as container, the stylesheet becomes global and is tracked - // to support auxiliary windows to clone the stylesheet. - if (container === mainWindow.document.head) { - const globalStylesheetClones = new Set(); - globalStylesheets.set(style, globalStylesheetClones); - - for (const { window: targetWindow, disposables } of getWindows()) { - if (targetWindow === mainWindow) { - continue; // main window is already tracked - } - - const cloneDisposable = disposables.add(cloneGlobalStyleSheet(style, globalStylesheetClones, targetWindow)); - disposableStore?.add(cloneDisposable); - } - } - - return style; -} - -export function cloneGlobalStylesheets(targetWindow: Window): IDisposable { - const disposables = new DisposableStore(); - - for (const [globalStylesheet, clonedGlobalStylesheets] of globalStylesheets) { - disposables.add(cloneGlobalStyleSheet(globalStylesheet, clonedGlobalStylesheets, targetWindow)); - } - - return disposables; -} - -function cloneGlobalStyleSheet(globalStylesheet: HTMLStyleElement, globalStylesheetClones: Set, targetWindow: Window): IDisposable { - const disposables = new DisposableStore(); - - const clone = globalStylesheet.cloneNode(true) as HTMLStyleElement; - targetWindow.document.head.appendChild(clone); - disposables.add(toDisposable(() => clone.remove())); - - for (const rule of getDynamicStyleSheetRules(globalStylesheet)) { - clone.sheet?.insertRule(rule.cssText, clone.sheet?.cssRules.length); - } - - disposables.add(sharedMutationObserver.observe(globalStylesheet, disposables, { childList: true })(() => { - clone.textContent = globalStylesheet.textContent; - })); - - globalStylesheetClones.add(clone); - disposables.add(toDisposable(() => globalStylesheetClones.delete(clone))); - - return disposables; -} - interface IMutationObserver { users: number; readonly observer: MutationObserver; @@ -1088,67 +989,6 @@ function createHeadElement(tagName: string, container: HTMLElement = mainWindow. return element; } -let _sharedStyleSheet: HTMLStyleElement | null = null; -function getSharedStyleSheet(): HTMLStyleElement { - if (!_sharedStyleSheet) { - _sharedStyleSheet = createStyleSheet(); - } - return _sharedStyleSheet; -} - -function getDynamicStyleSheetRules(style: HTMLStyleElement) { - if (style?.sheet?.rules) { - // Chrome, IE - return style.sheet.rules; - } - if (style?.sheet?.cssRules) { - // FF - return style.sheet.cssRules; - } - return []; -} - -export function createCSSRule(selector: string, cssText: string, style = getSharedStyleSheet()): void { - if (!style || !cssText) { - return; - } - - style.sheet?.insertRule(`${selector} {${cssText}}`, 0); - - // Apply rule also to all cloned global stylesheets - for (const clonedGlobalStylesheet of globalStylesheets.get(style) ?? []) { - createCSSRule(selector, cssText, clonedGlobalStylesheet); - } -} - -export function removeCSSRulesContainingSelector(ruleName: string, style = getSharedStyleSheet()): void { - if (!style) { - return; - } - - const rules = getDynamicStyleSheetRules(style); - const toDelete: number[] = []; - for (let i = 0; i < rules.length; i++) { - const rule = rules[i]; - if (isCSSStyleRule(rule) && rule.selectorText.indexOf(ruleName) !== -1) { - toDelete.push(i); - } - } - - for (let i = toDelete.length - 1; i >= 0; i--) { - style.sheet?.deleteRule(toDelete[i]); - } - - // Remove rules also from all cloned global stylesheets - for (const clonedGlobalStylesheet of globalStylesheets.get(style) ?? []) { - removeCSSRulesContainingSelector(ruleName, clonedGlobalStylesheet); - } -} - -function isCSSStyleRule(rule: CSSRule): rule is CSSStyleRule { - return typeof (rule as CSSStyleRule).selectorText === 'string'; -} - export function isHTMLElement(e: unknown): e is HTMLElement { // eslint-disable-next-line no-restricted-syntax return e instanceof HTMLElement || e instanceof getWindow(e as Node).HTMLElement; diff --git a/src/vs/base/browser/domObservable.ts b/src/vs/base/browser/domObservable.ts index 942797379832a..9ab3f50cde86c 100644 --- a/src/vs/base/browser/domObservable.ts +++ b/src/vs/base/browser/domObservable.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { createStyleSheet2 } from './dom.js'; +import { createStyleSheet2 } from './domStylesheets.js'; import { DisposableStore, IDisposable } from '../common/lifecycle.js'; import { autorun, IObservable } from '../common/observable.js'; diff --git a/src/vs/base/browser/domStylesheets.ts b/src/vs/base/browser/domStylesheets.ts new file mode 100644 index 0000000000000..ebc249c660827 --- /dev/null +++ b/src/vs/base/browser/domStylesheets.ts @@ -0,0 +1,168 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { DisposableStore, toDisposable, IDisposable } from '../common/lifecycle.js'; +import { getWindows, sharedMutationObserver } from './dom.js'; +import { mainWindow } from './window.js'; + +const globalStylesheets = new Map>(); + +export function isGlobalStylesheet(node: Node): boolean { + return globalStylesheets.has(node as HTMLStyleElement); +} + +/** + * A version of createStyleSheet which has a unified API to initialize/set the style content. + */ +export function createStyleSheet2(): WrappedStyleElement { + return new WrappedStyleElement(); +} + +class WrappedStyleElement { + private _currentCssStyle = ''; + private _styleSheet: HTMLStyleElement | undefined = undefined; + + public setStyle(cssStyle: string): void { + if (cssStyle === this._currentCssStyle) { + return; + } + this._currentCssStyle = cssStyle; + + if (!this._styleSheet) { + this._styleSheet = createStyleSheet(mainWindow.document.head, (s) => s.innerText = cssStyle); + } else { + this._styleSheet.innerText = cssStyle; + } + } + + public dispose(): void { + if (this._styleSheet) { + this._styleSheet.remove(); + this._styleSheet = undefined; + } + } +} + +export function createStyleSheet(container: HTMLElement = mainWindow.document.head, beforeAppend?: (style: HTMLStyleElement) => void, disposableStore?: DisposableStore): HTMLStyleElement { + const style = document.createElement('style'); + style.type = 'text/css'; + style.media = 'screen'; + beforeAppend?.(style); + container.appendChild(style); + + if (disposableStore) { + disposableStore.add(toDisposable(() => style.remove())); + } + + // With as container, the stylesheet becomes global and is tracked + // to support auxiliary windows to clone the stylesheet. + if (container === mainWindow.document.head) { + const globalStylesheetClones = new Set(); + globalStylesheets.set(style, globalStylesheetClones); + + for (const { window: targetWindow, disposables } of getWindows()) { + if (targetWindow === mainWindow) { + continue; // main window is already tracked + } + + const cloneDisposable = disposables.add(cloneGlobalStyleSheet(style, globalStylesheetClones, targetWindow)); + disposableStore?.add(cloneDisposable); + } + } + + return style; +} + +export function cloneGlobalStylesheets(targetWindow: Window): IDisposable { + const disposables = new DisposableStore(); + + for (const [globalStylesheet, clonedGlobalStylesheets] of globalStylesheets) { + disposables.add(cloneGlobalStyleSheet(globalStylesheet, clonedGlobalStylesheets, targetWindow)); + } + + return disposables; +} + +function cloneGlobalStyleSheet(globalStylesheet: HTMLStyleElement, globalStylesheetClones: Set, targetWindow: Window): IDisposable { + const disposables = new DisposableStore(); + + const clone = globalStylesheet.cloneNode(true) as HTMLStyleElement; + targetWindow.document.head.appendChild(clone); + disposables.add(toDisposable(() => clone.remove())); + + for (const rule of getDynamicStyleSheetRules(globalStylesheet)) { + clone.sheet?.insertRule(rule.cssText, clone.sheet?.cssRules.length); + } + + disposables.add(sharedMutationObserver.observe(globalStylesheet, disposables, { childList: true })(() => { + clone.textContent = globalStylesheet.textContent; + })); + + globalStylesheetClones.add(clone); + disposables.add(toDisposable(() => globalStylesheetClones.delete(clone))); + + return disposables; +} + +let _sharedStyleSheet: HTMLStyleElement | null = null; +function getSharedStyleSheet(): HTMLStyleElement { + if (!_sharedStyleSheet) { + _sharedStyleSheet = createStyleSheet(); + } + return _sharedStyleSheet; +} + +function getDynamicStyleSheetRules(style: HTMLStyleElement) { + if (style?.sheet?.rules) { + // Chrome, IE + return style.sheet.rules; + } + if (style?.sheet?.cssRules) { + // FF + return style.sheet.cssRules; + } + return []; +} + +export function createCSSRule(selector: string, cssText: string, style = getSharedStyleSheet()): void { + if (!style || !cssText) { + return; + } + + style.sheet?.insertRule(`${selector} {${cssText}}`, 0); + + // Apply rule also to all cloned global stylesheets + for (const clonedGlobalStylesheet of globalStylesheets.get(style) ?? []) { + createCSSRule(selector, cssText, clonedGlobalStylesheet); + } +} + +export function removeCSSRulesContainingSelector(ruleName: string, style = getSharedStyleSheet()): void { + if (!style) { + return; + } + + const rules = getDynamicStyleSheetRules(style); + const toDelete: number[] = []; + for (let i = 0; i < rules.length; i++) { + const rule = rules[i]; + if (isCSSStyleRule(rule) && rule.selectorText.indexOf(ruleName) !== -1) { + toDelete.push(i); + } + } + + for (let i = toDelete.length - 1; i >= 0; i--) { + style.sheet?.deleteRule(toDelete[i]); + } + + // Remove rules also from all cloned global stylesheets + for (const clonedGlobalStylesheet of globalStylesheets.get(style) ?? []) { + removeCSSRulesContainingSelector(ruleName, clonedGlobalStylesheet); + } +} + +function isCSSStyleRule(rule: CSSRule): rule is CSSStyleRule { + return typeof (rule as CSSStyleRule).selectorText === 'string'; +} diff --git a/src/vs/base/browser/formattedTextRenderer.ts b/src/vs/base/browser/formattedTextRenderer.ts index 66d0d575b9690..ed15b1316ab8e 100644 --- a/src/vs/base/browser/formattedTextRenderer.ts +++ b/src/vs/base/browser/formattedTextRenderer.ts @@ -9,7 +9,7 @@ import { IMouseEvent } from './mouseEvent.js'; import { DisposableStore } from '../common/lifecycle.js'; export interface IContentActionHandler { - callback: (content: string, event: IMouseEvent | IKeyboardEvent) => void; + readonly callback: (content: string, event: IMouseEvent | IKeyboardEvent) => void; readonly disposables: DisposableStore; } diff --git a/src/vs/base/browser/markdownRenderer.ts b/src/vs/base/browser/markdownRenderer.ts index d24679fcd37ce..5d58485ddd85b 100644 --- a/src/vs/base/browser/markdownRenderer.ts +++ b/src/vs/base/browser/markdownRenderer.ts @@ -96,63 +96,154 @@ const defaultMarkedRenderers = Object.freeze({ /** * Low-level way create a html element from a markdown string. * - * **Note** that for most cases you should be using [`MarkdownRenderer`](./src/vs/editor/contrib/markdownRenderer/browser/markdownRenderer.ts) + * **Note** that for most cases you should be using {@link import('../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js').MarkdownRenderer MarkdownRenderer} * which comes with support for pretty code block rendering and which uses the default way of handling links. */ -export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRenderOptions = {}, markedOptions: MarkedOptions = {}): { element: HTMLElement; dispose: () => void } { +export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRenderOptions = {}, markedOptions: Readonly = {}): { element: HTMLElement; dispose: () => void } { const disposables = new DisposableStore(); let isDisposed = false; const element = createElement(options); - const _uriMassage = function (part: string): string { - let data: any; - try { - data = parse(decodeURIComponent(part)); - } catch (e) { - // ignore - } - if (!data) { - return part; - } - data = cloneAndChange(data, value => { - if (markdown.uris && markdown.uris[value]) { - return URI.revive(markdown.uris[value]); - } else { - return undefined; + const { renderer, codeBlocks, syncCodeBlocks } = createMarkdownRenderer(options, markdown); + const value = preprocessMarkdownString(markdown); + + let renderedMarkdown: string; + if (options.fillInIncompleteTokens) { + // The defaults are applied by parse but not lexer()/parser(), and they need to be present + const opts: MarkedOptions = { + ...marked.defaults, + ...markedOptions, + renderer + }; + const tokens = marked.lexer(value, opts); + const newTokens = fillInIncompleteTokens(tokens); + renderedMarkdown = marked.parser(newTokens, opts); + } else { + renderedMarkdown = marked.parse(value, { ...markedOptions, renderer, async: false }); + } + + // Rewrite theme icons + if (markdown.supportThemeIcons) { + const elements = renderLabelWithIcons(renderedMarkdown); + renderedMarkdown = elements.map(e => typeof e === 'string' ? e : e.outerHTML).join(''); + } + + const htmlParser = new DOMParser(); + const markdownHtmlDoc = htmlParser.parseFromString(sanitizeRenderedMarkdown({ isTrusted: markdown.isTrusted, ...options.sanitizerOptions }, renderedMarkdown) as unknown as string, 'text/html'); + + rewriteRenderedLinks(markdown, options, markdownHtmlDoc.body); + + element.innerHTML = sanitizeRenderedMarkdown({ isTrusted: markdown.isTrusted, ...options.sanitizerOptions }, markdownHtmlDoc.body.innerHTML) as unknown as string; + + if (codeBlocks.length > 0) { + Promise.all(codeBlocks).then((tuples) => { + if (isDisposed) { + return; + } + const renderedElements = new Map(tuples); + const placeholderElements = element.querySelectorAll(`div[data-code]`); + for (const placeholderElement of placeholderElements) { + const renderedElement = renderedElements.get(placeholderElement.dataset['code'] ?? ''); + if (renderedElement) { + DOM.reset(placeholderElement, renderedElement); + } } + options.asyncRenderCallback?.(); }); - return encodeURIComponent(JSON.stringify(data)); - }; + } else if (syncCodeBlocks.length > 0) { + const renderedElements = new Map(syncCodeBlocks); + const placeholderElements = element.querySelectorAll(`div[data-code]`); + for (const placeholderElement of placeholderElements) { + const renderedElement = renderedElements.get(placeholderElement.dataset['code'] ?? ''); + if (renderedElement) { + DOM.reset(placeholderElement, renderedElement); + } + } + } - const _href = function (href: string, isDomUri: boolean): string { - const data = markdown.uris && markdown.uris[href]; - let uri = URI.revive(data); - if (isDomUri) { - if (href.startsWith(Schemas.data + ':')) { - return href; + // Signal size changes for image tags + if (options.asyncRenderCallback) { + for (const img of element.getElementsByTagName('img')) { + const listener = disposables.add(DOM.addDisposableListener(img, 'load', () => { + listener.dispose(); + options.asyncRenderCallback!(); + })); + } + } + + // Add event listeners for links + if (options.actionHandler) { + const onClick = options.actionHandler.disposables.add(new DomEmitter(element, 'click')); + const onAuxClick = options.actionHandler.disposables.add(new DomEmitter(element, 'auxclick')); + options.actionHandler.disposables.add(Event.any(onClick.event, onAuxClick.event)(e => { + const mouseEvent = new StandardMouseEvent(DOM.getWindow(element), e); + if (!mouseEvent.leftButton && !mouseEvent.middleButton) { + return; } - if (!uri) { - uri = URI.parse(href); + activateLink(markdown, options, mouseEvent); + })); + + options.actionHandler.disposables.add(DOM.addDisposableListener(element, 'keydown', (e) => { + const keyboardEvent = new StandardKeyboardEvent(e); + if (!keyboardEvent.equals(KeyCode.Space) && !keyboardEvent.equals(KeyCode.Enter)) { + return; } - // this URI will end up as "src"-attribute of a dom node - // and because of that special rewriting needs to be done - // so that the URI uses a protocol that's understood by - // browsers (like http or https) - return FileAccess.uriToBrowserUri(uri).toString(true); - } - if (!uri) { - return href; + activateLink(markdown, options, keyboardEvent); + })); + } + + return { + element, + dispose: () => { + isDisposed = true; + disposables.dispose(); } - if (URI.parse(href).toString() === uri.toString()) { - return href; // no transformation performed + }; +} + +function rewriteRenderedLinks(markdown: IMarkdownString, options: MarkdownRenderOptions, root: HTMLElement) { + for (const el of root.querySelectorAll('img, audio, video, source')) { + const src = el.getAttribute('src'); // Get the raw 'src' attribute value as text, not the resolved 'src' + if (src) { + let href = src; + try { + if (markdown.baseUri) { // absolute or relative local path, or file: uri + href = resolveWithBaseUri(URI.from(markdown.baseUri), href); + } + } catch (err) { } + + el.setAttribute('src', massageHref(markdown, href, true)); + + if (options.remoteImageIsAllowed) { + const uri = URI.parse(href); + if (uri.scheme !== Schemas.file && uri.scheme !== Schemas.data && !options.remoteImageIsAllowed(uri)) { + el.replaceWith(DOM.$('', undefined, el.outerHTML)); + } + } } - if (uri.query) { - uri = uri.with({ query: _uriMassage(uri.query) }); + } + + for (const el of root.querySelectorAll('a')) { + const href = el.getAttribute('href'); // Get the raw 'href' attribute value as text, not the resolved 'href' + el.setAttribute('href', ''); // Clear out href. We use the `data-href` for handling clicks instead + if (!href + || /^data:|javascript:/i.test(href) + || (/^command:/i.test(href) && !markdown.isTrusted) + || /^command:(\/\/\/)?_workbench\.downloadResource/i.test(href)) { + // drop the link + el.replaceWith(...el.childNodes); + } else { + let resolvedHref = massageHref(markdown, href, false); + if (markdown.baseUri) { + resolvedHref = resolveWithBaseUri(URI.from(markdown.baseUri), href); + } + el.dataset.href = resolvedHref; } - return uri.toString(); - }; + } +} +function createMarkdownRenderer(options: MarkdownRenderOptions, markdown: IMarkdownString): { renderer: marked.Renderer; codeBlocks: Promise<[string, HTMLElement]>[]; syncCodeBlocks: [string, HTMLElement][] } { const renderer = new marked.Renderer(); renderer.image = defaultMarkedRenderers.image; renderer.link = defaultMarkedRenderers.link; @@ -178,45 +269,6 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende }; } - if (options.actionHandler) { - const _activateLink = function (event: StandardMouseEvent | StandardKeyboardEvent): void { - const target = event.target.closest('a[data-href]'); - if (!DOM.isHTMLElement(target)) { - return; - } - - try { - let href = target.dataset['href']; - if (href) { - if (markdown.baseUri) { - href = resolveWithBaseUri(URI.from(markdown.baseUri), href); - } - options.actionHandler!.callback(href, event); - } - } catch (err) { - onUnexpectedError(err); - } finally { - event.preventDefault(); - } - }; - const onClick = options.actionHandler.disposables.add(new DomEmitter(element, 'click')); - const onAuxClick = options.actionHandler.disposables.add(new DomEmitter(element, 'auxclick')); - options.actionHandler.disposables.add(Event.any(onClick.event, onAuxClick.event)(e => { - const mouseEvent = new StandardMouseEvent(DOM.getWindow(element), e); - if (!mouseEvent.leftButton && !mouseEvent.middleButton) { - return; - } - _activateLink(mouseEvent); - })); - options.actionHandler.disposables.add(DOM.addDisposableListener(element, 'keydown', (e) => { - const keyboardEvent = new StandardKeyboardEvent(e); - if (!keyboardEvent.equals(KeyCode.Space) && !keyboardEvent.equals(KeyCode.Enter)) { - return; - } - _activateLink(keyboardEvent); - })); - } - if (!markdown.supportHtml) { // Note: we always pass the output through dompurify after this so that we don't rely on // marked for real sanitization. @@ -229,130 +281,92 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende return match ? text : ''; }; } + return { renderer, codeBlocks, syncCodeBlocks }; +} - markedOptions.renderer = renderer; +function preprocessMarkdownString(markdown: IMarkdownString) { + let value = markdown.value; // values that are too long will freeze the UI - let value = markdown.value ?? ''; if (value.length > 100_000) { value = `${value.substr(0, 100_000)}…`; } + // escape theme icons if (markdown.supportThemeIcons) { value = markdownEscapeEscapedIcons(value); } - let renderedMarkdown: string; - if (options.fillInIncompleteTokens) { - // The defaults are applied by parse but not lexer()/parser(), and they need to be present - const opts = { - ...marked.defaults, - ...markedOptions - }; - const tokens = marked.lexer(value, opts); - const newTokens = fillInIncompleteTokens(tokens); - renderedMarkdown = marked.parser(newTokens, opts); - } else { - renderedMarkdown = marked.parse(value, { ...markedOptions, async: false }); - } + return value; +} - // Rewrite theme icons - if (markdown.supportThemeIcons) { - const elements = renderLabelWithIcons(renderedMarkdown); - renderedMarkdown = elements.map(e => typeof e === 'string' ? e : e.outerHTML).join(''); +function activateLink(markdown: IMarkdownString, options: MarkdownRenderOptions, event: StandardMouseEvent | StandardKeyboardEvent): void { + const target = event.target.closest('a[data-href]'); + if (!DOM.isHTMLElement(target)) { + return; } - const htmlParser = new DOMParser(); - const markdownHtmlDoc = htmlParser.parseFromString(sanitizeRenderedMarkdown({ isTrusted: markdown.isTrusted, ...options.sanitizerOptions }, renderedMarkdown) as unknown as string, 'text/html'); - - markdownHtmlDoc.body.querySelectorAll('img, audio, video, source') - .forEach(img => { - const src = img.getAttribute('src'); // Get the raw 'src' attribute value as text, not the resolved 'src' - if (src) { - let href = src; - try { - if (markdown.baseUri) { // absolute or relative local path, or file: uri - href = resolveWithBaseUri(URI.from(markdown.baseUri), href); - } - } catch (err) { } - - img.setAttribute('src', _href(href, true)); - - if (options.remoteImageIsAllowed) { - const uri = URI.parse(href); - if (uri.scheme !== Schemas.file && uri.scheme !== Schemas.data && !options.remoteImageIsAllowed(uri)) { - img.replaceWith(DOM.$('', undefined, img.outerHTML)); - } - } - } - }); - - markdownHtmlDoc.body.querySelectorAll('a') - .forEach(a => { - const href = a.getAttribute('href'); // Get the raw 'href' attribute value as text, not the resolved 'href' - a.setAttribute('href', ''); // Clear out href. We use the `data-href` for handling clicks instead - if ( - !href - || /^data:|javascript:/i.test(href) - || (/^command:/i.test(href) && !markdown.isTrusted) - || /^command:(\/\/\/)?_workbench\.downloadResource/i.test(href) - ) { - // drop the link - a.replaceWith(...a.childNodes); - } else { - let resolvedHref = _href(href, false); - if (markdown.baseUri) { - resolvedHref = resolveWithBaseUri(URI.from(markdown.baseUri), href); - } - a.dataset.href = resolvedHref; - } - }); - - element.innerHTML = sanitizeRenderedMarkdown({ isTrusted: markdown.isTrusted, ...options.sanitizerOptions }, markdownHtmlDoc.body.innerHTML) as unknown as string; - - if (codeBlocks.length > 0) { - Promise.all(codeBlocks).then((tuples) => { - if (isDisposed) { - return; - } - const renderedElements = new Map(tuples); - const placeholderElements = element.querySelectorAll(`div[data-code]`); - for (const placeholderElement of placeholderElements) { - const renderedElement = renderedElements.get(placeholderElement.dataset['code'] ?? ''); - if (renderedElement) { - DOM.reset(placeholderElement, renderedElement); - } - } - options.asyncRenderCallback?.(); - }); - } else if (syncCodeBlocks.length > 0) { - const renderedElements = new Map(syncCodeBlocks); - const placeholderElements = element.querySelectorAll(`div[data-code]`); - for (const placeholderElement of placeholderElements) { - const renderedElement = renderedElements.get(placeholderElement.dataset['code'] ?? ''); - if (renderedElement) { - DOM.reset(placeholderElement, renderedElement); + try { + let href = target.dataset['href']; + if (href) { + if (markdown.baseUri) { + href = resolveWithBaseUri(URI.from(markdown.baseUri), href); } + options.actionHandler!.callback(href, event); } + } catch (err) { + onUnexpectedError(err); + } finally { + event.preventDefault(); } +} - // signal size changes for image tags - if (options.asyncRenderCallback) { - for (const img of element.getElementsByTagName('img')) { - const listener = disposables.add(DOM.addDisposableListener(img, 'load', () => { - listener.dispose(); - options.asyncRenderCallback!(); - })); - } +function uriMassage(markdown: IMarkdownString, part: string): string { + let data: any; + try { + data = parse(decodeURIComponent(part)); + } catch (e) { + // ignore + } + if (!data) { + return part; } + data = cloneAndChange(data, value => { + if (markdown.uris && markdown.uris[value]) { + return URI.revive(markdown.uris[value]); + } else { + return undefined; + } + }); + return encodeURIComponent(JSON.stringify(data)); +} - return { - element, - dispose: () => { - isDisposed = true; - disposables.dispose(); +function massageHref(markdown: IMarkdownString, href: string, isDomUri: boolean): string { + const data = markdown.uris && markdown.uris[href]; + let uri = URI.revive(data); + if (isDomUri) { + if (href.startsWith(Schemas.data + ':')) { + return href; } - }; + if (!uri) { + uri = URI.parse(href); + } + // this URI will end up as "src"-attribute of a dom node + // and because of that special rewriting needs to be done + // so that the URI uses a protocol that's understood by + // browsers (like http or https) + return FileAccess.uriToBrowserUri(uri).toString(true); + } + if (!uri) { + return href; + } + if (URI.parse(href).toString() === uri.toString()) { + return href; // no transformation performed + } + if (uri.query) { + uri = uri.with({ query: uriMassage(markdown, uri.query) }); + } + return uri.toString(); } function postProcessCodeBlockLanguageId(lang: string | undefined): string { @@ -542,8 +556,11 @@ export function renderStringAsPlaintext(string: IMarkdownString | string) { } /** - * Strips all markdown from `markdown`. For example `# Header` would be output as `Header`. - * provide @param withCodeBlocks to retain code blocks + * Strips all markdown from `markdown` + * + * For example `# Header` would be output as `Header`. + * + * @param withCodeBlocks Include the ``` of code blocks as well */ export function renderMarkdownAsPlaintext(markdown: IMarkdownString, withCodeBlocks?: boolean) { // values that are too long will freeze the UI @@ -553,7 +570,10 @@ export function renderMarkdownAsPlaintext(markdown: IMarkdownString, withCodeBlo } const html = marked.parse(value, { async: false, renderer: withCodeBlocks ? plainTextWithCodeBlocksRenderer.value : plainTextRenderer.value }); - return sanitizeRenderedMarkdown({ isTrusted: false }, html).toString().replace(/&(#\d+|[a-zA-Z]+);/g, m => unescapeInfo.get(m) ?? m); + return sanitizeRenderedMarkdown({ isTrusted: false }, html) + .toString() + .replace(/&(#\d+|[a-zA-Z]+);/g, m => unescapeInfo.get(m) ?? m) + .trim(); } const unescapeInfo = new Map([ @@ -569,7 +589,7 @@ function createRenderer(): marked.Renderer { const renderer = new marked.Renderer(); renderer.code = ({ text }: marked.Tokens.Code): string => { - return text; + return escape(text); }; renderer.blockquote = ({ text }: marked.Tokens.Blockquote): string => { return text + '\n'; @@ -608,7 +628,7 @@ function createRenderer(): marked.Renderer { return text; }; renderer.codespan = ({ text }: marked.Tokens.Codespan): string => { - return text; + return escape(text); }; renderer.br = (_: marked.Tokens.Br): string => { return '\n'; @@ -627,11 +647,12 @@ function createRenderer(): marked.Renderer { }; return renderer; } -const plainTextRenderer = new Lazy((withCodeBlocks?: boolean) => createRenderer()); +const plainTextRenderer = new Lazy(createRenderer); + const plainTextWithCodeBlocksRenderer = new Lazy(() => { const renderer = createRenderer(); renderer.code = ({ text }: marked.Tokens.Code): string => { - return `\n\`\`\`\n${text}\n\`\`\`\n`; + return `\n\`\`\`\n${escape(text)}\n\`\`\`\n`; }; return renderer; }); diff --git a/src/vs/base/browser/touch.ts b/src/vs/base/browser/touch.ts index 05e815ddd47a2..ac526347c908f 100644 --- a/src/vs/base/browser/touch.ts +++ b/src/vs/base/browser/touch.ts @@ -5,7 +5,6 @@ import * as DomUtils from './dom.js'; import { mainWindow } from './window.js'; -import * as arrays from '../common/arrays.js'; import { memoize } from '../common/decorators.js'; import { Event as EventUtils } from '../common/event.js'; import { Disposable, IDisposable, markAsSingleton, toDisposable } from '../common/lifecycle.js'; @@ -192,28 +191,28 @@ export class Gesture extends Disposable { holdTime = Date.now() - data.initialTimeStamp; if (holdTime < Gesture.HOLD_DELAY - && Math.abs(data.initialPageX - arrays.tail(data.rollingPageX)!) < 30 - && Math.abs(data.initialPageY - arrays.tail(data.rollingPageY)!) < 30) { + && Math.abs(data.initialPageX - data.rollingPageX.at(-1)!) < 30 + && Math.abs(data.initialPageY - data.rollingPageY.at(-1)!) < 30) { const evt = this.newGestureEvent(EventType.Tap, data.initialTarget); - evt.pageX = arrays.tail(data.rollingPageX)!; - evt.pageY = arrays.tail(data.rollingPageY)!; + evt.pageX = data.rollingPageX.at(-1)!; + evt.pageY = data.rollingPageY.at(-1)!; this.dispatchEvent(evt); } else if (holdTime >= Gesture.HOLD_DELAY - && Math.abs(data.initialPageX - arrays.tail(data.rollingPageX)!) < 30 - && Math.abs(data.initialPageY - arrays.tail(data.rollingPageY)!) < 30) { + && Math.abs(data.initialPageX - data.rollingPageX.at(-1)!) < 30 + && Math.abs(data.initialPageY - data.rollingPageY.at(-1)!) < 30) { const evt = this.newGestureEvent(EventType.Contextmenu, data.initialTarget); - evt.pageX = arrays.tail(data.rollingPageX)!; - evt.pageY = arrays.tail(data.rollingPageY)!; + evt.pageX = data.rollingPageX.at(-1)!; + evt.pageY = data.rollingPageY.at(-1)!; this.dispatchEvent(evt); } else if (activeTouchCount === 1) { - const finalX = arrays.tail(data.rollingPageX)!; - const finalY = arrays.tail(data.rollingPageY)!; + const finalX = data.rollingPageX.at(-1)!; + const finalY = data.rollingPageY.at(-1)!; - const deltaT = arrays.tail(data.rollingTimestamps)! - data.rollingTimestamps[0]; + const deltaT = data.rollingTimestamps.at(-1)! - data.rollingTimestamps[0]; const deltaX = finalX - data.rollingPageX[0]; const deltaY = finalY - data.rollingPageY[0]; @@ -345,8 +344,8 @@ export class Gesture extends Disposable { const data = this.activeTouches[touch.identifier]; const evt = this.newGestureEvent(EventType.Change, data.initialTarget); - evt.translationX = touch.pageX - arrays.tail(data.rollingPageX)!; - evt.translationY = touch.pageY - arrays.tail(data.rollingPageY)!; + evt.translationX = touch.pageX - data.rollingPageX.at(-1)!; + evt.translationY = touch.pageY - data.rollingPageY.at(-1)!; evt.pageX = touch.pageX; evt.pageY = touch.pageY; this.dispatchEvent(evt); diff --git a/src/vs/base/browser/ui/actionbar/actionViewItems.ts b/src/vs/base/browser/ui/actionbar/actionViewItems.ts index dc565e10304e7..84987a24288a5 100644 --- a/src/vs/base/browser/ui/actionbar/actionViewItems.ts +++ b/src/vs/base/browser/ui/actionbar/actionViewItems.ts @@ -270,6 +270,7 @@ export interface IActionViewItemOptions extends IBaseActionViewItemOptions { icon?: boolean; label?: boolean; keybinding?: string | null; + keybindingNotRenderedWithLabel?: boolean; toggleStyles?: IToggleStyles; } @@ -300,7 +301,7 @@ export class ActionViewItem extends BaseActionViewItem { this.label = label; this.element.appendChild(label); - if (this.options.label && this.options.keybinding) { + if (this.options.label && this.options.keybinding && !this.options.keybindingNotRenderedWithLabel) { const kbLabel = document.createElement('span'); kbLabel.classList.add('keybinding'); kbLabel.textContent = this.options.keybinding; @@ -365,9 +366,8 @@ export class ActionViewItem extends BaseActionViewItem { if (this.action.tooltip) { title = this.action.tooltip; - } else if (!this.options.label && this.action.label && this.options.icon) { + } else if (this.action.label) { title = this.action.label; - if (this.options.keybinding) { title = nls.localize({ key: 'titleLabel', comment: ['action title', 'action keybinding'] }, "{0} ({1})", title, this.options.keybinding); } diff --git a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts index a1abd1bbc4d53..b87e883bd3a09 100644 --- a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts +++ b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from '../../dom.js'; +import * as domStylesheetsJs from '../../domStylesheets.js'; import { IMouseEvent } from '../../mouseEvent.js'; import { DomScrollableElement } from '../scrollbar/scrollableElement.js'; import { commonPrefixLength } from '../../../common/arrays.js'; @@ -83,7 +84,7 @@ export class BreadcrumbsWidget { this._disposables.add(dom.addStandardDisposableListener(this._domNode, 'click', e => this._onClick(e))); container.appendChild(this._scrollable.getDomNode()); - const styleElement = dom.createStyleSheet(this._domNode); + const styleElement = domStylesheetsJs.createStyleSheet(this._domNode); this._style(styleElement, styles); const focusTracker = dom.trackFocus(this._domNode); diff --git a/src/vs/base/browser/ui/countBadge/countBadge.ts b/src/vs/base/browser/ui/countBadge/countBadge.ts index 0413067adce19..d9f61bae90af5 100644 --- a/src/vs/base/browser/ui/countBadge/countBadge.ts +++ b/src/vs/base/browser/ui/countBadge/countBadge.ts @@ -6,6 +6,8 @@ import { $, append } from '../../dom.js'; import { format } from '../../../common/strings.js'; import './countBadge.css'; +import { Disposable, IDisposable, MutableDisposable, toDisposable } from '../../../common/lifecycle.js'; +import { getBaseLayerHoverDelegate } from '../hover/hoverDelegate2.js'; export interface ICountBadgeOptions { readonly count?: number; @@ -25,19 +27,23 @@ export const unthemedCountStyles: ICountBadgeStyles = { badgeBorder: undefined }; -export class CountBadge { +export class CountBadge extends Disposable { private element: HTMLElement; private count: number = 0; private countFormat: string; private titleFormat: string; + private readonly hover = this._register(new MutableDisposable()); constructor(container: HTMLElement, private readonly options: ICountBadgeOptions, private readonly styles: ICountBadgeStyles) { + super(); this.element = append(container, $('.monaco-count-badge')); + this._register(toDisposable(() => container.removeChild(this.element))); this.countFormat = this.options.countFormat || '{0}'; this.titleFormat = this.options.titleFormat || ''; this.setCount(this.options.count || 0); + this.updateHover(); } setCount(count: number) { @@ -52,12 +58,20 @@ export class CountBadge { setTitleFormat(titleFormat: string) { this.titleFormat = titleFormat; + this.updateHover(); this.render(); } + private updateHover(): void { + if (this.titleFormat !== '' && !this.hover.value) { + this.hover.value = getBaseLayerHoverDelegate().setupDelayedHoverAtMouse(this.element, () => ({ content: format(this.titleFormat, this.count), appearance: { compact: true } })); + } else if (this.titleFormat === '' && this.hover.value) { + this.hover.value = undefined; + } + } + private render() { this.element.textContent = format(this.countFormat, this.count); - this.element.title = format(this.titleFormat, this.count); this.element.style.backgroundColor = this.styles.badgeBackground ?? ''; this.element.style.color = this.styles.badgeForeground ?? ''; diff --git a/src/vs/base/browser/ui/findinput/findInput.ts b/src/vs/base/browser/ui/findinput/findInput.ts index 481c62b4c284c..7546053dd64dc 100644 --- a/src/vs/base/browser/ui/findinput/findInput.ts +++ b/src/vs/base/browser/ui/findinput/findInput.ts @@ -17,6 +17,7 @@ import './findInput.css'; import * as nls from '../../../../nls.js'; import { DisposableStore, MutableDisposable } from '../../../common/lifecycle.js'; import { createInstantHoverDelegate } from '../hover/hoverDelegateFactory.js'; +import { IHistory } from '../../../common/history.js'; export interface IFindInputOptions { @@ -32,11 +33,11 @@ export interface IFindInputOptions { readonly appendCaseSensitiveLabel?: string; readonly appendWholeWordsLabel?: string; readonly appendRegexLabel?: string; - readonly history?: string[]; readonly additionalToggles?: Toggle[]; readonly showHistoryHint?: () => boolean; readonly toggleStyles: IToggleStyles; readonly inputBoxStyles: IInputBoxStyles; + readonly history?: IHistory; } const NLS_DEFAULT_LABEL = nls.localize('defaultLabel', "input"); @@ -92,7 +93,6 @@ export class FindInput extends Widget { const appendCaseSensitiveLabel = options.appendCaseSensitiveLabel || ''; const appendWholeWordsLabel = options.appendWholeWordsLabel || ''; const appendRegexLabel = options.appendRegexLabel || ''; - const history = options.history || []; const flexibleHeight = !!options.flexibleHeight; const flexibleWidth = !!options.flexibleWidth; const flexibleMaxHeight = options.flexibleMaxHeight; @@ -106,12 +106,12 @@ export class FindInput extends Widget { validationOptions: { validation: this.validation }, - history, showHistoryHint: options.showHistoryHint, flexibleHeight, flexibleWidth, flexibleMaxHeight, inputBoxStyles: options.inputBoxStyles, + history: options.history })); const hoverDelegate = this._register(createInstantHoverDelegate()); diff --git a/src/vs/base/browser/ui/findinput/replaceInput.ts b/src/vs/base/browser/ui/findinput/replaceInput.ts index e608b5f29235a..22a81e211c929 100644 --- a/src/vs/base/browser/ui/findinput/replaceInput.ts +++ b/src/vs/base/browser/ui/findinput/replaceInput.ts @@ -108,7 +108,7 @@ export class ReplaceInput extends Widget { validationOptions: { validation: this.validation }, - history, + history: new Set(history), showHistoryHint: options.showHistoryHint, flexibleHeight, flexibleWidth, diff --git a/src/vs/base/browser/ui/hover/hover.ts b/src/vs/base/browser/ui/hover/hover.ts index c2500e19f5640..7f30cd1854e64 100644 --- a/src/vs/base/browser/ui/hover/hover.ts +++ b/src/vs/base/browser/ui/hover/hover.ts @@ -14,7 +14,13 @@ import type { IDisposable } from '../../../common/lifecycle.js'; */ export interface IHoverDelegate2 { /** - * Shows a hover, provided a hover with the same {@link options} object is not already visible. + * Shows a hover immediately, provided a hover with the same {@link options} object is not + * already visible. + * + * Use this method when you want to: + * + * - Control showing the hover yourself. + * - Show the hover immediately. * * @param options A set of options defining the characteristics of the hover. * @param focus Whether to focus the hover (useful for keyboard accessibility). @@ -33,6 +39,67 @@ export interface IHoverDelegate2 { focus?: boolean ): IHoverWidget | undefined; + /** + * Shows a hover after a delay, or immediately if the {@link groupId} matches the currently + * shown hover. + * + * Use this method when you want to: + * + * - Control showing the hover yourself. + * - Show the hover after the standard delay. + * + * @param options The options of the hover. + * @param groupId The group ID of the hover. If the group ID is the same as the currently shown + * hover, the hover will be shown immediately, skipping the delay. + */ + showDelayedHover( + options: IHoverOptions, + lifecycleOptions: Pick, + ): IHoverWidget | undefined; + + /** + * A simple wrapper around showDelayedHover that includes listening to events on the + * {@link target} element that shows the hover. + * + * Use this method when you want to: + * + * - Let the hover service handle showing the hover. + * - Show the hover after the standard delay. + * - Want the hover positioned beside the {@link target} element. + * + * @param target The target element to listener for mouseover events on. + * @param hoverOptions The options of the hover. + * @param lifecycleOptions The options of the hover's lifecycle. + */ + setupDelayedHover( + target: HTMLElement, + hoverOptions: (() => IDelayedHoverOptions) | IDelayedHoverOptions, + lifecycleOptions?: IHoverLifecycleOptions, + ): IDisposable; + + /** + * A simple wrapper around showDelayedHover that includes listening to events on the + * {@link target} element that shows the hover. This differs from {@link setupDelayedHover} in + * that the hover will be shown at the mouse position instead of the + * {@link target target} element's position, ignoring any + * {@link IHoverOptions.position position options} that are passed in. + * + * Use this method when you want to: + * + * - Let the hover service handle showing the hover. + * - Show the hover after the standard delay. + * - Want the hover positioned beside the mouse. + * + * @param target The target element to listener for mouseover events on. + * @param hoverOptions The options of the hover. + * @param lifecycleOptions The options of the hover's lifecycle. + */ + setupDelayedHoverAtMouse( + target: HTMLElement, + hoverOptions: (() => IDelayedHoverAtMouseOptions) | IDelayedHoverAtMouseOptions, + lifecycleOptions?: IHoverLifecycleOptions, + ): IDisposable; + /** * Hides the hover if it was visible. This call will be ignored if the the hover is currently * "locked" via the alt/option key. @@ -57,17 +124,19 @@ export interface IHoverDelegate2 { * @param targetElement The target element to show the hover for. * @param content The content of the hover or a factory that creates it at the time it's shown. * @param options Additional options for the managed hover. + * + * @deprecated Use {@link setupDelayedHover} or {@link setupDelayedHoverAtMouse} instead where + * possible. */ - // TODO: The hoverDelegate parameter should be removed in favor of just a set of options. This - // will avoid confusion around IHoverDelegate/IHoverDelegate2 as well as align more with - // the design of the hover service. - // TODO: Align prototype closer to showHover, deriving options from IHoverOptions if possible. setupManagedHover(hoverDelegate: IHoverDelegate, targetElement: HTMLElement, content: IManagedHoverContentOrFactory, options?: IManagedHoverOptions): IManagedHover; /** * Shows the hover for the given element if one has been setup. * * @param targetElement The target element of the hover, as set up in {@link setupManagedHover}. + * + * @deprecated Use {@link setupDelayedHover} or {@link setupDelayedHoverAtMouse} instead where + * possible. */ showManagedHover(targetElement: HTMLElement): void; } @@ -106,8 +175,11 @@ export interface IHoverOptions { * An ID to associate with the hover to be used as an equality check. Normally when calling * {@link IHoverService.showHover} the options object itself is used to determine if the hover * is the same one that is already showing, when this is set, the ID will be used instead. + * + * When `undefined`, this will default to a serialized version of {@link content}. In this case + * it will remain `undefined` if {@link content} is a `HTMLElement`. */ - id?: number | string; + id?: string; /** * A set of actions for the hover's "status bar". @@ -149,13 +221,53 @@ export interface IHoverOptions { appearance?: IHoverAppearanceOptions; } +// `target` is ignored for delayed hover methods as it's included in the method and added +// automatically when the hover options get resolved. +export type IDelayedHoverOptions = Omit; + +// `position` is ignored for delayed at mouse hover methods as it's overwritten by the mouse event. +// `showPointer` is always false when using mouse positioning +export type IDelayedHoverAtMouseOptions = Omit & { appearance?: Omit }; + +export interface IHoverLifecycleOptions { + /** + * The group ID of the hover. If the group ID is the same as the currently shown hover, the + * hover will be shown immediately, skipping the delay. + * + * @example Use a UUID to set a unique `groupId` for related hovers + * + * ```typescript + * const groupId = generateUuid(); + * showDelayedHover({ content: 'Button 1', target: someElement1 }, { groupId }); + * showDelayedHover({ content: 'Button 2', target: someElement2 }, { groupId }); + * ``` + * + * @example Use a feature-specific string to set a unqiue `groupId` for related hovers + * + * ```typescript + * showDelayedHover({ content: 'Button 1', target: someElement1 }, { groupId: 'my-feature-items' }); + * showDelayedHover({ content: 'Button 2', target: someElement2 }, { groupId: 'my-feature-items' }); + * ``` + */ + groupId?: string; + + /** + * Whether to set up space and enter keyboard events for the hover, when these are pressed when + * the hover's target is focused it will show and focus the hover. + * + * Typically this should _not_ be used when the space or enter events are already handled by + * something else. + */ + setupKeyboardEvents?: boolean; +} + export interface IHoverPositionOptions { /** * Position of the hover. The default is to show above the target. This option will be ignored * if there is not enough room to layout the hover in the specified position, unless the * forcePosition option is set. */ - hoverPosition?: HoverPosition; + hoverPosition?: HoverPosition | MouseEvent; /** * Force the hover position, reducing the size of the hover instead of adjusting the hover @@ -242,7 +354,7 @@ export interface IHoverAction { /** * A target for a hover. */ -export interface IHoverTarget extends IDisposable { +export interface IHoverTarget extends Partial { /** * A set of target elements used to position the hover. If multiple elements are used the hover * will try to not overlap any target element. An example use case for this is show a hover for diff --git a/src/vs/base/browser/ui/hover/hoverDelegate2.ts b/src/vs/base/browser/ui/hover/hoverDelegate2.ts index 1d87b1721e43f..05b4c692d940d 100644 --- a/src/vs/base/browser/ui/hover/hoverDelegate2.ts +++ b/src/vs/base/browser/ui/hover/hoverDelegate2.ts @@ -3,10 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { Disposable } from '../../../common/lifecycle.js'; import type { IHoverDelegate2 } from './hover.js'; let baseHoverDelegate: IHoverDelegate2 = { showHover: () => undefined, + showDelayedHover: () => undefined, + setupDelayedHover: () => Disposable.None, + setupDelayedHoverAtMouse: () => Disposable.None, hideHover: () => undefined, showAndFocusLastHover: () => undefined, setupManagedHover: () => null!, diff --git a/src/vs/base/browser/ui/hover/hoverWidget.css b/src/vs/base/browser/ui/hover/hoverWidget.css index d69122654e579..e6b088e330139 100644 --- a/src/vs/base/browser/ui/hover/hoverWidget.css +++ b/src/vs/base/browser/ui/hover/hoverWidget.css @@ -10,11 +10,14 @@ user-select: text; -webkit-user-select: text; box-sizing: border-box; - animation: fadein 100ms linear; line-height: 1.5em; white-space: var(--vscode-hover-whiteSpace, normal); } +.monaco-hover.fade-in { + animation: fadein 100ms linear; +} + .monaco-hover.hidden { display: none; } @@ -128,6 +131,9 @@ .monaco-hover .hover-row.status-bar .actions .action-container { margin-right: 16px; cursor: pointer; + overflow: hidden; + text-wrap: nowrap; + text-overflow: ellipsis; } .monaco-hover .hover-row.status-bar .actions .action-container .action .icon { diff --git a/src/vs/base/browser/ui/hover/hoverWidget.ts b/src/vs/base/browser/ui/hover/hoverWidget.ts index 638af90bd8ea9..c2f976a8ce8e3 100644 --- a/src/vs/base/browser/ui/hover/hoverWidget.ts +++ b/src/vs/base/browser/ui/hover/hoverWidget.ts @@ -14,7 +14,10 @@ import { localize } from '../../../../nls.js'; const $ = dom.$; export const enum HoverPosition { - LEFT, RIGHT, BELOW, ABOVE + LEFT, + RIGHT, + BELOW, + ABOVE, } export class HoverWidget extends Disposable { @@ -23,11 +26,12 @@ export class HoverWidget extends Disposable { public readonly contentsDomNode: HTMLElement; public readonly scrollbar: DomScrollableElement; - constructor() { + constructor(fadeIn: boolean) { super(); this.containerDomNode = document.createElement('div'); this.containerDomNode.className = 'monaco-hover'; + this.containerDomNode.classList.toggle('fade-in', !!fadeIn); this.containerDomNode.tabIndex = 0; this.containerDomNode.setAttribute('role', 'tooltip'); @@ -53,7 +57,9 @@ export class HoverAction extends Disposable { public readonly actionLabel: string; public readonly actionKeybindingLabel: string | null; - private readonly actionContainer: HTMLElement; + public readonly actionRenderedLabel: string; + public readonly actionContainer: HTMLElement; + private readonly action: HTMLElement; private constructor(parent: HTMLElement, actionOptions: { label: string; iconClass?: string; run: (target: HTMLElement) => void; commandId: string }, keybindingLabel: string | null) { @@ -70,8 +76,9 @@ export class HoverAction extends Disposable { if (actionOptions.iconClass) { dom.append(this.action, $(`span.icon.${actionOptions.iconClass}`)); } + this.actionRenderedLabel = keybindingLabel ? `${actionOptions.label} (${keybindingLabel})` : actionOptions.label; const label = dom.append(this.action, $('span')); - label.textContent = keybindingLabel ? `${actionOptions.label} (${keybindingLabel})` : actionOptions.label; + label.textContent = this.actionRenderedLabel; this._store.add(new ClickAction(this.actionContainer, actionOptions.run)); this._store.add(new KeyDownAction(this.actionContainer, actionOptions.run, [KeyCode.Enter, KeyCode.Space])); diff --git a/src/vs/base/browser/ui/iconLabel/iconLabel.ts b/src/vs/base/browser/ui/iconLabel/iconLabel.ts index f9804179a896b..981933151ccbb 100644 --- a/src/vs/base/browser/ui/iconLabel/iconLabel.ts +++ b/src/vs/base/browser/ui/iconLabel/iconLabel.ts @@ -24,7 +24,7 @@ export interface IIconLabelCreationOptions { readonly supportDescriptionHighlights?: boolean; readonly supportIcons?: boolean; readonly hoverDelegate?: IHoverDelegate; - readonly hoverTargetOverrride?: HTMLElement; + readonly hoverTargetOverride?: HTMLElement; } export interface IIconLabelValueOptions { @@ -211,11 +211,11 @@ export class IconLabel extends Disposable { } let hoverTarget = htmlElement; - if (this.creationOptions?.hoverTargetOverrride) { - if (!dom.isAncestor(htmlElement, this.creationOptions.hoverTargetOverrride)) { + if (this.creationOptions?.hoverTargetOverride) { + if (!dom.isAncestor(htmlElement, this.creationOptions.hoverTargetOverride)) { throw new Error('hoverTargetOverrride must be an ancestor of the htmlElement'); } - hoverTarget = this.creationOptions.hoverTargetOverrride; + hoverTarget = this.creationOptions.hoverTargetOverride; } if (this.hoverDelegate.showNativeHover) { diff --git a/src/vs/base/browser/ui/inputbox/inputBox.ts b/src/vs/base/browser/ui/inputbox/inputBox.ts index 3e0eedcef9d1d..2f42792326967 100644 --- a/src/vs/base/browser/ui/inputbox/inputBox.ts +++ b/src/vs/base/browser/ui/inputbox/inputBox.ts @@ -12,18 +12,17 @@ import { MarkdownRenderOptions } from '../../markdownRenderer.js'; import { ActionBar } from '../actionbar/actionbar.js'; import * as aria from '../aria/aria.js'; import { AnchorAlignment, IContextViewProvider } from '../contextview/contextview.js'; -import type { IManagedHover } from '../hover/hover.js'; import { getBaseLayerHoverDelegate } from '../hover/hoverDelegate2.js'; -import { getDefaultHoverDelegate } from '../hover/hoverDelegateFactory.js'; import { ScrollableElement } from '../scrollbar/scrollableElement.js'; import { Widget } from '../widget.js'; import { IAction } from '../../../common/actions.js'; import { Emitter, Event } from '../../../common/event.js'; -import { HistoryNavigator } from '../../../common/history.js'; +import { HistoryNavigator, IHistory } from '../../../common/history.js'; import { equals } from '../../../common/objects.js'; import { ScrollbarVisibility } from '../../../common/scrollable.js'; import './inputBox.css'; import * as nls from '../../../../nls.js'; +import { MutableDisposable, type IDisposable } from '../../../common/lifecycle.js'; const $ = dom.$; @@ -40,6 +39,7 @@ export interface IInputOptions { readonly flexibleMaxHeight?: number; readonly actions?: ReadonlyArray; readonly inputBoxStyles: IInputBoxStyles; + readonly history?: IHistory; } export interface IInputBoxStyles { @@ -115,7 +115,7 @@ export class InputBox extends Widget { private cachedContentHeight: number | undefined; private maxHeight: number = Number.POSITIVE_INFINITY; private scrollableElement: ScrollableElement | undefined; - private hover: IManagedHover | undefined; + private readonly hover: MutableDisposable = this._register(new MutableDisposable()); private _onDidChange = this._register(new Emitter()); public readonly onDidChange: Event = this._onDidChange.event; @@ -235,10 +235,13 @@ export class InputBox extends Widget { public setTooltip(tooltip: string): void { this.tooltip = tooltip; - if (!this.hover) { - this.hover = this._register(getBaseLayerHoverDelegate().setupManagedHover(getDefaultHoverDelegate('mouse'), this.input, tooltip)); - } else { - this.hover.update(tooltip); + if (!this.hover.value) { + this.hover.value = this._register(getBaseLayerHoverDelegate().setupDelayedHoverAtMouse(this.input, () => ({ + content: tooltip, + appearance: { + compact: true, + } + }))); } } @@ -622,7 +625,6 @@ export class InputBox extends Widget { } export interface IHistoryInputOptions extends IInputOptions { - history: string[]; readonly showHistoryHint?: () => boolean; } diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index 2f4826648b8ae..ae9d5a46fb47e 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -4,7 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { IDragAndDropData } from '../../dnd.js'; -import { createStyleSheet, Dimension, EventHelper, getActiveElement, getWindow, isActiveElement, isEditableElement, isHTMLElement, isMouseEvent } from '../../dom.js'; +import { Dimension, EventHelper, getActiveElement, getWindow, isActiveElement, isEditableElement, isHTMLElement, isMouseEvent } from '../../dom.js'; +import { createStyleSheet } from '../../domStylesheets.js'; import { asCssValueWithDefault } from '../../cssValue.js'; import { DomEmitter } from '../../event.js'; import { IKeyboardEvent, StandardKeyboardEvent } from '../../keyboardEvent.js'; diff --git a/src/vs/base/browser/ui/menu/menu.ts b/src/vs/base/browser/ui/menu/menu.ts index d05498fb93a2e..eefff7f8b4849 100644 --- a/src/vs/base/browser/ui/menu/menu.ts +++ b/src/vs/base/browser/ui/menu/menu.ts @@ -5,7 +5,8 @@ import { isFirefox } from '../../browser.js'; import { EventType as TouchEventType, Gesture } from '../../touch.js'; -import { $, addDisposableListener, append, clearNode, createStyleSheet, Dimension, EventHelper, EventLike, EventType, getActiveElement, getWindow, IDomNodePagePosition, isAncestor, isInShadowDOM } from '../../dom.js'; +import { $, addDisposableListener, append, clearNode, Dimension, EventHelper, EventLike, EventType, getActiveElement, getWindow, IDomNodePagePosition, isAncestor, isInShadowDOM } from '../../dom.js'; +import { createStyleSheet } from '../../domStylesheets.js'; import { StandardKeyboardEvent } from '../../keyboardEvent.js'; import { StandardMouseEvent } from '../../mouseEvent.js'; import { ActionBar, ActionsOrientation, IActionViewItemProvider } from '../actionbar/actionbar.js'; diff --git a/src/vs/base/browser/ui/menu/menubar.ts b/src/vs/base/browser/ui/menu/menubar.ts index 3acf246b40c8d..aa1a58b9c5e9f 100644 --- a/src/vs/base/browser/ui/menu/menubar.ts +++ b/src/vs/base/browser/ui/menu/menubar.ts @@ -741,6 +741,12 @@ export class MenuBar extends Disposable { } if (this.focusedMenu) { + // When the menu is toggled on, it may be in compact state and trying to + // focus the first menu. In this case we should focus the overflow instead. + if (this.focusedMenu.index === 0 && this.numMenusShown === 0) { + this.focusedMenu.index = MenuBar.OVERFLOW_INDEX; + } + if (this.focusedMenu.index === MenuBar.OVERFLOW_INDEX) { this.overflowMenu.buttonElement.focus(); } else { diff --git a/src/vs/base/browser/ui/sash/sash.ts b/src/vs/base/browser/ui/sash/sash.ts index 8596d0cd8d998..8f51c7068c460 100644 --- a/src/vs/base/browser/ui/sash/sash.ts +++ b/src/vs/base/browser/ui/sash/sash.ts @@ -3,7 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { $, append, createStyleSheet, EventHelper, EventLike, getWindow, isHTMLElement } from '../../dom.js'; +import { $, append, EventHelper, EventLike, getWindow, isHTMLElement } from '../../dom.js'; +import { createStyleSheet } from '../../domStylesheets.js'; import { DomEmitter } from '../../event.js'; import { EventType, Gesture } from '../../touch.js'; import { Delayer } from '../../../common/async.js'; diff --git a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts index 24341bab33c52..9ccbafa594e08 100644 --- a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts +++ b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from '../../dom.js'; +import * as domStylesheetsJs from '../../domStylesheets.js'; import * as cssJs from '../../cssValue.js'; import { DomEmitter } from '../../event.js'; import { IContentActionHandler } from '../../formattedTextRenderer.js'; @@ -192,7 +193,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi this._dropDownPosition = AnchorPosition.BELOW; // Inline stylesheet for themes - this.styleElement = dom.createStyleSheet(this.selectDropDownContainer); + this.styleElement = domStylesheetsJs.createStyleSheet(this.selectDropDownContainer); // Prevent dragging of dropdown #114329 this.selectDropDownContainer.setAttribute('draggable', 'true'); diff --git a/src/vs/base/browser/ui/table/tableWidget.ts b/src/vs/base/browser/ui/table/tableWidget.ts index 3e2142482f05b..f352a926d1320 100644 --- a/src/vs/base/browser/ui/table/tableWidget.ts +++ b/src/vs/base/browser/ui/table/tableWidget.ts @@ -3,7 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { $, append, clearNode, createStyleSheet, getContentHeight, getContentWidth } from '../../dom.js'; +import { $, append, clearNode, getContentHeight, getContentWidth } from '../../dom.js'; +import { createStyleSheet } from '../../domStylesheets.js'; import { getBaseLayerHoverDelegate } from '../hover/hoverDelegate2.js'; import { getDefaultHoverDelegate } from '../hover/hoverDelegateFactory.js'; import { IListRenderer, IListVirtualDelegate } from '../list/list.js'; diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index d420f499b8b75..be65784fd80b4 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -4,7 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { IDragAndDropData } from '../../dnd.js'; -import { $, append, clearNode, createStyleSheet, getWindow, h, hasParentWithClass, isActiveElement, isKeyboardEvent, addDisposableListener, isEditableElement } from '../../dom.js'; +import { $, append, clearNode, h, hasParentWithClass, isActiveElement, isKeyboardEvent, addDisposableListener, isEditableElement } from '../../dom.js'; +import { createStyleSheet } from '../../domStylesheets.js'; import { asCssValueWithDefault } from '../../cssValue.js'; import { DomEmitter } from '../../event.js'; import { StandardKeyboardEvent } from '../../keyboardEvent.js'; @@ -30,7 +31,6 @@ import { KeyCode } from '../../../common/keyCodes.js'; import { Disposable, DisposableStore, dispose, IDisposable, toDisposable } from '../../../common/lifecycle.js'; import { clamp } from '../../../common/numbers.js'; import { ScrollEvent } from '../../../common/scrollable.js'; -import { isNumber } from '../../../common/types.js'; import './media/tree.css'; import { localize } from '../../../../nls.js'; import { IHoverDelegate } from '../hover/hoverDelegate.js'; @@ -156,7 +156,7 @@ class TreeNodeListDragAndDrop implements IListDragAndDrop< } } -function asListOptions(modelProvider: () => ITreeModel, options?: IAbstractTreeOptions): IListOptions> | undefined { +function asListOptions(modelProvider: () => ITreeModel, disposableStore: DisposableStore, options?: IAbstractTreeOptions): IListOptions> | undefined { return options && { ...options, identityProvider: options.identityProvider && { @@ -164,7 +164,7 @@ function asListOptions(modelProvider: () => ITreeModel implements IListR private readonly disposables = new DisposableStore(); constructor( - private renderer: ITreeRenderer, - private model: ITreeModel, + private readonly renderer: ITreeRenderer, + private readonly model: ITreeModel, onDidChangeCollapseState: Event>, - private activeNodes: Collection>, - private renderedIndentGuides: SetMap, HTMLDivElement>, + private readonly activeNodes: Collection>, + private readonly renderedIndentGuides: SetMap, HTMLDivElement>, options: ITreeRendererOptions = {} ) { this.templateId = renderer.templateId; @@ -565,10 +565,6 @@ export class TreeRenderer implements IListR this.activeIndentNodes = set; } - setModel(model: ITreeModel): void { - this.model = model; - } - dispose(): void { this.renderedNodes.clear(); this.renderedElements.clear(); @@ -596,12 +592,20 @@ export interface IFindFilter extends ITreeFilter implements IFindFilter, IDisposable { +export class FindFilter implements IFindFilter, IDisposable { private _totalCount = 0; get totalCount(): number { return this._totalCount; } private _matchCount = 0; get matchCount(): number { return this._matchCount; } + private _findMatchType: TreeFindMatchType = TreeFindMatchType.Fuzzy; + set findMatchType(type: TreeFindMatchType) { this._findMatchType = type; } + get findMatchType(): TreeFindMatchType { return this._findMatchType; } + + private _findMode: TreeFindMode = TreeFindMode.Highlight; + set findMode(mode: TreeFindMode) { this._findMode = mode; } + get findMode(): TreeFindMode { return this._findMode; } + private _pattern: string = ''; private _lowercasePattern: string = ''; private readonly disposables = new DisposableStore(); @@ -612,12 +616,10 @@ class FindFilter implements IFindFilter, IDisposable { } constructor( - private tree: AbstractTree, - private keyboardNavigationLabelProvider: IKeyboardNavigationLabelProvider, - private _filter?: ITreeFilter - ) { - tree.onWillRefilter(this.reset, this, this.disposables); - } + private readonly _keyboardNavigationLabelProvider: IKeyboardNavigationLabelProvider, + private readonly _filter?: ITreeFilter, + private readonly _defaultFindVisibility?: TreeVisibility | ((node: T) => TreeVisibility), + ) { } filter(element: T, parentVisibility: TreeVisibility): TreeFilterResult { let visibility = TreeVisibility.Visible; @@ -645,7 +647,7 @@ class FindFilter implements IFindFilter, IDisposable { return { data: FuzzyScore.Default, visibility }; } - const label = this.keyboardNavigationLabelProvider.getKeyboardNavigationLabel(element); + const label = this._keyboardNavigationLabelProvider.getKeyboardNavigationLabel(element); const labels = Array.isArray(label) ? label : [label]; for (const l of labels) { @@ -655,7 +657,7 @@ class FindFilter implements IFindFilter, IDisposable { } let score: FuzzyScore | undefined; - if (this.tree.findMatchType === TreeFindMatchType.Contiguous) { + if (this._findMatchType === TreeFindMatchType.Contiguous) { score = contiguousFuzzyScore(this._lowercasePattern, labelStr.toLowerCase()); } else { score = fuzzyScore(this._pattern, this._lowercasePattern, 0, labelStr, labelStr.toLowerCase(), 0, { firstMatchCanBeWeak: true, boostFullMatch: true }); @@ -668,11 +670,11 @@ class FindFilter implements IFindFilter, IDisposable { } } - if (this.tree.findMode === TreeFindMode.Filter) { - if (typeof this.tree.options.defaultFindVisibility === 'number') { - return this.tree.options.defaultFindVisibility; - } else if (this.tree.options.defaultFindVisibility) { - return this.tree.options.defaultFindVisibility(element); + if (this._findMode === TreeFindMode.Filter) { + if (typeof this._defaultFindVisibility === 'number') { + return this._defaultFindVisibility; + } else if (this._defaultFindVisibility) { + return this._defaultFindVisibility(element); } else { return TreeVisibility.Recurse; } @@ -681,7 +683,7 @@ class FindFilter implements IFindFilter, IDisposable { } } - private reset(): void { + reset(): void { this._totalCount = 0; this._matchCount = 0; } @@ -790,7 +792,6 @@ export enum TreeFindMatchType { class FindWidget extends Disposable { private readonly elements = h('.monaco-tree-type-filter', [ - h('.monaco-tree-type-filter-grab.codicon.codicon-debug-gripper@grab', { tabIndex: 0 }), h('.monaco-tree-type-filter-input@findInput'), h('.monaco-tree-type-filter-actionbar@actionbar'), ]); @@ -806,9 +807,6 @@ class FindWidget extends Disposable { private readonly findInput: FindInput; private readonly actionbar: ActionBar; private readonly toggles: TreeFindToggle[] = []; - private width = 0; - private right = 0; - private top = 0; readonly _onDidDisable = new Emitter(); readonly onDidDisable = this._onDidDisable.event; @@ -842,6 +840,7 @@ class FindWidget extends Disposable { this.toggles = toggleContributions.map(contribution => this._register(new TreeFindToggle(contribution, styles.toggleStyles, toggleHoverDelegate))); this.onDidToggleChange = Event.any(...this.toggles.map(toggle => Event.map(toggle.onChange, () => ({ id: toggle.id, isChecked: toggle.checked })))); + const history = options?.history || []; this.findInput = this._register(new FindInput(this.elements.findInput, contextViewProvider, { label: localize('type to search', "Type to search"), placeholder, @@ -849,7 +848,7 @@ class FindWidget extends Disposable { showCommonFindToggles: false, inputBoxStyles: styles.inputBoxStyles, toggleStyles: styles.toggleStyles, - history: options?.history + history: new Set(history) })); this.actionbar = this._register(new ActionBar(this.elements.actionbar)); @@ -892,79 +891,6 @@ class FindWidget extends Disposable { const closeAction = this._register(new Action('close', localize('close', "Close"), 'codicon codicon-close', true, () => this.dispose())); this.actionbar.push(closeAction, { icon: true, label: false }); - const onGrabMouseDown = this._register(new DomEmitter(this.elements.grab, 'mousedown')); - - this._register(onGrabMouseDown.event(e => { - const disposables = new DisposableStore(); - const onWindowMouseMove = disposables.add(new DomEmitter(getWindow(e), 'mousemove')); - const onWindowMouseUp = disposables.add(new DomEmitter(getWindow(e), 'mouseup')); - - const startRight = this.right; - const startX = e.pageX; - const startTop = this.top; - const startY = e.pageY; - this.elements.grab.classList.add('grabbing'); - - const transition = this.elements.root.style.transition; - this.elements.root.style.transition = 'unset'; - - const update = (e: MouseEvent) => { - const deltaX = e.pageX - startX; - this.right = startRight - deltaX; - const deltaY = e.pageY - startY; - this.top = startTop + deltaY; - this.layout(); - }; - - disposables.add(onWindowMouseMove.event(update)); - disposables.add(onWindowMouseUp.event(e => { - update(e); - this.elements.grab.classList.remove('grabbing'); - this.elements.root.style.transition = transition; - disposables.dispose(); - })); - })); - - const onGrabKeyDown = Event.chain(this._register(new DomEmitter(this.elements.grab, 'keydown')).event, $ => $.map(e => new StandardKeyboardEvent(e))); - - this._register(onGrabKeyDown((e) => { - let right: number | undefined; - let top: number | undefined; - - if (e.keyCode === KeyCode.LeftArrow) { - right = Number.POSITIVE_INFINITY; - } else if (e.keyCode === KeyCode.RightArrow) { - right = 0; - } else if (e.keyCode === KeyCode.Space) { - right = this.right === 0 ? Number.POSITIVE_INFINITY : 0; - } - - if (e.keyCode === KeyCode.UpArrow) { - top = 0; - } else if (e.keyCode === KeyCode.DownArrow) { - top = Number.POSITIVE_INFINITY; - } - - if (right !== undefined) { - e.preventDefault(); - e.stopPropagation(); - this.right = right; - this.layout(); - } - - if (top !== undefined) { - e.preventDefault(); - e.stopPropagation(); - this.top = top; - const transition = this.elements.root.style.transition; - this.elements.root.style.transition = 'unset'; - this.layout(); - setTimeout(() => { - this.elements.root.style.transition = transition; - }, 0); - } - })); - this.onDidChangeValue = this.findInput.onDidChange; } @@ -994,14 +920,6 @@ class FindWidget extends Disposable { this.findInput.inputBox.addToHistory(true); } - layout(width: number = this.width): void { - this.width = width; - this.right = clamp(this.right, 0, Math.max(0, width - 212)); - this.elements.root.style.right = `${this.right}px`; - this.top = clamp(this.top, 0, 24); - this.elements.root.style.top = `${this.top}px`; - } - showMessage(message: IMessage): void { this.findInput.showMessage(message); } @@ -1052,7 +970,6 @@ export abstract class AbstractFindController implements IDisposa } private widget: FindWidget | undefined; - private width = 0; private readonly _onDidChangePattern = new Emitter(); readonly onDidChangePattern = this._onDidChangePattern.event; @@ -1084,6 +1001,8 @@ export abstract class AbstractFindController implements IDisposa return; } + this.tree.updateOptions({ paddingTop: 30 }); + this.widget = new FindWidget(this.tree.getHTMLElement(), this.tree, this.contextViewProvider, this.placeholder, this.toggles.states(), { ...this.options, history: this._history }); this.enabledDisposables.add(this.widget); @@ -1091,7 +1010,6 @@ export abstract class AbstractFindController implements IDisposa this.widget.onDidDisable(this.close, this, this.enabledDisposables); this.widget.onDidToggleChange(this.onDidToggleChange, this, this.enabledDisposables); - this.widget.layout(this.width); this.widget.focus(); this.widget.value = this.previousPattern; @@ -1105,6 +1023,8 @@ export abstract class AbstractFindController implements IDisposa return; } + this.tree.updateOptions({ paddingTop: 0 }); + this._history = this.widget.getHistory(); this.widget = undefined; @@ -1136,10 +1056,10 @@ export abstract class AbstractFindController implements IDisposa this.widget?.setToggleState(id, checked); } - protected renderMessage(showNotFound: boolean): void { + protected renderMessage(showNotFound: boolean, warningMessage?: string): void { if (showNotFound) { if (this.tree.options.showNotFoundMessage ?? true) { - this.widget?.showMessage({ type: MessageType.WARNING, content: localize('not found', "No results found.") }); + this.widget?.showMessage({ type: MessageType.WARNING, content: warningMessage ?? localize('not found', "No results found.") }); } else { this.widget?.showMessage({ type: MessageType.WARNING }); } @@ -1156,11 +1076,6 @@ export abstract class AbstractFindController implements IDisposa } } - layout(width: number): void { - this.width = width; - this.widget?.layout(width); - } - dispose() { this._history = undefined; this._onDidChangePattern.dispose(); @@ -1169,7 +1084,7 @@ export abstract class AbstractFindController implements IDisposa } } -class FindController extends AbstractFindController { +export class FindController extends AbstractFindController { get mode(): TreeFindMode { return this.toggles.get(DefaultTreeToggles.Mode) ? TreeFindMode.Filter : TreeFindMode.Highlight; } set mode(mode: TreeFindMode) { @@ -1181,6 +1096,7 @@ class FindController extends AbstractFindController extends AbstractFindController extends AbstractFindController { @@ -1239,6 +1159,8 @@ class FindController extends AbstractFindController this.filter.reset())); } updateOptions(optionsUpdate: IAbstractTreeOptionsUpdate = {}): void { @@ -1291,7 +1213,7 @@ class FindController extends AbstractFindController 0; const showNotFound = noMatches && this.pattern.length > 0; @@ -1400,9 +1322,11 @@ class StickyScrollController extends Disposable { private readonly _widget: StickyScrollWidget; + private paddingTop: number; + constructor( private readonly tree: AbstractTree, - private model: ITreeModel, + private readonly model: ITreeModel, private readonly view: List>, renderers: TreeRenderer[], private readonly treeDelegate: IListVirtualDelegate>, @@ -1414,6 +1338,7 @@ class StickyScrollController extends Disposable { this.stickyScrollMaxItemCount = stickyScrollOptions.stickyScrollMaxItemCount; this.stickyScrollDelegate = options.stickyScrollDelegate ?? new DefaultStickyScrollDelegate(); + this.paddingTop = options.paddingTop ?? 0; this._widget = this._register(new StickyScrollWidget(view.getScrollableElement(), view, tree, renderers, treeDelegate, options.accessibilityProvider)); this.onDidChangeHasFocus = this._widget.onDidChangeHasFocus; @@ -1477,10 +1402,10 @@ class StickyScrollController extends Disposable { } private update() { - const firstVisibleNode = this.getNodeAtHeight(0); + const firstVisibleNode = this.getNodeAtHeight(this.paddingTop); // Don't render anything if there are no elements - if (!firstVisibleNode || this.tree.scrollTop === 0) { + if (!firstVisibleNode || this.tree.scrollTop <= this.paddingTop) { this._widget.setState(undefined); return; } @@ -1687,14 +1612,16 @@ class StickyScrollController extends Disposable { } updateOptions(optionsUpdate: IAbstractTreeOptionsUpdate = {}): void { - if (!optionsUpdate.stickyScrollMaxItemCount) { - return; + if (optionsUpdate.paddingTop !== undefined) { + this.paddingTop = optionsUpdate.paddingTop; } - const validatedOptions = this.validateStickySettings(optionsUpdate); - if (this.stickyScrollMaxItemCount !== validatedOptions.stickyScrollMaxItemCount) { - this.stickyScrollMaxItemCount = validatedOptions.stickyScrollMaxItemCount; - this.update(); + if (optionsUpdate.stickyScrollMaxItemCount !== undefined) { + const validatedOptions = this.validateStickySettings(optionsUpdate); + if (this.stickyScrollMaxItemCount !== validatedOptions.stickyScrollMaxItemCount) { + this.stickyScrollMaxItemCount = validatedOptions.stickyScrollMaxItemCount; + this.update(); + } } } @@ -1705,10 +1632,6 @@ class StickyScrollController extends Disposable { } return { stickyScrollMaxItemCount }; } - - setModel(model: ITreeModel): void { - this.model = model; - } } class StickyScrollWidget implements IDisposable { @@ -2258,6 +2181,7 @@ export interface IAbstractTreeOptionsUpdate extends ITreeRendererOptions { readonly expandOnlyOnTwistieClick?: boolean | ((e: any) => boolean); // e is T readonly enableStickyScroll?: boolean; readonly stickyScrollMaxItemCount?: number; + readonly paddingTop?: number; } export interface IAbstractTreeOptions extends IAbstractTreeOptionsUpdate, IListOptions { @@ -2701,8 +2625,8 @@ export abstract class AbstractTree implements IDisposable renderers: ITreeRenderer[], private _options: IAbstractTreeOptions = {} ) { - if (_options.keyboardNavigationLabelProvider) { - this.findFilter = new FindFilter(this, _options.keyboardNavigationLabelProvider, _options.filter as any as ITreeFilter); + if (_options.keyboardNavigationLabelProvider && (_options.findWidgetEnabled ?? true)) { + this.findFilter = new FindFilter(_options.keyboardNavigationLabelProvider, _options.filter as ITreeFilter, _options.defaultFindVisibility); _options = { ..._options, filter: this.findFilter as ITreeFilter }; // TODO need typescript help here this.disposables.add(this.findFilter); } @@ -2720,7 +2644,7 @@ export abstract class AbstractTree implements IDisposable this.focus = new Trait(() => this.view.getFocusedElements()[0], _options.identityProvider); this.selection = new Trait(() => this.view.getSelectedElements()[0], _options.identityProvider); this.anchor = new Trait(() => this.view.getAnchorElement(), _options.identityProvider); - this.view = new TreeNodeList(_user, container, this.treeDelegate, this.renderers, this.focus, this.selection, this.anchor, { ...asListOptions(() => this.model, _options), tree: this, stickyScrollProvider: () => this.stickyScrollController }); + this.view = new TreeNodeList(_user, container, this.treeDelegate, this.renderers, this.focus, this.selection, this.anchor, { ...asListOptions(() => this.model, this.disposables, _options), tree: this, stickyScrollProvider: () => this.stickyScrollController }); this.setupModel(this.model); // model needs to be setup after the traits have been created @@ -2896,10 +2820,6 @@ export abstract class AbstractTree implements IDisposable layout(height?: number, width?: number): void { this.view.layout(height, width); - - if (isNumber(width)) { - this.findController?.layout(width); - } } style(styles: IListStyles): void { @@ -3251,10 +3171,6 @@ export abstract class AbstractTree implements IDisposable protected abstract createModel(user: string, options: IAbstractTreeOptions): ITreeModel; - createNewModel(options: IAbstractTreeOptions = {}): ITreeModel { - return this.createModel(this._user, { ...this._options, filter: this.findFilter as ITreeFilter | undefined, ...options }); - } - private readonly modelDisposables = new DisposableStore(); private setupModel(model: ITreeModel) { this.modelDisposables.clear(); @@ -3300,29 +3216,6 @@ export abstract class AbstractTree implements IDisposable this.onDidSpliceModelRelay.input = model.onDidSpliceModel; } - setModel(newModel: ITreeModel) { - const oldModel = this.model; - - this.model = newModel; - this.setupModel(newModel); - - this.renderers.forEach(r => r.setModel(newModel)); - this.stickyScrollController?.setModel(newModel); - - this.focus.set([]); - this.selection.set([]); - this.anchor.set([]); - - this.view.splice(0, oldModel.getListRenderCount(oldModel.rootRef)); - newModel.refilter(); - - this.onDidSwapModel.fire(); - } - - getModel(): ITreeModel { - return this.model; - } - navigate(start?: TRef): ITreeNavigator { return new TreeNavigator(this.view, this.model, start); } diff --git a/src/vs/base/browser/ui/tree/asyncDataTree.ts b/src/vs/base/browser/ui/tree/asyncDataTree.ts index 21523d1cc87ff..c99cf001b9a75 100644 --- a/src/vs/base/browser/ui/tree/asyncDataTree.ts +++ b/src/vs/base/browser/ui/tree/asyncDataTree.ts @@ -4,27 +4,28 @@ *--------------------------------------------------------------------------------------------*/ import { IDragAndDropData } from '../../dnd.js'; -import { IIdentityProvider, IListDragAndDrop, IListDragOverReaction, IListVirtualDelegate } from '../list/list.js'; +import { IIdentityProvider, IKeyboardNavigationLabelProvider, IListDragAndDrop, IListDragOverReaction, IListVirtualDelegate } from '../list/list.js'; import { ElementsDragAndDropData, ListViewTargetSector } from '../list/listView.js'; import { IListStyles } from '../list/listWidget.js'; -import { ComposedTreeDelegate, TreeFindMode as TreeFindMode, IAbstractTreeOptions, IAbstractTreeOptionsUpdate, TreeFindMatchType, AbstractTreePart, LabelFuzzyScore, IFindFilter, AbstractFindController, ITreeFindToggleContribution, ITreeFindToggleChangeEvent } from './abstractTree.js'; +import { ComposedTreeDelegate, TreeFindMode as TreeFindMode, IAbstractTreeOptions, IAbstractTreeOptionsUpdate, TreeFindMatchType, AbstractTreePart, LabelFuzzyScore, FindFilter, FindController, ITreeFindToggleChangeEvent } from './abstractTree.js'; import { ICompressedTreeElement, ICompressedTreeNode } from './compressedObjectTreeModel.js'; import { getVisibleState, isFilterResult } from './indexTreeModel.js'; import { CompressibleObjectTree, ICompressibleKeyboardNavigationLabelProvider, ICompressibleObjectTreeOptions, ICompressibleTreeRenderer, IObjectTreeOptions, IObjectTreeSetChildrenOptions, ObjectTree } from './objectTree.js'; -import { IAsyncDataSource, ICollapseStateChangeEvent, IObjectTreeElement, ITreeContextMenuEvent, ITreeDragAndDrop, ITreeEvent, ITreeFilter, ITreeModel, ITreeMouseEvent, ITreeNavigator, ITreeNode, ITreeRenderer, ITreeSorter, ObjectTreeElementCollapseState, TreeError, TreeFilterResult, TreeVisibility, WeakMapper } from './tree.js'; -import { CancelablePromise, createCancelablePromise, Promises, timeout, TimeoutTimer } from '../../../common/async.js'; +import { IAsyncDataSource, ICollapseStateChangeEvent, IObjectTreeElement, ITreeContextMenuEvent, ITreeDragAndDrop, ITreeEvent, ITreeFilter, ITreeMouseEvent, ITreeNavigator, ITreeNode, ITreeRenderer, ITreeSorter, ObjectTreeElementCollapseState, TreeError, TreeFilterResult, TreeVisibility, WeakMapper } from './tree.js'; +import { CancelablePromise, createCancelablePromise, Promises, ThrottledDelayer, timeout } from '../../../common/async.js'; import { Codicon } from '../../../common/codicons.js'; import { ThemeIcon } from '../../../common/themables.js'; import { isCancellationError, onUnexpectedError } from '../../../common/errors.js'; import { Emitter, Event } from '../../../common/event.js'; import { Iterable } from '../../../common/iterator.js'; -import { DisposableStore, dispose, IDisposable } from '../../../common/lifecycle.js'; +import { DisposableStore, dispose, IDisposable, toDisposable } from '../../../common/lifecycle.js'; import { ScrollEvent } from '../../../common/scrollable.js'; -import { isIterable, isNumber } from '../../../common/types.js'; +import { isIterable } from '../../../common/types.js'; import { CancellationToken, CancellationTokenSource } from '../../../common/cancellation.js'; -import { IObjectTreeModel } from './objectTreeModel.js'; import { IContextViewProvider } from '../contextview/contextview.js'; import { FuzzyScore } from '../../../common/filters.js'; +import { splice } from '../../../common/arrays.js'; +import { localize } from '../../../../nls.js'; interface IAsyncDataTreeNode { element: TInput | T; @@ -221,374 +222,168 @@ class AsyncDataTreeNodeListDragAndDrop implements IListDragAndDrop { - element: T; - filterdata?: FuzzyScore | LabelFuzzyScore; +export interface IAsyncFindToggles { + matchType: TreeFindMatchType; + findMode: TreeFindMode; } -export interface IAsyncFindProvider { - toggles?: ITreeFindToggleContribution[]; - placeholder?: string; - getFindResults(pattern: string, sessionId: number, token: CancellationToken, toggleStates: { id: string; isChecked: boolean }[]): AsyncIterable>; - revealResultInTree?(findElement: T): void; +export interface IAsyncFindResultMetadata { + warningMessage?: string; } -class AsyncFindTreeNode { +export interface IAsyncFindProvider { + /** + * `startSession` is called when the user enters the first character in the find widget. + * This can be used to allocate some state to preserve for the session. + */ + startSession?(): void; - private _children: AsyncFindTreeNode[] = []; - get children(): Iterable> { - return this._children.values(); - } + /** + * `find` is called when the user types one or more character into the find input. + */ + find(pattern: string, toggles: IAsyncFindToggles, token: CancellationToken): Promise; - constructor( - public element: T | undefined, - public id: string, - private readonly sorter: ITreeSorter | undefined, - ) { } - - addChild(child: AsyncFindTreeNode): void { - if (!this.sorter) { - this._children.push(child); - return; - } + /** + * `isVisible` is called to check if an element should be visible. + * For an element to be visible, all its ancestors must also be visible and the label must match the find pattern. + */ + isVisible?(element: T): boolean; - const index = this._children.findIndex(existingChild => this.sorter!.compare(child.element!, existingChild.element!) < 0); - if (index !== -1) { - this._children.splice(index, 0, child); - } else { - this._children.push(child); - } - } + /** + * End Session is called when the user either closes the find widget or has an empty find input. + * This can be used to deallocate any state that was allocated. + */ + endSession?(): Promise; } -class AsyncFindTree { - - private cachedNodes = new Map>(); - private readonly root = new AsyncFindTreeNode(undefined, '_AsyncFindTreeRoot_', this.sorter); - get rootNodes(): AsyncFindTreeNode[] { - return [...this.root.children]; - } - - private _totalLeafs: number = 0; - get totalLeafs(): number { - return this._totalLeafs; - } - - get totalNodes(): number { - return this.cachedNodes.size; - } +class AsyncFindFilter extends FindFilter { - private _results: IAsyncFindResult[] = []; - get results(): IAsyncFindResult[] { - return [...this._results]; - } + public isFindSessionActive = false; constructor( - private readonly dataSource: IAsyncDataSource, - private readonly identityProvider: IIdentityProvider, - private readonly sorter?: ITreeSorter, + public readonly findProvider: IAsyncFindProvider, // remove public + keyboardNavigationLabelProvider: IKeyboardNavigationLabelProvider, + filter: ITreeFilter ) { - if (!dataSource.getParent) { - throw new Error('Data source must implement `getParent`'); - } + super(keyboardNavigationLabelProvider, filter); } - add(result: IAsyncFindResult): void { - this._results.push(result); + override filter(element: T, parentVisibility: TreeVisibility): TreeFilterResult { + const filterResult = super.filter(element, parentVisibility); - const element = result.element; - const elementId = this.identityProvider.getId(element).toString(); - if (this.cachedNodes.has(elementId)) { - return; + if (!this.isFindSessionActive || this.findMode === TreeFindMode.Highlight || !this.findProvider.isVisible) { + return filterResult; } - this._totalLeafs++; - - const node = new AsyncFindTreeNode(element, elementId, this.sorter); - this.cachedNodes.set(elementId, node); - - // Set up parents up till root - let currentNode = node; - while (true) { - let currentParentElement = this.dataSource.getParent!(currentNode.element!); - - // is root - if (currentParentElement === currentNode.element) { - this.root.addChild(currentNode); - break; - } - currentParentElement = currentParentElement as T; - - // is already in the tree - const parentId = this.identityProvider.getId(currentParentElement).toString(); - const parentNode = this.cachedNodes.get(parentId); - if (parentNode) { - parentNode.addChild(currentNode); - break; - } - - // create parent node - const newParent = new AsyncFindTreeNode(currentParentElement, parentId, this.sorter); - this.cachedNodes.set(parentId, newParent); - newParent.addChild(currentNode); - - currentNode = newParent; - } - } -} - -class AsyncFindFilter implements IFindFilter>, IDisposable { - - pattern: string = ''; - - private findFilterData: Map = new Map(); - - private readonly disposables = new DisposableStore(); - - constructor( - private _filter?: ITreeFilter, FuzzyScore>, - ) { } - - setFindResults(findResults: IAsyncFindResult[]): void { - this.findFilterData = new Map(findResults. - filter(result => result.filterdata !== undefined). - map(result => [result.element, result.filterdata!]) - ); - } - - filter(element: IAsyncDataTreeNode, parentVisibility: TreeVisibility): TreeFilterResult { - let visibility = TreeVisibility.Visible; - - if (this._filter) { - const result = this._filter.filter(element, parentVisibility); - - if (typeof result === 'boolean') { - visibility = result ? TreeVisibility.Visible : TreeVisibility.Hidden; - } else if (isFilterResult(result)) { - visibility = getVisibleState(result.visibility); - } else { - visibility = result; - } - - if (visibility === TreeVisibility.Hidden) { - return false; - } - } - - const filterData = this.findFilterData.get(element.element as T); - if (filterData !== undefined) { - return { data: filterData, visibility }; + const visibility = isFilterResult(filterResult) ? filterResult.visibility : filterResult; + if (getVisibleState(visibility) === TreeVisibility.Hidden) { + return TreeVisibility.Hidden; } - return { data: FuzzyScore.Default, visibility }; + return this.findProvider.isVisible(element) ? filterResult : TreeVisibility.Hidden; } - dispose(): void { - dispose(this.disposables); - } } -class AsyncFindController extends AbstractFindController | null, TFilterData> { - - declare protected readonly filter: AsyncFindFilter; - private readonly model: ITreeModel | null, TFilterData, IAsyncDataTreeNode | null>; - private readonly nodes = new Map>(); - - private previousScrollTop: number | undefined; - private previousFocus: (IAsyncDataTreeNode | null)[] = []; - private previousSelection: (IAsyncDataTreeNode | null)[] = []; - - private sessionId: number = 0; - private active: boolean = false; - +class AsyncFindController extends FindController { private activeTokenSource: CancellationTokenSource | undefined; - private scheduler: TimeoutTimer; - - private readonly store = new DisposableStore(); + private activeSession = false; + private asyncWorkInProgress = false; + private taskQueue = new ThrottledDelayer(250); constructor( - private readonly user: string, - protected override readonly tree: ObjectTree, TFilterData>, + tree: ObjectTree, TFilterData>, private readonly findProvider: IAsyncFindProvider, - filter: AsyncFindFilter, - private readonly sorter: ITreeSorter | undefined, - private readonly dataSource: IAsyncDataSource, - private readonly identityProvider: IIdentityProvider, + protected override filter: AsyncFindFilter, contextViewProvider: IContextViewProvider, options: IAbstractTreeOptions, TFilterData>, - private readonly asTreeElement: (node: IAsyncDataTreeNode) => IObjectTreeElement>, ) { - super(tree as any, filter, contextViewProvider, { ...options, placeholder: findProvider.placeholder, toggles: findProvider.toggles }); - - this.model = tree.getModel(); - this.scheduler = this.store.add(new TimeoutTimer()); - } - - private setFindModeActive(active: boolean): void { - if (this.active === active) { - return; - } - - if (active) { - this.activateFindMode(); - } else { - this.deactivateFindMode(); - } - - this.active = active; - } - - private activateFindMode(): void { - this.sessionId++; - - // store tree view state - this.previousScrollTop = this.tree.scrollTop; - this.previousFocus = this.tree.getFocus(); - this.previousSelection = this.tree.getSelection(); - - this.tree.scrollTop = 0; - const findModel = this.tree.createNewModel({ filter: this.filter as ITreeFilter | null, TFilterData> }); - this.tree.setModel(findModel); - } - - private deactivateFindMode(): void { - const focus = this.tree.getFocus()[0]; - this.tree.setModel(this.model); - - if (focus && focus.element && this.findProvider.revealResultInTree) { - this.findProvider.revealResultInTree(focus.element as T); - } else { - this.tree.scrollTop = this.previousScrollTop ?? 0; - this.tree.setFocus(this.previousFocus); - this.tree.setSelection(this.previousSelection); - } - - this.activeTokenSource = undefined; - this.nodes.clear(); + super(tree as any, filter, contextViewProvider, options); + // Always make sure to end the session before disposing + this.disposables.add(toDisposable(async () => { + if (this.activeSession) { + await this.findProvider.endSession?.(); + } + })); } - protected applyPattern(pattern: string): void { + protected override applyPattern(_pattern: string): void { this.renderMessage(false); - this.scheduler.cancel(); - this.activeTokenSource?.cancel(); - - if (!pattern) { - this.setFindModeActive(false); - return; - } + this.activeTokenSource?.cancel(); this.activeTokenSource = new CancellationTokenSource(); - const results = this.findProvider.getFindResults(pattern, this.sessionId, this.activeTokenSource.token, this.toggles.states()); - this.pocessFindResults(results, this.activeTokenSource.token); + this.taskQueue.trigger(() => this.applyPatternAsync()); } - private async pocessFindResults(results: AsyncIterable>, token: CancellationToken): Promise { - if (!this.dataSource.getParent || !this.identityProvider) { + private async applyPatternAsync(): Promise { + const token = this.activeTokenSource?.token; + if (!token || token.isCancellationRequested) { return; } + const pattern = this.pattern; - // Remove all nodes - this.clearFindResults(); + if (pattern === '') { + if (this.activeSession) { + this.asyncWorkInProgress = true; + await this.deactivateFindSession(); + this.asyncWorkInProgress = false; - const findTree = new AsyncFindTree(this.dataSource, this.identityProvider, this.sorter); - for await (const result of results) { - if (token.isCancellationRequested) { - return; + if (!token.isCancellationRequested) { + this.filter.reset(); + super.applyPattern(''); + } } + return; + } - findTree.add(result); + if (!this.activeSession) { + this.activateFindSession(); } + this.asyncWorkInProgress = true; + + const findMetadata = await this.findProvider.find(pattern, { matchType: this.matchType, findMode: this.mode }, token); if (token.isCancellationRequested) { return; } - // Redraw at the end - this.setFindResults(findTree); + this.asyncWorkInProgress = false; - this.activeTokenSource?.dispose(); - this.activeTokenSource = undefined; - } - - private clearFindResults(): void { - this.schedule(() => { - if (!this.active) { - this.setFindModeActive(true); - return; - } else { - this.filter.setFindResults([]); - this.tree.setChildren(null, []); - } - }, 600); - } + this.filter.reset(); + super.applyPattern(pattern); - private setFindResults(findTree: AsyncFindTree): void { - const rootChildren: IObjectTreeElement>[] = []; - for (const rootNodes of findTree.rootNodes) { - rootChildren.push(this.asTreeElement(this.findNodeToAsyncNode(rootNodes, null))); + if (findMetadata.warningMessage) { + this.renderMessage(true, findMetadata.warningMessage); } - - this.schedule(() => { - this.setFindModeActive(true); - this.filter.setFindResults(findTree.results); - this.tree.setChildren(null, rootChildren); - this.renderMessage(rootChildren.length === 0); - this.alertResults(findTree.totalLeafs); - }, 0); } - protected findNodeToAsyncNode(node: AsyncFindTreeNode, parent: IAsyncDataTreeNode | null): IAsyncDataTreeNode { - const children: IAsyncDataTreeNode[] = []; - - if (node.element === undefined) { - throw new TreeError(this.user, 'Found node without an element'); - } - - const asyncNode: IAsyncDataTreeNode = { - element: node.element, - parent, - children, - hasChildren: false, - defaultCollapseState: ObjectTreeElementCollapseState.PreserveOrExpanded, - stale: false, - refreshPromise: undefined, - slow: false, - forceExpanded: false - }; - - for (const child of node.children) { - children.push(this.findNodeToAsyncNode(child, asyncNode)); - } - - asyncNode.hasChildren = !!children.length; - - this.nodes.set(node.element, asyncNode); + private activateFindSession(): void { + this.activeSession = true; + this.filter.isFindSessionActive = true; + this.findProvider.startSession?.(); + } - return asyncNode; + private async deactivateFindSession(): Promise { + this.activeSession = false; + this.filter.isFindSessionActive = false; + await this.findProvider.endSession?.(); } - private schedule(fn: () => void, delay: number) { - if (delay === 0) { - this.scheduler.cancel(); - fn(); - return; + protected override render(): void { + if (!this.asyncWorkInProgress) { + super.render(); } - this.scheduler.cancelAndSet(fn, delay); } protected override onDidToggleChange(e: ITreeFindToggleChangeEvent): void { - super.onDidToggleChange(e); - this.applyPattern(this.pattern); - } - - getDataNode(element: TInput | T): IAsyncDataTreeNode | undefined { - return this.nodes.get(element as T); - } + // TODO@benibenj handle toggles nicely across all controllers and between controller and filter + this.toggles.set(e.id, e.isChecked); + this.filter.findMode = this.mode; + this.filter.findMatchType = this.matchType; + this.placeholder = this.mode === TreeFindMode.Filter ? localize('type to filter', "Type to filter") : localize('type to search', "Type to search"); - override dispose(): void { - super.dispose(); - this.store.dispose(); + this.applyPattern(this.pattern); } } @@ -673,7 +468,7 @@ export interface IAsyncDataTreeOptions extends IAsyncData readonly identityProvider?: IIdentityProvider; readonly sorter?: ITreeSorter; readonly autoExpandSingleChildren?: boolean; - readonly findResultsProvider?: IAsyncFindProvider; + readonly findProvider?: IAsyncFindProvider; } export interface IAsyncDataTreeViewState { @@ -697,7 +492,6 @@ function dfs(node: IAsyncDataTreeNode, fn: (node: IAsyncDa export class AsyncDataTree implements IDisposable { protected readonly tree: ObjectTree, TFilterData>; - private readonly model: IObjectTreeModel | null, TFilterData>; protected readonly root: IAsyncDataTreeNode; private readonly nodes = new Map>(); private readonly sorter?: ITreeSorter; @@ -776,15 +570,13 @@ export class AsyncDataTree implements IDisposable this.getDefaultCollapseState = e => options.collapseByDefault ? (options.collapseByDefault(e) ? ObjectTreeElementCollapseState.PreserveOrCollapsed : ObjectTreeElementCollapseState.PreserveOrExpanded) : undefined; let asyncFindEnabled = false; - if (options.findResultsProvider && (options.findWidgetEnabled ?? true) && options.keyboardNavigationLabelProvider && options.contextViewProvider) { - if (!this.dataSource.getParent || !this.identityProvider) { - throw new TreeError(this.user, 'Find Provider requires `getParent` and `identityProvider`'); - } + let findFilter: AsyncFindFilter | undefined; + if (options.findProvider && (options.findWidgetEnabled ?? true) && options.keyboardNavigationLabelProvider && options.contextViewProvider) { asyncFindEnabled = true; + findFilter = new AsyncFindFilter(options.findProvider, options.keyboardNavigationLabelProvider, options.filter as ITreeFilter); } - this.tree = this.createTree(user, container, delegate, renderers, { ...options, findWidgetEnabled: !asyncFindEnabled }); - this.model = this.tree.getModel() as IObjectTreeModel | null, TFilterData>; + this.tree = this.createTree(user, container, delegate, renderers, { ...options, findWidgetEnabled: !asyncFindEnabled, filter: findFilter as ITreeFilter ?? options.filter }); this.root = createAsyncDataTreeNode({ element: undefined!, @@ -805,9 +597,8 @@ export class AsyncDataTree implements IDisposable this.tree.onDidChangeCollapseState(this._onDidChangeCollapseState, this, this.disposables); if (asyncFindEnabled) { - const findFilter = this.disposables.add(new AsyncFindFilter(this.tree.options.filter as ITreeFilter, FuzzyScore>)); const findOptions = { styles: options.findWidgetStyles, showNotFoundMessage: options.showNotFoundMessage }; - this.findController = this.disposables.add(new AsyncFindController(user, this.tree, options.findResultsProvider!, findFilter!, this.sorter, this.dataSource, this.identityProvider!, options.contextViewProvider!, findOptions, node => this.asTreeElement(node))); + this.findController = this.disposables.add(new AsyncFindController(this.tree, options.findProvider!, findFilter!, this.tree.options.contextViewProvider!, findOptions)); this.onDidChangeFindOpenState = this.findController!.onDidChangeOpenState; this.onDidChangeFindMode = Event.None; @@ -917,10 +708,6 @@ export class AsyncDataTree implements IDisposable layout(height?: number, width?: number): void { this.tree.layout(height, width); - - if (isNumber(width)) { - this.findController?.layout(width); - } } style(styles: IListStyles): void { @@ -981,7 +768,7 @@ export class AsyncDataTree implements IDisposable } resort(element: TInput | T = this.root.element, recursive = true): void { - this.model.resort(this.getDataNode(element), recursive); + this.tree.resort(this.getDataNode(element), recursive); } hasNode(element: TInput | T): boolean { @@ -1072,7 +859,6 @@ export class AsyncDataTree implements IDisposable } const elements: T[] = []; - while (!this.hasNode(element)) { element = this.dataSource.getParent(element) as T; @@ -1210,12 +996,6 @@ export class AsyncDataTree implements IDisposable // Implementation protected getDataNode(element: TInput | T): IAsyncDataTreeNode { - const asyncFindNode: IAsyncDataTreeNode | undefined = this.findController?.getDataNode(element); - - if (asyncFindNode) { - return asyncFindNode; - } - const node: IAsyncDataTreeNode | undefined = this.nodes.get((element === this.root.element ? null : element) as T); if (!node) { @@ -1311,8 +1091,8 @@ export class AsyncDataTree implements IDisposable const children = await childrenPromise; return this.setChildren(node, children, recursive, viewStateContext); } catch (err) { - if (node !== this.root && this.model.has(node)) { - this.model.setCollapsed(node); + if (node !== this.root && this.tree.hasElement(node)) { + this.tree.collapse(node); } if (isCancellationError(err)) { @@ -1374,7 +1154,7 @@ export class AsyncDataTree implements IDisposable nodesToForget.set(child.element as T, child); if (this.identityProvider) { - childrenTreeNodesById.set(child.id!, { node: child, collapsed: this.model.has(child) && this.model.isCollapsed(child) }); + childrenTreeNodesById.set(child.id!, { node: child, collapsed: this.tree.hasElement(child) && this.tree.isCollapsed(child) }); } } @@ -1448,7 +1228,7 @@ export class AsyncDataTree implements IDisposable this.nodes.set(child.element as T, child); } - node.children.splice(0, node.children.length, ...children); + splice(node.children, 0, node.children.length, children); // TODO@joao this doesn't take filter into account if (node !== this.root && this.autoExpandSingleChildren && children.length === 1 && childrenToRefresh.length === 0) { @@ -1470,10 +1250,10 @@ export class AsyncDataTree implements IDisposable } }; - this.model.setChildren(node === this.root ? null : node, children, objectTreeOptions); + this.tree.setChildren(node === this.root ? null : node, children, objectTreeOptions); if (node !== this.root) { - this.model.setCollapsible(node, node.hasChildren); + this.tree.setCollapsible(node, node.hasChildren); } this._onDidRender.fire(); diff --git a/src/vs/base/browser/ui/tree/media/tree.css b/src/vs/base/browser/ui/tree/media/tree.css index 58099a56c27c0..ebdd254f2a02f 100644 --- a/src/vs/base/browser/ui/tree/media/tree.css +++ b/src/vs/base/browser/ui/tree/media/tree.css @@ -74,11 +74,12 @@ .monaco-tree-type-filter { position: absolute; top: 0; + right: 0; display: flex; padding: 3px; max-width: 200px; z-index: 100; - margin: 0 6px; + margin: 0 10px 0 6px; border: 1px solid var(--vscode-widget-border); border-bottom-left-radius: 4px; border-bottom-right-radius: 4px; @@ -92,18 +93,6 @@ top: -40px !important; } -.monaco-tree-type-filter-grab { - display: flex !important; - align-items: center; - justify-content: center; - cursor: grab; - margin-right: 2px; -} - -.monaco-tree-type-filter-grab.grabbing { - cursor: grabbing; -} - .monaco-tree-type-filter-input { flex: 1; } diff --git a/src/vs/base/common/date.ts b/src/vs/base/common/date.ts index 8484c42903f74..3ed5b600014f4 100644 --- a/src/vs/base/common/date.ts +++ b/src/vs/base/common/date.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from '../../nls.js'; +import { LANGUAGE_DEFAULT } from './platform.js'; const minute = 60; const hour = minute * 60; @@ -261,3 +262,34 @@ export function toLocalISOString(date: Date): string { '.' + (date.getMilliseconds() / 1000).toFixed(3).slice(2, 5) + 'Z'; } + +export const safeIntl = { + DateTimeFormat(locales?: Intl.LocalesArgument, options?: Intl.DateTimeFormatOptions): Intl.DateTimeFormat { + try { + return new Intl.DateTimeFormat(locales, options); + } catch { + return new Intl.DateTimeFormat(undefined, options); + } + }, + Collator(locales?: Intl.LocalesArgument, options?: Intl.CollatorOptions): Intl.Collator { + try { + return new Intl.Collator(locales, options); + } catch { + return new Intl.Collator(undefined, options); + } + }, + Segmenter(locales?: Intl.LocalesArgument, options?: Intl.SegmenterOptions): Intl.Segmenter { + try { + return new Intl.Segmenter(locales, options); + } catch { + return new Intl.Segmenter(undefined, options); + } + }, + Locale(tag: Intl.Locale | string, options?: Intl.LocaleOptions): Intl.Locale { + try { + return new Intl.Locale(tag, options); + } catch { + return new Intl.Locale(LANGUAGE_DEFAULT, options); + } + } +}; diff --git a/src/vs/base/common/history.ts b/src/vs/base/common/history.ts index 39413a67d56db..ed2a5e5232de9 100644 --- a/src/vs/base/common/history.ts +++ b/src/vs/base/common/history.ts @@ -6,14 +6,23 @@ import { SetWithKey } from './collections.js'; import { ArrayNavigator, INavigator } from './navigator.js'; -export class HistoryNavigator implements INavigator { +export interface IHistory { + delete(t: T): boolean; + add(t: T): this; + has(t: T): boolean; + clear(): void; + forEach(callbackfn: (value: T, value2: T, set: Set) => void, thisArg?: any): void; + replace?(t: T[]): void; +} - private _history!: Set; +export class HistoryNavigator implements INavigator { private _limit: number; private _navigator!: ArrayNavigator; - constructor(history: readonly T[] = [], limit: number = 10) { - this._initialize(history); + constructor( + private _history: IHistory = new Set(), + limit: number = 10, + ) { this._limit = limit; this._onChange(); } @@ -69,7 +78,7 @@ export class HistoryNavigator implements INavigator { } public clear(): void { - this._initialize([]); + this._history.clear(); this._onChange(); } @@ -82,7 +91,12 @@ export class HistoryNavigator implements INavigator { private _reduceToLimit() { const data = this._elements; if (data.length > this._limit) { - this._initialize(data.slice(data.length - this._limit)); + const replaceValue = data.slice(data.length - this._limit); + if (this._history.replace) { + this._history.replace(replaceValue); + } else { + this._history = new Set(replaceValue); + } } } @@ -95,13 +109,6 @@ export class HistoryNavigator implements INavigator { return this._elements.indexOf(currentElement); } - private _initialize(history: readonly T[]): void { - this._history = new Set(); - for (const entry of history) { - this._history.add(entry); - } - } - private get _elements(): T[] { const elements: T[] = []; this._history.forEach(e => elements.push(e)); diff --git a/src/vs/base/common/product.ts b/src/vs/base/common/product.ts index f4cfba7a1aaab..9c4f060e7c878 100644 --- a/src/vs/base/common/product.ts +++ b/src/vs/base/common/product.ts @@ -100,6 +100,7 @@ export interface IProductConfiguration { readonly itemUrl: string; readonly publisherUrl: string; readonly resourceUrlTemplate: string; + readonly extensionUrlTemplate: string; readonly controlUrl: string; readonly nlsBaseUrl: string; }; @@ -197,10 +198,14 @@ export interface IProductConfiguration { readonly defaultChatAgent?: { readonly extensionId: string; + readonly providerId: string; + readonly providerName: string; + readonly providerScopes: string[]; readonly name: string; readonly icon: string; readonly documentationUrl: string; readonly gettingStartedCommand: string; + readonly welcomeTitle: string; }; } @@ -313,6 +318,8 @@ export interface IGitHubEntitlement { entitlementUrl: string; extensionId: string; enablementKey: string; + trialKey: string; + trialValue: string; confirmationMessage: string; confirmationAction: string; } diff --git a/src/vs/base/node/id.ts b/src/vs/base/node/id.ts index f4e29d1e5805a..271c9aeb769c0 100644 --- a/src/vs/base/node/id.ts +++ b/src/vs/base/node/id.ts @@ -122,6 +122,6 @@ export async function getdevDeviceId(errorLogger: (error: any) => void): Promise return id; } catch (err) { errorLogger(err); - return ''; + return uuid.generateUuid(); } } diff --git a/src/vs/base/parts/request/browser/request.ts b/src/vs/base/parts/request/common/requestImpl.ts similarity index 94% rename from src/vs/base/parts/request/browser/request.ts rename to src/vs/base/parts/request/common/requestImpl.ts index fe0fa0e783806..11aac6bd6f02c 100644 --- a/src/vs/base/parts/request/browser/request.ts +++ b/src/vs/base/parts/request/common/requestImpl.ts @@ -6,9 +6,9 @@ import { bufferToStream, VSBuffer } from '../../../common/buffer.js'; import { CancellationToken } from '../../../common/cancellation.js'; import { canceled } from '../../../common/errors.js'; -import { IHeaders, IRequestContext, IRequestOptions, OfflineError } from '../common/request.js'; +import { IHeaders, IRequestContext, IRequestOptions, OfflineError } from './request.js'; -export async function request(options: IRequestOptions, token: CancellationToken): Promise { +export async function request(options: IRequestOptions, token: CancellationToken, isOnline?: () => boolean): Promise { if (token.isCancellationRequested) { throw canceled(); } @@ -35,7 +35,7 @@ export async function request(options: IRequestOptions, token: CancellationToken stream: bufferToStream(VSBuffer.wrap(new Uint8Array(await res.arrayBuffer()))), }; } catch (err) { - if (!navigator.onLine) { + if (isOnline && !isOnline()) { throw new OfflineError(); } if (err?.name === 'AbortError') { @@ -52,7 +52,7 @@ export async function request(options: IRequestOptions, token: CancellationToken function getRequestHeaders(options: IRequestOptions) { if (options.headers || options.user || options.password || options.proxyAuthorization) { - const headers: HeadersInit = new Headers(); + const headers = new Headers(); outer: for (const k in options.headers) { switch (k.toLowerCase()) { case 'user-agent': diff --git a/src/vs/base/parts/request/test/browser/request.test.ts b/src/vs/base/parts/request/test/electron-main/request.test.ts similarity index 93% rename from src/vs/base/parts/request/test/browser/request.test.ts rename to src/vs/base/parts/request/test/electron-main/request.test.ts index 687c8c57b9da2..0eea7d9a2fbc5 100644 --- a/src/vs/base/parts/request/test/browser/request.test.ts +++ b/src/vs/base/parts/request/test/electron-main/request.test.ts @@ -3,18 +3,16 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -// eslint-disable-next-line local/code-import-patterns import * as http from 'http'; -// eslint-disable-next-line local/code-import-patterns import { AddressInfo } from 'net'; import assert from 'assert'; import { CancellationToken, CancellationTokenSource } from '../../../../common/cancellation.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../test/common/utils.js'; -import { request } from '../../browser/request.js'; +import { request } from '../../common/requestImpl.js'; import { streamToBuffer } from '../../../../common/buffer.js'; -import { flakySuite } from '../../../../test/common/testUtils.js'; -flakySuite('Request', () => { + +suite('Request', () => { let port: number; let server: http.Server; diff --git a/src/vs/base/test/browser/markdownRenderer.test.ts b/src/vs/base/test/browser/markdownRenderer.test.ts index 1c00e3cbdd26e..6ec296beb4a5b 100644 --- a/src/vs/base/test/browser/markdownRenderer.test.ts +++ b/src/vs/base/test/browser/markdownRenderer.test.ts @@ -260,17 +260,34 @@ suite('MarkdownRenderer', () => { test('test code, blockquote, heading, list, listitem, paragraph, table, tablerow, tablecell, strong, em, br, del, text are rendered plaintext', () => { const markdown = { value: '`code`\n>quote\n# heading\n- list\n\ntable | table2\n--- | --- \none | two\n\n\nbo**ld**\n_italic_\n~~del~~\nsome text' }; - const expected = 'code\nquote\nheading\nlist\n\ntable table2\none two\nbold\nitalic\ndel\nsome text\n'; + const expected = 'code\nquote\nheading\nlist\n\ntable table2\none two\nbold\nitalic\ndel\nsome text'; const result: string = renderMarkdownAsPlaintext(markdown); assert.strictEqual(result, expected); }); test('test html, hr, image, link are rendered plaintext', () => { const markdown = { value: '
html
\n\n---\n![image](imageLink)\n[text](textLink)' }; - const expected = '\ntext\n'; + const expected = 'text'; const result: string = renderMarkdownAsPlaintext(markdown); assert.strictEqual(result, expected); }); + + test(`Should not remove html inside of code blocks`, () => { + const markdown = { + value: [ + '```html', + '
html
', + '```', + ].join('\n') + }; + const expected = [ + '```', + '
html
', + '```', + ].join('\n'); + const result: string = renderMarkdownAsPlaintext(markdown, true); + assert.strictEqual(result, expected); + }); }); suite('supportHtml', () => { diff --git a/src/vs/base/test/browser/ui/tree/objectTree.test.ts b/src/vs/base/test/browser/ui/tree/objectTree.test.ts index f8f02d88a8e6f..aa11fbe6036c4 100644 --- a/src/vs/base/test/browser/ui/tree/objectTree.test.ts +++ b/src/vs/base/test/browser/ui/tree/objectTree.test.ts @@ -5,11 +5,10 @@ import assert from 'assert'; import { IIdentityProvider, IListVirtualDelegate } from '../../../../browser/ui/list/list.js'; -import { CompressibleObjectTreeModel, ICompressedTreeNode } from '../../../../browser/ui/tree/compressedObjectTreeModel.js'; +import { ICompressedTreeNode } from '../../../../browser/ui/tree/compressedObjectTreeModel.js'; import { CompressibleObjectTree, ICompressibleTreeRenderer, ObjectTree } from '../../../../browser/ui/tree/objectTree.js'; import { ITreeNode, ITreeRenderer } from '../../../../browser/ui/tree/tree.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../common/utils.js'; -import { ObjectTreeModel } from '../../../../browser/ui/tree/objectTreeModel.js'; function getRowsTextContent(container: HTMLElement): string[] { const rows = [...container.querySelectorAll('.monaco-list-row')]; @@ -232,107 +231,6 @@ suite('ObjectTree', function () { tree.setChildren(null, [{ element: 100 }, { element: 101 }, { element: 102 }, { element: 103 }]); assert.deepStrictEqual(tree.getFocus(), [101]); }); - - test('swap model', function () { - const container = document.createElement('div'); - container.style.width = '200px'; - container.style.height = '200px'; - - const delegate = new Delegate(); - const renderer = new Renderer(); - const identityProvider = new IdentityProvider(); - - const tree = new ObjectTree('test', container, delegate, [renderer], { identityProvider }); - tree.layout(200); - - tree.setChildren(null, [{ element: 1 }, { element: 2 }, { element: 3 }, { element: 4 }]); - assert.deepStrictEqual(getRowsTextContent(container), ['1', '2', '3', '4']); - - const oldModel = tree.getModel(); - - const newModel = new ObjectTreeModel('test', {}); - tree.setModel(newModel); - assert.deepStrictEqual(getRowsTextContent(container), []); - - newModel.setChildren(null, [ - { element: 1, children: [{ element: 11 }] }, - { element: 2 } - ]); - assert.deepStrictEqual(getRowsTextContent(container), ['1', '11', '2']); - - tree.setChildren(11, [ - { element: 111, children: [{ element: 1111 }] }, - { element: 112 }, - ]); - assert.deepStrictEqual(getRowsTextContent(container), ['1', '11', '111', '1111', '112', '2']); - - tree.setModel(oldModel); - assert.deepStrictEqual(getRowsTextContent(container), ['1', '2', '3', '4']); - }); - - test('swap model events', function () { - const container = document.createElement('div'); - container.style.width = '200px'; - container.style.height = '200px'; - - const delegate = new Delegate(); - const renderer = new Renderer(); - const identityProvider = new IdentityProvider(); - - const tree = new ObjectTree('test', container, delegate, [renderer], { identityProvider }); - tree.layout(200); - - tree.setChildren(null, [{ element: 1 }, { element: 2 }, { element: 3 }, { element: 4 }]); - assert.deepStrictEqual(getRowsTextContent(container), ['1', '2', '3', '4']); - - const newModel = new ObjectTreeModel('test', {}); - newModel.setChildren(null, [ - { element: 1, children: [{ element: 11 }] }, - { element: 2 } - ]); - - let didChangeModel = false; - let didChangeRenderNodeCount = false; - let didChangeCollapseState = false; - - tree.onDidChangeModel(() => { didChangeModel = true; }); - tree.onDidChangeRenderNodeCount(() => { didChangeRenderNodeCount = true; }); - tree.onDidChangeCollapseState(() => { didChangeCollapseState = true; }); - - tree.setModel(newModel); - - assert.strictEqual(didChangeModel, true); - assert.strictEqual(didChangeRenderNodeCount, true); - assert.strictEqual(didChangeCollapseState, false); - }); - - // TODO@benibenj: Make sure user is updated in the tree when model is swapped - test.skip('swap model TreeError uses updated user', function () { - const container = document.createElement('div'); - container.style.width = '200px'; - container.style.height = '200px'; - - const delegate = new Delegate(); - const renderer = new Renderer(); - - const tree = new ObjectTree('test', container, delegate, [renderer], {}); - tree.layout(200); - - tree.setChildren(null, [{ element: 1 }]); - - const newModel = new ObjectTreeModel('NEW_USER_NAME', {}); - tree.setModel(newModel); - - try { - // This should throw an error because no identity provider is provided - tree.getViewState(); - } catch (e) { - assert.strictEqual(e.message.includes('NEW_USER_NAME'), true); - return; - } - - assert.fail('Expected error'); - }); }); suite('CompressibleObjectTree', function () { @@ -474,59 +372,4 @@ suite('CompressibleObjectTree', function () { tree.updateOptions({ compressionEnabled: true }); assert.deepStrictEqual(getRowsTextContent(container), ['1/11/111', '1111', '1112', '1113']); }); - - test('swapModel', () => { - const container = document.createElement('div'); - container.style.width = '200px'; - container.style.height = '200px'; - - const tree = ds.add(new CompressibleObjectTree('test', container, new Delegate(), [new Renderer()])); - tree.layout(200); - - tree.setChildren(null, [ - { - element: 1, children: [{ - element: 11, children: [{ - element: 111, children: [ - { element: 1111 }, - { element: 1112 }, - { element: 1113 }, - ] - }] - }] - } - ]); - - assert.deepStrictEqual(getRowsTextContent(container), ['1/11/111', '1111', '1112', '1113']); - - const newModel = new CompressibleObjectTreeModel('test', {}); - newModel.setChildren(null, [ - { - element: 1, children: [{ - element: 11 - }] - }, - { - element: 2, children: [{ - element: 21, children: [ - { element: 211 }, - { element: 212 }, - { element: 213 }, - ] - }] - } - ]); - - tree.setModel(newModel); - assert.deepStrictEqual(getRowsTextContent(container), ['1/11', '2/21', '211', '212', '213']); - - tree.setChildren(11, [ - { element: 111 }, - { element: 112 }, - { element: 113 }, - ]); - - assert.deepStrictEqual(getRowsTextContent(container), ['1/11', '111', '112', '113', '2/21', '211', '212', '213']); - - }); }); diff --git a/src/vs/base/test/common/date.test.ts b/src/vs/base/test/common/date.test.ts index 0734640169074..2260d6104b5f0 100644 --- a/src/vs/base/test/common/date.test.ts +++ b/src/vs/base/test/common/date.test.ts @@ -4,8 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import { strictEqual } from 'assert'; -import { fromNow, fromNowByDay, getDurationString } from '../../common/date.js'; +import { fromNow, fromNowByDay, getDurationString, safeIntl } from '../../common/date.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from './utils.js'; +import { LANGUAGE_DEFAULT } from '../../common/platform.js'; suite('Date', () => { ensureNoDisposablesAreLeakedInTestSuite(); @@ -68,5 +69,18 @@ suite('Date', () => { strictEqual(getDurationString(1000 * 60 * 60 * 24 - 1, true), '24 hours'); strictEqual(getDurationString(1000 * 60 * 60 * 24, true), '1 days'); }); + + suite('safeIntl', () => { + test('Collator fallback', () => { + const collator = safeIntl.Collator('en_IT'); + const comparison = collator.compare('a', 'b'); + strictEqual(comparison, -1); + }); + + test('Locale fallback', () => { + const locale = safeIntl.Locale('en_IT'); + strictEqual(locale.baseName, LANGUAGE_DEFAULT); + }); + }); }); }); diff --git a/src/vs/base/test/common/history.test.ts b/src/vs/base/test/common/history.test.ts index c836f610c2bb4..e75463cab8942 100644 --- a/src/vs/base/test/common/history.test.ts +++ b/src/vs/base/test/common/history.test.ts @@ -11,13 +11,13 @@ suite('History Navigator', () => { ensureNoDisposablesAreLeakedInTestSuite(); test('create reduces the input to limit', () => { - const testObject = new HistoryNavigator(['1', '2', '3', '4'], 2); + const testObject = new HistoryNavigator(new Set(['1', '2', '3', '4']), 2); assert.deepStrictEqual(['3', '4'], toArray(testObject)); }); test('create sets the position after last', () => { - const testObject = new HistoryNavigator(['1', '2', '3', '4'], 100); + const testObject = new HistoryNavigator(new Set(['1', '2', '3', '4']), 100); assert.strictEqual(testObject.current(), null); assert.strictEqual(testObject.isNowhere(), true); @@ -31,7 +31,7 @@ suite('History Navigator', () => { }); test('last returns last element', () => { - const testObject = new HistoryNavigator(['1', '2', '3', '4'], 100); + const testObject = new HistoryNavigator(new Set(['1', '2', '3', '4']), 100); assert.strictEqual(testObject.first(), '1'); assert.strictEqual(testObject.last(), '4'); @@ -40,7 +40,7 @@ suite('History Navigator', () => { }); test('first returns first element', () => { - const testObject = new HistoryNavigator(['1', '2', '3', '4'], 3); + const testObject = new HistoryNavigator(new Set(['1', '2', '3', '4']), 3); assert.strictEqual('2', testObject.first()); assert.strictEqual(testObject.isFirst(), true); @@ -48,7 +48,7 @@ suite('History Navigator', () => { }); test('next returns next element', () => { - const testObject = new HistoryNavigator(['1', '2', '3', '4'], 3); + const testObject = new HistoryNavigator(new Set(['1', '2', '3', '4']), 3); testObject.first(); @@ -58,7 +58,7 @@ suite('History Navigator', () => { }); test('previous returns previous element', () => { - const testObject = new HistoryNavigator(['1', '2', '3', '4'], 3); + const testObject = new HistoryNavigator(new Set(['1', '2', '3', '4']), 3); assert.strictEqual(testObject.previous(), '4'); assert.strictEqual(testObject.previous(), '3'); @@ -67,7 +67,7 @@ suite('History Navigator', () => { }); test('next on last element returns null and remains on last', () => { - const testObject = new HistoryNavigator(['1', '2', '3', '4'], 3); + const testObject = new HistoryNavigator(new Set(['1', '2', '3', '4']), 3); testObject.first(); testObject.last(); @@ -79,7 +79,7 @@ suite('History Navigator', () => { }); test('previous on first element returns null and remains on first', () => { - const testObject = new HistoryNavigator(['1', '2', '3', '4'], 3); + const testObject = new HistoryNavigator(new Set(['1', '2', '3', '4']), 3); testObject.first(); @@ -90,7 +90,7 @@ suite('History Navigator', () => { }); test('add reduces the input to limit', () => { - const testObject = new HistoryNavigator(['1', '2', '3', '4'], 2); + const testObject = new HistoryNavigator(new Set(['1', '2', '3', '4']), 2); testObject.add('5'); @@ -98,7 +98,7 @@ suite('History Navigator', () => { }); test('adding existing element changes the position', () => { - const testObject = new HistoryNavigator(['1', '2', '3', '4'], 5); + const testObject = new HistoryNavigator(new Set(['1', '2', '3', '4']), 5); testObject.add('2'); @@ -106,7 +106,7 @@ suite('History Navigator', () => { }); test('add resets the navigator to last', () => { - const testObject = new HistoryNavigator(['1', '2', '3', '4'], 3); + const testObject = new HistoryNavigator(new Set(['1', '2', '3', '4']), 3); testObject.first(); testObject.add('5'); @@ -118,7 +118,7 @@ suite('History Navigator', () => { }); test('adding an existing item changes the order', () => { - const testObject = new HistoryNavigator(['1', '2', '3']); + const testObject = new HistoryNavigator(new Set(['1', '2', '3'])); testObject.add('1'); @@ -126,7 +126,7 @@ suite('History Navigator', () => { }); test('previous returns null if the current position is the first one', () => { - const testObject = new HistoryNavigator(['1', '2', '3']); + const testObject = new HistoryNavigator(new Set(['1', '2', '3'])); testObject.first(); @@ -135,7 +135,7 @@ suite('History Navigator', () => { }); test('previous returns object if the current position is not the first one', () => { - const testObject = new HistoryNavigator(['1', '2', '3']); + const testObject = new HistoryNavigator(new Set(['1', '2', '3'])); testObject.first(); testObject.next(); @@ -144,7 +144,7 @@ suite('History Navigator', () => { }); test('next returns null if the current position is the last one', () => { - const testObject = new HistoryNavigator(['1', '2', '3']); + const testObject = new HistoryNavigator(new Set(['1', '2', '3'])); testObject.last(); @@ -154,7 +154,7 @@ suite('History Navigator', () => { }); test('next returns object if the current position is not the last one', () => { - const testObject = new HistoryNavigator(['1', '2', '3']); + const testObject = new HistoryNavigator(new Set(['1', '2', '3'])); testObject.last(); testObject.previous(); @@ -163,7 +163,7 @@ suite('History Navigator', () => { }); test('clear', () => { - const testObject = new HistoryNavigator(['a', 'b', 'c']); + const testObject = new HistoryNavigator(new Set(['a', 'b', 'c'])); assert.strictEqual(testObject.previous(), 'c'); testObject.clear(); assert.strictEqual(testObject.current(), null); diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 603edccc2c572..d8880a9b34f36 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -51,9 +51,8 @@ import { DiskFileSystemProvider } from '../../platform/files/node/diskFileSystem import { SyncDescriptor } from '../../platform/instantiation/common/descriptors.js'; import { IInstantiationService, ServicesAccessor } from '../../platform/instantiation/common/instantiation.js'; import { ServiceCollection } from '../../platform/instantiation/common/serviceCollection.js'; -import { IProcessMainService, IIssueMainService } from '../../platform/issue/common/issue.js'; -import { IssueMainService } from '../../platform/issue/electron-main/issueMainService.js'; -import { ProcessMainService } from '../../platform/issue/electron-main/processMainService.js'; +import { IProcessMainService } from '../../platform/process/common/process.js'; +import { ProcessMainService } from '../../platform/process/electron-main/processMainService.js'; import { IKeyboardLayoutMainService, KeyboardLayoutMainService } from '../../platform/keyboardLayout/electron-main/keyboardLayoutMainService.js'; import { ILaunchMainService, LaunchMainService } from '../../platform/launch/electron-main/launchMainService.js'; import { ILifecycleMainService, LifecycleMainPhase, ShutdownReason } from '../../platform/lifecycle/electron-main/lifecycleMainService.js'; @@ -103,7 +102,7 @@ import { ExtensionsScannerService } from '../../platform/extensionManagement/nod import { UserDataProfilesHandler } from '../../platform/userDataProfile/electron-main/userDataProfilesHandler.js'; import { ProfileStorageChangesListenerChannel } from '../../platform/userDataProfile/electron-main/userDataProfileStorageIpc.js'; import { Promises, RunOnceScheduler, runWhenGlobalIdle } from '../../base/common/async.js'; -import { resolveMachineId, resolveSqmId, resolvedevDeviceId } from '../../platform/telemetry/electron-main/telemetryUtils.js'; +import { resolveMachineId, resolveSqmId, resolvedevDeviceId, validatedevDeviceId } from '../../platform/telemetry/electron-main/telemetryUtils.js'; import { ExtensionsProfileScannerService } from '../../platform/extensionManagement/node/extensionsProfileScannerService.js'; import { LoggerChannel } from '../../platform/log/electron-main/logIpc.js'; import { ILoggerMainService } from '../../platform/log/electron-main/loggerService.js'; @@ -119,7 +118,6 @@ import { IAuxiliaryWindowsMainService } from '../../platform/auxiliaryWindow/ele import { AuxiliaryWindowsMainService } from '../../platform/auxiliaryWindow/electron-main/auxiliaryWindowsMainService.js'; import { normalizeNFC } from '../../base/common/normalization.js'; import { ICSSDevelopmentService, CSSDevelopmentService } from '../../platform/cssDev/node/cssDevService.js'; -import { ExtensionSignatureVerificationService, IExtensionSignatureVerificationService } from '../../platform/extensionManagement/node/extensionSignatureVerificationService.js'; /** * The main VS Code application. There will only ever be one instance, @@ -172,18 +170,17 @@ export class CodeApplication extends Disposable { ]); const allowedPermissionsInCore = new Set([ - 'media' + 'media', + 'local-fonts', ]); session.defaultSession.setPermissionRequestHandler((_webContents, permission, callback, details) => { if (isUrlFromWebview(details.requestingUrl)) { return callback(allowedPermissionsInWebview.has(permission)); } - if (isUrlFromWindow(details.requestingUrl)) { return callback(allowedPermissionsInCore.has(permission)); } - return callback(false); }); @@ -1024,9 +1021,6 @@ export class CodeApplication extends Disposable { services.set(IDiagnosticsMainService, new SyncDescriptor(DiagnosticsMainService, undefined, false /* proxied to other processes */)); services.set(IDiagnosticsService, ProxyChannel.toService(getDelayedChannel(sharedProcessReady.then(client => client.getChannel('diagnostics'))))); - // Issues - services.set(IIssueMainService, new SyncDescriptor(IssueMainService, [this.userEnv])); - // Process services.set(IProcessMainService, new SyncDescriptor(ProcessMainService, [this.userEnv])); @@ -1115,11 +1109,6 @@ export class CodeApplication extends Disposable { // Dev Only: CSS service (for ESM) services.set(ICSSDevelopmentService, new SyncDescriptor(CSSDevelopmentService, undefined, true)); - if (this.productService.quality !== 'stable') { - // extensions signature verification service - services.set(IExtensionSignatureVerificationService, new SyncDescriptor(ExtensionSignatureVerificationService, undefined, true)); - } - // Init services that require it await Promises.settled([ backupMainService.initialize(), @@ -1161,21 +1150,10 @@ export class CodeApplication extends Disposable { mainProcessElectronServer.registerChannel('userDataProfiles', userDataProfilesService); sharedProcessClient.then(client => client.registerChannel('userDataProfiles', userDataProfilesService)); - if (this.productService.quality !== 'stable') { - // Extension signature verification service - const extensionSignatureVerificationService = accessor.get(IExtensionSignatureVerificationService); - sharedProcessClient.then(client => client.registerChannel('signatureVerificationService', - ProxyChannel.fromService(extensionSignatureVerificationService, disposables))); - } - // Update const updateChannel = new UpdateChannel(accessor.get(IUpdateService)); mainProcessElectronServer.registerChannel('update', updateChannel); - // Issues - const issueChannel = ProxyChannel.fromService(accessor.get(IIssueMainService), disposables); - mainProcessElectronServer.registerChannel('issue', issueChannel); - // Process const processChannel = ProxyChannel.fromService(accessor.get(IProcessMainService), disposables); mainProcessElectronServer.registerChannel('process', processChannel); @@ -1395,6 +1373,9 @@ export class CodeApplication extends Disposable { if (isMacintosh && app.runningUnderARM64Translation) { this.windowsMainService?.sendToFocused('vscode:showTranslatedBuildWarning'); } + + // Validate Device ID is up to date + validatedevDeviceId(this.stateService, this.logService); } private async installMutex(): Promise { diff --git a/src/vs/code/electron-sandbox/processExplorer/processExplorer.ts b/src/vs/code/electron-sandbox/processExplorer/processExplorer.ts index 6df8c102be947..35db481251514 100644 --- a/src/vs/code/electron-sandbox/processExplorer/processExplorer.ts +++ b/src/vs/code/electron-sandbox/processExplorer/processExplorer.ts @@ -9,7 +9,7 @@ type IBootstrapWindow = import('vs/platform/window/electron-sandbox/window.js').IBootstrapWindow; type IProcessExplorerMain = import('vs/code/electron-sandbox/processExplorer/processExplorerMain.js').IProcessExplorerMain; - type ProcessExplorerWindowConfiguration = import('vs/platform/issue/common/issue.js').ProcessExplorerWindowConfiguration; + type ProcessExplorerWindowConfiguration = import('vs/platform/process/common/process.js').ProcessExplorerWindowConfiguration; const bootstrapWindow: IBootstrapWindow = (window as any).MonacoBootstrapWindow; // defined by bootstrap-window.ts diff --git a/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts b/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts index d9f79c61fbb9f..616165dc97734 100644 --- a/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts +++ b/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts @@ -6,7 +6,8 @@ import './media/processExplorer.css'; import '../../../base/browser/ui/codicons/codiconStyles.js'; // make sure codicon css is loaded import { localize } from '../../../nls.js'; -import { $, append, createStyleSheet } from '../../../base/browser/dom.js'; +import { $, append } from '../../../base/browser/dom.js'; +import { createStyleSheet } from '../../../base/browser/domStylesheets.js'; import { IListVirtualDelegate } from '../../../base/browser/ui/list/list.js'; import { DataTree } from '../../../base/browser/ui/tree/dataTree.js'; import { IDataSource, ITreeNode, ITreeRenderer } from '../../../base/browser/ui/tree/tree.js'; @@ -18,7 +19,7 @@ import { ipcRenderer } from '../../../base/parts/sandbox/electron-sandbox/global import { IRemoteDiagnosticError, isRemoteDiagnosticError } from '../../../platform/diagnostics/common/diagnostics.js'; import { ByteSize } from '../../../platform/files/common/files.js'; import { ElectronIPCMainProcessService } from '../../../platform/ipc/electron-sandbox/mainProcessService.js'; -import { ProcessExplorerData, ProcessExplorerStyles, ProcessExplorerWindowConfiguration } from '../../../platform/issue/common/issue.js'; +import { ProcessExplorerData, ProcessExplorerStyles, ProcessExplorerWindowConfiguration } from '../../../platform/process/common/process.js'; import { INativeHostService } from '../../../platform/native/common/native.js'; import { NativeHostService } from '../../../platform/native/common/nativeHostService.js'; import { getIconsStyleSheet } from '../../../platform/theme/browser/iconsStyleSheet.js'; diff --git a/src/vs/code/electron-sandbox/workbench/workbench-dev.html b/src/vs/code/electron-sandbox/workbench/workbench-dev.html index ea5cbee848a42..ef6ebd56d9d8e 100644 --- a/src/vs/code/electron-sandbox/workbench/workbench-dev.html +++ b/src/vs/code/electron-sandbox/workbench/workbench-dev.html @@ -62,6 +62,7 @@ notebookRenderer stickyScrollViewLayer tokenizeToString + notebookChatEditController ; "/> diff --git a/src/vs/code/electron-sandbox/workbench/workbench.html b/src/vs/code/electron-sandbox/workbench/workbench.html index c019f3d8913ac..0a0fb8242e092 100644 --- a/src/vs/code/electron-sandbox/workbench/workbench.html +++ b/src/vs/code/electron-sandbox/workbench/workbench.html @@ -60,6 +60,7 @@ notebookRenderer stickyScrollViewLayer tokenizeToString + notebookChatEditController ; "/> diff --git a/src/vs/code/electron-utility/sharedProcess/contrib/defaultExtensionsInitializer.ts b/src/vs/code/electron-utility/sharedProcess/contrib/defaultExtensionsInitializer.ts new file mode 100644 index 0000000000000..e03ae6ba2715d --- /dev/null +++ b/src/vs/code/electron-utility/sharedProcess/contrib/defaultExtensionsInitializer.ts @@ -0,0 +1,78 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { dirname, join } from 'path'; +import { Disposable } from '../../../../base/common/lifecycle.js'; +import { isWindows } from '../../../../base/common/platform.js'; +import { URI } from '../../../../base/common/uri.js'; +import { INativeEnvironmentService } from '../../../../platform/environment/common/environment.js'; +import { INativeServerExtensionManagementService } from '../../../../platform/extensionManagement/node/extensionManagementService.js'; +import { ILogService } from '../../../../platform/log/common/log.js'; +import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; +import { FileOperationResult, IFileService, IFileStat, toFileOperationResult } from '../../../../platform/files/common/files.js'; +import { getErrorMessage } from '../../../../base/common/errors.js'; + +const defaultExtensionsInitStatusKey = 'initializing-default-extensions'; + +export class DefaultExtensionsInitializer extends Disposable { + constructor( + @INativeEnvironmentService private readonly environmentService: INativeEnvironmentService, + @INativeServerExtensionManagementService private readonly extensionManagementService: INativeServerExtensionManagementService, + @IStorageService storageService: IStorageService, + @IFileService private readonly fileService: IFileService, + @ILogService private readonly logService: ILogService, + ) { + super(); + + if (isWindows && storageService.getBoolean(defaultExtensionsInitStatusKey, StorageScope.APPLICATION, true)) { + storageService.store(defaultExtensionsInitStatusKey, true, StorageScope.APPLICATION, StorageTarget.MACHINE); + this.initializeDefaultExtensions().then(() => storageService.store(defaultExtensionsInitStatusKey, false, StorageScope.APPLICATION, StorageTarget.MACHINE)); + } + } + + private async initializeDefaultExtensions(): Promise { + const extensionsLocation = this.getDefaultExtensionVSIXsLocation(); + let stat: IFileStat; + try { + stat = await this.fileService.resolve(extensionsLocation); + if (!stat.children) { + this.logService.debug('There are no default extensions to initialize', extensionsLocation.toString()); + return; + } + } catch (error) { + if (toFileOperationResult(error) === FileOperationResult.FILE_NOT_FOUND) { + this.logService.debug('There are no default extensions to initialize', extensionsLocation.toString()); + return; + } + this.logService.error('Error initializing extensions', error); + return; + } + + const vsixs = stat.children.filter(child => child.name.endsWith('.vsix')); + if (vsixs.length === 0) { + this.logService.debug('There are no default extensions to initialize', extensionsLocation.toString()); + return; + } + + this.logService.info('Initializing default extensions', extensionsLocation.toString()); + await Promise.all(vsixs.map(async vsix => { + this.logService.info('Installing default extension', vsix.resource.toString()); + try { + await this.extensionManagementService.install(vsix.resource, { donotIncludePackAndDependencies: true, keepExisting: false }); + this.logService.info('Default extension installed', vsix.resource.toString()); + } catch (error) { + this.logService.error('Error installing default extension', vsix.resource.toString(), getErrorMessage(error)); + } + })); + this.logService.info('Default extensions initialized', extensionsLocation.toString()); + } + + private getDefaultExtensionVSIXsLocation(): URI { + // appRoot = C:\Users\\AppData\Local\Programs\Microsoft VS Code Insiders\resources\app + // extensionsPath = C:\Users\\AppData\Local\Programs\Microsoft VS Code Insiders\extras\extensions + return URI.file(join(dirname(dirname(this.environmentService.appRoot)), 'extras', 'extensions')); + } + +} diff --git a/src/vs/code/electron-utility/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-utility/sharedProcess/sharedProcessMain.ts index 80e8fdab8104c..00a0b29125adb 100644 --- a/src/vs/code/electron-utility/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-utility/sharedProcess/sharedProcessMain.ts @@ -118,6 +118,7 @@ import { getOSReleaseInfo } from '../../../base/node/osReleaseInfo.js'; import { getDesktopEnvironment } from '../../../base/common/desktopEnvironmentInfo.js'; import { getCodeDisplayProtocol, getDisplayProtocol } from '../../../base/node/osDisplayProtocolInfo.js'; import { RequestService } from '../../../platform/request/electron-utility/requestService.js'; +import { DefaultExtensionsInitializer } from './contrib/defaultExtensionsInitializer.js'; class SharedProcessMain extends Disposable implements IClientConnectionFilter { @@ -187,7 +188,8 @@ class SharedProcessMain extends Disposable implements IClientConnectionFilter { instantiationService.createInstance(LogsDataCleaner), instantiationService.createInstance(LocalizationsUpdater), instantiationService.createInstance(ExtensionsContributions), - instantiationService.createInstance(UserDataProfilesCleaner) + instantiationService.createInstance(UserDataProfilesCleaner), + instantiationService.createInstance(DefaultExtensionsInitializer) )); } @@ -326,14 +328,7 @@ class SharedProcessMain extends Disposable implements IClientConnectionFilter { // Extension Management services.set(IExtensionsProfileScannerService, new SyncDescriptor(ExtensionsProfileScannerService, undefined, true)); services.set(IExtensionsScannerService, new SyncDescriptor(ExtensionsScannerService, undefined, true)); - - if (productService.quality === 'stable') { - services.set(IExtensionSignatureVerificationService, new SyncDescriptor(ExtensionSignatureVerificationService, undefined, true)); - } else { - // Do extension signature verification in the main process in insiders - services.set(IExtensionSignatureVerificationService, ProxyChannel.toService(mainProcessService.getChannel('signatureVerificationService'))); - } - + services.set(IExtensionSignatureVerificationService, new SyncDescriptor(ExtensionSignatureVerificationService, undefined, true)); services.set(INativeServerExtensionManagementService, new SyncDescriptor(ExtensionManagementService, undefined, true)); // Extension Gallery diff --git a/src/vs/code/node/cli.ts b/src/vs/code/node/cli.ts index d3c4d5104d8a4..e91bea07d6a83 100644 --- a/src/vs/code/node/cli.ts +++ b/src/vs/code/node/cli.ts @@ -109,7 +109,7 @@ export async function main(argv: string[]): Promise { // Usage: `[[ "$TERM_PROGRAM" == "vscode" ]] && . "$(code --locate-shell-integration-path zsh)"` case 'zsh': file = 'shellIntegration-rc.zsh'; break; // Usage: `string match -q "$TERM_PROGRAM" "vscode"; and . (code --locate-shell-integration-path fish)` - case 'fish': file = 'fish_xdg_data/fish/vendor_conf.d/shellIntegration.fish'; break; + case 'fish': file = 'shellIntegration.fish'; break; default: throw new Error('Error using --locate-shell-integration-path: Invalid shell type'); } console.log(join(getAppRoot(), 'out', 'vs', 'workbench', 'contrib', 'terminal', 'common', 'scripts', file)); diff --git a/src/vs/editor/browser/controller/editContext/clipboardUtils.ts b/src/vs/editor/browser/controller/editContext/clipboardUtils.ts index d7091d613d6d5..6095ad06da73d 100644 --- a/src/vs/editor/browser/controller/editContext/clipboardUtils.ts +++ b/src/vs/editor/browser/controller/editContext/clipboardUtils.ts @@ -5,6 +5,7 @@ import { IViewModel } from '../../../common/viewModel.js'; import { Range } from '../../../common/core/range.js'; import { isWindows } from '../../../../base/common/platform.js'; +import { Mimes } from '../../../../base/common/mime.js'; export function getDataToCopy(viewModel: IViewModel, modelSelections: Range[], emptySelectionClipboard: boolean, copyWithSyntaxHighlighting: boolean): ClipboardDataToCopy { const rawTextToCopy = viewModel.getPlainTextToCopy(modelSelections, emptySelectionClipboard, isWindows); @@ -84,3 +85,36 @@ interface InMemoryClipboardMetadata { lastCopiedValue: string; data: ClipboardStoredMetadata; } + +export const ClipboardEventUtils = { + + getTextData(clipboardData: DataTransfer): [string, ClipboardStoredMetadata | null] { + const text = clipboardData.getData(Mimes.text); + let metadata: ClipboardStoredMetadata | null = null; + const rawmetadata = clipboardData.getData('vscode-editor-data'); + if (typeof rawmetadata === 'string') { + try { + metadata = JSON.parse(rawmetadata); + if (metadata.version !== 1) { + metadata = null; + } + } catch (err) { + // no problem! + } + } + if (text.length === 0 && metadata === null && clipboardData.files.length > 0) { + // no textual data pasted, generate text from file names + const files: File[] = Array.prototype.slice.call(clipboardData.files, 0); + return [files.map(file => file.name).join('\n'), null]; + } + return [text, metadata]; + }, + + setTextData(clipboardData: DataTransfer, text: string, html: string | null | undefined, metadata: ClipboardStoredMetadata): void { + clipboardData.setData(Mimes.text, text); + if (typeof html === 'string') { + clipboardData.setData('text/html', html); + } + clipboardData.setData('vscode-editor-data', JSON.stringify(metadata)); + } +}; diff --git a/src/vs/editor/browser/controller/editContext/native/nativeEditContext.css b/src/vs/editor/browser/controller/editContext/native/nativeEditContext.css index bfdb765a37b53..792c15feec2be 100644 --- a/src/vs/editor/browser/controller/editContext/native/nativeEditContext.css +++ b/src/vs/editor/browser/controller/editContext/native/nativeEditContext.css @@ -13,6 +13,21 @@ text-wrap: nowrap; } +.monaco-editor .native-edit-context-textarea { + min-width: 0; + min-height: 0; + margin: 0; + padding: 0; + position: absolute; + outline: none !important; + resize: none; + border: none; + overflow: hidden; + color: transparent; + background-color: transparent; + z-index: -10; +} + .monaco-editor .edit-context-composition-none { background-color: transparent; border-bottom: none; diff --git a/src/vs/editor/browser/controller/editContext/native/nativeEditContext.ts b/src/vs/editor/browser/controller/editContext/native/nativeEditContext.ts index 333632168b123..246991ca96699 100644 --- a/src/vs/editor/browser/controller/editContext/native/nativeEditContext.ts +++ b/src/vs/editor/browser/controller/editContext/native/nativeEditContext.ts @@ -9,7 +9,6 @@ import { addDisposableListener, getActiveWindow, getWindow, getWindowId } from ' import { FastDomNode } from '../../../../../base/browser/fastDomNode.js'; import { StandardKeyboardEvent } from '../../../../../base/browser/keyboardEvent.js'; import { KeyCode } from '../../../../../base/common/keyCodes.js'; -import { IClipboardService } from '../../../../../platform/clipboard/common/clipboardService.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { EditorOption } from '../../../../common/config/editorOptions.js'; import { EndOfLinePreference, EndOfLineSequence, IModelDeltaDecoration } from '../../../../common/model.js'; @@ -17,7 +16,7 @@ import { ViewConfigurationChangedEvent, ViewCursorStateChangedEvent } from '../. import { ViewContext } from '../../../../common/viewModel/viewContext.js'; import { RestrictedRenderingContext, RenderingContext } from '../../../view/renderingContext.js'; import { ViewController } from '../../../view/viewController.js'; -import { ClipboardStoredMetadata, getDataToCopy, InMemoryClipboardMetadataManager } from '../clipboardUtils.js'; +import { ClipboardEventUtils, ClipboardStoredMetadata, getDataToCopy, InMemoryClipboardMetadataManager } from '../clipboardUtils.js'; import { AbstractEditContext } from '../editContext.js'; import { editContextAddDisposableListener, FocusTracker, ITypeData } from './nativeEditContextUtils.js'; import { ScreenReaderSupport } from './screenReaderSupport.js'; @@ -39,6 +38,10 @@ enum CompositionClassName { export class NativeEditContext extends AbstractEditContext { + public static TEXT_AREA_CLASS_NAME = 'native-edit-context-textarea'; + + // Text area used to handle paste events + public readonly textArea: FastDomNode; public readonly domNode: FastDomNode; private readonly _editContext: EditContext; private readonly _screenReaderSupport: ScreenReaderSupport; @@ -62,16 +65,19 @@ export class NativeEditContext extends AbstractEditContext { viewController: ViewController, private readonly _visibleRangeProvider: IVisibleRangeProvider, @IInstantiationService instantiationService: IInstantiationService, - @IClipboardService clipboardService: IClipboardService, @IAccessibilityService private readonly _accessibilityService: IAccessibilityService ) { super(context); this.domNode = new FastDomNode(document.createElement('div')); this.domNode.setClassName(`native-edit-context`); + this.textArea = new FastDomNode(document.createElement('textarea')); + this.textArea.setClassName(NativeEditContext.TEXT_AREA_CLASS_NAME); + this.textArea.setAttribute('modeluri', context.viewModel.model.uri.path); this._updateDomAttributes(); overflowGuardContainer.appendChild(this.domNode); + overflowGuardContainer.appendChild(this.textArea); this._parent = overflowGuardContainer.domNode; this._selectionChangeListener = this._register(new MutableDisposable()); @@ -86,9 +92,9 @@ export class NativeEditContext extends AbstractEditContext { this._screenReaderSupport = instantiationService.createInstance(ScreenReaderSupport, this.domNode, context); - this._register(addDisposableListener(this.domNode.domNode, 'copy', () => this._ensureClipboardGetsEditorSelection(clipboardService))); - this._register(addDisposableListener(this.domNode.domNode, 'cut', () => { - this._ensureClipboardGetsEditorSelection(clipboardService); + this._register(addDisposableListener(this.domNode.domNode, 'copy', (e) => this._ensureClipboardGetsEditorSelection(e))); + this._register(addDisposableListener(this.domNode.domNode, 'cut', (e) => { + this._ensureClipboardGetsEditorSelection(e); viewController.cut(); })); @@ -105,7 +111,7 @@ export class NativeEditContext extends AbstractEditContext { })); this._register(addDisposableListener(this.domNode.domNode, 'beforeinput', async (e) => { if (e.inputType === 'insertParagraph' || e.inputType === 'insertLineBreak') { - this._onType(viewController, { text: this._context.viewModel.model.getEOL(), replacePrevCharCnt: 0, replaceNextCharCnt: 0, positionDelta: 0 }); + this._onType(viewController, { text: '\n', replacePrevCharCnt: 0, replaceNextCharCnt: 0, positionDelta: 0 }); } })); @@ -129,6 +135,28 @@ export class NativeEditContext extends AbstractEditContext { // Emits ViewCompositionEndEvent which can be depended on by ViewEventHandlers this._context.viewModel.onCompositionEnd(); })); + this._register(addDisposableListener(this.textArea.domNode, 'paste', (e) => { + e.preventDefault(); + if (!e.clipboardData) { + return; + } + let [text, metadata] = ClipboardEventUtils.getTextData(e.clipboardData); + if (!text) { + return; + } + metadata = metadata || InMemoryClipboardMetadataManager.INSTANCE.get(text); + let pasteOnNewLine = false; + let multicursorText: string[] | null = null; + let mode: string | null = null; + if (metadata) { + const options = this._context.configuration.options; + const emptySelectionClipboard = options.get(EditorOption.emptySelectionClipboard); + pasteOnNewLine = emptySelectionClipboard && !!metadata.isFromEmptySelection; + multicursorText = typeof metadata.multicursorText !== 'undefined' ? metadata.multicursorText : null; + mode = metadata.mode; + } + viewController.paste(text, pasteOnNewLine, multicursorText, mode); + })); } // --- Public methods --- @@ -176,11 +204,20 @@ export class NativeEditContext extends AbstractEditContext { this._screenReaderSupport.writeScreenReaderContent(); } - public isFocused(): boolean { return this._focusTracker.isFocused; } + public isFocused(): boolean { + return this._focusTracker.isFocused || (getActiveWindow().document.activeElement === this.textArea.domNode); + } + + public focus(): void { + this._focusTracker.focus(); - public focus(): void { this._focusTracker.focus(); } + // If the editor is off DOM, focus cannot be really set, so let's double check that we have managed to set the focus + this.refreshFocusState(); + } - public refreshFocusState(): void { } + public refreshFocusState(): void { + this._focusTracker.refreshFocusState(); + } // TODO: added as a workaround fix for https://github.com/microsoft/vscode/issues/229825 // When this issue will be fixed the following should be removed. @@ -234,11 +271,15 @@ export class NativeEditContext extends AbstractEditContext { if (selectionEndOffset > e.updateRangeEnd) { text += this._editContext.text.substring(e.updateRangeEnd, selectionEndOffset); } + let positionDelta = 0; + if (e.selectionStart === e.selectionEnd && selectionStartOffset === selectionEndOffset) { + positionDelta = e.selectionStart - (e.updateRangeStart + e.text.length); + } const typeInput: ITypeData = { text, replacePrevCharCnt, replaceNextCharCnt, - positionDelta: 0, + positionDelta }; this._onType(viewController, typeInput); @@ -381,7 +422,7 @@ export class NativeEditContext extends AbstractEditContext { this._editContext.updateCharacterBounds(e.rangeStart, characterBounds); } - private _ensureClipboardGetsEditorSelection(clipboardService: IClipboardService): void { + private _ensureClipboardGetsEditorSelection(e: ClipboardEvent): void { const options = this._context.configuration.options; const emptySelectionClipboard = options.get(EditorOption.emptySelectionClipboard); const copyWithSyntaxHighlighting = options.get(EditorOption.copyWithSyntaxHighlighting); @@ -399,7 +440,10 @@ export class NativeEditContext extends AbstractEditContext { (isFirefox ? dataToCopy.text.replace(/\r\n/g, '\n') : dataToCopy.text), storedMetadata ); - clipboardService.writeText(dataToCopy.text); + e.preventDefault(); + if (e.clipboardData) { + ClipboardEventUtils.setTextData(e.clipboardData, dataToCopy.text, dataToCopy.html, storedMetadata); + } } private _setSelectionChangeListener(viewController: ViewController): IDisposable { diff --git a/src/vs/editor/browser/controller/editContext/native/nativeEditContextUtils.ts b/src/vs/editor/browser/controller/editContext/native/nativeEditContextUtils.ts index e55e1521fac13..b3166de0437a3 100644 --- a/src/vs/editor/browser/controller/editContext/native/nativeEditContextUtils.ts +++ b/src/vs/editor/browser/controller/editContext/native/nativeEditContextUtils.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { addDisposableListener } from '../../../../../base/browser/dom.js'; +import { addDisposableListener, getActiveWindow } from '../../../../../base/browser/dom.js'; import { IDisposable, Disposable } from '../../../../../base/common/lifecycle.js'; export interface ITypeData { @@ -40,6 +40,11 @@ export class FocusTracker extends Disposable { this._domNode.focus(); } + public refreshFocusState(): void { + const focused = this._domNode === getActiveWindow().document.activeElement; + this._handleFocusedChanged(focused); + } + get isFocused(): boolean { return this._isFocused; } diff --git a/src/vs/editor/browser/controller/editContext/textArea/textAreaEditContext.ts b/src/vs/editor/browser/controller/editContext/textArea/textAreaEditContext.ts index 72887583691f1..a3f8b75544bb0 100644 --- a/src/vs/editor/browser/controller/editContext/textArea/textAreaEditContext.ts +++ b/src/vs/editor/browser/controller/editContext/textArea/textAreaEditContext.ts @@ -474,6 +474,10 @@ export class TextAreaEditContext extends AbstractEditContext { this._textAreaInput.writeNativeTextAreaContent(reason); } + public getTextAreaDomNode(): HTMLTextAreaElement { + return this.textArea.domNode; + } + public override dispose(): void { super.dispose(); this.textArea.domNode.remove(); diff --git a/src/vs/editor/browser/controller/editContext/textArea/textAreaEditContextInput.ts b/src/vs/editor/browser/controller/editContext/textArea/textAreaEditContextInput.ts index 0fa91e15c0f0b..fc2dc0dd5ea16 100644 --- a/src/vs/editor/browser/controller/editContext/textArea/textAreaEditContextInput.ts +++ b/src/vs/editor/browser/controller/editContext/textArea/textAreaEditContextInput.ts @@ -12,14 +12,13 @@ import { RunOnceScheduler } from '../../../../../base/common/async.js'; import { Emitter, Event } from '../../../../../base/common/event.js'; import { KeyCode } from '../../../../../base/common/keyCodes.js'; import { Disposable, IDisposable, MutableDisposable } from '../../../../../base/common/lifecycle.js'; -import { Mimes } from '../../../../../base/common/mime.js'; import { OperatingSystem } from '../../../../../base/common/platform.js'; import * as strings from '../../../../../base/common/strings.js'; import { Position } from '../../../../common/core/position.js'; import { Selection } from '../../../../common/core/selection.js'; import { IAccessibilityService } from '../../../../../platform/accessibility/common/accessibility.js'; import { ILogService } from '../../../../../platform/log/common/log.js'; -import { ClipboardDataToCopy, ClipboardStoredMetadata, InMemoryClipboardMetadataManager } from '../clipboardUtils.js'; +import { ClipboardDataToCopy, ClipboardEventUtils, ClipboardStoredMetadata, InMemoryClipboardMetadataManager } from '../clipboardUtils.js'; import { _debugComposition, ITextAreaWrapper, ITypeData, TextAreaState } from './textAreaEditContextState.js'; export namespace TextAreaSyntethicEvents { @@ -618,41 +617,6 @@ export class TextAreaInput extends Disposable { } } -export const ClipboardEventUtils = { - - getTextData(clipboardData: DataTransfer): [string, ClipboardStoredMetadata | null] { - const text = clipboardData.getData(Mimes.text); - let metadata: ClipboardStoredMetadata | null = null; - const rawmetadata = clipboardData.getData('vscode-editor-data'); - if (typeof rawmetadata === 'string') { - try { - metadata = JSON.parse(rawmetadata); - if (metadata.version !== 1) { - metadata = null; - } - } catch (err) { - // no problem! - } - } - - if (text.length === 0 && metadata === null && clipboardData.files.length > 0) { - // no textual data pasted, generate text from file names - const files: File[] = Array.prototype.slice.call(clipboardData.files, 0); - return [files.map(file => file.name).join('\n'), null]; - } - - return [text, metadata]; - }, - - setTextData(clipboardData: DataTransfer, text: string, html: string | null | undefined, metadata: ClipboardStoredMetadata): void { - clipboardData.setData(Mimes.text, text); - if (typeof html === 'string') { - clipboardData.setData('text/html', html); - } - clipboardData.setData('vscode-editor-data', JSON.stringify(metadata)); - } -}; - export class TextAreaWrapper extends Disposable implements ICompleteTextAreaWrapper { public readonly onKeyDown = this._register(new DomEmitter(this._actual, 'keydown')).event; diff --git a/src/vs/editor/browser/controller/mouseHandler.ts b/src/vs/editor/browser/controller/mouseHandler.ts index cd9b92c4ed038..b1e326d835ac6 100644 --- a/src/vs/editor/browser/controller/mouseHandler.ts +++ b/src/vs/editor/browser/controller/mouseHandler.ts @@ -21,11 +21,13 @@ import { ViewEventHandler } from '../../common/viewEventHandler.js'; import { EditorOption } from '../../common/config/editorOptions.js'; import { NavigationCommandRevealType } from '../coreCommands.js'; import { MouseWheelClassifier } from '../../../base/browser/ui/scrollbar/scrollableElement.js'; +import type { ViewLinesGpu } from '../viewParts/viewLinesGpu/viewLinesGpu.js'; export interface IPointerHandlerHelper { viewDomNode: HTMLElement; linesContentDomNode: HTMLElement; viewLinesDomNode: HTMLElement; + viewLinesGpu: ViewLinesGpu | undefined; focusTextArea(): void; dispatchTextAreaEvent(event: CustomEvent): void; diff --git a/src/vs/editor/browser/controller/mouseTarget.ts b/src/vs/editor/browser/controller/mouseTarget.ts index 737daf6d2947f..28b3995239381 100644 --- a/src/vs/editor/browser/controller/mouseTarget.ts +++ b/src/vs/editor/browser/controller/mouseTarget.ts @@ -22,6 +22,7 @@ import { PositionAffinity } from '../../common/model.js'; import { InjectedText } from '../../common/modelLineProjectionData.js'; import { Mutable } from '../../../base/common/types.js'; import { Lazy } from '../../../base/common/lazy.js'; +import type { ViewLinesGpu } from '../viewParts/viewLinesGpu/viewLinesGpu.js'; const enum HitTestResultType { Unknown, @@ -238,6 +239,7 @@ export class HitTestContext { public readonly viewModel: IViewModel; public readonly layoutInfo: EditorLayoutInfo; public readonly viewDomNode: HTMLElement; + public readonly viewLinesGpu: ViewLinesGpu | undefined; public readonly lineHeight: number; public readonly stickyTabStops: boolean; public readonly typicalHalfwidthCharacterWidth: number; @@ -251,6 +253,7 @@ export class HitTestContext { const options = context.configuration.options; this.layoutInfo = options.get(EditorOption.layoutInfo); this.viewDomNode = viewHelper.viewDomNode; + this.viewLinesGpu = viewHelper.viewLinesGpu; this.lineHeight = options.get(EditorOption.lineHeight); this.stickyTabStops = options.get(EditorOption.stickyTabStops); this.typicalHalfwidthCharacterWidth = options.get(EditorOption.fontInfo).typicalHalfwidthCharacterWidth; @@ -754,6 +757,32 @@ export class MouseTargetFactory { const pos = new Position(lineNumber, ctx.viewModel.getLineMaxColumn(lineNumber)); return request.fulfillContentEmpty(pos, detail); } + } else { + if (ctx.viewLinesGpu) { + const lineNumber = ctx.getLineNumberAtVerticalOffset(request.mouseVerticalOffset); + if (ctx.viewModel.getLineLength(lineNumber) === 0) { + const lineWidth = ctx.getLineWidth(lineNumber); + const detail = createEmptyContentDataInLines(request.mouseContentHorizontalOffset - lineWidth); + return request.fulfillContentEmpty(new Position(lineNumber, 1), detail); + } + + const lineWidth = ctx.getLineWidth(lineNumber); + if (request.mouseContentHorizontalOffset >= lineWidth) { + // TODO: This is wrong for RTL + const detail = createEmptyContentDataInLines(request.mouseContentHorizontalOffset - lineWidth); + const pos = new Position(lineNumber, ctx.viewModel.getLineMaxColumn(lineNumber)); + return request.fulfillContentEmpty(pos, detail); + } + + const position = ctx.viewLinesGpu.getPositionAtCoordinate(lineNumber, request.mouseContentHorizontalOffset); + if (position) { + const detail: IMouseTargetContentTextData = { + injectedText: null, + mightBeForeignElement: false + }; + return request.fulfillContentText(position, EditorRange.fromPositions(position, position), detail); + } + } } // Do the hit test (if not already done) diff --git a/src/vs/editor/browser/editorBrowser.ts b/src/vs/editor/browser/editorBrowser.ts index 7401ec5de61b5..41c455436e962 100644 --- a/src/vs/editor/browser/editorBrowser.ts +++ b/src/vs/editor/browser/editorBrowser.ts @@ -204,7 +204,22 @@ export interface IContentWidget { * widget. Is being invoked with the selected position preference * or `null` if not rendered. */ - afterRender?(position: ContentWidgetPositionPreference | null): void; + afterRender?(position: ContentWidgetPositionPreference | null, coordinate: IContentWidgetRenderedCoordinate | null): void; +} + +/** + * Coordinatees passed in {@link IContentWidget.afterRender} + */ +export interface IContentWidgetRenderedCoordinate { + /** + * Top position relative to the editor content. + */ + readonly top: number; + + /** + * Left position relative to the editor content. + */ + readonly left: number; } /** diff --git a/src/vs/editor/browser/editorDom.ts b/src/vs/editor/browser/editorDom.ts index 75bd4acfae771..38100ab8407d5 100644 --- a/src/vs/editor/browser/editorDom.ts +++ b/src/vs/editor/browser/editorDom.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from '../../base/browser/dom.js'; +import * as domStylesheetsJs from '../../base/browser/domStylesheets.js'; import { GlobalPointerMoveMonitor } from '../../base/browser/globalPointerMoveMonitor.js'; import { StandardMouseEvent } from '../../base/browser/mouseEvent.js'; import { RunOnceScheduler } from '../../base/common/async.js'; @@ -368,7 +369,7 @@ class RefCountedCssRule { public readonly properties: CssProperties, ) { this._styleElementDisposables = new DisposableStore(); - this._styleElement = dom.createStyleSheet(_containerElement, undefined, this._styleElementDisposables); + this._styleElement = domStylesheetsJs.createStyleSheet(_containerElement, undefined, this._styleElementDisposables); this._styleElement.textContent = this.getCssText(this.className, this.properties); } diff --git a/src/vs/editor/browser/editorExtensions.ts b/src/vs/editor/browser/editorExtensions.ts index b9c67238fd567..c8453d78c1f83 100644 --- a/src/vs/editor/browser/editorExtensions.ts +++ b/src/vs/editor/browser/editorExtensions.ts @@ -334,11 +334,15 @@ export interface IEditorActionContextMenuOptions { when?: ContextKeyExpression; menuId?: MenuId; } -export interface IActionOptions extends ICommandOptions { +export type IActionOptions = ICommandOptions & { + contextMenuOpts?: IEditorActionContextMenuOptions | IEditorActionContextMenuOptions[]; +} & ({ + label: nls.ILocalizedString; + alias?: string; +} | { label: string; alias: string; - contextMenuOpts?: IEditorActionContextMenuOptions | IEditorActionContextMenuOptions[]; -} +}); export abstract class EditorAction extends EditorCommand { @@ -358,7 +362,7 @@ export abstract class EditorAction extends EditorCommand { item.menuId = MenuId.EditorContext; } if (!item.title) { - item.title = opts.label; + item.title = typeof opts.label === 'string' ? opts.label : opts.label.value; } item.when = ContextKeyExpr.and(opts.precondition, item.when); return item; @@ -379,8 +383,13 @@ export abstract class EditorAction extends EditorCommand { constructor(opts: IActionOptions) { super(EditorAction.convertOptions(opts)); - this.label = opts.label; - this.alias = opts.alias; + if (typeof opts.label === 'string') { + this.label = opts.label; + this.alias = opts.alias ?? opts.label; + } else { + this.label = opts.label.value; + this.alias = opts.alias ?? opts.label.original; + } } public runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void | Promise { diff --git a/src/vs/editor/browser/gpu/atlas/textureAtlas.ts b/src/vs/editor/browser/gpu/atlas/textureAtlas.ts index e8b8ad73c7a23..fb9ac44f4a8ec 100644 --- a/src/vs/editor/browser/gpu/atlas/textureAtlas.ts +++ b/src/vs/editor/browser/gpu/atlas/textureAtlas.ts @@ -5,6 +5,7 @@ import { getActiveWindow } from '../../../../base/browser/dom.js'; import { CharCode } from '../../../../base/common/charCode.js'; +import { BugIndicatingError } from '../../../../base/common/errors.js'; import { Emitter, Event } from '../../../../base/common/event.js'; import { Disposable, dispose, MutableDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; import { ThreeKeyMap } from '../../../../base/common/map.js'; @@ -22,7 +23,7 @@ export interface ITextureAtlasOptions { } export class TextureAtlas extends Disposable { - private _colorMap!: string[]; + private _colorMap?: string[]; private readonly _warmUpTask: MutableDisposable = this._register(new MutableDisposable()); private readonly _warmedUpRasterizers = new Set(); private readonly _allocatorType: AllocatorType; @@ -60,7 +61,9 @@ export class TextureAtlas extends Disposable { this._allocatorType = options?.allocatorType ?? 'slab'; this._register(Event.runAndSubscribe(this._themeService.onDidColorThemeChange, () => { - // TODO: Clear entire atlas on theme change + if (this._colorMap) { + this.clear(); + } this._colorMap = this._themeService.getColorTheme().tokenColorMap; })); @@ -147,13 +150,17 @@ export class TextureAtlas extends Disposable { * is distrubuted over multiple idle callbacks to avoid blocking the main thread. */ private _warmUpAtlas(rasterizer: IGlyphRasterizer): void { + const colorMap = this._colorMap; + if (!colorMap) { + throw new BugIndicatingError('Cannot warm atlas without color map'); + } this._warmUpTask.value?.clear(); const taskQueue = this._warmUpTask.value = new IdleTaskQueue(); // Warm up using roughly the larger glyphs first to help optimize atlas allocation // A-Z for (let code = CharCode.A; code <= CharCode.Z; code++) { taskQueue.enqueue(() => { - for (const fgColor of this._colorMap.keys()) { + for (const fgColor of colorMap.keys()) { this.getGlyph(rasterizer, String.fromCharCode(code), (fgColor << MetadataConsts.FOREGROUND_OFFSET) & MetadataConsts.FOREGROUND_MASK); } }); @@ -161,7 +168,7 @@ export class TextureAtlas extends Disposable { // a-z for (let code = CharCode.a; code <= CharCode.z; code++) { taskQueue.enqueue(() => { - for (const fgColor of this._colorMap.keys()) { + for (const fgColor of colorMap.keys()) { this.getGlyph(rasterizer, String.fromCharCode(code), (fgColor << MetadataConsts.FOREGROUND_OFFSET) & MetadataConsts.FOREGROUND_MASK); } }); @@ -169,7 +176,7 @@ export class TextureAtlas extends Disposable { // Remaining ascii for (let code = CharCode.ExclamationMark; code <= CharCode.Tilde; code++) { taskQueue.enqueue(() => { - for (const fgColor of this._colorMap.keys()) { + for (const fgColor of colorMap.keys()) { this.getGlyph(rasterizer, String.fromCharCode(code), (fgColor << MetadataConsts.FOREGROUND_OFFSET) & MetadataConsts.FOREGROUND_MASK); } }); diff --git a/src/vs/editor/browser/gpu/atlas/textureAtlasPage.ts b/src/vs/editor/browser/gpu/atlas/textureAtlasPage.ts index a661ef64bccd6..01edf66913051 100644 --- a/src/vs/editor/browser/gpu/atlas/textureAtlasPage.ts +++ b/src/vs/editor/browser/gpu/atlas/textureAtlasPage.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Event } from '../../../../base/common/event.js'; import { Disposable, toDisposable } from '../../../../base/common/lifecycle.js'; import { ThreeKeyMap } from '../../../../base/common/map.js'; import { ILogService, LogLevel } from '../../../../platform/log/common/log.js'; @@ -46,11 +45,12 @@ export class TextureAtlasPage extends Disposable implements IReadableTextureAtla pageSize: number, allocatorType: AllocatorType, @ILogService private readonly _logService: ILogService, - @IThemeService private readonly _themeService: IThemeService, + @IThemeService themeService: IThemeService, ) { super(); this._canvas = new OffscreenCanvas(pageSize, pageSize); + this._colorMap = themeService.getColorTheme().tokenColorMap; switch (allocatorType) { case 'shelf': this._allocator = new TextureAtlasShelfAllocator(this._canvas, textureIndex); break; @@ -58,11 +58,6 @@ export class TextureAtlasPage extends Disposable implements IReadableTextureAtla default: this._allocator = allocatorType(this._canvas, textureIndex); break; } - this._register(Event.runAndSubscribe(this._themeService.onDidColorThemeChange, () => { - // TODO: Clear entire atlas on theme change - this._colorMap = this._themeService.getColorTheme().tokenColorMap; - })); - // Reduce impact of a memory leak if this object is not released this._register(toDisposable(() => { this._canvas.width = 1; @@ -88,6 +83,8 @@ export class TextureAtlasPage extends Disposable implements IReadableTextureAtla // Ensure the glyph was allocated if (glyph === undefined) { + // TODO: undefined here can mean the glyph was too large for a slab on the page, this + // can lead to big problems if we don't handle it properly https://github.com/microsoft/vscode/issues/232984 return undefined; } diff --git a/src/vs/editor/browser/gpu/atlas/textureAtlasSlabAllocator.ts b/src/vs/editor/browser/gpu/atlas/textureAtlasSlabAllocator.ts index b41fed6978cf0..461fd507dbe15 100644 --- a/src/vs/editor/browser/gpu/atlas/textureAtlasSlabAllocator.ts +++ b/src/vs/editor/browser/gpu/atlas/textureAtlasSlabAllocator.ts @@ -55,7 +55,7 @@ export class TextureAtlasSlabAllocator implements ITextureAtlasAllocator { })); this._slabW = Math.min( - options?.slabW ?? (64 << (Math.floor(getActiveWindow().devicePixelRatio) - 1)), + options?.slabW ?? (64 << Math.max(Math.floor(getActiveWindow().devicePixelRatio) - 1, 0)), this._canvas.width ); this._slabH = Math.min( diff --git a/src/vs/editor/browser/gpu/fullFileRenderStrategy.ts b/src/vs/editor/browser/gpu/fullFileRenderStrategy.ts index 561185b7971a8..89707be51d46b 100644 --- a/src/vs/editor/browser/gpu/fullFileRenderStrategy.ts +++ b/src/vs/editor/browser/gpu/fullFileRenderStrategy.ts @@ -5,16 +5,16 @@ import { getActiveWindow } from '../../../base/browser/dom.js'; import { BugIndicatingError } from '../../../base/common/errors.js'; -import { Disposable } from '../../../base/common/lifecycle.js'; import { EditorOption } from '../../common/config/editorOptions.js'; import { CursorColumns } from '../../common/core/cursorColumns.js'; import type { IViewLineTokens } from '../../common/tokens/lineTokens.js'; +import { ViewEventHandler } from '../../common/viewEventHandler.js'; +import type { ViewConfigurationChangedEvent, ViewLinesChangedEvent, ViewLinesDeletedEvent, ViewLinesInsertedEvent, ViewScrollChangedEvent, ViewTokensChangedEvent } from '../../common/viewEvents.js'; import type { ViewportData } from '../../common/viewLayout/viewLinesViewportData.js'; import type { ViewLineRenderingData } from '../../common/viewModel.js'; import type { ViewContext } from '../../common/viewModel/viewContext.js'; import type { ViewLineOptions } from '../viewParts/viewLines/viewLineOptions.js'; import type { ITextureAtlasPageGlyph } from './atlas/atlas.js'; -import type { TextureAtlas } from './atlas/textureAtlas.js'; import { fullFileRenderStrategyWgsl } from './fullFileRenderStrategy.wgsl.js'; import { BindingId, type IGpuRenderStrategy } from './gpu.js'; import { GPULifecycle } from './gpuDisposable.js'; @@ -38,10 +38,7 @@ const enum CellBufferInfo { TextureIndex = 5, } -export class FullFileRenderStrategy extends Disposable implements IGpuRenderStrategy { - - private static _lineCount = 3000; - private static _columnCount = 200; +export class FullFileRenderStrategy extends ViewEventHandler implements IGpuRenderStrategy { readonly wgsl: string = fullFileRenderStrategyWgsl; @@ -58,9 +55,13 @@ export class FullFileRenderStrategy extends Disposable implements IGpuRenderStra private readonly _upToDateLines: [Set, Set] = [new Set(), new Set()]; private _visibleObjectCount: number = 0; + private _finalRenderedLine: number = 0; + + private _scrollOffsetBindBuffer: GPUBuffer; + private _scrollOffsetValueBuffer: Float32Array; + private _scrollInitialized: boolean = false; - private _scrollOffsetBindBuffer!: GPUBuffer; - private _scrollOffsetValueBuffers!: [Float32Array, Float32Array]; + private readonly _queuedBufferUpdates: [ViewLinesDeletedEvent[], ViewLinesDeletedEvent[]] = [[], []]; get bindGroupEntries(): GPUBindGroupEntry[] { return [ @@ -71,19 +72,20 @@ export class FullFileRenderStrategy extends Disposable implements IGpuRenderStra constructor( private readonly _context: ViewContext, + private readonly _viewGpuContext: ViewGpuContext, private readonly _device: GPUDevice, - private readonly _canvas: HTMLCanvasElement, - private readonly _atlas: TextureAtlas, ) { super(); + this._context.addEventHandler(this); + // TODO: Detect when lines have been tokenized and clear _upToDateLines const fontFamily = this._context.configuration.options.get(EditorOption.fontFamily); const fontSize = this._context.configuration.options.get(EditorOption.fontSize); this._glyphRasterizer = this._register(new GlyphRasterizer(fontSize, fontFamily)); - const bufferSize = FullFileRenderStrategy._lineCount * FullFileRenderStrategy._columnCount * Constants.IndicesPerCell * Float32Array.BYTES_PER_ELEMENT; + const bufferSize = this._viewGpuContext.maxGpuLines * this._viewGpuContext.maxGpuCols * Constants.IndicesPerCell * Float32Array.BYTES_PER_ELEMENT; this._cellBindBuffer = this._register(GPULifecycle.createBuffer(this._device, { label: 'Monaco full file cell buffer', size: bufferSize, @@ -100,12 +102,84 @@ export class FullFileRenderStrategy extends Disposable implements IGpuRenderStra size: scrollOffsetBufferSize * Float32Array.BYTES_PER_ELEMENT, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, })).object; - this._scrollOffsetValueBuffers = [ - new Float32Array(scrollOffsetBufferSize), - new Float32Array(scrollOffsetBufferSize), - ]; + this._scrollOffsetValueBuffer = new Float32Array(scrollOffsetBufferSize); + } + + // #region Event handlers + + public override onConfigurationChanged(e: ViewConfigurationChangedEvent): boolean { + this._upToDateLines[0].clear(); + this._upToDateLines[1].clear(); + return true; + } + + public override onTokensChanged(e: ViewTokensChangedEvent): boolean { + // TODO: This currently fires for the entire viewport whenever scrolling stops + // https://github.com/microsoft/vscode/issues/233942 + for (const range of e.ranges) { + for (let i = range.fromLineNumber; i <= range.toLineNumber; i++) { + this._upToDateLines[0].delete(i); + this._upToDateLines[1].delete(i); + } + } + return true; + } + + public override onLinesDeleted(e: ViewLinesDeletedEvent): boolean { + // TODO: This currently invalidates everything after the deleted line, it could shift the + // line data up to retain some up to date lines + // TODO: This does not invalidate lines that are no longer in the file + for (const i of [0, 1]) { + const upToDateLines = this._upToDateLines[i]; + const lines = Array.from(upToDateLines); + for (const upToDateLine of lines) { + if (upToDateLine > e.fromLineNumber) { + upToDateLines.delete(upToDateLine); + } + } + } + + // Queue updates that need to happen on the active buffer, not just the cache. This is + // deferred since the active buffer could be locked by the GPU which would block the main + // thread. + this._queueBufferUpdate(e); + + return true; + } + + public override onLinesInserted(e: ViewLinesInsertedEvent): boolean { + // TODO: This currently invalidates everything after the deleted line, it could shift the + // line data up to retain some up to date lines + for (const i of [0, 1]) { + const upToDateLines = this._upToDateLines[i]; + const lines = Array.from(upToDateLines); + for (const upToDateLine of lines) { + if (upToDateLine > e.fromLineNumber) { + upToDateLines.delete(upToDateLine); + } + } + } + return true; + } + + public override onLinesChanged(e: ViewLinesChangedEvent): boolean { + for (let i = e.fromLineNumber; i < e.fromLineNumber + e.count; i++) { + this._upToDateLines[0].delete(i); + this._upToDateLines[1].delete(i); + } + return true; + } + + public override onScrollChanged(e?: ViewScrollChangedEvent): boolean { + const dpr = getActiveWindow().devicePixelRatio; + this._scrollOffsetValueBuffer[0] = (e?.scrollLeft ?? this._context.viewLayout.getCurrentScrollLeft()) * dpr; + this._scrollOffsetValueBuffer[1] = (e?.scrollTop ?? this._context.viewLayout.getCurrentScrollTop()) * dpr; + this._device.queue.writeBuffer(this._scrollOffsetBindBuffer, 0, this._scrollOffsetValueBuffer); + return true; } + // #endregion + reset() { for (const bufferIndex of [0, 1]) { // Zero out buffer and upload to GPU to prevent stale rows from rendering @@ -123,12 +197,8 @@ export class FullFileRenderStrategy extends Disposable implements IGpuRenderStra let chars = ''; let y = 0; let x = 0; - let screenAbsoluteX = 0; - let screenAbsoluteY = 0; - let zeroToOneX = 0; - let zeroToOneY = 0; - let wgslX = 0; - let wgslY = 0; + let absoluteOffsetX = 0; + let absoluteOffsetY = 0; let xOffset = 0; let glyph: Readonly; let cellIndex = 0; @@ -146,31 +216,53 @@ export class FullFileRenderStrategy extends Disposable implements IGpuRenderStra const dpr = getActiveWindow().devicePixelRatio; - // Update scroll offset - const scrollOffsetBuffer = this._scrollOffsetValueBuffers[this._activeDoubleBufferIndex]; - scrollOffsetBuffer[0] = this._context.viewLayout.getCurrentScrollLeft() * dpr; - scrollOffsetBuffer[1] = this._context.viewLayout.getCurrentScrollTop() * dpr; - this._device.queue.writeBuffer(this._scrollOffsetBindBuffer, 0, scrollOffsetBuffer); + if (!this._scrollInitialized) { + this.onScrollChanged(); + this._scrollInitialized = true; + } // Update cell data const cellBuffer = new Float32Array(this._cellValueBuffers[this._activeDoubleBufferIndex]); - const lineIndexCount = FullFileRenderStrategy._columnCount * Constants.IndicesPerCell; + const lineIndexCount = this._viewGpuContext.maxGpuCols * Constants.IndicesPerCell; const upToDateLines = this._upToDateLines[this._activeDoubleBufferIndex]; let dirtyLineStart = Number.MAX_SAFE_INTEGER; let dirtyLineEnd = 0; + // Handle any queued buffer updates + const queuedBufferUpdates = this._queuedBufferUpdates[this._activeDoubleBufferIndex]; + while (queuedBufferUpdates.length) { + const e = queuedBufferUpdates.shift()!; + + // Shift content below deleted line up + const deletedLineContentStartIndex = (e.fromLineNumber - 1) * this._viewGpuContext.maxGpuCols * Constants.IndicesPerCell; + const deletedLineContentEndIndex = (e.toLineNumber) * this._viewGpuContext.maxGpuCols * Constants.IndicesPerCell; + const nullContentStartIndex = (this._finalRenderedLine - (e.toLineNumber - e.fromLineNumber + 1)) * this._viewGpuContext.maxGpuCols * Constants.IndicesPerCell; + cellBuffer.set(cellBuffer.subarray(deletedLineContentEndIndex), deletedLineContentStartIndex); + + // Zero out content on lines that are no longer valid + cellBuffer.fill(0, nullContentStartIndex); + + // Update dirty lines and final rendered line + dirtyLineStart = Math.min(dirtyLineStart, e.fromLineNumber); + dirtyLineEnd = this._finalRenderedLine; + this._finalRenderedLine -= e.toLineNumber - e.fromLineNumber + 1; + } + for (y = viewportData.startLineNumber; y <= viewportData.endLineNumber; y++) { // Only attempt to render lines that the GPU renderer can handle if (!ViewGpuContext.canRender(viewLineOptions, viewportData, y)) { + fillStartIndex = ((y - 1) * this._viewGpuContext.maxGpuCols) * Constants.IndicesPerCell; + fillEndIndex = (y * this._viewGpuContext.maxGpuCols) * Constants.IndicesPerCell; + cellBuffer.fill(0, fillStartIndex, fillEndIndex); continue; } - // TODO: Update on dirty lines; is this known by line before rendering? - // if (upToDateLines.has(y)) { - // continue; - // } + // Skip updating the line if it's already up to date + if (upToDateLines.has(y)) { + continue; + } dirtyLineStart = Math.min(dirtyLineStart, y); dirtyLineEnd = Math.max(dirtyLineEnd, y); @@ -178,29 +270,6 @@ export class FullFileRenderStrategy extends Disposable implements IGpuRenderStra content = lineData.content; xOffset = 0; - // See ViewLine#renderLine - // const renderLineInput = new RenderLineInput( - // options.useMonospaceOptimizations, - // options.canUseHalfwidthRightwardsArrow, - // lineData.content, - // lineData.continuesWithWrappedLine, - // lineData.isBasicASCII, - // lineData.containsRTL, - // lineData.minColumn - 1, - // lineData.tokens, - // actualInlineDecorations, - // lineData.tabSize, - // lineData.startVisibleColumn, - // options.spaceWidth, - // options.middotWidth, - // options.wsmiddotWidth, - // options.stopRenderingLineAfter, - // options.renderWhitespace, - // options.renderControlCharacters, - // options.fontLigatures !== EditorFontLigatures.OFF, - // selectionsOnLine - // ); - tokens = lineData.tokens; tokenStartIndex = lineData.minColumn - 1; tokenEndIndex = 0; @@ -211,32 +280,30 @@ export class FullFileRenderStrategy extends Disposable implements IGpuRenderStra continue; } - tokenMetadata = tokens.getMetadata(tokenIndex); - // console.log(`token: start=${tokenStartIndex}, end=${tokenEndIndex}, fg=${colorMap[tokenFg]}`); - - for (x = tokenStartIndex; x < tokenEndIndex; x++) { - // HACK: Prevent rendering past the end of the render buffer // TODO: This needs to move to a dynamic long line rendering strategy - if (x > FullFileRenderStrategy._columnCount) { + if (x > this._viewGpuContext.maxGpuCols) { break; } chars = content.charAt(x); - if (chars === ' ') { - continue; - } - if (chars === '\t') { - xOffset = CursorColumns.nextRenderTabStop(x + xOffset, lineData.tabSize) - x - 1; + if (chars === ' ' || chars === '\t') { + // Zero out glyph to ensure it doesn't get rendered + cellIndex = ((y - 1) * this._viewGpuContext.maxGpuCols + x) * Constants.IndicesPerCell; + cellBuffer.fill(0, cellIndex, cellIndex + CellBufferInfo.FloatsPerEntry); + // Adjust xOffset for tab stops + if (chars === '\t') { + xOffset = CursorColumns.nextRenderTabStop(x + xOffset, lineData.tabSize) - x - 1; + } continue; } - glyph = this._atlas.getGlyph(this._glyphRasterizer, chars, tokenMetadata); + glyph = this._viewGpuContext.atlas.getGlyph(this._glyphRasterizer, chars, tokenMetadata); // TODO: Support non-standard character widths - screenAbsoluteX = Math.round((x + xOffset) * viewLineOptions.spaceWidth * dpr); - screenAbsoluteY = ( + absoluteOffsetX = Math.round((x + xOffset) * viewLineOptions.spaceWidth * dpr); + absoluteOffsetY = ( Math.ceil(( // Top of line including line height viewportData.relativeVerticalOffset[y - viewportData.startLineNumber] + @@ -244,14 +311,10 @@ export class FullFileRenderStrategy extends Disposable implements IGpuRenderStra Math.floor((viewportData.lineHeight - this._context.configuration.options.get(EditorOption.fontSize)) / 2) ) * dpr) ); - zeroToOneX = screenAbsoluteX / this._canvas.width; - zeroToOneY = screenAbsoluteY / this._canvas.height; - wgslX = zeroToOneX * 2 - 1; - wgslY = zeroToOneY * 2 - 1; - - cellIndex = ((y - 1) * FullFileRenderStrategy._columnCount + (x + xOffset)) * Constants.IndicesPerCell; - cellBuffer[cellIndex + CellBufferInfo.Offset_X] = wgslX; - cellBuffer[cellIndex + CellBufferInfo.Offset_Y] = -wgslY; + + cellIndex = ((y - 1) * this._viewGpuContext.maxGpuCols + x) * Constants.IndicesPerCell; + cellBuffer[cellIndex + CellBufferInfo.Offset_X] = absoluteOffsetX; + cellBuffer[cellIndex + CellBufferInfo.Offset_Y] = absoluteOffsetY; cellBuffer[cellIndex + CellBufferInfo.GlyphIndex] = glyph.glyphIndex; cellBuffer[cellIndex + CellBufferInfo.TextureIndex] = glyph.pageIndex; } @@ -260,8 +323,8 @@ export class FullFileRenderStrategy extends Disposable implements IGpuRenderStra } // Clear to end of line - fillStartIndex = ((y - 1) * FullFileRenderStrategy._columnCount + (tokenEndIndex + xOffset)) * Constants.IndicesPerCell; - fillEndIndex = (y * FullFileRenderStrategy._columnCount) * Constants.IndicesPerCell; + fillStartIndex = ((y - 1) * this._viewGpuContext.maxGpuCols + tokenEndIndex) * Constants.IndicesPerCell; + fillEndIndex = (y * this._viewGpuContext.maxGpuCols) * Constants.IndicesPerCell; cellBuffer.fill(0, fillStartIndex, fillEndIndex); upToDateLines.add(y); @@ -281,6 +344,8 @@ export class FullFileRenderStrategy extends Disposable implements IGpuRenderStra ); } + this._finalRenderedLine = Math.max(this._finalRenderedLine, dirtyLineEnd); + this._activeDoubleBufferIndex = this._activeDoubleBufferIndex ? 0 : 1; this._visibleObjectCount = visibleObjectCount; @@ -295,7 +360,12 @@ export class FullFileRenderStrategy extends Disposable implements IGpuRenderStra quadVertices.length / 2, this._visibleObjectCount, undefined, - (viewportData.startLineNumber - 1) * FullFileRenderStrategy._columnCount + (viewportData.startLineNumber - 1) * this._viewGpuContext.maxGpuCols ); } + + private _queueBufferUpdate(e: ViewLinesDeletedEvent) { + this._queuedBufferUpdates[0].push(e); + this._queuedBufferUpdates[1].push(e); + } } diff --git a/src/vs/editor/browser/gpu/fullFileRenderStrategy.wgsl.ts b/src/vs/editor/browser/gpu/fullFileRenderStrategy.wgsl.ts index c5072ffb61691..531986de396f9 100644 --- a/src/vs/editor/browser/gpu/fullFileRenderStrategy.wgsl.ts +++ b/src/vs/editor/browser/gpu/fullFileRenderStrategy.wgsl.ts @@ -67,7 +67,12 @@ struct VSOutput { var vsOut: VSOutput; // Multiple vert.position by 2,-2 to get it into clipspace which ranged from -1 to 1 vsOut.position = vec4f( - (((vert.position * vec2f(2, -2)) / layoutInfo.canvasDims)) * glyph.size + cell.position + ((glyph.origin * vec2f(2, -2)) / layoutInfo.canvasDims) + (((layoutInfo.viewportOffset - scrollOffset.offset * vec2(1, -1)) * 2) / layoutInfo.canvasDims), + // Make everything relative to top left instead of center + vec2f(-1, 1) + + ((vert.position * vec2f(2, -2)) / layoutInfo.canvasDims) * glyph.size + + ((cell.position * vec2f(2, -2)) / layoutInfo.canvasDims) + + ((glyph.origin * vec2f(2, -2)) / layoutInfo.canvasDims) + + (((layoutInfo.viewportOffset - scrollOffset.offset * vec2(1, -1)) * 2) / layoutInfo.canvasDims), 0.0, 1.0 ); diff --git a/src/vs/editor/browser/gpu/gpu.ts b/src/vs/editor/browser/gpu/gpu.ts index 3d4f2a28453dd..8d965fcc5aab8 100644 --- a/src/vs/editor/browser/gpu/gpu.ts +++ b/src/vs/editor/browser/gpu/gpu.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import type { ViewConfigurationChangedEvent, ViewLinesChangedEvent, ViewLinesDeletedEvent, ViewLinesInsertedEvent, ViewScrollChangedEvent, ViewTokensChangedEvent } from '../../common/viewEvents.js'; import type { ViewportData } from '../../common/viewLayout/viewLinesViewportData.js'; import type { ViewLineOptions } from '../viewParts/viewLines/viewLineOptions.js'; @@ -21,6 +22,13 @@ export interface IGpuRenderStrategy { readonly wgsl: string; readonly bindGroupEntries: GPUBindGroupEntry[]; + onLinesDeleted(e: ViewLinesDeletedEvent): boolean; + onConfigurationChanged(e: ViewConfigurationChangedEvent): boolean; + onTokensChanged(e: ViewTokensChangedEvent): boolean; + onLinesInserted(e: ViewLinesInsertedEvent): boolean; + onLinesChanged(e: ViewLinesChangedEvent): boolean; + onScrollChanged(e?: ViewScrollChangedEvent): boolean; + /** * Resets the render strategy, clearing all data and setting up for a new frame. */ diff --git a/src/vs/editor/browser/gpu/rectangleRenderer.ts b/src/vs/editor/browser/gpu/rectangleRenderer.ts index 0859281f48b31..d0e087dbbc8a1 100644 --- a/src/vs/editor/browser/gpu/rectangleRenderer.ts +++ b/src/vs/editor/browser/gpu/rectangleRenderer.ts @@ -42,7 +42,6 @@ export class RectangleRenderer extends ViewEventHandler { private _scrollOffsetValueBuffer!: Float32Array; private _initialized: boolean = false; - private _scrollChanged: boolean = true; private readonly _shapeCollection: IObjectCollectionBuffer = this._register(createObjectCollectionBuffer([ { name: 'x' }, @@ -242,29 +241,29 @@ export class RectangleRenderer extends ViewEventHandler { return this._shapeCollection.createEntry({ x, y, width, height, red, green, blue, alpha }); } - // --- begin event handlers + // #region Event handlers public override onScrollChanged(e: ViewScrollChangedEvent): boolean { - this._scrollChanged = true; - return super.onScrollChanged(e); + if (this._device) { + const dpr = getActiveWindow().devicePixelRatio; + this._scrollOffsetValueBuffer[0] = this._context.viewLayout.getCurrentScrollLeft() * dpr; + this._scrollOffsetValueBuffer[1] = this._context.viewLayout.getCurrentScrollTop() * dpr; + this._device.queue.writeBuffer(this._scrollOffsetBindBuffer, 0, this._scrollOffsetValueBuffer); + } + return true; } - // --- end event handlers + // #endregion private _update() { + if (!this._device) { + return; + } const shapes = this._shapeCollection; if (shapes.dirtyTracker.isDirty) { this._device.queue.writeBuffer(this._shapeBindBuffer.value!.object, 0, shapes.buffer, shapes.dirtyTracker.dataOffset, shapes.dirtyTracker.dirtySize! * shapes.view.BYTES_PER_ELEMENT); shapes.dirtyTracker.clear(); } - - // Update scroll offset - if (this._scrollChanged) { - const dpr = getActiveWindow().devicePixelRatio; - this._scrollOffsetValueBuffer[0] = this._context.viewLayout.getCurrentScrollLeft() * dpr; - this._scrollOffsetValueBuffer[1] = this._context.viewLayout.getCurrentScrollTop() * dpr; - this._device.queue.writeBuffer(this._scrollOffsetBindBuffer, 0, this._scrollOffsetValueBuffer); - } } draw(viewportData: ViewportData) { diff --git a/src/vs/editor/browser/gpu/viewGpuContext.ts b/src/vs/editor/browser/gpu/viewGpuContext.ts index 877c1b3a8680d..307636b37e1fe 100644 --- a/src/vs/editor/browser/gpu/viewGpuContext.ts +++ b/src/vs/editor/browser/gpu/viewGpuContext.ts @@ -20,7 +20,24 @@ import { ensureNonNullable, observeDevicePixelDimensions } from './gpuUtils.js'; import { RectangleRenderer } from './rectangleRenderer.js'; import type { ViewContext } from '../../common/viewModel/viewContext.js'; +const enum GpuRenderLimits { + maxGpuLines = 3000, + maxGpuCols = 200, +} + export class ViewGpuContext extends Disposable { + /** + * The temporary hard cap for lines rendered by the GPU renderer. This can be removed once more + * dynamic allocation is implemented in https://github.com/microsoft/vscode/issues/227091 + */ + readonly maxGpuLines = GpuRenderLimits.maxGpuLines; + + /** + * The temporary hard cap for line columns rendered by the GPU renderer. This can be removed + * once more dynamic allocation is implemented in https://github.com/microsoft/vscode/issues/227108 + */ + readonly maxGpuCols = GpuRenderLimits.maxGpuCols; + readonly canvas: FastDomNode; readonly ctx: GPUCanvasContext; @@ -112,12 +129,37 @@ export class ViewGpuContext extends Disposable { const data = viewportData.getViewLineRenderingData(lineNumber); if ( data.containsRTL || - data.maxColumn > 200 || + data.maxColumn > GpuRenderLimits.maxGpuCols || data.continuesWithWrappedLine || - data.inlineDecorations.length > 0 + data.inlineDecorations.length > 0 || + lineNumber >= GpuRenderLimits.maxGpuLines ) { return false; } return true; } + + /** + * Like {@link canRender} but returned detailed information about why the line cannot be rendered. + */ + public static canRenderDetailed(options: ViewLineOptions, viewportData: ViewportData, lineNumber: number): string[] { + const data = viewportData.getViewLineRenderingData(lineNumber); + const reasons: string[] = []; + if (data.containsRTL) { + reasons.push('containsRTL'); + } + if (data.maxColumn > GpuRenderLimits.maxGpuCols) { + reasons.push('maxColumn > maxGpuCols'); + } + if (data.continuesWithWrappedLine) { + reasons.push('continuesWithWrappedLine'); + } + if (data.inlineDecorations.length > 0) { + reasons.push('inlineDecorations > 0'); + } + if (lineNumber >= GpuRenderLimits.maxGpuLines) { + reasons.push('lineNumber >= maxGpuLines'); + } + return reasons; + } } diff --git a/src/vs/editor/browser/observableCodeEditor.ts b/src/vs/editor/browser/observableCodeEditor.ts index 5747a39c438bc..4ada6de5c8a30 100644 --- a/src/vs/editor/browser/observableCodeEditor.ts +++ b/src/vs/editor/browser/observableCodeEditor.ts @@ -167,6 +167,24 @@ export class ObservableCodeEditor extends Disposable { }; }, () => this.editor.hasWidgetFocus()); + private _inComposition = false; + public readonly inComposition = observableFromEvent(this, e => { + const d1 = this.editor.onDidCompositionStart(() => { + this._inComposition = true; + e(undefined); + }); + const d2 = this.editor.onDidCompositionEnd(() => { + this._inComposition = false; + e(undefined); + }); + return { + dispose() { + d1.dispose(); + d2.dispose(); + } + }; + }, () => this._inComposition); + public readonly value = derivedWithSetter(this, reader => { this.versionId.read(reader); return this.model.read(reader)?.getValue() ?? ''; }, (value, tx) => { diff --git a/src/vs/editor/browser/services/abstractCodeEditorService.ts b/src/vs/editor/browser/services/abstractCodeEditorService.ts index 3e927a9a437df..0743bcae74ad5 100644 --- a/src/vs/editor/browser/services/abstractCodeEditorService.ts +++ b/src/vs/editor/browser/services/abstractCodeEditorService.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from '../../../base/browser/dom.js'; +import * as domStylesheets from '../../../base/browser/domStylesheets.js'; import * as cssJs from '../../../base/browser/cssValue.js'; import { Emitter, Event } from '../../../base/common/event.js'; import { IDisposable, DisposableStore, Disposable, toDisposable } from '../../../base/common/lifecycle.js'; @@ -128,7 +129,7 @@ export abstract class AbstractCodeEditorService extends Disposable implements IC } protected _createGlobalStyleSheet(): GlobalStyleSheet { - return new GlobalStyleSheet(dom.createStyleSheet()); + return new GlobalStyleSheet(domStylesheets.createStyleSheet()); } private _getOrCreateStyleSheet(editor: ICodeEditor | undefined): GlobalStyleSheet | RefCountedStyleSheet { @@ -141,7 +142,7 @@ export abstract class AbstractCodeEditorService extends Disposable implements IC } const editorId = editor.getId(); if (!this._editorStyleSheets.has(editorId)) { - const refCountedStyleSheet = new RefCountedStyleSheet(this, editorId, dom.createStyleSheet(domNode)); + const refCountedStyleSheet = new RefCountedStyleSheet(this, editorId, domStylesheets.createStyleSheet(domNode)); this._editorStyleSheets.set(editorId, refCountedStyleSheet); } return this._editorStyleSheets.get(editorId)!; @@ -348,11 +349,11 @@ class RefCountedStyleSheet { } public insertRule(selector: string, rule: string): void { - dom.createCSSRule(selector, rule, this._styleSheet); + domStylesheets.createCSSRule(selector, rule, this._styleSheet); } public removeRulesContainingSelector(ruleName: string): void { - dom.removeCSSRulesContainingSelector(ruleName, this._styleSheet); + domStylesheets.removeCSSRulesContainingSelector(ruleName, this._styleSheet); } } @@ -374,11 +375,11 @@ export class GlobalStyleSheet { } public insertRule(selector: string, rule: string): void { - dom.createCSSRule(selector, rule, this._styleSheet); + domStylesheets.createCSSRule(selector, rule, this._styleSheet); } public removeRulesContainingSelector(ruleName: string): void { - dom.removeCSSRulesContainingSelector(ruleName, this._styleSheet); + domStylesheets.removeCSSRulesContainingSelector(ruleName, this._styleSheet); } } diff --git a/src/vs/editor/browser/services/hoverService/hover.css b/src/vs/editor/browser/services/hoverService/hover.css index 87ed1d3c84ec4..21f7221bf68ee 100644 --- a/src/vs/editor/browser/services/hoverService/hover.css +++ b/src/vs/editor/browser/services/hoverService/hover.css @@ -22,10 +22,6 @@ border-bottom: none; } -.monaco-workbench .workbench-hover:not(.skip-fade-in) { - animation: fadein 100ms linear; -} - .monaco-workbench .workbench-hover.compact { font-size: 12px; } diff --git a/src/vs/editor/browser/services/hoverService/hoverService.ts b/src/vs/editor/browser/services/hoverService/hoverService.ts index 249aed774b75e..b4164c04d1d9f 100644 --- a/src/vs/editor/browser/services/hoverService/hoverService.ts +++ b/src/vs/editor/browser/services/hoverService/hoverService.ts @@ -20,10 +20,13 @@ import { IAccessibilityService } from '../../../../platform/accessibility/common import { ILayoutService } from '../../../../platform/layout/browser/layoutService.js'; import { mainWindow } from '../../../../base/browser/window.js'; import { ContextViewHandler } from '../../../../platform/contextview/browser/contextViewService.js'; -import type { IHoverOptions, IHoverWidget, IManagedHover, IManagedHoverContentOrFactory, IManagedHoverOptions } from '../../../../base/browser/ui/hover/hover.js'; +import type { IHoverLifecycleOptions, IHoverOptions, IHoverWidget, IManagedHover, IManagedHoverContentOrFactory, IManagedHoverOptions } from '../../../../base/browser/ui/hover/hover.js'; import type { IHoverDelegate, IHoverDelegateTarget } from '../../../../base/browser/ui/hover/hoverDelegate.js'; import { ManagedHoverWidget } from './updatableHoverWidget.js'; -import { TimeoutTimer } from '../../../../base/common/async.js'; +import { timeout, TimeoutTimer } from '../../../../base/common/async.js'; +import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; +import { isNumber } from '../../../../base/common/types.js'; +import { KeyCode } from '../../../../base/common/keyCodes.js'; export class HoverService extends Disposable implements IHoverService { declare readonly _serviceBrand: undefined; @@ -31,12 +34,16 @@ export class HoverService extends Disposable implements IHoverService { private _contextViewHandler: IContextViewProvider; private _currentHoverOptions: IHoverOptions | undefined; private _currentHover: HoverWidget | undefined; + private _currentDelayedHover: HoverWidget | undefined; + private _currentDelayedHoverWasShown: boolean = false; + private _currentDelayedHoverGroupId: number | string | undefined; private _lastHoverOptions: IHoverOptions | undefined; private _lastFocusedElementBeforeOpen: HTMLElement | undefined; constructor( @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IConfigurationService private readonly _configurationService: IConfigurationService, @IContextMenuService contextMenuService: IContextMenuService, @IKeybindingService private readonly _keybindingService: IKeybindingService, @ILayoutService private readonly _layoutService: ILayoutService, @@ -48,13 +55,123 @@ export class HoverService extends Disposable implements IHoverService { this._contextViewHandler = this._register(new ContextViewHandler(this._layoutService)); } - showHover(options: IHoverOptions, focus?: boolean, skipLastFocusedUpdate?: boolean): IHoverWidget | undefined { - if (getHoverOptionsIdentity(this._currentHoverOptions) === getHoverOptionsIdentity(options)) { + showHover(options: IHoverOptions, focus?: boolean, skipLastFocusedUpdate?: boolean, dontShow?: boolean): IHoverWidget | undefined { + const hover = this._createHover(options, skipLastFocusedUpdate); + if (!hover) { return undefined; } + this._showHover(hover, options, focus); + return hover; + } + + showDelayedHover( + options: IHoverOptions, + lifecycleOptions: Pick, + ): IHoverWidget | undefined { + if (!this._currentDelayedHover || this._currentDelayedHoverWasShown) { + // Current hover is sticky, reject + if (this._currentHover && this._currentHoverOptions?.persistence?.sticky) { + return undefined; + } + + // Identity is the same, return current hover + if (getHoverOptionsIdentity(this._currentHoverOptions) === getHoverOptionsIdentity(options)) { + return this._currentHover; + } + + // Check group identity, if it's the same skip the delay and show the hover immediately + if (this._currentHover && !this._currentHover.isDisposed && this._currentDelayedHoverGroupId !== undefined && this._currentDelayedHoverGroupId === lifecycleOptions?.groupId) { + return this.showHover({ + ...options, + appearance: { + ...options.appearance, + skipFadeInAnimation: true + } + }); + } + } + + const hover = this._createHover(options, undefined); + if (!hover) { + this._currentDelayedHover = undefined; + this._currentDelayedHoverWasShown = false; + this._currentDelayedHoverGroupId = undefined; + return undefined; + } + + this._currentDelayedHover = hover; + this._currentDelayedHoverWasShown = false; + this._currentDelayedHoverGroupId = lifecycleOptions?.groupId; + + timeout(this._configurationService.getValue('workbench.hover.delay')).then(() => { + if (hover && !hover.isDisposed) { + this._currentDelayedHoverWasShown = true; + this._currentDelayedHoverWasShown = true; + this._showHover(hover, options); + } + }); + + return hover; + } + + setupDelayedHover( + target: HTMLElement, + options: (() => Omit) | Omit, + lifecycleOptions?: IHoverLifecycleOptions, + ): IDisposable { + const resolveHoverOptions = () => ({ + ...typeof options === 'function' ? options() : options, + target + } satisfies IHoverOptions); + return this._setupDelayedHover(target, resolveHoverOptions, lifecycleOptions); + } + + setupDelayedHoverAtMouse( + target: HTMLElement, + options: (() => Omit) | Omit, + lifecycleOptions?: IHoverLifecycleOptions, + ): IDisposable { + const resolveHoverOptions = (e?: MouseEvent) => ({ + ...typeof options === 'function' ? options() : options, + target: { + targetElements: [target], + x: e !== undefined ? e.x + 10 : undefined, + } + } satisfies IHoverOptions); + return this._setupDelayedHover(target, resolveHoverOptions, lifecycleOptions); + } + + private _setupDelayedHover( + target: HTMLElement, + resolveHoverOptions: ((e?: MouseEvent) => IHoverOptions), + lifecycleOptions?: IHoverLifecycleOptions, + ) { + const store = new DisposableStore(); + store.add(addDisposableListener(target, EventType.MOUSE_OVER, e => { + this.showDelayedHover(resolveHoverOptions(e), { + groupId: lifecycleOptions?.groupId + }); + })); + if (lifecycleOptions?.setupKeyboardEvents) { + store.add(addDisposableListener(target, EventType.KEY_DOWN, e => { + const evt = new StandardKeyboardEvent(e); + if (evt.equals(KeyCode.Space) || evt.equals(KeyCode.Enter)) { + this.showHover(resolveHoverOptions(), true); + } + })); + } + return store; + } + + private _createHover(options: IHoverOptions, skipLastFocusedUpdate?: boolean): HoverWidget | undefined { + this._currentDelayedHover = undefined; + if (this._currentHover && this._currentHoverOptions?.persistence?.sticky) { return undefined; } + if (getHoverOptionsIdentity(this._currentHoverOptions) === getHoverOptionsIdentity(options)) { + return undefined; + } this._currentHoverOptions = options; this._lastHoverOptions = options; const trapFocus = options.trapFocus || this._accessibilityService.isScreenReaderOptimized(); @@ -69,11 +186,30 @@ export class HoverService extends Disposable implements IHoverService { this._lastFocusedElementBeforeOpen = undefined; } } + + // Set `id` to default if it's undefined + if (options.id === undefined) { + options.id = isHTMLElement(options.content) + ? undefined + : typeof options.content === 'string' + ? options.content.toString() + : options.content.value; + } + const hoverDisposables = new DisposableStore(); const hover = this._instantiationService.createInstance(HoverWidget, options); if (options.persistence?.sticky) { hover.isLocked = true; } + + // Adjust target position when a mouse event is provided as the hover position + if (options.position?.hoverPosition && !isNumber(options.position.hoverPosition)) { + options.target = { + targetElements: isHTMLElement(options.target) ? [options.target] : options.target.targetElements, + x: options.position.hoverPosition.x + 10 + }; + } + hover.onDispose(() => { const hoverWasFocused = this._currentHover?.domNode && isAncestorOfActiveElement(this._currentHover.domNode); if (hoverWasFocused) { @@ -83,7 +219,7 @@ export class HoverService extends Disposable implements IHoverService { // Only clear the current options if it's the current hover, the current options help // reduce flickering when the same hover is shown multiple times - if (this._currentHoverOptions === options) { + if (getHoverOptionsIdentity(this._currentHoverOptions) === getHoverOptionsIdentity(options)) { this._currentHoverOptions = undefined; } hoverDisposables.dispose(); @@ -94,10 +230,6 @@ export class HoverService extends Disposable implements IHoverService { options.container = this._layoutService.getContainer(getWindow(targetElement)); } - this._contextViewHandler.showContextView( - new HoverContextViewDelegate(hover, focus), - options.container - ); hover.onRequestLayout(() => this._contextViewHandler.layout(), undefined, hoverDisposables); if (options.persistence?.sticky) { hoverDisposables.add(addDisposableListener(getWindow(options.container).document, EventType.MOUSE_DOWN, e => { @@ -135,6 +267,13 @@ export class HoverService extends Disposable implements IHoverService { return hover; } + private _showHover(hover: HoverWidget, options: IHoverOptions, focus?: boolean) { + this._contextViewHandler.showContextView( + new HoverContextViewDelegate(hover, focus), + options.container + ); + } + hideHover(): void { if (this._currentHover?.isLocked || !this._currentHoverOptions) { return; @@ -194,7 +333,6 @@ export class HoverService extends Disposable implements IHoverService { // TODO: Investigate performance of this function. There seems to be a lot of content created // and thrown away on start up setupManagedHover(hoverDelegate: IHoverDelegate, targetElement: HTMLElement, content: IManagedHoverContentOrFactory, options?: IManagedHoverOptions | undefined): IManagedHover { - targetElement.setAttribute('custom-hover', 'true'); if (targetElement.title !== '') { @@ -231,25 +369,25 @@ export class HoverService extends Disposable implements IHoverService { }, delay); }; + const store = new DisposableStore(); let isMouseDown = false; - const mouseDownEmitter = addDisposableListener(targetElement, EventType.MOUSE_DOWN, () => { + store.add(addDisposableListener(targetElement, EventType.MOUSE_DOWN, () => { isMouseDown = true; hideHover(true, true); - }, true); - const mouseUpEmitter = addDisposableListener(targetElement, EventType.MOUSE_UP, () => { + }, true)); + store.add(addDisposableListener(targetElement, EventType.MOUSE_UP, () => { isMouseDown = false; - }, true); - const mouseLeaveEmitter = addDisposableListener(targetElement, EventType.MOUSE_LEAVE, (e: MouseEvent) => { + }, true)); + store.add(addDisposableListener(targetElement, EventType.MOUSE_LEAVE, (e: MouseEvent) => { isMouseDown = false; hideHover(false, (e).fromElement === targetElement); - }, true); - - const onMouseOver = (e: MouseEvent) => { + }, true)); + store.add(addDisposableListener(targetElement, EventType.MOUSE_OVER, (e: MouseEvent) => { if (hoverPreparation) { return; } - const toDispose: DisposableStore = new DisposableStore(); + const mouseOverStore: DisposableStore = new DisposableStore(); const target: IHoverDelegateTarget = { targetElements: [targetElement], @@ -263,18 +401,17 @@ export class HoverService extends Disposable implements IHoverService { hideHover(true, true); } }; - toDispose.add(addDisposableListener(targetElement, EventType.MOUSE_MOVE, onMouseMove, true)); + mouseOverStore.add(addDisposableListener(targetElement, EventType.MOUSE_MOVE, onMouseMove, true)); } - hoverPreparation = toDispose; + hoverPreparation = mouseOverStore; if ((isHTMLElement(e.target)) && getHoverTargetElement(e.target as HTMLElement, targetElement) !== targetElement) { return; // Do not show hover when the mouse is over another hover target } - toDispose.add(triggerShowHover(hoverDelegate.delay, false, target)); - }; - const mouseOverDomEmitter = addDisposableListener(targetElement, EventType.MOUSE_OVER, onMouseOver, true); + mouseOverStore.add(triggerShowHover(hoverDelegate.delay, false, target)); + }, true)); const onFocus = () => { if (isMouseDown || hoverPreparation) { @@ -292,9 +429,8 @@ export class HoverService extends Disposable implements IHoverService { }; // Do not show hover when focusing an input or textarea - let focusDomEmitter: undefined | IDisposable; if (!isEditableElement(targetElement)) { - focusDomEmitter = addDisposableListener(targetElement, EventType.FOCUS, onFocus, true); + store.add(addDisposableListener(targetElement, EventType.FOCUS, onFocus, true)); } const hover: IManagedHover = { @@ -311,11 +447,7 @@ export class HoverService extends Disposable implements IHoverService { }, dispose: () => { this._managedHovers.delete(targetElement); - mouseOverDomEmitter.dispose(); - mouseLeaveEmitter.dispose(); - mouseDownEmitter.dispose(); - mouseUpEmitter.dispose(); - focusDomEmitter?.dispose(); + store.dispose(); hideHover(true, true); } }; diff --git a/src/vs/editor/browser/services/hoverService/hoverWidget.ts b/src/vs/editor/browser/services/hoverService/hoverWidget.ts index b46758c352f93..15bda1211ac93 100644 --- a/src/vs/editor/browser/services/hoverService/hoverWidget.ts +++ b/src/vs/editor/browser/services/hoverService/hoverWidget.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import './hover.css'; -import { DisposableStore } from '../../../../base/common/lifecycle.js'; +import { DisposableStore, MutableDisposable } from '../../../../base/common/lifecycle.js'; import { Event, Emitter } from '../../../../base/common/event.js'; import * as dom from '../../../../base/browser/dom.js'; import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; @@ -23,6 +23,8 @@ import { isMacintosh } from '../../../../base/common/platform.js'; import { IAccessibilityService } from '../../../../platform/accessibility/common/accessibility.js'; import { status } from '../../../../base/browser/ui/aria/aria.js'; import type { IHoverOptions, IHoverTarget, IHoverWidget } from '../../../../base/browser/ui/hover/hover.js'; +import { TimeoutTimer } from '../../../../base/common/async.js'; +import { isNumber } from '../../../../base/common/types.js'; const $ = dom.$; type TargetRect = { @@ -110,14 +112,11 @@ export class HoverWidget extends Widget implements IHoverWidget { this._target = 'targetElements' in options.target ? options.target : new ElementHoverTarget(options.target); this._hoverPointer = options.appearance?.showPointer ? $('div.workbench-hover-pointer') : undefined; - this._hover = this._register(new BaseHoverWidget()); - this._hover.containerDomNode.classList.add('workbench-hover', 'fadeIn'); + this._hover = this._register(new BaseHoverWidget(!options.appearance?.skipFadeInAnimation)); + this._hover.containerDomNode.classList.add('workbench-hover'); if (options.appearance?.compact) { this._hover.containerDomNode.classList.add('workbench-hover', 'compact'); } - if (options.appearance?.skipFadeInAnimation) { - this._hover.containerDomNode.classList.add('skip-fade-in'); - } if (options.additionalClasses) { this._hover.containerDomNode.classList.add(...options.additionalClasses); } @@ -128,7 +127,12 @@ export class HoverWidget extends Widget implements IHoverWidget { this._enableFocusTraps = true; } - this._hoverPosition = options.position?.hoverPosition ?? HoverPosition.ABOVE; + // Default to position above when the position is unspecified or a mouse event + this._hoverPosition = options.position?.hoverPosition === undefined + ? HoverPosition.ABOVE + : isNumber(options.position.hoverPosition) + ? options.position.hoverPosition + : HoverPosition.BELOW; // Don't allow mousedown out of the widget, otherwise preventDefault will call and text will // not be selected. @@ -395,7 +399,6 @@ export class HoverWidget extends Widget implements IHoverWidget { this.setHoverPointerPosition(targetRect); } - this._hover.onContentsChanged(); } @@ -623,9 +626,9 @@ export class HoverWidget extends Widget implements IHoverWidget { public override dispose(): void { if (!this._isDisposed) { this._onDispose.fire(); + this._target.dispose?.(); this._hoverContainer.remove(); this._messageListeners.dispose(); - this._target.dispose(); super.dispose(); } this._isDisposed = true; @@ -634,43 +637,41 @@ export class HoverWidget extends Widget implements IHoverWidget { class CompositeMouseTracker extends Widget { private _isMouseIn: boolean = true; - private _mouseTimeout: number | undefined; + private readonly _mouseTimer: MutableDisposable = this._register(new MutableDisposable()); private readonly _onMouseOut = this._register(new Emitter()); get onMouseOut(): Event { return this._onMouseOut.event; } get isMouseIn(): boolean { return this._isMouseIn; } + /** + * @param _elements The target elements to track mouse in/out events on. + * @param _eventDebounceDelay The delay in ms to debounce the event firing. This is used to + * allow a short period for the mouse to move into the hover or a nearby target element. For + * example hovering a scroll bar will not hide the hover immediately. + */ constructor( - private _elements: HTMLElement[] + private _elements: HTMLElement[], + private _eventDebounceDelay: number = 200 ) { super(); - this._elements.forEach(n => this.onmouseover(n, () => this._onTargetMouseOver(n))); - this._elements.forEach(n => this.onmouseleave(n, () => this._onTargetMouseLeave(n))); + + for (const element of this._elements) { + this.onmouseover(element, () => this._onTargetMouseOver()); + this.onmouseleave(element, () => this._onTargetMouseLeave()); + } } - private _onTargetMouseOver(target: HTMLElement): void { + private _onTargetMouseOver(): void { this._isMouseIn = true; - this._clearEvaluateMouseStateTimeout(target); + this._mouseTimer.clear(); } - private _onTargetMouseLeave(target: HTMLElement): void { + private _onTargetMouseLeave(): void { this._isMouseIn = false; - this._evaluateMouseState(target); - } - - private _evaluateMouseState(target: HTMLElement): void { - this._clearEvaluateMouseStateTimeout(target); // Evaluate whether the mouse is still outside asynchronously such that other mouse targets // have the opportunity to first their mouse in event. - this._mouseTimeout = dom.getWindow(target).setTimeout(() => this._fireIfMouseOutside(), 0); - } - - private _clearEvaluateMouseStateTimeout(target: HTMLElement): void { - if (this._mouseTimeout) { - dom.getWindow(target).clearTimeout(this._mouseTimeout); - this._mouseTimeout = undefined; - } + this._mouseTimer.value = new TimeoutTimer(() => this._fireIfMouseOutside(), this._eventDebounceDelay); } private _fireIfMouseOutside(): void { diff --git a/src/vs/editor/browser/services/treeSitter/treeSitterParserService.ts b/src/vs/editor/browser/services/treeSitter/treeSitterParserService.ts index c865456f6a2e4..81d1736d775c1 100644 --- a/src/vs/editor/browser/services/treeSitter/treeSitterParserService.ts +++ b/src/vs/editor/browser/services/treeSitter/treeSitterParserService.ts @@ -5,7 +5,7 @@ import type { Parser } from '@vscode/tree-sitter-wasm'; import { AppResourcePath, FileAccess, nodeModulesAsarUnpackedPath, nodeModulesPath } from '../../../../base/common/network.js'; -import { EDITOR_EXPERIMENTAL_PREFER_TREESITTER, ITreeSitterParserService, ITreeSitterParseResult } from '../../../common/services/treeSitterParserService.js'; +import { EDITOR_EXPERIMENTAL_PREFER_TREESITTER, ITreeSitterParserService, ITreeSitterParseResult, ITextModelTreeSitter } from '../../../common/services/treeSitterParserService.js'; import { IModelService } from '../../../common/services/model.js'; import { Disposable, DisposableMap, DisposableStore, dispose, IDisposable } from '../../../../base/common/lifecycle.js'; import { ITextModel } from '../../../common/model.js'; @@ -31,7 +31,7 @@ function getModuleLocation(environmentService: IEnvironmentService): AppResource return `${(canASAR && environmentService.isBuilt) ? nodeModulesAsarUnpackedPath : nodeModulesPath}/${MODULE_LOCATION_SUBPATH}`; } -export class TextModelTreeSitter extends Disposable { +export class TextModelTreeSitter extends Disposable implements ITextModelTreeSitter { private _onDidChangeParseResult: Emitter = this._register(new Emitter()); public readonly onDidChangeParseResult: Event = this._onDidChangeParseResult.event; private _parseResult: TreeSitterParseResult | undefined; @@ -42,10 +42,15 @@ export class TextModelTreeSitter extends Disposable { private readonly _treeSitterLanguages: TreeSitterLanguages, private readonly _treeSitterImporter: TreeSitterImporter, private readonly _logService: ILogService, - private readonly _telemetryService: ITelemetryService + private readonly _telemetryService: ITelemetryService, + parseImmediately: boolean = true ) { super(); - this._register(Event.runAndSubscribe(this.model.onDidChangeLanguage, (e => this._onDidChangeLanguage(e ? e.newLanguage : this.model.getLanguageId())))); + if (parseImmediately) { + this._register(Event.runAndSubscribe(this.model.onDidChangeLanguage, (e => this._onDidChangeLanguage(e ? e.newLanguage : this.model.getLanguageId())))); + } else { + this._register(this.model.onDidChangeLanguage(e => this._onDidChangeLanguage(e ? e.newLanguage : this.model.getLanguageId()))); + } } private readonly _languageSessionDisposables = this._register(new DisposableStore()); @@ -53,6 +58,10 @@ export class TextModelTreeSitter extends Disposable { * Be very careful when making changes to this method as it is easy to introduce race conditions. */ private async _onDidChangeLanguage(languageId: string) { + this.parse(languageId); + } + + public async parse(languageId: string = this.model.getLanguageId()): Promise { this._languageSessionDisposables.clear(); this._parseResult = undefined; @@ -80,6 +89,7 @@ export class TextModelTreeSitter extends Disposable { } this._parseResult = treeSitterTree; + return this._parseResult; } private _getLanguage(languageId: string, token: CancellationToken): Promise { @@ -459,6 +469,10 @@ export class TreeSitterTextModelService extends Disposable implements ITreeSitte this._modelService.getModels().forEach(model => this._createTextModelTreeSitter(model)); } + public getTextModelTreeSitter(model: ITextModel): ITextModelTreeSitter { + return new TextModelTreeSitter(model, this._treeSitterLanguages, this._treeSitterImporter, this._logService, this._telemetryService, false); + } + private _createTextModelTreeSitter(model: ITextModel) { const textModelTreeSitter = new TextModelTreeSitter(model, this._treeSitterLanguages, this._treeSitterImporter, this._logService, this._telemetryService); const disposables = new DisposableStore(); diff --git a/src/vs/editor/browser/view.ts b/src/vs/editor/browser/view.ts index fb572d5f18d9e..9c0fa99f047bc 100644 --- a/src/vs/editor/browser/view.ts +++ b/src/vs/editor/browser/view.ts @@ -62,6 +62,7 @@ import { IVisibleRangeProvider, TextAreaEditContext } from './controller/editCon import { NativeEditContext } from './controller/editContext/native/nativeEditContext.js'; import { RulersGpu } from './viewParts/rulersGpu/rulersGpu.js'; import { EditContext } from './controller/editContext/native/editContextFactory.js'; +import { GpuMarkOverlay } from './viewParts/gpuMark/gpuMark.js'; export interface IContentWidgetData { @@ -194,6 +195,9 @@ export class View extends ViewEventHandler { marginViewOverlays.addDynamicOverlay(new MarginViewLineDecorationsOverlay(this._context)); marginViewOverlays.addDynamicOverlay(new LinesDecorationsOverlay(this._context)); marginViewOverlays.addDynamicOverlay(new LineNumbersOverlay(this._context)); + if (this._viewGpuContext) { + marginViewOverlays.addDynamicOverlay(new GpuMarkOverlay(this._context)); + } // Glyph margin widgets this._glyphMarginWidgets = new GlyphMarginWidgets(this._context); @@ -280,12 +284,15 @@ export class View extends ViewEventHandler { return; } this._experimentalEditContextEnabled = experimentalEditContextEnabled; + const isEditContextFocused = this._editContext.isFocused(); + const indexOfEditContext = this._viewParts.indexOf(this._editContext); this._editContext.dispose(); this._editContext = this._instantiateEditContext(experimentalEditContextEnabled); - // Replace the view parts with the new edit context - const indexOfEditContextHandler = this._viewParts.indexOf(this._editContext); - if (indexOfEditContextHandler !== -1) { - this._viewParts.splice(indexOfEditContextHandler, 1, this._editContext); + if (isEditContextFocused) { + this._editContext.focus(); + } + if (indexOfEditContext !== -1) { + this._viewParts.splice(indexOfEditContext, 1, this._editContext); } } @@ -326,6 +333,7 @@ export class View extends ViewEventHandler { viewDomNode: this.domNode.domNode, linesContentDomNode: this._linesContent.domNode, viewLinesDomNode: this._viewLines.getDomNode().domNode, + viewLinesGpu: this._viewLinesGpu, focusTextArea: () => { this.focus(); @@ -356,11 +364,18 @@ export class View extends ViewEventHandler { visibleRangeForPosition: (lineNumber: number, column: number) => { this._flushAccumulatedAndRenderNow(); - return this._viewLines.visibleRangeForPosition(new Position(lineNumber, column)); + const position = new Position(lineNumber, column); + return this._viewLines.visibleRangeForPosition(position) ?? this._viewLinesGpu?.visibleRangeForPosition(position) ?? null; }, getLineWidth: (lineNumber: number) => { this._flushAccumulatedAndRenderNow(); + if (this._viewLinesGpu) { + const result = this._viewLinesGpu.getLineWidth(lineNumber); + if (result !== undefined) { + return result; + } + } return this._viewLines.getLineWidth(lineNumber); } }; diff --git a/src/vs/editor/browser/view/renderingContext.ts b/src/vs/editor/browser/view/renderingContext.ts index 178f546082e82..e5ec4b30eca6e 100644 --- a/src/vs/editor/browser/view/renderingContext.ts +++ b/src/vs/editor/browser/view/renderingContext.ts @@ -80,7 +80,18 @@ export class RenderingContext extends RestrictedRenderingContext { } public linesVisibleRangesForRange(range: Range, includeNewLines: boolean): LineVisibleRanges[] | null { - return this._viewLines.linesVisibleRangesForRange(range, includeNewLines) ?? this._viewLinesGpu?.linesVisibleRangesForRange(range, includeNewLines) ?? null; + const domRanges = this._viewLines.linesVisibleRangesForRange(range, includeNewLines); + if (!this._viewLinesGpu) { + return domRanges ?? null; + } + const gpuRanges = this._viewLinesGpu.linesVisibleRangesForRange(range, includeNewLines); + if (!domRanges) { + return gpuRanges; + } + if (!gpuRanges) { + return domRanges; + } + return domRanges.concat(gpuRanges).sort((a, b) => a.lineNumber - b.lineNumber); } public visibleRangeForPosition(position: Position): HorizontalPosition | null { diff --git a/src/vs/editor/browser/view/viewPart.ts b/src/vs/editor/browser/view/viewPart.ts index 78467eb2e233c..a23bcb11b597c 100644 --- a/src/vs/editor/browser/view/viewPart.ts +++ b/src/vs/editor/browser/view/viewPart.ts @@ -37,7 +37,8 @@ export const enum PartFingerprint { ScrollableElement, TextArea, ViewLines, - Minimap + Minimap, + ViewLinesGpu } export class PartFingerprints { diff --git a/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts b/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts index be7bd39b7f9e2..dcce94f496363 100644 --- a/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts +++ b/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts @@ -567,7 +567,7 @@ class Widget { } if (typeof this._actual.afterRender === 'function') { - safeInvoke(this._actual.afterRender, this._actual, null); + safeInvoke(this._actual.afterRender, this._actual, null, null); } return; } @@ -588,7 +588,7 @@ class Widget { } if (typeof this._actual.afterRender === 'function') { - safeInvoke(this._actual.afterRender, this._actual, this._renderData.position); + safeInvoke(this._actual.afterRender, this._actual, this._renderData.position, this._renderData.coordinate); } } } diff --git a/src/vs/editor/browser/viewParts/gpuMark/gpuMark.css b/src/vs/editor/browser/viewParts/gpuMark/gpuMark.css new file mode 100644 index 0000000000000..988c4bcb24ad0 --- /dev/null +++ b/src/vs/editor/browser/viewParts/gpuMark/gpuMark.css @@ -0,0 +1,20 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.monaco-editor .margin-view-overlays .gpu-mark { + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: 100%; + display: inline-block; + border-left: solid 2px var(--vscode-editorWarning-foreground); + opacity: 0.2; + transition: background-color 0.1s linear; +} + +.monaco-editor .margin-view-overlays .gpu-mark:hover { + background-color: var(--vscode-editorWarning-foreground) +} diff --git a/src/vs/editor/browser/viewParts/gpuMark/gpuMark.ts b/src/vs/editor/browser/viewParts/gpuMark/gpuMark.ts new file mode 100644 index 0000000000000..7019eff5e007e --- /dev/null +++ b/src/vs/editor/browser/viewParts/gpuMark/gpuMark.ts @@ -0,0 +1,97 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as viewEvents from '../../../common/viewEvents.js'; +import { ViewContext } from '../../../common/viewModel/viewContext.js'; +import { ViewGpuContext } from '../../gpu/viewGpuContext.js'; +import { DynamicViewOverlay } from '../../view/dynamicViewOverlay.js'; +import { RenderingContext } from '../../view/renderingContext.js'; +import { ViewLineOptions } from '../viewLines/viewLineOptions.js'; +import './gpuMark.css'; + +/** + * A mark on lines to make identification of GPU-rendered lines vs DOM easier. + */ +export class GpuMarkOverlay extends DynamicViewOverlay { + + public static readonly CLASS_NAME = 'gpu-mark'; + + private readonly _context: ViewContext; + + private _renderResult: string[] | null; + + constructor(context: ViewContext) { + super(); + this._context = context; + this._renderResult = null; + this._context.addEventHandler(this); + } + + public override dispose(): void { + this._context.removeEventHandler(this); + this._renderResult = null; + super.dispose(); + } + + // --- begin event handlers + + public override onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { + return true; + } + public override onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean { + return true; + } + public override onFlushed(e: viewEvents.ViewFlushedEvent): boolean { + return true; + } + public override onLinesChanged(e: viewEvents.ViewLinesChangedEvent): boolean { + return true; + } + public override onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): boolean { + return true; + } + public override onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): boolean { + return true; + } + public override onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean { + return e.scrollTopChanged; + } + public override onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean { + return true; + } + public override onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean { + return true; + } + + // --- end event handlers + + public prepareRender(ctx: RenderingContext): void { + const visibleStartLineNumber = ctx.visibleRange.startLineNumber; + const visibleEndLineNumber = ctx.visibleRange.endLineNumber; + + const viewportData = ctx.viewportData; + const options = new ViewLineOptions(this._context.configuration, this._context.theme.type); + + const output: string[] = []; + for (let lineNumber = visibleStartLineNumber; lineNumber <= visibleEndLineNumber; lineNumber++) { + const lineIndex = lineNumber - visibleStartLineNumber; + const cannotRenderReasons = ViewGpuContext.canRenderDetailed(options, viewportData, lineNumber); + output[lineIndex] = cannotRenderReasons.length ? `
` : ''; + } + + this._renderResult = output; + } + + public render(startLineNumber: number, lineNumber: number): string { + if (!this._renderResult) { + return ''; + } + const lineIndex = lineNumber - startLineNumber; + if (lineIndex < 0 || lineIndex >= this._renderResult.length) { + return ''; + } + return this._renderResult[lineIndex]; + } +} diff --git a/src/vs/editor/browser/viewParts/rulersGpu/rulersGpu.ts b/src/vs/editor/browser/viewParts/rulersGpu/rulersGpu.ts index 8db2dc2823ed0..af0b20eb9a78d 100644 --- a/src/vs/editor/browser/viewParts/rulersGpu/rulersGpu.ts +++ b/src/vs/editor/browser/viewParts/rulersGpu/rulersGpu.ts @@ -10,7 +10,7 @@ import * as viewEvents from '../../../common/viewEvents.js'; import { EditorOption } from '../../../common/config/editorOptions.js'; import type { ViewGpuContext } from '../../gpu/viewGpuContext.js'; import type { IObjectCollectionBufferEntry } from '../../gpu/objectCollectionBuffer.js'; -import type { RectangleRendererEntrySpec } from '../../gpu/rectangleRenderer.js'; +import type { RectangleRenderer, RectangleRendererEntrySpec } from '../../gpu/rectangleRenderer.js'; import { Color } from '../../../../base/common/color.js'; import { editorRuler } from '../../../common/core/editorColorRegistry.js'; import { autorun, type IReader } from '../../../../base/common/observable.js'; @@ -57,7 +57,7 @@ export class RulersGpu extends ViewPart { const ruler = rulers[i]; const shape = this._gpuShapes[i]; const color = ruler.color ? Color.fromHex(ruler.color) : this._context.theme.getColor(editorRuler) ?? Color.white; - const rulerData = [ + const rulerData: Parameters = [ ruler.column * typicalHalfwidthCharacterWidth * devicePixelRatio, 0, Math.max(1, Math.ceil(devicePixelRatio)), @@ -66,7 +66,7 @@ export class RulersGpu extends ViewPart { color.rgba.g / 255, color.rgba.b / 255, color.rgba.a, - ] as const; + ]; if (!shape) { this._gpuShapes[i] = this._viewGpuContext.rectangleRenderer.register(...rulerData); } else { diff --git a/src/vs/editor/browser/viewParts/viewLinesGpu/viewLinesGpu.ts b/src/vs/editor/browser/viewParts/viewLinesGpu/viewLinesGpu.ts index 693bfc1f5da72..c4d8c0400d214 100644 --- a/src/vs/editor/browser/viewParts/viewLinesGpu/viewLinesGpu.ts +++ b/src/vs/editor/browser/viewParts/viewLinesGpu/viewLinesGpu.ts @@ -5,25 +5,24 @@ import { getActiveWindow } from '../../../../base/browser/dom.js'; import { BugIndicatingError } from '../../../../base/common/errors.js'; -import { autorun } from '../../../../base/common/observable.js'; +import { autorun, observableValue, runOnChange } from '../../../../base/common/observable.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { ILogService } from '../../../../platform/log/common/log.js'; import { EditorOption } from '../../../common/config/editorOptions.js'; -import type { Position } from '../../../common/core/position.js'; -import type { Range } from '../../../common/core/range.js'; -import type { ViewLinesChangedEvent, ViewScrollChangedEvent } from '../../../common/viewEvents.js'; +import { Position } from '../../../common/core/position.js'; +import { Range } from '../../../common/core/range.js'; import type { ViewportData } from '../../../common/viewLayout/viewLinesViewportData.js'; import type { ViewContext } from '../../../common/viewModel/viewContext.js'; import { TextureAtlasPage } from '../../gpu/atlas/textureAtlasPage.js'; import { FullFileRenderStrategy } from '../../gpu/fullFileRenderStrategy.js'; import { BindingId, type IGpuRenderStrategy } from '../../gpu/gpu.js'; import { GPULifecycle } from '../../gpu/gpuDisposable.js'; -import { observeDevicePixelDimensions, quadVertices } from '../../gpu/gpuUtils.js'; +import { quadVertices } from '../../gpu/gpuUtils.js'; import { ViewGpuContext } from '../../gpu/viewGpuContext.js'; -import { FloatHorizontalRange, HorizontalPosition, IViewLines, LineVisibleRanges, RenderingContext, RestrictedRenderingContext, VisibleRanges } from '../../view/renderingContext.js'; +import { FloatHorizontalRange, HorizontalPosition, HorizontalRange, IViewLines, LineVisibleRanges, RenderingContext, RestrictedRenderingContext, VisibleRanges } from '../../view/renderingContext.js'; import { ViewPart } from '../../view/viewPart.js'; import { ViewLineOptions } from '../viewLines/viewLineOptions.js'; - +import type * as viewEvents from '../../../common/viewEvents.js'; const enum GlyphStorageBufferInfo { FloatsPerEntry = 2 + 2 + 2, @@ -40,6 +39,7 @@ export class ViewLinesGpu extends ViewPart implements IViewLines { private readonly canvas: HTMLCanvasElement; + private _initViewportData?: ViewportData[]; private _lastViewportData?: ViewportData; private _lastViewLineOptions?: ViewLineOptions; @@ -59,6 +59,8 @@ export class ViewLinesGpu extends ViewPart implements IViewLines { private _renderStrategy!: IGpuRenderStrategy; + private _contentLeftObs = observableValue('contentLeft', 0); + constructor( context: ViewContext, private readonly _viewGpuContext: ViewGpuContext, @@ -69,9 +71,18 @@ export class ViewLinesGpu extends ViewPart implements IViewLines { this.canvas = this._viewGpuContext.canvas.domNode; + // Re-render the following frame after canvas device pixel dimensions change, provided a + // new render does not occur. this._register(autorun(reader => { - /*const dims = */this._viewGpuContext.canvasDevicePixelDimensions.read(reader); - // TODO: Request render, should this just call renderText with the last viewportData + this._viewGpuContext.canvasDevicePixelDimensions.read(reader); + const lastViewportData = this._lastViewportData; + if (lastViewportData) { + setTimeout(() => { + if (lastViewportData === this._lastViewportData) { + this.renderText(lastViewportData); + } + }); + } })); this.initWebgpu(); @@ -144,8 +155,11 @@ export class ViewLinesGpu extends ViewPart implements IViewLines { size: Info.BytesPerEntry, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, }, () => updateBufferValues())).object; - this._register(observeDevicePixelDimensions(this.canvas, getActiveWindow(), (w, h) => { - this._device.queue.writeBuffer(layoutInfoUniformBuffer, 0, updateBufferValues(w, h)); + this._register(runOnChange(this._viewGpuContext.canvasDevicePixelDimensions, ({ width, height }) => { + this._device.queue.writeBuffer(layoutInfoUniformBuffer, 0, updateBufferValues(width, height)); + })); + this._register(runOnChange(this._contentLeftObs, () => { + this._device.queue.writeBuffer(layoutInfoUniformBuffer, 0, updateBufferValues()); })); } @@ -173,15 +187,15 @@ export class ViewLinesGpu extends ViewPart implements IViewLines { // #region Storage buffers - this._renderStrategy = this._register(this._instantiationService.createInstance(FullFileRenderStrategy, this._context, this._device, this.canvas, atlas)); + this._renderStrategy = this._register(this._instantiationService.createInstance(FullFileRenderStrategy, this._context, this._viewGpuContext, this._device)); this._glyphStorageBuffer[0] = this._register(GPULifecycle.createBuffer(this._device, { - label: 'Monaco glyph storage buffer', + label: 'Monaco glyph storage buffer [0]', size: GlyphStorageBufferInfo.BytesPerEntry * TextureAtlasPage.maximumGlyphCount, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST, })).object; this._glyphStorageBuffer[1] = this._register(GPULifecycle.createBuffer(this._device, { - label: 'Monaco glyph storage buffer', + label: 'Monaco glyph storage buffer [1]', size: GlyphStorageBufferInfo.BytesPerEntry * TextureAtlasPage.maximumGlyphCount, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST, })).object; @@ -285,10 +299,26 @@ export class ViewLinesGpu extends ViewPart implements IViewLines { // endregion Bind group this._initialized = true; + + // Render the initial viewport immediately after initialization + if (this._initViewportData) { + // HACK: Rendering multiple times in the same frame like this isn't ideal, but there + // isn't an easy way to merge viewport data + for (const viewportData of this._initViewportData) { + this.renderText(viewportData); + } + this._initViewportData = undefined; + } } private _updateAtlasStorageBufferAndTexture() { for (const [layerIndex, page] of ViewGpuContext.atlas.pages.entries()) { + if (layerIndex >= 2) { + // TODO: Support arbitrary number of layers + console.log(`Attempt to upload atlas page [${layerIndex}], only 2 are supported currently`); + continue; + } + // Skip the update if it's already the latest version if (page.version === this._atlasGpuTextureVersions[layerIndex]) { continue; @@ -325,8 +355,8 @@ export class ViewLinesGpu extends ViewPart implements IViewLines { } }, { - width: page.usedArea.right - page.usedArea.left, - height: page.usedArea.bottom - page.usedArea.top + width: page.usedArea.right - page.usedArea.left + 1, + height: page.usedArea.bottom - page.usedArea.top + 1 }, ); } @@ -334,12 +364,6 @@ export class ViewLinesGpu extends ViewPart implements IViewLines { } } - public static canRender(options: ViewLineOptions, viewportData: ViewportData, lineNumber: number): boolean { - const d = viewportData.getViewLineRenderingData(lineNumber); - // TODO - return d.content.indexOf('e') !== -1; - } - public prepareRender(ctx: RenderingContext): void { throw new BugIndicatingError('Should not be called'); } @@ -348,19 +372,40 @@ export class ViewLinesGpu extends ViewPart implements IViewLines { throw new BugIndicatingError('Should not be called'); } - override onLinesChanged(e: ViewLinesChangedEvent): boolean { - return true; - } - - override onScrollChanged(e: ViewScrollChangedEvent): boolean { + // #region Event handlers + + // Since ViewLinesGpu currently coordinates rendering to the canvas, it must listen to all + // changed events that any GPU part listens to. This is because any drawing to the canvas will + // clear it for that frame, so all parts must be rendered every time. + // + // Additionally, since this is intrinsically linked to ViewLines, it must also listen to events + // from that side. Luckily rendering is cheap, it's only when uploaded data changes does it + // start to cost. + + override onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean { return true; } + override onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean { return true; } + override onFlushed(e: viewEvents.ViewFlushedEvent): boolean { return true; } + override onLinesChanged(e: viewEvents.ViewLinesChangedEvent): boolean { return true; } + override onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): boolean { return true; } + override onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): boolean { return true; } + override onRevealRangeRequest(e: viewEvents.ViewRevealRangeRequestEvent): boolean { return true; } + override onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean { return true; } + override onThemeChanged(e: viewEvents.ViewThemeChangedEvent): boolean { return true; } + override onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean { return true; } + + override onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { + this._contentLeftObs.set(this._context.configuration.options.get(EditorOption.layoutInfo).contentLeft, undefined); return true; } - // subscribe to more events + // #endregion public renderText(viewportData: ViewportData): void { if (this._initialized) { return this._renderText(viewportData); + } else { + this._initViewportData = this._initViewportData ?? []; + this._initViewportData.push(viewportData); } } @@ -383,7 +428,6 @@ export class ViewLinesGpu extends ViewPart implements IViewLines { pass.setBindGroup(0, this._bindGroup); if (this._renderStrategy?.draw) { - // TODO: Don't draw lines if ViewLinesGpu.canRender is false this._renderStrategy.draw(pass, viewportData); } else { pass.draw(quadVertices.length / 2, visibleObjectCount); @@ -399,8 +443,65 @@ export class ViewLinesGpu extends ViewPart implements IViewLines { this._lastViewLineOptions = options; } - linesVisibleRangesForRange(range: Range, includeNewLines: boolean): LineVisibleRanges[] | null { - return null; + linesVisibleRangesForRange(_range: Range, includeNewLines: boolean): LineVisibleRanges[] | null { + if (!this._lastViewportData) { + return null; + } + const originalEndLineNumber = _range.endLineNumber; + const range = Range.intersectRanges(_range, this._lastViewportData.visibleRange); + if (!range) { + return null; + } + + const rendStartLineNumber = this._lastViewportData.startLineNumber; + const rendEndLineNumber = this._lastViewportData.endLineNumber; + + const viewportData = this._lastViewportData; + const viewLineOptions = this._lastViewLineOptions; + + if (!viewportData || !viewLineOptions) { + return null; + } + + const visibleRanges: LineVisibleRanges[] = []; + + let nextLineModelLineNumber: number = 0; + if (includeNewLines) { + nextLineModelLineNumber = this._context.viewModel.coordinatesConverter.convertViewPositionToModelPosition(new Position(range.startLineNumber, 1)).lineNumber; + } + + for (let lineNumber = range.startLineNumber; lineNumber <= range.endLineNumber; lineNumber++) { + + if (lineNumber < rendStartLineNumber || lineNumber > rendEndLineNumber) { + continue; + } + const startColumn = lineNumber === range.startLineNumber ? range.startColumn : 1; + const continuesInNextLine = lineNumber !== range.endLineNumber; + const endColumn = continuesInNextLine ? this._context.viewModel.getLineMaxColumn(lineNumber) : range.endColumn; + + const visibleRangesForLine = this._visibleRangesForLineRange(lineNumber, startColumn, endColumn); + + if (!visibleRangesForLine) { + continue; + } + + if (includeNewLines && lineNumber < originalEndLineNumber) { + const currentLineModelLineNumber = nextLineModelLineNumber; + nextLineModelLineNumber = this._context.viewModel.coordinatesConverter.convertViewPositionToModelPosition(new Position(lineNumber + 1, 1)).lineNumber; + + if (currentLineModelLineNumber !== nextLineModelLineNumber) { + visibleRangesForLine.ranges[visibleRangesForLine.ranges.length - 1].width += viewLineOptions.spaceWidth; + } + } + + visibleRanges.push(new LineVisibleRanges(visibleRangesForLine.outsideRenderedLine, lineNumber, HorizontalRange.from(visibleRangesForLine.ranges), continuesInNextLine)); + } + + if (visibleRanges.length === 0) { + return null; + } + + return visibleRanges; } private _visibleRangesForLineRange(lineNumber: number, startColumn: number, endColumn: number): VisibleRanges | null { @@ -417,10 +518,22 @@ export class ViewLinesGpu extends ViewPart implements IViewLines { return null; } + // Resolve tab widths for this line + const lineData = viewportData.getViewLineRenderingData(lineNumber); + const content = lineData.content; + let resolvedStartColumnLeft = 0; + for (let x = 0; x < startColumn - 1; x++) { + resolvedStartColumnLeft += content[x] === '\t' ? lineData.tabSize : 1; + } + let resolvedRangeWidth = 0; + for (let x = startColumn - 1; x < endColumn - 1; x++) { + resolvedRangeWidth += content[x] === '\t' ? lineData.tabSize : 1; + } + // Visible horizontal range in _scaled_ pixels const result = new VisibleRanges(false, [new FloatHorizontalRange( - (startColumn - 1) * viewLineOptions.spaceWidth, - (endColumn - startColumn - 1) * viewLineOptions.spaceWidth) + resolvedStartColumnLeft * viewLineOptions.spaceWidth, + resolvedRangeWidth * viewLineOptions.spaceWidth) ]); return result; @@ -433,4 +546,43 @@ export class ViewLinesGpu extends ViewPart implements IViewLines { } return new HorizontalPosition(visibleRanges.outsideRenderedLine, visibleRanges.ranges[0].left); } + + getLineWidth(lineNumber: number): number | undefined { + if (!this._lastViewportData || !this._lastViewLineOptions) { + return undefined; + } + if (!ViewGpuContext.canRender(this._lastViewLineOptions, this._lastViewportData, lineNumber)) { + return undefined; + } + + const lineData = this._lastViewportData.getViewLineRenderingData(lineNumber); + const lineRange = this._visibleRangesForLineRange(lineNumber, 1, lineData.maxColumn); + const lastRange = lineRange?.ranges.at(-1); + if (lastRange) { + return lastRange.width; + } + + return undefined; + } + + getPositionAtCoordinate(lineNumber: number, mouseContentHorizontalOffset: number): Position | undefined { + if (!this._lastViewportData || !this._lastViewLineOptions) { + return undefined; + } + if (!ViewGpuContext.canRender(this._lastViewLineOptions, this._lastViewportData, lineNumber)) { + return undefined; + } + const lineData = this._lastViewportData.getViewLineRenderingData(lineNumber); + const content = lineData.content; + let visualColumn = Math.ceil(mouseContentHorizontalOffset / this._lastViewLineOptions.spaceWidth); + let contentColumn = 0; + while (visualColumn > 0) { + if (visualColumn - (content[contentColumn] === '\t' ? lineData.tabSize : 1) < 0) { + break; + } + visualColumn -= content[contentColumn] === '\t' ? lineData.tabSize : 1; + contentColumn++; + } + return new Position(lineNumber, contentColumn); + } } diff --git a/src/vs/editor/browser/widget/codeEditor/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditor/codeEditorWidget.ts index 59f027dd434ed..14c3f05121359 100644 --- a/src/vs/editor/browser/widget/codeEditor/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditor/codeEditorWidget.ts @@ -197,6 +197,9 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE private readonly _onEndUpdate: Emitter = this._register(new Emitter()); public readonly onEndUpdate: Event = this._onEndUpdate.event; + private readonly _onBeforeExecuteEdit = this._register(new Emitter<{ source: string | undefined }>()); + public readonly onBeforeExecuteEdit = this._onBeforeExecuteEdit.event; + //#endregion public get isSimpleWidget(): boolean { @@ -1232,6 +1235,8 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE cursorStateComputer = endCursorState; } + this._onBeforeExecuteEdit.fire({ source: source ?? undefined }); + this._modelData.viewModel.executeEdits(source, edits, cursorStateComputer); return true; } diff --git a/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/renderLines.ts b/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/renderLines.ts index fc20d63c8f099..d2ff0bf9fb8d7 100644 --- a/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/renderLines.ts +++ b/src/vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/renderLines.ts @@ -83,9 +83,9 @@ export function renderLines(source: LineSource, options: RenderOptions, decorati export class LineSource { constructor( public readonly lineTokens: LineTokens[], - public readonly lineBreakData: (ModelLineProjectionData | null)[], - public readonly mightContainNonBasicASCII: boolean, - public readonly mightContainRTL: boolean, + public readonly lineBreakData: (ModelLineProjectionData | null)[] = lineTokens.map(t => null), + public readonly mightContainNonBasicASCII: boolean = true, + public readonly mightContainRTL: boolean = true, ) { } } diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 423c4ca967630..9e50bfd86edc5 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -1655,6 +1655,10 @@ export interface IEditorFindOptions { * Controls whether the search result and diff result automatically restarts from the beginning (or the end) when no further matches can be found */ loop?: boolean; + /** + * Controls how the find widget search history should be stored + */ + findSearchHistory?: 'never' | 'workspace'; } /** @@ -1671,7 +1675,8 @@ class EditorFind extends BaseEditorOption(input.findSearchHistory, this.defaultValue.findSearchHistory, ['never', 'workspace']), }; } } @@ -4152,14 +4167,20 @@ export interface IInlineSuggestOptions { edits?: { experimental?: { enabled?: boolean; + useMixedLinesDiff?: 'never' | 'whenPossible' | 'afterJumpWhenPossible'; + useInterleavedLinesDiff?: 'never' | 'always' | 'afterJump'; }; }; } +type RequiredRecursive = { + [P in keyof T]-?: T[P] extends object | undefined ? RequiredRecursive : T[P]; +}; + /** * @internal */ -export type InternalInlineSuggestOptions = Readonly>; +export type InternalInlineSuggestOptions = Readonly>; /** * Configuration options for inline suggestions @@ -4177,6 +4198,8 @@ class InlineEditorSuggest extends BaseEditorOption a.equals(b)); + } } export class SingleTextEdit { diff --git a/src/vs/editor/common/core/wordCharacterClassifier.ts b/src/vs/editor/common/core/wordCharacterClassifier.ts index 649498c419e12..4ebacdd00877b 100644 --- a/src/vs/editor/common/core/wordCharacterClassifier.ts +++ b/src/vs/editor/common/core/wordCharacterClassifier.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { CharCode } from '../../../base/common/charCode.js'; +import { safeIntl } from '../../../base/common/date.js'; import { LRUCache } from '../../../base/common/map.js'; import { CharacterClassifier } from './characterClassifier.js'; @@ -24,7 +25,7 @@ export class WordCharacterClassifier extends CharacterClassifier 0) { - this._segmenter = new Intl.Segmenter(this.intlSegmenterLocales, { granularity: 'word' }); + this._segmenter = safeIntl.Segmenter(this.intlSegmenterLocales, { granularity: 'word' }); } else { this._segmenter = null; } diff --git a/src/vs/editor/common/cursor/cursorTypeEditOperations.ts b/src/vs/editor/common/cursor/cursorTypeEditOperations.ts index c20fcfad38a9d..7b30480af2b55 100644 --- a/src/vs/editor/common/cursor/cursorTypeEditOperations.ts +++ b/src/vs/editor/common/cursor/cursorTypeEditOperations.ts @@ -732,11 +732,6 @@ export class CompositionOperation { const startColumn = Math.max(1, pos.column - replacePrevCharCnt); const endColumn = Math.min(model.getLineMaxColumn(pos.lineNumber), pos.column + replaceNextCharCnt); const range = new Range(pos.lineNumber, startColumn, pos.lineNumber, endColumn); - const oldText = model.getValueInRange(range); - if (oldText === text && positionDelta === 0) { - // => ignore composition that doesn't do anything - return null; - } return new ReplaceCommandWithOffsetCursorState(range, text, 0, positionDelta); } } diff --git a/src/vs/editor/common/languages.ts b/src/vs/editor/common/languages.ts index 68bb4cd7fb3db..013fb74b704ed 100644 --- a/src/vs/editor/common/languages.ts +++ b/src/vs/editor/common/languages.ts @@ -93,6 +93,7 @@ export interface ITreeSitterTokenizationSupport { captureAtPosition(lineNumber: number, column: number, textModel: model.ITextModel): Parser.QueryCapture[]; captureAtPositionTree(lineNumber: number, column: number, tree: Parser.Tree): Parser.QueryCapture[]; onDidChangeTokens: Event<{ textModel: model.ITextModel; changes: IModelTokensChangedEvent }>; + tokenizeEncodedInstrumented(lineNumber: number, textModel: model.ITextModel): { result: Uint32Array; captureTime: number; metadataTime: number } | undefined; } /** @@ -759,6 +760,11 @@ export interface InlineCompletion { readonly command?: Command; + /** + * Is called the first time an inline completion is shown. + */ + readonly shownCommand?: Command; + /** * If set to `true`, unopened closing brackets are removed and unclosed opening brackets are closed. * Defaults to `false`. @@ -801,10 +807,12 @@ export interface InlineCompletionsProvider; resolveDocumentDropEdit?(edit: DocumentDropEdit, token: CancellationToken): Promise; @@ -2341,6 +2350,7 @@ export interface IInlineEdit { range: IRange; accepted?: Command; rejected?: Command; + shown?: Command; commands?: Command[]; } diff --git a/src/vs/editor/common/languages/defaultDocumentColorsComputer.ts b/src/vs/editor/common/languages/defaultDocumentColorsComputer.ts index c188113e8a2d1..3c8acf2c7cd61 100644 --- a/src/vs/editor/common/languages/defaultDocumentColorsComputer.ts +++ b/src/vs/editor/common/languages/defaultDocumentColorsComputer.ts @@ -101,7 +101,7 @@ function _findMatches(model: IDocumentColorComputerTarget | string, regex: RegEx function computeColors(model: IDocumentColorComputerTarget): IColorInformation[] { const result: IColorInformation[] = []; // Early validation for RGB and HSL - const initialValidationRegex = /\b(rgb|rgba|hsl|hsla)(\([0-9\s,.\%]*\))|(#)([A-Fa-f0-9]{3})\b|(#)([A-Fa-f0-9]{4})\b|(#)([A-Fa-f0-9]{6})\b|(#)([A-Fa-f0-9]{8})\b/gm; + const initialValidationRegex = /\b(rgb|rgba|hsl|hsla)(\([0-9\s,.\%]*\))|\s+(#)([A-Fa-f0-9]{6})\b|\s+(#)([A-Fa-f0-9]{8})\b|^(#)([A-Fa-f0-9]{6})\b|^(#)([A-Fa-f0-9]{8})\b/gm; const initialValidationMatches = _findMatches(model, initialValidationRegex); // Potential colors have been found, validate the parameters diff --git a/src/vs/editor/common/model/textModelOffsetEdit.ts b/src/vs/editor/common/model/textModelOffsetEdit.ts new file mode 100644 index 0000000000000..4cc02d88c2ef5 --- /dev/null +++ b/src/vs/editor/common/model/textModelOffsetEdit.ts @@ -0,0 +1,56 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { EditOperation } from '../core/editOperation.js'; +import { Range } from '../core/range.js'; +import { OffsetEdit, SingleOffsetEdit } from '../core/offsetEdit.js'; +import { OffsetRange } from '../core/offsetRange.js'; +import { DetailedLineRangeMapping } from '../diff/rangeMapping.js'; +import { ITextModel, IIdentifiedSingleEditOperation } from '../model.js'; +import { IModelContentChange } from '../textModelEvents.js'; + + +export abstract class OffsetEdits { + + private constructor() { + // static utils only! + } + + static asEditOperations(offsetEdit: OffsetEdit, doc: ITextModel): IIdentifiedSingleEditOperation[] { + const edits: IIdentifiedSingleEditOperation[] = []; + for (const singleEdit of offsetEdit.edits) { + const range = Range.fromPositions( + doc.getPositionAt(singleEdit.replaceRange.start), + doc.getPositionAt(singleEdit.replaceRange.start + singleEdit.replaceRange.length) + ); + edits.push(EditOperation.replace(range, singleEdit.newText)); + } + return edits; + } + + static fromContentChanges(contentChanges: readonly IModelContentChange[]) { + const editsArr = contentChanges.map(c => new SingleOffsetEdit(OffsetRange.ofStartAndLength(c.rangeOffset, c.rangeLength), c.text)); + editsArr.reverse(); + const edits = new OffsetEdit(editsArr); + return edits; + } + + static fromLineRangeMapping(original: ITextModel, modified: ITextModel, changes: readonly DetailedLineRangeMapping[]): OffsetEdit { + const edits: SingleOffsetEdit[] = []; + for (const c of changes) { + for (const i of c.innerChanges ?? []) { + const newText = modified.getValueInRange(i.modifiedRange); + + const startOrig = original.getOffsetAt(i.originalRange.getStartPosition()); + const endExOrig = original.getOffsetAt(i.originalRange.getEndPosition()); + const origRange = new OffsetRange(startOrig, endExOrig); + + edits.push(new SingleOffsetEdit(origRange, newText)); + } + } + + return new OffsetEdit(edits); + } +} diff --git a/src/vs/editor/common/model/tokenizationTextModelPart.ts b/src/vs/editor/common/model/tokenizationTextModelPart.ts index ae4bc09c82ed4..811c24b72561e 100644 --- a/src/vs/editor/common/model/tokenizationTextModelPart.ts +++ b/src/vs/editor/common/model/tokenizationTextModelPart.ts @@ -59,12 +59,6 @@ export class TokenizationTextModelPart extends TextModelPart implements ITokeniz ) { super(); - this._register(this._languageConfigurationService.onDidChange(e => { - if (e.affects(this._languageId)) { - this._onDidChangeLanguageConfiguration.fire({}); - } - })); - // We just look at registry changes to determine whether to use tree sitter. // This means that removing a language from the setting will not cause a switch to textmate and will require a reload. // Adding a language to the setting will not need a reload, however. diff --git a/src/vs/editor/common/services/getIconClasses.ts b/src/vs/editor/common/services/getIconClasses.ts index 61fd63dc8275e..cb60d8b7bf52c 100644 --- a/src/vs/editor/common/services/getIconClasses.ts +++ b/src/vs/editor/common/services/getIconClasses.ts @@ -35,13 +35,13 @@ export function getIconClasses(modelService: IModelService, languageService: ILa } else { const match = resource.path.match(fileIconDirectoryRegex); if (match) { - name = cssEscape(match[2].toLowerCase()); + name = fileIconSelectorEscape(match[2].toLowerCase()); if (match[1]) { - classes.push(`${cssEscape(match[1].toLowerCase())}-name-dir-icon`); // parent directory + classes.push(`${fileIconSelectorEscape(match[1].toLowerCase())}-name-dir-icon`); // parent directory } } else { - name = cssEscape(resource.authority.toLowerCase()); + name = fileIconSelectorEscape(resource.authority.toLowerCase()); } } @@ -77,7 +77,7 @@ export function getIconClasses(modelService: IModelService, languageService: ILa // Detected Mode const detectedLanguageId = detectLanguageId(modelService, languageService, resource); if (detectedLanguageId) { - classes.push(`${cssEscape(detectedLanguageId)}-lang-file-icon`); + classes.push(`${fileIconSelectorEscape(detectedLanguageId)}-lang-file-icon`); } } } @@ -85,7 +85,7 @@ export function getIconClasses(modelService: IModelService, languageService: ILa } export function getIconClassesForLanguageId(languageId: string): string[] { - return ['file-icon', `${cssEscape(languageId)}-lang-file-icon`]; + return ['file-icon', `${fileIconSelectorEscape(languageId)}-lang-file-icon`]; } function detectLanguageId(modelService: IModelService, languageService: ILanguageService, resource: uri): string | null { @@ -122,6 +122,6 @@ function detectLanguageId(modelService: IModelService, languageService: ILanguag return languageService.guessLanguageIdByFilepathOrFirstLine(resource); } -function cssEscape(str: string): string { +export function fileIconSelectorEscape(str: string): string { return str.replace(/[\s]/g, '/'); // HTML class names can not contain certain whitespace characters (https://dom.spec.whatwg.org/#interface-domtokenlist), use / instead, which doesn't exist in file names. } diff --git a/src/vs/editor/common/services/model.ts b/src/vs/editor/common/services/model.ts index d7eda7d8987ed..4bad6f50e4ee4 100644 --- a/src/vs/editor/common/services/model.ts +++ b/src/vs/editor/common/services/model.ts @@ -29,9 +29,9 @@ export interface IModelService { getModel(resource: URI): ITextModel | null; - onModelAdded: Event; + readonly onModelAdded: Event; - onModelRemoved: Event; + readonly onModelRemoved: Event; - onModelLanguageChanged: Event<{ model: ITextModel; oldLanguageId: string }>; + readonly onModelLanguageChanged: Event<{ readonly model: ITextModel; readonly oldLanguageId: string }>; } diff --git a/src/vs/editor/common/services/treeSitterParserService.ts b/src/vs/editor/common/services/treeSitterParserService.ts index d88b09a6e93c0..a19f3093be078 100644 --- a/src/vs/editor/common/services/treeSitterParserService.ts +++ b/src/vs/editor/common/services/treeSitterParserService.ts @@ -20,9 +20,21 @@ export interface ITreeSitterParserService { getParseResult(textModel: ITextModel): ITreeSitterParseResult | undefined; getTree(content: string, languageId: string): Promise; onDidUpdateTree: Event<{ textModel: ITextModel; ranges: Range[] }>; + /** + * For testing purposes so that the time to parse can be measured. + */ + getTextModelTreeSitter(textModel: ITextModel): ITextModelTreeSitter | undefined; } export interface ITreeSitterParseResult { readonly tree: Parser.Tree | undefined; readonly language: Parser.Language; } + +export interface ITextModelTreeSitter { + /** + * For testing purposes so that the time to parse can be measured. + */ + parse(languageId?: string): Promise; + dispose(): void; +} diff --git a/src/vs/editor/common/tokens/lineTokens.ts b/src/vs/editor/common/tokens/lineTokens.ts index 6409d662db522..7d2b07476d06d 100644 --- a/src/vs/editor/common/tokens/lineTokens.ts +++ b/src/vs/editor/common/tokens/lineTokens.ts @@ -280,6 +280,14 @@ export class LineTokens implements IViewLineTokens { callback(tokenIndex); } } + + toString(): string { + let result = ''; + this.forEach((i) => { + result += `[${this.getTokenText(i)}]{${this.getClassName(i)}}`; + }); + return result; + } } class SliceLineTokens implements IViewLineTokens { diff --git a/src/vs/editor/contrib/anchorSelect/browser/anchorSelect.ts b/src/vs/editor/contrib/anchorSelect/browser/anchorSelect.ts index 9de6d4735008e..ad8a149b3e54f 100644 --- a/src/vs/editor/contrib/anchorSelect/browser/anchorSelect.ts +++ b/src/vs/editor/contrib/anchorSelect/browser/anchorSelect.ts @@ -14,7 +14,7 @@ import { Selection } from '../../../common/core/selection.js'; import { IEditorContribution } from '../../../common/editorCommon.js'; import { EditorContextKeys } from '../../../common/editorContextKeys.js'; import { TrackedRangeStickiness } from '../../../common/model.js'; -import { localize } from '../../../../nls.js'; +import { localize, localize2 } from '../../../../nls.js'; import { IContextKey, IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js'; import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js'; @@ -103,8 +103,7 @@ class SetSelectionAnchor extends EditorAction { constructor() { super({ id: 'editor.action.setSelectionAnchor', - label: localize('setSelectionAnchor', "Set Selection Anchor"), - alias: 'Set Selection Anchor', + label: localize2('setSelectionAnchor', "Set Selection Anchor"), precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, @@ -123,8 +122,7 @@ class GoToSelectionAnchor extends EditorAction { constructor() { super({ id: 'editor.action.goToSelectionAnchor', - label: localize('goToSelectionAnchor', "Go to Selection Anchor"), - alias: 'Go to Selection Anchor', + label: localize2('goToSelectionAnchor', "Go to Selection Anchor"), precondition: SelectionAnchorSet, }); } @@ -138,8 +136,7 @@ class SelectFromAnchorToCursor extends EditorAction { constructor() { super({ id: 'editor.action.selectFromAnchorToCursor', - label: localize('selectFromAnchorToCursor', "Select from Anchor to Cursor"), - alias: 'Select from Anchor to Cursor', + label: localize2('selectFromAnchorToCursor', "Select from Anchor to Cursor"), precondition: SelectionAnchorSet, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, @@ -158,8 +155,7 @@ class CancelSelectionAnchor extends EditorAction { constructor() { super({ id: 'editor.action.cancelSelectionAnchor', - label: localize('cancelSelectionAnchor', "Cancel Selection Anchor"), - alias: 'Cancel Selection Anchor', + label: localize2('cancelSelectionAnchor', "Cancel Selection Anchor"), precondition: SelectionAnchorSet, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, diff --git a/src/vs/editor/contrib/bracketMatching/browser/bracketMatching.ts b/src/vs/editor/contrib/bracketMatching/browser/bracketMatching.ts index 01ba78eefb23f..d121efd2500d2 100644 --- a/src/vs/editor/contrib/bracketMatching/browser/bracketMatching.ts +++ b/src/vs/editor/contrib/bracketMatching/browser/bracketMatching.ts @@ -29,8 +29,7 @@ class JumpToBracketAction extends EditorAction { constructor() { super({ id: 'editor.action.jumpToBracket', - label: nls.localize('smartSelect.jumpBracket', "Go to Bracket"), - alias: 'Go to Bracket', + label: nls.localize2('smartSelect.jumpBracket', "Go to Bracket"), precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, @@ -49,8 +48,7 @@ class SelectToBracketAction extends EditorAction { constructor() { super({ id: 'editor.action.selectToBracket', - label: nls.localize('smartSelect.selectToBracket', "Select to Bracket"), - alias: 'Select to Bracket', + label: nls.localize2('smartSelect.selectToBracket', "Select to Bracket"), precondition: undefined, metadata: { description: nls.localize2('smartSelect.selectToBracketDescription', "Select the text inside and including the brackets or curly braces"), @@ -82,8 +80,7 @@ class RemoveBracketsAction extends EditorAction { constructor() { super({ id: 'editor.action.removeBrackets', - label: nls.localize('smartSelect.removeBrackets', "Remove Brackets"), - alias: 'Remove Brackets', + label: nls.localize2('smartSelect.removeBrackets', "Remove Brackets"), precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, diff --git a/src/vs/editor/contrib/caretOperations/browser/caretOperations.ts b/src/vs/editor/contrib/caretOperations/browser/caretOperations.ts index 95843f604106a..1eab245e0bf23 100644 --- a/src/vs/editor/contrib/caretOperations/browser/caretOperations.ts +++ b/src/vs/editor/contrib/caretOperations/browser/caretOperations.ts @@ -42,8 +42,7 @@ class MoveCaretLeftAction extends MoveCaretAction { constructor() { super(true, { id: 'editor.action.moveCarretLeftAction', - label: nls.localize('caret.moveLeft', "Move Selected Text Left"), - alias: 'Move Selected Text Left', + label: nls.localize2('caret.moveLeft', "Move Selected Text Left"), precondition: EditorContextKeys.writable }); } @@ -53,8 +52,7 @@ class MoveCaretRightAction extends MoveCaretAction { constructor() { super(false, { id: 'editor.action.moveCarretRightAction', - label: nls.localize('caret.moveRight', "Move Selected Text Right"), - alias: 'Move Selected Text Right', + label: nls.localize2('caret.moveRight', "Move Selected Text Right"), precondition: EditorContextKeys.writable }); } diff --git a/src/vs/editor/contrib/caretOperations/browser/transpose.ts b/src/vs/editor/contrib/caretOperations/browser/transpose.ts index e0fff82551e79..35ac22074532a 100644 --- a/src/vs/editor/contrib/caretOperations/browser/transpose.ts +++ b/src/vs/editor/contrib/caretOperations/browser/transpose.ts @@ -19,8 +19,7 @@ class TransposeLettersAction extends EditorAction { constructor() { super({ id: 'editor.action.transposeLetters', - label: nls.localize('transposeLetters.label', "Transpose Letters"), - alias: 'Transpose Letters', + label: nls.localize2('transposeLetters.label', "Transpose Letters"), precondition: EditorContextKeys.writable, kbOpts: { kbExpr: EditorContextKeys.textInputFocus, diff --git a/src/vs/editor/contrib/clipboard/browser/clipboard.ts b/src/vs/editor/contrib/clipboard/browser/clipboard.ts index de4c9a354074f..bb3a07ad2fcf9 100644 --- a/src/vs/editor/contrib/clipboard/browser/clipboard.ts +++ b/src/vs/editor/contrib/clipboard/browser/clipboard.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as browser from '../../../../base/browser/browser.js'; -import { getActiveDocument } from '../../../../base/browser/dom.js'; +import { getActiveDocument, getActiveWindow, isHTMLElement } from '../../../../base/browser/dom.js'; import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js'; import * as platform from '../../../../base/common/platform.js'; import * as nls from '../../../../nls.js'; @@ -14,6 +14,7 @@ import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextke import { ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js'; import { CopyOptions, InMemoryClipboardMetadataManager } from '../../../browser/controller/editContext/clipboardUtils.js'; +import { NativeEditContext } from '../../../browser/controller/editContext/native/nativeEditContext.js'; import { ICodeEditor } from '../../../browser/editorBrowser.js'; import { Command, EditorAction, MultiCommand, registerEditorAction } from '../../../browser/editorExtensions.js'; import { ICodeEditorService } from '../../../browser/services/codeEditorService.js'; @@ -156,8 +157,7 @@ class ExecCommandCopyWithSyntaxHighlightingAction extends EditorAction { constructor() { super({ id: 'editor.action.clipboardCopyWithSyntaxHighlightingAction', - label: nls.localize('actions.clipboard.copyWithSyntaxHighlightingLabel', "Copy With Syntax Highlighting"), - alias: 'Copy With Syntax Highlighting', + label: nls.localize2('actions.clipboard.copyWithSyntaxHighlightingLabel', "Copy With Syntax Highlighting"), precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.textInputFocus, @@ -232,13 +232,42 @@ if (PasteAction) { // Only if editor text focus (i.e. not if editor has widget focus). const focusedEditor = codeEditorService.getFocusedCodeEditor(); - if (focusedEditor && focusedEditor.hasTextFocus()) { + if (focusedEditor && focusedEditor.hasModel() && focusedEditor.hasTextFocus()) { // execCommand(paste) does not work with edit context - const canDoDocumentExecCommand = !focusedEditor.getOption(EditorOption.experimentalEditContextEnabled); - const result = canDoDocumentExecCommand && focusedEditor.getContainerDomNode().ownerDocument.execCommand('paste'); + let result: boolean; + const experimentalEditContextEnabled = focusedEditor.getOption(EditorOption.experimentalEditContextEnabled); + if (experimentalEditContextEnabled) { + const currentFocusedElement = getActiveWindow().document.activeElement; + // Since we can not call execCommand('paste') on a dom node with edit context set + // we added a hidden text area that receives the paste execution + // see nativeEditContext.ts for more details + const editorDomNode = focusedEditor.getContainerDomNode(); + const editorDocument = editorDomNode.ownerDocument; + const textAreaElements = editorDocument.getElementsByClassName(NativeEditContext.TEXT_AREA_CLASS_NAME); + let textAreaDomNode: Element | undefined; + for (let i = 0; i < textAreaElements.length; i++) { + const textAreaElement = textAreaElements.item(i); + if (textAreaElement && textAreaElement.getAttribute('modeluri') === focusedEditor.getModel().uri.path) { + textAreaDomNode = textAreaElement; + break; + } + } + if (textAreaDomNode && isHTMLElement(textAreaDomNode)) { + textAreaDomNode.focus(); + result = editorDocument.execCommand('paste'); + textAreaDomNode.textContent = ''; + if (isHTMLElement(currentFocusedElement)) { + currentFocusedElement.focus(); + } + } else { + result = false; + } + } else { + result = focusedEditor.getContainerDomNode().ownerDocument.execCommand('paste'); + } if (result) { return CopyPasteController.get(focusedEditor)?.finishedPaste() ?? Promise.resolve(); - } else if (platform.isWeb || !canDoDocumentExecCommand) { + } else if (platform.isWeb) { // Use the clipboard service if document.execCommand('paste') was not successful return (async () => { const clipboardText = await clipboardService.readText(); diff --git a/src/vs/editor/contrib/codeAction/browser/codeAction.ts b/src/vs/editor/contrib/codeAction/browser/codeAction.ts index c9b6312cd3e28..6120bae3584ae 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeAction.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeAction.ts @@ -26,9 +26,7 @@ import { IProgress, Progress } from '../../../../platform/progress/common/progre import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; import { CodeActionFilter, CodeActionItem, CodeActionKind, CodeActionSet, CodeActionTrigger, CodeActionTriggerSource, filtersAction, mayIncludeActionsOfKind } from '../common/types.js'; import { HierarchicalKind } from '../../../../base/common/hierarchicalKind.js'; -import { raceTimeout } from '../../../../base/common/async.js'; - - +import { AccessibilitySignal, IAccessibilitySignalService } from '../../../../platform/accessibilitySignal/browser/accessibilitySignalService.js'; export const codeActionCommandId = 'editor.action.codeAction'; export const quickFixCommandId = 'editor.action.quickFix'; @@ -123,10 +121,9 @@ export async function getCodeActions( const disposables = new DisposableStore(); const promises = providers.map(async provider => { + const handle = setTimeout(() => progress.report(provider), 1250); try { - const codeActionsPromise = Promise.resolve(provider.provideCodeActions(model, rangeOrSelection, codeActionContext, cts.token)); - - const providedCodeActions = await raceTimeout(codeActionsPromise, 1250, () => progress.report(provider)); + const providedCodeActions = await provider.provideCodeActions(model, rangeOrSelection, codeActionContext, cts.token); if (providedCodeActions) { disposables.add(providedCodeActions); @@ -148,6 +145,8 @@ export async function getCodeActions( } onUnexpectedExternalError(err); return emptyCodeActionsResponse; + } finally { + clearTimeout(handle); } }); @@ -267,6 +266,7 @@ export async function applyCodeAction( const commandService = accessor.get(ICommandService); const telemetryService = accessor.get(ITelemetryService); const notificationService = accessor.get(INotificationService); + const accessibilitySignalService = accessor.get(IAccessibilitySignalService); type ApplyCodeActionEvent = { codeActionTitle: string; @@ -289,7 +289,7 @@ export async function applyCodeAction( codeActionIsPreferred: !!item.action.isPreferred, reason: codeActionReason, }); - + accessibilitySignalService.playSignal(AccessibilitySignal.codeActionTriggered); await item.resolve(token); if (token.isCancellationRequested) { return; @@ -321,6 +321,8 @@ export async function applyCodeAction( : nls.localize('applyCodeActionFailed', "An unknown error occurred while applying the code action")); } } + // ensure the start sound and end sound do not overlap + setTimeout(() => accessibilitySignalService.playSignal(AccessibilitySignal.codeActionApplied), 100); } function asMessage(err: any): string | undefined { diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionCommands.ts b/src/vs/editor/contrib/codeAction/browser/codeActionCommands.ts index a8dbc97530d76..3f5f5917ef29c 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionCommands.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionCommands.ts @@ -69,8 +69,7 @@ export class QuickFixAction extends EditorAction { constructor() { super({ id: quickFixCommandId, - label: nls.localize('quickfix.trigger.label', "Quick Fix..."), - alias: 'Quick Fix...', + label: nls.localize2('quickfix.trigger.label', "Quick Fix..."), precondition: ContextKeyExpr.and(EditorContextKeys.writable, EditorContextKeys.hasCodeActionsProvider), kbOpts: { kbExpr: EditorContextKeys.textInputFocus, @@ -126,8 +125,7 @@ export class RefactorAction extends EditorAction { constructor() { super({ id: refactorCommandId, - label: nls.localize('refactor.label', "Refactor..."), - alias: 'Refactor...', + label: nls.localize2('refactor.label', "Refactor..."), precondition: ContextKeyExpr.and(EditorContextKeys.writable, EditorContextKeys.hasCodeActionsProvider), kbOpts: { kbExpr: EditorContextKeys.textInputFocus, @@ -177,8 +175,7 @@ export class SourceAction extends EditorAction { constructor() { super({ id: sourceActionCommandId, - label: nls.localize('source.label', "Source Action..."), - alias: 'Source Action...', + label: nls.localize2('source.label', "Source Action..."), precondition: ContextKeyExpr.and(EditorContextKeys.writable, EditorContextKeys.hasCodeActionsProvider), contextMenuOpts: { group: '1_modification', @@ -221,8 +218,7 @@ export class OrganizeImportsAction extends EditorAction { constructor() { super({ id: organizeImportsCommandId, - label: nls.localize('organizeImports.label', "Organize Imports"), - alias: 'Organize Imports', + label: nls.localize2('organizeImports.label', "Organize Imports"), precondition: ContextKeyExpr.and( EditorContextKeys.writable, contextKeyForSupportedActions(CodeActionKind.SourceOrganizeImports)), @@ -231,6 +227,9 @@ export class OrganizeImportsAction extends EditorAction { primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KeyO, weight: KeybindingWeight.EditorContrib }, + metadata: { + description: nls.localize2('organizeImports.description', "Organize imports in the current file. Also called 'Optimize Imports' by some tools") + } }); } @@ -247,8 +246,7 @@ export class FixAllAction extends EditorAction { constructor() { super({ id: fixAllCommandId, - label: nls.localize('fixAll.label', "Fix All"), - alias: 'Fix All', + label: nls.localize2('fixAll.label', "Fix All"), precondition: ContextKeyExpr.and( EditorContextKeys.writable, contextKeyForSupportedActions(CodeActionKind.SourceFixAll)) @@ -268,8 +266,7 @@ export class AutoFixAction extends EditorAction { constructor() { super({ id: autoFixCommandId, - label: nls.localize('autoFix.label', "Auto Fix..."), - alias: 'Auto Fix...', + label: nls.localize2('autoFix.label', "Auto Fix..."), precondition: ContextKeyExpr.and( EditorContextKeys.writable, contextKeyForSupportedActions(CodeActionKind.QuickFix)), diff --git a/src/vs/editor/contrib/codelens/browser/codelensController.ts b/src/vs/editor/contrib/codelens/browser/codelensController.ts index 6eca903fd57ad..df34ff5d67927 100644 --- a/src/vs/editor/contrib/codelens/browser/codelensController.ts +++ b/src/vs/editor/contrib/codelens/browser/codelensController.ts @@ -18,7 +18,7 @@ import { CodeLens, Command } from '../../../common/languages.js'; import { CodeLensItem, CodeLensModel, getCodeLensModel } from './codelens.js'; import { ICodeLensCache } from './codeLensCache.js'; import { CodeLensHelper, CodeLensWidget } from './codelensWidget.js'; -import { localize } from '../../../../nls.js'; +import { localize, localize2 } from '../../../../nls.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; import { INotificationService } from '../../../../platform/notification/common/notification.js'; import { IQuickInputService } from '../../../../platform/quickinput/common/quickInput.js'; @@ -464,8 +464,7 @@ registerEditorAction(class ShowLensesInCurrentLine extends EditorAction { super({ id: 'codelens.showLensesInCurrentLine', precondition: EditorContextKeys.hasCodeLensProvider, - label: localize('showLensOnLine', "Show CodeLens Commands For Current Line"), - alias: 'Show CodeLens Commands For Current Line', + label: localize2('showLensOnLine', "Show CodeLens Commands For Current Line"), }); } diff --git a/src/vs/editor/contrib/colorPicker/browser/colorPickerParticipantUtils.ts b/src/vs/editor/contrib/colorPicker/browser/colorPickerParticipantUtils.ts index 0975790a57a86..6c0744229c754 100644 --- a/src/vs/editor/contrib/colorPicker/browser/colorPickerParticipantUtils.ts +++ b/src/vs/editor/contrib/colorPicker/browser/colorPickerParticipantUtils.ts @@ -13,6 +13,11 @@ import { getColorPresentations } from './color.js'; import { ColorPickerModel } from './colorPickerModel.js'; import { Range } from '../../../common/core/range.js'; +export const enum ColorPickerWidgetType { + Hover = 'hover', + Standalone = 'standalone' +} + export interface BaseColor { readonly range: Range; readonly model: ColorPickerModel; diff --git a/src/vs/editor/contrib/colorPicker/browser/colorPickerParts/colorPickerBody.ts b/src/vs/editor/contrib/colorPicker/browser/colorPickerParts/colorPickerBody.ts index aa8c542882691..5655055ab6454 100644 --- a/src/vs/editor/contrib/colorPicker/browser/colorPickerParts/colorPickerBody.ts +++ b/src/vs/editor/contrib/colorPicker/browser/colorPickerParts/colorPickerBody.ts @@ -10,6 +10,7 @@ import { ColorPickerModel } from '../colorPickerModel.js'; import { SaturationBox } from './colorPickerSaturationBox.js'; import { InsertButton } from './colorPickerInsertButton.js'; import { HueStrip, OpacityStrip, Strip } from './colorPickerStrip.js'; +import { ColorPickerWidgetType } from '../colorPickerParticipantUtils.js'; const $ = dom.$; @@ -21,7 +22,7 @@ export class ColorPickerBody extends Disposable { private readonly _opacityStrip: Strip; private readonly _insertButton: InsertButton | null = null; - constructor(container: HTMLElement, private readonly model: ColorPickerModel, private pixelRatio: number, isStandaloneColorPicker: boolean = false) { + constructor(container: HTMLElement, private readonly model: ColorPickerModel, private pixelRatio: number, type: ColorPickerWidgetType) { super(); this._domNode = $('.colorpicker-body'); @@ -32,17 +33,17 @@ export class ColorPickerBody extends Disposable { this._register(this._saturationBox.onDidChange(this.onDidSaturationValueChange, this)); this._register(this._saturationBox.onColorFlushed(this.flushColor, this)); - this._opacityStrip = new OpacityStrip(this._domNode, this.model, isStandaloneColorPicker); + this._opacityStrip = new OpacityStrip(this._domNode, this.model, type); this._register(this._opacityStrip); this._register(this._opacityStrip.onDidChange(this.onDidOpacityChange, this)); this._register(this._opacityStrip.onColorFlushed(this.flushColor, this)); - this._hueStrip = new HueStrip(this._domNode, this.model, isStandaloneColorPicker); + this._hueStrip = new HueStrip(this._domNode, this.model, type); this._register(this._hueStrip); this._register(this._hueStrip.onDidChange(this.onDidHueChange, this)); this._register(this._hueStrip.onColorFlushed(this.flushColor, this)); - if (isStandaloneColorPicker) { + if (type === ColorPickerWidgetType.Standalone) { this._insertButton = this._register(new InsertButton(this._domNode)); this._domNode.classList.add('standalone-colorpicker'); } diff --git a/src/vs/editor/contrib/colorPicker/browser/colorPickerParts/colorPickerHeader.ts b/src/vs/editor/contrib/colorPicker/browser/colorPickerParts/colorPickerHeader.ts index 92cd6c65e68e1..8522f41e361e9 100644 --- a/src/vs/editor/contrib/colorPicker/browser/colorPickerParts/colorPickerHeader.ts +++ b/src/vs/editor/contrib/colorPicker/browser/colorPickerParts/colorPickerHeader.ts @@ -12,6 +12,7 @@ import { localize } from '../../../../../nls.js'; import { editorHoverBackground } from '../../../../../platform/theme/common/colorRegistry.js'; import { IThemeService } from '../../../../../platform/theme/common/themeService.js'; import { CloseButton } from './colorPickerCloseButton.js'; +import { ColorPickerWidgetType } from '../colorPickerParticipantUtils.js'; const $ = dom.$; @@ -24,7 +25,7 @@ export class ColorPickerHeader extends Disposable { private readonly _closeButton: CloseButton | null = null; private backgroundColor: Color; - constructor(container: HTMLElement, private readonly model: ColorPickerModel, themeService: IThemeService, private showingStandaloneColorPicker: boolean = false) { + constructor(container: HTMLElement, private readonly model: ColorPickerModel, themeService: IThemeService, private type: ColorPickerWidgetType) { super(); this._domNode = $('.colorpicker-header'); @@ -59,7 +60,7 @@ export class ColorPickerHeader extends Disposable { this.onDidChangeColor(this.model.color); // When the color picker widget is a standalone color picker widget, then add a close button - if (this.showingStandaloneColorPicker) { + if (this.type === ColorPickerWidgetType.Standalone) { this._domNode.classList.add('standalone-colorpicker'); this._closeButton = this._register(new CloseButton(this._domNode)); } diff --git a/src/vs/editor/contrib/colorPicker/browser/colorPickerParts/colorPickerStrip.ts b/src/vs/editor/contrib/colorPicker/browser/colorPickerParts/colorPickerStrip.ts index ff72ddf8732ca..5702c8e4bcd0e 100644 --- a/src/vs/editor/contrib/colorPicker/browser/colorPickerParts/colorPickerStrip.ts +++ b/src/vs/editor/contrib/colorPicker/browser/colorPickerParts/colorPickerStrip.ts @@ -9,6 +9,7 @@ import { Color, RGBA } from '../../../../../base/common/color.js'; import { Emitter, Event } from '../../../../../base/common/event.js'; import { Disposable } from '../../../../../base/common/lifecycle.js'; import { ColorPickerModel } from '../colorPickerModel.js'; +import { ColorPickerWidgetType } from '../colorPickerParticipantUtils.js'; const $ = dom.$; @@ -25,9 +26,9 @@ export abstract class Strip extends Disposable { private readonly _onColorFlushed = new Emitter(); readonly onColorFlushed: Event = this._onColorFlushed.event; - constructor(container: HTMLElement, protected model: ColorPickerModel, showingStandaloneColorPicker: boolean = false) { + constructor(container: HTMLElement, protected model: ColorPickerModel, type: ColorPickerWidgetType) { super(); - if (showingStandaloneColorPicker) { + if (type === ColorPickerWidgetType.Standalone) { this.domNode = dom.append(container, $('.standalone-strip')); this.overlay = dom.append(this.domNode, $('.standalone-overlay')); } else { @@ -92,8 +93,8 @@ export abstract class Strip extends Disposable { export class OpacityStrip extends Strip { - constructor(container: HTMLElement, model: ColorPickerModel, showingStandaloneColorPicker: boolean = false) { - super(container, model, showingStandaloneColorPicker); + constructor(container: HTMLElement, model: ColorPickerModel, type: ColorPickerWidgetType) { + super(container, model, type); this.domNode.classList.add('opacity-strip'); this.onDidChangeColor(this.model.color); @@ -115,8 +116,8 @@ export class OpacityStrip extends Strip { export class HueStrip extends Strip { - constructor(container: HTMLElement, model: ColorPickerModel, showingStandaloneColorPicker: boolean = false) { - super(container, model, showingStandaloneColorPicker); + constructor(container: HTMLElement, model: ColorPickerModel, type: ColorPickerWidgetType) { + super(container, model, type); this.domNode.classList.add('hue-strip'); } diff --git a/src/vs/editor/contrib/colorPicker/browser/hoverColorPicker/hoverColorPickerWidget.ts b/src/vs/editor/contrib/colorPicker/browser/colorPickerWidget.ts similarity index 58% rename from src/vs/editor/contrib/colorPicker/browser/hoverColorPicker/hoverColorPickerWidget.ts rename to src/vs/editor/contrib/colorPicker/browser/colorPickerWidget.ts index 9ba946953f955..cc56c1bf12f3e 100644 --- a/src/vs/editor/contrib/colorPicker/browser/hoverColorPicker/hoverColorPickerWidget.ts +++ b/src/vs/editor/contrib/colorPicker/browser/colorPickerWidget.ts @@ -3,15 +3,16 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import '../colorPicker.css'; -import { PixelRatio } from '../../../../../base/browser/pixelRatio.js'; -import * as dom from '../../../../../base/browser/dom.js'; -import { Widget } from '../../../../../base/browser/ui/widget.js'; -import { ColorPickerModel } from '../colorPickerModel.js'; -import { IEditorHoverColorPickerWidget } from '../../../hover/browser/hoverTypes.js'; -import { IThemeService } from '../../../../../platform/theme/common/themeService.js'; -import { ColorPickerBody } from '../colorPickerParts/colorPickerBody.js'; -import { ColorPickerHeader } from '../colorPickerParts/colorPickerHeader.js'; +import './colorPicker.css'; +import { PixelRatio } from '../../../../base/browser/pixelRatio.js'; +import * as dom from '../../../../base/browser/dom.js'; +import { Widget } from '../../../../base/browser/ui/widget.js'; +import { ColorPickerModel } from './colorPickerModel.js'; +import { IEditorHoverColorPickerWidget } from '../../hover/browser/hoverTypes.js'; +import { IThemeService } from '../../../../platform/theme/common/themeService.js'; +import { ColorPickerBody } from './colorPickerParts/colorPickerBody.js'; +import { ColorPickerHeader } from './colorPickerParts/colorPickerHeader.js'; +import { ColorPickerWidgetType } from './colorPickerParticipantUtils.js'; const $ = dom.$; @@ -23,7 +24,7 @@ export class ColorPickerWidget extends Widget implements IEditorHoverColorPicker body: ColorPickerBody; header: ColorPickerHeader; - constructor(container: Node, readonly model: ColorPickerModel, private pixelRatio: number, themeService: IThemeService, standaloneColorPicker: boolean = false) { + constructor(container: Node, readonly model: ColorPickerModel, private pixelRatio: number, themeService: IThemeService, type: ColorPickerWidgetType) { super(); this._register(PixelRatio.getInstance(dom.getWindow(container)).onDidChange(() => this.layout())); @@ -31,8 +32,8 @@ export class ColorPickerWidget extends Widget implements IEditorHoverColorPicker this._domNode = $('.colorpicker-widget'); container.appendChild(this._domNode); - this.header = this._register(new ColorPickerHeader(this._domNode, this.model, themeService, standaloneColorPicker)); - this.body = this._register(new ColorPickerBody(this._domNode, this.model, this.pixelRatio, standaloneColorPicker)); + this.header = this._register(new ColorPickerHeader(this._domNode, this.model, themeService, type)); + this.body = this._register(new ColorPickerBody(this._domNode, this.model, this.pixelRatio, type)); } getId(): string { diff --git a/src/vs/editor/contrib/colorPicker/browser/hoverColorPicker/hoverColorPickerContribution.ts b/src/vs/editor/contrib/colorPicker/browser/hoverColorPicker/hoverColorPickerContribution.ts index 46f7660dd2eee..63ed7dc725959 100644 --- a/src/vs/editor/contrib/colorPicker/browser/hoverColorPicker/hoverColorPickerContribution.ts +++ b/src/vs/editor/contrib/colorPicker/browser/hoverColorPicker/hoverColorPickerContribution.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable } from '../../../../../base/common/lifecycle.js'; -import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from '../../../../browser/editorBrowser.js'; +import { ICodeEditor, IEditorMouseEvent, IPartialEditorMouseEvent, MouseTargetType } from '../../../../browser/editorBrowser.js'; import { EditorOption } from '../../../../common/config/editorOptions.js'; import { Range } from '../../../../common/core/range.js'; import { IEditorContribution } from '../../../../common/editorCommon.js'; @@ -34,32 +34,28 @@ export class HoverColorPickerContribution extends Disposable implements IEditorC if (colorDecoratorsActivatedOn !== 'click' && colorDecoratorsActivatedOn !== 'clickAndHover') { return; } - - const target = mouseEvent.target; - - if (target.type !== MouseTargetType.CONTENT_TEXT) { + if (!isOnColorDecorator(mouseEvent)) { return; } - - if (!target.detail.injectedText) { + const hoverController = this._editor.getContribution(ContentHoverController.ID); + if (!hoverController) { return; } - - if (target.detail.injectedText.options.attachedData !== ColorDecorationInjectedTextMarker) { + if (hoverController.isColorPickerVisible) { return; } - - if (!target.range) { + const targetRange = mouseEvent.target.range; + if (!targetRange) { return; } - - const hoverController = this._editor.getContribution(ContentHoverController.ID); - if (!hoverController) { - return; - } - if (!hoverController.isColorPickerVisible) { - const range = new Range(target.range.startLineNumber, target.range.startColumn + 1, target.range.endLineNumber, target.range.endColumn + 1); - hoverController.showContentHover(range, HoverStartMode.Immediate, HoverStartSource.Mouse, false, true); - } + const range = new Range(targetRange.startLineNumber, targetRange.startColumn + 1, targetRange.endLineNumber, targetRange.endColumn + 1); + hoverController.showContentHover(range, HoverStartMode.Immediate, HoverStartSource.Click, false); } } + +export function isOnColorDecorator(mouseEvent: IPartialEditorMouseEvent): boolean { + const target = mouseEvent.target; + return !!target + && target.type === MouseTargetType.CONTENT_TEXT + && target.detail.injectedText?.options.attachedData === ColorDecorationInjectedTextMarker; +} diff --git a/src/vs/editor/contrib/colorPicker/browser/hoverColorPicker/hoverColorPickerParticipant.ts b/src/vs/editor/contrib/colorPicker/browser/hoverColorPicker/hoverColorPickerParticipant.ts index 7c7d4afeb0689..119810b7b6d12 100644 --- a/src/vs/editor/contrib/colorPicker/browser/hoverColorPicker/hoverColorPickerParticipant.ts +++ b/src/vs/editor/contrib/colorPicker/browser/hoverColorPicker/hoverColorPickerParticipant.ts @@ -11,15 +11,16 @@ import { IModelDecoration } from '../../../../common/model.js'; import { DocumentColorProvider } from '../../../../common/languages.js'; import { ColorDetector } from '../colorDetector.js'; import { ColorPickerModel } from '../colorPickerModel.js'; -import { ColorPickerWidget } from './hoverColorPickerWidget.js'; +import { ColorPickerWidget } from '../colorPickerWidget.js'; import { HoverAnchor, HoverAnchorType, IEditorHoverParticipant, IEditorHoverRenderContext, IHoverPart, IRenderedHoverPart, IRenderedHoverParts, RenderedHoverParts } from '../../../hover/browser/hoverTypes.js'; import { IThemeService } from '../../../../../platform/theme/common/themeService.js'; import * as nls from '../../../../../nls.js'; -import { BaseColor, createColorHover, updateColorPresentations, updateEditorModel } from '../colorPickerParticipantUtils.js'; +import { BaseColor, ColorPickerWidgetType, createColorHover, updateColorPresentations, updateEditorModel } from '../colorPickerParticipantUtils.js'; import { EditorOption } from '../../../../common/config/editorOptions.js'; import { Dimension } from '../../../../../base/browser/dom.js'; import { DisposableStore } from '../../../../../base/common/lifecycle.js'; import { Color } from '../../../../../base/common/color.js'; +import { HoverStartSource } from '../../../hover/browser/hoverOperation.js'; export class ColorHover implements IHoverPart, BaseColor { @@ -60,18 +61,21 @@ export class HoverColorPickerParticipant implements IEditorHoverParticipant { - return AsyncIterableObject.fromPromise(this._computeAsync(anchor, lineDecorations, token)); + public computeAsync(anchor: HoverAnchor, lineDecorations: IModelDecoration[], source: HoverStartSource, token: CancellationToken): AsyncIterableObject { + return AsyncIterableObject.fromPromise(this._computeAsync(anchor, lineDecorations, source)); } - private async _computeAsync(_anchor: HoverAnchor, lineDecorations: IModelDecoration[], _token: CancellationToken): Promise { + private async _computeAsync(_anchor: HoverAnchor, lineDecorations: IModelDecoration[], source: HoverStartSource): Promise { if (!this._editor.hasModel()) { return []; } + if (!this._isValidRequest(source)) { + return []; + } const colorDetector = ColorDetector.get(this._editor); if (!colorDetector) { return []; @@ -91,6 +95,18 @@ export class HoverColorPickerParticipant implements IEditorHoverParticipant { const editor = this._editor; if (hoverParts.length === 0 || !editor.hasModel()) { @@ -105,7 +121,7 @@ export class HoverColorPickerParticipant implements IEditorHoverParticipant('pasteWidgetVisible', false, localize('pasteWidgetVisible', "Whether the paste widget is showing")); -const vscodeClipboardMime = 'application/vnd.code.copyMetadata'; +const vscodeClipboardMime = 'application/vnd.code.copymetadata'; interface CopyMetadata { readonly id?: string; @@ -73,6 +79,12 @@ export class CopyPasteController extends Disposable implements IEditorContributi return editor.getContribution(CopyPasteController.ID); } + public static setConfigureDefaultAction(action: IAction) { + CopyPasteController._configureDefaultAction = action; + } + + private static _configureDefaultAction?: IAction; + /** * Global tracking the last copy operation. * @@ -98,6 +110,7 @@ export class CopyPasteController extends Disposable implements IEditorContributi @IInstantiationService instantiationService: IInstantiationService, @IBulkEditService private readonly _bulkEditService: IBulkEditService, @IClipboardService private readonly _clipboardService: IClipboardService, + @IConfigurationService private readonly _configService: IConfigurationService, @ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService, @IQuickInputService private readonly _quickInputService: IQuickInputService, @IProgressService private readonly _progressService: IProgressService, @@ -113,7 +126,10 @@ export class CopyPasteController extends Disposable implements IEditorContributi this._pasteProgressManager = this._register(new InlineProgressManager('pasteIntoEditor', editor, instantiationService)); - this._postPasteWidgetManager = this._register(instantiationService.createInstance(PostEditWidgetManager, 'pasteIntoEditor', editor, pasteWidgetVisibleCtx, { id: changePasteTypeCommandId, label: localize('postPasteWidgetTitle', "Show paste options...") })); + this._postPasteWidgetManager = this._register(instantiationService.createInstance(PostEditWidgetManager, 'pasteIntoEditor', editor, pasteWidgetVisibleCtx, + { id: changePasteTypeCommandId, label: localize('postPasteWidgetTitle', "Show paste options...") }, + () => CopyPasteController._configureDefaultAction ? [CopyPasteController._configureDefaultAction] : [] + )); } public changePasteType() { @@ -354,7 +370,7 @@ export class CopyPasteController extends Disposable implements IEditorContributi if (editSession.edits.length) { const canShowWidget = editor.getOption(EditorOption.pasteAs).showPasteSelector === 'afterPaste'; - return this._postPasteWidgetManager.applyEditAndShowIfNeeded(selections, { activeEditIndex: 0, allEdits: editSession.edits }, canShowWidget, (edit, token) => { + return this._postPasteWidgetManager.applyEditAndShowIfNeeded(selections, { activeEditIndex: this.getInitialActiveEditIndex(model, editSession.edits), allEdits: editSession.edits }, canShowWidget, (edit, token) => { return new Promise((resolve, reject) => { (async () => { try { @@ -464,14 +480,36 @@ export class CopyPasteController extends Disposable implements IEditorContributi if (preference) { pickedEdit = editSession.edits.at(0); } else { - const selected = await this._quickInputService.pick( - editSession.edits.map((edit): IQuickPickItem & { edit: DocumentPasteEdit } => ({ - label: edit.title, - description: edit.kind?.value, - edit, - })), { + type ItemWithEdit = IQuickPickItem & { edit?: DocumentPasteEdit }; + const configureDefaultItem: ItemWithEdit = { + id: 'editor.pasteAs.default', + label: localize('pasteAsDefault', "Configure default paste action"), + edit: undefined, + }; + + const selected = await this._quickInputService.pick( + [ + ...editSession.edits.map((edit): ItemWithEdit => ({ + label: edit.title, + description: edit.kind?.value, + edit, + })), + ...(CopyPasteController._configureDefaultAction ? [ + upcast({ type: 'separator' }), + { + label: CopyPasteController._configureDefaultAction.label, + edit: undefined, + } + ] : []) + ], { placeHolder: localize('pasteAsPickerPlaceholder', "Select Paste Action"), }); + + if (selected === configureDefaultItem) { + CopyPasteController._configureDefaultAction?.run(); + return; + } + pickedEdit = selected?.edit; } @@ -621,4 +659,16 @@ export class CopyPasteController extends Disposable implements IEditorContributi return provider.id === preference.providerId; } } + + private getInitialActiveEditIndex(model: ITextModel, edits: readonly DocumentPasteEdit[]): number { + const preferredProviders = this._configService.getValue(pasteAsPreferenceConfig, { resource: model.uri }); + for (const config of Array.isArray(preferredProviders) ? preferredProviders : []) { + const desiredKind = new HierarchicalKind(config); + const editIndex = edits.findIndex(edit => desiredKind.contains(edit.kind)); + if (editIndex >= 0) { + return editIndex; + } + } + return 0; + } } diff --git a/src/vs/editor/contrib/dropOrPasteInto/browser/defaultProviders.ts b/src/vs/editor/contrib/dropOrPasteInto/browser/defaultProviders.ts index f3beb9c518ce0..d8a016badbfd4 100644 --- a/src/vs/editor/contrib/dropOrPasteInto/browser/defaultProviders.ts +++ b/src/vs/editor/contrib/dropOrPasteInto/browser/defaultProviders.ts @@ -23,10 +23,19 @@ import { IWorkspaceContextService } from '../../../../platform/workspace/common/ abstract class SimplePasteAndDropProvider implements DocumentDropEditProvider, DocumentPasteEditProvider { - abstract readonly kind: HierarchicalKind; + readonly kind: HierarchicalKind; + readonly providedDropEditKinds: HierarchicalKind[]; + readonly providedPasteEditKinds: HierarchicalKind[]; + abstract readonly dropMimeTypes: readonly string[] | undefined; abstract readonly pasteMimeTypes: readonly string[]; + constructor(kind: HierarchicalKind) { + this.kind = kind; + this.providedDropEditKinds = [this.kind]; + this.providedPasteEditKinds = [this.kind]; + } + async provideDocumentPasteEdits(_model: ITextModel, _ranges: readonly IRange[], dataTransfer: IReadonlyVSDataTransfer, context: DocumentPasteContext, token: CancellationToken): Promise { const edit = await this.getEdit(dataTransfer, token); if (!edit) { @@ -56,13 +65,15 @@ abstract class SimplePasteAndDropProvider implements DocumentDropEditProvider, D export class DefaultTextPasteOrDropEditProvider extends SimplePasteAndDropProvider { static readonly id = 'text'; - static readonly kind = new HierarchicalKind('text.plain'); readonly id = DefaultTextPasteOrDropEditProvider.id; - readonly kind = DefaultTextPasteOrDropEditProvider.kind; readonly dropMimeTypes = [Mimes.text]; readonly pasteMimeTypes = [Mimes.text]; + constructor() { + super(HierarchicalKind.Empty.append('text', 'plain')); + } + protected async getEdit(dataTransfer: IReadonlyVSDataTransfer, _token: CancellationToken): Promise { const textEntry = dataTransfer.get(Mimes.text); if (!textEntry) { @@ -87,10 +98,13 @@ export class DefaultTextPasteOrDropEditProvider extends SimplePasteAndDropProvid class PathProvider extends SimplePasteAndDropProvider { - readonly kind = new HierarchicalKind('uri.absolute'); readonly dropMimeTypes = [Mimes.uriList]; readonly pasteMimeTypes = [Mimes.uriList]; + constructor() { + super(HierarchicalKind.Empty.append('uri', 'absolute')); + } + protected async getEdit(dataTransfer: IReadonlyVSDataTransfer, token: CancellationToken): Promise { const entries = await extractUriList(dataTransfer); if (!entries.length || token.isCancellationRequested) { @@ -133,14 +147,13 @@ class PathProvider extends SimplePasteAndDropProvider { class RelativePathProvider extends SimplePasteAndDropProvider { - readonly kind = new HierarchicalKind('uri.relative'); readonly dropMimeTypes = [Mimes.uriList]; readonly pasteMimeTypes = [Mimes.uriList]; constructor( @IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService ) { - super(); + super(HierarchicalKind.Empty.append('uri', 'relative')); } protected async getEdit(dataTransfer: IReadonlyVSDataTransfer, token: CancellationToken): Promise { @@ -173,6 +186,8 @@ class PasteHtmlProvider implements DocumentPasteEditProvider { public readonly kind = new HierarchicalKind('html'); + public readonly providedPasteEditKinds = [this.kind]; + public readonly pasteMimeTypes = ['text/html']; private readonly _yieldTo = [{ mimeType: Mimes.text }]; diff --git a/src/vs/editor/contrib/dropOrPasteInto/browser/dropIntoEditorContribution.ts b/src/vs/editor/contrib/dropOrPasteInto/browser/dropIntoEditorContribution.ts index 7af24cf81e831..77d8ce5c0c931 100644 --- a/src/vs/editor/contrib/dropOrPasteInto/browser/dropIntoEditorContribution.ts +++ b/src/vs/editor/contrib/dropOrPasteInto/browser/dropIntoEditorContribution.ts @@ -4,16 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js'; +import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js'; import { ICodeEditor } from '../../../browser/editorBrowser.js'; import { EditorCommand, EditorContributionInstantiation, ServicesAccessor, registerEditorCommand, registerEditorContribution } from '../../../browser/editorExtensions.js'; -import { editorConfigurationBaseNode } from '../../../common/config/editorConfigurationSchema.js'; import { registerEditorFeature } from '../../../common/editorFeatures.js'; import { DefaultDropProvidersFeature } from './defaultProviders.js'; -import * as nls from '../../../../nls.js'; -import { Extensions as ConfigurationExtensions, ConfigurationScope, IConfigurationRegistry } from '../../../../platform/configuration/common/configurationRegistry.js'; -import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js'; -import { Registry } from '../../../../platform/registry/common/platform.js'; -import { DropIntoEditorController, changeDropTypeCommandId, defaultProviderConfig, dropWidgetVisibleCtx } from './dropIntoEditorController.js'; +import { DropIntoEditorController, changeDropTypeCommandId, dropWidgetVisibleCtx } from './dropIntoEditorController.js'; registerEditorContribution(DropIntoEditorController.ID, DropIntoEditorController, EditorContributionInstantiation.BeforeFirstInteraction); registerEditorFeature(DefaultDropProvidersFeature); @@ -52,17 +48,5 @@ registerEditorCommand(new class extends EditorCommand { } }); -Registry.as(ConfigurationExtensions.Configuration).registerConfiguration({ - ...editorConfigurationBaseNode, - properties: { - [defaultProviderConfig]: { - type: 'object', - scope: ConfigurationScope.LANGUAGE_OVERRIDABLE, - description: nls.localize('defaultProviderDescription', "Configures the default drop provider to use for content of a given mime type."), - default: {}, - additionalProperties: { - type: 'string', - }, - }, - } -}); +export type PreferredDropConfiguration = string; + diff --git a/src/vs/editor/contrib/dropOrPasteInto/browser/dropIntoEditorController.ts b/src/vs/editor/contrib/dropOrPasteInto/browser/dropIntoEditorController.ts index da8ef9147c79a..54a5d6d4fe4f8 100644 --- a/src/vs/editor/contrib/dropOrPasteInto/browser/dropIntoEditorController.ts +++ b/src/vs/editor/contrib/dropOrPasteInto/browser/dropIntoEditorController.ts @@ -3,11 +3,19 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { IAction } from '../../../../base/common/actions.js'; import { coalesce } from '../../../../base/common/arrays.js'; import { CancelablePromise, createCancelablePromise, raceCancellation } from '../../../../base/common/async.js'; -import { VSDataTransfer, matchesMimeType } from '../../../../base/common/dataTransfer.js'; +import { CancellationToken } from '../../../../base/common/cancellation.js'; +import { VSDataTransfer } from '../../../../base/common/dataTransfer.js'; +import { isCancellationError } from '../../../../base/common/errors.js'; import { HierarchicalKind } from '../../../../base/common/hierarchicalKind.js'; import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js'; +import { localize } from '../../../../nls.js'; +import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; +import { RawContextKey } from '../../../../platform/contextkey/common/contextkey.js'; +import { LocalSelectionTransfer } from '../../../../platform/dnd/browser/dnd.js'; +import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { toExternalVSDataTransfer } from '../../../browser/dnd.js'; import { ICodeEditor } from '../../../browser/editorBrowser.js'; import { EditorOption } from '../../../common/config/editorOptions.js'; @@ -21,15 +29,11 @@ import { DraggedTreeItemsIdentifier } from '../../../common/services/treeViewsDn import { ITreeViewsDnDService } from '../../../common/services/treeViewsDndService.js'; import { CodeEditorStateFlag, EditorStateCancellationTokenSource } from '../../editorState/browser/editorState.js'; import { InlineProgressManager } from '../../inlineProgress/browser/inlineProgress.js'; -import { localize } from '../../../../nls.js'; -import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; -import { RawContextKey } from '../../../../platform/contextkey/common/contextkey.js'; -import { LocalSelectionTransfer } from '../../../../platform/dnd/browser/dnd.js'; -import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; +import { PreferredDropConfiguration } from './dropIntoEditorContribution.js'; import { sortEditsByYieldTo } from './edit.js'; import { PostEditWidgetManager } from './postEditWidget.js'; -export const defaultProviderConfig = 'editor.experimental.dropIntoEditor.defaultProvider'; +export const dropAsPreferenceConfig = 'editor.dropIntoEditor.preferences'; export const changeDropTypeCommandId = 'editor.changeDropType'; @@ -43,7 +47,18 @@ export class DropIntoEditorController extends Disposable implements IEditorContr return editor.getContribution(DropIntoEditorController.ID); } - private _currentOperation?: CancelablePromise; + public static setConfigureDefaultAction(action: IAction) { + this._configureDefaultAction = action; + } + + private static _configureDefaultAction?: IAction; + + /** + * Global tracking the current drop operation. + * + * TODO: figure out how to make this work with multiple windows + */ + private static _currentDropOperation?: CancelablePromise; private readonly _dropProgressManager: InlineProgressManager; private readonly _postDropWidgetManager: PostEditWidgetManager; @@ -60,7 +75,9 @@ export class DropIntoEditorController extends Disposable implements IEditorContr super(); this._dropProgressManager = this._register(instantiationService.createInstance(InlineProgressManager, 'dropIntoEditor', editor)); - this._postDropWidgetManager = this._register(instantiationService.createInstance(PostEditWidgetManager, 'dropIntoEditor', editor, dropWidgetVisibleCtx, { id: changeDropTypeCommandId, label: localize('postDropWidgetTitle', "Show drop options...") })); + this._postDropWidgetManager = this._register(instantiationService.createInstance(PostEditWidgetManager, 'dropIntoEditor', editor, dropWidgetVisibleCtx, + { id: changeDropTypeCommandId, label: localize('postDropWidgetTitle', "Show drop options...") }, + () => DropIntoEditorController._configureDefaultAction ? [DropIntoEditorController._configureDefaultAction] : [])); this._register(editor.onDropIntoEditor(e => this.onDropIntoEditor(editor, e.position, e.event))); } @@ -78,7 +95,7 @@ export class DropIntoEditorController extends Disposable implements IEditorContr return; } - this._currentOperation?.cancel(); + DropIntoEditorController._currentDropOperation?.cancel(); editor.focus(); editor.setPosition(position); @@ -108,7 +125,7 @@ export class DropIntoEditorController extends Disposable implements IEditorContr return provider.dropMimeTypes.some(mime => ourDataTransfer.matches(mime)); }); - const editSession = disposables.add(await this.getDropEdits(providers, model, position, ourDataTransfer, tokenSource)); + const editSession = disposables.add(await this.getDropEdits(providers, model, position, ourDataTransfer, tokenSource.token)); if (tokenSource.token.isCancellationRequested) { return; } @@ -121,31 +138,34 @@ export class DropIntoEditorController extends Disposable implements IEditorContr } } finally { disposables.dispose(); - if (this._currentOperation === p) { - this._currentOperation = undefined; + if (DropIntoEditorController._currentDropOperation === p) { + DropIntoEditorController._currentDropOperation = undefined; } } }); this._dropProgressManager.showWhile(position, localize('dropIntoEditorProgress', "Running drop handlers. Click to cancel"), p, { cancel: () => p.cancel() }); - this._currentOperation = p; + DropIntoEditorController._currentDropOperation = p; } - private async getDropEdits(providers: readonly DocumentDropEditProvider[], model: ITextModel, position: IPosition, dataTransfer: VSDataTransfer, tokenSource: EditorStateCancellationTokenSource) { + private async getDropEdits(providers: readonly DocumentDropEditProvider[], model: ITextModel, position: IPosition, dataTransfer: VSDataTransfer, token: CancellationToken) { const disposables = new DisposableStore(); const results = await raceCancellation(Promise.all(providers.map(async provider => { try { - const edits = await provider.provideDocumentDropEdits(model, position, dataTransfer, tokenSource.token); + const edits = await provider.provideDocumentDropEdits(model, position, dataTransfer, token); if (edits) { disposables.add(edits); } return edits?.edits.map(edit => ({ ...edit, providerId: provider.id })); } catch (err) { + if (!isCancellationError(err)) { + console.error(err); + } console.error(err); } return undefined; - })), tokenSource.token); + })), token); const edits = coalesce(results ?? []).flat(); return { @@ -154,13 +174,11 @@ export class DropIntoEditorController extends Disposable implements IEditorContr }; } - private getInitialActiveEditIndex(model: ITextModel, edits: ReadonlyArray) { - const preferredProviders = this._configService.getValue>(defaultProviderConfig, { resource: model.uri }); - for (const [configMime, desiredKindStr] of Object.entries(preferredProviders)) { - const desiredKind = new HierarchicalKind(desiredKindStr); - const editIndex = edits.findIndex(edit => - desiredKind.value === edit.providerId - && edit.handledMimeType && matchesMimeType(configMime, [edit.handledMimeType])); + private getInitialActiveEditIndex(model: ITextModel, edits: ReadonlyArray): number { + const preferredProviders = this._configService.getValue(dropAsPreferenceConfig, { resource: model.uri }); + for (const config of Array.isArray(preferredProviders) ? preferredProviders : []) { + const desiredKind = new HierarchicalKind(config); + const editIndex = edits.findIndex(edit => edit.kind && desiredKind.contains(edit.kind)); if (editIndex >= 0) { return editIndex; } diff --git a/src/vs/editor/contrib/dropOrPasteInto/browser/postEditWidget.css b/src/vs/editor/contrib/dropOrPasteInto/browser/postEditWidget.css index a0aee618ab6b4..496b989268e8f 100644 --- a/src/vs/editor/contrib/dropOrPasteInto/browser/postEditWidget.css +++ b/src/vs/editor/contrib/dropOrPasteInto/browser/postEditWidget.css @@ -7,7 +7,8 @@ box-shadow: 0 0 8px 2px var(--vscode-widget-shadow); border: 1px solid var(--vscode-widget-border, transparent); border-radius: 4px; - background-color: var(--vscode-editorWidget-background); + color: var(--vscode-button-foreground); + background-color: var(--vscode-button-background); overflow: hidden; } @@ -18,7 +19,7 @@ } .post-edit-widget .monaco-button:hover { - background-color: var(--vscode-button-secondaryHoverBackground) !important; + background-color: var(--vscode-button-hoverBackground) !important; } .post-edit-widget .monaco-button .codicon { diff --git a/src/vs/editor/contrib/dropOrPasteInto/browser/postEditWidget.ts b/src/vs/editor/contrib/dropOrPasteInto/browser/postEditWidget.ts index 6be92c50daa25..638145d25d04c 100644 --- a/src/vs/editor/contrib/dropOrPasteInto/browser/postEditWidget.ts +++ b/src/vs/editor/contrib/dropOrPasteInto/browser/postEditWidget.ts @@ -5,25 +5,26 @@ import * as dom from '../../../../base/browser/dom.js'; import { Button } from '../../../../base/browser/ui/button/button.js'; -import { toAction } from '../../../../base/common/actions.js'; +import { IAction } from '../../../../base/common/actions.js'; import { CancellationToken } from '../../../../base/common/cancellation.js'; import { toErrorMessage } from '../../../../base/common/errorMessage.js'; import { isCancellationError } from '../../../../base/common/errors.js'; import { Event } from '../../../../base/common/event.js'; import { Disposable, MutableDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; -import './postEditWidget.css'; +import { localize } from '../../../../nls.js'; +import { ActionListItemKind, IActionListItem } from '../../../../platform/actionWidget/browser/actionList.js'; +import { IActionWidgetService } from '../../../../platform/actionWidget/browser/actionWidget.js'; +import { IContextKey, IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js'; +import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; +import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; +import { INotificationService } from '../../../../platform/notification/common/notification.js'; import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from '../../../browser/editorBrowser.js'; import { IBulkEditResult, IBulkEditService } from '../../../browser/services/bulkEditService.js'; import { Range } from '../../../common/core/range.js'; import { DocumentDropEdit, DocumentPasteEdit } from '../../../common/languages.js'; import { TrackedRangeStickiness } from '../../../common/model.js'; import { createCombinedWorkspaceEdit } from './edit.js'; -import { localize } from '../../../../nls.js'; -import { IContextKey, IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js'; -import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js'; -import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; -import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; -import { INotificationService } from '../../../../platform/notification/common/notification.js'; +import './postEditWidget.css'; interface EditSet { @@ -55,9 +56,10 @@ class PostEditWidget extends Dis private readonly range: Range, private readonly edits: EditSet, private readonly onSelectNewEdit: (editIndex: number) => void, - @IContextMenuService private readonly _contextMenuService: IContextMenuService, + private readonly additionalActions: readonly IAction[], @IContextKeyService contextKeyService: IContextKeyService, @IKeybindingService private readonly _keybindingService: IKeybindingService, + @IActionWidgetService private readonly _actionWidgetService: IActionWidgetService, ) { super(); @@ -73,9 +75,7 @@ class PostEditWidget extends Dis this._register(toDisposable((() => this.editor.removeContentWidget(this)))); this._register(this.editor.onDidChangeCursorPosition(e => { - if (!range.containsPosition(e.position)) { - this.dispose(); - } + this.dispose(); })); this._register(Event.runAndSubscribe(_keybindingService.onDidUpdateKeybindings, () => { @@ -115,24 +115,33 @@ class PostEditWidget extends Dis } showSelector() { - this._contextMenuService.showContextMenu({ - getAnchor: () => { - const pos = dom.getDomNodePagePosition(this.button.element); - return { x: pos.left + pos.width, y: pos.top + pos.height }; - }, - getActions: () => { - return this.edits.allEdits.map((edit, i) => toAction({ - id: '', + const pos = dom.getDomNodePagePosition(this.button.element); + const anchor = { x: pos.left + pos.width, y: pos.top + pos.height }; + + this._actionWidgetService.show('postEditWidget', false, [ + ...this.edits.allEdits.map((edit, i): IActionListItem => { + return { + item: edit, + kind: ActionListItemKind.Action, label: edit.title, - checked: i === this.edits.activeEditIndex, - run: () => { - if (i !== this.edits.activeEditIndex) { - return this.onSelectNewEdit(i); - } - }, - })); - } - }); + disabled: false, + canPreview: false, + hideIcon: true + }; + }) + ], { + onHide: () => { + this.editor.focus(); + }, + onSelect: (item) => { + this._actionWidgetService.hide(false); + + const i = this.edits.allEdits.findIndex(edit => edit === item); + if (i !== this.edits.activeEditIndex) { + return this.onSelectNewEdit(i); + } + }, + }, anchor, this.editor.getDomNode() ?? undefined, this.additionalActions); } } @@ -145,6 +154,7 @@ export class PostEditWidgetManager, private readonly _showCommand: ShowCommand, + private readonly _getAdditionalActions: () => readonly IAction[], @IInstantiationService private readonly _instantiationService: IInstantiationService, @IBulkEditService private readonly _bulkEditService: IBulkEditService, @INotificationService private readonly _notificationService: INotificationService, @@ -234,7 +244,7 @@ export class PostEditWidgetManager, this._id, this._editor, this._visibleContext, this._showCommand, range, edits, onDidSelectEdit); + this._currentWidget.value = this._instantiationService.createInstance(PostEditWidget, this._id, this._editor, this._visibleContext, this._showCommand, range, edits, onDidSelectEdit, this._getAdditionalActions()); } } diff --git a/src/vs/editor/contrib/find/browser/findController.ts b/src/vs/editor/contrib/find/browser/findController.ts index 5033c2565d9b3..6d56e4d662c96 100644 --- a/src/vs/editor/contrib/find/browser/findController.ts +++ b/src/vs/editor/contrib/find/browser/findController.ts @@ -523,8 +523,7 @@ export class FindController extends CommonFindController implements IFindControl export const StartFindAction = registerMultiEditorAction(new MultiEditorAction({ id: FIND_IDS.StartFindAction, - label: nls.localize('startFindAction', "Find"), - alias: 'Find', + label: nls.localize2('startFindAction', "Find"), precondition: ContextKeyExpr.or(EditorContextKeys.focus, ContextKeyExpr.has('editorIsOpen')), kbOpts: { kbExpr: null, @@ -579,8 +578,7 @@ export class StartFindWithArgsAction extends EditorAction { constructor() { super({ id: FIND_IDS.StartFindWithArgs, - label: nls.localize('startFindWithArgsAction', "Find With Arguments"), - alias: 'Find With Arguments', + label: nls.localize2('startFindWithArgsAction', "Find With Arguments"), precondition: undefined, kbOpts: { kbExpr: null, @@ -629,8 +627,7 @@ export class StartFindWithSelectionAction extends EditorAction { constructor() { super({ id: FIND_IDS.StartFindWithSelection, - label: nls.localize('startFindWithSelectionAction', "Find With Selection"), - alias: 'Find With Selection', + label: nls.localize2('startFindWithSelectionAction', "Find With Selection"), precondition: undefined, kbOpts: { kbExpr: null, @@ -687,8 +684,7 @@ export class NextMatchFindAction extends MatchFindAction { constructor() { super({ id: FIND_IDS.NextMatchFindAction, - label: nls.localize('findNextMatchAction', "Find Next"), - alias: 'Find Next', + label: nls.localize2('findNextMatchAction', "Find Next"), precondition: undefined, kbOpts: [{ kbExpr: EditorContextKeys.focus, @@ -720,8 +716,7 @@ export class PreviousMatchFindAction extends MatchFindAction { constructor() { super({ id: FIND_IDS.PreviousMatchFindAction, - label: nls.localize('findPreviousMatchAction', "Find Previous"), - alias: 'Find Previous', + label: nls.localize2('findPreviousMatchAction', "Find Previous"), precondition: undefined, kbOpts: [{ kbExpr: EditorContextKeys.focus, @@ -748,8 +743,7 @@ export class MoveToMatchFindAction extends EditorAction { constructor() { super({ id: FIND_IDS.GoToMatchFindAction, - label: nls.localize('findMatchAction.goToMatch', "Go to Match..."), - alias: 'Go to Match...', + label: nls.localize2('findMatchAction.goToMatch', "Go to Match..."), precondition: CONTEXT_FIND_WIDGET_VISIBLE }); } @@ -894,8 +888,7 @@ export class NextSelectionMatchFindAction extends SelectionMatchFindAction { constructor() { super({ id: FIND_IDS.NextSelectionMatchFindAction, - label: nls.localize('nextSelectionMatchFindAction', "Find Next Selection"), - alias: 'Find Next Selection', + label: nls.localize2('nextSelectionMatchFindAction', "Find Next Selection"), precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.focus, @@ -915,8 +908,7 @@ export class PreviousSelectionMatchFindAction extends SelectionMatchFindAction { constructor() { super({ id: FIND_IDS.PreviousSelectionMatchFindAction, - label: nls.localize('previousSelectionMatchFindAction', "Find Previous Selection"), - alias: 'Find Previous Selection', + label: nls.localize2('previousSelectionMatchFindAction', "Find Previous Selection"), precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.focus, @@ -933,8 +925,7 @@ export class PreviousSelectionMatchFindAction extends SelectionMatchFindAction { export const StartFindReplaceAction = registerMultiEditorAction(new MultiEditorAction({ id: FIND_IDS.StartFindReplaceAction, - label: nls.localize('startReplace', "Replace"), - alias: 'Replace', + label: nls.localize2('startReplace', "Replace"), precondition: ContextKeyExpr.or(EditorContextKeys.focus, ContextKeyExpr.has('editorIsOpen')), kbOpts: { kbExpr: null, diff --git a/src/vs/editor/contrib/find/browser/findWidget.ts b/src/vs/editor/contrib/find/browser/findWidget.ts index bb3054e0db15b..b3a6a04e3a14c 100644 --- a/src/vs/editor/contrib/find/browser/findWidget.ts +++ b/src/vs/editor/contrib/find/browser/findWidget.ts @@ -46,6 +46,8 @@ import { Selection } from '../../../common/core/selection.js'; import { createInstantHoverDelegate, getDefaultHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js'; import { IHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegate.js'; import { IHoverService } from '../../../../platform/hover/browser/hover.js'; +import { FindWidgetSearchHistory } from './findWidgetSearchHistory.js'; +import { IHistory } from '../../../../base/common/history.js'; const findCollapsedIcon = registerIcon('find-collapsed', Codicon.chevronRight, nls.localize('findCollapsedIcon', 'Icon to indicate that the editor find widget is collapsed.')); const findExpandedIcon = registerIcon('find-expanded', Codicon.chevronDown, nls.localize('findExpandedIcon', 'Icon to indicate that the editor find widget is expanded.')); @@ -131,6 +133,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL private readonly _contextKeyService: IContextKeyService; private readonly _storageService: IStorageService; private readonly _notificationService: INotificationService; + private _findWidgetSearchHistory: IHistory; private _domNode!: HTMLElement; private _cachedHeight: number | null = null; @@ -183,6 +186,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL this._contextKeyService = contextKeyService; this._storageService = storageService; this._notificationService = notificationService; + this._findWidgetSearchHistory = new FindWidgetSearchHistory(this._storageService); this._ctrlEnterReplaceAllWarningPrompted = !!storageService.getBoolean(ctrlEnterReplaceAllWarningPromptedKey, StorageScope.PROFILE); @@ -939,6 +943,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL const flexibleHeight = true; const flexibleWidth = true; // Find input + const findSearchHistoryConfig = this._codeEditor.getOption(EditorOption.find).findSearchHistory; this._findInput = this._register(new ContextScopedFindInput(null, this._contextViewProvider, { width: FIND_INPUT_AREA_WIDTH, label: NLS_FIND_INPUT_LABEL, @@ -964,7 +969,8 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL showCommonFindToggles: true, showHistoryHint: () => showHistoryKeybindingHint(this._keybindingService), inputBoxStyles: defaultInputBoxStyles, - toggleStyles: defaultToggleStyles + toggleStyles: defaultToggleStyles, + history: findSearchHistoryConfig === 'workspace' ? this._findWidgetSearchHistory : new Set([]), }, this._contextKeyService)); this._findInput.setRegex(!!this._state.isRegex); this._findInput.setCaseSensitive(!!this._state.matchCase); diff --git a/src/vs/editor/contrib/find/browser/findWidgetSearchHistory.ts b/src/vs/editor/contrib/find/browser/findWidgetSearchHistory.ts new file mode 100644 index 0000000000000..0dd6f0753a1ac --- /dev/null +++ b/src/vs/editor/contrib/find/browser/findWidgetSearchHistory.ts @@ -0,0 +1,82 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IHistory } from '../../../../base/common/history.js'; +import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; + +export class FindWidgetSearchHistory implements IHistory { + public static readonly FIND_HISTORY_KEY = 'workbench.find.history'; + private inMemoryValues: Set = new Set(); + + constructor( + @IStorageService private readonly storageService: IStorageService, + ) { + this.load(); + } + + delete(t: string): boolean { + const result = this.inMemoryValues.delete(t); + this.save(); + return result; + } + + add(t: string): this { + this.inMemoryValues.add(t); + this.save(); + return this; + } + + has(t: string): boolean { + return this.inMemoryValues.has(t); + } + + clear(): void { + this.inMemoryValues.clear(); + this.save(); + } + + forEach(callbackfn: (value: string, value2: string, set: Set) => void, thisArg?: any): void { + // fetch latest from storage + this.load(); + return this.inMemoryValues.forEach(callbackfn); + } + replace?(t: string[]): void { + this.inMemoryValues = new Set(t); + this.save(); + } + + load() { + let result: [] | undefined; + const raw = this.storageService.get( + FindWidgetSearchHistory.FIND_HISTORY_KEY, + StorageScope.WORKSPACE + ); + + if (raw) { + try { + result = JSON.parse(raw); + } catch (e) { + // Invalid data + } + } + + this.inMemoryValues = new Set(result || []); + } + + // Run saves async + save(): Promise { + const elements: string[] = []; + this.inMemoryValues.forEach(e => elements.push(e)); + return new Promise(resolve => { + this.storageService.store( + FindWidgetSearchHistory.FIND_HISTORY_KEY, + JSON.stringify(elements), + StorageScope.WORKSPACE, + StorageTarget.USER, + ); + resolve(); + }); + } +} diff --git a/src/vs/editor/contrib/folding/browser/folding.ts b/src/vs/editor/contrib/folding/browser/folding.ts index ee657d44e14b9..dedd1ce796424 100644 --- a/src/vs/editor/contrib/folding/browser/folding.ts +++ b/src/vs/editor/contrib/folding/browser/folding.ts @@ -635,8 +635,7 @@ class UnfoldAction extends FoldingAction { constructor() { super({ id: 'editor.unfold', - label: nls.localize('unfoldAction.label', "Unfold"), - alias: 'Unfold', + label: nls.localize2('unfoldAction.label', "Unfold"), precondition: CONTEXT_FOLDING_ENABLED, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, @@ -699,8 +698,7 @@ class UnFoldRecursivelyAction extends FoldingAction { constructor() { super({ id: 'editor.unfoldRecursively', - label: nls.localize('unFoldRecursivelyAction.label', "Unfold Recursively"), - alias: 'Unfold Recursively', + label: nls.localize2('unFoldRecursivelyAction.label', "Unfold Recursively"), precondition: CONTEXT_FOLDING_ENABLED, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, @@ -720,8 +718,7 @@ class FoldAction extends FoldingAction { constructor() { super({ id: 'editor.fold', - label: nls.localize('foldAction.label', "Fold"), - alias: 'Fold', + label: nls.localize2('foldAction.label', "Fold"), precondition: CONTEXT_FOLDING_ENABLED, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, @@ -792,8 +789,7 @@ class ToggleFoldAction extends FoldingAction { constructor() { super({ id: 'editor.toggleFold', - label: nls.localize('toggleFoldAction.label', "Toggle Fold"), - alias: 'Toggle Fold', + label: nls.localize2('toggleFoldAction.label', "Toggle Fold"), precondition: CONTEXT_FOLDING_ENABLED, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, @@ -815,8 +811,7 @@ class FoldRecursivelyAction extends FoldingAction { constructor() { super({ id: 'editor.foldRecursively', - label: nls.localize('foldRecursivelyAction.label', "Fold Recursively"), - alias: 'Fold Recursively', + label: nls.localize2('foldRecursivelyAction.label', "Fold Recursively"), precondition: CONTEXT_FOLDING_ENABLED, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, @@ -838,8 +833,7 @@ class ToggleFoldRecursivelyAction extends FoldingAction { constructor() { super({ id: 'editor.toggleFoldRecursively', - label: nls.localize('toggleFoldRecursivelyAction.label', "Toggle Fold Recursively"), - alias: 'Toggle Fold Recursively', + label: nls.localize2('toggleFoldRecursivelyAction.label', "Toggle Fold Recursively"), precondition: CONTEXT_FOLDING_ENABLED, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, @@ -861,8 +855,7 @@ class FoldAllBlockCommentsAction extends FoldingAction { constructor() { super({ id: 'editor.foldAllBlockComments', - label: nls.localize('foldAllBlockComments.label', "Fold All Block Comments"), - alias: 'Fold All Block Comments', + label: nls.localize2('foldAllBlockComments.label', "Fold All Block Comments"), precondition: CONTEXT_FOLDING_ENABLED, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, @@ -894,8 +887,7 @@ class FoldAllRegionsAction extends FoldingAction { constructor() { super({ id: 'editor.foldAllMarkerRegions', - label: nls.localize('foldAllMarkerRegions.label', "Fold All Regions"), - alias: 'Fold All Regions', + label: nls.localize2('foldAllMarkerRegions.label', "Fold All Regions"), precondition: CONTEXT_FOLDING_ENABLED, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, @@ -927,8 +919,7 @@ class UnfoldAllRegionsAction extends FoldingAction { constructor() { super({ id: 'editor.unfoldAllMarkerRegions', - label: nls.localize('unfoldAllMarkerRegions.label', "Unfold All Regions"), - alias: 'Unfold All Regions', + label: nls.localize2('unfoldAllMarkerRegions.label', "Unfold All Regions"), precondition: CONTEXT_FOLDING_ENABLED, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, @@ -960,8 +951,7 @@ class FoldAllExceptAction extends FoldingAction { constructor() { super({ id: 'editor.foldAllExcept', - label: nls.localize('foldAllExcept.label', "Fold All Except Selected"), - alias: 'Fold All Except Selected', + label: nls.localize2('foldAllExcept.label', "Fold All Except Selected"), precondition: CONTEXT_FOLDING_ENABLED, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, @@ -983,8 +973,7 @@ class UnfoldAllExceptAction extends FoldingAction { constructor() { super({ id: 'editor.unfoldAllExcept', - label: nls.localize('unfoldAllExcept.label', "Unfold All Except Selected"), - alias: 'Unfold All Except Selected', + label: nls.localize2('unfoldAllExcept.label', "Unfold All Except Selected"), precondition: CONTEXT_FOLDING_ENABLED, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, @@ -1005,8 +994,7 @@ class FoldAllAction extends FoldingAction { constructor() { super({ id: 'editor.foldAll', - label: nls.localize('foldAllAction.label', "Fold All"), - alias: 'Fold All', + label: nls.localize2('foldAllAction.label', "Fold All"), precondition: CONTEXT_FOLDING_ENABLED, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, @@ -1026,8 +1014,7 @@ class UnfoldAllAction extends FoldingAction { constructor() { super({ id: 'editor.unfoldAll', - label: nls.localize('unfoldAllAction.label', "Unfold All"), - alias: 'Unfold All', + label: nls.localize2('unfoldAllAction.label', "Unfold All"), precondition: CONTEXT_FOLDING_ENABLED, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, @@ -1060,8 +1047,7 @@ class GotoParentFoldAction extends FoldingAction { constructor() { super({ id: 'editor.gotoParentFold', - label: nls.localize('gotoParentFold.label', "Go to Parent Fold"), - alias: 'Go to Parent Fold', + label: nls.localize2('gotoParentFold.label', "Go to Parent Fold"), precondition: CONTEXT_FOLDING_ENABLED, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, @@ -1091,8 +1077,7 @@ class GotoPreviousFoldAction extends FoldingAction { constructor() { super({ id: 'editor.gotoPreviousFold', - label: nls.localize('gotoPreviousFold.label', "Go to Previous Folding Range"), - alias: 'Go to Previous Folding Range', + label: nls.localize2('gotoPreviousFold.label', "Go to Previous Folding Range"), precondition: CONTEXT_FOLDING_ENABLED, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, @@ -1122,8 +1107,7 @@ class GotoNextFoldAction extends FoldingAction { constructor() { super({ id: 'editor.gotoNextFold', - label: nls.localize('gotoNextFold.label', "Go to Next Folding Range"), - alias: 'Go to Next Folding Range', + label: nls.localize2('gotoNextFold.label', "Go to Next Folding Range"), precondition: CONTEXT_FOLDING_ENABLED, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, @@ -1153,8 +1137,7 @@ class FoldRangeFromSelectionAction extends FoldingAction { constructor() { super({ id: 'editor.createFoldingRangeFromSelection', - label: nls.localize('createManualFoldRange.label', "Create Folding Range from Selection"), - alias: 'Create Folding Range from Selection', + label: nls.localize2('createManualFoldRange.label', "Create Folding Range from Selection"), precondition: CONTEXT_FOLDING_ENABLED, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, @@ -1205,8 +1188,7 @@ class RemoveFoldRangeFromSelectionAction extends FoldingAction { constructor() { super({ id: 'editor.removeManualFoldingRanges', - label: nls.localize('removeManualFoldingRanges.label', "Remove Manual Folding Ranges"), - alias: 'Remove Manual Folding Ranges', + label: nls.localize2('removeManualFoldingRanges.label', "Remove Manual Folding Ranges"), precondition: CONTEXT_FOLDING_ENABLED, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, @@ -1236,8 +1218,7 @@ class ToggleImportFoldAction extends FoldingAction { constructor() { super({ id: 'editor.toggleImportFold', - label: nls.localize('toggleImportFold.label', "Toggle Import Fold"), - alias: 'Toggle Import Fold', + label: nls.localize2('toggleImportFold.label', "Toggle Import Fold"), precondition: CONTEXT_FOLDING_ENABLED, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, @@ -1285,8 +1266,7 @@ for (let i = 1; i <= 7; i++) { registerInstantiatedEditorAction( new FoldLevelAction({ id: FoldLevelAction.ID(i), - label: nls.localize('foldLevelAction.label', "Fold Level {0}", i), - alias: `Fold Level ${i}`, + label: nls.localize2('foldLevelAction.label', "Fold Level {0}", i), precondition: CONTEXT_FOLDING_ENABLED, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, diff --git a/src/vs/editor/contrib/fontZoom/browser/fontZoom.ts b/src/vs/editor/contrib/fontZoom/browser/fontZoom.ts index 8093cfb91af49..26253866f1984 100644 --- a/src/vs/editor/contrib/fontZoom/browser/fontZoom.ts +++ b/src/vs/editor/contrib/fontZoom/browser/fontZoom.ts @@ -13,8 +13,7 @@ class EditorFontZoomIn extends EditorAction { constructor() { super({ id: 'editor.action.fontZoomIn', - label: nls.localize('EditorFontZoomIn.label', "Increase Editor Font Size"), - alias: 'Increase Editor Font Size', + label: nls.localize2('EditorFontZoomIn.label', "Increase Editor Font Size"), precondition: undefined }); } @@ -29,8 +28,7 @@ class EditorFontZoomOut extends EditorAction { constructor() { super({ id: 'editor.action.fontZoomOut', - label: nls.localize('EditorFontZoomOut.label', "Decrease Editor Font Size"), - alias: 'Decrease Editor Font Size', + label: nls.localize2('EditorFontZoomOut.label', "Decrease Editor Font Size"), precondition: undefined }); } @@ -45,8 +43,7 @@ class EditorFontZoomReset extends EditorAction { constructor() { super({ id: 'editor.action.fontZoomReset', - label: nls.localize('EditorFontZoomReset.label', "Reset Editor Font Size"), - alias: 'Reset Editor Font Size', + label: nls.localize2('EditorFontZoomReset.label', "Reset Editor Font Size"), precondition: undefined }); } diff --git a/src/vs/editor/contrib/format/browser/formatActions.ts b/src/vs/editor/contrib/format/browser/formatActions.ts index 6f43b2ddc5c51..f170a3e51677d 100644 --- a/src/vs/editor/contrib/format/browser/formatActions.ts +++ b/src/vs/editor/contrib/format/browser/formatActions.ts @@ -214,8 +214,7 @@ class FormatDocumentAction extends EditorAction { constructor() { super({ id: 'editor.action.formatDocument', - label: nls.localize('formatDocument.label', "Format Document"), - alias: 'Format Document', + label: nls.localize2('formatDocument.label', "Format Document"), precondition: ContextKeyExpr.and(EditorContextKeys.notInCompositeEditor, EditorContextKeys.writable, EditorContextKeys.hasDocumentFormattingProvider), kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, @@ -247,8 +246,7 @@ class FormatSelectionAction extends EditorAction { constructor() { super({ id: 'editor.action.formatSelection', - label: nls.localize('formatSelection.label', "Format Selection"), - alias: 'Format Selection', + label: nls.localize2('formatSelection.label', "Format Selection"), precondition: ContextKeyExpr.and(EditorContextKeys.writable, EditorContextKeys.hasDocumentSelectionFormattingProvider), kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, diff --git a/src/vs/editor/contrib/gotoError/browser/gotoError.ts b/src/vs/editor/contrib/gotoError/browser/gotoError.ts index 1dc7c5833ea37..6c121121be549 100644 --- a/src/vs/editor/contrib/gotoError/browser/gotoError.ts +++ b/src/vs/editor/contrib/gotoError/browser/gotoError.ts @@ -185,12 +185,11 @@ class MarkerNavigationAction extends EditorAction { export class NextMarkerAction extends MarkerNavigationAction { static ID: string = 'editor.action.marker.next'; - static LABEL: string = nls.localize('markerAction.next.label', "Go to Next Problem (Error, Warning, Info)"); + static LABEL = nls.localize2('markerAction.next.label', "Go to Next Problem (Error, Warning, Info)"); constructor() { super(true, false, { id: NextMarkerAction.ID, label: NextMarkerAction.LABEL, - alias: 'Go to Next Problem (Error, Warning, Info)', precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.focus, @@ -199,7 +198,7 @@ export class NextMarkerAction extends MarkerNavigationAction { }, menuOpts: { menuId: MarkerNavigationWidget.TitleMenu, - title: NextMarkerAction.LABEL, + title: NextMarkerAction.LABEL.value, icon: registerIcon('marker-navigation-next', Codicon.arrowDown, nls.localize('nextMarkerIcon', 'Icon for goto next marker.')), group: 'navigation', order: 1 @@ -210,12 +209,11 @@ export class NextMarkerAction extends MarkerNavigationAction { class PrevMarkerAction extends MarkerNavigationAction { static ID: string = 'editor.action.marker.prev'; - static LABEL: string = nls.localize('markerAction.previous.label', "Go to Previous Problem (Error, Warning, Info)"); + static LABEL = nls.localize2('markerAction.previous.label', "Go to Previous Problem (Error, Warning, Info)"); constructor() { super(false, false, { id: PrevMarkerAction.ID, label: PrevMarkerAction.LABEL, - alias: 'Go to Previous Problem (Error, Warning, Info)', precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.focus, @@ -224,7 +222,7 @@ class PrevMarkerAction extends MarkerNavigationAction { }, menuOpts: { menuId: MarkerNavigationWidget.TitleMenu, - title: PrevMarkerAction.LABEL, + title: PrevMarkerAction.LABEL.value, icon: registerIcon('marker-navigation-previous', Codicon.arrowUp, nls.localize('previousMarkerIcon', 'Icon for goto previous marker.')), group: 'navigation', order: 2 @@ -237,8 +235,7 @@ class NextMarkerInFilesAction extends MarkerNavigationAction { constructor() { super(true, true, { id: 'editor.action.marker.nextInFiles', - label: nls.localize('markerAction.nextInFiles.label', "Go to Next Problem in Files (Error, Warning, Info)"), - alias: 'Go to Next Problem in Files (Error, Warning, Info)', + label: nls.localize2('markerAction.nextInFiles.label', "Go to Next Problem in Files (Error, Warning, Info)"), precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.focus, @@ -259,8 +256,7 @@ class PrevMarkerInFilesAction extends MarkerNavigationAction { constructor() { super(false, true, { id: 'editor.action.marker.prevInFiles', - label: nls.localize('markerAction.previousInFiles.label', "Go to Previous Problem in Files (Error, Warning, Info)"), - alias: 'Go to Previous Problem in Files (Error, Warning, Info)', + label: nls.localize2('markerAction.previousInFiles.label', "Go to Previous Problem in Files (Error, Warning, Info)"), precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.focus, diff --git a/src/vs/editor/contrib/gotoError/browser/gotoErrorWidget.ts b/src/vs/editor/contrib/gotoError/browser/gotoErrorWidget.ts index b757cfd5de1ef..5a8bc4fb7ed7f 100644 --- a/src/vs/editor/contrib/gotoError/browser/gotoErrorWidget.ts +++ b/src/vs/editor/contrib/gotoError/browser/gotoErrorWidget.ts @@ -5,7 +5,6 @@ import * as dom from '../../../../base/browser/dom.js'; import { ScrollableElement } from '../../../../base/browser/ui/scrollbar/scrollableElement.js'; -import { IAction } from '../../../../base/common/actions.js'; import { isNonEmptyArray } from '../../../../base/common/arrays.js'; import { Color } from '../../../../base/common/color.js'; import { Emitter, Event } from '../../../../base/common/event.js'; @@ -20,7 +19,7 @@ import { Range } from '../../../common/core/range.js'; import { ScrollType } from '../../../common/editorCommon.js'; import { peekViewTitleForeground, peekViewTitleInfoForeground, PeekViewWidget } from '../../peekView/browser/peekView.js'; import * as nls from '../../../../nls.js'; -import { createAndFillInActionBarActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { getFlatActionBarActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { IMenuService, MenuId } from '../../../../platform/actions/common/actions.js'; import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; @@ -307,9 +306,8 @@ export class MarkerNavigationWidget extends PeekViewWidget { this._disposables.add(this._actionbarWidget!.actionRunner.onWillRun(e => this.editor.focus())); - const actions: IAction[] = []; const menu = this._menuService.getMenuActions(MarkerNavigationWidget.TitleMenu, this._contextKeyService); - createAndFillInActionBarActions(menu, actions); + const actions = getFlatActionBarActions(menu); this._actionbarWidget!.push(actions, { label: false, icon: true, index: 0 }); } diff --git a/src/vs/editor/contrib/gotoSymbol/browser/peek/referencesTree.ts b/src/vs/editor/contrib/gotoSymbol/browser/peek/referencesTree.ts index 15e550cfcfc87..c6cda84967568 100644 --- a/src/vs/editor/contrib/gotoSymbol/browser/peek/referencesTree.ts +++ b/src/vs/editor/contrib/gotoSymbol/browser/peek/referencesTree.ts @@ -118,7 +118,7 @@ class FileReferencesTemplate extends Disposable { parent.classList.add('reference-file'); this.file = this._register(new IconLabel(parent, { supportHighlights: true })); - this.badge = new CountBadge(dom.append(parent, dom.$('.count')), {}, defaultCountBadgeStyles); + this.badge = this._register(new CountBadge(dom.append(parent, dom.$('.count')), {}, defaultCountBadgeStyles)); container.appendChild(parent); } diff --git a/src/vs/editor/contrib/gpu/browser/gpuActions.ts b/src/vs/editor/contrib/gpu/browser/gpuActions.ts index e029dd3632c3e..29de263652703 100644 --- a/src/vs/editor/contrib/gpu/browser/gpuActions.ts +++ b/src/vs/editor/contrib/gpu/browser/gpuActions.ts @@ -5,7 +5,7 @@ import { VSBuffer } from '../../../../base/common/buffer.js'; import { URI } from '../../../../base/common/uri.js'; -import { localize } from '../../../../nls.js'; +import { localize, localize2 } from '../../../../nls.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js'; import { IFileService } from '../../../../platform/files/common/files.js'; @@ -24,8 +24,7 @@ class DebugEditorGpuRendererAction extends EditorAction { constructor() { super({ id: 'editor.action.debugEditorGpuRenderer', - label: localize('gpuDebug.label', "Developer: Debug Editor GPU Renderer"), - alias: 'Developer: Debug Editor GPU Renderer', + label: localize2('gpuDebug.label', "Developer: Debug Editor GPU Renderer"), // TODO: Why doesn't `ContextKeyExpr.equals('config:editor.experimentalGpuAcceleration', 'on')` work? precondition: ContextKeyExpr.true(), }); diff --git a/src/vs/editor/contrib/hover/browser/contentHoverComputer.ts b/src/vs/editor/contrib/hover/browser/contentHoverComputer.ts index c24cc1607494d..6306de9e5f77f 100644 --- a/src/vs/editor/contrib/hover/browser/contentHoverComputer.ts +++ b/src/vs/editor/contrib/hover/browser/contentHoverComputer.ts @@ -78,7 +78,7 @@ export class ContentHoverComputer implements IHoverComputer()); @@ -57,10 +52,7 @@ export class ContentHoverController extends Disposable implements IEditorContrib private _reactToEditorMouseMoveRunner: RunOnceScheduler; private _hoverSettings!: IHoverSettings; - private _hoverState: IHoverState = { - mouseDown: false, - activatedByDecoratorClick: false - }; + private _isMouseDown: boolean = false; constructor( private readonly _editor: ICodeEditor, @@ -68,11 +60,9 @@ export class ContentHoverController extends Disposable implements IEditorContrib @IKeybindingService private readonly _keybindingService: IKeybindingService ) { super(); - this._reactToEditorMouseMoveRunner = this._register( - new RunOnceScheduler( - () => this._reactToEditorMouseMove(this._mouseMoveEvent), 0 - ) - ); + this._reactToEditorMouseMoveRunner = this._register(new RunOnceScheduler( + () => this._reactToEditorMouseMove(this._mouseMoveEvent), 0 + )); this._hookListeners(); this._register(this._editor.onDidChangeConfiguration((e: ConfigurationChangedEvent) => { if (e.hasChanged(EditorOption.hover)) { @@ -87,37 +77,35 @@ export class ContentHoverController extends Disposable implements IEditorContrib } private _hookListeners(): void { - const hoverOpts = this._editor.getOption(EditorOption.hover); this._hoverSettings = { enabled: hoverOpts.enabled, sticky: hoverOpts.sticky, hidingDelay: hoverOpts.hidingDelay }; - if (hoverOpts.enabled) { this._listenersStore.add(this._editor.onMouseDown((e: IEditorMouseEvent) => this._onEditorMouseDown(e))); this._listenersStore.add(this._editor.onMouseUp(() => this._onEditorMouseUp())); this._listenersStore.add(this._editor.onMouseMove((e: IEditorMouseEvent) => this._onEditorMouseMove(e))); this._listenersStore.add(this._editor.onKeyDown((e: IKeyboardEvent) => this._onKeyDown(e))); + this._listenersStore.add(this._editor.onMouseLeave((e) => this._onEditorMouseLeave(e))); + this._listenersStore.add(this._editor.onDidChangeModel(() => this._cancelSchedulerAndHide())); + this._listenersStore.add(this._editor.onDidChangeModelContent(() => this._cancelScheduler())); + this._listenersStore.add(this._editor.onDidScrollChange((e: IScrollEvent) => this._onEditorScrollChanged(e))); } else { - this._listenersStore.add(this._editor.onMouseMove((e: IEditorMouseEvent) => this._onEditorMouseMove(e))); - this._listenersStore.add(this._editor.onKeyDown((e: IKeyboardEvent) => this._onKeyDown(e))); + this._cancelSchedulerAndHide(); } - - this._listenersStore.add(this._editor.onMouseLeave((e) => this._onEditorMouseLeave(e))); - this._listenersStore.add(this._editor.onDidChangeModel(() => { - this._cancelScheduler(); - this._hideWidgets(); - })); - this._listenersStore.add(this._editor.onDidChangeModelContent(() => this._cancelScheduler())); - this._listenersStore.add(this._editor.onDidScrollChange((e: IScrollEvent) => this._onEditorScrollChanged(e))); } private _unhookListeners(): void { this._listenersStore.clear(); } + private _cancelSchedulerAndHide(): void { + this._cancelScheduler(); + this.hideContentHover(); + } + private _cancelScheduler() { this._mouseMoveEvent = undefined; this._reactToEditorMouseMoveRunner.cancel(); @@ -125,53 +113,47 @@ export class ContentHoverController extends Disposable implements IEditorContrib private _onEditorScrollChanged(e: IScrollEvent): void { if (e.scrollTopChanged || e.scrollLeftChanged) { - this._hideWidgets(); + this.hideContentHover(); } } private _onEditorMouseDown(mouseEvent: IEditorMouseEvent): void { - - this._hoverState.mouseDown = true; - - const shouldNotHideCurrentHoverWidget = this._shouldNotHideCurrentHoverWidget(mouseEvent); - if (shouldNotHideCurrentHoverWidget) { + this._isMouseDown = true; + const shouldKeepHoverWidgetVisible = this._shouldKeepHoverWidgetVisible(mouseEvent); + if (shouldKeepHoverWidgetVisible) { return; } - - this._hideWidgets(); + this.hideContentHover(); } - private _shouldNotHideCurrentHoverWidget(mouseEvent: IPartialEditorMouseEvent): boolean { - return this._isMouseOnContentHoverWidget(mouseEvent) || this._isContentWidgetResizing(); + private _shouldKeepHoverWidgetVisible(mouseEvent: IPartialEditorMouseEvent): boolean { + return this._isMouseOnContentHoverWidget(mouseEvent) || this._isContentWidgetResizing() || isOnColorDecorator(mouseEvent); } private _isMouseOnContentHoverWidget(mouseEvent: IPartialEditorMouseEvent): boolean { - const contentWidgetNode = this._contentWidget?.getDomNode(); - if (contentWidgetNode) { - return isMousePositionWithinElement(contentWidgetNode, mouseEvent.event.posx, mouseEvent.event.posy); + if (!this._contentWidget) { + return false; } - return false; + return isMousePositionWithinElement(this._contentWidget.getDomNode(), mouseEvent.event.posx, mouseEvent.event.posy); } private _onEditorMouseUp(): void { - this._hoverState.mouseDown = false; + this._isMouseDown = false; } private _onEditorMouseLeave(mouseEvent: IPartialEditorMouseEvent): void { if (this.shouldKeepOpenOnEditorMouseMoveOrLeave) { return; } - this._cancelScheduler(); - - const shouldNotHideCurrentHoverWidget = this._shouldNotHideCurrentHoverWidget(mouseEvent); - if (shouldNotHideCurrentHoverWidget) { + const shouldKeepHoverWidgetVisible = this._shouldKeepHoverWidgetVisible(mouseEvent); + if (shouldKeepHoverWidgetVisible) { return; } if (_sticky) { return; } - this._hideWidgets(); + this.hideContentHover(); } private _shouldNotRecomputeCurrentHoverWidget(mouseEvent: IEditorMouseEvent): boolean { @@ -193,136 +175,110 @@ export class ContentHoverController extends Disposable implements IEditorContrib && this._contentWidget?.containsNode(mouseEvent.event.browserEvent.view?.document.activeElement) && !mouseEvent.event.browserEvent.view?.getSelection()?.isCollapsed) ?? false; }; - return isMouseOnStickyContentHoverWidget(mouseEvent, isHoverSticky) || isMouseOnColorPicker(mouseEvent) || isTextSelectedWithinContentHoverWidget(mouseEvent, isHoverSticky); } private _onEditorMouseMove(mouseEvent: IEditorMouseEvent): void { - if (this.shouldKeepOpenOnEditorMouseMoveOrLeave) { + const shouldReactToEditorMouseMove = this._shouldReactToEditorMouseMove(mouseEvent); + if (!shouldReactToEditorMouseMove) { return; } + const shouldRescheduleHoverComputation = this._shouldRescheduleHoverComputation(); + if (shouldRescheduleHoverComputation) { + if (!this._reactToEditorMouseMoveRunner.isScheduled()) { + this._reactToEditorMouseMoveRunner.schedule(this._hoverSettings.hidingDelay); + } + return; + } + this._reactToEditorMouseMove(mouseEvent); + } + private _shouldReactToEditorMouseMove(mouseEvent: IEditorMouseEvent): boolean { + if (this.shouldKeepOpenOnEditorMouseMoveOrLeave) { + return false; + } this._mouseMoveEvent = mouseEvent; - if (this._contentWidget?.isFocused || this._contentWidget?.isResizing) { - return; + if (this._contentWidget && (this._contentWidget.isFocused || this._contentWidget.isResizing || this._isMouseDown && this._contentWidget.isColorPickerVisible)) { + return false; } const sticky = this._hoverSettings.sticky; if (sticky && this._contentWidget?.isVisibleFromKeyboard) { // Sticky mode is on and the hover has been shown via keyboard // so moving the mouse has no effect - return; + return false; } - const shouldNotRecomputeCurrentHoverWidget = this._shouldNotRecomputeCurrentHoverWidget(mouseEvent); if (shouldNotRecomputeCurrentHoverWidget) { this._reactToEditorMouseMoveRunner.cancel(); - return; + return false; } + return true; + } + private _shouldRescheduleHoverComputation(): boolean { const hidingDelay = this._hoverSettings.hidingDelay; - const isContentHoverWidgetVisible = this._contentWidget?.isVisible; + const isContentHoverWidgetVisible = this._contentWidget?.isVisible ?? false; // If the mouse is not over the widget, and if sticky is on, // then give it a grace period before reacting to the mouse event - const shouldRescheduleHoverComputation = isContentHoverWidgetVisible && sticky && hidingDelay > 0; - - if (shouldRescheduleHoverComputation) { - if (!this._reactToEditorMouseMoveRunner.isScheduled()) { - this._reactToEditorMouseMoveRunner.schedule(hidingDelay); - } - return; - } - this._reactToEditorMouseMove(mouseEvent); + return isContentHoverWidgetVisible && this._hoverSettings.sticky && hidingDelay > 0; } private _reactToEditorMouseMove(mouseEvent: IEditorMouseEvent | undefined): void { - if (!mouseEvent) { return; } - - const target = mouseEvent.target; - const mouseOnDecorator = target.element?.classList.contains('colorpicker-color-decoration'); - const decoratorActivatedOn = this._editor.getOption(EditorOption.colorDecoratorsActivatedOn); - - const enabled = this._hoverSettings.enabled; - const activatedByDecoratorClick = this._hoverState.activatedByDecoratorClick; - if ( - ( - mouseOnDecorator && ( - (decoratorActivatedOn === 'click' && !activatedByDecoratorClick) || - (decoratorActivatedOn === 'hover' && !enabled && !_sticky) || - (decoratorActivatedOn === 'clickAndHover' && !enabled && !activatedByDecoratorClick)) - ) || ( - !mouseOnDecorator && !enabled && !activatedByDecoratorClick - ) - ) { - this._hideWidgets(); - return; - } - - const contentHoverShowsOrWillShow = this._tryShowHoverWidget(mouseEvent); - if (contentHoverShowsOrWillShow) { + const contentWidget: ContentHoverWidgetWrapper = this._getOrCreateContentWidget(); + if (contentWidget.showsOrWillShow(mouseEvent)) { return; } - if (_sticky) { return; } - this._hideWidgets(); - } - - private _tryShowHoverWidget(mouseEvent: IEditorMouseEvent): boolean { - const contentWidget: IHoverWidget = this._getOrCreateContentWidget(); - return contentWidget.showsOrWillShow(mouseEvent); + this.hideContentHover(); } private _onKeyDown(e: IKeyboardEvent): void { if (!this._editor.hasModel()) { return; } + const isPotentialKeyboardShortcut = this._isPotentialKeyboardShortcut(e); + const isModifierKeyPressed = this._isModifierKeyPressed(e); + if (isPotentialKeyboardShortcut || isModifierKeyPressed) { + return; + } + this.hideContentHover(); + } + private _isPotentialKeyboardShortcut(e: IKeyboardEvent): boolean { + if (!this._editor.hasModel() || !this._contentWidget) { + return false; + } const resolvedKeyboardEvent = this._keybindingService.softDispatch(e, this._editor.getDomNode()); + const moreChordsAreNeeded = resolvedKeyboardEvent.kind === ResultKind.MoreChordsNeeded; + const isHoverAction = resolvedKeyboardEvent.kind === ResultKind.KbFound + && (resolvedKeyboardEvent.commandId === SHOW_OR_FOCUS_HOVER_ACTION_ID + || resolvedKeyboardEvent.commandId === INCREASE_HOVER_VERBOSITY_ACTION_ID + || resolvedKeyboardEvent.commandId === DECREASE_HOVER_VERBOSITY_ACTION_ID) + && this._contentWidget.isVisible; + return moreChordsAreNeeded || isHoverAction; + } - // If the beginning of a multi-chord keybinding is pressed, - // or the command aims to focus the hover, - // set the variable to true, otherwise false - const shouldKeepHoverVisible = ( - resolvedKeyboardEvent.kind === ResultKind.MoreChordsNeeded || - (resolvedKeyboardEvent.kind === ResultKind.KbFound - && (resolvedKeyboardEvent.commandId === SHOW_OR_FOCUS_HOVER_ACTION_ID - || resolvedKeyboardEvent.commandId === INCREASE_HOVER_VERBOSITY_ACTION_ID - || resolvedKeyboardEvent.commandId === DECREASE_HOVER_VERBOSITY_ACTION_ID) - && this._contentWidget?.isVisible - ) - ); - - if ( - e.keyCode === KeyCode.Ctrl + private _isModifierKeyPressed(e: IKeyboardEvent): boolean { + return e.keyCode === KeyCode.Ctrl || e.keyCode === KeyCode.Alt || e.keyCode === KeyCode.Meta - || e.keyCode === KeyCode.Shift - || shouldKeepHoverVisible - ) { - // Do not hide hover when a modifier key is pressed - return; - } - - this._hideWidgets(); + || e.keyCode === KeyCode.Shift; } - private _hideWidgets(): void { + public hideContentHover(): void { if (_sticky) { return; } - if (( - this._hoverState.mouseDown - && this._contentWidget?.isColorPickerVisible - ) || InlineSuggestionHintsContentWidget.dropDownVisible) { + if (InlineSuggestionHintsContentWidget.dropDownVisible) { return; } - this._hoverState.activatedByDecoratorClick = false; this._contentWidget?.hide(); } @@ -334,18 +290,12 @@ export class ContentHoverController extends Disposable implements IEditorContrib return this._contentWidget; } - public hideContentHover(): void { - this._hideWidgets(); - } - public showContentHover( range: Range, mode: HoverStartMode, source: HoverStartSource, - focus: boolean, - activatedByColorDecoratorClick: boolean = false + focus: boolean ): void { - this._hoverState.activatedByDecoratorClick = activatedByColorDecoratorClick; this._getOrCreateContentWidget().startShowingAtRange(range, mode, source, focus); } diff --git a/src/vs/editor/contrib/hover/browser/contentHoverRendered.ts b/src/vs/editor/contrib/hover/browser/contentHoverRendered.ts index af9f9f7397bb5..c72ecfb5910bd 100644 --- a/src/vs/editor/contrib/hover/browser/contentHoverRendered.ts +++ b/src/vs/editor/contrib/hover/browser/contentHoverRendered.ts @@ -21,6 +21,7 @@ import { localize } from '../../../../nls.js'; import { InlayHintsHover } from '../../inlayHints/browser/inlayHintsHover.js'; import { BugIndicatingError } from '../../../../base/common/errors.js'; import { HoverAction } from '../../../../base/browser/ui/hover/hoverWidget.js'; +import { IHoverService } from '../../../../platform/hover/browser/hover.js'; export class RenderedContentHover extends Disposable { @@ -41,7 +42,8 @@ export class RenderedContentHover extends Disposable { hoverResult: ContentHoverResult, participants: IEditorHoverParticipant[], context: IEditorHoverContext, - keybindingService: IKeybindingService + @IKeybindingService keybindingService: IKeybindingService, + @IHoverService hoverService: IHoverService ) { super(); const parts = hoverResult.hoverParts; @@ -49,8 +51,9 @@ export class RenderedContentHover extends Disposable { editor, participants, parts, + context, keybindingService, - context + hoverService )); const contentHoverComputerOptions = hoverResult.options; const anchor = contentHoverComputerOptions.anchor; @@ -225,13 +228,14 @@ class RenderedContentHoverParts extends Disposable { editor: ICodeEditor, participants: IEditorHoverParticipant[], hoverParts: IHoverPart[], - keybindingService: IKeybindingService, - context: IEditorHoverContext + context: IEditorHoverContext, + @IKeybindingService keybindingService: IKeybindingService, + @IHoverService hoverService: IHoverService ) { super(); this._context = context; this._fragment = document.createDocumentFragment(); - this._register(this._renderParts(participants, hoverParts, context, keybindingService)); + this._register(this._renderParts(participants, hoverParts, context, keybindingService, hoverService)); this._register(this._registerListenersOnRenderedParts()); this._register(this._createEditorDecorations(editor, hoverParts)); this._updateMarkdownAndColorParticipantInfo(participants); @@ -256,8 +260,8 @@ class RenderedContentHoverParts extends Disposable { }); } - private _renderParts(participants: IEditorHoverParticipant[], hoverParts: IHoverPart[], hoverContext: IEditorHoverContext, keybindingService: IKeybindingService): IDisposable { - const statusBar = new EditorHoverStatusBar(keybindingService); + private _renderParts(participants: IEditorHoverParticipant[], hoverParts: IHoverPart[], hoverContext: IEditorHoverContext, keybindingService: IKeybindingService, hoverService: IHoverService): IDisposable { + const statusBar = new EditorHoverStatusBar(keybindingService, hoverService); const hoverRenderingContext: IEditorHoverRenderContext = { fragment: this._fragment, statusBar, diff --git a/src/vs/editor/contrib/hover/browser/contentHoverStatusBar.ts b/src/vs/editor/contrib/hover/browser/contentHoverStatusBar.ts index 220b7597603e6..c799ff52c1180 100644 --- a/src/vs/editor/contrib/hover/browser/contentHoverStatusBar.ts +++ b/src/vs/editor/contrib/hover/browser/contentHoverStatusBar.ts @@ -7,6 +7,8 @@ import { HoverAction } from '../../../../base/browser/ui/hover/hoverWidget.js'; import { Disposable } from '../../../../base/common/lifecycle.js'; import { IEditorHoverAction, IEditorHoverStatusBar } from './hoverTypes.js'; import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; +import { IHoverService } from '../../../../platform/hover/browser/hover.js'; +import { getDefaultHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js'; const $ = dom.$; @@ -24,6 +26,7 @@ export class EditorHoverStatusBar extends Disposable implements IEditorHoverStat constructor( @IKeybindingService private readonly _keybindingService: IKeybindingService, + @IHoverService private readonly _hoverService: IHoverService, ) { super(); this.hoverElement = $('div.hover-row.status-bar'); @@ -42,6 +45,7 @@ export class EditorHoverStatusBar extends Disposable implements IEditorHoverStat const keybindingLabel = keybinding ? keybinding.getLabel() : null; this._hasContent = true; const action = this._register(HoverAction.render(this.actionsElement, actionOptions, keybindingLabel)); + this._register(this._hoverService.setupManagedHover(getDefaultHoverDelegate('element'), action.actionContainer, action.actionRenderedLabel)); this.actions.push(action); return action; } diff --git a/src/vs/editor/contrib/hover/browser/contentHoverWidget.ts b/src/vs/editor/contrib/hover/browser/contentHoverWidget.ts index 5dc3019098034..ad27c1544d58a 100644 --- a/src/vs/editor/contrib/hover/browser/contentHoverWidget.ts +++ b/src/vs/editor/contrib/hover/browser/contentHoverWidget.ts @@ -31,7 +31,7 @@ export class ContentHoverWidget extends ResizableContentWidget { private _minimumSize: dom.Dimension; private _contentWidth: number | undefined; - private readonly _hover: HoverWidget = this._register(new HoverWidget()); + private readonly _hover: HoverWidget = this._register(new HoverWidget(true)); private readonly _hoverVisibleKey: IContextKey; private readonly _hoverFocusedKey: IContextKey; diff --git a/src/vs/editor/contrib/hover/browser/contentHoverWidgetWrapper.ts b/src/vs/editor/contrib/hover/browser/contentHoverWidgetWrapper.ts index 61c2be8e63e55..5beccabb33b06 100644 --- a/src/vs/editor/contrib/hover/browser/contentHoverWidgetWrapper.ts +++ b/src/vs/editor/contrib/hover/browser/contentHoverWidgetWrapper.ts @@ -21,6 +21,7 @@ import { ContentHoverResult } from './contentHoverTypes.js'; import { Emitter } from '../../../../base/common/event.js'; import { RenderedContentHover } from './contentHoverRendered.js'; import { isMousePositionWithinElement } from './hoverUtils.js'; +import { IHoverService } from '../../../../platform/hover/browser/hover.js'; export class ContentHoverWidgetWrapper extends Disposable implements IHoverWidget { @@ -38,6 +39,7 @@ export class ContentHoverWidgetWrapper extends Disposable implements IHoverWidge private readonly _editor: ICodeEditor, @IInstantiationService private readonly _instantiationService: IInstantiationService, @IKeybindingService private readonly _keybindingService: IKeybindingService, + @IHoverService private readonly _hoverService: IHoverService ) { super(); this._contentHoverWidget = this._register(this._instantiationService.createInstance(ContentHoverWidget, this._editor)); @@ -205,7 +207,7 @@ export class ContentHoverWidgetWrapper extends Disposable implements IHoverWidge private _showHover(hoverResult: ContentHoverResult): void { const context = this._getHoverContext(); - this._renderedContentHover = new RenderedContentHover(this._editor, hoverResult, this._participants, context, this._keybindingService); + this._renderedContentHover = new RenderedContentHover(this._editor, hoverResult, this._participants, context, this._keybindingService, this._hoverService); if (this._renderedContentHover.domNodeHasChildren) { this._contentHoverWidget.show(this._renderedContentHover); } else { @@ -215,6 +217,7 @@ export class ContentHoverWidgetWrapper extends Disposable implements IHoverWidge private _hideHover(): void { this._contentHoverWidget.hide(); + this._participants.forEach(participant => participant.handleHide?.()); } private _getHoverContext(): IEditorHoverContext { diff --git a/src/vs/editor/contrib/hover/browser/glyphHoverWidget.ts b/src/vs/editor/contrib/hover/browser/glyphHoverWidget.ts index 8432aa39f3a4f..f5b8fbb2fc5ff 100644 --- a/src/vs/editor/contrib/hover/browser/glyphHoverWidget.ts +++ b/src/vs/editor/contrib/hover/browser/glyphHoverWidget.ts @@ -45,7 +45,7 @@ export class GlyphHoverWidget extends Disposable implements IOverlayWidget, IHov this._isVisible = false; this._messages = []; - this._hover = this._register(new HoverWidget()); + this._hover = this._register(new HoverWidget(true)); this._hover.containerDomNode.classList.toggle('hidden', !this._isVisible); this._markdownRenderer = this._register(new MarkdownRenderer({ editor: this._editor }, languageService, openerService)); diff --git a/src/vs/editor/contrib/hover/browser/hoverActions.ts b/src/vs/editor/contrib/hover/browser/hoverActions.ts index 7a4fbfe622847..8368eae643fe0 100644 --- a/src/vs/editor/contrib/hover/browser/hoverActions.ts +++ b/src/vs/editor/contrib/hover/browser/hoverActions.ts @@ -30,7 +30,7 @@ export class ShowOrFocusHoverAction extends EditorAction { constructor() { super({ id: SHOW_OR_FOCUS_HOVER_ACTION_ID, - label: nls.localize({ + label: nls.localize2({ key: 'showOrFocusHover', comment: [ 'Label for action that will trigger the showing/focusing of a hover in the editor.', @@ -59,7 +59,6 @@ export class ShowOrFocusHoverAction extends EditorAction { } }] }, - alias: 'Show or Focus Hover', precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, @@ -112,14 +111,13 @@ export class ShowDefinitionPreviewHoverAction extends EditorAction { constructor() { super({ id: SHOW_DEFINITION_PREVIEW_HOVER_ACTION_ID, - label: nls.localize({ + label: nls.localize2({ key: 'showDefinitionPreviewHover', comment: [ 'Label for action that will trigger the showing of definition preview hover in the editor.', 'This allows for users to show the definition preview hover without using the mouse.' ] }, "Show Definition Preview Hover"), - alias: 'Show Definition Preview Hover', precondition: undefined, metadata: { description: nls.localize2('showDefinitionPreviewHoverDescription', 'Show the definition preview hover in the editor.'), @@ -156,13 +154,12 @@ export class ScrollUpHoverAction extends EditorAction { constructor() { super({ id: SCROLL_UP_HOVER_ACTION_ID, - label: nls.localize({ + label: nls.localize2({ key: 'scrollUpHover', comment: [ 'Action that allows to scroll up in the hover widget with the up arrow when the hover widget is focused.' ] }, "Scroll Up Hover"), - alias: 'Scroll Up Hover', precondition: EditorContextKeys.hoverFocused, kbOpts: { kbExpr: EditorContextKeys.hoverFocused, @@ -189,13 +186,12 @@ export class ScrollDownHoverAction extends EditorAction { constructor() { super({ id: SCROLL_DOWN_HOVER_ACTION_ID, - label: nls.localize({ + label: nls.localize2({ key: 'scrollDownHover', comment: [ 'Action that allows to scroll down in the hover widget with the up arrow when the hover widget is focused.' ] }, "Scroll Down Hover"), - alias: 'Scroll Down Hover', precondition: EditorContextKeys.hoverFocused, kbOpts: { kbExpr: EditorContextKeys.hoverFocused, @@ -222,13 +218,12 @@ export class ScrollLeftHoverAction extends EditorAction { constructor() { super({ id: SCROLL_LEFT_HOVER_ACTION_ID, - label: nls.localize({ + label: nls.localize2({ key: 'scrollLeftHover', comment: [ 'Action that allows to scroll left in the hover widget with the left arrow when the hover widget is focused.' ] }, "Scroll Left Hover"), - alias: 'Scroll Left Hover', precondition: EditorContextKeys.hoverFocused, kbOpts: { kbExpr: EditorContextKeys.hoverFocused, @@ -255,13 +250,12 @@ export class ScrollRightHoverAction extends EditorAction { constructor() { super({ id: SCROLL_RIGHT_HOVER_ACTION_ID, - label: nls.localize({ + label: nls.localize2({ key: 'scrollRightHover', comment: [ 'Action that allows to scroll right in the hover widget with the right arrow when the hover widget is focused.' ] }, "Scroll Right Hover"), - alias: 'Scroll Right Hover', precondition: EditorContextKeys.hoverFocused, kbOpts: { kbExpr: EditorContextKeys.hoverFocused, @@ -288,13 +282,12 @@ export class PageUpHoverAction extends EditorAction { constructor() { super({ id: PAGE_UP_HOVER_ACTION_ID, - label: nls.localize({ + label: nls.localize2({ key: 'pageUpHover', comment: [ 'Action that allows to page up in the hover widget with the page up command when the hover widget is focused.' ] }, "Page Up Hover"), - alias: 'Page Up Hover', precondition: EditorContextKeys.hoverFocused, kbOpts: { kbExpr: EditorContextKeys.hoverFocused, @@ -322,13 +315,12 @@ export class PageDownHoverAction extends EditorAction { constructor() { super({ id: PAGE_DOWN_HOVER_ACTION_ID, - label: nls.localize({ + label: nls.localize2({ key: 'pageDownHover', comment: [ 'Action that allows to page down in the hover widget with the page down command when the hover widget is focused.' ] }, "Page Down Hover"), - alias: 'Page Down Hover', precondition: EditorContextKeys.hoverFocused, kbOpts: { kbExpr: EditorContextKeys.hoverFocused, @@ -356,13 +348,12 @@ export class GoToTopHoverAction extends EditorAction { constructor() { super({ id: GO_TO_TOP_HOVER_ACTION_ID, - label: nls.localize({ + label: nls.localize2({ key: 'goToTopHover', comment: [ 'Action that allows to go to the top of the hover widget with the home command when the hover widget is focused.' ] }, "Go To Top Hover"), - alias: 'Go To Bottom Hover', precondition: EditorContextKeys.hoverFocused, kbOpts: { kbExpr: EditorContextKeys.hoverFocused, @@ -391,13 +382,12 @@ export class GoToBottomHoverAction extends EditorAction { constructor() { super({ id: GO_TO_BOTTOM_HOVER_ACTION_ID, - label: nls.localize({ + label: nls.localize2({ key: 'goToBottomHover', comment: [ 'Action that allows to go to the bottom in the hover widget with the end command when the hover widget is focused.' ] }, "Go To Bottom Hover"), - alias: 'Go To Bottom Hover', precondition: EditorContextKeys.hoverFocused, kbOpts: { kbExpr: EditorContextKeys.hoverFocused, diff --git a/src/vs/editor/contrib/hover/browser/hoverOperation.ts b/src/vs/editor/contrib/hover/browser/hoverOperation.ts index cb73dddfb5cc1..daa3e06cc3bdf 100644 --- a/src/vs/editor/contrib/hover/browser/hoverOperation.ts +++ b/src/vs/editor/contrib/hover/browser/hoverOperation.ts @@ -37,7 +37,8 @@ export const enum HoverStartMode { export const enum HoverStartSource { Mouse = 0, - Keyboard = 1 + Click = 1, + Keyboard = 2 } export class HoverResult { diff --git a/src/vs/editor/contrib/hover/browser/hoverTypes.ts b/src/vs/editor/contrib/hover/browser/hoverTypes.ts index b557fce3ba3ab..e67fdbe9ff1b8 100644 --- a/src/vs/editor/contrib/hover/browser/hoverTypes.ts +++ b/src/vs/editor/contrib/hover/browser/hoverTypes.ts @@ -12,6 +12,7 @@ import { Position } from '../../../common/core/position.js'; import { Range } from '../../../common/core/range.js'; import { IModelDecoration } from '../../../common/model.js'; import { BrandedService, IConstructorSignature } from '../../../../platform/instantiation/common/instantiation.js'; +import { HoverStartSource } from './hoverOperation.js'; export interface IHoverPart { /** @@ -155,12 +156,13 @@ export class RenderedHoverParts implements IRenderedHoverP export interface IEditorHoverParticipant { readonly hoverOrdinal: number; suggestHoverAnchor?(mouseEvent: IEditorMouseEvent): HoverAnchor | null; - computeSync(anchor: HoverAnchor, lineDecorations: IModelDecoration[]): T[]; - computeAsync?(anchor: HoverAnchor, lineDecorations: IModelDecoration[], token: CancellationToken): AsyncIterableObject; + computeSync(anchor: HoverAnchor, lineDecorations: IModelDecoration[], source: HoverStartSource): T[]; + computeAsync?(anchor: HoverAnchor, lineDecorations: IModelDecoration[], source: HoverStartSource, token: CancellationToken): AsyncIterableObject; createLoadingMessage?(anchor: HoverAnchor): T | null; renderHoverParts(context: IEditorHoverRenderContext, hoverParts: T[]): IRenderedHoverParts; getAccessibleContent(hoverPart: T): string; handleResize?(): void; + handleHide?(): void; } export type IEditorHoverParticipantCtor = IConstructorSignature; diff --git a/src/vs/editor/contrib/hover/browser/markdownHoverParticipant.ts b/src/vs/editor/contrib/hover/browser/markdownHoverParticipant.ts index db9528985a708..7e01b66410536 100644 --- a/src/vs/editor/contrib/hover/browser/markdownHoverParticipant.ts +++ b/src/vs/editor/contrib/hover/browser/markdownHoverParticipant.ts @@ -34,6 +34,7 @@ import { AsyncIterableObject } from '../../../../base/common/async.js'; import { LanguageFeatureRegistry } from '../../../common/languageFeatureRegistry.js'; import { getHoverProviderResultsAsAsyncIterable } from './getHover.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; +import { HoverStartSource } from './hoverOperation.js'; const $ = dom.$; const increaseHoverVerbosityIcon = registerIcon('hover-increase-verbosity', Codicon.add, nls.localize('increaseHoverVerbosity', 'Icon for increaseing hover verbosity.')); @@ -151,7 +152,7 @@ export class MarkdownHoverParticipant implements IEditorHoverParticipant { + public computeAsync(anchor: HoverAnchor, lineDecorations: IModelDecoration[], source: HoverStartSource, token: CancellationToken): AsyncIterableObject { if (!this._editor.hasModel() || anchor.type !== HoverAnchorType.Range) { return AsyncIterableObject.EMPTY; } diff --git a/src/vs/editor/contrib/inPlaceReplace/browser/inPlaceReplace.ts b/src/vs/editor/contrib/inPlaceReplace/browser/inPlaceReplace.ts index 248747f56a785..2e42151eadc29 100644 --- a/src/vs/editor/contrib/inPlaceReplace/browser/inPlaceReplace.ts +++ b/src/vs/editor/contrib/inPlaceReplace/browser/inPlaceReplace.ts @@ -131,8 +131,7 @@ class InPlaceReplaceUp extends EditorAction { constructor() { super({ id: 'editor.action.inPlaceReplace.up', - label: nls.localize('InPlaceReplaceAction.previous.label', "Replace with Previous Value"), - alias: 'Replace with Previous Value', + label: nls.localize2('InPlaceReplaceAction.previous.label', "Replace with Previous Value"), precondition: EditorContextKeys.writable, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, @@ -156,8 +155,7 @@ class InPlaceReplaceDown extends EditorAction { constructor() { super({ id: 'editor.action.inPlaceReplace.down', - label: nls.localize('InPlaceReplaceAction.next.label', "Replace with Next Value"), - alias: 'Replace with Next Value', + label: nls.localize2('InPlaceReplaceAction.next.label', "Replace with Next Value"), precondition: EditorContextKeys.writable, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, diff --git a/src/vs/editor/contrib/indentation/browser/indentation.ts b/src/vs/editor/contrib/indentation/browser/indentation.ts index 2773edebf9b3e..e467efa9ac01d 100644 --- a/src/vs/editor/contrib/indentation/browser/indentation.ts +++ b/src/vs/editor/contrib/indentation/browser/indentation.ts @@ -34,8 +34,7 @@ export class IndentationToSpacesAction extends EditorAction { constructor() { super({ id: IndentationToSpacesAction.ID, - label: nls.localize('indentationToSpaces', "Convert Indentation to Spaces"), - alias: 'Convert Indentation to Spaces', + label: nls.localize2('indentationToSpaces', "Convert Indentation to Spaces"), precondition: EditorContextKeys.writable, metadata: { description: nls.localize2('indentationToSpacesDescription', "Convert the tab indentation to spaces."), @@ -71,8 +70,7 @@ export class IndentationToTabsAction extends EditorAction { constructor() { super({ id: IndentationToTabsAction.ID, - label: nls.localize('indentationToTabs', "Convert Indentation to Tabs"), - alias: 'Convert Indentation to Tabs', + label: nls.localize2('indentationToTabs', "Convert Indentation to Tabs"), precondition: EditorContextKeys.writable, metadata: { description: nls.localize2('indentationToTabsDescription', "Convert the spaces indentation to tabs."), @@ -167,8 +165,7 @@ export class IndentUsingTabs extends ChangeIndentationSizeAction { constructor() { super(false, false, { id: IndentUsingTabs.ID, - label: nls.localize('indentUsingTabs', "Indent Using Tabs"), - alias: 'Indent Using Tabs', + label: nls.localize2('indentUsingTabs', "Indent Using Tabs"), precondition: undefined, metadata: { description: nls.localize2('indentUsingTabsDescription', "Use indentation with tabs."), @@ -184,8 +181,7 @@ export class IndentUsingSpaces extends ChangeIndentationSizeAction { constructor() { super(true, false, { id: IndentUsingSpaces.ID, - label: nls.localize('indentUsingSpaces', "Indent Using Spaces"), - alias: 'Indent Using Spaces', + label: nls.localize2('indentUsingSpaces', "Indent Using Spaces"), precondition: undefined, metadata: { description: nls.localize2('indentUsingSpacesDescription', "Use indentation with spaces."), @@ -201,8 +197,7 @@ export class ChangeTabDisplaySize extends ChangeIndentationSizeAction { constructor() { super(true, true, { id: ChangeTabDisplaySize.ID, - label: nls.localize('changeTabDisplaySize', "Change Tab Display Size"), - alias: 'Change Tab Display Size', + label: nls.localize2('changeTabDisplaySize', "Change Tab Display Size"), precondition: undefined, metadata: { description: nls.localize2('changeTabDisplaySizeDescription', "Change the space size equivalent of the tab."), @@ -218,8 +213,7 @@ export class DetectIndentation extends EditorAction { constructor() { super({ id: DetectIndentation.ID, - label: nls.localize('detectIndentation', "Detect Indentation from Content"), - alias: 'Detect Indentation from Content', + label: nls.localize2('detectIndentation', "Detect Indentation from Content"), precondition: undefined, metadata: { description: nls.localize2('detectIndentationDescription', "Detect the indentation from content."), @@ -244,8 +238,7 @@ export class ReindentLinesAction extends EditorAction { constructor() { super({ id: 'editor.action.reindentlines', - label: nls.localize('editor.reindentlines', "Reindent Lines"), - alias: 'Reindent Lines', + label: nls.localize2('editor.reindentlines', "Reindent Lines"), precondition: EditorContextKeys.writable, metadata: { description: nls.localize2('editor.reindentlinesDescription', "Reindent the lines of the editor."), @@ -273,8 +266,7 @@ export class ReindentSelectedLinesAction extends EditorAction { constructor() { super({ id: 'editor.action.reindentselectedlines', - label: nls.localize('editor.reindentselectedlines', "Reindent Selected Lines"), - alias: 'Reindent Selected Lines', + label: nls.localize2('editor.reindentselectedlines', "Reindent Selected Lines"), precondition: EditorContextKeys.writable, metadata: { description: nls.localize2('editor.reindentselectedlinesDescription', "Reindent the selected lines of the editor."), diff --git a/src/vs/editor/contrib/inlayHints/browser/inlayHintsHover.ts b/src/vs/editor/contrib/inlayHints/browser/inlayHintsHover.ts index 7587ab9a72d43..9c7320f98b5fa 100644 --- a/src/vs/editor/contrib/inlayHints/browser/inlayHintsHover.ts +++ b/src/vs/editor/contrib/inlayHints/browser/inlayHintsHover.ts @@ -27,6 +27,7 @@ import { isNonEmptyArray } from '../../../../base/common/arrays.js'; import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; import { IHoverService } from '../../../../platform/hover/browser/hover.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; +import { HoverStartSource } from '../../hover/browser/hoverOperation.js'; class InlayHintsHoverAnchor extends HoverForeignElementAnchor { constructor( @@ -76,7 +77,7 @@ export class InlayHintsHover extends MarkdownHoverParticipant implements IEditor return []; } - override computeAsync(anchor: HoverAnchor, _lineDecorations: IModelDecoration[], token: CancellationToken): AsyncIterableObject { + override computeAsync(anchor: HoverAnchor, _lineDecorations: IModelDecoration[], source: HoverStartSource, token: CancellationToken): AsyncIterableObject { if (!(anchor instanceof InlayHintsHoverAnchor)) { return AsyncIterableObject.EMPTY; } diff --git a/src/vs/editor/contrib/inlineCompletions/browser/controller/commands.ts b/src/vs/editor/contrib/inlineCompletions/browser/controller/commands.ts index d2074be9aae4b..647a9726e3cf6 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/controller/commands.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/controller/commands.ts @@ -19,14 +19,14 @@ import { Context as SuggestContext } from '../../../suggest/browser/suggest.js'; import { inlineSuggestCommitId, showNextInlineSuggestionActionId, showPreviousInlineSuggestionActionId } from './commandIds.js'; import { InlineCompletionContextKeys } from './inlineCompletionContextKeys.js'; import { InlineCompletionsController } from './inlineCompletionsController.js'; +import { INotificationService, Severity } from '../../../../../platform/notification/common/notification.js'; export class ShowNextInlineSuggestionAction extends EditorAction { public static ID = showNextInlineSuggestionActionId; constructor() { super({ id: ShowNextInlineSuggestionAction.ID, - label: nls.localize('action.inlineSuggest.showNext', "Show Next Inline Suggestion"), - alias: 'Show Next Inline Suggestion', + label: nls.localize2('action.inlineSuggest.showNext', "Show Next Inline Suggestion"), precondition: ContextKeyExpr.and(EditorContextKeys.writable, InlineCompletionContextKeys.inlineSuggestionVisible), kbOpts: { weight: 100, @@ -46,8 +46,7 @@ export class ShowPreviousInlineSuggestionAction extends EditorAction { constructor() { super({ id: ShowPreviousInlineSuggestionAction.ID, - label: nls.localize('action.inlineSuggest.showPrevious', "Show Previous Inline Suggestion"), - alias: 'Show Previous Inline Suggestion', + label: nls.localize2('action.inlineSuggest.showPrevious', "Show Previous Inline Suggestion"), precondition: ContextKeyExpr.and(EditorContextKeys.writable, InlineCompletionContextKeys.inlineSuggestionVisible), kbOpts: { weight: 100, @@ -66,8 +65,7 @@ export class TriggerInlineSuggestionAction extends EditorAction { constructor() { super({ id: 'editor.action.inlineSuggest.trigger', - label: nls.localize('action.inlineSuggest.trigger', "Trigger Inline Suggestion"), - alias: 'Trigger Inline Suggestion', + label: nls.localize2('action.inlineSuggest.trigger', "Trigger Inline Suggestion"), precondition: EditorContextKeys.writable }); } @@ -82,12 +80,34 @@ export class TriggerInlineSuggestionAction extends EditorAction { } } +export class TriggerInlineEditAction extends EditorAction { + constructor() { + super({ + id: 'editor.action.inlineSuggest.trigger.inlineEdit', + label: nls.localize2('action.inlineSuggest.trigger.inlineEdit', "Trigger Inline Edit"), + precondition: EditorContextKeys.writable + }); + } + + public async run(accessor: ServicesAccessor | undefined, editor: ICodeEditor): Promise { + const notificationService = accessor!.get(INotificationService); + const controller = InlineCompletionsController.get(editor); + + await controller?.model.get()?.triggerExplicitly(undefined, true); + if (!controller?.model.get()?.inlineEditAvailable.get()) { + notificationService.notify({ + severity: Severity.Info, + message: nls.localize('noInlineEditAvailable', "No inline edit is available.") + }); + } + } +} + export class AcceptNextWordOfInlineCompletion extends EditorAction { constructor() { super({ id: 'editor.action.inlineSuggest.acceptNextWord', - label: nls.localize('action.inlineSuggest.acceptNextWord', "Accept Next Word Of Inline Suggestion"), - alias: 'Accept Next Word Of Inline Suggestion', + label: nls.localize2('action.inlineSuggest.acceptNextWord', "Accept Next Word Of Inline Suggestion"), precondition: ContextKeyExpr.and(EditorContextKeys.writable, InlineCompletionContextKeys.inlineSuggestionVisible), kbOpts: { weight: KeybindingWeight.EditorContrib + 1, @@ -113,8 +133,7 @@ export class AcceptNextLineOfInlineCompletion extends EditorAction { constructor() { super({ id: 'editor.action.inlineSuggest.acceptNextLine', - label: nls.localize('action.inlineSuggest.acceptNextLine', "Accept Next Line Of Inline Suggestion"), - alias: 'Accept Next Line Of Inline Suggestion', + label: nls.localize2('action.inlineSuggest.acceptNextLine', "Accept Next Line Of Inline Suggestion"), precondition: ContextKeyExpr.and(EditorContextKeys.writable, InlineCompletionContextKeys.inlineSuggestionVisible), kbOpts: { weight: KeybindingWeight.EditorContrib + 1, @@ -138,8 +157,7 @@ export class AcceptInlineCompletion extends EditorAction { constructor() { super({ id: inlineSuggestCommitId, - label: nls.localize('action.inlineSuggest.accept', "Accept Inline Suggestion"), - alias: 'Accept Inline Suggestion', + label: nls.localize2('action.inlineSuggest.accept', "Accept Inline Suggestion"), precondition: ContextKeyExpr.or(InlineCompletionContextKeys.inlineSuggestionVisible, InlineCompletionContextKeys.inlineEditVisible), menuOpts: [{ menuId: MenuId.InlineSuggestionToolbar, @@ -171,9 +189,7 @@ export class AcceptInlineCompletion extends EditorAction { SuggestContext.Visible.toNegated(), EditorContextKeys.hoverFocused.toNegated(), - //InlineCompletionContextKeys.cursorInIndentation.toNegated(), - InlineCompletionContextKeys.hasSelection.toNegated(), - InlineCompletionContextKeys.cursorAtInlineEdit, + InlineCompletionContextKeys.tabShouldAcceptInlineEdit, ) ), }, @@ -205,8 +221,7 @@ export class JumpToNextInlineEdit extends EditorAction { constructor() { super({ id: 'editor.action.inlineSuggest.jump', - label: nls.localize('action.inlineSuggest.jump', "Jump to next inline edit"), - alias: 'Jump to next inline edit', + label: nls.localize2('action.inlineSuggest.jump', "Jump to next inline edit"), precondition: InlineCompletionContextKeys.inlineEditVisible, menuOpts: [{ menuId: MenuId.InlineEditsActions, @@ -220,12 +235,10 @@ export class JumpToNextInlineEdit extends EditorAction { weight: 201, kbExpr: ContextKeyExpr.and( InlineCompletionContextKeys.inlineEditVisible, - //InlineCompletionContextKeys.cursorInIndentation.toNegated(), - InlineCompletionContextKeys.hasSelection.toNegated(), EditorContextKeys.tabMovesFocus.toNegated(), SuggestContext.Visible.toNegated(), EditorContextKeys.hoverFocused.toNegated(), - InlineCompletionContextKeys.cursorAtInlineEdit.toNegated(), + InlineCompletionContextKeys.tabShouldJumpToInlineEdit, ), } }); @@ -245,8 +258,7 @@ export class HideInlineCompletion extends EditorAction { constructor() { super({ id: HideInlineCompletion.ID, - label: nls.localize('action.inlineSuggest.hide', "Hide Inline Suggestion"), - alias: 'Hide Inline Suggestion', + label: nls.localize2('action.inlineSuggest.hide', "Hide Inline Suggestion"), precondition: ContextKeyExpr.or(InlineCompletionContextKeys.inlineSuggestionVisible, InlineCompletionContextKeys.inlineEditVisible), kbOpts: { weight: 100, @@ -258,7 +270,7 @@ export class HideInlineCompletion extends EditorAction { public async run(accessor: ServicesAccessor | undefined, editor: ICodeEditor): Promise { const controller = InlineCompletionsController.get(editor); transaction(tx => { - controller?.model.get()?.stop(tx); + controller?.model.get()?.stop('explicitCancel', tx); }); } } diff --git a/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionContextKeys.ts b/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionContextKeys.ts index 5b0407d8afc31..db7027aa6bd7c 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionContextKeys.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionContextKeys.ts @@ -23,7 +23,8 @@ export class InlineCompletionContextKeys extends Disposable { public static readonly hasSelection = new RawContextKey('editor.hasSelection', false, localize('editor.hasSelection', "Whether the editor has a selection")); public static readonly cursorAtInlineEdit = new RawContextKey('cursorAtInlineEdit', false, localize('cursorAtInlineEdit', "Whether the cursor is at an inline edit")); public static readonly inlineEditVisible = new RawContextKey('inlineEditIsVisible', false, localize('inlineEditVisible', "Whether an inline edit is visible")); - + public static readonly tabShouldJumpToInlineEdit = new RawContextKey('tabShouldJumpToInlineEdit', false, localize('tabShouldJumpToInlineEdit', "Whether tab should jump to an inline edit.")); + public static readonly tabShouldAcceptInlineEdit = new RawContextKey('tabShouldAcceptInlineEdit', false, localize('tabShouldAcceptInlineEdit', "Whether tab should accept the inline edit.")); public readonly inlineCompletionVisible = InlineCompletionContextKeys.inlineSuggestionVisible.bindTo(this.contextKeyService); public readonly inlineCompletionSuggestsIndentation = InlineCompletionContextKeys.inlineSuggestionHasIndentation.bindTo(this.contextKeyService); diff --git a/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts b/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts index 5188dcac047e2..9447ee4cb86ee 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts @@ -30,11 +30,12 @@ import { CursorChangeReason } from '../../../../common/cursorEvents.js'; import { ILanguageFeatureDebounceService } from '../../../../common/services/languageFeatureDebounce.js'; import { ILanguageFeaturesService } from '../../../../common/services/languageFeatures.js'; import { InlineCompletionsHintsWidget, InlineSuggestionHintsContentWidget } from '../hintsWidget/inlineCompletionsHintsWidget.js'; +import { TextModelChangeRecorder } from '../model/changeRecorder.js'; import { InlineCompletionsModel } from '../model/inlineCompletionsModel.js'; -import { SuggestWidgetAdaptor } from '../model/suggestWidgetAdaptor.js'; +import { SuggestWidgetAdaptor } from '../model/suggestWidgetAdapter.js'; import { convertItemsToStableObservables, ObservableContextKeyService } from '../utils.js'; import { GhostTextView } from '../view/ghostText/ghostTextView.js'; -import { InlineEditsViewAndDiffProducer } from '../view/inlineEdits/inlineEditsView.js'; +import { InlineEditsViewAndDiffProducer } from '../view/inlineEdits/inlineEditsViewAndDiffProducer.js'; import { inlineSuggestCommitId } from './commandIds.js'; import { InlineCompletionContextKeys } from './inlineCompletionContextKeys.js'; @@ -91,10 +92,6 @@ export class InlineCompletionsController extends Disposable { return cursorPos.column <= indentMaxColumn; }); - private readonly _shouldHideInlineEdit = derived(this, reader => { - return this._cursorIsInIndentation.read(reader); - }); - private readonly optionPreview = this._editorObs.getOption(EditorOption.suggest).map(v => v.preview); private readonly optionPreviewMode = this._editorObs.getOption(EditorOption.suggest).map(v => v.previewMode); private readonly optionMode = this._editorObs.getOption(EditorOption.inlineSuggest).map(v => v.mode); @@ -117,7 +114,6 @@ export class InlineCompletionsController extends Disposable { this.optionMode, this._enabled, this.optionInlineEditsEnabled, - this._shouldHideInlineEdit, this.editor, ); return model; @@ -228,7 +224,7 @@ export class InlineCompletionsController extends Disposable { transaction(tx => { /** @description InlineCompletionsController.onDidBlurEditorWidget */ - this.model.get()?.stop(tx); + this.model.get()?.stop('automatic', tx); }); })); @@ -304,6 +300,10 @@ export class InlineCompletionsController extends Disposable { const s = m?.state?.read(reader); return s?.kind === 'inlineEdit' && s.cursorAtInlineEdit; }))); + this._register(contextKeySvcObs.bind(InlineCompletionContextKeys.tabShouldAcceptInlineEdit, this.model.map((m, r) => !!m?.tabShouldAcceptInlineEdit.read(r)))); + this._register(contextKeySvcObs.bind(InlineCompletionContextKeys.tabShouldJumpToInlineEdit, this.model.map((m, r) => !!m?.tabShouldJumpToInlineEdit.read(r)))); + + this._register(this._instantiationService.createInstance(TextModelChangeRecorder, this.editor)); } public playAccessibilitySignal(tx: ITransaction) { @@ -332,9 +332,9 @@ export class InlineCompletionsController extends Disposable { return this._ghostTextWidgets.get()[0]?.get().ownsViewZone(viewZoneId) ?? false; } - public hide() { + public reject() { transaction(tx => { - this.model.get()?.stop(tx); + this.model.get()?.stop('explicitCancel', tx); }); } diff --git a/src/vs/editor/contrib/inlineCompletions/browser/hintsWidget/inlineCompletionsHintsWidget.ts b/src/vs/editor/contrib/inlineCompletions/browser/hintsWidget/inlineCompletionsHintsWidget.ts index 1c1bc0a34dbd8..7d5e0ac15a919 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/hintsWidget/inlineCompletionsHintsWidget.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/hintsWidget/inlineCompletionsHintsWidget.ts @@ -15,7 +15,7 @@ import { IObservable, autorun, autorunWithStore, derived, derivedObservableWithC import { OS } from '../../../../../base/common/platform.js'; import { ThemeIcon } from '../../../../../base/common/themables.js'; import { localize } from '../../../../../nls.js'; -import { MenuEntryActionViewItem, createAndFillInActionBarActions } from '../../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { MenuEntryActionViewItem, getActionBarActions } from '../../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { IMenuWorkbenchToolBarOptions, WorkbenchToolBar } from '../../../../../platform/actions/browser/toolbar.js'; import { IMenuService, MenuId, MenuItemAction } from '../../../../../platform/actions/common/actions.js'; import { ICommandService } from '../../../../../platform/commands/common/commands.js'; @@ -340,12 +340,8 @@ export class CustomizedMenuWorkbenchToolBar extends WorkbenchToolBar { } private updateToolbar(): void { - const primary: IAction[] = []; - const secondary: IAction[] = []; - createAndFillInActionBarActions( - this.menu, - this.options2?.menuOptions, - { primary, secondary }, + const { primary, secondary } = getActionBarActions( + this.menu.getActions(this.options2?.menuOptions), this.options2?.toolbarOptions?.primaryGroup, this.options2?.toolbarOptions?.shouldInlineSubmenu, this.options2?.toolbarOptions?.useSeparatorsInPrimaryActions ); diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletions.contribution.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletions.contribution.ts index c6cc12c987f67..dcdd962071471 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletions.contribution.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletions.contribution.ts @@ -8,7 +8,7 @@ import { registerAction2 } from '../../../../platform/actions/common/actions.js' import { wrapInHotClass1 } from '../../../../platform/observable/common/wrapInHotClass.js'; import { EditorContributionInstantiation, registerEditorAction, registerEditorContribution } from '../../../browser/editorExtensions.js'; import { HoverParticipantRegistry } from '../../hover/browser/hoverTypes.js'; -import { AcceptInlineCompletion, AcceptNextLineOfInlineCompletion, AcceptNextWordOfInlineCompletion, DevExtractReproSample, HideInlineCompletion, JumpToNextInlineEdit, ShowNextInlineSuggestionAction, ShowPreviousInlineSuggestionAction, ToggleAlwaysShowInlineSuggestionToolbar, TriggerInlineSuggestionAction } from './controller/commands.js'; +import { AcceptInlineCompletion, AcceptNextLineOfInlineCompletion, AcceptNextWordOfInlineCompletion, DevExtractReproSample, HideInlineCompletion, JumpToNextInlineEdit, ShowNextInlineSuggestionAction, ShowPreviousInlineSuggestionAction, ToggleAlwaysShowInlineSuggestionToolbar, TriggerInlineEditAction, TriggerInlineSuggestionAction } from './controller/commands.js'; import { InlineCompletionsController } from './controller/inlineCompletionsController.js'; import { InlineCompletionsHoverParticipant } from './hintsWidget/hoverParticipant.js'; import { InlineCompletionsAccessibleView } from './inlineCompletionsAccessibleView.js'; @@ -20,6 +20,7 @@ registerEditorContribution(InlineEditsAdapterContribution.ID, InlineEditsAdapter registerEditorContribution(InlineCompletionsController.ID, wrapInHotClass1(InlineCompletionsController.hot), EditorContributionInstantiation.Eventually); registerEditorAction(TriggerInlineSuggestionAction); +registerEditorAction(TriggerInlineEditAction); registerEditorAction(ShowNextInlineSuggestionAction); registerEditorAction(ShowPreviousInlineSuggestionAction); registerEditorAction(AcceptNextWordOfInlineCompletion); diff --git a/src/vs/editor/contrib/inlineCompletions/browser/model/changeRecorder.ts b/src/vs/editor/contrib/inlineCompletions/browser/model/changeRecorder.ts new file mode 100644 index 0000000000000..5ff01999261cc --- /dev/null +++ b/src/vs/editor/contrib/inlineCompletions/browser/model/changeRecorder.ts @@ -0,0 +1,51 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable } from '../../../../../base/common/lifecycle.js'; +import { autorunWithStore } from '../../../../../base/common/observable.js'; +import { IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js'; +import { ILogService } from '../../../../../platform/log/common/log.js'; +import { ICodeEditor } from '../../../../browser/editorBrowser.js'; +import { CodeEditorWidget } from '../../../../browser/widget/codeEditor/codeEditorWidget.js'; +import { formatRecordableLogEntry, IRecordableEditorLogEntry, observableContextKey } from './inlineCompletionsSource.js'; + +export class TextModelChangeRecorder extends Disposable { + private readonly _recordingLoggingEnabled = observableContextKey('editor.inlineSuggest.logChangeReason', this._contextKeyService).recomputeInitiallyAndOnChange(this._store); + + constructor( + private readonly _editor: ICodeEditor, + @IContextKeyService private readonly _contextKeyService: IContextKeyService, + @ILogService private readonly _logService: ILogService + ) { + super(); + this._register(autorunWithStore((reader, store) => { + if (!(this._editor instanceof CodeEditorWidget)) { return; } + if (!this._recordingLoggingEnabled.read(reader)) { return; } + + const sources: string[] = []; + + store.add(this._editor.onBeforeExecuteEdit(({ source }) => { + if (source) { + sources.push(source); + } + })); + + store.add(this._editor.onDidChangeModelContent(e => { + const tm = this._editor.getModel(); + if (!tm) { return; } + for (const source of sources) { + this._logService.info(formatRecordableLogEntry('TextModel.setChangeReason', { + time: Date.now(), + modelUri: tm.uri.toString(), + modelVersion: tm.getVersionId(), + source: source, + })); + } + sources.length = 0; + })); + + })); + } +} diff --git a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts index e6986cb4bc0cf..10670fc68b18b 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts @@ -13,6 +13,7 @@ import { isDefined } from '../../../../../base/common/types.js'; import { ICommandService } from '../../../../../platform/commands/common/commands.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { ICodeEditor } from '../../../../browser/editorBrowser.js'; +import { observableCodeEditor } from '../../../../browser/observableCodeEditor.js'; import { EditOperation } from '../../../../common/core/editOperation.js'; import { LineRange } from '../../../../common/core/lineRange.js'; import { Position } from '../../../../common/core/position.js'; @@ -24,7 +25,6 @@ import { ScrollType } from '../../../../common/editorCommon.js'; import { Command, InlineCompletion, InlineCompletionContext, InlineCompletionTriggerKind, PartialAcceptTriggerKind } from '../../../../common/languages.js'; import { ILanguageConfigurationService } from '../../../../common/languages/languageConfigurationRegistry.js'; import { EndOfLinePreference, ITextModel } from '../../../../common/model.js'; -import { TextModelText } from '../../../../common/model/textModelText.js'; import { IFeatureDebounceInformation } from '../../../../common/services/languageFeatureDebounce.js'; import { IModelContentChangedEvent } from '../../../../common/textModelEvents.js'; import { SnippetController2 } from '../../../snippet/browser/snippetController2.js'; @@ -35,11 +35,12 @@ import { InlineCompletionWithUpdatedRange, InlineCompletionsSource } from './inl import { InlineEdit } from './inlineEdit.js'; import { InlineCompletionItem } from './provideInlineCompletions.js'; import { singleTextEditAugments, singleTextRemoveCommonPrefix } from './singleTextEditHelpers.js'; -import { SuggestItemInfo } from './suggestWidgetAdaptor.js'; +import { SuggestItemInfo } from './suggestWidgetAdapter.js'; export class InlineCompletionsModel extends Disposable { private readonly _source = this._register(this._instantiationService.createInstance(InlineCompletionsSource, this.textModel, this._textModelVersionId, this._debounceValue)); private readonly _isActive = observableValue(this, false); + private readonly _onlyRequestInlineEditsSignal = observableSignal(this); private readonly _forceUpdateExplicitlySignal = observableSignal(this); // We use a semantic id to keep the same inline completion selected even if the provider reorders the completions. @@ -49,6 +50,8 @@ export class InlineCompletionsModel extends Disposable { private _isAcceptingPartially = false; public get isAcceptingPartially() { return this._isAcceptingPartially; } + private readonly _editorObs = observableCodeEditor(this._editor); + constructor( public readonly textModel: ITextModel, public readonly selectedSuggestItem: IObservable, @@ -60,7 +63,6 @@ export class InlineCompletionsModel extends Disposable { private readonly _inlineSuggestMode: IObservable<'prefix' | 'subword' | 'subwordSmart'>, private readonly _enabled: IObservable, private readonly _inlineEditsEnabled: IObservable, - private readonly _shouldHideInlineEdit: IObservable, private readonly _editor: ICodeEditor, @IInstantiationService private readonly _instantiationService: IInstantiationService, @ICommandService private readonly _commandService: ICommandService, @@ -106,7 +108,8 @@ export class InlineCompletionsModel extends Disposable { createEmptyChangeSummary: () => ({ dontRefetch: false, preserveCurrentCompletion: false, - inlineCompletionTriggerKind: InlineCompletionTriggerKind.Automatic + inlineCompletionTriggerKind: InlineCompletionTriggerKind.Automatic, + onlyRequestInlineEdits: false, }), handleChange: (ctx, changeSummary) => { /** @description fetch inline completions */ @@ -116,11 +119,14 @@ export class InlineCompletionsModel extends Disposable { changeSummary.inlineCompletionTriggerKind = InlineCompletionTriggerKind.Explicit; } else if (ctx.didChange(this.dontRefetchSignal)) { changeSummary.dontRefetch = true; + } else if (ctx.didChange(this._onlyRequestInlineEditsSignal)) { + changeSummary.onlyRequestInlineEdits = true; } return true; }, }, (reader, changeSummary) => { this.dontRefetchSignal.read(reader); + this._onlyRequestInlineEditsSignal.read(reader); this._forceUpdateExplicitlySignal.read(reader); const shouldUpdate = (this._enabled.read(reader) && this.selectedSuggestItem.read(reader)) || this._isActive.read(reader); if (!shouldUpdate) { @@ -151,7 +157,7 @@ export class InlineCompletionsModel extends Disposable { const context: InlineCompletionContext = { triggerKind: changeSummary.inlineCompletionTriggerKind, selectedSuggestionInfo: suggestItem?.toSelectedSuggestionInfo(), - includeInlineCompletions: true, + includeInlineCompletions: !changeSummary.onlyRequestInlineEdits, includeInlineEdits: this._inlineEditsEnabled.read(reader), }; const itemToPreserveCandidate = this.selectedInlineCompletion.get(); @@ -165,16 +171,26 @@ export class InlineCompletionsModel extends Disposable { await this._fetchInlineCompletionsPromise.get(); } - public async triggerExplicitly(tx?: ITransaction): Promise { + public async triggerExplicitly(tx?: ITransaction, onlyFetchInlineEdits: boolean = false): Promise { subtransaction(tx, tx => { + if (onlyFetchInlineEdits) { + this._onlyRequestInlineEditsSignal.trigger(tx); + } this._isActive.set(true, tx); this._forceUpdateExplicitlySignal.trigger(tx); }); await this._fetchInlineCompletionsPromise.get(); } - public stop(tx?: ITransaction): void { + public stop(stopReason: 'explicitCancel' | 'automatic' = 'automatic', tx?: ITransaction): void { subtransaction(tx, tx => { + if (stopReason === 'explicitCancel') { + const completion = this.state.get()?.inlineCompletion?.inlineCompletion; + if (completion && completion.source.provider.handleRejection) { + completion.source.provider.handleRejection(completion.source.inlineCompletions, completion.sourceInlineCompletion); + } + } + this._isActive.set(false, tx); this._source.clear(tx); }); @@ -192,26 +208,32 @@ export class InlineCompletionsModel extends Disposable { const c = this._source.inlineCompletions.read(reader); if (!c) { return undefined; } const cursorPosition = this._primaryPosition.read(reader); - let inlineEditCompletion: InlineCompletionWithUpdatedRange | undefined = undefined; - const filteredCompletions: InlineCompletionWithUpdatedRange[] = []; + let inlineEdit: InlineCompletionWithUpdatedRange | undefined = undefined; + const visibleCompletions: InlineCompletionWithUpdatedRange[] = []; for (const completion of c.inlineCompletions) { if (!completion.inlineCompletion.sourceInlineCompletion.isInlineEdit) { if (completion.isVisible(this.textModel, cursorPosition, reader)) { - filteredCompletions.push(completion); + visibleCompletions.push(completion); } - } else if (filteredCompletions.length === 0 && completion.inlineCompletion.sourceInlineCompletion.isInlineEdit) { - inlineEditCompletion = completion; + } else { + inlineEdit = completion; } } + + if (visibleCompletions.length !== 0) { + // Don't show the inline edit if there is a visible completion + inlineEdit = undefined; + } + return { - items: filteredCompletions, - inlineEditCompletion, + inlineCompletions: visibleCompletions, + inlineEdit, }; }); private readonly _filteredInlineCompletionItems = derivedOpts({ owner: this, equalsFn: itemsEquals() }, reader => { const c = this._inlineCompletionItems.read(reader); - return c?.items ?? []; + return c?.inlineCompletions ?? []; }); public readonly selectedInlineCompletionIndex = derived(this, (reader) => { @@ -279,27 +301,26 @@ export class InlineCompletionsModel extends Disposable { const model = this.textModel; const item = this._inlineCompletionItems.read(reader); - if (item?.inlineEditCompletion) { - let edit = item.inlineEditCompletion.toSingleTextEdit(reader); + if (item?.inlineEdit) { + let edit = item.inlineEdit.toSingleTextEdit(reader); edit = singleTextRemoveCommonPrefix(edit, model); - if (edit.isEffectiveDeletion(new TextModelText(model))) { return undefined; } - const cursorPos = this._primaryPosition.read(reader); const cursorAtInlineEdit = LineRange.fromRangeInclusive(edit.range).addMargin(1, 1).contains(cursorPos.lineNumber); - if (item.inlineEditCompletion.request.context.triggerKind === InlineCompletionTriggerKind.Automatic && this._shouldHideInlineEdit.read(reader) && !cursorAtInlineEdit) { return undefined; } - const cursorDist = LineRange.fromRange(edit.range).distanceToLine(this._primaryPosition.read(reader).lineNumber); const disableCollapsing = true; - const currentItemIsCollapsed = !disableCollapsing && (cursorDist > 1 && this._collapsedInlineEditId.read(reader) === item.inlineEditCompletion.semanticId); + const currentItemIsCollapsed = !disableCollapsing && (cursorDist > 1 && this._collapsedInlineEditId.read(reader) === item.inlineEdit.semanticId); - const commands = item.inlineEditCompletion.inlineCompletion.source.inlineCompletions.commands; - const inlineEdit = new InlineEdit(edit, currentItemIsCollapsed, false, commands ?? []); + const commands = item.inlineEdit.inlineCompletion.source.inlineCompletions.commands; + const renderExplicitly = this._jumpedTo.read(reader); + const inlineEdit = new InlineEdit(edit, currentItemIsCollapsed, renderExplicitly, commands ?? [], item.inlineEdit.inlineCompletion); - return { kind: 'inlineEdit', inlineEdit, inlineCompletion: item.inlineEditCompletion, edits: [edit], cursorAtInlineEdit }; + return { kind: 'inlineEdit', inlineEdit, inlineCompletion: item.inlineEdit, edits: [edit], cursorAtInlineEdit }; } + this._jumpedTo.set(false, undefined); + const suggestItem = this.selectedSuggestItem.read(reader); if (suggestItem) { const suggestCompletionEdit = singleTextRemoveCommonPrefix(suggestItem.toSingleTextEdit(), model); @@ -336,6 +357,15 @@ export class InlineCompletionsModel extends Disposable { } }); + private readonly _alwaysShowInlineEdit = observableValue(this, false); + + protected readonly _resetAlwaysShowInlineEdit = this._register(autorun(reader => { + this._primaryPosition.read(reader); + this._textModelVersionId.read(reader); + + this._alwaysShowInlineEdit.set(false, undefined); + })); + public readonly status = derived(this, reader => { if (this._source.loading.read(reader)) { return 'loading'; } const s = this.state.read(reader); @@ -347,6 +377,9 @@ export class InlineCompletionsModel extends Disposable { public readonly inlineCompletionState = derived(reader => { const s = this.state.read(reader); if (!s || s.kind !== 'ghostText') { return undefined; } + if (this._editorObs.inComposition.read(reader)) { + return undefined; + } return s; }); @@ -393,6 +426,44 @@ export class InlineCompletionsModel extends Disposable { return v?.primaryGhostText; }); + private readonly _tabShouldIndent = derived(this, reader => { + function isMultiLine(range: Range): boolean { + return range.startLineNumber !== range.endLineNumber; + } + + function getNonIndentationRange(model: ITextModel, lineNumber: number): Range { + const columnStart = model.getLineIndentColumn(lineNumber); + const lastNonWsColumn = model.getLineLastNonWhitespaceColumn(lineNumber); + const columnEnd = Math.max(lastNonWsColumn, columnStart); + return new Range(lineNumber, columnStart, lineNumber, columnEnd); + } + + const selections = this._editorObs.selections.read(reader); + return selections?.some(s => { + if (s.isEmpty()) { + return this.textModel.getLineLength(s.startLineNumber) === 0; + } else { + return isMultiLine(s) || s.containsRange(getNonIndentationRange(this.textModel, s.startLineNumber)); + } + }); + }); + + public readonly tabShouldJumpToInlineEdit = derived(this, reader => { + if (this._tabShouldIndent.read(reader)) { return false; } + + const s = this.inlineEditState.read(reader); + if (!s) { return false; } + return !s.cursorAtInlineEdit; + }); + + public readonly tabShouldAcceptInlineEdit = derived(this, reader => { + if (this._tabShouldIndent.read(reader)) { return false; } + + const s = this.inlineEditState.read(reader); + if (!s) { return false; } + return s.cursorAtInlineEdit; + }); + private async _deltaSelectedInlineCompletionIndex(delta: 1 | -1): Promise { await this.triggerExplicitly(); @@ -405,13 +476,9 @@ export class InlineCompletionsModel extends Disposable { } } - public async next(): Promise { - await this._deltaSelectedInlineCompletionIndex(1); - } + public async next(): Promise { await this._deltaSelectedInlineCompletionIndex(1); } - public async previous(): Promise { - await this._deltaSelectedInlineCompletionIndex(-1); - } + public async previous(): Promise { await this._deltaSelectedInlineCompletionIndex(-1); } public async accept(editor: ICodeEditor): Promise { if (editor.getModel() !== this.textModel) { @@ -467,6 +534,8 @@ export class InlineCompletionsModel extends Disposable { .then(undefined, onUnexpectedExternalError); completion.source.removeRef(); } + + this._alwaysShowInlineEdit.set(true, undefined); } public async acceptNextWord(editor: ICodeEditor): Promise { @@ -598,17 +667,31 @@ export class InlineCompletionsModel extends Disposable { }; } + private _jumpedTo = observableValue(this, false); + public jump(): void { const s = this.inlineEditState.get(); if (!s) { return; } transaction(tx => { + this._jumpedTo.set(true, tx); this.dontRefetchSignal.trigger(tx); this._editor.setPosition(s.inlineEdit.range.getStartPosition(), 'inlineCompletions.jump'); this._editor.revealLine(s.inlineEdit.range.startLineNumber); this._editor.focus(); }); } + + public async handleInlineCompletionShown(inlineCompletion: InlineCompletionItem): Promise { + if (!inlineCompletion.shownCommand) { + return; + } + if (inlineCompletion.didShow) { + return; + } + inlineCompletion.markAsShown(); + await this._commandService.executeCommand(inlineCompletion.shownCommand.id, ...(inlineCompletion.shownCommand.arguments || [])); + } } interface Repro { diff --git a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts index 0c9354e7c26ec..2272c22ed2a7f 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts @@ -7,8 +7,9 @@ import { CancellationToken, CancellationTokenSource } from '../../../../../base/ import { equalsIfDefined, itemEquals } from '../../../../../base/common/equals.js'; import { matchesSubString } from '../../../../../base/common/filters.js'; import { Disposable, IDisposable, MutableDisposable } from '../../../../../base/common/lifecycle.js'; -import { IObservable, IReader, ITransaction, derivedOpts, disposableObservableValue, observableValue, transaction } from '../../../../../base/common/observable.js'; +import { IObservable, IReader, ITransaction, derivedOpts, disposableObservableValue, observableFromEvent, observableValue, transaction } from '../../../../../base/common/observable.js'; import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; +import { IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js'; import { ILogService } from '../../../../../platform/log/common/log.js'; import { observableConfigValue } from '../../../../../platform/observable/common/platformObservableUtils.js'; import { Position } from '../../../../common/core/position.js'; @@ -31,6 +32,7 @@ export class InlineCompletionsSource extends Disposable { public readonly suggestWidgetInlineCompletions = disposableObservableValue('suggestWidgetInlineCompletions', undefined); private readonly _loggingEnabled = observableConfigValue('editor.inlineSuggest.logFetch', false, this._configurationService).recomputeInitiallyAndOnChange(this._store); + private readonly _recordingLoggingEnabled = observableContextKey('editor.inlineSuggest.logFetch', this._contextKeyService).recomputeInitiallyAndOnChange(this._store); constructor( private readonly _textModel: ITextModel, @@ -40,6 +42,7 @@ export class InlineCompletionsSource extends Disposable { @ILanguageConfigurationService private readonly _languageConfigurationService: ILanguageConfigurationService, @ILogService private readonly _logService: ILogService, @IConfigurationService private readonly _configurationService: IConfigurationService, + @IContextKeyService private readonly _contextKeyService: IContextKeyService, ) { super(); @@ -48,8 +51,11 @@ export class InlineCompletionsSource extends Disposable { })); } - private _log(entry: { kind: 'start'; uri: string; modelVersion: number; requestId: number; context: unknown } | { kind: 'end'; error: any; durationMs: number; result: unknown; requestId: number }) { - this._logService.info('InlineCompletionsSource.fetch ' + JSON.stringify(entry)); + private _log(entry: + { kind: 'start'; requestId: number; context: unknown } & IRecordableEditorLogEntry + | { kind: 'end'; error: any; durationMs: number; result: unknown; requestId: number } & IRecordableLogEntry + ) { + this._logService.info(formatRecordableLogEntry('InlineCompletions.fetch', entry)); } public readonly loading = observableValue(this, false); @@ -84,8 +90,8 @@ export class InlineCompletionsSource extends Disposable { } const requestId = InlineCompletionsSource._requestId++; - if (this._loggingEnabled.get()) { - this._log({ kind: 'start', requestId, uri: this._textModel.uri.toString(), modelVersion: this._textModel.getVersionId(), context: { triggerKind: context.triggerKind } }); + if (this._loggingEnabled.get() || this._recordingLoggingEnabled.get()) { + this._log({ kind: 'start', requestId, modelUri: this._textModel.uri.toString(), modelVersion: this._textModel.getVersionId(), context: { triggerKind: context.triggerKind }, time: Date.now() }); } const startTime = new Date(); @@ -104,7 +110,7 @@ export class InlineCompletionsSource extends Disposable { error = e; throw e; } finally { - if (this._loggingEnabled.get()) { + if (this._loggingEnabled.get() || this._recordingLoggingEnabled.get()) { if (source.token.isCancellationRequested) { error = 'canceled'; } @@ -114,7 +120,7 @@ export class InlineCompletionsSource extends Disposable { isInlineEdit: !!c.sourceInlineCompletion.isInlineEdit, source: c.source.provider.groupId, })); - this._log({ kind: 'end', requestId, durationMs: (Date.now() - startTime.getTime()), error, result }); + this._log({ kind: 'end', requestId, durationMs: (Date.now() - startTime.getTime()), error, result, time: Date.now() }); } } @@ -367,3 +373,23 @@ export class InlineCompletionWithUpdatedRange { } const emptyRange = new Range(1, 1, 1, 1); + +interface IRecordableLogEntry { + time: number; +} + +export interface IRecordableEditorLogEntry extends IRecordableLogEntry { + modelUri: string; + modelVersion: number; +} + +/** + * The sourceLabel must not contain '@'! +*/ +export function formatRecordableLogEntry(sourceId: string, entry: T): string { + return sourceId + ' @@ ' + JSON.stringify(entry); +} + +export function observableContextKey(key: string, contextKeyService: IContextKeyService): IObservable { + return observableFromEvent(contextKeyService.onDidChangeContext, () => contextKeyService.getContextKeyValue(key)); +} diff --git a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineEdit.ts b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineEdit.ts index 8090816954a56..d87d1c26d9b33 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineEdit.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineEdit.ts @@ -5,13 +5,15 @@ import { SingleTextEdit } from '../../../../common/core/textEdit.js'; import { Command } from '../../../../common/languages.js'; +import { InlineCompletionItem } from './provideInlineCompletions.js'; export class InlineEdit { constructor( public readonly edit: SingleTextEdit, public readonly isCollapsed: boolean, - public readonly showInlineIfPossible: boolean, + public readonly renderExplicitly: boolean, public readonly commands: readonly Command[], + public readonly inlineCompletion: InlineCompletionItem, ) { } public get range() { @@ -23,6 +25,9 @@ export class InlineEdit { } public equals(other: InlineEdit): boolean { - return this.edit.equals(other.edit) && this.isCollapsed === other.isCollapsed; + return this.edit.equals(other.edit) + && this.isCollapsed === other.isCollapsed + && this.renderExplicitly === other.renderExplicitly + && this.inlineCompletion === other.inlineCompletion; } } diff --git a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineEditsAdapter.ts b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineEditsAdapter.ts index 5024437497eb4..8d651a32a85f3 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineEditsAdapter.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineEditsAdapter.ts @@ -6,10 +6,11 @@ import { CancellationToken } from '../../../../../base/common/cancellation.js'; import { Disposable } from '../../../../../base/common/lifecycle.js'; import { autorunWithStore, observableSignalFromEvent } from '../../../../../base/common/observable.js'; +import { ICommandService } from '../../../../../platform/commands/common/commands.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { ICodeEditor } from '../../../../browser/editorBrowser.js'; import { Position } from '../../../../common/core/position.js'; -import { IInlineEdit, InlineCompletionContext, InlineCompletions, InlineCompletionsProvider, InlineEditProvider, InlineEditTriggerKind } from '../../../../common/languages.js'; +import { IInlineEdit, InlineCompletion, InlineCompletionContext, InlineCompletions, InlineCompletionsProvider, InlineEditProvider, InlineEditTriggerKind } from '../../../../common/languages.js'; import { ITextModel } from '../../../../common/model.js'; import { ILanguageFeaturesService } from '../../../../common/services/languageFeatures.js'; @@ -33,6 +34,7 @@ export class InlineEditsAdapterContribution extends Disposable { export class InlineEditsAdapter extends Disposable { constructor( @ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService, + @ICommandService private readonly _commandService: ICommandService, ) { super(); @@ -41,14 +43,14 @@ export class InlineEditsAdapter extends Disposable { this._register(autorunWithStore((reader, store) => { didChangeSignal.read(reader); - type InlineCompletionsAndEdits = InlineCompletions & { + type InlineCompletionsAndEdits = InlineCompletions & { edits: { result: IInlineEdit; provider: InlineEditProvider; }[]; }; - store.add(this._languageFeaturesService.inlineCompletionsProvider.register('*', new class implements InlineCompletionsProvider { + store.add(this._languageFeaturesService.inlineCompletionsProvider.register('*', { async provideInlineCompletions(model: ITextModel, position: Position, context: InlineCompletionContext, token: CancellationToken): Promise { if (!context.includeInlineEdits) { return undefined; } @@ -69,21 +71,28 @@ export class InlineEditsAdapter extends Disposable { range: e.result.range, insertText: e.result.text, command: e.result.accepted, + shownCommand: e.result.shown, isInlineEdit: true, + edit: e.result, }; }), commands: definedEdits.flatMap(e => e.result.commands ?? []), }; - } + }, + handleRejection: (completions: InlineCompletions, item: InlineCompletionsAndEdits['items'][number]): void => { + if (item.edit.rejected) { + this._commandService.executeCommand(item.edit.rejected.id, ...(item.edit.rejected.arguments ?? [])); + } + }, freeInlineCompletions(c: InlineCompletionsAndEdits) { for (const e of c.edits) { e.provider.freeInlineEdit(e.result); } - } + }, toString(): string { return 'InlineEditsAdapter'; } - })); + } satisfies InlineCompletionsProvider)); })); } } diff --git a/src/vs/editor/contrib/inlineCompletions/browser/model/provideInlineCompletions.ts b/src/vs/editor/contrib/inlineCompletions/browser/model/provideInlineCompletions.ts index 743ad46707845..91c047eff9d82 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/model/provideInlineCompletions.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/model/provideInlineCompletions.ts @@ -177,6 +177,12 @@ async function addRefAndCreateResult( completions.addRef(); lists.push(completions); for (const item of completions.inlineCompletions.items) { + if (!context.includeInlineEdits && item.isInlineEdit) { + continue; + } + if (!context.includeInlineCompletions && !item.isInlineEdit) { + continue; + } const inlineCompletionItem = InlineCompletionItem.from( item, completions, @@ -184,9 +190,11 @@ async function addRefAndCreateResult( model, languageConfigurationService ); + itemsByHash.set(inlineCompletionItem.hash(), inlineCompletionItem); - if (context.triggerKind === InlineCompletionTriggerKind.Automatic) { + // Stop after first visible inline completion + if (!item.isInlineEdit && context.triggerKind === InlineCompletionTriggerKind.Automatic) { const minifiedEdit = inlineCompletionItem.toSingleTextEdit().removeCommonPrefix(new TextModelText(model)); if (!minifiedEdit.isEmpty) { shouldStop = true; @@ -315,6 +323,7 @@ export class InlineCompletionItem { return new InlineCompletionItem( insertText, inlineCompletion.command, + inlineCompletion.shownCommand, range, insertText, snippetInfo, @@ -324,9 +333,12 @@ export class InlineCompletionItem { ); } + private _didCallShow = false; + constructor( readonly filterText: string, readonly command: Command | undefined, + readonly shownCommand: Command | undefined, readonly range: Range, readonly insertText: string, readonly snippetInfo: SnippetInfo | undefined, @@ -350,10 +362,18 @@ export class InlineCompletionItem { insertText = filterText.replace(/\r\n|\r/g, '\n'); } + public get didShow(): boolean { + return this._didCallShow; + } + public markAsShown(): void { + this._didCallShow = true; + } + public withRange(updatedRange: Range): InlineCompletionItem { return new InlineCompletionItem( this.filterText, this.command, + this.shownCommand, updatedRange, this.insertText, this.snippetInfo, diff --git a/src/vs/editor/contrib/inlineCompletions/browser/model/suggestWidgetAdaptor.ts b/src/vs/editor/contrib/inlineCompletions/browser/model/suggestWidgetAdapter.ts similarity index 100% rename from src/vs/editor/contrib/inlineCompletions/browser/model/suggestWidgetAdaptor.ts rename to src/vs/editor/contrib/inlineCompletions/browser/model/suggestWidgetAdapter.ts diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineDiffView.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineDiffView.ts index d285d0ad143cc..5339284ec69cc 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineDiffView.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineDiffView.ts @@ -4,22 +4,27 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable } from '../../../../../../base/common/lifecycle.js'; -import { autorunWithStore, derived, IObservable } from '../../../../../../base/common/observable.js'; +import { autorunWithStore, derived, IObservable, observableFromEvent } from '../../../../../../base/common/observable.js'; import { ICodeEditor } from '../../../../../browser/editorBrowser.js'; import { observableCodeEditor } from '../../../../../browser/observableCodeEditor.js'; import { rangeIsSingleLine } from '../../../../../browser/widget/diffEditor/components/diffEditorViewZones/diffEditorViewZones.js'; +import { LineSource, renderLines, RenderOptions } from '../../../../../browser/widget/diffEditor/components/diffEditorViewZones/renderLines.js'; import { diffLineDeleteDecorationBackgroundWithIndicator, diffLineDeleteDecorationBackground, diffLineAddDecorationBackgroundWithIndicator, diffLineAddDecorationBackground, diffWholeLineAddDecoration, diffAddDecorationEmpty, diffAddDecoration } from '../../../../../browser/widget/diffEditor/registrations.contribution.js'; +import { applyViewZones, IObservableViewZone } from '../../../../../browser/widget/diffEditor/utils.js'; +import { EditorOption } from '../../../../../common/config/editorOptions.js'; import { Range } from '../../../../../common/core/range.js'; import { AbstractText } from '../../../../../common/core/textEdit.js'; import { DetailedLineRangeMapping } from '../../../../../common/diff/rangeMapping.js'; -import { IModelDeltaDecoration } from '../../../../../common/model.js'; +import { IModelDeltaDecoration, ITextModel } from '../../../../../common/model.js'; import { ModelDecorationOptions } from '../../../../../common/model/textModel.js'; -import { classNames } from './inlineEditsView.js'; +import { InlineDecoration, InlineDecorationType } from '../../../../../common/viewModel.js'; +import { classNames } from './utils.js'; export interface IOriginalEditorInlineDiffViewState { diff: DetailedLineRangeMapping[]; modifiedText: AbstractText; - showInline: boolean; + mode: 'mixedLines' | 'interleavedLines' | 'sideBySide'; + modifiedCodeEditor: ICodeEditor; } @@ -31,6 +36,7 @@ export class OriginalEditorInlineDiffView extends Disposable { constructor( private readonly _originalEditor: ICodeEditor, private readonly _state: IObservable, + private readonly _modifiedTextModel: ITextModel, ) { super(); @@ -43,6 +49,61 @@ export class OriginalEditorInlineDiffView extends Disposable { store.add(observableCodeEditor(e).setDecorations(this._decorations.map(d => d?.modifiedDecorations ?? []))); } })); + + const editor = observableCodeEditor(this._originalEditor); + + const tokenizationFinished = modelTokenizationFinished(_modifiedTextModel); + + const originalViewZones = derived(this, (reader) => { + const originalModel = editor.model.read(reader); + if (!originalModel) { return []; } + + const origViewZones: IObservableViewZone[] = []; + const renderOptions = RenderOptions.fromEditor(this._originalEditor); + const modLineHeight = editor.getOption(EditorOption.lineHeight).read(reader); + + const s = this._state.read(reader); + if (!s) { return origViewZones; } + + for (const diff of s.diff) { + if (s.mode !== 'interleavedLines') { + continue; + } + + tokenizationFinished.read(reader); // Update view-zones once tokenization completes + + const source = new LineSource(diff.modified.mapToLineArray(l => this._modifiedTextModel.tokenization.getLineTokens(l))); + + const decorations: InlineDecoration[] = []; + for (const i of diff.innerChanges || []) { + decorations.push(new InlineDecoration( + i.modifiedRange.delta(-(diff.original.startLineNumber - 1)), + diffAddDecoration.className!, + InlineDecorationType.Regular, + )); + } + + const deletedCodeDomNode = document.createElement('div'); + deletedCodeDomNode.classList.add('view-lines', 'line-insert', 'monaco-mouse-cursor-text'); + // .inline-deleted-margin-view-zone + + const result = renderLines(source, renderOptions, decorations, deletedCodeDomNode); + + origViewZones.push({ + afterLineNumber: diff.original.endLineNumberExclusive - 1, + domNode: deletedCodeDomNode, + heightInPx: result.heightInLines * modLineHeight, + minWidthInPx: result.minWidthInPx, + + showInHiddenAreas: true, + suppressMouseDown: true, + }); + } + + return origViewZones; + }); + + this._register(applyViewZones(this._originalEditor, originalViewZones)); } private readonly _decorations = derived(this, reader => { @@ -50,7 +111,7 @@ export class OriginalEditorInlineDiffView extends Disposable { if (!diff) { return undefined; } const modified = diff.modifiedText; - const showInline = diff.showInline; + const showInline = diff.mode === 'mixedLines'; const renderIndicators = false; const showEmptyDecorations = true; @@ -59,7 +120,7 @@ export class OriginalEditorInlineDiffView extends Disposable { const modifiedDecorations: IModelDeltaDecoration[] = []; for (const m of diff.diff) { - const showFullLineDecorations = false; + const showFullLineDecorations = true; if (showFullLineDecorations) { if (!m.original.isEmpty) { originalDecorations.push({ range: m.original.toInclusiveRange()!, options: renderIndicators ? diffLineDeleteDecorationBackgroundWithIndicator : diffLineDeleteDecorationBackground }); @@ -131,7 +192,10 @@ function allowsTrueInlineDiffRendering(mapping: DetailedLineRangeMapping): boole return false; } return mapping.innerChanges.every(c => - (rangeIsSingleLine(c.modifiedRange) && rangeIsSingleLine(c.originalRange)) - || c.originalRange.equalsRange(new Range(1, 1, 1, 1)) - ); + (rangeIsSingleLine(c.modifiedRange) && rangeIsSingleLine(c.originalRange))); +} + +let i = 0; +function modelTokenizationFinished(model: ITextModel): IObservable { + return observableFromEvent(model.onDidChangeTokens, () => i++); } diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsIndicatorView.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsIndicatorView.ts index 02d9d6950bc09..d6aa3988cca5e 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsIndicatorView.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsIndicatorView.ts @@ -8,7 +8,7 @@ import { renderIcon } from '../../../../../../base/browser/ui/iconLabel/iconLabe import { Codicon } from '../../../../../../base/common/codicons.js'; import { Disposable } from '../../../../../../base/common/lifecycle.js'; import { IObservable, constObservable, autorun } from '../../../../../../base/common/observable.js'; -import { editorHoverBackground, editorHoverBorder, editorHoverForeground } from '../../../../../../platform/theme/common/colorRegistry.js'; +import { buttonBackground, buttonForeground, buttonSeparator } from '../../../../../../platform/theme/common/colorRegistry.js'; import { registerColor } from '../../../../../../platform/theme/common/colorUtils.js'; import { ObservableCodeEditor } from '../../../../../browser/observableCodeEditor.js'; import { OffsetRange } from '../../../../../common/core/offsetRange.js'; @@ -20,9 +20,12 @@ export interface IInlineEditsIndicatorState { showAlways: boolean; } -export const inlineEditIndicatorForeground = registerColor('inlineEdit.indicator.foreground', editorHoverForeground, ''); -export const inlineEditIndicatorBackground = registerColor('inlineEdit.indicator.background', editorHoverBackground, ''); -export const inlineEditIndicatorBorder = registerColor('inlineEdit.indicator.border', editorHoverBorder, ''); +// editorHoverForeground +export const inlineEditIndicatorForeground = registerColor('inlineEdit.indicator.foreground', buttonForeground, ''); +// editorHoverBackground +export const inlineEditIndicatorBackground = registerColor('inlineEdit.indicator.background', buttonBackground, ''); +// editorHoverBorder +export const inlineEditIndicatorBorder = registerColor('inlineEdit.indicator.border', buttonSeparator, ''); export class InlineEditsIndicator extends Disposable { private readonly _indicator = h('div.inline-edits-view-indicator', { diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsView.css b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsView.css index 0b1990a2aa901..b955689cb7cd2 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsView.css +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsView.css @@ -62,6 +62,9 @@ margin: 0 2px; transform: none; transition: transform 0.2s ease-in-out; + .codicon { + color: var(--vscode-inlineEdit-indicator-foreground); + } } .label { @@ -74,10 +77,7 @@ } .inline-edits-view { - --widget-color: var(--vscode-editorHoverWidget-background); - - &.toolbarDropdownVisible, - .editorContainer:hover { + &.toolbarDropdownVisible, .editorContainer:hover { .toolbar { display: block; } @@ -85,9 +85,6 @@ .editorContainer { color: var(--vscode-editorHoverWidget-foreground); - background-color: var(--vscode-editorHoverWidget-background); - /*border: 1px solid var(--vscode-editorHoverWidget-border);*/ - /*border-radius: 3px;*/ .toolbar { display: none; @@ -135,7 +132,7 @@ border: none; } - --vscode-editor-background: var(--widget-color); + --vscode-editor-background: transparent; } } } diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsView.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsView.ts index 6deed7d0410b3..65987b8e7b356 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsView.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsView.ts @@ -4,35 +4,23 @@ *--------------------------------------------------------------------------------------------*/ import { h, svgElem } from '../../../../../../base/browser/dom.js'; -import { numberComparator } from '../../../../../../base/common/arrays.js'; -import { findFirstMin } from '../../../../../../base/common/arraysFind.js'; -import { createHotClass } from '../../../../../../base/common/hotReloadHelpers.js'; import { Disposable } from '../../../../../../base/common/lifecycle.js'; -import { autorun, constObservable, derived, derivedDisposable, derivedOpts, derivedWithCancellationToken, IObservable, observableFromEvent, ObservablePromise } from '../../../../../../base/common/observable.js'; -import { getIndentationLength, splitLines } from '../../../../../../base/common/strings.js'; +import { autorun, constObservable, derived, derivedOpts, IObservable, observableFromEvent } from '../../../../../../base/common/observable.js'; import { MenuId, MenuItemAction } from '../../../../../../platform/actions/common/actions.js'; import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; import { ICodeEditor } from '../../../../../browser/editorBrowser.js'; import { observableCodeEditor } from '../../../../../browser/observableCodeEditor.js'; import { EmbeddedCodeEditorWidget } from '../../../../../browser/widget/codeEditor/embeddedCodeEditorWidget.js'; -import { IDiffProviderFactoryService } from '../../../../../browser/widget/diffEditor/diffProviderFactoryService.js'; import { appendRemoveOnDispose } from '../../../../../browser/widget/diffEditor/utils.js'; import { EditorOption } from '../../../../../common/config/editorOptions.js'; -import { SingleLineEdit } from '../../../../../common/core/lineEdit.js'; import { LineRange } from '../../../../../common/core/lineRange.js'; -import { OffsetRange } from '../../../../../common/core/offsetRange.js'; -import { Position } from '../../../../../common/core/position.js'; import { Range } from '../../../../../common/core/range.js'; -import { AbstractText, SingleTextEdit, StringText, TextEdit } from '../../../../../common/core/textEdit.js'; -import { TextLength } from '../../../../../common/core/textLength.js'; +import { StringText } from '../../../../../common/core/textEdit.js'; import { lineRangeMappingFromRangeMappings, RangeMapping } from '../../../../../common/diff/rangeMapping.js'; import { TextModel } from '../../../../../common/model/textModel.js'; -import { TextModelText } from '../../../../../common/model/textModelText.js'; -import { IModelService } from '../../../../../common/services/model.js'; -import { InlineEdit } from '../../model/inlineEdit.js'; import './inlineEditsView.css'; import { IOriginalEditorInlineDiffViewState, OriginalEditorInlineDiffView } from './inlineDiffView.js'; -import { applyEditToModifiedRangeMappings, maxLeftInRange, Point, StatusBarViewItem, UniqueUriGenerator } from './utils.js'; +import { applyEditToModifiedRangeMappings, createReindentEdit, maxLeftInRange, PathBuilder, Point, StatusBarViewItem } from './utils.js'; import { IInlineEditsIndicatorState, InlineEditsIndicator } from './inlineEditsIndicatorView.js'; import { darken, lighten, registerColor, transparent } from '../../../../../../platform/theme/common/colorUtils.js'; import { diffInserted, diffRemoved } from '../../../../../../platform/theme/common/colorRegistry.js'; @@ -44,88 +32,7 @@ import { IAction } from '../../../../../../base/common/actions.js'; import { editorLineHighlightBorder } from '../../../../../common/core/editorColorRegistry.js'; import { ActionViewItem } from '../../../../../../base/browser/ui/actionbar/actionViewItems.js'; import { InlineCompletionsModel } from '../../model/inlineCompletionsModel.js'; - -export class InlineEditsViewAndDiffProducer extends Disposable { - public static readonly hot = createHotClass(InlineEditsViewAndDiffProducer); - - private readonly _modelUriGenerator = new UniqueUriGenerator('inline-edits'); - - private readonly _originalModel = derivedDisposable(() => this._modelService.createModel( - '', null, this._modelUriGenerator.getUniqueUri())).keepObserved(this._store); - private readonly _modifiedModel = derivedDisposable(() => this._modelService.createModel( - '', null, this._modelUriGenerator.getUniqueUri())).keepObserved(this._store); - - private readonly _inlineEditPromise = derivedWithCancellationToken | undefined>(this, (reader, token) => { - const inlineEdit = this._edit.read(reader); - if (!inlineEdit) { return undefined; } - - //if (inlineEdit.text.trim() === '') { return undefined; } - - const text = new TextModelText(this._editor.getModel()!); - const edit = inlineEdit.edit.extendToFullLine(text); - - this._originalModel.get().setValue(this._editor.getModel()!.getValueInRange(edit.range)); - this._modifiedModel.get().setValue(edit.text); - - const diffAlgo = this._diffProviderFactoryService.createDiffProvider({ diffAlgorithm: 'advanced' }); - return ObservablePromise.fromFn(async () => { - const result = await diffAlgo.computeDiff(this._originalModel.get(), this._modifiedModel.get(), { - computeMoves: false, - ignoreTrimWhitespace: false, - maxComputationTimeMs: 1000, - }, token); - - if (token.isCancellationRequested || result.identical) { return undefined; } - - const rangeStartPos = edit.range.getStartPosition(); - const innerChanges = result.changes.flatMap(c => c.innerChanges!); - - function addRangeToPos(pos: Position, range: Range): Range { - const start = TextLength.fromPosition(range.getStartPosition()); - return TextLength.ofRange(range).createRange(start.addToPosition(pos)); - } - - const edits = innerChanges.map(c => new SingleTextEdit( - addRangeToPos(rangeStartPos, c.originalRange), - this._modifiedModel.get()!.getValueInRange(c.modifiedRange) - )); - const diffEdits = new TextEdit(edits); - - return new InlineEditWithChanges(text, diffEdits, inlineEdit.isCollapsed, true, inlineEdit.commands); //inlineEdit.showInlineIfPossible); - }); - }); - - private readonly _inlineEdit = this._inlineEditPromise.map((p, reader) => p?.promiseResult?.read(reader)?.data); - - constructor( - private readonly _editor: ICodeEditor, - private readonly _edit: IObservable, - private readonly _model: IObservable, - @IInstantiationService private readonly _instantiationService: IInstantiationService, - @IDiffProviderFactoryService private readonly _diffProviderFactoryService: IDiffProviderFactoryService, - @IModelService private readonly _modelService: IModelService, - ) { - super(); - - this._register(this._instantiationService.createInstance(InlineEditsView, this._editor, this._inlineEdit, this._model)); - } -} - -export class InlineEditWithChanges { - public readonly lineEdit = SingleLineEdit.fromSingleTextEdit(this.edit.toSingle(this.originalText), this.originalText); - - public readonly originalLineRange = this.lineEdit.lineRange; - public readonly modifiedLineRange = this.lineEdit.toLineEdit().getNewLineRanges()[0]; - - constructor( - public readonly originalText: AbstractText, - public readonly edit: TextEdit, - public readonly isCollapsed: boolean, - public readonly showInlineIfPossible: boolean, - public readonly commands: readonly Command[], - ) { - } -} +import { InlineEditWithChanges } from './inlineEditsViewAndDiffProducer.js'; export const originalBackgroundColor = registerColor( 'inlineEdit.originalBackground', @@ -162,15 +69,17 @@ export class InlineEditsView extends Disposable { left: '0px', }, }, [ + svgElem('svg@svg', { style: { overflow: 'visible', pointerEvents: 'none', position: 'absolute' }, }, []), h('div.editorContainer@editorContainer', { style: { position: 'absolute' } }, [ h('div.preview@editor', { style: {} }), h('div.toolbar@toolbar', { style: {} }), ]), - svgElem('svg@svg', { style: { overflow: 'visible', pointerEvents: 'none', position: 'absolute' }, }, [ - - ]), + svgElem('svg@svg2', { style: { overflow: 'visible', pointerEvents: 'none', position: 'absolute' }, }, []), ]); + private readonly _useMixedLinesDiff = observableCodeEditor(this._editor).getOption(EditorOption.inlineSuggest).map(s => s.edits.experimental.useMixedLinesDiff); + private readonly _useInterleavedLinesDiff = observableCodeEditor(this._editor).getOption(EditorOption.inlineSuggest).map(s => s.edits.experimental.useInterleavedLinesDiff); + constructor( private readonly _editor: ICodeEditor, private readonly _edit: IObservable, @@ -229,6 +138,12 @@ export class InlineEditsView extends Disposable { pathBuilder3.moveTo(layoutInfo.code1); pathBuilder3.lineTo(layoutInfo.code2); + const pathBuilder4 = new PathBuilder(); + pathBuilder4.moveTo(layoutInfo.code1); + pathBuilder4.lineTo(layoutInfo.code1.deltaX(1000)); + pathBuilder4.lineTo(layoutInfo.code2.deltaX(1000)); + pathBuilder4.lineTo(layoutInfo.code2); + const path1 = document.createElementNS('http://www.w3.org/2000/svg', 'path'); path1.setAttribute('d', pathBuilder1.build()); path1.style.fill = 'var(--vscode-inlineEdit-originalBackground, transparent)'; @@ -241,12 +156,22 @@ export class InlineEditsView extends Disposable { path2.style.stroke = 'var(--vscode-inlineEdit-border)'; path2.style.strokeWidth = '1px'; + const pathModifiedBackground = document.createElementNS('http://www.w3.org/2000/svg', 'path'); + pathModifiedBackground.setAttribute('d', pathBuilder2.build()); + pathModifiedBackground.style.fill = 'var(--vscode-editor-background, transparent)'; + pathModifiedBackground.style.strokeWidth = '1px'; + const path3 = document.createElementNS('http://www.w3.org/2000/svg', 'path'); path3.setAttribute('d', pathBuilder3.build()); path3.style.stroke = 'var(--vscode-inlineEdit-border)'; path3.style.strokeWidth = '1px'; - this._elements.svg.replaceChildren(path1, path2, path3); + const path4 = document.createElementNS('http://www.w3.org/2000/svg', 'path'); + path4.setAttribute('d', pathBuilder4.build()); + path4.style.fill = 'var(--vscode-editor-background, transparent)'; + + this._elements.svg.replaceChildren(path4, pathModifiedBackground); + this._elements.svg2.replaceChildren(path1, path2, path3); this._elements.editorContainer.style.top = `${topEdit.y}px`; this._elements.editorContainer.style.left = `${topEdit.x}px`; @@ -265,14 +190,31 @@ export class InlineEditsView extends Disposable { const edit = this._edit.read(reader); if (!edit) { return undefined; } - let newText = edit.edit.apply(edit.originalText); - const indentationAdjustmentEdit = createReindentEdit(newText, edit.modifiedLineRange); - newText = indentationAdjustmentEdit.applyToString(newText); + this._model.get()?.handleInlineCompletionShown(edit.inlineCompletion); let mappings = RangeMapping.fromEdit(edit.edit); - mappings = applyEditToModifiedRangeMappings(mappings, indentationAdjustmentEdit); + let newText = edit.edit.apply(edit.originalText); + let diff = lineRangeMappingFromRangeMappings(mappings, edit.originalText, new StringText(newText)); + + let state: 'collapsed' | 'mixedLines' | 'interleavedLines' | 'sideBySide'; + if (edit.isCollapsed) { + state = 'collapsed'; + } else if (diff.every(m => OriginalEditorInlineDiffView.supportsInlineDiffRendering(m)) && + (this._useMixedLinesDiff.read(reader) === 'whenPossible' || (edit.userJumpedToIt && this._useMixedLinesDiff.read(reader) === 'afterJumpWhenPossible'))) { + state = 'mixedLines'; + } else if ((this._useInterleavedLinesDiff.read(reader) === 'always' || (edit.userJumpedToIt && this._useInterleavedLinesDiff.read(reader) === 'afterJump'))) { + state = 'interleavedLines'; + } else { + state = 'sideBySide'; + } + + if (state === 'sideBySide') { + const indentationAdjustmentEdit = createReindentEdit(newText, edit.modifiedLineRange); + newText = indentationAdjustmentEdit.applyToString(newText); - const diff = lineRangeMappingFromRangeMappings(mappings, edit.originalText, new StringText(newText)); + mappings = applyEditToModifiedRangeMappings(mappings, indentationAdjustmentEdit); + diff = lineRangeMappingFromRangeMappings(mappings, edit.originalText, new StringText(newText)); + } const originalDisplayRange = edit.originalText.lineRange.intersect( edit.originalLineRange.join( @@ -280,13 +222,6 @@ export class InlineEditsView extends Disposable { ) )!; - let state: 'collapsed' | 'inline' | 'sideBySide' = 'sideBySide'; - if (edit.isCollapsed) { - state = 'collapsed'; - } else if (edit.showInlineIfPossible && diff.every(m => OriginalEditorInlineDiffView.supportsInlineDiffRendering(m))) { - state = 'inline'; - } - return { state, diff, @@ -346,6 +281,8 @@ export class InlineEditsView extends Disposable { this._toolbar.setAdditionalSecondaryActions(secondaryExtraActions); })); + // #region preview editor + private readonly _previewTextModel = this._register(this._instantiationService.createInstance( TextModel, '', @@ -380,6 +317,7 @@ export class InlineEditsView extends Disposable { scrollbar: { vertical: 'hidden', horizontal: 'hidden', + handleMouseWheel: false, }, readOnly: true, wordWrap: 'off', @@ -405,10 +343,15 @@ export class InlineEditsView extends Disposable { this._previewTextModel.setValue(uiState.newText); const range = uiState.edit.originalLineRange; - this._previewEditor.setHiddenAreas([ - new Range(1, 1, range.startLineNumber - 1, 1), - new Range(range.startLineNumber + uiState.newTextLineCount, 1, this._previewTextModel.getLineCount() + 1, 1), - ], undefined, true); + const hiddenAreas: Range[] = []; + if (range.startLineNumber > 1) { + hiddenAreas.push(new Range(1, 1, range.startLineNumber - 1, 1)); + } + if (range.startLineNumber + uiState.newTextLineCount < this._previewTextModel.getLineCount() + 1) { + hiddenAreas.push(new Range(range.startLineNumber + uiState.newTextLineCount, 1, this._previewTextModel.getLineCount() + 1, 1)); + } + + this._previewEditor.setHiddenAreas(hiddenAreas, undefined, true); }).recomputeInitiallyAndOnChange(this._store); @@ -427,7 +370,13 @@ export class InlineEditsView extends Disposable { const maxLeft = maxLeftInRange(this._editorObs, state.originalDisplayRange, reader); const contentLeft = this._editorObs.layoutInfoContentLeft.read(reader); - return { left: contentLeft + maxLeft }; + const editorLayoutInfo = this._editorObs.layoutInfo.read(reader); + + const minLeft = (editorLayoutInfo.width - editorLayoutInfo.minimap.minimapWidth) * 0.65; + + const scrollLeft = this._editorObs.scrollLeft.read(reader); + + return { left: Math.min(contentLeft + maxLeft, minLeft + scrollLeft) }; }); /** @@ -473,6 +422,8 @@ export class InlineEditsView extends Disposable { }; }); + // #endregion + private readonly _inlineDiffViewState = derived(this, reader => { const e = this._uiState.read(reader); if (!e) { return undefined; } @@ -480,11 +431,11 @@ export class InlineEditsView extends Disposable { return { modifiedText: new StringText(e.newText), diff: e.diff, - showInline: e.edit.showInlineIfPossible, + mode: e.state === 'collapsed' ? 'sideBySide' : e.state, modifiedCodeEditor: this._previewEditor, }; }); - protected readonly _inlineDiffView = this._register(new OriginalEditorInlineDiffView(this._editor, this._inlineDiffViewState)); + protected readonly _inlineDiffView = this._register(new OriginalEditorInlineDiffView(this._editor, this._inlineDiffViewState, this._previewTextModel)); protected readonly _indicator = this._register(new InlineEditsIndicator( this._editorObs, @@ -492,59 +443,8 @@ export class InlineEditsView extends Disposable { const state = this._uiState.read(reader); const edit1 = this._previewEditorLayoutInfo.read(reader)?.edit1; if (!edit1 || !state) { return undefined; } - return { editTopLeft: edit1, showAlways: state.state === 'collapsed' || state.state === 'inline' }; + return { editTopLeft: edit1, showAlways: state.state !== 'sideBySide' }; }), this._model, )); } - -export function classNames(...classes: (string | false | undefined | null)[]) { - return classes.filter(c => typeof c === 'string').join(' '); -} - -function offsetRangeToRange(columnOffsetRange: OffsetRange, startPos: Position): Range { - return new Range( - startPos.lineNumber, - startPos.column + columnOffsetRange.start, - startPos.lineNumber, - startPos.column + columnOffsetRange.endExclusive, - ); -} - -function createReindentEdit(text: string, range: LineRange): TextEdit { - const newLines = splitLines(text); - const edits: SingleTextEdit[] = []; - const minIndent = findFirstMin(range.mapToLineArray(l => getIndentationLength(newLines[l - 1])), numberComparator)!; - range.forEach(lineNumber => { - edits.push(new SingleTextEdit(offsetRangeToRange(new OffsetRange(0, minIndent), new Position(lineNumber, 1)), '')); - }); - return new TextEdit(edits); -} - -class PathBuilder { - private _data: string = ''; - - public moveTo(point: Point): this { - this._data += `M ${point.x} ${point.y} `; - return this; - } - - public lineTo(point: Point): this { - this._data += `L ${point.x} ${point.y} `; - return this; - } - - public curveTo(cp: Point, to: Point): this { - this._data += `Q ${cp.x} ${cp.y} ${to.x} ${to.y} `; - return this; - } - - public curveTo2(cp1: Point, cp2: Point, to: Point): this { - this._data += `C ${cp1.x} ${cp1.y} ${cp2.x} ${cp2.y} ${to.x} ${to.y} `; - return this; - } - - public build(): string { - return this._data; - } -} diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViewAndDiffProducer.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViewAndDiffProducer.ts new file mode 100644 index 0000000000000..ea3c17e867b01 --- /dev/null +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViewAndDiffProducer.ts @@ -0,0 +1,129 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { LRUCachedFunction } from '../../../../../../base/common/cache.js'; +import { CancellationToken } from '../../../../../../base/common/cancellation.js'; +import { equalsIfDefined, itemEquals } from '../../../../../../base/common/equals.js'; +import { createHotClass } from '../../../../../../base/common/hotReloadHelpers.js'; +import { Disposable } from '../../../../../../base/common/lifecycle.js'; +import { derivedDisposable, ObservablePromise, derived, IObservable, derivedOpts } from '../../../../../../base/common/observable.js'; +import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; +import { ICodeEditor } from '../../../../../browser/editorBrowser.js'; +import { IDiffProviderFactoryService } from '../../../../../browser/widget/diffEditor/diffProviderFactoryService.js'; +import { SingleLineEdit } from '../../../../../common/core/lineEdit.js'; +import { Position } from '../../../../../common/core/position.js'; +import { Range } from '../../../../../common/core/range.js'; +import { SingleTextEdit, TextEdit, AbstractText } from '../../../../../common/core/textEdit.js'; +import { TextLength } from '../../../../../common/core/textLength.js'; +import { Command } from '../../../../../common/languages.js'; +import { TextModelText } from '../../../../../common/model/textModelText.js'; +import { IModelService } from '../../../../../common/services/model.js'; +import { InlineCompletionsModel } from '../../model/inlineCompletionsModel.js'; +import { InlineEdit } from '../../model/inlineEdit.js'; +import { InlineCompletionItem } from '../../model/provideInlineCompletions.js'; +import { InlineEditsView } from './inlineEditsView.js'; +import { UniqueUriGenerator } from './utils.js'; + +export class InlineEditsViewAndDiffProducer extends Disposable { + public static readonly hot = createHotClass(InlineEditsViewAndDiffProducer); + + private readonly _modelUriGenerator = new UniqueUriGenerator('inline-edits'); + + private readonly _originalModel = derivedDisposable(() => this._modelService.createModel( + '', null, this._modelUriGenerator.getUniqueUri())).keepObserved(this._store); + private readonly _modifiedModel = derivedDisposable(() => this._modelService.createModel( + '', null, this._modelUriGenerator.getUniqueUri())).keepObserved(this._store); + + private readonly _differ = new LRUCachedFunction({ getCacheKey: JSON.stringify }, (arg: { original: string; modified: string }) => { + this._originalModel.get().setValue(arg.original); + this._modifiedModel.get().setValue(arg.modified); + + const diffAlgo = this._diffProviderFactoryService.createDiffProvider({ diffAlgorithm: 'advanced' }); + + return ObservablePromise.fromFn(async () => { + const result = await diffAlgo.computeDiff(this._originalModel.get(), this._modifiedModel.get(), { + computeMoves: false, + ignoreTrimWhitespace: false, + maxComputationTimeMs: 1000, + }, CancellationToken.None); + return result; + }); + }); + + private readonly _inlineEditPromise = derived | undefined>(this, (reader) => { + const inlineEdit = this._edit.read(reader); + if (!inlineEdit) { return undefined; } + + //if (inlineEdit.text.trim() === '') { return undefined; } + const text = new TextModelText(this._editor.getModel()!); + const edit = inlineEdit.edit.extendToFullLine(text); + + const diffResult = this._differ.get({ original: this._editor.getModel()!.getValueInRange(edit.range), modified: edit.text }); + + return diffResult.promiseResult.map(p => { + if (!p || !p.data) { + return undefined; + } + const result = p.data; + + const rangeStartPos = edit.range.getStartPosition(); + const innerChanges = result.changes.flatMap(c => c.innerChanges!); + + function addRangeToPos(pos: Position, range: Range): Range { + const start = TextLength.fromPosition(range.getStartPosition()); + return TextLength.ofRange(range).createRange(start.addToPosition(pos)); + } + + const edits = innerChanges.map(c => new SingleTextEdit( + addRangeToPos(rangeStartPos, c.originalRange), + this._modifiedModel.get()!.getValueInRange(c.modifiedRange) + )); + const diffEdits = new TextEdit(edits); + + return new InlineEditWithChanges(text, diffEdits, inlineEdit.isCollapsed, inlineEdit.renderExplicitly, inlineEdit.commands, inlineEdit.inlineCompletion); //inlineEdit.showInlineIfPossible); + }); + }); + + private readonly _inlineEdit = derivedOpts({ owner: this, equalsFn: equalsIfDefined(itemEquals()) }, reader => this._inlineEditPromise.read(reader)?.read(reader)); + + constructor( + private readonly _editor: ICodeEditor, + private readonly _edit: IObservable, + private readonly _model: IObservable, + @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IDiffProviderFactoryService private readonly _diffProviderFactoryService: IDiffProviderFactoryService, + @IModelService private readonly _modelService: IModelService + ) { + super(); + + this._register(this._instantiationService.createInstance(InlineEditsView, this._editor, this._inlineEdit, this._model)); + } +} + +export class InlineEditWithChanges { + public readonly lineEdit = SingleLineEdit.fromSingleTextEdit(this.edit.toSingle(this.originalText), this.originalText); + + public readonly originalLineRange = this.lineEdit.lineRange; + public readonly modifiedLineRange = this.lineEdit.toLineEdit().getNewLineRanges()[0]; + + constructor( + public readonly originalText: AbstractText, + public readonly edit: TextEdit, + public readonly isCollapsed: boolean, + public readonly userJumpedToIt: boolean, + public readonly commands: readonly Command[], + public readonly inlineCompletion: InlineCompletionItem, + ) { + } + + equals(other: InlineEditWithChanges) { + return this.originalText.getValue() === other.originalText.getValue() && + this.edit.equals(other.edit) && + this.isCollapsed === other.isCollapsed && + this.userJumpedToIt === other.userJumpedToIt && + this.commands === other.commands && + this.inlineCompletion === other.inlineCompletion; + } +} diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/utils.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/utils.ts index d0d995b05e4ae..a5bae20bbfa2d 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/utils.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/utils.ts @@ -5,14 +5,20 @@ import { h } from '../../../../../../base/browser/dom.js'; import { KeybindingLabel, unthemedKeybindingLabelOptions } from '../../../../../../base/browser/ui/keybindingLabel/keybindingLabel.js'; +import { numberComparator } from '../../../../../../base/common/arrays.js'; +import { findFirstMin } from '../../../../../../base/common/arraysFind.js'; import { IReader } from '../../../../../../base/common/observable.js'; import { OS } from '../../../../../../base/common/platform.js'; +import { splitLines, getIndentationLength } from '../../../../../../base/common/strings.js'; import { URI } from '../../../../../../base/common/uri.js'; import { MenuEntryActionViewItem } from '../../../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { ObservableCodeEditor } from '../../../../../browser/observableCodeEditor.js'; import { LineRange } from '../../../../../common/core/lineRange.js'; -import { TextEdit } from '../../../../../common/core/textEdit.js'; +import { OffsetRange } from '../../../../../common/core/offsetRange.js'; +import { SingleTextEdit, TextEdit } from '../../../../../common/core/textEdit.js'; import { RangeMapping } from '../../../../../common/diff/rangeMapping.js'; +import { Range } from '../../../../../common/core/range.js'; +import { Position } from '../../../../../common/core/position.js'; export function maxLeftInRange(editor: ObservableCodeEditor, range: LineRange, reader: IReader): number { editor.layoutInfo.read(reader); @@ -91,3 +97,55 @@ export function applyEditToModifiedRangeMappings(rangeMapping: RangeMapping[], e } return updatedMappings; } + + +export function classNames(...classes: (string | false | undefined | null)[]) { + return classes.filter(c => typeof c === 'string').join(' '); +} + +function offsetRangeToRange(columnOffsetRange: OffsetRange, startPos: Position): Range { + return new Range( + startPos.lineNumber, + startPos.column + columnOffsetRange.start, + startPos.lineNumber, + startPos.column + columnOffsetRange.endExclusive, + ); +} + +export function createReindentEdit(text: string, range: LineRange): TextEdit { + const newLines = splitLines(text); + const edits: SingleTextEdit[] = []; + const minIndent = findFirstMin(range.mapToLineArray(l => getIndentationLength(newLines[l - 1])), numberComparator)!; + range.forEach(lineNumber => { + edits.push(new SingleTextEdit(offsetRangeToRange(new OffsetRange(0, minIndent), new Position(lineNumber, 1)), '')); + }); + return new TextEdit(edits); +} + +export class PathBuilder { + private _data: string = ''; + + public moveTo(point: Point): this { + this._data += `M ${point.x} ${point.y} `; + return this; + } + + public lineTo(point: Point): this { + this._data += `L ${point.x} ${point.y} `; + return this; + } + + public curveTo(cp: Point, to: Point): this { + this._data += `Q ${cp.x} ${cp.y} ${to.x} ${to.y} `; + return this; + } + + public curveTo2(cp1: Point, cp2: Point, to: Point): this { + this._data += `C ${cp1.x} ${cp1.y} ${cp2.x} ${cp2.y} ${to.x} ${to.y} `; + return this; + } + + public build(): string { + return this._data; + } +} diff --git a/src/vs/editor/contrib/lineSelection/browser/lineSelection.ts b/src/vs/editor/contrib/lineSelection/browser/lineSelection.ts index 1ad614b8d74e5..c111f826df93d 100644 --- a/src/vs/editor/contrib/lineSelection/browser/lineSelection.ts +++ b/src/vs/editor/contrib/lineSelection/browser/lineSelection.ts @@ -16,8 +16,7 @@ export class ExpandLineSelectionAction extends EditorAction { constructor() { super({ id: 'expandLineSelection', - label: nls.localize('expandLineSelection', "Expand Line Selection"), - alias: 'Expand Line Selection', + label: nls.localize2('expandLineSelection', "Expand Line Selection"), precondition: undefined, kbOpts: { weight: KeybindingWeight.EditorCore, diff --git a/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts b/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts index c9cff45b8f12f..0ca8dd2abfb46 100644 --- a/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts +++ b/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts @@ -79,8 +79,7 @@ class CopyLinesUpAction extends AbstractCopyLinesAction { constructor() { super(false, { id: 'editor.action.copyLinesUpAction', - label: nls.localize('lines.copyUp', "Copy Line Up"), - alias: 'Copy Line Up', + label: nls.localize2('lines.copyUp', "Copy Line Up"), precondition: EditorContextKeys.writable, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, @@ -102,8 +101,7 @@ class CopyLinesDownAction extends AbstractCopyLinesAction { constructor() { super(true, { id: 'editor.action.copyLinesDownAction', - label: nls.localize('lines.copyDown', "Copy Line Down"), - alias: 'Copy Line Down', + label: nls.localize2('lines.copyDown', "Copy Line Down"), precondition: EditorContextKeys.writable, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, @@ -126,8 +124,7 @@ export class DuplicateSelectionAction extends EditorAction { constructor() { super({ id: 'editor.action.duplicateSelection', - label: nls.localize('duplicateSelection', "Duplicate Selection"), - alias: 'Duplicate Selection', + label: nls.localize2('duplicateSelection', "Duplicate Selection"), precondition: EditorContextKeys.writable, menuOpts: { menuId: MenuId.MenubarSelectionMenu, @@ -194,8 +191,7 @@ class MoveLinesUpAction extends AbstractMoveLinesAction { constructor() { super(false, { id: 'editor.action.moveLinesUpAction', - label: nls.localize('lines.moveUp', "Move Line Up"), - alias: 'Move Line Up', + label: nls.localize2('lines.moveUp', "Move Line Up"), precondition: EditorContextKeys.writable, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, @@ -217,8 +213,7 @@ class MoveLinesDownAction extends AbstractMoveLinesAction { constructor() { super(true, { id: 'editor.action.moveLinesDownAction', - label: nls.localize('lines.moveDown', "Move Line Down"), - alias: 'Move Line Down', + label: nls.localize2('lines.moveDown', "Move Line Down"), precondition: EditorContextKeys.writable, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, @@ -277,8 +272,7 @@ export class SortLinesAscendingAction extends AbstractSortLinesAction { constructor() { super(false, { id: 'editor.action.sortLinesAscending', - label: nls.localize('lines.sortAscending', "Sort Lines Ascending"), - alias: 'Sort Lines Ascending', + label: nls.localize2('lines.sortAscending', "Sort Lines Ascending"), precondition: EditorContextKeys.writable }); } @@ -288,8 +282,7 @@ export class SortLinesDescendingAction extends AbstractSortLinesAction { constructor() { super(true, { id: 'editor.action.sortLinesDescending', - label: nls.localize('lines.sortDescending', "Sort Lines Descending"), - alias: 'Sort Lines Descending', + label: nls.localize2('lines.sortDescending', "Sort Lines Descending"), precondition: EditorContextKeys.writable }); } @@ -299,8 +292,7 @@ export class DeleteDuplicateLinesAction extends EditorAction { constructor() { super({ id: 'editor.action.removeDuplicateLines', - label: nls.localize('lines.deleteDuplicates', "Delete Duplicate Lines"), - alias: 'Delete Duplicate Lines', + label: nls.localize2('lines.deleteDuplicates', "Delete Duplicate Lines"), precondition: EditorContextKeys.writable }); } @@ -378,8 +370,7 @@ export class TrimTrailingWhitespaceAction extends EditorAction { constructor() { super({ id: TrimTrailingWhitespaceAction.ID, - label: nls.localize('lines.trimTrailingWhitespace', "Trim Trailing Whitespace"), - alias: 'Trim Trailing Whitespace', + label: nls.localize2('lines.trimTrailingWhitespace', "Trim Trailing Whitespace"), precondition: EditorContextKeys.writable, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, @@ -430,8 +421,7 @@ export class DeleteLinesAction extends EditorAction { constructor() { super({ id: 'editor.action.deleteLines', - label: nls.localize('lines.delete', "Delete Line"), - alias: 'Delete Line', + label: nls.localize2('lines.delete', "Delete Line"), precondition: EditorContextKeys.writable, kbOpts: { kbExpr: EditorContextKeys.textInputFocus, @@ -532,8 +522,7 @@ export class IndentLinesAction extends EditorAction { constructor() { super({ id: 'editor.action.indentLines', - label: nls.localize('lines.indent', "Indent Line"), - alias: 'Indent Line', + label: nls.localize2('lines.indent', "Indent Line"), precondition: EditorContextKeys.writable, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, @@ -558,8 +547,7 @@ class OutdentLinesAction extends EditorAction { constructor() { super({ id: 'editor.action.outdentLines', - label: nls.localize('lines.outdent', "Outdent Line"), - alias: 'Outdent Line', + label: nls.localize2('lines.outdent', "Outdent Line"), precondition: EditorContextKeys.writable, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, @@ -578,8 +566,7 @@ export class InsertLineBeforeAction extends EditorAction { constructor() { super({ id: 'editor.action.insertLineBefore', - label: nls.localize('lines.insertBefore', "Insert Line Above"), - alias: 'Insert Line Above', + label: nls.localize2('lines.insertBefore', "Insert Line Above"), precondition: EditorContextKeys.writable, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, @@ -603,8 +590,7 @@ export class InsertLineAfterAction extends EditorAction { constructor() { super({ id: 'editor.action.insertLineAfter', - label: nls.localize('lines.insertAfter', "Insert Line Below"), - alias: 'Insert Line Below', + label: nls.localize2('lines.insertAfter', "Insert Line Below"), precondition: EditorContextKeys.writable, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, @@ -671,8 +657,7 @@ export class DeleteAllLeftAction extends AbstractDeleteAllToBoundaryAction { constructor() { super({ id: 'deleteAllLeft', - label: nls.localize('lines.deleteAllLeft', "Delete All Left"), - alias: 'Delete All Left', + label: nls.localize2('lines.deleteAllLeft', "Delete All Left"), precondition: EditorContextKeys.writable, kbOpts: { kbExpr: EditorContextKeys.textInputFocus, @@ -749,8 +734,7 @@ export class DeleteAllRightAction extends AbstractDeleteAllToBoundaryAction { constructor() { super({ id: 'deleteAllRight', - label: nls.localize('lines.deleteAllRight', "Delete All Right"), - alias: 'Delete All Right', + label: nls.localize2('lines.deleteAllRight', "Delete All Right"), precondition: EditorContextKeys.writable, kbOpts: { kbExpr: EditorContextKeys.textInputFocus, @@ -816,8 +800,7 @@ export class JoinLinesAction extends EditorAction { constructor() { super({ id: 'editor.action.joinLines', - label: nls.localize('lines.joinLines', "Join Lines"), - alias: 'Join Lines', + label: nls.localize2('lines.joinLines', "Join Lines"), precondition: EditorContextKeys.writable, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, @@ -976,8 +959,7 @@ export class TransposeAction extends EditorAction { constructor() { super({ id: 'editor.action.transpose', - label: nls.localize('editor.transpose', "Transpose Characters around the Cursor"), - alias: 'Transpose Characters around the Cursor', + label: nls.localize2('editor.transpose', "Transpose Characters around the Cursor"), precondition: EditorContextKeys.writable }); } @@ -1075,8 +1057,7 @@ export class UpperCaseAction extends AbstractCaseAction { constructor() { super({ id: 'editor.action.transformToUppercase', - label: nls.localize('editor.transformToUppercase', "Transform to Uppercase"), - alias: 'Transform to Uppercase', + label: nls.localize2('editor.transformToUppercase', "Transform to Uppercase"), precondition: EditorContextKeys.writable }); } @@ -1090,8 +1071,7 @@ export class LowerCaseAction extends AbstractCaseAction { constructor() { super({ id: 'editor.action.transformToLowercase', - label: nls.localize('editor.transformToLowercase', "Transform to Lowercase"), - alias: 'Transform to Lowercase', + label: nls.localize2('editor.transformToLowercase', "Transform to Lowercase"), precondition: EditorContextKeys.writable }); } @@ -1138,8 +1118,7 @@ export class TitleCaseAction extends AbstractCaseAction { constructor() { super({ id: 'editor.action.transformToTitlecase', - label: nls.localize('editor.transformToTitlecase', "Transform to Title Case"), - alias: 'Transform to Title Case', + label: nls.localize2('editor.transformToTitlecase', "Transform to Title Case"), precondition: EditorContextKeys.writable }); } @@ -1164,8 +1143,7 @@ export class SnakeCaseAction extends AbstractCaseAction { constructor() { super({ id: 'editor.action.transformToSnakecase', - label: nls.localize('editor.transformToSnakecase', "Transform to Snake Case"), - alias: 'Transform to Snake Case', + label: nls.localize2('editor.transformToSnakecase', "Transform to Snake Case"), precondition: EditorContextKeys.writable }); } @@ -1191,8 +1169,7 @@ export class CamelCaseAction extends AbstractCaseAction { constructor() { super({ id: 'editor.action.transformToCamelcase', - label: nls.localize('editor.transformToCamelcase', "Transform to Camel Case"), - alias: 'Transform to Camel Case', + label: nls.localize2('editor.transformToCamelcase', "Transform to Camel Case"), precondition: EditorContextKeys.writable }); } @@ -1217,8 +1194,7 @@ export class PascalCaseAction extends AbstractCaseAction { constructor() { super({ id: 'editor.action.transformToPascalcase', - label: nls.localize('editor.transformToPascalcase', "Transform to Pascal Case"), - alias: 'Transform to Pascal Case', + label: nls.localize2('editor.transformToPascalcase', "Transform to Pascal Case"), precondition: EditorContextKeys.writable }); } @@ -1258,8 +1234,7 @@ export class KebabCaseAction extends AbstractCaseAction { constructor() { super({ id: 'editor.action.transformToKebabcase', - label: nls.localize('editor.transformToKebabcase', 'Transform to Kebab Case'), - alias: 'Transform to Kebab Case', + label: nls.localize2('editor.transformToKebabcase', 'Transform to Kebab Case'), precondition: EditorContextKeys.writable }); } diff --git a/src/vs/editor/contrib/linkedEditing/browser/linkedEditing.ts b/src/vs/editor/contrib/linkedEditing/browser/linkedEditing.ts index 62c8ab664b0f7..0644fcfff23fb 100644 --- a/src/vs/editor/contrib/linkedEditing/browser/linkedEditing.ts +++ b/src/vs/editor/contrib/linkedEditing/browser/linkedEditing.ts @@ -392,8 +392,7 @@ export class LinkedEditingAction extends EditorAction { constructor() { super({ id: 'editor.action.linkedEditing', - label: nls.localize('linkedEditing.label', "Start Linked Editing"), - alias: 'Start Linked Editing', + label: nls.localize2('linkedEditing.label', "Start Linked Editing"), precondition: ContextKeyExpr.and(EditorContextKeys.writable, EditorContextKeys.hasRenameProvider), kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, diff --git a/src/vs/editor/contrib/links/browser/links.ts b/src/vs/editor/contrib/links/browser/links.ts index 4fc3c17d938dd..ef1b67addd1b3 100644 --- a/src/vs/editor/contrib/links/browser/links.ts +++ b/src/vs/editor/contrib/links/browser/links.ts @@ -400,8 +400,7 @@ class OpenLinkAction extends EditorAction { constructor() { super({ id: 'editor.action.openLink', - label: nls.localize('label', "Open Link"), - alias: 'Open Link', + label: nls.localize2('label', "Open Link"), precondition: undefined }); } diff --git a/src/vs/editor/contrib/multicursor/browser/multicursor.ts b/src/vs/editor/contrib/multicursor/browser/multicursor.ts index d9b7237f221ab..c3bcf55dd7ce7 100644 --- a/src/vs/editor/contrib/multicursor/browser/multicursor.ts +++ b/src/vs/editor/contrib/multicursor/browser/multicursor.ts @@ -43,8 +43,7 @@ export class InsertCursorAbove extends EditorAction { constructor() { super({ id: 'editor.action.insertCursorAbove', - label: nls.localize('mutlicursor.insertAbove', "Add Cursor Above"), - alias: 'Add Cursor Above', + label: nls.localize2('mutlicursor.insertAbove', "Add Cursor Above"), precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, @@ -96,8 +95,7 @@ export class InsertCursorBelow extends EditorAction { constructor() { super({ id: 'editor.action.insertCursorBelow', - label: nls.localize('mutlicursor.insertBelow', "Add Cursor Below"), - alias: 'Add Cursor Below', + label: nls.localize2('mutlicursor.insertBelow', "Add Cursor Below"), precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, @@ -149,8 +147,7 @@ class InsertCursorAtEndOfEachLineSelected extends EditorAction { constructor() { super({ id: 'editor.action.insertCursorAtEndOfEachLineSelected', - label: nls.localize('mutlicursor.insertAtEndOfEachLineSelected', "Add Cursors to Line Ends"), - alias: 'Add Cursors to Line Ends', + label: nls.localize2('mutlicursor.insertAtEndOfEachLineSelected', "Add Cursors to Line Ends"), precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, @@ -204,8 +201,7 @@ class InsertCursorAtEndOfLineSelected extends EditorAction { constructor() { super({ id: 'editor.action.addCursorsToBottom', - label: nls.localize('mutlicursor.addCursorsToBottom', "Add Cursors To Bottom"), - alias: 'Add Cursors To Bottom', + label: nls.localize2('mutlicursor.addCursorsToBottom', "Add Cursors To Bottom"), precondition: undefined }); } @@ -237,8 +233,7 @@ class InsertCursorAtTopOfLineSelected extends EditorAction { constructor() { super({ id: 'editor.action.addCursorsToTop', - label: nls.localize('mutlicursor.addCursorsToTop', "Add Cursors To Top"), - alias: 'Add Cursors To Top', + label: nls.localize2('mutlicursor.addCursorsToTop', "Add Cursors To Top"), precondition: undefined }); } @@ -691,8 +686,7 @@ export class AddSelectionToNextFindMatchAction extends MultiCursorSelectionContr constructor() { super({ id: 'editor.action.addSelectionToNextFindMatch', - label: nls.localize('addSelectionToNextFindMatch', "Add Selection To Next Find Match"), - alias: 'Add Selection To Next Find Match', + label: nls.localize2('addSelectionToNextFindMatch', "Add Selection To Next Find Match"), precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.focus, @@ -716,8 +710,7 @@ export class AddSelectionToPreviousFindMatchAction extends MultiCursorSelectionC constructor() { super({ id: 'editor.action.addSelectionToPreviousFindMatch', - label: nls.localize('addSelectionToPreviousFindMatch', "Add Selection To Previous Find Match"), - alias: 'Add Selection To Previous Find Match', + label: nls.localize2('addSelectionToPreviousFindMatch', "Add Selection To Previous Find Match"), precondition: undefined, menuOpts: { menuId: MenuId.MenubarSelectionMenu, @@ -736,8 +729,7 @@ export class MoveSelectionToNextFindMatchAction extends MultiCursorSelectionCont constructor() { super({ id: 'editor.action.moveSelectionToNextFindMatch', - label: nls.localize('moveSelectionToNextFindMatch', "Move Last Selection To Next Find Match"), - alias: 'Move Last Selection To Next Find Match', + label: nls.localize2('moveSelectionToNextFindMatch', "Move Last Selection To Next Find Match"), precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.focus, @@ -755,8 +747,7 @@ export class MoveSelectionToPreviousFindMatchAction extends MultiCursorSelection constructor() { super({ id: 'editor.action.moveSelectionToPreviousFindMatch', - label: nls.localize('moveSelectionToPreviousFindMatch', "Move Last Selection To Previous Find Match"), - alias: 'Move Last Selection To Previous Find Match', + label: nls.localize2('moveSelectionToPreviousFindMatch', "Move Last Selection To Previous Find Match"), precondition: undefined }); } @@ -769,8 +760,7 @@ export class SelectHighlightsAction extends MultiCursorSelectionControllerAction constructor() { super({ id: 'editor.action.selectHighlights', - label: nls.localize('selectAllOccurrencesOfFindMatch', "Select All Occurrences of Find Match"), - alias: 'Select All Occurrences of Find Match', + label: nls.localize2('selectAllOccurrencesOfFindMatch', "Select All Occurrences of Find Match"), precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.focus, @@ -794,8 +784,7 @@ export class CompatChangeAll extends MultiCursorSelectionControllerAction { constructor() { super({ id: 'editor.action.changeAll', - label: nls.localize('changeAll.label', "Change All Occurrences"), - alias: 'Change All Occurrences', + label: nls.localize2('changeAll.label', "Change All Occurrences"), precondition: ContextKeyExpr.and(EditorContextKeys.writable, EditorContextKeys.editorTextFocus), kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, @@ -1079,12 +1068,11 @@ export class FocusNextCursor extends EditorAction { constructor() { super({ id: 'editor.action.focusNextCursor', - label: nls.localize('mutlicursor.focusNextCursor', "Focus Next Cursor"), + label: nls.localize2('mutlicursor.focusNextCursor', "Focus Next Cursor"), metadata: { description: nls.localize('mutlicursor.focusNextCursor.description', "Focuses the next cursor"), args: [], }, - alias: 'Focus Next Cursor', precondition: undefined }); } @@ -1118,12 +1106,11 @@ export class FocusPreviousCursor extends EditorAction { constructor() { super({ id: 'editor.action.focusPreviousCursor', - label: nls.localize('mutlicursor.focusPreviousCursor', "Focus Previous Cursor"), + label: nls.localize2('mutlicursor.focusPreviousCursor', "Focus Previous Cursor"), metadata: { description: nls.localize('mutlicursor.focusPreviousCursor.description', "Focuses the previous cursor"), args: [], }, - alias: 'Focus Previous Cursor', precondition: undefined }); } diff --git a/src/vs/editor/contrib/parameterHints/browser/parameterHints.ts b/src/vs/editor/contrib/parameterHints/browser/parameterHints.ts index afeaa40c90329..ee10657bd3082 100644 --- a/src/vs/editor/contrib/parameterHints/browser/parameterHints.ts +++ b/src/vs/editor/contrib/parameterHints/browser/parameterHints.ts @@ -77,8 +77,7 @@ export class TriggerParameterHintsAction extends EditorAction { constructor() { super({ id: 'editor.action.triggerParameterHints', - label: nls.localize('parameterHints.trigger.label', "Trigger Parameter Hints"), - alias: 'Trigger Parameter Hints', + label: nls.localize2('parameterHints.trigger.label', "Trigger Parameter Hints"), precondition: EditorContextKeys.hasSignatureHelpProvider, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, diff --git a/src/vs/editor/contrib/rename/browser/rename.ts b/src/vs/editor/contrib/rename/browser/rename.ts index e6e77aa01ae95..2165ff1e350e0 100644 --- a/src/vs/editor/contrib/rename/browser/rename.ts +++ b/src/vs/editor/contrib/rename/browser/rename.ts @@ -412,8 +412,7 @@ export class RenameAction extends EditorAction { constructor() { super({ id: 'editor.action.rename', - label: nls.localize('rename.label', "Rename Symbol"), - alias: 'Rename Symbol', + label: nls.localize2('rename.label', "Rename Symbol"), precondition: ContextKeyExpr.and(EditorContextKeys.writable, EditorContextKeys.hasRenameProvider), kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, diff --git a/src/vs/editor/contrib/rename/browser/renameWidget.ts b/src/vs/editor/contrib/rename/browser/renameWidget.ts index f9294c16cdaea..6e93d0862fd6d 100644 --- a/src/vs/editor/contrib/rename/browser/renameWidget.ts +++ b/src/vs/editor/contrib/rename/browser/renameWidget.ts @@ -6,9 +6,7 @@ import * as dom from '../../../../base/browser/dom.js'; import { StandardKeyboardEvent } from '../../../../base/browser/keyboardEvent.js'; import * as aria from '../../../../base/browser/ui/aria/aria.js'; -import { IManagedHover } from '../../../../base/browser/ui/hover/hover.js'; import { getBaseLayerHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegate2.js'; -import { getDefaultHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js'; import { renderIcon } from '../../../../base/browser/ui/iconLabel/iconLabels.js'; import { IListRenderer, IListVirtualDelegate } from '../../../../base/browser/ui/list/list.js'; import { List } from '../../../../base/browser/ui/list/listWidget.js'; @@ -900,7 +898,7 @@ class InputWithButton implements IDisposable { private _domNode: HTMLDivElement | undefined; private _inputNode: HTMLInputElement | undefined; private _buttonNode: HTMLElement | undefined; - private _buttonHover: IManagedHover | undefined; + private _buttonHoverContent: string = ''; private _buttonGenHoverText: string | undefined; private _buttonCancelHoverText: string | undefined; private _sparkleIcon: HTMLElement | undefined; @@ -934,8 +932,14 @@ class InputWithButton implements IDisposable { this._buttonGenHoverText = nls.localize('generateRenameSuggestionsButton', "Generate new name suggestions"); this._buttonCancelHoverText = nls.localize('cancelRenameSuggestionsButton', "Cancel"); - this._buttonHover = getBaseLayerHoverDelegate().setupManagedHover(getDefaultHoverDelegate('element'), this._buttonNode, this._buttonGenHoverText); - this._disposables.add(this._buttonHover); + this._buttonHoverContent = this._buttonGenHoverText; + this._disposables.add(getBaseLayerHoverDelegate().setupDelayedHover(this._buttonNode, () => ({ + content: this._buttonHoverContent, + appearance: { + showPointer: true, + compact: true, + } + }))); this._domNode.appendChild(this._buttonNode); @@ -985,7 +989,7 @@ class InputWithButton implements IDisposable { dom.clearNode(this.button); this.button.appendChild(this._sparkleIcon); this.button.setAttribute('aria-label', 'Generating new name suggestions'); - this._buttonHover?.update(this._buttonGenHoverText); + this._buttonHoverContent = this._buttonGenHoverText!; this.input.focus(); } @@ -995,7 +999,7 @@ class InputWithButton implements IDisposable { dom.clearNode(this.button); this.button.appendChild(this._stopIcon); this.button.setAttribute('aria-label', 'Cancel generating new name suggestions'); - this._buttonHover?.update(this._buttonCancelHoverText); + this._buttonHoverContent = this._buttonCancelHoverText!; this.input.focus(); } diff --git a/src/vs/editor/contrib/semanticTokens/browser/documentSemanticTokens.ts b/src/vs/editor/contrib/semanticTokens/browser/documentSemanticTokens.ts index 836b30f9b9b6e..2bc7cab868a76 100644 --- a/src/vs/editor/contrib/semanticTokens/browser/documentSemanticTokens.ts +++ b/src/vs/editor/contrib/semanticTokens/browser/documentSemanticTokens.ts @@ -3,29 +3,30 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Disposable, IDisposable, dispose } from '../../../../base/common/lifecycle.js'; -import * as errors from '../../../../base/common/errors.js'; -import { ITextModel } from '../../../common/model.js'; -import { IModelContentChangedEvent } from '../../../common/textModelEvents.js'; -import { DocumentSemanticTokensProvider, SemanticTokens, SemanticTokensEdits } from '../../../common/languages.js'; -import { IModelService } from '../../../common/services/model.js'; -import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { RunOnceScheduler } from '../../../../base/common/async.js'; import { CancellationTokenSource } from '../../../../base/common/cancellation.js'; +import * as errors from '../../../../base/common/errors.js'; +import { Disposable, IDisposable, dispose } from '../../../../base/common/lifecycle.js'; +import { ResourceMap } from '../../../../base/common/map.js'; +import { StopWatch } from '../../../../base/common/stopwatch.js'; +import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { IThemeService } from '../../../../platform/theme/common/themeService.js'; -import { SemanticTokensProviderStyling, toMultilineTokens2 } from '../../../common/services/semanticTokensProviderStyling.js'; -import { getDocumentSemanticTokens, hasDocumentSemanticTokensProvider, isSemanticTokens, isSemanticTokensEdits } from '../common/getSemanticTokens.js'; +import { registerEditorFeature } from '../../../common/editorFeatures.js'; +import { LanguageFeatureRegistry } from '../../../common/languageFeatureRegistry.js'; +import { DocumentSemanticTokensProvider, SemanticTokens, SemanticTokensEdits } from '../../../common/languages.js'; +import { ITextModel } from '../../../common/model.js'; import { IFeatureDebounceInformation, ILanguageFeatureDebounceService } from '../../../common/services/languageFeatureDebounce.js'; -import { StopWatch } from '../../../../base/common/stopwatch.js'; import { ILanguageFeaturesService } from '../../../common/services/languageFeatures.js'; -import { LanguageFeatureRegistry } from '../../../common/languageFeatureRegistry.js'; +import { IModelService } from '../../../common/services/model.js'; +import { SemanticTokensProviderStyling, toMultilineTokens2 } from '../../../common/services/semanticTokensProviderStyling.js'; import { ISemanticTokensStylingService } from '../../../common/services/semanticTokensStyling.js'; -import { registerEditorFeature } from '../../../common/editorFeatures.js'; +import { IModelContentChangedEvent } from '../../../common/textModelEvents.js'; +import { getDocumentSemanticTokens, hasDocumentSemanticTokensProvider, isSemanticTokens, isSemanticTokensEdits } from '../common/getSemanticTokens.js'; import { SEMANTIC_HIGHLIGHTING_SETTING_ID, isSemanticColoringEnabled } from '../common/semanticTokensConfig.js'; export class DocumentSemanticTokensFeature extends Disposable { - private readonly _watchers: Record; + private readonly _watchers = new ResourceMap(); constructor( @ISemanticTokensStylingService semanticTokensStylingService: ISemanticTokensStylingService, @@ -36,18 +37,18 @@ export class DocumentSemanticTokensFeature extends Disposable { @ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService, ) { super(); - this._watchers = Object.create(null); const register = (model: ITextModel) => { - this._watchers[model.uri.toString()] = new ModelSemanticColoring(model, semanticTokensStylingService, themeService, languageFeatureDebounceService, languageFeaturesService); + this._watchers.get(model.uri)?.dispose(); + this._watchers.set(model.uri, new ModelSemanticColoring(model, semanticTokensStylingService, themeService, languageFeatureDebounceService, languageFeaturesService)); }; const deregister = (model: ITextModel, modelSemanticColoring: ModelSemanticColoring) => { modelSemanticColoring.dispose(); - delete this._watchers[model.uri.toString()]; + this._watchers.delete(model.uri); }; const handleSettingOrThemeChange = () => { for (const model of modelService.getModels()) { - const curr = this._watchers[model.uri.toString()]; + const curr = this._watchers.get(model.uri); if (isSemanticColoringEnabled(model, themeService, configurationService)) { if (!curr) { register(model); @@ -70,7 +71,7 @@ export class DocumentSemanticTokensFeature extends Disposable { } })); this._register(modelService.onModelRemoved((model) => { - const curr = this._watchers[model.uri.toString()]; + const curr = this._watchers.get(model.uri); if (curr) { deregister(model, curr); } @@ -84,10 +85,9 @@ export class DocumentSemanticTokensFeature extends Disposable { } override dispose(): void { - // Dispose all watchers - for (const watcher of Object.values(this._watchers)) { - watcher.dispose(); - } + dispose(this._watchers.values()); + this._watchers.clear(); + super.dispose(); } } diff --git a/src/vs/editor/contrib/smartSelect/browser/smartSelect.ts b/src/vs/editor/contrib/smartSelect/browser/smartSelect.ts index 650f6c9111d7a..520e71d28bf61 100644 --- a/src/vs/editor/contrib/smartSelect/browser/smartSelect.ts +++ b/src/vs/editor/contrib/smartSelect/browser/smartSelect.ts @@ -151,8 +151,7 @@ class GrowSelectionAction extends AbstractSmartSelect { constructor() { super(true, { id: 'editor.action.smartSelect.expand', - label: nls.localize('smartSelect.expand', "Expand Selection"), - alias: 'Expand Selection', + label: nls.localize2('smartSelect.expand', "Expand Selection"), precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, @@ -180,8 +179,7 @@ class ShrinkSelectionAction extends AbstractSmartSelect { constructor() { super(false, { id: 'editor.action.smartSelect.shrink', - label: nls.localize('smartSelect.shrink', "Shrink Selection"), - alias: 'Shrink Selection', + label: nls.localize2('smartSelect.shrink', "Shrink Selection"), precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, diff --git a/src/vs/editor/contrib/suggest/browser/suggestController.ts b/src/vs/editor/contrib/suggest/browser/suggestController.ts index 0bf524ab1eecf..0ede4624f2307 100644 --- a/src/vs/editor/contrib/suggest/browser/suggestController.ts +++ b/src/vs/editor/contrib/suggest/browser/suggestController.ts @@ -804,8 +804,7 @@ export class TriggerSuggestAction extends EditorAction { constructor() { super({ id: TriggerSuggestAction.id, - label: nls.localize('suggest.trigger.label', "Trigger Suggest"), - alias: 'Trigger Suggest', + label: nls.localize2('suggest.trigger.label', "Trigger Suggest"), precondition: ContextKeyExpr.and(EditorContextKeys.writable, EditorContextKeys.hasCompletionItemProvider, SuggestContext.Visible.toNegated()), kbOpts: { kbExpr: EditorContextKeys.textInputFocus, @@ -1119,8 +1118,7 @@ registerEditorAction(class extends EditorAction { constructor() { super({ id: 'editor.action.resetSuggestSize', - label: nls.localize('suggest.reset.label', "Reset Suggest Widget Size"), - alias: 'Reset Suggest Widget Size', + label: nls.localize2('suggest.reset.label', "Reset Suggest Widget Size"), precondition: undefined }); } diff --git a/src/vs/editor/contrib/tokenization/browser/tokenization.ts b/src/vs/editor/contrib/tokenization/browser/tokenization.ts index ba16ce4609116..ce55b674f6d41 100644 --- a/src/vs/editor/contrib/tokenization/browser/tokenization.ts +++ b/src/vs/editor/contrib/tokenization/browser/tokenization.ts @@ -12,8 +12,7 @@ class ForceRetokenizeAction extends EditorAction { constructor() { super({ id: 'editor.action.forceRetokenize', - label: nls.localize('forceRetokenize', "Developer: Force Retokenize"), - alias: 'Developer: Force Retokenize', + label: nls.localize2('forceRetokenize', "Developer: Force Retokenize"), precondition: undefined }); } diff --git a/src/vs/editor/contrib/unicodeHighlighter/browser/unicodeHighlighter.ts b/src/vs/editor/contrib/unicodeHighlighter/browser/unicodeHighlighter.ts index d3747c2cdeb33..e39eaa2eb5533 100644 --- a/src/vs/editor/contrib/unicodeHighlighter/browser/unicodeHighlighter.ts +++ b/src/vs/editor/contrib/unicodeHighlighter/browser/unicodeHighlighter.ts @@ -578,8 +578,7 @@ export class DisableHighlightingInCommentsAction extends EditorAction implements constructor() { super({ id: DisableHighlightingOfAmbiguousCharactersAction.ID, - label: nls.localize('action.unicodeHighlight.disableHighlightingInComments', 'Disable highlighting of characters in comments'), - alias: 'Disable highlighting of characters in comments', + label: nls.localize2('action.unicodeHighlight.disableHighlightingInComments', "Disable highlighting of characters in comments"), precondition: undefined }); } @@ -602,8 +601,7 @@ export class DisableHighlightingInStringsAction extends EditorAction implements constructor() { super({ id: DisableHighlightingOfAmbiguousCharactersAction.ID, - label: nls.localize('action.unicodeHighlight.disableHighlightingInStrings', 'Disable highlighting of characters in strings'), - alias: 'Disable highlighting of characters in strings', + label: nls.localize2('action.unicodeHighlight.disableHighlightingInStrings', "Disable highlighting of characters in strings"), precondition: undefined }); } @@ -626,8 +624,7 @@ export class DisableHighlightingOfAmbiguousCharactersAction extends EditorAction constructor() { super({ id: DisableHighlightingOfAmbiguousCharactersAction.ID, - label: nls.localize('action.unicodeHighlight.disableHighlightingOfAmbiguousCharacters', 'Disable highlighting of ambiguous characters'), - alias: 'Disable highlighting of ambiguous characters', + label: nls.localize2('action.unicodeHighlight.disableHighlightingOfAmbiguousCharacters', "Disable highlighting of ambiguous characters"), precondition: undefined }); } @@ -650,8 +647,7 @@ export class DisableHighlightingOfInvisibleCharactersAction extends EditorAction constructor() { super({ id: DisableHighlightingOfInvisibleCharactersAction.ID, - label: nls.localize('action.unicodeHighlight.disableHighlightingOfInvisibleCharacters', 'Disable highlighting of invisible characters'), - alias: 'Disable highlighting of invisible characters', + label: nls.localize2('action.unicodeHighlight.disableHighlightingOfInvisibleCharacters', "Disable highlighting of invisible characters"), precondition: undefined }); } @@ -674,8 +670,7 @@ export class DisableHighlightingOfNonBasicAsciiCharactersAction extends EditorAc constructor() { super({ id: DisableHighlightingOfNonBasicAsciiCharactersAction.ID, - label: nls.localize('action.unicodeHighlight.disableHighlightingOfNonBasicAsciiCharacters', 'Disable highlighting of non basic ASCII characters'), - alias: 'Disable highlighting of non basic ASCII characters', + label: nls.localize2('action.unicodeHighlight.disableHighlightingOfNonBasicAsciiCharacters', "Disable highlighting of non basic ASCII characters"), precondition: undefined }); } @@ -704,8 +699,7 @@ export class ShowExcludeOptions extends EditorAction { constructor() { super({ id: ShowExcludeOptions.ID, - label: nls.localize('action.unicodeHighlight.showExcludeOptions', "Show Exclude Options"), - alias: 'Show Exclude Options', + label: nls.localize2('action.unicodeHighlight.showExcludeOptions', "Show Exclude Options"), precondition: undefined }); } diff --git a/src/vs/editor/contrib/wordHighlighter/browser/wordHighlighter.ts b/src/vs/editor/contrib/wordHighlighter/browser/wordHighlighter.ts index 68398268d5bd6..c5b51b3ac491d 100644 --- a/src/vs/editor/contrib/wordHighlighter/browser/wordHighlighter.ts +++ b/src/vs/editor/contrib/wordHighlighter/browser/wordHighlighter.ts @@ -931,8 +931,7 @@ class NextWordHighlightAction extends WordHighlightNavigationAction { constructor() { super(true, { id: 'editor.action.wordHighlight.next', - label: nls.localize('wordHighlight.next.label', "Go to Next Symbol Highlight"), - alias: 'Go to Next Symbol Highlight', + label: nls.localize2('wordHighlight.next.label', "Go to Next Symbol Highlight"), precondition: ctxHasWordHighlights, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, @@ -947,8 +946,7 @@ class PrevWordHighlightAction extends WordHighlightNavigationAction { constructor() { super(false, { id: 'editor.action.wordHighlight.prev', - label: nls.localize('wordHighlight.previous.label', "Go to Previous Symbol Highlight"), - alias: 'Go to Previous Symbol Highlight', + label: nls.localize2('wordHighlight.previous.label', "Go to Previous Symbol Highlight"), precondition: ctxHasWordHighlights, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, @@ -963,8 +961,7 @@ class TriggerWordHighlightAction extends EditorAction { constructor() { super({ id: 'editor.action.wordHighlight.trigger', - label: nls.localize('wordHighlight.trigger.label', "Trigger Symbol Highlight"), - alias: 'Trigger Symbol Highlight', + label: nls.localize2('wordHighlight.trigger.label', "Trigger Symbol Highlight"), precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, diff --git a/src/vs/editor/contrib/wordOperations/browser/wordOperations.ts b/src/vs/editor/contrib/wordOperations/browser/wordOperations.ts index 7022ec9bf389d..62f52402ebc3c 100644 --- a/src/vs/editor/contrib/wordOperations/browser/wordOperations.ts +++ b/src/vs/editor/contrib/wordOperations/browser/wordOperations.ts @@ -473,8 +473,7 @@ export class DeleteInsideWord extends EditorAction { super({ id: 'deleteInsideWord', precondition: EditorContextKeys.writable, - label: nls.localize('deleteInsideWord', "Delete Word"), - alias: 'Delete Word' + label: nls.localize2('deleteInsideWord', "Delete Word"), }); } diff --git a/src/vs/editor/contrib/zoneWidget/browser/zoneWidget.ts b/src/vs/editor/contrib/zoneWidget/browser/zoneWidget.ts index a6a07eb284d2d..6d3c3b9489316 100644 --- a/src/vs/editor/contrib/zoneWidget/browser/zoneWidget.ts +++ b/src/vs/editor/contrib/zoneWidget/browser/zoneWidget.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from '../../../../base/browser/dom.js'; +import * as domStylesheetsJs from '../../../../base/browser/domStylesheets.js'; import { IHorizontalSashLayoutProvider, ISashEvent, Orientation, Sash, SashState } from '../../../../base/browser/ui/sash/sash.js'; import { Color, RGBA } from '../../../../base/common/color.js'; import { IdGenerator } from '../../../../base/common/idGenerator.js'; @@ -127,7 +128,7 @@ class Arrow { dispose(): void { this.hide(); - dom.removeCSSRulesContainingSelector(this._ruleName); + domStylesheetsJs.removeCSSRulesContainingSelector(this._ruleName); } set color(value: string) { @@ -145,8 +146,8 @@ class Arrow { } private _updateStyle(): void { - dom.removeCSSRulesContainingSelector(this._ruleName); - dom.createCSSRule( + domStylesheetsJs.removeCSSRulesContainingSelector(this._ruleName); + domStylesheetsJs.createCSSRule( `.monaco-editor ${this._ruleName}`, `border-style: solid; border-color: transparent; border-bottom-color: ${this._color}; border-width: ${this._height}px; bottom: -${this._height}px !important; margin-left: -${this._height}px; ` ); @@ -179,6 +180,7 @@ export abstract class ZoneWidget implements IHorizontalSashLayoutProvider { private _arrow: Arrow | null = null; private _overlayWidget: OverlayWidgetDelegate | null = null; private _resizeSash: Sash | null = null; + private _isSashResizeHeight: boolean = false; private readonly _positionMarkerId: IEditorDecorationsCollection; protected _viewZone: ViewZoneDelegate | null = null; @@ -354,6 +356,7 @@ export abstract class ZoneWidget implements IHorizontalSashLayoutProvider { } this._arrow?.hide(); this._positionMarkerId.clear(); + this._isSashResizeHeight = false; } protected _decoratingElementsHeight(): number { @@ -512,12 +515,13 @@ export abstract class ZoneWidget implements IHorizontalSashLayoutProvider { this._resizeSash.state = SashState.Disabled; } - let data: { startY: number; heightInLines: number } | undefined; + let data: { startY: number; heightInLines: number; minLines: number; maxLines: number } | undefined; this._disposables.add(this._resizeSash.onDidStart((e: ISashEvent) => { if (this._viewZone) { data = { startY: e.startY, heightInLines: this._viewZone.heightInLines, + ... this._getResizeBounds() }; } })); @@ -532,13 +536,22 @@ export abstract class ZoneWidget implements IHorizontalSashLayoutProvider { const roundedLineDelta = lineDelta < 0 ? Math.ceil(lineDelta) : Math.floor(lineDelta); const newHeightInLines = data.heightInLines + roundedLineDelta; - if (newHeightInLines > 5 && newHeightInLines < 35) { + if (newHeightInLines > data.minLines && newHeightInLines < data.maxLines) { + this._isSashResizeHeight = true; this._relayout(newHeightInLines); } } })); } + protected get _usesResizeHeight(): boolean { + return this._isSashResizeHeight; + } + + protected _getResizeBounds(): { readonly minLines: number; readonly maxLines: number } { + return { minLines: 5, maxLines: 35 }; + } + getHorizontalSashLeft() { return 0; } diff --git a/src/vs/editor/standalone/browser/standaloneThemeService.ts b/src/vs/editor/standalone/browser/standaloneThemeService.ts index eb2470683cc96..8a697f653ff58 100644 --- a/src/vs/editor/standalone/browser/standaloneThemeService.ts +++ b/src/vs/editor/standalone/browser/standaloneThemeService.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from '../../../base/browser/dom.js'; +import * as domStylesheetsJs from '../../../base/browser/domStylesheets.js'; import { addMatchMediaChangeListener } from '../../../base/browser/browser.js'; import { Color } from '../../../base/common/color.js'; import { Emitter } from '../../../base/common/event.js'; @@ -275,7 +276,7 @@ export class StandaloneThemeService extends Disposable implements IStandaloneThe private _registerRegularEditorContainer(): IDisposable { if (!this._globalStyleElement) { - this._globalStyleElement = dom.createStyleSheet(undefined, style => { + this._globalStyleElement = domStylesheetsJs.createStyleSheet(undefined, style => { style.className = 'monaco-colors'; style.textContent = this._allCSS; }); @@ -285,7 +286,7 @@ export class StandaloneThemeService extends Disposable implements IStandaloneThe } private _registerShadowDomContainer(domNode: HTMLElement): IDisposable { - const styleElement = dom.createStyleSheet(domNode, style => { + const styleElement = domStylesheetsJs.createStyleSheet(domNode, style => { style.className = 'monaco-colors'; style.textContent = this._allCSS; }); diff --git a/src/vs/editor/standalone/browser/standaloneTreeSitterService.ts b/src/vs/editor/standalone/browser/standaloneTreeSitterService.ts index de27e7bea6ef5..be9b21fbdd90f 100644 --- a/src/vs/editor/standalone/browser/standaloneTreeSitterService.ts +++ b/src/vs/editor/standalone/browser/standaloneTreeSitterService.ts @@ -6,7 +6,7 @@ import type { Parser } from '@vscode/tree-sitter-wasm'; import { Event } from '../../../base/common/event.js'; import { ITextModel } from '../../common/model.js'; -import { ITreeSitterParseResult, ITreeSitterParserService } from '../../common/services/treeSitterParserService.js'; +import { ITextModelTreeSitter, ITreeSitterParseResult, ITreeSitterParserService } from '../../common/services/treeSitterParserService.js'; import { Range } from '../../common/core/range.js'; /** @@ -14,6 +14,9 @@ import { Range } from '../../common/core/range.js'; * We use a dummy sertive here to make the build happy. */ export class StandaloneTreeSitterParserService implements ITreeSitterParserService { + getTextModelTreeSitter(textModel: ITextModel): ITextModelTreeSitter | undefined { + return undefined; + } async getTree(content: string, languageId: string): Promise { return undefined; } diff --git a/src/vs/editor/test/browser/services/decorationRenderOptions.test.ts b/src/vs/editor/test/browser/services/decorationRenderOptions.test.ts index 7a35640c79beb..74555b9a823a9 100644 --- a/src/vs/editor/test/browser/services/decorationRenderOptions.test.ts +++ b/src/vs/editor/test/browser/services/decorationRenderOptions.test.ts @@ -130,7 +130,7 @@ suite('Decoration Render Options', () => { // single quote must always be escaped/encoded s.registerDecorationType('test', 'example', { gutterIconPath: URI.file('c:\\files\\foo\\b\'ar.png') }); - assertBackground('file:///c:/files/foo/b%27ar.png', 'vscode-file://vscode-app/c:/files/foo/b%27ar.png'); + assertBackground('file:///c:/files/foo/b\\000027ar.png', 'vscode-file://vscode-app/c:/files/foo/b\\000027ar.png'); s.removeDecorationType('example'); } else { // unix file path (used as string) @@ -140,12 +140,12 @@ suite('Decoration Render Options', () => { // single quote must always be escaped/encoded s.registerDecorationType('test', 'example', { gutterIconPath: URI.file('/Users/foo/b\'ar.png') }); - assertBackground('file:///Users/foo/b%27ar.png', 'vscode-file://vscode-app/Users/foo/b%27ar.png'); + assertBackground('file:///Users/foo/b\\000027ar.png', 'vscode-file://vscode-app/Users/foo/b\\000027ar.png'); s.removeDecorationType('example'); } s.registerDecorationType('test', 'example', { gutterIconPath: URI.parse('http://test/pa\'th') }); - assert(readStyleSheet(styleSheet).indexOf(`{background:url('http://test/pa%27th') center center no-repeat;}`) > 0); + assert(readStyleSheet(styleSheet).indexOf(`{background:url('http://test/pa\\000027th') center center no-repeat;}`) > 0); s.removeDecorationType('example'); }); }); diff --git a/src/vs/editor/test/common/services/testTreeSitterService.ts b/src/vs/editor/test/common/services/testTreeSitterService.ts index 2d5721bb32fd5..fd54b59ee5c4c 100644 --- a/src/vs/editor/test/common/services/testTreeSitterService.ts +++ b/src/vs/editor/test/common/services/testTreeSitterService.ts @@ -6,10 +6,13 @@ import type { Parser } from '@vscode/tree-sitter-wasm'; import { Event } from '../../../../base/common/event.js'; import { ITextModel } from '../../../common/model.js'; -import { ITreeSitterParserService, ITreeSitterParseResult } from '../../../common/services/treeSitterParserService.js'; +import { ITreeSitterParserService, ITreeSitterParseResult, ITextModelTreeSitter } from '../../../common/services/treeSitterParserService.js'; import { Range } from '../../../common/core/range.js'; export class TestTreeSitterParserService implements ITreeSitterParserService { + getTextModelTreeSitter(textModel: ITextModel): ITextModelTreeSitter | undefined { + throw new Error('Method not implemented.'); + } getTree(content: string, languageId: string): Promise { throw new Error('Method not implemented.'); } diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index f26d9206020c0..11b8f7415dcc2 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -4061,6 +4061,10 @@ declare namespace monaco.editor { * Controls whether the search result and diff result automatically restarts from the beginning (or the end) when no further matches can be found */ loop?: boolean; + /** + * Controls how the find widget search history should be stored + */ + findSearchHistory?: 'never' | 'workspace'; } export type GoToLocationValues = 'peek' | 'gotoAndPeek' | 'goto'; @@ -4593,10 +4597,16 @@ declare namespace monaco.editor { edits?: { experimental?: { enabled?: boolean; + useMixedLinesDiff?: 'never' | 'whenPossible' | 'afterJumpWhenPossible'; + useInterleavedLinesDiff?: 'never' | 'always' | 'afterJump'; }; }; } + type RequiredRecursive = { + [P in keyof T]-?: T[P] extends object | undefined ? RequiredRecursive : T[P]; + }; + export interface IBracketPairColorizationOptions { /** * Enable or disable bracket pair colorization. @@ -5149,7 +5159,7 @@ declare namespace monaco.editor { smoothScrolling: IEditorOption; stopRenderingLineAfter: IEditorOption; suggest: IEditorOption>>; - inlineSuggest: IEditorOption>>; + inlineSuggest: IEditorOption>>; inlineCompletionsAccessibilityVerbose: IEditorOption; suggestFontSize: IEditorOption; suggestLineHeight: IEditorOption; @@ -5382,7 +5392,21 @@ declare namespace monaco.editor { * widget. Is being invoked with the selected position preference * or `null` if not rendered. */ - afterRender?(position: ContentWidgetPositionPreference | null): void; + afterRender?(position: ContentWidgetPositionPreference | null, coordinate: IContentWidgetRenderedCoordinate | null): void; + } + + /** + * Coordinatees passed in {@link IContentWidget.afterRender} + */ + export interface IContentWidgetRenderedCoordinate { + /** + * Top position relative to the editor content. + */ + readonly top: number; + /** + * Left position relative to the editor content. + */ + readonly left: number; } /** @@ -7226,6 +7250,10 @@ declare namespace monaco.languages { */ readonly range?: IRange; readonly command?: Command; + /** + * Is called the first time an inline completion is shown. + */ + readonly shownCommand?: Command; /** * If set to `true`, unopened closing brackets are removed and unclosed opening brackets are closed. * Defaults to `false`. @@ -7257,9 +7285,10 @@ declare namespace monaco.languages { */ handleItemDidShow?(completions: T, item: T['items'][number], updatedInsertText: string): void; /** - * Will be called when an item is partially accepted. + * Will be called when an item is partially accepted. TODO: also handle full acceptance here! */ handlePartialAccept?(completions: T, item: T['items'][number], acceptedCharacters: number, info: PartialAcceptInfo): void; + handleRejection?(completions: T, item: T['items'][number]): void; /** * Will be called when a completions list is no longer in use and can be garbage-collected. */ @@ -8113,6 +8142,7 @@ declare namespace monaco.languages { range: IRange; accepted?: Command; rejected?: Command; + shown?: Command; commands?: Command[]; } diff --git a/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts b/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts index ed312b7113ebf..3c8b7d5b719ce 100644 --- a/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts +++ b/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts @@ -304,11 +304,11 @@ export class Sound { public static readonly diffLineInserted = Sound.register({ fileName: 'diffLineInserted.mp3' }); public static readonly diffLineDeleted = Sound.register({ fileName: 'diffLineDeleted.mp3' }); public static readonly diffLineModified = Sound.register({ fileName: 'diffLineModified.mp3' }); - public static readonly chatRequestSent = Sound.register({ fileName: 'chatRequestSent.mp3' }); - public static readonly chatResponseReceived1 = Sound.register({ fileName: 'chatResponseReceived1.mp3' }); - public static readonly chatResponseReceived2 = Sound.register({ fileName: 'chatResponseReceived2.mp3' }); - public static readonly chatResponseReceived3 = Sound.register({ fileName: 'chatResponseReceived3.mp3' }); - public static readonly chatResponseReceived4 = Sound.register({ fileName: 'chatResponseReceived4.mp3' }); + public static readonly requestSent = Sound.register({ fileName: 'requestSent.mp3' }); + public static readonly responseReceived1 = Sound.register({ fileName: 'responseReceived1.mp3' }); + public static readonly responseReceived2 = Sound.register({ fileName: 'responseReceived2.mp3' }); + public static readonly responseReceived3 = Sound.register({ fileName: 'responseReceived3.mp3' }); + public static readonly responseReceived4 = Sound.register({ fileName: 'responseReceived4.mp3' }); public static readonly clear = Sound.register({ fileName: 'clear.mp3' }); public static readonly save = Sound.register({ fileName: 'save.mp3' }); public static readonly format = Sound.register({ fileName: 'format.mp3' }); @@ -543,7 +543,7 @@ export class AccessibilitySignal { public static readonly chatRequestSent = AccessibilitySignal.register({ name: localize('accessibilitySignals.chatRequestSent', 'Chat Request Sent'), - sound: Sound.chatRequestSent, + sound: Sound.requestSent, legacySoundSettingsKey: 'audioCues.chatRequestSent', legacyAnnouncementSettingsKey: 'accessibility.alert.chatRequestSent', announcementMessage: localize('accessibility.signals.chatRequestSent', 'Chat Request Sent'), @@ -555,15 +555,32 @@ export class AccessibilitySignal { legacySoundSettingsKey: 'audioCues.chatResponseReceived', sound: { randomOneOf: [ - Sound.chatResponseReceived1, - Sound.chatResponseReceived2, - Sound.chatResponseReceived3, - Sound.chatResponseReceived4 + Sound.responseReceived1, + Sound.responseReceived2, + Sound.responseReceived3, + Sound.responseReceived4 ] }, settingsKey: 'accessibility.signals.chatResponseReceived' }); + public static readonly codeActionTriggered = AccessibilitySignal.register({ + name: localize('accessibilitySignals.codeActionRequestTriggered', 'Code Action Request Triggered'), + sound: Sound.voiceRecordingStarted, + legacySoundSettingsKey: 'audioCues.codeActionRequestTriggered', + legacyAnnouncementSettingsKey: 'accessibility.alert.codeActionRequestTriggered', + announcementMessage: localize('accessibility.signals.codeActionRequestTriggered', 'Code Action Request Triggered'), + settingsKey: 'accessibility.signals.codeActionTriggered', + }); + + public static readonly codeActionApplied = AccessibilitySignal.register({ + name: localize('accessibilitySignals.codeActionApplied', 'Code Action Applied'), + legacySoundSettingsKey: 'audioCues.codeActionApplied', + sound: Sound.voiceRecordingStopped, + settingsKey: 'accessibility.signals.codeActionApplied' + }); + + public static readonly progress = AccessibilitySignal.register({ name: localize('accessibilitySignals.progress', 'Progress'), sound: Sound.progress, diff --git a/src/vs/platform/accessibilitySignal/browser/media/chatRequestSent.mp3 b/src/vs/platform/accessibilitySignal/browser/media/requestSent.mp3 similarity index 100% rename from src/vs/platform/accessibilitySignal/browser/media/chatRequestSent.mp3 rename to src/vs/platform/accessibilitySignal/browser/media/requestSent.mp3 diff --git a/src/vs/platform/accessibilitySignal/browser/media/chatResponseReceived1.mp3 b/src/vs/platform/accessibilitySignal/browser/media/responseReceived1.mp3 similarity index 100% rename from src/vs/platform/accessibilitySignal/browser/media/chatResponseReceived1.mp3 rename to src/vs/platform/accessibilitySignal/browser/media/responseReceived1.mp3 diff --git a/src/vs/platform/accessibilitySignal/browser/media/chatResponseReceived2.mp3 b/src/vs/platform/accessibilitySignal/browser/media/responseReceived2.mp3 similarity index 100% rename from src/vs/platform/accessibilitySignal/browser/media/chatResponseReceived2.mp3 rename to src/vs/platform/accessibilitySignal/browser/media/responseReceived2.mp3 diff --git a/src/vs/platform/accessibilitySignal/browser/media/chatResponseReceived3.mp3 b/src/vs/platform/accessibilitySignal/browser/media/responseReceived3.mp3 similarity index 100% rename from src/vs/platform/accessibilitySignal/browser/media/chatResponseReceived3.mp3 rename to src/vs/platform/accessibilitySignal/browser/media/responseReceived3.mp3 diff --git a/src/vs/platform/accessibilitySignal/browser/media/chatResponseReceived4.mp3 b/src/vs/platform/accessibilitySignal/browser/media/responseReceived4.mp3 similarity index 100% rename from src/vs/platform/accessibilitySignal/browser/media/chatResponseReceived4.mp3 rename to src/vs/platform/accessibilitySignal/browser/media/responseReceived4.mp3 diff --git a/src/vs/platform/actionWidget/browser/actionList.ts b/src/vs/platform/actionWidget/browser/actionList.ts index 9a48bf706a31e..bebab4ec9a4b5 100644 --- a/src/vs/platform/actionWidget/browser/actionList.ts +++ b/src/vs/platform/actionWidget/browser/actionList.ts @@ -37,6 +37,7 @@ export interface IActionListItem { readonly label?: string; readonly keybinding?: ResolvedKeybinding; canPreview?: boolean | undefined; + readonly hideIcon?: boolean; } interface IActionMenuTemplateData { @@ -118,6 +119,8 @@ class ActionItemRenderer implements IListRenderer, IAction return; } + dom.setVisibility(!element.hideIcon, data.icon); + data.text.textContent = stripNewlines(element.label); data.keybinding.set(element.keybinding); @@ -139,8 +142,8 @@ class ActionItemRenderer implements IListRenderer, IAction } } - disposeTemplate(_templateData: IActionMenuTemplateData): void { - _templateData.keybinding.dispose(); + disposeTemplate(templateData: IActionMenuTemplateData): void { + templateData.keybinding.dispose(); } } diff --git a/src/vs/platform/actions/browser/buttonbar.ts b/src/vs/platform/actions/browser/buttonbar.ts index 0f4fc56be5980..e477d3ddbdd03 100644 --- a/src/vs/platform/actions/browser/buttonbar.ts +++ b/src/vs/platform/actions/browser/buttonbar.ts @@ -11,7 +11,7 @@ import { Emitter, Event } from '../../../base/common/event.js'; import { DisposableStore } from '../../../base/common/lifecycle.js'; import { ThemeIcon } from '../../../base/common/themables.js'; import { localize } from '../../../nls.js'; -import { createAndFillInActionBarActions } from './menuEntryActionViewItem.js'; +import { getActionBarActions } from './menuEntryActionViewItem.js'; import { IToolBarRenderOptions } from './toolbar.js'; import { MenuId, IMenuService, MenuItemAction, IMenuActionOptions } from '../common/actions.js'; import { IContextKeyService } from '../../contextkey/common/contextkey.js'; @@ -94,27 +94,36 @@ export class WorkbenchButtonBar extends ButtonBar { actionRunner: this._actionRunner, actions: rest, contextMenuProvider: this._contextMenuService, - ariaLabel: action.label + ariaLabel: action.label, + supportIcons: true, }); } else { action = actionOrSubmenu; btn = this.addButton({ secondary: conifgProvider(action, i)?.isSecondary ?? secondary, - ariaLabel: action.label + ariaLabel: action.label, + supportIcons: true, }); } btn.enabled = action.enabled; btn.checked = action.checked ?? false; btn.element.classList.add('default-colors'); - if (conifgProvider(action, i)?.showLabel ?? true) { + const showLabel = conifgProvider(action, i)?.showLabel ?? true; + if (showLabel) { btn.label = action.label; } else { btn.element.classList.add('monaco-text-button'); } if (conifgProvider(action, i)?.showIcon) { if (action instanceof MenuItemAction && ThemeIcon.isThemeIcon(action.item.icon)) { - btn.icon = action.item.icon; + if (!showLabel) { + btn.icon = action.item.icon; + } else { + // this is REALLY hacky but combining a codicon and normal text is ugly because + // the former define a font which doesn't work for text + btn.label = `$(${action.item.icon.id}) ${action.label}`; + } } else if (action.class) { btn.element.classList.add(...action.class.split(' ')); } @@ -122,9 +131,9 @@ export class WorkbenchButtonBar extends ButtonBar { const kb = this._keybindingService.lookupKeybinding(action.id); let tooltip: string; if (kb) { - tooltip = localize('labelWithKeybinding', "{0} ({1})", action.tooltip ?? action.label, kb.getLabel()); + tooltip = localize('labelWithKeybinding', "{0} ({1})", action.tooltip || action.label, kb.getLabel()); } else { - tooltip = action.tooltip ?? action.label; + tooltip = action.tooltip || action.label; } this._updateStore.add(this._hoverService.setupManagedHover(hoverDelegate, btn.element, tooltip)); this._updateStore.add(btn.onDidClick(async () => { @@ -187,16 +196,12 @@ export class MenuWorkbenchButtonBar extends WorkbenchButtonBar { this.clear(); - const primary: IAction[] = []; - const secondary: IAction[] = []; - createAndFillInActionBarActions( - menu, - options?.menuOptions, - { primary, secondary }, + const actions = getActionBarActions( + menu.getActions(options?.menuOptions), options?.toolbarOptions?.primaryGroup ); - super.update(primary, secondary); + super.update(actions.primary, actions.secondary); }; this._store.add(menu.onDidChange(update)); update(); diff --git a/src/vs/platform/actions/browser/dropdownWithPrimaryActionViewItem.ts b/src/vs/platform/actions/browser/dropdownWithPrimaryActionViewItem.ts index 3a971d78c4b03..f9dec8ce9b523 100644 --- a/src/vs/platform/actions/browser/dropdownWithPrimaryActionViewItem.ts +++ b/src/vs/platform/actions/browser/dropdownWithPrimaryActionViewItem.ts @@ -154,6 +154,10 @@ export class DropdownWithPrimaryActionViewItem extends BaseActionViewItem { } } + showDropdown(): void { + this._dropdown.show(); + } + override dispose() { this._primaryAction.dispose(); this._dropdown.dispose(); diff --git a/src/vs/platform/actions/browser/floatingMenu.ts b/src/vs/platform/actions/browser/floatingMenu.ts index dc72cf005c3f9..fa6b7d1aa1a0f 100644 --- a/src/vs/platform/actions/browser/floatingMenu.ts +++ b/src/vs/platform/actions/browser/floatingMenu.ts @@ -8,7 +8,7 @@ import { Widget } from '../../../base/browser/ui/widget.js'; import { IAction } from '../../../base/common/actions.js'; import { Emitter } from '../../../base/common/event.js'; import { Disposable, DisposableStore, toDisposable } from '../../../base/common/lifecycle.js'; -import { createAndFillInActionBarActions } from './menuEntryActionViewItem.js'; +import { getFlatActionBarActions } from './menuEntryActionViewItem.js'; import { IMenu, IMenuService, MenuId } from '../common/actions.js'; import { IContextKeyService } from '../../contextkey/common/contextkey.js'; import { IInstantiationService } from '../../instantiation/common/instantiation.js'; @@ -69,8 +69,7 @@ export abstract class AbstractFloatingClickMenu extends Disposable { if (!this.isVisible()) { return; } - const actions: IAction[] = []; - createAndFillInActionBarActions(this.menu, { renderShortTitle: true, shouldForwardArgs: true }, actions); + const actions = getFlatActionBarActions(this.menu.getActions({ renderShortTitle: true, shouldForwardArgs: true })); if (actions.length === 0) { return; } diff --git a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts index 3386feda5ddcc..517bd2f66deff 100644 --- a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts +++ b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts @@ -3,20 +3,23 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { $, addDisposableListener, append, EventType, ModifierKeyEmitter, prepend } from '../../../base/browser/dom.js'; import { asCSSUrl } from '../../../base/browser/cssValue.js'; +import { $, addDisposableListener, append, EventType, ModifierKeyEmitter, prepend } from '../../../base/browser/dom.js'; import { StandardKeyboardEvent } from '../../../base/browser/keyboardEvent.js'; import { ActionViewItem, BaseActionViewItem, SelectActionViewItem } from '../../../base/browser/ui/actionbar/actionViewItems.js'; import { DropdownMenuActionViewItem, IDropdownMenuActionViewItemOptions } from '../../../base/browser/ui/dropdown/dropdownActionViewItem.js'; +import { IHoverDelegate } from '../../../base/browser/ui/hover/hoverDelegate.js'; import { ActionRunner, IAction, IRunEvent, Separator, SubmenuAction } from '../../../base/common/actions.js'; import { Event } from '../../../base/common/event.js'; import { UILabelProvider } from '../../../base/common/keybindingLabels.js'; +import { ResolvedKeybinding } from '../../../base/common/keybindings.js'; import { KeyCode } from '../../../base/common/keyCodes.js'; import { combinedDisposable, MutableDisposable, toDisposable } from '../../../base/common/lifecycle.js'; import { isLinux, isWindows, OS } from '../../../base/common/platform.js'; -import './menuEntryActionViewItem.css'; +import { ThemeIcon } from '../../../base/common/themables.js'; +import { assertType } from '../../../base/common/types.js'; import { localize } from '../../../nls.js'; -import { IMenu, IMenuActionOptions, IMenuService, MenuItemAction, SubmenuItemAction } from '../common/actions.js'; +import { IAccessibilityService } from '../../accessibility/common/accessibility.js'; import { ICommandAction, isICommandActionToggleInfo } from '../../action/common/action.js'; import { IContextKeyService } from '../../contextkey/common/contextkey.js'; import { IContextMenuService, IContextViewService } from '../../contextview/browser/contextView.js'; @@ -24,81 +27,76 @@ import { IInstantiationService } from '../../instantiation/common/instantiation. import { IKeybindingService } from '../../keybinding/common/keybinding.js'; import { INotificationService } from '../../notification/common/notification.js'; import { IStorageService, StorageScope, StorageTarget } from '../../storage/common/storage.js'; -import { IThemeService } from '../../theme/common/themeService.js'; -import { ThemeIcon } from '../../../base/common/themables.js'; -import { isDark } from '../../theme/common/theme.js'; -import { IHoverDelegate } from '../../../base/browser/ui/hover/hoverDelegate.js'; -import { assertType } from '../../../base/common/types.js'; -import { asCssVariable, selectBorder } from '../../theme/common/colorRegistry.js'; import { defaultSelectBoxStyles } from '../../theme/browser/defaultStyles.js'; -import { IAccessibilityService } from '../../accessibility/common/accessibility.js'; -import { ResolvedKeybinding } from '../../../base/common/keybindings.js'; +import { asCssVariable, selectBorder } from '../../theme/common/colorRegistry.js'; +import { isDark } from '../../theme/common/theme.js'; +import { IThemeService } from '../../theme/common/themeService.js'; +import { IMenuService, MenuItemAction, SubmenuItemAction } from '../common/actions.js'; +import './menuEntryActionViewItem.css'; -export function createAndFillInContextMenuActions(menu: IMenu, options: IMenuActionOptions | undefined, target: IAction[] | { primary: IAction[]; secondary: IAction[] }, primaryGroup?: string): void; -export function createAndFillInContextMenuActions(menu: [string, Array][], target: IAction[] | { primary: IAction[]; secondary: IAction[] }, primaryGroup?: string): void; -export function createAndFillInContextMenuActions(menu: IMenu | [string, Array][], optionsOrTarget: IMenuActionOptions | undefined | IAction[] | { primary: IAction[]; secondary: IAction[] }, targetOrPrimaryGroup?: IAction[] | { primary: IAction[]; secondary: IAction[] } | string, primaryGroupOrUndefined?: string): void { - let target: IAction[] | { primary: IAction[]; secondary: IAction[] }; - let primaryGroup: string | ((actionGroup: string) => boolean) | undefined; - let groups: [string, Array][]; - if (Array.isArray(menu)) { - groups = menu; - target = optionsOrTarget as IAction[] | { primary: IAction[]; secondary: IAction[] }; - primaryGroup = targetOrPrimaryGroup as string | undefined; - } else { - const options: IMenuActionOptions | undefined = optionsOrTarget as IMenuActionOptions | undefined; - groups = menu.getActions(options); - target = targetOrPrimaryGroup as IAction[] | { primary: IAction[]; secondary: IAction[] }; - primaryGroup = primaryGroupOrUndefined; - } +export interface PrimaryAndSecondaryActions { + primary: IAction[]; + secondary: IAction[]; +} + +export function getContextMenuActions( + groups: ReadonlyArray<[string, ReadonlyArray]>, + primaryGroup?: string +): PrimaryAndSecondaryActions { + const target: PrimaryAndSecondaryActions = { primary: [], secondary: [] }; + getContextMenuActionsImpl(groups, target, primaryGroup); + return target; +} + +export function getFlatContextMenuActions( + groups: ReadonlyArray<[string, ReadonlyArray]>, + primaryGroup?: string +): IAction[] { + const target: IAction[] = []; + getContextMenuActionsImpl(groups, target, primaryGroup); + return target; +} +function getContextMenuActionsImpl( + groups: ReadonlyArray<[string, ReadonlyArray]>, + target: IAction[] | PrimaryAndSecondaryActions, + primaryGroup?: string +) { const modifierKeyEmitter = ModifierKeyEmitter.getInstance(); const useAlternativeActions = modifierKeyEmitter.keyStatus.altKey || ((isWindows || isLinux) && modifierKeyEmitter.keyStatus.shiftKey); fillInActions(groups, target, useAlternativeActions, primaryGroup ? actionGroup => actionGroup === primaryGroup : actionGroup => actionGroup === 'navigation'); } -export function createAndFillInActionBarActions( - menu: IMenu, - options: IMenuActionOptions | undefined, - target: IAction[] | { primary: IAction[]; secondary: IAction[] }, + +export function getActionBarActions( + groups: [string, Array][], primaryGroup?: string | ((actionGroup: string) => boolean), shouldInlineSubmenu?: (action: SubmenuAction, group: string, groupSize: number) => boolean, useSeparatorsInPrimaryActions?: boolean -): void; -export function createAndFillInActionBarActions( - menu: [string, Array][], - target: IAction[] | { primary: IAction[]; secondary: IAction[] }, +): PrimaryAndSecondaryActions { + const target: PrimaryAndSecondaryActions = { primary: [], secondary: [] }; + fillInActionBarActions(groups, target, primaryGroup, shouldInlineSubmenu, useSeparatorsInPrimaryActions); + return target; +} + +export function getFlatActionBarActions( + groups: [string, Array][], primaryGroup?: string | ((actionGroup: string) => boolean), shouldInlineSubmenu?: (action: SubmenuAction, group: string, groupSize: number) => boolean, useSeparatorsInPrimaryActions?: boolean -): void; -export function createAndFillInActionBarActions( - menu: IMenu | [string, Array][], - optionsOrTarget: IMenuActionOptions | undefined | IAction[] | { primary: IAction[]; secondary: IAction[] }, - targetOrPrimaryGroup?: IAction[] | { primary: IAction[]; secondary: IAction[] } | string | ((actionGroup: string) => boolean), - primaryGroupOrShouldInlineSubmenu?: string | ((actionGroup: string) => boolean) | ((action: SubmenuAction, group: string, groupSize: number) => boolean), - shouldInlineSubmenuOrUseSeparatorsInPrimaryActions?: ((action: SubmenuAction, group: string, groupSize: number) => boolean) | boolean, - useSeparatorsInPrimaryActionsOrUndefined?: boolean -): void { - let target: IAction[] | { primary: IAction[]; secondary: IAction[] }; - let primaryGroup: string | ((actionGroup: string) => boolean) | undefined; - let shouldInlineSubmenu: ((action: SubmenuAction, group: string, groupSize: number) => boolean) | undefined; - let useSeparatorsInPrimaryActions: boolean | undefined; - let groups: [string, Array][]; - if (Array.isArray(menu)) { - groups = menu; - target = optionsOrTarget as IAction[] | { primary: IAction[]; secondary: IAction[] }; - primaryGroup = targetOrPrimaryGroup as string | ((actionGroup: string) => boolean) | undefined; - shouldInlineSubmenu = primaryGroupOrShouldInlineSubmenu as (action: SubmenuAction, group: string, groupSize: number) => boolean; - useSeparatorsInPrimaryActions = shouldInlineSubmenuOrUseSeparatorsInPrimaryActions as boolean | undefined; - } else { - const options: IMenuActionOptions | undefined = optionsOrTarget as IMenuActionOptions | undefined; - groups = menu.getActions(options); - target = targetOrPrimaryGroup as IAction[] | { primary: IAction[]; secondary: IAction[] }; - primaryGroup = primaryGroupOrShouldInlineSubmenu as string | ((actionGroup: string) => boolean) | undefined; - shouldInlineSubmenu = shouldInlineSubmenuOrUseSeparatorsInPrimaryActions as (action: SubmenuAction, group: string, groupSize: number) => boolean; - useSeparatorsInPrimaryActions = useSeparatorsInPrimaryActionsOrUndefined; - } +): IAction[] { + const target: IAction[] = []; + fillInActionBarActions(groups, target, primaryGroup, shouldInlineSubmenu, useSeparatorsInPrimaryActions); + return target; +} +export function fillInActionBarActions( + groups: [string, Array][], + target: IAction[] | PrimaryAndSecondaryActions, + primaryGroup?: string | ((actionGroup: string) => boolean), + shouldInlineSubmenu?: (action: SubmenuAction, group: string, groupSize: number) => boolean, + useSeparatorsInPrimaryActions?: boolean +): void { const isPrimaryAction = typeof primaryGroup === 'string' ? (actionGroup: string) => actionGroup === primaryGroup : primaryGroup; // Action bars handle alternative actions on their own so the alternative actions should be ignored @@ -106,7 +104,8 @@ export function createAndFillInActionBarActions( } function fillInActions( - groups: ReadonlyArray<[string, ReadonlyArray]>, target: IAction[] | { primary: IAction[]; secondary: IAction[] }, + groups: ReadonlyArray<[string, ReadonlyArray]>, + target: IAction[] | PrimaryAndSecondaryActions, useAlternativeActions: boolean, isPrimaryAction: (actionGroup: string) => boolean = actionGroup => actionGroup === 'navigation', shouldInlineSubmenu: (action: SubmenuAction, group: string, groupSize: number) => boolean = () => false, diff --git a/src/vs/platform/actions/browser/toolbar.ts b/src/vs/platform/actions/browser/toolbar.ts index 741f946c6f8d9..5e46b390271de 100644 --- a/src/vs/platform/actions/browser/toolbar.ts +++ b/src/vs/platform/actions/browser/toolbar.ts @@ -14,7 +14,7 @@ import { Emitter, Event } from '../../../base/common/event.js'; import { Iterable } from '../../../base/common/iterator.js'; import { DisposableStore } from '../../../base/common/lifecycle.js'; import { localize } from '../../../nls.js'; -import { createActionViewItem, createAndFillInActionBarActions } from './menuEntryActionViewItem.js'; +import { createActionViewItem, getActionBarActions } from './menuEntryActionViewItem.js'; import { IMenuActionOptions, IMenuService, MenuId, MenuItemAction, SubmenuItemAction } from '../common/actions.js'; import { createConfigureKeybindingAction } from '../common/menuService.js'; import { ICommandService } from '../../commands/common/commands.js'; @@ -364,12 +364,8 @@ export class MenuWorkbenchToolBar extends WorkbenchToolBar { // update logic const menu = this._store.add(menuService.createMenu(menuId, contextKeyService, { emitEventsForSubmenuChanges: true, eventDebounceDelay: options?.eventDebounceDelay })); const updateToolbar = () => { - const primary: IAction[] = []; - const secondary: IAction[] = []; - createAndFillInActionBarActions( - menu, - options?.menuOptions, - { primary, secondary }, + const { primary, secondary } = getActionBarActions( + menu.getActions(options?.menuOptions), options?.toolbarOptions?.primaryGroup, options?.toolbarOptions?.shouldInlineSubmenu, options?.toolbarOptions?.useSeparatorsInPrimaryActions ); container.classList.toggle('has-no-actions', primary.length === 0 && secondary.length === 0); diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 58f87feeee3e8..0f807112d8c72 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -226,7 +226,10 @@ export class MenuId { static readonly ChatInput = new MenuId('ChatInput'); static readonly ChatInputSide = new MenuId('ChatInputSide'); static readonly ChatEditingWidgetToolbar = new MenuId('ChatEditingWidgetToolbar'); + static readonly ChatEditingEditorContent = new MenuId('ChatEditingEditorContent'); + static readonly ChatEditingEditorHunk = new MenuId('ChatEditingEditorHunk'); static readonly ChatEditingWidgetModifiedFilesToolbar = new MenuId('ChatEditingWidgetModifiedFilesToolbar'); + static readonly ChatInputResourceAttachmentContext = new MenuId('ChatInputResourceAttachmentContext'); static readonly ChatInlineResourceAnchorContext = new MenuId('ChatInlineResourceAnchorContext'); static readonly ChatInlineSymbolAnchorContext = new MenuId('ChatInlineSymbolAnchorContext'); static readonly ChatEditingCodeBlockContext = new MenuId('ChatEditingCodeBlockContext'); diff --git a/src/vs/platform/configuration/common/configuration.ts b/src/vs/platform/configuration/common/configuration.ts index 770ce0f6426b2..a93872f4cd77e 100644 --- a/src/vs/platform/configuration/common/configuration.ts +++ b/src/vs/platform/configuration/common/configuration.ts @@ -279,7 +279,9 @@ function doRemoveFromValueTree(valueTree: any, segments: string[]): void { /** * A helper function to get the configuration value with a specific settings path (e.g. config.some.setting) */ -export function getConfigurationValue(config: any, settingPath: string, defaultValue?: T): T { +export function getConfigurationValue(config: any, settingPath: string): T | undefined; +export function getConfigurationValue(config: any, settingPath: string, defaultValue: T): T; +export function getConfigurationValue(config: any, settingPath: string, defaultValue?: T): T | undefined { function accessSetting(config: any, path: string[]): any { let current = config; for (const component of path) { diff --git a/src/vs/platform/contextkey/browser/contextKeyService.ts b/src/vs/platform/contextkey/browser/contextKeyService.ts index f125af404f29b..bb3c267c0bcf1 100644 --- a/src/vs/platform/contextkey/browser/contextKeyService.ts +++ b/src/vs/platform/contextkey/browser/contextKeyService.ts @@ -518,8 +518,8 @@ class OverlayContext implements IContext { constructor(private parent: IContext, private overlay: ReadonlyMap) { } - getValue(key: string): T | undefined { - return this.overlay.has(key) ? this.overlay.get(key) : this.parent.getValue(key); + getValue(key: string): T | undefined { + return this.overlay.has(key) ? this.overlay.get(key) : this.parent.getValue(key); } } diff --git a/src/vs/platform/contextview/browser/contextMenuService.ts b/src/vs/platform/contextview/browser/contextMenuService.ts index b6b7bea2bac79..30577f4459375 100644 --- a/src/vs/platform/contextview/browser/contextMenuService.ts +++ b/src/vs/platform/contextview/browser/contextMenuService.ts @@ -8,7 +8,7 @@ import { ModifierKeyEmitter } from '../../../base/browser/dom.js'; import { IAction, Separator } from '../../../base/common/actions.js'; import { Emitter } from '../../../base/common/event.js'; import { Disposable } from '../../../base/common/lifecycle.js'; -import { createAndFillInContextMenuActions } from '../../actions/browser/menuEntryActionViewItem.js'; +import { getFlatContextMenuActions } from '../../actions/browser/menuEntryActionViewItem.js'; import { IMenuService, MenuId } from '../../actions/common/actions.js'; import { IContextKeyService } from '../../contextkey/common/contextkey.js'; import { IKeybindingService } from '../../keybinding/common/keybinding.js'; @@ -84,10 +84,10 @@ export namespace ContextMenuMenuDelegate { return { ...delegate, getActions: () => { - const target: IAction[] = []; + let target: IAction[] = []; if (menuId) { const menu = menuService.getMenuActions(menuId, contextKeyService ?? globalContextKeyService, menuActionOptions); - createAndFillInContextMenuActions(menu, target); + target = getFlatContextMenuActions(menu); } if (!delegate.getActions) { return target; diff --git a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index 1f6ef7a53f18a..28282587c55cd 100644 --- a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -15,7 +15,7 @@ import { URI } from '../../../base/common/uri.js'; import { IHeaders, IRequestContext, IRequestOptions, isOfflineError } from '../../../base/parts/request/common/request.js'; import { IConfigurationService } from '../../configuration/common/configuration.js'; import { IEnvironmentService } from '../../environment/common/environment.js'; -import { getTargetPlatform, IExtensionGalleryService, IExtensionIdentifier, IExtensionInfo, IGalleryExtension, IGalleryExtensionAsset, IGalleryExtensionAssets, IGalleryExtensionVersion, InstallOperation, IQueryOptions, IExtensionsControlManifest, isNotWebExtensionInWebTargetPlatform, isTargetPlatformCompatible, ITranslation, SortBy, SortOrder, StatisticType, toTargetPlatform, WEB_EXTENSION_TAG, IExtensionQueryOptions, IDeprecationInfo, ISearchPrefferedResults, ExtensionGalleryError, ExtensionGalleryErrorCode, IProductVersion } from './extensionManagement.js'; +import { getTargetPlatform, IExtensionGalleryService, IExtensionIdentifier, IExtensionInfo, IGalleryExtension, IGalleryExtensionAsset, IGalleryExtensionAssets, IGalleryExtensionVersion, InstallOperation, IQueryOptions, IExtensionsControlManifest, isNotWebExtensionInWebTargetPlatform, isTargetPlatformCompatible, ITranslation, SortBy, SortOrder, StatisticType, toTargetPlatform, WEB_EXTENSION_TAG, IExtensionQueryOptions, IDeprecationInfo, ISearchPrefferedResults, ExtensionGalleryError, ExtensionGalleryErrorCode, IProductVersion, UseUnpkgResourceApi } from './extensionManagement.js'; import { adoptToGalleryExtensionId, areSameExtensions, getGalleryExtensionId, getGalleryExtensionTelemetryData } from './extensionManagementUtil.js'; import { IExtensionManifest, TargetPlatform } from '../../extensions/common/extensions.js'; import { areApiProposalsCompatible, isEngineValid } from '../../extensions/common/extensionValidator.js'; @@ -27,6 +27,7 @@ import { resolveMarketplaceHeaders } from '../../externalServices/common/marketp import { IStorageService } from '../../storage/common/storage.js'; import { ITelemetryService } from '../../telemetry/common/telemetry.js'; import { StopWatch } from '../../../base/common/stopwatch.js'; +import { format2 } from '../../../base/common/strings.js'; const CURRENT_TARGET_PLATFORM = isWeb ? TargetPlatform.WEB : getTargetPlatform(platform, arch); const ACTIVITY_HEADER_NAME = 'X-Market-Search-Activity-Id'; @@ -604,6 +605,7 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi private readonly extensionsGalleryUrl: string | undefined; private readonly extensionsGallerySearchUrl: string | undefined; private readonly extensionsControlUrl: string | undefined; + private readonly extensionUrlTemplate: string | undefined; private readonly commonHeadersPromise: Promise; private readonly extensionsEnabledWithApiProposalVersion: string[]; @@ -623,6 +625,7 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi this.extensionsGalleryUrl = isPPEEnabled ? config.servicePPEUrl : config?.serviceUrl; this.extensionsGallerySearchUrl = isPPEEnabled ? undefined : config?.searchUrl; this.extensionsControlUrl = config?.controlUrl; + this.extensionUrlTemplate = config?.extensionUrlTemplate; this.extensionsEnabledWithApiProposalVersion = productService.extensionsEnabledWithApiProposalVersion?.map(id => id.toLowerCase()) ?? []; this.commonHeadersPromise = resolveMarketplaceHeaders( productService.version, @@ -645,9 +648,17 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi getExtensions(extensionInfos: ReadonlyArray, token: CancellationToken): Promise; getExtensions(extensionInfos: ReadonlyArray, options: IExtensionQueryOptions, token: CancellationToken): Promise; async getExtensions(extensionInfos: ReadonlyArray, arg1: any, arg2?: any): Promise { + if (!this.isEnabled()) { + throw new Error('No extension gallery service configured.'); + } + const options = CancellationToken.isCancellationToken(arg1) ? {} : arg1 as IExtensionQueryOptions; const token = CancellationToken.isCancellationToken(arg1) ? arg1 : arg2 as CancellationToken; - const result = await this.doGetExtensions(extensionInfos, options, token); + + const useResourceApi = options.preferResourceApi && (this.configurationService.getValue(UseUnpkgResourceApi) ?? false); + const result = useResourceApi + ? await this.getExtensionsUsingResourceApi(extensionInfos, options, token) + : await this.doGetExtensions(extensionInfos, options, token); const uuids = result.map(r => r.identifier.uuid); const extensionInfosByName: IExtensionInfo[] = []; @@ -719,6 +730,72 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi return extensions; } + private async getExtensionsUsingResourceApi(extensionInfos: ReadonlyArray, options: IExtensionQueryOptions, token: CancellationToken): Promise { + + const toQuery: IExtensionInfo[] = []; + const result: IGalleryExtension[] = []; + + await Promise.allSettled(extensionInfos.map(async extensionInfo => { + if (extensionInfo.version) { + toQuery.push(extensionInfo); + return; + } + + try { + const rawGalleryExtension = await this.getLatestRawGalleryExtension(extensionInfo.id, token); + if (!rawGalleryExtension) { + toQuery.push(extensionInfo); + return; + } + + const extension = await this.toGalleryExtensionWithCriteria(rawGalleryExtension, { + targetPlatform: options.targetPlatform ?? CURRENT_TARGET_PLATFORM, + includePreRelease: !!extensionInfo.preRelease, + compatible: !!options.compatible, + productVersion: options.productVersion ?? { + version: this.productService.version, + date: this.productService.date + } + }); + + if (extension) { + result.push(extension); + } + + // report telemetry + else { + toQuery.push(extensionInfo); + this.telemetryService.publicLog2< + { + extension: string; + preRelease: boolean; + compatible: boolean; + }, + { + owner: 'sandy081'; + comment: 'Report the fallback to the Marketplace query for fetching extensions'; + extension: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Extension id' }; + preRelease: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Get pre-release version' }; + compatible: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Get compatible version' }; + }>('galleryService:fallbacktoquery', { + extension: extensionInfo.id, + preRelease: !!extensionInfo.preRelease, + compatible: !!options.compatible + }); + } + } catch (error) { + // Skip if there is an error while getting the latest version + this.logService.error(`Error while getting the latest version for the extension ${extensionInfo.id}.`, getErrorMessage(error)); + toQuery.push(extensionInfo); + } + })); + + const extensions = await this.doGetExtensions(toQuery, options, token); + result.push(...extensions); + + return result; + } + async getCompatibleExtension(extension: IGalleryExtension, includePreRelease: boolean, targetPlatform: TargetPlatform, productVersion: IProductVersion = { version: this.productService.version, date: this.productService.date }): Promise { if (isNotWebExtensionInWebTargetPlatform(extension.allTargetPlatforms, targetPlatform)) { return null; @@ -832,10 +909,6 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi } query = query.withSortBy(SortBy.NoneOrRelevance); - } else if (options.ids) { - query = query.withFilter(FilterType.ExtensionId, ...options.ids); - } else if (options.names) { - query = query.withFilter(FilterType.ExtensionName, ...options.names); } else { query = query.withSortBy(SortBy.InstallCount); } @@ -1093,6 +1166,80 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi } } + private async getLatestRawGalleryExtension(extensionId: string, token: CancellationToken): Promise { + let errorCode: string | undefined; + const stopWatch = new StopWatch(); + + try { + const [publisher, name] = extensionId.split('.'); + if (!publisher || !name) { + errorCode = 'InvalidExtensionId'; + return undefined; + } + const uri = URI.parse(format2(this.extensionUrlTemplate!, { publisher, name })); + const commonHeaders = await this.commonHeadersPromise; + const headers = { + ...commonHeaders, + 'Content-Type': 'application/json', + 'Accept': 'application/json;api-version=3.0-preview.1', + 'Accept-Encoding': 'gzip', + }; + + const context = await this.requestService.request({ + type: 'GET', + url: uri.toString(true), + headers, + timeout: 10000 /*10s*/ + }, token); + + if (context.res.statusCode && context.res.statusCode !== 200) { + errorCode = `GalleryServiceError:` + context.res.statusCode; + this.logService.warn('Error getting latest version of the extension', extensionId, context.res.statusCode); + return undefined; + } + + const result = await asJson(context); + if (result) { + return result; + } + + errorCode = 'NoData'; + this.logService.warn('Error getting latest version of the extension', extensionId, errorCode); + + } + + catch (error) { + if (isCancellationError(error)) { + errorCode = ExtensionGalleryErrorCode.Cancelled; + } else { + const errorMessage = getErrorMessage(error); + errorCode = isOfflineError(error) + ? ExtensionGalleryErrorCode.Offline + : errorMessage.startsWith('XHR timeout') + ? ExtensionGalleryErrorCode.Timeout + : ExtensionGalleryErrorCode.Failed; + } + } + + finally { + type GalleryServiceGetLatestEventClassification = { + owner: 'sandy081'; + comment: 'Report the query to the the Marketplace for fetching latest version of an extension'; + extension: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The identifier of the extension' }; + duration: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; isMeasurement: true; comment: 'Duration in ms for the query' }; + errorCode?: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The error code in case of error' }; + }; + type GalleryServiceGetLatestEvent = { + extension: string; + duration: number; + errorCode?: string; + }; + this.telemetryService.publicLog2('galleryService:getLatest', { extension: extensionId, duration: stopWatch.elapsed(), errorCode }); + } + + return undefined; + } + async reportStatistic(publisher: string, name: string, version: string, type: StatisticType): Promise { if (!this.isEnabled()) { return undefined; diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index ea7b30d9422d1..42e8875e7ccbb 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -11,6 +11,7 @@ import { Platform } from '../../../base/common/platform.js'; import { URI } from '../../../base/common/uri.js'; import { localize2 } from '../../../nls.js'; import { ExtensionType, IExtension, IExtensionManifest, TargetPlatform } from '../../extensions/common/extensions.js'; +import { IFileService } from '../../files/common/files.js'; import { createDecorator } from '../../instantiation/common/instantiation.js'; export const EXTENSION_IDENTIFIER_PATTERN = '^([a-z0-9A-Z][a-z0-9-A-Z]*)\\.([a-z0-9A-Z][a-z0-9-A-Z]*)$'; @@ -257,6 +258,7 @@ export type Metadata = Partial; export interface ILocalExtension extends IExtension { @@ -271,6 +273,7 @@ export interface ILocalExtension extends IExtension { updated: boolean; pinned: boolean; source: InstallSource; + size: number; } export const enum SortBy { @@ -292,8 +295,7 @@ export const enum SortOrder { export interface IQueryOptions { text?: string; - ids?: string[]; - names?: string[]; + exclude?: string[]; pageSize?: number; sortBy?: SortBy; sortOrder?: SortOrder; @@ -354,6 +356,7 @@ export interface IExtensionQueryOptions { compatible?: boolean; queryAllVersions?: boolean; source?: string; + preferResourceApi?: boolean; } export const IExtensionGalleryService = createDecorator('extensionGalleryService'); @@ -624,5 +627,15 @@ export interface IExtensionTipsService { getOtherExecutableBasedTips(): Promise; } +export async function computeSize(location: URI, fileService: IFileService): Promise { + const stat = await fileService.resolve(location); + if (stat.children) { + const sizes = await Promise.all(stat.children.map(c => computeSize(c.resource, fileService))); + return sizes.reduce((r, s) => r + s, 0); + } + return stat.size ?? 0; +} + export const ExtensionsLocalizedLabel = localize2('extensions', "Extensions"); export const PreferencesLocalizedLabel = localize2('preferences', 'Preferences'); +export const UseUnpkgResourceApi = 'extensions.gallery.useUnpkgResourceApi'; diff --git a/src/vs/platform/extensionManagement/common/extensionsScannerService.ts b/src/vs/platform/extensionManagement/common/extensionsScannerService.ts index 8f238a8402973..0b2e581229429 100644 --- a/src/vs/platform/extensionManagement/common/extensionsScannerService.ts +++ b/src/vs/platform/extensionManagement/common/extensionsScannerService.ts @@ -652,6 +652,9 @@ class ExtensionsScanner extends Disposable { manifest.publisher = UNDEFINED_PUBLISHER; } metadata = metadata ?? manifest.__metadata; + if (metadata && !metadata?.size && manifest.__metadata?.size) { + metadata.size = manifest.__metadata?.size; + } delete manifest.__metadata; const id = getGalleryExtensionId(manifest.publisher, manifest.name); const identifier = metadata?.id ? { id, uuid: metadata.id } : { id }; diff --git a/src/vs/platform/extensionManagement/node/extensionDownloader.ts b/src/vs/platform/extensionManagement/node/extensionDownloader.ts index 850dba7113dcb..436dc936f8865 100644 --- a/src/vs/platform/extensionManagement/node/extensionDownloader.ts +++ b/src/vs/platform/extensionManagement/node/extensionDownloader.ts @@ -20,9 +20,10 @@ import { ExtensionKey, groupByExtension } from '../common/extensionManagementUti import { fromExtractError } from './extensionManagementUtil.js'; import { IExtensionSignatureVerificationService } from './extensionSignatureVerificationService.js'; import { TargetPlatform } from '../../extensions/common/extensions.js'; -import { IFileService, IFileStatWithMetadata } from '../../files/common/files.js'; +import { FileOperationResult, IFileService, IFileStatWithMetadata, toFileOperationResult } from '../../files/common/files.js'; import { ILogService } from '../../log/common/log.js'; import { ITelemetryService } from '../../telemetry/common/telemetry.js'; +import { IUriIdentityService } from '../../uriIdentity/common/uriIdentity.js'; type RetryDownloadClassification = { owner: 'sandy081'; @@ -40,6 +41,7 @@ export class ExtensionsDownloader extends Disposable { private static readonly SignatureArchiveExtension = '.sigzip'; readonly extensionsDownloadDir: URI; + private readonly extensionsTrashDir: URI; private readonly cache: number; private readonly cleanUpPromise: Promise; @@ -49,10 +51,12 @@ export class ExtensionsDownloader extends Disposable { @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, @IExtensionSignatureVerificationService private readonly extensionSignatureVerificationService: IExtensionSignatureVerificationService, @ITelemetryService private readonly telemetryService: ITelemetryService, + @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, @ILogService private readonly logService: ILogService, ) { super(); this.extensionsDownloadDir = environmentService.extensionsDownloadLocation; + this.extensionsTrashDir = uriIdentityService.extUri.joinPath(environmentService.extensionsDownloadLocation, `.trash`); this.cache = 20; // Cache 20 downloaded VSIX files this.cleanUpPromise = this.cleanUp(); } @@ -132,7 +136,7 @@ export class ExtensionsDownloader extends Disposable { private async downloadSignatureArchive(extension: IGalleryExtension): Promise { try { - const location = joinPath(this.extensionsDownloadDir, `.${generateUuid()}`); + const location = joinPath(this.extensionsDownloadDir, `${this.getName(extension)}${ExtensionsDownloader.SignatureArchiveExtension}`); const attempts = await this.doDownload(extension, 'sigzip', async () => { await this.extensionGalleryService.downloadSignatureArchive(extension, location); try { @@ -224,7 +228,12 @@ export class ExtensionsDownloader extends Disposable { async delete(location: URI): Promise { await this.cleanUpPromise; - await this.fileService.del(location); + const trashRelativePath = this.uriIdentityService.extUri.relativePath(this.extensionsDownloadDir, location); + if (trashRelativePath) { + await this.fileService.move(location, this.uriIdentityService.extUri.joinPath(this.extensionsTrashDir, trashRelativePath), true); + } else { + await this.fileService.del(location); + } } private async cleanUp(): Promise { @@ -233,6 +242,15 @@ export class ExtensionsDownloader extends Disposable { this.logService.trace('Extension VSIX downloads cache dir does not exist'); return; } + + try { + await this.fileService.del(this.extensionsTrashDir, { recursive: true }); + } catch (error) { + if (toFileOperationResult(error) !== FileOperationResult.FILE_NOT_FOUND) { + this.logService.error(error); + } + } + const folderStat = await this.fileService.resolve(this.extensionsDownloadDir, { resolveMetadata: true }); if (folderStat.children) { const toDelete: URI[] = []; @@ -272,7 +290,7 @@ export class ExtensionsDownloader extends Disposable { } private getName(extension: IGalleryExtension): string { - return this.cache ? ExtensionKey.create(extension).toString().toLowerCase() : generateUuid(); + return ExtensionKey.create(extension).toString().toLowerCase(); } } diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index 680e0dd15be8d..585272ac91d1b 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -33,6 +33,7 @@ import { IProductVersion, EXTENSION_INSTALL_CLIENT_TARGET_PLATFORM_CONTEXT, ExtensionSignatureVerificationCode, + computeSize, } from '../common/extensionManagement.js'; import { areSameExtensions, computeTargetPlatform, ExtensionKey, getGalleryExtensionId, groupByExtension } from '../common/extensionManagementUtil.js'; import { IExtensionsProfileScannerService, IScannedProfileExtension } from '../common/extensionsProfileScannerService.js'; @@ -331,6 +332,16 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi }, false, token); + + if (verificationStatus !== ExtensionSignatureVerificationCode.Success && this.environmentService.isBuilt) { + try { + await this.extensionsDownloader.delete(location); + } catch (e) { + /* Ignore */ + this.logService.warn(`Error while deleting the downloaded file`, location.toString(), getErrorMessage(e)); + } + } + return { local, verificationStatus }; } catch (error) { try { @@ -351,6 +362,13 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi const { location, verificationStatus } = await this.extensionsDownloader.download(extension, operation, verifySignature, clientTargetPlatform); if (verificationStatus !== ExtensionSignatureVerificationCode.Success && verifySignature && this.environmentService.isBuilt && !isLinux) { + try { + await this.extensionsDownloader.delete(location); + } catch (e) { + /* Ignore */ + this.logService.warn(`Error while deleting the downloaded file`, location.toString(), getErrorMessage(e)); + } + if (!extension.isSigned) { throw new ExtensionManagementError(nls.localize('not signed', "Extension is not signed."), ExtensionManagementErrorCode.PackageNotSigned); } @@ -391,7 +409,7 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi pinned: options.installGivenVersion ? true : !!options.pinned, source: 'vsix', }, - options.keepExisting ?? true, + isBoolean(options.keepExisting) ? !options.keepExisting : true, token); return { local }; } @@ -563,6 +581,7 @@ export class ExtensionsScanner extends Disposable { async cleanUp(): Promise { await this.removeTemporarilyDeletedFolders(); await this.removeUninstalledExtensions(); + await this.initializeMetadata(); } async scanExtensions(type: ExtensionType | null, profileLocation: URI, productVersion: IProductVersion): Promise { @@ -649,6 +668,13 @@ export class ExtensionsScanner extends Disposable { throw fromExtractError(e); } + try { + metadata.size = await computeSize(tempLocation, this.fileService); + } catch (error) { + // Log & ignore + this.logService.warn(`Error while getting the size of the extracted extension : ${tempLocation.fsPath}`, getErrorMessage(error)); + } + try { await this.extensionsScannerService.updateMetadata(tempLocation, metadata); } catch (error) { @@ -875,10 +901,22 @@ export class ExtensionsScanner extends Disposable { updated: !!extension.metadata?.updated, pinned: !!extension.metadata?.pinned, isWorkspaceScoped: false, - source: extension.metadata?.source ?? (extension.identifier.uuid ? 'gallery' : 'vsix') + source: extension.metadata?.source ?? (extension.identifier.uuid ? 'gallery' : 'vsix'), + size: extension.metadata?.size ?? 0, }; } + private async initializeMetadata(): Promise { + const extensions = await this.extensionsScannerService.scanUserExtensions({ includeInvalid: true }); + await Promise.all(extensions.map(async extension => { + // set size if not set before + if (!extension.metadata?.size && extension.metadata?.source !== 'resource') { + const size = await computeSize(extension.location, this.fileService); + await this.extensionsScannerService.updateMetadata(extension.location, { size }); + } + })); + } + private async removeUninstalledExtensions(): Promise { const uninstalled = await this.getUninstalledExtensions(); if (Object.keys(uninstalled).length === 0) { diff --git a/src/vs/platform/extensionManagement/test/node/extensionDownloader.test.ts b/src/vs/platform/extensionManagement/test/node/extensionDownloader.test.ts index aa437e000a63a..366e13804d118 100644 --- a/src/vs/platform/extensionManagement/test/node/extensionDownloader.test.ts +++ b/src/vs/platform/extensionManagement/test/node/extensionDownloader.test.ts @@ -22,6 +22,8 @@ import { FileService } from '../../../files/common/fileService.js'; import { InMemoryFileSystemProvider } from '../../../files/common/inMemoryFilesystemProvider.js'; import { TestInstantiationService } from '../../../instantiation/test/common/instantiationServiceMock.js'; import { ILogService, NullLogService } from '../../../log/common/log.js'; +import { IUriIdentityService } from '../../../uriIdentity/common/uriIdentity.js'; +import { UriIdentityService } from '../../../uriIdentity/common/uriIdentityService.js'; const ROOT = URI.file('tests').with({ scheme: 'vscode-tests' }); @@ -67,6 +69,7 @@ suite('ExtensionDownloader Tests', () => { instantiationService.stub(ILogService, logService); instantiationService.stub(IFileService, fileService); instantiationService.stub(ILogService, logService); + instantiationService.stub(IUriIdentityService, disposables.add(new UriIdentityService(fileService))); instantiationService.stub(INativeEnvironmentService, { extensionsDownloadLocation: joinPath(ROOT, 'CachedExtensionVSIXs') }); instantiationService.stub(IExtensionGalleryService, { async download(extension, location, operation) { diff --git a/src/vs/platform/extensions/common/extensionValidator.ts b/src/vs/platform/extensions/common/extensionValidator.ts index 87923dc1cb003..87a9288104de8 100644 --- a/src/vs/platform/extensions/common/extensionValidator.ts +++ b/src/vs/platform/extensions/common/extensionValidator.ts @@ -361,14 +361,11 @@ export function areApiProposalsCompatible(apiProposals: string[], arg1?: any): b const incompatibleProposals: string[] = []; const parsedProposals = parseApiProposals(apiProposals); for (const { proposalName, version } of parsedProposals) { - const existingProposal = productApiProposals[proposalName]; - if (!existingProposal) { - continue; - } if (!version) { continue; } - if (existingProposal.version !== version) { + const existingProposal = productApiProposals[proposalName]; + if (existingProposal?.version !== version) { incompatibleProposals.push(proposalName); } } diff --git a/src/vs/platform/extensions/common/extensionsApiProposals.ts b/src/vs/platform/extensions/common/extensionsApiProposals.ts index 2296726d8e0e5..b8d13ce3f0030 100644 --- a/src/vs/platform/extensions/common/extensionsApiProposals.ts +++ b/src/vs/platform/extensions/common/extensionsApiProposals.ts @@ -14,9 +14,7 @@ const _allApiProposals = { }, aiTextSearchProvider: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.aiTextSearchProvider.d.ts', - }, - aiTextSearchProviderNew: { - proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.aiTextSearchProviderNew.d.ts', + version: 2 }, attributableCoverage: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.attributableCoverage.d.ts', @@ -30,6 +28,9 @@ const _allApiProposals = { canonicalUriProvider: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.canonicalUriProvider.d.ts', }, + chatEditing: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatEditing.d.ts', + }, chatParticipantAdditions: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts', }, @@ -100,9 +101,6 @@ const _allApiProposals = { contribEditorContentMenu: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribEditorContentMenu.d.ts', }, - contribIssueReporter: { - proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribIssueReporter.d.ts', - }, contribLabelFormatterWorkspaceTooltip: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribLabelFormatterWorkspaceTooltip.d.ts', }, @@ -200,20 +198,18 @@ const _allApiProposals = { fileSearchProvider: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.fileSearchProvider.d.ts', }, - fileSearchProviderNew: { - proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.fileSearchProviderNew.d.ts', + fileSearchProvider2: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.fileSearchProvider2.d.ts', }, findFiles2: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.findFiles2.d.ts', - }, - findFiles2New: { - proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.findFiles2New.d.ts', + version: 2 }, findTextInFiles: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.findTextInFiles.d.ts', }, - findTextInFilesNew: { - proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.findTextInFilesNew.d.ts', + findTextInFiles2: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.findTextInFiles2.d.ts', }, fsChunks: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.fsChunks.d.ts', @@ -248,6 +244,9 @@ const _allApiProposals = { multiDocumentHighlightProvider: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.multiDocumentHighlightProvider.d.ts', }, + nativeWindowHandle: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.nativeWindowHandle.d.ts', + }, newSymbolNamesProvider: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.newSymbolNamesProvider.d.ts', }, @@ -365,14 +364,14 @@ const _allApiProposals = { testRelatedCode: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.testRelatedCode.d.ts', }, - textSearchCompleteNew: { - proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.textSearchCompleteNew.d.ts', + textSearchComplete2: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.textSearchComplete2.d.ts', }, textSearchProvider: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.textSearchProvider.d.ts', }, - textSearchProviderNew: { - proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.textSearchProviderNew.d.ts', + textSearchProvider2: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.textSearchProvider2.d.ts', }, timeline: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.timeline.d.ts', @@ -395,6 +394,9 @@ const _allApiProposals = { tunnels: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.tunnels.d.ts', }, + valueSelectionInQuickPick: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.valueSelectionInQuickPick.d.ts', + }, workspaceTrust: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.workspaceTrust.d.ts', } diff --git a/src/vs/platform/extensions/test/common/extensionValidator.test.ts b/src/vs/platform/extensions/test/common/extensionValidator.test.ts index 82c3cf0bf8a25..3b42e5fc06bb7 100644 --- a/src/vs/platform/extensions/test/common/extensionValidator.test.ts +++ b/src/vs/platform/extensions/test/common/extensionValidator.test.ts @@ -436,6 +436,7 @@ suite('Extension Version Validator', () => { assert.strictEqual(areApiProposalsCompatible(['proposal1', 'proposal2'], {}), true); assert.strictEqual(areApiProposalsCompatible(['proposal1', 'proposal2'], { 'proposal1': { proposal: '' } }), true); + assert.strictEqual(areApiProposalsCompatible(['proposal2@1'], { 'proposal1': { proposal: '' } }), false); assert.strictEqual(areApiProposalsCompatible(['proposal1@1'], { 'proposal1': { proposal: '', version: 2 } }), false); assert.strictEqual(areApiProposalsCompatible(['proposal1@1'], { 'proposal1': { proposal: '' } }), false); }); diff --git a/src/vs/platform/hover/browser/hover.ts b/src/vs/platform/hover/browser/hover.ts index 779a4598fbcb6..64fe7c21defe6 100644 --- a/src/vs/platform/hover/browser/hover.ts +++ b/src/vs/platform/hover/browser/hover.ts @@ -63,7 +63,11 @@ export class WorkbenchHoverDelegate extends Disposable implements IHoverDelegate })); } - const id = isHTMLElement(options.content) ? undefined : options.content.toString(); + const id = isHTMLElement(options.content) + ? undefined + : typeof options.content === 'string' + ? options.content.toString() + : options.content.value; return this.hoverService.showHover({ ...options, diff --git a/src/vs/platform/hover/test/browser/nullHoverService.ts b/src/vs/platform/hover/test/browser/nullHoverService.ts index a4266ef7c9309..4644330752bf6 100644 --- a/src/vs/platform/hover/test/browser/nullHoverService.ts +++ b/src/vs/platform/hover/test/browser/nullHoverService.ts @@ -10,6 +10,9 @@ export const NullHoverService: IHoverService = { _serviceBrand: undefined, hideHover: () => undefined, showHover: () => undefined, + showDelayedHover: () => undefined, + setupDelayedHover: () => Disposable.None, + setupDelayedHoverAtMouse: () => Disposable.None, setupManagedHover: () => Disposable.None as any, showAndFocusLastHover: () => undefined, showManagedHover: () => undefined diff --git a/src/vs/platform/issue/common/issueReporterUtil.ts b/src/vs/platform/issue/common/issueReporterUtil.ts deleted file mode 100644 index 3a2266e308e43..0000000000000 --- a/src/vs/platform/issue/common/issueReporterUtil.ts +++ /dev/null @@ -1,26 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { rtrim } from '../../../base/common/strings.js'; - -export function normalizeGitHubUrl(url: string): string { - // If the url has a .git suffix, remove it - if (url.endsWith('.git')) { - url = url.substr(0, url.length - 4); - } - - // Remove trailing slash - url = rtrim(url, '/'); - - if (url.endsWith('/new')) { - url = rtrim(url, '/new'); - } - - if (url.endsWith('/issues')) { - url = rtrim(url, '/issues'); - } - - return url; -} diff --git a/src/vs/platform/issue/electron-main/issueMainService.ts b/src/vs/platform/issue/electron-main/issueMainService.ts deleted file mode 100644 index 5be83d34d2234..0000000000000 --- a/src/vs/platform/issue/electron-main/issueMainService.ts +++ /dev/null @@ -1,305 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { BrowserWindow, BrowserWindowConstructorOptions, Display, screen } from 'electron'; -import { arch, release, type } from 'os'; -import { raceTimeout } from '../../../base/common/async.js'; -import { CancellationTokenSource } from '../../../base/common/cancellation.js'; -import { DisposableStore } from '../../../base/common/lifecycle.js'; -import { FileAccess } from '../../../base/common/network.js'; -import { IProcessEnvironment, isMacintosh } from '../../../base/common/platform.js'; -import { validatedIpcMain } from '../../../base/parts/ipc/electron-main/ipcMain.js'; -import { getNLSLanguage, getNLSMessages, localize } from '../../../nls.js'; -import { IDialogMainService } from '../../dialogs/electron-main/dialogMainService.js'; -import { IEnvironmentMainService } from '../../environment/electron-main/environmentMainService.js'; -import { IIssueMainService, OldIssueReporterData, OldIssueReporterWindowConfiguration } from '../common/issue.js'; -import { ILogService } from '../../log/common/log.js'; -import { INativeHostMainService } from '../../native/electron-main/nativeHostMainService.js'; -import product from '../../product/common/product.js'; -import { IIPCObjectUrl, IProtocolMainService } from '../../protocol/electron-main/protocol.js'; -import { zoomLevelToZoomFactor } from '../../window/common/window.js'; -import { ICodeWindow, IWindowState } from '../../window/electron-main/window.js'; -import { IWindowsMainService } from '../../windows/electron-main/windows.js'; -import { ICSSDevelopmentService } from '../../cssDev/node/cssDevService.js'; - -interface IBrowserWindowOptions { - backgroundColor: string | undefined; - title: string; - zoomLevel: number; - alwaysOnTop: boolean; -} - -type IStrictWindowState = Required>; - -export class IssueMainService implements IIssueMainService { - - declare readonly _serviceBrand: undefined; - - private static readonly DEFAULT_BACKGROUND_COLOR = '#1E1E1E'; - - private issueReporterWindow: BrowserWindow | null = null; - private issueReporterParentWindow: BrowserWindow | null = null; - - constructor( - private userEnv: IProcessEnvironment, - @IEnvironmentMainService private readonly environmentMainService: IEnvironmentMainService, - @ILogService private readonly logService: ILogService, - @IDialogMainService private readonly dialogMainService: IDialogMainService, - @INativeHostMainService private readonly nativeHostMainService: INativeHostMainService, - @IProtocolMainService private readonly protocolMainService: IProtocolMainService, - @IWindowsMainService private readonly windowsMainService: IWindowsMainService, - @ICSSDevelopmentService private readonly cssDevelopmentService: ICSSDevelopmentService, - ) { } - - //#region Used by renderer - - async openReporter(data: OldIssueReporterData): Promise { - if (!this.issueReporterWindow) { - this.issueReporterParentWindow = BrowserWindow.getFocusedWindow(); - if (this.issueReporterParentWindow) { - const issueReporterDisposables = new DisposableStore(); - - const issueReporterWindowConfigUrl = issueReporterDisposables.add(this.protocolMainService.createIPCObjectUrl()); - const position = this.getWindowPosition(this.issueReporterParentWindow, 700, 800); - - this.issueReporterWindow = this.createBrowserWindow(position, issueReporterWindowConfigUrl, { - backgroundColor: data.styles.backgroundColor, - title: localize('issueReporter', "Issue Reporter"), - zoomLevel: data.zoomLevel, - alwaysOnTop: false - }, 'issue-reporter'); - - // Store into config object URL - issueReporterWindowConfigUrl.update({ - appRoot: this.environmentMainService.appRoot, - windowId: this.issueReporterWindow.id, - userEnv: this.userEnv, - data, - disableExtensions: !!this.environmentMainService.disableExtensions, - os: { - type: type(), - arch: arch(), - release: release(), - }, - product, - nls: { - messages: getNLSMessages(), - language: getNLSLanguage() - }, - cssModules: this.cssDevelopmentService.isEnabled ? await this.cssDevelopmentService.getCssModules() : undefined - }); - - this.issueReporterWindow.loadURL( - FileAccess.asBrowserUri(`vs/workbench/contrib/issue/electron-sandbox/issueReporter${this.environmentMainService.isBuilt ? '' : '-dev'}.html`).toString(true) - ); - - this.issueReporterWindow.on('close', () => { - this.issueReporterWindow = null; - issueReporterDisposables.dispose(); - }); - - this.issueReporterParentWindow.on('closed', () => { - if (this.issueReporterWindow) { - this.issueReporterWindow.close(); - this.issueReporterWindow = null; - issueReporterDisposables.dispose(); - } - }); - } - } - - else if (this.issueReporterWindow) { - this.focusWindow(this.issueReporterWindow); - } - } - - //#endregion - - //#region used by issue reporter window - async $reloadWithExtensionsDisabled(): Promise { - if (this.issueReporterParentWindow) { - try { - await this.nativeHostMainService.reload(this.issueReporterParentWindow.id, { disableExtensions: true }); - } catch (error) { - this.logService.error(error); - } - } - } - - async $showConfirmCloseDialog(): Promise { - if (this.issueReporterWindow) { - const { response } = await this.dialogMainService.showMessageBox({ - type: 'warning', - message: localize('confirmCloseIssueReporter', "Your input will not be saved. Are you sure you want to close this window?"), - buttons: [ - localize({ key: 'yes', comment: ['&& denotes a mnemonic'] }, "&&Yes"), - localize('cancel', "Cancel") - ] - }, this.issueReporterWindow); - - if (response === 0) { - if (this.issueReporterWindow) { - this.issueReporterWindow.destroy(); - this.issueReporterWindow = null; - } - } - } - } - - async $showClipboardDialog(): Promise { - if (this.issueReporterWindow) { - const { response } = await this.dialogMainService.showMessageBox({ - type: 'warning', - message: localize('issueReporterWriteToClipboard', "There is too much data to send to GitHub directly. The data will be copied to the clipboard, please paste it into the GitHub issue page that is opened."), - buttons: [ - localize({ key: 'ok', comment: ['&& denotes a mnemonic'] }, "&&OK"), - localize('cancel', "Cancel") - ] - }, this.issueReporterWindow); - - return response === 0; - } - - return false; - } - - issueReporterWindowCheck(): ICodeWindow { - if (!this.issueReporterParentWindow) { - throw new Error('Issue reporter window not available'); - } - const window = this.windowsMainService.getWindowById(this.issueReporterParentWindow.id); - if (!window) { - throw new Error('Window not found'); - } - return window; - } - - async $sendReporterMenu(extensionId: string, extensionName: string): Promise { - const window = this.issueReporterWindowCheck(); - const replyChannel = `vscode:triggerReporterMenu`; - const cts = new CancellationTokenSource(); - window.sendWhenReady(replyChannel, cts.token, { replyChannel, extensionId, extensionName }); - const result = await raceTimeout(new Promise(resolve => validatedIpcMain.once(`vscode:triggerReporterMenuResponse:${extensionId}`, (_: unknown, data: OldIssueReporterData | undefined) => resolve(data))), 5000, () => { - this.logService.error(`Error: Extension ${extensionId} timed out waiting for menu response`); - cts.cancel(); - }); - return result as OldIssueReporterData | undefined; - } - - async $closeReporter(): Promise { - this.issueReporterWindow?.close(); - } - - //#endregion - - private focusWindow(window: BrowserWindow): void { - if (window.isMinimized()) { - window.restore(); - } - - window.focus(); - } - - private createBrowserWindow(position: IWindowState, ipcObjectUrl: IIPCObjectUrl, options: IBrowserWindowOptions, windowKind: string): BrowserWindow { - const windowOptions: BrowserWindowConstructorOptions & { experimentalDarkMode: boolean } = { - fullscreen: false, - skipTaskbar: false, - resizable: true, - width: position.width, - height: position.height, - minWidth: 300, - minHeight: 200, - x: position.x, - y: position.y, - title: options.title, - backgroundColor: options.backgroundColor || IssueMainService.DEFAULT_BACKGROUND_COLOR, - webPreferences: { - preload: FileAccess.asFileUri('vs/base/parts/sandbox/electron-sandbox/preload.js').fsPath, - additionalArguments: [`--vscode-window-config=${ipcObjectUrl.resource.toString()}`], - v8CacheOptions: this.environmentMainService.useCodeCache ? 'bypassHeatCheck' : 'none', - enableWebSQL: false, - spellcheck: false, - zoomFactor: zoomLevelToZoomFactor(options.zoomLevel), - sandbox: true - }, - alwaysOnTop: options.alwaysOnTop, - experimentalDarkMode: true - }; - const window = new BrowserWindow(windowOptions); - - window.setMenuBarVisibility(false); - - return window; - } - - private getWindowPosition(parentWindow: BrowserWindow, defaultWidth: number, defaultHeight: number): IStrictWindowState { - - // We want the new window to open on the same display that the parent is in - let displayToUse: Display | undefined; - const displays = screen.getAllDisplays(); - - // Single Display - if (displays.length === 1) { - displayToUse = displays[0]; - } - - // Multi Display - else { - - // on mac there is 1 menu per window so we need to use the monitor where the cursor currently is - if (isMacintosh) { - const cursorPoint = screen.getCursorScreenPoint(); - displayToUse = screen.getDisplayNearestPoint(cursorPoint); - } - - // if we have a last active window, use that display for the new window - if (!displayToUse && parentWindow) { - displayToUse = screen.getDisplayMatching(parentWindow.getBounds()); - } - - // fallback to primary display or first display - if (!displayToUse) { - displayToUse = screen.getPrimaryDisplay() || displays[0]; - } - } - - const displayBounds = displayToUse.bounds; - - const state: IStrictWindowState = { - width: defaultWidth, - height: defaultHeight, - x: displayBounds.x + (displayBounds.width / 2) - (defaultWidth / 2), - y: displayBounds.y + (displayBounds.height / 2) - (defaultHeight / 2) - }; - - if (displayBounds.width > 0 && displayBounds.height > 0 /* Linux X11 sessions sometimes report wrong display bounds */) { - if (state.x < displayBounds.x) { - state.x = displayBounds.x; // prevent window from falling out of the screen to the left - } - - if (state.y < displayBounds.y) { - state.y = displayBounds.y; // prevent window from falling out of the screen to the top - } - - if (state.x > (displayBounds.x + displayBounds.width)) { - state.x = displayBounds.x; // prevent window from falling out of the screen to the right - } - - if (state.y > (displayBounds.y + displayBounds.height)) { - state.y = displayBounds.y; // prevent window from falling out of the screen to the bottom - } - - if (state.width > displayBounds.width) { - state.width = displayBounds.width; // prevent window from exceeding display bounds width - } - - if (state.height > displayBounds.height) { - state.height = displayBounds.height; // prevent window from exceeding display bounds height - } - } - - return state; - } -} diff --git a/src/vs/platform/layout/browser/zIndexRegistry.ts b/src/vs/platform/layout/browser/zIndexRegistry.ts index 671eb7754ecf5..3c11201f622dc 100644 --- a/src/vs/platform/layout/browser/zIndexRegistry.ts +++ b/src/vs/platform/layout/browser/zIndexRegistry.ts @@ -3,7 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { clearNode, createCSSRule, createStyleSheet } from '../../../base/browser/dom.js'; +import { clearNode } from '../../../base/browser/dom.js'; +import { createCSSRule, createStyleSheet } from '../../../base/browser/domStylesheets.js'; import { RunOnceScheduler } from '../../../base/common/async.js'; export enum ZIndex { diff --git a/src/vs/platform/log/common/log.ts b/src/vs/platform/log/common/log.ts index 2471e0573d8c4..e503417a554dd 100644 --- a/src/vs/platform/log/common/log.ts +++ b/src/vs/platform/log/common/log.ts @@ -259,6 +259,13 @@ export abstract class AbstractLogger extends Disposable implements ILogger { return this.level !== LogLevel.Off && this.level <= level; } + protected canLog(level: LogLevel): boolean { + if (this._store.isDisposed) { + return false; + } + return this.checkLogLevel(level); + } + abstract trace(message: string, ...args: any[]): void; abstract debug(message: string, ...args: any[]): void; abstract info(message: string, ...args: any[]): void; @@ -269,8 +276,6 @@ export abstract class AbstractLogger extends Disposable implements ILogger { export abstract class AbstractMessageLogger extends AbstractLogger implements ILogger { - protected abstract log(level: LogLevel, message: string): void; - constructor(private readonly logAlways?: boolean) { super(); } @@ -280,32 +285,31 @@ export abstract class AbstractMessageLogger extends AbstractLogger implements IL } trace(message: string, ...args: any[]): void { - if (this.checkLogLevel(LogLevel.Trace)) { + if (this.canLog(LogLevel.Trace)) { this.log(LogLevel.Trace, format([message, ...args], true)); } } debug(message: string, ...args: any[]): void { - if (this.checkLogLevel(LogLevel.Debug)) { + if (this.canLog(LogLevel.Debug)) { this.log(LogLevel.Debug, format([message, ...args])); } } info(message: string, ...args: any[]): void { - if (this.checkLogLevel(LogLevel.Info)) { + if (this.canLog(LogLevel.Info)) { this.log(LogLevel.Info, format([message, ...args])); } } warn(message: string, ...args: any[]): void { - if (this.checkLogLevel(LogLevel.Warning)) { + if (this.canLog(LogLevel.Warning)) { this.log(LogLevel.Warning, format([message, ...args])); } } error(message: string | Error, ...args: any[]): void { - if (this.checkLogLevel(LogLevel.Error)) { - + if (this.canLog(LogLevel.Error)) { if (message instanceof Error) { const array = Array.prototype.slice.call(arguments) as any[]; array[0] = message.stack; @@ -317,6 +321,8 @@ export abstract class AbstractMessageLogger extends AbstractLogger implements IL } flush(): void { } + + protected abstract log(level: LogLevel, message: string): void; } @@ -331,7 +337,7 @@ export class ConsoleMainLogger extends AbstractLogger implements ILogger { } trace(message: string, ...args: any[]): void { - if (this.checkLogLevel(LogLevel.Trace)) { + if (this.canLog(LogLevel.Trace)) { if (this.useColors) { console.log(`\x1b[90m[main ${now()}]\x1b[0m`, message, ...args); } else { @@ -341,7 +347,7 @@ export class ConsoleMainLogger extends AbstractLogger implements ILogger { } debug(message: string, ...args: any[]): void { - if (this.checkLogLevel(LogLevel.Debug)) { + if (this.canLog(LogLevel.Debug)) { if (this.useColors) { console.log(`\x1b[90m[main ${now()}]\x1b[0m`, message, ...args); } else { @@ -351,7 +357,7 @@ export class ConsoleMainLogger extends AbstractLogger implements ILogger { } info(message: string, ...args: any[]): void { - if (this.checkLogLevel(LogLevel.Info)) { + if (this.canLog(LogLevel.Info)) { if (this.useColors) { console.log(`\x1b[90m[main ${now()}]\x1b[0m`, message, ...args); } else { @@ -361,7 +367,7 @@ export class ConsoleMainLogger extends AbstractLogger implements ILogger { } warn(message: string | Error, ...args: any[]): void { - if (this.checkLogLevel(LogLevel.Warning)) { + if (this.canLog(LogLevel.Warning)) { if (this.useColors) { console.warn(`\x1b[93m[main ${now()}]\x1b[0m`, message, ...args); } else { @@ -371,7 +377,7 @@ export class ConsoleMainLogger extends AbstractLogger implements ILogger { } error(message: string, ...args: any[]): void { - if (this.checkLogLevel(LogLevel.Error)) { + if (this.canLog(LogLevel.Error)) { if (this.useColors) { console.error(`\x1b[91m[main ${now()}]\x1b[0m`, message, ...args); } else { @@ -394,7 +400,7 @@ export class ConsoleLogger extends AbstractLogger implements ILogger { } trace(message: string, ...args: any[]): void { - if (this.checkLogLevel(LogLevel.Trace)) { + if (this.canLog(LogLevel.Trace)) { if (this.useColors) { console.log('%cTRACE', 'color: #888', message, ...args); } else { @@ -404,7 +410,7 @@ export class ConsoleLogger extends AbstractLogger implements ILogger { } debug(message: string, ...args: any[]): void { - if (this.checkLogLevel(LogLevel.Debug)) { + if (this.canLog(LogLevel.Debug)) { if (this.useColors) { console.log('%cDEBUG', 'background: #eee; color: #888', message, ...args); } else { @@ -414,7 +420,7 @@ export class ConsoleLogger extends AbstractLogger implements ILogger { } info(message: string, ...args: any[]): void { - if (this.checkLogLevel(LogLevel.Info)) { + if (this.canLog(LogLevel.Info)) { if (this.useColors) { console.log('%c INFO', 'color: #33f', message, ...args); } else { @@ -424,7 +430,7 @@ export class ConsoleLogger extends AbstractLogger implements ILogger { } warn(message: string | Error, ...args: any[]): void { - if (this.checkLogLevel(LogLevel.Warning)) { + if (this.canLog(LogLevel.Warning)) { if (this.useColors) { console.log('%c WARN', 'color: #993', message, ...args); } else { @@ -434,7 +440,7 @@ export class ConsoleLogger extends AbstractLogger implements ILogger { } error(message: string, ...args: any[]): void { - if (this.checkLogLevel(LogLevel.Error)) { + if (this.canLog(LogLevel.Error)) { if (this.useColors) { console.log('%c ERR', 'color: #f33', message, ...args); } else { @@ -457,31 +463,31 @@ export class AdapterLogger extends AbstractLogger implements ILogger { } trace(message: string, ...args: any[]): void { - if (this.checkLogLevel(LogLevel.Trace)) { + if (this.canLog(LogLevel.Trace)) { this.adapter.log(LogLevel.Trace, [this.extractMessage(message), ...args]); } } debug(message: string, ...args: any[]): void { - if (this.checkLogLevel(LogLevel.Debug)) { + if (this.canLog(LogLevel.Debug)) { this.adapter.log(LogLevel.Debug, [this.extractMessage(message), ...args]); } } info(message: string, ...args: any[]): void { - if (this.checkLogLevel(LogLevel.Info)) { + if (this.canLog(LogLevel.Info)) { this.adapter.log(LogLevel.Info, [this.extractMessage(message), ...args]); } } warn(message: string | Error, ...args: any[]): void { - if (this.checkLogLevel(LogLevel.Warning)) { + if (this.canLog(LogLevel.Warning)) { this.adapter.log(LogLevel.Warning, [this.extractMessage(message), ...args]); } } error(message: string | Error, ...args: any[]): void { - if (this.checkLogLevel(LogLevel.Error)) { + if (this.canLog(LogLevel.Error)) { this.adapter.log(LogLevel.Error, [this.extractMessage(message), ...args]); } } @@ -491,7 +497,7 @@ export class AdapterLogger extends AbstractLogger implements ILogger { return msg; } - return toErrorMessage(msg, this.checkLogLevel(LogLevel.Trace)); + return toErrorMessage(msg, this.canLog(LogLevel.Trace)); } flush(): void { diff --git a/src/vs/platform/log/node/spdlogLog.ts b/src/vs/platform/log/node/spdlogLog.ts index ef99c32831a10..7627505622294 100644 --- a/src/vs/platform/log/node/spdlogLog.ts +++ b/src/vs/platform/log/node/spdlogLog.ts @@ -111,9 +111,9 @@ export class SpdLogLogger extends AbstractMessageLogger implements ILogger { override flush(): void { if (this._logger) { - this._logger.flush(); + this.flushLogger(); } else { - this._loggerCreationPromise.then(() => this.flush()); + this._loggerCreationPromise.then(() => this.flushLogger()); } } @@ -126,6 +126,12 @@ export class SpdLogLogger extends AbstractMessageLogger implements ILogger { super.dispose(); } + private flushLogger(): void { + if (this._logger) { + this._logger.flush(); + } + } + private disposeLogger(): void { if (this._logger) { this._logger.drop(); diff --git a/src/vs/platform/observable/common/wrapInHotClass.ts b/src/vs/platform/observable/common/wrapInHotClass.ts index 75e706ed1b08f..73c071efe9257 100644 --- a/src/vs/platform/observable/common/wrapInHotClass.ts +++ b/src/vs/platform/observable/common/wrapInHotClass.ts @@ -5,7 +5,7 @@ import { isHotReloadEnabled } from '../../../base/common/hotReload.js'; import { IDisposable } from '../../../base/common/lifecycle.js'; import { autorunWithStore, IObservable } from '../../../base/common/observable.js'; -import { BrandedService, GetLeadingNonServiceArgs, IInstantiationService } from '../../instantiation/common/instantiation.js'; +import { BrandedService, IInstantiationService } from '../../instantiation/common/instantiation.js'; export function hotClassGetOriginalInstance(value: T): T { if (value instanceof BaseClass) { @@ -19,7 +19,7 @@ export function hotClassGetOriginalInstance(value: T): T { * When the wrapper is created, the original class is created. * When the original class changes, the instance is re-created. */ -export function wrapInHotClass0(clazz: IObservable>): Result> { +export function wrapInHotClass0(clazz: IObservable>): Result { return !isHotReloadEnabled() ? clazz.get() : createWrapper(clazz, BaseClass0); } @@ -61,7 +61,7 @@ class BaseClass0 extends BaseClass { * When the wrapper is created, the original class is created. * When the original class changes, the instance is re-created. */ -export function wrapInHotClass1(clazz: IObservable>): Result> { +export function wrapInHotClass1(clazz: IObservable>): Result { return !isHotReloadEnabled() ? clazz.get() : createWrapper(clazz, BaseClass1); } diff --git a/src/vs/platform/observable/common/wrapInReloadableClass.ts b/src/vs/platform/observable/common/wrapInReloadableClass.ts index 2bb37d1f40151..c7dff91d50bb4 100644 --- a/src/vs/platform/observable/common/wrapInReloadableClass.ts +++ b/src/vs/platform/observable/common/wrapInReloadableClass.ts @@ -13,7 +13,7 @@ import { BrandedService, GetLeadingNonServiceArgs, IInstantiationService } from * When the wrapper is created, the original class is created. * When the original class changes, the instance is re-created. */ -export function wrapInReloadableClass0(getClass: () => Result): Result> { +export function wrapInReloadableClass0(getClass: () => Result): Result { return !isHotReloadEnabled() ? getClass() : createWrapper(getClass, BaseClass0); } diff --git a/src/vs/platform/issue/common/issue.ts b/src/vs/platform/process/common/process.ts similarity index 52% rename from src/vs/platform/issue/common/issue.ts rename to src/vs/platform/process/common/process.ts index 3fc9e76e85200..50f155da42c56 100644 --- a/src/vs/platform/issue/common/issue.ts +++ b/src/vs/platform/process/common/process.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { UriComponents } from '../../../base/common/uri.js'; import { ISandboxConfiguration } from '../../../base/parts/sandbox/common/sandboxTypes.js'; import { PerformanceInfo, SystemInfo } from '../../diagnostics/common/diagnostics.js'; import { createDecorator } from '../../instantiation/common/instantiation.js'; @@ -19,68 +18,11 @@ export interface WindowData { zoomLevel: number; } -export const enum OldIssueType { - Bug, - PerformanceIssue, - FeatureRequest -} - export enum IssueSource { VSCode = 'vscode', Extension = 'extension', Marketplace = 'marketplace' } - -export interface OldIssueReporterStyles extends WindowStyles { - textLinkColor?: string; - textLinkActiveForeground?: string; - inputBackground?: string; - inputForeground?: string; - inputBorder?: string; - inputErrorBorder?: string; - inputErrorBackground?: string; - inputErrorForeground?: string; - inputActiveBorder?: string; - buttonBackground?: string; - buttonForeground?: string; - buttonHoverBackground?: string; - sliderBackgroundColor?: string; - sliderHoverColor?: string; - sliderActiveColor?: string; -} - -export interface OldIssueReporterExtensionData { - name: string; - publisher: string | undefined; - version: string; - id: string; - isTheme: boolean; - isBuiltin: boolean; - displayName: string | undefined; - repositoryUrl: string | undefined; - bugsUrl: string | undefined; - extensionData?: string; - extensionTemplate?: string; - data?: string; - uri?: UriComponents; -} - -export interface OldIssueReporterData extends WindowData { - styles: OldIssueReporterStyles; - enabledExtensions: OldIssueReporterExtensionData[]; - issueType?: OldIssueType; - issueSource?: IssueSource; - extensionId?: string; - experiments?: string; - restrictedMode: boolean; - isUnsupported: boolean; - githubAccessToken: string; - issueTitle?: string; - issueBody?: string; - data?: string; - uri?: UriComponents; -} - export interface ISettingSearchResult { extensionId: string; key: string; @@ -109,33 +51,10 @@ export interface ProcessExplorerData extends WindowData { applicationName: string; } -export interface OldIssueReporterWindowConfiguration extends ISandboxConfiguration { - disableExtensions: boolean; - data: OldIssueReporterData; - os: { - type: string; - arch: string; - release: string; - }; -} - export interface ProcessExplorerWindowConfiguration extends ISandboxConfiguration { data: ProcessExplorerData; } -export const IIssueMainService = createDecorator('issueService'); - -export interface IIssueMainService { - readonly _serviceBrand: undefined; - // Used by the issue reporter - openReporter(data: OldIssueReporterData): Promise; - $reloadWithExtensionsDisabled(): Promise; - $showConfirmCloseDialog(): Promise; - $showClipboardDialog(): Promise; - $sendReporterMenu(extensionId: string, extensionName: string): Promise; - $closeReporter(): Promise; -} - export const IProcessMainService = createDecorator('processService'); export interface IProcessMainService { diff --git a/src/vs/platform/issue/electron-main/processMainService.ts b/src/vs/platform/process/electron-main/processMainService.ts similarity index 99% rename from src/vs/platform/issue/electron-main/processMainService.ts rename to src/vs/platform/process/electron-main/processMainService.ts index bbf187a92b426..d2f6bbfd98a91 100644 --- a/src/vs/platform/issue/electron-main/processMainService.ts +++ b/src/vs/platform/process/electron-main/processMainService.ts @@ -16,7 +16,7 @@ import { IDiagnosticsMainService } from '../../diagnostics/electron-main/diagnos import { IDialogMainService } from '../../dialogs/electron-main/dialogMainService.js'; import { IEnvironmentMainService } from '../../environment/electron-main/environmentMainService.js'; import { ICSSDevelopmentService } from '../../cssDev/node/cssDevService.js'; -import { IProcessMainService, ProcessExplorerData, ProcessExplorerWindowConfiguration } from '../common/issue.js'; +import { IProcessMainService, ProcessExplorerData, ProcessExplorerWindowConfiguration } from '../common/process.js'; import { ILogService } from '../../log/common/log.js'; import { INativeHostMainService } from '../../native/electron-main/nativeHostMainService.js'; import product from '../../product/common/product.js'; diff --git a/src/vs/platform/protocol/electron-main/protocolMainService.ts b/src/vs/platform/protocol/electron-main/protocolMainService.ts index 5f666fe4f733d..86e0a616af8ad 100644 --- a/src/vs/platform/protocol/electron-main/protocolMainService.ts +++ b/src/vs/platform/protocol/electron-main/protocolMainService.ts @@ -24,7 +24,7 @@ export class ProtocolMainService extends Disposable implements IProtocolMainServ declare readonly _serviceBrand: undefined; private readonly validRoots = TernarySearchTree.forPaths(!isLinux); - private readonly validExtensions = new Set(['.svg', '.png', '.jpg', '.jpeg', '.gif', '.bmp', '.webp', '.mp4']); // https://github.com/microsoft/vscode/issues/119384 + private readonly validExtensions = new Set(['.svg', '.png', '.jpg', '.jpeg', '.gif', '.bmp', '.webp', '.mp4', '.otf', '.ttf']); // https://github.com/microsoft/vscode/issues/119384 constructor( @INativeEnvironmentService private readonly environmentService: INativeEnvironmentService, diff --git a/src/vs/platform/quickinput/browser/quickInputController.ts b/src/vs/platform/quickinput/browser/quickInputController.ts index 93ca64256bea8..7b77f077c03ce 100644 --- a/src/vs/platform/quickinput/browser/quickInputController.ts +++ b/src/vs/platform/quickinput/browser/quickInputController.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from '../../../base/browser/dom.js'; +import * as domStylesheetsJs from '../../../base/browser/domStylesheets.js'; import { ActionBar } from '../../../base/browser/ui/actionbar/actionbar.js'; import { ActionViewItem } from '../../../base/browser/ui/actionbar/actionViewItems.js'; import { Button } from '../../../base/browser/ui/button/button.js'; @@ -113,7 +114,7 @@ export class QuickInputController extends Disposable { container.tabIndex = -1; container.style.display = 'none'; - const styleSheet = dom.createStyleSheet(container); + const styleSheet = domStylesheetsJs.createStyleSheet(container); const titleBar = dom.append(container, $('.quick-input-titlebar')); @@ -150,11 +151,11 @@ export class QuickInputController extends Disposable { const visibleCountContainer = dom.append(filterContainer, $('.quick-input-visible-count')); visibleCountContainer.setAttribute('aria-live', 'polite'); visibleCountContainer.setAttribute('aria-atomic', 'true'); - const visibleCount = new CountBadge(visibleCountContainer, { countFormat: localize({ key: 'quickInput.visibleCount', comment: ['This tells the user how many items are shown in a list of items to select from. The items can be anything. Currently not visible, but read by screen readers.'] }, "{0} Results") }, this.styles.countBadge); + const visibleCount = this._register(new CountBadge(visibleCountContainer, { countFormat: localize({ key: 'quickInput.visibleCount', comment: ['This tells the user how many items are shown in a list of items to select from. The items can be anything. Currently not visible, but read by screen readers.'] }, "{0} Results") }, this.styles.countBadge)); const countContainer = dom.append(filterContainer, $('.quick-input-count')); countContainer.setAttribute('aria-live', 'polite'); - const count = new CountBadge(countContainer, { countFormat: localize({ key: 'quickInput.countSelected', comment: ['This tells the user how many items are selected in a list of items to select from. The items can be anything.'] }, "{0} Selected") }, this.styles.countBadge); + const count = this._register(new CountBadge(countContainer, { countFormat: localize({ key: 'quickInput.countSelected', comment: ['This tells the user how many items are selected in a list of items to select from. The items can be anything.'] }, "{0} Selected") }, this.styles.countBadge)); const inlineActionBar = this._register(new ActionBar(headerContainer, { hoverDelegate: this.options.hoverDelegate })); inlineActionBar.domNode.classList.add('quick-input-inline-action-bar'); diff --git a/src/vs/platform/quickinput/browser/quickInputTree.ts b/src/vs/platform/quickinput/browser/quickInputTree.ts index 0cc8255971d8a..3203be9e53570 100644 --- a/src/vs/platform/quickinput/browser/quickInputTree.ts +++ b/src/vs/platform/quickinput/browser/quickInputTree.ts @@ -33,7 +33,7 @@ import { Lazy } from '../../../base/common/lazy.js'; import { IParsedLabelWithIcons, getCodiconAriaLabel, matchesFuzzyIconAware, parseLabelWithIcons } from '../../../base/common/iconLabels.js'; import { HoverPosition } from '../../../base/browser/ui/hover/hoverWidget.js'; import { compareAnything } from '../../../base/common/comparers.js'; -import { ltrim } from '../../../base/common/strings.js'; +import { escape, ltrim } from '../../../base/common/strings.js'; import { RenderIndentGuides } from '../../../base/browser/ui/tree/abstractTree.js'; import { ThrottledDelayer } from '../../../base/common/async.js'; import { isCancellationError } from '../../../base/common/errors.js'; @@ -443,7 +443,7 @@ class QuickPickItemElementRenderer extends BaseQuickInputListRenderer { + const actualDeviceId = await getdevDeviceId(logService.error.bind(logService)); + const currentDeviceId = await resolveNodedevDeviceId(stateService, logService); + if (actualDeviceId !== currentDeviceId) { + stateService.setItem(devDeviceIdKey, actualDeviceId); + } +} diff --git a/src/vs/platform/telemetry/test/common/telemetryLogAppender.test.ts b/src/vs/platform/telemetry/test/common/telemetryLogAppender.test.ts index 91721dc7bd549..eb72191f928c7 100644 --- a/src/vs/platform/telemetry/test/common/telemetryLogAppender.test.ts +++ b/src/vs/platform/telemetry/test/common/telemetryLogAppender.test.ts @@ -21,31 +21,31 @@ class TestTelemetryLogger extends AbstractLogger implements ILogger { } trace(message: string, ...args: any[]): void { - if (this.checkLogLevel(LogLevel.Trace)) { + if (this.canLog(LogLevel.Trace)) { this.logs.push(message + JSON.stringify(args)); } } debug(message: string, ...args: any[]): void { - if (this.checkLogLevel(LogLevel.Debug)) { + if (this.canLog(LogLevel.Debug)) { this.logs.push(message); } } info(message: string, ...args: any[]): void { - if (this.checkLogLevel(LogLevel.Info)) { + if (this.canLog(LogLevel.Info)) { this.logs.push(message); } } warn(message: string | Error, ...args: any[]): void { - if (this.checkLogLevel(LogLevel.Warning)) { + if (this.canLog(LogLevel.Warning)) { this.logs.push(message.toString()); } } error(message: string, ...args: any[]): void { - if (this.checkLogLevel(LogLevel.Error)) { + if (this.canLog(LogLevel.Error)) { this.logs.push(message); } } diff --git a/src/vs/platform/terminal/common/capabilities/commandDetection/promptInputModel.ts b/src/vs/platform/terminal/common/capabilities/commandDetection/promptInputModel.ts index 50ff706be7f51..eda4c7b93d245 100644 --- a/src/vs/platform/terminal/common/capabilities/commandDetection/promptInputModel.ts +++ b/src/vs/platform/terminal/common/capabilities/commandDetection/promptInputModel.ts @@ -33,8 +33,11 @@ export interface IPromptInputModel extends IPromptInputModelState { /** * Gets the prompt input as a user-friendly string where `|` is the cursor position and `[` and * `]` wrap any ghost text. + * + * @param emptyStringWhenEmpty If true, an empty string is returned when the prompt input is + * empty (as opposed to '|'). */ - getCombinedString(): string; + getCombinedString(emptyStringWhenEmpty?: boolean): string; } export interface IPromptInputModelState { @@ -149,7 +152,7 @@ export class PromptInputModel extends Disposable implements IPromptInputModel { } } - getCombinedString(): string { + getCombinedString(emptyStringWhenEmpty?: boolean): string { const value = this._value.replaceAll('\n', '\u23CE'); if (this._cursorIndex === -1) { return value; @@ -161,6 +164,9 @@ export class PromptInputModel extends Disposable implements IPromptInputModel { } else { result += value.substring(this.cursorIndex); } + if (result === '|' && emptyStringWhenEmpty) { + return ''; + } return result; } diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index 34195bc820db8..c638fb625a20a 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -7,7 +7,7 @@ import { Event } from '../../../base/common/event.js'; import { IProcessEnvironment, OperatingSystem } from '../../../base/common/platform.js'; import { URI, UriComponents } from '../../../base/common/uri.js'; import { createDecorator } from '../../instantiation/common/instantiation.js'; -import { IPtyHostProcessReplayEvent, ISerializedCommandDetectionCapability, ITerminalCapabilityStore } from './capabilities/capabilities.js'; +import { IPtyHostProcessReplayEvent, ISerializedCommandDetectionCapability, ITerminalCapabilityStore, type ITerminalCommand } from './capabilities/capabilities.js'; import { IGetTerminalLayoutInfoArgs, IProcessDetails, ISetTerminalLayoutInfoArgs } from './terminalProcess.js'; import { ThemeIcon } from '../../../base/common/themables.js'; import { ISerializableEnvironmentVariableCollections } from './environmentVariable.js'; @@ -16,6 +16,8 @@ import { IWorkspaceFolder } from '../../workspace/common/workspace.js'; import { Registry } from '../../registry/common/platform.js'; import type * as performance from '../../../base/common/performance.js'; import { ILogService } from '../../log/common/log.js'; +import type { IAction } from '../../../base/common/actions.js'; +import type { IDisposable } from '../../../base/common/lifecycle.js'; export const terminalTabFocusModeContextKey = new RawContextKey('terminalTabFocusMode', false, true); @@ -114,6 +116,7 @@ export const enum TerminalSettingId { SmoothScrolling = 'terminal.integrated.smoothScrolling', IgnoreBracketedPasteMode = 'terminal.integrated.ignoreBracketedPasteMode', FocusAfterRun = 'terminal.integrated.focusAfterRun', + FontLigatures = 'terminal.integrated.fontLigatures', // Debug settings that are hidden from user @@ -929,6 +932,10 @@ export interface IShellIntegration { deserialize(serialized: ISerializedCommandDetectionCapability): void; } +export interface IDecorationAddon { + registerMenuItems(command: ITerminalCommand, items: IAction[]): IDisposable; +} + export interface ITerminalContributions { profiles?: ITerminalProfileContribution[]; } diff --git a/src/vs/platform/terminal/node/terminalEnvironment.ts b/src/vs/platform/terminal/node/terminalEnvironment.ts index cfedd6496b73d..fbaf3031506ae 100644 --- a/src/vs/platform/terminal/node/terminalEnvironment.ts +++ b/src/vs/platform/terminal/node/terminalEnvironment.ts @@ -167,7 +167,7 @@ export function getShellIntegrationInjection( newArgs = shellIntegrationArgs.get(ShellIntegrationExecutable.Bash); } else if (areZshBashFishLoginArgs(originalArgs)) { envMixin['VSCODE_SHELL_LOGIN'] = '1'; - addEnvMixinPathPrefix(options, envMixin); + addEnvMixinPathPrefix(options, envMixin, shell); newArgs = shellIntegrationArgs.get(ShellIntegrationExecutable.Bash); } if (!newArgs) { @@ -189,7 +189,7 @@ export function getShellIntegrationInjection( newArgs = shellIntegrationArgs.get(ShellIntegrationExecutable.Bash); } else if (areZshBashFishLoginArgs(originalArgs)) { envMixin['VSCODE_SHELL_LOGIN'] = '1'; - addEnvMixinPathPrefix(options, envMixin); + addEnvMixinPathPrefix(options, envMixin, shell); newArgs = shellIntegrationArgs.get(ShellIntegrationExecutable.Bash); } if (!newArgs) { @@ -205,13 +205,17 @@ export function getShellIntegrationInjection( newArgs = shellIntegrationArgs.get(ShellIntegrationExecutable.Fish); } else if (areZshBashFishLoginArgs(originalArgs)) { newArgs = shellIntegrationArgs.get(ShellIntegrationExecutable.FishLogin); - addEnvMixinPathPrefix(options, envMixin); } else if (originalArgs === shellIntegrationArgs.get(ShellIntegrationExecutable.Fish) || originalArgs === shellIntegrationArgs.get(ShellIntegrationExecutable.FishLogin)) { newArgs = originalArgs; } if (!newArgs) { return undefined; } + + // On fish, '$fish_user_paths' is always prepended to the PATH, for both login and non-login shells, so we need + // to apply the path prefix fix always, not only for login shells (see #232291) + addEnvMixinPathPrefix(options, envMixin, shell); + newArgs = [...newArgs]; // Shallow clone the array to avoid setting the default array newArgs[newArgs.length - 1] = format(newArgs[newArgs.length - 1], appRoot); return { newArgs, envMixin }; @@ -238,7 +242,7 @@ export function getShellIntegrationInjection( newArgs = shellIntegrationArgs.get(ShellIntegrationExecutable.Zsh); } else if (areZshBashFishLoginArgs(originalArgs)) { newArgs = shellIntegrationArgs.get(ShellIntegrationExecutable.ZshLogin); - addEnvMixinPathPrefix(options, envMixin); + addEnvMixinPathPrefix(options, envMixin, shell); } else if (originalArgs === shellIntegrationArgs.get(ShellIntegrationExecutable.Zsh) || originalArgs === shellIntegrationArgs.get(ShellIntegrationExecutable.ZshLogin)) { newArgs = originalArgs; } @@ -284,16 +288,19 @@ export function getShellIntegrationInjection( } /** - * On macOS the profile calls path_helper which adds a bunch of standard bin directories to the - * beginning of the PATH. This causes significant problems for the environment variable + * There are a few situations where some directories are added to the beginning of the PATH. + * 1. On macOS when the profile calls path_helper. + * 2. For fish terminals, which always prepend "$fish_user_paths" to the PATH. + * + * This causes significant problems for the environment variable * collection API as the custom paths added to the end will now be somewhere in the middle of * the PATH. To combat this, VSCODE_PATH_PREFIX is used to re-apply any prefix after the profile * has run. This will cause duplication in the PATH but should fix the issue. * * See #99878 for more information. */ -function addEnvMixinPathPrefix(options: ITerminalProcessOptions, envMixin: IProcessEnvironment): void { - if (isMacintosh && options.environmentVariableCollections) { +function addEnvMixinPathPrefix(options: ITerminalProcessOptions, envMixin: IProcessEnvironment, shell: string): void { + if ((isMacintosh || shell === 'fish') && options.environmentVariableCollections) { // Deserialize and merge const deserialized = deserializeEnvironmentVariableCollections(options.environmentVariableCollections); const merged = new MergedEnvironmentVariableCollection(deserialized); @@ -337,8 +344,8 @@ shellIntegrationArgs.set(ShellIntegrationExecutable.PwshLogin, ['-l', '-noexit', shellIntegrationArgs.set(ShellIntegrationExecutable.Zsh, ['-i']); shellIntegrationArgs.set(ShellIntegrationExecutable.ZshLogin, ['-il']); shellIntegrationArgs.set(ShellIntegrationExecutable.Bash, ['--init-file', '{0}/out/vs/workbench/contrib/terminal/common/scripts/shellIntegration-bash.sh']); -shellIntegrationArgs.set(ShellIntegrationExecutable.Fish, ['--init-command', '. {0}/out/vs/workbench/contrib/terminal/common/scripts/fish_xdg_data/fish/vendor_conf.d/shellIntegration.fish']); -shellIntegrationArgs.set(ShellIntegrationExecutable.FishLogin, ['-l', '--init-command', '. {0}/out/vs/workbench/contrib/terminal/common/scripts/fish_xdg_data/fish/vendor_conf.d/shellIntegration.fish']); +shellIntegrationArgs.set(ShellIntegrationExecutable.Fish, ['--init-command', '. {0}/out/vs/workbench/contrib/terminal/common/scripts/shellIntegration.fish']); +shellIntegrationArgs.set(ShellIntegrationExecutable.FishLogin, ['-l', '--init-command', '. {0}/out/vs/workbench/contrib/terminal/common/scripts/shellIntegration.fish']); const pwshLoginArgs = ['-login', '-l']; const shLoginArgs = ['--login', '-l']; const shInteractiveArgs = ['-i', '--interactive']; diff --git a/src/vs/platform/terminal/node/windowsShellHelper.ts b/src/vs/platform/terminal/node/windowsShellHelper.ts index 2d63433bbdc32..15edb8c02cab3 100644 --- a/src/vs/platform/terminal/node/windowsShellHelper.ts +++ b/src/vs/platform/terminal/node/windowsShellHelper.ts @@ -23,13 +23,20 @@ const SHELL_EXECUTABLES = [ 'powershell.exe', 'pwsh.exe', 'bash.exe', + 'git-cmd.exe', 'wsl.exe', 'ubuntu.exe', 'ubuntu1804.exe', 'kali.exe', 'debian.exe', 'opensuse-42.exe', - 'sles-12.exe' + 'sles-12.exe', + 'julia.exe', + 'nu.exe', +]; + +const SHELL_EXECUTABLE_REGEXES = [ + /^python(\d(\.\d{0,2})?)?\.exe$/, ]; let windowsProcessTree: typeof WindowsProcessTreeType; @@ -90,6 +97,11 @@ export class WindowsShellHelper extends Disposable implements IWindowsShellHelpe if (SHELL_EXECUTABLES.indexOf(tree.name) === -1) { return tree.name; } + for (const regex of SHELL_EXECUTABLE_REGEXES) { + if (tree.name.match(regex)) { + return tree.name; + } + } if (!tree.children || tree.children.length === 0) { return tree.name; } @@ -143,7 +155,7 @@ export class WindowsShellHelper extends Disposable implements IWindowsShellHelpe case 'bash.exe': case 'git-cmd.exe': return WindowsShellType.GitBash; - case 'julialauncher.exe': + case 'julia.exe': return GeneralShellType.Julia; case 'nu.exe': return GeneralShellType.NuShell; diff --git a/src/vs/platform/terminal/test/common/capabilities/commandDetection/promptInputModel.test.ts b/src/vs/platform/terminal/test/common/capabilities/commandDetection/promptInputModel.test.ts index 4b7b06f41fe0a..ac3ebfbe950fa 100644 --- a/src/vs/platform/terminal/test/common/capabilities/commandDetection/promptInputModel.test.ts +++ b/src/vs/platform/terminal/test/common/capabilities/commandDetection/promptInputModel.test.ts @@ -2,9 +2,8 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -/* eslint-disable local/code-import-patterns */ -import type { Terminal } from '@xterm/xterm'; +import type { Terminal } from '@xterm/headless'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../base/test/common/utils.js'; import { NullLogService } from '../../../../../log/common/log.js'; import { PromptInputModel, type IPromptInputModelState } from '../../../../common/capabilities/commandDetection/promptInputModel.js'; diff --git a/src/vs/platform/theme/browser/iconsStyleSheet.ts b/src/vs/platform/theme/browser/iconsStyleSheet.ts index 9133d54c5c73f..7215f066d452a 100644 --- a/src/vs/platform/theme/browser/iconsStyleSheet.ts +++ b/src/vs/platform/theme/browser/iconsStyleSheet.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { asCSSPropertyValue, asCSSUrl } from '../../../base/browser/cssValue.js'; +import * as css from '../../../base/browser/cssValue.js'; import { Emitter, Event } from '../../../base/common/event.js'; import { DisposableStore, IDisposable } from '../../../base/common/lifecycle.js'; import { ThemeIcon } from '../../../base/common/themables.js'; @@ -11,7 +11,7 @@ import { getIconRegistry, IconContribution, IconFontDefinition } from '../common import { IProductIconTheme, IThemeService } from '../common/themeService.js'; export interface IIconsStyleSheet extends IDisposable { - getCSS(): string; + getCSS(): css.CssFragment; readonly onDidChange: Event; } @@ -28,12 +28,12 @@ export function getIconsStyleSheet(themeService: IThemeService | undefined): IIc return { dispose: () => disposable.dispose(), onDidChange: onDidChangeEmmiter.event, - getCSS() { + getCSS(): css.CssFragment { const productIconTheme = themeService ? themeService.getProductIconTheme() : new UnthemedProductIconTheme(); const usedFontIds: { [id: string]: IconFontDefinition } = {}; - const rules: string[] = []; - const rootAttribs: string[] = []; + const rules = new css.Builder(); + const rootAttribs = new css.Builder(); for (const contribution of iconRegistry.getIcons()) { const definition = productIconTheme.getIcon(contribution); if (!definition) { @@ -41,30 +41,34 @@ export function getIconsStyleSheet(themeService: IThemeService | undefined): IIc } const fontContribution = definition.font; - const fontFamilyVar = `--vscode-icon-${contribution.id}-font-family`; - const contentVar = `--vscode-icon-${contribution.id}-content`; + const fontFamilyVar = css.inline`--vscode-icon-${css.className(contribution.id)}-font-family`; + const contentVar = css.inline`--vscode-icon-${css.className(contribution.id)}-content`; if (fontContribution) { usedFontIds[fontContribution.id] = fontContribution.definition; rootAttribs.push( - `${fontFamilyVar}: ${asCSSPropertyValue(fontContribution.id)};`, - `${contentVar}: '${definition.fontCharacter}';`, + css.inline`${fontFamilyVar}: ${css.stringValue(fontContribution.id)};`, + css.inline`${contentVar}: ${css.stringValue(definition.fontCharacter)};`, ); - rules.push(`.codicon-${contribution.id}:before { content: '${definition.fontCharacter}'; font-family: ${asCSSPropertyValue(fontContribution.id)}; }`); + rules.push(css.inline`.codicon-${css.className(contribution.id)}:before { content: ${css.stringValue(definition.fontCharacter)}; font-family: ${css.stringValue(fontContribution.id)}; }`); } else { - rootAttribs.push(`${contentVar}: '${definition.fontCharacter}'; ${fontFamilyVar}: 'codicon';`); - rules.push(`.codicon-${contribution.id}:before { content: '${definition.fontCharacter}'; }`); + rootAttribs.push(css.inline`${contentVar}: ${css.stringValue(definition.fontCharacter)}; ${fontFamilyVar}: 'codicon';`); + rules.push(css.inline`.codicon-${css.className(contribution.id)}:before { content: ${css.stringValue(definition.fontCharacter)}; }`); } } for (const id in usedFontIds) { const definition = usedFontIds[id]; - const fontWeight = definition.weight ? `font-weight: ${definition.weight};` : ''; - const fontStyle = definition.style ? `font-style: ${definition.style};` : ''; - const src = definition.src.map(l => `${asCSSUrl(l.location)} format('${l.format}')`).join(', '); - rules.push(`@font-face { src: ${src}; font-family: ${asCSSPropertyValue(id)};${fontWeight}${fontStyle} font-display: block; }`); + const fontWeight = definition.weight ? css.inline`font-weight: ${css.identValue(definition.weight)};` : css.inline``; + const fontStyle = definition.style ? css.inline`font-style: ${css.identValue(definition.style)};` : css.inline``; + + const src = new css.Builder(); + for (const l of definition.src) { + src.push(css.inline`${css.asCSSUrl(l.location)} format(${css.stringValue(l.format)})`); + } + rules.push(css.inline`@font-face { src: ${src.join(', ')}; font-family: ${css.stringValue(id)};${fontWeight}${fontStyle} font-display: block; }`); } - rules.push(`:root { ${rootAttribs.join(' ')} }`); + rules.push(css.inline`:root { ${rootAttribs.join(' ')} }`); return rules.join('\n'); } diff --git a/src/vs/platform/theme/common/colors/miscColors.ts b/src/vs/platform/theme/common/colors/miscColors.ts index 7be17f5c44ed1..95048e2876e0f 100644 --- a/src/vs/platform/theme/common/colors/miscColors.ts +++ b/src/vs/platform/theme/common/colors/miscColors.ts @@ -30,6 +30,22 @@ export const badgeForeground = registerColor('badge.foreground', { dark: Color.white, light: '#333', hcDark: Color.white, hcLight: Color.white }, nls.localize('badgeForeground', "Badge foreground color. Badges are small information labels, e.g. for search results count.")); +export const activityWarningBadgeForeground = registerColor('activityWarningBadge.foreground', + { dark: Color.black.lighten(0.2), light: Color.white, hcDark: null, hcLight: Color.black.lighten(0.2) }, + nls.localize('activityWarningBadge.foreground', 'Foreground color of the warning activity badge')); + +export const activityWarningBadgeBackground = registerColor('activityWarningBadge.background', + { dark: '#CCA700', light: '#BF8803', hcDark: null, hcLight: '#CCA700' }, + nls.localize('activityWarningBadge.background', 'Background color of the warning activity badge')); + +export const activityErrorBadgeForeground = registerColor('activityErrorBadge.foreground', + { dark: Color.black.lighten(0.2), light: Color.white, hcDark: null, hcLight: Color.black.lighten(0.2) }, + nls.localize('activityErrorBadge.foreground', 'Foreground color of the error activity badge')); + +export const activityErrorBadgeBackground = registerColor('activityErrorBadge.background', + { dark: '#F14C4C', light: '#E51400', hcDark: null, hcLight: '#F14C4C' }, + nls.localize('activityErrorBadge.background', 'Background color of the error activity badge')); + // ----- scrollbar diff --git a/src/vs/platform/theme/common/iconRegistry.ts b/src/vs/platform/theme/common/iconRegistry.ts index 279b30ba11358..5dee6ce44d625 100644 --- a/src/vs/platform/theme/common/iconRegistry.ts +++ b/src/vs/platform/theme/common/iconRegistry.ts @@ -26,15 +26,15 @@ export const Extensions = { export type IconDefaults = ThemeIcon | IconDefinition; export interface IconDefinition { - font?: IconFontContribution; // undefined for the default font (codicon) - fontCharacter: string; + readonly font?: IconFontContribution; // undefined for the default font (codicon) + readonly fontCharacter: string; } export interface IconContribution { readonly id: string; description: string | undefined; - deprecationMessage?: string; + readonly deprecationMessage?: string; readonly defaults: IconDefaults; } diff --git a/src/vs/platform/window/common/window.ts b/src/vs/platform/window/common/window.ts index a3c02dba44905..cd0240d8bdcbc 100644 --- a/src/vs/platform/window/common/window.ts +++ b/src/vs/platform/window/common/window.ts @@ -354,6 +354,7 @@ export interface IOSConfiguration { export interface INativeWindowConfiguration extends IWindowConfiguration, NativeParsedArgs, ISandboxConfiguration { mainPid: number; + handle?: string; machineId: string; sqmId: string; diff --git a/src/vs/platform/windows/electron-main/windowImpl.ts b/src/vs/platform/windows/electron-main/windowImpl.ts index f0540656eedb4..6a3ecb6a7e1aa 100644 --- a/src/vs/platform/windows/electron-main/windowImpl.ts +++ b/src/vs/platform/windows/electron-main/windowImpl.ts @@ -1092,6 +1092,11 @@ export class CodeWindow extends BaseWindow implements ICodeWindow { } // Update window related properties + try { + configuration.handle = this._win.getNativeWindowHandle().toString('base64'); + } catch (error) { + this.logService.error(`Error getting native window handle: ${error}`); + } configuration.fullscreen = this.isFullScreen; configuration.maximized = this._win.isMaximized(); configuration.partsSplash = this.themeMainService.getWindowSplash(); diff --git a/src/vs/server/node/server.cli.ts b/src/vs/server/node/server.cli.ts index 6e2da224dcfa4..4c821c003d5cf 100644 --- a/src/vs/server/node/server.cli.ts +++ b/src/vs/server/node/server.cli.ts @@ -15,8 +15,7 @@ import { createWaitMarkerFileSync } from '../../platform/environment/node/wait.j import { PipeCommand } from '../../workbench/api/node/extHostCLIServer.js'; import { hasStdinWithoutTty, getStdinFilePath, readFromStdin } from '../../platform/environment/node/stdin.js'; import { DeferredPromise } from '../../base/common/async.js'; - -const __dirname = dirname(url.fileURLToPath(import.meta.url)); +import { FileAccess } from '../../base/common/network.js'; /* * Implements a standalone CLI app that opens VS Code from a remote terminal. @@ -147,10 +146,10 @@ export async function main(desc: ProductDescription, args: string[]): Promise console.log(err)); return; } @@ -496,6 +495,10 @@ function mapFileToRemoteUri(uri: string): string { return uri.replace(/^file:\/\//, 'vscode-remote://' + cliRemoteAuthority); } +function getAppRoot() { + return dirname(FileAccess.asFileUri('').fsPath); +} + const [, , productName, version, commit, executableName, ...remainingArgs] = process.argv; main({ productName, version, commit, executableName }, remainingArgs).then(null, err => { console.error(err.message || err.stack || err); diff --git a/src/vs/server/node/serverServices.ts b/src/vs/server/node/serverServices.ts index cd1091150dbbe..42517aa71b3ad 100644 --- a/src/vs/server/node/serverServices.ts +++ b/src/vs/server/node/serverServices.ts @@ -276,7 +276,7 @@ class ServerLogger extends AbstractLogger { } trace(message: string, ...args: any[]): void { - if (this.checkLogLevel(LogLevel.Trace)) { + if (this.canLog(LogLevel.Trace)) { if (this.useColors) { console.log(`\x1b[90m[${now()}]\x1b[0m`, message, ...args); } else { @@ -286,7 +286,7 @@ class ServerLogger extends AbstractLogger { } debug(message: string, ...args: any[]): void { - if (this.checkLogLevel(LogLevel.Debug)) { + if (this.canLog(LogLevel.Debug)) { if (this.useColors) { console.log(`\x1b[90m[${now()}]\x1b[0m`, message, ...args); } else { @@ -296,7 +296,7 @@ class ServerLogger extends AbstractLogger { } info(message: string, ...args: any[]): void { - if (this.checkLogLevel(LogLevel.Info)) { + if (this.canLog(LogLevel.Info)) { if (this.useColors) { console.log(`\x1b[90m[${now()}]\x1b[0m`, message, ...args); } else { @@ -306,7 +306,7 @@ class ServerLogger extends AbstractLogger { } warn(message: string | Error, ...args: any[]): void { - if (this.checkLogLevel(LogLevel.Warning)) { + if (this.canLog(LogLevel.Warning)) { if (this.useColors) { console.warn(`\x1b[93m[${now()}]\x1b[0m`, message, ...args); } else { @@ -316,7 +316,7 @@ class ServerLogger extends AbstractLogger { } error(message: string, ...args: any[]): void { - if (this.checkLogLevel(LogLevel.Error)) { + if (this.canLog(LogLevel.Error)) { if (this.useColors) { console.error(`\x1b[91m[${now()}]\x1b[0m`, message, ...args); } else { diff --git a/src/vs/workbench/api/browser/mainThreadAuthentication.ts b/src/vs/workbench/api/browser/mainThreadAuthentication.ts index 67ba984057628..081c5f8057033 100644 --- a/src/vs/workbench/api/browser/mainThreadAuthentication.ts +++ b/src/vs/workbench/api/browser/mainThreadAuthentication.ts @@ -20,6 +20,7 @@ import { getAuthenticationProviderActivationEvent } from '../../services/authent import { URI, UriComponents } from '../../../base/common/uri.js'; import { IOpenerService } from '../../../platform/opener/common/opener.js'; import { CancellationError } from '../../../base/common/errors.js'; +import { ILogService } from '../../../platform/log/common/log.js'; interface AuthenticationForceNewSessionOptions { detail?: string; @@ -70,6 +71,7 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu private readonly _proxy: ExtHostAuthenticationShape; private readonly _registrations = this._register(new DisposableMap()); + private _sentProviderUsageEvents = new Set(); constructor( extHostContext: IExtHostContext, @@ -81,7 +83,8 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu @INotificationService private readonly notificationService: INotificationService, @IExtensionService private readonly extensionService: IExtensionService, @ITelemetryService private readonly telemetryService: ITelemetryService, - @IOpenerService private readonly openerService: IOpenerService + @IOpenerService private readonly openerService: IOpenerService, + @ILogService private readonly logService: ILogService ) { super(); this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostAuthentication); @@ -96,6 +99,16 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu } async $registerAuthenticationProvider(id: string, label: string, supportsMultipleAccounts: boolean): Promise { + if (!this.authenticationService.declaredProviders.find(p => p.id === id)) { + // If telemetry shows that this is not happening much, we can instead throw an error here. + this.logService.warn(`Authentication provider ${id} was not declared in the Extension Manifest.`); + type AuthProviderNotDeclaredClassification = { + owner: 'TylerLeonhardt'; + comment: 'An authentication provider was not declared in the Extension Manifest.'; + id: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The provider id.' }; + }; + this.telemetryService.publicLog2<{ id: string }, AuthProviderNotDeclaredClassification>('authentication.providerNotDeclared', { id }); + } const emitter = new Emitter(); this._registrations.set(id, emitter); const provider = new MainThreadAuthenticationProvider(this._proxy, id, label, supportsMultipleAccounts, this.notificationService, emitter); @@ -302,6 +315,11 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu } private sendProviderUsageTelemetry(extensionId: string, providerId: string): void { + const key = `${extensionId}|${providerId}`; + if (this._sentProviderUsageEvents.has(key)) { + return; + } + this._sentProviderUsageEvents.add(key); type AuthProviderUsageClassification = { owner: 'TylerLeonhardt'; comment: 'Used to see which extensions are using which providers'; diff --git a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts index 5de097d706d31..cf4513383754a 100644 --- a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts +++ b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts @@ -25,6 +25,7 @@ import { IChatWidgetService } from '../../contrib/chat/browser/chat.js'; import { ChatInputPart } from '../../contrib/chat/browser/chatInputPart.js'; import { AddDynamicVariableAction, IAddDynamicVariableContext } from '../../contrib/chat/browser/contrib/chatDynamicVariables.js'; import { ChatAgentLocation, IChatAgentHistoryEntry, IChatAgentImplementation, IChatAgentRequest, IChatAgentService } from '../../contrib/chat/common/chatAgents.js'; +import { IChatEditingService, IChatRelatedFileProviderMetadata } from '../../contrib/chat/common/chatEditingService.js'; import { ChatRequestAgentPart } from '../../contrib/chat/common/chatParserTypes.js'; import { ChatRequestParser } from '../../contrib/chat/common/chatRequestParser.js'; import { IChatContentInlineReference, IChatContentReference, IChatFollowup, IChatProgress, IChatService, IChatTask, IChatWarningMessage } from '../../contrib/chat/common/chatService.js'; @@ -79,6 +80,8 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA private readonly _chatParticipantDetectionProviders = this._register(new DisposableMap()); + private readonly _chatRelatedFilesProviders = this._register(new DisposableMap()); + private readonly _pendingProgress = new Map void>(); private readonly _proxy: ExtHostChatAgentsShape2; @@ -91,6 +94,7 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA extHostContext: IExtHostContext, @IChatAgentService private readonly _chatAgentService: IChatAgentService, @IChatService private readonly _chatService: IChatService, + @IChatEditingService private readonly _chatEditingService: IChatEditingService, @ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService, @IChatWidgetService private readonly _chatWidgetService: IChatWidgetService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @@ -135,7 +139,8 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA this._chatService.transferChatSession({ sessionId, inputValue }, URI.revive(toWorkspace)); } - $registerAgent(handle: number, extension: ExtensionIdentifier, id: string, metadata: IExtensionChatAgentMetadata, dynamicProps: IDynamicChatAgentProps | undefined): void { + async $registerAgent(handle: number, extension: ExtensionIdentifier, id: string, metadata: IExtensionChatAgentMetadata, dynamicProps: IDynamicChatAgentProps | undefined): Promise { + await this._extensionService.whenInstalledExtensionsRegistered(); const staticAgentRegistration = this._chatAgentService.getAgent(id, true); if (!staticAgentRegistration && !dynamicProps) { if (this._chatAgentService.getAgentsByName(id).length) { @@ -205,7 +210,8 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA }); } - $updateAgent(handle: number, metadataUpdate: IExtensionChatAgentMetadata): void { + async $updateAgent(handle: number, metadataUpdate: IExtensionChatAgentMetadata): Promise { + await this._extensionService.whenInstalledExtensionsRegistered(); const data = this._agents.get(handle); if (!data) { this._logService.error(`MainThreadChatAgents2#$updateAgent: No agent with handle ${handle} registered`); @@ -342,6 +348,19 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA $unregisterChatParticipantDetectionProvider(handle: number): void { this._chatParticipantDetectionProviders.deleteAndDispose(handle); } + + $registerRelatedFilesProvider(handle: number, metadata: IChatRelatedFileProviderMetadata): void { + this._chatRelatedFilesProviders.set(handle, this._chatEditingService.registerRelatedFilesProvider(handle, { + description: metadata.description, + provideRelatedFiles: async (request, token) => { + return (await this._proxy.$provideRelatedFiles(handle, request, token))?.map((v) => ({ uri: URI.from(v.uri), description: v.description })) ?? []; + } + })); + } + + $unregisterRelatedFilesProvider(handle: number): void { + this._chatRelatedFilesProviders.deleteAndDispose(handle); + } } diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index 58d9ca75eed2b..9e5223bcb08fc 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -1113,6 +1113,8 @@ class MainThreadDocumentOnDropEditProvider implements languages.DocumentDropEdit readonly dropMimeTypes?: readonly string[]; + readonly providedDropEditKinds: readonly HierarchicalKind[] | undefined; + readonly resolveDocumentDropEdit?: languages.DocumentDropEditProvider['resolveDocumentDropEdit']; constructor( @@ -1122,6 +1124,7 @@ class MainThreadDocumentOnDropEditProvider implements languages.DocumentDropEdit @IUriIdentityService private readonly _uriIdentService: IUriIdentityService ) { this.dropMimeTypes = metadata?.dropMimeTypes ?? ['*/*']; + this.providedDropEditKinds = metadata?.providedDropKinds?.map(kind => new HierarchicalKind(kind)); if (metadata?.supportsResolve) { this.resolveDocumentDropEdit = async (edit, token) => { diff --git a/src/vs/workbench/api/browser/mainThreadTreeViews.ts b/src/vs/workbench/api/browser/mainThreadTreeViews.ts index 74089f182b0fd..c61530258bc98 100644 --- a/src/vs/workbench/api/browser/mainThreadTreeViews.ts +++ b/src/vs/workbench/api/browser/mainThreadTreeViews.ts @@ -5,7 +5,7 @@ import { Disposable, DisposableMap, DisposableStore } from '../../../base/common/lifecycle.js'; import { ExtHostContext, MainThreadTreeViewsShape, ExtHostTreeViewsShape, MainContext, CheckboxUpdate } from '../common/extHost.protocol.js'; -import { ITreeViewDataProvider, ITreeItem, ITreeView, IViewsRegistry, ITreeViewDescriptor, IRevealOptions, Extensions, ResolvableTreeItem, ITreeViewDragAndDropController, IViewBadge, NoTreeViewError } from '../../common/views.js'; +import { ITreeItem, ITreeView, IViewsRegistry, ITreeViewDescriptor, IRevealOptions, Extensions, ResolvableTreeItem, ITreeViewDragAndDropController, IViewBadge, NoTreeViewError, ITreeViewDataProvider } from '../../common/views.js'; import { extHostNamedCustomer, IExtHostContext } from '../../services/extensions/common/extHostCustomers.js'; import { distinct } from '../../../base/common/arrays.js'; import { INotificationService } from '../../../platform/notification/common/notification.js'; @@ -270,13 +270,21 @@ class TreeViewDataProvider implements ITreeViewDataProvider { this.hasResolve = this._proxy.$hasResolve(this.treeViewId); } - getChildren(treeItem?: ITreeItem): Promise { - if (!treeItem) { + async getChildren(treeItem?: ITreeItem): Promise { + const batches = await this.getChildrenBatch(treeItem ? [treeItem] : undefined); + return batches?.[0]; + } + + getChildrenBatch(treeItems?: ITreeItem[]): Promise { + if (!treeItems) { this.itemsMap.clear(); } - return this._proxy.$getChildren(this.treeViewId, treeItem ? treeItem.handle : undefined) + return this._proxy.$getChildren(this.treeViewId, treeItems ? treeItems.map(item => item.handle) : undefined) .then( - children => this.postGetChildren(children), + children => { + const convertedChildren = this.convertTransferChildren(treeItems ?? [], children); + return this.postGetChildren(convertedChildren); + }, err => { // It can happen that a tree view is disposed right as `getChildren` is called. This results in an error because the data provider gets removed. // The tree will shortly get cleaned up in this case. We just need to handle the error here. @@ -287,6 +295,17 @@ class TreeViewDataProvider implements ITreeViewDataProvider { }); } + private convertTransferChildren(parents: ITreeItem[], children: (number | ITreeItem)[][] | undefined) { + const convertedChildren: ITreeItem[][] = Array(parents.length); + if (children) { + for (const childGroup of children) { + const childGroupIndex = childGroup[0] as number; + convertedChildren[childGroupIndex] = childGroup.slice(1) as ITreeItem[]; + } + } + return convertedChildren; + } + getItemsToRefresh(itemsToRefreshByHandle: { [treeItemHandle: string]: ITreeItem }): { items: ITreeItem[]; checkboxes: ITreeItem[] } { const itemsToRefresh: ITreeItem[] = []; const checkboxesToRefresh: ITreeItem[] = []; @@ -325,22 +344,26 @@ class TreeViewDataProvider implements ITreeViewDataProvider { return this.itemsMap.size === 0; } - private async postGetChildren(elements: ITreeItem[] | undefined): Promise { - if (elements === undefined) { + private async postGetChildren(elementGroups: ITreeItem[][] | undefined): Promise { + if (elementGroups === undefined) { return undefined; } - const result: ResolvableTreeItem[] = []; + const resultGroups: ResolvableTreeItem[][] = []; const hasResolve = await this.hasResolve; - if (elements) { - for (const element of elements) { - const resolvable = new ResolvableTreeItem(element, hasResolve ? (token) => { - return this._proxy.$resolve(this.treeViewId, element.handle, token); - } : undefined); - this.itemsMap.set(element.handle, resolvable); - result.push(resolvable); + if (elementGroups) { + for (const elements of elementGroups) { + const result: ResolvableTreeItem[] = []; + resultGroups.push(result); + for (const element of elements) { + const resolvable = new ResolvableTreeItem(element, hasResolve ? (token) => { + return this._proxy.$resolve(this.treeViewId, element.handle, token); + } : undefined); + this.itemsMap.set(element.handle, resolvable); + result.push(resolvable); + } } } - return result; + return resultGroups; } private updateTreeItem(current: ITreeItem, treeItem: ITreeItem): void { diff --git a/src/vs/workbench/api/common/configurationExtensionPoint.ts b/src/vs/workbench/api/common/configurationExtensionPoint.ts index e45e567775c99..3fa10161b9a22 100644 --- a/src/vs/workbench/api/common/configurationExtensionPoint.ts +++ b/src/vs/workbench/api/common/configurationExtensionPoint.ts @@ -132,7 +132,8 @@ const defaultConfigurationExtPoint = ExtensionsRegistry.registerExtensionPoint { @@ -195,7 +196,8 @@ const configurationExtPoint = ExtensionsRegistry.registerExtensionPoint = new ExtensionIdentifierMap(); diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 68734bfe120d3..cca8cfe756a94 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -106,7 +106,7 @@ import { ExtensionDescriptionRegistry } from '../../services/extensions/common/e import { UIKind } from '../../services/extensions/common/extensionHostProtocol.js'; import { checkProposedApiEnabled, isProposedApiEnabled } from '../../services/extensions/common/extensions.js'; import { ProxyIdentifier } from '../../services/extensions/common/proxyIdentifier.js'; -import { ExcludeSettingOptions, TextSearchCompleteMessageType, TextSearchContextNew, TextSearchMatchNew } from '../../services/search/common/searchExtTypes.js'; +import { ExcludeSettingOptions, TextSearchCompleteMessageType, TextSearchContext2, TextSearchMatch2 } from '../../services/search/common/searchExtTypes.js'; import type * as vscode from 'vscode'; import { ExtHostCodeMapper } from './extHostCodeMapper.js'; @@ -437,6 +437,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I checkProposedApiEnabled(extension, 'resolvers'); return initData.commit; }, + get handle(): string | undefined { + checkProposedApiEnabled(extension, 'nativeWindowHandle'); + return initData.handle; + } }; if (!initData.environment.extensionTestsLocationURI) { // allow to patch env-function when running tests @@ -961,14 +965,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I // Note, undefined/null have different meanings on "exclude" return extHostWorkspace.findFiles(include, exclude, maxResults, extension.identifier, token); }, - findFiles2: (filePattern: vscode.GlobPattern, options?: vscode.FindFiles2Options, token?: vscode.CancellationToken): Thenable => { + findFiles2: (filePattern: vscode.GlobPattern[], options?: vscode.FindFiles2Options, token?: vscode.CancellationToken): Thenable => { checkProposedApiEnabled(extension, 'findFiles2'); return extHostWorkspace.findFiles2(filePattern, options, extension.identifier, token); }, - findFiles2New: (filePattern: vscode.GlobPattern[], options?: vscode.FindFiles2OptionsNew, token?: vscode.CancellationToken): Thenable => { - checkProposedApiEnabled(extension, 'findFiles2New'); - return extHostWorkspace.findFiles2New(filePattern, options, extension.identifier, token); - }, findTextInFiles: (query: vscode.TextSearchQuery, optionsOrCallback: vscode.FindTextInFilesOptions | ((result: vscode.TextSearchResult) => void), callbackOrToken?: vscode.CancellationToken | ((result: vscode.TextSearchResult) => void), token?: vscode.CancellationToken) => { checkProposedApiEnabled(extension, 'findTextInFiles'); let options: vscode.FindTextInFilesOptions; @@ -985,10 +985,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return extHostWorkspace.findTextInFiles(query, options || {}, callback, extension.identifier, token); }, - findTextInFilesNew: (query: vscode.TextSearchQueryNew, options?: vscode.FindTextInFilesOptionsNew, token?: vscode.CancellationToken): vscode.FindTextInFilesResponse => { - checkProposedApiEnabled(extension, 'findTextInFilesNew'); - checkProposedApiEnabled(extension, 'textSearchProviderNew'); - return extHostWorkspace.findTextInFilesNew(query, options, extension.identifier, token); + findTextInFiles2: (query: vscode.TextSearchQuery2, options?: vscode.FindTextInFilesOptions2, token?: vscode.CancellationToken): vscode.FindTextInFilesResponse => { + checkProposedApiEnabled(extension, 'findTextInFiles2'); + checkProposedApiEnabled(extension, 'textSearchProvider2'); + return extHostWorkspace.findTextInFiles2(query, options, extension.identifier, token); }, save: (uri) => { return extHostWorkspace.save(uri); @@ -1136,23 +1136,17 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I registerAITextSearchProvider: (scheme: string, provider: vscode.AITextSearchProvider) => { // there are some dependencies on textSearchProvider, so we need to check for both checkProposedApiEnabled(extension, 'aiTextSearchProvider'); - checkProposedApiEnabled(extension, 'textSearchProvider'); - return extHostSearch.registerAITextSearchProviderOld(scheme, provider); + checkProposedApiEnabled(extension, 'textSearchProvider2'); + return extHostSearch.registerAITextSearchProvider(scheme, provider); }, - registerFileSearchProviderNew: (scheme: string, provider: vscode.FileSearchProviderNew) => { - checkProposedApiEnabled(extension, 'fileSearchProviderNew'); + registerFileSearchProvider2: (scheme: string, provider: vscode.FileSearchProvider2) => { + checkProposedApiEnabled(extension, 'fileSearchProvider2'); return extHostSearch.registerFileSearchProvider(scheme, provider); }, - registerTextSearchProviderNew: (scheme: string, provider: vscode.TextSearchProviderNew) => { - checkProposedApiEnabled(extension, 'textSearchProviderNew'); + registerTextSearchProvider2: (scheme: string, provider: vscode.TextSearchProvider2) => { + checkProposedApiEnabled(extension, 'textSearchProvider2'); return extHostSearch.registerTextSearchProvider(scheme, provider); }, - registerAITextSearchProviderNew: (scheme: string, provider: vscode.AITextSearchProviderNew) => { - // there are some dependencies on textSearchProvider, so we need to check for both - checkProposedApiEnabled(extension, 'aiTextSearchProviderNew'); - checkProposedApiEnabled(extension, 'textSearchProviderNew'); - return extHostSearch.registerAITextSearchProvider(scheme, provider); - }, registerRemoteAuthorityResolver: (authorityPrefix: string, resolver: vscode.RemoteAuthorityResolver) => { checkProposedApiEnabled(extension, 'resolvers'); return extensionService.registerRemoteAuthorityResolver(authorityPrefix, resolver); @@ -1456,6 +1450,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I checkProposedApiEnabled(extension, 'chatParticipantAdditions'); return extHostChatAgents2.registerChatParticipantDetectionProvider(extension, provider); }, + registerRelatedFilesProvider(provider: vscode.ChatRelatedFilesProvider, metadata: vscode.ChatRelatedFilesProviderMetadata) { + checkProposedApiEnabled(extension, 'chatEditing'); + return extHostChatAgents2.registerRelatedFilesProvider(extension, provider, metadata); + } }; // namespace: lm @@ -1702,6 +1700,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I NotebookEditorRevealType: extHostTypes.NotebookEditorRevealType, NotebookCellOutput: extHostTypes.NotebookCellOutput, NotebookCellOutputItem: extHostTypes.NotebookCellOutputItem, + CellErrorStackFrame: extHostTypes.CellErrorStackFrame, NotebookCellStatusBarItem: extHostTypes.NotebookCellStatusBarItem, NotebookControllerAffinity: extHostTypes.NotebookControllerAffinity, NotebookControllerAffinity2: extHostTypes.NotebookControllerAffinity2, @@ -1792,8 +1791,8 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I InlineEdit: extHostTypes.InlineEdit, InlineEditTriggerKind: extHostTypes.InlineEditTriggerKind, ExcludeSettingOptions: ExcludeSettingOptions, - TextSearchContextNew: TextSearchContextNew, - TextSearchMatchNew: TextSearchMatchNew, + TextSearchContext2: TextSearchContext2, + TextSearchMatch2: TextSearchMatch2, TextSearchCompleteMessageTypeNew: TextSearchCompleteMessageType, }; }; diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 02b40ac0cbf27..a4532ed13ec3d 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -52,6 +52,7 @@ import { IRevealOptions, ITreeItem, IViewBadge } from '../../common/views.js'; import { CallHierarchyItem } from '../../contrib/callHierarchy/common/callHierarchy.js'; import { ChatAgentLocation, IChatAgentMetadata, IChatAgentRequest, IChatAgentResult, IChatWelcomeMessageContent } from '../../contrib/chat/common/chatAgents.js'; import { ICodeMapperRequest, ICodeMapperResult } from '../../contrib/chat/common/chatCodeMapperService.js'; +import { IChatRelatedFile, IChatRelatedFileProviderMetadata as IChatRelatedFilesProviderMetadata, IChatRequestDraft } from '../../contrib/chat/common/chatEditingService.js'; import { IChatProgressHistoryResponseContent } from '../../contrib/chat/common/chatModel.js'; import { IChatContentInlineReference, IChatFollowup, IChatProgress, IChatResponseErrorDetails, IChatTask, IChatTaskDto, IChatUserActionEvent, IChatVoteAction } from '../../contrib/chat/common/chatService.js'; import { IChatRequestVariableValue, IChatVariableData, IChatVariableResolverProgress } from '../../contrib/chat/common/chatVariables.js'; @@ -1274,6 +1275,8 @@ export interface MainThreadChatAgentsShape2 extends IDisposable { $registerAgent(handle: number, extension: ExtensionIdentifier, id: string, metadata: IExtensionChatAgentMetadata, dynamicProps: IDynamicChatAgentProps | undefined): void; $registerChatParticipantDetectionProvider(handle: number): void; $unregisterChatParticipantDetectionProvider(handle: number): void; + $registerRelatedFilesProvider(handle: number, metadata: IChatRelatedFilesProviderMetadata): void; + $unregisterRelatedFilesProvider(handle: number): void; $registerAgentCompletionsProvider(handle: number, id: string, triggerCharacters: string[]): void; $unregisterAgentCompletionsProvider(handle: number, id: string): void; $updateAgent(handle: number, metadataUpdate: IExtensionChatAgentMetadata): void; @@ -1331,6 +1334,7 @@ export interface ExtHostChatAgentsShape2 { $provideSampleQuestions(handle: number, location: ChatAgentLocation, token: CancellationToken): Promise; $releaseSession(sessionId: string): void; $detectChatParticipant(handle: number, request: Dto, context: { history: IChatAgentHistoryEntryDto[] }, options: { participants: IChatParticipantMetadata[]; location: ChatAgentLocation }, token: CancellationToken): Promise; + $provideRelatedFiles(handle: number, request: Dto, token: CancellationToken): Promise[] | undefined>; } export interface IChatParticipantMetadata { participant: string; @@ -1862,7 +1866,17 @@ export interface CheckboxUpdate { } export interface ExtHostTreeViewsShape { - $getChildren(treeViewId: string, treeItemHandle?: string): Promise; + /** + * To reduce what is sent on the wire: + * w + * x + * y + * z + * + * for [x,y] returns + * [[1,z]], where the inner array is [original index, ...children] + */ + $getChildren(treeViewId: string, treeItemHandles?: string[]): Promise<(number | ITreeItem)[][] | undefined>; $handleDrop(destinationViewId: string, requestId: number, treeDataTransfer: DataTransferDTO, targetHandle: string | undefined, token: CancellationToken, operationUuid?: string, sourceViewId?: string, sourceTreeItemHandles?: string[]): Promise; $handleDrag(sourceViewId: string, sourceTreeItemHandles: string[], operationUuid: string, token: CancellationToken): Promise; $setExpanded(treeViewId: string, treeItemHandle: string, expanded: boolean): void; @@ -2217,7 +2231,8 @@ export interface IPasteEditDto { export interface IDocumentDropEditProviderMetadata { readonly supportsResolve: boolean; - dropMimeTypes: readonly string[]; + readonly dropMimeTypes: readonly string[]; + readonly providedDropKinds?: readonly string[]; } export interface IDocumentDropEditDto { diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index 837f25dfef88d..14d0984389547 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -31,6 +31,7 @@ import { ExtHostLanguageModels } from './extHostLanguageModels.js'; import * as typeConvert from './extHostTypeConverters.js'; import * as extHostTypes from './extHostTypes.js'; import { isChatViewTitleActionContext } from '../../contrib/chat/common/chatActions.js'; +import { IChatRelatedFile, IChatRequestDraft } from '../../contrib/chat/common/chatEditingService.js'; class ChatAgentResponseStream { @@ -217,7 +218,8 @@ class ChatAgentResponseStream { throwIfDone(this.textEdit); checkProposedApiEnabled(that._extension, 'chatParticipantAdditions'); - const part = new extHostTypes.ChatResponseTextEditPart(target, edits); + const part = new extHostTypes.ChatResponseTextEditPart(target, Array.isArray(edits) ? edits : []); + part.isDone = edits === true ? true : undefined; const dto = typeConvert.ChatResponseTextEditPart.from(part); _report(dto); return this; @@ -304,6 +306,9 @@ export class ExtHostChatAgents2 extends Disposable implements ExtHostChatAgentsS private static _participantDetectionProviderIdPool = 0; private readonly _participantDetectionProviders = new Map(); + private static _relatedFilesProviderIdPool = 0; + private readonly _relatedFilesProviders = new Map(); + private readonly _sessionDisposables: DisposableMap = this._register(new DisposableMap()); private readonly _completionDisposables: DisposableMap = this._register(new DisposableMap()); @@ -361,6 +366,26 @@ export class ExtHostChatAgents2 extends Disposable implements ExtHostChatAgentsS }); } + registerRelatedFilesProvider(extension: IExtensionDescription, provider: vscode.ChatRelatedFilesProvider, metadata: vscode.ChatRelatedFilesProviderMetadata): vscode.Disposable { + const handle = ExtHostChatAgents2._relatedFilesProviderIdPool++; + this._relatedFilesProviders.set(handle, new ExtHostRelatedFilesProvider(extension, provider)); + this._proxy.$registerRelatedFilesProvider(handle, metadata); + return toDisposable(() => { + this._relatedFilesProviders.delete(handle); + this._proxy.$unregisterRelatedFilesProvider(handle); + }); + } + + async $provideRelatedFiles(handle: number, request: IChatRequestDraft, token: CancellationToken): Promise[] | undefined> { + const provider = this._relatedFilesProviders.get(handle); + if (!provider) { + return Promise.resolve([]); + } + + const extRequestDraft = typeConvert.ChatRequestDraft.to(request); + return await provider.provider.provideRelatedFiles(extRequestDraft, token) ?? undefined; + } + async $detectChatParticipant(handle: number, requestDto: Dto, context: { history: IChatAgentHistoryEntryDto[] }, options: { location: ChatAgentLocation; participants?: vscode.ChatParticipantMetadata[] }, token: CancellationToken): Promise { const { request, location, history } = await this._createRequest(requestDto, context); @@ -638,6 +663,13 @@ class ExtHostParticipantDetector { ) { } } +class ExtHostRelatedFilesProvider { + constructor( + public readonly extension: IExtensionDescription, + public readonly provider: vscode.ChatRelatedFilesProvider, + ) { } +} + class ExtHostChatAgent { private _followupProvider: vscode.ChatFollowupProvider | undefined; diff --git a/src/vs/workbench/api/common/extHostDebugService.ts b/src/vs/workbench/api/common/extHostDebugService.ts index fe33d966e66ea..b0666520e47a5 100644 --- a/src/vs/workbench/api/common/extHostDebugService.ts +++ b/src/vs/workbench/api/common/extHostDebugService.ts @@ -20,7 +20,7 @@ import { DebugVisualizationType, IAdapterDescriptor, IConfig, IDebugAdapter, IDe import { convertToDAPaths, convertToVSCPaths, isDebuggerMainContribution } from '../../contrib/debug/common/debugUtils.js'; import { ExtensionDescriptionRegistry } from '../../services/extensions/common/extensionDescriptionRegistry.js'; import { Dto } from '../../services/extensions/common/proxyIdentifier.js'; -import { DebugSessionUUID, ExtHostDebugServiceShape, IBreakpointsDeltaDto, IDebugSessionDto, IFunctionBreakpointDto, ISourceMultiBreakpointDto, IStackFrameFocusDto, IThreadFocusDto, MainContext, MainThreadDebugServiceShape } from './extHost.protocol.js'; +import { DebugSessionUUID, ExtHostDebugServiceShape, IBreakpointsDeltaDto, IDebugSessionDto, IFunctionBreakpointDto, ISourceMultiBreakpointDto, IStackFrameFocusDto, IThreadFocusDto, MainContext, MainThreadDebugServiceShape, MainThreadTelemetryShape } from './extHost.protocol.js'; import { IExtHostCommands } from './extHostCommands.js'; import { IExtHostConfiguration } from './extHostConfiguration.js'; import { IExtHostEditorTabs } from './extHostEditorTabs.js'; @@ -116,15 +116,17 @@ export abstract class ExtHostDebugServiceBase extends DisposableCls implements I private readonly _visualizers = new Map(); private _visualizerIdCounter = 0; + private _telemetryProxy: MainThreadTelemetryShape; + constructor( @IExtHostRpcService extHostRpcService: IExtHostRpcService, - @IExtHostWorkspace protected _workspaceService: IExtHostWorkspace, - @IExtHostExtensionService private _extensionService: IExtHostExtensionService, - @IExtHostConfiguration protected _configurationService: IExtHostConfiguration, - @IExtHostEditorTabs protected _editorTabs: IExtHostEditorTabs, - @IExtHostVariableResolverProvider private _variableResolver: IExtHostVariableResolverProvider, - @IExtHostCommands private _commands: IExtHostCommands, - @IExtHostTesting private _testing: IExtHostTesting, + @IExtHostWorkspace protected readonly _workspaceService: IExtHostWorkspace, + @IExtHostExtensionService private readonly _extensionService: IExtHostExtensionService, + @IExtHostConfiguration protected readonly _configurationService: IExtHostConfiguration, + @IExtHostEditorTabs protected readonly _editorTabs: IExtHostEditorTabs, + @IExtHostVariableResolverProvider private readonly _variableResolver: IExtHostVariableResolverProvider, + @IExtHostCommands private readonly _commands: IExtHostCommands, + @IExtHostTesting private readonly _testing: IExtHostTesting, ) { super(); @@ -161,6 +163,8 @@ export abstract class ExtHostDebugServiceBase extends DisposableCls implements I })); this.registerAllDebugTypes(extensionRegistry); }); + + this._telemetryProxy = extHostRpcService.getProxy(MainContext.MainThreadTelemetry); } public async $getVisualizerTreeItem(treeId: string, element: IDebugVisualizationContext): Promise { @@ -654,7 +658,14 @@ export abstract class ExtHostDebugServiceBase extends DisposableCls implements I } // DA -> VS Code - message = convertToVSCPaths(message, true); + try { + // Try to catch details for #233167 + message = convertToVSCPaths(message, true); + } catch (e) { + const type = message.type + '_' + ((message as any).command ?? (message as any).event ?? ''); + this._telemetryProxy.$publicLog2('debugProtocolMessageError', { type, from: session.type }); + throw e; + } mythis._debugServiceProxy.$acceptDAMessage(debugAdapterHandle, message); } @@ -1273,3 +1284,16 @@ export class WorkerExtHostDebugService extends ExtHostDebugServiceBase { super(extHostRpcService, workspaceService, extensionService, configurationService, editorTabs, variableResolver, commands, testing); } } + +// Collecting info for #233167 specifically +type DebugProtocolMessageErrorClassification = { + from: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The type of the debug adapter that the event is from.' }; + type: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The type of the event that was malformed.' }; + owner: 'roblourens'; + comment: 'Sent to collect details about misbehaving debug extensions.'; +}; + +type DebugProtocolMessageErrorEvent = { + from: string; + type: string; +}; diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index d081be0469283..c39b9347e70de 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -1554,6 +1554,14 @@ class InlineEditAdapter { rejectCommand = this._commands.toInternal(result.rejected, disposableStore); } + let shownCommand: languages.Command | undefined = undefined; + if (result.shown) { + if (!disposableStore) { + disposableStore = new DisposableStore(); + } + shownCommand = this._commands.toInternal(result.shown, disposableStore); + } + if (!disposableStore) { disposableStore = new DisposableStore(); } @@ -1563,6 +1571,7 @@ class InlineEditAdapter { range: typeConvert.Range.from(result.range), accepted: acceptCommand, rejected: rejectCommand, + shown: shownCommand, commands: result.commands?.map(c => this._commands.toInternal(c, disposableStore)), }; @@ -2900,6 +2909,7 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF this._proxy.$registerDocumentOnDropEditProvider(handle, this._transformDocumentSelector(selector, extension), isProposedApiEnabled(extension, 'documentPaste') && metadata ? { supportsResolve: !!provider.resolveDocumentDropEdit, dropMimeTypes: metadata.dropMimeTypes, + providedDropKinds: metadata.providedDropEditKinds?.map(x => x.value), } : undefined); return this._createDisposable(handle); diff --git a/src/vs/workbench/api/common/extHostLanguageModelTools.ts b/src/vs/workbench/api/common/extHostLanguageModelTools.ts index e944f7eedc6bd..f078897e24ab7 100644 --- a/src/vs/workbench/api/common/extHostLanguageModelTools.ts +++ b/src/vs/workbench/api/common/extHostLanguageModelTools.ts @@ -15,15 +15,7 @@ import { IPreparedToolInvocation, isToolInvocationContext, IToolInvocation, IToo import { ExtHostLanguageModelToolsShape, IMainContext, IToolDataDto, MainContext, MainThreadLanguageModelToolsShape } from './extHost.protocol.js'; import * as typeConvert from './extHostTypeConverters.js'; -/** - * @deprecated - */ -type CompatLanguageModelToolInvocationOptions = vscode.LanguageModelToolInvocationOptions & { parameters?: any }; -/** - * @deprecated - */ -type CompactLanguageModelToolInvocationPrepareOptions = vscode.LanguageModelToolInvocationPrepareOptions & { parameters: any }; export class ExtHostLanguageModelTools implements ExtHostLanguageModelToolsShape { /** A map of tools that were registered in this EH */ @@ -53,7 +45,7 @@ export class ExtHostLanguageModelTools implements ExtHostLanguageModelToolsShape return await fn(input, token); } - async invokeTool(toolId: string, options: CompatLanguageModelToolInvocationOptions, token?: CancellationToken): Promise { + async invokeTool(toolId: string, options: vscode.LanguageModelToolInvocationOptions, token?: CancellationToken): Promise { const callId = generateUuid(); if (options.tokenizationOptions) { this._tokenCountFuncs.set(callId, options.tokenizationOptions.countTokens); @@ -68,7 +60,7 @@ export class ExtHostLanguageModelTools implements ExtHostLanguageModelToolsShape const result = await this._proxy.$invokeTool({ toolId, callId, - parameters: options.input ?? options.parameters, + parameters: options.input, tokenBudget: options.tokenizationOptions?.tokenBudget, context: options.toolInvocationToken as IToolInvocationContext | undefined, }, token); @@ -96,7 +88,7 @@ export class ExtHostLanguageModelTools implements ExtHostLanguageModelToolsShape throw new Error(`Unknown tool ${dto.toolId}`); } - const options: CompatLanguageModelToolInvocationOptions = { input: dto.parameters, parameters: dto.parameters, toolInvocationToken: dto.context as vscode.ChatParticipantToolToken | undefined }; + const options: vscode.LanguageModelToolInvocationOptions = { input: dto.parameters, toolInvocationToken: dto.context as vscode.ChatParticipantToolToken | undefined }; if (dto.tokenBudget !== undefined) { options.tokenizationOptions = { tokenBudget: dto.tokenBudget, @@ -113,7 +105,7 @@ export class ExtHostLanguageModelTools implements ExtHostLanguageModelToolsShape return typeConvert.LanguageModelToolResult.from(extensionResult); } - async $prepareToolInvocation(toolId: string, parameters: any, token: CancellationToken): Promise { + async $prepareToolInvocation(toolId: string, input: any, token: CancellationToken): Promise { const item = this._registeredTools.get(toolId); if (!item) { throw new Error(`Unknown tool ${toolId}`); @@ -123,7 +115,7 @@ export class ExtHostLanguageModelTools implements ExtHostLanguageModelToolsShape return undefined; } - const options: CompactLanguageModelToolInvocationPrepareOptions = { parameters, input: parameters }; + const options: vscode.LanguageModelToolInvocationPrepareOptions = { input }; const result = await item.tool.prepareInvocation(options, token); if (!result) { return undefined; @@ -134,7 +126,11 @@ export class ExtHostLanguageModelTools implements ExtHostLanguageModelToolsShape title: result.confirmationMessages.title, message: typeof result.confirmationMessages.message === 'string' ? result.confirmationMessages.message : typeConvert.MarkdownString.from(result.confirmationMessages.message), } : undefined, - invocationMessage: result.invocationMessage + invocationMessage: typeof result.invocationMessage === 'string' ? + result.invocationMessage : + (result.invocationMessage ? + typeConvert.MarkdownString.from(result.invocationMessage) : + undefined), }; } diff --git a/src/vs/workbench/api/common/extHostLanguageModels.ts b/src/vs/workbench/api/common/extHostLanguageModels.ts index 4a4384668a37c..b5267b761c74a 100644 --- a/src/vs/workbench/api/common/extHostLanguageModels.ts +++ b/src/vs/workbench/api/common/extHostLanguageModels.ts @@ -206,7 +206,7 @@ export class ExtHostLanguageModels implements ExtHostLanguageModelsShape { let part: IChatResponsePart | undefined; if (fragment.part instanceof extHostTypes.LanguageModelToolCallPart) { - part = { type: 'tool_use', name: fragment.part.name, parameters: fragment.part.input ?? fragment.part.parameters, toolCallId: fragment.part.callId }; + part = { type: 'tool_use', name: fragment.part.name, parameters: fragment.part.input, toolCallId: fragment.part.callId }; } else if (fragment.part instanceof extHostTypes.LanguageModelTextPart) { part = { type: 'text', value: fragment.part.value }; } @@ -233,15 +233,11 @@ export class ExtHostLanguageModels implements ExtHostLanguageModelsShape { } else { - const progress2 = new Progress(async fragment => { - progress.report({ index: fragment.index, part: new extHostTypes.LanguageModelTextPart(fragment.part) }); - }); - p = Promise.resolve(data.provider.provideLanguageModelResponse( messages.map(typeConvert.LanguageModelChatMessage.to), - options?.modelOptions ?? {}, + options, ExtensionIdentifier.toKey(from), - progress2, + progress, token )); } @@ -382,19 +378,6 @@ export class ExtHostLanguageModels implements ExtHostLanguageModelsShape { } } - if (options.tools) { - // TODO@API backwards compat, remove - // when getting tools passed massage them to have inputSchema set - type OldChatTool = vscode.LanguageModelChatTool & { parametersSchema?: object }; - options.tools = options.tools.map((tool: OldChatTool) => { - return { - ...tool, - inputSchema: tool.inputSchema ?? tool.parametersSchema, - parametersSchema: tool.inputSchema ?? tool.parametersSchema, - }; - }); - } - try { const requestId = (Math.random() * 1e6) | 0; const res = new LanguageModelResponse(); diff --git a/src/vs/workbench/api/common/extHostNotebookKernels.ts b/src/vs/workbench/api/common/extHostNotebookKernels.ts index 584a450800353..9c923a427456e 100644 --- a/src/vs/workbench/api/common/extHostNotebookKernels.ts +++ b/src/vs/workbench/api/common/extHostNotebookKernels.ts @@ -721,17 +721,7 @@ class NotebookCellExecutionTask extends Disposable { // so we use updateSoon and immediately flush. that._collector.flush(); - const error = executionError ? { - message: executionError.message, - stack: executionError.stack, - location: executionError?.location ? { - startLineNumber: executionError.location.start.line, - startColumn: executionError.location.start.character, - endLineNumber: executionError.location.end.line, - endColumn: executionError.location.end.character - } : undefined, - uri: executionError.uri - } : undefined; + const error = createSerializeableError(executionError); that._proxy.$completeExecution(that._handle, new SerializableObjectWithBuffers({ runEndTime: endTime, @@ -769,6 +759,31 @@ class NotebookCellExecutionTask extends Disposable { } } +function createSerializeableError(executionError: vscode.CellExecutionError | undefined) { + const convertRange = (range: vscode.Range | undefined) => (range ? { + startLineNumber: range.start.line, + startColumn: range.start.character, + endLineNumber: range.end.line, + endColumn: range.end.character + } : undefined); + + const convertStackFrame = (frame: vscode.CellErrorStackFrame) => ({ + uri: frame.uri, + position: frame.position, + label: frame.label + }); + + const error = executionError ? { + name: executionError.name, + message: executionError.message, + stack: executionError.stack instanceof Array + ? executionError.stack.map(frame => convertStackFrame(frame)) + : executionError.stack, + location: convertRange(executionError.location), + uri: executionError.uri + } : undefined; + return error; +} enum NotebookExecutionTaskState { Init, diff --git a/src/vs/workbench/api/common/extHostQuickOpen.ts b/src/vs/workbench/api/common/extHostQuickOpen.ts index a5194dce8ef58..cb17214f2a4c0 100644 --- a/src/vs/workbench/api/common/extHostQuickOpen.ts +++ b/src/vs/workbench/api/common/extHostQuickOpen.ts @@ -283,6 +283,7 @@ export function createExtHostQuickOpen(mainContext: IMainContext, workspace: IEx private _busy = false; private _ignoreFocusOut = true; private _value = ''; + private _valueSelection: readonly [number, number] | undefined = undefined; private _placeholder: string | undefined; private _buttons: QuickInputButton[] = []; private _handlesToButtons = new Map(); @@ -367,6 +368,15 @@ export function createExtHostQuickOpen(mainContext: IMainContext, workspace: IEx this.update({ value }); } + get valueSelection() { + return this._valueSelection; + } + + set valueSelection(valueSelection: readonly [number, number] | undefined) { + this._valueSelection = valueSelection; + this.update({ valueSelection }); + } + get placeholder() { return this._placeholder; } @@ -713,7 +723,6 @@ export function createExtHostQuickOpen(mainContext: IMainContext, workspace: IEx private _password = false; private _prompt: string | undefined; - private _valueSelection: readonly [number, number] | undefined; private _validationMessage: string | InputBoxValidationMessage | undefined; constructor(extension: IExtensionDescription, onDispose: () => void) { @@ -739,15 +748,6 @@ export function createExtHostQuickOpen(mainContext: IMainContext, workspace: IEx this.update({ prompt }); } - get valueSelection() { - return this._valueSelection; - } - - set valueSelection(valueSelection: readonly [number, number] | undefined) { - this._valueSelection = valueSelection; - this.update({ valueSelection }); - } - get validationMessage() { return this._validationMessage; } diff --git a/src/vs/workbench/api/common/extHostSearch.ts b/src/vs/workbench/api/common/extHostSearch.ts index fbb111a79d1f1..fb92adb1fc9fd 100644 --- a/src/vs/workbench/api/common/extHostSearch.ts +++ b/src/vs/workbench/api/common/extHostSearch.ts @@ -16,15 +16,14 @@ import { URI, UriComponents } from '../../../base/common/uri.js'; import { TextSearchManager } from '../../services/search/common/textSearchManager.js'; import { CancellationToken } from '../../../base/common/cancellation.js'; import { revive } from '../../../base/common/marshalling.js'; -import { OldAITextSearchProviderConverter, OldFileSearchProviderConverter, OldTextSearchProviderConverter } from '../../services/search/common/searchExtConversionTypes.js'; +import { OldFileSearchProviderConverter, OldTextSearchProviderConverter } from '../../services/search/common/searchExtConversionTypes.js'; export interface IExtHostSearch extends ExtHostSearchShape { registerTextSearchProviderOld(scheme: string, provider: vscode.TextSearchProvider): IDisposable; - registerAITextSearchProviderOld(scheme: string, provider: vscode.AITextSearchProvider): IDisposable; registerFileSearchProviderOld(scheme: string, provider: vscode.FileSearchProvider): IDisposable; - registerTextSearchProvider(scheme: string, provider: vscode.TextSearchProviderNew): IDisposable; - registerAITextSearchProvider(scheme: string, provider: vscode.AITextSearchProviderNew): IDisposable; - registerFileSearchProvider(scheme: string, provider: vscode.FileSearchProviderNew): IDisposable; + registerTextSearchProvider(scheme: string, provider: vscode.TextSearchProvider2): IDisposable; + registerAITextSearchProvider(scheme: string, provider: vscode.AITextSearchProvider): IDisposable; + registerFileSearchProvider(scheme: string, provider: vscode.FileSearchProvider2): IDisposable; doInternalFileSearchWithCustomCallback(query: IFileQuery, token: CancellationToken, handleFileMatch: (data: URI[]) => void): Promise; } @@ -35,13 +34,13 @@ export class ExtHostSearch implements IExtHostSearch { protected readonly _proxy: MainThreadSearchShape = this.extHostRpc.getProxy(MainContext.MainThreadSearch); protected _handlePool: number = 0; - private readonly _textSearchProvider = new Map(); + private readonly _textSearchProvider = new Map(); private readonly _textSearchUsedSchemes = new Set(); - private readonly _aiTextSearchProvider = new Map(); + private readonly _aiTextSearchProvider = new Map(); private readonly _aiTextSearchUsedSchemes = new Set(); - private readonly _fileSearchProvider = new Map(); + private readonly _fileSearchProvider = new Map(); private readonly _fileSearchUsedSchemes = new Set(); private readonly _fileSearchManager = new FileSearchManager(); @@ -72,7 +71,7 @@ export class ExtHostSearch implements IExtHostSearch { }); } - registerTextSearchProvider(scheme: string, provider: vscode.TextSearchProviderNew): IDisposable { + registerTextSearchProvider(scheme: string, provider: vscode.TextSearchProvider2): IDisposable { if (this._textSearchUsedSchemes.has(scheme)) { throw new Error(`a text search provider for the scheme '${scheme}' is already registered`); } @@ -88,23 +87,7 @@ export class ExtHostSearch implements IExtHostSearch { }); } - registerAITextSearchProviderOld(scheme: string, provider: vscode.AITextSearchProvider): IDisposable { - if (this._aiTextSearchUsedSchemes.has(scheme)) { - throw new Error(`an AI text search provider for the scheme '${scheme}'is already registered`); - } - - this._aiTextSearchUsedSchemes.add(scheme); - const handle = this._handlePool++; - this._aiTextSearchProvider.set(handle, new OldAITextSearchProviderConverter(provider)); - this._proxy.$registerAITextSearchProvider(handle, this._transformScheme(scheme)); - return toDisposable(() => { - this._aiTextSearchUsedSchemes.delete(scheme); - this._aiTextSearchProvider.delete(handle); - this._proxy.$unregisterProvider(handle); - }); - } - - registerAITextSearchProvider(scheme: string, provider: vscode.AITextSearchProviderNew): IDisposable { + registerAITextSearchProvider(scheme: string, provider: vscode.AITextSearchProvider): IDisposable { if (this._aiTextSearchUsedSchemes.has(scheme)) { throw new Error(`an AI text search provider for the scheme '${scheme}'is already registered`); } @@ -136,7 +119,7 @@ export class ExtHostSearch implements IExtHostSearch { }); } - registerFileSearchProvider(scheme: string, provider: vscode.FileSearchProviderNew): IDisposable { + registerFileSearchProvider(scheme: string, provider: vscode.FileSearchProvider2): IDisposable { if (this._fileSearchUsedSchemes.has(scheme)) { throw new Error(`a file search provider for the scheme '${scheme}' is already registered`); } @@ -208,14 +191,14 @@ export class ExtHostSearch implements IExtHostSearch { return provider.name ?? 'AI'; } - protected createTextSearchManager(query: ITextQuery, provider: vscode.TextSearchProviderNew): TextSearchManager { + protected createTextSearchManager(query: ITextQuery, provider: vscode.TextSearchProvider2): TextSearchManager { return new TextSearchManager({ query, provider }, { readdir: resource => Promise.resolve([]), toCanonicalName: encoding => encoding }, 'textSearchProvider'); } - protected createAITextSearchManager(query: IAITextQuery, provider: vscode.AITextSearchProviderNew): TextSearchManager { + protected createAITextSearchManager(query: IAITextQuery, provider: vscode.AITextSearchProvider): TextSearchManager { return new TextSearchManager({ query, provider }, { readdir: resource => Promise.resolve([]), toCanonicalName: encoding => encoding diff --git a/src/vs/workbench/api/common/extHostTerminalShellIntegration.ts b/src/vs/workbench/api/common/extHostTerminalShellIntegration.ts index 3d7b37e07aed0..7a1c24029d6a3 100644 --- a/src/vs/workbench/api/common/extHostTerminalShellIntegration.ts +++ b/src/vs/workbench/api/common/extHostTerminalShellIntegration.ts @@ -174,9 +174,16 @@ class InternalTerminalShellIntegration extends Disposable { // executeCommand(commandLine: string): vscode.TerminalShellExecution; // executeCommand(executable: string, args: string[]): vscode.TerminalShellExecution; executeCommand(commandLineOrExecutable: string, args?: string[]): vscode.TerminalShellExecution { - let commandLineValue: string = commandLineOrExecutable; - if (args && args.length > 0) { - commandLineValue += ` "${args.map(e => `${e.replaceAll('"', '\\"')}`).join('" "')}"`; + let commandLineValue = commandLineOrExecutable; + if (args) { + for (const arg of args) { + const wrapInQuotes = !arg.match(/["'`]/) && arg.match(/\s/); + if (wrapInQuotes) { + commandLineValue += ` "${arg}"`; + } else { + commandLineValue += ` ${arg}`; + } + } } that._onDidRequestShellExecution.fire(commandLineValue); diff --git a/src/vs/workbench/api/common/extHostTreeViews.ts b/src/vs/workbench/api/common/extHostTreeViews.ts index d3c2aae3c12e6..4179deb5ad3d2 100644 --- a/src/vs/workbench/api/common/extHostTreeViews.ts +++ b/src/vs/workbench/api/common/extHostTreeViews.ts @@ -156,12 +156,26 @@ export class ExtHostTreeViews extends Disposable implements ExtHostTreeViewsShap return view as vscode.TreeView; } - $getChildren(treeViewId: string, treeItemHandle?: string): Promise { + async $getChildren(treeViewId: string, treeItemHandles?: string[]): Promise<(number | ITreeItem)[][] | undefined> { const treeView = this.treeViews.get(treeViewId); if (!treeView) { return Promise.reject(new NoTreeViewError(treeViewId)); } - return treeView.getChildren(treeItemHandle); + if (!treeItemHandles) { + const children = await treeView.getChildren(); + return children ? [[0, ...children]] : undefined; + } + // Keep order of treeItemHandles in case extension trees already depend on this + const result = []; + for (let i = 0; i < treeItemHandles.length; i++) { + const treeItemHandle = treeItemHandles[i]; + const children = await treeView.getChildren(treeItemHandle); + if (children) { + result.push([i, ...children]); + } + + } + return result; } async $handleDrop(destinationViewId: string, requestId: number, treeDataTransferDTO: DataTransferDTO, targetItemHandle: string | undefined, token: CancellationToken, diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 3f2b1c42c7a23..f33ea98cf4f3c 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -57,6 +57,7 @@ import * as types from './extHostTypes.js'; import { IChatResponseTextPart, IChatResponsePromptTsxPart } from '../../contrib/chat/common/languageModels.js'; import { LanguageModelTextPart, LanguageModelPromptTsxPart } from './extHostTypes.js'; import { MarshalledId } from '../../../base/common/marshallingIds.js'; +import { IChatRequestDraft } from '../../contrib/chat/common/chatEditingService.js'; export namespace Command { @@ -2407,7 +2408,7 @@ export namespace LanguageModelChatMessage { type: 'tool_use', toolCallId: c.callId, name: c.name, - parameters: c.input ?? c.parameters + parameters: c.input }; } else if (c instanceof types.LanguageModelTextPart) { return { @@ -2640,11 +2641,14 @@ export namespace ChatResponseTextEditPart { return { kind: 'textEdit', uri: part.uri, - edits: part.edits.map(e => TextEdit.from(e)) + edits: part.edits.map(e => TextEdit.from(e)), + done: part.isDone }; } export function to(part: Dto): vscode.ChatResponseTextEditPart { - return new types.ChatResponseTextEditPart(URI.revive(part.uri), part.edits.map(e => TextEdit.to(e))); + const result = new types.ChatResponseTextEditPart(URI.revive(part.uri), part.edits.map(e => TextEdit.to(e))); + result.isDone = part.done; + return result; } } @@ -2794,6 +2798,15 @@ export namespace ChatAgentRequest { } } +export namespace ChatRequestDraft { + export function to(request: IChatRequestDraft): vscode.ChatRequestDraft { + return { + prompt: request.prompt, + files: request.files.map((uri) => URI.revive(uri)) + }; + } +} + export namespace ChatLocation { export function to(loc: ChatAgentLocation): types.ChatLocation { switch (loc) { @@ -2917,7 +2930,21 @@ export namespace ChatAgentUserActionEvent { } else if (event.action.kind === 'inlineChat') { return { action: { kind: 'editor', accepted: event.action.action === 'accepted' }, result: ehResult }; } else if (event.action.kind === 'chatEditingSessionAction') { - return { action: { kind: 'chatEditingSessionAction', outcome: event.action.outcome === 'accepted' ? types.ChatEditingSessionActionOutcome.Accepted : types.ChatEditingSessionActionOutcome.Rejected, uri: URI.revive(event.action.uri), hasRemainingEdits: event.action.hasRemainingEdits }, result: ehResult }; + + const outcomes = new Map([ + ['accepted', types.ChatEditingSessionActionOutcome.Accepted], + ['rejected', types.ChatEditingSessionActionOutcome.Rejected], + ['saved', types.ChatEditingSessionActionOutcome.Saved], + ]); + + return { + action: { + kind: 'chatEditingSessionAction', + outcome: outcomes.get(event.action.outcome) ?? types.ChatEditingSessionActionOutcome.Rejected, + uri: URI.revive(event.action.uri), + hasRemainingEdits: event.action.hasRemainingEdits + }, result: ehResult + }; } else { return { action: event.action, result: ehResult }; } @@ -2973,13 +3000,12 @@ export namespace DebugTreeItem { } export namespace LanguageModelToolDescription { - export function to(item: IToolData): vscode.LanguageModelToolInformation & { parametersSchema: any } { + export function to(item: IToolData): vscode.LanguageModelToolInformation { return { // Note- the reason this is a unique 'name' is just to avoid confusion with the toolCallId name: item.id, description: item.modelDescription, inputSchema: item.inputSchema, - parametersSchema: item.inputSchema, // TODO@API backwards compat, remove tags: item.tags ?? [], }; } diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 6a8faefd64e1a..c1ed849707bb7 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -3907,6 +3907,19 @@ export class NotebookCellOutput { } } +export class CellErrorStackFrame { + /** + * @param label The name of the stack frame + * @param file The file URI of the stack frame + * @param position The position of the stack frame within the file + */ + constructor( + public label: string, + public uri?: vscode.Uri, + public position?: Position, + ) { } +} + export enum NotebookCellKind { Markup = 1, Code = 2 @@ -4345,7 +4358,8 @@ export class ChatCompletionItem implements vscode.ChatCompletionItem { export enum ChatEditingSessionActionOutcome { Accepted = 1, - Rejected = 2 + Rejected = 2, + Saved = 3 } //#endregion @@ -4511,12 +4525,18 @@ export class ChatResponseMovePart { } } -export class ChatResponseTextEditPart { +export class ChatResponseTextEditPart implements vscode.ChatResponseTextEditPart { uri: vscode.Uri; edits: vscode.TextEdit[]; - constructor(uri: vscode.Uri, edits: vscode.TextEdit | vscode.TextEdit[]) { + isDone?: boolean; + constructor(uri: vscode.Uri, editsOrDone: vscode.TextEdit | vscode.TextEdit[] | true) { this.uri = uri; - this.edits = Array.isArray(edits) ? edits : [edits]; + if (editsOrDone === true) { + this.isDone = true; + this.edits = []; + } else { + this.edits = Array.isArray(editsOrDone) ? editsOrDone : [editsOrDone]; + } } } @@ -4659,22 +4679,11 @@ export class LanguageModelToolCallPart implements vscode.LanguageModelToolCallPa name: string; input: any; - /** @deprecated */ - parameters: any; - constructor(callId: string, name: string, input: any) { - // TODO TEMP- swapped the order of these two arguments, trying to preserve the behavior for a build or two - if (name.startsWith('call_')) { - this.name = callId; - this.callId = name; - } else { - this.callId = callId; - this.name = name; - } + this.callId = callId; + this.name = name; this.input = input; - // TODO@API backwards compat, remove - this.parameters = input; } } @@ -4770,7 +4779,7 @@ export class LanguageModelError extends Error { } export class LanguageModelToolResult { - constructor(public content: (LanguageModelTextPart | LanguageModelPromptTsxPart | unknown)[]) { } + constructor(public content: (LanguageModelTextPart | LanguageModelPromptTsxPart)[]) { } toJSON() { return { diff --git a/src/vs/workbench/api/common/extHostWorkspace.ts b/src/vs/workbench/api/common/extHostWorkspace.ts index abb49a3cc2717..2f40bb8a39271 100644 --- a/src/vs/workbench/api/common/extHostWorkspace.ts +++ b/src/vs/workbench/api/common/extHostWorkspace.ts @@ -34,7 +34,7 @@ import type * as vscode from 'vscode'; import { ExtHostWorkspaceShape, IRelativePatternDto, IWorkspaceData, MainContext, MainThreadMessageOptions, MainThreadMessageServiceShape, MainThreadWorkspaceShape } from './extHost.protocol.js'; import { revive } from '../../../base/common/marshalling.js'; import { AuthInfo, Credentials } from '../../../platform/request/common/request.js'; -import { ExcludeSettingOptions, TextSearchContextNew, TextSearchMatchNew } from '../../services/search/common/searchExtTypes.js'; +import { ExcludeSettingOptions, TextSearchContext2, TextSearchMatch2 } from '../../services/search/common/searchExtTypes.js'; export interface IExtHostWorkspaceProvider { getWorkspaceFolder2(uri: vscode.Uri, resolveParent?: boolean): Promise; @@ -478,34 +478,9 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac }, token); } - findFiles2(filePattern: vscode.GlobPattern | undefined, - options: vscode.FindFiles2Options = {}, - extensionId: ExtensionIdentifier, - token: vscode.CancellationToken = CancellationToken.None): Promise { - this._logService.trace(`extHostWorkspace#findFiles2: fileSearch, extension: ${extensionId.value}, entryPoint: findFiles2`); - - - const useDefaultExcludes = options.useDefaultExcludes ?? true; - const useDefaultSearchExcludes = options.useDefaultSearchExcludes ?? true; - const excludeSetting = useDefaultExcludes ? - (useDefaultSearchExcludes ? ExcludeSettingOptions.SearchAndFilesExclude : ExcludeSettingOptions.FilesExclude) : - ExcludeSettingOptions.None; - const newOptions: vscode.FindFiles2OptionsNew = { - exclude: options.exclude ? [options.exclude] : undefined, - useIgnoreFiles: { - local: options.useIgnoreFiles, - global: options.useGlobalIgnoreFiles, - parent: options.useParentIgnoreFiles - }, - useExcludeSettings: excludeSetting, - followSymlinks: options.followSymlinks, - maxResults: options.maxResults, - }; - return this._findFilesImpl(undefined, filePattern !== undefined ? [filePattern] : [], newOptions, token); - } - findFiles2New(filePatterns: vscode.GlobPattern[], - options: vscode.FindFiles2OptionsNew = {}, + findFiles2(filePatterns: vscode.GlobPattern[], + options: vscode.FindFiles2Options = {}, extensionId: ExtensionIdentifier, token: vscode.CancellationToken = CancellationToken.None): Promise { this._logService.trace(`extHostWorkspace#findFiles2New: fileSearch, extension: ${extensionId.value}, entryPoint: findFiles2New`); @@ -517,7 +492,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac // `filePattern` is the proper way to handle this, since it takes less precedence than the ignore files. include: vscode.GlobPattern | undefined, filePatterns: vscode.GlobPattern[] | undefined, - options: vscode.FindFiles2OptionsNew, + options: vscode.FindFiles2Options, token: vscode.CancellationToken = CancellationToken.None): Promise { if (token && token.isCancellationRequested) { return Promise.resolve([]); @@ -571,8 +546,8 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac return result.flat(); } - findTextInFilesNew(query: vscode.TextSearchQueryNew, options: vscode.FindTextInFilesOptionsNew | undefined, extensionId: ExtensionIdentifier, token: vscode.CancellationToken = CancellationToken.None): vscode.FindTextInFilesResponse { - this._logService.trace(`extHostWorkspace#findTextInFilesNew: textSearch, extension: ${extensionId.value}, entryPoint: findTextInFilesNew`); + findTextInFiles2(query: vscode.TextSearchQuery2, options: vscode.FindTextInFilesOptions2 | undefined, extensionId: ExtensionIdentifier, token: vscode.CancellationToken = CancellationToken.None): vscode.FindTextInFilesResponse { + this._logService.trace(`extHostWorkspace#findTextInFiles2: textSearch, extension: ${extensionId.value}, entryPoint: findTextInFiles2`); const getOptions = (include: vscode.GlobPattern | undefined): QueryOptions => { @@ -623,12 +598,12 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac (result, uri) => progressEmitter.fire({ result, uri }), token ); - const asyncIterable = new AsyncIterableObject(async emitter => { + const asyncIterable = new AsyncIterableObject(async emitter => { disposables.add(progressEmitter.event(e => { const result = e.result; const uri = e.uri; if (resultIsMatch(result)) { - emitter.emitOne(new TextSearchMatchNew( + emitter.emitOne(new TextSearchMatch2( uri, result.rangeLocations.map((range) => ({ previewRange: new Range(range.preview.startLineNumber, range.preview.startColumn, range.preview.endLineNumber, range.preview.endColumn), @@ -638,7 +613,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac )); } else { - emitter.emitOne(new TextSearchContextNew( + emitter.emitOne(new TextSearchContext2( uri, result.text, result.lineNumber diff --git a/src/vs/workbench/api/node/extHostSearch.ts b/src/vs/workbench/api/node/extHostSearch.ts index c91dd9046075a..879d589c7abd9 100644 --- a/src/vs/workbench/api/node/extHostSearch.ts +++ b/src/vs/workbench/api/node/extHostSearch.ts @@ -161,7 +161,7 @@ export class NativeExtHostSearch extends ExtHostSearch implements IDisposable { return super.$clearCache(cacheKey); } - protected override createTextSearchManager(query: ITextQuery, provider: vscode.TextSearchProviderNew): TextSearchManager { + protected override createTextSearchManager(query: ITextQuery, provider: vscode.TextSearchProvider2): TextSearchManager { return new NativeTextSearchManager(query, provider, undefined, 'textSearchProvider'); } } diff --git a/src/vs/workbench/api/node/proxyResolver.ts b/src/vs/workbench/api/node/proxyResolver.ts index e23039e6c6b7e..8378d4fb98a03 100644 --- a/src/vs/workbench/api/node/proxyResolver.ts +++ b/src/vs/workbench/api/node/proxyResolver.ts @@ -11,16 +11,20 @@ import { ExtHostExtensionService } from './extHostExtensionService.js'; import { URI } from '../../../base/common/uri.js'; import { ILogService, LogLevel as LogServiceLevel } from '../../../platform/log/common/log.js'; import { IExtensionDescription } from '../../../platform/extensions/common/extensions.js'; -import { LogLevel, createHttpPatch, createProxyResolver, createTlsPatch, ProxySupportSetting, ProxyAgentParams, createNetPatch, loadSystemCertificates } from '@vscode/proxy-agent'; +import { LogLevel, createHttpPatch, createProxyResolver, createTlsPatch, ProxySupportSetting, ProxyAgentParams, createNetPatch, loadSystemCertificates, ResolveProxyWithRequest, getOrLoadAdditionalCertificates, LookupProxyAuthorization } from '@vscode/proxy-agent'; import { AuthInfo } from '../../../platform/request/common/request.js'; import { DisposableStore } from '../../../base/common/lifecycle.js'; import { createRequire } from 'node:module'; +import type * as undiciType from 'undici-types'; +import type * as tlsType from 'tls'; +import type * as streamType from 'stream'; const require = createRequire(import.meta.url); const http = require('http'); const https = require('https'); -const tls = require('tls'); +const tls: typeof tlsType = require('tls'); const net = require('net'); +const undici: typeof undiciType = require('undici'); const systemCertificatesV2Default = false; const useElectronFetchDefault = false; @@ -35,8 +39,6 @@ export function connectProxyResolver( disposables: DisposableStore, ) { - patchGlobalFetch(configProvider, mainThreadTelemetry, initData, disposables); - const useHostProxy = initData.environment.useHostProxy; const doUseHostProxy = typeof useHostProxy === 'boolean' ? useHostProxy : !initData.remote.isRemote; const params: ProxyAgentParams = { @@ -86,8 +88,11 @@ export function connectProxyResolver( }, env: process.env, }; - const resolveProxy = createProxyResolver(params); - const lookup = createPatchedModules(params, resolveProxy); + const { resolveProxyWithRequest, resolveProxyURL } = createProxyResolver(params); + + patchGlobalFetch(configProvider, mainThreadTelemetry, initData, resolveProxyURL, params.lookupProxyAuthorization!, getOrLoadAdditionalCertificates.bind(undefined, params), disposables); + + const lookup = createPatchedModules(params, resolveProxyWithRequest); return configureModuleLoading(extensionService, lookup); } @@ -103,20 +108,24 @@ const unsafeHeaders = [ 'set-cookie', ]; -function patchGlobalFetch(configProvider: ExtHostConfigProvider, mainThreadTelemetry: MainThreadTelemetryShape, initData: IExtensionHostInitData, disposables: DisposableStore) { - if (!initData.remote.isRemote && !(globalThis as any).__originalFetch) { +function patchGlobalFetch(configProvider: ExtHostConfigProvider, mainThreadTelemetry: MainThreadTelemetryShape, initData: IExtensionHostInitData, resolveProxyURL: (url: string) => Promise, lookupProxyAuthorization: LookupProxyAuthorization, loadAdditionalCertificates: () => Promise, disposables: DisposableStore) { + if (!(globalThis as any).__vscodeOriginalFetch) { const originalFetch = globalThis.fetch; - (globalThis as any).__originalFetch = originalFetch; - let useElectronFetch = configProvider.getConfiguration('http').get('electronFetch', useElectronFetchDefault); - disposables.add(configProvider.onDidChangeConfiguration(e => { - if (e.affectsConfiguration('http.electronFetch')) { - useElectronFetch = configProvider.getConfiguration('http').get('electronFetch', useElectronFetchDefault); - } - })); - const electron = require('electron'); + (globalThis as any).__vscodeOriginalFetch = originalFetch; + const patchedFetch = patchFetch(originalFetch, configProvider, resolveProxyURL, lookupProxyAuthorization, loadAdditionalCertificates); + (globalThis as any).__vscodePatchedFetch = patchedFetch; + let useElectronFetch = false; + if (!initData.remote.isRemote) { + useElectronFetch = configProvider.getConfiguration('http').get('electronFetch', useElectronFetchDefault); + disposables.add(configProvider.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('http.electronFetch')) { + useElectronFetch = configProvider.getConfiguration('http').get('electronFetch', useElectronFetchDefault); + } + })); + } // https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API - globalThis.fetch = async function fetch(input: any /* RequestInfo */ | URL, init?: RequestInit) { - function getRequestProperty(name: keyof any /* Request */ & keyof RequestInit) { + globalThis.fetch = async function fetch(input: string | URL | Request, init?: RequestInit) { + function getRequestProperty(name: keyof Request & keyof RequestInit) { return init && name in init ? init[name] : typeof input === 'object' && 'cache' in input ? input[name] : undefined; } // Limitations: https://github.com/electron/electron/pull/36733#issuecomment-1405615494 @@ -139,7 +148,7 @@ function patchGlobalFetch(configProvider: ExtHostConfigProvider, mainThreadTelem recordFetchFeatureUse(mainThreadTelemetry, 'integrity'); } if (!useElectronFetch || isDataUrl || isBlobUrl || isManualRedirect || integrity) { - const response = await originalFetch(input, init); + const response = await patchedFetch(input, init, urlString); monitorResponseProperties(mainThreadTelemetry, response, urlString); return response; } @@ -153,6 +162,7 @@ function patchGlobalFetch(configProvider: ExtHostConfigProvider, mainThreadTelem } // Support for URL: https://github.com/electron/electron/issues/43712 const electronInput = input instanceof URL ? input.toString() : input; + const electron = require('electron'); const response = await electron.net.fetch(electronInput, init); monitorResponseProperties(mainThreadTelemetry, response, urlString); return response; @@ -160,6 +170,120 @@ function patchGlobalFetch(configProvider: ExtHostConfigProvider, mainThreadTelem } } +function patchFetch(originalFetch: typeof globalThis.fetch, configProvider: ExtHostConfigProvider, resolveProxyURL: (url: string) => Promise, lookupProxyAuthorization: LookupProxyAuthorization, loadAdditionalCertificates: () => Promise) { + return async function patchedFetch(input: string | URL | Request, init?: RequestInit, urlString?: string) { + const config = configProvider.getConfiguration('http'); + const enabled = config.get('fetchAdditionalSupport'); + if (!enabled) { + return originalFetch(input, init); + } + const proxySupport = config.get('proxySupport') || 'off'; + const doResolveProxy = proxySupport === 'override' || proxySupport === 'fallback' || (proxySupport === 'on' && ((init as any)?.dispatcher) === undefined); + const addCerts = config.get('systemCertificates'); + if (!doResolveProxy && !addCerts) { + return originalFetch(input, init); + } + if (!urlString) { // for testing + urlString = typeof input === 'string' ? input : 'cache' in input ? input.url : input.toString(); + } + const proxyURL = doResolveProxy ? await resolveProxyURL(urlString) : undefined; + if (!proxyURL && !addCerts) { + return originalFetch(input, init); + } + const ca = addCerts ? [...tls.rootCertificates, ...await loadAdditionalCertificates()] : undefined; + const { allowH2, requestCA, proxyCA } = getAgentOptions(ca, init); + if (!proxyURL) { + const modifiedInit = { + ...init, + dispatcher: new undici.Agent({ + allowH2, + connect: { ca: requestCA }, + }) + }; + return originalFetch(input, modifiedInit); + } + + const state: Record = {}; + const proxyAuthorization = await lookupProxyAuthorization(proxyURL, undefined, state); + const modifiedInit = { + ...init, + dispatcher: new undici.ProxyAgent({ + uri: proxyURL, + allowH2, + headers: proxyAuthorization ? { 'Proxy-Authorization': proxyAuthorization } : undefined, + ...(requestCA ? { requestTls: { ca: requestCA } } : {}), + ...(proxyCA ? { proxyTls: { ca: proxyCA } } : {}), + clientFactory: (origin: URL, opts: object): undiciType.Dispatcher => (new undici.Pool(origin, opts) as any).compose((dispatch: undiciType.Dispatcher['dispatch']) => { + class ProxyAuthHandler extends undici.DecoratorHandler { + private abort: ((err?: Error) => void) | undefined; + constructor(private dispatch: undiciType.Dispatcher['dispatch'], private options: undiciType.Dispatcher.DispatchOptions, private handler: undiciType.Dispatcher.DispatchHandlers) { + super(handler); + } + onConnect(abort: (err?: Error) => void): void { + this.abort = abort; + this.handler.onConnect?.(abort); + } + onError(err: Error): void { + if (!(err instanceof ProxyAuthError)) { + return this.handler.onError?.(err); + } + (async () => { + try { + const proxyAuthorization = await lookupProxyAuthorization(proxyURL!, err.proxyAuthenticate, state); + if (proxyAuthorization) { + if (!this.options.headers) { + this.options.headers = ['Proxy-Authorization', proxyAuthorization]; + } else if (Array.isArray(this.options.headers)) { + const i = this.options.headers.findIndex((value, index) => index % 2 === 0 && value.toLowerCase() === 'proxy-authorization'); + if (i === -1) { + this.options.headers.push('Proxy-Authorization', proxyAuthorization); + } else { + this.options.headers[i + 1] = proxyAuthorization; + } + } else { + this.options.headers['Proxy-Authorization'] = proxyAuthorization; + } + this.dispatch(this.options, this); + } else { + this.handler.onError?.(new undici.errors.RequestAbortedError(`Proxy response (407) ?.== 200 when HTTP Tunneling`)); // Mimick undici's behavior + } + } catch (err) { + this.handler.onError?.(err); + } + })(); + } + onUpgrade(statusCode: number, headers: Buffer[] | string[] | null, socket: streamType.Duplex): void { + if (statusCode === 407 && headers) { + const proxyAuthenticate: string[] = []; + for (let i = 0; i < headers.length; i += 2) { + if (headers[i].toString().toLowerCase() === 'proxy-authenticate') { + proxyAuthenticate.push(headers[i + 1].toString()); + } + } + if (proxyAuthenticate.length) { + this.abort?.(new ProxyAuthError(proxyAuthenticate)); + return; + } + } + this.handler.onUpgrade?.(statusCode, headers, socket); + } + } + return function proxyAuthDispatch(options: undiciType.Dispatcher.DispatchOptions, handler: undiciType.Dispatcher.DispatchHandlers) { + return dispatch(options, new ProxyAuthHandler(dispatch, options, handler)); + }; + }), + }) + }; + return originalFetch(input, modifiedInit); + }; +} + +class ProxyAuthError extends Error { + constructor(public proxyAuthenticate: string[]) { + super('Proxy authentication required'); + } +} + function monitorResponseProperties(mainThreadTelemetry: MainThreadTelemetryShape, response: Response, urlString: string) { const originalUrl = response.url; Object.defineProperty(response, 'url', { @@ -220,7 +344,7 @@ function recordFetchFeatureUse(mainThreadTelemetry: MainThreadTelemetryShape, fe } } -function createPatchedModules(params: ProxyAgentParams, resolveProxy: ReturnType) { +function createPatchedModules(params: ProxyAgentParams, resolveProxy: ResolveProxyWithRequest) { function mergeModules(module: any, patch: any) { return Object.assign(module.default || module, patch); @@ -244,7 +368,7 @@ function certSettingV2(configProvider: ExtHostConfigProvider) { return !!http.get('experimental.systemCertificatesV2', systemCertificatesV2Default) && !!http.get('systemCertificates'); } -const modulesCache = new Map(); +const modulesCache = new Map(); function configureModuleLoading(extensionService: ExtHostExtensionService, lookup: ReturnType): Promise { return extensionService.getExtensionPathIndex() .then(extensionPaths => { @@ -259,7 +383,7 @@ function configureModuleLoading(extensionService: ExtHostExtensionService, looku return lookup.tls; } - if (request !== 'http' && request !== 'https') { + if (request !== 'http' && request !== 'https' && request !== 'undici') { return original.apply(this, arguments); } @@ -269,14 +393,68 @@ function configureModuleLoading(extensionService: ExtHostExtensionService, looku modulesCache.set(ext, cache = {}); } if (!cache[request]) { - const mod = lookup[request]; - cache[request] = { ...mod }; // Copy to work around #93167. + if (request === 'undici') { + const undici = original.apply(this, arguments); + patchUndici(undici); + cache[request] = undici; + } else { + const mod = lookup[request]; + cache[request] = { ...mod }; // Copy to work around #93167. + } } return cache[request]; }; }); } +const agentOptions = Symbol('agentOptions'); +const proxyAgentOptions = Symbol('proxyAgentOptions'); + +function patchUndici(undici: typeof undiciType) { + const originalAgent = undici.Agent; + const patchedAgent = function PatchedAgent(opts?: undiciType.Agent.Options): undiciType.Agent { + const agent = new originalAgent(opts); + (agent as any)[agentOptions] = { + ...opts, + ...(opts?.connect && typeof opts?.connect === 'object' ? { connect: { ...opts.connect } } : undefined), + }; + return agent; + }; + patchedAgent.prototype = originalAgent.prototype; + (undici as any).Agent = patchedAgent; + + const originalProxyAgent = undici.ProxyAgent; + const patchedProxyAgent = function PatchedProxyAgent(opts: undiciType.ProxyAgent.Options | string): undiciType.ProxyAgent { + const proxyAgent = new originalProxyAgent(opts); + (proxyAgent as any)[proxyAgentOptions] = typeof opts === 'string' ? opts : { + ...opts, + ...(opts?.connect && typeof opts?.connect === 'object' ? { connect: { ...opts.connect } } : undefined), + }; + return proxyAgent; + }; + patchedProxyAgent.prototype = originalProxyAgent.prototype; + (undici as any).ProxyAgent = patchedProxyAgent; +} + +function getAgentOptions(systemCA: string[] | undefined, requestInit: RequestInit | undefined) { + let allowH2: boolean | undefined; + let requestCA: string | Buffer | Array | undefined = systemCA; + let proxyCA: string | Buffer | Array | undefined = systemCA; + const dispatcher: undiciType.Dispatcher = (requestInit as any)?.dispatcher; + const originalAgentOptions: undiciType.Agent.Options | undefined = dispatcher && (dispatcher as any)[agentOptions]; + if (originalAgentOptions) { + allowH2 = originalAgentOptions.allowH2; + requestCA = originalAgentOptions.connect && typeof originalAgentOptions.connect === 'object' && 'ca' in originalAgentOptions.connect && originalAgentOptions.connect.ca || systemCA; + } + const originalProxyAgentOptions: undiciType.ProxyAgent.Options | string | undefined = dispatcher && (dispatcher as any)[proxyAgentOptions]; + if (originalProxyAgentOptions && typeof originalProxyAgentOptions === 'object') { + allowH2 = originalProxyAgentOptions.allowH2; + requestCA = originalProxyAgentOptions.requestTls && 'ca' in originalProxyAgentOptions.requestTls && originalProxyAgentOptions.requestTls.ca || systemCA; + proxyCA = originalProxyAgentOptions.proxyTls && 'ca' in originalProxyAgentOptions.proxyTls && originalProxyAgentOptions.proxyTls.ca || systemCA; + } + return { allowH2, requestCA, proxyCA }; +} + async function lookupProxyAuthorization( extHostWorkspace: IExtHostWorkspaceProvider, extHostLogService: ILogService, diff --git a/src/vs/workbench/api/test/browser/extHostAuthentication.integrationTest.ts b/src/vs/workbench/api/test/browser/extHostAuthentication.integrationTest.ts index ec42932dfa290..918c097f5693f 100644 --- a/src/vs/workbench/api/test/browser/extHostAuthentication.integrationTest.ts +++ b/src/vs/workbench/api/test/browser/extHostAuthentication.integrationTest.ts @@ -31,6 +31,7 @@ import { IProductService } from '../../../../platform/product/common/productServ import { AuthenticationAccessService, IAuthenticationAccessService } from '../../../services/authentication/browser/authenticationAccessService.js'; import { AuthenticationUsageService, IAuthenticationUsageService } from '../../../services/authentication/browser/authenticationUsageService.js'; import { AuthenticationExtensionsService } from '../../../services/authentication/browser/authenticationExtensionsService.js'; +import { ILogService, NullLogService } from '../../../../platform/log/common/log.js'; class AuthQuickPick { private listener: ((e: IQuickPickDidAcceptEvent) => any) | undefined; @@ -105,6 +106,7 @@ suite('ExtHostAuthentication', () => { suiteSetup(async () => { instantiationService = new TestInstantiationService(); + instantiationService.stub(ILogService, new NullLogService()); instantiationService.stub(IDialogService, new TestDialogService({ confirmed: true })); instantiationService.stub(IStorageService, new TestStorageService()); instantiationService.stub(IQuickInputService, new AuthTestQuickInputService()); diff --git a/src/vs/workbench/api/test/browser/extHostTreeViews.test.ts b/src/vs/workbench/api/test/browser/extHostTreeViews.test.ts index d00b0f7c98865..997f0b33ed81c 100644 --- a/src/vs/workbench/api/test/browser/extHostTreeViews.test.ts +++ b/src/vs/workbench/api/test/browser/extHostTreeViews.test.ts @@ -20,6 +20,16 @@ import { runWithFakedTimers } from '../../../../base/test/common/timeTravelSched import { IExtHostTelemetry } from '../../common/extHostTelemetry.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js'; +function unBatchChildren(result: (number | ITreeItem)[][] | undefined): ITreeItem[] | undefined { + if (!result || result.length === 0) { + return undefined; + } + if (result.length > 1) { + throw new Error('Unexpected result length, all tests are unbatched.'); + } + return result[0].slice(1) as ITreeItem[]; +} + suite('ExtHostTreeView', function () { const store = ensureNoDisposablesAreLeakedInTestSuite(); @@ -96,25 +106,25 @@ suite('ExtHostTreeView', function () { test('construct node tree', () => { return testObject.$getChildren('testNodeTreeProvider') .then(elements => { - const actuals = elements?.map(e => e.handle); + const actuals = unBatchChildren(elements)?.map(e => e.handle); assert.deepStrictEqual(actuals, ['0/0:a', '0/0:b']); return Promise.all([ - testObject.$getChildren('testNodeTreeProvider', '0/0:a') + testObject.$getChildren('testNodeTreeProvider', ['0/0:a']) .then(children => { - const actuals = children?.map(e => e.handle); + const actuals = unBatchChildren(children)?.map(e => e.handle); assert.deepStrictEqual(actuals, ['0/0:a/0:aa', '0/0:a/0:ab']); return Promise.all([ - testObject.$getChildren('testNodeTreeProvider', '0/0:a/0:aa').then(children => assert.strictEqual(children?.length, 0)), - testObject.$getChildren('testNodeTreeProvider', '0/0:a/0:ab').then(children => assert.strictEqual(children?.length, 0)) + testObject.$getChildren('testNodeTreeProvider', ['0/0:a/0:aa']).then(children => assert.strictEqual(unBatchChildren(children)?.length, 0)), + testObject.$getChildren('testNodeTreeProvider', ['0/0:a/0:ab']).then(children => assert.strictEqual(unBatchChildren(children)?.length, 0)) ]); }), - testObject.$getChildren('testNodeTreeProvider', '0/0:b') + testObject.$getChildren('testNodeTreeProvider', ['0/0:b']) .then(children => { - const actuals = children?.map(e => e.handle); + const actuals = unBatchChildren(children)?.map(e => e.handle); assert.deepStrictEqual(actuals, ['0/0:b/0:ba', '0/0:b/0:bb']); return Promise.all([ - testObject.$getChildren('testNodeTreeProvider', '0/0:b/0:ba').then(children => assert.strictEqual(children?.length, 0)), - testObject.$getChildren('testNodeTreeProvider', '0/0:b/0:bb').then(children => assert.strictEqual(children?.length, 0)) + testObject.$getChildren('testNodeTreeProvider', ['0/0:b/0:ba']).then(children => assert.strictEqual(unBatchChildren(children)?.length, 0)), + testObject.$getChildren('testNodeTreeProvider', ['0/0:b/0:bb']).then(children => assert.strictEqual(unBatchChildren(children)?.length, 0)) ]); }) ]); @@ -124,25 +134,25 @@ suite('ExtHostTreeView', function () { test('construct id tree', () => { return testObject.$getChildren('testNodeWithIdTreeProvider') .then(elements => { - const actuals = elements?.map(e => e.handle); + const actuals = unBatchChildren(elements)?.map(e => e.handle); assert.deepStrictEqual(actuals, ['1/a', '1/b']); return Promise.all([ - testObject.$getChildren('testNodeWithIdTreeProvider', '1/a') + testObject.$getChildren('testNodeWithIdTreeProvider', ['1/a']) .then(children => { - const actuals = children?.map(e => e.handle); + const actuals = unBatchChildren(children)?.map(e => e.handle); assert.deepStrictEqual(actuals, ['1/aa', '1/ab']); return Promise.all([ - testObject.$getChildren('testNodeWithIdTreeProvider', '1/aa').then(children => assert.strictEqual(children?.length, 0)), - testObject.$getChildren('testNodeWithIdTreeProvider', '1/ab').then(children => assert.strictEqual(children?.length, 0)) + testObject.$getChildren('testNodeWithIdTreeProvider', ['1/aa']).then(children => assert.strictEqual(unBatchChildren(children)?.length, 0)), + testObject.$getChildren('testNodeWithIdTreeProvider', ['1/ab']).then(children => assert.strictEqual(unBatchChildren(children)?.length, 0)) ]); }), - testObject.$getChildren('testNodeWithIdTreeProvider', '1/b') + testObject.$getChildren('testNodeWithIdTreeProvider', ['1/b']) .then(children => { - const actuals = children?.map(e => e.handle); + const actuals = unBatchChildren(children)?.map(e => e.handle); assert.deepStrictEqual(actuals, ['1/ba', '1/bb']); return Promise.all([ - testObject.$getChildren('testNodeWithIdTreeProvider', '1/ba').then(children => assert.strictEqual(children?.length, 0)), - testObject.$getChildren('testNodeWithIdTreeProvider', '1/bb').then(children => assert.strictEqual(children?.length, 0)) + testObject.$getChildren('testNodeWithIdTreeProvider', ['1/ba']).then(children => assert.strictEqual(unBatchChildren(children)?.length, 0)), + testObject.$getChildren('testNodeWithIdTreeProvider', ['1/bb']).then(children => assert.strictEqual(unBatchChildren(children)?.length, 0)) ]); }) ]); @@ -152,7 +162,7 @@ suite('ExtHostTreeView', function () { test('construct highlights tree', () => { return testObject.$getChildren('testNodeWithHighlightsTreeProvider') .then(elements => { - assert.deepStrictEqual(removeUnsetKeys(elements), [{ + assert.deepStrictEqual(removeUnsetKeys(unBatchChildren(elements)), [{ handle: '1/a', label: { label: 'a', highlights: [[0, 2], [3, 5]] }, collapsibleState: TreeItemCollapsibleState.Collapsed @@ -162,9 +172,9 @@ suite('ExtHostTreeView', function () { collapsibleState: TreeItemCollapsibleState.Collapsed }]); return Promise.all([ - testObject.$getChildren('testNodeWithHighlightsTreeProvider', '1/a') + testObject.$getChildren('testNodeWithHighlightsTreeProvider', ['1/a']) .then(children => { - assert.deepStrictEqual(removeUnsetKeys(children), [{ + assert.deepStrictEqual(removeUnsetKeys(unBatchChildren(children)), [{ handle: '1/aa', parentHandle: '1/a', label: { label: 'aa', highlights: [[0, 2], [3, 5]] }, @@ -176,9 +186,9 @@ suite('ExtHostTreeView', function () { collapsibleState: TreeItemCollapsibleState.None }]); }), - testObject.$getChildren('testNodeWithHighlightsTreeProvider', '1/b') + testObject.$getChildren('testNodeWithHighlightsTreeProvider', ['1/b']) .then(children => { - assert.deepStrictEqual(removeUnsetKeys(children), [{ + assert.deepStrictEqual(removeUnsetKeys(unBatchChildren(children)), [{ handle: '1/ba', parentHandle: '1/b', label: { label: 'ba', highlights: [[0, 2], [3, 5]] }, @@ -206,10 +216,10 @@ suite('ExtHostTreeView', function () { store.add(target.onRefresh.event(() => { testObject.$getChildren('testNodeWithIdTreeProvider') .then(elements => { - const actuals = elements?.map(e => e.handle); + const actuals = unBatchChildren(elements)?.map(e => e.handle); assert.deepStrictEqual(actuals, ['1/a', '1/b']); - return testObject.$getChildren('testNodeWithIdTreeProvider', '1/a') - .then(() => testObject.$getChildren('testNodeWithIdTreeProvider', '1/b')) + return testObject.$getChildren('testNodeWithIdTreeProvider', ['1/a']) + .then(() => testObject.$getChildren('testNodeWithIdTreeProvider', ['1/b'])) .then(() => assert.fail('Should fail with duplicate id')) .catch(() => caughtExpectedError = true) .finally(() => caughtExpectedError ? done() : assert.fail('Expected duplicate id error not thrown.')); @@ -406,7 +416,7 @@ suite('ExtHostTreeView', function () { store.add(target.onRefresh.event(() => { testObject.$getChildren('testNodeTreeProvider') .then(elements => { - assert.deepStrictEqual(elements?.map(e => e.handle), ['0/0:a//0:b']); + assert.deepStrictEqual(unBatchChildren(elements)?.map(e => e.handle), ['0/0:a//0:b']); done(); }); })); @@ -448,11 +458,11 @@ suite('ExtHostTreeView', function () { store.add(target.onRefresh.event(() => { testObject.$getChildren('testNodeTreeProvider') .then(elements => { - const actuals = elements?.map(e => e.handle); + const actuals = unBatchChildren(elements)?.map(e => e.handle); assert.deepStrictEqual(actuals, ['0/0:a', '0/0:b', '0/1:a', '0/0:d', '0/1:b', '0/0:f', '0/2:a']); - return testObject.$getChildren('testNodeTreeProvider', '0/1:b') + return testObject.$getChildren('testNodeTreeProvider', ['0/1:b']) .then(elements => { - const actuals = elements?.map(e => e.handle); + const actuals = unBatchChildren(elements)?.map(e => e.handle); assert.deepStrictEqual(actuals, ['0/1:b/0:h', '0/1:b/1:h', '0/1:b/0:j', '0/1:b/1:j', '0/1:b/2:h']); done(); }); @@ -470,7 +480,7 @@ suite('ExtHostTreeView', function () { store.add(target.onRefresh.event(() => { testObject.$getChildren('testNodeTreeProvider') .then(elements => { - assert.deepStrictEqual(elements?.map(e => e.handle), ['0/0:c']); + assert.deepStrictEqual(unBatchChildren(elements)?.map(e => e.handle), ['0/0:c']); done(); }); })); @@ -485,7 +495,7 @@ suite('ExtHostTreeView', function () { return testObject.$getChildren('testNodeTreeProvider') .then(elements => { - assert.deepStrictEqual(elements?.map(e => e.handle), ['0/0:a', '0/0:b']); + assert.deepStrictEqual(unBatchChildren(elements)?.map(e => e.handle), ['0/0:a', '0/0:b']); }); }); @@ -537,7 +547,7 @@ suite('ExtHostTreeView', function () { parentChain: [{ handle: '0/0:a', label: { label: 'a' }, collapsibleState: TreeItemCollapsibleState.Collapsed }] }; return testObject.$getChildren('treeDataProvider') - .then(() => testObject.$getChildren('treeDataProvider', '0/0:a')) + .then(() => testObject.$getChildren('treeDataProvider', ['0/0:a'])) .then(() => treeView.reveal({ key: 'aa' }) .then(() => { assert.ok(revealTarget.calledOnce); @@ -650,8 +660,13 @@ suite('ExtHostTreeView', function () { }); function loadCompleteTree(treeId: string, element?: string): Promise { - return testObject.$getChildren(treeId, element) - .then(elements => elements?.map(e => loadCompleteTree(treeId, e.handle))) + return testObject.$getChildren(treeId, element ? [element] : undefined) + .then(elements => { + if (!elements || elements?.length === 0) { + return null; + } + return elements[0].slice(1).map(e => loadCompleteTree(treeId, (e as ITreeItem).handle)); + }) .then(() => null); } diff --git a/src/vs/workbench/api/test/browser/extHostWorkspace.test.ts b/src/vs/workbench/api/test/browser/extHostWorkspace.test.ts index 0c019f59b2c5f..1c0bec661f090 100644 --- a/src/vs/workbench/api/test/browser/extHostWorkspace.test.ts +++ b/src/vs/workbench/api/test/browser/extHostWorkspace.test.ts @@ -716,12 +716,12 @@ suite('ExtHostWorkspace', function () { }); const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); - return ws.findFiles2('foo', { maxResults: 10, useDefaultExcludes: true }, new ExtensionIdentifier('test')).then(() => { + return ws.findFiles2(['foo'], { maxResults: 10, useExcludeSettings: ExcludeSettingOptions.FilesExclude }, new ExtensionIdentifier('test')).then(() => { assert(mainThreadCalled, 'mainThreadCalled'); }); }); - function testFindFiles2Include(pattern: RelativePattern) { + function testFindFiles2Include(pattern: RelativePattern[]) { const root = '/project/foo'; const rpcProtocol = new TestRPCProtocol(); @@ -745,11 +745,11 @@ suite('ExtHostWorkspace', function () { } test('RelativePattern include (string)', () => { - return testFindFiles2Include(new RelativePattern('/other/folder', 'glob/**')); + return testFindFiles2Include([new RelativePattern('/other/folder', 'glob/**')]); }); test('RelativePattern include (URI)', () => { - return testFindFiles2Include(new RelativePattern(URI.file('/other/folder'), 'glob/**')); + return testFindFiles2Include([new RelativePattern(URI.file('/other/folder'), 'glob/**')]); }); test('no excludes', () => { @@ -770,7 +770,7 @@ suite('ExtHostWorkspace', function () { }); const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); - return ws.findFiles2(new RelativePattern('/other/folder', 'glob/**'), {}, new ExtensionIdentifier('test')).then(() => { + return ws.findFiles2([new RelativePattern('/other/folder', 'glob/**')], {}, new ExtensionIdentifier('test')).then(() => { assert(mainThreadCalled, 'mainThreadCalled'); }); }); @@ -790,7 +790,7 @@ suite('ExtHostWorkspace', function () { const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); const token = CancellationToken.Cancelled; - return ws.findFiles2(new RelativePattern('/other/folder', 'glob/**'), {}, new ExtensionIdentifier('test'), token).then(() => { + return ws.findFiles2([new RelativePattern('/other/folder', 'glob/**')], {}, new ExtensionIdentifier('test'), token).then(() => { assert(!mainThreadCalled, '!mainThreadCalled'); }); }); @@ -811,7 +811,7 @@ suite('ExtHostWorkspace', function () { }); const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); - return ws.findFiles2('', { exclude: new RelativePattern(root, 'glob/**') }, new ExtensionIdentifier('test')).then(() => { + return ws.findFiles2([''], { exclude: [new RelativePattern(root, 'glob/**')] }, new ExtensionIdentifier('test')).then(() => { assert(mainThreadCalled, 'mainThreadCalled'); }); }); @@ -832,7 +832,7 @@ suite('ExtHostWorkspace', function () { }); const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); - return ws.findFiles2('', { useIgnoreFiles: true, useParentIgnoreFiles: true, useGlobalIgnoreFiles: true }, new ExtensionIdentifier('test')).then(() => { + return ws.findFiles2([''], { useIgnoreFiles: { local: true, parent: true, global: true } }, new ExtensionIdentifier('test')).then(() => { assert(mainThreadCalled, 'mainThreadCalled'); }); }); @@ -851,168 +851,7 @@ suite('ExtHostWorkspace', function () { }); const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); - return ws.findFiles2('', { followSymlinks: true }, new ExtensionIdentifier('test')).then(() => { - assert(mainThreadCalled, 'mainThreadCalled'); - }); - }); - }); - - suite('findFiles2New -', function () { - test('string include', () => { - const root = '/project/foo'; - const rpcProtocol = new TestRPCProtocol(); - - let mainThreadCalled = false; - rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock() { - override $startFileSearch(_includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise { - mainThreadCalled = true; - assert.strictEqual(options.filePattern, 'foo'); - assert.strictEqual(options.includePattern, undefined); - assert.strictEqual(_includeFolder, null); - assert.strictEqual(options.excludePattern, undefined); - assert.strictEqual(options.disregardExcludeSettings, false); - assert.strictEqual(options.maxResults, 10); - return Promise.resolve(null); - } - }); - - const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); - return ws.findFiles2New(['foo'], { maxResults: 10, useExcludeSettings: ExcludeSettingOptions.FilesExclude }, new ExtensionIdentifier('test')).then(() => { - assert(mainThreadCalled, 'mainThreadCalled'); - }); - }); - - function testFindFiles2NewInclude(pattern: RelativePattern[]) { - const root = '/project/foo'; - const rpcProtocol = new TestRPCProtocol(); - - let mainThreadCalled = false; - rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock() { - override $startFileSearch(_includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise { - mainThreadCalled = true; - assert.strictEqual(options.filePattern, 'glob/**'); - assert.strictEqual(options.includePattern, undefined); - assert.deepStrictEqual(_includeFolder ? URI.from(_includeFolder).toJSON() : null, URI.file('/other/folder').toJSON()); - assert.strictEqual(options.excludePattern, undefined); - assert.strictEqual(options.disregardExcludeSettings, false); - return Promise.resolve(null); - } - }); - - const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); - return ws.findFiles2New(pattern, { maxResults: 10 }, new ExtensionIdentifier('test')).then(() => { - assert(mainThreadCalled, 'mainThreadCalled'); - }); - } - - test('RelativePattern include (string)', () => { - return testFindFiles2NewInclude([new RelativePattern('/other/folder', 'glob/**')]); - }); - - test('RelativePattern include (URI)', () => { - return testFindFiles2NewInclude([new RelativePattern(URI.file('/other/folder'), 'glob/**')]); - }); - - test('no excludes', () => { - const root = '/project/foo'; - const rpcProtocol = new TestRPCProtocol(); - - let mainThreadCalled = false; - rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock() { - override $startFileSearch(_includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise { - mainThreadCalled = true; - assert.strictEqual(options.filePattern, 'glob/**'); - assert.strictEqual(options.includePattern, undefined); - assert.deepStrictEqual(URI.revive(_includeFolder!).toString(), URI.file('/other/folder').toString()); - assert.strictEqual(options.excludePattern, undefined); - assert.strictEqual(options.disregardExcludeSettings, false); - return Promise.resolve(null); - } - }); - - const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); - return ws.findFiles2New([new RelativePattern('/other/folder', 'glob/**')], {}, new ExtensionIdentifier('test')).then(() => { - assert(mainThreadCalled, 'mainThreadCalled'); - }); - }); - - test('with cancelled token', () => { - const root = '/project/foo'; - const rpcProtocol = new TestRPCProtocol(); - - let mainThreadCalled = false; - rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock() { - override $startFileSearch(_includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise { - mainThreadCalled = true; - return Promise.resolve(null); - } - }); - - const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); - - const token = CancellationToken.Cancelled; - return ws.findFiles2New([new RelativePattern('/other/folder', 'glob/**')], {}, new ExtensionIdentifier('test'), token).then(() => { - assert(!mainThreadCalled, '!mainThreadCalled'); - }); - }); - - test('RelativePattern exclude', () => { - const root = '/project/foo'; - const rpcProtocol = new TestRPCProtocol(); - - let mainThreadCalled = false; - rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock() { - override $startFileSearch(_includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise { - mainThreadCalled = true; - assert.strictEqual(options.disregardExcludeSettings, false); - assert.strictEqual(options.excludePattern?.length, 1); - assert.strictEqual(options.excludePattern[0].pattern, 'glob/**'); // Note that the base portion is ignored, see #52651 - return Promise.resolve(null); - } - }); - - const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); - return ws.findFiles2New([''], { exclude: [new RelativePattern(root, 'glob/**')] }, new ExtensionIdentifier('test')).then(() => { - assert(mainThreadCalled, 'mainThreadCalled'); - }); - }); - test('useIgnoreFiles', () => { - const root = '/project/foo'; - const rpcProtocol = new TestRPCProtocol(); - - let mainThreadCalled = false; - rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock() { - override $startFileSearch(_includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise { - mainThreadCalled = true; - assert.strictEqual(options.disregardExcludeSettings, false); - assert.strictEqual(options.disregardIgnoreFiles, false); - assert.strictEqual(options.disregardGlobalIgnoreFiles, false); - assert.strictEqual(options.disregardParentIgnoreFiles, false); - return Promise.resolve(null); - } - }); - - const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); - return ws.findFiles2New([''], { useIgnoreFiles: { local: true, parent: true, global: true } }, new ExtensionIdentifier('test')).then(() => { - assert(mainThreadCalled, 'mainThreadCalled'); - }); - }); - - test('use symlinks', () => { - const root = '/project/foo'; - const rpcProtocol = new TestRPCProtocol(); - - let mainThreadCalled = false; - rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock() { - override $startFileSearch(_includeFolder: UriComponents | null, options: IFileQueryBuilderOptions, token: CancellationToken): Promise { - mainThreadCalled = true; - assert.strictEqual(options.ignoreSymlinks, false); - return Promise.resolve(null); - } - }); - - const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); - return ws.findFiles2New([''], { followSymlinks: true }, new ExtensionIdentifier('test')).then(() => { + return ws.findFiles2([''], { followSymlinks: true }, new ExtensionIdentifier('test')).then(() => { assert(mainThreadCalled, 'mainThreadCalled'); }); }); @@ -1126,7 +965,7 @@ suite('ExtHostWorkspace', function () { }); }); - suite('findTextInFilesNew -', function () { + suite('findTextInFiles2 -', function () { test('no include', async () => { const root = '/project/foo'; const rpcProtocol = new TestRPCProtocol(); @@ -1144,7 +983,7 @@ suite('ExtHostWorkspace', function () { }); const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); - await (ws.findTextInFilesNew({ pattern: 'foo' }, {}, new ExtensionIdentifier('test'))).complete; + await (ws.findTextInFiles2({ pattern: 'foo' }, {}, new ExtensionIdentifier('test'))).complete; assert(mainThreadCalled, 'mainThreadCalled'); }); @@ -1165,7 +1004,7 @@ suite('ExtHostWorkspace', function () { }); const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); - await (ws.findTextInFilesNew({ pattern: 'foo' }, { include: ['**/files'] }, new ExtensionIdentifier('test'))).complete; + await (ws.findTextInFiles2({ pattern: 'foo' }, { include: ['**/files'] }, new ExtensionIdentifier('test'))).complete; assert(mainThreadCalled, 'mainThreadCalled'); }); @@ -1186,7 +1025,7 @@ suite('ExtHostWorkspace', function () { }); const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); - await (ws.findTextInFilesNew({ pattern: 'foo' }, { include: [new RelativePattern('/other/folder', 'glob/**')] }, new ExtensionIdentifier('test'))).complete; + await (ws.findTextInFiles2({ pattern: 'foo' }, { include: [new RelativePattern('/other/folder', 'glob/**')] }, new ExtensionIdentifier('test'))).complete; assert(mainThreadCalled, 'mainThreadCalled'); }); @@ -1204,7 +1043,7 @@ suite('ExtHostWorkspace', function () { const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); const token = CancellationToken.Cancelled; - await (ws.findTextInFilesNew({ pattern: 'foo' }, undefined, new ExtensionIdentifier('test'), token)).complete; + await (ws.findTextInFiles2({ pattern: 'foo' }, undefined, new ExtensionIdentifier('test'), token)).complete; assert(!mainThreadCalled, '!mainThreadCalled'); }); @@ -1226,7 +1065,7 @@ suite('ExtHostWorkspace', function () { }); const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); - await (ws.findTextInFilesNew({ pattern: 'foo' }, { exclude: [new RelativePattern('/other/folder', 'glob/**')] }, new ExtensionIdentifier('test'))).complete; + await (ws.findTextInFiles2({ pattern: 'foo' }, { exclude: [new RelativePattern('/other/folder', 'glob/**')] }, new ExtensionIdentifier('test'))).complete; assert(mainThreadCalled, 'mainThreadCalled'); }); diff --git a/src/vs/workbench/api/test/browser/mainThreadTreeViews.test.ts b/src/vs/workbench/api/test/browser/mainThreadTreeViews.test.ts index ee16dbe37d741..152b128f35ad4 100644 --- a/src/vs/workbench/api/test/browser/mainThreadTreeViews.test.ts +++ b/src/vs/workbench/api/test/browser/mainThreadTreeViews.test.ts @@ -32,8 +32,8 @@ suite('MainThreadHostTreeView', function () { } class MockExtHostTreeViewsShape extends mock() { - override async $getChildren(treeViewId: string, treeItemHandle?: string): Promise { - return [{ handle: 'testItem1', collapsibleState: TreeItemCollapsibleState.Expanded, customProp: customValue }]; + override async $getChildren(treeViewId: string, treeItemHandle?: string[]): Promise<(number | ITreeItem)[][]> { + return [[0, { handle: 'testItem1', collapsibleState: TreeItemCollapsibleState.Expanded, customProp: customValue }]]; } override async $hasResolve(): Promise { diff --git a/src/vs/workbench/api/test/node/extHostSearch.test.ts b/src/vs/workbench/api/test/node/extHostSearch.test.ts index 75e6b2f34c6bd..94380c10ec4f6 100644 --- a/src/vs/workbench/api/test/node/extHostSearch.test.ts +++ b/src/vs/workbench/api/test/node/extHostSearch.test.ts @@ -170,7 +170,7 @@ suite('ExtHostSearch', () => { this._pfs = mockPFS as any; } - protected override createTextSearchManager(query: ITextQuery, provider: vscode.TextSearchProviderNew): TextSearchManager { + protected override createTextSearchManager(query: ITextQuery, provider: vscode.TextSearchProvider2): TextSearchManager { return new NativeTextSearchManager(query, provider, this._pfs); } }); diff --git a/src/vs/workbench/browser/actions.ts b/src/vs/workbench/browser/actions.ts index 83f4cf4a45586..437ca7ff48bdb 100644 --- a/src/vs/workbench/browser/actions.ts +++ b/src/vs/workbench/browser/actions.ts @@ -8,7 +8,7 @@ import { Disposable, DisposableStore, IDisposable } from '../../base/common/life import { Emitter, Event } from '../../base/common/event.js'; import { MenuId, IMenuService, IMenu, SubmenuItemAction, IMenuActionOptions } from '../../platform/actions/common/actions.js'; import { IContextKeyService } from '../../platform/contextkey/common/contextkey.js'; -import { createAndFillInActionBarActions } from '../../platform/actions/browser/menuEntryActionViewItem.js'; +import { getActionBarActions } from '../../platform/actions/browser/menuEntryActionViewItem.js'; class MenuActions extends Disposable { @@ -41,9 +41,9 @@ class MenuActions extends Disposable { private updateActions(): void { this.disposables.clear(); - this._primaryActions = []; - this._secondaryActions = []; - createAndFillInActionBarActions(this.menu, this.options, { primary: this._primaryActions, secondary: this._secondaryActions }); + const newActions = getActionBarActions(this.menu.getActions(this.options)); + this._primaryActions = newActions.primary; + this._secondaryActions = newActions.secondary; this.disposables.add(this.updateSubmenus([...this._primaryActions, ...this._secondaryActions], {})); this._onDidChange.fire(); } @@ -93,13 +93,11 @@ export class CompositeMenuActions extends Disposable { } getContextMenuActions(): IAction[] { - const actions: IAction[] = []; - if (this.contextMenuId) { const menu = this.menuService.getMenuActions(this.contextMenuId, this.contextKeyService, this.options); - createAndFillInActionBarActions(menu, { primary: [], secondary: actions }); + return getActionBarActions(menu).secondary; } - return actions; + return []; } } diff --git a/src/vs/workbench/browser/actions/developerActions.ts b/src/vs/workbench/browser/actions/developerActions.ts index bf0ca0f7b2d0a..468b22e85c8cf 100644 --- a/src/vs/workbench/browser/actions/developerActions.ts +++ b/src/vs/workbench/browser/actions/developerActions.ts @@ -11,7 +11,8 @@ import { DomEmitter } from '../../../base/browser/event.js'; import { Color } from '../../../base/common/color.js'; import { Emitter, Event } from '../../../base/common/event.js'; import { IDisposable, toDisposable, dispose, DisposableStore, setDisposableTracker, DisposableTracker, DisposableInfo } from '../../../base/common/lifecycle.js'; -import { getDomNodePagePosition, createStyleSheet, createCSSRule, append, $, getActiveDocument, onDidRegisterWindow, getWindows } from '../../../base/browser/dom.js'; +import { getDomNodePagePosition, append, $, getActiveDocument, onDidRegisterWindow, getWindows } from '../../../base/browser/dom.js'; +import { createCSSRule, createStyleSheet } from '../../../base/browser/domStylesheets.js'; import { IConfigurationService } from '../../../platform/configuration/common/configuration.js'; import { ContextKeyExpr, IContextKeyService, RawContextKey } from '../../../platform/contextkey/common/contextkey.js'; import { Context } from '../../../platform/contextkey/browser/contextKeyService.js'; diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 9946f38f356e2..d835ce1dc9ff8 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -121,6 +121,7 @@ export const TITLE_BAR_SETTINGS = [ LayoutSettings.COMMAND_CENTER, LayoutSettings.EDITOR_ACTIONS_LOCATION, LayoutSettings.LAYOUT_ACTIONS, + 'workbench.navigationControl.enabled', 'window.menuBarVisibility', TitleBarSetting.TITLE_BAR_STYLE, TitleBarSetting.CUSTOM_TITLE_BAR_VISIBILITY, diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index 0dafe5609aaec..0935255446a36 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -29,15 +29,15 @@ import { IPaneCompositePart } from '../paneCompositePart.js'; import { IPaneCompositeBarOptions, PaneCompositeBar } from '../paneCompositeBar.js'; import { GlobalCompositeBar } from '../globalCompositeBar.js'; import { IStorageService } from '../../../../platform/storage/common/storage.js'; -import { Action2, IAction2Options, IMenuService, MenuId, MenuRegistry, registerAction2 } from '../../../../platform/actions/common/actions.js'; +import { Action2, IMenuService, MenuId, MenuRegistry, registerAction2 } from '../../../../platform/actions/common/actions.js'; import { ContextKeyExpr, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; import { Categories } from '../../../../platform/action/common/actionCommonCategories.js'; -import { createAndFillInContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { getContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { IViewDescriptorService, ViewContainerLocation, ViewContainerLocationToString } from '../../../common/views.js'; -import { IPaneCompositePartService } from '../../../services/panecomposite/browser/panecomposite.js'; import { IExtensionService } from '../../../services/extensions/common/extensions.js'; import { IWorkbenchEnvironmentService } from '../../../services/environment/common/environmentService.js'; import { IViewsService } from '../../../services/views/common/viewsService.js'; +import { SwitchCompositeViewAction } from '../compositeBarActions.js'; export class ActivitybarPart extends Part { @@ -370,8 +370,7 @@ export class ActivityBarCompositeBar extends PaneCompositeBar { getActivityBarContextMenuActions(): IAction[] { const activityBarPositionMenu = this.menuService.getMenuActions(MenuId.ActivityBarPositionMenu, this.contextKeyService, { shouldForwardArgs: true, renderShortTitle: true }); - const positionActions: IAction[] = []; - createAndFillInContextMenuActions(activityBarPositionMenu, { primary: [], secondary: positionActions }); + const positionActions = getContextMenuActions(activityBarPositionMenu).secondary; return [ new SubmenuAction('workbench.action.panel.position', localize('activity bar position', "Activity Bar Position"), positionActions), toAction({ id: ToggleSidebarPositionAction.ID, label: ToggleSidebarPositionAction.getLabel(this.layoutService), run: () => this.instantiationService.invokeFunction(accessor => new ToggleSidebarPositionAction().run(accessor)) }) @@ -507,61 +506,27 @@ MenuRegistry.appendMenuItem(MenuId.ViewTitleContext, { order: 1 }); -class SwitchSideBarViewAction extends Action2 { - - constructor( - desc: Readonly, - private readonly offset: number - ) { - super(desc); - } - - async run(accessor: ServicesAccessor): Promise { - const paneCompositeService = accessor.get(IPaneCompositePartService); - - const visibleViewletIds = paneCompositeService.getVisiblePaneCompositeIds(ViewContainerLocation.Sidebar); - - const activeViewlet = paneCompositeService.getActivePaneComposite(ViewContainerLocation.Sidebar); - if (!activeViewlet) { - return; - } - let targetViewletId: string | undefined; - for (let i = 0; i < visibleViewletIds.length; i++) { - if (visibleViewletIds[i] === activeViewlet.getId()) { - targetViewletId = visibleViewletIds[(i + visibleViewletIds.length + this.offset) % visibleViewletIds.length]; - break; - } - } - - await paneCompositeService.openPaneComposite(targetViewletId, ViewContainerLocation.Sidebar, true); - } -} - -registerAction2( - class PreviousSideBarViewAction extends SwitchSideBarViewAction { - constructor() { - super({ - id: 'workbench.action.previousSideBarView', - title: localize2('previousSideBarView', 'Previous Primary Side Bar View'), - category: Categories.View, - f1: true - }, -1); - } +registerAction2(class extends SwitchCompositeViewAction { + constructor() { + super({ + id: 'workbench.action.previousSideBarView', + title: localize2('previousSideBarView', 'Previous Primary Side Bar View'), + category: Categories.View, + f1: true + }, ViewContainerLocation.Sidebar, -1); } -); +}); -registerAction2( - class NextSideBarViewAction extends SwitchSideBarViewAction { - constructor() { - super({ - id: 'workbench.action.nextSideBarView', - title: localize2('nextSideBarView', 'Next Primary Side Bar View'), - category: Categories.View, - f1: true - }, 1); - } +registerAction2(class extends SwitchCompositeViewAction { + constructor() { + super({ + id: 'workbench.action.nextSideBarView', + title: localize2('nextSideBarView', 'Next Primary Side Bar View'), + category: Categories.View, + f1: true + }, ViewContainerLocation.Sidebar, 1); } -); +}); registerAction2( class FocusActivityBarAction extends Action2 { diff --git a/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarActions.ts b/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarActions.ts index b2c7e5f01c77a..4d24cad6198c4 100644 --- a/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarActions.ts +++ b/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarActions.ts @@ -16,7 +16,7 @@ import { IPaneCompositePartService } from '../../../services/panecomposite/brows import { ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js'; import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js'; - +import { SwitchCompositeViewAction } from '../compositeBarActions.js'; const auxiliaryBarRightIcon = registerIcon('auxiliarybar-right-layout-icon', Codicon.layoutSidebarRight, localize('toggleAuxiliaryIconRight', 'Icon to toggle the auxiliary bar off in its right position.')); const auxiliaryBarRightOffIcon = registerIcon('auxiliarybar-right-off-layout-icon', Codicon.layoutSidebarRightOff, localize('toggleAuxiliaryIconRightOn', 'Icon to toggle the auxiliary bar on in its right position.')); @@ -136,3 +136,25 @@ MenuRegistry.appendMenuItems([ } } ]); + +registerAction2(class extends SwitchCompositeViewAction { + constructor() { + super({ + id: 'workbench.action.previousAuxiliaryBarView', + title: localize2('previousAuxiliaryBarView', 'Previous Secondary Side Bar View'), + category: Categories.View, + f1: true + }, ViewContainerLocation.AuxiliaryBar, -1); + } +}); + +registerAction2(class extends SwitchCompositeViewAction { + constructor() { + super({ + id: 'workbench.action.nextAuxiliaryBarView', + title: localize2('nextAuxiliaryBarView', 'Next Secondary Side Bar View'), + category: Categories.View, + f1: true + }, ViewContainerLocation.AuxiliaryBar, 1); + } +}); diff --git a/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts b/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts index 264794c159260..2524f73353d32 100644 --- a/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts +++ b/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts @@ -30,7 +30,7 @@ import { ActionsOrientation, IActionViewItem, prepareActions } from '../../../.. import { IPaneCompositeBarOptions } from '../paneCompositeBar.js'; import { IMenuService, MenuId } from '../../../../platform/actions/common/actions.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; -import { createAndFillInContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { getContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { $ } from '../../../../base/browser/dom.js'; import { HiddenItemStrategy, WorkbenchToolBar } from '../../../../platform/actions/browser/toolbar.js'; import { ActionViewItem, IActionViewItemOptions } from '../../../../base/browser/ui/actionbar/actionViewItems.js'; @@ -185,15 +185,17 @@ export class AuxiliaryBarPart extends AbstractPaneCompositePart { private fillExtraContextMenuActions(actions: IAction[]): void { const currentPositionRight = this.layoutService.getSideBarPosition() === Position.LEFT; - const viewsSubmenuAction = this.getViewsSubmenuAction(); - if (viewsSubmenuAction) { - actions.push(new Separator()); - actions.push(viewsSubmenuAction); + + if (this.getCompositeBarPosition() === CompositeBarPosition.TITLE) { + const viewsSubmenuAction = this.getViewsSubmenuAction(); + if (viewsSubmenuAction) { + actions.push(new Separator()); + actions.push(viewsSubmenuAction); + } } const activityBarPositionMenu = this.menuService.getMenuActions(MenuId.ActivityBarPositionMenu, this.contextKeyService, { shouldForwardArgs: true, renderShortTitle: true }); - const positionActions: IAction[] = []; - createAndFillInContextMenuActions(activityBarPositionMenu, { primary: [], secondary: positionActions }); + const positionActions = getContextMenuActions(activityBarPositionMenu).secondary; actions.push(...[ new Separator(), diff --git a/src/vs/workbench/browser/parts/compositeBar.ts b/src/vs/workbench/browser/parts/compositeBar.ts index 9af8928288c3e..4da7a57e952e7 100644 --- a/src/vs/workbench/browser/parts/compositeBar.ts +++ b/src/vs/workbench/browser/parts/compositeBar.ts @@ -49,16 +49,23 @@ export class CompositeDragAndDrop implements ICompositeDragAndDrop { if (dragData.type === 'composite') { const currentContainer = this.viewDescriptorService.getViewContainerById(dragData.id)!; const currentLocation = this.viewDescriptorService.getViewContainerLocation(currentContainer); + let moved = false; // ... on the same composite bar if (currentLocation === this.targetContainerLocation) { if (targetCompositeId) { this.moveComposite(dragData.id, targetCompositeId, before); + moved = true; } } // ... on a different composite bar else { this.viewDescriptorService.moveViewContainerToLocation(currentContainer, this.targetContainerLocation, this.getTargetIndex(targetCompositeId, before), 'dnd'); + moved = true; + } + + if (moved) { + this.openComposite(currentContainer.id, true); } } diff --git a/src/vs/workbench/browser/parts/compositeBarActions.ts b/src/vs/workbench/browser/parts/compositeBarActions.ts index 89876b6dffc61..e22d2dd19e61e 100644 --- a/src/vs/workbench/browser/parts/compositeBarActions.ts +++ b/src/vs/workbench/browser/parts/compositeBarActions.ts @@ -11,7 +11,7 @@ import { toDisposable, DisposableStore, MutableDisposable } from '../../../base/ import { IContextMenuService } from '../../../platform/contextview/browser/contextView.js'; import { IThemeService, IColorTheme } from '../../../platform/theme/common/themeService.js'; import { NumberBadge, IBadge, IActivity, ProgressBadge, IconBadge } from '../../services/activity/common/activity.js'; -import { IInstantiationService } from '../../../platform/instantiation/common/instantiation.js'; +import { IInstantiationService, ServicesAccessor } from '../../../platform/instantiation/common/instantiation.js'; import { DelayedDragHandler } from '../../../base/browser/dnd.js'; import { IKeybindingService } from '../../../platform/keybinding/common/keybinding.js'; import { Emitter, Event } from '../../../base/common/event.js'; @@ -21,12 +21,14 @@ import { BaseActionViewItem, IActionViewItemOptions } from '../../../base/browse import { Codicon } from '../../../base/common/codicons.js'; import { ThemeIcon } from '../../../base/common/themables.js'; import { IHoverService } from '../../../platform/hover/browser/hover.js'; -import { RunOnceScheduler } from '../../../base/common/async.js'; import { IConfigurationService } from '../../../platform/configuration/common/configuration.js'; import { HoverPosition } from '../../../base/browser/ui/hover/hoverWidget.js'; import { URI } from '../../../base/common/uri.js'; import { badgeBackground, badgeForeground, contrastBorder } from '../../../platform/theme/common/colorRegistry.js'; -import type { IHoverWidget } from '../../../base/browser/ui/hover/hover.js'; +import { Action2, IAction2Options } from '../../../platform/actions/common/actions.js'; +import { ViewContainerLocation } from '../../common/views.js'; +import { IPaneCompositePartService } from '../../services/panecomposite/browser/panecomposite.js'; +import { createConfigureKeybindingAction } from '../../../platform/actions/common/menuService.js'; export interface ICompositeBar { @@ -151,8 +153,6 @@ export interface ICompositeBarActionViewItemOptions extends IActionViewItemOptio export class CompositeBarActionViewItem extends BaseActionViewItem { - private static hoverLeaveTime = 0; - protected container!: HTMLElement; protected label!: HTMLElement; protected badge!: HTMLElement; @@ -163,10 +163,6 @@ export class CompositeBarActionViewItem extends BaseActionViewItem { private mouseUpTimeout: any; private keybindingLabel: string | undefined | null; - private readonly hoverDisposables = this._register(new DisposableStore()); - private lastHover: IHoverWidget | undefined; - private readonly showHoverScheduler = new RunOnceScheduler(() => this.showHover(), 0); - constructor( action: CompositeBarAction, options: ICompositeBarActionViewItemOptions, @@ -184,7 +180,6 @@ export class CompositeBarActionViewItem extends BaseActionViewItem { this._register(action.onDidChangeCompositeBarActionItem(() => this.update())); this._register(Event.filter(keybindingService.onDidUpdateKeybindings, () => this.keybindingLabel !== this.computeKeybindingLabel())(() => this.updateTitle())); this._register(action.onDidChangeActivity(() => this.updateActivity())); - this._register(toDisposable(() => this.showHoverScheduler.cancel())); } protected get compositeBarActionItem(): ICompositeBarActionItem { @@ -263,6 +258,20 @@ export class CompositeBarActionViewItem extends BaseActionViewItem { }, 800); // delayed to prevent focus feedback from showing on mouse up })); + this._register(this.hoverService.setupDelayedHover(this.container, () => ({ + content: this.computeTitle(), + position: { + hoverPosition: this.options.hoverOptions.position(), + }, + persistence: { + hideOnKeyDown: true, + }, + appearance: { + showPointer: true, + compact: true, + } + }), { groupId: 'composite-bar-actions' })); + // Label this.label = append(container, $('a')); @@ -277,7 +286,7 @@ export class CompositeBarActionViewItem extends BaseActionViewItem { this.update(); this.updateStyles(); - this.updateHover(); + this.updateTitle(); } private onThemeChange(theme: IColorTheme): void { @@ -408,58 +417,6 @@ export class CompositeBarActionViewItem extends BaseActionViewItem { return keybinding?.getLabel(); } - private updateHover(): void { - this.hoverDisposables.clear(); - - this.updateTitle(); - - this.hoverDisposables.add(addDisposableListener(this.container, EventType.MOUSE_OVER, () => { - if (!this.showHoverScheduler.isScheduled()) { - if (Date.now() - CompositeBarActionViewItem.hoverLeaveTime < 200) { - this.showHover(true); - } else { - this.showHoverScheduler.schedule(this.configurationService.getValue('workbench.hover.delay')); - } - } - }, true)); - - this.hoverDisposables.add(addDisposableListener(this.container, EventType.MOUSE_LEAVE, e => { - if (e.target === this.container) { - CompositeBarActionViewItem.hoverLeaveTime = Date.now(); - this.hoverService.hideHover(); - this.showHoverScheduler.cancel(); - } - }, true)); - - this.hoverDisposables.add(toDisposable(() => { - this.hoverService.hideHover(); - this.showHoverScheduler.cancel(); - })); - } - - showHover(skipFadeInAnimation: boolean = false): void { - if (this.lastHover && !this.lastHover.isDisposed) { - return; - } - - const hoverPosition = this.options.hoverOptions.position(); - this.lastHover = this.hoverService.showHover({ - target: this.container, - content: this.computeTitle(), - position: { - hoverPosition, - }, - persistence: { - hideOnKeyDown: true, - }, - appearance: { - showPointer: true, - compact: true, - skipFadeInAnimation, - } - }); - } - override dispose(): void { super.dispose(); @@ -569,6 +526,7 @@ export class CompositeActionViewItem extends CompositeBarActionViewItem { @IThemeService themeService: IThemeService, @IHoverService hoverService: IHoverService, @IConfigurationService configurationService: IConfigurationService, + @ICommandService private readonly commandService: ICommandService ) { super( compositeActivityAction, @@ -678,7 +636,13 @@ export class CompositeActionViewItem extends CompositeBarActionViewItem { } private showContextMenu(container: HTMLElement): void { - const actions: IAction[] = [this.toggleCompositePinnedAction, this.toggleCompositeBadgeAction]; + const actions: IAction[] = []; + + if (this.compositeBarActionItem.keybindingId) { + actions.push(createConfigureKeybindingAction(this.commandService, this.keybindingService, this.compositeBarActionItem.keybindingId)); + } + + actions.push(this.toggleCompositePinnedAction, this.toggleCompositeBadgeAction); const compositeContextMenuActions = this.compositeContextMenuActionsProvider(this.compositeBarActionItem.id); if (compositeContextMenuActions.length) { @@ -798,3 +762,36 @@ export class ToggleCompositeBadgeAction extends Action { this.compositeBar.toggleBadgeEnablement(id); } } + +export class SwitchCompositeViewAction extends Action2 { + constructor( + desc: Readonly, + private readonly location: ViewContainerLocation, + private readonly offset: number + ) { + super(desc); + } + + async run(accessor: ServicesAccessor): Promise { + const paneCompositeService = accessor.get(IPaneCompositePartService); + + const activeComposite = paneCompositeService.getActivePaneComposite(this.location); + if (!activeComposite) { + return; + } + + let targetCompositeId: string | undefined; + + const visibleCompositeIds = paneCompositeService.getVisiblePaneCompositeIds(this.location); + for (let i = 0; i < visibleCompositeIds.length; i++) { + if (visibleCompositeIds[i] === activeComposite.getId()) { + targetCompositeId = visibleCompositeIds[(i + visibleCompositeIds.length + this.offset) % visibleCompositeIds.length]; + break; + } + } + + if (typeof targetCompositeId !== 'undefined') { + await paneCompositeService.openPaneComposite(targetCompositeId, this.location, true); + } + } +} diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index 8fa44fe2e1bfe..c976a9565e3ff 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts @@ -6,7 +6,6 @@ import * as dom from '../../../../base/browser/dom.js'; import { StandardMouseEvent } from '../../../../base/browser/mouseEvent.js'; import { BreadcrumbsItem, BreadcrumbsWidget, IBreadcrumbsItemEvent, IBreadcrumbsWidgetStyles } from '../../../../base/browser/ui/breadcrumbs/breadcrumbsWidget.js'; -import { tail } from '../../../../base/common/arrays.js'; import { timeout } from '../../../../base/common/async.js'; import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js'; import { combinedDisposable, DisposableStore, MutableDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; @@ -638,7 +637,7 @@ function focusAndSelectHandler(accessor: ServicesAccessor, select: boolean): voi const breadcrumbs = accessor.get(IBreadcrumbsService); const widget = breadcrumbs.getWidget(groups.activeGroup.id); if (widget) { - const item = tail(widget.getItems()); + const item = widget.getItems().at(-1); widget.setFocused(item); if (select) { widget.setSelection(item, BreadcrumbsControl.Payload_Pick); diff --git a/src/vs/workbench/browser/parts/editor/editorActions.ts b/src/vs/workbench/browser/parts/editor/editorActions.ts index 1b1a39b30cacc..2deaa49417b4c 100644 --- a/src/vs/workbench/browser/parts/editor/editorActions.ts +++ b/src/vs/workbench/browser/parts/editor/editorActions.ts @@ -1425,7 +1425,7 @@ export class NavigateForwardAction extends Action2 { }, menu: [ { id: MenuId.MenubarGoMenu, group: '1_history_nav', order: 2 }, - { id: MenuId.CommandCenter, order: 2 } + { id: MenuId.CommandCenter, order: 2, when: ContextKeyExpr.has('config.workbench.navigationControl.enabled') } ] }); } @@ -1460,7 +1460,7 @@ export class NavigateBackwardsAction extends Action2 { }, menu: [ { id: MenuId.MenubarGoMenu, group: '1_history_nav', order: 1 }, - { id: MenuId.CommandCenter, order: 1 } + { id: MenuId.CommandCenter, order: 1, when: ContextKeyExpr.has('config.workbench.navigationControl.enabled') } ] }); } diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index 8e9756045fb5f..2b7d3ed86ebf2 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -5,7 +5,7 @@ import './media/editorgroupview.css'; import { EditorGroupModel, IEditorOpenOptions, IGroupModelChangeEvent, ISerializedEditorGroupModel, isGroupEditorCloseEvent, isGroupEditorOpenEvent, isSerializedEditorGroupModel } from '../../../common/editor/editorGroupModel.js'; -import { GroupIdentifier, CloseDirection, IEditorCloseEvent, IEditorPane, SaveReason, IEditorPartOptionsChangeEvent, EditorsOrder, IVisibleEditorPane, EditorResourceAccessor, EditorInputCapabilities, IUntypedEditorInput, DEFAULT_EDITOR_ASSOCIATION, SideBySideEditor, EditorCloseContext, IEditorWillMoveEvent, IEditorWillOpenEvent, IMatchEditorOptions, GroupModelChangeKind, IActiveEditorChangeEvent, IFindEditorOptions, IToolbarActions, TEXT_DIFF_EDITOR_ID } from '../../../common/editor.js'; +import { GroupIdentifier, CloseDirection, IEditorCloseEvent, IEditorPane, SaveReason, IEditorPartOptionsChangeEvent, EditorsOrder, IVisibleEditorPane, EditorResourceAccessor, EditorInputCapabilities, IUntypedEditorInput, DEFAULT_EDITOR_ASSOCIATION, SideBySideEditor, EditorCloseContext, IEditorWillMoveEvent, IEditorWillOpenEvent, IMatchEditorOptions, GroupModelChangeKind, IActiveEditorChangeEvent, IFindEditorOptions, TEXT_DIFF_EDITOR_ID } from '../../../common/editor.js'; import { ActiveEditorGroupLockedContext, ActiveEditorDirtyContext, EditorGroupEditorsCountContext, ActiveEditorStickyContext, ActiveEditorPinnedContext, ActiveEditorLastInGroupContext, ActiveEditorFirstInGroupContext, ResourceContextKey, applyAvailableEditorIds, ActiveEditorAvailableEditorIdsContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorContext, ActiveEditorReadonlyContext, ActiveEditorCanRevertContext, ActiveEditorCanToggleReadonlyContext, ActiveCompareEditorCanSwapContext, MultipleEditorsSelectedInGroupContext, TwoEditorsSelectedInGroupContext, SelectedEditorsInGroupFileOrUntitledResourceContextKey } from '../../../common/contextkeys.js'; import { EditorInput } from '../../../common/editor/editorInput.js'; import { SideBySideEditorInput } from '../../../common/editor/sideBySideEditorInput.js'; @@ -31,10 +31,10 @@ import { EventType as TouchEventType, GestureEvent } from '../../../../base/brow import { IEditorGroupsView, IEditorGroupView, fillActiveEditorViewState, EditorServiceImpl, IEditorGroupTitleHeight, IInternalEditorOpenOptions, IInternalMoveCopyOptions, IInternalEditorCloseOptions, IInternalEditorTitleControlOptions, IEditorPartsView, IEditorGroupViewOptions } from './editor.js'; import { ActionBar } from '../../../../base/browser/ui/actionbar/actionbar.js'; import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; -import { IAction, SubmenuAction } from '../../../../base/common/actions.js'; +import { SubmenuAction } from '../../../../base/common/actions.js'; import { IMenuService, MenuId } from '../../../../platform/actions/common/actions.js'; import { StandardMouseEvent } from '../../../../base/browser/mouseEvent.js'; -import { createAndFillInActionBarActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { getActionBarActions, PrimaryAndSecondaryActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js'; import { IEditorService } from '../../../services/editor/common/editorService.js'; import { hash } from '../../../../base/common/hash.js'; @@ -419,16 +419,13 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Toolbar actions const containerToolbarMenu = this._register(this.menuService.createMenu(MenuId.EmptyEditorGroup, this.scopedContextKeyService)); const updateContainerToolbar = () => { - const actions: IToolbarActions = { primary: [], secondary: [] }; // Clear old actions this.containerToolBarMenuDisposable.value = toDisposable(() => containerToolbar.clear()); // Create new actions - createAndFillInActionBarActions( - containerToolbarMenu, - { arg: { groupId: this.id }, shouldForwardArgs: true }, - actions, + const actions = getActionBarActions( + containerToolbarMenu.getActions({ arg: { groupId: this.id }, shouldForwardArgs: true }), 'navigation' ); @@ -2092,8 +2089,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { //#region Editor Actions createEditorActions(disposables: DisposableStore): IActiveEditorActions { - const primary: IAction[] = []; - const secondary: IAction[] = []; + let actions: PrimaryAndSecondaryActions = { primary: [], secondary: [] }; let onDidChange; @@ -2106,10 +2102,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView { const shouldInlineGroup = (action: SubmenuAction, group: string) => group === 'navigation' && action.actions.length <= 1; - createAndFillInActionBarActions( - editorTitleMenu, - { arg: this.resourceContext.get(), shouldForwardArgs: true }, - { primary, secondary }, + actions = getActionBarActions( + editorTitleMenu.getActions({ arg: this.resourceContext.get(), shouldForwardArgs: true }), 'navigation', shouldInlineGroup ); @@ -2121,7 +2115,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { disposables.add(this.onDidActiveEditorChange(() => _onDidChange.fire())); } - return { actions: { primary, secondary }, onDidChange }; + return { actions, onDidChange }; } //#endregion diff --git a/src/vs/workbench/browser/parts/editor/editorGroupWatermark.ts b/src/vs/workbench/browser/parts/editor/editorGroupWatermark.ts index db8ede5ffa8dc..3c96d9f9ffc09 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupWatermark.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupWatermark.ts @@ -15,61 +15,78 @@ import { CommandsRegistry } from '../../../../platform/commands/common/commands. import { ContextKeyExpr, ContextKeyExpression, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; import { defaultKeybindingLabelStyles } from '../../../../platform/theme/browser/defaultStyles.js'; import { editorForeground, registerColor, transparent } from '../../../../platform/theme/common/colorRegistry.js'; - -registerColor('editorWatermark.foreground', { dark: transparent(editorForeground, 0.6), light: transparent(editorForeground, 0.68), hcDark: editorForeground, hcLight: editorForeground }, localize('editorLineHighlight', 'Foreground color for the labels in the editor watermark.')); +import { IStorageService, StorageScope, StorageTarget, WillSaveStateReason } from '../../../../platform/storage/common/storage.js'; +import { coalesce, shuffle } from '../../../../base/common/arrays.js'; interface WatermarkEntry { - readonly text: string; readonly id: string; - readonly mac?: boolean; - readonly when?: ContextKeyExpression; + readonly text: string; + readonly when?: { + native?: ContextKeyExpression; + web?: ContextKeyExpression; + }; } const showCommands: WatermarkEntry = { text: localize('watermark.showCommands', "Show All Commands"), id: 'workbench.action.showCommands' }; -const quickAccess: WatermarkEntry = { text: localize('watermark.quickAccess', "Go to File"), id: 'workbench.action.quickOpen' }; -const openFileNonMacOnly: WatermarkEntry = { text: localize('watermark.openFile', "Open File"), id: 'workbench.action.files.openFile', mac: false }; -const openFolderNonMacOnly: WatermarkEntry = { text: localize('watermark.openFolder', "Open Folder"), id: 'workbench.action.files.openFolder', mac: false }; -const openFileOrFolderMacOnly: WatermarkEntry = { text: localize('watermark.openFileFolder', "Open File or Folder"), id: 'workbench.action.files.openFileFolder', mac: true }; +const gotoFile: WatermarkEntry = { text: localize('watermark.quickAccess', "Go to File"), id: 'workbench.action.quickOpen' }; +const openFile: WatermarkEntry = { text: localize('watermark.openFile', "Open File"), id: 'workbench.action.files.openFile' }; +const openFolder: WatermarkEntry = { text: localize('watermark.openFolder', "Open Folder"), id: 'workbench.action.files.openFolder' }; +const openFileOrFolder: WatermarkEntry = { text: localize('watermark.openFileFolder', "Open File or Folder"), id: 'workbench.action.files.openFileFolder' }; const openRecent: WatermarkEntry = { text: localize('watermark.openRecent', "Open Recent"), id: 'workbench.action.openRecent' }; -const newUntitledFileMacOnly: WatermarkEntry = { text: localize('watermark.newUntitledFile', "New Untitled Text File"), id: 'workbench.action.files.newUntitledFile', mac: true }; +const newUntitledFile: WatermarkEntry = { text: localize('watermark.newUntitledFile', "New Untitled Text File"), id: 'workbench.action.files.newUntitledFile' }; const findInFiles: WatermarkEntry = { text: localize('watermark.findInFiles', "Find in Files"), id: 'workbench.action.findInFiles' }; -const toggleTerminal: WatermarkEntry = { text: localize({ key: 'watermark.toggleTerminal', comment: ['toggle is a verb here'] }, "Toggle Terminal"), id: 'workbench.action.terminal.toggleTerminal', when: ContextKeyExpr.equals('terminalProcessSupported', true) }; -const startDebugging: WatermarkEntry = { text: localize('watermark.startDebugging', "Start Debugging"), id: 'workbench.action.debug.start', when: ContextKeyExpr.equals('terminalProcessSupported', true) }; -const toggleFullscreen: WatermarkEntry = { text: localize({ key: 'watermark.toggleFullscreen', comment: ['toggle is a verb here'] }, "Toggle Full Screen"), id: 'workbench.action.toggleFullScreen' }; -const showSettings: WatermarkEntry = { text: localize('watermark.showSettings', "Show Settings"), id: 'workbench.action.openSettings' }; +const toggleTerminal: WatermarkEntry = { text: localize({ key: 'watermark.toggleTerminal', comment: ['toggle is a verb here'] }, "Toggle Terminal"), id: 'workbench.action.terminal.toggleTerminal', when: { web: ContextKeyExpr.equals('terminalProcessSupported', true) } }; +const startDebugging: WatermarkEntry = { text: localize('watermark.startDebugging', "Start Debugging"), id: 'workbench.action.debug.start', when: { web: ContextKeyExpr.equals('terminalProcessSupported', true) } }; +const openSettings: WatermarkEntry = { text: localize('watermark.openSettings', "Open Settings"), id: 'workbench.action.openSettings' }; +const openChat: WatermarkEntry = { text: localize('watermark.openChat', "Open Chat"), id: 'workbench.action.chat.open', when: { native: ContextKeyExpr.equals('chatPanelParticipantRegistered', true), web: ContextKeyExpr.equals('chatPanelParticipantRegistered', true) } }; +const openCopilotEdits: WatermarkEntry = { text: localize('watermark.openCopilotEdits', "Open Copilot Edits"), id: 'workbench.action.chat.openEditSession', when: { native: ContextKeyExpr.equals('chatEditingParticipantRegistered', true), web: ContextKeyExpr.equals('chatEditingParticipantRegistered', true) } }; -const noFolderEntries = [ +const emptyWindowEntries: WatermarkEntry[] = coalesce([ showCommands, - openFileNonMacOnly, - openFolderNonMacOnly, - openFileOrFolderMacOnly, + ...(isMacintosh && !isWeb ? [openFileOrFolder] : [openFile, openFolder]), openRecent, - newUntitledFileMacOnly + isMacintosh && !isWeb ? newUntitledFile : undefined, // fill in one more on macOS to get to 5 entries + openChat +]); + +const randomEmptyWindowEntries: WatermarkEntry[] = [ + /* Nothing yet */ ]; -const folderEntries = [ +const workspaceEntries: WatermarkEntry[] = [ showCommands, - quickAccess, + gotoFile, + openChat +]; + +const randomWorkspaceEntries: WatermarkEntry[] = [ findInFiles, startDebugging, toggleTerminal, - toggleFullscreen, - showSettings + openSettings, + openCopilotEdits ]; export class EditorGroupWatermark extends Disposable { + + private static readonly CACHED_WHEN = 'editorGroupWatermark.whenConditions'; + + private readonly cachedWhen: { [when: string]: boolean } = this.storageService.getObject(EditorGroupWatermark.CACHED_WHEN, StorageScope.PROFILE, Object.create(null)); + private readonly shortcuts: HTMLElement; private readonly transientDisposables = this._register(new DisposableStore()); - private enabled: boolean = false; - private workbenchState: WorkbenchState; - private keybindingLabels = new Set(); + private readonly keybindingLabels = this._register(new DisposableStore()); + + private enabled = false; + private workbenchState = this.contextService.getWorkbenchState(); constructor( container: HTMLElement, @IKeybindingService private readonly keybindingService: IKeybindingService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IContextKeyService private readonly contextKeyService: IContextKeyService, - @IConfigurationService private readonly configurationService: IConfigurationService + @IConfigurationService private readonly configurationService: IConfigurationService, + @IStorageService private readonly storageService: IStorageService ) { super(); @@ -83,75 +100,72 @@ export class EditorGroupWatermark extends Disposable { this.registerListeners(); - this.workbenchState = contextService.getWorkbenchState(); this.render(); } private registerListeners(): void { this._register(this.configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration('workbench.tips.enabled')) { + if (e.affectsConfiguration('workbench.tips.enabled') && this.enabled !== this.configurationService.getValue('workbench.tips.enabled')) { this.render(); } })); this._register(this.contextService.onDidChangeWorkbenchState(workbenchState => { - if (this.workbenchState === workbenchState) { - return; + if (this.workbenchState !== workbenchState) { + this.workbenchState = workbenchState; + this.render(); } - - this.workbenchState = workbenchState; - this.render(); })); - const allEntriesWhenClauses = [...noFolderEntries, ...folderEntries].filter(entry => entry.when !== undefined).map(entry => entry.when!); - const allKeys = new Set(); - allEntriesWhenClauses.forEach(when => when.keys().forEach(key => allKeys.add(key))); - this._register(this.contextKeyService.onDidChangeContext(e => { - if (e.affectsSome(allKeys)) { - this.render(); + this._register(this.storageService.onWillSaveState(e => { + if (e.reason === WillSaveStateReason.SHUTDOWN) { + const entries = [...emptyWindowEntries, ...randomEmptyWindowEntries, ...workspaceEntries, ...randomWorkspaceEntries]; + for (const entry of entries) { + const when = isWeb ? entry.when?.web : entry.when?.native; + if (when) { + this.cachedWhen[entry.id] = this.contextKeyService.contextMatchesRules(when); + } + } + + this.storageService.store(EditorGroupWatermark.CACHED_WHEN, JSON.stringify(this.cachedWhen), StorageScope.PROFILE, StorageTarget.MACHINE); } })); } private render(): void { - const enabled = this.configurationService.getValue('workbench.tips.enabled'); - - if (enabled === this.enabled) { - return; - } + this.enabled = this.configurationService.getValue('workbench.tips.enabled'); - this.enabled = enabled; - this.clear(); + clearNode(this.shortcuts); + this.transientDisposables.clear(); - if (!enabled) { + if (!this.enabled) { return; } + const fixedEntries = this.filterEntries(this.workbenchState !== WorkbenchState.EMPTY ? workspaceEntries : emptyWindowEntries, false /* not shuffled */); + const randomEntries = this.filterEntries(this.workbenchState !== WorkbenchState.EMPTY ? randomWorkspaceEntries : randomEmptyWindowEntries, true /* shuffled */).slice(0, Math.max(0, 5 - fixedEntries.length)); + const entries = [...fixedEntries, ...randomEntries]; + const box = append(this.shortcuts, $('.watermark-box')); - const folder = this.workbenchState !== WorkbenchState.EMPTY; - const selected = (folder ? folderEntries : noFolderEntries) - .filter(entry => !('when' in entry) || this.contextKeyService.contextMatchesRules(entry.when)) - .filter(entry => !('mac' in entry) || entry.mac === (isMacintosh && !isWeb)) - .filter(entry => !!CommandsRegistry.getCommand(entry.id)) - .filter(entry => !!this.keybindingService.lookupKeybinding(entry.id)); const update = () => { clearNode(box); - this.keybindingLabels.forEach(label => label.dispose()); this.keybindingLabels.clear(); - for (const entry of selected) { + for (const entry of entries) { const keys = this.keybindingService.lookupKeybinding(entry.id); if (!keys) { continue; } + const dl = append(box, $('dl')); const dt = append(dl, $('dt')); dt.textContent = entry.text; + const dd = append(dl, $('dd')); - const label = new KeybindingLabel(dd, OS, { renderUnboundKeybindings: true, ...defaultKeybindingLabelStyles }); + + const label = this.keybindingLabels.add(new KeybindingLabel(dd, OS, { renderUnboundKeybindings: true, ...defaultKeybindingLabelStyles })); label.set(keys); - this.keybindingLabels.add(label); } }; @@ -159,14 +173,18 @@ export class EditorGroupWatermark extends Disposable { this.transientDisposables.add(this.keybindingService.onDidUpdateKeybindings(update)); } - private clear(): void { - clearNode(this.shortcuts); - this.transientDisposables.clear(); - } + private filterEntries(entries: WatermarkEntry[], shuffleEntries: boolean): WatermarkEntry[] { + const filteredEntries = entries + .filter(entry => (isWeb && !entry.when?.web) || (!isWeb && !entry.when?.native) || this.cachedWhen[entry.id]) + .filter(entry => !!CommandsRegistry.getCommand(entry.id)) + .filter(entry => !!this.keybindingService.lookupKeybinding(entry.id)); - override dispose(): void { - super.dispose(); - this.clear(); - this.keybindingLabels.forEach(label => label.dispose()); + if (shuffleEntries) { + shuffle(filteredEntries); + } + + return filteredEntries; } } + +registerColor('editorWatermark.foreground', { dark: transparent(editorForeground, 0.6), light: transparent(editorForeground, 0.68), hcDark: editorForeground, hcLight: editorForeground }, localize('editorLineHighlight', 'Foreground color for the labels in the editor watermark.')); diff --git a/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts index 397d6204cc1a6..fdf85346726ae 100644 --- a/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts @@ -817,7 +817,7 @@ export class MultiEditorTabsControl extends EditorTabsControl { tabContainer.appendChild(tabBorderTopContainer); // Tab Editor Label - const editorLabel = this.tabResourceLabels.create(tabContainer, { hoverTargetOverrride: tabContainer }); + const editorLabel = this.tabResourceLabels.create(tabContainer, { hoverTargetOverride: tabContainer }); // Tab Actions const tabActionsContainer = document.createElement('div'); diff --git a/src/vs/workbench/browser/parts/globalCompositeBar.ts b/src/vs/workbench/browser/parts/globalCompositeBar.ts index 8c96ab3e46127..d93cb9b1ef1ab 100644 --- a/src/vs/workbench/browser/parts/globalCompositeBar.ts +++ b/src/vs/workbench/browser/parts/globalCompositeBar.ts @@ -24,7 +24,7 @@ import { StandardMouseEvent } from '../../../base/browser/mouseEvent.js'; import { EventType as TouchEventType, GestureEvent } from '../../../base/browser/touch.js'; import { AnchorAlignment, AnchorAxisAlignment } from '../../../base/browser/ui/contextview/contextview.js'; import { Lazy } from '../../../base/common/lazy.js'; -import { createAndFillInActionBarActions } from '../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { getActionBarActions } from '../../../platform/actions/browser/menuEntryActionViewItem.js'; import { IConfigurationService } from '../../../platform/configuration/common/configuration.js'; import { IContextKeyService } from '../../../platform/contextkey/common/contextkey.js'; import { IContextMenuService } from '../../../platform/contextview/browser/contextView.js'; @@ -277,9 +277,7 @@ abstract class AbstractGlobalActivityActionViewItem extends CompositeBarActionVi } protected async resolveMainMenuActions(menu: IMenu, _disposable: DisposableStore): Promise { - const actions: IAction[] = []; - createAndFillInActionBarActions(menu, { renderShortTitle: true }, { primary: [], secondary: actions }); - return actions; + return getActionBarActions(menu.getActions({ renderShortTitle: true })).secondary; } } diff --git a/src/vs/workbench/browser/parts/paneCompositeBar.ts b/src/vs/workbench/browser/parts/paneCompositeBar.ts index 419aecb0d2533..db3d98034a003 100644 --- a/src/vs/workbench/browser/parts/paneCompositeBar.ts +++ b/src/vs/workbench/browser/parts/paneCompositeBar.ts @@ -11,7 +11,8 @@ import { IInstantiationService } from '../../../platform/instantiation/common/in import { IDisposable, DisposableStore, Disposable, DisposableMap } from '../../../base/common/lifecycle.js'; import { IColorTheme } from '../../../platform/theme/common/themeService.js'; import { CompositeBar, ICompositeBarItem, CompositeDragAndDrop } from './compositeBar.js'; -import { Dimension, createCSSRule, isMouseEvent } from '../../../base/browser/dom.js'; +import { Dimension, isMouseEvent } from '../../../base/browser/dom.js'; +import { createCSSRule } from '../../../base/browser/domStylesheets.js'; import { asCSSUrl } from '../../../base/browser/cssValue.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../platform/storage/common/storage.js'; import { IExtensionService } from '../../services/extensions/common/extensions.js'; @@ -24,7 +25,7 @@ import { IWorkbenchEnvironmentService } from '../../services/environment/common/ import { isNative } from '../../../base/common/platform.js'; import { Before2D, ICompositeDragAndDrop } from '../dnd.js'; import { ThemeIcon } from '../../../base/common/themables.js'; -import { IAction, toAction } from '../../../base/common/actions.js'; +import { IAction, Separator, SubmenuAction, toAction } from '../../../base/common/actions.js'; import { StringSHA1 } from '../../../base/common/hash.js'; import { GestureEvent } from '../../../base/browser/touch.js'; import { IPaneCompositePart } from './paneCompositePart.js'; @@ -158,19 +159,42 @@ export class PaneCompositeBar extends Disposable { } private getContextMenuActionsForComposite(compositeId: string): IAction[] { - const actions: IAction[] = []; + const actions: IAction[] = [new Separator()]; const viewContainer = this.viewDescriptorService.getViewContainerById(compositeId)!; const defaultLocation = this.viewDescriptorService.getDefaultViewContainerLocation(viewContainer)!; - if (defaultLocation !== this.viewDescriptorService.getViewContainerLocation(viewContainer)) { - actions.push(toAction({ id: 'resetLocationAction', label: localize('resetLocation', "Reset Location"), run: () => this.viewDescriptorService.moveViewContainerToLocation(viewContainer, defaultLocation, undefined, 'resetLocationAction') })); + const currentLocation = this.viewDescriptorService.getViewContainerLocation(viewContainer); + + // Move View Container + const moveActions = []; + for (const location of [ViewContainerLocation.Sidebar, ViewContainerLocation.AuxiliaryBar, ViewContainerLocation.Panel]) { + if (currentLocation !== location) { + moveActions.push(this.createMoveAction(viewContainer, location, defaultLocation)); + } + } + + actions.push(new SubmenuAction('moveToMenu', localize('moveToMenu', "Move To"), moveActions)); + + // Reset Location + if (defaultLocation !== currentLocation) { + actions.push(toAction({ + id: 'resetLocationAction', label: localize('resetLocation', "Reset Location"), run: () => { + this.viewDescriptorService.moveViewContainerToLocation(viewContainer, defaultLocation, undefined, 'resetLocationAction'); + this.viewService.openViewContainer(viewContainer.id, true); + } + })); } else { const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer); if (viewContainerModel.allViewDescriptors.length === 1) { const viewToReset = viewContainerModel.allViewDescriptors[0]; const defaultContainer = this.viewDescriptorService.getDefaultContainerById(viewToReset.id)!; if (defaultContainer !== viewContainer) { - actions.push(toAction({ id: 'resetLocationAction', label: localize('resetLocation', "Reset Location"), run: () => this.viewDescriptorService.moveViewsToContainer([viewToReset], defaultContainer, undefined, 'resetLocationAction') })); + actions.push(toAction({ + id: 'resetLocationAction', label: localize('resetLocation', "Reset Location"), run: () => { + this.viewDescriptorService.moveViewsToContainer([viewToReset], defaultContainer, undefined, 'resetLocationAction'); + this.viewService.openViewContainer(viewContainer.id, true); + } + })); } } } @@ -178,6 +202,23 @@ export class PaneCompositeBar extends Disposable { return actions; } + private createMoveAction(viewContainer: ViewContainer, newLocation: ViewContainerLocation, defaultLocation: ViewContainerLocation): IAction { + return toAction({ + id: `moveViewContainerTo${newLocation}`, + label: newLocation === ViewContainerLocation.Panel ? localize('panel', "Panel") : newLocation === ViewContainerLocation.Sidebar ? localize('sidebar', "Primary Side Bar") : localize('auxiliarybar', "Secondary Side Bar"), + run: () => { + let index: number | undefined; + if (newLocation !== defaultLocation) { + index = this.viewDescriptorService.getViewContainersByLocation(newLocation).length; // move to the end of the location + } else { + index = undefined; // restore default location + } + this.viewDescriptorService.moveViewContainerToLocation(viewContainer, newLocation, index); + this.viewService.openViewContainer(viewContainer.id, true); + } + }); + } + private registerListeners(): void { // View Container Changes this._register(this.viewDescriptorService.onDidChangeViewContainers(({ added, removed }) => this.onDidChangeViewContainers(added, removed))); diff --git a/src/vs/workbench/browser/parts/paneCompositePart.ts b/src/vs/workbench/browser/parts/paneCompositePart.ts index ea3364b2d132d..413ce899f786f 100644 --- a/src/vs/workbench/browser/parts/paneCompositePart.ts +++ b/src/vs/workbench/browser/parts/paneCompositePart.ts @@ -37,7 +37,7 @@ import { StandardMouseEvent } from '../../../base/browser/mouseEvent.js'; import { IAction, SubmenuAction } from '../../../base/common/actions.js'; import { Composite } from '../composite.js'; import { ViewsSubMenu } from './views/viewPaneContainer.js'; -import { createAndFillInActionBarActions } from '../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { getActionBarActions } from '../../../platform/actions/browser/menuEntryActionViewItem.js'; import { IHoverService } from '../../../platform/hover/browser/hover.js'; import { HiddenItemStrategy, WorkbenchToolBar } from '../../../platform/actions/browser/toolbar.js'; @@ -661,11 +661,10 @@ export abstract class AbstractPaneCompositePart extends CompositePart true); + const viewsActions = getActionBarActions(menu, () => true).primary; disposables.dispose(); return viewsActions.length > 1 && viewsActions.some(a => a.enabled) ? new SubmenuAction('views', localize('views', "Views"), viewsActions) : undefined; } @@ -675,5 +674,4 @@ export abstract class AbstractPaneCompositePart extends CompositePart { }); }); -class SwitchPanelViewAction extends Action2 { - - constructor(id: string, title: ICommandActionTitle) { +registerAction2(class extends SwitchCompositeViewAction { + constructor() { super({ - id, - title, + id: 'workbench.action.previousPanelView', + title: localize2('previousPanelView', "Previous Panel View"), category: Categories.View, - f1: true, - }); - } - - override async run(accessor: ServicesAccessor, offset: number): Promise { - const paneCompositeService = accessor.get(IPaneCompositePartService); - const pinnedPanels = paneCompositeService.getVisiblePaneCompositeIds(ViewContainerLocation.Panel); - const activePanel = paneCompositeService.getActivePaneComposite(ViewContainerLocation.Panel); - if (!activePanel) { - return; - } - let targetPanelId: string | undefined; - for (let i = 0; i < pinnedPanels.length; i++) { - if (pinnedPanels[i] === activePanel.getId()) { - targetPanelId = pinnedPanels[(i + pinnedPanels.length + offset) % pinnedPanels.length]; - break; - } - } - if (typeof targetPanelId === 'string') { - await paneCompositeService.openPaneComposite(targetPanelId, ViewContainerLocation.Panel, true); - } - } -} - -registerAction2(class extends SwitchPanelViewAction { - constructor() { - super('workbench.action.previousPanelView', localize2('previousPanelView', "Previous Panel View")); - } - - override run(accessor: ServicesAccessor): Promise { - return super.run(accessor, -1); + f1: true + }, ViewContainerLocation.Panel, -1); } }); -registerAction2(class extends SwitchPanelViewAction { +registerAction2(class extends SwitchCompositeViewAction { constructor() { - super('workbench.action.nextPanelView', localize2('nextPanelView', "Next Panel View")); - } - - override run(accessor: ServicesAccessor): Promise { - return super.run(accessor, 1); + super({ + id: 'workbench.action.nextPanelView', + title: localize2('nextPanelView', "Next Panel View"), + category: Categories.View, + f1: true + }, ViewContainerLocation.Panel, 1); } }); @@ -352,7 +324,7 @@ registerAction2(class extends Action2 { id: MenuId.AuxiliaryBarTitle, group: 'navigation', order: 2, - when: ContextKeyExpr.notEquals(`config.${LayoutSettings.ACTIVITY_BAR_LOCATION}`, ActivityBarPosition.TOP) + when: ContextKeyExpr.equals(`config.${LayoutSettings.ACTIVITY_BAR_LOCATION}`, ActivityBarPosition.DEFAULT) }] }); } diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index dbbefb43ab49a..63b4b45ac3ef0 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -27,7 +27,7 @@ import { HoverPosition } from '../../../../base/browser/ui/hover/hoverWidget.js' import { IMenuService, MenuId } from '../../../../platform/actions/common/actions.js'; import { AbstractPaneCompositePart, CompositeBarPosition } from '../paneCompositePart.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; -import { createAndFillInContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { getContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { IPaneCompositeBarOptions } from '../paneCompositeBar.js'; import { IHoverService } from '../../../../platform/hover/browser/hover.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; @@ -159,12 +159,18 @@ export class PanelPart extends AbstractPaneCompositePart { } private fillExtraContextMenuActions(actions: IAction[]): void { + if (this.getCompositeBarPosition() === CompositeBarPosition.TITLE) { + const viewsSubmenuAction = this.getViewsSubmenuAction(); + if (viewsSubmenuAction) { + actions.push(new Separator()); + actions.push(viewsSubmenuAction); + } + } + const panelPositionMenu = this.menuService.getMenuActions(MenuId.PanelPositionMenu, this.contextKeyService, { shouldForwardArgs: true }); const panelAlignMenu = this.menuService.getMenuActions(MenuId.PanelAlignmentMenu, this.contextKeyService, { shouldForwardArgs: true }); - const positionActions: IAction[] = []; - const alignActions: IAction[] = []; - createAndFillInContextMenuActions(panelPositionMenu, { primary: [], secondary: positionActions }); - createAndFillInContextMenuActions(panelAlignMenu, { primary: [], secondary: alignActions }); + const positionActions = getContextMenuActions(panelPositionMenu).secondary; + const alignActions = getContextMenuActions(panelAlignMenu).secondary; const panelShowLabels = this.configurationService.getValue('workbench.panel.showLabels'); const toggleShowLabelsAction = toAction({ diff --git a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts index bb16041ddf12b..5747c23b1df15 100644 --- a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts +++ b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts @@ -179,10 +179,12 @@ export class SidebarPart extends AbstractPaneCompositePart { position: () => this.getCompositeBarPosition() === CompositeBarPosition.BOTTOM ? HoverPosition.ABOVE : HoverPosition.BELOW, }, fillExtraContextMenuActions: actions => { - const viewsSubmenuAction = this.getViewsSubmenuAction(); - if (viewsSubmenuAction) { - actions.push(new Separator()); - actions.push(viewsSubmenuAction); + if (this.getCompositeBarPosition() === CompositeBarPosition.TITLE) { + const viewsSubmenuAction = this.getViewsSubmenuAction(); + if (viewsSubmenuAction) { + actions.push(new Separator()); + actions.push(viewsSubmenuAction); + } } }, compositeSize: 0, diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts index a1182f5bd0e40..fddbd06e30762 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts @@ -16,7 +16,8 @@ import { IThemeService } from '../../../../platform/theme/common/themeService.js import { STATUS_BAR_BACKGROUND, STATUS_BAR_FOREGROUND, STATUS_BAR_NO_FOLDER_BACKGROUND, STATUS_BAR_ITEM_HOVER_BACKGROUND, STATUS_BAR_BORDER, STATUS_BAR_NO_FOLDER_FOREGROUND, STATUS_BAR_NO_FOLDER_BORDER, STATUS_BAR_ITEM_COMPACT_HOVER_BACKGROUND, STATUS_BAR_ITEM_FOCUS_BORDER, STATUS_BAR_FOCUS_BORDER } from '../../../common/theme.js'; import { IWorkspaceContextService, WorkbenchState } from '../../../../platform/workspace/common/workspace.js'; import { contrastBorder, activeContrastBorder } from '../../../../platform/theme/common/colorRegistry.js'; -import { EventHelper, createStyleSheet, addDisposableListener, EventType, clearNode, getWindow } from '../../../../base/browser/dom.js'; +import { EventHelper, addDisposableListener, EventType, clearNode, getWindow } from '../../../../base/browser/dom.js'; +import { createStyleSheet } from '../../../../base/browser/domStylesheets.js'; import { IStorageService } from '../../../../platform/storage/common/storage.js'; import { Parts, IWorkbenchLayoutService } from '../../../services/layout/browser/layoutService.js'; import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; diff --git a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css index a0d84cfca565a..6aa44168c7b95 100644 --- a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css +++ b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css @@ -392,7 +392,7 @@ /* Action Tool Bar Controls */ .monaco-workbench .part.titlebar > .titlebar-container > .titlebar-right > .action-toolbar-container { display: none; - padding-right: 2px; + padding-right: 4px; flex-grow: 0; flex-shrink: 0; text-align: center; diff --git a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts index f495bad700a78..ac4940f1c28ce 100644 --- a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts @@ -38,7 +38,7 @@ import { ICommandService } from '../../../../platform/commands/common/commands.j import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; import { OpenRecentAction } from '../../actions/windowActions.js'; import { isICommandActionToggleInfo } from '../../../../platform/action/common/action.js'; -import { createAndFillInContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { getFlatContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { defaultMenuStyles } from '../../../../platform/theme/browser/defaultStyles.js'; import { mainWindow } from '../../../../base/browser/window.js'; import { ActivityBarPosition } from '../../../services/layout/browser/layoutService.js'; @@ -561,9 +561,7 @@ export class CustomMenubarControl extends MenubarControl { } private toActionsArray(menu: IMenu): IAction[] { - const result: IAction[] = []; - createAndFillInContextMenuActions(menu, { shouldForwardArgs: true }, result); - return result; + return getFlatContextMenuActions(menu.getActions({ shouldForwardArgs: true })); } private readonly reinstallDisposables = this._register(new DisposableStore()); diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts b/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts index ec26331d37e11..54ba1575b78fb 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarActions.ts @@ -57,9 +57,15 @@ registerAction2(class ToggleCommandCenter extends ToggleTitleBarConfigAction { } }); +registerAction2(class ToggleNavigationControl extends ToggleTitleBarConfigAction { + constructor() { + super('workbench.navigationControl.enabled', localize('toggle.navigation', 'Navigation Controls'), localize('toggle.navigationDescription', "Toggle visibility of the Navigation Controls in title bar"), 2, false, ContextKeyExpr.has('config.window.commandCenter')); + } +}); + registerAction2(class ToggleLayoutControl extends ToggleTitleBarConfigAction { constructor() { - super('workbench.layoutControl.enabled', localize('toggle.layout', 'Layout Controls'), localize('toggle.layoutDescription', "Toggle visibility of the Layout Controls in title bar"), 2, true); + super(LayoutSettings.LAYOUT_ACTIONS, localize('toggle.layout', 'Layout Controls'), localize('toggle.layoutDescription', "Toggle visibility of the Layout Controls in title bar"), 3, true); } }); diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index e49557028f875..717c0a0e95557 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -25,7 +25,7 @@ import { IInstantiationService, ServicesAccessor } from '../../../../platform/in import { Emitter, Event } from '../../../../base/common/event.js'; import { IStorageService, StorageScope } from '../../../../platform/storage/common/storage.js'; import { Parts, IWorkbenchLayoutService, ActivityBarPosition, LayoutSettings, EditorActionsLocation, EditorTabsMode } from '../../../services/layout/browser/layoutService.js'; -import { createActionViewItem, createAndFillInActionBarActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { createActionViewItem, fillInActionBarActions as fillInActionBarActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { Action2, IMenu, IMenuService, MenuId, registerAction2 } from '../../../../platform/actions/common/actions.js'; import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; import { IHostService } from '../../../services/host/browser/host.js'; @@ -56,6 +56,7 @@ import { createInstantHoverDelegate } from '../../../../base/browser/ui/hover/ho import { IBaseActionViewItemOptions } from '../../../../base/browser/ui/actionbar/actionViewItems.js'; import { IHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegate.js'; import { CommandsRegistry } from '../../../../platform/commands/common/commands.js'; +import { safeIntl } from '../../../../base/common/date.js'; export interface ITitleVariable { readonly name: string; @@ -484,7 +485,7 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { // Check if the locale is RTL, macOS will move traffic lights in RTL locales // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/textInfo - const localeInfo = new Intl.Locale(platformLocale) as any; + const localeInfo = safeIntl.Locale(platformLocale) as any; if (localeInfo?.textInfo?.direction === 'rtl') { primaryWindowControlsLocation = 'right'; } @@ -643,16 +644,6 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { } } - // --- Layout Actions - if (this.layoutToolbarMenu) { - createAndFillInActionBarActions( - this.layoutToolbarMenu, - {}, - actions, - () => !this.editorActionsEnabled // Layout Actions in overflow menu when editor actions enabled in title bar - ); - } - // --- Activity Actions if (this.activityActionsEnabled) { if (isAccountsActionVisible(this.storageService)) { @@ -661,6 +652,15 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { actions.primary.push(GLOBAL_ACTIVITY_TITLE_ACTION); } + // --- Layout Actions + if (this.layoutToolbarMenu) { + fillInActionBarActions( + this.layoutToolbarMenu.getActions(), + actions, + () => !this.editorActionsEnabled // Layout Actions in overflow menu when editor actions enabled in title bar + ); + } + this.actionToolBar.setActions(prepareActions(actions.primary), prepareActions(actions.secondary)); }; diff --git a/src/vs/workbench/browser/parts/views/treeView.ts b/src/vs/workbench/browser/parts/views/treeView.ts index e003591cd42c0..08927639e7627 100644 --- a/src/vs/workbench/browser/parts/views/treeView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -33,7 +33,7 @@ import { generateUuid } from '../../../../base/common/uuid.js'; import './media/views.css'; import { VSDataTransfer } from '../../../../base/common/dataTransfer.js'; import { localize } from '../../../../nls.js'; -import { createActionViewItem, createAndFillInContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { createActionViewItem, getContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { Action2, IMenuService, MenuId, MenuRegistry, registerAction2 } from '../../../../platform/actions/common/actions.js'; import { CommandsRegistry, ICommandService } from '../../../../platform/commands/common/commands.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; @@ -367,34 +367,63 @@ abstract class AbstractTreeView extends Disposable implements ITreeView { return this._isEmpty; } - async getChildren(node?: ITreeItem): Promise { - let children: ITreeItem[]; + async getChildren(element?: ITreeItem): Promise { + const batches = await this.getChildrenBatch(element ? [element] : undefined); + return batches?.[0]; + } + + private updateEmptyState(nodes: ITreeItem[], childrenGroups: ITreeItem[][]): void { + if ((nodes.length === 1) && (nodes[0] instanceof Root)) { + const oldEmpty = this._isEmpty; + this._isEmpty = (childrenGroups.length === 0) || (childrenGroups[0].length === 0); + if (oldEmpty !== this._isEmpty) { + this._onDidChangeEmpty.fire(); + } + } + } + + private findCheckboxesUpdated(nodes: ITreeItem[], childrenGroups: ITreeItem[][]): ITreeItem[] { + if (childrenGroups.length === 0) { + return []; + } const checkboxesUpdated: ITreeItem[] = []; - if (node && node.children) { - children = node.children; - } else { - node = node ?? self.root; - node.children = await (node instanceof Root ? dataProvider.getChildren() : dataProvider.getChildren(node)); - children = node.children ?? []; - children.forEach(child => { + + for (let i = 0; i < nodes.length; i++) { + const node = nodes[i]; + const children = childrenGroups[i]; + for (const child of children) { child.parent = node; if (!self.manuallyManageCheckboxes && (node?.checkbox?.isChecked === true) && (child.checkbox?.isChecked === false)) { child.checkbox.isChecked = true; checkboxesUpdated.push(child); } - }); + } } - if (node instanceof Root) { - const oldEmpty = this._isEmpty; - this._isEmpty = children.length === 0; - if (oldEmpty !== this._isEmpty) { - this._onDidChangeEmpty.fire(); + return checkboxesUpdated; + } + + async getChildrenBatch(nodes?: ITreeItem[]): Promise { + let childrenGroups: ITreeItem[][]; + let checkboxesUpdated: ITreeItem[] = []; + if (nodes && nodes.every((node): node is Required => !!node.children)) { + childrenGroups = nodes.map(node => node.children); + } else { + nodes = nodes ?? [self.root]; + const batchedChildren = await (nodes.length === 1 && nodes[0] instanceof Root ? doGetChildrenOrBatch(dataProvider, undefined) : doGetChildrenOrBatch(dataProvider, nodes)); + for (let i = 0; i < nodes.length; i++) { + const node = nodes[i]; + node.children = batchedChildren ? batchedChildren[i] : undefined; } + childrenGroups = batchedChildren ?? []; + checkboxesUpdated = this.findCheckboxesUpdated(nodes, childrenGroups); } + + this.updateEmptyState(nodes, childrenGroups); + if (checkboxesUpdated.length > 0) { self._onDidChangeCheckboxState.fire(checkboxesUpdated); } - return children; + return childrenGroups; } }; if (this._dataProvider.onDidChangeEmpty) { @@ -662,9 +691,9 @@ abstract class AbstractTreeView extends Disposable implements ITreeView { const treeMenus = this.treeDisposables.add(this.instantiationService.createInstance(TreeMenus, this.id)); this.treeLabels = this.treeDisposables.add(this.instantiationService.createInstance(ResourceLabels, this)); const dataSource = this.instantiationService.createInstance(TreeDataSource, this, (task: Promise) => this.progressService.withProgress({ location: this.id }, () => task)); - const aligner = new Aligner(this.themeService); + const aligner = this.treeDisposables.add(new Aligner(this.themeService)); const checkboxStateHandler = this.treeDisposables.add(new CheckboxStateHandler()); - const renderer = this.instantiationService.createInstance(TreeRenderer, this.id, treeMenus, this.treeLabels, actionViewItemProvider, aligner, checkboxStateHandler, () => this.manuallyManageCheckboxes); + const renderer = this.treeDisposables.add(this.instantiationService.createInstance(TreeRenderer, this.id, treeMenus, this.treeLabels, actionViewItemProvider, aligner, checkboxStateHandler, () => this.manuallyManageCheckboxes)); this.treeDisposables.add(renderer.onDidChangeCheckboxState(e => this._onDidChangeCheckboxState.fire(e))); const widgetAriaLabel = this._title; @@ -724,7 +753,7 @@ abstract class AbstractTreeView extends Disposable implements ITreeView { this.treeDisposables.add(this.tree); treeMenus.setContextKeyService(this.tree.contextKeyService); aligner.tree = this.tree; - const actionRunner = new MultipleSelectionActionRunner(this.notificationService, () => this.tree!.getSelection()); + const actionRunner = this.treeDisposables.add(new MultipleSelectionActionRunner(this.notificationService, () => this.tree!.getSelection())); renderer.actionRunner = actionRunner; this.tree.contextKeyService.createKey(this.id, true); @@ -1138,6 +1167,18 @@ class TreeViewDelegate implements IListVirtualDelegate { } } +async function doGetChildrenOrBatch(dataProvider: ITreeViewDataProvider, nodes: ITreeItem[] | undefined): Promise { + if (dataProvider.getChildrenBatch) { + return dataProvider.getChildrenBatch(nodes); + } else { + if (nodes) { + return Promise.all(nodes.map(node => dataProvider.getChildren(node).then(children => children ?? []))); + } else { + return [await dataProvider.getChildren()].filter(children => children !== undefined); + } + } +} + class TreeDataSource implements IAsyncDataSource { constructor( @@ -1150,18 +1191,37 @@ class TreeDataSource implements IAsyncDataSource { return !!this.treeView.dataProvider && (element.collapsibleState !== TreeItemCollapsibleState.None); } + private batch: ITreeItem[] | undefined; + private batchPromise: Promise | undefined; async getChildren(element: ITreeItem): Promise { - let result: ITreeItem[] = []; - if (this.treeView.dataProvider) { - try { - result = (await this.withProgress(this.treeView.dataProvider.getChildren(element))) ?? []; - } catch (e) { - if (!(e.message).startsWith('Bad progress location:')) { - throw e; - } - } + const dataProvider = this.treeView.dataProvider; + if (!dataProvider) { + return []; } - return result; + if (this.batch === undefined) { + this.batch = [element]; + this.batchPromise = undefined; + } else { + this.batch.push(element); + } + const indexInBatch = this.batch.length - 1; + return new Promise((resolve, reject) => { + setTimeout(async () => { + const batch = this.batch; + this.batch = undefined; + if (!this.batchPromise) { + this.batchPromise = this.withProgress(doGetChildrenOrBatch(dataProvider, batch)); + } + try { + const result = await this.batchPromise; + resolve((result && (indexInBatch < result.length)) ? result[indexInBatch] : []); + } catch (e) { + if (!(e.message).startsWith('Bad progress location:')) { + reject(e); + } + } + }, 0); + }); } } @@ -1665,10 +1725,7 @@ class TreeMenus implements IDisposable { const menuData = this.menuService.getMenuActions(menuId, contextKeyService, { shouldForwardArgs: true }); - const primary: IAction[] = []; - const secondary: IAction[] = []; - const result = { primary, secondary }; - createAndFillInContextMenuActions(menuData, result, 'inline'); + const result = getContextMenuActions(menuData, 'inline'); if (i === 0) { primaryGroups = this.createGroups(result.primary); secondaryGroups = this.createGroups(result.secondary); diff --git a/src/vs/workbench/browser/parts/views/viewFilter.ts b/src/vs/workbench/browser/parts/views/viewFilter.ts index 2dfd599de65d8..9eae6e9bc5f8d 100644 --- a/src/vs/workbench/browser/parts/views/viewFilter.ts +++ b/src/vs/workbench/browser/parts/views/viewFilter.ts @@ -165,10 +165,11 @@ export class FilterWidget extends Widget { } private createInput(container: HTMLElement): [ContextScopedHistoryInputBox, DOM.IFocusTracker] { + const history = this.options.history || []; const inputBox = this._register(this.instantiationService.createInstance(ContextScopedHistoryInputBox, container, this.contextViewService, { placeholder: this.options.placeholder, ariaLabel: this.options.ariaLabel, - history: this.options.history || [], + history: new Set(history), showHistoryHint: () => showHistoryKeybindingHint(this.keybindingService), inputBoxStyles: defaultInputBoxStyles })); diff --git a/src/vs/workbench/browser/parts/views/viewPane.ts b/src/vs/workbench/browser/parts/views/viewPane.ts index 2e175a9bd6fdf..0e3e70d07eecf 100644 --- a/src/vs/workbench/browser/parts/views/viewPane.ts +++ b/src/vs/workbench/browser/parts/views/viewPane.ts @@ -7,7 +7,8 @@ import './media/paneviewlet.css'; import * as nls from '../../../../nls.js'; import { Event, Emitter } from '../../../../base/common/event.js'; import { asCssVariable, foreground } from '../../../../platform/theme/common/colorRegistry.js'; -import { after, append, $, trackFocus, EventType, addDisposableListener, createCSSRule, Dimension, reset } from '../../../../base/browser/dom.js'; +import { after, append, $, trackFocus, EventType, addDisposableListener, Dimension, reset, isAncestorOfActiveElement, isActiveElement } from '../../../../base/browser/dom.js'; +import { createCSSRule } from '../../../../base/browser/domStylesheets.js'; import { asCssValueWithDefault, asCSSUrl } from '../../../../base/browser/cssValue.js'; import { DisposableStore, toDisposable } from '../../../../base/common/lifecycle.js'; import { Action, IAction, IActionRunner } from '../../../../base/common/actions.js'; @@ -656,6 +657,8 @@ export abstract class ViewPane extends Pane implements IView { this.viewWelcomeController.focus(); } else if (this.element) { this.element.focus(); + } + if (isActiveElement(this.element) || isAncestorOfActiveElement(this.element)) { this._onDidFocus.fire(); } } diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index 4d2143e0ecf97..bb783fb1afb6c 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -610,6 +610,13 @@ const registry = Registry.as(ConfigurationExtensions.Con tags: ['accessibility'], enum: ['on', 'off', 'auto'] }, + 'workbench.navigationControl.enabled': { + 'type': 'boolean', + 'default': true, + 'markdownDescription': isWeb ? + localize('navigationControlEnabledWeb', "Controls whether the navigation control in the title bar is shown.") : + localize({ key: 'navigationControlEnabled', comment: ['{0}, {1} is a placeholder for a setting identifier.'] }, "Controls whether the navigation control is shown in the custom title bar. This setting only has an effect when {0} is not set to {1}.", '`#window.customTitleBarVisibility#`', '`never`') + }, [LayoutSettings.LAYOUT_ACTIONS]: { 'type': 'boolean', 'default': true, @@ -625,7 +632,7 @@ const registry = Registry.as(ConfigurationExtensions.Con localize('layoutcontrol.type.toggles', "Shows several buttons for toggling the visibility of the panels and side bar."), localize('layoutcontrol.type.both', "Shows both the dropdown and toggle buttons."), ], - 'default': 'both', + 'default': 'toggles', 'description': localize('layoutControlType', "Controls whether the layout control in the custom title bar is displayed as a single menu button or with multiple UI toggles."), }, 'workbench.tips.enabled': { diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index 5f6069ef05787..37e1b8f620c75 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -847,6 +847,7 @@ export interface ITreeViewDataProvider { readonly isTreeEmpty?: boolean; onDidChangeEmpty?: Event; getChildren(element?: ITreeItem): Promise; + getChildrenBatch?(element?: ITreeItem[]): Promise; } export interface ITreeViewDragAndDropController { diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index 5f00d92b18088..9e64c7226d728 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -58,8 +58,7 @@ export const enum AccessibilityVerbositySettingId { Hover = 'accessibility.verbosity.hover', Notification = 'accessibility.verbosity.notification', EmptyEditorHint = 'accessibility.verbosity.emptyEditorHint', - ReplEditor = 'accessibility.verbosity.notebook', - ReplInputHint = 'accessibility.verbosity.replInputHint', + ReplEditor = 'accessibility.verbosity.replEditor', Comments = 'accessibility.verbosity.comments', DiffEditorActive = 'accessibility.verbosity.diffEditorActive', Debug = 'accessibility.verbosity.debug', @@ -164,8 +163,8 @@ const configuration: IConfigurationNode = { description: localize('verbosity.emptyEditorHint', 'Provide information about relevant actions in an empty text editor.'), ...baseVerbosityProperty }, - [AccessibilityVerbositySettingId.ReplInputHint]: { - description: localize('verbosity.replInputHint', 'Provide information about relevant actions For the Repl input.'), + [AccessibilityVerbositySettingId.ReplEditor]: { + description: localize('verbosity.replEditor.description', 'Provide information about how to access the REPL editor accessibility help menu when the REPL editor is focused.'), ...baseVerbosityProperty }, [AccessibilityVerbositySettingId.Comments]: { @@ -531,40 +530,60 @@ const configuration: IConfigurationNode = { }, } }, - 'accessibility.signals.chatRequestSent': { + 'accessibility.signals.progress': { ...signalFeatureBase, - 'description': localize('accessibility.signals.chatRequestSent', "Plays a signal - sound (audio cue) and/or announcement (alert) - when a chat request is made."), + 'description': localize('accessibility.signals.progress', "Plays a signal - sound (audio cue) and/or announcement (alert) - on loop while progress is occurring."), 'properties': { 'sound': { - 'description': localize('accessibility.signals.chatRequestSent.sound', "Plays a sound when a chat request is made."), + 'description': localize('accessibility.signals.progress.sound', "Plays a sound on loop while progress is occurring."), ...soundFeatureBase }, 'announcement': { - 'description': localize('accessibility.signals.chatRequestSent.announcement', "Announces when a chat request is made."), + 'description': localize('accessibility.signals.progress.announcement', "Alerts on loop while progress is occurring."), ...announcementFeatureBase }, - } + }, }, - 'accessibility.signals.progress': { + 'accessibility.signals.chatRequestSent': { ...signalFeatureBase, - 'description': localize('accessibility.signals.progress', "Plays a signal - sound (audio cue) and/or announcement (alert) - on loop while progress is occurring."), + 'description': localize('accessibility.signals.chatRequestSent', "Plays a signal - sound (audio cue) and/or announcement (alert) - when a chat request is made."), 'properties': { 'sound': { - 'description': localize('accessibility.signals.progress.sound', "Plays a sound on loop while progress is occurring."), + 'description': localize('accessibility.signals.chatRequestSent.sound', "Plays a sound when a chat request is made."), ...soundFeatureBase }, 'announcement': { - 'description': localize('accessibility.signals.progress.announcement', "Alerts on loop while progress is occurring."), + 'description': localize('accessibility.signals.chatRequestSent.announcement', "Announces when a chat request is made."), ...announcementFeatureBase }, - }, + } }, 'accessibility.signals.chatResponseReceived': { ...defaultNoAnnouncement, 'description': localize('accessibility.signals.chatResponseReceived', "Plays a sound / audio cue when the response has been received."), 'properties': { 'sound': { - 'description': localize('accessibility.signals.chatResponseReceived.sound', "Plays a sound on loop while the response has been received."), + 'description': localize('accessibility.signals.chatResponseReceived.sound', "Plays a sound on when the response has been received."), + ...soundFeatureBase + }, + } + }, + 'accessibility.signals.codeActionTriggered': { + ...defaultNoAnnouncement, + 'description': localize('accessibility.signals.codeActionTriggered', "Plays a sound / audio cue - when a code action has been triggered."), + 'properties': { + 'sound': { + 'description': localize('accessibility.signals.codeActionTriggered.sound', "Plays a sound when a code action has been triggered."), + ...soundFeatureBase + } + } + }, + 'accessibility.signals.codeActionApplied': { + ...defaultNoAnnouncement, + 'description': localize('accessibility.signals.codeActionApplied', "Plays a sound / audio cue when the code action has been applied."), + 'properties': { + 'sound': { + 'description': localize('accessibility.signals.codeActionApplied.sound', "Plays a sound when the code action has been applied."), ...soundFeatureBase }, } @@ -685,6 +704,17 @@ const configuration: IConfigurationNode = { 'description': localize('accessibility.debugWatchVariableAnnouncements', "Controls whether variable changes should be announced in the debug watch view."), 'default': true, }, + 'accessibility.replEditor.readLastExecutionOutput': { + 'type': 'boolean', + 'description': localize('accessibility.replEditor.readLastExecutedOutput', "Controls whether the output from an execution in the native REPL will be announced."), + 'default': true, + }, + 'accessibility.replEditor.autoFocusReplExecution': { + type: 'string', + enum: ['none', 'input', 'lastExecution'], + default: 'input', + description: localize('replEditor.autoFocusAppendedCell', "Control whether focus should automatically be sent to the REPL when code is executed."), + } } }; diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts index 132b052c85812..c9acf7b6a28c9 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts @@ -26,7 +26,7 @@ import { CodeActionController } from '../../../../editor/contrib/codeAction/brow import { localize } from '../../../../nls.js'; import { AccessibleViewProviderId, AccessibleViewType, AccessibleContentProvider, ExtensionContentProvider, IAccessibleViewService, IAccessibleViewSymbol } from '../../../../platform/accessibility/browser/accessibleView.js'; import { ACCESSIBLE_VIEW_SHOWN_STORAGE_PREFIX, IAccessibilityService } from '../../../../platform/accessibility/common/accessibility.js'; -import { createAndFillInActionBarActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { getFlatActionBarActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { WorkbenchToolBar } from '../../../../platform/actions/browser/toolbar.js'; import { IMenuService, MenuId } from '../../../../platform/actions/common/actions.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; @@ -655,9 +655,8 @@ export class AccessibleView extends Disposable implements ITextModelContentProvi private _updateToolbar(providedActions?: IAction[], type?: AccessibleViewType): void { this._toolbar.setAriaLabel(type === AccessibleViewType.Help ? localize('accessibleHelpToolbar', 'Accessibility Help') : localize('accessibleViewToolbar', "Accessible View")); - const menuActions: IAction[] = []; const toolbarMenu = this._register(this._menuService.createMenu(MenuId.AccessibleView, this._contextKeyService)); - createAndFillInActionBarActions(toolbarMenu, {}, menuActions); + const menuActions = getFlatActionBarActions(toolbarMenu.getActions({})); if (providedActions) { for (const providedAction of providedActions) { providedAction.class = providedAction.class || ThemeIcon.asClassName(Codicon.primitiveSquare); diff --git a/src/vs/workbench/contrib/accessibility/browser/editorAccessibilityHelp.ts b/src/vs/workbench/contrib/accessibility/browser/editorAccessibilityHelp.ts index 4363a2f6f1f08..9f0b4190a226b 100644 --- a/src/vs/workbench/contrib/accessibility/browser/editorAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/accessibility/browser/editorAccessibilityHelp.ts @@ -13,7 +13,7 @@ import { IContextKeyService } from '../../../../platform/contextkey/common/conte import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; import { AccessibilityHelpAction } from './accessibleViewActions.js'; -import { CONTEXT_CHAT_ENABLED } from '../../chat/common/chatContextKeys.js'; +import { ChatContextKeys } from '../../chat/common/chatContextKeys.js'; import { CommentAccessibilityHelpNLS } from '../../comments/browser/commentsAccessibility.js'; import { CommentContextKeys } from '../../comments/common/commentContextKeys.js'; import { NEW_UNTITLED_FILE_COMMAND_ID } from '../../files/browser/fileConstants.js'; @@ -115,7 +115,7 @@ export function getCommentCommandInfo(keybindingService: IKeybindingService, con } export function getChatCommandInfo(keybindingService: IKeybindingService, contextKeyService: IContextKeyService): string | undefined { - if (CONTEXT_CHAT_ENABLED.getValue(contextKeyService)) { + if (ChatContextKeys.enabled.getValue(contextKeyService)) { return [AccessibilityHelpNLS.quickChat, AccessibilityHelpNLS.startInlineChat].join('\n'); } return; diff --git a/src/vs/workbench/contrib/accessibility/browser/unfocusedViewDimmingContribution.ts b/src/vs/workbench/contrib/accessibility/browser/unfocusedViewDimmingContribution.ts index 136a9282778cf..4a5dbf71a8667 100644 --- a/src/vs/workbench/contrib/accessibility/browser/unfocusedViewDimmingContribution.ts +++ b/src/vs/workbench/contrib/accessibility/browser/unfocusedViewDimmingContribution.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { createStyleSheet } from '../../../../base/browser/dom.js'; +import { createStyleSheet } from '../../../../base/browser/domStylesheets.js'; import { Event } from '../../../../base/common/event.js'; import { Disposable, DisposableStore, toDisposable } from '../../../../base/common/lifecycle.js'; import { clamp } from '../../../../base/common/numbers.js'; diff --git a/src/vs/workbench/contrib/authentication/browser/authentication.contribution.ts b/src/vs/workbench/contrib/authentication/browser/authentication.contribution.ts index 3575284deb696..59a151ce08cb7 100644 --- a/src/vs/workbench/contrib/authentication/browser/authentication.contribution.ts +++ b/src/vs/workbench/contrib/authentication/browser/authentication.contribution.ts @@ -3,9 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IJSONSchema } from '../../../../base/common/jsonSchema.js'; import { Disposable, IDisposable } from '../../../../base/common/lifecycle.js'; -import { isFalsyOrWhitespace } from '../../../../base/common/strings.js'; import { localize } from '../../../../nls.js'; import { MenuId, MenuRegistry, registerAction2 } from '../../../../platform/actions/common/actions.js'; import { CommandsRegistry } from '../../../../platform/commands/common/commands.js'; @@ -15,10 +13,9 @@ import { SyncDescriptor } from '../../../../platform/instantiation/common/descri import { Registry } from '../../../../platform/registry/common/platform.js'; import { IWorkbenchContribution, WorkbenchPhase, registerWorkbenchContribution2 } from '../../../common/contributions.js'; import { SignOutOfAccountAction } from './actions/signOutOfAccountAction.js'; -import { AuthenticationProviderInformation, IAuthenticationService } from '../../../services/authentication/common/authentication.js'; +import { IAuthenticationService } from '../../../services/authentication/common/authentication.js'; import { IBrowserWorkbenchEnvironmentService } from '../../../services/environment/browser/environmentService.js'; import { Extensions, IExtensionFeatureTableRenderer, IExtensionFeaturesRegistry, IRenderedData, IRowData, ITableData } from '../../../services/extensionManagement/common/extensionFeatures.js'; -import { ExtensionsRegistry } from '../../../services/extensions/common/extensionsRegistry.js'; import { ManageTrustedExtensionsForAccountAction } from './actions/manageTrustedExtensionsForAccountAction.js'; import { ManageAccountPreferencesForExtensionAction } from './actions/manageAccountPreferencesForExtensionAction.js'; import { IAuthenticationUsageService } from '../../../services/authentication/browser/authenticationUsageService.js'; @@ -28,37 +25,6 @@ const codeExchangeProxyCommand = CommandsRegistry.registerCommand('workbench.get return environmentService.options?.codeExchangeProxyEndpoints; }); -const authenticationDefinitionSchema: IJSONSchema = { - type: 'object', - additionalProperties: false, - properties: { - id: { - type: 'string', - description: localize('authentication.id', 'The id of the authentication provider.') - }, - label: { - type: 'string', - description: localize('authentication.label', 'The human readable name of the authentication provider.'), - } - } -}; - -const authenticationExtPoint = ExtensionsRegistry.registerExtensionPoint({ - extensionPoint: 'authentication', - jsonSchema: { - description: localize({ key: 'authenticationExtensionPoint', comment: [`'Contributes' means adds here`] }, 'Contributes authentication'), - type: 'array', - items: authenticationDefinitionSchema - }, - activationEventsGenerator: (authenticationProviders, result) => { - for (const authenticationProvider of authenticationProviders) { - if (authenticationProvider.id) { - result.push(`onAuthenticationRequest:${authenticationProvider.id}`); - } - } - } -}); - class AuthenticationDataRenderer extends Disposable implements IExtensionFeatureTableRenderer { readonly type = 'table'; @@ -127,42 +93,9 @@ class AuthenticationContribution extends Disposable implements IWorkbenchContrib this._clearPlaceholderMenuItem(); } this._registerHandlers(); - this._registerAuthenticationExtentionPointHandler(); this._registerActions(); } - private _registerAuthenticationExtentionPointHandler(): void { - authenticationExtPoint.setHandler((extensions, { added, removed }) => { - added.forEach(point => { - for (const provider of point.value) { - if (isFalsyOrWhitespace(provider.id)) { - point.collector.error(localize('authentication.missingId', 'An authentication contribution must specify an id.')); - continue; - } - - if (isFalsyOrWhitespace(provider.label)) { - point.collector.error(localize('authentication.missingLabel', 'An authentication contribution must specify a label.')); - continue; - } - - if (!this._authenticationService.declaredProviders.some(p => p.id === provider.id)) { - this._authenticationService.registerDeclaredAuthenticationProvider(provider); - } else { - point.collector.error(localize('authentication.idConflict', "This authentication id '{0}' has already been registered", provider.id)); - } - } - }); - - const removedExtPoints = removed.flatMap(r => r.value); - removedExtPoints.forEach(point => { - const provider = this._authenticationService.declaredProviders.find(provider => provider.id === point.id); - if (provider) { - this._authenticationService.unregisterDeclaredAuthenticationProvider(provider.id); - } - }); - }); - } - private _registerHandlers(): void { this._register(this._authenticationService.onDidRegisterAuthenticationProvider(_e => { this._clearPlaceholderMenuItem(); diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkFileEdits.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkFileEdits.ts index c61d10b2b817f..237b768fbd424 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkFileEdits.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkFileEdits.ts @@ -16,7 +16,6 @@ import { ILogService } from '../../../../platform/log/common/log.js'; import { VSBuffer } from '../../../../base/common/buffer.js'; import { ResourceFileEdit } from '../../../../editor/browser/services/bulkEditService.js'; import { CancellationToken } from '../../../../base/common/cancellation.js'; -import { tail } from '../../../../base/common/arrays.js'; import { ITextFileService } from '../../../services/textfile/common/textfiles.js'; import { Schemas } from '../../../../base/common/network.js'; @@ -362,7 +361,7 @@ export class BulkFileEdits { for (let i = 1; i < edits.length; i++) { const edit = edits[i]; - const lastGroup = tail(groups); + const lastGroup = groups.at(-1); if (lastGroup?.[0].type === edit.type) { lastGroup.push(edit); } else { diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts index 3b4ab40d0efb3..75da6466a36d1 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts +++ b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts @@ -26,14 +26,13 @@ import { toDisposable, DisposableStore } from '../../../../base/common/lifecycle import { TrackedRangeStickiness, IModelDeltaDecoration, IModelDecorationOptions, OverviewRulerLane } from '../../../../editor/common/model.js'; import { themeColorFromId, IThemeService, IColorTheme } from '../../../../platform/theme/common/themeService.js'; import { IPosition } from '../../../../editor/common/core/position.js'; -import { IAction } from '../../../../base/common/actions.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; import { Color } from '../../../../base/common/color.js'; import { TreeMouseEventTarget, ITreeNode } from '../../../../base/browser/ui/tree/tree.js'; import { URI } from '../../../../base/common/uri.js'; import { MenuId, IMenuService } from '../../../../platform/actions/common/actions.js'; import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; -import { createAndFillInActionBarActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { getFlatActionBarActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; const enum State { Loading = 'loading', @@ -129,8 +128,7 @@ export class CallHierarchyTreePeekWidget extends peekView.PeekViewWidget { const menu = this._menuService.createMenu(CallHierarchyTreePeekWidget.TitleMenu, this._contextKeyService); const updateToolbar = () => { - const actions: IAction[] = []; - createAndFillInActionBarActions(menu, undefined, actions); + const actions = getFlatActionBarActions(menu.getActions()); this._actionbarWidget!.clear(); this._actionbarWidget!.push(actions, { label: false, icon: true }); }; diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts b/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts index 9259096b48a0a..b1fcadf5d5070 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts @@ -3,26 +3,26 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from '../../../../../nls.js'; import { ICodeEditor } from '../../../../../editor/browser/editorBrowser.js'; import { ServicesAccessor } from '../../../../../editor/browser/editorExtensions.js'; -import { IChatWidgetService } from '../chat.js'; -import { AccessibleViewProviderId, AccessibleViewType, AccessibleContentProvider } from '../../../../../platform/accessibility/browser/accessibleView.js'; -import { AccessibilityVerbositySettingId } from '../../../accessibility/browser/accessibilityConfiguration.js'; -import { AccessibleDiffViewerNext } from '../../../../../editor/browser/widget/diffEditor/commands.js'; -import { INLINE_CHAT_ID } from '../../../inlineChat/common/inlineChat.js'; import { ICodeEditorService } from '../../../../../editor/browser/services/codeEditorService.js'; -import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js'; -import { CONTEXT_IN_CHAT_SESSION, CONTEXT_RESPONSE, CONTEXT_REQUEST, CONTEXT_CHAT_LOCATION, CONTEXT_IN_QUICK_CHAT } from '../../common/chatContextKeys.js'; +import { AccessibleDiffViewerNext } from '../../../../../editor/browser/widget/diffEditor/commands.js'; +import { localize } from '../../../../../nls.js'; +import { AccessibleContentProvider, AccessibleViewProviderId, AccessibleViewType } from '../../../../../platform/accessibility/browser/accessibleView.js'; import { IAccessibleViewImplentation } from '../../../../../platform/accessibility/browser/accessibleViewRegistry.js'; -import { ChatAgentLocation } from '../../common/chatAgents.js'; +import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js'; import { IKeybindingService } from '../../../../../platform/keybinding/common/keybinding.js'; +import { AccessibilityVerbositySettingId } from '../../../accessibility/browser/accessibilityConfiguration.js'; +import { INLINE_CHAT_ID } from '../../../inlineChat/common/inlineChat.js'; +import { ChatAgentLocation } from '../../common/chatAgents.js'; +import { ChatContextKeys } from '../../common/chatContextKeys.js'; +import { IChatWidgetService } from '../chat.js'; export class PanelChatAccessibilityHelp implements IAccessibleViewImplentation { readonly priority = 107; readonly name = 'panelChat'; readonly type = AccessibleViewType.Help; - readonly when = ContextKeyExpr.and(CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.Panel), CONTEXT_IN_QUICK_CHAT.negate(), ContextKeyExpr.or(CONTEXT_IN_CHAT_SESSION, CONTEXT_RESPONSE, CONTEXT_REQUEST)); + readonly when = ContextKeyExpr.and(ChatContextKeys.location.isEqualTo(ChatAgentLocation.Panel), ChatContextKeys.inQuickChat.negate(), ContextKeyExpr.or(ChatContextKeys.inChatSession, ChatContextKeys.isResponse, ChatContextKeys.isRequest)); getProvider(accessor: ServicesAccessor) { const codeEditor = accessor.get(ICodeEditorService).getActiveCodeEditor() || accessor.get(ICodeEditorService).getFocusedCodeEditor(); return getChatAccessibilityHelpProvider(accessor, codeEditor ?? undefined, 'panelChat'); @@ -33,7 +33,7 @@ export class QuickChatAccessibilityHelp implements IAccessibleViewImplentation { readonly priority = 107; readonly name = 'quickChat'; readonly type = AccessibleViewType.Help; - readonly when = ContextKeyExpr.and(CONTEXT_IN_QUICK_CHAT, ContextKeyExpr.or(CONTEXT_IN_CHAT_SESSION, CONTEXT_RESPONSE, CONTEXT_REQUEST)); + readonly when = ContextKeyExpr.and(ChatContextKeys.inQuickChat, ContextKeyExpr.or(ChatContextKeys.inChatSession, ChatContextKeys.isResponse, ChatContextKeys.isRequest)); getProvider(accessor: ServicesAccessor) { const codeEditor = accessor.get(ICodeEditorService).getActiveCodeEditor() || accessor.get(ICodeEditorService).getFocusedCodeEditor(); return getChatAccessibilityHelpProvider(accessor, codeEditor ?? undefined, 'quickChat'); @@ -43,10 +43,16 @@ export class QuickChatAccessibilityHelp implements IAccessibleViewImplentation { export function getAccessibilityHelpText(type: 'panelChat' | 'inlineChat' | 'quickChat', keybindingService: IKeybindingService): string { const content = []; if (type === 'panelChat' || type === 'quickChat') { - content.push(localize('chat.overview', 'The chat view is comprised of an input box and a request/response list. The input box is used to make requests and the list is used to display responses.')); + if (type === 'quickChat') { + content.push(localize('chat.overview', 'The quick chat view is comprised of an input box and a request/response list. The input box is used to make requests and the list is used to display responses.')); + content.push(localize('chat.differenceQuick', 'The quick chat view is a transient interface for making and viewing requests, while the panel chat view is a persistent interface that also supports navigating suggested follow-up questions.')); + } + if (type === 'panelChat') { + content.push(localize('chat.differencePanel', 'The panel chat view is a persistent interface that also supports navigating suggested follow-up questions, while the quick chat view is a transient interface for making and viewing requests.')); + content.push(localize('chat.followUp', 'In the input box, navigate to the suggested follow up question (Shift+Tab) and press Enter to run it.')); + } content.push(localize('chat.requestHistory', 'In the input box, use up and down arrows to navigate your request history. Edit input and use enter or the submit button to run a new request.')); content.push(localize('chat.inspectResponse', 'In the input box, inspect the last response in the accessible view{0}.', '')); - content.push(localize('chat.followUp', 'In the input box, navigate to the suggested follow up question (Shift+Tab) and press Enter to run it.')); content.push(localize('chat.announcement', 'Chat responses will be announced as they come in. A response will indicate the number of code blocks, if any, and then the rest of the response.')); content.push(localize('workbench.action.chat.focus', 'To focus the chat request/response list, which can be navigated with up and down arrows, invoke the Focus Chat command{0}.', getChatFocusKeybindingLabel(keybindingService, type, false))); content.push(localize('workbench.action.chat.focusInput', 'To focus the input box for chat requests, invoke the Focus Chat Input command{0}.', getChatFocusKeybindingLabel(keybindingService, type, true))); diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index 3b308d9607878..1417c25685305 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -11,7 +11,7 @@ import { KeyCode, KeyMod } from '../../../../../base/common/keyCodes.js'; import { DisposableStore } from '../../../../../base/common/lifecycle.js'; import { ThemeIcon } from '../../../../../base/common/themables.js'; import { ICodeEditor } from '../../../../../editor/browser/editorBrowser.js'; -import { EditorAction2, ServicesAccessor } from '../../../../../editor/browser/editorExtensions.js'; +import { EditorAction2 } from '../../../../../editor/browser/editorExtensions.js'; import { Position } from '../../../../../editor/common/core/position.js'; import { SuggestController } from '../../../../../editor/contrib/suggest/browser/suggestController.js'; import { localize, localize2 } from '../../../../../nls.js'; @@ -20,25 +20,22 @@ import { DropdownWithPrimaryActionViewItem } from '../../../../../platform/actio import { Action2, MenuId, MenuItemAction, MenuRegistry, registerAction2, SubmenuItemAction } from '../../../../../platform/actions/common/actions.js'; import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js'; import { IsLinuxContext, IsWindowsContext } from '../../../../../platform/contextkey/common/contextkeys.js'; -import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; +import { IInstantiationService, ServicesAccessor } from '../../../../../platform/instantiation/common/instantiation.js'; import { KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js'; import { IOpenerService } from '../../../../../platform/opener/common/opener.js'; -import { IProductService } from '../../../../../platform/product/common/productService.js'; -import { ProgressLocation } from '../../../../../platform/progress/common/progress.js'; import { IQuickInputButton, IQuickInputService, IQuickPickItem, IQuickPickSeparator } from '../../../../../platform/quickinput/common/quickInput.js'; import { ToggleTitleBarConfigAction } from '../../../../browser/parts/titlebar/titlebarActions.js'; import { IWorkbenchContribution } from '../../../../common/contributions.js'; import { IEditorGroupsService } from '../../../../services/editor/common/editorGroupsService.js'; import { ACTIVE_GROUP, IEditorService } from '../../../../services/editor/common/editorService.js'; import { IViewsService } from '../../../../services/views/common/viewsService.js'; -import { IExtensionsWorkbenchService } from '../../../extensions/common/extensions.js'; import { ChatAgentLocation, IChatAgentService } from '../../common/chatAgents.js'; -import { CONTEXT_CHAT_ENABLED, CONTEXT_CHAT_INPUT_CURSOR_AT_TOP, CONTEXT_CHAT_INSTALL_ENTITLED, CONTEXT_CHAT_LOCATION, CONTEXT_CHAT_PANEL_PARTICIPANT_REGISTERED, CONTEXT_IN_CHAT_INPUT, CONTEXT_IN_CHAT_SESSION, CONTEXT_IN_QUICK_CHAT } from '../../common/chatContextKeys.js'; +import { ChatContextKeys } from '../../common/chatContextKeys.js'; import { extractAgentAndCommand } from '../../common/chatParserTypes.js'; import { IChatDetail, IChatService } from '../../common/chatService.js'; import { IChatRequestViewModel, IChatResponseViewModel, isRequestVM } from '../../common/chatViewModel.js'; import { IChatWidgetHistoryService } from '../../common/chatWidgetHistoryService.js'; -import { CHAT_VIEW_ID, IChatWidget, IChatWidgetService, showChatView } from '../chat.js'; +import { ChatViewId, IChatWidget, IChatWidgetService, showChatView } from '../chat.js'; import { IChatEditorOptions } from '../chatEditor.js'; import { ChatEditorInput } from '../chatEditorInput.js'; import { ChatViewPane } from '../chatViewPane.js'; @@ -47,8 +44,7 @@ import { clearChatEditor } from './chatClear.js'; import product from '../../../../../platform/product/common/product.js'; import { URI } from '../../../../../base/common/uri.js'; import { IHostService } from '../../../../services/host/browser/host.js'; -import { isCancellationError } from '../../../../../base/common/errors.js'; -import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js'; +import { IChatVariablesService } from '../../common/chatVariables.js'; export const CHAT_CATEGORY = localize2('chat.category', 'Chat'); export const CHAT_OPEN_ACTION_ID = 'workbench.action.chat.open'; @@ -62,6 +58,10 @@ export interface IChatViewOpenOptions { * Whether the query is partial and will await more input from the user. */ isPartialQuery?: boolean; + /** + * A list of simple variables that will be resolved and attached if they exist. + */ + variableIds?: string[]; /** * Any previous chat requests and responses that should be shown in the chat view. */ @@ -78,14 +78,6 @@ export interface IChatViewOpenRequestEntry { response: string; } -const defaultChat = { - extensionId: product.defaultChatAgent?.extensionId ?? '', - name: product.defaultChatAgent?.name ?? '', - icon: Codicon[product.defaultChatAgent?.icon as keyof typeof Codicon ?? 'commentDiscussion'], - documentationUrl: product.defaultChatAgent?.documentationUrl ?? '', - gettingStartedCommand: product.defaultChatAgent?.gettingStartedCommand ?? '', -}; - class OpenChatGlobalAction extends Action2 { static readonly TITLE = localize2('openChat', "Open Chat"); @@ -96,7 +88,7 @@ class OpenChatGlobalAction extends Action2 { title: OpenChatGlobalAction.TITLE, icon: defaultChat.icon, f1: true, - precondition: CONTEXT_CHAT_PANEL_PARTICIPANT_REGISTERED, + precondition: ChatContextKeys.panelParticipantRegistered, category: CHAT_CATEGORY, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -107,7 +99,7 @@ class OpenChatGlobalAction extends Action2 { }, menu: { id: MenuId.ChatCommandCenter, - group: 'a_chat', + group: 'a_open', order: 1 } }); @@ -117,8 +109,10 @@ class OpenChatGlobalAction extends Action2 { opts = typeof opts === 'string' ? { query: opts } : opts; const chatService = accessor.get(IChatService); + const chatVariablesService = accessor.get(IChatVariablesService); const viewsService = accessor.get(IViewsService); const hostService = accessor.get(IHostService); + const chatWidget = await showChatView(viewsService); if (!chatWidget) { return; @@ -141,6 +135,21 @@ class OpenChatGlobalAction extends Action2 { chatWidget.acceptInput(opts.query); } } + if (opts?.variableIds && opts.variableIds.length > 0) { + const actualVariables = chatVariablesService.getVariables(ChatAgentLocation.Panel); + for (const actualVariable of actualVariables) { + if (opts.variableIds.includes(actualVariable.id)) { + chatWidget.attachmentModel.addContext({ + range: undefined, + id: actualVariable.id ?? '', + value: undefined, + fullName: actualVariable.fullName, + name: actualVariable.name, + icon: actualVariable.icon + }); + } + } + } chatWidget.focusInput(); } @@ -153,14 +162,14 @@ class ChatHistoryAction extends Action2 { title: localize2('chat.history.label', "Show Chats..."), menu: { id: MenuId.ViewTitle, - when: ContextKeyExpr.equals('view', CHAT_VIEW_ID), + when: ContextKeyExpr.equals('view', ChatViewId), group: 'navigation', order: 2 }, category: CHAT_CATEGORY, icon: Codicon.history, f1: true, - precondition: CONTEXT_CHAT_ENABLED + precondition: ChatContextKeys.enabled }); } @@ -244,7 +253,7 @@ class ChatHistoryAction extends Action2 { try { const item = picker.selectedItems[0]; const sessionId = item.chat.sessionId; - const view = await viewsService.openView(CHAT_VIEW_ID) as ChatViewPane; + const view = await viewsService.openView(ChatViewId) as ChatViewPane; view.loadSession(sessionId); } finally { picker.hide(); @@ -265,7 +274,7 @@ class OpenChatEditorAction extends Action2 { title: localize2('interactiveSession.open', "Open Editor"), f1: true, category: CHAT_CATEGORY, - precondition: CONTEXT_CHAT_ENABLED + precondition: ChatContextKeys.enabled }); } @@ -286,7 +295,7 @@ class ChatAddAction extends Action2 { category: CHAT_CATEGORY, menu: { id: MenuId.ChatInput, - when: CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.Panel), + when: ChatContextKeys.location.isEqualTo(ChatAgentLocation.Panel), group: 'navigation', order: 1 } @@ -331,7 +340,7 @@ export function registerChatActions() { super({ id: 'workbench.action.chat.clearInputHistory', title: localize2('interactiveSession.clearHistory.label', "Clear Input History"), - precondition: CONTEXT_CHAT_ENABLED, + precondition: ChatContextKeys.enabled, category: CHAT_CATEGORY, f1: true, }); @@ -347,7 +356,7 @@ export function registerChatActions() { super({ id: 'workbench.action.chat.clearHistory', title: localize2('chat.clear.label', "Clear All Workspace Chats"), - precondition: CONTEXT_CHAT_ENABLED, + precondition: ChatContextKeys.enabled, category: CHAT_CATEGORY, f1: true, }); @@ -359,7 +368,7 @@ export function registerChatActions() { const chatService = accessor.get(IChatService); chatService.clearAllHistoryEntries(); - const chatView = viewsService.getViewWithId(CHAT_VIEW_ID) as ChatViewPane | undefined; + const chatView = viewsService.getViewWithId(ChatViewId) as ChatViewPane | undefined; if (chatView) { chatView.widget.clear(); } @@ -381,23 +390,23 @@ export function registerChatActions() { super({ id: 'chat.action.focus', title: localize2('actions.interactiveSession.focus', 'Focus Chat List'), - precondition: ContextKeyExpr.and(CONTEXT_IN_CHAT_INPUT), + precondition: ContextKeyExpr.and(ChatContextKeys.inChatInput), category: CHAT_CATEGORY, keybinding: [ // On mac, require that the cursor is at the top of the input, to avoid stealing cmd+up to move the cursor to the top { - when: ContextKeyExpr.and(CONTEXT_CHAT_INPUT_CURSOR_AT_TOP, CONTEXT_IN_QUICK_CHAT.negate()), + when: ContextKeyExpr.and(ChatContextKeys.inputCursorAtTop, ChatContextKeys.inQuickChat.negate()), primary: KeyMod.CtrlCmd | KeyCode.UpArrow, weight: KeybindingWeight.EditorContrib, }, // On win/linux, ctrl+up can always focus the chat list { - when: ContextKeyExpr.and(ContextKeyExpr.or(IsWindowsContext, IsLinuxContext), CONTEXT_IN_QUICK_CHAT.negate()), + when: ContextKeyExpr.and(ContextKeyExpr.or(IsWindowsContext, IsLinuxContext), ChatContextKeys.inQuickChat.negate()), primary: KeyMod.CtrlCmd | KeyCode.UpArrow, weight: KeybindingWeight.EditorContrib, }, { - when: ContextKeyExpr.and(CONTEXT_IN_CHAT_SESSION, CONTEXT_IN_QUICK_CHAT), + when: ContextKeyExpr.and(ChatContextKeys.inChatSession, ChatContextKeys.inQuickChat), primary: KeyMod.CtrlCmd | KeyCode.DownArrow, weight: KeybindingWeight.WorkbenchContrib, } @@ -424,10 +433,10 @@ export function registerChatActions() { { primary: KeyMod.CtrlCmd | KeyCode.DownArrow, weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(CONTEXT_IN_CHAT_SESSION, CONTEXT_IN_CHAT_INPUT.negate(), CONTEXT_IN_QUICK_CHAT.negate()), + when: ContextKeyExpr.and(ChatContextKeys.inChatSession, ChatContextKeys.inChatInput.negate(), ChatContextKeys.inQuickChat.negate()), }, { - when: ContextKeyExpr.and(CONTEXT_IN_CHAT_SESSION, CONTEXT_IN_CHAT_INPUT.negate(), CONTEXT_IN_QUICK_CHAT), + when: ContextKeyExpr.and(ChatContextKeys.inChatSession, ChatContextKeys.inChatInput.negate(), ChatContextKeys.inQuickChat), primary: KeyMod.CtrlCmd | KeyCode.UpArrow, weight: KeybindingWeight.WorkbenchContrib, } @@ -440,9 +449,31 @@ export function registerChatActions() { } }); - registerAction2(InstallChatWithPromptAction); - registerAction2(InstallChatWithoutPromptAction); - registerAction2(LearnMoreChatAction); + registerAction2(class LearnMoreChatAction extends Action2 { + + static readonly ID = 'workbench.action.chat.learnMore'; + static readonly TITLE = localize2('learnMore', "Learn More"); + + constructor() { + super({ + id: LearnMoreChatAction.ID, + title: LearnMoreChatAction.TITLE, + category: CHAT_CATEGORY, + menu: { + id: MenuId.ChatCommandCenter, + group: 'z_learn', + order: 1 + } + }); + } + + override async run(accessor: ServicesAccessor): Promise { + const openerService = accessor.get(IOpenerService); + if (defaultChat.documentationUrl) { + openerService.open(URI.parse(defaultChat.documentationUrl)); + } + } + }); } export function stringifyItem(item: IChatRequestViewModel | IChatResponseViewModel, includeName = true): string { @@ -456,13 +487,23 @@ export function stringifyItem(item: IChatRequestViewModel | IChatResponseViewMod // --- command center chat +const defaultChat = { + name: product.defaultChatAgent?.name ?? '', + icon: Codicon[product.defaultChatAgent?.icon as keyof typeof Codicon ?? 'commentDiscussion'], + documentationUrl: product.defaultChatAgent?.documentationUrl ?? '', +}; + MenuRegistry.appendMenuItem(MenuId.CommandCenter, { submenu: MenuId.ChatCommandCenter, title: localize('title4', "Chat"), icon: defaultChat.icon, when: ContextKeyExpr.and( ContextKeyExpr.has('config.chat.commandCenter.enabled'), - ContextKeyExpr.or(CONTEXT_CHAT_PANEL_PARTICIPANT_REGISTERED, CONTEXT_CHAT_INSTALL_ENTITLED) + ContextKeyExpr.or( + ChatContextKeys.panelParticipantRegistered, + ChatContextKeys.ChatSetup.entitled, + ContextKeyExpr.has('config.chat.experimental.offerSetup') + ) ), order: 10001, }); @@ -472,10 +513,14 @@ registerAction2(class ToggleChatControl extends ToggleTitleBarConfigAction { super( 'chat.commandCenter.enabled', localize('toggle.chatControl', 'Chat Controls'), - localize('toggle.chatControlsDescription', "Toggle visibility of the Chat Controls in title bar"), 3, false, + localize('toggle.chatControlsDescription', "Toggle visibility of the Chat Controls in title bar"), 4, false, ContextKeyExpr.and( ContextKeyExpr.has('config.window.commandCenter'), - ContextKeyExpr.or(CONTEXT_CHAT_PANEL_PARTICIPANT_REGISTERED, CONTEXT_CHAT_INSTALL_ENTITLED) + ContextKeyExpr.or( + ChatContextKeys.panelParticipantRegistered, + ChatContextKeys.ChatSetup.entitled, + ContextKeyExpr.has('config.chat.experimental.offerSetup') + ) ) ); } @@ -485,16 +530,12 @@ export class ChatCommandCenterRendering implements IWorkbenchContribution { static readonly ID = 'chat.commandCenterRendering'; - private readonly _store = new DisposableStore(); - constructor( @IActionViewItemService actionViewItemService: IActionViewItemService, @IChatAgentService agentService: IChatAgentService, @IInstantiationService instantiationService: IInstantiationService, ) { - - this._store.add(actionViewItemService.register(MenuId.CommandCenter, MenuId.ChatCommandCenter, (action, options) => { - + actionViewItemService.register(MenuId.CommandCenter, MenuId.ChatCommandCenter, (action, options) => { if (!(action instanceof SubmenuItemAction)) { return undefined; } @@ -508,142 +549,12 @@ export class ChatCommandCenterRendering implements IWorkbenchContribution { const chatExtensionInstalled = agentService.getAgents().some(agent => agent.isDefault); const primaryAction = instantiationService.createInstance(MenuItemAction, { - id: chatExtensionInstalled ? CHAT_OPEN_ACTION_ID : InstallChatWithPromptAction.ID, - title: chatExtensionInstalled ? OpenChatGlobalAction.TITLE : InstallChatWithPromptAction.TITLE, + id: chatExtensionInstalled ? CHAT_OPEN_ACTION_ID : 'workbench.action.chat.triggerSetup', // TODO@bpasero revisit layering of this action + title: OpenChatGlobalAction.TITLE, icon: defaultChat.icon, }, undefined, undefined, undefined, undefined); - return instantiationService.createInstance( - DropdownWithPrimaryActionViewItem, - primaryAction, dropdownAction, action.actions, - '', - { - ...options, - skipTelemetry: true, // already handled by the workbench action bar - } - ); - - }, agentService.onDidChangeAgents)); - } - - dispose() { - this._store.dispose(); - } -} - -abstract class BaseInstallChatAction extends Action2 { - - protected abstract getJustification(productService: IProductService): string | undefined; - - override async run(accessor: ServicesAccessor): Promise { - const extensionsWorkbenchService = accessor.get(IExtensionsWorkbenchService); - const productService = accessor.get(IProductService); - const telemetryService = accessor.get(ITelemetryService); - - type InstallChatClassification = { - owner: 'bpasero'; - comment: 'Provides insight into chat installation.'; - installResult: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the extension was installed successfully, cancelled or failed to install.' }; - hasJustification: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The type of window error to understand the nature of the error better.' }; - }; - type InstallChatEvent = { - hasJustification: boolean; - installResult: 'installed' | 'cancelled' | 'failed'; - }; - - const justification = this.getJustification(productService); - - let installResult: 'installed' | 'cancelled' | 'failed'; - try { - await extensionsWorkbenchService.install(defaultChat.extensionId, { - justification, - enable: true, - installPreReleaseVersion: productService.quality !== 'stable' - }, ProgressLocation.Notification); - - installResult = 'installed'; - } catch (error) { - installResult = isCancellationError(error) ? 'cancelled' : 'failed'; - } - - telemetryService.publicLog2('commandCenter.chatInstall', { - installResult, - hasJustification: !!justification - }); - } -} - -class InstallChatWithPromptAction extends BaseInstallChatAction { - - static readonly ID = 'workbench.action.chat.installWithPrompt'; - static readonly TITLE = localize2('installChat', "Install {0}", defaultChat.name); - - constructor() { - super({ - id: InstallChatWithPromptAction.ID, - title: InstallChatWithPromptAction.TITLE, - icon: defaultChat.icon, - category: CHAT_CATEGORY - }); - } - - protected getJustification(productService: IProductService): string { - return localize('installChatGlobalAction.justification', "AI features in {0} require this extension.", productService.nameShort); - } -} - -class InstallChatWithoutPromptAction extends BaseInstallChatAction { - - static readonly ID = 'workbench.action.chat.installWithoutPrompt'; - static readonly TITLE = localize2('installChat', "Install {0}", defaultChat.name); - - constructor() { - super({ - id: InstallChatWithoutPromptAction.ID, - title: InstallChatWithoutPromptAction.TITLE, - category: CHAT_CATEGORY, - menu: { - id: MenuId.ChatCommandCenter, - group: 'a_atfirst', - order: 1, - when: CONTEXT_CHAT_PANEL_PARTICIPANT_REGISTERED.negate() - } - }); - } - - protected getJustification(): string | undefined { - return undefined; - } -} - -class LearnMoreChatAction extends Action2 { - - static readonly ID = 'workbench.action.chat.learnMore'; - static readonly TITLE = localize2('learnMore', "Learn More"); - - constructor() { - super({ - id: LearnMoreChatAction.ID, - title: LearnMoreChatAction.TITLE, - category: CHAT_CATEGORY, - menu: [{ - id: MenuId.ChatCommandCenter, - group: 'a_atfirst', - order: 2, - when: CONTEXT_CHAT_PANEL_PARTICIPANT_REGISTERED.negate() - }, { - id: MenuId.ChatCommandCenter, - group: 'z_atlast', - order: 1, - when: CONTEXT_CHAT_PANEL_PARTICIPANT_REGISTERED - }] - }); - } - - override async run(accessor: ServicesAccessor): Promise { - const openerService = accessor.get(IOpenerService); - if (defaultChat.documentationUrl) { - openerService.open(URI.parse(defaultChat.documentationUrl)); - } + return instantiationService.createInstance(DropdownWithPrimaryActionViewItem, primaryAction, dropdownAction, action.actions, '', { ...options, skipTelemetry: true }); + }, agentService.onDidChangeAgents); } } diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts index 65205a0b0c67b..b84f3df93e0f7 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts @@ -16,9 +16,9 @@ import { ActiveEditorContext } from '../../../../common/contextkeys.js'; import { IViewsService } from '../../../../services/views/common/viewsService.js'; import { isChatViewTitleActionContext } from '../../common/chatActions.js'; import { ChatAgentLocation } from '../../common/chatAgents.js'; -import { CONTEXT_CHAT_EDITING_CAN_REDO, CONTEXT_CHAT_EDITING_CAN_UNDO, CONTEXT_CHAT_EDITING_PARTICIPANT_REGISTERED, CONTEXT_CHAT_ENABLED, CONTEXT_CHAT_LOCATION, CONTEXT_IN_CHAT_SESSION } from '../../common/chatContextKeys.js'; +import { ChatContextKeys } from '../../common/chatContextKeys.js'; import { hasAppliedChatEditsContextKey, hasUndecidedChatEditingResourceContextKey, IChatEditingService, WorkingSetEntryState } from '../../common/chatEditingService.js'; -import { CHAT_VIEW_ID, EDITS_VIEW_ID, IChatWidgetService } from '../chat.js'; +import { ChatViewId, EditsViewId, IChatWidgetService } from '../chat.js'; import { ChatEditorInput } from '../chatEditorInput.js'; import { ChatViewPane } from '../chatViewPane.js'; import { CHAT_CATEGORY } from './chatActions.js'; @@ -36,7 +36,7 @@ export function registerNewChatActions() { title: localize2('chat.newChat.label', "New Chat"), icon: Codicon.plus, f1: false, - precondition: CONTEXT_CHAT_ENABLED, + precondition: ChatContextKeys.enabled, menu: [{ id: MenuId.EditorTitle, group: 'navigation', @@ -58,7 +58,7 @@ export function registerNewChatActions() { title: localize2('chat.newChat.label', "New Chat"), category: CHAT_CATEGORY, icon: Codicon.plus, - precondition: CONTEXT_CHAT_ENABLED, + precondition: ContextKeyExpr.and(ChatContextKeys.enabled, ChatContextKeys.location.notEqualsTo(ChatAgentLocation.EditingSession)), f1: true, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -66,7 +66,7 @@ export function registerNewChatActions() { mac: { primary: KeyMod.WinCtrl | KeyCode.KeyL }, - when: CONTEXT_IN_CHAT_SESSION + when: ChatContextKeys.inChatSession }, menu: [{ id: MenuId.ChatContext, @@ -74,7 +74,7 @@ export function registerNewChatActions() { }, { id: MenuId.ViewTitle, - when: ContextKeyExpr.equals('view', CHAT_VIEW_ID), + when: ContextKeyExpr.equals('view', ChatViewId), group: 'navigation', order: -1 }] @@ -85,21 +85,15 @@ export function registerNewChatActions() { const context = args[0]; const accessibilitySignalService = accessor.get(IAccessibilitySignalService); const widgetService = accessor.get(IChatWidgetService); + + let widget = widgetService.lastFocusedWidget; + if (isChatViewTitleActionContext(context)) { - const widget = widgetService.getWidgetBySessionId(context.sessionId); // Is running in the Chat view title - announceChatCleared(accessibilitySignalService); - if (widget) { - widget.clear(); - widget.focusInput(); - } - } else { - // Is running from f1 or keybinding - const viewsService = accessor.get(IViewsService); - - const chatView = await viewsService.openView(CHAT_VIEW_ID) as ChatViewPane; - const widget = chatView.widget; + widget = widgetService.getWidgetBySessionId(context.sessionId); + } + if (widget) { announceChatCleared(accessibilitySignalService); widget.clear(); widget.focusInput(); @@ -114,7 +108,7 @@ export function registerNewChatActions() { title: localize2('chat.newEdits.label', "New Edit Session"), category: CHAT_CATEGORY, icon: Codicon.plus, - precondition: ContextKeyExpr.and(CONTEXT_CHAT_ENABLED, CONTEXT_CHAT_EDITING_PARTICIPANT_REGISTERED), + precondition: ContextKeyExpr.and(ChatContextKeys.enabled, ChatContextKeys.editingParticipantRegistered), f1: true, menu: [{ id: MenuId.ChatContext, @@ -122,7 +116,7 @@ export function registerNewChatActions() { }, { id: MenuId.ViewTitle, - when: ContextKeyExpr.equals('view', EDITS_VIEW_ID), + when: ContextKeyExpr.equals('view', EditsViewId), group: 'navigation', order: -1 }, @@ -144,21 +138,21 @@ export function registerNewChatActions() { if (undecidedEdits.length) { const { result } = await dialogService.prompt({ title: localize('chat.startEditing.confirmation.title', "Start new editing session?"), - message: localize('chat.startEditing.confirmation.pending.message', "Starting a new editing session will end your current session. Do you want to discard pending edits to {0} files?", undecidedEdits.length), + message: localize('chat.startEditing.confirmation.pending.message.2', "Starting a new editing session will end your current session. Do you want to accept pending edits to {0} files?", undecidedEdits.length), type: 'info', cancelButton: true, buttons: [ { - label: localize('chat.startEditing.confirmation.discardEdits', "Discard & Continue"), + label: localize('chat.startEditing.confirmation.acceptEdits', "Accept & Continue"), run: async () => { - await currentEditingSession.reject(); + await currentEditingSession.accept(); return true; } }, { - label: localize('chat.startEditing.confirmation.acceptEdits', "Accept & Continue"), + label: localize('chat.startEditing.confirmation.discardEdits', "Discard & Continue"), run: async () => { - await currentEditingSession.accept(); + await currentEditingSession.reject(); return true; } } @@ -194,7 +188,7 @@ export function registerNewChatActions() { } } else { // Is running from f1 or keybinding - const chatView = await viewsService.openView(EDITS_VIEW_ID) as ChatViewPane; + const chatView = await viewsService.openView(EditsViewId) as ChatViewPane; const widget = chatView.widget; announceChatCleared(accessibilitySignalService); @@ -212,11 +206,11 @@ export function registerNewChatActions() { id: 'workbench.action.chat.done', title: localize2('chat.done.label', "Done"), category: CHAT_CATEGORY, - precondition: ContextKeyExpr.and(CONTEXT_CHAT_ENABLED, CONTEXT_CHAT_EDITING_PARTICIPANT_REGISTERED), + precondition: ContextKeyExpr.and(ChatContextKeys.enabled, ChatContextKeys.editingParticipantRegistered), f1: false, menu: [{ id: MenuId.ChatEditingWidgetToolbar, - when: ContextKeyExpr.and(hasUndecidedChatEditingResourceContextKey.negate(), hasAppliedChatEditsContextKey, CONTEXT_CHAT_EDITING_PARTICIPANT_REGISTERED, CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.EditingSession)), + when: ContextKeyExpr.and(hasUndecidedChatEditingResourceContextKey.negate(), hasAppliedChatEditsContextKey, ChatContextKeys.editingParticipantRegistered, ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession)), group: 'navigation', order: 0 }] @@ -240,7 +234,7 @@ export function registerNewChatActions() { // Is running from f1 or keybinding const viewsService = accessor.get(IViewsService); - const chatView = await viewsService.openView(EDITS_VIEW_ID) as ChatViewPane; + const chatView = await viewsService.openView(EditsViewId) as ChatViewPane; const widget = chatView.widget; announceChatCleared(accessibilitySignalService); @@ -258,11 +252,11 @@ export function registerNewChatActions() { title: localize2('chat.undoEdit.label', "Undo Last Edit"), category: CHAT_CATEGORY, icon: Codicon.discard, - precondition: ContextKeyExpr.and(CONTEXT_CHAT_EDITING_CAN_UNDO, CONTEXT_CHAT_ENABLED, CONTEXT_CHAT_EDITING_PARTICIPANT_REGISTERED), + precondition: ContextKeyExpr.and(ChatContextKeys.chatEditingCanUndo, ChatContextKeys.enabled, ChatContextKeys.editingParticipantRegistered), f1: true, menu: [{ id: MenuId.ViewTitle, - when: ContextKeyExpr.equals('view', EDITS_VIEW_ID), + when: ContextKeyExpr.equals('view', EditsViewId), group: 'navigation', order: -3 }] @@ -290,11 +284,11 @@ export function registerNewChatActions() { title: localize2('chat.redoEdit.label', "Redo Last Edit"), category: CHAT_CATEGORY, icon: Codicon.redo, - precondition: ContextKeyExpr.and(CONTEXT_CHAT_EDITING_CAN_REDO, CONTEXT_CHAT_ENABLED, CONTEXT_CHAT_EDITING_PARTICIPANT_REGISTERED), + precondition: ContextKeyExpr.and(ChatContextKeys.chatEditingCanRedo, ChatContextKeys.enabled, ChatContextKeys.editingParticipantRegistered), f1: true, menu: [{ id: MenuId.ViewTitle, - when: ContextKeyExpr.equals('view', EDITS_VIEW_ID), + when: ContextKeyExpr.equals('view', EditsViewId), group: 'navigation', order: -2 }] @@ -322,18 +316,27 @@ export function registerNewChatActions() { title: localize2('chat.openEdits.label', "Open {0}", 'Copilot Edits'), category: CHAT_CATEGORY, icon: Codicon.goToEditingSession, - precondition: ContextKeyExpr.and(CONTEXT_CHAT_ENABLED, CONTEXT_CHAT_EDITING_PARTICIPANT_REGISTERED), + precondition: ContextKeyExpr.and(ChatContextKeys.enabled, ChatContextKeys.editingParticipantRegistered), f1: true, menu: [{ id: MenuId.ViewTitle, - when: ContextKeyExpr.and(ContextKeyExpr.equals('view', CHAT_VIEW_ID), CONTEXT_CHAT_EDITING_PARTICIPANT_REGISTERED), + when: ContextKeyExpr.and(ContextKeyExpr.equals('view', ChatViewId), ChatContextKeys.editingParticipantRegistered, + ContextKeyExpr.equals(`view.${EditsViewId}.visible`, false), + ContextKeyExpr.or( + ContextKeyExpr.and(ContextKeyExpr.equals(`workbench.panel.chat.defaultViewContainerLocation`, true), ContextKeyExpr.equals(`workbench.panel.chatEditing.defaultViewContainerLocation`, false)), + ContextKeyExpr.and(ContextKeyExpr.equals(`workbench.panel.chat.defaultViewContainerLocation`, false), ContextKeyExpr.equals(`workbench.panel.chatEditing.defaultViewContainerLocation`, true)), + )), group: 'navigation', order: 1 }, { id: MenuId.ChatCommandCenter, - when: CONTEXT_CHAT_EDITING_PARTICIPANT_REGISTERED, - group: 'a_chatEdit', - order: 1 + when: ChatContextKeys.editingParticipantRegistered, + group: 'a_open', + order: 2 + }, { + id: MenuId.ChatEditingEditorContent, + group: 'navigate', + order: 4, }], keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -341,14 +344,14 @@ export function registerNewChatActions() { linux: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyMod.Shift | KeyCode.KeyI }, - when: ContextKeyExpr.and(ContextKeyExpr.notEquals('view', EDITS_VIEW_ID), CONTEXT_CHAT_EDITING_PARTICIPANT_REGISTERED) + when: ContextKeyExpr.and(ContextKeyExpr.notEquals('view', EditsViewId), ChatContextKeys.editingParticipantRegistered) } }); } async run(accessor: ServicesAccessor, ...args: any[]) { const viewsService = accessor.get(IViewsService); - const chatView = await viewsService.openView(EDITS_VIEW_ID) as ChatViewPane; + const chatView = await viewsService.openView(EditsViewId) as ChatViewPane; chatView.widget.focusInput(); } }); diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts index 3425dad368f04..7244011a4329d 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts @@ -21,8 +21,8 @@ import { IUntitledTextResourceEditorInput } from '../../../../common/editor.js'; import { IEditorService } from '../../../../services/editor/common/editorService.js'; import { accessibleViewInCodeBlock } from '../../../accessibility/browser/accessibilityConfiguration.js'; import { ITerminalEditorService, ITerminalGroupService, ITerminalService } from '../../../terminal/browser/terminal.js'; -import { CONTEXT_CHAT_EDIT_APPLIED, CONTEXT_CHAT_ENABLED, CONTEXT_IN_CHAT_INPUT, CONTEXT_IN_CHAT_SESSION } from '../../common/chatContextKeys.js'; -import { IChatEditingService } from '../../common/chatEditingService.js'; +import { ChatAgentLocation } from '../../common/chatAgents.js'; +import { ChatContextKeys } from '../../common/chatContextKeys.js'; import { ChatCopyKind, IChatService } from '../../common/chatService.js'; import { IChatResponseViewModel, isResponseVM } from '../../common/chatViewModel.js'; import { IChatCodeBlockContextProviderService, IChatWidgetService } from '../chat.js'; @@ -186,22 +186,29 @@ export function registerChatCodeBlockActions() { super({ id: 'workbench.action.chat.applyInEditor', title: localize2('interactive.applyInEditor.label', "Apply in Editor"), - precondition: CONTEXT_CHAT_ENABLED, + precondition: ChatContextKeys.enabled, f1: true, category: CHAT_CATEGORY, icon: Codicon.gitPullRequestGoToChanges, - menu: { - id: MenuId.ChatCodeBlock, - group: 'navigation', - when: ContextKeyExpr.and( - CONTEXT_IN_CHAT_SESSION, - ...shellLangIds.map(e => ContextKeyExpr.notEquals(EditorContextKeys.languageId.key, e)) - ), - order: 10 - }, + menu: [ + { + id: MenuId.ChatCodeBlock, + group: 'navigation', + when: ContextKeyExpr.and( + ...shellLangIds.map(e => ContextKeyExpr.notEquals(EditorContextKeys.languageId.key, e)) + ), + order: 10 + }, + { + id: MenuId.ChatCodeBlock, + when: ContextKeyExpr.or( + ...shellLangIds.map(e => ContextKeyExpr.equals(EditorContextKeys.languageId.key, e)) + ) + }, + ], keybinding: { - when: ContextKeyExpr.or(ContextKeyExpr.and(CONTEXT_IN_CHAT_SESSION, CONTEXT_IN_CHAT_INPUT.negate()), accessibleViewInCodeBlock), + when: ContextKeyExpr.or(ContextKeyExpr.and(ChatContextKeys.inChatSession, ChatContextKeys.inChatInput.negate()), accessibleViewInCodeBlock), primary: KeyMod.CtrlCmd | KeyCode.Enter, mac: { primary: KeyMod.WinCtrl | KeyCode.Enter }, weight: KeybindingWeight.ExternalExtension + 1 @@ -217,55 +224,29 @@ export function registerChatCodeBlockActions() { } }); - registerAction2(class ApplyAllAction extends Action2 { - constructor() { - super({ - id: 'workbench.action.chat.applyAll', - title: localize2('chat.applyAll.label', "Apply All Edits"), - precondition: CONTEXT_CHAT_ENABLED, // improve this condition - f1: true, - category: CHAT_CATEGORY, - icon: Codicon.edit - }); - } - - override async run(accessor: ServicesAccessor, ...args: any[]) { - const chatWidgetService = accessor.get(IChatWidgetService); - const chatEditingService = accessor.get(IChatEditingService); - - const widget = chatWidgetService.lastFocusedWidget; - if (!widget || !widget.viewModel) { - return; - } - - const applyEditsId = args[0]; - - const chatModel = widget.viewModel.model; - const request = chatModel.getRequests().find(request => request.response?.result?.metadata?.applyEditsId === applyEditsId); - if (request && request.response) { - await chatEditingService.startOrContinueEditingSession(widget.viewModel.sessionId, { silent: true }); // make sure we have an editing session - await chatEditingService.triggerEditComputation(request.response); - } - } - }); - registerAction2(class SmartApplyInEditorAction extends ChatCodeBlockAction { constructor() { super({ id: 'workbench.action.chat.insertCodeBlock', title: localize2('interactive.insertCodeBlock.label', "Insert At Cursor"), - precondition: CONTEXT_CHAT_ENABLED, + precondition: ChatContextKeys.enabled, f1: true, category: CHAT_CATEGORY, icon: Codicon.insert, - menu: { + menu: [{ id: MenuId.ChatCodeBlock, group: 'navigation', - when: CONTEXT_IN_CHAT_SESSION, + when: ContextKeyExpr.and(ChatContextKeys.inChatSession, ChatContextKeys.location.notEqualsTo(ChatAgentLocation.Terminal)), order: 20 - }, + }, { + id: MenuId.ChatCodeBlock, + group: 'navigation', + when: ContextKeyExpr.and(ChatContextKeys.inChatSession, ChatContextKeys.location.isEqualTo(ChatAgentLocation.Terminal)), + isHiddenByDefault: true, + order: 20 + }], keybinding: { - when: ContextKeyExpr.or(ContextKeyExpr.and(CONTEXT_IN_CHAT_SESSION, CONTEXT_IN_CHAT_INPUT.negate()), accessibleViewInCodeBlock), + when: ContextKeyExpr.or(ContextKeyExpr.and(ChatContextKeys.inChatSession, ChatContextKeys.inChatInput.negate()), accessibleViewInCodeBlock), primary: KeyMod.CtrlCmd | KeyCode.Enter, mac: { primary: KeyMod.WinCtrl | KeyCode.Enter }, weight: KeybindingWeight.ExternalExtension + 1 @@ -284,7 +265,7 @@ export function registerChatCodeBlockActions() { super({ id: 'workbench.action.chat.insertIntoNewFile', title: localize2('interactive.insertIntoNewFile.label', "Insert into New File"), - precondition: CONTEXT_CHAT_ENABLED, + precondition: ChatContextKeys.enabled, f1: true, category: CHAT_CATEGORY, icon: Codicon.newFile, @@ -331,7 +312,7 @@ export function registerChatCodeBlockActions() { super({ id: 'workbench.action.chat.runInTerminal', title: localize2('interactive.runInTerminal.label', "Insert into Terminal"), - precondition: CONTEXT_CHAT_ENABLED, + precondition: ChatContextKeys.enabled, f1: true, category: CHAT_CATEGORY, icon: Codicon.terminal, @@ -339,7 +320,7 @@ export function registerChatCodeBlockActions() { id: MenuId.ChatCodeBlock, group: 'navigation', when: ContextKeyExpr.and( - CONTEXT_IN_CHAT_SESSION, + ChatContextKeys.inChatSession, ContextKeyExpr.or(...shellLangIds.map(e => ContextKeyExpr.equals(EditorContextKeys.languageId.key, e))) ), }, @@ -348,7 +329,7 @@ export function registerChatCodeBlockActions() { group: 'navigation', isHiddenByDefault: true, when: ContextKeyExpr.and( - CONTEXT_IN_CHAT_SESSION, + ChatContextKeys.inChatSession, ...shellLangIds.map(e => ContextKeyExpr.notEquals(EditorContextKeys.languageId.key, e)) ) }], @@ -358,7 +339,7 @@ export function registerChatCodeBlockActions() { primary: KeyMod.WinCtrl | KeyMod.Alt | KeyCode.Enter }, weight: KeybindingWeight.EditorContrib, - when: ContextKeyExpr.or(CONTEXT_IN_CHAT_SESSION, accessibleViewInCodeBlock), + when: ContextKeyExpr.or(ChatContextKeys.inChatSession, accessibleViewInCodeBlock), }] }); } @@ -448,9 +429,9 @@ export function registerChatCodeBlockActions() { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.PageDown, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.PageDown, }, weight: KeybindingWeight.WorkbenchContrib, - when: CONTEXT_IN_CHAT_SESSION, + when: ChatContextKeys.inChatSession, }, - precondition: CONTEXT_CHAT_ENABLED, + precondition: ChatContextKeys.enabled, f1: true, category: CHAT_CATEGORY, }); @@ -470,9 +451,9 @@ export function registerChatCodeBlockActions() { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.PageUp, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.PageUp, }, weight: KeybindingWeight.WorkbenchContrib, - when: CONTEXT_IN_CHAT_SESSION, + when: ChatContextKeys.inChatSession, }, - precondition: CONTEXT_CHAT_ENABLED, + precondition: ChatContextKeys.enabled, f1: true, category: CHAT_CATEGORY, }); @@ -537,7 +518,7 @@ export function registerChatCodeCompareBlockActions() { f1: false, category: CHAT_CATEGORY, icon: Codicon.check, - precondition: ContextKeyExpr.and(EditorContextKeys.hasChanges, CONTEXT_CHAT_EDIT_APPLIED.negate()), + precondition: ContextKeyExpr.and(EditorContextKeys.hasChanges, ChatContextKeys.editApplied.negate()), menu: { id: MenuId.ChatCompareBlock, group: 'navigation', @@ -569,7 +550,7 @@ export function registerChatCodeCompareBlockActions() { f1: false, category: CHAT_CATEGORY, icon: Codicon.trash, - precondition: ContextKeyExpr.and(EditorContextKeys.hasChanges, CONTEXT_CHAT_EDIT_APPLIED.negate()), + precondition: ContextKeyExpr.and(EditorContextKeys.hasChanges, ChatContextKeys.editApplied.negate()), menu: { id: MenuId.ChatCompareBlock, group: 'navigation', diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts index 40f1a198567d6..be164a6ec03a5 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts @@ -8,6 +8,7 @@ import { Codicon } from '../../../../../base/common/codicons.js'; import { KeyCode, KeyMod } from '../../../../../base/common/keyCodes.js'; import { Schemas } from '../../../../../base/common/network.js'; import { isElectron } from '../../../../../base/common/platform.js'; +import { dirname } from '../../../../../base/common/resources.js'; import { compare } from '../../../../../base/common/strings.js'; import { ThemeIcon } from '../../../../../base/common/themables.js'; import { URI } from '../../../../../base/common/uri.js'; @@ -35,17 +36,18 @@ import { UntitledTextEditorInput } from '../../../../services/untitled/common/un import { IViewsService } from '../../../../services/views/common/viewsService.js'; import { FileEditorInput } from '../../../files/browser/editors/fileEditorInput.js'; import { AnythingQuickAccessProvider } from '../../../search/browser/anythingQuickAccess.js'; +import { isSearchTreeFileMatch, isSearchTreeMatch } from '../../../search/browser/searchTreeModel/searchTreeCommon.js'; import { SearchView } from '../../../search/browser/searchView.js'; import { ISymbolQuickPickItem, SymbolsQuickAccessProvider } from '../../../search/browser/symbolsQuickAccess.js'; import { SearchContext } from '../../../search/common/constants.js'; import { ChatAgentLocation, IChatAgentService } from '../../common/chatAgents.js'; -import { CONTEXT_CHAT_ENABLED, CONTEXT_CHAT_LOCATION, CONTEXT_IN_CHAT_INPUT } from '../../common/chatContextKeys.js'; +import { ChatContextKeys } from '../../common/chatContextKeys.js'; import { IChatEditingService } from '../../common/chatEditingService.js'; import { IChatRequestVariableEntry } from '../../common/chatModel.js'; import { ChatRequestAgentPart } from '../../common/chatParserTypes.js'; import { IChatVariableData, IChatVariablesService } from '../../common/chatVariables.js'; import { ILanguageModelToolsService } from '../../common/languageModelToolsService.js'; -import { IChatWidget, IChatWidgetService, IQuickChatService, showChatView } from '../chat.js'; +import { IChatWidget, IChatWidgetService, IQuickChatService, showChatView, showEditsView } from '../chat.js'; import { imageToHash, isImage } from '../chatPasteProviders.js'; import { isQuickChat } from '../chatWidget.js'; import { convertBufferToScreenshotVariable, ScreenshotVariableId } from '../contrib/screenshot.js'; @@ -53,14 +55,16 @@ import { CHAT_CATEGORY } from './chatActions.js'; export function registerChatContextActions() { registerAction2(AttachContextAction); - registerAction2(AttachFileAction); - registerAction2(AttachSelectionAction); + registerAction2(AttachFileToChatAction); + registerAction2(AttachSelectionToChatAction); + registerAction2(AttachFileToEditingSessionAction); + registerAction2(AttachSelectionToEditingSessionAction); } /** * We fill the quickpick with these types, and enable some quick access providers */ -type IAttachmentQuickPickItem = ICommandVariableQuickPickItem | IQuickAccessQuickPickItem | IToolQuickPickItem | IImageQuickPickItem | IVariableQuickPickItem | IOpenEditorsQuickPickItem | ISearchResultsQuickPickItem | IScreenShotQuickPickItem; +type IAttachmentQuickPickItem = ICommandVariableQuickPickItem | IQuickAccessQuickPickItem | IToolQuickPickItem | IImageQuickPickItem | IVariableQuickPickItem | IOpenEditorsQuickPickItem | ISearchResultsQuickPickItem | IScreenShotQuickPickItem | IRelatedFilesQuickPickItem; /** * These are the types that we can get out of the quick pick @@ -107,6 +111,19 @@ function isScreenshotQuickPickItem(obj: unknown): obj is IScreenShotQuickPickIte && (obj as IScreenShotQuickPickItem).kind === 'screenshot'); } +function isRelatedFileQuickPickItem(obj: unknown): obj is IRelatedFilesQuickPickItem { + return ( + typeof obj === 'object' + && (obj as IRelatedFilesQuickPickItem).kind === 'related-files' + ); +} + +interface IRelatedFilesQuickPickItem extends IQuickPickItem { + kind: 'related-files'; + id: string; + label: string; +} + interface IImageQuickPickItem extends IQuickPickItem { kind: 'image'; id: string; @@ -159,52 +176,85 @@ interface IScreenShotQuickPickItem extends IQuickPickItem { icon?: ThemeIcon; } -class AttachFileAction extends Action2 { +abstract class AttachFileAction extends Action2 { + getFiles(accessor: ServicesAccessor, ...args: any[]): URI[] { + const textEditorService = accessor.get(IEditorService); + + const contexts = Array.isArray(args[1]) ? args[1] : [args[0]]; + const files = []; + for (const context of contexts) { + let uri; + if (URI.isUri(context)) { + uri = context; + } else if (isSearchTreeFileMatch(context)) { + uri = context.resource; + } else if (isSearchTreeMatch(context)) { + uri = context.parent().resource; + } else if (!context && textEditorService.activeTextEditorControl?.getEditorType() === EditorType.ICodeEditor) { + uri = textEditorService.activeEditor?.resource; + } + + if (uri && [Schemas.file, Schemas.vscodeRemote, Schemas.untitled].includes(uri.scheme)) { + files.push(uri); + } + } + + return files; + } +} + +class AttachFileToChatAction extends AttachFileAction { static readonly ID = 'workbench.action.chat.attachFile'; constructor() { super({ - id: AttachFileAction.ID, + id: AttachFileToChatAction.ID, title: localize2('workbench.action.chat.attachFile.label', "Add File to Chat"), category: CHAT_CATEGORY, f1: false, - precondition: ContextKeyExpr.and(CONTEXT_CHAT_ENABLED, ActiveEditorContext.isEqualTo('workbench.editors.files.textFileEditor')), - menu: { + precondition: ChatContextKeys.enabled, + menu: [{ id: MenuId.ChatCommandCenter, - group: 'a_chat', + group: 'b_chat_context', + when: ActiveEditorContext.isEqualTo('workbench.editors.files.textFileEditor'), order: 10, - } + }, { + id: MenuId.SearchContext, + group: 'z_chat', + order: 1 + }] }); } override async run(accessor: ServicesAccessor, ...args: any[]): Promise { const variablesService = accessor.get(IChatVariablesService); - const textEditorService = accessor.get(IEditorService); + const files = this.getFiles(accessor, ...args); - const activeUri = textEditorService.activeEditor?.resource; - if (textEditorService.activeTextEditorControl?.getEditorType() === EditorType.ICodeEditor && activeUri && [Schemas.file, Schemas.vscodeRemote, Schemas.untitled].includes(activeUri.scheme)) { + if (files.length) { (await showChatView(accessor.get(IViewsService)))?.focusInput(); - variablesService.attachContext('file', activeUri, ChatAgentLocation.Panel); + for (const file of files) { + variablesService.attachContext('file', file, ChatAgentLocation.Panel); + } } } } -class AttachSelectionAction extends Action2 { +class AttachSelectionToChatAction extends Action2 { static readonly ID = 'workbench.action.chat.attachSelection'; constructor() { super({ - id: AttachSelectionAction.ID, + id: AttachSelectionToChatAction.ID, title: localize2('workbench.action.chat.attachSelection.label', "Add Selection to Chat"), category: CHAT_CATEGORY, f1: false, - precondition: ContextKeyExpr.and(CONTEXT_CHAT_ENABLED, ActiveEditorContext.isEqualTo('workbench.editors.files.textFileEditor')), + precondition: ContextKeyExpr.and(ChatContextKeys.enabled, ActiveEditorContext.isEqualTo('workbench.editors.files.textFileEditor')), menu: { id: MenuId.ChatCommandCenter, - group: 'a_chat', - order: 11, + group: 'b_chat_context', + order: 15, } }); } @@ -225,16 +275,88 @@ class AttachSelectionAction extends Action2 { } } +class AttachFileToEditingSessionAction extends AttachFileAction { + + static readonly ID = 'workbench.action.edits.attachFile'; + + constructor() { + super({ + id: AttachFileToEditingSessionAction.ID, + title: localize2('workbench.action.edits.attachFile.label', "Add File to {0}", 'Copilot Edits'), + category: CHAT_CATEGORY, + f1: false, + precondition: ChatContextKeys.enabled, + menu: [{ + id: MenuId.ChatCommandCenter, + group: 'c_edits_context', + when: ActiveEditorContext.isEqualTo('workbench.editors.files.textFileEditor'), + order: 10, + }, { + id: MenuId.SearchContext, + group: 'z_chat', + order: 2 + }] + }); + } + + override async run(accessor: ServicesAccessor, ...args: any[]): Promise { + const variablesService = accessor.get(IChatVariablesService); + const files = this.getFiles(accessor, ...args); + + if (files.length) { + (await showEditsView(accessor.get(IViewsService)))?.focusInput(); + for (const file of files) { + variablesService.attachContext('file', file, ChatAgentLocation.EditingSession); + } + } + } +} + +class AttachSelectionToEditingSessionAction extends Action2 { + + static readonly ID = 'workbench.action.edits.attachSelection'; + + constructor() { + super({ + id: AttachSelectionToEditingSessionAction.ID, + title: localize2('workbench.action.edits.attachSelection.label', "Add Selection to {0}", 'Copilot Edits'), + category: CHAT_CATEGORY, + f1: false, + precondition: ContextKeyExpr.and(ChatContextKeys.enabled, ActiveEditorContext.isEqualTo('workbench.editors.files.textFileEditor')), + menu: { + id: MenuId.ChatCommandCenter, + group: 'c_edits_context', + order: 15, + } + }); + } + + override async run(accessor: ServicesAccessor, ...args: any[]): Promise { + const variablesService = accessor.get(IChatVariablesService); + const textEditorService = accessor.get(IEditorService); + + const activeEditor = textEditorService.activeTextEditorControl; + const activeUri = textEditorService.activeEditor?.resource; + if (textEditorService.activeTextEditorControl?.getEditorType() === EditorType.ICodeEditor && activeUri && [Schemas.file, Schemas.vscodeRemote, Schemas.untitled].includes(activeUri.scheme)) { + const selection = activeEditor?.getSelection(); + if (selection) { + (await showEditsView(accessor.get(IViewsService)))?.focusInput(); + variablesService.attachContext('file', { uri: activeUri, range: selection }, ChatAgentLocation.EditingSession); + } + } + } +} + export class AttachContextAction extends Action2 { static readonly ID = 'workbench.action.chat.attachContext'; // used to enable/disable the keybinding and defined menu containment protected static _cdt = ContextKeyExpr.or( - ContextKeyExpr.and(CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.Panel)), - ContextKeyExpr.and(CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.Editor)), - ContextKeyExpr.and(CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.Notebook)), - ContextKeyExpr.and(CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.Terminal)), + ContextKeyExpr.and(ChatContextKeys.location.isEqualTo(ChatAgentLocation.Panel)), + ContextKeyExpr.and(ChatContextKeys.location.isEqualTo(ChatAgentLocation.Editor)), + ContextKeyExpr.and(ChatContextKeys.location.isEqualTo(ChatAgentLocation.Notebook)), + ContextKeyExpr.and(ChatContextKeys.location.isEqualTo(ChatAgentLocation.Terminal)), ); constructor(desc: Readonly = { @@ -242,21 +364,21 @@ export class AttachContextAction extends Action2 { title: localize2('workbench.action.chat.attachContext.label', "Attach Context"), icon: Codicon.attach, category: CHAT_CATEGORY, - precondition: ContextKeyExpr.or(AttachContextAction._cdt, ContextKeyExpr.and(CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.EditingSession))), + precondition: ContextKeyExpr.or(AttachContextAction._cdt, ContextKeyExpr.and(ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession))), keybinding: { - when: CONTEXT_IN_CHAT_INPUT, + when: ChatContextKeys.inChatInput, primary: KeyMod.CtrlCmd | KeyCode.Slash, weight: KeybindingWeight.EditorContrib }, menu: [ { - when: ContextKeyExpr.or(ContextKeyExpr.and(CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.EditingSession)), ContextKeyExpr.and(ContextKeyExpr.or(CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.Panel), CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.EditingSession)), AttachContextAction._cdt)), + when: ContextKeyExpr.or(ContextKeyExpr.and(ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession)), ContextKeyExpr.and(ContextKeyExpr.or(ChatContextKeys.location.isEqualTo(ChatAgentLocation.Panel), ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession)), AttachContextAction._cdt)), id: MenuId.ChatInput, group: 'navigation', order: 2 }, { - when: ContextKeyExpr.and(CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.Panel).negate(), AttachContextAction._cdt), + when: ContextKeyExpr.and(ChatContextKeys.location.isEqualTo(ChatAgentLocation.Panel).negate(), AttachContextAction._cdt), id: MenuId.ChatExecute, group: 'navigation', order: 1 @@ -276,12 +398,13 @@ export class AttachContextAction extends Action2 { `:${item.range.startLineNumber}`); } - private async _attachContext(widget: IChatWidget, commandService: ICommandService, clipboardService: IClipboardService, editorService: IEditorService, labelService: ILabelService, viewsService: IViewsService, chatEditingService: IChatEditingService | undefined, hostService: IHostService, isInBackground?: boolean, ...picks: IChatContextQuickPickItem[]) { + private async _attachContext(widget: IChatWidget, quickInputService: IQuickInputService, commandService: ICommandService, clipboardService: IClipboardService, editorService: IEditorService, labelService: ILabelService, viewsService: IViewsService, chatEditingService: IChatEditingService | undefined, hostService: IHostService, isInBackground?: boolean, ...picks: IChatContextQuickPickItem[]) { const toAttach: IChatRequestVariableEntry[] = []; for (const pick of picks) { if (isISymbolQuickPickItem(pick) && pick.symbol) { // Workspace symbol toAttach.push({ + kind: 'symbol', id: this._getFileContextId(pick.symbol.location), value: pick.symbol.location, fullName: pick.label, @@ -354,6 +477,37 @@ export class AttachContextAction extends Action2 { }); } } + } else if (isRelatedFileQuickPickItem(pick)) { + // Get all provider results and show them in a second tier picker + const chatSessionId = widget.viewModel?.sessionId; + if (!chatSessionId || !chatEditingService) { + continue; + } + const relatedFiles = await chatEditingService.getRelatedFiles(chatSessionId, widget.getInput(), CancellationToken.None); + if (!relatedFiles) { + continue; + } + const attachments = widget.attachmentModel.getAttachmentIDs(); + const itemsPromise = chatEditingService.getRelatedFiles(chatSessionId, widget.getInput(), CancellationToken.None) + .then((files) => (files ?? []).reduce<((IQuickPickItem & { value: URI }) | IQuickPickSeparator)[]>((acc, cur) => { + acc.push({ type: 'separator', label: cur.group }); + const workingSet = chatEditingService.currentEditingSessionObs.get()?.workingSet; + for (const file of cur.files) { + acc.push({ + type: 'item', + label: labelService.getUriBasenameLabel(file.uri), + description: labelService.getUriLabel(dirname(file.uri), { relative: true }), + value: file.uri, + disabled: workingSet?.has(file.uri) || attachments.has(this._getFileContextId({ resource: file.uri })), + picked: true + }); + } + return acc; + }, [])); + const selectedFiles = await quickInputService.pick(itemsPromise, { placeHolder: localize('relatedFiles', 'Add related files to your working set'), canPickMany: true }); + for (const file of selectedFiles ?? []) { + chatEditingService?.currentEditingSessionObs.get()?.addFileToWorkingSet(file.value); + } } else if (isScreenshotQuickPickItem(pick)) { const blob = await hostService.getScreenshot(); if (blob) { @@ -546,6 +700,14 @@ export class AttachContextAction extends Action2 { }); } } else if (context.showFilesOnly) { + if (chatEditingService?.hasRelatedFilesProviders() && (widget.getInput() || chatEditingService.currentEditingSessionObs.get()?.workingSet.size)) { + quickPickItems.push({ + kind: 'related-files', + id: 'related-files', + label: localize('chatContext.relatedFiles', 'Related Files'), + iconClass: ThemeIcon.asClassName(Codicon.sparkle), + }); + } if (editorService.editors.filter(e => e instanceof FileEditorInput || e instanceof DiffEditorInput || e instanceof UntitledTextEditorInput).length > 0) { quickPickItems.push({ kind: 'open-editors', @@ -590,7 +752,7 @@ export class AttachContextAction extends Action2 { if (!clipboardService) { return; } - this._attachContext(widget, commandService, clipboardService, editorService, labelService, viewsService, chatEditingService, hostService, isBackgroundAccept, item); + this._attachContext(widget, quickInputService, commandService, clipboardService, editorService, labelService, viewsService, chatEditingService, hostService, isBackgroundAccept, item); if (isQuickChat(widget)) { quickChatService.open(); } @@ -660,7 +822,7 @@ registerAction2(class AttachFilesAction extends AttachContextAction { title: localize2('workbench.action.chat.editing.attachFiles.label', "Add Files to Working Set"), f1: false, category: CHAT_CATEGORY, - precondition: CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.EditingSession) + precondition: ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession) }); } diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatCopyActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatCopyActions.ts index 989d2d72a41c0..1c09419c4c615 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatCopyActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatCopyActions.ts @@ -9,7 +9,7 @@ import { Action2, MenuId, registerAction2 } from '../../../../../platform/action import { IClipboardService } from '../../../../../platform/clipboard/common/clipboardService.js'; import { CHAT_CATEGORY, stringifyItem } from './chatActions.js'; import { IChatWidgetService } from '../chat.js'; -import { CONTEXT_RESPONSE_FILTERED } from '../../common/chatContextKeys.js'; +import { ChatContextKeys } from '../../common/chatContextKeys.js'; import { IChatRequestViewModel, IChatResponseViewModel, isRequestVM, isResponseVM } from '../../common/chatViewModel.js'; export function registerChatCopyActions() { @@ -22,7 +22,7 @@ export function registerChatCopyActions() { category: CHAT_CATEGORY, menu: { id: MenuId.ChatContext, - when: CONTEXT_RESPONSE_FILTERED.toNegated(), + when: ChatContextKeys.responseIsFiltered.toNegated(), group: 'copy', } }); @@ -54,7 +54,7 @@ export function registerChatCopyActions() { category: CHAT_CATEGORY, menu: { id: MenuId.ChatContext, - when: CONTEXT_RESPONSE_FILTERED.toNegated(), + when: ChatContextKeys.responseIsFiltered.toNegated(), group: 'copy', } }); diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts index ec8aff95a584f..2e2159439ef35 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts @@ -14,11 +14,11 @@ import { IDialogService } from '../../../../../platform/dialogs/common/dialogs.j import { KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js'; import { IViewsService } from '../../../../services/views/common/viewsService.js'; import { ChatAgentLocation, IChatAgentService } from '../../common/chatAgents.js'; -import { CONTEXT_CHAT_EDITING_PARTICIPANT_REGISTERED, CONTEXT_CHAT_ENABLED, CONTEXT_CHAT_INPUT_HAS_AGENT, CONTEXT_CHAT_INPUT_HAS_TEXT, CONTEXT_CHAT_LOCATION, CONTEXT_CHAT_REQUEST_IN_PROGRESS, CONTEXT_IN_CHAT_INPUT, CONTEXT_LANGUAGE_MODELS_ARE_USER_SELECTABLE } from '../../common/chatContextKeys.js'; +import { ChatContextKeys } from '../../common/chatContextKeys.js'; import { applyingChatEditsContextKey, IChatEditingService } from '../../common/chatEditingService.js'; import { chatAgentLeader, extractAgentAndCommand } from '../../common/chatParserTypes.js'; import { IChatService } from '../../common/chatService.js'; -import { EDITS_VIEW_ID, IChatWidget, IChatWidgetService } from '../chat.js'; +import { EditsViewId, IChatWidget, IChatWidgetService } from '../chat.js'; import { ChatViewPane } from '../chatViewPane.js'; import { CHAT_CATEGORY } from './chatActions.js'; @@ -32,19 +32,29 @@ export interface IChatExecuteActionContext { voice?: IVoiceChatExecuteActionContext; } -export class SubmitAction extends Action2 { +abstract class SubmitAction extends Action2 { + run(accessor: ServicesAccessor, ...args: any[]) { + const context: IChatExecuteActionContext | undefined = args[0]; + + const widgetService = accessor.get(IChatWidgetService); + const widget = context?.widget ?? widgetService.lastFocusedWidget; + widget?.acceptInput(context?.inputValue); + } +} + +export class ChatSubmitAction extends SubmitAction { static readonly ID = 'workbench.action.chat.submit'; constructor() { super({ - id: SubmitAction.ID, + id: ChatSubmitAction.ID, title: localize2('interactive.submit.label', "Send and Dispatch"), f1: false, category: CHAT_CATEGORY, icon: Codicon.send, - precondition: ContextKeyExpr.and(CONTEXT_CHAT_INPUT_HAS_TEXT, CONTEXT_CHAT_REQUEST_IN_PROGRESS.negate(), ContextKeyExpr.or(CONTEXT_CHAT_LOCATION.notEqualsTo(ChatAgentLocation.EditingSession), ContextKeyExpr.and(CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.EditingSession), applyingChatEditsContextKey.toNegated()))), + precondition: ContextKeyExpr.and(ChatContextKeys.inputHasText, ChatContextKeys.requestInProgress.negate(), ChatContextKeys.location.notEqualsTo(ChatAgentLocation.EditingSession)), keybinding: { - when: CONTEXT_IN_CHAT_INPUT, + when: ChatContextKeys.inChatInput, primary: KeyCode.Enter, weight: KeybindingWeight.EditorContrib }, @@ -57,19 +67,45 @@ export class SubmitAction extends Action2 { { id: MenuId.ChatExecute, order: 4, - when: ContextKeyExpr.and(CONTEXT_CHAT_REQUEST_IN_PROGRESS.negate(), ContextKeyExpr.or(CONTEXT_CHAT_LOCATION.notEqualsTo(ChatAgentLocation.EditingSession), ContextKeyExpr.and(CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.EditingSession), applyingChatEditsContextKey.toNegated()))), + when: ContextKeyExpr.and(ChatContextKeys.requestInProgress.negate(), ChatContextKeys.location.notEqualsTo(ChatAgentLocation.EditingSession)), group: 'navigation', }, ] }); } +} - run(accessor: ServicesAccessor, ...args: any[]) { - const context: IChatExecuteActionContext | undefined = args[0]; +export class ChatEditingSessionSubmitAction extends SubmitAction { + static readonly ID = 'workbench.action.edits.submit'; - const widgetService = accessor.get(IChatWidgetService); - const widget = context?.widget ?? widgetService.lastFocusedWidget; - widget?.acceptInput(context?.inputValue); + constructor() { + super({ + id: ChatEditingSessionSubmitAction.ID, + title: localize2('edits.submit.label', "Send"), + f1: false, + category: CHAT_CATEGORY, + icon: Codicon.send, + precondition: ContextKeyExpr.and(ChatContextKeys.inputHasText, ChatContextKeys.requestInProgress.negate(), ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession), applyingChatEditsContextKey.toNegated()), + keybinding: { + when: ChatContextKeys.inChatInput, + primary: KeyCode.Enter, + weight: KeybindingWeight.EditorContrib + }, + menu: [ + { + id: MenuId.ChatExecuteSecondary, + group: 'group_1', + when: ContextKeyExpr.and(ChatContextKeys.requestInProgress.negate(), ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession), applyingChatEditsContextKey.toNegated()), + order: 1 + }, + { + id: MenuId.ChatExecute, + order: 4, + when: ContextKeyExpr.and(ChatContextKeys.requestInProgress.negate(), ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession), applyingChatEditsContextKey.toNegated()), + group: 'navigation', + }, + ] + }); } } @@ -83,11 +119,14 @@ class SubmitWithoutDispatchingAction extends Action2 { f1: false, category: CHAT_CATEGORY, precondition: ContextKeyExpr.and( - CONTEXT_CHAT_INPUT_HAS_TEXT, - CONTEXT_CHAT_REQUEST_IN_PROGRESS.negate(), - ContextKeyExpr.and(CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.Panel))), + ChatContextKeys.inputHasText, + ChatContextKeys.requestInProgress.negate(), + ContextKeyExpr.and(ContextKeyExpr.or( + ChatContextKeys.location.isEqualTo(ChatAgentLocation.Panel), + ChatContextKeys.location.isEqualTo(ChatAgentLocation.Editor), + ))), keybinding: { - when: CONTEXT_IN_CHAT_INPUT, + when: ChatContextKeys.inChatInput, primary: KeyMod.Alt | KeyMod.Shift | KeyCode.Enter, weight: KeybindingWeight.EditorContrib }, @@ -119,11 +158,13 @@ MenuRegistry.appendMenuItem(MenuId.ChatExecute, { order: 3, group: 'navigation', when: ContextKeyExpr.and( - CONTEXT_LANGUAGE_MODELS_ARE_USER_SELECTABLE, + ChatContextKeys.languageModelsAreUserSelectable, ContextKeyExpr.or( - ContextKeyExpr.equals(CONTEXT_CHAT_LOCATION.key, ChatAgentLocation.Panel), - ContextKeyExpr.equals(CONTEXT_CHAT_LOCATION.key, ChatAgentLocation.EditingSession), - ContextKeyExpr.equals(CONTEXT_CHAT_LOCATION.key, ChatAgentLocation.Editor) + ContextKeyExpr.equals(ChatContextKeys.location.key, ChatAgentLocation.Panel), + ContextKeyExpr.equals(ChatContextKeys.location.key, ChatAgentLocation.EditingSession), + ContextKeyExpr.equals(ChatContextKeys.location.key, ChatAgentLocation.Editor), + ContextKeyExpr.equals(ChatContextKeys.location.key, ChatAgentLocation.Notebook), + ContextKeyExpr.equals(ChatContextKeys.location.key, ChatAgentLocation.Terminal) ) ), }); @@ -135,14 +176,15 @@ export class ChatSubmitSecondaryAgentAction extends Action2 { super({ id: ChatSubmitSecondaryAgentAction.ID, title: localize2({ key: 'actions.chat.submitSecondaryAgent', comment: ['Send input from the chat input box to the secondary agent'] }, "Submit to Secondary Agent"), - precondition: ContextKeyExpr.and(CONTEXT_CHAT_INPUT_HAS_TEXT, CONTEXT_CHAT_INPUT_HAS_AGENT.negate(), CONTEXT_CHAT_REQUEST_IN_PROGRESS.negate()), + precondition: ContextKeyExpr.and(ChatContextKeys.inputHasText, ChatContextKeys.inputHasAgent.negate(), ChatContextKeys.requestInProgress.negate()), menu: { id: MenuId.ChatExecuteSecondary, group: 'group_1', - order: 3 + order: 3, + when: ContextKeyExpr.equals(ChatContextKeys.location.key, ChatAgentLocation.Panel) }, keybinding: { - when: CONTEXT_IN_CHAT_INPUT, + when: ChatContextKeys.inChatInput, primary: KeyMod.CtrlCmd | KeyCode.Enter, weight: KeybindingWeight.EditorContrib }, @@ -177,19 +219,29 @@ class SendToChatEditingAction extends Action2 { super({ id: 'workbench.action.chat.sendToChatEditing', title: localize2('chat.sendToChatEditing.label', "Send to Copilot Edits"), - precondition: ContextKeyExpr.and(CONTEXT_CHAT_REQUEST_IN_PROGRESS.negate(), CONTEXT_CHAT_INPUT_HAS_AGENT.negate(), CONTEXT_CHAT_INPUT_HAS_TEXT), + precondition: ContextKeyExpr.and(ChatContextKeys.requestInProgress.negate(), ChatContextKeys.inputHasAgent.negate(), ChatContextKeys.inputHasText), category: CHAT_CATEGORY, f1: false, menu: { id: MenuId.ChatExecuteSecondary, group: 'group_1', order: 4, - when: ContextKeyExpr.and(CONTEXT_CHAT_ENABLED, CONTEXT_CHAT_EDITING_PARTICIPANT_REGISTERED, CONTEXT_CHAT_LOCATION.notEqualsTo(ChatAgentLocation.EditingSession)) + when: ContextKeyExpr.and( + ChatContextKeys.enabled, + ChatContextKeys.editingParticipantRegistered, + ChatContextKeys.location.notEqualsTo(ChatAgentLocation.EditingSession), + ChatContextKeys.location.notEqualsTo(ChatAgentLocation.Editor) + ) }, keybinding: { weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Enter, - when: ContextKeyExpr.and(CONTEXT_CHAT_ENABLED, CONTEXT_CHAT_EDITING_PARTICIPANT_REGISTERED, CONTEXT_CHAT_LOCATION.notEqualsTo(ChatAgentLocation.EditingSession)) + when: ContextKeyExpr.and( + ChatContextKeys.enabled, + ChatContextKeys.editingParticipantRegistered, + ChatContextKeys.location.notEqualsTo(ChatAgentLocation.EditingSession), + ChatContextKeys.location.notEqualsTo(ChatAgentLocation.Editor), + ) } }); } @@ -230,7 +282,7 @@ class SendToChatEditingAction extends Action2 { await currentEditingSession?.stop(); } - const { widget: editingWidget } = await viewsService.openView(EDITS_VIEW_ID) as ChatViewPane; + const { widget: editingWidget } = await viewsService.openView(EditsViewId) as ChatViewPane; for (const attachment of widget.attachmentModel.attachments) { if (attachment.isFile && URI.isUri(attachment.value)) { chatEditingService.currentEditingSessionObs.get()?.addFileToWorkingSet(attachment.value); @@ -252,17 +304,19 @@ class SendToNewChatAction extends Action2 { super({ id: 'workbench.action.chat.sendToNewChat', title: localize2('chat.newChat.label', "Send to New Chat"), - precondition: ContextKeyExpr.and(CONTEXT_CHAT_REQUEST_IN_PROGRESS.negate(), CONTEXT_CHAT_INPUT_HAS_TEXT), + precondition: ContextKeyExpr.and(ChatContextKeys.requestInProgress.negate(), ChatContextKeys.inputHasText), category: CHAT_CATEGORY, f1: false, menu: { id: MenuId.ChatExecuteSecondary, - group: 'group_2' + group: 'group_2', + when: ContextKeyExpr.equals(ChatContextKeys.location.key, ChatAgentLocation.Panel) + }, keybinding: { weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Enter, - when: CONTEXT_IN_CHAT_INPUT, + when: ChatContextKeys.inChatInput, } }); } @@ -292,7 +346,7 @@ export class CancelAction extends Action2 { icon: Codicon.stopCircle, menu: { id: MenuId.ChatExecute, - when: ContextKeyExpr.or(CONTEXT_CHAT_REQUEST_IN_PROGRESS, ContextKeyExpr.and(CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.EditingSession), applyingChatEditsContextKey)), + when: ContextKeyExpr.or(ChatContextKeys.requestInProgress, ContextKeyExpr.and(ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession), applyingChatEditsContextKey)), order: 4, group: 'navigation', }, @@ -327,7 +381,8 @@ export class CancelAction extends Action2 { } export function registerChatExecuteActions() { - registerAction2(SubmitAction); + registerAction2(ChatSubmitAction); + registerAction2(ChatEditingSessionSubmitAction); registerAction2(SubmitWithoutDispatchingAction); registerAction2(CancelAction); registerAction2(SendToNewChatAction); diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatFileTreeActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatFileTreeActions.ts index 6a04473055fce..41ddea9c443d7 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatFileTreeActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatFileTreeActions.ts @@ -10,7 +10,7 @@ import { Action2, registerAction2 } from '../../../../../platform/actions/common import { KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js'; import { CHAT_CATEGORY } from './chatActions.js'; import { IChatWidgetService } from '../chat.js'; -import { CONTEXT_IN_CHAT_SESSION, CONTEXT_CHAT_ENABLED } from '../../common/chatContextKeys.js'; +import { ChatContextKeys } from '../../common/chatContextKeys.js'; import { IChatResponseViewModel, isResponseVM } from '../../common/chatViewModel.js'; export function registerChatFileTreeActions() { @@ -22,9 +22,9 @@ export function registerChatFileTreeActions() { keybinding: { primary: KeyMod.CtrlCmd | KeyCode.F9, weight: KeybindingWeight.WorkbenchContrib, - when: CONTEXT_IN_CHAT_SESSION, + when: ChatContextKeys.inChatSession, }, - precondition: CONTEXT_CHAT_ENABLED, + precondition: ChatContextKeys.enabled, f1: true, category: CHAT_CATEGORY, }); @@ -43,9 +43,9 @@ export function registerChatFileTreeActions() { keybinding: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.F9, weight: KeybindingWeight.WorkbenchContrib, - when: CONTEXT_IN_CHAT_SESSION, + when: ChatContextKeys.inChatSession, }, - precondition: CONTEXT_CHAT_ENABLED, + precondition: ChatContextKeys.enabled, f1: true, category: CHAT_CATEGORY, }); diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatGettingStarted.ts b/src/vs/workbench/contrib/chat/browser/actions/chatGettingStarted.ts index 1cecb8cd9a318..34cd4aa113ece 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatGettingStarted.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatGettingStarted.ts @@ -10,22 +10,27 @@ import { ICommandService } from '../../../../../platform/commands/common/command import { IExtensionService } from '../../../../services/extensions/common/extensions.js'; import { ExtensionIdentifier } from '../../../../../platform/extensions/common/extensions.js'; import { CHAT_OPEN_ACTION_ID } from './chatActions.js'; -import { IExtensionManagementService } from '../../../../../platform/extensionManagement/common/extensionManagement.js'; +import { IExtensionManagementService, InstallOperation } from '../../../../../platform/extensionManagement/common/extensionManagement.js'; +import { IStorageService, StorageScope, StorageTarget } from '../../../../../platform/storage/common/storage.js'; export class ChatGettingStartedContribution extends Disposable implements IWorkbenchContribution { static readonly ID = 'workbench.contrib.chatGettingStarted'; private recentlyInstalled: boolean = false; + private static readonly hideWelcomeView = 'workbench.chat.hideWelcomeView'; + constructor( @IProductService private readonly productService: IProductService, @IExtensionService private readonly extensionService: IExtensionService, @ICommandService private readonly commandService: ICommandService, @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, + @IStorageService private readonly storageService: IStorageService, ) { super(); - if (!this.productService.gitHubEntitlement) { + const hideWelcomeView = this.storageService.getBoolean(ChatGettingStartedContribution.hideWelcomeView, StorageScope.APPLICATION, false); + if (!this.productService.gitHubEntitlement || hideWelcomeView) { return; } @@ -36,7 +41,7 @@ export class ChatGettingStartedContribution extends Disposable implements IWorkb this._register(this.extensionManagementService.onDidInstallExtensions(async (result) => { for (const e of result) { - if (ExtensionIdentifier.equals(this.productService.gitHubEntitlement!.extensionId, e.identifier.id)) { + if (ExtensionIdentifier.equals(this.productService.gitHubEntitlement!.extensionId, e.identifier.id) && e.operation === InstallOperation.Install) { this.recentlyInstalled = true; return; } @@ -49,6 +54,7 @@ export class ChatGettingStartedContribution extends Disposable implements IWorkb const extensionStatus = this.extensionService.getExtensionsStatus(); if (extensionStatus[ext.value].activationTimes && this.recentlyInstalled) { await this.commandService.executeCommand(CHAT_OPEN_ACTION_ID); + this.storageService.store(ChatGettingStartedContribution.hideWelcomeView, true, StorageScope.APPLICATION, StorageTarget.MACHINE); this.recentlyInstalled = false; return; } diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatImportExport.ts b/src/vs/workbench/contrib/chat/browser/actions/chatImportExport.ts index 709d2605bfede..9cb268db6b868 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatImportExport.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatImportExport.ts @@ -14,7 +14,7 @@ import { CHAT_CATEGORY } from './chatActions.js'; import { IChatWidgetService } from '../chat.js'; import { IChatEditorOptions } from '../chatEditor.js'; import { ChatEditorInput } from '../chatEditorInput.js'; -import { CONTEXT_CHAT_ENABLED } from '../../common/chatContextKeys.js'; +import { ChatContextKeys } from '../../common/chatContextKeys.js'; import { isExportableSessionData } from '../../common/chatModel.js'; import { IChatService } from '../../common/chatService.js'; import { IEditorService } from '../../../../services/editor/common/editorService.js'; @@ -29,7 +29,7 @@ export function registerChatExportActions() { id: 'workbench.action.chat.export', category: CHAT_CATEGORY, title: localize2('chat.export.label', "Export Chat..."), - precondition: CONTEXT_CHAT_ENABLED, + precondition: ChatContextKeys.enabled, f1: true, }); } @@ -70,7 +70,7 @@ export function registerChatExportActions() { id: 'workbench.action.chat.import', title: localize2('chat.import.label', "Import Chat..."), category: CHAT_CATEGORY, - precondition: CONTEXT_CHAT_ENABLED, + precondition: ChatContextKeys.enabled, f1: true, }); } diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts index 8f68515d6fca3..e927be89e7735 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts @@ -9,11 +9,11 @@ import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contex import { ServicesAccessor } from '../../../../../platform/instantiation/common/instantiation.js'; import { ActiveEditorContext } from '../../../../common/contextkeys.js'; import { CHAT_CATEGORY } from './chatActions.js'; -import { CHAT_VIEW_ID, IChatWidgetService } from '../chat.js'; +import { ChatViewId, IChatWidgetService } from '../chat.js'; import { ChatEditor, IChatEditorOptions } from '../chatEditor.js'; import { ChatEditorInput } from '../chatEditorInput.js'; import { ChatViewPane } from '../chatViewPane.js'; -import { CONTEXT_CHAT_ENABLED } from '../../common/chatContextKeys.js'; +import { ChatContextKeys } from '../../common/chatContextKeys.js'; import { IEditorGroupsService } from '../../../../services/editor/common/editorGroupsService.js'; import { ACTIVE_GROUP, AUX_WINDOW_GROUP, IEditorService } from '../../../../services/editor/common/editorService.js'; import { IViewsService } from '../../../../services/views/common/viewsService.js'; @@ -31,12 +31,13 @@ export function registerMoveActions() { id: `workbench.action.chat.openInEditor`, title: localize2('chat.openInEditor.label', "Open Chat in Editor"), category: CHAT_CATEGORY, - precondition: CONTEXT_CHAT_ENABLED, + precondition: ChatContextKeys.enabled, f1: true, menu: { id: MenuId.ViewTitle, - when: ContextKeyExpr.equals('view', CHAT_VIEW_ID), - order: 0 + when: ContextKeyExpr.equals('view', ChatViewId), + order: 0, + group: '1_open' }, }); } @@ -53,12 +54,13 @@ export function registerMoveActions() { id: `workbench.action.chat.openInNewWindow`, title: localize2('chat.openInNewWindow.label', "Open Chat in New Window"), category: CHAT_CATEGORY, - precondition: CONTEXT_CHAT_ENABLED, + precondition: ChatContextKeys.enabled, f1: true, menu: { id: MenuId.ViewTitle, - when: ContextKeyExpr.equals('view', CHAT_VIEW_ID), - order: 0 + when: ContextKeyExpr.equals('view', ChatViewId), + order: 0, + group: '1_open' }, }); } @@ -75,7 +77,7 @@ export function registerMoveActions() { id: `workbench.action.chat.openInSidebar`, title: localize2('interactiveSession.openInSidebar.label', "Open Chat in Side Bar"), category: CHAT_CATEGORY, - precondition: CONTEXT_CHAT_ENABLED, + precondition: ChatContextKeys.enabled, f1: true, menu: [{ id: MenuId.EditorTitle, @@ -125,10 +127,10 @@ async function moveToSidebar(accessor: ServicesAccessor): Promise { let view: ChatViewPane; if (chatEditor instanceof ChatEditor && chatEditorInput instanceof ChatEditorInput && chatEditorInput.sessionId) { await editorService.closeEditor({ editor: chatEditor.input, groupId: editorGroupService.activeGroup.id }); - view = await viewsService.openView(CHAT_VIEW_ID) as ChatViewPane; + view = await viewsService.openView(ChatViewId) as ChatViewPane; view.loadSession(chatEditorInput.sessionId, chatEditor.getViewState()); } else { - view = await viewsService.openView(CHAT_VIEW_ID) as ChatViewPane; + view = await viewsService.openView(ChatViewId) as ChatViewPane; } view.focus(); diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatQuickInputActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatQuickInputActions.ts index 43b7bf8b6fae7..3c75e0c503c04 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatQuickInputActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatQuickInputActions.ts @@ -13,7 +13,7 @@ import { ServicesAccessor } from '../../../../../platform/instantiation/common/i import { KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js'; import { CHAT_CATEGORY } from './chatActions.js'; import { IQuickChatOpenOptions, IQuickChatService } from '../chat.js'; -import { CONTEXT_CHAT_ENABLED } from '../../common/chatContextKeys.js'; +import { ChatContextKeys } from '../../common/chatContextKeys.js'; import { InlineChatController } from '../../../inlineChat/browser/inlineChatController.js'; export const ASK_QUICK_QUESTION_ACTION_ID = 'workbench.action.quickchat.toggle'; @@ -103,7 +103,7 @@ class QuickChatGlobalAction extends Action2 { super({ id: ASK_QUICK_QUESTION_ACTION_ID, title: localize2('quickChat', 'Quick Chat'), - precondition: CONTEXT_CHAT_ENABLED, + precondition: ChatContextKeys.enabled, icon: Codicon.commentDiscussion, f1: false, category: CHAT_CATEGORY, @@ -113,7 +113,7 @@ class QuickChatGlobalAction extends Action2 { }, menu: { id: MenuId.ChatCommandCenter, - group: 'c_quickChat', + group: 'e_quickChat', order: 5 }, metadata: { diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts index f0775d757d784..078be1cba9174 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts @@ -4,10 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import { Codicon } from '../../../../../base/common/codicons.js'; +import { Event } from '../../../../../base/common/event.js'; import { KeyCode, KeyMod } from '../../../../../base/common/keyCodes.js'; import { ResourceSet } from '../../../../../base/common/map.js'; import { marked } from '../../../../../base/common/marked/marked.js'; -import { Schemas } from '../../../../../base/common/network.js'; import { basename } from '../../../../../base/common/resources.js'; import { URI } from '../../../../../base/common/uri.js'; import { ServicesAccessor } from '../../../../../editor/browser/editorExtensions.js'; @@ -19,6 +19,8 @@ import { IConfigurationService } from '../../../../../platform/configuration/com import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js'; import { IDialogService } from '../../../../../platform/dialogs/common/dialogs.js'; import { KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js'; +import { ILogService } from '../../../../../platform/log/common/log.js'; +import { IQuickInputService, IQuickPickItem } from '../../../../../platform/quickinput/common/quickInput.js'; import { IEditorService } from '../../../../services/editor/common/editorService.js'; import { IViewsService } from '../../../../services/views/common/viewsService.js'; import { ResourceNotebookCellEdit } from '../../../bulkEdit/browser/bulkCellEdits.js'; @@ -27,13 +29,12 @@ import { INotebookEditor } from '../../../notebook/browser/notebookBrowser.js'; import { CellEditType, CellKind, NOTEBOOK_EDITOR_ID } from '../../../notebook/common/notebookCommon.js'; import { NOTEBOOK_IS_ACTIVE_EDITOR } from '../../../notebook/common/notebookContextKeys.js'; import { ChatAgentLocation, IChatAgentService } from '../../common/chatAgents.js'; -import { CONTEXT_CHAT_EDITING_PARTICIPANT_REGISTERED, CONTEXT_CHAT_LOCATION, CONTEXT_CHAT_RESPONSE_SUPPORT_ISSUE_REPORTING, CONTEXT_IN_CHAT_INPUT, CONTEXT_IN_CHAT_SESSION, CONTEXT_ITEM_ID, CONTEXT_LAST_ITEM_ID, CONTEXT_REQUEST, CONTEXT_RESPONSE, CONTEXT_RESPONSE_ERROR, CONTEXT_RESPONSE_FILTERED, CONTEXT_RESPONSE_VOTE } from '../../common/chatContextKeys.js'; -import { applyingChatEditsFailedContextKey, IChatEditingService, WorkingSetEntryState } from '../../common/chatEditingService.js'; -import { IParsedChatRequest } from '../../common/chatParserTypes.js'; -import { ChatAgentVoteDirection, ChatAgentVoteDownReason, IChatProgress, IChatService } from '../../common/chatService.js'; +import { ChatContextKeys } from '../../common/chatContextKeys.js'; +import { applyingChatEditsFailedContextKey, ChatEditingSessionState, IChatEditingService, isChatEditingActionContext } from '../../common/chatEditingService.js'; +import { IChatRequestModel } from '../../common/chatModel.js'; +import { ChatAgentVoteDirection, ChatAgentVoteDownReason, IChatService } from '../../common/chatService.js'; import { isRequestVM, isResponseVM } from '../../common/chatViewModel.js'; -import { ChatTreeItem, EDITS_VIEW_ID, IChatWidgetService } from '../chat.js'; -import { ChatViewPane } from '../chatViewPane.js'; +import { ChatTreeItem, EditsViewId, IChatWidgetService } from '../chat.js'; import { CHAT_CATEGORY } from './chatActions.js'; export const MarkUnhelpfulActionId = 'workbench.action.chat.markUnhelpful'; @@ -47,17 +48,17 @@ export function registerChatTitleActions() { f1: false, category: CHAT_CATEGORY, icon: Codicon.thumbsup, - toggled: CONTEXT_RESPONSE_VOTE.isEqualTo('up'), + toggled: ChatContextKeys.responseVote.isEqualTo('up'), menu: [{ id: MenuId.ChatMessageFooter, group: 'navigation', order: 1, - when: ContextKeyExpr.and(CONTEXT_RESPONSE, CONTEXT_RESPONSE_ERROR.negate()) + when: ContextKeyExpr.and(ChatContextKeys.isResponse, ChatContextKeys.responseHasError.negate()) }, { id: MENU_INLINE_CHAT_WIDGET_SECONDARY, group: 'navigation', order: 1, - when: ContextKeyExpr.and(CONTEXT_RESPONSE, CONTEXT_RESPONSE_ERROR.negate()) + when: ContextKeyExpr.and(ChatContextKeys.isResponse, ChatContextKeys.responseHasError.negate()) }] }); } @@ -94,17 +95,17 @@ export function registerChatTitleActions() { f1: false, category: CHAT_CATEGORY, icon: Codicon.thumbsdown, - toggled: CONTEXT_RESPONSE_VOTE.isEqualTo('down'), + toggled: ChatContextKeys.responseVote.isEqualTo('down'), menu: [{ id: MenuId.ChatMessageFooter, group: 'navigation', order: 2, - when: ContextKeyExpr.and(CONTEXT_RESPONSE) + when: ContextKeyExpr.and(ChatContextKeys.isResponse) }, { id: MENU_INLINE_CHAT_WIDGET_SECONDARY, group: 'navigation', order: 2, - when: ContextKeyExpr.and(CONTEXT_RESPONSE, CONTEXT_RESPONSE_ERROR.negate()) + when: ContextKeyExpr.and(ChatContextKeys.isResponse, ChatContextKeys.responseHasError.negate()) }] }); } @@ -151,12 +152,12 @@ export function registerChatTitleActions() { id: MenuId.ChatMessageFooter, group: 'navigation', order: 3, - when: ContextKeyExpr.and(CONTEXT_CHAT_RESPONSE_SUPPORT_ISSUE_REPORTING, CONTEXT_RESPONSE) + when: ContextKeyExpr.and(ChatContextKeys.responseSupportsIssueReporting, ChatContextKeys.isResponse) }, { id: MENU_INLINE_CHAT_WIDGET_SECONDARY, group: 'navigation', order: 3, - when: ContextKeyExpr.and(CONTEXT_CHAT_RESPONSE_SUPPORT_ISSUE_REPORTING, CONTEXT_RESPONSE) + when: ContextKeyExpr.and(ChatContextKeys.responseSupportsIssueReporting, ChatContextKeys.isResponse) }] }); } @@ -194,8 +195,8 @@ export function registerChatTitleActions() { id: MenuId.ChatMessageFooter, group: 'navigation', when: ContextKeyExpr.and( - CONTEXT_RESPONSE, - ContextKeyExpr.in(CONTEXT_ITEM_ID.key, CONTEXT_LAST_ITEM_ID.key)) + ChatContextKeys.isResponse, + ContextKeyExpr.in(ChatContextKeys.itemId.key, ChatContextKeys.lastItemId.key)) }, { id: MenuId.ChatEditingWidgetToolbar, @@ -211,7 +212,8 @@ export function registerChatTitleActions() { const chatWidgetService = accessor.get(IChatWidgetService); let item = args[0]; - if (typeof item === 'object' && !!item && 'sessionId' in item) { + if (isChatEditingActionContext(item)) { + // Resolve chat editing action context to the last response VM item = chatWidgetService.getWidgetBySessionId(item.sessionId)?.viewModel?.getItems().at(-1); } if (!isResponseVM(item)) { @@ -277,7 +279,7 @@ export function registerChatTitleActions() { id: MenuId.ChatMessageFooter, group: 'navigation', isHiddenByDefault: true, - when: ContextKeyExpr.and(NOTEBOOK_IS_ACTIVE_EDITOR, CONTEXT_RESPONSE, CONTEXT_RESPONSE_FILTERED.negate()) + when: ContextKeyExpr.and(NOTEBOOK_IS_ACTIVE_EDITOR, ChatContextKeys.isResponse, ChatContextKeys.responseIsFiltered.negate()) } }); } @@ -346,20 +348,20 @@ export function registerChatTitleActions() { f1: false, category: CHAT_CATEGORY, icon: Codicon.x, - precondition: CONTEXT_CHAT_LOCATION.notEqualsTo(ChatAgentLocation.EditingSession), + precondition: ChatContextKeys.location.notEqualsTo(ChatAgentLocation.EditingSession), keybinding: { primary: KeyCode.Delete, mac: { primary: KeyMod.CtrlCmd | KeyCode.Backspace, }, - when: ContextKeyExpr.and(CONTEXT_CHAT_LOCATION.notEqualsTo(ChatAgentLocation.EditingSession), CONTEXT_IN_CHAT_SESSION, CONTEXT_IN_CHAT_INPUT.negate()), + when: ContextKeyExpr.and(ChatContextKeys.location.notEqualsTo(ChatAgentLocation.EditingSession), ChatContextKeys.inChatSession, ChatContextKeys.inChatInput.negate()), weight: KeybindingWeight.WorkbenchContrib, }, menu: { id: MenuId.ChatMessageTitle, group: 'navigation', order: 2, - when: ContextKeyExpr.and(CONTEXT_CHAT_LOCATION.notEqualsTo(ChatAgentLocation.EditingSession), CONTEXT_REQUEST) + when: ContextKeyExpr.and(ChatContextKeys.location.notEqualsTo(ChatAgentLocation.EditingSession), ChatContextKeys.isRequest) } }); } @@ -400,150 +402,123 @@ export function registerChatTitleActions() { f1: false, category: CHAT_CATEGORY, icon: Codicon.goToEditingSession, - precondition: ContextKeyExpr.and(CONTEXT_CHAT_EDITING_PARTICIPANT_REGISTERED, CONTEXT_CHAT_LOCATION.notEqualsTo(ChatAgentLocation.EditingSession)), + precondition: ContextKeyExpr.and(ChatContextKeys.editingParticipantRegistered, ChatContextKeys.requestInProgress.toNegated(), ChatContextKeys.location.isEqualTo(ChatAgentLocation.Panel)), menu: { id: MenuId.ChatMessageFooter, group: 'navigation', order: 4, - when: ContextKeyExpr.false() - // when: ContextKeyExpr.and(CONTEXT_CHAT_ENABLED, CONTEXT_CHAT_EDITING_PARTICIPANT_REGISTERED, CONTEXT_CHAT_LOCATION.notEqualsTo(ChatAgentLocation.EditingSession)) + when: ContextKeyExpr.and(ChatContextKeys.enabled, ChatContextKeys.isResponse, ChatContextKeys.editingParticipantRegistered, ChatContextKeys.location.isEqualTo(ChatAgentLocation.Panel)) } }); } async run(accessor: ServicesAccessor, ...args: any[]) { - if (!accessor.get(IChatAgentService).getDefaultAgent(ChatAgentLocation.EditingSession)) { - return; - } + const logService = accessor.get(ILogService); const chatWidgetService = accessor.get(IChatWidgetService); const chatService = accessor.get(IChatService); + const chatAgentService = accessor.get(IChatAgentService); const viewsService = accessor.get(IViewsService); - const dialogService = accessor.get(IDialogService); + // const dialogService = accessor.get(IDialogService); const chatEditingService = accessor.get(IChatEditingService); + const quickPickService = accessor.get(IQuickInputService); - let item: ChatTreeItem | undefined = args[0]; - if (!isResponseVM(item)) { - const widget = chatWidgetService.lastFocusedWidget; - item = widget?.getFocus(); + const editAgent = chatAgentService.getDefaultAgent(ChatAgentLocation.EditingSession); + if (!editAgent) { + logService.trace('[CHAT_MOVE] No edit agent found'); + return; } - if (!item) { + const sourceWidget = chatWidgetService.lastFocusedWidget; + if (!sourceWidget || !sourceWidget.viewModel) { + logService.trace('[CHAT_MOVE] NO source model'); return; } - const chatModel = chatService.getSession(item.sessionId); - if (chatModel?.initialLocation === ChatAgentLocation.EditingSession) { + const sourceModel = sourceWidget.viewModel.model; + let sourceRequests = sourceModel.getRequests().slice(); + + // when a response is passed (clicked on) ignore all item after it + const [first] = args; + if (isResponseVM(first)) { + const idx = sourceRequests.findIndex(candidate => candidate.id === first.requestId); + if (idx >= 0) { + sourceRequests.length = idx + 1; + } + } + + // when having multiple turns, let the user pick + if (sourceRequests.length > 1) { + const picks: (IQuickPickItem & { request: IChatRequestModel })[] = []; + for (const request of sourceRequests) { + picks.push({ + picked: true, + request: request, + label: request.message.text, + description: request.response?.response.toString() + }); + } + const result = await quickPickService.pick(picks, { + placeHolder: localize('chat.startEditing.pickRequest', "Select requests that you want to use for editing"), + canPickMany: true + }); + if (!result) { + return; + } + sourceRequests = picks.map(pick => pick.request); + } + + if (sourceRequests.length === 0) { + logService.trace('[CHAT_MOVE] NO requests to move'); + } + + await viewsService.openView(EditsViewId); + + let editingSession = chatEditingService.currentEditingSessionObs.get(); + if (!editingSession) { + await Event.toPromise(chatEditingService.onDidCreateEditingSession); + editingSession = chatEditingService.currentEditingSessionObs.get(); return; } - const requestId = isRequestVM(item) ? item.id : - isResponseVM(item) ? item.requestId : undefined; - const request = chatModel?.getRequests().find(candidate => candidate.id === requestId); - - if (request) { - const currentEditingSession = chatEditingService.currentEditingSessionObs.get(); - const currentEdits = currentEditingSession?.entries.get(); - const currentEditCount = currentEdits?.length; - - if (currentEditingSession && currentEditCount) { - - const undecidedEdits = currentEdits.filter((edit) => edit.state.get() === WorkingSetEntryState.Modified); - if (undecidedEdits.length) { - const { result } = await dialogService.prompt({ - title: localize('chat.startEditing.confirmation.title', "Start new editing session?"), - message: localize('chat.startEditing.confirmation.pending.message', "Starting a new editing session will end your current session. Do you want to discard pending edits to {0} files?", undecidedEdits.length), - type: 'info', - buttons: [ - { - label: localize('chat.startEditing.confirmation.discardEdits', "Discard & Continue"), - run: async () => { - await currentEditingSession.reject(); - return true; - } - }, - { - label: localize('chat.startEditing.confirmation.acceptEdits', "Accept & Continue"), - run: async () => { - await currentEditingSession.accept(); - return true; - } - } - ], - }); - - if (!result) { - return; - } - } else { - const result = await dialogService.confirm({ - title: localize('chat.startEditing.confirmation.title', "Start new editing session?"), - message: currentEditCount - ? localize('chat.startEditing.confirmation.message.one', "Starting a new editing session will end your current editing session containing {0} file. Do you wish to proceed?", currentEditCount) - : localize('chat.startEditing.confirmation.message.many', "Starting a new editing session will end your current editing session containing {0} files. Do you wish to proceed?", currentEditCount), - type: 'info', - primaryButton: localize('chat.startEditing.confirmation.primaryButton', "Yes") - }); - - if (!result.confirmed) { - return; - } - } + if (!editingSession) { + return; + } - await currentEditingSession?.stop(); - const existingEditingChatWidget = chatWidgetService.getWidgetBySessionId(currentEditingSession.chatSessionId); - existingEditingChatWidget?.clear(); - existingEditingChatWidget?.attachmentModel.clear(); - } + const state = editingSession.state.get(); + if (state === ChatEditingSessionState.Disposed) { + return; + } - const { widget } = await viewsService.openView(EDITS_VIEW_ID) as ChatViewPane; - if (widget.viewModel) { - const workingSetInputs = new ResourceSet(); - const message: IChatProgress[] = []; - for (const item of request.response?.response.value ?? []) { - if (item.kind === 'inlineReference') { - workingSetInputs.add(isLocation(item.inlineReference) ? item.inlineReference.uri : URI.isUri(item.inlineReference) ? item.inlineReference : item.inlineReference.location.uri); - } - - if (item.kind === 'textEditGroup') { - for (const group of item.edits) { - message.push({ - kind: 'textEdit', - edits: group, - uri: item.uri - }); - } - } else { - message.push(item); - } - } + // adopt request items and collect new working set entries + const workingSetAdditions = new ResourceSet(); + for (const request of sourceRequests) { + await chatService.adoptRequest(editingSession.chatSessionId, request); + this._collectWorkingSetAdditions(request, workingSetAdditions); + } + workingSetAdditions.forEach(uri => editingSession.addFileToWorkingSet(uri)); - chatService.addCompleteRequest(widget.viewModel.sessionId, - request.message as IParsedChatRequest, - request.variableData, - request.attempt, - { - message, - result: request.response?.result, - followups: request.response?.followups - }); - - if (workingSetInputs.size) { - for (const reference of workingSetInputs) { - chatEditingService.currentEditingSessionObs.get()?.addFileToWorkingSet(reference); - } - } else { - for (const { reference } of request.response?.contentReferences ?? []) { - if (URI.isUri(reference) && [Schemas.file, Schemas.vscodeRemote].includes(reference.scheme)) { - chatEditingService.currentEditingSessionObs.get()?.addFileToWorkingSet(reference); - } - } - } - } - widget.focusInput(); + // make request + await chatService.sendRequest(editingSession.chatSessionId, '', { + agentId: editAgent.id, + acceptedConfirmationData: [{ _type: 'toEditTransfer', transferedTurnResults: sourceRequests.map(v => v.response?.result) }], // TODO@jrieken HACKY + confirmation: typeof this.desc.title === 'string' ? this.desc.title : this.desc.title.value + }); + } + private _collectWorkingSetAdditions(request: IChatRequestModel, bucket: ResourceSet) { + for (const item of request.response?.response.value ?? []) { + if (item.kind === 'inlineReference') { + bucket.add(isLocation(item.inlineReference) + ? item.inlineReference.uri + : URI.isUri(item.inlineReference) + ? item.inlineReference + : item.inlineReference.location.uri + ); + } } } + }); } diff --git a/src/vs/workbench/contrib/chat/browser/attachments/implicitContextAttachment.ts b/src/vs/workbench/contrib/chat/browser/attachments/implicitContextAttachment.ts index f8fdee41e1412..73ab2960823fa 100644 --- a/src/vs/workbench/contrib/chat/browser/attachments/implicitContextAttachment.ts +++ b/src/vs/workbench/contrib/chat/browser/attachments/implicitContextAttachment.ts @@ -4,17 +4,25 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from '../../../../../base/browser/dom.js'; +import { StandardMouseEvent } from '../../../../../base/browser/mouseEvent.js'; import { Button } from '../../../../../base/browser/ui/button/button.js'; import { getDefaultHoverDelegate } from '../../../../../base/browser/ui/hover/hoverDelegateFactory.js'; import { Codicon } from '../../../../../base/common/codicons.js'; import { Disposable, DisposableStore } from '../../../../../base/common/lifecycle.js'; import { basename, dirname } from '../../../../../base/common/resources.js'; import { URI } from '../../../../../base/common/uri.js'; +import { ILanguageService } from '../../../../../editor/common/languages/language.js'; +import { IModelService } from '../../../../../editor/common/services/model.js'; import { localize } from '../../../../../nls.js'; -import { FileKind } from '../../../../../platform/files/common/files.js'; +import { getFlatContextMenuActions } from '../../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { IMenuService, MenuId } from '../../../../../platform/actions/common/actions.js'; +import { IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js'; +import { IContextMenuService } from '../../../../../platform/contextview/browser/contextView.js'; +import { FileKind, IFileService } from '../../../../../platform/files/common/files.js'; import { IHoverService } from '../../../../../platform/hover/browser/hover.js'; import { ILabelService } from '../../../../../platform/label/common/label.js'; import { ResourceLabels } from '../../../../browser/labels.js'; +import { ResourceContextKey } from '../../../../common/contextkeys.js'; import { IChatRequestImplicitVariableEntry } from '../../common/chatModel.js'; export class ImplicitContextAttachmentWidget extends Disposable { @@ -25,8 +33,14 @@ export class ImplicitContextAttachmentWidget extends Disposable { constructor( private readonly attachment: IChatRequestImplicitVariableEntry, private readonly resourceLabels: ResourceLabels, - @ILabelService private readonly labelService: ILabelService, + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IContextMenuService private readonly contextMenuService: IContextMenuService, @IHoverService private readonly hoverService: IHoverService, + @ILabelService private readonly labelService: ILabelService, + @IMenuService private readonly menuService: IMenuService, + @IFileService private readonly fileService: IFileService, + @ILanguageService private readonly languageService: ILanguageService, + @IModelService private readonly modelService: IModelService, ) { super(); @@ -71,5 +85,25 @@ export class ImplicitContextAttachmentWidget extends Disposable { e.stopPropagation(); // prevent it from triggering the click handler on the parent immediately after rerendering this.attachment.enabled = !this.attachment.enabled; })); + + // Context menu + const scopedContextKeyService = this.renderDisposables.add(this.contextKeyService.createScoped(this.domNode)); + + const resourceContextKey = this.renderDisposables.add(new ResourceContextKey(scopedContextKeyService, this.fileService, this.languageService, this.modelService)); + resourceContextKey.set(file); + + this.renderDisposables.add(dom.addDisposableListener(this.domNode, dom.EventType.CONTEXT_MENU, async domEvent => { + const event = new StandardMouseEvent(dom.getWindow(domEvent), domEvent); + dom.EventHelper.stop(domEvent, true); + + this.contextMenuService.showContextMenu({ + contextKeyService: scopedContextKeyService, + getAnchor: () => event, + getActions: () => { + const menu = this.menuService.getMenuActions(MenuId.ChatInputResourceAttachmentContext, scopedContextKeyService, { arg: file }); + return getFlatContextMenuActions(menu); + }, + }); + })); } } diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 22755a3f6f7c1..50db228ffcd08 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -44,7 +44,7 @@ import { registerChatCodeBlockActions, registerChatCodeCompareBlockActions } fro import { registerChatContextActions } from './actions/chatContextActions.js'; import { registerChatCopyActions } from './actions/chatCopyActions.js'; import { registerChatDeveloperActions } from './actions/chatDeveloperActions.js'; -import { SubmitAction, registerChatExecuteActions } from './actions/chatExecuteActions.js'; +import { ChatSubmitAction, registerChatExecuteActions } from './actions/chatExecuteActions.js'; import { registerChatFileTreeActions } from './actions/chatFileTreeActions.js'; import { registerChatExportActions } from './actions/chatImportExport.js'; import { registerMoveActions } from './actions/chatMoveActions.js'; @@ -76,6 +76,9 @@ import { LanguageModelToolsService } from './languageModelToolsService.js'; import { ChatViewsWelcomeHandler } from './viewsWelcome/chatViewsWelcomeContributions.js'; import { ILanguageModelIgnoredFilesService, LanguageModelIgnoredFilesService } from '../common/ignoredFiles.js'; import { ChatGettingStartedContribution } from './actions/chatGettingStarted.js'; +import { Extensions, IConfigurationMigrationRegistry } from '../../../common/configuration.js'; +import { ChatEditorOverlayController } from './chatEditorOverlay.js'; +import { ChatRelatedFilesContribution } from './contrib/chatInputRelatedFilesContrib.js'; // Register configuration const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); @@ -116,6 +119,12 @@ configurationRegistry.registerConfiguration({ markdownDescription: nls.localize('chat.commandCenter.enabled', "Controls whether the command center shows a menu for chat actions (requires {0}).", '`#window.commandCenter#`'), default: true }, + 'chat.experimental.offerSetup': { + type: 'boolean', + default: false, + markdownDescription: nls.localize('chat.experimental.offerSetup', "Controls whether setup is offered for Chat if not done already."), + tags: ['experimental', 'onExP'] + }, 'chat.editing.alwaysSaveWithGeneratedChanges': { type: 'boolean', scope: ConfigurationScope.APPLICATION, @@ -134,17 +143,17 @@ configurationRegistry.registerConfiguration({ markdownDescription: nls.localize('chat.editing.confirmEditRequestRetry', "Whether to show a confirmation before retrying a request and its associated edits."), default: true, }, - 'chat.editing.experimental.enableRestoreFile': { - type: 'boolean', - scope: ConfigurationScope.APPLICATION, - markdownDescription: nls.localize('chat.editing.enableRestoreFile', "Whether to show a toggle to restore an earlier version of a file that was edited in a chat editing session request."), - default: false, - }, 'chat.experimental.detectParticipant.enabled': { type: 'boolean', + deprecationMessage: nls.localize('chat.experimental.detectParticipant.enabled.deprecated', "This setting is deprecated. Please use `chat.detectParticipant.enabled` instead."), description: nls.localize('chat.experimental.detectParticipant.enabled', "Enables chat participant autodetection for panel chat."), default: null }, + 'chat.detectParticipant.enabled': { + type: 'boolean', + description: nls.localize('chat.detectParticipant.enabled', "Enables chat participant autodetection for panel chat."), + default: true + }, } }); Registry.as(EditorExtensions.EditorPane).registerEditorPane( @@ -157,6 +166,15 @@ Registry.as(EditorExtensions.EditorPane).registerEditorPane new SyncDescriptor(ChatEditorInput) ] ); +Registry.as(Extensions.ConfigurationMigration).registerConfigurationMigrations([ + { + key: 'chat.experimental.detectParticipant.enabled', + migrateFn: (value, _accessor) => ([ + ['chat.experimental.detectParticipant.enabled', { value: undefined }], + ['chat.detectParticipant.enabled', { value: value !== false }] + ]) + } +]); class ChatResolverContribution extends Disposable { @@ -248,7 +266,7 @@ class ChatSlashStaticSlashCommandsContribution extends Disposable { return (agentLine + '\n' + commandText).trim(); }))).join('\n'); - progress.report({ content: new MarkdownString(agentText, { isTrusted: { enabledCommands: [SubmitAction.ID] } }), kind: 'markdownContent' }); + progress.report({ content: new MarkdownString(agentText, { isTrusted: { enabledCommands: [ChatSubmitAction.ID] } }), kind: 'markdownContent' }); // Report variables if (defaultAgent?.metadata.helpTextVariablesPrefix) { @@ -293,8 +311,9 @@ registerWorkbenchContribution2(ChatSlashStaticSlashCommandsContribution.ID, Chat registerWorkbenchContribution2(ChatExtensionPointHandler.ID, ChatExtensionPointHandler, WorkbenchPhase.BlockStartup); registerWorkbenchContribution2(LanguageModelToolsExtensionPointHandler.ID, LanguageModelToolsExtensionPointHandler, WorkbenchPhase.BlockRestore); registerWorkbenchContribution2(ChatCompatibilityNotifier.ID, ChatCompatibilityNotifier, WorkbenchPhase.Eventually); -registerWorkbenchContribution2(ChatCommandCenterRendering.ID, ChatCommandCenterRendering, WorkbenchPhase.AfterRestored); +registerWorkbenchContribution2(ChatCommandCenterRendering.ID, ChatCommandCenterRendering, WorkbenchPhase.BlockRestore); registerWorkbenchContribution2(ChatImplicitContextContribution.ID, ChatImplicitContextContribution, WorkbenchPhase.Eventually); +registerWorkbenchContribution2(ChatRelatedFilesContribution.ID, ChatRelatedFilesContribution, WorkbenchPhase.Eventually); registerWorkbenchContribution2(ChatEditorSaving.ID, ChatEditorSaving, WorkbenchPhase.AfterRestored); registerWorkbenchContribution2(ChatViewsWelcomeHandler.ID, ChatViewsWelcomeHandler, WorkbenchPhase.BlockStartup); registerWorkbenchContribution2(ChatGettingStartedContribution.ID, ChatGettingStartedContribution, WorkbenchPhase.Eventually); @@ -316,6 +335,7 @@ registerChatEditorActions(); registerEditorFeature(ChatPasteProvidersFeature); registerEditorContribution(ChatEditorController.ID, ChatEditorController, EditorContributionInstantiation.Eventually); +registerEditorContribution(ChatEditorOverlayController.ID, ChatEditorOverlayController, EditorContributionInstantiation.Eventually); registerSingleton(IChatService, ChatService, InstantiationType.Delayed); registerSingleton(IChatWidgetService, ChatWidgetService, InstantiationType.Delayed); diff --git a/src/vs/workbench/contrib/chat/browser/chat.ts b/src/vs/workbench/contrib/chat/browser/chat.ts index 088418b608353..9762b500e2ebb 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.ts @@ -8,7 +8,6 @@ import { IDisposable } from '../../../../base/common/lifecycle.js'; import { URI } from '../../../../base/common/uri.js'; import { ICodeEditor } from '../../../../editor/browser/editorBrowser.js'; import { Selection } from '../../../../editor/common/core/selection.js'; -import { localize } from '../../../../nls.js'; import { MenuId } from '../../../../platform/actions/common/actions.js'; import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; @@ -37,19 +36,18 @@ export interface IChatWidgetService { readonly onDidAddWidget: Event; - getAllWidgets(location: ChatAgentLocation): ReadonlyArray; getWidgetByInputUri(uri: URI): IChatWidget | undefined; getWidgetBySessionId(sessionId: string): IChatWidget | undefined; - getWidgetByLocation(location: ChatAgentLocation): IChatWidget[]; + getWidgetsByLocations(location: ChatAgentLocation): ReadonlyArray; } export async function showChatView(viewsService: IViewsService): Promise { - return (await viewsService.openView(CHAT_VIEW_ID))?.widget; + return (await viewsService.openView(ChatViewId))?.widget; } export async function showEditsView(viewsService: IViewsService): Promise { - return (await viewsService.openView(EDITS_VIEW_ID))?.widget; + return (await viewsService.openView(EditsViewId))?.widget; } export const IQuickChatService = createDecorator('quickChatService'); @@ -92,6 +90,7 @@ export interface IChatCodeBlockInfo { readonly codeBlockIndex: number; readonly element: ChatTreeItem; readonly uri: URI | undefined; + readonly uriPromise: Promise; codemapperUri: URI | undefined; readonly isStreaming: boolean; focus(): void; @@ -183,6 +182,7 @@ export interface IChatWidget { getFocus(): ChatTreeItem | undefined; setInput(query?: string): void; getInput(): string; + refreshParsedInput(): void; logInputHistory(): void; acceptInput(query?: string, options?: IChatAcceptInputOptions): Promise; acceptInputWithPrefix(prefix: string): void; @@ -211,8 +211,6 @@ export interface IChatCodeBlockContextProviderService { registerProvider(provider: ICodeBlockActionContextProvider, id: string): IDisposable; } -export const GeneratingPhrase = localize('generating', "Generating"); +export const ChatViewId = `workbench.panel.chat.view.${CHAT_PROVIDER_ID}`; -export const CHAT_VIEW_ID = `workbench.panel.chat.view.${CHAT_PROVIDER_ID}`; - -export const EDITS_VIEW_ID = 'workbench.panel.chat.view.edits'; +export const EditsViewId = 'workbench.panel.chat.view.edits'; diff --git a/src/vs/workbench/contrib/chat/browser/chatAttachmentModel.ts b/src/vs/workbench/contrib/chat/browser/chatAttachmentModel.ts index 3fae7db826c51..9307985db0a05 100644 --- a/src/vs/workbench/contrib/chat/browser/chatAttachmentModel.ts +++ b/src/vs/workbench/contrib/chat/browser/chatAttachmentModel.ts @@ -41,9 +41,9 @@ export class ChatAttachmentModel extends Disposable { this.addContext(this.asVariableEntry(uri, range)); } - asVariableEntry(uri: URI, range?: IRange) { + asVariableEntry(uri: URI, range?: IRange): IChatRequestVariableEntry { return { - value: uri, + value: range ? { uri, range } : uri, id: uri.toString() + (range?.toString() ?? ''), name: basename(uri), isFile: true, diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatAttachmentsContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatAttachmentsContentPart.ts index 7165e2f8b8e7f..d5936b500bde2 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatAttachmentsContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatAttachmentsContentPart.ts @@ -9,13 +9,17 @@ import { Emitter } from '../../../../../base/common/event.js'; import { Disposable, DisposableStore } from '../../../../../base/common/lifecycle.js'; import { basename, dirname } from '../../../../../base/common/path.js'; import { URI } from '../../../../../base/common/uri.js'; -import { Range } from '../../../../../editor/common/core/range.js'; +import { IRange, Range } from '../../../../../editor/common/core/range.js'; import { localize } from '../../../../../nls.js'; +import { ICommandService } from '../../../../../platform/commands/common/commands.js'; +import { ITextEditorOptions } from '../../../../../platform/editor/common/editor.js'; import { FileKind, IFileService } from '../../../../../platform/files/common/files.js'; import { IHoverService } from '../../../../../platform/hover/browser/hover.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; -import { IOpenerService } from '../../../../../platform/opener/common/opener.js'; +import { IOpenerService, OpenInternalOptions } from '../../../../../platform/opener/common/opener.js'; +import { FolderThemeIcon, IThemeService } from '../../../../../platform/theme/common/themeService.js'; import { ResourceLabels } from '../../../../browser/labels.js'; +import { revealInSideBarCommand } from '../../../files/browser/fileActions.contribution.js'; import { IChatRequestVariableEntry } from '../../common/chatModel.js'; import { ChatResponseReferencePartStatusKind, IChatContentReference } from '../../common/chatService.js'; @@ -33,7 +37,9 @@ export class ChatAttachmentsContentPart extends Disposable { @IInstantiationService private readonly instantiationService: IInstantiationService, @IOpenerService private readonly openerService: IOpenerService, @IHoverService private readonly hoverService: IHoverService, - @IFileService private readonly fileService: IFileService + @IFileService private readonly fileService: IFileService, + @ICommandService private readonly commandService: ICommandService, + @IThemeService private readonly themeService: IThemeService ) { super(); @@ -47,15 +53,15 @@ export class ChatAttachmentsContentPart extends Disposable { const hoverDelegate = this.attachedContextDisposables.add(createInstantHoverDelegate()); this.variables.forEach(async (attachment) => { - const file = URI.isUri(attachment.value) ? attachment.value : attachment.value && typeof attachment.value === 'object' && 'uri' in attachment.value && URI.isUri(attachment.value.uri) ? attachment.value.uri : undefined; + const resource = URI.isUri(attachment.value) ? attachment.value : attachment.value && typeof attachment.value === 'object' && 'uri' in attachment.value && URI.isUri(attachment.value.uri) ? attachment.value.uri : undefined; const range = attachment.value && typeof attachment.value === 'object' && 'range' in attachment.value && Range.isIRange(attachment.value.range) ? attachment.value.range : undefined; - if (file && attachment.isFile && this.workingSet.find(entry => entry.toString() === file.toString())) { + if (resource && attachment.isFile && this.workingSet.find(entry => entry.toString() === resource.toString())) { // Don't render attachment if it's in the working set return; } const widget = dom.append(container, dom.$('.chat-attached-context-attachment.show-file-icons')); - const label = this._contextResourceLabels.create(widget, { supportIcons: true, hoverDelegate, hoverTargetOverrride: widget }); + const label = this._contextResourceLabels.create(widget, { supportIcons: true, hoverDelegate, hoverTargetOverride: widget }); const correspondingContentReference = this.contentReferences.find((ref) => typeof ref.reference === 'object' && 'variableName' in ref.reference && ref.reference.variableName === attachment.name); const isAttachmentOmitted = correspondingContentReference?.options?.status?.kind === ChatResponseReferencePartStatusKind.Omitted; @@ -63,9 +69,9 @@ export class ChatAttachmentsContentPart extends Disposable { let ariaLabel: string | undefined; - if (file && attachment.isFile) { - const fileBasename = basename(file.path); - const fileDirname = dirname(file.path); + if (resource && (attachment.isFile || attachment.isDirectory)) { + const fileBasename = basename(resource.path); + const fileDirname = dirname(resource.path); const friendlyName = `${fileBasename} ${fileDirname}`; if (isAttachmentOmitted) { @@ -76,12 +82,20 @@ export class ChatAttachmentsContentPart extends Disposable { ariaLabel = range ? localize('chat.fileAttachmentWithRange3', "Attached: {0}, line {1} to line {2}.", friendlyName, range.startLineNumber, range.endLineNumber) : localize('chat.fileAttachment3', "Attached: {0}.", friendlyName); } - label.setFile(file, { - fileKind: FileKind.FILE, + const fileOptions = { hidePath: true, - range, title: correspondingContentReference?.options?.status?.description + }; + label.setFile(resource, attachment.isFile ? { + ...fileOptions, + fileKind: FileKind.FILE, + range, + } : { + ...fileOptions, + fileKind: FileKind.FOLDER, + icon: !this.themeService.getFileIconTheme().hasFolderIcons ? FolderThemeIcon : undefined }); + } else if (attachment.isImage) { ariaLabel = localize('chat.imageAttachment', "Attached image, {0}", attachment.name); @@ -134,19 +148,16 @@ export class ChatAttachmentsContentPart extends Disposable { } } - if (file) { + if (resource) { widget.style.cursor = 'pointer'; if (!this.attachedContextDisposables.isDisposed) { this.attachedContextDisposables.add(dom.addDisposableListener(widget, dom.EventType.CLICK, async (e: MouseEvent) => { dom.EventHelper.stop(e, true); - this.openerService.open( - file, - { - fromUserGesture: true, - editorOptions: { - selection: range - } as any - }); + if (attachment.isDirectory) { + this.openResource(resource, true); + } else { + this.openResource(resource, false, range); + } })); } } @@ -156,6 +167,24 @@ export class ChatAttachmentsContentPart extends Disposable { }); } + private openResource(resource: URI, isDirectory: true): void; + private openResource(resource: URI, isDirectory: false, range: IRange | undefined): void; + private openResource(resource: URI, isDirectory?: boolean, range?: IRange): void { + if (isDirectory) { + // Reveal Directory in explorer + this.commandService.executeCommand(revealInSideBarCommand.id, resource); + return; + } + + // Open file in editor + const openTextEditorOptions: ITextEditorOptions | undefined = range ? { selection: range } : undefined; + const options: OpenInternalOptions = { + fromUserGesture: true, + editorOptions: openTextEditorOptions, + }; + this.openerService.open(resource, options); + } + // Helper function to create and replace image private async createImageElements(buffer: ArrayBuffer | Uint8Array, widget: HTMLElement, hoverElement: HTMLElement) { const blob = new Blob([buffer], { type: 'image/png' }); diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart.ts index f0ca929f866c7..e07c684a330a5 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart.ts @@ -5,20 +5,22 @@ import * as dom from '../../../../../base/browser/dom.js'; import { StandardMouseEvent } from '../../../../../base/browser/mouseEvent.js'; -import { IAction } from '../../../../../base/common/actions.js'; import { Codicon } from '../../../../../base/common/codicons.js'; import { Emitter } from '../../../../../base/common/event.js'; -import { Disposable, IDisposable } from '../../../../../base/common/lifecycle.js'; +import { Disposable, DisposableStore, IDisposable } from '../../../../../base/common/lifecycle.js'; +import { autorun } from '../../../../../base/common/observable.js'; import { equalsIgnoreCase } from '../../../../../base/common/strings.js'; import { ThemeIcon } from '../../../../../base/common/themables.js'; import { URI } from '../../../../../base/common/uri.js'; import { MarkdownRenderer } from '../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; import { Range } from '../../../../../editor/common/core/range.js'; import { ILanguageService } from '../../../../../editor/common/languages/language.js'; +import { ITextModel } from '../../../../../editor/common/model.js'; import { getIconClasses } from '../../../../../editor/common/services/getIconClasses.js'; import { IModelService } from '../../../../../editor/common/services/model.js'; -import { IResolvedTextEditorModel, ITextModelService } from '../../../../../editor/common/services/resolverService.js'; -import { createAndFillInContextMenuActions } from '../../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { ITextModelService } from '../../../../../editor/common/services/resolverService.js'; +import { localize } from '../../../../../nls.js'; +import { getFlatContextMenuActions } from '../../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { IMenuService, MenuId } from '../../../../../platform/actions/common/actions.js'; import { IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js'; import { IContextMenuService } from '../../../../../platform/contextview/browser/contextView.js'; @@ -27,11 +29,13 @@ import { IInstantiationService } from '../../../../../platform/instantiation/com import { ILabelService } from '../../../../../platform/label/common/label.js'; import { IEditorService } from '../../../../services/editor/common/editorService.js'; import { IMarkdownVulnerability } from '../../common/annotations.js'; +import { IChatEditingService } from '../../common/chatEditingService.js'; import { IChatProgressRenderableResponseContent } from '../../common/chatModel.js'; import { IChatMarkdownContent } from '../../common/chatService.js'; import { isRequestVM, isResponseVM } from '../../common/chatViewModel.js'; import { CodeBlockModelCollection } from '../../common/codeBlockModelCollection.js'; import { IChatCodeBlockInfo, IChatListItemRendererOptions } from '../chat.js'; +import { AnimatedValue, ObservableAnimatedValue } from '../chatEditorOverlay.js'; import { IChatRendererDelegate } from '../chatListRenderer.js'; import { ChatMarkdownDecorationsRenderer } from '../chatMarkdownDecorationsRenderer.js'; import { ChatEditorOptions } from '../chatOptions.js'; @@ -84,7 +88,7 @@ export class ChatMarkdownContentPart extends Disposable implements IChatContentP return hideEmptyCodeblock; } const index = codeBlockIndex++; - let textModel: Promise; + let textModel: Promise; let range: Range | undefined; let vulns: readonly IMarkdownVulnerability[] | undefined; let codemapperUri: URI | undefined; @@ -92,24 +96,24 @@ export class ChatMarkdownContentPart extends Disposable implements IChatContentP try { const parsedBody = parseLocalFileData(text); range = parsedBody.range && Range.lift(parsedBody.range); - textModel = this.textModelService.createModelReference(parsedBody.uri).then(ref => ref.object); + textModel = this.textModelService.createModelReference(parsedBody.uri).then(ref => ref.object.textEditorModel); } catch (e) { return $('div'); } } else { const sessionId = isResponseVM(element) || isRequestVM(element) ? element.sessionId : ''; const modelEntry = this.codeBlockModelCollection.getOrCreate(sessionId, element, index); - const fastUpdateModelEntry = this.codeBlockModelCollection.updateSync(sessionId, element, index, { text, languageId }); + const fastUpdateModelEntry = this.codeBlockModelCollection.updateSync(sessionId, element, index, { text, languageId, isComplete: isCodeBlockComplete }); vulns = modelEntry.vulns; codemapperUri = fastUpdateModelEntry.codemapperUri; textModel = modelEntry.model; } const hideToolbar = isResponseVM(element) && element.errorDetails?.responseIsFiltered; - const codeBlockInfo = { languageId, textModel, codeBlockIndex: index, element, range, hideToolbar, parentContextKeyService: contextKeyService, vulns, codemapperUri }; + const codeBlockInfo: ICodeBlockData = { languageId, textModel, codeBlockIndex: index, element, range, hideToolbar, parentContextKeyService: contextKeyService, vulns, codemapperUri }; if (!rendererOptions.renderCodeBlockPills || element.isCompleteAddedRequest || !codemapperUri) { - const ref = this.renderCodeBlock(codeBlockInfo, text, currentWidth, rendererOptions.editableCodeBlock); + const ref = this.renderCodeBlock(codeBlockInfo, text, isCodeBlockComplete, currentWidth); this.allRefs.push(ref); // Attach this after updating text/layout of the editor, so it should only be fired when the size updates later (horizontal scrollbar, wrapping) @@ -128,6 +132,7 @@ export class ChatMarkdownContentPart extends Disposable implements IChatContentP // async and the uri might be undefined when it's read immediately return ref.object.uri; } + readonly uriPromise = textModel.then(model => model.uri); public focus() { ref.object.focus(); } @@ -140,11 +145,10 @@ export class ChatMarkdownContentPart extends Disposable implements IChatContentP return ref.object.element; } else { const requestId = isRequestVM(element) ? element.id : element.requestId; - const isStreaming = isResponseVM(element) ? !element.isComplete : !isCodeBlockComplete; - const ref = this.renderCodeBlockPill(element.sessionId, requestId, codeBlockInfo.codemapperUri, !isStreaming); + const ref = this.renderCodeBlockPill(element.sessionId, requestId, codeBlockInfo.codemapperUri, !isCodeBlockComplete); if (isResponseVM(codeBlockInfo.element)) { // TODO@joyceerhl: remove this code when we change the codeblockUri API to make the URI available synchronously - this.codeBlockModelCollection.update(codeBlockInfo.element.sessionId, codeBlockInfo.element, codeBlockInfo.codeBlockIndex, { text, languageId: codeBlockInfo.languageId }).then((e) => { + this.codeBlockModelCollection.update(codeBlockInfo.element.sessionId, codeBlockInfo.element, codeBlockInfo.codeBlockIndex, { text, languageId: codeBlockInfo.languageId, isComplete: isCodeBlockComplete }).then((e) => { // Update the existing object's codemapperUri this.codeblocks[codeBlockInfo.codeBlockIndex].codemapperUri = e.codemapperUri; this._onDidChangeHeight.fire(); @@ -156,11 +160,12 @@ export class ChatMarkdownContentPart extends Disposable implements IChatContentP readonly ownerMarkdownPartId = ownerMarkdownPartId; readonly codeBlockIndex = index; readonly element = element; - readonly isStreaming = isStreaming; + readonly isStreaming = !isCodeBlockComplete; readonly codemapperUri = codemapperUri; public get uri() { return undefined; } + readonly uriPromise = Promise.resolve(undefined); public focus() { return ref.object.element.focus(); } @@ -183,10 +188,10 @@ export class ChatMarkdownContentPart extends Disposable implements IChatContentP this.domNode = result.element; } - private renderCodeBlockPill(sessionId: string, requestId: string, codemapperUri: URI | undefined, isCodeBlockComplete?: boolean): IDisposableReference { + private renderCodeBlockPill(sessionId: string, requestId: string, codemapperUri: URI | undefined, isStreaming: boolean): IDisposableReference { const codeBlock = this.instantiationService.createInstance(CollapsedCodeBlock, sessionId, requestId); if (codemapperUri) { - codeBlock.render(codemapperUri, !isCodeBlockComplete); + codeBlock.render(codemapperUri, isStreaming); } return { object: codeBlock, @@ -195,18 +200,18 @@ export class ChatMarkdownContentPart extends Disposable implements IChatContentP }; } - private renderCodeBlock(data: ICodeBlockData, text: string, currentWidth: number, editableCodeBlock: boolean | undefined): IDisposableReference { + private renderCodeBlock(data: ICodeBlockData, text: string, isComplete: boolean, currentWidth: number): IDisposableReference { const ref = this.editorPool.get(); const editorInfo = ref.object; if (isResponseVM(data.element)) { - this.codeBlockModelCollection.update(data.element.sessionId, data.element, data.codeBlockIndex, { text, languageId: data.languageId }).then((e) => { + this.codeBlockModelCollection.update(data.element.sessionId, data.element, data.codeBlockIndex, { text, languageId: data.languageId, isComplete }).then((e) => { // Update the existing object's codemapperUri this.codeblocks[data.codeBlockIndex].codemapperUri = e.codemapperUri; this._onDidChangeHeight.fire(); }); } - editorInfo.render(data, currentWidth, editableCodeBlock); + editorInfo.render(data, currentWidth); return ref; } @@ -278,7 +283,7 @@ class CollapsedCodeBlock extends Disposable { return this._uri; } - private isStreaming: boolean | undefined; + private readonly _progressStore = new DisposableStore(); constructor( sessionId: string, @@ -290,6 +295,7 @@ class CollapsedCodeBlock extends Disposable { @IContextMenuService private readonly contextMenuService: IContextMenuService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IMenuService private readonly menuService: IMenuService, + @IChatEditingService private readonly chatEditingService: IChatEditingService, ) { super(); this.element = $('.chat-codeblock-pill-widget'); @@ -308,26 +314,23 @@ class CollapsedCodeBlock extends Disposable { getAnchor: () => event, getActions: () => { const menu = this.menuService.getMenuActions(MenuId.ChatEditingCodeBlockContext, this.contextKeyService, { arg: { sessionId, requestId, uri: this.uri } }); - const primary: IAction[] = []; - createAndFillInContextMenuActions(menu, primary); - return primary; + return getFlatContextMenuActions(menu); }, }); })); } - render(uri: URI, isStreaming?: boolean) { - if (this.uri?.toString() === uri.toString() && this.isStreaming === isStreaming) { - return; - } + render(uri: URI, isStreaming?: boolean): void { + this._progressStore.clear(); this._uri = uri; - this.isStreaming = isStreaming; const iconText = this.labelService.getUriBasenameLabel(uri); + const modifiedEntry = this.chatEditingService.currentEditingSession?.entries.get().find(entry => entry.modifiedURI.toString() === uri.toString()); + const isComplete = !modifiedEntry?.isCurrentlyBeingModified.get(); let iconClasses: string[] = []; - if (isStreaming) { + if (isStreaming || !isComplete) { const codicon = ThemeIcon.modify(Codicon.loading, 'spin'); iconClasses = ThemeIcon.asClassNameArray(codicon); } else { @@ -337,6 +340,40 @@ class CollapsedCodeBlock extends Disposable { const iconEl = dom.$('span.icon'); iconEl.classList.add(...iconClasses); - this.element.replaceChildren(iconEl, dom.$('span.icon-label', {}, iconText)); + + const children = [dom.$('span.icon-label', {}, iconText)]; + if (isStreaming) { + children.push(dom.$('span.label-detail', {}, localize('chat.codeblock.generating', "Generating edits..."))); + } else if (!isComplete) { + children.push(dom.$('span.label-detail', {}, '')); + } + this.element.replaceChildren(iconEl, ...children); + this.element.title = this.labelService.getUriLabel(uri, { relative: false }); + + // Show a percentage progress that is driven by the rewrite + const slickRatio = ObservableAnimatedValue.const(0); + let t = Date.now(); + this._progressStore.add(autorun(r => { + const rewriteRatio = modifiedEntry?.rewriteRatio.read(r); + if (rewriteRatio) { + slickRatio.changeAnimation(prev => { + const result = new AnimatedValue(prev.getValue(), rewriteRatio, Date.now() - t); + t = Date.now(); + return result; + }, undefined); + } + + const labelDetail = this.element.querySelector('.label-detail'); + const isComplete = !modifiedEntry?.isCurrentlyBeingModified.read(r); + if (labelDetail && !isStreaming && !isComplete) { + const value = slickRatio.getValue(undefined); + labelDetail.textContent = value === 0 ? localize('chat.codeblock.applying', "Applying edits...") : localize('chat.codeblock.applyingPercentage', "Applying edits ({0}%)...", Math.round(value * 100)); + } else if (labelDetail && !isStreaming && isComplete) { + iconEl.classList.remove(...iconClasses); + const fileKind = uri.path.endsWith('/') ? FileKind.FOLDER : FileKind.FILE; + iconEl.classList.add(...getIconClasses(this.modelService, this.languageService, uri, fileKind)); + labelDetail.textContent = ''; + } + })); } } diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatProgressContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatProgressContentPart.ts index df3964998b2c1..079b9b944b4c6 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatProgressContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatProgressContentPart.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { $ } from '../../../../../base/browser/dom.js'; +import { $, append } from '../../../../../base/browser/dom.js'; import { alert } from '../../../../../base/browser/ui/aria/aria.js'; import { Codicon } from '../../../../../base/common/codicons.js'; import { MarkdownString } from '../../../../../base/common/htmlContent.js'; @@ -45,14 +45,18 @@ export class ChatProgressContentPart extends Disposable implements IChatContentP // this step is in progress, communicate it to SR users alert(progress.content.value); } - const codicon = icon ? icon.id : this.showSpinner ? ThemeIcon.modify(Codicon.loading, 'spin').id : Codicon.check.id; - const markdown = new MarkdownString(`$(${codicon}) ${progress.content.value}`, { + const codicon = icon ? icon : this.showSpinner ? ThemeIcon.modify(Codicon.loading, 'spin') : Codicon.check; + const markdown = new MarkdownString(progress.content.value, { supportThemeIcons: true }); const result = this._register(renderer.render(markdown)); result.element.classList.add('progress-step'); - this.domNode = result.element; + this.domNode = $('.progress-container'); + const iconElement = $('div'); + iconElement.classList.add(...ThemeIcon.asClassNameArray(codicon)); + append(this.domNode, iconElement); + append(this.domNode, result.element); } hasSameContent(other: IChatRendererContent, followingContent: IChatRendererContent[], element: ChatTreeItem): boolean { diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatReferencesContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatReferencesContentPart.ts index b83b47e69886a..6e5e2f3eef61c 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatReferencesContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatReferencesContentPart.ts @@ -6,7 +6,6 @@ import * as dom from '../../../../../base/browser/dom.js'; import { Button } from '../../../../../base/browser/ui/button/button.js'; import { IListRenderer, IListVirtualDelegate } from '../../../../../base/browser/ui/list/list.js'; -import { IAction } from '../../../../../base/common/actions.js'; import { coalesce } from '../../../../../base/common/arrays.js'; import { Codicon } from '../../../../../base/common/codicons.js'; import { Emitter, Event } from '../../../../../base/common/event.js'; @@ -17,7 +16,7 @@ import { basenameOrAuthority, isEqualAuthority } from '../../../../../base/commo import { ThemeIcon } from '../../../../../base/common/themables.js'; import { URI } from '../../../../../base/common/uri.js'; import { localize, localize2 } from '../../../../../nls.js'; -import { createAndFillInContextMenuActions } from '../../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { getFlatContextMenuActions } from '../../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { MenuWorkbenchToolBar } from '../../../../../platform/actions/browser/toolbar.js'; import { Action2, IMenuService, MenuId, registerAction2 } from '../../../../../platform/actions/common/actions.js'; import { IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js'; @@ -49,6 +48,7 @@ const $ = dom.$; export interface IChatReferenceListItem extends IChatContentReference { title?: string; + description?: string; state?: WorkingSetEntryState; } @@ -141,9 +141,7 @@ export class ChatCollapsibleListContentPart extends Disposable implements IChatC getAnchor: () => e.anchor, getActions: () => { const menu = menuService.getMenuActions(MenuId.ChatAttachmentsContext, list.contextKeyService, { shouldForwardArgs: true, arg: uri }); - const primary: IAction[] = []; - createAndFillInContextMenuActions(menu, primary); - return primary; + return getFlatContextMenuActions(menu); } }); })); @@ -385,12 +383,12 @@ class CollapsibleListRenderer implements IListRenderer this._onNeedsRerender.fire()); } else { - const message = toolInvocation.invocationMessage + '…'; + const content = typeof toolInvocation.invocationMessage === 'string' ? + new MarkdownString().appendText(toolInvocation.invocationMessage + '…') : + new MarkdownString(toolInvocation.invocationMessage.value + '…'); const progressMessage: IChatProgressMessage = { kind: 'progressMessage', - content: { value: message } + content }; const iconOverride = toolInvocation.isConfirmed === false ? Codicon.error : diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/media/chatConfirmationWidget.css b/src/vs/workbench/contrib/chat/browser/chatContentParts/media/chatConfirmationWidget.css index bb9d7bc7b7eec..4d711ebf78385 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/media/chatConfirmationWidget.css +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/media/chatConfirmationWidget.css @@ -33,6 +33,7 @@ display: flex; gap: 8px; margin-top: 13px; + flex-wrap: wrap; } .chat-confirmation-widget.hideButtons .chat-confirmation-buttons-container { diff --git a/src/vs/workbench/contrib/chat/browser/chatDragAndDrop.ts b/src/vs/workbench/contrib/chat/browser/chatDragAndDrop.ts index 94bc8b0f15acc..bffe9ac790ea1 100644 --- a/src/vs/workbench/contrib/chat/browser/chatDragAndDrop.ts +++ b/src/vs/workbench/contrib/chat/browser/chatDragAndDrop.ts @@ -8,10 +8,12 @@ import { $, DragAndDropObserver } from '../../../../base/browser/dom.js'; import { renderLabelWithIcons } from '../../../../base/browser/ui/iconLabel/iconLabels.js'; import { coalesce } from '../../../../base/common/arrays.js'; import { Codicon } from '../../../../base/common/codicons.js'; +import { Mimes } from '../../../../base/common/mime.js'; import { basename } from '../../../../base/common/resources.js'; import { URI } from '../../../../base/common/uri.js'; import { localize } from '../../../../nls.js'; import { containsDragType, extractEditorsDropData, IDraggedResourceEditorInput } from '../../../../platform/dnd/browser/dnd.js'; +import { IFileService } from '../../../../platform/files/common/files.js'; import { IThemeService, Themable } from '../../../../platform/theme/common/themeService.js'; import { EditorInput } from '../../../common/editor/editorInput.js'; import { IExtensionService, isProposedApiEnabled } from '../../../services/extensions/common/extensions.js'; @@ -20,7 +22,9 @@ import { ChatInputPart } from './chatInputPart.js'; import { IChatWidgetStyles } from './chatWidget.js'; enum ChatDragAndDropType { - FILE, + FILE_INTERNAL, + FILE_EXTERNAL, + FOLDER, IMAGE } @@ -35,7 +39,8 @@ export class ChatDragAndDrop extends Themable { private readonly inputPart: ChatInputPart, private readonly styles: IChatWidgetStyles, @IThemeService themeService: IThemeService, - @IExtensionService private readonly extensionService: IExtensionService + @IExtensionService private readonly extensionService: IExtensionService, + @IFileService private readonly fileService: IFileService ) { super(themeService); @@ -85,8 +90,11 @@ export class ChatDragAndDrop extends Themable { private onDrop(e: DragEvent): void { this.updateDropFeedback(e, undefined); + this.drop(e); + } - const contexts = this.getAttachContext(e); + private async drop(e: DragEvent): Promise { + const contexts = await this.getAttachContext(e); if (contexts.length === 0) { return; } @@ -94,17 +102,7 @@ export class ChatDragAndDrop extends Themable { e.stopPropagation(); e.preventDefault(); - // Make sure to attach only new contexts - const currentContextIds = this.inputPart.attachmentModel.getAttachmentIDs(); - const filteredContext = []; - for (const context of contexts) { - if (!currentContextIds.has(context.id)) { - currentContextIds.add(context.id); - filteredContext.push(context); - } - } - - this.inputPart.attachmentModel.addContext(...filteredContext); + this.inputPart.attachmentModel.addContext(...contexts); } private updateDropFeedback(e: DragEvent, dropType: ChatDragAndDropType | undefined): void { @@ -116,6 +114,36 @@ export class ChatDragAndDrop extends Themable { this.setOverlay(dropType); } + private guessDropType(e: DragEvent): ChatDragAndDropType | undefined { + // This is an esstimation based on the datatransfer types/items + if (this.isImageDnd(e)) { + return this.extensionService.extensions.some(ext => isProposedApiEnabled(ext, 'chatReferenceBinaryData')) ? ChatDragAndDropType.IMAGE : undefined; + } else if (containsDragType(e, DataTransfers.FILES)) { + return ChatDragAndDropType.FILE_EXTERNAL; + } else if (containsDragType(e, DataTransfers.INTERNAL_URI_LIST)) { + return ChatDragAndDropType.FILE_INTERNAL; + } else if (containsDragType(e, Mimes.uriList)) { + return ChatDragAndDropType.FOLDER; + } + + return undefined; + } + + private isDragEventSupported(e: DragEvent): boolean { + // if guessed drop type is undefined, it means the drop is not supported + const dropType = this.guessDropType(e); + return dropType !== undefined; + } + + private getDropTypeName(type: ChatDragAndDropType): string { + switch (type) { + case ChatDragAndDropType.FILE_INTERNAL: return localize('file', 'File'); + case ChatDragAndDropType.FILE_EXTERNAL: return localize('file', 'File'); + case ChatDragAndDropType.FOLDER: return localize('folder', 'Folder'); + case ChatDragAndDropType.IMAGE: return localize('image', 'Image'); + } + } + private isImageDnd(e: DragEvent): boolean { // Image detection should not have false positives, only false negatives are allowed if (containsDragType(e, 'image')) { @@ -139,42 +167,18 @@ export class ChatDragAndDrop extends Themable { return false; } - private guessDropType(e: DragEvent): ChatDragAndDropType | undefined { - // This is an esstimation based on the datatransfer types/items - if (this.isImageDnd(e)) { - return this.extensionService.extensions.some(ext => isProposedApiEnabled(ext, 'chatReferenceBinaryData')) ? ChatDragAndDropType.IMAGE : undefined; - } else if (containsDragType(e, DataTransfers.FILES, DataTransfers.INTERNAL_URI_LIST)) { - return ChatDragAndDropType.FILE; - } - - return undefined; - } - - private isDragEventSupported(e: DragEvent): boolean { - // if guessed drop type is undefined, it means the drop is not supported - const dropType = this.guessDropType(e); - return dropType !== undefined; - } - - private getDropTypeName(type: ChatDragAndDropType): string { - switch (type) { - case ChatDragAndDropType.FILE: return localize('file', 'File'); - case ChatDragAndDropType.IMAGE: return localize('image', 'Image'); - } - } - - private getAttachContext(e: DragEvent): IChatRequestVariableEntry[] { + private async getAttachContext(e: DragEvent): Promise { if (!this.isDragEventSupported(e)) { return []; } const data = extractEditorsDropData(e); - return coalesce(data.map(editorInput => { + return coalesce(await Promise.all(data.map(editorInput => { return this.resolveAttachContext(editorInput); - })); + }))); } - private resolveAttachContext(editorInput: IDraggedResourceEditorInput): IChatRequestVariableEntry | undefined { + private async resolveAttachContext(editorInput: IDraggedResourceEditorInput): Promise { // Image const imageContext = getImageAttachContext(editorInput); if (imageContext) { @@ -182,7 +186,26 @@ export class ChatDragAndDrop extends Themable { } // File - return getEditorAttachContext(editorInput); + return await this.getEditorAttachContext(editorInput); + } + + private async getEditorAttachContext(editor: EditorInput | IDraggedResourceEditorInput): Promise { + if (!editor.resource) { + return undefined; + } + + let stat; + try { + stat = await this.fileService.stat(editor.resource); + } catch { + return undefined; + } + + if (!stat.isDirectory && !stat.isFile) { + return undefined; + } + + return getResourceAttachContext(editor.resource, stat.isDirectory); } private setOverlay(type: ChatDragAndDropType | undefined): void { @@ -217,20 +240,14 @@ export class ChatDragAndDrop extends Themable { } } -function getEditorAttachContext(editor: EditorInput | IDraggedResourceEditorInput): IChatRequestVariableEntry | undefined { - if (!editor.resource) { - return undefined; - } - - return getFileAttachContext(editor.resource); -} -function getFileAttachContext(resource: URI): IChatRequestVariableEntry | undefined { +function getResourceAttachContext(resource: URI, isDirectory: boolean): IChatRequestVariableEntry | undefined { return { value: resource, id: resource.toString(), name: basename(resource), - isFile: true, + isFile: !isDirectory, + isDirectory, isDynamic: true }; } diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts index 3fa5ba9d642fe..73f41a5bc166d 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts @@ -21,8 +21,8 @@ import { IListService } from '../../../../../platform/list/browser/listService.j import { GroupsOrder, IEditorGroupsService } from '../../../../services/editor/common/editorGroupsService.js'; import { IEditorService } from '../../../../services/editor/common/editorService.js'; import { ChatAgentLocation } from '../../common/chatAgents.js'; -import { CONTEXT_CHAT_INPUT_HAS_TEXT, CONTEXT_CHAT_LOCATION, CONTEXT_CHAT_REQUEST_IN_PROGRESS, CONTEXT_IN_CHAT_INPUT, CONTEXT_IN_CHAT_SESSION, CONTEXT_ITEM_ID, CONTEXT_LAST_ITEM_ID, CONTEXT_REQUEST, CONTEXT_RESPONSE } from '../../common/chatContextKeys.js'; -import { applyingChatEditsContextKey, applyingChatEditsFailedContextKey, CHAT_EDITING_MULTI_DIFF_SOURCE_RESOLVER_SCHEME, chatEditingResourceContextKey, chatEditingWidgetFileStateContextKey, decidedChatEditingResourceContextKey, hasAppliedChatEditsContextKey, hasUndecidedChatEditingResourceContextKey, IChatEditingService, IChatEditingSession, isChatRequestCheckpointed, WorkingSetEntryState } from '../../common/chatEditingService.js'; +import { ChatContextKeys } from '../../common/chatContextKeys.js'; +import { applyingChatEditsFailedContextKey, CHAT_EDITING_MULTI_DIFF_SOURCE_RESOLVER_SCHEME, chatEditingResourceContextKey, chatEditingWidgetFileStateContextKey, decidedChatEditingResourceContextKey, hasAppliedChatEditsContextKey, hasUndecidedChatEditingResourceContextKey, IChatEditingService, IChatEditingSession, WorkingSetEntryState } from '../../common/chatEditingService.js'; import { IChatService } from '../../common/chatService.js'; import { isRequestVM, isResponseVM } from '../../common/chatViewModel.js'; import { CHAT_CATEGORY } from '../actions/chatActions.js'; @@ -57,6 +57,27 @@ abstract class WorkingSetAction extends Action2 { abstract runWorkingSetAction(accessor: ServicesAccessor, currentEditingSession: IChatEditingSession, chatWidget: IChatWidget | undefined, ...uris: URI[]): any; } +registerAction2(class AddFileToWorkingSet extends WorkingSetAction { + constructor() { + super({ + id: 'chatEditing.addFileToWorkingSet', + title: localize2('addFileToWorkingSet', 'Add File'), + icon: Codicon.plus, + menu: [{ + id: MenuId.ChatEditingWidgetModifiedFilesToolbar, + when: ContextKeyExpr.or(ContextKeyExpr.equals(chatEditingWidgetFileStateContextKey.key, WorkingSetEntryState.Transient), ContextKeyExpr.equals(chatEditingWidgetFileStateContextKey.key, WorkingSetEntryState.Suggested)), + order: 0, + group: 'navigation' + }], + }); + } + + async runWorkingSetAction(_accessor: ServicesAccessor, currentEditingSession: IChatEditingSession, _chatWidget: IChatWidget, ...uris: URI[]): Promise { + for (const uri of uris) { + currentEditingSession.addFileToWorkingSet(uri); + } + } +}); registerAction2(class RemoveFileFromWorkingSet extends WorkingSetAction { constructor() { @@ -66,7 +87,7 @@ registerAction2(class RemoveFileFromWorkingSet extends WorkingSetAction { icon: Codicon.close, menu: [{ id: MenuId.ChatEditingWidgetModifiedFilesToolbar, - when: ContextKeyExpr.or(ContextKeyExpr.equals(chatEditingWidgetFileStateContextKey.key, WorkingSetEntryState.Attached), ContextKeyExpr.equals(chatEditingWidgetFileStateContextKey.key, WorkingSetEntryState.Transient)), + when: ContextKeyExpr.or(ContextKeyExpr.equals(chatEditingWidgetFileStateContextKey.key, WorkingSetEntryState.Attached), ContextKeyExpr.equals(chatEditingWidgetFileStateContextKey.key, WorkingSetEntryState.Suggested), ContextKeyExpr.equals(chatEditingWidgetFileStateContextKey.key, WorkingSetEntryState.Transient)), order: 0, group: 'navigation' }], @@ -180,10 +201,10 @@ export class ChatEditingAcceptAllAction extends Action2 { title: localize('accept', 'Accept'), icon: Codicon.check, tooltip: localize('acceptAllEdits', 'Accept All Edits'), - precondition: ContextKeyExpr.and(CONTEXT_CHAT_REQUEST_IN_PROGRESS.negate(), hasUndecidedChatEditingResourceContextKey), + precondition: ContextKeyExpr.and(ChatContextKeys.requestInProgress.negate(), hasUndecidedChatEditingResourceContextKey), keybinding: { primary: KeyMod.CtrlCmd | KeyCode.Enter, - when: ContextKeyExpr.and(CONTEXT_CHAT_REQUEST_IN_PROGRESS.negate(), hasUndecidedChatEditingResourceContextKey, CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.EditingSession), CONTEXT_IN_CHAT_INPUT), + when: ContextKeyExpr.and(ChatContextKeys.requestInProgress.negate(), hasUndecidedChatEditingResourceContextKey, ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession), ChatContextKeys.inChatInput), weight: KeybindingWeight.WorkbenchContrib, }, menu: [ @@ -197,7 +218,7 @@ export class ChatEditingAcceptAllAction extends Action2 { id: MenuId.ChatEditingWidgetToolbar, group: 'navigation', order: 0, - when: ContextKeyExpr.and(applyingChatEditsFailedContextKey.negate(), ContextKeyExpr.or(hasAppliedChatEditsContextKey.negate(), ContextKeyExpr.and(hasUndecidedChatEditingResourceContextKey, ContextKeyExpr.and(CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.EditingSession))))) + when: ContextKeyExpr.and(applyingChatEditsFailedContextKey.negate(), ContextKeyExpr.or(hasAppliedChatEditsContextKey.negate(), ContextKeyExpr.and(hasUndecidedChatEditingResourceContextKey, ContextKeyExpr.and(ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession))))) } ] }); @@ -222,7 +243,7 @@ export class ChatEditingDiscardAllAction extends Action2 { title: localize('discard', 'Discard'), icon: Codicon.discard, tooltip: localize('discardAllEdits', 'Discard All Edits'), - precondition: ContextKeyExpr.and(CONTEXT_CHAT_REQUEST_IN_PROGRESS.negate(), hasUndecidedChatEditingResourceContextKey), + precondition: ContextKeyExpr.and(ChatContextKeys.requestInProgress.negate(), hasUndecidedChatEditingResourceContextKey), menu: [ { when: ContextKeyExpr.equals('resourceScheme', CHAT_EDITING_MULTI_DIFF_SOURCE_RESOLVER_SCHEME), @@ -234,11 +255,11 @@ export class ChatEditingDiscardAllAction extends Action2 { id: MenuId.ChatEditingWidgetToolbar, group: 'navigation', order: 1, - when: ContextKeyExpr.and(applyingChatEditsFailedContextKey.negate(), ContextKeyExpr.or(hasAppliedChatEditsContextKey.negate(), ContextKeyExpr.and(CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.EditingSession), hasUndecidedChatEditingResourceContextKey))) + when: ContextKeyExpr.and(applyingChatEditsFailedContextKey.negate(), ContextKeyExpr.or(hasAppliedChatEditsContextKey.negate(), ContextKeyExpr.and(ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession), hasUndecidedChatEditingResourceContextKey))) } ], keybinding: { - when: ContextKeyExpr.and(CONTEXT_CHAT_REQUEST_IN_PROGRESS.negate(), hasUndecidedChatEditingResourceContextKey, CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.EditingSession), CONTEXT_IN_CHAT_INPUT, CONTEXT_CHAT_INPUT_HAS_TEXT.negate()), + when: ContextKeyExpr.and(ChatContextKeys.requestInProgress.negate(), hasUndecidedChatEditingResourceContextKey, ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession), ChatContextKeys.inChatInput, ChatContextKeys.inputHasText.negate()), weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.CtrlCmd | KeyCode.Backspace, }, @@ -247,10 +268,28 @@ export class ChatEditingDiscardAllAction extends Action2 { async run(accessor: ServicesAccessor, ...args: any[]): Promise { const chatEditingService = accessor.get(IChatEditingService); + const dialogService = accessor.get(IDialogService); const currentEditingSession = chatEditingService.currentEditingSession; if (!currentEditingSession) { return; } + + // Ask for confirmation if there are any edits + const entries = currentEditingSession.entries.get(); + if (entries.length > 0) { + const confirmation = await dialogService.confirm({ + title: localize('chat.editing.discardAll.confirmation.title', "Discard all edits?"), + message: entries.length === 1 + ? localize('chat.editing.discardAll.confirmation.oneFile', "This will undo changes made by {0} in {1}. Do you want to proceed?", 'Copilot Edits', basename(entries[0].modifiedURI)) + : localize('chat.editing.discardAll.confirmation.manyFiles', "This will undo changes made by {0} in {1} files. Do you want to proceed?", 'Copilot Edits', entries.length), + primaryButton: localize('chat.editing.discardAll.confirmation.primaryButton', "Yes"), + type: 'info' + }); + if (!confirmation.confirmed) { + return; + } + } + await currentEditingSession.reject(); } } @@ -273,7 +312,7 @@ export class ChatEditingShowChangesAction extends Action2 { id: MenuId.ChatEditingWidgetToolbar, group: 'navigation', order: 4, - when: ContextKeyExpr.and(applyingChatEditsFailedContextKey.negate(), ContextKeyExpr.or(hasAppliedChatEditsContextKey.negate(), ContextKeyExpr.and(hasAppliedChatEditsContextKey, hasUndecidedChatEditingResourceContextKey, CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.EditingSession)))) + when: ContextKeyExpr.and(applyingChatEditsFailedContextKey.negate(), ContextKeyExpr.or(hasAppliedChatEditsContextKey.negate(), ContextKeyExpr.and(hasAppliedChatEditsContextKey, hasUndecidedChatEditingResourceContextKey, ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession)))) } ], }); @@ -297,7 +336,7 @@ registerAction2(class AddFilesToWorkingSetAction extends Action2 { title: localize2('workbench.action.chat.addSelectedFilesToWorkingSet.label', "Add Selected Files to Working Set"), icon: Codicon.attach, category: CHAT_CATEGORY, - precondition: CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.EditingSession), + precondition: ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession), f1: true }); } @@ -334,60 +373,6 @@ registerAction2(class AddFilesToWorkingSetAction extends Action2 { } }); - -registerAction2(class RestoreWorkingSetAction extends Action2 { - constructor() { - super({ - id: 'workbench.action.chat.restoreFile', - title: localize2('chat.restoreSnapshot.label', 'Restore File Snapshot'), - f1: false, - icon: Codicon.target, - shortTitle: localize2('chat.restoreSnapshot.shortTitle', 'Restore Snapshot'), - toggled: { - condition: isChatRequestCheckpointed, - title: localize2('chat.restoreSnapshot.title', 'Using Snapshot').value, - tooltip: localize('chat.restoreSnapshot.tooltip', 'Toggle to use a previous snapshot of an edited file in your next request') - }, - precondition: ContextKeyExpr.and(applyingChatEditsContextKey.negate(), CONTEXT_CHAT_REQUEST_IN_PROGRESS.negate()), - menu: { - id: MenuId.ChatMessageFooter, - group: 'navigation', - order: 1000, - when: ContextKeyExpr.and(ContextKeyExpr.equals('config.chat.editing.experimental.enableRestoreFile', true), CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.EditingSession), CONTEXT_RESPONSE, ContextKeyExpr.notIn(CONTEXT_ITEM_ID.key, CONTEXT_LAST_ITEM_ID.key)) - } - }); - } - - override run(accessor: ServicesAccessor, ...args: any[]): void { - const chatEditingService = accessor.get(IChatEditingService); - const item = args[0]; - if (!isResponseVM(item)) { - return; - } - - const { session, requestId } = item.model; - const shouldUnsetCheckpoint = requestId === session.checkpoint?.id; - if (shouldUnsetCheckpoint) { - // Unset the existing checkpoint - session.setCheckpoint(undefined); - } else { - session.setCheckpoint(requestId); - } - - // The next request is associated with the working set snapshot representing - // the 'good state' from this checkpointed request - const chatService = accessor.get(IChatService); - const chatModel = chatService.getSession(item.sessionId); - const chatRequests = chatModel?.getRequests(); - const snapshot = chatRequests?.find((v, i) => i > 0 && chatRequests[i - 1]?.id === requestId); - if (!shouldUnsetCheckpoint && snapshot !== undefined) { - chatEditingService.restoreSnapshot(snapshot.id); - } else if (shouldUnsetCheckpoint) { - chatEditingService.restoreSnapshot(undefined); - } - } -}); - registerAction2(class RemoveAction extends Action2 { constructor() { super({ @@ -401,7 +386,7 @@ registerAction2(class RemoveAction extends Action2 { mac: { primary: KeyMod.CtrlCmd | KeyCode.Backspace, }, - when: ContextKeyExpr.and(CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.EditingSession), CONTEXT_IN_CHAT_SESSION, CONTEXT_IN_CHAT_INPUT.negate()), + when: ContextKeyExpr.and(ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession), ChatContextKeys.inChatSession, ChatContextKeys.inChatInput.negate()), weight: KeybindingWeight.WorkbenchContrib, }, menu: [ @@ -409,7 +394,7 @@ registerAction2(class RemoveAction extends Action2 { id: MenuId.ChatMessageTitle, group: 'navigation', order: 2, - when: ContextKeyExpr.and(CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.EditingSession), CONTEXT_REQUEST) + when: ContextKeyExpr.and(ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession), ChatContextKeys.isRequest) } ] }); @@ -507,7 +492,7 @@ registerAction2(class OpenWorkingSetHistoryAction extends Action2 { id: MenuId.ChatEditingCodeBlockContext, group: 'navigation', order: 0, - when: ContextKeyExpr.notIn(CONTEXT_ITEM_ID.key, CONTEXT_LAST_ITEM_ID.key), + when: ContextKeyExpr.notIn(ChatContextKeys.itemId.key, ChatContextKeys.lastItemId.key), },] }); } diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedFileEntry.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedFileEntry.ts index dfd387cc97be1..9054e245f92f8 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedFileEntry.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedFileEntry.ts @@ -9,25 +9,25 @@ import { Disposable, IReference, toDisposable } from '../../../../../base/common import { IObservable, ITransaction, observableValue, transaction } from '../../../../../base/common/observable.js'; import { themeColorFromId } from '../../../../../base/common/themables.js'; import { URI } from '../../../../../base/common/uri.js'; -import { IBulkEditService } from '../../../../../editor/browser/services/bulkEditService.js'; import { EditOperation, ISingleEditOperation } from '../../../../../editor/common/core/editOperation.js'; -import { OffsetEdit, SingleOffsetEdit } from '../../../../../editor/common/core/offsetEdit.js'; -import { OffsetRange } from '../../../../../editor/common/core/offsetRange.js'; -import { Range } from '../../../../../editor/common/core/range.js'; +import { OffsetEdit } from '../../../../../editor/common/core/offsetEdit.js'; import { IDocumentDiff, nullDocumentDiff } from '../../../../../editor/common/diff/documentDiffProvider.js'; import { TextEdit } from '../../../../../editor/common/languages.js'; import { ILanguageService } from '../../../../../editor/common/languages/language.js'; -import { IIdentifiedSingleEditOperation, IModelDeltaDecoration, ITextModel, OverviewRulerLane } from '../../../../../editor/common/model.js'; +import { IModelDeltaDecoration, ITextModel, OverviewRulerLane } from '../../../../../editor/common/model.js'; import { SingleModelEditStackElement } from '../../../../../editor/common/model/editStack.js'; import { ModelDecorationOptions, createTextBufferFactoryFromSnapshot } from '../../../../../editor/common/model/textModel.js'; +import { OffsetEdits } from '../../../../../editor/common/model/textModelOffsetEdit.js'; import { IEditorWorkerService } from '../../../../../editor/common/services/editorWorker.js'; import { IModelService } from '../../../../../editor/common/services/model.js'; import { IResolvedTextEditorModel, ITextModelService } from '../../../../../editor/common/services/resolverService.js'; -import { IModelContentChange, IModelContentChangedEvent } from '../../../../../editor/common/textModelEvents.js'; +import { IModelContentChangedEvent } from '../../../../../editor/common/textModelEvents.js'; import { localize } from '../../../../../nls.js'; import { IFileService } from '../../../../../platform/files/common/files.js'; import { editorSelectionBackground } from '../../../../../platform/theme/common/colorRegistry.js'; import { IUndoRedoService } from '../../../../../platform/undoRedo/common/undoRedo.js'; +import { SaveReason } from '../../../../common/editor.js'; +import { IResolvedTextFileEditorModel } from '../../../../services/textfile/common/textfiles.js'; import { IChatAgentResult } from '../../common/chatAgents.js'; import { ChatEditKind, IModifiedFileEntry, WorkingSetEntryState } from '../../common/chatEditingService.js'; import { IChatService } from '../../common/chatService.js'; @@ -36,11 +36,14 @@ import { ChatEditingSnapshotTextModelContentProvider, ChatEditingTextModelConten export class ChatEditingModifiedFileEntry extends Disposable implements IModifiedFileEntry { public static readonly scheme = 'modified-file-entry'; - static lastEntryId = 0; + private static lastEntryId = 0; public readonly entryId = `${ChatEditingModifiedFileEntry.scheme}::${++ChatEditingModifiedFileEntry.lastEntryId}`; - public readonly docSnapshot: ITextModel; + private readonly docSnapshot: ITextModel; + private readonly originalContent; private readonly doc: ITextModel; + private readonly docFileEditorModel: IResolvedTextFileEditorModel; + private _allEditsAreFromUs: boolean = true; private readonly _onDidDelete = this._register(new Emitter()); public get onDidDelete() { @@ -56,7 +59,7 @@ export class ChatEditingModifiedFileEntry extends Disposable implements IModifie } get modifiedURI(): URI { - return this.doc.uri; + return this.modifiedModel.uri; } get modifiedModel(): ITextModel { @@ -73,6 +76,11 @@ export class ChatEditingModifiedFileEntry extends Disposable implements IModifie return this._isCurrentlyBeingModifiedObs; } + private readonly _rewriteRatioObs = observableValue(this, 0); + public get rewriteRatio(): IObservable { + return this._rewriteRatioObs; + } + private _isFirstEditAfterStartOrSnapshot: boolean = true; private _edit: OffsetEdit = OffsetEdit.empty; private _isEditFromUs: boolean = false; @@ -109,7 +117,6 @@ export class ChatEditingModifiedFileEntry extends Disposable implements IModifie } constructor( - public readonly resource: URI, resourceRef: IReference, private readonly _multiDiffEntryDelegate: { collapse: (transaction: ITransaction | undefined) => void }, private _telemetryInfo: IModifiedEntryTelemetryInfo, @@ -117,7 +124,6 @@ export class ChatEditingModifiedFileEntry extends Disposable implements IModifie @IModelService modelService: IModelService, @ITextModelService textModelService: ITextModelService, @ILanguageService languageService: ILanguageService, - @IBulkEditService public readonly bulkEditService: IBulkEditService, @IChatService private readonly _chatService: IChatService, @IEditorWorkerService private readonly _editorWorkerService: IEditorWorkerService, @IUndoRedoService private readonly _undoRedoService: IUndoRedoService, @@ -127,12 +133,15 @@ export class ChatEditingModifiedFileEntry extends Disposable implements IModifie if (kind === ChatEditKind.Created) { this.createdInRequestId = this._telemetryInfo.requestId; } + this.docFileEditorModel = this._register(resourceRef).object as IResolvedTextFileEditorModel; this.doc = resourceRef.object.textEditorModel; + + this.originalContent = this.doc.getValue(); const docSnapshot = this.docSnapshot = this._register( modelService.createModel( createTextBufferFactoryFromSnapshot(this.doc.createSnapshot()), languageService.createById(this.doc.getLanguageId()), - ChatEditingTextModelContentProvider.getFileURI(this.entryId, resource.path), + ChatEditingTextModelContentProvider.getFileURI(this.entryId, this.modifiedURI.path), false ) ); @@ -147,9 +156,14 @@ export class ChatEditingModifiedFileEntry extends Disposable implements IModifie this._register(reference); })(); - this._register(resourceRef); this._register(this.doc.onDidChangeContent(e => this._mirrorEdits(e))); + this._register(this._fileService.watch(this.modifiedURI)); + this._register(this._fileService.onDidFilesChange(e => { + if (e.affects(this.modifiedURI) && kind === ChatEditKind.Created && e.gotDeleted()) { + this._onDidDelete.fire(); + } + })); this._register(toDisposable(() => { this._clearCurrentEditLineDecoration(); @@ -190,17 +204,21 @@ export class ChatEditingModifiedFileEntry extends Disposable implements IModifie } acceptStreamingEditsStart(tx: ITransaction) { - this._isCurrentlyBeingModifiedObs.set(false, tx); - this._clearCurrentEditLineDecoration(); + this._resetEditsState(tx); } acceptStreamingEditsEnd(tx: ITransaction) { + this._resetEditsState(tx); + } + + private _resetEditsState(tx: ITransaction): void { this._isCurrentlyBeingModifiedObs.set(false, tx); + this._rewriteRatioObs.set(0, tx); this._clearCurrentEditLineDecoration(); } private _mirrorEdits(event: IModelContentChangedEvent) { - const edit = fromContentChange(event.changes); + const edit = OffsetEdits.fromContentChanges(event.changes); if (this._isEditFromUs) { const e_sum = this._edit; @@ -234,16 +252,30 @@ export class ChatEditingModifiedFileEntry extends Disposable implements IModifie // user edits overlaps/conflicts with AI edits this._edit = e_ai.compose(e_user); } else { - const edits = toEditOperations(e_user_r, this.docSnapshot); + const edits = OffsetEdits.asEditOperations(e_user_r, this.docSnapshot); this.docSnapshot.applyEdits(edits); this._edit = e_ai.tryRebase(e_user_r); } + + this._allEditsAreFromUs = false; + } + + if (!this.isCurrentlyBeingModified.get()) { + const didResetToOriginalContent = this.doc.getValue() === this.originalContent; + const currentState = this._stateObs.get(); + switch (currentState) { + case WorkingSetEntryState.Modified: + if (didResetToOriginalContent) { + this._stateObs.set(WorkingSetEntryState.Rejected, undefined); + break; + } + } } this._updateDiffInfoSeq(!this._isEditFromUs); } - acceptAgentEdits(textEdits: TextEdit[]): void { + acceptAgentEdits(textEdits: TextEdit[], isLastEdits: boolean): void { // highlight edits this._editDecorations = this.doc.deltaDecorations(this._editDecorations, textEdits.map(edit => { @@ -262,11 +294,21 @@ export class ChatEditingModifiedFileEntry extends Disposable implements IModifie this._undoRedoService.pushElement(new SingleModelEditStackElement(label, 'chat.edit', this.doc, null)); } - this._applyEdits(textEdits.map(TextEdit.asEditOperation)); + const ops = textEdits.map(TextEdit.asEditOperation); + this._applyEdits(ops); transaction((tx) => { - this._stateObs.set(WorkingSetEntryState.Modified, tx); - this._isCurrentlyBeingModifiedObs.set(true, tx); + if (!isLastEdits) { + this._stateObs.set(WorkingSetEntryState.Modified, tx); + this._isCurrentlyBeingModifiedObs.set(true, tx); + const maxLineNumber = ops.reduce((max, op) => Math.max(max, op.range.endLineNumber), 0); + const lineCount = this.doc.getLineCount(); + this._rewriteRatioObs.set(Math.min(1, maxLineNumber / lineCount), tx); + } else { + this._resetEditsState(tx); + this._updateDiffInfoSeq(true); + this._rewriteRatioObs.set(1, tx); + } }); } @@ -291,6 +333,10 @@ export class ChatEditingModifiedFileEntry extends Disposable implements IModifie private async _updateDiffInfo(fast: boolean): Promise { + if (this.docSnapshot.isDisposed() || this.doc.isDisposed()) { + return; + } + const docVersionNow = this.doc.getVersionId(); const snapshotVersionNow = this.docSnapshot.getVersionId(); @@ -304,11 +350,15 @@ export class ChatEditingModifiedFileEntry extends Disposable implements IModifie timeout(fast ? 50 : 800) // DON't diff too fast ]); + if (this.docSnapshot.isDisposed() || this.doc.isDisposed()) { + return; + } + // only update the diff if the documents didn't change in the meantime if (this.doc.getVersionId() === docVersionNow && this.docSnapshot.getVersionId() === snapshotVersionNow) { const diff2 = diff ?? nullDocumentDiff; this._diffInfo.set(diff2, undefined); - this._edit = fromDiff(this.docSnapshot, this.doc, diff2); + this._edit = OffsetEdits.fromLineRangeMapping(this.docSnapshot, this.doc, diff2.changes); } } @@ -319,6 +369,7 @@ export class ChatEditingModifiedFileEntry extends Disposable implements IModifie } this.docSnapshot.setValue(this.doc.createSnapshot()); + this._edit = OffsetEdit.empty; this._stateObs.set(WorkingSetEntryState.Accepted, transaction); await this.collapse(transaction); this._notifyAction('accepted'); @@ -333,11 +384,15 @@ export class ChatEditingModifiedFileEntry extends Disposable implements IModifie this._stateObs.set(WorkingSetEntryState.Rejected, transaction); this._notifyAction('rejected'); if (this.createdInRequestId === this._telemetryInfo.requestId) { - await this._fileService.del(this.resource); + await this._fileService.del(this.modifiedURI); this._onDidDelete.fire(); - this.dispose(); } else { this._setDocValue(this.docSnapshot.getValue()); + if (this._allEditsAreFromUs) { + // save the file after discarding so that the dirty indicator goes away + // and so that an intermediate saved state gets reverted + await this.docFileEditorModel.save({ reason: SaveReason.EXPLICIT }); + } await this.collapse(transaction); } } @@ -357,7 +412,7 @@ export class ChatEditingModifiedFileEntry extends Disposable implements IModifie private _notifyAction(outcome: 'accepted' | 'rejected') { this._chatService.notifyUserAction({ - action: { kind: 'chatEditingSessionAction', uri: this.resource, hasRemainingEdits: false, outcome }, + action: { kind: 'chatEditingSessionAction', uri: this.modifiedURI, hasRemainingEdits: false, outcome }, agentId: this._telemetryInfo.agentId, command: this._telemetryInfo.command, sessionId: this._telemetryInfo.sessionId, @@ -385,39 +440,3 @@ export interface ISnapshotEntry { readonly state: WorkingSetEntryState; telemetryInfo: IModifiedEntryTelemetryInfo; } - -function toEditOperations(offsetEdit: OffsetEdit, doc: ITextModel): IIdentifiedSingleEditOperation[] { - const edits: IIdentifiedSingleEditOperation[] = []; - for (const singleEdit of offsetEdit.edits) { - const range = Range.fromPositions( - doc.getPositionAt(singleEdit.replaceRange.start), - doc.getPositionAt(singleEdit.replaceRange.start + singleEdit.replaceRange.length) - ); - edits.push(EditOperation.replace(range, singleEdit.newText)); - } - return edits; -} - -function fromContentChange(contentChanges: readonly IModelContentChange[]) { - const editsArr = contentChanges.map(c => new SingleOffsetEdit(OffsetRange.ofStartAndLength(c.rangeOffset, c.rangeLength), c.text)); - editsArr.reverse(); - const edits = new OffsetEdit(editsArr); - return edits; -} - -function fromDiff(original: ITextModel, modified: ITextModel, diff: IDocumentDiff): OffsetEdit { - const edits: SingleOffsetEdit[] = []; - for (const c of diff.changes) { - for (const i of c.innerChanges ?? []) { - const newText = modified.getValueInRange(i.modifiedRange); - - const startOrig = original.getOffsetAt(i.originalRange.getStartPosition()); - const endExOrig = original.getOffsetAt(i.originalRange.getEndPosition()); - const origRange = new OffsetRange(startOrig, endExOrig); - - edits.push(new SingleOffsetEdit(origRange, newText)); - } - } - - return new OffsetEdit(edits); -} diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingService.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingService.ts index aacde88330c45..4cbe061241983 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingService.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingService.ts @@ -3,13 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { compareBy, delta } from '../../../../../base/common/arrays.js'; +import { coalesce, compareBy, delta } from '../../../../../base/common/arrays.js'; import { AsyncIterableSource } from '../../../../../base/common/async.js'; import { CancellationToken, CancellationTokenSource } from '../../../../../base/common/cancellation.js'; import { Codicon } from '../../../../../base/common/codicons.js'; import { BugIndicatingError } from '../../../../../base/common/errors.js'; import { Emitter, Event } from '../../../../../base/common/event.js'; -import { Disposable, DisposableStore, IDisposable } from '../../../../../base/common/lifecycle.js'; +import { Disposable, DisposableStore, IDisposable, toDisposable } from '../../../../../base/common/lifecycle.js'; import { ResourceMap } from '../../../../../base/common/map.js'; import { derived, IObservable, observableValue, runOnChange, ValueWithChangeEventFromObservable } from '../../../../../base/common/observable.js'; import { compare } from '../../../../../base/common/strings.js'; @@ -32,9 +32,9 @@ import { IEditorService } from '../../../../services/editor/common/editorService import { MultiDiffEditor } from '../../../multiDiffEditor/browser/multiDiffEditor.js'; import { MultiDiffEditorInput } from '../../../multiDiffEditor/browser/multiDiffEditorInput.js'; import { IMultiDiffSourceResolver, IMultiDiffSourceResolverService, IResolvedMultiDiffSource, MultiDiffEditorItem } from '../../../multiDiffEditor/browser/multiDiffSourceResolverService.js'; -import { ICodeMapperResponse, ICodeMapperService } from '../../common/chatCodeMapperService.js'; -import { CONTEXT_CHAT_EDITING_CAN_REDO, CONTEXT_CHAT_EDITING_CAN_UNDO } from '../../common/chatContextKeys.js'; -import { applyingChatEditsContextKey, applyingChatEditsFailedContextKey, CHAT_EDITING_MULTI_DIFF_SOURCE_RESOLVER_SCHEME, chatEditingMaxFileAssignmentName, chatEditingResourceContextKey, ChatEditingSessionState, decidedChatEditingResourceContextKey, defaultChatEditingMaxFileLimit, hasAppliedChatEditsContextKey, hasUndecidedChatEditingResourceContextKey, IChatEditingService, IChatEditingSession, IChatEditingSessionStream, inChatEditingSessionContextKey, WorkingSetEntryState } from '../../common/chatEditingService.js'; +import { ChatAgentLocation, IChatAgentService } from '../../common/chatAgents.js'; +import { ChatContextKeys } from '../../common/chatContextKeys.js'; +import { applyingChatEditsContextKey, applyingChatEditsFailedContextKey, CHAT_EDITING_MULTI_DIFF_SOURCE_RESOLVER_SCHEME, chatEditingMaxFileAssignmentName, chatEditingResourceContextKey, ChatEditingSessionState, decidedChatEditingResourceContextKey, defaultChatEditingMaxFileLimit, hasAppliedChatEditsContextKey, hasUndecidedChatEditingResourceContextKey, IChatEditingService, IChatEditingSession, IChatEditingSessionStream, IChatRelatedFile, IChatRelatedFilesProvider, IModifiedFileEntry, inChatEditingSessionContextKey, WorkingSetEntryState } from '../../common/chatEditingService.js'; import { IChatResponseModel, IChatTextEditGroup } from '../../common/chatModel.js'; import { IChatService } from '../../common/chatService.js'; import { ChatEditingSession } from './chatEditingSession.js'; @@ -76,6 +76,8 @@ export class ChatEditingService extends Disposable implements IChatEditingServic private _applyingChatEditsFailedContextKey: IContextKey; + private _chatRelatedFilesProviders = new Map(); + constructor( @IEditorGroupsService private readonly _editorGroupsService: IEditorGroupsService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @@ -84,7 +86,6 @@ export class ChatEditingService extends Disposable implements IChatEditingServic @IContextKeyService contextKeyService: IContextKeyService, @IChatService private readonly _chatService: IChatService, @IProgressService private readonly _progressService: IProgressService, - @ICodeMapperService private readonly _codeMapperService: ICodeMapperService, @IEditorService private readonly _editorService: IEditorService, @IDecorationsService decorationsService: IDecorationsService, @IFileService private readonly _fileService: IFileService, @@ -93,7 +94,7 @@ export class ChatEditingService extends Disposable implements IChatEditingServic super(); this._applyingChatEditsFailedContextKey = applyingChatEditsFailedContextKey.bindTo(contextKeyService); this._applyingChatEditsFailedContextKey.set(false); - this._register(decorationsService.registerDecorationsProvider(new ChatDecorationsProvider(this._currentSessionObs))); + this._register(decorationsService.registerDecorationsProvider(_instantiationService.createInstance(ChatDecorationsProvider, this._currentSessionObs))); this._register(multiDiffSourceResolverService.registerResolver(_instantiationService.createInstance(ChatEditingMultiDiffSourceResolver, this._currentSessionObs))); textModelService.registerTextModelContentProvider(ChatEditingTextModelContentProvider.scheme, _instantiationService.createInstance(ChatEditingTextModelContentProvider, this._currentSessionObs)); textModelService.registerTextModelContentProvider(ChatEditingSnapshotTextModelContentProvider.scheme, _instantiationService.createInstance(ChatEditingSnapshotTextModelContentProvider, this._currentSessionObs)); @@ -129,14 +130,15 @@ export class ChatEditingService extends Disposable implements IChatEditingServic this._register(bindContextKey(applyingChatEditsContextKey, contextKeyService, (reader) => { return this._currentAutoApplyOperationObs.read(reader) !== null; })); - this._register(bindContextKey(CONTEXT_CHAT_EDITING_CAN_UNDO, contextKeyService, (r) => { + this._register(bindContextKey(ChatContextKeys.chatEditingCanUndo, contextKeyService, (r) => { return this._currentSessionObs.read(r)?.canUndo.read(r) || false; })); - this._register(bindContextKey(CONTEXT_CHAT_EDITING_CAN_REDO, contextKeyService, (r) => { + this._register(bindContextKey(ChatContextKeys.chatEditingCanRedo, contextKeyService, (r) => { return this._currentSessionObs.read(r)?.canRedo.read(r) || false; })); this._register(this._chatService.onDidDisposeSession((e) => { if (e.reason === 'cleared' && this._currentSessionObs.get()?.chatSessionId === e.sessionId) { + this._applyingChatEditsFailedContextKey.set(false); void this._currentSessionObs.get()?.stop(); } })); @@ -217,15 +219,6 @@ export class ChatEditingService extends Disposable implements IChatEditingServic return session; } - public triggerEditComputation(responseModel: IChatResponseModel): Promise { - return this._continueEditingSession(async (builder, token) => { - const codeMapperResponse: ICodeMapperResponse = { - textEdit: (resource, edits) => builder.textEdits(resource, edits, responseModel), - }; - await this._codeMapperService.mapCodeFromResponse(responseModel, codeMapperResponse, token); - }, { silent: true }); - } - public createSnapshot(requestId: string): void { this._currentSessionObs.get()?.createSnapshot(requestId); } @@ -244,16 +237,15 @@ export class ChatEditingService extends Disposable implements IChatEditingServic const observerDisposables = new DisposableStore(); let editsSource: AsyncIterableSource | undefined; + let editsPromise: Promise | undefined; const editsSeen = new ResourceMap<{ seen: number }>(); const editedFilesExist = new ResourceMap>(); const onResponseComplete = (responseModel: IChatResponseModel) => { - if (responseModel.result?.errorDetails) { + if (responseModel.result?.errorDetails && !responseModel.result.errorDetails.responseIsIncomplete) { // Roll back everything this.restoreSnapshot(responseModel.requestId); this._applyingChatEditsFailedContextKey.set(true); - } else if (responseModel.result?.metadata?.autoApplyEdits) { - this.triggerEditComputation(responseModel); } editsSource?.resolve(); @@ -263,7 +255,7 @@ export class ChatEditingService extends Disposable implements IChatEditingServic }; - const handleResponseParts = (responseModel: IChatResponseModel) => { + const handleResponseParts = async (responseModel: IChatResponseModel) => { for (const part of responseModel.response.value) { if (part.kind === 'codeblockUri' || part.kind === 'textEditGroup') { // ensure editor is open asap @@ -289,35 +281,42 @@ export class ChatEditingService extends Disposable implements IChatEditingServic entry.seen += newEdits.length; editsSource ??= new AsyncIterableSource(); - editsSource.emitOne({ uri: part.uri, edits: newEdits, kind: 'textEditGroup' }); + editsSource.emitOne({ uri: part.uri, edits: newEdits, kind: 'textEditGroup', done: part.kind === 'textEditGroup' && part.done }); if (first) { - this._continueEditingSession(async (builder, token) => { + + await editsPromise; + + editsPromise = this._continueEditingSession(async (builder, token) => { for await (const item of editsSource!.asyncIterable) { if (token.isCancellationRequested) { break; } - for (const group of item.edits) { - builder.textEdits(item.uri, group, responseModel); + for (let i = 0; i < item.edits.length; i++) { + const group = item.edits[i]; + const isLastGroup = i === item.edits.length - 1; + builder.textEdits(item.uri, group, isLastGroup && (item.done ?? false), responseModel); } } - }, { silent: true }); + }, { silent: true }).finally(() => { + editsPromise = undefined; + }); } } } }; - observerDisposables.add(chatModel.onDidChange(e => { + observerDisposables.add(chatModel.onDidChange(async e => { if (e.kind === 'addRequest') { this._applyingChatEditsFailedContextKey.set(false); const responseModel = e.request.response; if (responseModel) { if (responseModel.isComplete) { - handleResponseParts(responseModel); + await handleResponseParts(responseModel); onResponseComplete(responseModel); } else { - const disposable = responseModel.onDidChange(() => { - handleResponseParts(responseModel); + const disposable = responseModel.onDidChange(async () => { + await handleResponseParts(responseModel); if (responseModel.isComplete) { onResponseComplete(responseModel); disposable.dispose(); @@ -355,8 +354,8 @@ export class ChatEditingService extends Disposable implements IChatEditingServic } const stream: IChatEditingSessionStream = { - textEdits: (resource: URI, textEdits: TextEdit[], responseModel: IChatResponseModel) => { - session.acceptTextEdits(resource, textEdits, responseModel); + textEdits: (resource: URI, textEdits: TextEdit[], isDone: boolean, responseModel: IChatResponseModel) => { + session.acceptTextEdits(resource, textEdits, isDone, responseModel); } }; session.acceptStreamingEditsStart(); @@ -393,6 +392,47 @@ export class ChatEditingService extends Disposable implements IChatEditingServic } return editors; } + + hasRelatedFilesProviders(): boolean { + return this._chatRelatedFilesProviders.size > 0; + } + + registerRelatedFilesProvider(handle: number, provider: IChatRelatedFilesProvider): IDisposable { + this._chatRelatedFilesProviders.set(handle, provider); + return toDisposable(() => { + this._chatRelatedFilesProviders.delete(handle); + }); + } + + async getRelatedFiles(chatSessionId: string, prompt: string, token: CancellationToken): Promise<{ group: string; files: IChatRelatedFile[] }[] | undefined> { + const currentSession = this._currentSessionObs.get(); + if (!currentSession || chatSessionId !== currentSession.chatSessionId) { + return undefined; + } + const userAddedWorkingSetEntries: URI[] = []; + for (const entry of currentSession.workingSet) { + // Don't incorporate suggested files into the related files request + // but do consider transient entries like open editors + if (entry[1].state !== WorkingSetEntryState.Suggested) { + userAddedWorkingSetEntries.push(entry[0]); + } + } + + const providers = Array.from(this._chatRelatedFilesProviders.values()); + const result = await Promise.all(providers.map(async provider => { + try { + const relatedFiles = await provider.provideRelatedFiles({ prompt, files: userAddedWorkingSetEntries }, token); + if (relatedFiles?.length) { + return { group: provider.description, files: relatedFiles }; + } + return undefined; + } catch (e) { + return undefined; + } + })); + + return coalesce(result); + } } /** @@ -412,7 +452,7 @@ class ChatDecorationsProvider extends Disposable implements IDecorationsProvider readonly label: string = localize('chat', "Chat Editing"); - private readonly _currentlyEditingUris = derived(this, (r) => { + private readonly _currentEntries = derived(this, (r) => { const session = this._session.read(r); if (!session) { return []; @@ -421,27 +461,51 @@ class ChatDecorationsProvider extends Disposable implements IDecorationsProvider if (state === ChatEditingSessionState.Disposed) { return []; } - return session.entries.read(r).filter(entry => entry.isCurrentlyBeingModified.read(r)).map(entry => entry.modifiedURI); + return session.entries.read(r); }); - public readonly onDidChange = observeArrayChanges(this._currentlyEditingUris, compareBy(uri => uri.toString(), compare), this._store); + private readonly _currentlyEditingUris = derived(this, (r) => { + const uri = this._currentEntries.read(r); + return uri.filter(entry => entry.isCurrentlyBeingModified.read(r)).map(entry => entry.modifiedURI); + }); + + private readonly _modifiedUris = derived(this, (r) => { + const uri = this._currentEntries.read(r); + return uri.filter(entry => !entry.isCurrentlyBeingModified.read(r) && entry.state.read(r) === WorkingSetEntryState.Modified).map(entry => entry.modifiedURI); + }); + + public readonly onDidChange = Event.any( + observeArrayChanges(this._currentlyEditingUris, compareBy(uri => uri.toString(), compare), this._store), + observeArrayChanges(this._modifiedUris, compareBy(uri => uri.toString(), compare), this._store), + ); constructor( - private readonly _session: IObservable + private readonly _session: IObservable, + @IChatAgentService private readonly _chatAgentService: IChatAgentService ) { super(); } provideDecorations(uri: URI, _token: CancellationToken): IDecorationData | undefined { const isCurrentlyBeingModified = this._currentlyEditingUris.get().some(e => e.toString() === uri.toString()); - if (!isCurrentlyBeingModified) { - return undefined; + if (isCurrentlyBeingModified) { + return { + weight: 1000, + letter: ThemeIcon.modify(Codicon.loading, 'spin'), + bubble: false + }; } - return { - weight: 1000, - letter: ThemeIcon.modify(Codicon.loading, 'spin'), - bubble: false - }; + const isModified = this._modifiedUris.get().some(e => e.toString() === uri.toString()); + if (isModified) { + const defaultAgentName = this._chatAgentService.getDefaultAgent(ChatAgentLocation.EditingSession)?.fullName; + return { + weight: 1000, + letter: Codicon.diffModified, + tooltip: defaultAgentName ? localize('chatEditing.modified', "Pending changes from {0}", defaultAgentName) : localize('chatEditing.modified2', "Pending changes from chat"), + bubble: true + }; + } + return undefined; } } diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts index e8905eb58180e..3ab832cd41322 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts @@ -8,7 +8,7 @@ import { BugIndicatingError } from '../../../../../base/common/errors.js'; import { Emitter } from '../../../../../base/common/event.js'; import { Disposable } from '../../../../../base/common/lifecycle.js'; import { ResourceMap, ResourceSet } from '../../../../../base/common/map.js'; -import { derived, IObservable, ITransaction, observableValue, transaction } from '../../../../../base/common/observable.js'; +import { autorun, derived, IObservable, ITransaction, observableValue, transaction } from '../../../../../base/common/observable.js'; import { URI } from '../../../../../base/common/uri.js'; import { isCodeEditor, isDiffEditor } from '../../../../../editor/browser/editorBrowser.js'; import { IBulkEditService } from '../../../../../editor/browser/services/bulkEditService.js'; @@ -30,13 +30,14 @@ import { IEditorService } from '../../../../services/editor/common/editorService import { MultiDiffEditor } from '../../../multiDiffEditor/browser/multiDiffEditor.js'; import { MultiDiffEditorInput } from '../../../multiDiffEditor/browser/multiDiffEditorInput.js'; import { ChatAgentLocation, IChatAgentService } from '../../common/chatAgents.js'; -import { ChatEditingSessionState, ChatEditKind, IChatEditingSession, WorkingSetEntryState } from '../../common/chatEditingService.js'; +import { ChatEditingSessionChangeType, ChatEditingSessionState, ChatEditKind, IChatEditingSession, WorkingSetDisplayMetadata, WorkingSetEntryState } from '../../common/chatEditingService.js'; import { IChatResponseModel } from '../../common/chatModel.js'; import { IChatWidgetService } from '../chat.js'; import { ChatEditingMultiDiffSourceResolver } from './chatEditingService.js'; import { ChatEditingModifiedFileEntry, IModifiedEntryTelemetryInfo, ISnapshotEntry } from './chatEditingModifiedFileEntry.js'; import { ChatEditingTextModelContentProvider } from './chatEditingTextModelContentProviders.js'; import { Schemas } from '../../../../../base/common/network.js'; +import { isEqual } from '../../../../../base/common/resources.js'; export class ChatEditingSession extends Disposable implements IChatEditingSession { private readonly _state = observableValue(this, ChatEditingSessionState.Initial); @@ -58,19 +59,21 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio } private readonly _sequencer = new Sequencer(); - private _workingSet = new ResourceMap(); + private _workingSet = new ResourceMap(); get workingSet() { this._assertNotDisposed(); // Return here a reunion between the AI modified entries and the user built working set - const result = new ResourceMap(this._workingSet); + const result = new ResourceMap(this._workingSet); for (const entry of this._entriesObs.get()) { - result.set(entry.modifiedURI, entry.state.get()); + result.set(entry.modifiedURI, { state: entry.state.get() }); } return result; } + private _removedTransientEntries = new ResourceSet(); + get state(): IObservable { return this._state; } @@ -98,7 +101,7 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio return linearHistory.slice(linearHistoryIndex).map(s => s.requestId).filter((r): r is string => !!r); }); - private readonly _onDidChange = new Emitter(); + private readonly _onDidChange = new Emitter(); get onDidChange() { this._assertNotDisposed(); return this._onDidChange.event; @@ -126,7 +129,7 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio @IBulkEditService public readonly _bulkEditService: IBulkEditService, @IEditorGroupsService private readonly _editorGroupsService: IEditorGroupsService, @IEditorService private readonly _editorService: IEditorService, - @IChatWidgetService private readonly _chatWidgetService: IChatWidgetService, + @IChatWidgetService chatWidgetService: IChatWidgetService, @IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService, @IFileService private readonly _fileService: IFileService, @IFileDialogService private readonly _dialogService: IFileDialogService, @@ -134,7 +137,7 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio ) { super(); - const widget = _chatWidgetService.getWidgetBySessionId(chatSessionId); + const widget = chatWidgetService.getWidgetBySessionId(chatSessionId); if (!widget) { return; // Shouldn't happen } @@ -147,27 +150,24 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio this._register(this._editorService.onDidCloseEditor((e) => { this._trackCurrentEditorsInWorkingSet(e); })); + this._register(autorun(reader => { + const entries = this.entries.read(reader); + entries.forEach(entry => { + entry.state.read(reader); + }); + this._onDidChange.fire(ChatEditingSessionChangeType.WorkingSet); + })); } private _trackCurrentEditorsInWorkingSet(e?: IEditorCloseEvent) { - const widget = this._chatWidgetService.getWidgetBySessionId(this.chatSessionId); - const requests = widget?.viewModel?.getItems(); - if (requests && requests.length > 0) { - return; - } - const closedEditor = e?.editor.resource?.toString(); const existingTransientEntries = new ResourceSet(); for (const file of this._workingSet.keys()) { - if (this._workingSet.get(file) === WorkingSetEntryState.Transient) { + if (this._workingSet.get(file)?.state === WorkingSetEntryState.Transient) { existingTransientEntries.add(file); } } - if (existingTransientEntries.size === 0 && this._workingSet.size > 0) { - // The user manually added or removed attachments, don't inherit the visible editors - return; - } const activeEditors = new ResourceSet(); this._editorGroupsService.groups.forEach((group) => { @@ -185,7 +185,9 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio // Continue, since we want this to be deleted from the working set } else if (existingTransientEntries.has(uri)) { existingTransientEntries.delete(uri); - } else { + } else if (!this._workingSet.has(uri) && !this._removedTransientEntries.has(uri)) { + // Don't add as a transient entry if it's already part of the working set + // or if the user has intentionally removed it from the working set activeEditors.add(uri); } } @@ -197,12 +199,12 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio } for (const entry of activeEditors) { - this._workingSet.set(entry, WorkingSetEntryState.Transient); + this._workingSet.set(entry, { state: WorkingSetEntryState.Transient, description: localize('chatEditing.transient', "Open Editor") }); didChange = true; } if (didChange) { - this._onDidChange.fire(); + this._onDidChange.fire(ChatEditingSessionChangeType.WorkingSet); } } @@ -211,7 +213,7 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio if (requestId) { this._snapshots.set(requestId, snapshot); for (const workingSetItem of this._workingSet.keys()) { - this._workingSet.set(workingSetItem, WorkingSetEntryState.Sent); + this._workingSet.set(workingSetItem, { state: WorkingSetEntryState.Sent }); } const linearHistory = this._linearHistory.get(); const linearHistoryIndex = this._linearHistoryIndex.get(); @@ -227,7 +229,7 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio } private _createSnapshot(requestId: string | undefined): IChatEditingSessionSnapshot { - const workingSet = new ResourceMap(); + const workingSet = new ResourceMap(); for (const [file, state] of this._workingSet) { workingSet.set(file, state); } @@ -248,7 +250,7 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio return null; } - const snapshotEntry = [...entries.values()].find((e) => e.snapshotUri.toString() === snapshotUri.toString()); + const snapshotEntry = [...entries.values()].find((e) => isEqual(e.snapshotUri, snapshotUri)); if (!snapshotEntry) { return null; } @@ -322,14 +324,21 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio let didRemoveUris = false; for (const uri of uris) { + const state = this._workingSet.get(uri); + if (state === undefined) { + continue; + } didRemoveUris = this._workingSet.delete(uri) || didRemoveUris; + if (state.state === WorkingSetEntryState.Transient || state.state === WorkingSetEntryState.Suggested) { + this._removedTransientEntries.add(uri); + } } if (!didRemoveUris) { return; // noop } - this._onDidChange.fire(); + this._onDidChange.fire(ChatEditingSessionChangeType.WorkingSet); } private _assertNotDisposed(): void { @@ -346,13 +355,13 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio } for (const uri of uris) { - const entry = this._entriesObs.get().find(e => e.modifiedURI.toString() === uri.toString()); + const entry = this._entriesObs.get().find(e => isEqual(e.modifiedURI, uri)); if (entry) { await entry.accept(undefined); } } - this._onDidChange.fire(); + this._onDidChange.fire(ChatEditingSessionChangeType.Other); } async reject(...uris: URI[]): Promise { @@ -363,13 +372,13 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio } for (const uri of uris) { - const entry = this._entriesObs.get().find(e => e.modifiedURI.toString() === uri.toString()); + const entry = this._entriesObs.get().find(e => isEqual(e.modifiedURI, uri)); if (entry) { await entry.reject(undefined); } } - this._onDidChange.fire(); + this._onDidChange.fire(ChatEditingSessionChangeType.Other); } async show(): Promise { @@ -426,7 +435,7 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio this._assertNotDisposed(); const entry = this._entriesObs.get().find(e => e.entryId === documentId); - return entry?.docSnapshot ?? null; + return entry?.originalModel ?? null; } acceptStreamingEditsStart(): void { @@ -439,14 +448,14 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio this._sequencer.queue(() => this._acceptStreamingEditsStart()); } - acceptTextEdits(resource: URI, textEdits: TextEdit[], responseModel: IChatResponseModel): void { + acceptTextEdits(resource: URI, textEdits: TextEdit[], isLastEdits: boolean, responseModel: IChatResponseModel): void { if (this._state.get() === ChatEditingSessionState.Disposed) { // we don't throw in this case because there could be a builder still connected to a disposed session return; } // ensure that the edits are processed sequentially - this._sequencer.queue(() => this._acceptTextEdits(resource, textEdits, responseModel)); + this._sequencer.queue(() => this._acceptTextEdits(resource, textEdits, isLastEdits, responseModel)); } resolve(): void { @@ -459,18 +468,17 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio this._sequencer.queue(() => this._resolve()); } - addFileToWorkingSet(resource: URI) { - if (!this._workingSet.has(resource)) { - this._workingSet.set(resource, WorkingSetEntryState.Attached); - - // Convert all transient entries to attachments - for (const file of this._workingSet.keys()) { - if (this._workingSet.get(file) === WorkingSetEntryState.Transient) { - this._workingSet.set(file, WorkingSetEntryState.Attached); - } + addFileToWorkingSet(resource: URI, description?: string, proposedState?: WorkingSetEntryState.Suggested): void { + const state = this._workingSet.get(resource); + if (!state && proposedState === WorkingSetEntryState.Suggested) { + if (this._removedTransientEntries.has(resource)) { + return; } - - this._onDidChange.fire(); + this._workingSet.set(resource, { description, state: WorkingSetEntryState.Suggested }); + this._onDidChange.fire(ChatEditingSessionChangeType.WorkingSet); + } else if (state === undefined || state.state === WorkingSetEntryState.Transient) { + this._workingSet.set(resource, { description, state: WorkingSetEntryState.Attached }); + this._onDidChange.fire(ChatEditingSessionChangeType.WorkingSet); } } @@ -508,12 +516,12 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio }); } - private async _acceptTextEdits(resource: URI, textEdits: TextEdit[], responseModel: IChatResponseModel): Promise { + private async _acceptTextEdits(resource: URI, textEdits: TextEdit[], isLastEdits: boolean, responseModel: IChatResponseModel): Promise { if (this._filesToSkipCreating.has(resource)) { return; } - if (!this._entriesObs.get().find(e => e.resource.toString() === resource.toString()) && this._entriesObs.get().length >= (await this.editingSessionFileLimitPromise)) { + if (!this._entriesObs.get().find(e => isEqual(e.modifiedURI, resource)) && this._entriesObs.get().length >= (await this.editingSessionFileLimitPromise)) { // Do not create files in a single editing session that would be in excess of our limit return; } @@ -538,7 +546,7 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio get result() { return responseModel.result; } }; const entry = await this._getOrCreateModifiedFileEntry(resource, telemetryInfo); - entry.acceptAgentEdits(textEdits); + entry.acceptAgentEdits(textEdits, isLastEdits); // await this._editorService.openEditor({ resource: entry.modifiedURI, options: { inactive: true } }); } @@ -549,11 +557,11 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio } this._state.set(ChatEditingSessionState.Idle, tx); }); - this._onDidChange.fire(); + this._onDidChange.fire(ChatEditingSessionChangeType.Other); } private async _getOrCreateModifiedFileEntry(resource: URI, responseModel: IModifiedEntryTelemetryInfo): Promise { - const existingEntry = this._entriesObs.get().find(e => e.resource.toString() === resource.toString()); + const existingEntry = this._entriesObs.get().find(e => isEqual(e.modifiedURI, resource)); if (existingEntry) { if (responseModel.requestId !== existingEntry.telemetryInfo.requestId) { existingEntry.updateTelemetryInfo(responseModel); @@ -570,14 +578,15 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio // remove it from the entries and don't show it in the working set anymore // so that it can be recreated e.g. through retry this._register(entry.onDidDelete(() => { - const newEntries = this._entriesObs.get().filter(e => e.modifiedURI.toString() !== entry.modifiedURI.toString()); + const newEntries = this._entriesObs.get().filter(e => !isEqual(e.modifiedURI, entry.modifiedURI)); this._entriesObs.set(newEntries, undefined); this._workingSet.delete(entry.modifiedURI); - this._onDidChange.fire(); + entry.dispose(); + this._onDidChange.fire(ChatEditingSessionChangeType.WorkingSet); })); const entriesArr = [...this._entriesObs.get(), entry]; this._entriesObs.set(entriesArr, undefined); - this._onDidChange.fire(); + this._onDidChange.fire(ChatEditingSessionChangeType.WorkingSet); return entry; } @@ -586,7 +595,7 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio try { const ref = await this._textModelService.createModelReference(resource); - return this._instantiationService.createInstance(ChatEditingModifiedFileEntry, resource, ref, { collapse: (transaction: ITransaction | undefined) => this._collapse(resource, transaction) }, responseModel, mustExist ? ChatEditKind.Created : ChatEditKind.Modified); + return this._instantiationService.createInstance(ChatEditingModifiedFileEntry, ref, { collapse: (transaction: ITransaction | undefined) => this._collapse(resource, transaction) }, responseModel, mustExist ? ChatEditKind.Created : ChatEditKind.Modified); } catch (err) { if (mustExist) { throw err; @@ -601,13 +610,16 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio private _collapse(resource: URI, transaction: ITransaction | undefined) { const multiDiffItem = this.editorPane?.findDocumentDiffItem(resource); if (multiDiffItem) { - this.editorPane?.viewModel?.items.get().find((documentDiffItem) => String(documentDiffItem.originalUri) === String(multiDiffItem.originalUri) && String(documentDiffItem.modifiedUri) === String(multiDiffItem.modifiedUri))?.collapsed.set(true, transaction); + this.editorPane?.viewModel?.items.get().find((documentDiffItem) => + isEqual(documentDiffItem.originalUri, multiDiffItem.originalUri) && + isEqual(documentDiffItem.modifiedUri, multiDiffItem.modifiedUri)) + ?.collapsed.set(true, transaction); } } } export interface IChatEditingSessionSnapshot { requestId: string | undefined; - workingSet: ResourceMap; + workingSet: ResourceMap; entries: ResourceMap; } diff --git a/src/vs/workbench/contrib/chat/browser/chatEditor.ts b/src/vs/workbench/contrib/chat/browser/chatEditor.ts index 5a107c88c92a9..69774d4b6db0c 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditor.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditor.ts @@ -66,7 +66,10 @@ export class ChatEditor extends EditorPane { ChatWidget, ChatAgentLocation.Panel, undefined, - { supportsFileReferences: true }, + { + supportsFileReferences: true, + enableImplicitContext: true + }, { listForeground: editorForeground, listBackground: editorBackground, diff --git a/src/vs/workbench/contrib/chat/browser/chatEditorActions.ts b/src/vs/workbench/contrib/chat/browser/chatEditorActions.ts index b4ad84e28037c..4b41c60fea157 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditorActions.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditorActions.ts @@ -2,9 +2,9 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { isCodeEditor } from '../../../../editor/browser/editorBrowser.js'; +import { ICodeEditor, isCodeEditor } from '../../../../editor/browser/editorBrowser.js'; import { localize2 } from '../../../../nls.js'; -import { ServicesAccessor } from '../../../../editor/browser/editorExtensions.js'; +import { EditorAction2, ServicesAccessor } from '../../../../editor/browser/editorExtensions.js'; import { Codicon } from '../../../../base/common/codicons.js'; import { Action2, MenuId, registerAction2 } from '../../../../platform/actions/common/actions.js'; import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js'; @@ -13,8 +13,13 @@ import { CHAT_CATEGORY } from './actions/chatActions.js'; import { ChatEditorController, ctxHasEditorModification } from './chatEditorController.js'; import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js'; import { EditorContextKeys } from '../../../../editor/common/editorContextKeys.js'; -import { IEditorService } from '../../../services/editor/common/editorService.js'; -import { IChatEditingService } from '../common/chatEditingService.js'; +import { ACTIVE_GROUP, IEditorService } from '../../../services/editor/common/editorService.js'; +import { hasUndecidedChatEditingResourceContextKey, IChatEditingService } from '../common/chatEditingService.js'; +import { ChatContextKeys } from '../common/chatContextKeys.js'; +import { isEqual } from '../../../../base/common/resources.js'; +import { Range } from '../../../../editor/common/core/range.js'; +import { getNotebookEditorFromEditorPane } from '../../notebook/browser/notebookBrowser.js'; +import { ctxNotebookHasEditorModification } from '../../notebook/browser/chatEdit/notebookChatEditController.js'; abstract class NavigateAction extends Action2 { @@ -33,31 +38,73 @@ abstract class NavigateAction extends Action2 { ? KeyMod.Alt | KeyCode.F5 : KeyMod.Alt | KeyMod.Shift | KeyCode.F5, weight: KeybindingWeight.EditorContrib, - when: ContextKeyExpr.and(ctxHasEditorModification, EditorContextKeys.focus), + when: ContextKeyExpr.and(ContextKeyExpr.or(ctxHasEditorModification, ctxNotebookHasEditorModification), EditorContextKeys.focus), }, f1: true, menu: { - id: MenuId.EditorTitle, - group: 'navigation', - order: next ? -100 : -101, - when: ctxHasEditorModification + id: MenuId.ChatEditingEditorContent, + group: 'navigate', + order: !next ? 2 : 3, } }); } override run(accessor: ServicesAccessor) { - const editor = accessor.get(IEditorService).activeTextEditorControl; + const chatEditingService = accessor.get(IChatEditingService); + const editorService = accessor.get(IEditorService); - if (!isCodeEditor(editor)) { + const editor = editorService.activeTextEditorControl; + if (!isCodeEditor(editor) || !editor.hasModel()) { return; } - if (this.next) { - ChatEditorController.get(editor)?.revealNext(); - } else { - ChatEditorController.get(editor)?.revealPrevious(); + const session = chatEditingService.currentEditingSession; + if (!session) { + return; + } + + const ctrl = ChatEditorController.get(editor); + if (!ctrl) { + return; + } + + const done = this.next + ? ctrl.revealNext(true) + : ctrl.revealPrevious(true); + + if (done) { + return; + } + + const entries = session.entries.get(); + const idx = entries.findIndex(e => isEqual(e.modifiedURI, editor.getModel().uri)); + if (idx < 0) { + return; } + + const newIdx = (idx + (this.next ? 1 : -1) + entries.length) % entries.length; + if (idx === newIdx) { + // wrap inside the same file + if (this.next) { + ctrl.revealNext(false); + } else { + ctrl.revealPrevious(false); + } + return; + } + + const entry = entries[newIdx]; + const change = entry.diffInfo.get().changes.at(0); + + return editorService.openEditor({ + resource: entry.modifiedURI, + options: { + selection: change && Range.fromPositions({ lineNumber: change.original.startLineNumber, column: 1 }), + revealIfOpened: false, + revealIfVisible: false, + } + }, ACTIVE_GROUP); } } @@ -70,16 +117,27 @@ abstract class AcceptDiscardAction extends Action2 { : 'chatEditor.action.reject', title: accept ? localize2('accept', 'Accept Chat Edit') - : localize2('reject', 'Reject Chat Edit'), + : localize2('discard', 'Discard Chat Edit'), + shortTitle: accept + ? localize2('accept2', 'Accept') + : localize2('discard2', 'Discard'), category: CHAT_CATEGORY, + precondition: ContextKeyExpr.and(ChatContextKeys.requestInProgress.negate(), hasUndecidedChatEditingResourceContextKey, ContextKeyExpr.or(ctxHasEditorModification, ctxNotebookHasEditorModification)), icon: accept ? Codicon.check : Codicon.discard, + f1: true, + keybinding: { + when: EditorContextKeys.focus, + weight: KeybindingWeight.WorkbenchContrib, + primary: accept + ? KeyMod.CtrlCmd | KeyCode.Enter + : KeyMod.CtrlCmd | KeyCode.Backspace + }, menu: { - id: MenuId.EditorTitle, - group: 'navigation', - order: accept ? -103 : -102, - when: ctxHasEditorModification + id: MenuId.ChatEditingEditorContent, + group: 'a_resolve', + order: accept ? 0 : 1, } }); } @@ -88,28 +146,80 @@ abstract class AcceptDiscardAction extends Action2 { const chatEditingService = accessor.get(IChatEditingService); const editorService = accessor.get(IEditorService); - const editor = editorService.activeTextEditorControl; - if (!isCodeEditor(editor) || !editor.hasModel()) { + let uri = getNotebookEditorFromEditorPane(editorService.activeEditorPane)?.textModel?.uri; + if (!uri) { + const editor = editorService.activeTextEditorControl; + uri = isCodeEditor(editor) && editor.hasModel() ? editor.getModel().uri : undefined; + } + if (!uri) { return; } - const session = chatEditingService.getEditingSession(editor.getModel().uri); + const session = chatEditingService.getEditingSession(uri); if (!session) { return; } if (this.accept) { - session.accept(editor.getModel().uri); + session.accept(uri); } else { - session.reject(editor.getModel().uri); + session.reject(uri); } } } +class UndoHunkAction extends EditorAction2 { + constructor() { + super({ + id: 'chatEditor.action.undoHunk', + title: localize2('undo', 'Undo this Change'), + shortTitle: localize2('undo2', 'Undo'), + category: CHAT_CATEGORY, + precondition: ContextKeyExpr.and(ChatContextKeys.requestInProgress.negate(), hasUndecidedChatEditingResourceContextKey), + icon: Codicon.discard, + f1: true, + keybinding: { + when: EditorContextKeys.focus, + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Backspace + }, + menu: { + id: MenuId.ChatEditingEditorHunk, + order: 1 + } + }); + } + + override runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor, ...args: any[]) { + ChatEditorController.get(editor)?.undoNearestChange(args[0]); + } +} + +class OpenDiffFromHunkAction extends EditorAction2 { + constructor() { + super({ + id: 'chatEditor.action.diffHunk', + title: localize2('diff', 'Open Diff'), + category: CHAT_CATEGORY, + precondition: ContextKeyExpr.and(ChatContextKeys.requestInProgress.negate(), hasUndecidedChatEditingResourceContextKey), + icon: Codicon.diffSingle, + menu: { + id: MenuId.ChatEditingEditorHunk, + order: 10 + } + }); + } + + override runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor, ...args: any[]) { + ChatEditorController.get(editor)?.openDiff(args[0]); + } +} export function registerChatEditorActions() { registerAction2(class NextAction extends NavigateAction { constructor() { super(true); } }); registerAction2(class PrevAction extends NavigateAction { constructor() { super(false); } }); registerAction2(class AcceptAction extends AcceptDiscardAction { constructor() { super(true); } }); registerAction2(class RejectAction extends AcceptDiscardAction { constructor() { super(false); } }); + registerAction2(UndoHunkAction); + registerAction2(OpenDiffFromHunkAction); } diff --git a/src/vs/workbench/contrib/chat/browser/chatEditorController.ts b/src/vs/workbench/contrib/chat/browser/chatEditorController.ts index 7d8332702bf6f..fb684a083b59d 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditorController.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditorController.ts @@ -3,25 +3,35 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import './media/chatEditorController.css'; +import { getTotalWidth } from '../../../../base/browser/dom.js'; import { binarySearch, coalesceInPlace } from '../../../../base/common/arrays.js'; -import { Disposable, DisposableStore, toDisposable } from '../../../../base/common/lifecycle.js'; -import { autorun, derived } from '../../../../base/common/observable.js'; +import { Disposable, DisposableStore, dispose, toDisposable } from '../../../../base/common/lifecycle.js'; +import { autorun, derived, observableFromEvent } from '../../../../base/common/observable.js'; import { isEqual } from '../../../../base/common/resources.js'; import { themeColorFromId } from '../../../../base/common/themables.js'; -import { ICodeEditor, IViewZone } from '../../../../editor/browser/editorBrowser.js'; +import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition, IOverlayWidgetPositionCoordinates, IViewZone, MouseTargetType } from '../../../../editor/browser/editorBrowser.js'; import { LineSource, renderLines, RenderOptions } from '../../../../editor/browser/widget/diffEditor/components/diffEditorViewZones/renderLines.js'; import { diffAddDecoration, diffDeleteDecoration, diffWholeLineAddDecoration } from '../../../../editor/browser/widget/diffEditor/registrations.contribution.js'; import { EditorOption } from '../../../../editor/common/config/editorOptions.js'; +import { EditOperation, ISingleEditOperation } from '../../../../editor/common/core/editOperation.js'; import { Range } from '../../../../editor/common/core/range.js'; import { IDocumentDiff } from '../../../../editor/common/diff/documentDiffProvider.js'; import { IEditorContribution, ScrollType } from '../../../../editor/common/editorCommon.js'; -import { IModelDeltaDecoration, ITextModel, MinimapPosition, OverviewRulerLane, TrackedRangeStickiness } from '../../../../editor/common/model.js'; +import { IModelDeltaDecoration, MinimapPosition, OverviewRulerLane, TrackedRangeStickiness } from '../../../../editor/common/model.js'; import { ModelDecorationOptions } from '../../../../editor/common/model/textModel.js'; import { InlineDecoration, InlineDecorationType } from '../../../../editor/common/viewModel.js'; import { localize } from '../../../../nls.js'; import { IContextKey, IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js'; import { minimapGutterAddedBackground, minimapGutterDeletedBackground, minimapGutterModifiedBackground, overviewRulerAddedForeground, overviewRulerDeletedForeground, overviewRulerModifiedForeground } from '../../scm/browser/dirtydiffDecorator.js'; -import { ChatEditingSessionState, IChatEditingService, IChatEditingSession, IModifiedFileEntry, WorkingSetEntryState } from '../common/chatEditingService.js'; +import { ChatEditingSessionState, IChatEditingService, IModifiedFileEntry, WorkingSetEntryState } from '../common/chatEditingService.js'; +import { Event } from '../../../../base/common/event.js'; +import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; +import { MenuId } from '../../../../platform/actions/common/actions.js'; +import { IEditorService } from '../../../services/editor/common/editorService.js'; +import { Position } from '../../../../editor/common/core/position.js'; +import { Selection } from '../../../../editor/common/core/selection.js'; +import { HiddenItemStrategy, MenuWorkbenchToolBar } from '../../../../platform/actions/browser/toolbar.js'; export const ctxHasEditorModification = new RawContextKey('chat.hasEditorModifications', undefined, localize('chat.hasEditorModifications', "The current editor contains chat modifications")); @@ -29,8 +39,10 @@ export class ChatEditorController extends Disposable implements IEditorContribut public static readonly ID = 'editor.contrib.chatEditorController'; - private readonly _sessionStore = this._register(new DisposableStore()); private readonly _decorations = this._editor.createDecorationsCollection(); + private readonly _diffHunksRenderStore = this._register(new DisposableStore()); + private readonly _diffHunkWidgets: DiffHunkWidget[] = []; + private _viewZones: string[] = []; private readonly _ctxHasEditorModification: IContextKey; @@ -41,29 +53,35 @@ export class ChatEditorController extends Disposable implements IEditorContribut constructor( private readonly _editor: ICodeEditor, + @IInstantiationService private readonly _instantiationService: IInstantiationService, @IChatEditingService private readonly _chatEditingService: IChatEditingService, + @IEditorService private readonly _editorService: IEditorService, @IContextKeyService contextKeyService: IContextKeyService, ) { super(); - this._register(this._editor.onDidChangeModel(() => this._update())); - this._register(this._editor.onDidChangeConfiguration((e) => { - if (e.hasChanged(EditorOption.fontInfo) || e.hasChanged(EditorOption.lineHeight)) { - this._update(); - } - })); - this._register(this._chatEditingService.onDidChangeEditingSession(() => this._updateSessionDecorations())); - this._register(toDisposable(() => this._clearRendering())); this._ctxHasEditorModification = ctxHasEditorModification.bindTo(contextKeyService); + const configSignal = observableFromEvent( + Event.filter(this._editor.onDidChangeConfiguration, e => e.hasChanged(EditorOption.fontInfo) || e.hasChanged(EditorOption.lineHeight)), + _ => undefined + ); + + const modelObs = observableFromEvent(this._editor.onDidChangeModel, _ => this._editor.getModel()); + this._register(autorun(r => { if (this._editor.getOption(EditorOption.inDiffEditor)) { + this._clearRendering(); return; } + configSignal.read(r); + + const model = modelObs.read(r); + const session = this._chatEditingService.currentEditingSessionObs.read(r); - const entry = session?.entries.read(r).find(e => isEqual(e.modifiedURI, this._editor.getModel()?.uri)); + const entry = session?.entries.read(r).find(e => isEqual(e.modifiedURI, model?.uri)); if (!entry || entry.state.read(r) !== WorkingSetEntryState.Modified) { this._clearRendering(); @@ -114,46 +132,6 @@ export class ChatEditorController extends Disposable implements IEditorContribut super.dispose(); } - private _update(): void { - this._sessionStore.clear(); - if (!this._editor.hasModel()) { - return; - } - if (this._editor.getOption(EditorOption.inDiffEditor)) { - return; - } - if (this._editor.getOption(EditorOption.inDiffEditor)) { - this._clearRendering(); - return; - } - this._updateSessionDecorations(); - } - - private _updateSessionDecorations(): void { - if (!this._editor.hasModel()) { - this._clearRendering(); - return; - } - const model = this._editor.getModel(); - const editingSession = this._chatEditingService.getEditingSession(model.uri); - const entry = this._getEntry(editingSession, model); - - if (!entry || entry.state.get() !== WorkingSetEntryState.Modified) { - this._clearRendering(); - return; - } - - const diff = entry.diffInfo.get(); - this._updateWithDiff(entry, diff); - } - - private _getEntry(editingSession: IChatEditingSession | null, model: ITextModel): IModifiedFileEntry | null { - if (!editingSession) { - return null; - } - return editingSession.entries.get().find(e => e.modifiedURI.toString() === model.uri.toString()) || null; - } - private _clearRendering() { this._editor.changeViewZones((viewZoneChangeAccessor) => { for (const id of this._viewZones) { @@ -161,6 +139,7 @@ export class ChatEditorController extends Disposable implements IEditorContribut } }); this._viewZones = []; + this._diffHunksRenderStore.clear(); this._decorations.clear(); this._ctxHasEditorModification.reset(); } @@ -193,6 +172,10 @@ export class ChatEditorController extends Disposable implements IEditorContribut const addedDecoration = createOverviewDecoration(overviewRulerAddedForeground, minimapGutterAddedBackground); const deletedDecoration = createOverviewDecoration(overviewRulerDeletedForeground, minimapGutterDeletedBackground); + this._diffHunksRenderStore.clear(); + this._diffHunkWidgets.length = 0; + const diffHunkDecorations: IModelDeltaDecoration[] = []; + this._editor.changeViewZones((viewZoneChangeAccessor) => { for (const id of this._viewZones) { viewZoneChangeAccessor.removeZone(id); @@ -202,6 +185,7 @@ export class ChatEditorController extends Disposable implements IEditorContribut const mightContainNonBasicASCII = originalModel.mightContainNonBasicASCII(); const mightContainRTL = originalModel.mightContainRTL(); const renderOptions = RenderOptions.fromEditor(this._editor); + const editorLineCount = this._editor.getModel()?.getLineCount(); for (const diffEntry of diff.changes) { const originalRange = diffEntry.original; @@ -219,13 +203,23 @@ export class ChatEditorController extends Disposable implements IEditorContribut diffDeleteDecoration.className!, InlineDecorationType.Regular )); - modifiedDecorations.push({ - range: i.modifiedRange, options: chatDiffAddDecoration - }); + + // If the original range is empty, the start line number is 1 and the new range spans the entire file, don't draw an Added decoration + if (!(i.originalRange.isEmpty() && i.originalRange.startLineNumber === 1 && i.modifiedRange.endLineNumber === editorLineCount) && !i.modifiedRange.isEmpty()) { + modifiedDecorations.push({ + range: i.modifiedRange, options: chatDiffAddDecoration + }); + } } - if (!diffEntry.modified.isEmpty) { + + // Render an added decoration but don't also render a deleted decoration for newly inserted content at the start of the file + // Note, this is a workaround for the `LineRange.isEmpty()` in diffEntry.original being `false` for newly inserted content + const isCreatedContent = decorations.length === 1 && decorations[0].range.isEmpty() && diffEntry.original.startLineNumber === 1; + + if (!diffEntry.modified.isEmpty && !(isCreatedContent && (diffEntry.modified.endLineNumberExclusive - 1) === editorLineCount)) { modifiedDecorations.push({ - range: diffEntry.modified.toInclusiveRange()!, options: chatDiffWholeLineAddDecoration + range: diffEntry.modified.toInclusiveRange()!, + options: chatDiffWholeLineAddDecoration }); } @@ -238,7 +232,7 @@ export class ChatEditorController extends Disposable implements IEditorContribut } else if (diffEntry.modified.isEmpty) { // deletion modifiedDecorations.push({ - range: new Range(diffEntry.modified.startLineNumber, 1, diffEntry.modified.startLineNumber, 1), + range: new Range(diffEntry.modified.startLineNumber - 1, 1, diffEntry.modified.startLineNumber, 1), options: deletedDecoration }); } else { @@ -251,32 +245,125 @@ export class ChatEditorController extends Disposable implements IEditorContribut const domNode = document.createElement('div'); domNode.className = 'chat-editing-original-zone view-lines line-delete monaco-mouse-cursor-text'; const result = renderLines(source, renderOptions, decorations, domNode); - const viewZoneData: IViewZone = { - afterLineNumber: diffEntry.modified.startLineNumber - 1, - heightInLines: result.heightInLines, - domNode, - ordinal: 50000 + 2 // more than https://github.com/microsoft/vscode/blob/bf52a5cfb2c75a7327c9adeaefbddc06d529dcad/src/vs/workbench/contrib/inlineChat/browser/inlineChatZoneWidget.ts#L42 - }; - - this._viewZones.push(viewZoneChangeAccessor.addZone(viewZoneData)); + + if (!isCreatedContent) { + const viewZoneData: IViewZone = { + afterLineNumber: diffEntry.modified.startLineNumber - 1, + heightInLines: result.heightInLines, + domNode, + ordinal: 50000 + 2 // more than https://github.com/microsoft/vscode/blob/bf52a5cfb2c75a7327c9adeaefbddc06d529dcad/src/vs/workbench/contrib/inlineChat/browser/inlineChatZoneWidget.ts#L42 + }; + + this._viewZones.push(viewZoneChangeAccessor.addZone(viewZoneData)); + } + + // Add content widget for each diff change + const undoEdits: ISingleEditOperation[] = []; + for (const c of diffEntry.innerChanges ?? []) { + const oldText = originalModel.getValueInRange(c.originalRange); + undoEdits.push(EditOperation.replace(c.modifiedRange, oldText)); + } + + const widget = this._instantiationService.createInstance(DiffHunkWidget, entry, undoEdits, this._editor.getModel()!.getVersionId(), this._editor, isCreatedContent ? 0 : result.heightInLines); + widget.layout(diffEntry.modified.startLineNumber); + + this._diffHunkWidgets.push(widget); + diffHunkDecorations.push({ + range: diffEntry.modified.toInclusiveRange() ?? new Range(diffEntry.modified.startLineNumber, 1, diffEntry.modified.startLineNumber, Number.MAX_SAFE_INTEGER), + options: { + description: 'diff-hunk-widget', + stickiness: TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges + } + }); } this._decorations.set(modifiedDecorations); }); + + const diffHunkDecoCollection = this._editor.createDecorationsCollection(diffHunkDecorations); + + this._diffHunksRenderStore.add(toDisposable(() => { + dispose(this._diffHunkWidgets); + this._diffHunkWidgets.length = 0; + diffHunkDecoCollection.clear(); + })); + + + + const positionObs = observableFromEvent(this._editor.onDidChangeCursorPosition, _ => this._editor.getPosition()); + + const activeWidgetIdx = derived(r => { + const position = positionObs.read(r); + if (!position) { + return -1; + } + const idx = diffHunkDecoCollection.getRanges().findIndex(r => r.containsPosition(position)); + return idx; + }); + const toggleWidget = (activeWidget: DiffHunkWidget | undefined) => { + const positionIdx = activeWidgetIdx.get(); + for (let i = 0; i < this._diffHunkWidgets.length; i++) { + const widget = this._diffHunkWidgets[i]; + widget.toggle(widget === activeWidget || i === positionIdx); + } + }; + + this._diffHunksRenderStore.add(autorun(r => { + // reveal when cursor inside + const idx = activeWidgetIdx.read(r); + const widget = this._diffHunkWidgets[idx]; + toggleWidget(widget); + })); + + + this._diffHunksRenderStore.add(this._editor.onMouseMove(e => { + + // reveal when hovering over + if (e.target.type === MouseTargetType.OVERLAY_WIDGET) { + const id = e.target.detail; + const widget = this._diffHunkWidgets.find(w => w.getId() === id); + toggleWidget(widget); + + } else if (e.target.type === MouseTargetType.CONTENT_VIEW_ZONE) { + const zone = e.target.detail; + const idx = this._viewZones.findIndex(id => id === zone.viewZoneId); + toggleWidget(this._diffHunkWidgets[idx]); + + } else if (e.target.position) { + const { position } = e.target; + const idx = diffHunkDecoCollection.getRanges().findIndex(r => r.containsPosition(position)); + toggleWidget(this._diffHunkWidgets[idx]); + + } else { + toggleWidget(undefined); + } + })); + + this._diffHunksRenderStore.add(Event.any(this._editor.onDidScrollChange, this._editor.onDidLayoutChange)(() => { + for (let i = 0; i < this._diffHunkWidgets.length; i++) { + const widget = this._diffHunkWidgets[i]; + const range = diffHunkDecoCollection.getRange(i); + if (range) { + widget.layout(range?.startLineNumber); + } else { + widget.dispose(); + } + } + })); } - revealNext() { - this._reveal(true); + revealNext(strict = false): boolean { + return this._reveal(true, strict); } - revealPrevious() { - this._reveal(false); + revealPrevious(strict = false): boolean { + return this._reveal(false, strict); } - private _reveal(next: boolean) { + private _reveal(next: boolean, strict: boolean): boolean { const position = this._editor.getPosition(); if (!position) { - return; + return false; } const decorations: (Range | undefined)[] = this._decorations @@ -299,7 +386,7 @@ export class ChatEditorController extends Disposable implements IEditorContribut coalesceInPlace(decorations); if (decorations.length === 0) { - return; + return false; } let idx = binarySearch(decorations, Range.fromPositions(position), Range.compareRangesUsingStarts); @@ -314,12 +401,171 @@ export class ChatEditorController extends Disposable implements IEditorContribut target = next ? idx : idx - 1; } - target = (target + decorations.length) % decorations.length; + if (strict && (target < 0 || target >= decorations.length)) { + return false; + } + target = (target + decorations.length) % decorations.length; const targetPosition = decorations[target].getStartPosition(); this._editor.setPosition(targetPosition); this._editor.revealPositionInCenter(targetPosition, ScrollType.Smooth); this._editor.focus(); + return true; + } + + undoNearestChange(closestWidget: DiffHunkWidget | undefined): void { + if (!this._editor.hasModel()) { + return; + } + const lineRelativeTop = this._editor.getTopForLineNumber(this._editor.getPosition().lineNumber) - this._editor.getScrollTop(); + let closestDistance = Number.MAX_VALUE; + + if (!(closestWidget instanceof DiffHunkWidget)) { + for (const widget of this._diffHunkWidgets) { + const widgetTop = (widget.getPosition()?.preference)?.top; + if (widgetTop !== undefined) { + const distance = Math.abs(widgetTop - lineRelativeTop); + if (distance < closestDistance) { + closestDistance = distance; + closestWidget = widget; + } + } + } + } + + if (closestWidget instanceof DiffHunkWidget) { + closestWidget.undo(); + } + } + + async openDiff(widget: DiffHunkWidget | undefined): Promise { + if (!this._editor.hasModel()) { + return; + } + const lineRelativeTop = this._editor.getTopForLineNumber(this._editor.getPosition().lineNumber) - this._editor.getScrollTop(); + let closestDistance = Number.MAX_VALUE; + + if (!(widget instanceof DiffHunkWidget)) { + for (const candidate of this._diffHunkWidgets) { + const widgetTop = (candidate.getPosition()?.preference)?.top; + if (widgetTop !== undefined) { + const distance = Math.abs(widgetTop - lineRelativeTop); + if (distance < closestDistance) { + closestDistance = distance; + widget = candidate; + } + } + } + } + + if (widget instanceof DiffHunkWidget) { + + const lineNumber = widget.getStartLineNumber(); + const position = lineNumber ? new Position(lineNumber, 1) : undefined; + let selection = this._editor.getSelection(); + if (position && !selection.containsPosition(position)) { + selection = Selection.fromPositions(position); + } + + const diffEditor = await this._editorService.openEditor({ + original: { resource: widget.entry.originalURI, options: { selection: undefined } }, + modified: { resource: widget.entry.modifiedURI, options: { selection } }, + }); + + // this is needed, passing the selection doesn't seem to work + diffEditor?.getControl()?.setSelection(selection); + } + } +} + +class DiffHunkWidget implements IOverlayWidget { + + private static _idPool = 0; + private readonly _id: string = `diff-change-widget-${DiffHunkWidget._idPool++}`; + + private readonly _domNode: HTMLElement; + private readonly _store = new DisposableStore(); + private _position: IOverlayWidgetPosition | undefined; + private _lastStartLineNumber: number | undefined; + + + constructor( + readonly entry: IModifiedFileEntry, + private readonly _undoEdits: ISingleEditOperation[], + private readonly _versionId: number, + private readonly _editor: ICodeEditor, + private readonly _lineDelta: number, + @IInstantiationService instaService: IInstantiationService, + ) { + this._domNode = document.createElement('div'); + this._domNode.className = 'chat-diff-change-content-widget'; + + const toolbar = instaService.createInstance(MenuWorkbenchToolBar, this._domNode, MenuId.ChatEditingEditorHunk, { + telemetrySource: 'chatEditingEditorHunk', + hiddenItemStrategy: HiddenItemStrategy.NoHide, + toolbarOptions: { primaryGroup: () => true, }, + menuOptions: { + renderShortTitle: true, + arg: this, + }, + }); + + this._store.add(toolbar); + this._editor.addOverlayWidget(this); + } + + dispose(): void { + this._store.dispose(); + this._editor.removeOverlayWidget(this); + } + + getId(): string { + return this._id; + } + + layout(startLineNumber: number): void { + + const lineHeight = this._editor.getOption(EditorOption.lineHeight); + const { contentLeft, contentWidth, verticalScrollbarWidth } = this._editor.getLayoutInfo(); + const scrollTop = this._editor.getScrollTop(); + + this._position = { + stackOridinal: 1, + preference: { + top: this._editor.getTopForLineNumber(startLineNumber) - scrollTop - (lineHeight * this._lineDelta), + left: contentLeft + contentWidth - (2 * verticalScrollbarWidth + getTotalWidth(this._domNode)) + } + }; + + this._editor.layoutOverlayWidget(this); + this._lastStartLineNumber = startLineNumber; + } + + toggle(show: boolean) { + this._domNode.classList.toggle('hover', show); + if (this._lastStartLineNumber) { + this.layout(this._lastStartLineNumber); + } + } + + getDomNode(): HTMLElement { + return this._domNode; + } + + getPosition(): IOverlayWidgetPosition | null { + return this._position ?? null; + } + + getStartLineNumber(): number | undefined { + return this._lastStartLineNumber; + } + + // --- + + undo() { + if (this._versionId === this._editor.getModel()?.getVersionId()) { + this._editor.executeEdits('chatEdits.undo', this._undoEdits); + } } } diff --git a/src/vs/workbench/contrib/chat/browser/chatEditorOverlay.ts b/src/vs/workbench/contrib/chat/browser/chatEditorOverlay.ts new file mode 100644 index 0000000000000..0c12333e37c41 --- /dev/null +++ b/src/vs/workbench/contrib/chat/browser/chatEditorOverlay.ts @@ -0,0 +1,409 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import './media/chatEditorOverlay.css'; +import { DisposableStore, MutableDisposable } from '../../../../base/common/lifecycle.js'; +import { autorun, IReader, ISettableObservable, ITransaction, observableFromEvent, observableSignal, observableValue, transaction } from '../../../../base/common/observable.js'; +import { isEqual } from '../../../../base/common/resources.js'; +import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition, OverlayWidgetPositionPreference } from '../../../../editor/browser/editorBrowser.js'; +import { IEditorContribution } from '../../../../editor/common/editorCommon.js'; +import { HiddenItemStrategy, MenuWorkbenchToolBar, WorkbenchToolBar } from '../../../../platform/actions/browser/toolbar.js'; +import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; +import { ChatEditingSessionState, IChatEditingService, IChatEditingSession, IModifiedFileEntry, WorkingSetEntryState } from '../common/chatEditingService.js'; +import { MenuId, MenuRegistry } from '../../../../platform/actions/common/actions.js'; +import { ActionViewItem } from '../../../../base/browser/ui/actionbar/actionViewItems.js'; +import { ACTIVE_GROUP, IEditorService } from '../../../services/editor/common/editorService.js'; +import { Range } from '../../../../editor/common/core/range.js'; +import { IActionRunner } from '../../../../base/common/actions.js'; +import { getWindow, reset, scheduleAtNextAnimationFrame } from '../../../../base/browser/dom.js'; +import { EditorOption } from '../../../../editor/common/config/editorOptions.js'; +import { renderIcon } from '../../../../base/browser/ui/iconLabel/iconLabels.js'; +import { ThemeIcon } from '../../../../base/common/themables.js'; +import { Codicon } from '../../../../base/common/codicons.js'; +import { assertType } from '../../../../base/common/types.js'; +import { localize } from '../../../../nls.js'; +import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js'; + +class ChatEditorOverlayWidget implements IOverlayWidget { + + readonly allowEditorOverflow = false; + + private readonly _domNode: HTMLElement; + private readonly _progressNode: HTMLElement; + private readonly _toolbar: WorkbenchToolBar; + + private _isAdded: boolean = false; + private readonly _showStore = new DisposableStore(); + + private readonly _entry = observableValue<{ entry: IModifiedFileEntry; next: IModifiedFileEntry } | undefined>(this, undefined); + + private readonly _navigationBearings = observableValue<{ changeCount: number; activeIdx: number }>(this, { changeCount: -1, activeIdx: -1 }); + + constructor( + private readonly _editor: ICodeEditor, + @IEditorService editorService: IEditorService, + @IInstantiationService instaService: IInstantiationService, + ) { + this._domNode = document.createElement('div'); + this._domNode.classList.add('chat-editor-overlay-widget'); + + this._progressNode = document.createElement('div'); + this._progressNode.classList.add('chat-editor-overlay-progress'); + this._domNode.appendChild(this._progressNode); + + const toolbarNode = document.createElement('div'); + toolbarNode.classList.add('chat-editor-overlay-toolbar'); + this._domNode.appendChild(toolbarNode); + + this._toolbar = instaService.createInstance(MenuWorkbenchToolBar, toolbarNode, MenuId.ChatEditingEditorContent, { + telemetrySource: 'chatEditor.overlayToolbar', + hiddenItemStrategy: HiddenItemStrategy.Ignore, + toolbarOptions: { + primaryGroup: () => true, + useSeparatorsInPrimaryActions: true + }, + menuOptions: { renderShortTitle: true }, + actionViewItemProvider: (action, options) => { + const that = this; + + if (action.id === navigationBearingFakeActionId) { + return new class extends ActionViewItem { + + constructor() { + super(undefined, action, { ...options, icon: false, label: true, keybindingNotRenderedWithLabel: true }); + } + + override render(container: HTMLElement) { + super.render(container); + + container.classList.add('label-item'); + + this._store.add(autorun(r => { + assertType(this.label); + + const { changeCount, activeIdx } = that._navigationBearings.read(r); + const n = activeIdx === -1 ? '?' : `${activeIdx + 1}`; + const m = changeCount === -1 ? '?' : `${changeCount}`; + this.label.innerText = localize('nOfM', "{0} of {1}", n, m); + })); + } + + protected override getTooltip(): string | undefined { + return undefined; + } + }; + } + + if (action.id === 'chatEditor.action.accept' || action.id === 'chatEditor.action.reject') { + return new class extends ActionViewItem { + + private readonly _reveal = this._store.add(new MutableDisposable()); + + constructor() { + super(undefined, action, { ...options, icon: false, label: true, keybindingNotRenderedWithLabel: true }); + } + override set actionRunner(actionRunner: IActionRunner) { + super.actionRunner = actionRunner; + + const store = new DisposableStore(); + + store.add(actionRunner.onWillRun(_e => { + that._editor.focus(); + })); + + store.add(actionRunner.onDidRun(e => { + if (e.action !== this.action) { + return; + } + const d = that._entry.get(); + if (!d || d.entry === d.next) { + return; + } + const change = d.next.diffInfo.get().changes.at(0); + return editorService.openEditor({ + resource: d.next.modifiedURI, + options: { + selection: change && Range.fromPositions({ lineNumber: change.original.startLineNumber, column: 1 }), + revealIfOpened: false, + revealIfVisible: false, + } + }, ACTIVE_GROUP); + })); + + this._reveal.value = store; + } + override get actionRunner(): IActionRunner { + return super.actionRunner; + } + }; + } + return undefined; + } + }); + } + + dispose() { + this.hide(); + this._showStore.dispose(); + this._toolbar.dispose(); + } + + getId(): string { + return 'chatEditorOverlayWidget'; + } + + getDomNode(): HTMLElement { + return this._domNode; + } + + getPosition(): IOverlayWidgetPosition | null { + return { preference: OverlayWidgetPositionPreference.BOTTOM_RIGHT_CORNER }; + } + + show(session: IChatEditingSession, activeEntry: IModifiedFileEntry, next: IModifiedFileEntry) { + + this._showStore.clear(); + + this._entry.set({ entry: activeEntry, next }, undefined); + + this._showStore.add(autorun(r => { + const busy = activeEntry.isCurrentlyBeingModified.read(r); + this._domNode.classList.toggle('busy', busy); + })); + + const slickRatio = ObservableAnimatedValue.const(0); + let t = Date.now(); + this._showStore.add(autorun(r => { + const value = activeEntry.rewriteRatio.read(r); + + slickRatio.changeAnimation(prev => { + const result = new AnimatedValue(prev.getValue(), value, Date.now() - t); + t = Date.now(); + return result; + }, undefined); + + const value2 = slickRatio.getValue(r); + reset(this._progressNode, value === 0 + ? renderIcon(ThemeIcon.modify(Codicon.loading, 'spin')) + : `${Math.round(value2 * 100)}%` + ); + })); + + + const editorPositionObs = observableFromEvent(this._editor.onDidChangeCursorPosition, () => this._editor.getPosition()); + + this._showStore.add(autorun(r => { + const position = editorPositionObs.read(r); + + if (!position) { + return; + } + + const entries = session.entries.read(r); + + let changes = 0; + let activeIdx = -1; + for (const entry of entries) { + const diffInfo = entry.diffInfo.read(r); + + if (activeIdx !== -1 || entry !== activeEntry) { + // just add up + changes += diffInfo.changes.length; + + } else { + for (const change of diffInfo.changes) { + if (change.modified.includes(position.lineNumber)) { + activeIdx = changes; + } + changes += 1; + } + } + } + + this._navigationBearings.set({ changeCount: changes, activeIdx }, undefined); + })); + + if (!this._isAdded) { + this._editor.addOverlayWidget(this); + this._isAdded = true; + } + } + + hide() { + + transaction(tx => { + this._entry.set(undefined, tx); + this._navigationBearings.set({ changeCount: -1, activeIdx: -1 }, tx); + }); + + if (this._isAdded) { + this._editor.removeOverlayWidget(this); + this._isAdded = false; + this._showStore.clear(); + } + } +} + +const navigationBearingFakeActionId = 'chatEditor.navigation.bearings'; + +MenuRegistry.appendMenuItem(MenuId.ChatEditingEditorContent, { + command: { + id: navigationBearingFakeActionId, + title: localize('label', "Navigation Status"), + precondition: ContextKeyExpr.false(), + }, + group: 'navigate', + order: -1 +}); + + +export class ObservableAnimatedValue { + public static const(value: number): ObservableAnimatedValue { + return new ObservableAnimatedValue(AnimatedValue.const(value)); + } + + private readonly _value: ISettableObservable; + + constructor( + initialValue: AnimatedValue, + ) { + this._value = observableValue(this, initialValue); + } + + setAnimation(value: AnimatedValue, tx: ITransaction | undefined): void { + this._value.set(value, tx); + } + + changeAnimation(fn: (prev: AnimatedValue) => AnimatedValue, tx: ITransaction | undefined): void { + const value = fn(this._value.get()); + this._value.set(value, tx); + } + + getValue(reader: IReader | undefined): number { + const value = this._value.read(reader); + if (!value.isFinished()) { + Scheduler.instance.invalidateOnNextAnimationFrame(reader); + } + return value.getValue(); + } +} + +class Scheduler { + static instance = new Scheduler(); + + private readonly _signal = observableSignal(this); + + private _isScheduled = false; + + invalidateOnNextAnimationFrame(reader: IReader | undefined): void { + this._signal.read(reader); + if (!this._isScheduled) { + this._isScheduled = true; + scheduleAtNextAnimationFrame(getWindow(undefined), () => { + this._isScheduled = false; + this._signal.trigger(undefined); + }); + } + } +} + +export class AnimatedValue { + + static const(value: number): AnimatedValue { + return new AnimatedValue(value, value, 0); + } + + readonly startTimeMs = Date.now(); + + constructor( + readonly startValue: number, + readonly endValue: number, + readonly durationMs: number, + ) { + if (startValue === endValue) { + this.durationMs = 0; + } + } + + isFinished(): boolean { + return Date.now() >= this.startTimeMs + this.durationMs; + } + + getValue(): number { + const timePassed = Date.now() - this.startTimeMs; + if (timePassed >= this.durationMs) { + return this.endValue; + } + const value = easeOutExpo(timePassed, this.startValue, this.endValue - this.startValue, this.durationMs); + return value; + } +} + +function easeOutExpo(passedTime: number, start: number, length: number, totalDuration: number): number { + return passedTime === totalDuration + ? start + length + : length * (-Math.pow(2, -10 * passedTime / totalDuration) + 1) + start; +} + + +export class ChatEditorOverlayController implements IEditorContribution { + + static readonly ID = 'editor.contrib.chatOverlayController'; + + private readonly _store = new DisposableStore(); + + static get(editor: ICodeEditor) { + return editor.getContribution(ChatEditorOverlayController.ID); + } + + constructor( + private readonly _editor: ICodeEditor, + @IChatEditingService chatEditingService: IChatEditingService, + @IInstantiationService instaService: IInstantiationService, + ) { + const modelObs = observableFromEvent(this._editor.onDidChangeModel, () => this._editor.getModel()); + const widget = instaService.createInstance(ChatEditorOverlayWidget, this._editor); + + if (this._editor.getOption(EditorOption.inDiffEditor)) { + return; + } + + this._store.add(autorun(r => { + const model = modelObs.read(r); + const session = chatEditingService.currentEditingSessionObs.read(r); + if (!session || !model) { + widget.hide(); + return; + } + + const state = session.state.read(r); + if (state === ChatEditingSessionState.Disposed) { + widget.hide(); + return; + } + + const entries = session.entries.read(r); + const idx = entries.findIndex(e => isEqual(e.modifiedURI, model.uri)); + if (idx < 0) { + widget.hide(); + return; + } + + const isModifyingOrModified = entries.some(e => e.state.read(r) === WorkingSetEntryState.Modified || e.isCurrentlyBeingModified.read(r)); + if (!isModifyingOrModified) { + widget.hide(); + return; + } + + const entry = entries[idx]; + widget.show(session, entry, entries[(idx + 1) % entries.length]); + + })); + } + + dispose() { + this._store.dispose(); + } +} diff --git a/src/vs/workbench/contrib/chat/browser/chatEditorSaving.ts b/src/vs/workbench/contrib/chat/browser/chatEditorSaving.ts index 790ff8e78c771..c5c62968a66c0 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditorSaving.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditorSaving.ts @@ -10,6 +10,9 @@ import { Iterable } from '../../../../base/common/iterator.js'; import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js'; import { Disposable, DisposableMap, DisposableStore, MutableDisposable } from '../../../../base/common/lifecycle.js'; import { ResourceSet } from '../../../../base/common/map.js'; +import { autorunWithStore } from '../../../../base/common/observable.js'; +import { isEqual } from '../../../../base/common/resources.js'; +import { assertType } from '../../../../base/common/types.js'; import { URI } from '../../../../base/common/uri.js'; import { ServicesAccessor } from '../../../../editor/browser/editorExtensions.js'; import { localize } from '../../../../nls.js'; @@ -25,8 +28,11 @@ import { IEditorService } from '../../../services/editor/common/editorService.js import { IFilesConfigurationService } from '../../../services/filesConfiguration/common/filesConfigurationService.js'; import { ITextFileService } from '../../../services/textfile/common/textfiles.js'; import { ChatAgentLocation, IChatAgentService } from '../common/chatAgents.js'; -import { CONTEXT_CHAT_LOCATION, CONTEXT_CHAT_REQUEST_IN_PROGRESS, CONTEXT_IN_CHAT_INPUT } from '../common/chatContextKeys.js'; -import { applyingChatEditsFailedContextKey, CHAT_EDITING_MULTI_DIFF_SOURCE_RESOLVER_SCHEME, hasAppliedChatEditsContextKey, hasUndecidedChatEditingResourceContextKey, IChatEditingService, IChatEditingSession, WorkingSetEntryState } from '../common/chatEditingService.js'; +import { ChatContextKeys } from '../common/chatContextKeys.js'; +import { applyingChatEditsFailedContextKey, CHAT_EDITING_MULTI_DIFF_SOURCE_RESOLVER_SCHEME, hasAppliedChatEditsContextKey, hasUndecidedChatEditingResourceContextKey, IChatEditingService, IChatEditingSession, IModifiedFileEntry, WorkingSetEntryState } from '../common/chatEditingService.js'; +import { IChatModel } from '../common/chatModel.js'; +import { IChatService } from '../common/chatService.js'; +import { ChatEditingModifiedFileEntry } from './chatEditing/chatEditingModifiedFileEntry.js'; export class ChatEditorSaving extends Disposable implements IWorkbenchContribution { @@ -43,12 +49,32 @@ export class ChatEditorSaving extends Disposable implements IWorkbenchContributi @ITextFileService textFileService: ITextFileService, @ILabelService labelService: ILabelService, @IDialogService dialogService: IDialogService, + @IChatService private readonly _chatService: IChatService, @IFilesConfigurationService private readonly _fileConfigService: IFilesConfigurationService, ) { super(); - const store = this._store.add(new DisposableStore()); + // --- report that save happened + this._store.add(autorunWithStore((r, store) => { + const session = chatEditingService.currentEditingSessionObs.read(r); + if (!session) { + return; + } + const chatSession = this._chatService.getSession(session.chatSessionId); + if (!chatSession) { + return; + } + const entries = session.entries.read(r); + store.add(textFileService.files.onDidSave(e => { + const entry = entries.find(entry => isEqual(entry.modifiedURI, e.model.resource)); + if (entry && entry.state.get() === WorkingSetEntryState.Modified) { + this._reportSavedWhenReady(chatSession, entry); + } + })); + })); + + const store = this._store.add(new DisposableStore()); const update = () => { @@ -157,6 +183,35 @@ export class ChatEditorSaving extends Disposable implements IWorkbenchContributi update(); } + private _reportSaved(entry: IModifiedFileEntry) { + assertType(entry instanceof ChatEditingModifiedFileEntry); + + this._chatService.notifyUserAction({ + action: { kind: 'chatEditingSessionAction', uri: entry.modifiedURI, hasRemainingEdits: false, outcome: 'saved' }, + agentId: entry.telemetryInfo.agentId, + command: entry.telemetryInfo.command, + sessionId: entry.telemetryInfo.sessionId, + requestId: entry.telemetryInfo.requestId, + result: entry.telemetryInfo.result + }); + } + + private _reportSavedWhenReady(session: IChatModel, entry: IModifiedFileEntry) { + if (!session.requestInProgress) { + this._reportSaved(entry); + return; + } + // wait until no more request is pending + const d = session.onDidChange(e => { + if (!session.requestInProgress) { + this._reportSaved(entry); + this._store.delete(d); + d.dispose(); + } + }); + this._store.add(d); + } + private _handleNewEditingSession(session: IChatEditingSession, container: DisposableStore) { const store = new DisposableStore(); @@ -199,7 +254,7 @@ export class ChatEditingSaveAllAction extends Action2 { id: ChatEditingSaveAllAction.ID, title: ChatEditingSaveAllAction.LABEL, tooltip: ChatEditingSaveAllAction.LABEL, - precondition: ContextKeyExpr.and(CONTEXT_CHAT_REQUEST_IN_PROGRESS.negate(), hasUndecidedChatEditingResourceContextKey), + precondition: ContextKeyExpr.and(ChatContextKeys.requestInProgress.negate(), hasUndecidedChatEditingResourceContextKey), icon: Codicon.saveAll, menu: [ { @@ -212,19 +267,18 @@ export class ChatEditingSaveAllAction extends Action2 { id: MenuId.ChatEditingWidgetToolbar, group: 'navigation', order: 2, - // Show the option to save without accepting if the user has autosave - // and also hasn't configured the setting to always save with generated changes + // Show the option to save without accepting if the user hasn't configured the setting to always save with generated changes when: ContextKeyExpr.and( applyingChatEditsFailedContextKey.negate(), ContextKeyExpr.or(hasUndecidedChatEditingResourceContextKey, hasAppliedChatEditsContextKey.negate()), - ContextKeyExpr.notEquals('config.files.autoSave', 'off'), ContextKeyExpr.equals(`config.${ChatEditorSaving._config}`, false), - CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.EditingSession) + ContextKeyExpr.equals(`config.${ChatEditorSaving._config}`, false), + ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession) ) } ], keybinding: { primary: KeyMod.CtrlCmd | KeyCode.KeyS, - when: ContextKeyExpr.and(CONTEXT_CHAT_REQUEST_IN_PROGRESS.negate(), hasUndecidedChatEditingResourceContextKey, CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.EditingSession), CONTEXT_IN_CHAT_INPUT), + when: ContextKeyExpr.and(ChatContextKeys.requestInProgress.negate(), hasUndecidedChatEditingResourceContextKey, ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession), ChatContextKeys.inChatInput), weight: KeybindingWeight.WorkbenchContrib, }, }); diff --git a/src/vs/workbench/contrib/chat/browser/chatInlineAnchorWidget.ts b/src/vs/workbench/contrib/chat/browser/chatInlineAnchorWidget.ts index aed5b07953210..669f08b918703 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInlineAnchorWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInlineAnchorWidget.ts @@ -6,7 +6,6 @@ import * as dom from '../../../../base/browser/dom.js'; import { StandardMouseEvent } from '../../../../base/browser/mouseEvent.js'; import { getDefaultHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js'; -import { IAction } from '../../../../base/common/actions.js'; import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js'; import { Lazy } from '../../../../base/common/lazy.js'; import { Disposable } from '../../../../base/common/lifecycle.js'; @@ -25,7 +24,7 @@ import { ITextModelService } from '../../../../editor/common/services/resolverSe import { DefinitionAction } from '../../../../editor/contrib/gotoSymbol/browser/goToCommands.js'; import * as nls from '../../../../nls.js'; import { localize } from '../../../../nls.js'; -import { createAndFillInContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { getFlatContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { Action2, IMenuService, MenuId, registerAction2 } from '../../../../platform/actions/common/actions.js'; import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; @@ -214,9 +213,7 @@ export class InlineAnchorWidget extends Disposable { getAnchor: () => event, getActions: () => { const menu = menuService.getMenuActions(contextMenuId, contextKeyService, { arg: contextMenuArg }); - const primary: IAction[] = []; - createAndFillInContextMenuActions(menu, primary); - return primary; + return getFlatContextMenuActions(menu); }, }); })); diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 63a5b9520bdf4..4f29de2189713 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -8,6 +8,7 @@ import { addDisposableListener } from '../../../../base/browser/dom.js'; import { DEFAULT_FONT_FAMILY } from '../../../../base/browser/fonts.js'; import { IHistoryNavigationWidget } from '../../../../base/browser/history.js'; import { StandardKeyboardEvent } from '../../../../base/browser/keyboardEvent.js'; +import { StandardMouseEvent } from '../../../../base/browser/mouseEvent.js'; import * as aria from '../../../../base/browser/ui/aria/aria.js'; import { Button } from '../../../../base/browser/ui/button/button.js'; import { IHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegate.js'; @@ -25,7 +26,6 @@ import { Disposable, DisposableStore, IDisposable, MutableDisposable } from '../ import { ResourceSet } from '../../../../base/common/map.js'; import { basename, dirname } from '../../../../base/common/path.js'; import { isMacintosh } from '../../../../base/common/platform.js'; -import type { Mutable } from '../../../../base/common/types.js'; import { URI } from '../../../../base/common/uri.js'; import { IEditorConstructionOptions } from '../../../../editor/browser/config/editorConfiguration.js'; import { EditorExtensionsRegistry } from '../../../../editor/browser/editorExtensions.js'; @@ -33,7 +33,8 @@ import { CodeEditorWidget } from '../../../../editor/browser/widget/codeEditor/c import { EditorOptions } from '../../../../editor/common/config/editorOptions.js'; import { IDimension } from '../../../../editor/common/core/dimension.js'; import { IPosition } from '../../../../editor/common/core/position.js'; -import { Range } from '../../../../editor/common/core/range.js'; +import { IRange, Range } from '../../../../editor/common/core/range.js'; +import { ILanguageService } from '../../../../editor/common/languages/language.js'; import { ITextModel } from '../../../../editor/common/model.js'; import { IModelService } from '../../../../editor/common/services/model.js'; import { CopyPasteController } from '../../../../editor/contrib/dropOrPasteInto/browser/copyPasteController.js'; @@ -44,14 +45,14 @@ import { localize } from '../../../../nls.js'; import { IAccessibilityService } from '../../../../platform/accessibility/common/accessibility.js'; import { MenuWorkbenchButtonBar } from '../../../../platform/actions/browser/buttonbar.js'; import { DropdownWithPrimaryActionViewItem, IDropdownWithPrimaryActionViewItemOptions } from '../../../../platform/actions/browser/dropdownWithPrimaryActionViewItem.js'; -import { createAndFillInActionBarActions, IMenuEntryActionViewItemOptions, MenuEntryActionViewItem } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { getFlatActionBarActions, getFlatContextMenuActions, IMenuEntryActionViewItemOptions, MenuEntryActionViewItem } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { HiddenItemStrategy, MenuWorkbenchToolBar } from '../../../../platform/actions/browser/toolbar.js'; import { IMenuService, MenuId, MenuItemAction } from '../../../../platform/actions/common/actions.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { IContextKey, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js'; -import type { ITextEditorOptions } from '../../../../platform/editor/common/editor.js'; +import { ITextEditorOptions } from '../../../../platform/editor/common/editor.js'; import { FileKind, IFileService } from '../../../../platform/files/common/files.js'; import { registerAndCreateHistoryNavigationContext } from '../../../../platform/history/browser/contextScopedHistoryWidget.js'; import { IHoverService } from '../../../../platform/hover/browser/hover.js'; @@ -62,21 +63,25 @@ import { WorkbenchList } from '../../../../platform/list/browser/listService.js' import { ILogService } from '../../../../platform/log/common/log.js'; import { INotificationService } from '../../../../platform/notification/common/notification.js'; import { IOpenerService, type OpenInternalOptions } from '../../../../platform/opener/common/opener.js'; -import { IThemeService } from '../../../../platform/theme/common/themeService.js'; -import { ResourceLabels } from '../../../browser/labels.js'; +import { FolderThemeIcon, IThemeService } from '../../../../platform/theme/common/themeService.js'; +import { fillEditorsDragData } from '../../../browser/dnd.js'; +import { IFileLabelOptions, ResourceLabels } from '../../../browser/labels.js'; +import { ResourceContextKey } from '../../../common/contextkeys.js'; import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from '../../../services/editor/common/editorService.js'; import { AccessibilityVerbositySettingId } from '../../accessibility/browser/accessibilityConfiguration.js'; import { AccessibilityCommandId } from '../../accessibility/common/accessibilityCommands.js'; import { getSimpleCodeEditorWidgetOptions, getSimpleEditorOptions, setupSimpleEditorSelectionStyling } from '../../codeEditor/browser/simpleEditorOptions.js'; +import { revealInSideBarCommand } from '../../files/browser/fileActions.contribution.js'; import { ChatAgentLocation, IChatAgentService } from '../common/chatAgents.js'; -import { CONTEXT_CHAT_HAS_FILE_ATTACHMENTS, CONTEXT_CHAT_INPUT_CURSOR_AT_TOP, CONTEXT_CHAT_INPUT_HAS_FOCUS, CONTEXT_CHAT_INPUT_HAS_TEXT, CONTEXT_IN_CHAT_INPUT } from '../common/chatContextKeys.js'; +import { ChatContextKeys } from '../common/chatContextKeys.js'; import { ChatEditingSessionState, IChatEditingService, IChatEditingSession, WorkingSetEntryState } from '../common/chatEditingService.js'; import { IChatRequestVariableEntry } from '../common/chatModel.js'; +import { ChatRequestDynamicVariablePart } from '../common/chatParserTypes.js'; import { IChatFollowup } from '../common/chatService.js'; import { IChatResponseViewModel } from '../common/chatViewModel.js'; import { IChatHistoryEntry, IChatInputState, IChatWidgetHistoryService } from '../common/chatWidgetHistoryService.js'; import { ILanguageModelChatMetadata, ILanguageModelsService } from '../common/languageModels.js'; -import { CancelAction, ChatModelPickerActionId, ChatSubmitSecondaryAgentAction, IChatExecuteActionContext, SubmitAction } from './actions/chatExecuteActions.js'; +import { CancelAction, ChatModelPickerActionId, ChatSubmitSecondaryAgentAction, IChatExecuteActionContext, ChatSubmitAction } from './actions/chatExecuteActions.js'; import { ImplicitContextAttachmentWidget } from './attachments/implicitContextAttachment.js'; import { IChatWidget } from './chat.js'; import { ChatAttachmentModel } from './chatAttachmentModel.js'; @@ -195,7 +200,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge private inputEditorHasFocus: IContextKey; private readonly _waitForPersistedLanguageModel = this._register(new MutableDisposable()); - private _onDidChangeCurrentLanguageModel = new Emitter(); + private _onDidChangeCurrentLanguageModel = this._register(new Emitter()); private _currentLanguageModel: string | undefined; get currentLanguageModel() { return this._currentLanguageModel; @@ -223,6 +228,15 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge } return edits; } + + private _attemptedWorkingSetEntriesCount: number = 0; + /** + * The number of working set entries that the user actually wanted to attach. + * This is less than or equal to {@link ChatInputPart.chatEditWorkingSetFiles}. + */ + public get attemptedWorkingSetEntriesCount() { + return this._attemptedWorkingSetEntriesCount; + } private _combinedChatEditWorkingSetEntries: URI[] = []; public get chatEditWorkingSetFiles() { return this._combinedChatEditWorkingSetEntries; @@ -239,6 +253,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge @IModelService private readonly modelService: IModelService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IContextMenuService private readonly contextMenuService: IContextMenuService, @IConfigurationService private readonly configurationService: IConfigurationService, @IKeybindingService private readonly keybindingService: IKeybindingService, @IAccessibilityService private readonly accessibilityService: IAccessibilityService, @@ -250,6 +265,9 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge @IEditorService private readonly editorService: IEditorService, @IOpenerService private readonly openerService: IOpenerService, @IChatEditingService private readonly chatEditingService: IChatEditingService, + @IMenuService private readonly menuService: IMenuService, + @ILanguageService private readonly languageService: ILanguageService, + @IThemeService private readonly themeService: IThemeService, ) { super(); @@ -262,9 +280,9 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge }; this.inputEditorMaxHeight = this.options.renderStyle === 'compact' ? INPUT_EDITOR_MAX_HEIGHT / 3 : INPUT_EDITOR_MAX_HEIGHT; - this.inputEditorHasText = CONTEXT_CHAT_INPUT_HAS_TEXT.bindTo(contextKeyService); - this.chatCursorAtTop = CONTEXT_CHAT_INPUT_CURSOR_AT_TOP.bindTo(contextKeyService); - this.inputEditorHasFocus = CONTEXT_CHAT_INPUT_HAS_FOCUS.bindTo(contextKeyService); + this.inputEditorHasText = ChatContextKeys.inputHasText.bindTo(contextKeyService); + this.chatCursorAtTop = ChatContextKeys.inputCursorAtTop.bindTo(contextKeyService); + this.inputEditorHasFocus = ChatContextKeys.inputHasFocus.bindTo(contextKeyService); this.history = this.loadHistory(); this._register(this.historyService.onDidClearHistory(() => this.history = new HistoryNavigator2([{ text: '' }], 50, historyKeyFn))); @@ -277,7 +295,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge this._chatEditsListPool = this._register(this.instantiationService.createInstance(CollapsibleListPool, this._onDidChangeVisibility.event, MenuId.ChatEditingWidgetModifiedFilesToolbar)); - this._hasFileAttachmentContextKey = CONTEXT_CHAT_HAS_FILE_ATTACHMENTS.bindTo(contextKeyService); + this._hasFileAttachmentContextKey = ChatContextKeys.hasFileAttachments.bindTo(contextKeyService); } private setCurrentLanguageModelToDefault() { @@ -538,7 +556,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge this.renderChatEditingSessionState(null, widget); const inputScopedContextKeyService = this._register(this.contextKeyService.createScoped(inputContainer)); - CONTEXT_IN_CHAT_INPUT.bindTo(inputScopedContextKeyService).set(true); + ChatContextKeys.inChatInput.bindTo(inputScopedContextKeyService).set(true); const scopedInstantiationService = this._register(this.instantiationService.createChild(new ServiceCollection([IContextKeyService, inputScopedContextKeyService]))); const { historyNavigationBackwardsEnablement, historyNavigationForwardsEnablement } = this._register(registerAndCreateHistoryNavigationContext(inputScopedContextKeyService, this)); @@ -584,6 +602,12 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge const inputHasText = !!model && model.getValue().trim().length > 0; this.inputEditorHasText.set(inputHasText); })); + this._register(this._inputEditor.onDidContentSizeChange(e => { + if (e.contentHeightChanged) { + this.inputEditorHeight = e.contentHeight; + this._onDidChangeHeight.fire(); + } + })); this._register(this._inputEditor.onDidFocusEditorText(() => { this.inputEditorHasFocus.set(true); this._onDidFocus.fire(); @@ -619,8 +643,8 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge hoverDelegate, hiddenItemStrategy: HiddenItemStrategy.Ignore, // keep it lean when hiding items and avoid a "..." overflow menu actionViewItemProvider: (action, options) => { - if (this.location === ChatAgentLocation.Panel) { - if ((action.id === SubmitAction.ID || action.id === CancelAction.ID) && action instanceof MenuItemAction) { + if (this.location === ChatAgentLocation.Panel || this.location === ChatAgentLocation.Editor) { + if ((action.id === ChatSubmitAction.ID || action.id === CancelAction.ID) && action instanceof MenuItemAction) { const dropdownAction = this.instantiationService.createInstance(MenuItemAction, { id: 'chat.moreExecuteActions', title: localize('notebook.moreExecuteActionsLabel', "More..."), icon: Codicon.chevronDown }, undefined, undefined, undefined, undefined); return this.instantiationService.createInstance(ChatSubmitDropdownActionItem, action, dropdownAction, options); } @@ -645,6 +669,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge return undefined; } })); + this.executeToolbar.getElement().classList.add('chat-execute-toolbar'); this.executeToolbar.context = { widget } satisfies IChatExecuteActionContext; this._register(this.executeToolbar.onDidChangeMenuItems(() => { if (this.cachedDimensions && typeof this.cachedExecuteToolbarWidth === 'number' && this.cachedExecuteToolbarWidth !== this.executeToolbar.getItemsWidth()) { @@ -698,6 +723,10 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge }; this._register(this._inputEditor.onDidChangeCursorPosition(e => onDidChangeCursorPosition())); onDidChangeCursorPosition(); + + this._register(this.themeService.onDidFileIconThemeChange(() => { + this.renderAttachedContext(); + })); } private async renderAttachedContext() { @@ -708,8 +737,12 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge dom.clearNode(container); const hoverDelegate = store.add(createInstantHoverDelegate()); - dom.setVisibility(Boolean(this.attachmentModel.size) || Boolean(this.implicitContext?.value), this.attachedContextContainer); - if (!this.attachmentModel.size) { + const attachments = this.location === ChatAgentLocation.EditingSession + // Render as attachments anything that isn't a file, but still render specific ranges in a file + ? [...this.attachmentModel.attachments.entries()].filter(([_, attachment]) => !attachment.isFile || attachment.isFile && typeof attachment.value === 'object' && !!attachment.value && 'range' in attachment.value) + : [...this.attachmentModel.attachments.entries()]; + dom.setVisibility(Boolean(attachments.length) || Boolean(this.implicitContext?.value), this.attachedContextContainer); + if (!attachments.length) { this._indexOfLastAttachedContextDeletedWithKeyboard = -1; } @@ -719,31 +752,49 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge } const attachmentInitPromises: Promise[] = []; - for (const [index, attachment] of this.attachmentModel.attachments.entries()) { - if (attachment.isFile && this.location === ChatAgentLocation.EditingSession) { - return; - } - + for (const [index, attachment] of attachments) { const widget = dom.append(container, $('.chat-attached-context-attachment.show-file-icons')); - const label = this._contextResourceLabels.create(widget, { supportIcons: true, hoverDelegate, hoverTargetOverrride: widget }); + const label = this._contextResourceLabels.create(widget, { supportIcons: true, hoverDelegate, hoverTargetOverride: widget }); let ariaLabel: string | undefined; - const file = URI.isUri(attachment.value) ? attachment.value : attachment.value && typeof attachment.value === 'object' && 'uri' in attachment.value && URI.isUri(attachment.value.uri) ? attachment.value.uri : undefined; + const resource = URI.isUri(attachment.value) ? attachment.value : attachment.value && typeof attachment.value === 'object' && 'uri' in attachment.value && URI.isUri(attachment.value.uri) ? attachment.value.uri : undefined; const range = attachment.value && typeof attachment.value === 'object' && 'range' in attachment.value && Range.isIRange(attachment.value.range) ? attachment.value.range : undefined; - if (file && attachment.isFile) { - const fileBasename = basename(file.path); - const fileDirname = dirname(file.path); + if (resource && (attachment.isFile || attachment.isDirectory)) { + const fileBasename = basename(resource.path); + const fileDirname = dirname(resource.path); const friendlyName = `${fileBasename} ${fileDirname}`; ariaLabel = range ? localize('chat.fileAttachmentWithRange', "Attached file, {0}, line {1} to line {2}", friendlyName, range.startLineNumber, range.endLineNumber) : localize('chat.fileAttachment', "Attached file, {0}", friendlyName); - label.setFile(file, { + const fileOptions: IFileLabelOptions = { hidePath: true }; + label.setFile(resource, attachment.isFile ? { + ...fileOptions, fileKind: FileKind.FILE, - hidePath: true, range, + } : { + ...fileOptions, + fileKind: FileKind.FOLDER, + icon: !this.themeService.getFileIconTheme().hasFolderIcons ? FolderThemeIcon : undefined }); - this.attachButtonAndDisposables(widget, index, attachment, hoverDelegate); + + const scopedContextKeyService = store.add(this.contextKeyService.createScoped(widget)); + const resourceContextKey = store.add(new ResourceContextKey(scopedContextKeyService, this.fileService, this.languageService, this.modelService)); + resourceContextKey.set(resource); + + this.attachButtonAndDisposables(widget, index, attachment, hoverDelegate, { + contextMenuArg: resource, + contextKeyService: scopedContextKeyService, + contextMenuId: MenuId.ChatInputResourceAttachmentContext, + }); + + // Drag and drop + widget.draggable = true; + this._register(dom.addDisposableListener(widget, 'dragstart', e => { + this.instantiationService.invokeFunction(accessor => fillEditorsDragData(accessor, [resource], e)); + e.dataTransfer?.setDragImage(widget, 0, 0); + })); + } else if (attachment.isImage) { ariaLabel = localize('chat.imageAttachment', "Attached image, {0}", attachment.name); @@ -793,36 +844,26 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge return; } - if (file) { + if (resource) { widget.style.cursor = 'pointer'; store.add(dom.addDisposableListener(widget, dom.EventType.CLICK, (e: MouseEvent) => { dom.EventHelper.stop(e, true); - const options: Mutable = { - fromUserGesture: true - }; - if (range) { - const textEditorOptions: ITextEditorOptions = { - selection: range - }; - options.editorOptions = textEditorOptions; + if (attachment.isDirectory) { + this.openResource(resource, true); + } else { + this.openResource(resource, false, range); } - this.openerService.open(file, options); })); store.add(dom.addDisposableListener(widget, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => { const event = new StandardKeyboardEvent(e); if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) { dom.EventHelper.stop(e, true); - const options: Mutable = { - fromUserGesture: true - }; - if (range) { - const textEditorOptions: ITextEditorOptions = { - selection: range - }; - options.editorOptions = textEditorOptions; + if (attachment.isDirectory) { + this.openResource(resource, true); + } else { + this.openResource(resource, false, range); } - this.openerService.open(file, options); } })); } @@ -836,7 +877,25 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge } } - private attachButtonAndDisposables(widget: HTMLElement, index: number, attachment: IChatRequestVariableEntry, hoverDelegate: IHoverDelegate) { + private openResource(resource: URI, isDirectory: true): void; + private openResource(resource: URI, isDirectory: false, range: IRange | undefined): void; + private openResource(resource: URI, isDirectory?: boolean, range?: IRange): void { + if (isDirectory) { + // Reveal Directory in explorer + this.commandService.executeCommand(revealInSideBarCommand.id, resource); + return; + } + + // Open file in editor + const openTextEditorOptions: ITextEditorOptions | undefined = range ? { selection: range } : undefined; + const options: OpenInternalOptions = { + fromUserGesture: true, + editorOptions: openTextEditorOptions, + }; + this.openerService.open(resource, options); + } + + private attachButtonAndDisposables(widget: HTMLElement, index: number, attachment: IChatRequestVariableEntry, hoverDelegate: IHoverDelegate, contextMenuOpts?: { contextMenuId: MenuId; contextKeyService: IContextKeyService; contextMenuArg: unknown }) { const store = this.attachedContextDisposables.value; if (!store) { return; @@ -855,7 +914,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge store.add(clearButton); clearButton.icon = Codicon.close; - const disp = Event.once(clearButton.onDidClick)((e) => { + store.add(Event.once(clearButton.onDidClick)((e) => { this._attachmentModel.delete(attachment.id); // Set focus to the next attached context item if deletion was triggered by a keystroke (vs a mouse click) @@ -871,8 +930,24 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge } this._onDidChangeContext.fire({ removed: [attachment] }); - }); - store.add(disp); + })); + + // Context menu + if (contextMenuOpts) { + store.add(dom.addDisposableListener(widget, dom.EventType.CONTEXT_MENU, async domEvent => { + const event = new StandardMouseEvent(dom.getWindow(domEvent), domEvent); + dom.EventHelper.stop(domEvent, true); + + this.contextMenuService.showContextMenu({ + contextKeyService: contextMenuOpts.contextKeyService, + getAnchor: () => event, + getActions: () => { + const menu = this.menuService.getMenuActions(contextMenuOpts.contextMenuId, contextMenuOpts.contextKeyService, { arg: contextMenuOpts.contextMenuArg }); + return getFlatContextMenuActions(menu); + }, + }); + })); + } } // Helper function to create and replace image @@ -916,9 +991,9 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge // Summary of number of files changed const innerContainer = this.chatEditingSessionWidgetContainer.querySelector('.chat-editing-session-container.show-file-icons') as HTMLElement ?? dom.append(this.chatEditingSessionWidgetContainer, $('.chat-editing-session-container.show-file-icons')); - const modifiedFiles = new ResourceSet(); + const seenEntries = new ResourceSet(); let entries: IChatCollapsibleListItem[] = chatEditingSession?.entries.get().map((entry) => { - modifiedFiles.add(entry.modifiedURI); + seenEntries.add(entry.modifiedURI); return { reference: entry.modifiedURI, state: entry.state.get(), @@ -926,20 +1001,32 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge }; }) ?? []; for (const attachment of this.attachmentModel.attachments) { - if (attachment.isFile && URI.isUri(attachment.value) && !modifiedFiles.has(attachment.value)) { + if (attachment.isFile && URI.isUri(attachment.value) && !seenEntries.has(attachment.value)) { entries.unshift({ reference: attachment.value, state: WorkingSetEntryState.Attached, kind: 'reference', }); - modifiedFiles.add(attachment.value); + seenEntries.add(attachment.value); } } for (const [file, state] of chatEditingSession.workingSet.entries()) { - if (!modifiedFiles.has(file)) { + if (!seenEntries.has(file)) { entries.unshift({ reference: file, - state: state, + state: state.state, + description: state.description, + kind: 'reference', + }); + seenEntries.add(file); + } + } + // Factor file variables that are part of the user query into the working set + for (const part of chatWidget?.parsedInput.parts ?? []) { + if (part instanceof ChatRequestDynamicVariablePart && part.isFile && URI.isUri(part.data) && !seenEntries.has(part.data)) { + entries.unshift({ + reference: part.data, + state: WorkingSetEntryState.Attached, kind: 'reference', }); } @@ -958,6 +1045,9 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge const overviewText = overviewRegion.querySelector('span') ?? dom.append(overviewRegion, $('span')); overviewText.textContent = localize('chatEditingSession.workingSet', 'Working Set'); + // Record the number of entries that the user wanted to add to the working set + this._attemptedWorkingSetEntriesCount = entries.length; + if (entries.length === 1) { overviewText.textContent += ' ' + localize('chatEditingSession.oneFile', '(1 file)'); } else if (entries.length >= remainingFileEntriesBudget) { @@ -1014,6 +1104,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge } // Working set + const workingSetContainer = innerContainer.querySelector('.chat-editing-session-list') as HTMLElement ?? dom.append(innerContainer, $('.chat-editing-session-list')); if (!this._chatEditList) { this._chatEditList = this._chatEditsListPool.get(); const list = this._chatEditList.object; @@ -1043,7 +1134,8 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge this._onDidFocus.fire(); } }, true)); - dom.append(innerContainer, list.getHTMLElement()); + dom.append(workingSetContainer, list.getHTMLElement()); + dom.append(innerContainer, workingSetContainer); } const maxItemsShown = 6; @@ -1192,8 +1284,7 @@ class ChatSubmitDropdownActionItem extends DropdownWithPrimaryActionViewItem { accessibilityService); const menu = menuService.createMenu(MenuId.ChatExecuteSecondary, contextKeyService); const setActions = () => { - const secondary: IAction[] = []; - createAndFillInActionBarActions(menu, { shouldForwardArgs: true }, secondary); + const secondary = getFlatActionBarActions(menu.getActions({ shouldForwardArgs: true })); const secondaryAgent = chatAgentService.getSecondaryAgent(); if (secondaryAgent) { secondary.forEach(a => { @@ -1260,8 +1351,7 @@ class ModelPickerActionViewItem extends MenuEntryActionViewItem { if (this.label) { const model = this._languageModelsService.lookupLanguageModel(this.currentLanguageModel); if (model) { - this.label.textContent = model.name; - dom.reset(this.label, ...renderLabelWithIcons(`${model.name}$(chevron-down)`)); + dom.reset(this.label, dom.$('span.chat-model-label', undefined, model.name), ...renderLabelWithIcons(`$(chevron-down)`)); } } } diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index cce9be8dd264f..cd22960d85b91 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -43,8 +43,7 @@ import { IThemeService } from '../../../../platform/theme/common/themeService.js import { IWorkbenchIssueService } from '../../issue/common/issue.js'; import { annotateSpecialMarkdownContent } from '../common/annotations.js'; import { ChatAgentLocation, IChatAgentMetadata } from '../common/chatAgents.js'; -import { CONTEXT_CHAT_RESPONSE_SUPPORT_ISSUE_REPORTING, CONTEXT_ITEM_ID, CONTEXT_REQUEST, CONTEXT_RESPONSE, CONTEXT_RESPONSE_DETECTED_AGENT_COMMAND, CONTEXT_RESPONSE_ERROR, CONTEXT_RESPONSE_FILTERED, CONTEXT_RESPONSE_VOTE } from '../common/chatContextKeys.js'; -import { isChatRequestCheckpointed } from '../common/chatEditingService.js'; +import { ChatContextKeys } from '../common/chatContextKeys.js'; import { IChatRequestVariableEntry, IChatTextEditGroup } from '../common/chatModel.js'; import { chatSubcommandLeader } from '../common/chatParserTypes.js'; import { ChatAgentVoteDirection, ChatAgentVoteDownReason, IChatConfirmation, IChatContentReference, IChatFollowup, IChatMarkdownContent, IChatTask, IChatToolInvocation, IChatToolInvocationSerialized, IChatTreeData } from '../common/chatService.js'; @@ -52,7 +51,7 @@ import { IChatCodeCitations, IChatReferences, IChatRendererContent, IChatRequest import { getNWords } from '../common/chatWordCounter.js'; import { CodeBlockModelCollection } from '../common/codeBlockModelCollection.js'; import { MarkUnhelpfulActionId } from './actions/chatTitleActions.js'; -import { ChatTreeItem, GeneratingPhrase, IChatCodeBlockInfo, IChatFileTreeInfo, IChatListItemRendererOptions, IChatWidgetService } from './chat.js'; +import { ChatTreeItem, IChatCodeBlockInfo, IChatFileTreeInfo, IChatListItemRendererOptions, IChatWidgetService } from './chat.js'; import { ChatAgentHover, getChatAgentHoverOptions } from './chatAgentHover.js'; import { ChatAttachmentsContentPart } from './chatContentParts/chatAttachmentsContentPart.js'; import { ChatCodeCitationContentPart } from './chatContentParts/chatCodeCitationContentPart.js'; @@ -89,7 +88,6 @@ interface IChatListItemTemplate { readonly templateDisposables: IDisposable; readonly elementDisposables: DisposableStore; readonly agentHover: ChatAgentHover; - readonly disabledOverlay: HTMLElement; } interface IItemHeightChangeParams { @@ -242,7 +240,6 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer { codeBlocksByResponseId[codeBlockStartIndex + i] = info; - if (info.uri) { - const uri = info.uri; + info.uriPromise.then(uri => { + if (!uri) { + return; + } + this.codeBlocksByEditorUri.set(uri, info); markdownPart.addDisposable(toDisposable(() => { const codeblock = this.codeBlocksByEditorUri.get(uri); @@ -940,7 +940,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer { - const hidden = this.storageService.getBoolean(MoveChatViewContribution.hideMovedChatWelcomeViewStorageKey, StorageScope.APPLICATION, false); - - // If the view is already hidden, then we just want to register keybindings. - if (hidden) { - this.registerKeybindings(); - return; - } - - await this.hideViewIfCopilotIsNotInstalled(); - this.updateContextKey(); - this.registerListeners(); - this.registerKeybindings(); - this.registerCommands(); - this.registerMovedChatWelcomeView(); - this.hideViewIfOldViewIsMovedFromDefaultLocation(); - } - - private markViewToHide(): void { - this.storageService.store(MoveChatViewContribution.hideMovedChatWelcomeViewStorageKey, true, StorageScope.APPLICATION, StorageTarget.USER); - this.updateContextKey(); - } - - private async hideViewIfCopilotIsNotInstalled(): Promise { - const extensions = await this.extensionManagementService.getInstalled(); - const installed = extensions.find(value => ExtensionIdentifier.equals(value.identifier.id, this.productService.gitHubEntitlement?.extensionId)); - if (!installed) { - this.markViewToHide(); - } - } - - private hideViewIfOldViewIsMovedFromDefaultLocation(): void { - // If the chat view is not actually moved to the new view container, then we should hide the welcome view. - const newViewContainer = this.viewDescriptorService.getViewContainerById(CHAT_SIDEBAR_PANEL_ID); - if (!newViewContainer) { - return; - } - - const currentChatViewContainer = this.viewDescriptorService.getViewContainerByViewId(CHAT_VIEW_ID); - if (currentChatViewContainer !== newViewContainer) { - this.markViewToHide(); - return; - } - - // If the chat view is in the new location, but the old view container was in the auxiliary bar anyway, then we should hide the welcome view. - const oldViewContainer = this.viewDescriptorService.getViewContainerById(CHAT_SIDEBAR_OLD_VIEW_PANEL_ID); - if (!oldViewContainer) { - return; - } - - const oldLocation = this.viewDescriptorService.getViewContainerLocation(oldViewContainer); - if (oldLocation === ViewContainerLocation.AuxiliaryBar) { - this.markViewToHide(); - } - } - - private updateContextKey(): void { - const hidden = this.storageService.getBoolean(MoveChatViewContribution.hideMovedChatWelcomeViewStorageKey, StorageScope.APPLICATION, false); - this.showWelcomeViewCtx.set(!hidden); - } - - private registerListeners(): void { - this._register(this.storageService.onDidChangeValue(StorageScope.APPLICATION, MoveChatViewContribution.hideMovedChatWelcomeViewStorageKey, this._store)(() => this.updateContextKey())); - } - - private registerKeybindings(): void { - KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: CHAT_SIDEBAR_OLD_VIEW_PANEL_ID, - weight: KeybindingWeight.WorkbenchContrib, - when: CONTEXT_CHAT_PANEL_PARTICIPANT_REGISTERED, - primary: 0, - handler: accessor => showChatView(accessor.get(IViewsService)) - }); - } - - private registerCommands(): void { - CommandsRegistry.registerCommand({ - id: '_chatMovedViewWelcomeView.ok', - handler: async (accessor: ServicesAccessor) => { - showChatView(accessor.get(IViewsService)); - this.markViewToHide(); - } - }); - - CommandsRegistry.registerCommand({ - id: '_chatMovedViewWelcomeView.restore', - handler: async () => { - const oldViewContainer = this.viewDescriptorService.getViewContainerById(CHAT_SIDEBAR_OLD_VIEW_PANEL_ID); - const newViewContainer = this.viewDescriptorService.getViewContainerById(CHAT_SIDEBAR_PANEL_ID); - if (!oldViewContainer || !newViewContainer) { - this.markViewToHide(); - return; - } - - const oldLocation = this.viewDescriptorService.getViewContainerLocation(oldViewContainer); - const newLocation = this.viewDescriptorService.getViewContainerLocation(newViewContainer); - - if (oldLocation === newLocation || oldLocation === null || newLocation === null) { - this.markViewToHide(); - return; - } - - const viewContainerIds = this.paneCompositePartService.getPaneCompositeIds(oldLocation); - const targetIndex = viewContainerIds.indexOf(oldViewContainer.id); - - this.viewDescriptorService.moveViewContainerToLocation(newViewContainer, oldLocation, targetIndex); - this.viewsService.openViewContainer(newViewContainer.id, true); - - this.markViewToHide(); - } - }); - - CommandsRegistry.registerCommand({ - id: '_chatMovedViewWelcomeView.learnMore', - handler: async (accessor: ServicesAccessor) => { - const openerService = accessor.get(IOpenerService); - openerService.open(URI.parse('https://aka.ms/vscode-secondary-sidebar')); - } - }); - } - - private registerMovedChatWelcomeView(): IDisposable { - // This is a welcome view container intended to show up where the old chat view was positioned to inform - // the user that we have changed the default location and how they can move it back or use the new location. - const title = localize2('chat.viewContainer.movedChat.label', "Chat (Old Location)"); - const icon = Codicon.commentDiscussion; - const viewContainerId = CHAT_SIDEBAR_OLD_VIEW_PANEL_ID; - const viewContainer: ViewContainer = Registry.as(ViewExtensions.ViewContainersRegistry).registerViewContainer({ - id: viewContainerId, - title, - icon, - ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [viewContainerId, { mergeViewWithContainerWhenSingleView: true }]), - storageId: viewContainerId, - hideIfEmpty: true, - order: 100, - }, ViewContainerLocation.Sidebar, { doNotRegisterOpenCommand: true }); - - const viewId = 'workbench.chat.movedView.welcomeView'; - const viewDescriptor: IViewDescriptor = { - id: viewId, - name: title, - order: 1, - canToggleVisibility: false, - canMoveView: false, - when: ContextKeyExpr.and(CONTEXT_CHAT_SHOULD_SHOW_MOVED_VIEW_WELCOME, ContextKeyExpr.or(CONTEXT_CHAT_PANEL_PARTICIPANT_REGISTERED, CONTEXT_CHAT_EXTENSION_INVALID)), - ctorDescriptor: new SyncDescriptor(MovedChatViewPane, [{ id: viewId }]), - }; - - Registry.as(ViewExtensions.ViewsRegistry).registerViews([viewDescriptor], viewContainer); - - - const secondarySideBarLeft = this.configurationService.getValue('workbench.sideBar.location') !== 'left'; - - - let welcomeViewMainMessage = secondarySideBarLeft ? - localize('chatMovedMainMessage1Left', "Chat has been moved to the Secondary Side Bar on the left for a more integrated AI experience in your editor.") : - localize('chatMovedMainMessage1Right', "Chat has been moved to the Secondary Side Bar on the right for a more integrated AI experience in your editor."); - - const chatViewKeybinding = this.keybindingService.lookupKeybinding(CHAT_SIDEBAR_PANEL_ID)?.getLabel(); - const copilotIcon = `$(${this.productService.defaultChatAgent?.icon ?? 'comment-discussion'})`; - let quicklyAccessMessage = undefined; - if (this.hasCommandCenterChat() && chatViewKeybinding) { - quicklyAccessMessage = localize('chatMovedCommandCenterAndKeybind', "You can quickly access Chat via the new Copilot icon ({0}) in the editor title bar or with the keyboard shortcut {1}.", copilotIcon, chatViewKeybinding); - } else if (this.hasCommandCenterChat()) { - quicklyAccessMessage = localize('chatMovedCommandCenter', "You can quickly access Chat via the new Copilot icon ({0}) in the editor title bar.", copilotIcon); - } else if (chatViewKeybinding) { - quicklyAccessMessage = localize('chatMovedKeybind', "You can quickly access Chat with the keyboard shortcut {0}.", chatViewKeybinding); - } - - if (quicklyAccessMessage) { - welcomeViewMainMessage = `${welcomeViewMainMessage}\n\n${quicklyAccessMessage}`; - } - - const okButton = `[${localize('ok', "Got it")}](command:_chatMovedViewWelcomeView.ok)`; - const restoreButton = `[${localize('restore', "Restore Old Location")}](command:_chatMovedViewWelcomeView.restore)`; - - const welcomeViewFooterMessage = localize('chatMovedFooterMessage', "[Learn more](command:_chatMovedViewWelcomeView.learnMore) about the Secondary Side Bar."); - - const viewsRegistry = Registry.as(ViewExtensions.ViewsRegistry); - return viewsRegistry.registerViewWelcomeContent(viewId, { - content: [welcomeViewMainMessage, okButton, restoreButton, welcomeViewFooterMessage].join('\n\n'), - renderSecondaryButtons: true, - when: ContextKeyExpr.and(CONTEXT_CHAT_SHOULD_SHOW_MOVED_VIEW_WELCOME, ContextKeyExpr.or(CONTEXT_CHAT_PANEL_PARTICIPANT_REGISTERED, CONTEXT_CHAT_EXTENSION_INVALID)) - }); - } - - private hasCommandCenterChat(): boolean { - if ( - this.configurationService.getValue('chat.commandCenter.enabled') === false || - this.configurationService.getValue('window.commandCenter') === false - ) { - return false; - } - - return true; - } -} - -registerWorkbenchContribution2(MoveChatViewContribution.ID, MoveChatViewContribution, WorkbenchPhase.BlockStartup); diff --git a/src/vs/workbench/contrib/chat/browser/chatParticipantContributions.ts b/src/vs/workbench/contrib/chat/browser/chatParticipantContributions.ts index 99b4fdd4924a5..5809333e2cda3 100644 --- a/src/vs/workbench/contrib/chat/browser/chatParticipantContributions.ts +++ b/src/vs/workbench/contrib/chat/browser/chatParticipantContributions.ts @@ -25,9 +25,9 @@ import * as extensionsRegistry from '../../../services/extensions/common/extensi import { showExtensionsWithIdsCommandId } from '../../extensions/browser/extensionsActions.js'; import { IExtension, IExtensionsWorkbenchService } from '../../extensions/common/extensions.js'; import { ChatAgentLocation, IChatAgentData, IChatAgentService } from '../common/chatAgents.js'; -import { CONTEXT_CHAT_EDITING_PARTICIPANT_REGISTERED, CONTEXT_CHAT_EXTENSION_INVALID, CONTEXT_CHAT_PANEL_PARTICIPANT_REGISTERED } from '../common/chatContextKeys.js'; +import { ChatContextKeys } from '../common/chatContextKeys.js'; import { IRawChatParticipantContribution } from '../common/chatParticipantContribTypes.js'; -import { CHAT_VIEW_ID } from './chat.js'; +import { ChatViewId } from './chat.js'; import { CHAT_EDITING_SIDEBAR_PANEL_ID, CHAT_SIDEBAR_PANEL_ID, ChatViewPane } from './chatViewPane.js'; const chatParticipantExtensionPoint = extensionsRegistry.ExtensionsRegistry.registerExtensionPoint({ @@ -299,7 +299,7 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution { // Register View. Name must be hardcoded because we want to show it even when the extension fails to load due to an API version incompatibility. const name = 'GitHub Copilot'; const viewDescriptor: IViewDescriptor[] = [{ - id: CHAT_VIEW_ID, + id: ChatViewId, containerIcon: this._viewContainer.icon, containerTitle: this._viewContainer.title.value, singleViewPaneContainerTitle: this._viewContainer.title.value, @@ -308,16 +308,22 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution { canMoveView: true, openCommandActionDescriptor: { id: CHAT_SIDEBAR_PANEL_ID, + title: this._viewContainer.title, + mnemonicTitle: localize({ key: 'miToggleChat', comment: ['&& denotes a mnemonic'] }, "&&Chat"), keybindings: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KeyI, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KeyI } }, - order: 100 + order: 1 }, ctorDescriptor: new SyncDescriptor(ChatViewPane, [{ location: ChatAgentLocation.Panel }]), - when: ContextKeyExpr.or(CONTEXT_CHAT_PANEL_PARTICIPANT_REGISTERED, CONTEXT_CHAT_EXTENSION_INVALID) + when: ContextKeyExpr.or( + ChatContextKeys.panelParticipantRegistered, + ChatContextKeys.extensionInvalid, + ChatContextKeys.setupRunning + ) }]; Registry.as(ViewExtensions.ViewsRegistry).registerViews(viewDescriptor, this._viewContainer); @@ -337,20 +343,32 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution { ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [viewContainerId, { mergeViewWithContainerWhenSingleView: true }]), storageId: viewContainerId, hideIfEmpty: true, - order: 100, - }, ViewContainerLocation.AuxiliaryBar); + order: 101, + }, ViewContainerLocation.AuxiliaryBar, { doNotRegisterOpenCommand: true }); const id = 'workbench.panel.chat.view.edits'; const viewDescriptor: IViewDescriptor[] = [{ - id: id, + id, containerIcon: viewContainer.icon, containerTitle: title.value, singleViewPaneContainerTitle: title.value, name: { value: title.value, original: title.value }, canToggleVisibility: false, canMoveView: true, + openCommandActionDescriptor: { + id: viewContainerId, + title, + mnemonicTitle: localize({ key: 'miToggleEdits', comment: ['&& denotes a mnemonic'] }, "Copilot Ed&&its"), + keybindings: { + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyI, + linux: { + primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyMod.Shift | KeyCode.KeyI + } + }, + order: 2 + }, ctorDescriptor: new SyncDescriptor(ChatViewPane, [{ location: ChatAgentLocation.EditingSession }]), - when: CONTEXT_CHAT_EDITING_PARTICIPANT_REGISTERED + when: ChatContextKeys.editingParticipantRegistered }]; Registry.as(ViewExtensions.ViewsRegistry).registerViews(viewDescriptor, viewContainer); @@ -379,7 +397,7 @@ export class ChatCompatibilityNotifier extends Disposable implements IWorkbenchC // It may be better to have some generic UI for this, for any extension that is incompatible, // but this is only enabled for Copilot Chat now and it needs to be obvious. - const isInvalid = CONTEXT_CHAT_EXTENSION_INVALID.bindTo(contextKeyService); + const isInvalid = ChatContextKeys.extensionInvalid.bindTo(contextKeyService); this._register(Event.runAndSubscribe( extensionsWorkbenchService.onDidChangeExtensionsNotification, () => { @@ -406,9 +424,9 @@ export class ChatCompatibilityNotifier extends Disposable implements IWorkbenchC const commandButton = `[${showExtensionLabel}](command:${showExtensionsWithIdsCommandId}?${encodeURIComponent(JSON.stringify([['GitHub.copilot-chat']]))})`; const versionMessage = `GitHub Copilot Chat version: ${chatExtension.version}`; const viewsRegistry = Registry.as(ViewExtensions.ViewsRegistry); - this._register(viewsRegistry.registerViewWelcomeContent(CHAT_VIEW_ID, { + this._register(viewsRegistry.registerViewWelcomeContent(ChatViewId, { content: [mainMessage, commandButton, versionMessage].join('\n\n'), - when: CONTEXT_CHAT_EXTENSION_INVALID, + when: ChatContextKeys.extensionInvalid, })); } } diff --git a/src/vs/workbench/contrib/chat/browser/chatResponseAccessibleView.ts b/src/vs/workbench/contrib/chat/browser/chatResponseAccessibleView.ts index d6d0ff2be07fd..75f75066e1bde 100644 --- a/src/vs/workbench/contrib/chat/browser/chatResponseAccessibleView.ts +++ b/src/vs/workbench/contrib/chat/browser/chatResponseAccessibleView.ts @@ -10,7 +10,7 @@ import { AccessibleViewProviderId, AccessibleViewType, IAccessibleViewContentPro import { IAccessibleViewImplentation } from '../../../../platform/accessibility/browser/accessibleViewRegistry.js'; import { ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; import { AccessibilityVerbositySettingId } from '../../accessibility/browser/accessibilityConfiguration.js'; -import { CONTEXT_IN_CHAT_SESSION } from '../common/chatContextKeys.js'; +import { ChatContextKeys } from '../common/chatContextKeys.js'; import { isResponseVM } from '../common/chatViewModel.js'; import { ChatTreeItem, IChatWidget, IChatWidgetService } from './chat.js'; @@ -18,7 +18,7 @@ export class ChatResponseAccessibleView implements IAccessibleViewImplentation { readonly priority = 100; readonly name = 'panelChat'; readonly type = AccessibleViewType.View; - readonly when = CONTEXT_IN_CHAT_SESSION; + readonly when = ChatContextKeys.inChatSession; getProvider(accessor: ServicesAccessor) { const widgetService = accessor.get(IChatWidgetService); const widget = widgetService.lastFocusedWidget; diff --git a/src/vs/workbench/contrib/chat/browser/chatSetup.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatSetup.contribution.ts new file mode 100644 index 0000000000000..a4172030d6e93 --- /dev/null +++ b/src/vs/workbench/contrib/chat/browser/chatSetup.contribution.ts @@ -0,0 +1,497 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IWorkbenchContribution, registerWorkbenchContribution2, WorkbenchPhase } from '../../../common/contributions.js'; +import { Disposable, toDisposable } from '../../../../base/common/lifecycle.js'; +import { ContextKeyExpr, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; +import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; +import { AuthenticationSession, IAuthenticationService } from '../../../services/authentication/common/authentication.js'; +import { IProductService } from '../../../../platform/product/common/productService.js'; +import { IExtensionManagementService } from '../../../../platform/extensionManagement/common/extensionManagement.js'; +import { ExtensionIdentifier } from '../../../../platform/extensions/common/extensions.js'; +import { IExtensionService } from '../../../services/extensions/common/extensions.js'; +import { IRequestService, asText } from '../../../../platform/request/common/request.js'; +import { CancellationTokenSource } from '../../../../base/common/cancellation.js'; +import { ChatContextKeys } from '../common/chatContextKeys.js'; +import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; +import { IRequestContext } from '../../../../base/parts/request/common/request.js'; +import { IGitHubEntitlement } from '../../../../base/common/product.js'; +import { timeout } from '../../../../base/common/async.js'; +import { isCancellationError } from '../../../../base/common/errors.js'; +import { ServicesAccessor } from '../../../../editor/browser/editorExtensions.js'; +import { localize, localize2 } from '../../../../nls.js'; +import { Action2, MenuId, registerAction2 } from '../../../../platform/actions/common/actions.js'; +import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; +import { IWorkbenchLayoutService, Parts } from '../../../services/layout/browser/layoutService.js'; +import { IViewsService } from '../../../services/views/common/viewsService.js'; +import { IExtensionsWorkbenchService } from '../../extensions/common/extensions.js'; +import { CHAT_CATEGORY } from './actions/chatActions.js'; +import { showChatView, ChatViewId } from './chat.js'; +import { IChatAgentService } from '../common/chatAgents.js'; +import { Event } from '../../../../base/common/event.js'; +import product from '../../../../platform/product/common/product.js'; +import { Codicon } from '../../../../base/common/codicons.js'; +import { Registry } from '../../../../platform/registry/common/platform.js'; +import { IChatViewsWelcomeContributionRegistry, ChatViewsWelcomeExtensions } from './viewsWelcome/chatViewsWelcome.js'; +import { MarkdownString } from '../../../../base/common/htmlContent.js'; +import { IViewDescriptorService, ViewContainerLocation } from '../../../common/views.js'; + +const defaultChat = { + extensionId: product.defaultChatAgent?.extensionId ?? '', + name: product.defaultChatAgent?.name ?? '', + providerId: product.defaultChatAgent?.providerId ?? '', + providerName: product.defaultChatAgent?.providerName ?? '', + providerScopes: product.defaultChatAgent?.providerScopes ?? [], + icon: Codicon[product.defaultChatAgent?.icon as keyof typeof Codicon ?? 'commentDiscussion'], + documentationUrl: product.defaultChatAgent?.documentationUrl ?? '', + gettingStartedCommand: product.defaultChatAgent?.gettingStartedCommand ?? '', + welcomeTitle: product.defaultChatAgent?.welcomeTitle ?? '', +}; + +type ChatSetupEntitlementEnablementClassification = { + entitled: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Flag indicating if the user is chat setup entitled' }; + trial: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Flag indicating if the user is subscribed to chat trial' }; + owner: 'bpasero'; + comment: 'Reporting if the user is chat setup entitled'; +}; + +type ChatSetupEntitlementEnablementEvent = { + entitled: boolean; + trial: boolean; +}; + +type InstallChatClassification = { + owner: 'bpasero'; + comment: 'Provides insight into chat installation.'; + installResult: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the extension was installed successfully, cancelled or failed to install.' }; + signedIn: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the user did sign in prior to installing the extension.' }; +}; +type InstallChatEvent = { + installResult: 'installed' | 'cancelled' | 'failedInstall' | 'failedNotSignedIn'; + signedIn: boolean; +}; + +class ChatSetupContribution extends Disposable implements IWorkbenchContribution { + + private static readonly CHAT_EXTENSION_INSTALLED_KEY = 'chat.extensionInstalled'; + + private readonly chatSetupSignedInContextKey = ChatContextKeys.ChatSetup.signedIn.bindTo(this.contextKeyService); + private readonly chatSetupEntitledContextKey = ChatContextKeys.ChatSetup.entitled.bindTo(this.contextKeyService); + + private resolvedEntitlement: boolean | undefined = undefined; + + constructor( + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @ITelemetryService private readonly telemetryService: ITelemetryService, + @IAuthenticationService private readonly authenticationService: IAuthenticationService, + @IProductService private readonly productService: IProductService, + @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, + @IExtensionService private readonly extensionService: IExtensionService, + @IRequestService private readonly requestService: IRequestService, + @IStorageService private readonly storageService: IStorageService, + @IInstantiationService private readonly instantiationService: IInstantiationService + ) { + super(); + + const entitlement = this.productService.gitHubEntitlement; + if (!entitlement) { + return; + } + + this.checkExtensionInstallation(entitlement); + + this.registerChatWelcome(); + + this.registerEntitlementListeners(entitlement); + this.registerAuthListeners(entitlement); + } + + private async checkExtensionInstallation(entitlement: IGitHubEntitlement): Promise { + const extensions = await this.extensionManagementService.getInstalled(); + + const installed = extensions.find(value => ExtensionIdentifier.equals(value.identifier.id, entitlement.extensionId)); + this.updateExtensionInstalled(installed ? true : false); + } + + private registerChatWelcome(): void { + + // Action to hide setup + const chatSetupTrigger = this.instantiationService.createInstance(ChatSetupTrigger); + const disableChatSetupActionId = 'workbench.action.chat.disableSetup'; + + registerAction2(class extends Action2 { + + constructor() { + super({ + id: disableChatSetupActionId, + title: localize2('hideChatSetup', "Hide Chat Setup"), + f1: false + }); + } + + override async run(accessor: ServicesAccessor): Promise { + const viewsDescriptorService = accessor.get(IViewDescriptorService); + const layoutService = accessor.get(IWorkbenchLayoutService); + + const location = viewsDescriptorService.getViewLocationById(ChatViewId); + + chatSetupTrigger.update(false); + + if (location === ViewContainerLocation.AuxiliaryBar) { + const activeContainers = viewsDescriptorService.getViewContainersByLocation(location).filter(container => viewsDescriptorService.getViewContainerModel(container).activeViewDescriptors.length > 0); + if (activeContainers.length === 0) { + layoutService.setPartHidden(true, Parts.AUXILIARYBAR_PART); // hide if there are no views in the secondary sidebar + } + } + } + }); + + // Setup: Triggered (signed-out) + Registry.as(ChatViewsWelcomeExtensions.ChatViewsWelcomeRegistry).register({ + title: defaultChat.welcomeTitle, + when: ContextKeyExpr.and( + ChatContextKeys.ChatSetup.triggering, + ChatContextKeys.ChatSetup.signedIn.negate(), + ChatContextKeys.ChatSetup.signingIn.negate(), + ChatContextKeys.ChatSetup.installing.negate(), + ChatContextKeys.extensionInvalid.negate(), + ChatContextKeys.panelParticipantRegistered.negate() + )!, + icon: defaultChat.icon, + content: new MarkdownString(`${localize('setupContent', "{0} is your AI pair programmer that helps you write code faster and smarter.", defaultChat.name)}\n\n[${localize('signInAndSetup', "Sign in to use {0}", defaultChat.name)}](command:${ChatSetupSignInAndInstallChatAction.ID})\n\n[${localize('learnMore', "Learn More")}](${defaultChat.documentationUrl}) | [${localize('hideSetup', "Hide")}](command:${disableChatSetupActionId})`, { isTrusted: true }), + }); + + // Setup: Triggered (signed-in) + Registry.as(ChatViewsWelcomeExtensions.ChatViewsWelcomeRegistry).register({ + title: defaultChat.welcomeTitle, + when: ContextKeyExpr.and( + ChatContextKeys.ChatSetup.triggering, + ChatContextKeys.ChatSetup.signedIn, + ChatContextKeys.ChatSetup.signingIn.negate(), + ChatContextKeys.ChatSetup.installing.negate(), + ChatContextKeys.extensionInvalid.negate(), + ChatContextKeys.panelParticipantRegistered.negate() + )!, + icon: defaultChat.icon, + content: new MarkdownString(`${localize('setupContent', "{0} is your AI pair programmer that helps you write code faster and smarter.", defaultChat.name)}\n\n[${localize('setup', "Install {0}", defaultChat.name)}](command:${ChatSetupInstallAction.ID})\n\n[${localize('learnMore', "Learn More")}](${defaultChat.documentationUrl}) | [${localize('hideSetup', "Hide")}](command:${disableChatSetupActionId})`, { isTrusted: true }), + }); + + // Setup: Signing-in + Registry.as(ChatViewsWelcomeExtensions.ChatViewsWelcomeRegistry).register({ + title: defaultChat.welcomeTitle, + when: ContextKeyExpr.and( + ChatContextKeys.ChatSetup.signingIn, + ChatContextKeys.extensionInvalid.negate(), + ChatContextKeys.panelParticipantRegistered.negate() + )!, + icon: defaultChat.icon, + progress: localize('setupChatSigningIn', "Signing in to {0}...", defaultChat.providerName), + content: new MarkdownString(`\n\n[${localize('learnMore', "Learn More")}](${defaultChat.documentationUrl})`, { isTrusted: true }), + }); + + // Setup: Installing + Registry.as(ChatViewsWelcomeExtensions.ChatViewsWelcomeRegistry).register({ + title: defaultChat.welcomeTitle, + when: ChatContextKeys.ChatSetup.installing, + icon: defaultChat.icon, + progress: localize('setupChatInstalling', "Setting up Chat for you..."), + content: new MarkdownString(`\n\n[${localize('learnMore', "Learn More")}](${defaultChat.documentationUrl})`, { isTrusted: true }), + }); + } + + private registerEntitlementListeners(entitlement: IGitHubEntitlement): void { + this._register(this.extensionService.onDidChangeExtensions(result => { + for (const extension of result.removed) { + if (ExtensionIdentifier.equals(entitlement.extensionId, extension.identifier)) { + this.updateExtensionInstalled(false); + break; + } + } + + for (const extension of result.added) { + if (ExtensionIdentifier.equals(entitlement.extensionId, extension.identifier)) { + this.updateExtensionInstalled(true); + break; + } + } + })); + + this._register(this.authenticationService.onDidChangeSessions(e => { + if (e.providerId === entitlement.providerId) { + if (e.event.added?.length) { + this.resolveEntitlement(entitlement, e.event.added[0]); + } else if (e.event.removed?.length) { + this.chatSetupEntitledContextKey.set(false); + } + } + })); + + this._register(this.authenticationService.onDidRegisterAuthenticationProvider(async e => { + if (e.id === entitlement.providerId) { + this.resolveEntitlement(entitlement, (await this.authenticationService.getSessions(e.id))[0]); + } + })); + } + + private registerAuthListeners(entitlement: IGitHubEntitlement): void { + const hasProviderSessions = async () => { + const sessions = await this.authenticationService.getSessions(entitlement.providerId); + return sessions.length > 0; + }; + + const handleDeclaredAuthProviders = async () => { + if (this.authenticationService.declaredProviders.find(p => p.id === entitlement.providerId)) { + this.chatSetupSignedInContextKey.set(await hasProviderSessions()); + } + }; + this._register(this.authenticationService.onDidChangeDeclaredProviders(handleDeclaredAuthProviders)); + this._register(this.authenticationService.onDidRegisterAuthenticationProvider(handleDeclaredAuthProviders)); + + handleDeclaredAuthProviders(); + + this._register(this.authenticationService.onDidChangeSessions(async ({ providerId }) => { + if (providerId === entitlement.providerId) { + this.chatSetupSignedInContextKey.set(await hasProviderSessions()); + } + })); + } + + private async resolveEntitlement(entitlement: IGitHubEntitlement, session: AuthenticationSession | undefined): Promise { + if (!session) { + return; + } + + const entitled = await this.doResolveEntitlement(entitlement, session); + this.chatSetupEntitledContextKey.set(entitled); + } + + private async doResolveEntitlement(entitlement: IGitHubEntitlement, session: AuthenticationSession): Promise { + if (typeof this.resolvedEntitlement === 'boolean') { + return this.resolvedEntitlement; + } + + const cts = new CancellationTokenSource(); + this._register(toDisposable(() => cts.dispose(true))); + + let context: IRequestContext; + try { + context = await this.requestService.request({ + type: 'GET', + url: entitlement.entitlementUrl, + headers: { + 'Authorization': `Bearer ${session.accessToken}` + } + }, cts.token); + } catch (error) { + return false; + } + + if (context.res.statusCode && context.res.statusCode !== 200) { + return false; + } + + const result = await asText(context); + if (!result) { + return false; + } + + let parsedResult: any; + try { + parsedResult = JSON.parse(result); + } catch (err) { + return false; //ignore + } + + this.resolvedEntitlement = Boolean(parsedResult[entitlement.enablementKey]); + const trial = parsedResult[entitlement.trialKey] === entitlement.trialValue; + this.telemetryService.publicLog2('chatInstallEntitlement', { + entitled: this.resolvedEntitlement, + trial + }); + + return this.resolvedEntitlement; + } + + private updateExtensionInstalled(isExtensionInstalled: boolean): void { + this.storageService.store(ChatSetupContribution.CHAT_EXTENSION_INSTALLED_KEY, isExtensionInstalled, StorageScope.PROFILE, StorageTarget.MACHINE); + } +} + +class ChatSetupTriggerAction extends Action2 { + + static readonly ID = 'workbench.action.chat.triggerSetup'; + static readonly TITLE = localize2('triggerChatSetup', "Trigger Chat Setup"); + + constructor() { + super({ + id: ChatSetupTriggerAction.ID, + title: ChatSetupTriggerAction.TITLE, + f1: false + }); + } + + override async run(accessor: ServicesAccessor): Promise { + const viewsService = accessor.get(IViewsService); + const instantiationService = accessor.get(IInstantiationService); + + instantiationService.createInstance(ChatSetupTrigger).update(true); + + showChatView(viewsService); + } +} + +class ChatSetupTrigger { + + private static readonly CHAT_SETUP_TRIGGERD = 'chat.setupTriggered'; + + private readonly chatSetupTriggeringContext = ChatContextKeys.ChatSetup.triggering.bindTo(this.contextKeyService); + + constructor( + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IStorageService private readonly storageService: IStorageService + ) { + if (this.storageService.getBoolean(ChatSetupTrigger.CHAT_SETUP_TRIGGERD, StorageScope.PROFILE)) { + this.update(true); + } + } + + update(enabled: boolean): void { + if (enabled) { + this.storageService.store(ChatSetupTrigger.CHAT_SETUP_TRIGGERD, enabled, StorageScope.PROFILE, StorageTarget.MACHINE); + this.storageService.remove('chat.welcomeMessageContent.panel', StorageScope.APPLICATION); // fixes flicker issues with cached welcome from previous install + } else { + this.storageService.remove(ChatSetupTrigger.CHAT_SETUP_TRIGGERD, StorageScope.PROFILE); + } + + this.chatSetupTriggeringContext.set(enabled); + } +} + +class ChatSetupInstallAction extends Action2 { + + static readonly ID = 'workbench.action.chat.install'; + static readonly TITLE = localize2('installChat', "Install {0}", defaultChat.name); + + constructor() { + super({ + id: ChatSetupInstallAction.ID, + title: ChatSetupInstallAction.TITLE, + category: CHAT_CATEGORY, + menu: { + id: MenuId.ChatCommandCenter, + group: 'a_open', + order: 0, + when: ContextKeyExpr.and( + ChatContextKeys.panelParticipantRegistered.negate(), + ContextKeyExpr.or( + ChatContextKeys.ChatSetup.entitled, + ChatContextKeys.ChatSetup.signedIn + ) + ) + } + }); + } + + override run(accessor: ServicesAccessor): Promise { + return ChatSetupInstallAction.install(accessor, false); + } + + static async install(accessor: ServicesAccessor, signedIn: boolean) { + const extensionsWorkbenchService = accessor.get(IExtensionsWorkbenchService); + const productService = accessor.get(IProductService); + const telemetryService = accessor.get(ITelemetryService); + const contextKeyService = accessor.get(IContextKeyService); + const viewsService = accessor.get(IViewsService); + const chatAgentService = accessor.get(IChatAgentService); + + const setupInstallingContextKey = ChatContextKeys.ChatSetup.installing.bindTo(contextKeyService); + + let installResult: 'installed' | 'cancelled' | 'failedInstall'; + try { + setupInstallingContextKey.set(true); + showChatView(viewsService); + + await extensionsWorkbenchService.install(defaultChat.extensionId, { + enable: true, + isMachineScoped: false, + installPreReleaseVersion: productService.quality !== 'stable' + }, ChatViewId); + + installResult = 'installed'; + } catch (error) { + installResult = isCancellationError(error) ? 'cancelled' : 'failedInstall'; + } finally { + Promise.race([ + timeout(2000), // helps prevent flicker with sign-in welcome view + Event.toPromise(chatAgentService.onDidChangeAgents) // https://github.com/microsoft/vscode-copilot/issues/9274 + ]).finally(() => setupInstallingContextKey.reset()); + } + + telemetryService.publicLog2('commandCenter.chatInstall', { installResult, signedIn }); + } +} + +class ChatSetupSignInAndInstallChatAction extends Action2 { + + static readonly ID = 'workbench.action.chat.signInAndInstall'; + static readonly TITLE = localize2('signInAndInstallChat', "Sign in to use {0}", defaultChat.name); + + constructor() { + super({ + id: ChatSetupSignInAndInstallChatAction.ID, + title: ChatSetupSignInAndInstallChatAction.TITLE, + category: CHAT_CATEGORY, + menu: { + id: MenuId.ChatCommandCenter, + group: 'a_open', + order: 0, + when: ContextKeyExpr.and( + ChatContextKeys.panelParticipantRegistered.negate(), + ChatContextKeys.ChatSetup.entitled.negate(), + ChatContextKeys.ChatSetup.signedIn.negate() + ) + } + }); + } + + override async run(accessor: ServicesAccessor): Promise { + const authenticationService = accessor.get(IAuthenticationService); + const instantiationService = accessor.get(IInstantiationService); + const telemetryService = accessor.get(ITelemetryService); + const contextKeyService = accessor.get(IContextKeyService); + const viewsService = accessor.get(IViewsService); + const layoutService = accessor.get(IWorkbenchLayoutService); + + const hideSecondarySidebar = !layoutService.isVisible(Parts.AUXILIARYBAR_PART); + + const setupSigningInContextKey = ChatContextKeys.ChatSetup.signingIn.bindTo(contextKeyService); + + let session: AuthenticationSession | undefined; + try { + setupSigningInContextKey.set(true); + showChatView(viewsService); + session = await authenticationService.createSession(defaultChat.providerId, defaultChat.providerScopes); + } catch (error) { + // noop + } finally { + setupSigningInContextKey.reset(); + } + + if (session) { + instantiationService.invokeFunction(accessor => ChatSetupInstallAction.install(accessor, true)); + } else { + if (hideSecondarySidebar) { + layoutService.setPartHidden(true, Parts.AUXILIARYBAR_PART); + } + telemetryService.publicLog2('commandCenter.chatInstall', { installResult: 'failedNotSignedIn', signedIn: false }); + } + } +} + +registerAction2(ChatSetupTriggerAction); +registerAction2(ChatSetupInstallAction); +registerAction2(ChatSetupSignInAndInstallChatAction); + +registerWorkbenchContribution2('workbench.chat.setup', ChatSetupContribution, WorkbenchPhase.BlockRestore); diff --git a/src/vs/workbench/contrib/chat/browser/chatVariables.ts b/src/vs/workbench/contrib/chat/browser/chatVariables.ts index a340a3446e1b7..4ab91fa76d76b 100644 --- a/src/vs/workbench/contrib/chat/browser/chatVariables.ts +++ b/src/vs/workbench/contrib/chat/browser/chatVariables.ts @@ -17,7 +17,7 @@ import { IChatModel, IChatRequestVariableData, IChatRequestVariableEntry } from import { ChatRequestDynamicVariablePart, ChatRequestToolPart, ChatRequestVariablePart, IParsedChatRequest } from '../common/chatParserTypes.js'; import { IChatContentReference } from '../common/chatService.js'; import { IChatRequestVariableValue, IChatVariableData, IChatVariableResolver, IChatVariableResolverProgress, IChatVariablesService, IDynamicVariable } from '../common/chatVariables.js'; -import { IChatWidgetService, showChatView } from './chat.js'; +import { IChatWidgetService, showChatView, showEditsView } from './chat.js'; import { ChatDynamicVariableModel } from './contrib/chatDynamicVariables.js'; interface IChatData { @@ -161,11 +161,13 @@ export class ChatVariablesService implements IChatVariablesService { } async attachContext(name: string, value: string | URI | Location, location: ChatAgentLocation) { - if (location !== ChatAgentLocation.Panel) { + if (location !== ChatAgentLocation.Panel && location !== ChatAgentLocation.EditingSession) { return; } - const widget = this.chatWidgetService.lastFocusedWidget ?? await showChatView(this.viewsService); + const widget = location === ChatAgentLocation.EditingSession + ? await showEditsView(this.viewsService) + : (this.chatWidgetService.lastFocusedWidget ?? await showChatView(this.viewsService)); if (!widget || !widget.viewModel) { return; } diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index c3da00e0968ac..3cbafbdb38d64 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -29,12 +29,12 @@ import { ServiceCollection } from '../../../../platform/instantiation/common/ser import { WorkbenchObjectTree } from '../../../../platform/list/browser/listService.js'; import { ILogService } from '../../../../platform/log/common/log.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; +import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; import { buttonSecondaryBackground, buttonSecondaryForeground, buttonSecondaryHoverBackground } from '../../../../platform/theme/common/colorRegistry.js'; import { asCssVariable } from '../../../../platform/theme/common/colorUtils.js'; import { IThemeService } from '../../../../platform/theme/common/themeService.js'; -import { TerminalChatController } from '../../terminal/terminalContribChatExports.js'; import { ChatAgentLocation, IChatAgentCommand, IChatAgentData, IChatAgentService, IChatWelcomeMessageContent, isChatWelcomeMessageContent } from '../common/chatAgents.js'; -import { CONTEXT_CHAT_INPUT_HAS_AGENT, CONTEXT_CHAT_LOCATION, CONTEXT_CHAT_REQUEST_IN_PROGRESS, CONTEXT_IN_CHAT_SESSION, CONTEXT_IN_QUICK_CHAT, CONTEXT_LAST_ITEM_ID, CONTEXT_RESPONSE_FILTERED } from '../common/chatContextKeys.js'; +import { ChatContextKeys } from '../common/chatContextKeys.js'; import { ChatEditingSessionState, IChatEditingService, IChatEditingSession } from '../common/chatEditingService.js'; import { IChatModel, IChatRequestVariableEntry, IChatResponseModel } from '../common/chatModel.js'; import { ChatRequestAgentPart, IParsedChatRequest, chatAgentLeader, chatSubcommandLeader, formatChatQuestion } from '../common/chatParserTypes.js'; @@ -236,6 +236,7 @@ export class ChatWidget extends Disposable implements IChatWidget { @IChatSlashCommandService private readonly chatSlashCommandService: IChatSlashCommandService, @IChatEditingService private readonly chatEditingService: IChatEditingService, @IStorageService private readonly storageService: IStorageService, + @ITelemetryService private readonly telemetryService: ITelemetryService, ) { super(); @@ -247,11 +248,11 @@ export class ChatWidget extends Disposable implements IChatWidget { this._location = { location }; } - CONTEXT_IN_CHAT_SESSION.bindTo(contextKeyService).set(true); - CONTEXT_CHAT_LOCATION.bindTo(contextKeyService).set(this._location.location); - CONTEXT_IN_QUICK_CHAT.bindTo(contextKeyService).set(isQuickChat(this)); - this.agentInInput = CONTEXT_CHAT_INPUT_HAS_AGENT.bindTo(contextKeyService); - this.requestInProgress = CONTEXT_CHAT_REQUEST_IN_PROGRESS.bindTo(contextKeyService); + ChatContextKeys.inChatSession.bindTo(contextKeyService).set(true); + ChatContextKeys.location.bindTo(contextKeyService).set(this._location.location); + ChatContextKeys.inQuickChat.bindTo(contextKeyService).set(isQuickChat(this)); + this.agentInInput = ChatContextKeys.inputHasAgent.bindTo(contextKeyService); + this.requestInProgress = ChatContextKeys.requestInProgress.bindTo(contextKeyService); this._codeBlockModelCollection = this._register(instantiationService.createInstance(CodeBlockModelCollection)); @@ -273,6 +274,15 @@ export class ChatWidget extends Disposable implements IChatWidget { chatEditingSessionDisposables.clear(); this.renderChatEditingSessionState(null); })); + chatEditingSessionDisposables.add(this.onDidChangeParsedInput(() => { + this.renderChatEditingSessionState(session); + })); + chatEditingSessionDisposables.add(this.inputEditor.onDidChangeModelContent(() => { + if (this.getInput() === '') { + this.refreshParsedInput(); + this.renderChatEditingSessionState(session); + } + })); this.renderChatEditingSessionState(session); })); @@ -461,6 +471,14 @@ export class ChatWidget extends Disposable implements IChatWidget { return this.inputPart.hasFocus(); } + refreshParsedInput() { + if (!this.viewModel) { + return; + } + this.parsedChatRequest = this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(this.viewModel.sessionId, this.getInput(), this.location, { selectedAgent: this._lastSelectedAgent }); + this._onDidChangeParsedInput.fire(); + } + getSibling(item: ChatTreeItem, type: 'next' | 'previous'): ChatTreeItem | undefined { if (!isResponseVM(item)) { return; @@ -506,8 +524,6 @@ export class ChatWidget extends Disposable implements IChatWidget { this.tree.setChildren(null, treeItems, { diffIdentityProvider: { getId: (element) => { - const requestId = isRequestVM(element) ? element.id : element.requestId; - const checkpointedRequestId = this._viewModel?.model.checkpoint?.id; return element.dataId + // Ensure re-rendering an element once slash commands are loaded, so the colorization can be applied. `${(isRequestVM(element)) /* && !!this.lastSlashCommands ? '_scLoaded' : '' */}` + @@ -516,12 +532,8 @@ export class ChatWidget extends Disposable implements IChatWidget { `${isResponseVM(element) && element.renderData ? `_${this.visibleChangeCount}` : ''}` + // Re-render once content references are loaded (isResponseVM(element) ? `_${element.contentReferences.length}` : '') + - // Re-render if element becomes enabled/disabled due to checkpointing - `_${element.isDisabled ? '1' : '0'}` + // Re-render if element becomes hidden due to undo/redo `_${element.isHidden ? '1' : '0'}` + - // Re-render if element checkpoint state changed - `_${requestId === checkpointedRequestId ? '1' : '0'}` + // Rerender request if we got new content references in the response // since this may change how we render the corresponding attachments in the request (isRequestVM(element) && element.contentReferences ? `_${element.contentReferences?.length}` : ''); @@ -535,7 +547,7 @@ export class ChatWidget extends Disposable implements IChatWidget { const lastItem = treeItems[treeItems.length - 1]?.element; if (lastItem) { - CONTEXT_LAST_ITEM_ID.bindTo(this.contextKeyService).set([lastItem.id]); + ChatContextKeys.lastItemId.bindTo(this.contextKeyService).set([lastItem.id]); } if (lastItem && isResponseVM(lastItem) && lastItem.isComplete) { this.renderFollowups(lastItem.replyFollowups, lastItem); @@ -605,7 +617,7 @@ export class ChatWidget extends Disposable implements IChatWidget { } private createList(listContainer: HTMLElement, options: IChatListItemRendererOptions): void { - const scopedInstantiationService = this._register(this._register(this.instantiationService.createChild(new ServiceCollection([IContextKeyService, this.contextKeyService])))); + const scopedInstantiationService = this._register(this.instantiationService.createChild(new ServiceCollection([IContextKeyService, this.contextKeyService]))); const delegate = scopedInstantiationService.createInstance(ChatListDelegate, this.viewOptions.defaultElementHeight ?? 200); const rendererDelegate: IChatRendererDelegate = { getListLength: () => this.tree.getNode(null).visibleChildrenCount, @@ -702,7 +714,7 @@ export class ChatWidget extends Disposable implements IChatWidget { const selected = e.element; const scopedContextKeyService = this.contextKeyService.createOverlay([ - [CONTEXT_RESPONSE_FILTERED.key, isResponseVM(selected) && !!selected.errorDetails?.responseIsFiltered] + [ChatContextKeys.responseIsFiltered.key, isResponseVM(selected) && !!selected.errorDetails?.responseIsFiltered] ]); this.contextMenuService.showContextMenu({ menuId: MenuId.ChatContext, @@ -764,6 +776,7 @@ export class ChatWidget extends Disposable implements IChatWidget { c.setInputState(contribState); } }); + this.refreshParsedInput(); })); this._register(this.inputPart.onDidFocus(() => this._onDidFocus.fire())); this._register(this.inputPart.onDidAcceptFollowup(e => { @@ -876,6 +889,7 @@ export class ChatWidget extends Disposable implements IChatWidget { c.setInputState(viewState.inputState?.[c.id]); } }); + this.refreshParsedInput(); this.viewModelDisposables.add(model.onDidChange((e) => { if (e.kind === 'setAgent') { this._onDidChangeAgent.fire({ agent: e.agent, slashCommand: e.command }); @@ -928,6 +942,7 @@ export class ChatWidget extends Disposable implements IChatWidget { setInput(value = ''): void { this.inputPart.setValue(value, false); + this.refreshParsedInput(); } getInput(): string { @@ -973,7 +988,7 @@ export class ChatWidget extends Disposable implements IChatWidget { const requests = this.viewModel.model.getRequests(); for (let i = requests.length - 1; i >= 0; i -= 1) { const request = requests[i]; - if (request.isDisabled || request.isHidden) { + if (request.isHidden) { this.chatService.removeRequest(this.viewModel.sessionId, request.id); } } @@ -997,6 +1012,14 @@ export class ChatWidget extends Disposable implements IChatWidget { } } + const currentEditingSession = this.chatEditingService.currentEditingSessionObs.get(); + for (const file of uniqueWorkingSetEntries) { + // Make sure that any files that we sent are part of the working set + // but do not permanently add file variables from previous requests to the working set + // since the user may subsequently edit the chat history + currentEditingSession?.addFileToWorkingSet(file); + } + // Collect file variables from previous requests before sending the request const previousRequests = this.viewModel.model.getRequests(); for (const request of previousRequests) { @@ -1012,12 +1035,19 @@ export class ChatWidget extends Disposable implements IChatWidget { } } workingSet = [...uniqueWorkingSetEntries.values()]; - const currentEditingSession = this.chatEditingService.currentEditingSessionObs.get(); - for (const file of workingSet) { - // Make sure that any files that we sent are part of the working set - currentEditingSession?.addFileToWorkingSet(file); - } attachedContext = editingSessionAttachedContext; + + type ChatEditingWorkingSetClassification = { + owner: 'joyceerhl'; + comment: 'Information about the working set size in a chat editing request'; + originalSize: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The number of files that the user tried to attach in their editing request.' }; + actualSize: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The number of files that were actually sent in their editing request.' }; + }; + type ChatEditingWorkingSetEvent = { + originalSize: number; + actualSize: number; + }; + this.telemetryService.publicLog2('chatEditing/workingSetSize', { originalSize: this.inputPart.attemptedWorkingSetEntriesCount, actualSize: uniqueWorkingSetEntries.size }); } const result = await this.chatService.sendRequest(this.viewModel.sessionId, input, { @@ -1037,8 +1067,6 @@ export class ChatWidget extends Disposable implements IChatWidget { const responses = this.viewModel?.getItems().filter(isResponseVM); const lastResponse = responses?.[responses.length - 1]; this.chatAccessibilityService.acceptResponse(lastResponse, requestId, options?.isVoiceInput); - // Keep the checkpoint toggled on until the response is complete to help the user keep their place in the chat history - this.viewModel?.model.setCheckpoint(undefined); if (lastResponse?.result?.nextQuestion) { const { prompt, participant, command } = lastResponse.result.nextQuestion; const question = formatChatQuestion(this.chatAgentService, this.location, prompt, participant, command); @@ -1102,7 +1130,7 @@ export class ChatWidget extends Disposable implements IChatWidget { this.tree.getHTMLElement().style.height = `${listHeight}px`; // Push the welcome message down so it doesn't change position when followups appear - const followupsOffset = Math.max(100 - this.inputPart.followupsHeight, 0); + const followupsOffset = this.viewOptions.renderFollowups ? Math.max(100 - this.inputPart.followupsHeight, 0) : 0; this.welcomeMessageContainer.style.height = `${listHeight - followupsOffset}px`; this.welcomeMessageContainer.style.paddingBottom = `${followupsOffset}px`; this.renderer.layout(width); @@ -1251,10 +1279,10 @@ export class ChatWidgetService extends Disposable implements IChatWidgetService readonly onDidAddWidget: Event = this._onDidAddWidget.event; get lastFocusedWidget(): IChatWidget | undefined { - return TerminalChatController.activeChatController?.chatWidget ?? this._lastFocusedWidget; + return this._lastFocusedWidget; } - getAllWidgets(location: ChatAgentLocation): ReadonlyArray { + getWidgetsByLocations(location: ChatAgentLocation): ReadonlyArray { return this._widgets.filter(w => w.location === location); } @@ -1262,10 +1290,6 @@ export class ChatWidgetService extends Disposable implements IChatWidgetService return this._widgets.find(w => isEqual(w.inputUri, uri)); } - getWidgetByLocation(location: ChatAgentLocation): ChatWidget[] { - return this._widgets.filter(w => w.location === location); - } - getWidgetBySessionId(sessionId: string): ChatWidget | undefined { return this._widgets.find(w => w.viewModel?.sessionId === sessionId); } diff --git a/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts b/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts index 88db10091c076..d1bb9d8889516 100644 --- a/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts +++ b/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts @@ -11,13 +11,12 @@ import { Button } from '../../../../base/browser/ui/button/button.js'; import { CancellationToken } from '../../../../base/common/cancellation.js'; import { Codicon } from '../../../../base/common/codicons.js'; import { Emitter, Event } from '../../../../base/common/event.js'; -import { combinedDisposable, Disposable, DisposableStore, MutableDisposable } from '../../../../base/common/lifecycle.js'; +import { combinedDisposable, Disposable, MutableDisposable } from '../../../../base/common/lifecycle.js'; import { Schemas } from '../../../../base/common/network.js'; import { isEqual } from '../../../../base/common/resources.js'; import { assertType } from '../../../../base/common/types.js'; import { URI, UriComponents } from '../../../../base/common/uri.js'; import { IEditorConstructionOptions } from '../../../../editor/browser/config/editorConfiguration.js'; -import { TabFocus } from '../../../../editor/browser/config/tabFocus.js'; import { IDiffEditor } from '../../../../editor/browser/editorBrowser.js'; import { EditorExtensionsRegistry } from '../../../../editor/browser/editorExtensions.js'; import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js'; @@ -31,7 +30,7 @@ import { EndOfLinePreference, ITextModel } from '../../../../editor/common/model import { TextModelText } from '../../../../editor/common/model/textModelText.js'; import { IModelService } from '../../../../editor/common/services/model.js'; import { DefaultModelSHA1Computer } from '../../../../editor/common/services/modelService.js'; -import { IResolvedTextEditorModel, ITextModelContentProvider, ITextModelService } from '../../../../editor/common/services/resolverService.js'; +import { ITextModelContentProvider, ITextModelService } from '../../../../editor/common/services/resolverService.js'; import { BracketMatchingController } from '../../../../editor/contrib/bracketMatching/browser/bracketMatching.js'; import { ColorDetector } from '../../../../editor/contrib/colorPicker/browser/colorDetector.js'; import { ContextMenuController } from '../../../../editor/contrib/contextmenu/browser/contextmenu.js'; @@ -63,7 +62,7 @@ import { MenuPreventer } from '../../codeEditor/browser/menuPreventer.js'; import { SelectionClipboardContributionID } from '../../codeEditor/browser/selectionClipboard.js'; import { getSimpleEditorOptions } from '../../codeEditor/browser/simpleEditorOptions.js'; import { IMarkdownVulnerability } from '../common/annotations.js'; -import { CONTEXT_CHAT_EDIT_APPLIED } from '../common/chatContextKeys.js'; +import { ChatContextKeys } from '../common/chatContextKeys.js'; import { IChatResponseModel, IChatTextEditGroup } from '../common/chatModel.js'; import { IChatResponseViewModel, isResponseVM } from '../common/chatViewModel.js'; import { ChatTreeItem } from './chat.js'; @@ -76,7 +75,7 @@ export interface ICodeBlockData { readonly codeBlockIndex: number; readonly element: unknown; - readonly textModel: Promise; + readonly textModel: Promise; readonly languageId: string; readonly codemapperUri?: URI; @@ -151,7 +150,6 @@ export class CodeBlockPart extends Disposable { private currentCodeBlockData: ICodeBlockData | undefined; private currentScrollWidth = 0; - private readonly disposableStore = this._register(new DisposableStore()); private isDisposed = false; private resourceContextKey: ResourceContextKey; @@ -364,7 +362,7 @@ export class CodeBlockPart extends Disposable { return this.editor.getContentHeight(); } - async render(data: ICodeBlockData, width: number, editable: boolean | undefined) { + async render(data: ICodeBlockData, width: number) { this.currentCodeBlockData = data; if (data.parentContextKeyService) { this.contextKeyService.updateParent(data.parentContextKeyService); @@ -382,12 +380,7 @@ export class CodeBlockPart extends Disposable { } this.layout(width); - if (editable) { - this.disposableStore.clear(); - this.disposableStore.add(this.editor.onDidFocusEditorWidget(() => TabFocus.setTabFocusMode(true))); - this.disposableStore.add(this.editor.onDidBlurEditorWidget(() => TabFocus.setTabFocusMode(false))); - } - this.editor.updateOptions({ ariaLabel: localize('chat.codeBlockLabel', "Code block {0}", data.codeBlockIndex + 1), readOnly: !editable }); + this.editor.updateOptions({ ariaLabel: localize('chat.codeBlockLabel', "Code block {0}", data.codeBlockIndex + 1) }); if (data.hideToolbar) { dom.hide(this.toolbar.getElement()); @@ -416,7 +409,7 @@ export class CodeBlockPart extends Disposable { } private async updateEditor(data: ICodeBlockData): Promise { - const textModel = (await data.textModel).textEditorModel; + const textModel = await data.textModel; this.editor.setModel(textModel); if (data.range) { this.editor.setSelection(data.range); @@ -748,7 +741,7 @@ export class CodeCompareBlockPart extends Disposable { const isEditApplied = Boolean(data.edit.state?.applied ?? 0); - CONTEXT_CHAT_EDIT_APPLIED.bindTo(this.contextKeyService).set(isEditApplied); + ChatContextKeys.editApplied.bindTo(this.contextKeyService).set(isEditApplied); this.element.classList.toggle('no-diff', isEditApplied); diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables.ts index 29977605ad323..3c3c64d522232 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables.ts @@ -57,6 +57,7 @@ export class ChatDynamicVariableModel extends Disposable implements IChatWidgetC range: rangeToDelete, text: '', }]); + this.widget.refreshParsedInput(); } return null; } else if (Range.compareRangesUsingStarts(ref.range, c.range) > 0) { @@ -96,6 +97,7 @@ export class ChatDynamicVariableModel extends Disposable implements IChatWidgetC addReference(ref: IDynamicVariable): void { this._variables.push(ref); this.updateDecorations(); + this.widget.refreshParsedInput(); } private updateDecorations(): void { diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatImplicitContext.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatImplicitContext.ts index 9a1810ffa1a20..ed33aec41d9c9 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatImplicitContext.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatImplicitContext.ts @@ -3,38 +3,49 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { CancellationTokenSource } from '../../../../../base/common/cancellation.js'; import { Emitter, Event } from '../../../../../base/common/event.js'; -import { Disposable, DisposableStore } from '../../../../../base/common/lifecycle.js'; +import { Disposable, DisposableStore, MutableDisposable } from '../../../../../base/common/lifecycle.js'; import { basename } from '../../../../../base/common/resources.js'; import { URI } from '../../../../../base/common/uri.js'; +import { ICodeEditor, isCodeEditor, isDiffEditor } from '../../../../../editor/browser/editorBrowser.js'; import { ICodeEditorService } from '../../../../../editor/browser/services/codeEditorService.js'; import { Location } from '../../../../../editor/common/languages.js'; import { IWorkbenchContribution } from '../../../../common/contributions.js'; +import { EditorsOrder } from '../../../../common/editor.js'; import { IEditorService } from '../../../../services/editor/common/editorService.js'; import { ChatAgentLocation } from '../../common/chatAgents.js'; import { IBaseChatRequestVariableEntry, IChatRequestImplicitVariableEntry } from '../../common/chatModel.js'; +import { ILanguageModelIgnoredFilesService } from '../../common/ignoredFiles.js'; import { IChatWidget, IChatWidgetService } from '../chat.js'; export class ChatImplicitContextContribution extends Disposable implements IWorkbenchContribution { static readonly ID = 'chat.implicitContext'; + private readonly _currentCancelTokenSource = this._register(new MutableDisposable()); + constructor( @ICodeEditorService private readonly codeEditorService: ICodeEditorService, - @IEditorService editorService: IEditorService, + @IEditorService private readonly editorService: IEditorService, @IChatWidgetService private readonly chatWidgetService: IChatWidgetService, + @ILanguageModelIgnoredFilesService private readonly ignoredFilesService: ILanguageModelIgnoredFilesService, ) { super(); const activeEditorDisposables = this._register(new DisposableStore()); this._register(Event.runAndSubscribe( - editorService.onDidActiveEditorChange, + editorService.onDidVisibleEditorsChange, (() => { activeEditorDisposables.clear(); - const codeEditor = codeEditorService.getActiveCodeEditor(); + const codeEditor = this.findActiveCodeEditor(); if (codeEditor) { - activeEditorDisposables.add(codeEditor.onDidChangeModel(() => this.updateImplicitContext())); - activeEditorDisposables.add(Event.debounce(codeEditor.onDidChangeCursorSelection, () => undefined, 500)(() => this.updateImplicitContext())); - activeEditorDisposables.add(Event.debounce(codeEditor.onDidScrollChange, () => undefined, 500)(() => this.updateImplicitContext())); + activeEditorDisposables.add(Event.debounce( + Event.any( + codeEditor.onDidChangeModel, + codeEditor.onDidChangeCursorSelection, + codeEditor.onDidScrollChange), + () => undefined, + 500)(() => this.updateImplicitContext())); } this.updateImplicitContext(); @@ -42,8 +53,35 @@ export class ChatImplicitContextContribution extends Disposable implements IWork this._register(chatWidgetService.onDidAddWidget(widget => this.updateImplicitContext(widget))); } - private updateImplicitContext(updateWidget?: IChatWidget): void { + private findActiveCodeEditor(): ICodeEditor | undefined { const codeEditor = this.codeEditorService.getActiveCodeEditor(); + if (codeEditor) { + const model = codeEditor.getModel(); + if (model) { + return codeEditor; + } + } + for (const codeOrDiffEditor of this.editorService.getVisibleTextEditorControls(EditorsOrder.MOST_RECENTLY_ACTIVE)) { + let codeEditor: ICodeEditor; + if (isDiffEditor(codeOrDiffEditor)) { + codeEditor = codeOrDiffEditor.getModifiedEditor(); + } else if (isCodeEditor(codeOrDiffEditor)) { + codeEditor = codeOrDiffEditor; + } else { + continue; + } + + const model = codeEditor.getModel(); + if (model) { + return codeEditor; + } + } + return undefined; + } + + private async updateImplicitContext(updateWidget?: IChatWidget): Promise { + const cancelTokenSource = this._currentCancelTokenSource.value = new CancellationTokenSource(); + const codeEditor = this.findActiveCodeEditor(); const model = codeEditor?.getModel(); const selection = codeEditor?.getSelection(); let newValue: Location | URI | undefined; @@ -68,7 +106,16 @@ export class ChatImplicitContextContribution extends Disposable implements IWork } } - const widgets = updateWidget ? [updateWidget] : this.chatWidgetService.getAllWidgets(ChatAgentLocation.Panel); + const uri = newValue instanceof URI ? newValue : newValue?.uri; + if (uri && await this.ignoredFilesService.fileIsIgnored(uri, cancelTokenSource.token)) { + newValue = undefined; + } + + if (cancelTokenSource.token.isCancellationRequested) { + return; + } + + const widgets = updateWidget ? [updateWidget] : [...this.chatWidgetService.getWidgetsByLocations(ChatAgentLocation.Panel), ...this.chatWidgetService.getWidgetsByLocations(ChatAgentLocation.Editor)]; for (const widget of widgets) { if (widget.input.implicitContext) { widget.input.implicitContext.setValue(newValue, isSelection); diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts index 426a4b67f2dc5..917561dfaee3b 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { raceTimeout } from '../../../../../base/common/async.js'; import { CancellationToken } from '../../../../../base/common/cancellation.js'; import { isPatternInWord } from '../../../../../base/common/filters.js'; import { Disposable } from '../../../../../base/common/lifecycle.js'; @@ -29,11 +30,12 @@ import { LifecyclePhase } from '../../../../services/lifecycle/common/lifecycle. import { QueryBuilder } from '../../../../services/search/common/queryBuilder.js'; import { ISearchService } from '../../../../services/search/common/search.js'; import { ChatAgentLocation, IChatAgentData, IChatAgentNameService, IChatAgentService, getFullyQualifiedId } from '../../common/chatAgents.js'; +import { IChatEditingService } from '../../common/chatEditingService.js'; import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestTextPart, ChatRequestToolPart, ChatRequestVariablePart, chatAgentLeader, chatSubcommandLeader, chatVariableLeader } from '../../common/chatParserTypes.js'; import { IChatSlashCommandService } from '../../common/chatSlashCommands.js'; import { IChatVariablesService, IDynamicVariable } from '../../common/chatVariables.js'; import { ILanguageModelToolsService } from '../../common/languageModelToolsService.js'; -import { SubmitAction } from '../actions/chatExecuteActions.js'; +import { ChatEditingSessionSubmitAction, ChatSubmitAction } from '../actions/chatExecuteActions.js'; import { IChatWidget, IChatWidgetService } from '../chat.js'; import { ChatInputPart } from '../chatInputPart.js'; import { ChatDynamicVariableModel, SelectAndInsertFileAction } from './chatDynamicVariables.js'; @@ -60,6 +62,11 @@ class SlashCommandCompletions extends Disposable { return null; } + if (!isEmptyUpToCompletionWord(model, range)) { + // No text allowed before the completion + return; + } + const parsedRequest = widget.parsedInput.parts; const usedAgent = parsedRequest.find(p => p instanceof ChatRequestAgentPart); if (usedAgent) { @@ -79,10 +86,10 @@ class SlashCommandCompletions extends Disposable { label: withSlash, insertText: c.executeImmediately ? '' : `${withSlash} `, detail: c.detail, - range: new Range(1, 1, 1, 1), + range, sortText: c.sortText ?? 'a'.repeat(i + 1), kind: CompletionItemKind.Text, // The icons are disabled here anyway, - command: c.executeImmediately ? { id: SubmitAction.ID, title: withSlash, arguments: [{ widget, inputValue: `${withSlash} ` }] } : undefined, + command: c.executeImmediately ? { id: widget.location === ChatAgentLocation.EditingSession ? ChatEditingSessionSubmitAction.ID : ChatSubmitAction.ID, title: withSlash, arguments: [{ widget, inputValue: `${withSlash} ` }] } : undefined, }; }) }; @@ -90,7 +97,7 @@ class SlashCommandCompletions extends Disposable { })); this._register(this.languageFeaturesService.completionProvider.register({ scheme: ChatInputPart.INPUT_SCHEME, hasAccessToAllModels: true }, { _debugDisplayName: 'globalSlashCommandsAt', - triggerCharacters: ['@'], + triggerCharacters: [chatAgentLeader], provideCompletionItems: async (model: ITextModel, position: Position, _context: CompletionContext, _token: CancellationToken) => { const widget = this.chatWidgetService.getWidgetByInputUri(model.uri); if (!widget || !widget.viewModel) { @@ -102,6 +109,11 @@ class SlashCommandCompletions extends Disposable { return null; } + if (!isEmptyUpToCompletionWord(model, range)) { + // No text allowed before the completion + return; + } + const slashCommands = this.chatSlashCommandService.getCommands(widget.location); if (!slashCommands) { return null; @@ -114,11 +126,11 @@ class SlashCommandCompletions extends Disposable { label: withSlash, insertText: c.executeImmediately ? '' : `${withSlash} `, detail: c.detail, - range: new Range(1, 1, 1, 1), + range, filterText: `${chatAgentLeader}${c.command}`, sortText: c.sortText ?? 'z'.repeat(i + 1), kind: CompletionItemKind.Text, // The icons are disabled here anyway, - command: c.executeImmediately ? { id: SubmitAction.ID, title: withSlash, arguments: [{ widget, inputValue: `${withSlash} ` }] } : undefined, + command: c.executeImmediately ? { id: widget.location === ChatAgentLocation.EditingSession ? ChatEditingSessionSubmitAction.ID : ChatSubmitAction.ID, title: withSlash, arguments: [{ widget, inputValue: `${withSlash} ` }] } : undefined, }; }) }; @@ -205,6 +217,11 @@ class AgentCompletions extends Disposable { return null; } + if (!isEmptyUpToCompletionWord(model, range)) { + // No text allowed before the completion + return; + } + const agents = this.chatAgentService.getAgents() .filter(a => a.locations.includes(widget.location)); @@ -231,7 +248,7 @@ class AgentCompletions extends Disposable { detail, filterText: `${chatAgentLeader}${agent.name}`, insertText: `${agentLabel} `, - range: new Range(1, 1, 1, 1), + range, kind: CompletionItemKind.Text, sortText: `${chatAgentLeader}${agent.name}`, command: { id: AssignSelectedAgentAction.ID, title: AssignSelectedAgentAction.ID, arguments: [{ agent, widget } satisfies AssignSelectedAgentActionArgs] }, @@ -251,7 +268,7 @@ class AgentCompletions extends Disposable { filterText: getFilterText(agent, c.name), commitCharacters: [' '], insertText: label + ' ', - range: new Range(1, 1, 1, 1), + range, kind: CompletionItemKind.Text, // The icons are disabled here anyway sortText: `x${chatAgentLeader}${agent.name}${c.name}`, command: { id: AssignSelectedAgentAction.ID, title: AssignSelectedAgentAction.ID, arguments: [{ agent, widget } satisfies AssignSelectedAgentActionArgs] }, @@ -286,6 +303,11 @@ class AgentCompletions extends Disposable { return null; } + if (!isEmptyUpToCompletionWord(model, range)) { + // No text allowed before the completion + return; + } + const agents = this.chatAgentService.getAgents() .filter(a => a.locations.includes(widget.location)); @@ -300,7 +322,7 @@ class AgentCompletions extends Disposable { commitCharacters: [' '], insertText: `${agentLabel} ${withSlash} `, detail: `(${agentLabel}) ${c.description ?? ''}`, - range: new Range(1, 1, 1, 1), + range, kind: CompletionItemKind.Text, // The icons are disabled here anyway sortText, command: { id: AssignSelectedAgentAction.ID, title: AssignSelectedAgentAction.ID, arguments: [{ agent, widget } satisfies AssignSelectedAgentActionArgs] }, @@ -333,11 +355,21 @@ class AgentCompletions extends Disposable { return; } + const range = computeCompletionRanges(model, position, /(@|\/)\w*/g); + if (!range) { + return; + } + + if (!isEmptyUpToCompletionWord(model, range)) { + // No text allowed before the completion + return; + } + const label = localize('installLabel', "Install Chat Extensions..."); const item: CompletionItem = { label, insertText: '', - range: new Range(1, 1, 1, 1), + range, kind: CompletionItemKind.Text, // The icons are disabled here anyway command: { id: 'workbench.extensions.search', title: '', arguments: ['@tag:chat-participant'] }, filterText: chatAgentLeader + label, @@ -407,6 +439,7 @@ class BuiltinDynamicCompletions extends Disposable { @ILabelService private readonly labelService: ILabelService, @ILanguageFeaturesService private readonly languageFeaturesService: ILanguageFeaturesService, @IChatWidgetService private readonly chatWidgetService: IChatWidgetService, + @IChatEditingService private readonly _chatEditingService: IChatEditingService, @IInstantiationService private readonly instantiationService: IInstantiationService, ) { super(); @@ -454,18 +487,23 @@ class BuiltinDynamicCompletions extends Disposable { private async addFileEntries(widget: IChatWidget, result: CompletionList, info: { insert: Range; replace: Range; varWord: IWordAtPosition | null }, token: CancellationToken) { - const makeFileCompletionItem = (resource: URI): CompletionItem => { + const makeFileCompletionItem = (resource: URI, description?: string): CompletionItem => { const basename = this.labelService.getUriBasenameLabel(resource); const text = `${chatVariableLeader}file:${basename}`; + const uriLabel = this.labelService.getUriLabel(resource, { relative: true }); + const labelDescription = description + ? localize('fileEntryDescription', '{0} ({1})', uriLabel, description) + : uriLabel; + const sortText = description ? 'z' : '{'; // after `z` return { - label: { label: basename, description: this.labelService.getUriLabel(resource, { relative: true }) }, + label: { label: basename, description: labelDescription }, filterText: `${chatVariableLeader}${basename}`, insertText: info.varWord?.endColumn === info.replace.endColumn ? `${text} ` : text, range: info, kind: CompletionItemKind.File, - sortText: '{', // after `z` + sortText, command: { id: BuiltinDynamicCompletions.addReferenceCommand, title: '', arguments: [new ReferenceArgument(widget, { id: 'vscode.file', @@ -486,6 +524,20 @@ class BuiltinDynamicCompletions extends Disposable { const seen = new ResourceSet(); const len = result.suggestions.length; + // RELATED FILES + if (widget.location === ChatAgentLocation.EditingSession && widget.viewModel && this._chatEditingService.currentEditingSessionObs.get()?.chatSessionId === widget.viewModel?.sessionId) { + const relatedFiles = (await raceTimeout(this._chatEditingService.getRelatedFiles(widget.viewModel.sessionId, widget.getInput(), token), 1000)) ?? []; + for (const relatedFileGroup of relatedFiles) { + for (const relatedFile of relatedFileGroup.files) { + if (seen.has(relatedFile.uri)) { + continue; + } + seen.add(relatedFile.uri); + result.suggestions.push(makeFileCompletionItem(relatedFile.uri, relatedFile.description)); + } + } + } + // HISTORY // always take the last N items for (const item of this.historyService.getHistory()) { @@ -557,7 +609,13 @@ class BuiltinDynamicCompletions extends Disposable { Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(BuiltinDynamicCompletions, LifecyclePhase.Eventually); -export function computeCompletionRanges(model: ITextModel, position: Position, reg: RegExp, onlyOnWordStart = false): { insert: Range; replace: Range; varWord: IWordAtPosition | null } | undefined { +export interface IChatCompletionRangeResult { + insert: Range; + replace: Range; + varWord: IWordAtPosition | null; +} + +export function computeCompletionRanges(model: ITextModel, position: Position, reg: RegExp, onlyOnWordStart = false): IChatCompletionRangeResult | undefined { const varWord = getWordAtText(position.column, reg, model.getLineContent(position.lineNumber), 0); if (!varWord && model.getWordUntilPosition(position).word) { // inside a "normal" word @@ -591,6 +649,11 @@ export function computeCompletionRanges(model: ITextModel, position: Position, r return { insert, replace, varWord }; } +function isEmptyUpToCompletionWord(model: ITextModel, rangeResult: IChatCompletionRangeResult): boolean { + const startToCompletionWordStart = new Range(1, 1, rangeResult.replace.startLineNumber, rangeResult.replace.startColumn); + return !!model.getValueInRange(startToCompletionWordStart).match(/^\s*$/); +} + class VariableCompletions extends Disposable { private static readonly VariableNameDef = new RegExp(`(?<=^|\\s)${chatVariableLeader}\\w*`, 'g'); // MUST be using `g`-flag diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts index 0f084633c369d..313e90a2567bf 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts @@ -321,6 +321,7 @@ class ChatTokenDeleter extends Disposable { range: rangeToDelete, text: '', }]); + this.widget.refreshParsedInput(); } }); } diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatInputRelatedFilesContrib.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatInputRelatedFilesContrib.ts new file mode 100644 index 0000000000000..f61099ebc7fd7 --- /dev/null +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatInputRelatedFilesContrib.ts @@ -0,0 +1,124 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { CancellationToken } from '../../../../../base/common/cancellation.js'; +import { Event } from '../../../../../base/common/event.js'; +import { Disposable, DisposableStore } from '../../../../../base/common/lifecycle.js'; +import { ResourceSet } from '../../../../../base/common/map.js'; +import { URI } from '../../../../../base/common/uri.js'; +import { localize } from '../../../../../nls.js'; +import { IWorkbenchContribution } from '../../../../common/contributions.js'; +import { ChatEditingSessionChangeType, IChatEditingService, WorkingSetEntryState } from '../../common/chatEditingService.js'; +import { IChatWidgetService } from '../chat.js'; + +export class ChatRelatedFilesContribution extends Disposable implements IWorkbenchContribution { + static readonly ID = 'chat.relatedFilesWorkingSet'; + + private readonly chatEditingSessionDisposables = new DisposableStore(); + private _currentRelatedFilesRetrievalOperation: Promise | undefined; + + constructor( + @IChatEditingService private readonly chatEditingService: IChatEditingService, + @IChatWidgetService private readonly chatWidgetService: IChatWidgetService + ) { + super(); + + this._handleNewEditingSession(); + this._register(this.chatEditingService.onDidCreateEditingSession(() => { + this.chatEditingSessionDisposables.clear(); + this._handleNewEditingSession(); + })); + } + + private _updateRelatedFileSuggestions() { + if (this._currentRelatedFilesRetrievalOperation) { + return; + } + + const currentEditingSession = this.chatEditingService.currentEditingSessionObs.get(); + if (currentEditingSession) { + const workingSetEntries = currentEditingSession.entries.get(); + if (workingSetEntries.length > 0) { + // Do this only for the initial working set state + return; + } + + const widget = this.chatWidgetService.getWidgetBySessionId(currentEditingSession.chatSessionId); + if (!widget) { + return; + } + + this._currentRelatedFilesRetrievalOperation = this.chatEditingService.getRelatedFiles(currentEditingSession.chatSessionId, widget.getInput(), CancellationToken.None) + .then((files) => { + if (!files?.length) { + return; + } + + const currentEditingSession = this.chatEditingService.currentEditingSessionObs.get(); + if (!currentEditingSession || currentEditingSession.chatSessionId !== widget.viewModel?.sessionId || currentEditingSession.entries.get()) { + return; // Might have disposed while we were calculating + } + + // Pick up to 2 related files, or however many we can still fit in the working set + const maximumRelatedFiles = Math.min(2, this.chatEditingService.editingSessionFileLimit - widget.input.chatEditWorkingSetFiles.length); + const newSuggestions = new ResourceSet(); + for (const group of files) { + for (const file of group.files) { + if (newSuggestions.size >= maximumRelatedFiles) { + break; + } + newSuggestions.add(file.uri); + } + } + + // Remove the existing related file suggestions from the working set + const existingSuggestedEntriesToRemove: URI[] = []; + for (const entry of currentEditingSession.workingSet) { + if (entry[1].state === WorkingSetEntryState.Suggested && !newSuggestions.has(entry[0])) { + existingSuggestedEntriesToRemove.push(entry[0]); + } + } + currentEditingSession?.remove(...existingSuggestedEntriesToRemove); + + // Add the new related file suggestions to the working set + for (const file of newSuggestions) { + currentEditingSession.addFileToWorkingSet(file, localize('relatedFile', "Suggested File"), WorkingSetEntryState.Suggested); + } + }) + .finally(() => { + this._currentRelatedFilesRetrievalOperation = undefined; + }); + } + } + + private _handleNewEditingSession() { + const currentEditingSession = this.chatEditingService.currentEditingSessionObs.get(); + if (!currentEditingSession) { + return; + } + const widget = this.chatWidgetService.getWidgetBySessionId(currentEditingSession.chatSessionId); + if (!widget || widget.viewModel?.sessionId !== currentEditingSession.chatSessionId) { + return; + } + this.chatEditingSessionDisposables.add(currentEditingSession.onDidDispose(() => { + this.chatEditingSessionDisposables.clear(); + })); + this._updateRelatedFileSuggestions(); + const onDebouncedType = Event.debounce(widget.inputEditor.onDidChangeModelContent, () => null, 3000); + this.chatEditingSessionDisposables.add(onDebouncedType(() => { + this._updateRelatedFileSuggestions(); + })); + this.chatEditingSessionDisposables.add(currentEditingSession.onDidChange((e) => { + if (e === ChatEditingSessionChangeType.WorkingSet) { + this._updateRelatedFileSuggestions(); + } + })); + } + + override dispose() { + this.chatEditingSessionDisposables.dispose(); + super.dispose(); + } +} diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index c6bfe4cbada84..15cb95d53d41f 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -24,10 +24,6 @@ -webkit-user-select: text; } -.interactive-item-container.disabled { - filter: blur(2px); -} - .interactive-item-container .header { display: flex; align-items: center; @@ -309,11 +305,38 @@ margin: 8px 0 16px 0; } +.interactive-item-container .value .rendered-markdown { + /* Codicons next to text need to be aligned with the text */ + .codicon { + position: relative; + top: 2px; + } + + /* But codicons in toolbars and other widgets assume the natural position of the codicon */ + .chat-codeblock-pill-widget .codicon, + .monaco-toolbar .codicon { + position: initial; + top: initial; + } + + /* Code blocks at the beginning of an answer should not have a margin as it means it won't align with the agent icon*/ + > div[data-code]:first-child { + margin-top: 0; + + } + /* Override the top to avoid the toolbar getting clipped by overflow:hidden */ + > div[data-code]:first-child .interactive-result-code-block .interactive-result-code-block-toolbar > .monaco-action-bar, + > div[data-code]:first-child .interactive-result-code-block .interactive-result-code-block-toolbar > .monaco-toolbar { + top: 6px; + } +} + .interactive-item-container .value .rendered-markdown p { line-height: 1.5em; } .interactive-item-container .value > .rendered-markdown p { + /* Targetting normal text paras. `p` can also appear in other elements/widgets */ margin: 0 0 16px 0; } @@ -450,6 +473,10 @@ have to be updated for changes to the rules above, or to support more deeply nes flex-grow: 1; } +.interactive-item-container.interactive-request.minimal .rendered-markdown .chat-animated-ellipsis { + display: inline-flex; +} + .interactive-item-container.minimal .user > .username { display: none; } @@ -556,6 +583,7 @@ have to be updated for changes to the rules above, or to support more deeply nes white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + align-content: center; } .interactive-session .chat-editing-session .chat-editing-session-container .monaco-progress-container { @@ -571,6 +599,10 @@ have to be updated for changes to the rules above, or to support more deeply nes align-items: center; } +.interactive-session .chat-editing-session .chat-editing-session-toolbar-actions { + margin: 3px 0px; +} + .interactive-session .chat-editing-session .monaco-button { height: 17px; width: fit-content; @@ -698,6 +730,24 @@ have to be updated for changes to the rules above, or to support more deeply nes margin-right: auto; } +.interactive-session .chat-input-toolbars > .chat-execute-toolbar { + min-width: 0px; + + .chat-modelPicker-item { + min-width: 0px; + + .chat-model-label { + min-width: 0px; + overflow: hidden; + text-overflow: ellipsis; + } + + .codicon { + flex-shrink: 0; + } + } +} + .interactive-session .chat-input-toolbars .chat-modelPicker-item .action-label { height: 16px; padding: 3px 0px 3px 6px; @@ -832,6 +882,7 @@ have to be updated for changes to the rules above, or to support more deeply nes padding-right: 4px; padding-left: 2px; height: calc(100% + 4px); + outline-offset: -4px; } .interactive-session .chat-attached-context .chat-attached-context-attachment .monaco-button:hover { @@ -923,6 +974,10 @@ have to be updated for changes to the rules above, or to support more deeply nes padding: 0 2px 0 0; } +.interactive-session .chat-attached-context .chat-attached-context-attachment .monaco-icon-label.predefined-file-icon::before { + padding: 0 0 0 2px; +} + .interactive-session .interactive-item-container.interactive-request .chat-attached-context .chat-attached-context-attachment { padding-right: 6px; } @@ -1043,21 +1098,6 @@ have to be updated for changes to the rules above, or to support more deeply nes margin: 0; } -.disabled-overlay { - display: none; -} - -.disabled-overlay.disabled { - position: absolute; - width: 100%; - height: 100%; - background-color: rgba(64, 64, 64, 0.2); - pointer-events: none; - display: flex; - cursor: default; - z-index: 1; -} - .interactive-response .interactive-response-codicon-details { display: flex; align-items: start; @@ -1113,6 +1153,13 @@ have to be updated for changes to the rules above, or to support more deeply nes } } +.interactive-session .chat-editing-session-list { + + .monaco-icon-label { + padding: 0px 3px; + } +} + .interactive-item-container .chat-notification-widget { padding: 8px 12px; } @@ -1161,36 +1208,33 @@ have to be updated for changes to the rules above, or to support more deeply nes font-size: 12px; } -.interactive-item-container .rendered-markdown.progress-step { +.interactive-item-container .progress-container { display: flex; - gap: 2px; - margin-left: 4px; - white-space: normal; -} + gap: 8px; + margin: 0 0 6px 4px; -.interactive-item-container .rendered-markdown.progress-step .monaco-button { - flex: 0 0 content; - font-size: 12px; - padding: 2px; -} + > .codicon { + height: 16px; + } -.interactive-item-container .rendered-markdown.progress-step > p { - color: var(--vscode-descriptionForeground); - font-size: 12px; - display: flex; - gap: 8px; - align-items: center; - margin-bottom: 6px; - flex: 1; -} + .codicon { + /* Very aggressive list styles try to apply focus colors to every codicon in a list row. */ + color: var(--vscode-icon-foreground) !important; -.interactive-item-container .rendered-markdown.progress-step > p .codicon { - /* Very aggressive list styles try to apply focus colors to every codicon in a list row. */ - color: var(--vscode-icon-foreground) !important; -} + &.codicon-check { + color: var(--vscode-debugIcon-startForeground) !important; + } + } -.interactive-item-container .rendered-markdown.progress-step > p .codicon.codicon-check { - color: var(--vscode-debugIcon-startForeground) !important; + .rendered-markdown.progress-step { + white-space: normal; + + & > p { + color: var(--vscode-descriptionForeground); + font-size: 12px; + margin: 0; + } + } } .interactive-item-container .chat-command-button { diff --git a/src/vs/workbench/contrib/chat/browser/media/chatCodeBlockPill.css b/src/vs/workbench/contrib/chat/browser/media/chatCodeBlockPill.css index 78b24e11f2985..24462f9d2e0db 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chatCodeBlockPill.css +++ b/src/vs/workbench/contrib/chat/browser/media/chatCodeBlockPill.css @@ -44,3 +44,9 @@ background-repeat: no-repeat; flex-shrink: 0; } + +span.label-detail { + padding-left: 4px; + font-style: italic; + color: var(--vscode-descriptionForeground); +} diff --git a/src/vs/workbench/contrib/chat/browser/media/chatEditorController.css b/src/vs/workbench/contrib/chat/browser/media/chatEditorController.css new file mode 100644 index 0000000000000..03d8b6e617166 --- /dev/null +++ b/src/vs/workbench/contrib/chat/browser/media/chatEditorController.css @@ -0,0 +1,39 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.chat-diff-change-content-widget { + opacity: 0; + transition: opacity 0.2s ease-in-out; + display: flex; +} + +.chat-diff-change-content-widget.hover { + opacity: 1; +} + +.chat-diff-change-content-widget .monaco-action-bar { + padding: 0; + border-radius: 2px; + background-color: var(--vscode-button-background); + color: var(--vscode-button-foreground); +} + +.chat-diff-change-content-widget .monaco-action-bar .action-item .action-label { + height: 14px; + border-radius: 2px; + color: var(--vscode-button-foreground); +} + +.chat-diff-change-content-widget .monaco-action-bar .action-item .action-label.codicon { + width: unset; + padding: 2px; + font-size: 12px; + line-height: 14px; + color: var(--vscode-button-foreground); +} + +.chat-diff-change-content-widget .monaco-action-bar .action-item .action-label.codicon[class*='codicon-'] { + font-size: 12px; +} diff --git a/src/vs/workbench/contrib/chat/browser/media/chatEditorOverlay.css b/src/vs/workbench/contrib/chat/browser/media/chatEditorOverlay.css new file mode 100644 index 0000000000000..cd44686c9e6be --- /dev/null +++ b/src/vs/workbench/contrib/chat/browser/media/chatEditorOverlay.css @@ -0,0 +1,59 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.chat-editor-overlay-widget { + padding: 0px; + color: var(--vscode-button-foreground); + background-color: var(--vscode-button-background); + border-radius: 5px; + border: 1px solid var(--vscode-contrastBorder); + display: flex; + align-items: center; + z-index: 10; +} + +.chat-editor-overlay-widget .chat-editor-overlay-progress { + display: none; + padding: 0px 5px; + font-size: 12px; +} + +.chat-editor-overlay-widget.busy .chat-editor-overlay-progress { + display: inherit; +} + +.chat-editor-overlay-widget .action-item > .action-label { + padding: 5px; + font-size: 12px; +} + +.chat-editor-overlay-widget .action-item:first-child > .action-label { + padding-left: 9px; +} + +.chat-editor-overlay-widget .action-item:last-child > .action-label { + padding-right: 9px; +} + +.chat-editor-overlay-widget.busy .chat-editor-overlay-progress .codicon, +.chat-editor-overlay-widget .action-item > .action-label.codicon { + color: var(--vscode-button-foreground); +} + +.chat-editor-overlay-widget .action-item.disabled > .action-label.codicon::before, +.chat-editor-overlay-widget .action-item.disabled > .action-label.codicon, +.chat-editor-overlay-widget .action-item.disabled > .action-label, +.chat-editor-overlay-widget .action-item.disabled > .action-label:hover { + color: var(--vscode-button-foreground); + opacity: 0.7; +} + + + +.chat-editor-overlay-widget .action-item.label-item > .action-label, +.chat-editor-overlay-widget .action-item.label-item > .action-label:hover { + color: var(--vscode-button-foreground); + opacity: 1; +} diff --git a/src/vs/workbench/contrib/chat/browser/media/chatInlineAnchorWidget.css b/src/vs/workbench/contrib/chat/browser/media/chatInlineAnchorWidget.css index 370a96439b2c6..475155bed7d62 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chatInlineAnchorWidget.css +++ b/src/vs/workbench/contrib/chat/browser/media/chatInlineAnchorWidget.css @@ -31,6 +31,8 @@ line-height: 1em; font-size: 90% !important; overflow: hidden; + + top: 0 !important; } .show-file-icons.chat-inline-anchor-widget .icon::before { diff --git a/src/vs/workbench/contrib/chat/browser/media/chatViewWelcome.css b/src/vs/workbench/contrib/chat/browser/media/chatViewWelcome.css index 741dcbd6a135d..31b95b07b3f73 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chatViewWelcome.css +++ b/src/vs/workbench/contrib/chat/browser/media/chatViewWelcome.css @@ -54,6 +54,16 @@ div.chat-welcome-view { font-size: 11px; } + & > .chat-welcome-view-progress { + display: flex; + gap: 6px; + color: var(--vscode-descriptionForeground); + text-align: center; + max-width: 350px; + padding: 0 20px; + margin-top: 20px; + } + & > .chat-welcome-view-message { color: var(--vscode-descriptionForeground); text-align: center; diff --git a/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewWelcomeController.ts b/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewWelcomeController.ts index d5992926d9772..bae1465625ef0 100644 --- a/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewWelcomeController.ts +++ b/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewWelcomeController.ts @@ -17,6 +17,7 @@ import { IInstantiationService } from '../../../../../platform/instantiation/com import { ILogService } from '../../../../../platform/log/common/log.js'; import { IOpenerService } from '../../../../../platform/opener/common/opener.js'; import { defaultButtonStyles } from '../../../../../platform/theme/browser/defaultStyles.js'; +import { spinningLoading } from '../../../../../platform/theme/common/iconRegistry.js'; import { ChatAgentLocation } from '../../common/chatAgents.js'; import { chatViewsWelcomeRegistry, IChatViewsWelcomeDescriptor } from './chatViewsWelcome.js'; @@ -52,7 +53,7 @@ export class ChatViewWelcomeController extends Disposable { private update(force?: boolean): void { const enabled = this.delegate.shouldShowWelcome(); - if (this.enabled === enabled || force) { + if (this.enabled === enabled && !force) { return; } @@ -87,6 +88,7 @@ export class ChatViewWelcomeController extends Disposable { icon: enabledDescriptor.icon, title: enabledDescriptor.title, message: enabledDescriptor.content, + progress: enabledDescriptor.progress }; const welcomeView = this.renderDisposables.add(this.instantiationService.createInstance(ChatViewWelcomePart, content, { firstLinkToButton: true, location: this.location })); this.element!.appendChild(welcomeView.element); @@ -101,6 +103,7 @@ export interface IChatViewWelcomeContent { icon?: ThemeIcon; title: string; message: IMarkdownString; + progress?: string; tips?: IMarkdownString; } @@ -123,14 +126,23 @@ export class ChatViewWelcomePart extends Disposable { this.element = dom.$('.chat-welcome-view'); try { - const icon = dom.append(this.element!, $('.chat-welcome-view-icon')); - const title = dom.append(this.element!, $('.chat-welcome-view-title')); + const icon = dom.append(this.element, $('.chat-welcome-view-icon')); + const title = dom.append(this.element, $('.chat-welcome-view-title')); if (options?.location === ChatAgentLocation.EditingSession) { - const featureIndicator = dom.append(this.element!, $('.chat-welcome-view-indicator')); + const featureIndicator = dom.append(this.element, $('.chat-welcome-view-indicator')); featureIndicator.textContent = localize('preview', 'PREVIEW'); } - const message = dom.append(this.element!, $('.chat-welcome-view-message')); + + if (content.progress) { + const progress = dom.append(this.element, $('.chat-welcome-view-progress')); + progress.appendChild(renderIcon(spinningLoading)); + + const progressLabel = dom.append(progress, $('span')); + progressLabel.textContent = content.progress; + } + + const message = dom.append(this.element, $('.chat-welcome-view-message')); if (content.icon) { icon.appendChild(renderIcon(content.icon)); @@ -155,7 +167,7 @@ export class ChatViewWelcomePart extends Disposable { dom.append(message, messageResult.element); if (content.tips) { - const tips = dom.append(this.element!, $('.chat-welcome-view-tips')); + const tips = dom.append(this.element, $('.chat-welcome-view-tips')); const tipsResult = this._register(renderer.render(content.tips)); tips.appendChild(tipsResult.element); } diff --git a/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewsWelcome.ts b/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewsWelcome.ts index cb63071052167..9980a78c5dd0a 100644 --- a/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewsWelcome.ts +++ b/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewsWelcome.ts @@ -17,6 +17,7 @@ export interface IChatViewsWelcomeDescriptor { icon?: ThemeIcon; title: string; content: IMarkdownString; + progress?: string; // TODO@bpasero remove me if not used anymore when: ContextKeyExpression; } diff --git a/src/vs/workbench/contrib/chat/common/chatAgents.ts b/src/vs/workbench/contrib/chat/common/chatAgents.ts index a153c6f61720d..b8c595115e58f 100644 --- a/src/vs/workbench/contrib/chat/common/chatAgents.ts +++ b/src/vs/workbench/contrib/chat/common/chatAgents.ts @@ -23,7 +23,7 @@ import { ILogService } from '../../../../platform/log/common/log.js'; import { IProductService } from '../../../../platform/product/common/productService.js'; import { asJson, IRequestService } from '../../../../platform/request/common/request.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; -import { CONTEXT_CHAT_EDITING_PARTICIPANT_REGISTERED, CONTEXT_CHAT_ENABLED, CONTEXT_CHAT_PANEL_PARTICIPANT_REGISTERED } from './chatContextKeys.js'; +import { ChatContextKeys } from './chatContextKeys.js'; import { IChatProgressHistoryResponseContent, IChatRequestVariableData, ISerializableChatAgentData } from './chatModel.js'; import { IRawChatCommandContribution, RawChatParticipantLocation } from './chatParticipantContribTypes.js'; import { IChatFollowup, IChatLocationData, IChatProgress, IChatResponseErrorDetails, IChatTaskDto } from './chatService.js'; @@ -251,13 +251,15 @@ export class ChatAgentService extends Disposable implements IChatAgentService { private readonly _defaultAgentRegistered: IContextKey; private readonly _editingAgentRegistered: IContextKey; + private _chatParticipantDetectionProviders = new Map(); + constructor( @IContextKeyService private readonly contextKeyService: IContextKeyService, ) { super(); - this._hasDefaultAgent = CONTEXT_CHAT_ENABLED.bindTo(this.contextKeyService); - this._defaultAgentRegistered = CONTEXT_CHAT_PANEL_PARTICIPANT_REGISTERED.bindTo(this.contextKeyService); - this._editingAgentRegistered = CONTEXT_CHAT_EDITING_PARTICIPANT_REGISTERED.bindTo(this.contextKeyService); + this._hasDefaultAgent = ChatContextKeys.enabled.bindTo(this.contextKeyService); + this._defaultAgentRegistered = ChatContextKeys.panelParticipantRegistered.bindTo(this.contextKeyService); + this._editingAgentRegistered = ChatContextKeys.editingParticipantRegistered.bindTo(this.contextKeyService); this._register(contextKeyService.onDidChangeContext((e) => { if (e.affectsSome(this._agentsContextKeys)) { this._updateContextKeys(); @@ -481,7 +483,6 @@ export class ChatAgentService extends Disposable implements IChatAgentService { return data.impl.provideChatTitle(history, token); } - private _chatParticipantDetectionProviders = new Map(); registerChatParticipantDetectionProvider(handle: number, provider: IChatParticipantDetectionProvider) { this._chatParticipantDetectionProviders.set(handle, provider); return toDisposable(() => { diff --git a/src/vs/workbench/contrib/chat/common/chatContextKeys.ts b/src/vs/workbench/contrib/chat/common/chatContextKeys.ts index 9fee8ccab3a63..57b4d0b9fff33 100644 --- a/src/vs/workbench/contrib/chat/common/chatContextKeys.ts +++ b/src/vs/workbench/contrib/chat/common/chatContextKeys.ts @@ -4,42 +4,50 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from '../../../../nls.js'; -import { RawContextKey } from '../../../../platform/contextkey/common/contextkey.js'; +import { ContextKeyExpr, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js'; import { ChatAgentLocation } from './chatAgents.js'; -export const CONTEXT_RESPONSE_VOTE = new RawContextKey('chatSessionResponseVote', '', { type: 'string', description: localize('interactiveSessionResponseVote', "When the response has been voted up, is set to 'up'. When voted down, is set to 'down'. Otherwise an empty string.") }); -export const CONTEXT_RESPONSE_DETECTED_AGENT_COMMAND = new RawContextKey('chatSessionResponseDetectedAgentOrCommand', false, { type: 'boolean', description: localize('chatSessionResponseDetectedAgentOrCommand', "When the agent or command was automatically detected") }); -export const CONTEXT_CHAT_RESPONSE_SUPPORT_ISSUE_REPORTING = new RawContextKey('chatResponseSupportsIssueReporting', false, { type: 'boolean', description: localize('chatResponseSupportsIssueReporting', "True when the current chat response supports issue reporting.") }); -export const CONTEXT_RESPONSE_FILTERED = new RawContextKey('chatSessionResponseFiltered', false, { type: 'boolean', description: localize('chatResponseFiltered', "True when the chat response was filtered out by the server.") }); -export const CONTEXT_RESPONSE_ERROR = new RawContextKey('chatSessionResponseError', false, { type: 'boolean', description: localize('chatResponseErrored', "True when the chat response resulted in an error.") }); -export const CONTEXT_CHAT_REQUEST_IN_PROGRESS = new RawContextKey('chatSessionRequestInProgress', false, { type: 'boolean', description: localize('interactiveSessionRequestInProgress', "True when the current request is still in progress.") }); - -export const CONTEXT_RESPONSE = new RawContextKey('chatResponse', false, { type: 'boolean', description: localize('chatResponse', "The chat item is a response.") }); -export const CONTEXT_REQUEST = new RawContextKey('chatRequest', false, { type: 'boolean', description: localize('chatRequest', "The chat item is a request") }); -export const CONTEXT_ITEM_ID = new RawContextKey('chatItemId', '', { type: 'string', description: localize('chatItemId', "The id of the chat item.") }); -export const CONTEXT_LAST_ITEM_ID = new RawContextKey('chatLastItemId', [], { type: 'string', description: localize('chatLastItemId', "The id of the last chat item.") }); - -export const CONTEXT_CHAT_EDIT_APPLIED = new RawContextKey('chatEditApplied', false, { type: 'boolean', description: localize('chatEditApplied', "True when the chat text edits have been applied.") }); - -export const CONTEXT_CHAT_INPUT_HAS_TEXT = new RawContextKey('chatInputHasText', false, { type: 'boolean', description: localize('interactiveInputHasText', "True when the chat input has text.") }); -export const CONTEXT_CHAT_INPUT_HAS_FOCUS = new RawContextKey('chatInputHasFocus', false, { type: 'boolean', description: localize('interactiveInputHasFocus', "True when the chat input has focus.") }); -export const CONTEXT_IN_CHAT_INPUT = new RawContextKey('inChatInput', false, { type: 'boolean', description: localize('inInteractiveInput', "True when focus is in the chat input, false otherwise.") }); -export const CONTEXT_IN_CHAT_SESSION = new RawContextKey('inChat', false, { type: 'boolean', description: localize('inChat', "True when focus is in the chat widget, false otherwise.") }); - -export const CONTEXT_CHAT_ENABLED = new RawContextKey('chatIsEnabled', false, { type: 'boolean', description: localize('chatIsEnabled', "True when chat is enabled because a default chat participant is activated with an implementation.") }); -export const CONTEXT_CHAT_PANEL_PARTICIPANT_REGISTERED = new RawContextKey('chatPanelParticipantRegistered', false, { type: 'boolean', description: localize('chatParticipantRegistered', "True when a default chat participant is registered for the panel.") }); -export const CONTEXT_CHAT_EDITING_PARTICIPANT_REGISTERED = new RawContextKey('chatEditingParticipantRegistered', false, { type: 'boolean', description: localize('chatEditingParticipantRegistered', "True when a default chat participant is registered for editing.") }); -export const CONTEXT_CHAT_EDITING_CAN_UNDO = new RawContextKey('chatEditingCanUndo', false, { type: 'boolean', description: localize('chatEditingCanUndo', "True when it is possible to undo an interaction in the editing panel.") }); -export const CONTEXT_CHAT_EDITING_CAN_REDO = new RawContextKey('chatEditingCanRedo', false, { type: 'boolean', description: localize('chatEditingCanRedo', "True when it is possible to redo an interaction in the editing panel.") }); -export const CONTEXT_CHAT_EXTENSION_INVALID = new RawContextKey('chatExtensionInvalid', false, { type: 'boolean', description: localize('chatExtensionInvalid', "True when the installed chat extension is invalid and needs to be updated.") }); -export const CONTEXT_CHAT_INPUT_CURSOR_AT_TOP = new RawContextKey('chatCursorAtTop', false); -export const CONTEXT_CHAT_INPUT_HAS_AGENT = new RawContextKey('chatInputHasAgent', false); -export const CONTEXT_CHAT_LOCATION = new RawContextKey('chatLocation', undefined); -export const CONTEXT_IN_QUICK_CHAT = new RawContextKey('quickChatHasFocus', false, { type: 'boolean', description: localize('inQuickChat', "True when the quick chat UI has focus, false otherwise.") }); -export const CONTEXT_CHAT_HAS_FILE_ATTACHMENTS = new RawContextKey('chatHasFileAttachments', false, { type: 'boolean', description: localize('chatHasFileAttachments', "True when the chat has file attachments.") }); - -export const CONTEXT_LANGUAGE_MODELS_ARE_USER_SELECTABLE = new RawContextKey('chatModelsAreUserSelectable', false, { type: 'boolean', description: localize('chatModelsAreUserSelectable', "True when the chat model can be selected manually by the user.") }); - -export const CONTEXT_CHAT_INSTALL_ENTITLED = new RawContextKey('chatInstallEntitled', false, { type: 'boolean', description: localize('chatInstallEntitled', "True when the user is entitled for chat installation.") }); - -export const CONTEXT_CHAT_SHOULD_SHOW_MOVED_VIEW_WELCOME = new RawContextKey('chatShouldShowMovedViewWelcome', false, { type: 'boolean', description: localize('chatShouldShowMovedViewWelcome', "True when the user should be shown the moved view welcome view.") }); +export namespace ChatContextKeys { + export const responseVote = new RawContextKey('chatSessionResponseVote', '', { type: 'string', description: localize('interactiveSessionResponseVote', "When the response has been voted up, is set to 'up'. When voted down, is set to 'down'. Otherwise an empty string.") }); + export const responseDetectedAgentCommand = new RawContextKey('chatSessionResponseDetectedAgentOrCommand', false, { type: 'boolean', description: localize('chatSessionResponseDetectedAgentOrCommand', "When the agent or command was automatically detected") }); + export const responseSupportsIssueReporting = new RawContextKey('chatResponseSupportsIssueReporting', false, { type: 'boolean', description: localize('chatResponseSupportsIssueReporting', "True when the current chat response supports issue reporting.") }); + export const responseIsFiltered = new RawContextKey('chatSessionResponseFiltered', false, { type: 'boolean', description: localize('chatResponseFiltered', "True when the chat response was filtered out by the server.") }); + export const responseHasError = new RawContextKey('chatSessionResponseError', false, { type: 'boolean', description: localize('chatResponseErrored', "True when the chat response resulted in an error.") }); + export const requestInProgress = new RawContextKey('chatSessionRequestInProgress', false, { type: 'boolean', description: localize('interactiveSessionRequestInProgress', "True when the current request is still in progress.") }); + + export const isResponse = new RawContextKey('chatResponse', false, { type: 'boolean', description: localize('chatResponse', "The chat item is a response.") }); + export const isRequest = new RawContextKey('chatRequest', false, { type: 'boolean', description: localize('chatRequest', "The chat item is a request") }); + export const itemId = new RawContextKey('chatItemId', '', { type: 'string', description: localize('chatItemId', "The id of the chat item.") }); + export const lastItemId = new RawContextKey('chatLastItemId', [], { type: 'string', description: localize('chatLastItemId', "The id of the last chat item.") }); + + export const editApplied = new RawContextKey('chatEditApplied', false, { type: 'boolean', description: localize('chatEditApplied', "True when the chat text edits have been applied.") }); + + export const inputHasText = new RawContextKey('chatInputHasText', false, { type: 'boolean', description: localize('interactiveInputHasText', "True when the chat input has text.") }); + export const inputHasFocus = new RawContextKey('chatInputHasFocus', false, { type: 'boolean', description: localize('interactiveInputHasFocus', "True when the chat input has focus.") }); + export const inChatInput = new RawContextKey('inChatInput', false, { type: 'boolean', description: localize('inInteractiveInput', "True when focus is in the chat input, false otherwise.") }); + export const inChatSession = new RawContextKey('inChat', false, { type: 'boolean', description: localize('inChat', "True when focus is in the chat widget, false otherwise.") }); + + export const enabled = new RawContextKey('chatIsEnabled', false, { type: 'boolean', description: localize('chatIsEnabled', "True when chat is enabled because a default chat participant is activated with an implementation.") }); + export const panelParticipantRegistered = new RawContextKey('chatPanelParticipantRegistered', false, { type: 'boolean', description: localize('chatParticipantRegistered', "True when a default chat participant is registered for the panel.") }); + export const editingParticipantRegistered = new RawContextKey('chatEditingParticipantRegistered', false, { type: 'boolean', description: localize('chatEditingParticipantRegistered', "True when a default chat participant is registered for editing.") }); + export const chatEditingCanUndo = new RawContextKey('chatEditingCanUndo', false, { type: 'boolean', description: localize('chatEditingCanUndo', "True when it is possible to undo an interaction in the editing panel.") }); + export const chatEditingCanRedo = new RawContextKey('chatEditingCanRedo', false, { type: 'boolean', description: localize('chatEditingCanRedo', "True when it is possible to redo an interaction in the editing panel.") }); + export const extensionInvalid = new RawContextKey('chatExtensionInvalid', false, { type: 'boolean', description: localize('chatExtensionInvalid', "True when the installed chat extension is invalid and needs to be updated.") }); + export const inputCursorAtTop = new RawContextKey('chatCursorAtTop', false); + export const inputHasAgent = new RawContextKey('chatInputHasAgent', false); + export const location = new RawContextKey('chatLocation', undefined); + export const inQuickChat = new RawContextKey('quickChatHasFocus', false, { type: 'boolean', description: localize('inQuickChat', "True when the quick chat UI has focus, false otherwise.") }); + export const hasFileAttachments = new RawContextKey('chatHasFileAttachments', false, { type: 'boolean', description: localize('chatHasFileAttachments', "True when the chat has file attachments.") }); + + export const languageModelsAreUserSelectable = new RawContextKey('chatModelsAreUserSelectable', false, { type: 'boolean', description: localize('chatModelsAreUserSelectable', "True when the chat model can be selected manually by the user.") }); + + export const ChatSetup = { + signedIn: new RawContextKey('chatSetupSignedIn', false, { type: 'boolean', description: localize('chatSetupSignedIn', "True when chat setup is offered for a signed-in user.") }), + entitled: new RawContextKey('chatSetupEntitled', false, { type: 'boolean', description: localize('chatSetupEntitled', "True when chat setup is offered for a signed-in, entitled user.") }), + + triggering: new RawContextKey('chatSetupTriggered', false, { type: 'boolean', description: localize('chatSetupTriggered', "True when chat setup is triggered.") }), + installing: new RawContextKey('chatSetupInstalling', false, { type: 'boolean', description: localize('chatSetupInstalling', "True when chat setup is installing chat.") }), + signingIn: new RawContextKey('chatSetupSigningIn', false, { type: 'boolean', description: localize('chatSetupSigningIn', "True when chat setup is waiting for signing in.") }) + }; + export const setupRunning = ContextKeyExpr.or(ChatSetup.triggering, ChatSetup.signingIn, ChatSetup.installing); +} diff --git a/src/vs/workbench/contrib/chat/common/chatEditingService.ts b/src/vs/workbench/contrib/chat/common/chatEditingService.ts index b5e6d173dda4e..4cdc8f3fb38a1 100644 --- a/src/vs/workbench/contrib/chat/common/chatEditingService.ts +++ b/src/vs/workbench/contrib/chat/common/chatEditingService.ts @@ -3,8 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CancellationTokenSource } from '../../../../base/common/cancellation.js'; +import { CancellationToken, CancellationTokenSource } from '../../../../base/common/cancellation.js'; import { Event } from '../../../../base/common/event.js'; +import { IDisposable } from '../../../../base/common/lifecycle.js'; import { ResourceMap } from '../../../../base/common/map.js'; import { IObservable, ITransaction } from '../../../../base/common/observable.js'; import { URI } from '../../../../base/common/uri.js'; @@ -36,23 +37,47 @@ export interface IChatEditingService { readonly editingSessionFileLimit: number; startOrContinueEditingSession(chatSessionId: string, options?: { silent: boolean }): Promise; - triggerEditComputation(responseModel: IChatResponseModel): Promise; getEditingSession(resource: URI): IChatEditingSession | null; createSnapshot(requestId: string): void; getSnapshotUri(requestId: string, uri: URI): URI | undefined; restoreSnapshot(requestId: string | undefined): Promise; + + hasRelatedFilesProviders(): boolean; + registerRelatedFilesProvider(handle: number, provider: IChatRelatedFilesProvider): IDisposable; + getRelatedFiles(chatSessionId: string, prompt: string, token: CancellationToken): Promise<{ group: string; files: IChatRelatedFile[] }[] | undefined>; +} + +export interface IChatRequestDraft { + readonly prompt: string; + readonly files: readonly URI[]; +} + +export interface IChatRelatedFileProviderMetadata { + readonly description: string; +} + +export interface IChatRelatedFile { + readonly uri: URI; + readonly description: string; +} + +export interface IChatRelatedFilesProvider { + readonly description: string; + provideRelatedFiles(chatRequest: IChatRequestDraft, token: CancellationToken): Promise; } +export interface WorkingSetDisplayMetadata { state: WorkingSetEntryState; description?: string } + export interface IChatEditingSession { readonly chatSessionId: string; - readonly onDidChange: Event; + readonly onDidChange: Event; readonly onDidDispose: Event; readonly state: IObservable; readonly entries: IObservable; readonly hiddenRequestIds: IObservable; - readonly workingSet: ResourceMap; + readonly workingSet: ResourceMap; readonly isVisible: boolean; - addFileToWorkingSet(uri: URI): void; + addFileToWorkingSet(uri: URI, description?: string, kind?: WorkingSetEntryState.Transient | WorkingSetEntryState.Suggested): void; show(): Promise; remove(...uris: URI[]): void; accept(...uris: URI[]): Promise; @@ -72,7 +97,13 @@ export const enum WorkingSetEntryState { Rejected, Transient, Attached, - Sent, + Sent, // TODO@joyceerhl remove this + Suggested, +} + +export const enum ChatEditingSessionChangeType { + WorkingSet, + Other, } export interface IModifiedFileEntry { @@ -81,6 +112,7 @@ export interface IModifiedFileEntry { readonly modifiedURI: URI; readonly state: IObservable; readonly isCurrentlyBeingModified: IObservable; + readonly rewriteRatio: IObservable; readonly diffInfo: IObservable; readonly lastModifyingRequestId: string; accept(transaction: ITransaction | undefined): Promise; @@ -88,7 +120,7 @@ export interface IModifiedFileEntry { } export interface IChatEditingSessionStream { - textEdits(resource: URI, textEdits: TextEdit[], responseModel: IChatResponseModel): void; + textEdits(resource: URI, textEdits: TextEdit[], isLastEdits: boolean, responseModel: IChatResponseModel): void; } export const enum ChatEditingSessionState { @@ -105,7 +137,6 @@ export const decidedChatEditingResourceContextKey = new RawContextKey( export const chatEditingResourceContextKey = new RawContextKey('chatEditingResource', undefined); export const inChatEditingSessionContextKey = new RawContextKey('inChatEditingSession', undefined); export const applyingChatEditsContextKey = new RawContextKey('isApplyingChatEdits', undefined); -export const isChatRequestCheckpointed = new RawContextKey('isChatRequestCheckpointed', false); export const hasUndecidedChatEditingResourceContextKey = new RawContextKey('hasUndecidedChatEditingResource', false); export const hasAppliedChatEditsContextKey = new RawContextKey('hasAppliedChatEdits', false); export const applyingChatEditsFailedContextKey = new RawContextKey('applyingChatEditsFailed', false); @@ -117,3 +148,12 @@ export const enum ChatEditKind { Created, Modified, } + +export interface IChatEditingActionContext { + // The chat session ID that this editing session is associated with + sessionId: string; +} + +export function isChatEditingActionContext(thing: unknown): thing is IChatEditingActionContext { + return typeof thing === 'object' && !!thing && 'sessionId' in thing; +} diff --git a/src/vs/workbench/contrib/chat/common/chatInstallEntitlement.contribution.ts b/src/vs/workbench/contrib/chat/common/chatInstallEntitlement.contribution.ts deleted file mode 100644 index be612959e179e..0000000000000 --- a/src/vs/workbench/contrib/chat/common/chatInstallEntitlement.contribution.ts +++ /dev/null @@ -1,153 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { IWorkbenchContribution, registerWorkbenchContribution2, WorkbenchPhase } from '../../../common/contributions.js'; -import { Disposable, toDisposable } from '../../../../base/common/lifecycle.js'; -import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; -import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; -import { AuthenticationSession, IAuthenticationService } from '../../../services/authentication/common/authentication.js'; -import { IProductService } from '../../../../platform/product/common/productService.js'; -import { IExtensionManagementService } from '../../../../platform/extensionManagement/common/extensionManagement.js'; -import { ExtensionIdentifier } from '../../../../platform/extensions/common/extensions.js'; -import { IExtensionService } from '../../../services/extensions/common/extensions.js'; -import { IRequestService, asText } from '../../../../platform/request/common/request.js'; -import { CancellationTokenSource } from '../../../../base/common/cancellation.js'; -import { CONTEXT_CHAT_INSTALL_ENTITLED } from './chatContextKeys.js'; -import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; - -// TODO@bpasero revisit this flow - -type ChatInstallEntitlementEnablementClassification = { - entitled: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Flag indicating if the user is chat install entitled' }; - owner: 'bpasero'; - comment: 'Reporting if the user is chat install entitled'; -}; - -type ChatInstallEntitlementEnablementEvent = { - entitled: boolean; -}; - -class ChatInstallEntitlementContribution extends Disposable implements IWorkbenchContribution { - - private static readonly CHAT_EXTENSION_INSTALLED_KEY = 'chat.extensionInstalled'; - - private readonly chatInstallEntitledContextKey = CONTEXT_CHAT_INSTALL_ENTITLED.bindTo(this.contextService); - - private resolvedEntitlement: boolean | undefined = undefined; - - constructor( - @IContextKeyService private readonly contextService: IContextKeyService, - @ITelemetryService private readonly telemetryService: ITelemetryService, - @IAuthenticationService private readonly authenticationService: IAuthenticationService, - @IProductService private readonly productService: IProductService, - @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, - @IExtensionService private readonly extensionService: IExtensionService, - @IRequestService private readonly requestService: IRequestService, - @IStorageService private readonly storageService: IStorageService - ) { - super(); - - if (!this.productService.gitHubEntitlement) { - return; - } - - this.checkExtensionInstallation(); - this.registerListeners(); - } - - private async checkExtensionInstallation(): Promise { - const extensions = await this.extensionManagementService.getInstalled(); - - const installed = extensions.find(value => ExtensionIdentifier.equals(value.identifier.id, this.productService.gitHubEntitlement?.extensionId)); - this.updateExtensionInstalled(installed ? true : false); - } - - private registerListeners(): void { - this._register(this.extensionService.onDidChangeExtensions(result => { - for (const extension of result.removed) { - if (ExtensionIdentifier.equals(this.productService.gitHubEntitlement?.extensionId, extension.identifier)) { - this.updateExtensionInstalled(false); - break; - } - } - - for (const extension of result.added) { - if (ExtensionIdentifier.equals(this.productService.gitHubEntitlement?.extensionId, extension.identifier)) { - this.updateExtensionInstalled(true); - break; - } - } - })); - - this._register(this.authenticationService.onDidChangeSessions(async e => { - if (e.providerId === this.productService.gitHubEntitlement?.providerId) { - if (e.event.added?.length) { - this.resolveEntitlement(e.event.added[0]); - } else if (e.event.removed?.length) { - this.chatInstallEntitledContextKey.set(false); - } - } - })); - - this._register(this.authenticationService.onDidRegisterAuthenticationProvider(async e => { - if (e.id === this.productService.gitHubEntitlement?.providerId) { - this.resolveEntitlement((await this.authenticationService.getSessions(e.id))[0]); - } - })); - } - - private async resolveEntitlement(session: AuthenticationSession | undefined): Promise { - if (!session) { - return; - } - - const entitled = await this.doResolveEntitlement(session); - this.chatInstallEntitledContextKey.set(entitled); - } - - private async doResolveEntitlement(session: AuthenticationSession): Promise { - if (typeof this.resolvedEntitlement === 'boolean') { - return this.resolvedEntitlement; - } - - const cts = new CancellationTokenSource(); - this._register(toDisposable(() => cts.dispose(true))); - - const context = await this.requestService.request({ - type: 'GET', - url: this.productService.gitHubEntitlement!.entitlementUrl, - headers: { - 'Authorization': `Bearer ${session.accessToken}` - } - }, cts.token); - - if (context.res.statusCode && context.res.statusCode !== 200) { - return false; - } - - const result = await asText(context); - if (!result) { - return false; - } - - let parsedResult: any; - try { - parsedResult = JSON.parse(result); - } catch (err) { - return false; //ignore - } - - this.resolvedEntitlement = Boolean(parsedResult[this.productService.gitHubEntitlement!.enablementKey]); - this.telemetryService.publicLog2('chatInstallEntitlement', { entitled: this.resolvedEntitlement }); - - return this.resolvedEntitlement; - } - - private updateExtensionInstalled(isExtensionInstalled: boolean): void { - this.storageService.store(ChatInstallEntitlementContribution.CHAT_EXTENSION_INSTALLED_KEY, isExtensionInstalled, StorageScope.PROFILE, StorageTarget.MACHINE); - } -} - -registerWorkbenchContribution2('workbench.chat.installEntitlement', ChatInstallEntitlementContribution, WorkbenchPhase.BlockRestore); diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index 6e32e1b7f93e5..f3a9d57ac4cdc 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -36,24 +36,38 @@ export interface IBaseChatRequestVariableEntry { mimeType?: string; // TODO these represent different kinds, should be extracted to new interfaces with kind tags - kind?: unknown; + kind?: never; /** * True if the variable has a value vs being a reference to a variable */ isDynamic?: boolean; isFile?: boolean; + isDirectory?: boolean; isTool?: boolean; isImage?: boolean; } export interface IChatRequestImplicitVariableEntry extends Omit { readonly kind: 'implicit'; + readonly isDynamic: true; + readonly isFile: true; readonly value: URI | Location | undefined; readonly isSelection: boolean; enabled: boolean; } -export type IChatRequestVariableEntry = IChatRequestImplicitVariableEntry | IBaseChatRequestVariableEntry; +export interface ISymbolVariableEntry extends Omit { + readonly kind: 'symbol'; + readonly isDynamic: true; + readonly value: Location; +} + +export interface ICommandResultVariableEntry extends Omit { + readonly kind: 'command'; + readonly isDynamic: true; +} + +export type IChatRequestVariableEntry = IChatRequestImplicitVariableEntry | ISymbolVariableEntry | ICommandResultVariableEntry | IBaseChatRequestVariableEntry; export function isImplicitVariableEntry(obj: IChatRequestVariableEntry): obj is IChatRequestImplicitVariableEntry { return obj.kind === 'implicit'; @@ -85,7 +99,6 @@ export interface IChatRequestModel { readonly workingSet?: URI[]; readonly isCompleteAddedRequest: boolean; readonly response?: IChatResponseModel; - isDisabled: boolean; isHidden: boolean; } @@ -99,6 +112,7 @@ export interface IChatTextEditGroup { edits: TextEdit[][]; state?: IChatTextEditGroupState; kind: 'textEditGroup'; + done: boolean | undefined; } /** @@ -160,7 +174,6 @@ export interface IChatResponseModel { readonly response: IResponse; readonly isComplete: boolean; readonly isCanceled: boolean; - isDisabled: boolean; readonly isHidden: boolean; readonly isCompleteAddedRequest: boolean; /** A stale response is one that has been persisted and rehydrated, so e.g. Commands that have their arguments stored in the EH are gone. */ @@ -179,8 +192,6 @@ export class ChatRequestModel implements IChatRequestModel { public response: ChatResponseModel | undefined; - public isDisabled: boolean = false; - public readonly id: string; public get session() { @@ -310,25 +321,26 @@ export class Response extends Disposable implements IResponse { } this._updateRepr(quiet); } else if (progress.kind === 'textEdit') { - if (progress.edits.length > 0) { - // merge text edits for the same file no matter when they come in - let found = false; - for (let i = 0; !found && i < this._responseParts.length; i++) { - const candidate = this._responseParts[i]; - if (candidate.kind === 'textEditGroup' && isEqual(candidate.uri, progress.uri)) { - candidate.edits.push(progress.edits); - found = true; - } - } - if (!found) { - this._responseParts.push({ - kind: 'textEditGroup', - uri: progress.uri, - edits: [progress.edits] - }); + // merge text edits for the same file no matter when they come in + let found = false; + for (let i = 0; !found && i < this._responseParts.length; i++) { + const candidate = this._responseParts[i]; + if (candidate.kind === 'textEditGroup' && isEqual(candidate.uri, progress.uri)) { + candidate.edits.push(progress.edits); + candidate.done = progress.done; + found = true; } - this._updateRepr(quiet); } + if (!found) { + this._responseParts.push({ + kind: 'textEditGroup', + uri: progress.uri, + edits: [progress.edits], + done: progress.done + }); + } + this._updateRepr(quiet); + } else if (progress.kind === 'progressTask') { // Add a new resolving part const responsePosition = this._responseParts.push(progress) - 1; @@ -420,10 +432,6 @@ export class ChatResponseModel extends Disposable implements IChatResponseModel return this._session; } - public get isDisabled() { - return this._isDisabled; - } - public get isHidden() { return this._isHidden; } @@ -522,7 +530,6 @@ export class ChatResponseModel extends Disposable implements IChatResponseModel private _voteDownReason?: ChatAgentVoteDownReason, private _result?: IChatAgentResult, followups?: ReadonlyArray, - private _isDisabled: boolean = false, public readonly isCompleteAddedRequest = false, private _isHidden: boolean = false ) { @@ -534,16 +541,6 @@ export class ChatResponseModel extends Disposable implements IChatResponseModel this._followups = followups ? [...followups] : undefined; this._response = this._register(new Response(_response)); this._register(this._response.onDidChangeValue(() => this._onDidChange.fire())); - this._register(this._session.onDidChange((e) => { - if (e.kind === 'setCheckpoint') { - const isDisabled = e.disabledResponseIds.has(this.id); - const didChange = this._isDisabled !== isDisabled; - this._isDisabled = isDisabled; - if (didChange) { - this._onDidChange.fire(); - } - } - })); this.id = 'response_' + ChatResponseModel.nextId++; } @@ -575,7 +572,7 @@ export class ChatResponseModel extends Disposable implements IChatResponseModel setAgent(agent: IChatAgentData, slashCommand?: IChatAgentCommand) { this._agent = agent; this._slashCommand = slashCommand; - this._agentOrSlashCommandDetected = true; + this._agentOrSlashCommandDetected = !agent.isDefault; this._onDidChange.fire(); } @@ -643,10 +640,8 @@ export interface IChatModel { readonly sampleQuestions: IChatFollowup[] | undefined; readonly requestInProgress: boolean; readonly inputPlaceholder?: string; - readonly checkpoint: IChatRequestModel | undefined; - setCheckpoint(requestId: string | undefined): void; disableRequests(requestIds: ReadonlyArray): void; - getRequests(includeDisabledRequests?: boolean): IChatRequestModel[]; + getRequests(): IChatRequestModel[]; toExport(): IExportableChatData; toJSON(): ISerializableChatData; } @@ -792,7 +787,6 @@ export type IChatChangeEvent = | IChatAddResponseEvent | IChatSetAgentEvent | IChatMoveEvent - | IChatSetCheckpointEvent | IChatSetHiddenEvent ; @@ -835,12 +829,6 @@ export interface IChatRemoveRequestEvent { reason: ChatRequestRemovalReason; } -export interface IChatSetCheckpointEvent { - kind: 'setCheckpoint'; - disabledRequestIds: Set; - disabledResponseIds: Set; -} - export interface IChatSetHiddenEvent { kind: 'setHidden'; hiddenRequestIds: Set; @@ -1019,7 +1007,7 @@ export class ChatModel extends Disposable implements IChatModel { const result = 'responseErrorDetails' in raw ? // eslint-disable-next-line local/code-no-dangerous-type-assertions { errorDetails: raw.responseErrorDetails } as IChatAgentResult : raw.result; - request.response = new ChatResponseModel(raw.response ?? [new MarkdownString(raw.response)], this, agent, raw.slashCommand, request.id, true, raw.isCanceled, raw.vote, raw.voteDownReason, result, raw.followups, request.isDisabled); + request.response = new ChatResponseModel(raw.response ?? [new MarkdownString(raw.response)], this, agent, raw.slashCommand, request.id, true, raw.isCanceled, raw.vote, raw.voteDownReason, result, raw.followups); if (raw.usedContext) { // @ulugbekna: if this's a new vscode sessions, doc versions are incorrect anyway? request.response.applyReference(revive(raw.usedContext)); } @@ -1108,19 +1096,8 @@ export class ChatModel extends Disposable implements IChatModel { return this._isInitializedDeferred.p; } - getRequests(includeDisabledRequests = true): ChatRequestModel[] { - if (includeDisabledRequests) { - return this._requests; - } - - const requests: ChatRequestModel[] = []; - for (const request of this._requests) { - if (request.isDisabled) { - break; - } - requests.push(request); - } - return requests; + getRequests(): ChatRequestModel[] { + return this._requests; } private _checkpoint: ChatRequestModel | undefined = undefined; @@ -1128,48 +1105,6 @@ export class ChatModel extends Disposable implements IChatModel { return this._checkpoint; } - setCheckpoint(requestId: string | undefined) { - let checkpoint: ChatRequestModel | undefined; - let checkpointIndex = -1; - if (requestId !== undefined) { - this._requests.forEach((request, index) => { - if (request.id === requestId) { - checkpointIndex = index; - checkpoint = request; - } - }); - - if (!checkpoint) { - return; // Invalid request ID - } - } - - const disabledRequestIds = new Set(); - const disabledResponseIds = new Set(); - for (let i = this._requests.length - 1; i >= 0; i -= 1) { - const request = this._requests[i]; - if (this._checkpoint && !checkpoint) { - // The user removed the checkpoint - request.isDisabled = false; - } else if (checkpoint && i > checkpointIndex) { - request.isDisabled = true; - disabledRequestIds.add(request.id); - if (request.response) { - disabledResponseIds.add(request.response.id); - } - } else if (checkpoint && i <= checkpointIndex) { - request.isDisabled = false; - } - } - - this._checkpoint = checkpoint; - this._onDidChange.fire({ - kind: 'setCheckpoint', - disabledRequestIds, - disabledResponseIds - }); - } - disableRequests(requestIds: ReadonlyArray) { this._requests.forEach((request) => { const isHidden = requestIds.includes(request.id); diff --git a/src/vs/workbench/contrib/chat/common/chatProgressTypes/chatToolInvocation.ts b/src/vs/workbench/contrib/chat/common/chatProgressTypes/chatToolInvocation.ts index 4c7bb0902f44b..7632ee2f0b6cd 100644 --- a/src/vs/workbench/contrib/chat/common/chatProgressTypes/chatToolInvocation.ts +++ b/src/vs/workbench/contrib/chat/common/chatProgressTypes/chatToolInvocation.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { DeferredPromise } from '../../../../../base/common/async.js'; +import { IMarkdownString } from '../../../../../base/common/htmlContent.js'; import { IChatToolInvocation, IChatToolInvocationSerialized } from '../chatService.js'; import { IToolConfirmationMessages } from '../languageModelToolsService.js'; @@ -36,7 +37,7 @@ export class ChatToolInvocation implements IChatToolInvocation { } constructor( - public readonly invocationMessage: string, + public readonly invocationMessage: string | IMarkdownString, private _confirmationMessages: IToolConfirmationMessages | undefined) { if (!_confirmationMessages) { // No confirmation needed diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index 80ec93ae6c73b..c4ec8a00582f9 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -183,6 +183,7 @@ export interface IChatTextEdit { uri: URI; edits: TextEdit[]; kind: 'textEdit'; + done?: boolean; } export interface IChatConfirmation { @@ -200,7 +201,7 @@ export interface IChatToolInvocation { confirmed: DeferredPromise; /** A 3-way: undefined=don't know yet. */ isConfirmed: boolean | undefined; - invocationMessage: string; + invocationMessage: string | IMarkdownString; isComplete: boolean; isCompleteDeferred: DeferredPromise; @@ -211,7 +212,7 @@ export interface IChatToolInvocation { * This is a IChatToolInvocation that has been serialized, like after window reload, so it is no longer an active tool invocation. */ export interface IChatToolInvocationSerialized { - invocationMessage: string; + invocationMessage: string | IMarkdownString; isConfirmed: boolean; isComplete: boolean; kind: 'toolInvocationSerialized'; @@ -332,7 +333,7 @@ export interface IChatEditingSessionAction { kind: 'chatEditingSessionAction'; uri: URI; hasRemainingEdits: boolean; - outcome: 'accepted' | 'rejected'; + outcome: 'accepted' | 'rejected' | 'saved'; } export type ChatUserAction = IChatVoteAction | IChatCopyAction | IChatInsertAction | IChatApplyAction | IChatTerminalAction | IChatCommandAction | IChatFollowupAction | IChatBugReportAction | IChatInlineChatCodeAction | IChatEditingSessionAction; diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 957b924667f98..4de49985649bc 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -14,16 +14,15 @@ import { Disposable, DisposableMap, IDisposable } from '../../../../base/common/ import { revive } from '../../../../base/common/marshalling.js'; import { StopWatch } from '../../../../base/common/stopwatch.js'; import { URI, UriComponents } from '../../../../base/common/uri.js'; +import { isLocation } from '../../../../editor/common/languages.js'; import { localize } from '../../../../nls.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; -import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { ILogService } from '../../../../platform/log/common/log.js'; import { Progress } from '../../../../platform/progress/common/progress.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js'; -import { IWorkbenchAssignmentService } from '../../../services/assignment/common/assignmentService.js'; import { IExtensionService } from '../../../services/extensions/common/extensions.js'; import { ChatAgentLocation, IChatAgent, IChatAgentCommand, IChatAgentData, IChatAgentHistoryEntry, IChatAgentRequest, IChatAgentResult, IChatAgentService } from './chatAgents.js'; import { ChatModel, ChatRequestModel, ChatRequestRemovalReason, IChatModel, IChatRequestModel, IChatRequestVariableData, IChatResponseModel, IExportableChatData, ISerializableChatData, ISerializableChatDataIn, ISerializableChatsData, normalizeSerializableChatData, toChatHistoryContent, updateRanges } from './chatModel.js'; @@ -60,6 +59,7 @@ type ChatProviderInvokedEvent = { numCodeBlocks: number; isParticipantDetected: boolean; enableCommandDetection: boolean; + attachmentKinds: string[]; }; type ChatProviderInvokedClassification = { @@ -76,6 +76,7 @@ type ChatProviderInvokedClassification = { numCodeBlocks: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The number of code blocks in the response.' }; isParticipantDetected: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the participant was automatically detected.' }; enableCommandDetection: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether participation detection was disabled for this invocation.' }; + attachmentKinds: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The types of variables/attachments that the user included with their query.' }; owner: 'roblourens'; comment: 'Provides insight into the performance of Chat agents.'; }; @@ -131,8 +132,6 @@ export class ChatService extends Disposable implements IChatService { @IChatSlashCommandService private readonly chatSlashCommandService: IChatSlashCommandService, @IChatVariablesService private readonly chatVariablesService: IChatVariablesService, @IChatAgentService private readonly chatAgentService: IChatAgentService, - @IWorkbenchAssignmentService workbenchAssignmentService: IWorkbenchAssignmentService, - @IContextKeyService contextKeyService: IContextKeyService, @IConfigurationService private readonly configurationService: IConfigurationService ) { super(); @@ -167,7 +166,7 @@ export class ChatService extends Disposable implements IChatService { private saveState(): void { const liveChats = Array.from(this._sessionModels.values()) - .filter(session => session.initialLocation === ChatAgentLocation.Panel) + .filter(session => session.initialLocation === ChatAgentLocation.Panel || session.initialLocation === ChatAgentLocation.EditingSession) .filter(session => session.getRequests().length > 0); const isEmptyWindow = !this.workspaceContextService.getWorkspace().folders.length; @@ -539,7 +538,7 @@ export class ChatService extends Disposable implements IChatService { const agentPart = 'kind' in parsedRequest ? undefined : parsedRequest.parts.find((r): r is ChatRequestAgentPart => r instanceof ChatRequestAgentPart); const agentSlashCommandPart = 'kind' in parsedRequest ? undefined : parsedRequest.parts.find((r): r is ChatRequestAgentSubcommandPart => r instanceof ChatRequestAgentSubcommandPart); const commandPart = 'kind' in parsedRequest ? undefined : parsedRequest.parts.find((r): r is ChatRequestSlashCommandPart => r instanceof ChatRequestSlashCommandPart); - const requests = [...model.getRequests(false)]; + const requests = [...model.getRequests()]; let gotProgress = false; const requestType = commandPart ? 'slashCommand' : 'string'; @@ -585,15 +584,16 @@ export class ChatService extends Disposable implements IChatService { totalTime: stopWatch.elapsed(), result: 'cancelled', requestType, - agent: agentPart?.agent.id ?? '', - agentExtensionId: agentPart?.agent.extensionId.value ?? '', + agent: detectedAgent?.id ?? agentPart?.agent.id ?? '', + agentExtensionId: detectedAgent?.extensionId.value ?? agentPart?.agent.extensionId.value ?? '', slashCommand: agentSlashCommandPart ? agentSlashCommandPart.command.name : commandPart?.slashCommand.command, chatSessionId: model.sessionId, location, citations: request?.response?.codeCitations.length ?? 0, numCodeBlocks: getCodeBlocks(request.response?.response.toString() ?? '').length, isParticipantDetected: !!detectedAgent, - enableCommandDetection + enableCommandDetection, + attachmentKinds: this.attachmentKindsForTelemetry(request.variableData) }); model.cancelRequest(request); @@ -646,7 +646,7 @@ export class ChatService extends Disposable implements IChatService { } satisfies IChatAgentRequest; }; - if (this.configurationService.getValue('chat.experimental.detectParticipant.enabled') !== false && this.chatAgentService.hasChatParticipantDetectionProviders() && !agentPart && !commandPart && enableCommandDetection) { + if (this.configurationService.getValue('chat.detectParticipant.enabled') !== false && this.chatAgentService.hasChatParticipantDetectionProviders() && !agentPart && !commandPart && enableCommandDetection) { // We have no agent or command to scope history with, pass the full history to the participant detection provider const defaultAgentHistory = this.getHistoryEntriesFromModel(requests, model.sessionId, location, defaultAgent.id); @@ -677,14 +677,14 @@ export class ChatService extends Disposable implements IChatService { const agentResult = await this.chatAgentService.invokeAgent(agent.id, requestProps, progressCallback, history, token); rawResult = agentResult; agentOrCommandFollowups = this.chatAgentService.getFollowups(agent.id, requestProps, agentResult, history, followupsCancelToken); - chatTitlePromise = model.getRequests(false).length === 1 && !model.customTitle ? this.chatAgentService.getChatTitle(defaultAgent.id, this.getHistoryEntriesFromModel(model.getRequests(false), model.sessionId, location, agent.id), CancellationToken.None) : undefined; + chatTitlePromise = model.getRequests().length === 1 && !model.customTitle ? this.chatAgentService.getChatTitle(defaultAgent.id, this.getHistoryEntriesFromModel(model.getRequests(), model.sessionId, location, agent.id), CancellationToken.None) : undefined; } else if (commandPart && this.chatSlashCommandService.hasCommand(commandPart.slashCommand.command)) { request = model.addRequest(parsedRequest, { variables: [] }, attempt); completeResponseCreated(); // contributed slash commands // TODO: spell this out in the UI const history: IChatMessage[] = []; - for (const request of model.getRequests(false)) { + for (const request of model.getRequests()) { if (!request.response) { continue; } @@ -720,15 +720,16 @@ export class ChatService extends Disposable implements IChatService { totalTime: rawResult.timings?.totalElapsed, result, requestType, - agent: agentPart?.agent.id ?? '', - agentExtensionId: agentPart?.agent.extensionId.value ?? '', + agent: detectedAgent?.id ?? agentPart?.agent.id ?? '', + agentExtensionId: detectedAgent?.extensionId.value ?? agentPart?.agent.extensionId.value ?? '', slashCommand: commandForTelemetry, chatSessionId: model.sessionId, enableCommandDetection, isParticipantDetected: !!detectedAgent, location, citations: request.response?.codeCitations.length ?? 0, - numCodeBlocks: getCodeBlocks(request.response?.response.toString() ?? '').length + numCodeBlocks: getCodeBlocks(request.response?.response.toString() ?? '').length, + attachmentKinds: this.attachmentKindsForTelemetry(request.variableData) }); model.setResponse(request, rawResult); completeResponseCreated(); @@ -754,15 +755,16 @@ export class ChatService extends Disposable implements IChatService { totalTime: undefined, result, requestType, - agent: agentPart?.agent.id ?? '', - agentExtensionId: agentPart?.agent.extensionId.value ?? '', + agent: detectedAgent?.id ?? agentPart?.agent.id ?? '', + agentExtensionId: detectedAgent?.extensionId.value ?? agentPart?.agent.extensionId.value ?? '', slashCommand: agentSlashCommandPart ? agentSlashCommandPart.command.name : commandPart?.slashCommand.command, chatSessionId: model.sessionId, location, citations: 0, numCodeBlocks: 0, enableCommandDetection, - isParticipantDetected: !!detectedAgent + isParticipantDetected: !!detectedAgent, + attachmentKinds: this.attachmentKindsForTelemetry(request.variableData) }); this.logService.error(`Error while handling chat request: ${toErrorMessage(err, true)}`); if (request) { @@ -786,6 +788,44 @@ export class ChatService extends Disposable implements IChatService { }; } + private attachmentKindsForTelemetry(variableData: IChatRequestVariableData): string[] { + // TODO this shows why attachments still have to be cleaned up somewhat + return variableData.variables.map(v => { + if (v.kind === 'implicit') { + return 'implicit'; + } else if (v.range) { + // 'range' is range within the prompt text + if (v.isTool) { + return 'toolInPrompt'; + } else if (v.isDynamic) { + return 'fileInPrompt'; + } else { + return 'variableInPrompt'; + } + } else if (v.kind === 'command') { + return 'command'; + } else if (v.kind === 'symbol') { + return 'symbol'; + } else if (v.isImage) { + return 'image'; + } else if (v.isDirectory) { + return 'directory'; + } else if (v.isTool) { + return 'tool'; + } else if (v.isDynamic) { + if (URI.isUri(v.value)) { + return 'file'; + } else if (isLocation(v.value)) { + return 'location'; + } else { + return 'otherAttachment'; + } + } else { + return 'variableAttachment'; + } + }); + } + private getHistoryEntriesFromModel(requests: IChatRequestModel[], sessionId: string, location: ChatAgentLocation, forAgentId: string): IChatAgentHistoryEntry[] { const history: IChatAgentHistoryEntry[] = []; for (const request of requests) { diff --git a/src/vs/workbench/contrib/chat/common/chatViewModel.ts b/src/vs/workbench/contrib/chat/common/chatViewModel.ts index be75cae4eed2a..1f7578eeba43f 100644 --- a/src/vs/workbench/contrib/chat/common/chatViewModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatViewModel.ts @@ -28,7 +28,7 @@ export function isResponseVM(item: unknown): item is IChatResponseViewModel { return !!item && typeof (item as IChatResponseViewModel).setVote !== 'undefined'; } -export type IChatViewModelChangeEvent = IChatAddRequestEvent | IChangePlaceholderEvent | IChatSessionInitEvent | IChatSetCheckpointEvent | IChatSetHiddenEvent | null; +export type IChatViewModelChangeEvent = IChatAddRequestEvent | IChangePlaceholderEvent | IChatSessionInitEvent | IChatSetHiddenEvent | null; export interface IChatAddRequestEvent { kind: 'addRequest'; @@ -42,10 +42,6 @@ export interface IChatSessionInitEvent { kind: 'initialize'; } -export interface IChatSetCheckpointEvent { - kind: 'setCheckpoint'; -} - export interface IChatSetHiddenEvent { kind: 'setHidden'; } @@ -78,8 +74,8 @@ export interface IChatRequestViewModel { readonly contentReferences?: ReadonlyArray; readonly workingSet?: ReadonlyArray; readonly confirmation?: string; - readonly isDisabled?: boolean; readonly isHidden: boolean; + readonly isComplete: boolean; readonly isCompleteAddedRequest: boolean; } @@ -182,7 +178,6 @@ export interface IChatResponseViewModel { readonly errorDetails?: IChatResponseErrorDetails; readonly result?: IChatAgentResult; readonly contentUpdateTimings?: IChatLiveUpdateData; - readonly isDisabled: boolean; readonly isHidden: boolean; readonly isCompleteAddedRequest: boolean; renderData?: IChatResponseRenderData; @@ -283,9 +278,8 @@ export class ChatViewModel extends Disposable implements IChatViewModel { const modelEventToVmEvent: IChatViewModelChangeEvent = e.kind === 'addRequest' ? { kind: 'addRequest' } : e.kind === 'initialize' ? { kind: 'initialize' } - : e.kind === 'setCheckpoint' ? { kind: 'setCheckpoint' } - : e.kind === 'setHidden' ? { kind: 'setHidden' } - : null; + : e.kind === 'setHidden' ? { kind: 'setHidden' } + : null; this._onDidChange.fire(modelEventToVmEvent); })); } @@ -326,7 +320,7 @@ export class ChatViewModel extends Disposable implements IChatViewModel { if (token.type === 'code') { const lang = token.lang || ''; const text = token.text; - this.codeBlockModelCollection.update(this._model.sessionId, model, codeBlockIndex++, { text, languageId: lang }); + this.codeBlockModelCollection.update(this._model.sessionId, model, codeBlockIndex++, { text, languageId: lang, isComplete: true }); } }); } @@ -338,7 +332,7 @@ export class ChatRequestViewModel implements IChatRequestViewModel { } get dataId() { - return this.id + `_${ChatModelInitState[this._model.session.initState]}_${hash(this.variables)}`; + return this.id + `_${ChatModelInitState[this._model.session.initState]}_${hash(this.variables)}_${hash(this.isComplete)}`; } get sessionId() { @@ -381,8 +375,8 @@ export class ChatRequestViewModel implements IChatRequestViewModel { return this._model.confirmation; } - get isDisabled() { - return this._model.isDisabled; + get isComplete() { + return this._model.response?.isComplete ?? false; } get isCompleteAddedRequest() { @@ -482,10 +476,6 @@ export class ChatResponseViewModel extends Disposable implements IChatResponseVi return this._model.isCanceled; } - get isDisabled() { - return this._model.isDisabled; - } - get isHidden() { return this._model.isHidden; } diff --git a/src/vs/workbench/contrib/chat/common/codeBlockModelCollection.ts b/src/vs/workbench/contrib/chat/common/codeBlockModelCollection.ts index cf9e2e32987f8..7dd9973b03b3d 100644 --- a/src/vs/workbench/contrib/chat/common/codeBlockModelCollection.ts +++ b/src/vs/workbench/contrib/chat/common/codeBlockModelCollection.ts @@ -3,22 +3,34 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { Iterable } from '../../../../base/common/iterator.js'; import { Disposable, IReference } from '../../../../base/common/lifecycle.js'; -import { ResourceMap } from '../../../../base/common/map.js'; import { Schemas } from '../../../../base/common/network.js'; import { URI } from '../../../../base/common/uri.js'; import { Range } from '../../../../editor/common/core/range.js'; import { ILanguageService } from '../../../../editor/common/languages/language.js'; -import { EndOfLinePreference } from '../../../../editor/common/model.js'; +import { EndOfLinePreference, ITextModel } from '../../../../editor/common/model.js'; import { IResolvedTextEditorModel, ITextModelService } from '../../../../editor/common/services/resolverService.js'; import { extractCodeblockUrisFromText, extractVulnerabilitiesFromText, IMarkdownVulnerability } from './annotations.js'; import { IChatRequestViewModel, IChatResponseViewModel, isResponseVM } from './chatViewModel.js'; +interface CodeBlockContent { + readonly text: string; + readonly languageId?: string; + readonly isComplete: boolean; +} + +interface CodeBlockEntry { + readonly model: Promise; + readonly vulns: readonly IMarkdownVulnerability[]; + readonly codemapperUri?: URI; +} + export class CodeBlockModelCollection extends Disposable { - private readonly _models = new ResourceMap<{ - readonly model: Promise>; + private readonly _models = new Map>; vulns: readonly IMarkdownVulnerability[]; codemapperUri?: URI; }>(); @@ -32,7 +44,7 @@ export class CodeBlockModelCollection extends Disposable { constructor( @ILanguageService private readonly languageService: ILanguageService, - @ITextModelService private readonly textModelService: ITextModelService + @ITextModelService private readonly textModelService: ITextModelService, ) { super(); } @@ -42,44 +54,52 @@ export class CodeBlockModelCollection extends Disposable { this.clear(); } - get(sessionId: string, chat: IChatRequestViewModel | IChatResponseViewModel, codeBlockIndex: number): { model: Promise; readonly vulns: readonly IMarkdownVulnerability[]; readonly codemapperUri?: URI } | undefined { - const uri = this.getUri(sessionId, chat, codeBlockIndex); - const entry = this._models.get(uri); + get(sessionId: string, chat: IChatRequestViewModel | IChatResponseViewModel, codeBlockIndex: number): CodeBlockEntry | undefined { + const entry = this._models.get(this.getKey(sessionId, chat, codeBlockIndex)); if (!entry) { return; } - return { model: entry.model.then(ref => ref.object), vulns: entry.vulns, codemapperUri: entry.codemapperUri }; + return { + model: entry.model.then(ref => ref.object.textEditorModel), + vulns: entry.vulns, + codemapperUri: entry.codemapperUri + }; } - getOrCreate(sessionId: string, chat: IChatRequestViewModel | IChatResponseViewModel, codeBlockIndex: number): { model: Promise; readonly vulns: readonly IMarkdownVulnerability[]; readonly codemapperUri?: URI } { + getOrCreate(sessionId: string, chat: IChatRequestViewModel | IChatResponseViewModel, codeBlockIndex: number): CodeBlockEntry { const existing = this.get(sessionId, chat, codeBlockIndex); if (existing) { return existing; } - const uri = this.getUri(sessionId, chat, codeBlockIndex); - const ref = this.textModelService.createModelReference(uri); - this._models.set(uri, { model: ref, vulns: [], codemapperUri: undefined }); + const uri = this.getCodeBlockUri(sessionId, chat, codeBlockIndex); + const model = this.textModelService.createModelReference(uri); + this._models.set(this.getKey(sessionId, chat, codeBlockIndex), { + model: model, + vulns: [], + codemapperUri: undefined, + }); while (this._models.size > this.maxModelCount) { - const first = Array.from(this._models.keys()).at(0); + const first = Iterable.first(this._models.keys()); if (!first) { break; } this.delete(first); } - return { model: ref.then(ref => ref.object), vulns: [], codemapperUri: undefined }; + return { model: model.then(x => x.object.textEditorModel), vulns: [], codemapperUri: undefined }; } - private delete(codeBlockUri: URI) { - const entry = this._models.get(codeBlockUri); + private delete(key: string) { + const entry = this._models.get(key); if (!entry) { return; } - entry.model.then(ref => ref.dispose()); - this._models.delete(codeBlockUri); + entry.model.then(ref => ref.object.dispose()); + + this._models.delete(key); } clear(): void { @@ -87,7 +107,7 @@ export class CodeBlockModelCollection extends Disposable { this._models.clear(); } - updateSync(sessionId: string, chat: IChatRequestViewModel | IChatResponseViewModel, codeBlockIndex: number, content: { text: string; languageId?: string }) { + updateSync(sessionId: string, chat: IChatRequestViewModel | IChatResponseViewModel, codeBlockIndex: number, content: CodeBlockContent): CodeBlockEntry { const entry = this.getOrCreate(sessionId, chat, codeBlockIndex); const extractedVulns = extractVulnerabilitiesFromText(content.text); @@ -99,10 +119,22 @@ export class CodeBlockModelCollection extends Disposable { this.setCodemapperUri(sessionId, chat, codeBlockIndex, codeblockUri.uri); } + if (content.isComplete) { + this.markCodeBlockCompleted(sessionId, chat, codeBlockIndex); + } + return this.get(sessionId, chat, codeBlockIndex) ?? entry; } - async update(sessionId: string, chat: IChatRequestViewModel | IChatResponseViewModel, codeBlockIndex: number, content: { text: string; languageId?: string }) { + markCodeBlockCompleted(sessionId: string, chat: IChatRequestViewModel | IChatResponseViewModel, codeBlockIndex: number): void { + const entry = this._models.get(this.getKey(sessionId, chat, codeBlockIndex)); + if (!entry) { + return; + } + // TODO: fill this in once we've implemented https://github.com/microsoft/vscode/issues/232538 + } + + async update(sessionId: string, chat: IChatRequestViewModel | IChatResponseViewModel, codeBlockIndex: number, content: CodeBlockContent): Promise { const entry = this.getOrCreate(sessionId, chat, codeBlockIndex); const extractedVulns = extractVulnerabilitiesFromText(content.text); @@ -115,7 +147,15 @@ export class CodeBlockModelCollection extends Disposable { newText = codeblockUri.textWithoutResult; } - const textModel = (await entry.model).textEditorModel; + if (content.isComplete) { + this.markCodeBlockCompleted(sessionId, chat, codeBlockIndex); + } + + const textModel = await entry.model; + if (textModel.isDisposed()) { + return entry; + } + if (content.languageId) { const vscodeLanguageId = this.languageService.getLanguageIdByLanguageName(content.languageId); if (vscodeLanguageId && vscodeLanguageId !== textModel.getLanguageId()) { @@ -142,22 +182,24 @@ export class CodeBlockModelCollection extends Disposable { } private setCodemapperUri(sessionId: string, chat: IChatRequestViewModel | IChatResponseViewModel, codeBlockIndex: number, codemapperUri: URI) { - const uri = this.getUri(sessionId, chat, codeBlockIndex); - const entry = this._models.get(uri); + const entry = this._models.get(this.getKey(sessionId, chat, codeBlockIndex)); if (entry) { entry.codemapperUri = codemapperUri; } } private setVulns(sessionId: string, chat: IChatRequestViewModel | IChatResponseViewModel, codeBlockIndex: number, vulnerabilities: IMarkdownVulnerability[]) { - const uri = this.getUri(sessionId, chat, codeBlockIndex); - const entry = this._models.get(uri); + const entry = this._models.get(this.getKey(sessionId, chat, codeBlockIndex)); if (entry) { entry.vulns = vulnerabilities; } } - private getUri(sessionId: string, chat: IChatRequestViewModel | IChatResponseViewModel, index: number): URI { + private getKey(sessionId: string, chat: IChatRequestViewModel | IChatResponseViewModel, index: number): string { + return `${sessionId}/${chat.id}/${index}`; + } + + private getCodeBlockUri(sessionId: string, chat: IChatRequestViewModel | IChatResponseViewModel, index: number): URI { const metadata = this.getUriMetaData(chat); return URI.from({ scheme: Schemas.vscodeChatCodeBlock, diff --git a/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts b/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts index b4bb445b7ced3..6699d50ee5963 100644 --- a/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts +++ b/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts @@ -62,7 +62,7 @@ export interface IToolConfirmationMessages { } export interface IPreparedToolInvocation { - invocationMessage?: string; + invocationMessage?: string | IMarkdownString; confirmationMessages?: IToolConfirmationMessages; } diff --git a/src/vs/workbench/contrib/chat/common/languageModels.ts b/src/vs/workbench/contrib/chat/common/languageModels.ts index 7cee12f0e27c6..b3310ea5332db 100644 --- a/src/vs/workbench/contrib/chat/common/languageModels.ts +++ b/src/vs/workbench/contrib/chat/common/languageModels.ts @@ -16,7 +16,7 @@ import { createDecorator } from '../../../../platform/instantiation/common/insta import { ILogService } from '../../../../platform/log/common/log.js'; import { IExtensionService, isProposedApiEnabled } from '../../../services/extensions/common/extensions.js'; import { ExtensionsRegistry } from '../../../services/extensions/common/extensionsRegistry.js'; -import { CONTEXT_LANGUAGE_MODELS_ARE_USER_SELECTABLE } from './chatContextKeys.js'; +import { ChatContextKeys } from './chatContextKeys.js'; export const enum ChatMessageRole { System, @@ -190,7 +190,7 @@ export class LanguageModelsService implements ILanguageModelsService { @ILogService private readonly _logService: ILogService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, ) { - this._hasUserSelectableModels = CONTEXT_LANGUAGE_MODELS_ARE_USER_SELECTABLE.bindTo(this._contextKeyService); + this._hasUserSelectableModels = ChatContextKeys.languageModelsAreUserSelectable.bindTo(this._contextKeyService); this._store.add(languageModelExtensionPoint.setHandler((extensions) => { diff --git a/src/vs/workbench/contrib/chat/common/tools/languageModelToolsContribution.ts b/src/vs/workbench/contrib/chat/common/tools/languageModelToolsContribution.ts index e5593d79c2e55..d48a1b54011bd 100644 --- a/src/vs/workbench/contrib/chat/common/tools/languageModelToolsContribution.ts +++ b/src/vs/workbench/contrib/chat/common/tools/languageModelToolsContribution.ts @@ -27,12 +27,6 @@ export interface IRawToolContribution { tags?: string[]; userDescription?: string; inputSchema?: IJSONSchema; - - /** - * TODO@API backwards compat, remove - * @deprecated - */ - parametersSchema?: IJSONSchema; canBeReferencedInPrompt?: boolean; } @@ -91,7 +85,7 @@ const languageModelToolsExtensionPoint = extensionsRegistry.ExtensionsRegistry.r }, inputSchema: { description: localize('parametersSchema', "A JSON schema for the input this tool accepts. The input must be an object at the top level. A particular language model may not support all JSON schema features. See the documentation for the language model family you are using for more information."), - $ref: toolsParametersSchemaSchemaId, + $ref: toolsParametersSchemaSchemaId }, canBeReferencedInPrompt: { markdownDescription: localize('canBeReferencedInPrompt', "If true, this tool shows up as an attachment that the user can add manually to their request. Chat participants will receive the tool in {0}.", '`ChatRequest#toolReferences`'), @@ -179,7 +173,7 @@ export class LanguageModelToolsExtensionPointHandler implements IWorkbenchContri const tool: IToolData = { ...rawTool, - inputSchema: rawTool.inputSchema ?? rawTool.parametersSchema, // BACKWARDS compatibility + inputSchema: rawTool.inputSchema, id: rawTool.name, icon, when: rawTool.when ? ContextKeyExpr.deserialize(rawTool.when) : undefined, diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts index 3935d4ed23552..198a49bb8696b 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -37,7 +37,7 @@ import { CHAT_CATEGORY } from '../../browser/actions/chatActions.js'; import { IChatExecuteActionContext } from '../../browser/actions/chatExecuteActions.js'; import { IChatWidget, IChatWidgetService, IQuickChatService, showChatView } from '../../browser/chat.js'; import { ChatAgentLocation, IChatAgentService } from '../../common/chatAgents.js'; -import { CONTEXT_CHAT_REQUEST_IN_PROGRESS, CONTEXT_IN_CHAT_INPUT, CONTEXT_CHAT_ENABLED, CONTEXT_RESPONSE, CONTEXT_RESPONSE_FILTERED, CONTEXT_CHAT_LOCATION } from '../../common/chatContextKeys.js'; +import { ChatContextKeys } from '../../common/chatContextKeys.js'; import { KEYWORD_ACTIVIATION_SETTING_ID } from '../../common/chatService.js'; import { ChatResponseViewModel, IChatResponseViewModel, isResponseVM } from '../../common/chatViewModel.js'; import { IVoiceChatService, VoiceChatInProgress as GlobalVoiceChatInProgress } from '../../common/voiceChatService.js'; @@ -46,8 +46,6 @@ import { InlineChatController } from '../../../inlineChat/browser/inlineChatCont import { CTX_INLINE_CHAT_FOCUSED, MENU_INLINE_CHAT_WIDGET_SECONDARY } from '../../../inlineChat/common/inlineChat.js'; import { NOTEBOOK_EDITOR_FOCUSED } from '../../../notebook/common/notebookContextKeys.js'; import { HasSpeechProvider, ISpeechService, KeywordRecognitionStatus, SpeechToTextInProgress, SpeechToTextStatus, TextToSpeechStatus, TextToSpeechInProgress as GlobalTextToSpeechInProgress } from '../../../speech/common/speechService.js'; -import { ITerminalService } from '../../../terminal/browser/terminal.js'; -import { TerminalChatContextKeys, TerminalChatController } from '../../../terminal/terminalContribChatExports.js'; import { IEditorService } from '../../../../services/editor/common/editorService.js'; import { IHostService } from '../../../../services/host/browser/host.js'; import { IWorkbenchLayoutService, Parts } from '../../../../services/layout/browser/layoutService.js'; @@ -59,15 +57,13 @@ import { renderStringAsPlaintext } from '../../../../../base/browser/markdownRen //#region Speech to Text -type VoiceChatSessionContext = 'view' | 'inline' | 'terminal' | 'quick' | 'editor'; -const VoiceChatSessionContexts: VoiceChatSessionContext[] = ['view', 'inline', 'terminal', 'quick', 'editor']; - -const TerminalChatExecute = MenuId.for('terminalChatInput'); // unfortunately, terminal decided to go with their own menu (https://github.com/microsoft/vscode/issues/208789) +type VoiceChatSessionContext = 'view' | 'inline' | 'quick' | 'editor'; +const VoiceChatSessionContexts: VoiceChatSessionContext[] = ['view', 'inline', 'quick', 'editor']; // Global Context Keys (set on global context key service) -const CanVoiceChat = ContextKeyExpr.and(CONTEXT_CHAT_ENABLED, HasSpeechProvider); -const FocusInChatInput = ContextKeyExpr.or(CTX_INLINE_CHAT_FOCUSED, CONTEXT_IN_CHAT_INPUT); -const AnyChatRequestInProgress = ContextKeyExpr.or(CONTEXT_CHAT_REQUEST_IN_PROGRESS, TerminalChatContextKeys.requestActive); +const CanVoiceChat = ContextKeyExpr.and(ChatContextKeys.enabled, HasSpeechProvider); +const FocusInChatInput = ContextKeyExpr.or(CTX_INLINE_CHAT_FOCUSED, ChatContextKeys.inChatInput); +const AnyChatRequestInProgress = ChatContextKeys.requestInProgress; // Scoped Context Keys (set on per-chat-context scoped context key service) const ScopedVoiceChatGettingReady = new RawContextKey('scopedVoiceChatGettingReady', false, { type: 'boolean', description: localize('scopedVoiceChatGettingReady', "True when getting ready for receiving voice input from the microphone for voice chat. This key is only defined scoped, per chat context.") }); @@ -106,12 +102,11 @@ class VoiceChatSessionControllerFactory { const quickChatService = accessor.get(IQuickChatService); const layoutService = accessor.get(IWorkbenchLayoutService); const editorService = accessor.get(IEditorService); - const terminalService = accessor.get(ITerminalService); const viewsService = accessor.get(IViewsService); switch (context) { case 'focused': { - const controller = VoiceChatSessionControllerFactory.doCreateForFocusedChat(terminalService, chatWidgetService, layoutService); + const controller = VoiceChatSessionControllerFactory.doCreateForFocusedChat(chatWidgetService, layoutService); return controller ?? VoiceChatSessionControllerFactory.create(accessor, 'view'); // fallback to 'view' } case 'view': { @@ -143,18 +138,7 @@ class VoiceChatSessionControllerFactory { return undefined; } - private static doCreateForFocusedChat(terminalService: ITerminalService, chatWidgetService: IChatWidgetService, layoutService: IWorkbenchLayoutService): IVoiceChatSessionController | undefined { - - // 1.) probe terminal chat which is not part of chat widget service - const activeInstance = terminalService.activeInstance; - if (activeInstance) { - const terminalChat = TerminalChatController.activeChatController || TerminalChatController.get(activeInstance); - if (terminalChat?.hasFocus()) { - return VoiceChatSessionControllerFactory.doCreateForTerminalChat(terminalChat); - } - } - - // 2.) otherwise go via chat widget service + private static doCreateForFocusedChat(chatWidgetService: IChatWidgetService, layoutService: IWorkbenchLayoutService): IVoiceChatSessionController | undefined { const chatWidget = chatWidgetService.lastFocusedWidget; if (chatWidget?.hasInputFocus()) { @@ -217,23 +201,6 @@ class VoiceChatSessionControllerFactory { updateState: VoiceChatSessionControllerFactory.createChatContextKeyController(chatWidget.scopedContextKeyService, context) }; } - - private static doCreateForTerminalChat(terminalChat: TerminalChatController): IVoiceChatSessionController { - const context = 'terminal'; - return { - context, - scopedContextKeyService: terminalChat.scopedContextKeyService, - onDidAcceptInput: terminalChat.onDidAcceptInput, - onDidHideInput: terminalChat.onDidHide, - focusInput: () => terminalChat.focus(), - acceptInput: () => terminalChat.acceptInput(true), - updateInput: text => terminalChat.updateInput(text, false), - getInput: () => terminalChat.getInput(), - setInputPlaceholder: text => terminalChat.setPlaceholder(text), - clearInputPlaceholder: () => terminalChat.resetPlaceholder(), - updateState: VoiceChatSessionControllerFactory.createChatContextKeyController(terminalChat.scopedContextKeyService, context) - }; - } } interface IVoiceChatSession { @@ -468,7 +435,7 @@ export class VoiceChatInChatViewAction extends VoiceChatWithHoldModeAction { category: CHAT_CATEGORY, precondition: ContextKeyExpr.and( CanVoiceChat, - CONTEXT_CHAT_REQUEST_IN_PROGRESS.negate() // disable when a chat request is in progress + ChatContextKeys.requestInProgress.negate() // disable when a chat request is in progress ), f1: true }, 'view'); @@ -487,7 +454,7 @@ export class HoldToVoiceChatInChatViewAction extends Action2 { weight: KeybindingWeight.WorkbenchContrib, when: ContextKeyExpr.and( CanVoiceChat, - CONTEXT_CHAT_REQUEST_IN_PROGRESS.negate(), // disable when a chat request is in progress + ChatContextKeys.requestInProgress.negate(), // disable when a chat request is in progress FocusInChatInput?.negate(), // when already in chat input, disable this action and prefer to start voice chat directly EditorContextKeys.focus.negate(), // do not steal the inline-chat keybinding NOTEBOOK_EDITOR_FOCUSED.negate() // do not steal the notebook keybinding @@ -541,7 +508,7 @@ export class InlineVoiceChatAction extends VoiceChatWithHoldModeAction { precondition: ContextKeyExpr.and( CanVoiceChat, ActiveEditorContext, - CONTEXT_CHAT_REQUEST_IN_PROGRESS.negate() // disable when a chat request is in progress + ChatContextKeys.requestInProgress.negate() // disable when a chat request is in progress ), f1: true }, 'inline'); @@ -559,7 +526,7 @@ export class QuickVoiceChatAction extends VoiceChatWithHoldModeAction { category: CHAT_CATEGORY, precondition: ContextKeyExpr.and( CanVoiceChat, - CONTEXT_CHAT_REQUEST_IN_PROGRESS.negate() // disable when a chat request is in progress + ChatContextKeys.requestInProgress.negate() // disable when a chat request is in progress ), f1: true }, 'quick'); @@ -601,26 +568,16 @@ export class StartVoiceChatAction extends Action2 { menu: [ { id: MenuId.ChatInput, - when: ContextKeyExpr.and(ContextKeyExpr.or(CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.Panel), CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.EditingSession)), menuCondition), + when: ContextKeyExpr.and(ContextKeyExpr.or(ChatContextKeys.location.isEqualTo(ChatAgentLocation.Panel), ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession)), menuCondition), group: 'navigation', order: 3 }, { id: MenuId.ChatExecute, - when: ContextKeyExpr.and(CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.Panel).negate(), CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.EditingSession).negate(), menuCondition), + when: ContextKeyExpr.and(ChatContextKeys.location.isEqualTo(ChatAgentLocation.Panel).negate(), ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession).negate(), menuCondition), group: 'navigation', order: 2 - }, - { - id: TerminalChatExecute, - when: ContextKeyExpr.and( - HasSpeechProvider, - ScopedChatSynthesisInProgress.negate(), // hide when text to speech is in progress - AnyScopedVoiceChatInProgress?.negate(), // hide when voice chat is in progress - ), - group: 'navigation', - order: -1 - }, + } ] }); } @@ -659,22 +616,16 @@ export class StopListeningAction extends Action2 { menu: [ { id: MenuId.ChatInput, - when: ContextKeyExpr.and(CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.Panel), AnyScopedVoiceChatInProgress), + when: ContextKeyExpr.and(ChatContextKeys.location.isEqualTo(ChatAgentLocation.Panel), AnyScopedVoiceChatInProgress), group: 'navigation', order: 3 }, { id: MenuId.ChatExecute, - when: ContextKeyExpr.and(CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.Panel).negate(), AnyScopedVoiceChatInProgress), + when: ContextKeyExpr.and(ChatContextKeys.location.isEqualTo(ChatAgentLocation.Panel).negate(), AnyScopedVoiceChatInProgress), group: 'navigation', order: 2 - }, { - - id: TerminalChatExecute, - when: AnyScopedVoiceChatInProgress, - group: 'navigation', - order: -1 - }, + } ] }); } @@ -742,22 +693,6 @@ class ChatSynthesizerSessionController { private static doCreateForFocusedChat(accessor: ServicesAccessor, response: IChatResponseModel): IChatSynthesizerSessionController { const chatWidgetService = accessor.get(IChatWidgetService); const contextKeyService = accessor.get(IContextKeyService); - const terminalService = accessor.get(ITerminalService); - - // 1.) probe terminal chat which is not part of chat widget service - const activeInstance = terminalService.activeInstance; - if (activeInstance) { - const terminalChat = TerminalChatController.activeChatController || TerminalChatController.get(activeInstance); - if (terminalChat?.hasFocus()) { - return { - onDidHideChat: terminalChat.onDidHide, - contextKeyService: terminalChat.scopedContextKeyService, - response - }; - } - } - - // 2.) otherwise go via chat widget service let chatWidget = chatWidgetService.getWidgetBySessionId(response.session.sessionId); if (chatWidget?.location === ChatAgentLocation.Editor) { // TODO@bpasero workaround for https://github.com/microsoft/vscode/issues/212785 @@ -915,9 +850,9 @@ export class ReadChatResponseAloud extends Action2 { id: MenuId.ChatMessageFooter, when: ContextKeyExpr.and( CanVoiceChat, - CONTEXT_RESPONSE, // only for responses + ChatContextKeys.isResponse, // only for responses ScopedChatSynthesisInProgress.negate(), // but not when already in progress - CONTEXT_RESPONSE_FILTERED.negate(), // and not when response is filtered + ChatContextKeys.responseIsFiltered.negate(), // and not when response is filtered ), group: 'navigation', order: -10 // first @@ -925,9 +860,9 @@ export class ReadChatResponseAloud extends Action2 { id: MENU_INLINE_CHAT_WIDGET_SECONDARY, when: ContextKeyExpr.and( CanVoiceChat, - CONTEXT_RESPONSE, // only for responses + ChatContextKeys.isResponse, // only for responses ScopedChatSynthesisInProgress.negate(), // but not when already in progress - CONTEXT_RESPONSE_FILTERED.negate() // and not when response is filtered + ChatContextKeys.responseIsFiltered.negate() // and not when response is filtered ), group: 'navigation', order: -10 // first @@ -1001,21 +936,15 @@ export class StopReadAloud extends Action2 { menu: [ { id: MenuId.ChatInput, - when: ContextKeyExpr.and(CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.Panel), ScopedChatSynthesisInProgress), + when: ContextKeyExpr.and(ChatContextKeys.location.isEqualTo(ChatAgentLocation.Panel), ScopedChatSynthesisInProgress), group: 'navigation', order: 3 }, { id: MenuId.ChatExecute, - when: ContextKeyExpr.and(CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.Panel).negate(), ScopedChatSynthesisInProgress), + when: ContextKeyExpr.and(ChatContextKeys.location.isEqualTo(ChatAgentLocation.Panel).negate(), ScopedChatSynthesisInProgress), group: 'navigation', order: 2 - }, - { - id: TerminalChatExecute, - when: ScopedChatSynthesisInProgress, - group: 'navigation', - order: -1 } ] }); @@ -1045,8 +974,8 @@ export class StopReadChatItemAloud extends Action2 { id: MenuId.ChatMessageFooter, when: ContextKeyExpr.and( ScopedChatSynthesisInProgress, // only when in progress - CONTEXT_RESPONSE, // only for responses - CONTEXT_RESPONSE_FILTERED.negate() // but not when response is filtered + ChatContextKeys.isResponse, // only for responses + ChatContextKeys.responseIsFiltered.negate() // but not when response is filtered ), group: 'navigation', order: -10 // first @@ -1055,8 +984,8 @@ export class StopReadChatItemAloud extends Action2 { id: MENU_INLINE_CHAT_WIDGET_SECONDARY, when: ContextKeyExpr.and( ScopedChatSynthesisInProgress, // only when in progress - CONTEXT_RESPONSE, // only for responses - CONTEXT_RESPONSE_FILTERED.negate() // but not when response is filtered + ChatContextKeys.isResponse, // only for responses + ChatContextKeys.responseIsFiltered.negate() // but not when response is filtered ), group: 'navigation', order: -10 // first @@ -1354,21 +1283,15 @@ export class InstallSpeechProviderForVoiceChatAction extends BaseInstallSpeechPr menu: [ { id: MenuId.ChatInput, - when: ContextKeyExpr.and(HasSpeechProvider.negate(), ContextKeyExpr.or(CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.Panel), CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.EditingSession))), + when: ContextKeyExpr.and(HasSpeechProvider.negate(), ContextKeyExpr.or(ChatContextKeys.location.isEqualTo(ChatAgentLocation.Panel), ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession))), group: 'navigation', order: 3 }, { id: MenuId.ChatExecute, - when: ContextKeyExpr.and(HasSpeechProvider.negate(), CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.Panel).negate(), CONTEXT_CHAT_LOCATION.isEqualTo(ChatAgentLocation.EditingSession).negate()), + when: ContextKeyExpr.and(HasSpeechProvider.negate(), ChatContextKeys.location.isEqualTo(ChatAgentLocation.Panel).negate(), ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession).negate()), group: 'navigation', order: 2 - }, - { - id: TerminalChatExecute, - when: HasSpeechProvider.negate(), - group: 'navigation', - order: -1 } ] }); diff --git a/src/vs/workbench/contrib/chat/test/browser/mockChatWidget.ts b/src/vs/workbench/contrib/chat/test/browser/mockChatWidget.ts index 3b5a9541d3ed5..cf64e8379ac5b 100644 --- a/src/vs/workbench/contrib/chat/test/browser/mockChatWidget.ts +++ b/src/vs/workbench/contrib/chat/test/browser/mockChatWidget.ts @@ -26,11 +26,7 @@ export class MockChatWidgetService implements IChatWidgetService { return undefined; } - getWidgetByLocation(location: ChatAgentLocation): IChatWidget[] { - return []; - } - - getAllWidgets(location: ChatAgentLocation): ReadonlyArray { + getWidgetsByLocations(location: ChatAgentLocation): ReadonlyArray { return []; } } diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_deserialize_with_response.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_deserialize_with_response.0.snap new file mode 100644 index 0000000000000..08839ed2bf7a4 --- /dev/null +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatService_can_deserialize_with_response.0.snap @@ -0,0 +1,84 @@ +{ + requesterUsername: "test", + requesterAvatarIconUri: undefined, + responderUsername: "", + responderAvatarIconUri: undefined, + initialLocation: "panel", + requests: [ + { + message: { + text: "@ChatProviderWithUsedContext test request", + parts: [ + { + range: { + start: 0, + endExclusive: 28 + }, + editorRange: { + startLineNumber: 1, + startColumn: 1, + endLineNumber: 1, + endColumn: 29 + }, + agent: { + name: "ChatProviderWithUsedContext", + id: "ChatProviderWithUsedContext", + extensionId: { + value: "nullExtensionDescription", + _lower: "nullextensiondescription" + }, + extensionPublisherId: "", + publisherDisplayName: "", + extensionDisplayName: "", + locations: [ "panel" ], + metadata: { }, + slashCommands: [ ], + disambiguation: [ ] + }, + kind: "agent" + }, + { + range: { + start: 28, + endExclusive: 41 + }, + editorRange: { + startLineNumber: 1, + startColumn: 29, + endLineNumber: 1, + endColumn: 42 + }, + text: " test request", + kind: "text" + } + ] + }, + variableData: { variables: [ ] }, + response: [ ], + result: { errorDetails: { message: "No activated agent with id \"ChatProviderWithUsedContext\"" } }, + followups: undefined, + isCanceled: false, + vote: undefined, + voteDownReason: undefined, + agent: { + name: "ChatProviderWithUsedContext", + id: "ChatProviderWithUsedContext", + extensionId: { + value: "nullExtensionDescription", + _lower: "nullextensiondescription" + }, + extensionPublisherId: "", + publisherDisplayName: "", + extensionDisplayName: "", + locations: [ "panel" ], + metadata: { }, + slashCommands: [ ], + disambiguation: [ ] + }, + slashCommand: undefined, + usedContext: undefined, + contentReferences: [ ], + codeCitations: [ ] + } + ] +} \ No newline at end of file diff --git a/src/vs/workbench/contrib/chat/test/common/chatService.test.ts b/src/vs/workbench/contrib/chat/test/common/chatService.test.ts index d496011366256..393c3813b83aa 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatService.test.ts @@ -33,6 +33,7 @@ import { NullWorkbenchAssignmentService } from '../../../../services/assignment/ import { IExtensionService, nullExtensionDescription } from '../../../../services/extensions/common/extensions.js'; import { IViewsService } from '../../../../services/views/common/viewsService.js'; import { TestContextService, TestExtensionService, TestStorageService } from '../../../../test/common/workbenchTestServices.js'; +import { MarkdownString } from '../../../../../base/common/htmlContent.js'; const chatAgentWithUsedContextId = 'ChatProviderWithUsedContext'; const chatAgentWithUsedContext: IChatAgent = { @@ -67,6 +68,27 @@ const chatAgentWithUsedContext: IChatAgent = { }, }; +const chatAgentWithMarkdownId = 'ChatProviderWithMarkdown'; +const chatAgentWithMarkdown: IChatAgent = { + id: chatAgentWithMarkdownId, + name: chatAgentWithMarkdownId, + extensionId: nullExtensionDescription.identifier, + publisherDisplayName: '', + extensionPublisherId: '', + extensionDisplayName: '', + locations: [ChatAgentLocation.Panel], + metadata: {}, + slashCommands: [], + disambiguation: [], + async invoke(request, progress, history, token) { + progress({ kind: 'markdownContent', content: new MarkdownString('test') }); + return { metadata: { metadataKey: 'value' } }; + }, + async provideFollowups(sessionId, token) { + return []; + }, +}; + function getAgentData(id: string) { return { name: id, @@ -116,6 +138,7 @@ suite('ChatService', () => { }; testDisposables.add(chatAgentService.registerAgent('testAgent', { ...getAgentData('testAgent'), isDefault: true })); testDisposables.add(chatAgentService.registerAgent(chatAgentWithUsedContextId, getAgentData(chatAgentWithUsedContextId))); + testDisposables.add(chatAgentService.registerAgent(chatAgentWithMarkdownId, getAgentData(chatAgentWithMarkdownId))); testDisposables.add(chatAgentService.registerAgentImplementation('testAgent', agent)); chatAgentService.updateAgent('testAgent', { requester: { name: 'test' } }); }); @@ -253,4 +276,32 @@ suite('ChatService', () => { await assertSnapshot(chatModel2.toExport()); }); + + test('can deserialize with response', async () => { + let serializedChatData: ISerializableChatData; + testDisposables.add(chatAgentService.registerAgentImplementation(chatAgentWithMarkdownId, chatAgentWithMarkdown)); + + { + const testService = testDisposables.add(instantiationService.createInstance(ChatService)); + + const chatModel1 = testDisposables.add(testService.startSession(ChatAgentLocation.Panel, CancellationToken.None)); + assert.strictEqual(chatModel1.getRequests().length, 0); + + const response = await testService.sendRequest(chatModel1.sessionId, `@${chatAgentWithUsedContextId} test request`); + assert(response); + + await response.responseCompletePromise; + + serializedChatData = JSON.parse(JSON.stringify(chatModel1)); + } + + // try deserializing the state into a new service + + const testService2 = testDisposables.add(instantiationService.createInstance(ChatService)); + + const chatModel2 = testService2.loadSessionFromContent(serializedChatData); + assert(chatModel2); + + await assertSnapshot(chatModel2.toExport()); + }); }); diff --git a/src/vs/workbench/contrib/codeActions/browser/codeActions.contribution.ts b/src/vs/workbench/contrib/codeActions/browser/codeActions.contribution.ts index 7fdb96233ce52..6cdcca2cee0c2 100644 --- a/src/vs/workbench/contrib/codeActions/browser/codeActions.contribution.ts +++ b/src/vs/workbench/contrib/codeActions/browser/codeActions.contribution.ts @@ -4,18 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import { Extensions, IConfigurationRegistry } from '../../../../platform/configuration/common/configurationRegistry.js'; -import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { Registry } from '../../../../platform/registry/common/platform.js'; -import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from '../../../common/contributions.js'; -import { CodeActionsExtensionPoint, codeActionsExtensionPointDescriptor } from '../common/codeActionsExtensionPoint.js'; -import { DocumentationExtensionPoint, documentationExtensionPointDescriptor } from '../common/documentationExtensionPoint.js'; -import { ExtensionsRegistry } from '../../../services/extensions/common/extensionsRegistry.js'; +import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from '../../../common/contributions.js'; import { LifecyclePhase } from '../../../services/lifecycle/common/lifecycle.js'; import { CodeActionsContribution, editorConfiguration, notebookEditorConfiguration } from './codeActionsContribution.js'; -import { CodeActionDocumentationContribution } from './documentationContribution.js'; - -const codeActionsExtensionPoint = ExtensionsRegistry.registerExtensionPoint(codeActionsExtensionPointDescriptor); -const documentationExtensionPoint = ExtensionsRegistry.registerExtensionPoint(documentationExtensionPointDescriptor); Registry.as(Extensions.Configuration) .registerConfiguration(editorConfiguration); @@ -23,14 +15,5 @@ Registry.as(Extensions.Configuration) Registry.as(Extensions.Configuration) .registerConfiguration(notebookEditorConfiguration); -class WorkbenchConfigurationContribution { - constructor( - @IInstantiationService instantiationService: IInstantiationService, - ) { - instantiationService.createInstance(CodeActionsContribution, codeActionsExtensionPoint); - instantiationService.createInstance(CodeActionDocumentationContribution, documentationExtensionPoint); - } -} - Registry.as(WorkbenchExtensions.Workbench) - .registerWorkbenchContribution(WorkbenchConfigurationContribution, LifecyclePhase.Eventually); + .registerWorkbenchContribution(CodeActionsContribution, LifecyclePhase.Eventually); diff --git a/src/vs/workbench/contrib/codeActions/browser/codeActionsContribution.ts b/src/vs/workbench/contrib/codeActions/browser/codeActionsContribution.ts index ca408f84aed09..a7a7ce4c176ae 100644 --- a/src/vs/workbench/contrib/codeActions/browser/codeActionsContribution.ts +++ b/src/vs/workbench/contrib/codeActions/browser/codeActionsContribution.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Emitter } from '../../../../base/common/event.js'; +import { Emitter, Event } from '../../../../base/common/event.js'; import { HierarchicalKind } from '../../../../base/common/hierarchicalKind.js'; import { IJSONSchema, IJSONSchemaMap } from '../../../../base/common/jsonSchema.js'; import { Disposable } from '../../../../base/common/lifecycle.js'; @@ -16,8 +16,6 @@ import { ConfigurationScope, Extensions, IConfigurationNode, IConfigurationPrope import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; import { Registry } from '../../../../platform/registry/common/platform.js'; import { IWorkbenchContribution } from '../../../common/contributions.js'; -import { CodeActionsExtensionPoint, ContributedCodeAction } from '../common/codeActionsExtensionPoint.js'; -import { IExtensionPoint } from '../../../services/extensions/common/extensionsRegistry.js'; const createCodeActionsAutoSave = (description: string): IJSONSchema => { return { @@ -114,93 +112,60 @@ export const notebookEditorConfiguration = Object.freeze({ export class CodeActionsContribution extends Disposable implements IWorkbenchContribution { - private _contributedCodeActions: CodeActionsExtensionPoint[] = []; - private settings: Set = new Set(); + private readonly _onDidChangeSchemaContributions = this._register(new Emitter()); - private readonly _onDidChangeContributions = this._register(new Emitter()); + private _allProvidedCodeActionKinds: HierarchicalKind[] = []; constructor( - codeActionsExtensionPoint: IExtensionPoint, @IKeybindingService keybindingService: IKeybindingService, @ILanguageFeaturesService private readonly languageFeatures: ILanguageFeaturesService ) { super(); // TODO: @justschen caching of code actions based on extensions loaded: https://github.com/microsoft/vscode/issues/216019 - languageFeatures.codeActionProvider.onDidChange(() => { - this.updateSettingsFromCodeActionProviders(); - this.updateConfigurationSchemaFromContribs(); - }, 2000); - - codeActionsExtensionPoint.setHandler(extensionPoints => { - this._contributedCodeActions = extensionPoints.flatMap(x => x.value).filter(x => Array.isArray(x.actions)); - this.updateConfigurationSchema(this._contributedCodeActions); - this._onDidChangeContributions.fire(); - }); + this._register( + Event.runAndSubscribe( + Event.debounce(languageFeatures.codeActionProvider.onDidChange, () => { }, 1000), + () => { + this._allProvidedCodeActionKinds = this.getAllProvidedCodeActionKinds(); + this.updateConfigurationSchema(this._allProvidedCodeActionKinds); + this._onDidChangeSchemaContributions.fire(); + })); keybindingService.registerSchemaContribution({ - getSchemaAdditions: () => this.getSchemaAdditions(), - onDidChange: this._onDidChangeContributions.event, + getSchemaAdditions: () => this.getKeybindingSchemaAdditions(), + onDidChange: this._onDidChangeSchemaContributions.event, }); } - private updateSettingsFromCodeActionProviders(): void { - const providers = this.languageFeatures.codeActionProvider.allNoModel(); - providers.forEach(provider => { - if (provider.providedCodeActionKinds) { - provider.providedCodeActionKinds.forEach(kind => { - if (!this.settings.has(kind) && CodeActionKind.Source.contains(new HierarchicalKind(kind))) { - this.settings.add(kind); - } - }); + private getAllProvidedCodeActionKinds(): Array { + const out = new Map(); + for (const provider of this.languageFeatures.codeActionProvider.allNoModel()) { + for (const kind of provider.providedCodeActionKinds ?? []) { + out.set(kind, new HierarchicalKind(kind)); } - }); - } - - private updateConfigurationSchema(codeActionContributions: readonly CodeActionsExtensionPoint[]) { - const newProperties: IJSONSchemaMap = {}; - const newNotebookProperties: IJSONSchemaMap = {}; - for (const [sourceAction, props] of this.getSourceActions(codeActionContributions)) { - this.settings.add(sourceAction); - newProperties[sourceAction] = createCodeActionsAutoSave(nls.localize('codeActionsOnSave.generic', "Controls whether '{0}' actions should be run on file save.", props.title)); - newNotebookProperties[sourceAction] = createNotebookCodeActionsAutoSave(nls.localize('codeActionsOnSave.generic', "Controls whether '{0}' actions should be run on file save.", props.title)); } - codeActionsOnSaveSchema.properties = newProperties; - notebookCodeActionsOnSaveSchema.properties = newNotebookProperties; - Registry.as(Extensions.Configuration) - .notifyConfigurationSchemaUpdated(editorConfiguration); + return Array.from(out.values()); } - private updateConfigurationSchemaFromContribs() { + private updateConfigurationSchema(allProvidedKinds: Iterable): void { const properties: IJSONSchemaMap = { ...codeActionsOnSaveSchema.properties }; const notebookProperties: IJSONSchemaMap = { ...notebookCodeActionsOnSaveSchema.properties }; - for (const codeActionKind of this.settings) { - if (!properties[codeActionKind]) { - properties[codeActionKind] = createCodeActionsAutoSave(nls.localize('codeActionsOnSave.generic', "Controls whether '{0}' actions should be run on file save.", codeActionKind)); - notebookProperties[codeActionKind] = createNotebookCodeActionsAutoSave(nls.localize('codeActionsOnSave.generic', "Controls whether '{0}' actions should be run on file save.", codeActionKind)); + for (const codeActionKind of allProvidedKinds) { + if (CodeActionKind.Source.contains(codeActionKind) && !properties[codeActionKind.value]) { + properties[codeActionKind.value] = createCodeActionsAutoSave(nls.localize('codeActionsOnSave.generic', "Controls whether '{0}' actions should be run on file save.", codeActionKind.value)); + notebookProperties[codeActionKind.value] = createNotebookCodeActionsAutoSave(nls.localize('codeActionsOnSave.generic', "Controls whether '{0}' actions should be run on file save.", codeActionKind.value)); } } codeActionsOnSaveSchema.properties = properties; notebookCodeActionsOnSaveSchema.properties = notebookProperties; + Registry.as(Extensions.Configuration) .notifyConfigurationSchemaUpdated(editorConfiguration); } - private getSourceActions(contributions: readonly CodeActionsExtensionPoint[]) { - const sourceActions = new Map(); - for (const contribution of contributions) { - for (const action of contribution.actions) { - const kind = new HierarchicalKind(action.kind); - if (CodeActionKind.Source.contains(kind)) { - sourceActions.set(kind.value, action); - } - } - } - return sourceActions; - } - - private getSchemaAdditions(): IJSONSchema[] { - const conditionalSchema = (command: string, actions: readonly ContributedCodeAction[]): IJSONSchema => { + private getKeybindingSchemaAdditions(): IJSONSchema[] { + const conditionalSchema = (command: string, kinds: readonly string[]): IJSONSchema => { return { if: { required: ['command'], @@ -215,10 +180,7 @@ export class CodeActionsContribution extends Disposable implements IWorkbenchCon properties: { 'kind': { anyOf: [ - { - enum: actions.map(action => action.kind), - enumDescriptions: actions.map(action => action.description ?? action.title), - }, + { enum: Array.from(kinds) }, { type: 'string' }, ] } @@ -229,22 +191,20 @@ export class CodeActionsContribution extends Disposable implements IWorkbenchCon }; }; - const getActions = (ofKind: HierarchicalKind): ContributedCodeAction[] => { - const allActions = this._contributedCodeActions.flatMap(desc => desc.actions); - - const out = new Map(); - for (const action of allActions) { - if (!out.has(action.kind) && ofKind.contains(new HierarchicalKind(action.kind))) { - out.set(action.kind, action); + const filterProvidedKinds = (ofKind: HierarchicalKind): string[] => { + const out = new Set(); + for (const providedKind of this._allProvidedCodeActionKinds) { + if (ofKind.contains(providedKind)) { + out.add(providedKind.value); } } - return Array.from(out.values()); + return Array.from(out); }; return [ - conditionalSchema(codeActionCommandId, getActions(HierarchicalKind.Empty)), - conditionalSchema(refactorCommandId, getActions(CodeActionKind.Refactor)), - conditionalSchema(sourceActionCommandId, getActions(CodeActionKind.Source)), + conditionalSchema(codeActionCommandId, filterProvidedKinds(HierarchicalKind.Empty)), + conditionalSchema(refactorCommandId, filterProvidedKinds(CodeActionKind.Refactor)), + conditionalSchema(sourceActionCommandId, filterProvidedKinds(CodeActionKind.Source)), ]; } } diff --git a/src/vs/workbench/contrib/codeActions/browser/documentationContribution.ts b/src/vs/workbench/contrib/codeActions/browser/documentationContribution.ts deleted file mode 100644 index d55cd2c212d6d..0000000000000 --- a/src/vs/workbench/contrib/codeActions/browser/documentationContribution.ts +++ /dev/null @@ -1,87 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { CancellationToken } from '../../../../base/common/cancellation.js'; -import { HierarchicalKind } from '../../../../base/common/hierarchicalKind.js'; -import { Disposable } from '../../../../base/common/lifecycle.js'; -import { Range } from '../../../../editor/common/core/range.js'; -import { Selection } from '../../../../editor/common/core/selection.js'; -import * as languages from '../../../../editor/common/languages.js'; -import { ITextModel } from '../../../../editor/common/model.js'; -import { ILanguageFeaturesService } from '../../../../editor/common/services/languageFeatures.js'; -import { CodeActionKind } from '../../../../editor/contrib/codeAction/common/types.js'; -import { ContextKeyExpr, ContextKeyExpression, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; -import { IWorkbenchContribution } from '../../../common/contributions.js'; -import { DocumentationExtensionPoint } from '../common/documentationExtensionPoint.js'; -import { IExtensionPoint } from '../../../services/extensions/common/extensionsRegistry.js'; - - -export class CodeActionDocumentationContribution extends Disposable implements IWorkbenchContribution, languages.CodeActionProvider { - - private contributions: { - title: string; - when: ContextKeyExpression; - command: string; - }[] = []; - - private readonly emptyCodeActionsList = { - actions: [], - dispose: () => { } - }; - - constructor( - extensionPoint: IExtensionPoint, - @IContextKeyService private readonly contextKeyService: IContextKeyService, - @ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService, - ) { - super(); - - this._register(languageFeaturesService.codeActionProvider.register('*', this)); - - extensionPoint.setHandler(points => { - this.contributions = []; - for (const documentation of points) { - if (!documentation.value.refactoring) { - continue; - } - - for (const contribution of documentation.value.refactoring) { - const precondition = ContextKeyExpr.deserialize(contribution.when); - if (!precondition) { - continue; - } - - this.contributions.push({ - title: contribution.title, - when: precondition, - command: contribution.command - }); - - } - } - }); - } - - async provideCodeActions(_model: ITextModel, _range: Range | Selection, context: languages.CodeActionContext, _token: CancellationToken): Promise { - return this.emptyCodeActionsList; - } - - public _getAdditionalMenuItems(context: languages.CodeActionContext, actions: readonly languages.CodeAction[]): languages.Command[] { - if (context.only !== CodeActionKind.Refactor.value) { - if (!actions.some(action => action.kind && CodeActionKind.Refactor.contains(new HierarchicalKind(action.kind)))) { - return []; - } - } - - return this.contributions - .filter(contribution => this.contextKeyService.contextMatchesRules(contribution.when)) - .map(contribution => { - return { - id: contribution.command, - title: contribution.title - }; - }); - } -} diff --git a/src/vs/workbench/contrib/codeActions/common/codeActionsExtensionPoint.ts b/src/vs/workbench/contrib/codeActions/common/codeActionsExtensionPoint.ts deleted file mode 100644 index ff096f3e33fc4..0000000000000 --- a/src/vs/workbench/contrib/codeActions/common/codeActionsExtensionPoint.ts +++ /dev/null @@ -1,127 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as nls from '../../../../nls.js'; -import { IConfigurationPropertySchema } from '../../../../platform/configuration/common/configurationRegistry.js'; -import { languagesExtPoint } from '../../../services/language/common/languageService.js'; -import { Extensions as ExtensionFeaturesExtensions, IExtensionFeatureTableRenderer, IExtensionFeaturesRegistry, IRenderedData, IRowData, ITableData } from '../../../services/extensionManagement/common/extensionFeatures.js'; -import { Disposable } from '../../../../base/common/lifecycle.js'; -import { SyncDescriptor } from '../../../../platform/instantiation/common/descriptors.js'; -import { IExtensionManifest } from '../../../../platform/extensions/common/extensions.js'; -import { Registry } from '../../../../platform/registry/common/platform.js'; -import { MarkdownString } from '../../../../base/common/htmlContent.js'; - -enum CodeActionExtensionPointFields { - languages = 'languages', - actions = 'actions', - kind = 'kind', - title = 'title', - description = 'description' -} - -export interface ContributedCodeAction { - readonly [CodeActionExtensionPointFields.kind]: string; - readonly [CodeActionExtensionPointFields.title]: string; - readonly [CodeActionExtensionPointFields.description]?: string; -} - -export interface CodeActionsExtensionPoint { - readonly [CodeActionExtensionPointFields.languages]: readonly string[]; - readonly [CodeActionExtensionPointFields.actions]: readonly ContributedCodeAction[]; -} - -const codeActionsExtensionPointSchema = Object.freeze({ - type: 'array', - markdownDescription: nls.localize('contributes.codeActions', "Configure which editor to use for a resource."), - items: { - type: 'object', - required: [CodeActionExtensionPointFields.languages, CodeActionExtensionPointFields.actions], - properties: { - [CodeActionExtensionPointFields.languages]: { - type: 'array', - description: nls.localize('contributes.codeActions.languages', "Language modes that the code actions are enabled for."), - items: { type: 'string' } - }, - [CodeActionExtensionPointFields.actions]: { - type: 'object', - required: [CodeActionExtensionPointFields.kind, CodeActionExtensionPointFields.title], - properties: { - [CodeActionExtensionPointFields.kind]: { - type: 'string', - markdownDescription: nls.localize('contributes.codeActions.kind', "`CodeActionKind` of the contributed code action."), - }, - [CodeActionExtensionPointFields.title]: { - type: 'string', - description: nls.localize('contributes.codeActions.title', "Label for the code action used in the UI."), - }, - [CodeActionExtensionPointFields.description]: { - type: 'string', - description: nls.localize('contributes.codeActions.description', "Description of what the code action does."), - }, - } - } - } - } -}); - -export const codeActionsExtensionPointDescriptor = { - extensionPoint: 'codeActions', - deps: [languagesExtPoint], - jsonSchema: codeActionsExtensionPointSchema -}; - -class CodeActionsTableRenderer extends Disposable implements IExtensionFeatureTableRenderer { - - readonly type = 'table'; - - shouldRender(manifest: IExtensionManifest): boolean { - return !!manifest.contributes?.codeActions; - } - - render(manifest: IExtensionManifest): IRenderedData { - const codeActions = manifest.contributes?.codeActions || []; - if (!codeActions.length) { - return { data: { headers: [], rows: [] }, dispose: () => { } }; - } - - const flatActions = - codeActions.map(contribution => - contribution.actions.map(action => ({ ...action, languages: contribution.languages }))).flat(); - - const headers = [ - nls.localize('codeActions.title', "Title"), - nls.localize('codeActions.kind', "Kind"), - nls.localize('codeActions.description', "Description"), - nls.localize('codeActions.languages', "Languages") - ]; - - const rows: IRowData[][] = flatActions.sort((a, b) => a.title.localeCompare(b.title)) - .map(action => { - return [ - action.title, - new MarkdownString().appendMarkdown(`\`${action.kind}\``), - action.description ?? '', - new MarkdownString().appendMarkdown(`${action.languages.map(lang => `\`${lang}\``).join(' ')}`), - ]; - }); - - return { - data: { - headers, - rows - }, - dispose: () => { } - }; - } -} - -Registry.as(ExtensionFeaturesExtensions.ExtensionFeaturesRegistry).registerExtensionFeature({ - id: 'codeActions', - label: nls.localize('codeactions', "Code Actions"), - access: { - canToggle: false, - }, - renderer: new SyncDescriptor(CodeActionsTableRenderer), -}); diff --git a/src/vs/workbench/contrib/codeActions/common/documentationExtensionPoint.ts b/src/vs/workbench/contrib/codeActions/common/documentationExtensionPoint.ts deleted file mode 100644 index 512db5723d166..0000000000000 --- a/src/vs/workbench/contrib/codeActions/common/documentationExtensionPoint.ts +++ /dev/null @@ -1,64 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as nls from '../../../../nls.js'; -import { IConfigurationPropertySchema } from '../../../../platform/configuration/common/configurationRegistry.js'; -import { languagesExtPoint } from '../../../services/language/common/languageService.js'; - -enum DocumentationExtensionPointFields { - when = 'when', - title = 'title', - command = 'command', -} - -interface RefactoringDocumentationExtensionPoint { - readonly [DocumentationExtensionPointFields.title]: string; - readonly [DocumentationExtensionPointFields.when]: string; - readonly [DocumentationExtensionPointFields.command]: string; -} - -export interface DocumentationExtensionPoint { - readonly refactoring?: readonly RefactoringDocumentationExtensionPoint[]; -} - -const documentationExtensionPointSchema = Object.freeze({ - type: 'object', - description: nls.localize('contributes.documentation', "Contributed documentation."), - properties: { - 'refactoring': { - type: 'array', - description: nls.localize('contributes.documentation.refactorings', "Contributed documentation for refactorings."), - items: { - type: 'object', - description: nls.localize('contributes.documentation.refactoring', "Contributed documentation for refactoring."), - required: [ - DocumentationExtensionPointFields.title, - DocumentationExtensionPointFields.when, - DocumentationExtensionPointFields.command - ], - properties: { - [DocumentationExtensionPointFields.title]: { - type: 'string', - description: nls.localize('contributes.documentation.refactoring.title', "Label for the documentation used in the UI."), - }, - [DocumentationExtensionPointFields.when]: { - type: 'string', - description: nls.localize('contributes.documentation.refactoring.when', "When clause."), - }, - [DocumentationExtensionPointFields.command]: { - type: 'string', - description: nls.localize('contributes.documentation.refactoring.command', "Command executed."), - }, - }, - } - } - } -}); - -export const documentationExtensionPointDescriptor = { - extensionPoint: 'documentation', - deps: [languagesExtPoint], - jsonSchema: documentationExtensionPointSchema -}; diff --git a/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts b/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts index 11557af2c051d..2b0f345b3c587 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts @@ -120,8 +120,7 @@ class InspectEditorTokens extends EditorAction { constructor() { super({ id: 'editor.action.inspectTMScopes', - label: nls.localize('inspectEditorTokens', "Developer: Inspect Editor Tokens and Scopes"), - alias: 'Developer: Inspect Editor Tokens and Scopes', + label: nls.localize2('inspectEditorTokens', "Developer: Inspect Editor Tokens and Scopes"), precondition: undefined }); } diff --git a/src/vs/workbench/contrib/codeEditor/browser/simpleEditorOptions.ts b/src/vs/workbench/contrib/codeEditor/browser/simpleEditorOptions.ts index 6180a3ca0300b..101c2143c6c25 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/simpleEditorOptions.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/simpleEditorOptions.ts @@ -48,6 +48,7 @@ export function getSimpleEditorOptions(configurationService: IConfigurationServi accessibilitySupport: configurationService.getValue<'auto' | 'off' | 'on'>('editor.accessibilitySupport'), cursorBlinking: configurationService.getValue<'blink' | 'smooth' | 'phase' | 'expand' | 'solid'>('editor.cursorBlinking'), experimentalEditContextEnabled: configurationService.getValue('editor.experimentalEditContextEnabled'), + defaultColorDecorators: false, }; } diff --git a/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts b/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts index 7f84c87d2ab06..8241f44ff1955 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts @@ -361,7 +361,7 @@ export class SuggestEnabledInputWithHistory extends SuggestEnabledInput implemen @IConfigurationService configurationService: IConfigurationService ) { super(id, parent, suggestionProvider, ariaLabel, resourceHandle, suggestOptions, instantiationService, modelService, contextKeyService, languageFeaturesService, configurationService); - this.history = new HistoryNavigator(history, 100); + this.history = new HistoryNavigator(new Set(history), 100); } public addToHistory(): void { diff --git a/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts b/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts index dc7f412c22abd..76bc1b9ddb07c 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts @@ -56,8 +56,7 @@ class ToggleWordWrapAction extends EditorAction { constructor() { super({ id: TOGGLE_WORD_WRAP_ID, - label: nls.localize('toggle.wordwrap', "View: Toggle Word Wrap"), - alias: 'View: Toggle Word Wrap', + label: nls.localize2('toggle.wordwrap', "View: Toggle Word Wrap"), precondition: undefined, kbOpts: { kbExpr: null, @@ -358,5 +357,5 @@ MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { precondition: CAN_TOGGLE_WORD_WRAP }, order: 1, - group: '5_editor' + group: '6_editor' }); diff --git a/src/vs/workbench/contrib/codeEditor/electron-sandbox/selectionClipboard.ts b/src/vs/workbench/contrib/codeEditor/electron-sandbox/selectionClipboard.ts index 0c4db4a8b9966..0516a68436446 100644 --- a/src/vs/workbench/contrib/codeEditor/electron-sandbox/selectionClipboard.ts +++ b/src/vs/workbench/contrib/codeEditor/electron-sandbox/selectionClipboard.ts @@ -120,8 +120,7 @@ class PasteSelectionClipboardAction extends EditorAction { constructor() { super({ id: 'editor.action.selectionClipboardPaste', - label: nls.localize('actions.pasteSelectionClipboard', "Paste Selection Clipboard"), - alias: 'Paste Selection Clipboard', + label: nls.localize2('actions.pasteSelectionClipboard', "Paste Selection Clipboard"), precondition: EditorContextKeys.writable }); } diff --git a/src/vs/workbench/contrib/comments/browser/commentMenus.ts b/src/vs/workbench/contrib/comments/browser/commentMenus.ts index 486f5c4e3388c..504739f7db1e6 100644 --- a/src/vs/workbench/contrib/comments/browser/commentMenus.ts +++ b/src/vs/workbench/contrib/comments/browser/commentMenus.ts @@ -4,11 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import { IDisposable } from '../../../../base/common/lifecycle.js'; -import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; -import { IMenuService, MenuId, IMenu } from '../../../../platform/actions/common/actions.js'; -import { IAction } from '../../../../base/common/actions.js'; import { Comment } from '../../../../editor/common/languages.js'; -import { createAndFillInContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { IMenu, IMenuCreateOptions, IMenuService, MenuId } from '../../../../platform/actions/common/actions.js'; +import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; export class CommentMenus implements IDisposable { constructor( @@ -28,7 +26,7 @@ export class CommentMenus implements IDisposable { } getCommentThreadAdditionalActions(contextKeyService: IContextKeyService): IMenu { - return this.getMenu(MenuId.CommentThreadAdditionalActions, contextKeyService); + return this.getMenu(MenuId.CommentThreadAdditionalActions, contextKeyService, { emitEventsForSubmenuChanges: true }); } getCommentTitleActions(comment: Comment, contextKeyService: IContextKeyService): IMenu { @@ -43,16 +41,8 @@ export class CommentMenus implements IDisposable { return this.getMenu(MenuId.CommentThreadTitleContext, contextKeyService); } - private getMenu(menuId: MenuId, contextKeyService: IContextKeyService): IMenu { - const menu = this.menuService.createMenu(menuId, contextKeyService); - - const primary: IAction[] = []; - const secondary: IAction[] = []; - const result = { primary, secondary }; - - createAndFillInContextMenuActions(menu, { shouldForwardArgs: true }, result, 'inline'); - - return menu; + private getMenu(menuId: MenuId, contextKeyService: IContextKeyService, options?: IMenuCreateOptions): IMenu { + return this.menuService.createMenu(menuId, contextKeyService, options); } dispose(): void { diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadHeader.ts b/src/vs/workbench/contrib/comments/browser/commentThreadHeader.ts index c76c842f122ff..3f42e0cdf323f 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadHeader.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadHeader.ts @@ -23,6 +23,7 @@ import { IContextMenuService } from '../../../../platform/contextview/browser/co import { MarshalledId } from '../../../../base/common/marshallingIds.js'; import { StandardMouseEvent } from '../../../../base/browser/mouseEvent.js'; import { MarshalledCommentThread } from '../../../common/comments.js'; +import { CommentCommandId } from '../common/commentCommandIds.js'; const collapseIcon = registerIcon('review-comment-collapse', Codicon.chevronUp, nls.localize('collapseIcon', 'Icon to collapse a review comment.')); const COLLAPSE_ACTION_CLASS = 'expand-review-action ' + ThemeIcon.asClassName(collapseIcon); @@ -68,7 +69,7 @@ export class CommentThreadHeader extends Disposable { this._register(this._actionbarWidget); const collapseClass = threadHasComments(this._commentThread.comments) ? COLLAPSE_ACTION_CLASS : DELETE_ACTION_CLASS; - this._collapseAction = new Action('review.expand', nls.localize('label.collapse', "Collapse"), collapseClass, true, () => this._delegate.collapse()); + this._collapseAction = new Action(CommentCommandId.Hide, nls.localize('label.collapse', "Collapse"), collapseClass, true, () => this._delegate.collapse()); if (!threadHasComments(this._commentThread.comments)) { const commentsChanged: MutableDisposable = this._register(new MutableDisposable()); commentsChanged.value = this._commentThread.onDidChangeComments(() => { diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts index ebad6e3f24bde..94578614d7bf4 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts @@ -5,6 +5,7 @@ import './media/review.css'; import * as dom from '../../../../base/browser/dom.js'; +import * as domStylesheets from '../../../../base/browser/domStylesheets.js'; import { Emitter } from '../../../../base/common/event.js'; import { Disposable, dispose, IDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; import { URI } from '../../../../base/common/uri.js'; @@ -36,7 +37,6 @@ import { AccessibilityVerbositySettingId } from '../../accessibility/browser/acc import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; import { AccessibilityCommandId } from '../../accessibility/common/accessibilityCommands.js'; import { LayoutableEditor } from './simpleCommentEditor.js'; -import { DomEmitter } from '../../../../base/browser/event.js'; import { isCodeEditor } from '../../../../editor/browser/editorBrowser.js'; export const COMMENTEDITOR_DECORATION_KEY = 'commenteditordecoration'; @@ -143,7 +143,7 @@ export class CommentThreadWidget extends ) as unknown as CommentThreadBody; this._register(this._body); this._setAriaLabel(); - this._styleElement = dom.createStyleSheet(this.container); + this._styleElement = domStylesheets.createStyleSheet(this.container); this._commentThreadContextValue = CommentContextKeys.commentThreadContext.bindTo(this._contextKeyService); @@ -157,14 +157,6 @@ export class CommentThreadWidget extends } this.currentThreadListeners(); - this._register(new DomEmitter(this.container, 'keydown').event(e => { - if (dom.isKeyboardEvent(e) && e.key === 'Escape') { - if (Range.isIRange(this.commentThread.range) && isCodeEditor(this._parentEditor)) { - this._parentEditor.setSelection(this.commentThread.range); - } - this.collapse(); - } - })); } private _setAriaLabel(): void { @@ -384,6 +376,9 @@ export class CommentThreadWidget extends } collapse() { + if (Range.isIRange(this.commentThread.range) && isCodeEditor(this._parentEditor)) { + this._parentEditor.setSelection(this.commentThread.range); + } this._containerDelegate.collapse(); } diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts index 4ef7bbc6025c1..6790337c79aca 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts @@ -504,6 +504,10 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget this._refresh(this._commentThreadWidget.getDimensions()); } + collapseAndFocusRange() { + this._commentThreadWidget.collapse(); + } + override hide() { if (this._isExpanded) { this._isExpanded = false; diff --git a/src/vs/workbench/contrib/comments/browser/commentsController.ts b/src/vs/workbench/contrib/comments/browser/commentsController.ts index c4cb1896a5386..6fc73d49776fc 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsController.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsController.ts @@ -1388,12 +1388,8 @@ export class CommentController implements IEditorContribution { } } - public closeWidget(): void { - this._commentWidgets?.forEach(widget => widget.hide()); - if (this.editor) { - this.editor.focus(); - this.editor.revealRangeInCenter(this.editor.getSelection()!); - } + public collapseAndFocusRange(threadId: string): void { + this._commentWidgets?.find(widget => widget.commentThread.threadId === threadId)?.collapseAndFocusRange(); } private removeCommentWidgetsAndStoreCache() { diff --git a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts index 7574e10e00787..ce0458cd9421e 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts @@ -421,11 +421,32 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ weight: KeybindingWeight.EditorContrib, primary: KeyCode.Escape, secondary: [KeyMod.Shift | KeyCode.Escape], - when: ctxCommentEditorFocused, + when: ContextKeyExpr.or(ctxCommentEditorFocused, CommentContextKeys.commentFocused), handler: (accessor, args) => { const activeCodeEditor = accessor.get(ICodeEditorService).getFocusedCodeEditor(); if (activeCodeEditor instanceof SimpleCommentEditor) { activeCodeEditor.getParentThread().collapse(); + } else if (activeCodeEditor) { + const controller = CommentController.get(activeCodeEditor); + if (!controller) { + return; + } + const notificationService = accessor.get(INotificationService); + const commentService = accessor.get(ICommentService); + let error = false; + try { + const activeComment = commentService.lastActiveCommentcontroller?.activeComment; + if (!activeComment) { + error = true; + } else { + controller.collapseAndFocusRange(activeComment.thread.threadId); + } + } catch (e) { + error = true; + } + if (error) { + notificationService.error(nls.localize('comments.focusCommand.error', "The cursor must be on a line with a comment to focus the comment")); + } } } }); diff --git a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts index 0c58c66d95d25..69684b02b2da0 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts @@ -34,7 +34,7 @@ import { ILocalizedString } from '../../../../platform/action/common/action.js'; import { CommentsModel } from './commentsModel.js'; import { getDefaultHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js'; import { ActionBar, IActionViewItemProvider } from '../../../../base/browser/ui/actionbar/actionbar.js'; -import { createActionViewItem, createAndFillInContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { createActionViewItem, getContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { IMenuService, MenuId } from '../../../../platform/actions/common/actions.js'; import { IAction } from '../../../../base/common/actions.js'; import { MarshalledId } from '../../../../base/common/marshallingIds.js'; @@ -169,12 +169,7 @@ export class CommentsMenus implements IDisposable { const contextKeyService = this.contextKeyService.createOverlay(overlay); const menu = this.menuService.getMenuActions(menuId, contextKeyService, { shouldForwardArgs: true }); - const primary: IAction[] = []; - const secondary: IAction[] = []; - const result = { primary, secondary, menu }; - createAndFillInContextMenuActions(menu, result, 'inline'); - - return result; + return getContextMenuActions(menu, 'inline'); } dispose() { diff --git a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts index cf094e01bd6af..bc5b100d27b46 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts @@ -14,7 +14,7 @@ import { InputBox } from '../../../../base/browser/ui/inputbox/inputBox.js'; import { IListContextMenuEvent, IListRenderer, IListVirtualDelegate } from '../../../../base/browser/ui/list/list.js'; import { IListAccessibilityProvider } from '../../../../base/browser/ui/list/listWidget.js'; import { Orientation } from '../../../../base/browser/ui/splitview/splitview.js'; -import { Action, IAction } from '../../../../base/common/actions.js'; +import { Action } from '../../../../base/common/actions.js'; import { equals } from '../../../../base/common/arrays.js'; import { RunOnceScheduler } from '../../../../base/common/async.js'; import { Codicon } from '../../../../base/common/codicons.js'; @@ -28,7 +28,7 @@ import { isCodeEditor } from '../../../../editor/browser/editorBrowser.js'; import { ServicesAccessor } from '../../../../editor/browser/editorExtensions.js'; import { ILanguageService } from '../../../../editor/common/languages/language.js'; import { localize, localize2 } from '../../../../nls.js'; -import { createAndFillInActionBarActions, createAndFillInContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { getActionBarActions, getContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { Action2, IMenu, IMenuService, MenuId, registerAction2 } from '../../../../platform/actions/common/actions.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { ContextKeyExpr, IContextKey, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; @@ -269,8 +269,7 @@ export class BreakpointsView extends ViewPane { this.breakpointSupportsCondition.set(conditionSupported); this.breakpointIsDataBytes.set(element instanceof DataBreakpoint && element.src.type === DataBreakpointSetType.Address); - const secondary: IAction[] = []; - createAndFillInContextMenuActions(this.menu, { arg: e.element, shouldForwardArgs: false }, { primary: [], secondary }, 'inline'); + const { secondary } = getContextMenuActions(this.menu.getActions({ arg: e.element, shouldForwardArgs: false }), 'inline'); this.contextMenuService.showContextMenu({ getAnchor: () => e.anchor, @@ -567,12 +566,11 @@ class BreakpointsRenderer implements IListRenderer 1); - createAndFillInActionBarActions(this.menu, { arg: breakpoint, shouldForwardArgs: true }, { primary, secondary: [] }, 'inline'); + const { primary } = getActionBarActions(this.menu.getActions({ arg: breakpoint, shouldForwardArgs: true }), 'inline'); data.actionBar.clear(); data.actionBar.push(primary, { icon: true, label: false }); breakpointIdToActionBarDomeNode.set(breakpoint.getId(), data.actionBar.domNode); @@ -651,11 +649,10 @@ class ExceptionBreakpointsRenderer implements IListRenderer 1); - createAndFillInActionBarActions(this.menu, { arg: exceptionBreakpoint, shouldForwardArgs: true }, { primary, secondary: [] }, 'inline'); + const { primary } = getActionBarActions(this.menu.getActions({ arg: exceptionBreakpoint, shouldForwardArgs: true }), 'inline'); data.actionBar.clear(); data.actionBar.push(primary, { icon: true, label: false }); breakpointIdToActionBarDomeNode.set(exceptionBreakpoint.getId(), data.actionBar.domNode); @@ -744,10 +741,9 @@ class FunctionBreakpointsRenderer implements IListRenderer 1); this.breakpointItemType.set('dataBreakpoint'); this.breakpointIsDataBytes.set(dataBreakpoint.src.type === DataBreakpointSetType.Address); - createAndFillInActionBarActions(this.menu, { arg: dataBreakpoint, shouldForwardArgs: true }, { primary, secondary: [] }, 'inline'); + const { primary } = getActionBarActions(this.menu.getActions({ arg: dataBreakpoint, shouldForwardArgs: true }), 'inline'); data.actionBar.clear(); data.actionBar.push(primary, { icon: true, label: false }); breakpointIdToActionBarDomeNode.set(dataBreakpoint.getId(), data.actionBar.domNode); diff --git a/src/vs/workbench/contrib/debug/browser/callStackView.ts b/src/vs/workbench/contrib/debug/browser/callStackView.ts index c1f2e3e287eee..782aeae9bbb9b 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackView.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackView.ts @@ -13,7 +13,7 @@ import { ITreeCompressionDelegate } from '../../../../base/browser/ui/tree/async import { ICompressedTreeNode } from '../../../../base/browser/ui/tree/compressedObjectTreeModel.js'; import { ICompressibleTreeRenderer } from '../../../../base/browser/ui/tree/objectTree.js'; import { IAsyncDataSource, ITreeContextMenuEvent, ITreeNode } from '../../../../base/browser/ui/tree/tree.js'; -import { Action, IAction } from '../../../../base/common/actions.js'; +import { Action } from '../../../../base/common/actions.js'; import { RunOnceScheduler } from '../../../../base/common/async.js'; import { Codicon } from '../../../../base/common/codicons.js'; import { Event } from '../../../../base/common/event.js'; @@ -23,7 +23,7 @@ import { posix } from '../../../../base/common/path.js'; import { commonSuffixLength } from '../../../../base/common/strings.js'; import { localize } from '../../../../nls.js'; import { ICommandActionTitle, Icon } from '../../../../platform/action/common/action.js'; -import { createAndFillInActionBarActions, createAndFillInContextMenuActions, MenuEntryActionViewItem, SubmenuEntryActionViewItem } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { getActionBarActions, getContextMenuActions, MenuEntryActionViewItem, SubmenuEntryActionViewItem } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { IMenuService, MenuId, MenuItemAction, MenuRegistry, registerAction2, SubmenuItemAction } from '../../../../platform/actions/common/actions.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { ContextKeyExpr, ContextKeyExpression, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; @@ -461,12 +461,9 @@ export class CallStackView extends ViewPane { overlay = getStackFrameContextOverlay(element); } - const primary: IAction[] = []; - const secondary: IAction[] = []; - const result = { primary, secondary }; const contextKeyService = this.contextKeyService.createOverlay(overlay); const menu = this.menuService.getMenuActions(MenuId.DebugCallStackContext, contextKeyService, { arg: getContextForContributedActions(element), shouldForwardArgs: true }); - createAndFillInContextMenuActions(menu, result, 'inline'); + const result = getContextMenuActions(menu, 'inline'); this.contextMenuService.showContextMenu({ getAnchor: () => e.anchor, getActions: () => result.secondary, @@ -592,11 +589,7 @@ class SessionsRenderer implements ICompressibleTreeRenderer { data.actionBar.clear(); - const primary: IAction[] = []; - const secondary: IAction[] = []; - const result = { primary, secondary }; - - createAndFillInActionBarActions(menu, { arg: getContextForContributedActions(session), shouldForwardArgs: true }, result, 'inline'); + const { primary } = getActionBarActions(menu.getActions({ arg: getContextForContributedActions(session), shouldForwardArgs: true }), 'inline'); data.actionBar.push(primary, { icon: true, label: false }); // We need to set our internal context on the action bar, since our commands depend on that one // While the external context our extensions rely on @@ -681,11 +674,7 @@ class ThreadsRenderer implements ICompressibleTreeRenderer { data.actionBar.clear(); - const primary: IAction[] = []; - const secondary: IAction[] = []; - const result = { primary, secondary }; - - createAndFillInActionBarActions(menu, { arg: getContextForContributedActions(thread), shouldForwardArgs: true }, result, 'inline'); + const { primary } = getActionBarActions(menu.getActions({ arg: getContextForContributedActions(thread), shouldForwardArgs: true }), 'inline'); data.actionBar.push(primary, { icon: true, label: false }); // We need to set our internal context on the action bar, since our commands depend on that one // While the external context our extensions rely on diff --git a/src/vs/workbench/contrib/debug/browser/debugCommands.ts b/src/vs/workbench/contrib/debug/browser/debugCommands.ts index 096a909b74a51..c6d45bda96265 100644 --- a/src/vs/workbench/contrib/debug/browser/debugCommands.ts +++ b/src/vs/workbench/contrib/debug/browser/debugCommands.ts @@ -36,7 +36,7 @@ import { showLoadedScriptMenu } from '../common/loadedScriptsPicker.js'; import { showDebugSessionMenu } from './debugSessionPicker.js'; import { TEXT_FILE_EDITOR_ID } from '../../files/common/files.js'; import { ILocalizedString } from '../../../../platform/action/common/action.js'; -import { CONTEXT_IN_CHAT_SESSION } from '../../chat/common/chatContextKeys.js'; +import { ChatContextKeys } from '../../chat/common/chatContextKeys.js'; import { DisposableStore } from '../../../../base/common/lifecycle.js'; export const ADD_CONFIGURATION_ID = 'debug.addConfiguration'; @@ -1014,7 +1014,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorContext, { CONTEXT_IN_DEBUG_MODE, PanelFocusContext.toNegated(), EditorContextKeys.editorTextFocus, - CONTEXT_IN_CHAT_SESSION.toNegated()), + ChatContextKeys.inChatSession.toNegated()), group: 'debug', order: 1 }); diff --git a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts index 84fd17595e032..26d6f90be38bc 100644 --- a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts +++ b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts @@ -226,8 +226,8 @@ export class ConfigurationManager implements IConfigurationManager { const picks: Promise[] = []; const provider = this.configProviders.find(p => p.type === type && p.triggerKind === DebugConfigurationProviderTriggerKind.Dynamic && p.provideDebugConfigurations); this.getLaunches().forEach(launch => { - if (launch.workspace && provider) { - picks.push(provider.provideDebugConfigurations!(launch.workspace.uri, token.token).then(configurations => configurations.map(config => ({ + if (provider) { + picks.push(provider.provideDebugConfigurations!(launch.workspace?.uri, token.token).then(configurations => configurations.map(config => ({ label: config.name, description: launch.name, config, diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts b/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts index 4072cf0b64a10..f6a5af3713a50 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts @@ -23,7 +23,7 @@ import { ServicesAccessor } from '../../../../platform/instantiation/common/inst import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js'; import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js'; import { PanelFocusContext } from '../../../common/contextkeys.js'; -import { CONTEXT_IN_CHAT_SESSION } from '../../chat/common/chatContextKeys.js'; +import { ChatContextKeys } from '../../chat/common/chatContextKeys.js'; import { openBreakpointSource } from './breakpointsView.js'; import { DisassemblyView } from './disassemblyView.js'; import { Repl } from './repl.js'; @@ -100,8 +100,7 @@ class ConditionalBreakpointAction extends EditorAction { constructor() { super({ id: 'editor.debug.action.conditionalBreakpoint', - label: nls.localize('conditionalBreakpointEditorAction', "Debug: Add Conditional Breakpoint..."), - alias: 'Debug: Add Conditional Breakpoint...', + label: nls.localize2('conditionalBreakpointEditorAction', "Debug: Add Conditional Breakpoint..."), precondition: CONTEXT_DEBUGGERS_AVAILABLE, menuOpts: { menuId: MenuId.MenubarNewBreakpointMenu, @@ -128,9 +127,8 @@ class LogPointAction extends EditorAction { constructor() { super({ id: 'editor.debug.action.addLogPoint', - label: nls.localize('logPointEditorAction', "Debug: Add Logpoint..."), + label: nls.localize2('logPointEditorAction', "Debug: Add Logpoint..."), precondition: CONTEXT_DEBUGGERS_AVAILABLE, - alias: 'Debug: Add Logpoint...', menuOpts: [ { menuId: MenuId.MenubarNewBreakpointMenu, @@ -309,7 +307,7 @@ export class RunToCursorAction extends EditorAction { CONTEXT_DEBUGGERS_AVAILABLE, PanelFocusContext.toNegated(), ContextKeyExpr.or(EditorContextKeys.editorTextFocus, CONTEXT_DISASSEMBLY_VIEW_FOCUS), - CONTEXT_IN_CHAT_SESSION.negate() + ChatContextKeys.inChatSession.negate() ), contextMenuOpts: { group: 'debug', @@ -354,7 +352,7 @@ export class SelectionToReplAction extends EditorAction { precondition: ContextKeyExpr.and( CONTEXT_IN_DEBUG_MODE, EditorContextKeys.editorTextFocus, - CONTEXT_IN_CHAT_SESSION.negate()), + ChatContextKeys.inChatSession.negate()), contextMenuOpts: { group: 'debug', order: 0 @@ -397,7 +395,7 @@ export class SelectionToWatchExpressionsAction extends EditorAction { precondition: ContextKeyExpr.and( CONTEXT_IN_DEBUG_MODE, EditorContextKeys.editorTextFocus, - CONTEXT_IN_CHAT_SESSION.negate()), + ChatContextKeys.inChatSession.negate()), contextMenuOpts: { group: 'debug', order: 1 @@ -443,8 +441,7 @@ class ShowDebugHoverAction extends EditorAction { constructor() { super({ id: 'editor.debug.action.showDebugHover', - label: nls.localize('showDebugHover', "Debug: Show Hover"), - alias: 'Debug: Show Hover', + label: nls.localize2('showDebugHover', "Debug: Show Hover"), precondition: CONTEXT_IN_DEBUG_MODE, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, @@ -596,8 +593,7 @@ class GoToNextBreakpointAction extends GoToBreakpointAction { constructor() { super(true, { id: 'editor.debug.action.goToNextBreakpoint', - label: nls.localize('goToNextBreakpoint', "Debug: Go to Next Breakpoint"), - alias: 'Debug: Go to Next Breakpoint', + label: nls.localize2('goToNextBreakpoint', "Debug: Go to Next Breakpoint"), precondition: CONTEXT_DEBUGGERS_AVAILABLE }); } @@ -607,8 +603,7 @@ class GoToPreviousBreakpointAction extends GoToBreakpointAction { constructor() { super(false, { id: 'editor.debug.action.goToPreviousBreakpoint', - label: nls.localize('goToPreviousBreakpoint', "Debug: Go to Previous Breakpoint"), - alias: 'Debug: Go to Previous Breakpoint', + label: nls.localize2('goToPreviousBreakpoint', "Debug: Go to Previous Breakpoint"), precondition: CONTEXT_DEBUGGERS_AVAILABLE }); } @@ -619,8 +614,7 @@ class CloseExceptionWidgetAction extends EditorAction { constructor() { super({ id: 'editor.debug.action.closeExceptionWidget', - label: nls.localize('closeExceptionWidget', "Close Exception Widget"), - alias: 'Close Exception Widget', + label: nls.localize2('closeExceptionWidget', "Close Exception Widget"), precondition: CONTEXT_EXCEPTION_WIDGET_VISIBLE, kbOpts: { primary: KeyCode.Escape, diff --git a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts index e4bc5719080e0..2cbbdc5602aaf 100644 --- a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts +++ b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts @@ -22,7 +22,7 @@ import { ServicesAccessor } from '../../../../editor/browser/editorExtensions.js import { localize } from '../../../../nls.js'; import { ICommandAction, ICommandActionTitle } from '../../../../platform/action/common/action.js'; import { DropdownWithPrimaryActionViewItem, IDropdownWithPrimaryActionViewItemOptions } from '../../../../platform/actions/browser/dropdownWithPrimaryActionViewItem.js'; -import { createActionViewItem, createAndFillInActionBarActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { createActionViewItem, getFlatActionBarActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { IMenu, IMenuService, MenuId, MenuItemAction, MenuRegistry } from '../../../../platform/actions/common/actions.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { ContextKeyExpr, ContextKeyExpression, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; @@ -132,8 +132,7 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { return this.hide(); } - const actions: IAction[] = []; - createAndFillInActionBarActions(this.debugToolBarMenu, { shouldForwardArgs: true }, actions); + const actions = getFlatActionBarActions(this.debugToolBarMenu.getActions({ shouldForwardArgs: true })); if (!arrays.equals(actions, this.activeActions, (first, second) => first.id === second.id && first.enabled === second.enabled)) { this.actionBar.clear(); this.actionBar.push(actions, { icon: true, label: false }); @@ -364,8 +363,7 @@ export function createDisconnectMenuItemAction(action: MenuItemAction, disposabl const instantiationService = accessor.get(IInstantiationService); const menu = menuService.getMenuActions(MenuId.DebugToolBarStop, contextKeyService, { shouldForwardArgs: true }); - const secondary: IAction[] = []; - createAndFillInActionBarActions(menu, secondary); + const secondary = getFlatActionBarActions(menu); if (!secondary.length) { return undefined; diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index 613fed3ce95ba..90e6524600970 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from '../../../../base/browser/dom.js'; +import * as domStylesheetsJs from '../../../../base/browser/domStylesheets.js'; import { IHistoryNavigationWidget } from '../../../../base/browser/history.js'; import { IActionViewItem } from '../../../../base/browser/ui/actionbar/actionbar.js'; import * as aria from '../../../../base/browser/ui/aria/aria.js'; @@ -39,7 +40,7 @@ import { ITextResourcePropertiesService } from '../../../../editor/common/servic import { SuggestController } from '../../../../editor/contrib/suggest/browser/suggestController.js'; import { localize, localize2 } from '../../../../nls.js'; import { AccessibilitySignal, IAccessibilitySignalService } from '../../../../platform/accessibilitySignal/browser/accessibilitySignalService.js'; -import { createAndFillInContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { getFlatContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { Action2, IMenu, IMenuService, MenuId, registerAction2 } from '../../../../platform/actions/common/actions.js'; import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; @@ -576,11 +577,7 @@ export class Repl extends FilterViewPane implements IHistoryNavigationWidget { private get refreshScheduler(): RunOnceScheduler { const autoExpanded = new Set(); return new RunOnceScheduler(async () => { - if (!this.tree) { - return; - } - - if (!this.tree.getInput()) { + if (!this.tree || !this.tree.getInput() || !this.isVisible()) { return; } @@ -712,7 +709,7 @@ export class Repl extends FilterViewPane implements IHistoryNavigationWidget { })); // Make sure to select the session if debugging is already active this.selectSession(); - this.styleElement = dom.createStyleSheet(this.container); + this.styleElement = domStylesheetsJs.createStyleSheet(this.container); this.onDidStyleChange(); } @@ -772,8 +769,7 @@ export class Repl extends FilterViewPane implements IHistoryNavigationWidget { } private onContextMenu(e: ITreeContextMenuEvent): void { - const actions: IAction[] = []; - createAndFillInContextMenuActions(this.menu, { arg: e.element, shouldForwardArgs: false }, actions); + const actions = getFlatContextMenuActions(this.menu.getActions({ arg: e.element, shouldForwardArgs: false })); this.contextMenuService.showContextMenu({ getAnchor: () => e.anchor, getActions: () => actions, @@ -907,8 +903,7 @@ class AcceptReplInputAction extends EditorAction { constructor() { super({ id: 'repl.action.acceptInput', - label: localize({ key: 'actions.repl.acceptInput', comment: ['Apply input from the debug console input box'] }, "Debug Console: Accept Input"), - alias: 'Debug Console: Accept Input', + label: localize2({ key: 'actions.repl.acceptInput', comment: ['Apply input from the debug console input box'] }, "Debug Console: Accept Input"), precondition: CONTEXT_IN_DEBUG_REPL, kbOpts: { kbExpr: EditorContextKeys.textInputFocus, diff --git a/src/vs/workbench/contrib/debug/browser/replViewer.ts b/src/vs/workbench/contrib/debug/browser/replViewer.ts index 944636a224615..ca2d6da347ce4 100644 --- a/src/vs/workbench/contrib/debug/browser/replViewer.ts +++ b/src/vs/workbench/contrib/debug/browser/replViewer.ts @@ -220,6 +220,7 @@ export class ReplOutputElementRenderer implements ITreeRenderer, _index: number, templateData: IOutputReplElementTemplateData): void { diff --git a/src/vs/workbench/contrib/debug/browser/variablesView.ts b/src/vs/workbench/contrib/debug/browser/variablesView.ts index 696d7eebc0ef6..a40de3b4a3648 100644 --- a/src/vs/workbench/contrib/debug/browser/variablesView.ts +++ b/src/vs/workbench/contrib/debug/browser/variablesView.ts @@ -19,7 +19,7 @@ import { FuzzyScore, createMatches } from '../../../../base/common/filters.js'; import { IDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; import { localize } from '../../../../nls.js'; -import { createAndFillInContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { getContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { IMenuService, MenuId, registerAction2 } from '../../../../platform/actions/common/actions.js'; import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js'; import { CommandsRegistry } from '../../../../platform/commands/common/commands.js'; @@ -250,8 +250,7 @@ export async function openContextMenuForVariableTreeElement(parentContextKeyServ const context: IVariablesContext = getVariablesContext(variable); const menu = menuService.getMenuActions(menuId, contextKeyService, { arg: context, shouldForwardArgs: false }); - const secondary: IAction[] = []; - createAndFillInContextMenuActions(menu, { primary: [], secondary }, 'inline'); + const { secondary } = getContextMenuActions(menu, 'inline'); contextMenuService.showContextMenu({ getAnchor: () => e.anchor, getActions: () => secondary @@ -496,8 +495,7 @@ export class VisualizedVariableRenderer extends AbstractExpressionsRenderer { const context = viz.original ? getVariablesContext(viz.original) : undefined; const menu = this.menuService.getMenuActions(MenuId.DebugVariablesContext, contextKeyService, { arg: context, shouldForwardArgs: false }); - const primary: IAction[] = []; - createAndFillInContextMenuActions(menu, { primary, secondary: [] }, 'inline'); + const { primary } = getContextMenuActions(menu, 'inline'); if (viz.original) { const action = new Action('debugViz', localize('removeVisualizer', 'Remove Visualizer'), ThemeIcon.asClassName(Codicon.eye), true, () => this.debugService.getViewModel().setVisualizedExpression(viz.original!, undefined)); @@ -572,10 +570,9 @@ export class VariablesRenderer extends AbstractExpressionsRenderer { const variable = expression as Variable; const contextKeyService = getContextForVariableMenuBase(this.contextKeyService, variable); - const primary: IAction[] = []; const context = getVariablesContext(variable); const menu = this.menuService.getMenuActions(MenuId.DebugVariablesContext, contextKeyService, { arg: context, shouldForwardArgs: false }); - createAndFillInContextMenuActions(menu, { primary, secondary: [] }, 'inline'); + const { primary } = getContextMenuActions(menu, 'inline'); actionBar.clear(); actionBar.context = context; diff --git a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts index 95d69163a663d..c785c370565a7 100644 --- a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts +++ b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts @@ -10,12 +10,11 @@ import { IListVirtualDelegate, ListDragOverEffectPosition, ListDragOverEffectTyp import { ElementsDragAndDropData, ListViewTargetSector } from '../../../../base/browser/ui/list/listView.js'; import { IListAccessibilityProvider } from '../../../../base/browser/ui/list/listWidget.js'; import { ITreeContextMenuEvent, ITreeDragAndDrop, ITreeDragOverReaction, ITreeMouseEvent, ITreeNode } from '../../../../base/browser/ui/tree/tree.js'; -import { IAction } from '../../../../base/common/actions.js'; import { RunOnceScheduler } from '../../../../base/common/async.js'; import { Codicon } from '../../../../base/common/codicons.js'; import { FuzzyScore } from '../../../../base/common/filters.js'; import { localize } from '../../../../nls.js'; -import { createAndFillInContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { getContextMenuActions, getFlatContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { Action2, IMenu, IMenuService, MenuId, registerAction2 } from '../../../../platform/actions/common/actions.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { ContextKeyExpr, IContextKey, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; @@ -219,10 +218,9 @@ export class WatchExpressionsView extends ViewPane { const selection = this.tree.getSelection(); this.watchItemType.set(element instanceof Expression ? 'expression' : element instanceof Variable ? 'variable' : undefined); - const actions: IAction[] = []; const attributes = element instanceof Variable ? element.presentationHint?.attributes : undefined; this.variableReadonly.set(!!attributes && attributes.indexOf('readOnly') >= 0 || !!element?.presentationHint?.lazy); - createAndFillInContextMenuActions(this.menu, { arg: element, shouldForwardArgs: true }, actions); + const actions = getFlatContextMenuActions(this.menu.getActions({ arg: element, shouldForwardArgs: true })); this.contextMenuService.showContextMenu({ getAnchor: () => e.anchor, getActions: () => actions, @@ -379,8 +377,7 @@ export class WatchExpressionsRenderer extends AbstractExpressionsRenderer { const context = expression; const menu = this.menuService.getMenuActions(MenuId.DebugWatchContext, contextKeyService, { arg: context, shouldForwardArgs: false }); - const primary: IAction[] = []; - createAndFillInContextMenuActions(menu, { primary, secondary: [] }, 'inline'); + const { primary } = getContextMenuActions(menu, 'inline'); actionBar.clear(); actionBar.context = context; diff --git a/src/vs/workbench/contrib/dropOrPasteInto/browser/commands.ts b/src/vs/workbench/contrib/dropOrPasteInto/browser/commands.ts new file mode 100644 index 0000000000000..6b8a830058e03 --- /dev/null +++ b/src/vs/workbench/contrib/dropOrPasteInto/browser/commands.ts @@ -0,0 +1,45 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { toAction } from '../../../../base/common/actions.js'; +import { CopyPasteController, pasteAsPreferenceConfig } from '../../../../editor/contrib/dropOrPasteInto/browser/copyPasteController.js'; +import { DropIntoEditorController, dropAsPreferenceConfig } from '../../../../editor/contrib/dropOrPasteInto/browser/dropIntoEditorController.js'; +import { localize } from '../../../../nls.js'; +import { IWorkbenchContribution } from '../../../common/contributions.js'; +import { IPreferencesService } from '../../../services/preferences/common/preferences.js'; + +export class DropOrPasteIntoCommands implements IWorkbenchContribution { + public static ID = 'workbench.contrib.dropOrPasteInto'; + + constructor( + @IPreferencesService private readonly _preferencesService: IPreferencesService + ) { + CopyPasteController.setConfigureDefaultAction(toAction({ + id: 'workbench.action.configurePreferredPasteAction', + label: localize('configureDefaultPaste.label', 'Configure preferred paste action...'), + run: () => this.configurePreferredPasteAction() + })); + + DropIntoEditorController.setConfigureDefaultAction(toAction({ + id: 'workbench.action.configurePreferredDropAction', + label: localize('configureDefaultDrop.label', 'Configure preferred drop action...'), + run: () => this.configurePreferredDropAction() + })); + } + + private configurePreferredPasteAction() { + return this._preferencesService.openUserSettings({ + jsonEditor: true, + revealSetting: { key: pasteAsPreferenceConfig, edit: true } + }); + } + + private configurePreferredDropAction() { + return this._preferencesService.openUserSettings({ + jsonEditor: true, + revealSetting: { key: dropAsPreferenceConfig, edit: true } + }); + } +} diff --git a/src/vs/workbench/contrib/dropOrPasteInto/browser/configurationSchema.ts b/src/vs/workbench/contrib/dropOrPasteInto/browser/configurationSchema.ts new file mode 100644 index 0000000000000..50d72b1cfc72f --- /dev/null +++ b/src/vs/workbench/contrib/dropOrPasteInto/browser/configurationSchema.ts @@ -0,0 +1,157 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Emitter, Event } from '../../../../base/common/event.js'; +import { HierarchicalKind } from '../../../../base/common/hierarchicalKind.js'; +import { IJSONSchema } from '../../../../base/common/jsonSchema.js'; +import { Disposable } from '../../../../base/common/lifecycle.js'; +import { editorConfigurationBaseNode } from '../../../../editor/common/config/editorConfigurationSchema.js'; +import { ILanguageFeaturesService } from '../../../../editor/common/services/languageFeatures.js'; +import { pasteAsCommandId } from '../../../../editor/contrib/dropOrPasteInto/browser/copyPasteContribution.js'; +import { pasteAsPreferenceConfig } from '../../../../editor/contrib/dropOrPasteInto/browser/copyPasteController.js'; +import { dropAsPreferenceConfig } from '../../../../editor/contrib/dropOrPasteInto/browser/dropIntoEditorController.js'; +import * as nls from '../../../../nls.js'; +import { ConfigurationScope, Extensions, IConfigurationNode, IConfigurationPropertySchema, IConfigurationRegistry } from '../../../../platform/configuration/common/configurationRegistry.js'; +import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; +import { Registry } from '../../../../platform/registry/common/platform.js'; +import { IWorkbenchContribution } from '../../../common/contributions.js'; + +const dropEnumValues: string[] = []; + +const dropAsPreferenceSchema: IConfigurationPropertySchema = { + type: 'array', + scope: ConfigurationScope.LANGUAGE_OVERRIDABLE, + description: nls.localize('dropPreferredDescription', "Configures the preferred type of edit to use when dropping content.\n\nThis is an ordered list of edit kinds. The first available edit of a preferred kind will be used."), + default: [], + items: { + description: nls.localize('dropKind', "The kind identifier of the drop edit."), + anyOf: [ + { type: 'string' }, + { enum: dropEnumValues } + ], + } +}; + +const pasteEnumValues: string[] = []; + +const pasteAsPreferenceSchema: IConfigurationPropertySchema = { + type: 'array', + scope: ConfigurationScope.LANGUAGE_OVERRIDABLE, + description: nls.localize('pastePreferredDescription', "Configures the preferred type of edit to use when pasting content.\n\nThis is an ordered list of edit kinds. The first available edit of a preferred kind will be used."), + default: [], + items: { + description: nls.localize('pasteKind', "The kind identifier of the paste edit."), + anyOf: [ + { type: 'string' }, + { enum: pasteEnumValues } + ] + } +}; + +export const editorConfiguration = Object.freeze({ + ...editorConfigurationBaseNode, + properties: { + [pasteAsPreferenceConfig]: pasteAsPreferenceSchema, + [dropAsPreferenceConfig]: dropAsPreferenceSchema, + } +}); + +export class DropOrPasteSchemaContribution extends Disposable implements IWorkbenchContribution { + + public static ID = 'workbench.contrib.dropOrPasteIntoSchema'; + + private readonly _onDidChangeSchemaContributions = this._register(new Emitter()); + + private _allProvidedDropKinds: HierarchicalKind[] = []; + private _allProvidedPasteKinds: HierarchicalKind[] = []; + + constructor( + @IKeybindingService keybindingService: IKeybindingService, + @ILanguageFeaturesService private readonly languageFeatures: ILanguageFeaturesService + ) { + super(); + + this._register( + Event.runAndSubscribe( + Event.debounce( + Event.any(languageFeatures.documentPasteEditProvider.onDidChange, languageFeatures.documentPasteEditProvider.onDidChange), + () => { }, + 1000, + ), () => { + this.updateProvidedKinds(); + this.updateConfigurationSchema(); + + this._onDidChangeSchemaContributions.fire(); + })); + + keybindingService.registerSchemaContribution({ + getSchemaAdditions: () => this.getKeybindingSchemaAdditions(), + onDidChange: this._onDidChangeSchemaContributions.event, + }); + } + + private updateProvidedKinds(): void { + // Drop + const dropKinds = new Map(); + for (const provider of this.languageFeatures.documentDropEditProvider.allNoModel()) { + for (const kind of provider.providedDropEditKinds ?? []) { + dropKinds.set(kind.value, kind); + } + } + this._allProvidedDropKinds = Array.from(dropKinds.values()); + + // Paste + const pasteKinds = new Map(); + for (const provider of this.languageFeatures.documentPasteEditProvider.allNoModel()) { + for (const kind of provider.providedPasteEditKinds ?? []) { + pasteKinds.set(kind.value, kind); + } + } + this._allProvidedPasteKinds = Array.from(pasteKinds.values()); + } + + private updateConfigurationSchema(): void { + pasteEnumValues.length = 0; + for (const codeActionKind of this._allProvidedPasteKinds) { + pasteEnumValues.push(codeActionKind.value); + } + + dropEnumValues.length = 0; + for (const codeActionKind of this._allProvidedDropKinds) { + dropEnumValues.push(codeActionKind.value); + } + + Registry.as(Extensions.Configuration) + .notifyConfigurationSchemaUpdated(editorConfiguration); + } + + private getKeybindingSchemaAdditions(): IJSONSchema[] { + return [ + { + if: { + required: ['command'], + properties: { + 'command': { const: pasteAsCommandId } + } + }, + then: { + properties: { + 'args': { + required: ['kind'], + properties: { + 'kind': { + anyOf: [ + { enum: Array.from(this._allProvidedPasteKinds.map(x => x.value)) }, + { type: 'string' }, + ] + } + } + } + } + } + }, + ]; + } +} diff --git a/src/vs/workbench/contrib/dropOrPasteInto/browser/dropOrPasteInto.contribution.ts b/src/vs/workbench/contrib/dropOrPasteInto/browser/dropOrPasteInto.contribution.ts new file mode 100644 index 0000000000000..4957bd47736aa --- /dev/null +++ b/src/vs/workbench/contrib/dropOrPasteInto/browser/dropOrPasteInto.contribution.ts @@ -0,0 +1,15 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import { Extensions, IConfigurationRegistry } from '../../../../platform/configuration/common/configurationRegistry.js'; +import { Registry } from '../../../../platform/registry/common/platform.js'; +import { registerWorkbenchContribution2, WorkbenchPhase } from '../../../common/contributions.js'; +import { DropOrPasteIntoCommands } from './commands.js'; +import { DropOrPasteSchemaContribution, editorConfiguration } from './configurationSchema.js'; + +registerWorkbenchContribution2(DropOrPasteIntoCommands.ID, DropOrPasteIntoCommands, WorkbenchPhase.Eventually); +registerWorkbenchContribution2(DropOrPasteSchemaContribution.ID, DropOrPasteSchemaContribution, WorkbenchPhase.Eventually); + +Registry.as(Extensions.Configuration) + .registerConfiguration(editorConfiguration); diff --git a/src/vs/workbench/contrib/emmet/browser/actions/expandAbbreviation.ts b/src/vs/workbench/contrib/emmet/browser/actions/expandAbbreviation.ts index afd2c722adf26..59e1c903fdd7b 100644 --- a/src/vs/workbench/contrib/emmet/browser/actions/expandAbbreviation.ts +++ b/src/vs/workbench/contrib/emmet/browser/actions/expandAbbreviation.ts @@ -16,8 +16,7 @@ class ExpandAbbreviationAction extends EmmetEditorAction { constructor() { super({ id: 'editor.emmet.action.expandAbbreviation', - label: nls.localize('expandAbbreviationAction', "Emmet: Expand Abbreviation"), - alias: 'Emmet: Expand Abbreviation', + label: nls.localize2('expandAbbreviationAction', "Emmet: Expand Abbreviation"), precondition: EditorContextKeys.writable, actionName: 'expand_abbreviation', kbOpts: { diff --git a/src/vs/workbench/contrib/emmet/browser/emmetActions.ts b/src/vs/workbench/contrib/emmet/browser/emmetActions.ts index dc3b0655872d4..eb0d2befd7e9d 100644 --- a/src/vs/workbench/contrib/emmet/browser/emmetActions.ts +++ b/src/vs/workbench/contrib/emmet/browser/emmetActions.ts @@ -42,9 +42,9 @@ class GrammarContributions implements IGrammarContributions { } } -interface IEmmetActionOptions extends IActionOptions { +type IEmmetActionOptions = IActionOptions & { actionName: string; -} +}; export abstract class EmmetEditorAction extends EditorAction { diff --git a/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts b/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts index f6873bf5524f0..32f18eed45601 100644 --- a/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/abstractRuntimeExtensionsEditor.ts @@ -17,7 +17,7 @@ import { IDisposable, dispose } from '../../../../base/common/lifecycle.js'; import { Schemas } from '../../../../base/common/network.js'; import * as nls from '../../../../nls.js'; import { Categories } from '../../../../platform/action/common/actionCommonCategories.js'; -import { createAndFillInContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; +import { getContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { Action2, IMenuService, MenuId } from '../../../../platform/actions/common/actions.js'; import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js'; import { ContextKeyExpr, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; @@ -497,9 +497,8 @@ export abstract class AbstractRuntimeExtensionsEditor extends EditorPane { } actions.push(new Separator()); - const menuActions = this._menuService.getMenuActions(MenuId.ExtensionEditorContextMenu, this.contextKeyService); - createAndFillInContextMenuActions(menuActions, { primary: [], secondary: actions }); + actions.push(...getContextMenuActions(menuActions,).secondary); this._contextMenuService.showContextMenu({ getAnchor: () => e.anchor, diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index b3cc78f56ccde..749790dd3a884 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { $, Dimension, addDisposableListener, append, setParentFlowTo } from '../../../../base/browser/dom.js'; +import { $, Dimension, addDisposableListener, append, hide, setParentFlowTo, show } from '../../../../base/browser/dom.js'; import { ActionBar } from '../../../../base/browser/ui/actionbar/actionbar.js'; import { getDefaultHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js'; import { DomScrollableElement } from '../../../../base/browser/ui/scrollbar/scrollableElement.js'; @@ -17,8 +17,7 @@ import { Emitter, Event } from '../../../../base/common/event.js'; import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js'; import { Disposable, DisposableStore, MutableDisposable, dispose, toDisposable } from '../../../../base/common/lifecycle.js'; import { Schemas, matchesScheme } from '../../../../base/common/network.js'; -import { language } from '../../../../base/common/platform.js'; -import * as semver from '../../../../base/common/semver/semver.js'; +import { isNative, language } from '../../../../base/common/platform.js'; import { isUndefined } from '../../../../base/common/types.js'; import { URI } from '../../../../base/common/uri.js'; import { generateUuid } from '../../../../base/common/uuid.js'; @@ -31,7 +30,7 @@ import { localize } from '../../../../nls.js'; import { Action2, registerAction2 } from '../../../../platform/actions/common/actions.js'; import { ContextKeyExpr, IContextKey, IContextKeyService, IScopedContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js'; import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js'; -import { IExtensionGalleryService, IGalleryExtension } from '../../../../platform/extensionManagement/common/extensionManagement.js'; +import { computeSize, IExtensionGalleryService, IGalleryExtension, ILocalExtension } from '../../../../platform/extensionManagement/common/extensionManagement.js'; import { areSameExtensions } from '../../../../platform/extensionManagement/common/extensionManagementUtil.js'; import { ExtensionType, IExtensionManifest } from '../../../../platform/extensions/common/extensions.js'; import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; @@ -88,6 +87,26 @@ import { IViewsService } from '../../../services/views/common/viewsService.js'; import { VIEW_ID as EXPLORER_VIEW_ID } from '../../files/common/files.js'; import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js'; import { IHoverService } from '../../../../platform/hover/browser/hover.js'; +import { IFileService } from '../../../../platform/files/common/files.js'; +import { IUserDataProfilesService } from '../../../../platform/userDataProfile/common/userDataProfile.js'; +import { IRemoteAgentService } from '../../../services/remote/common/remoteAgentService.js'; + +function toDateString(date: Date) { + return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}, ${date.toLocaleTimeString(language, { hourCycle: 'h23' })}`; +} + +function toMemoryString(bytes: number) { + if (bytes < 1024) { + return `${bytes} B`; + } + if (bytes < 1024 * 1024) { + return `${(bytes / 1024).toFixed(1)} KB`; + } + if (bytes < 1024 * 1024 * 1024) { + return `${(bytes / 1024 / 1024).toFixed(1)} MB`; + } + return `${(bytes / 1024 / 1024 / 1024).toFixed(1)} GB`; +} class NavBar extends Disposable { @@ -197,15 +216,16 @@ class VersionWidget extends ExtensionWithDifferentGalleryVersionWidget { hoverService: IHoverService ) { super(); - this.element = append(container, $('code.version')); + this.element = append(container, $('code.version', undefined, 'pre-release')); this._register(hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), this.element, localize('extension version', "Extension Version"))); this.render(); } render(): void { - if (!this.extension || !semver.valid(this.extension.version)) { - return; + if (this.extension?.preRelease) { + show(this.element); + } else { + hide(this.element); } - this.element.textContent = `v${this.gallery?.version ?? this.extension.version}${this.extension.isPreReleaseVersion ? ' (pre-release)' : ''}`; } } @@ -255,6 +275,9 @@ export class ExtensionEditor extends EditorPane { @IViewsService private readonly viewsService: IViewsService, @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, @IHoverService private readonly hoverService: IHoverService, + @IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService, + @IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService, + @IFileService private readonly fileService: IFileService, ) { super(ExtensionEditor.ID, group, telemetryService, themeService, storageService); this.extensionReadme = null; @@ -923,9 +946,14 @@ export class ExtensionEditor extends EditorPane { this.contentDisposables.add(toDisposable(removeLayoutParticipant)); this.contentDisposables.add(scrollableContent); + if (extension.local) { + this.renderInstallInfo(content, extension.local); + } + if (extension.gallery) { + this.renderMarketplaceInfo(content, extension); + } this.renderCategories(content, extension); this.renderExtensionResources(content, extension); - this.renderMoreInfo(content, extension); append(container, scrollableContent.getDomNode()); scrollableContent.scanDomNode(); @@ -980,37 +1008,122 @@ export class ExtensionEditor extends EditorPane { } } - private renderMoreInfo(container: HTMLElement, extension: IExtension): void { + private renderInstallInfo(container: HTMLElement, extension: ILocalExtension): void { + const installInfoContainer = append(container, $('.more-info-container.additional-details-element')); + append(installInfoContainer, $('.additional-details-title', undefined, localize('Install Info', "Installation"))); + const installInfo = append(installInfoContainer, $('.more-info')); + append(installInfo, + $('.more-info-entry', undefined, + $('div', undefined, localize('id', "Identifier")), + $('code', undefined, extension.identifier.id) + )); + append(installInfo, + $('.more-info-entry', undefined, + $('div', undefined, localize('Version', "Version")), + $('code', undefined, extension.manifest.version) + ) + ); + if (extension.installedTimestamp) { + append(installInfo, + $('.more-info-entry', undefined, + $('div', undefined, localize('last updated', "Last Updated")), + $('div', undefined, toDateString(new Date(extension.installedTimestamp))) + ) + ); + } + if (extension.source !== 'gallery') { + const element = $('div', undefined, extension.source === 'vsix' ? localize('vsix', "VSIX") : localize('other', "Local")); + append(installInfo, + $('.more-info-entry', undefined, + $('div', undefined, localize('source', "Source")), + element + ) + ); + if (isNative && extension.source === 'resource' && extension.location.scheme === Schemas.file) { + element.classList.add('link'); + element.title = extension.location.fsPath; + this.transientDisposables.add(onClick(element, () => this.openerService.open(extension.location, { openExternal: true }))); + } + } + if (extension.size) { + const element = $('div', undefined, toMemoryString(extension.size)); + append(installInfo, + $('.more-info-entry', undefined, + $('div', { title: localize('size when installed', "Size when installed") }, localize('size', "Size")), + element + ) + ); + if (isNative && extension.location.scheme === Schemas.file) { + element.classList.add('link'); + element.title = extension.location.fsPath; + this.transientDisposables.add(onClick(element, () => this.openerService.open(extension.location, { openExternal: true }))); + } + } + this.getCacheLocation(extension).then(cacheLocation => { + if (!cacheLocation) { + return; + } + computeSize(cacheLocation, this.fileService).then(cacheSize => { + if (!cacheSize) { + return; + } + const element = $('div', undefined, toMemoryString(cacheSize)); + append(installInfo, + $('.more-info-entry', undefined, + $('div', { title: localize('disk space used', "Cache size") }, localize('cache size', "Cache")), + element) + ); + if (isNative && extension.location.scheme === Schemas.file) { + element.classList.add('link'); + element.title = cacheLocation.fsPath; + this.transientDisposables.add(onClick(element, () => this.openerService.open(cacheLocation.with({ scheme: Schemas.file }), { openExternal: true }))); + } + }); + }); + } + + private async getCacheLocation(extension: ILocalExtension): Promise { + let extensionCacheLocation = this.uriIdentityService.extUri.joinPath(this.userDataProfilesService.defaultProfile.globalStorageHome, extension.identifier.id.toLowerCase()); + if (extension.location.scheme === Schemas.vscodeRemote) { + const environment = await this.remoteAgentService.getEnvironment(); + if (!environment) { + return undefined; + } + extensionCacheLocation = this.uriIdentityService.extUri.joinPath(environment.globalStorageHome, extension.identifier.id.toLowerCase()); + } + return extensionCacheLocation; + } + + private renderMarketplaceInfo(container: HTMLElement, extension: IExtension): void { const gallery = extension.gallery; const moreInfoContainer = append(container, $('.more-info-container.additional-details-element')); - append(moreInfoContainer, $('.additional-details-title', undefined, localize('Marketplace Info', "More Info"))); + append(moreInfoContainer, $('.additional-details-title', undefined, localize('Marketplace Info', "Marketplace"))); const moreInfo = append(moreInfoContainer, $('.more-info')); - const toDateString = (date: Date) => `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}, ${date.toLocaleTimeString(language, { hourCycle: 'h23' })}`; if (gallery) { + if (!extension.local) { + append(moreInfo, + $('.more-info-entry', undefined, + $('div', undefined, localize('id', "Identifier")), + $('code', undefined, extension.identifier.id) + )); + append(moreInfo, + $('.more-info-entry', undefined, + $('div', undefined, localize('Version', "Version")), + $('code', undefined, gallery.version) + ) + ); + } append(moreInfo, $('.more-info-entry', undefined, $('div', undefined, localize('published', "Published")), $('div', undefined, toDateString(new Date(gallery.releaseDate))) ), $('.more-info-entry', undefined, - $('div', undefined, localize('last released', "Last released")), + $('div', undefined, localize('last released', "Last Released")), $('div', undefined, toDateString(new Date(gallery.lastUpdated))) ) ); } - if (extension.local && extension.local.installedTimestamp) { - append(moreInfo, - $('.more-info-entry', undefined, - $('div', undefined, localize('last updated', "Last updated")), - $('div', undefined, toDateString(new Date(extension.local.installedTimestamp))) - ) - ); - } - append(moreInfo, - $('.more-info-entry', undefined, - $('div', undefined, localize('id', "Identifier")), - $('code', undefined, extension.identifier.id) - )); } private openChangelog(extension: IExtension, template: IExtensionEditorTemplate, token: CancellationToken): Promise { diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index f4d62c70da15b..4db7fda24f24f 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -8,7 +8,7 @@ import { KeyMod, KeyCode } from '../../../../base/common/keyCodes.js'; import { Registry } from '../../../../platform/registry/common/platform.js'; import { MenuRegistry, MenuId, registerAction2, Action2, IMenuItem, IAction2Options } from '../../../../platform/actions/common/actions.js'; import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; -import { ExtensionsLocalizedLabel, IExtensionManagementService, IExtensionGalleryService, PreferencesLocalizedLabel, EXTENSION_INSTALL_SOURCE_CONTEXT, ExtensionInstallSource } from '../../../../platform/extensionManagement/common/extensionManagement.js'; +import { ExtensionsLocalizedLabel, IExtensionManagementService, IExtensionGalleryService, PreferencesLocalizedLabel, EXTENSION_INSTALL_SOURCE_CONTEXT, ExtensionInstallSource, UseUnpkgResourceApi, InstallOperation } from '../../../../platform/extensionManagement/common/extensionManagement.js'; import { EnablementState, IExtensionManagementServerService, IWorkbenchExtensionEnablementService, IWorkbenchExtensionManagementService, extensionsConfigurationNodeBase } from '../../../services/extensionManagement/common/extensionManagement.js'; import { IExtensionIgnoredRecommendationsService, IExtensionRecommendationsService } from '../../../services/extensionRecommendations/common/extensionRecommendations.js'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from '../../../common/contributions.js'; @@ -77,6 +77,7 @@ import { CONTEXT_KEYBINDINGS_EDITOR } from '../../preferences/common/preferences import { ProgressLocation } from '../../../../platform/progress/common/progress.js'; import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js'; import { IConfigurationMigrationRegistry, Extensions as ConfigurationMigrationExtensions } from '../../../common/configuration.js'; +import { IProductService } from '../../../../platform/product/common/productService.js'; // Singletons registerSingleton(IExtensionsWorkbenchService, ExtensionsWorkbenchService, InstantiationType.Eager /* Auto updates extensions */); @@ -259,6 +260,13 @@ Registry.as(ConfigurationExtensions.Configuration) default: true, scope: ConfigurationScope.APPLICATION, included: isNative && !isLinux + }, + [UseUnpkgResourceApi]: { + type: 'boolean', + description: localize('extensions.gallery.useUnpkgResourceApi', "When enabled, extensions to update are fetched from Unpkg service."), + default: true, + scope: ConfigurationScope.APPLICATION, + tags: ['onExp', 'usesOnlineServices'] } } }); @@ -484,7 +492,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi constructor( @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, - @IExtensionGalleryService extensionGalleryService: IExtensionGalleryService, + @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, @IContextKeyService contextKeyService: IContextKeyService, @IViewsService private readonly viewsService: IViewsService, @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, @@ -492,6 +500,10 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi @IInstantiationService private readonly instantiationService: IInstantiationService, @IDialogService private readonly dialogService: IDialogService, @ICommandService private readonly commandService: ICommandService, + @IFileDialogService private readonly fileDialogService: IFileDialogService, + @IProductService private readonly productService: IProductService, + @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, + @INotificationService private readonly notificationService: INotificationService, ) { super(); const hasGalleryContext = CONTEXT_HAS_GALLERY.bindTo(contextKeyService); @@ -1575,6 +1587,53 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi run: async (accessor: ServicesAccessor, id: string) => accessor.get(IPreferencesService).openSettings({ jsonEditor: false, query: `@ext:${id}` }) }); + const downloadVSIX = async (extensionId: string, preRelease: boolean) => { + const result = await this.fileDialogService.showOpenDialog({ + title: localize('download title', "Select folder to download the VSIX"), + canSelectFiles: false, + canSelectFolders: true, + canSelectMany: false, + openLabel: localize('download', "Download"), + }); + + if (!result?.[0]) { + return; + } + + const [galleryExtension] = await this.extensionGalleryService.getExtensions([{ id: extensionId, preRelease: true }], { compatible: true }, CancellationToken.None); + if (!galleryExtension) { + throw new Error(localize('not found', "Extension '{0}' not found.", extensionId)); + } + await this.extensionGalleryService.download(galleryExtension, this.uriIdentityService.extUri.joinPath(result[0], `${galleryExtension.identifier.id}-${galleryExtension.version}.vsix`), InstallOperation.None); + this.notificationService.info(localize('download.completed', "Successfully downloaded the VSIX")); + }; + + this.registerExtensionAction({ + id: 'workbench.extensions.action.download', + title: localize('download VSIX', "Download VSIX"), + menu: { + id: MenuId.ExtensionContext, + when: ContextKeyExpr.and(ContextKeyExpr.equals('extensionStatus', 'uninstalled'), ContextKeyExpr.has('isGalleryExtension')), + order: this.productService.quality === 'stable' ? 0 : 1 + }, + run: async (accessor: ServicesAccessor, extensionId: string) => { + downloadVSIX(extensionId, false); + } + }); + + this.registerExtensionAction({ + id: 'workbench.extensions.action.downloadPreRelease', + title: localize('download pre-release', "Download Pre-Release VSIX"), + menu: { + id: MenuId.ExtensionContext, + when: ContextKeyExpr.and(ContextKeyExpr.equals('extensionStatus', 'uninstalled'), ContextKeyExpr.has('isGalleryExtension'), ContextKeyExpr.has('extensionHasPreReleaseVersion')), + order: this.productService.quality === 'stable' ? 1 : 0 + }, + run: async (accessor: ServicesAccessor, extensionId: string) => { + downloadVSIX(extensionId, true); + } + }); + this.registerExtensionAction({ id: 'workbench.extensions.action.manageAccountPreferences', title: localize2('workbench.extensions.action.changeAccountPreference', "Account Preferences"), diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts index ebaa858818104..d49b37d221211 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts @@ -6,9 +6,9 @@ import { localize } from '../../../../nls.js'; import { Disposable, DisposableStore, toDisposable } from '../../../../base/common/lifecycle.js'; import { Event, Emitter } from '../../../../base/common/event.js'; -import { isCancellationError, getErrorMessage } from '../../../../base/common/errors.js'; +import { isCancellationError, getErrorMessage, CancellationError } from '../../../../base/common/errors.js'; import { createErrorWithActions } from '../../../../base/common/errorMessage.js'; -import { PagedModel, IPagedModel, IPager, DelayedPagedModel } from '../../../../base/common/paging.js'; +import { PagedModel, IPagedModel, DelayedPagedModel, IPager } from '../../../../base/common/paging.js'; import { SortOrder, IQueryOptions as IGalleryQueryOptions, SortBy as GallerySortBy, InstallExtensionInfo, ExtensionGalleryErrorCode, ExtensionGalleryError } from '../../../../platform/extensionManagement/common/extensionManagement.js'; import { IExtensionManagementServer, IExtensionManagementServerService, EnablementState, IWorkbenchExtensionManagementService, IWorkbenchExtensionEnablementService } from '../../../services/extensionManagement/common/extensionManagement.js'; import { IExtensionRecommendationsService } from '../../../services/extensionRecommendations/common/extensionRecommendations.js'; @@ -31,10 +31,10 @@ import { IConfigurationService } from '../../../../platform/configuration/common import { INotificationService, Severity } from '../../../../platform/notification/common/notification.js'; import { ViewPane, IViewPaneOptions, ViewPaneShowActions } from '../../../browser/parts/views/viewPane.js'; import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js'; -import { coalesce, distinct } from '../../../../base/common/arrays.js'; +import { coalesce, distinct, range } from '../../../../base/common/arrays.js'; import { alert } from '../../../../base/browser/ui/aria/aria.js'; import { IListContextMenuEvent } from '../../../../base/browser/ui/list/list.js'; -import { CancellationToken } from '../../../../base/common/cancellation.js'; +import { CancellationToken, CancellationTokenSource } from '../../../../base/common/cancellation.js'; import { IAction, Action, Separator, ActionRunner } from '../../../../base/common/actions.js'; import { ExtensionIdentifier, ExtensionIdentifierMap, ExtensionUntrustedWorkspaceSupportType, ExtensionVirtualWorkspaceSupportType, IExtensionDescription, isLanguagePackExtension } from '../../../../platform/extensions/common/extensions.js'; import { CancelablePromise, createCancelablePromise, ThrottledDelayer } from '../../../../base/common/async.js'; @@ -174,7 +174,7 @@ export class ExtensionsListView extends ViewPane { super.renderHeader(container); if (!this.options.hideBadge) { - this.badge = new CountBadge(append(container, $('.count-badge-wrapper')), {}, defaultCountBadgeStyles); + this.badge = this._register(new CountBadge(append(container, $('.count-badge-wrapper')), {}, defaultCountBadgeStyles)); } } @@ -387,7 +387,7 @@ export class ExtensionsListView extends ViewPane { result.push(...galleryResult); } - return this.getPagedModel(result); + return new PagedModel(result); } private async queryLocal(query: Query, options: IQueryOptions): Promise { @@ -773,32 +773,41 @@ export class ExtensionsListView extends ViewPane { const text = query.value; + if (!text) { + options.source = 'viewlet'; + const pager = await this.extensionsWorkbenchService.queryGallery(options, token); + return new PagedModel(pager); + } + if (/\bext:([^\s]+)\b/g.test(text)) { options.text = text; options.source = 'file-extension-tags'; - return this.extensionsWorkbenchService.queryGallery(options, token).then(pager => this.getPagedModel(pager)); + const pager = await this.extensionsWorkbenchService.queryGallery(options, token); + return new PagedModel(pager); + } + + options.text = text.substring(0, 350); + options.source = 'searchText'; + + if (hasUserDefinedSortOrder || /\b(category|tag):([^\s]+)\b/gi.test(text) || /\bfeatured(\s+|\b|$)/gi.test(text)) { + const pager = await this.extensionsWorkbenchService.queryGallery(options, token); + return new PagedModel(pager); } let preferredResults: string[] = []; - if (text) { - options.text = text.substring(0, 350); - options.source = 'searchText'; - if (!hasUserDefinedSortOrder) { - const manifest = await this.extensionManagementService.getExtensionsControlManifest(); - const search = manifest.search; - if (Array.isArray(search)) { - for (const s of search) { - if (s.query && s.query.toLowerCase() === text.toLowerCase() && Array.isArray(s.preferredResults)) { - preferredResults = s.preferredResults; - break; - } - } + const manifest = await this.extensionManagementService.getExtensionsControlManifest(); + const search = manifest.search; + if (Array.isArray(search)) { + for (const s of search) { + if (s.query && s.query.toLowerCase() === text.toLowerCase() && Array.isArray(s.preferredResults)) { + preferredResults = s.preferredResults; + break; } } - } else { - options.source = 'viewlet'; } + const searchText = options.text.toLowerCase(); + const preferredExtensions = this.extensionsWorkbenchService.local.filter(e => !e.isBuiltin && (e.name.toLowerCase().indexOf(searchText) > -1 || e.displayName.toLowerCase().indexOf(searchText) > -1 || e.description.toLowerCase().indexOf(searchText) > -1)); const pager = await this.extensionsWorkbenchService.queryGallery(options, token); let positionToUpdate = 0; @@ -814,8 +823,8 @@ export class ExtensionsListView extends ViewPane { } } } - return this.getPagedModel(pager); + return preferredExtensions.length ? new PreferredExtensionsPagedModel(preferredExtensions, pager) : new PagedModel(pager); } private sortExtensions(extensions: IExtension[], options: IQueryOptions): IExtension[] { @@ -1144,19 +1153,6 @@ export class ExtensionsListView extends ViewPane { this.notificationService.error(err); } - private getPagedModel(arg: IPager | IExtension[]): IPagedModel { - if (Array.isArray(arg)) { - return new PagedModel(arg); - } - const pager = { - total: arg.total, - pageSize: arg.pageSize, - firstPage: arg.firstPage, - getPage: (pageIndex: number, cancellationToken: CancellationToken) => arg.getPage(pageIndex, cancellationToken) - }; - return new PagedModel(pager); - } - override dispose(): void { super.dispose(); if (this.queryRequest) { @@ -1582,3 +1578,120 @@ export function getAriaLabelForExtension(extension: IExtension | null): string { const rating = extension?.rating ? localize('extension.arialabel.rating', "Rated {0} out of 5 stars by {1} users", extension.rating.toFixed(2), extension.ratingCount) : ''; return `${extension.displayName}, ${deprecated ? `${deprecated}, ` : ''}${extension.version}, ${publisher}, ${extension.description} ${rating ? `, ${rating}` : ''}`; } + +class PreferredExtensionsPagedModel implements IPagedModel { + + private readonly resolved = new Map(); + private preferredGalleryExtensions = new Set(); + private resolvedGalleryExtensionsFromQuery: IExtension[] = []; + private readonly pages: Array<{ + promise: Promise | null; + cts: CancellationTokenSource | null; + promiseIndexes: Set; + }>; + + public readonly length: number; + + constructor( + private readonly preferredExtensions: IExtension[], + private readonly pager: IPager, + ) { + for (let i = 0; i < this.preferredExtensions.length; i++) { + this.resolved.set(i, this.preferredExtensions[i]); + } + + for (const e of preferredExtensions) { + if (e.gallery?.identifier.uuid) { + this.preferredGalleryExtensions.add(e.gallery.identifier.uuid); + } + } + + // expected that all preferred gallery extensions will be part of the query results + this.length = (preferredExtensions.length - this.preferredGalleryExtensions.size) + this.pager.total; + + const totalPages = Math.ceil(this.pager.total / this.pager.pageSize); + this.pages = range(totalPages).map(() => ({ + promise: null, + cts: null, + promiseIndexes: new Set(), + })); + } + + isResolved(index: number): boolean { + return this.resolved.has(index); + } + + get(index: number): IExtension { + return this.resolved.get(index)!; + } + + async resolve(index: number, cancellationToken: CancellationToken): Promise { + if (cancellationToken.isCancellationRequested) { + throw new CancellationError(); + } + + if (this.isResolved(index)) { + return this.get(index); + } + + const indexInPagedModel = index - this.preferredExtensions.length + this.resolvedGalleryExtensionsFromQuery.length; + const pageIndex = Math.floor(indexInPagedModel / this.pager.pageSize); + const page = this.pages[pageIndex]; + + if (!page.promise) { + page.cts = new CancellationTokenSource(); + page.promise = this.resolvePage(pageIndex, page.cts.token) + .catch(e => { page.promise = null; throw e; }) + .finally(() => page.cts = null); + } + + const listener = cancellationToken.onCancellationRequested(() => { + if (!page.cts) { + return; + } + page.promiseIndexes.delete(index); + if (page.promiseIndexes.size === 0) { + page.cts.cancel(); + } + }); + + page.promiseIndexes.add(index); + + try { + await page.promise; + } finally { + listener.dispose(); + } + + return this.get(index); + } + + private async resolvePage(pageIndex: number, cancellationToken: CancellationToken): Promise { + const extensions = await this.pager.getPage(pageIndex, cancellationToken); + + let adjustIndexOfNextPagesBy = 0; + const pageStartIndex = pageIndex * this.pager.pageSize; + for (let i = 0; i < extensions.length; i++) { + const e = extensions[i]; + if (e.gallery?.identifier.uuid && this.preferredGalleryExtensions.has(e.gallery.identifier.uuid)) { + this.resolvedGalleryExtensionsFromQuery.push(e); + adjustIndexOfNextPagesBy++; + } else { + this.resolved.set(this.preferredExtensions.length - this.resolvedGalleryExtensionsFromQuery.length + pageStartIndex + i, e); + } + } + if (adjustIndexOfNextPagesBy) { + const nextPageStartIndex = (pageIndex + 1) * this.pager.pageSize; + const indices = [...this.resolved.keys()].sort(); + for (const index of indices) { + if (index >= nextPageStartIndex) { + const e = this.resolved.get(index); + if (e) { + this.resolved.delete(index); + this.resolved.set(index - adjustIndexOfNextPagesBy, e); + } + } + } + } + } +} diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts index 417c7bba2a767..503acfe26fdcb 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts @@ -425,6 +425,7 @@ class RemoteBadge extends Disposable { export class ExtensionPackCountWidget extends ExtensionWidget { private element: HTMLElement | undefined; + private countBadge: CountBadge | undefined; constructor( private readonly parent: HTMLElement, @@ -436,6 +437,8 @@ export class ExtensionPackCountWidget extends ExtensionWidget { private clear(): void { this.element?.remove(); + this.countBadge?.dispose(); + this.countBadge = undefined; } render(): void { @@ -444,8 +447,8 @@ export class ExtensionPackCountWidget extends ExtensionWidget { return; } this.element = append(this.parent, $('.extension-badge.extension-pack-badge')); - const countBadge = new CountBadge(this.element, {}, defaultCountBadgeStyles); - countBadge.setCount(this.extension.extensionPack.length); + this.countBadge = new CountBadge(this.element, {}, defaultCountBadgeStyles); + this.countBadge.setCount(this.extension.extensionPack.length); } } @@ -837,10 +840,10 @@ export class ExtensionRecommendationWidget extends ExtensionWidget { } } -export const extensionRatingIconColor = registerColor('extensionIcon.starForeground', { light: '#DF6100', dark: '#FF8E00', hcDark: '#FF8E00', hcLight: textLinkForeground }, localize('extensionIconStarForeground', "The icon color for extension ratings."), true); -export const extensionVerifiedPublisherIconColor = registerColor('extensionIcon.verifiedForeground', textLinkForeground, localize('extensionIconVerifiedForeground', "The icon color for extension verified publisher."), true); -export const extensionPreReleaseIconColor = registerColor('extensionIcon.preReleaseForeground', { dark: '#1d9271', light: '#1d9271', hcDark: '#1d9271', hcLight: textLinkForeground }, localize('extensionPreReleaseForeground', "The icon color for pre-release extension."), true); -export const extensionSponsorIconColor = registerColor('extensionIcon.sponsorForeground', { light: '#B51E78', dark: '#D758B3', hcDark: null, hcLight: '#B51E78' }, localize('extensionIcon.sponsorForeground', "The icon color for extension sponsor."), true); +export const extensionRatingIconColor = registerColor('extensionIcon.starForeground', { light: '#DF6100', dark: '#FF8E00', hcDark: '#FF8E00', hcLight: textLinkForeground }, localize('extensionIconStarForeground', "The icon color for extension ratings."), false); +export const extensionVerifiedPublisherIconColor = registerColor('extensionIcon.verifiedForeground', textLinkForeground, localize('extensionIconVerifiedForeground', "The icon color for extension verified publisher."), false); +export const extensionPreReleaseIconColor = registerColor('extensionIcon.preReleaseForeground', { dark: '#1d9271', light: '#1d9271', hcDark: '#1d9271', hcLight: textLinkForeground }, localize('extensionPreReleaseForeground', "The icon color for pre-release extension."), false); +export const extensionSponsorIconColor = registerColor('extensionIcon.sponsorForeground', { light: '#B51E78', dark: '#D758B3', hcDark: null, hcLight: '#B51E78' }, localize('extensionIcon.sponsorForeground', "The icon color for extension sponsor."), false); registerThemingParticipant((theme, collector) => { const extensionRatingIcon = theme.getColor(extensionRatingIconColor); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index 1575be24d0db9..e30b4d2c3875b 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -1847,7 +1847,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension this.telemetryService.publicLog2('galleryService:checkingForUpdates', { count: infos.length, }); - const galleryExtensions = await this.galleryService.getExtensions(infos, { targetPlatform, compatible: true, productVersion: this.getProductVersion() }, CancellationToken.None); + const galleryExtensions = await this.galleryService.getExtensions(infos, { targetPlatform, compatible: true, productVersion: this.getProductVersion(), preferResourceApi: true }, CancellationToken.None); if (galleryExtensions.length) { await this.syncInstalledExtensionsWithGallery(galleryExtensions); } @@ -2236,7 +2236,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension return false; } - async install(arg: string | URI | IExtension, installOptions: InstallExtensionOptions = {}, progressLocation?: ProgressLocation): Promise { + async install(arg: string | URI | IExtension, installOptions: InstallExtensionOptions = {}, progressLocation?: ProgressLocation | string): Promise { let installable: URI | IGalleryExtension | IResourceExtension | undefined; let extension: IExtension | undefined; @@ -2607,7 +2607,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension return extension; } - private doInstall(extension: IExtension | undefined, installTask: () => Promise, progressLocation?: ProgressLocation): Promise { + private doInstall(extension: IExtension | undefined, installTask: () => Promise, progressLocation?: ProgressLocation | string): Promise { const title = extension ? nls.localize('installing named extension', "Installing '{0}' extension....", extension.displayName) : nls.localize('installing extension', 'Installing extension....'); return this.withProgress({ location: progressLocation ?? ProgressLocation.Extensions, diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css index 6a742b8bddbe8..3201223829698 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css @@ -308,11 +308,17 @@ margin-bottom: 0px; } +.extension-editor > .body > .content > .details > .additional-details-container .more-info-container > .more-info > .more-info-entry > .link { + cursor: pointer; +} + +.extension-editor > .body > .content > .details > .additional-details-container .more-info-container > .more-info > .more-info-entry > .link, .extension-editor > .header > .details > .pre-release-text a, .extension-editor > .header > .details > .actions-status-container > .status a { color: var(--vscode-textLink-foreground) } +.extension-editor > .body > .content > .details > .additional-details-container .more-info-container > .more-info > .more-info-entry .link:hover, .extension-editor > .header > .details > .pre-release-text a:hover, .extension-editor > .header > .details > .actions-status-container > .status a:hover { text-decoration: underline; diff --git a/src/vs/workbench/contrib/extensions/common/extensions.ts b/src/vs/workbench/contrib/extensions/common/extensions.ts index 0b880a40d551e..f55dea181fba1 100644 --- a/src/vs/workbench/contrib/extensions/common/extensions.ts +++ b/src/vs/workbench/contrib/extensions/common/extensions.ts @@ -134,9 +134,9 @@ export interface IExtensionsWorkbenchService { getExtensions(extensionInfos: IExtensionInfo[], options: IExtensionQueryOptions, token: CancellationToken): Promise; getResourceExtensions(locations: URI[], isWorkspaceScoped: boolean): Promise; canInstall(extension: IExtension): Promise; - install(id: string, installOptions?: InstallExtensionOptions, progressLocation?: ProgressLocation): Promise; - install(vsix: URI, installOptions?: InstallExtensionOptions, progressLocation?: ProgressLocation): Promise; - install(extension: IExtension, installOptions?: InstallExtensionOptions, progressLocation?: ProgressLocation): Promise; + install(id: string, installOptions?: InstallExtensionOptions, progressLocation?: ProgressLocation | string): Promise; + install(vsix: URI, installOptions?: InstallExtensionOptions, progressLocation?: ProgressLocation | string): Promise; + install(extension: IExtension, installOptions?: InstallExtensionOptions, progressLocation?: ProgressLocation | string): Promise; installInServer(extension: IExtension, server: IExtensionManagementServer): Promise; uninstall(extension: IExtension): Promise; reinstall(extension: IExtension): Promise; diff --git a/src/vs/workbench/contrib/files/browser/explorerService.ts b/src/vs/workbench/contrib/files/browser/explorerService.ts index e9da7fceeba6d..cb60e6d4a34bf 100644 --- a/src/vs/workbench/contrib/files/browser/explorerService.ts +++ b/src/vs/workbench/contrib/files/browser/explorerService.ts @@ -364,6 +364,11 @@ export class ExplorerService implements IExplorerService { } async refresh(reveal = true): Promise { + // Do not refresh the tree when it is showing temporary nodes (phantom elements) + if (this.view?.hasPhantomElements()) { + return; + } + this.model.roots.forEach(r => r.forgetChildren()); if (this.view) { await this.view.refresh(true); diff --git a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts index ec2a3a69d0aaa..9da55d34ce374 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts @@ -18,7 +18,7 @@ import { FilesExplorerFocusCondition, ExplorerRootContext, ExplorerFolderContext import { ADD_ROOT_FOLDER_COMMAND_ID, ADD_ROOT_FOLDER_LABEL } from '../../../browser/actions/workspaceCommands.js'; import { CLOSE_SAVED_EDITORS_COMMAND_ID, CLOSE_EDITORS_IN_GROUP_COMMAND_ID, CLOSE_EDITOR_COMMAND_ID, CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID, REOPEN_WITH_COMMAND_ID } from '../../../browser/parts/editor/editorCommands.js'; import { AutoSaveAfterShortDelayContext } from '../../../services/filesConfiguration/common/filesConfigurationService.js'; -import { WorkbenchListDoubleSelection, WorkbenchTreeFindOpen } from '../../../../platform/list/browser/listService.js'; +import { WorkbenchListDoubleSelection } from '../../../../platform/list/browser/listService.js'; import { Schemas } from '../../../../base/common/network.js'; import { DirtyWorkingCopiesContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, WorkbenchStateContext, WorkspaceFolderCountContext, SidebarFocusContext, ActiveEditorCanRevertContext, ActiveEditorContext, ResourceContextKey, ActiveEditorAvailableEditorIdsContext, MultipleEditorsSelectedInGroupContext, TwoEditorsSelectedInGroupContext, SelectedEditorsInGroupFileOrUntitledResourceContextKey } from '../../../common/contextkeys.js'; import { IsWebContext } from '../../../../platform/contextkey/common/contextkeys.js'; @@ -64,7 +64,7 @@ const MOVE_FILE_TO_TRASH_ID = 'moveFileToTrash'; KeybindingsRegistry.registerCommandAndKeybindingRule({ id: MOVE_FILE_TO_TRASH_ID, weight: KeybindingWeight.WorkbenchContrib + explorerCommandsWeightBonus, - when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerResourceNotReadonlyContext, ExplorerResourceMoveableToTrash, WorkbenchTreeFindOpen.toNegated()), + when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerResourceNotReadonlyContext, ExplorerResourceMoveableToTrash), primary: KeyCode.Delete, mac: { primary: KeyMod.CtrlCmd | KeyCode.Backspace, @@ -77,7 +77,7 @@ const DELETE_FILE_ID = 'deleteFile'; KeybindingsRegistry.registerCommandAndKeybindingRule({ id: DELETE_FILE_ID, weight: KeybindingWeight.WorkbenchContrib + explorerCommandsWeightBonus, - when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerResourceNotReadonlyContext, WorkbenchTreeFindOpen.toNegated()), + when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerResourceNotReadonlyContext), primary: KeyMod.Shift | KeyCode.Delete, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Backspace @@ -88,7 +88,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ KeybindingsRegistry.registerCommandAndKeybindingRule({ id: DELETE_FILE_ID, weight: KeybindingWeight.WorkbenchContrib + explorerCommandsWeightBonus, - when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerResourceNotReadonlyContext, ExplorerResourceMoveableToTrash.toNegated(), WorkbenchTreeFindOpen.toNegated()), + when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerResourceNotReadonlyContext, ExplorerResourceMoveableToTrash.toNegated()), primary: KeyCode.Delete, mac: { primary: KeyMod.CtrlCmd | KeyCode.Backspace @@ -100,7 +100,7 @@ const CUT_FILE_ID = 'filesExplorer.cut'; KeybindingsRegistry.registerCommandAndKeybindingRule({ id: CUT_FILE_ID, weight: KeybindingWeight.WorkbenchContrib + explorerCommandsWeightBonus, - when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerRootContext.toNegated(), ExplorerResourceNotReadonlyContext, WorkbenchTreeFindOpen.toNegated()), + when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerRootContext.toNegated(), ExplorerResourceNotReadonlyContext), primary: KeyMod.CtrlCmd | KeyCode.KeyX, handler: cutFileHandler, }); @@ -121,7 +121,7 @@ CommandsRegistry.registerCommand(PASTE_FILE_ID, pasteFileHandler); KeybindingsRegistry.registerKeybindingRule({ id: `^${PASTE_FILE_ID}`, // the `^` enables pasting files into the explorer by preventing default bubble up weight: KeybindingWeight.WorkbenchContrib + explorerCommandsWeightBonus, - when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerResourceNotReadonlyContext, WorkbenchTreeFindOpen.toNegated()), + when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerResourceNotReadonlyContext), primary: KeyMod.CtrlCmd | KeyCode.KeyV, }); @@ -154,7 +154,7 @@ const copyRelativePathCommand = { title: nls.localize('copyRelativePath', "Copy Relative Path") }; -export const revealInsideBarCommand = { +export const revealInSideBarCommand = { id: REVEAL_IN_EXPLORER_COMMAND_ID, title: nls.localize('revealInSideBar', "Reveal in Explorer View") }; @@ -162,7 +162,7 @@ export const revealInsideBarCommand = { // Editor Title Context Menu appendEditorTitleContextMenuItem(COPY_PATH_COMMAND_ID, copyPathCommand.title, ResourceContextKey.IsFileSystemResource, '1_cutcopypaste', true); appendEditorTitleContextMenuItem(COPY_RELATIVE_PATH_COMMAND_ID, copyRelativePathCommand.title, ResourceContextKey.IsFileSystemResource, '1_cutcopypaste', true); -appendEditorTitleContextMenuItem(revealInsideBarCommand.id, revealInsideBarCommand.title, ResourceContextKey.IsFileSystemResource, '2_files', false, 1); +appendEditorTitleContextMenuItem(revealInSideBarCommand.id, revealInSideBarCommand.title, ResourceContextKey.IsFileSystemResource, '2_files', false, 1); export function appendEditorTitleContextMenuItem(id: string, title: string, when: ContextKeyExpression | undefined, group: string, supportsMultiSelect: boolean, order?: number): void { const precondition = supportsMultiSelect !== true ? MultipleEditorsSelectedInGroupContext.negate() : undefined; @@ -479,7 +479,7 @@ MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { command: { id: NEW_FILE_COMMAND_ID, title: NEW_FILE_LABEL, - precondition: ContextKeyExpr.and(ExplorerResourceNotReadonlyContext, WorkbenchTreeFindOpen.toNegated()) + precondition: ExplorerResourceNotReadonlyContext }, when: ExplorerFolderContext }); @@ -490,7 +490,7 @@ MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { command: { id: NEW_FOLDER_COMMAND_ID, title: NEW_FOLDER_LABEL, - precondition: ContextKeyExpr.and(ExplorerResourceNotReadonlyContext, WorkbenchTreeFindOpen.toNegated()) + precondition: ExplorerResourceNotReadonlyContext }, when: ExplorerFolderContext }); @@ -539,7 +539,6 @@ MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { command: { id: CUT_FILE_ID, title: nls.localize('cut', "Cut"), - precondition: WorkbenchTreeFindOpen.toNegated() }, when: ContextKeyExpr.and(ExplorerRootContext.toNegated(), ExplorerResourceNotReadonlyContext) }); @@ -560,7 +559,7 @@ MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { command: { id: PASTE_FILE_ID, title: PASTE_FILE_LABEL, - precondition: ContextKeyExpr.and(ExplorerResourceNotReadonlyContext, FileCopiedContext, WorkbenchTreeFindOpen.toNegated()) + precondition: ContextKeyExpr.and(ExplorerResourceNotReadonlyContext, FileCopiedContext) }, when: ExplorerFolderContext }); @@ -619,7 +618,6 @@ MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { command: { id: ADD_ROOT_FOLDER_COMMAND_ID, title: ADD_ROOT_FOLDER_LABEL, - precondition: WorkbenchTreeFindOpen.toNegated() }, when: ContextKeyExpr.and(ExplorerRootContext, ContextKeyExpr.or(EnterMultiRootWorkspaceSupportContext, WorkbenchStateContext.isEqualTo('workspace'))) }); @@ -630,7 +628,6 @@ MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { command: { id: REMOVE_ROOT_FOLDER_COMMAND_ID, title: REMOVE_ROOT_FOLDER_LABEL, - precondition: WorkbenchTreeFindOpen.toNegated() }, when: ContextKeyExpr.and(ExplorerRootContext, ExplorerFolderContext, ContextKeyExpr.and(WorkspaceFolderCountContext.notEqualsTo('0'), ContextKeyExpr.or(EnterMultiRootWorkspaceSupportContext, WorkbenchStateContext.isEqualTo('workspace')))) }); @@ -641,7 +638,7 @@ MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { command: { id: RENAME_ID, title: TRIGGER_RENAME_LABEL, - precondition: ContextKeyExpr.and(ExplorerResourceNotReadonlyContext, WorkbenchTreeFindOpen.toNegated()), + precondition: ExplorerResourceNotReadonlyContext, }, when: ExplorerRootContext.toNegated() }); @@ -652,12 +649,12 @@ MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { command: { id: MOVE_FILE_TO_TRASH_ID, title: MOVE_FILE_TO_TRASH_LABEL, - precondition: ContextKeyExpr.and(ExplorerResourceNotReadonlyContext, WorkbenchTreeFindOpen.toNegated()), + precondition: ContextKeyExpr.and(ExplorerResourceNotReadonlyContext), }, alt: { id: DELETE_FILE_ID, title: nls.localize('deleteFile', "Delete Permanently"), - precondition: ContextKeyExpr.and(ExplorerResourceNotReadonlyContext, WorkbenchTreeFindOpen.toNegated()), + precondition: ContextKeyExpr.and(ExplorerResourceNotReadonlyContext), }, when: ContextKeyExpr.and(ExplorerRootContext.toNegated(), ExplorerResourceMoveableToTrash) }); @@ -668,7 +665,7 @@ MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { command: { id: DELETE_FILE_ID, title: nls.localize('deleteFile', "Delete Permanently"), - precondition: ContextKeyExpr.and(ExplorerResourceNotReadonlyContext, WorkbenchTreeFindOpen.toNegated()), + precondition: ExplorerResourceNotReadonlyContext, }, when: ContextKeyExpr.and(ExplorerRootContext.toNegated(), ExplorerResourceMoveableToTrash.toNegated()) }); @@ -779,7 +776,7 @@ MenuRegistry.appendMenuItem(MenuId.ChatAttachmentsContext, { MenuRegistry.appendMenuItem(MenuId.ChatAttachmentsContext, { group: 'navigation', order: 20, - command: revealInsideBarCommand, + command: revealInSideBarCommand, when: ResourceContextKey.IsFileSystemResource }); @@ -797,32 +794,34 @@ MenuRegistry.appendMenuItem(MenuId.ChatAttachmentsContext, { when: ResourceContextKey.IsFileSystemResource }); -// Chat resource anchor context menu +// Chat resource anchor attachments/anchors context menu -MenuRegistry.appendMenuItem(MenuId.ChatInlineResourceAnchorContext, { - group: 'navigation', - order: 10, - command: openToSideCommand, - when: ContextKeyExpr.and(ResourceContextKey.HasResource, ExplorerFolderContext.toNegated()) -}); +for (const menuId of [MenuId.ChatInlineResourceAnchorContext, MenuId.ChatInputResourceAttachmentContext]) { + MenuRegistry.appendMenuItem(menuId, { + group: 'navigation', + order: 10, + command: openToSideCommand, + when: ContextKeyExpr.and(ResourceContextKey.HasResource, ExplorerFolderContext.toNegated()) + }); -MenuRegistry.appendMenuItem(MenuId.ChatInlineResourceAnchorContext, { - group: 'navigation', - order: 20, - command: revealInsideBarCommand, - when: ResourceContextKey.IsFileSystemResource -}); + MenuRegistry.appendMenuItem(menuId, { + group: 'navigation', + order: 20, + command: revealInSideBarCommand, + when: ResourceContextKey.IsFileSystemResource + }); -MenuRegistry.appendMenuItem(MenuId.ChatInlineResourceAnchorContext, { - group: '1_cutcopypaste', - order: 10, - command: copyPathCommand, - when: ResourceContextKey.IsFileSystemResource -}); + MenuRegistry.appendMenuItem(menuId, { + group: '1_cutcopypaste', + order: 10, + command: copyPathCommand, + when: ResourceContextKey.IsFileSystemResource + }); -MenuRegistry.appendMenuItem(MenuId.ChatInlineResourceAnchorContext, { - group: '1_cutcopypaste', - order: 20, - command: copyRelativePathCommand, - when: ResourceContextKey.IsFileSystemResource -}); + MenuRegistry.appendMenuItem(menuId, { + group: '1_cutcopypaste', + order: 20, + command: copyRelativePathCommand, + when: ResourceContextKey.IsFileSystemResource + }); +} diff --git a/src/vs/workbench/contrib/files/browser/files.ts b/src/vs/workbench/contrib/files/browser/files.ts index a810de1604a06..00307a068dee4 100644 --- a/src/vs/workbench/contrib/files/browser/files.ts +++ b/src/vs/workbench/contrib/files/browser/files.ts @@ -63,6 +63,7 @@ export interface IExplorerView { getFocus(): ExplorerItem[]; focusNext(): void; focusLast(): void; + hasPhantomElements(): boolean; } function getFocus(listService: IListService): unknown | undefined { diff --git a/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css b/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css index 7a3237b22f5a5..a08c3754676d6 100644 --- a/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css +++ b/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css @@ -57,6 +57,15 @@ line-height: normal; } +.explorer-folders-view .monaco-list-row .explorer-item .monaco-count-badge { + margin-left: 5px; + display: none; +} + +.explorer-folders-view .monaco-list-row[aria-expanded="false"] .explorer-item.highlight-badge .monaco-count-badge { + display: inline-block; +} + .explorer-folders-view .explorer-item .monaco-icon-name-container.multiple > .label-name > .monaco-highlighted-label { border-radius: 3px; } diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index 623aa97aadec9..e420a6c9a2317 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -8,7 +8,7 @@ import { URI } from '../../../../../base/common/uri.js'; import * as perf from '../../../../../base/common/performance.js'; import { WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from '../../../../../base/common/actions.js'; import { memoize } from '../../../../../base/common/decorators.js'; -import { IFilesConfiguration, ExplorerFolderContext, FilesExplorerFocusedContext, ExplorerFocusedContext, ExplorerRootContext, ExplorerResourceReadonlyContext, ExplorerResourceCut, ExplorerResourceMoveableToTrash, ExplorerCompressedFocusContext, ExplorerCompressedFirstFocusContext, ExplorerCompressedLastFocusContext, ExplorerResourceAvailableEditorIdsContext, VIEW_ID, ExplorerResourceNotReadonlyContext, ViewHasSomeCollapsibleRootItemContext, FoldersViewVisibleContext, ExplorerResourceParentReadOnlyContext } from '../../common/files.js'; +import { IFilesConfiguration, ExplorerFolderContext, FilesExplorerFocusedContext, ExplorerFocusedContext, ExplorerRootContext, ExplorerResourceReadonlyContext, ExplorerResourceCut, ExplorerResourceMoveableToTrash, ExplorerCompressedFocusContext, ExplorerCompressedFirstFocusContext, ExplorerCompressedLastFocusContext, ExplorerResourceAvailableEditorIdsContext, VIEW_ID, ExplorerResourceNotReadonlyContext, ViewHasSomeCollapsibleRootItemContext, FoldersViewVisibleContext, ExplorerResourceParentReadOnlyContext, ExplorerFindProviderActive } from '../../common/files.js'; import { FileCopiedContext, NEW_FILE_COMMAND_ID, NEW_FOLDER_COMMAND_ID } from '../fileActions.js'; import * as DOM from '../../../../../base/browser/dom.js'; import { IWorkbenchLayoutService } from '../../../../services/layout/browser/layoutService.js'; @@ -27,7 +27,7 @@ import { DelayedDragHandler } from '../../../../../base/browser/dnd.js'; import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from '../../../../services/editor/common/editorService.js'; import { IViewPaneOptions, ViewPane } from '../../../../browser/parts/views/viewPane.js'; import { ILabelService } from '../../../../../platform/label/common/label.js'; -import { ExplorerDelegate, ExplorerDataSource, FilesRenderer, ICompressedNavigationController, FilesFilter, FileSorter, FileDragAndDrop, ExplorerCompressionDelegate, isCompressedFolderName } from './explorerViewer.js'; +import { ExplorerDelegate, ExplorerDataSource, FilesRenderer, ICompressedNavigationController, FilesFilter, FileSorter, FileDragAndDrop, ExplorerCompressionDelegate, isCompressedFolderName, ExplorerFindProvider } from './explorerViewer.js'; import { IThemeService, IFileIconTheme } from '../../../../../platform/theme/common/themeService.js'; import { IWorkbenchThemeService } from '../../../../services/themes/common/workbenchThemeService.js'; import { ITreeContextMenuEvent, TreeVisibility } from '../../../../../base/browser/ui/tree/tree.js'; @@ -36,8 +36,8 @@ import { ITelemetryService } from '../../../../../platform/telemetry/common/tele import { ExplorerItem, NewExplorerItem } from '../../common/explorerModel.js'; import { ResourceLabels } from '../../../../browser/labels.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../../platform/storage/common/storage.js'; -import { IAsyncDataTreeViewState, IAsyncFindProvider, IAsyncFindResult } from '../../../../../base/browser/ui/tree/asyncDataTree.js'; -import { fuzzyScore, FuzzyScore } from '../../../../../base/common/filters.js'; +import { IAsyncDataTreeViewState } from '../../../../../base/browser/ui/tree/asyncDataTree.js'; +import { FuzzyScore } from '../../../../../base/common/filters.js'; import { IClipboardService } from '../../../../../platform/clipboard/common/clipboardService.js'; import { IFileService, FileSystemProviderCapabilities } from '../../../../../platform/files/common/files.js'; import { IDisposable } from '../../../../../base/common/lifecycle.js'; @@ -53,12 +53,8 @@ import { ICommandService } from '../../../../../platform/commands/common/command import { IEditorResolverService } from '../../../../services/editor/common/editorResolverService.js'; import { EditorOpenSource } from '../../../../../platform/editor/common/editor.js'; import { ResourceMap } from '../../../../../base/common/map.js'; -import { AbstractTreePart, contiguousFuzzyScore, ITreeFindToggleContribution } from '../../../../../base/browser/ui/tree/abstractTree.js'; +import { AbstractTreePart } from '../../../../../base/browser/ui/tree/abstractTree.js'; import { IHoverService } from '../../../../../platform/hover/browser/hover.js'; -import { basename, relativePath } from '../../../../../base/common/resources.js'; -import { IFilesConfigurationService } from '../../../../services/filesConfiguration/common/filesConfigurationService.js'; -import { getExcludes, ISearchComplete, ISearchConfiguration, ISearchService, QueryType } from '../../../../services/search/common/search.js'; -import { CancellationToken } from '../../../../../base/common/cancellation.js'; function hasExpandedRootChild(tree: WorkbenchCompressibleAsyncDataTree, treeInput: ExplorerItem[]): boolean { @@ -154,124 +150,12 @@ export interface IExplorerViewPaneOptions extends IViewPaneOptions { delegate: IExplorerViewContainerDelegate; } -const explorerFuzzyMatch = { - id: 'fuzzyMatch', - title: 'Fuzzy Match', - icon: Codicon.searchFuzzy, - isChecked: false -}; - -class ExplorerFindProvider implements IAsyncFindProvider { - - readonly toggles: ITreeFindToggleContribution[] = [explorerFuzzyMatch]; - readonly placeholder: string = nls.localize('type to search files', "Type to search files"); - - constructor( - @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, - @ISearchService private readonly searchService: ISearchService, - @IFileService private readonly fileService: IFileService, - @IConfigurationService private readonly configurationService: IConfigurationService, - @IFilesConfigurationService private readonly filesConfigService: IFilesConfigurationService, - @IProgressService private readonly progressService: IProgressService, - @IExplorerService private readonly explorerService: IExplorerService, - ) { } - - async *getFindResults(pattern: string, sessionId: number, token: CancellationToken, toggleStates: ITreeFindToggleContribution[]): AsyncIterable> { - const isFuzzyMatch = toggleStates.find(t => t.id === explorerFuzzyMatch.id)?.isChecked!!; - - const workspaceFolders = this.workspaceContextService.getWorkspace().folders; - const folderPromises = Promise.all(workspaceFolders.map(async folder => { - // Get exclude settings used for search - const searchExcludePattern = getExcludes(this.configurationService.getValue({ resource: folder.uri })) || {}; - - const result = await this.searchService.fileSearch({ - folderQueries: [{ folder: folder.uri }], - type: QueryType.File, - shouldGlobMatchFilePattern: !isFuzzyMatch, - filePattern: isFuzzyMatch ? pattern : `**/*${pattern}*`, - maxResults: 512, - sortByScore: true, - cacheKey: `explorerfindprovider:${folder.index}:${sessionId}`, - excludePattern: searchExcludePattern, - }, token); - - return { folder: folder.uri, result }; - })); - - const folderResults = await this.progressService.withProgress({ - location: ProgressLocation.Explorer, - delay: 1000, - }, _progress => folderPromises); - - if (token.isCancellationRequested) { - return; - } - - yield* this.createResultItems(folderResults, pattern, isFuzzyMatch); - } - - private async *createResultItems(folderResults: { folder: URI; result: ISearchComplete }[], pattern: string, isFuzzyMatch: boolean): AsyncIterable> { - const lowercasePattern = pattern.toLowerCase(); - - for (const { folder, result } of folderResults) { - const folderRoot = new ExplorerItem(folder, this.fileService, this.configurationService, this.filesConfigService, undefined); - - for (const file of result.results) { - const baseName = basename(file.resource); - - let filterdata; - if (isFuzzyMatch) { - filterdata = fuzzyScore(pattern, lowercasePattern, 0, baseName, baseName.toLowerCase(), 0, { firstMatchCanBeWeak: true, boostFullMatch: true }); - } else { - filterdata = contiguousFuzzyScore(lowercasePattern, baseName.toLowerCase()); - } - - if (!filterdata) { - continue; - } - - const item = this.createItem(file.resource, folderRoot); - if (item) { - yield { element: item, filterdata }; - } - } - } - } - - private createItem(resource: URI, root: ExplorerItem): ExplorerItem | undefined { - const relativePathToRoot = relativePath(root.resource, resource); - if (!relativePathToRoot) { - return undefined; - } - - let currentItem = root; - let currentResource = root.resource; - const pathSegments = relativePathToRoot.split('/'); - for (const stat of pathSegments) { - currentResource = currentResource.with({ path: `${currentResource.path}/${stat}` }); - - let child = currentItem.children.get(stat); - if (!child) { - const isDirectory = pathSegments[pathSegments.length - 1] !== stat; - child = new ExplorerItem(currentResource, this.fileService, this.configurationService, this.filesConfigService, currentItem, isDirectory); - } - - currentItem = child; - } - - return currentItem; - } - - revealResultInTree(findElement: ExplorerItem): void { - this.explorerService.select(findElement.resource, true); - } -} - export class ExplorerView extends ViewPane implements IExplorerView { static readonly TREE_VIEW_STATE_STORAGE_KEY: string = 'workbench.explorer.treeViewState'; private tree!: WorkbenchCompressibleAsyncDataTree; private filter!: FilesFilter; + private findProvider!: ExplorerFindProvider; private resourceContext: ResourceContextKey; private folderContext: IContextKey; @@ -551,8 +435,10 @@ export class ExplorerView extends ViewPane implements IExplorerView { const explorerLabels = this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this.onDidChangeBodyVisibility }); this._register(explorerLabels); + this.findProvider = this.instantiationService.createInstance(ExplorerFindProvider, () => this.tree); + const updateWidth = (stat: ExplorerItem) => this.tree.updateWidth(stat); - this.renderer = this.instantiationService.createInstance(FilesRenderer, container, explorerLabels, updateWidth); + this.renderer = this.instantiationService.createInstance(FilesRenderer, container, explorerLabels, this.findProvider.highlightTree, updateWidth); this._register(this.renderer); this._register(createFileIconThemableTreeContainerScope(container, this.themeService)); @@ -562,7 +448,7 @@ export class ExplorerView extends ViewPane implements IExplorerView { const getFileNestingSettings = (item?: ExplorerItem) => this.configurationService.getValue({ resource: item?.root.resource }).explorer.fileNesting; this.tree = >this.instantiationService.createInstance(WorkbenchCompressibleAsyncDataTree, 'FileExplorer', container, new ExplorerDelegate(), new ExplorerCompressionDelegate(), [this.renderer], - this.instantiationService.createInstance(ExplorerDataSource, this.filter), { + this.instantiationService.createInstance(ExplorerDataSource, this.filter, this.findProvider), { compressionEnabled: isCompressionEnabled(), accessibilityProvider: this.renderer, identityProvider, @@ -591,6 +477,9 @@ export class ExplorerView extends ViewPane implements IExplorerView { if (e.hasNests && getFileNestingSettings(e).expand) { return false; } + if (this.findProvider.isShowingFilterResults()) { + return false; + } } return true; }, @@ -608,7 +497,7 @@ export class ExplorerView extends ViewPane implements IExplorerView { }, paddingBottom: ExplorerDelegate.ITEM_HEIGHT, overrideStyles: this.getLocationBasedColors().listOverrideStyles, - findResultsProvider: this.instantiationService.createInstance(ExplorerFindProvider), + findProvider: this.findProvider, }); this._register(this.tree); this._register(this.themeService.onDidColorThemeChange(() => this.tree.rerender())); @@ -807,7 +696,7 @@ export class ExplorerView extends ViewPane implements IExplorerView { * If the item is passed we refresh only that level of the tree, otherwise we do a full refresh. */ refresh(recursive: boolean, item?: ExplorerItem, cancelEditing: boolean = true): Promise { - if (!this.tree || !this.isBodyVisible() || (item && !this.tree.hasNode(item))) { + if (!this.tree || !this.isBodyVisible() || (item && !this.tree.hasNode(item)) || (this.findProvider?.isShowingFilterResults() && recursive)) { // Tree node doesn't exist yet, when it becomes visible we will refresh return Promise.resolve(undefined); } @@ -1074,6 +963,10 @@ export class ExplorerView extends ViewPane implements IExplorerView { this.storeTreeViewState(); } + hasPhantomElements(): boolean { + return this.findProvider.isShowingFilterResults(); + } + override dispose(): void { this.dragHandler?.dispose(); super.dispose(); @@ -1157,11 +1050,12 @@ registerAction2(class extends Action2 { id: MenuId.ViewTitle, group: 'navigation', when: ContextKeyExpr.equals('view', VIEW_ID), - order: 30 + order: 30, }, metadata: { description: nls.localize2('refreshExplorerMetadata', "Forces a refresh of the Explorer.") - } + }, + precondition: ExplorerFindProviderActive.negate() }); } diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index 975f494cbe122..b17d1a20cee9d 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -19,8 +19,8 @@ import { ITreeNode, ITreeFilter, TreeVisibility, IAsyncDataSource, ITreeSorter, import { IContextMenuService, IContextViewService } from '../../../../../platform/contextview/browser/contextView.js'; import { IThemeService } from '../../../../../platform/theme/common/themeService.js'; import { IConfigurationChangeEvent, IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; -import { IFilesConfiguration, UndoConfirmLevel } from '../../common/files.js'; -import { dirname, joinPath, distinctParents } from '../../../../../base/common/resources.js'; +import { ExplorerFindProviderActive, IFilesConfiguration, UndoConfirmLevel } from '../../common/files.js'; +import { dirname, joinPath, distinctParents, relativePath } from '../../../../../base/common/resources.js'; import { InputBox, MessageType } from '../../../../../base/browser/ui/inputbox/inputBox.js'; import { localize } from '../../../../../nls.js'; import { createSingleCallFunction } from '../../../../../base/common/functional.js'; @@ -44,7 +44,7 @@ import { IWorkspaceFolderCreationData } from '../../../../../platform/workspaces import { findValidPasteFileTarget } from '../fileActions.js'; import { FuzzyScore, createMatches } from '../../../../../base/common/filters.js'; import { Emitter, Event, EventMultiplexer } from '../../../../../base/common/event.js'; -import { ITreeCompressionDelegate } from '../../../../../base/browser/ui/tree/asyncDataTree.js'; +import { IAsyncDataTreeViewState, IAsyncFindProvider, IAsyncFindResultMetadata, IAsyncFindToggles, ITreeCompressionDelegate } from '../../../../../base/browser/ui/tree/asyncDataTree.js'; import { ICompressibleTreeRenderer } from '../../../../../base/browser/ui/tree/objectTree.js'; import { ICompressedTreeNode } from '../../../../../base/browser/ui/tree/compressedObjectTreeModel.js'; import { ILabelService } from '../../../../../platform/label/common/label.js'; @@ -60,11 +60,20 @@ import { WebFileSystemAccess } from '../../../../../platform/files/browser/webFi import { IgnoreFile } from '../../../../services/search/common/ignoreFile.js'; import { ResourceSet } from '../../../../../base/common/map.js'; import { TernarySearchTree } from '../../../../../base/common/ternarySearchTree.js'; -import { defaultInputBoxStyles } from '../../../../../platform/theme/browser/defaultStyles.js'; +import { defaultCountBadgeStyles, defaultInputBoxStyles } from '../../../../../platform/theme/browser/defaultStyles.js'; import { timeout } from '../../../../../base/common/async.js'; import { IFilesConfigurationService } from '../../../../services/filesConfiguration/common/filesConfigurationService.js'; import { mainWindow } from '../../../../../base/browser/window.js'; import { IExplorerFileContribution, explorerFileContribRegistry } from '../explorerFileContrib.js'; +import { WorkbenchCompressibleAsyncDataTree } from '../../../../../platform/list/browser/listService.js'; +import { ISearchService, QueryType, getExcludes, ISearchConfiguration, ISearchComplete, IFileQuery } from '../../../../services/search/common/search.js'; +import { CancellationToken } from '../../../../../base/common/cancellation.js'; +import { TreeFindMatchType, TreeFindMode } from '../../../../../base/browser/ui/tree/abstractTree.js'; +import { isCancellationError } from '../../../../../base/common/errors.js'; +import { IContextKey, IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js'; +import { CountBadge } from '../../../../../base/browser/ui/countBadge/countBadge.js'; +import { listFilterMatchHighlight, listFilterMatchHighlightBorder } from '../../../../../platform/theme/common/colorRegistry.js'; +import { asCssVariable } from '../../../../../platform/theme/common/colorUtils.js'; export class ExplorerDelegate implements IListVirtualDelegate { @@ -83,7 +92,8 @@ export const explorerRootErrorEmitter = new Emitter(); export class ExplorerDataSource implements IAsyncDataSource { constructor( - private fileFilter: FilesFilter, + private readonly fileFilter: FilesFilter, + private readonly findProvider: ExplorerFindProvider, @IProgressService private readonly progressService: IProgressService, @IConfigurationService private readonly configService: IConfigurationService, @INotificationService private readonly notificationService: INotificationService, @@ -95,25 +105,8 @@ export class ExplorerDataSource implements IAsyncDataSource 1; - - if (isMultiRoot) { - // If we have multiple folders, the root is a workspace folder - // Workspace folders are rendered and can be returned directly - if (element.isRoot) { - return element; - } else if (element.parent) { - return element.parent; - } - } else if (element.parent) { - // If we have a single folder, the root the workspace folder - // The workspace folder is not rendered, so all it's children are tree roots - if (element.parent.isRoot) { - return element; - } else { - return element.parent; - } + if (element.parent) { + return element.parent; } throw new Error('getParent only supported for cached parents'); @@ -129,6 +122,10 @@ export class ExplorerDataSource implements IAsyncDataSource(); + private readonly _highlightedItems = new Map(); + get highlightedItems(): ExplorerItem[] { + return Array.from(this._highlightedItems.values()); + } + + get(item: ExplorerItem): number { + const rootLayer = this._tree.get(item.root.name); + if (rootLayer === undefined) { + return 0; + } + + const relPath = relativePath(item.root.resource, item.resource); + if (relPath === undefined || relPath.startsWith('..')) { + throw new Error('Resource is not a child of the root'); + } + + let treeLayer = rootLayer; + for (const segment of relPath.split('/')) { + if (!treeLayer.stats[segment]) { + return 0; + } + + treeLayer = treeLayer.stats[segment]; + } + + this._highlightedItems.set(relPath, item); + + return treeLayer.total; + } + + add(resource: URI, root: ExplorerItem): void { + const relPath = relativePath(root.resource, resource); + if (relPath === undefined || relPath.startsWith('..')) { + throw new Error('Resource is not a child of the root'); + } + + if (!this._tree.get(root.name)) { + this._tree.set(root.name, { total: 0, stats: {} }); + } + + let treeLayer = this._tree.get(root.name)!; + for (const stat of relPath.split('/').slice(0, -1)) { + if (!treeLayer.stats[stat]) { + treeLayer.stats[stat] = { total: 0, stats: {} }; + } + + treeLayer = treeLayer.stats[stat]; + treeLayer.total++; + } + } + + clear(): void { + this._tree.clear(); + } + +} + +export class ExplorerFindProvider implements IAsyncFindProvider { + + private sessionId: number = 0; + private filterSessionStartState: { viewState: IAsyncDataTreeViewState; input: ExplorerItem[] | ExplorerItem; rootsWithProviders: Set } | undefined; + private highlightSessionStartState: { rootsWithProviders: Set } | undefined; + private explorerFindActiveContextKey: IContextKey; + private phantomParents = new Set(); + private findHighlightTree = new ExplorerFindHighlightTree(); + get highlightTree(): IExplorerFindHighlightTree { + return this.findHighlightTree; + } + + constructor( + private readonly treeProvider: () => WorkbenchCompressibleAsyncDataTree, + @ISearchService private readonly searchService: ISearchService, + @IFileService private readonly fileService: IFileService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @IFilesConfigurationService private readonly filesConfigService: IFilesConfigurationService, + @IProgressService private readonly progressService: IProgressService, + @IExplorerService private readonly explorerService: IExplorerService, + @IContextKeyService contextKeyService: IContextKeyService + ) { + this.explorerFindActiveContextKey = ExplorerFindProviderActive.bindTo(contextKeyService); + } + + isShowingFilterResults(): boolean { + return !!this.filterSessionStartState; + } + + isVisible(element: ExplorerItem): boolean { + if (!this.filterSessionStartState) { + return true; + } + + if (this.explorerService.isEditable(element)) { + return true; + } + + return this.filterSessionStartState.rootsWithProviders.has(element.root) ? element.isMarkedAsFiltered() : true; + } + + startSession(): void { + this.sessionId++; + } + + async endSession(): Promise { + // Restore view state + if (this.filterSessionStartState) { + await this.endFilterSession(); + } + + if (this.highlightSessionStartState) { + this.endHighlightSession(); + } + } + + async find(pattern: string, toggles: IAsyncFindToggles, token: CancellationToken): Promise { + const promise = this.doFind(pattern, toggles, token); + + return await this.progressService.withProgress({ + location: ProgressLocation.Explorer, + delay: 750, + }, _progress => promise); + } + + async doFind(pattern: string, toggles: IAsyncFindToggles, token: CancellationToken): Promise { + if (toggles.findMode === TreeFindMode.Highlight) { + if (this.filterSessionStartState) { + await this.endFilterSession(); + } + + if (!this.highlightSessionStartState) { + this.startHighlightSession(); + } + + return await this.doHighlightFind(pattern, toggles.matchType, token); + } + + if (this.highlightSessionStartState) { + this.endHighlightSession(); + } + + if (!this.filterSessionStartState) { + this.startFilterSession(); + } + + return await this.doFilterFind(pattern, toggles.matchType, token); + } + + // Filter + + private startFilterSession(): void { + const tree = this.treeProvider(); + const input = tree.getInput(); + if (!input) { + return; + } + + const roots = this.explorerService.roots.filter(root => this.searchSupportsScheme(root.resource.scheme)); + this.filterSessionStartState = { viewState: tree.getViewState(), input, rootsWithProviders: new Set(roots) }; + + this.explorerFindActiveContextKey.set(true); + } + + async doFilterFind(pattern: string, matchType: TreeFindMatchType, token: CancellationToken): Promise { + if (!this.filterSessionStartState) { + throw new Error('ExplorerFindProvider: no session state'); + } + + const roots = Array.from(this.filterSessionStartState.rootsWithProviders); + const searchResults = await this.getSearchResults(pattern, roots, matchType, token); + + if (token.isCancellationRequested) { + return {}; + } + + this.clearPhantomElements(); + for (const { explorerRoot, files, directories } of searchResults) { + this.addWorkspaceFilterResults(explorerRoot, files, directories); + } + + const tree = this.treeProvider(); + await tree.setInput(this.filterSessionStartState.input); + + const hitMaxResults = searchResults.some(({ hitMaxResults }) => hitMaxResults); + return { warningMessage: hitMaxResults ? localize('searchMaxResultsWarning', "The result set only contains a subset of all matches. Be more specific in your search to narrow down the results.") : undefined }; + } + + private addWorkspaceFilterResults(root: ExplorerItem, files: URI[], directories: URI[]): void { + const results = [ + ...files.map(file => ({ resource: file, isDirectory: false })), + ...directories.map(directory => ({ resource: directory, isDirectory: true })) + ]; + + for (const { resource, isDirectory } of results) { + const element = root.find(resource); + if (element && element.root === root) { + // File is already in the model + element.markItemAndParentsAsFiltered(); + continue; + } + + // File is not in the model, create phantom items for the file and it's parents + const phantomElements = this.createPhantomItems(resource, root, isDirectory); + if (phantomElements.length === 0) { + throw new Error('Phantom item was not created even though it is not in the model'); + } + + // Store the first ancestor of the file which is already present in the model + const firstPhantomParent = phantomElements[0].parent!; + if (!(firstPhantomParent instanceof PhantomExplorerItem)) { + this.phantomParents.add(firstPhantomParent); + } + + const phantomFileElement = phantomElements[phantomElements.length - 1]; + phantomFileElement.markItemAndParentsAsFiltered(); + } + } + + private createPhantomItems(resource: URI, root: ExplorerItem, resourceIsDirectory: boolean): PhantomExplorerItem[] { + const relativePathToRoot = relativePath(root.resource, resource); + if (!relativePathToRoot) { + throw new Error('Resource is not a child of the root'); + } + + const phantomElements: PhantomExplorerItem[] = []; + + let currentItem = root; + let currentResource = root.resource; + const pathSegments = relativePathToRoot.split('/'); + for (const stat of pathSegments) { + currentResource = currentResource.with({ path: `${currentResource.path}/${stat}` }); + + let child = currentItem.getChild(stat); + if (!child) { + const isDirectory = pathSegments[pathSegments.length - 1] === stat ? resourceIsDirectory : true; + child = new PhantomExplorerItem(currentResource, this.fileService, this.configurationService, this.filesConfigService, currentItem, isDirectory); + currentItem.addChild(child); + phantomElements.push(child as PhantomExplorerItem); + } + + currentItem = child; + } + + return phantomElements; + } + + async endFilterSession(): Promise { + this.clearPhantomElements(); + + this.explorerFindActiveContextKey.set(false); + + // Restore view state + if (!this.filterSessionStartState) { + throw new Error('ExplorerFindProvider: no session state to restore'); + } + + const tree = this.treeProvider(); + await tree.setInput(this.filterSessionStartState.input, this.filterSessionStartState.viewState); + + this.filterSessionStartState = undefined; + this.explorerService.refresh(); + } + + private clearPhantomElements(): void { + for (const phantomParent of this.phantomParents) { + // Clear phantom nodes from model + phantomParent.forgetChildren(); + } + this.phantomParents.clear(); + this.explorerService.roots.forEach(root => root.unmarkItemAndChildren()); + } + + // Highlight + + private startHighlightSession(): void { + const roots = this.explorerService.roots.filter(root => this.searchSupportsScheme(root.resource.scheme)); + this.highlightSessionStartState = { rootsWithProviders: new Set(roots) }; + } + + async doHighlightFind(pattern: string, matchType: TreeFindMatchType, token: CancellationToken): Promise { + if (!this.highlightSessionStartState) { + throw new Error('ExplorerFindProvider: no highlight session state'); + } + + const roots = Array.from(this.highlightSessionStartState.rootsWithProviders); + const searchResults = await this.getSearchResults(pattern, roots, matchType, token); + + if (token.isCancellationRequested) { + return {}; + } + + this.clearHighlights(); + for (const { explorerRoot, files, directories } of searchResults) { + this.addWorkspaceHighlightResults(explorerRoot, files.concat(directories)); + } + + const hitMaxResults = searchResults.some(({ hitMaxResults }) => hitMaxResults); + return { warningMessage: hitMaxResults ? localize('searchMaxResultsWarning', "The result set only contains a subset of all matches. Be more specific in your search to narrow down the results.") : undefined }; + } + + private addWorkspaceHighlightResults(root: ExplorerItem, resources: URI[]): void { + const highlightedDirectories = new Set(); + const storeDirectories = (item: ExplorerItem | undefined) => { + while (item) { + highlightedDirectories.add(item); + item = item.parent; + } + }; + + for (const resource of resources) { + const element = root.find(resource); + if (element && element.root === root) { + // File is already in the model + this.findHighlightTree.add(resource, root); + storeDirectories(element.parent); + continue; + } + + const firstParent = findFirstParent(resource, root); + if (firstParent) { + this.findHighlightTree.add(resource, root); + storeDirectories(firstParent); + } + } + + const tree = this.treeProvider(); + for (const directory of highlightedDirectories) { + tree.rerender(directory); + } + } + + private endHighlightSession(): void { + this.highlightSessionStartState = undefined; + this.clearHighlights(); + } + + private clearHighlights(): void { + const tree = this.treeProvider(); + for (const item of this.findHighlightTree.highlightedItems) { + if (tree.hasNode(item)) { + tree.rerender(item); + } + } + this.findHighlightTree.clear(); + } + + // Search + + private searchSupportsScheme(scheme: string): boolean { + // Limited by the search API + if (scheme !== Schemas.file && scheme !== Schemas.vscodeRemote) { + return false; + } + return this.searchService.schemeHasFileSearchProvider(scheme); + } + + private async getSearchResults(pattern: string, roots: ExplorerItem[], matchType: TreeFindMatchType, token: CancellationToken): Promise<{ explorerRoot: ExplorerItem; files: URI[]; directories: URI[]; hitMaxResults: boolean }[]> { + const patternLowercase = pattern.toLowerCase(); + const isFuzzyMatch = matchType === TreeFindMatchType.Fuzzy; + return await Promise.all(roots.map((root, index) => this.searchInWorkspace(patternLowercase, root, index, isFuzzyMatch, token))); + } + + private async searchInWorkspace(patternLowercase: string, root: ExplorerItem, rootIndex: number, isFuzzyMatch: boolean, token: CancellationToken): Promise<{ explorerRoot: ExplorerItem; files: URI[]; directories: URI[]; hitMaxResults: boolean }> { + const segmentMatchPattern = caseInsensitiveGlobPattern(isFuzzyMatch ? fuzzyMatchingGlobPattern(patternLowercase) : continousMatchingGlobPattern(patternLowercase)); + + const searchExcludePattern = getExcludes(this.configurationService.getValue({ resource: root.resource })) || {}; + const searchOptions: IFileQuery = { + folderQueries: [{ folder: root.resource }], + type: QueryType.File, + shouldGlobMatchFilePattern: true, + cacheKey: `explorerfindprovider:${root.name}:${rootIndex}:${this.sessionId}`, + excludePattern: searchExcludePattern, + }; + + let fileResults: ISearchComplete | undefined; + let folderResults: ISearchComplete | undefined; + try { + [fileResults, folderResults] = await Promise.all([ + this.searchService.fileSearch({ ...searchOptions, filePattern: `**/${segmentMatchPattern}`, maxResults: 512 }, token), + this.searchService.fileSearch({ ...searchOptions, filePattern: `**/${segmentMatchPattern}/**` }, token) + ]); + } catch (e) { + if (!isCancellationError(e)) { + throw e; + } + } + + if (!fileResults || !folderResults || token.isCancellationRequested) { + return { explorerRoot: root, files: [], directories: [], hitMaxResults: false }; + } + + const fileResultResources = fileResults.results.map(result => result.resource); + const directoryResources = getMatchingDirectoriesFromFiles(folderResults.results.map(result => result.resource), root, segmentMatchPattern); + + return { explorerRoot: root, files: fileResultResources, directories: directoryResources, hitMaxResults: !!fileResults.limitHit || !!folderResults.limitHit }; + } +} + +function getMatchingDirectoriesFromFiles(resources: URI[], root: ExplorerItem, segmentMatchPattern: string): URI[] { + const uniqueDirectories = new ResourceSet(); + for (const resource of resources) { + const relativePathToRoot = relativePath(root.resource, resource); + if (!relativePathToRoot) { + throw new Error('Resource is not a child of the root'); + } + + let dirResource = root.resource; + const stats = relativePathToRoot.split('/').slice(0, -1); + for (const stat of stats) { + dirResource = dirResource.with({ path: `${dirResource.path}/${stat}` }); + uniqueDirectories.add(dirResource); + } + } + + const matchingDirectories: URI[] = []; + for (const dirResource of uniqueDirectories) { + const stats = dirResource.path.split('/'); + const dirStat = stats[stats.length - 1]; + if (!dirStat || !glob.match(segmentMatchPattern, dirStat)) { + continue; + } + + matchingDirectories.push(dirResource); + } + + return matchingDirectories; +} + +function findFirstParent(resource: URI, root: ExplorerItem): ExplorerItem | undefined { + const relativePathToRoot = relativePath(root.resource, resource); + if (!relativePathToRoot) { + throw new Error('Resource is not a child of the root'); + } + + let currentItem = root; + let currentResource = root.resource; + const pathSegments = relativePathToRoot.split('/'); + for (const stat of pathSegments) { + currentResource = currentResource.with({ path: `${currentResource.path}/${stat}` }); + const child = currentItem.getChild(stat); + if (!child) { + return currentItem; + } + + currentItem = child; + } + + return undefined; +} + +function fuzzyMatchingGlobPattern(pattern: string): string { + if (!pattern) { + return '*'; + } + return '*' + pattern.split('').join('*') + '*'; +} + +function continousMatchingGlobPattern(pattern: string): string { + if (!pattern) { + return '*'; + } + return '*' + pattern + '*'; +} + +function caseInsensitiveGlobPattern(pattern: string): string { + let caseInsensitiveFilePattern = ''; + for (let i = 0; i < pattern.length; i++) { + const char = pattern[i]; + if (/[a-zA-Z]/.test(char)) { + caseInsensitiveFilePattern += `[${char.toLowerCase()}${char.toUpperCase()}]`; + } else { + caseInsensitiveFilePattern += char; + } + } + return caseInsensitiveFilePattern; +} + export interface ICompressedNavigationController { readonly current: ExplorerItem; readonly currentId: string; @@ -307,6 +806,7 @@ export class FilesRenderer implements ICompressibleTreeRenderer void, @IContextViewService private readonly contextViewService: IContextViewService, @IThemeService private readonly themeService: IThemeService, @@ -404,9 +904,17 @@ export class FilesRenderer implements ICompressibleTreeRenderer e.name); + + // If there is a fuzzy score, we need to adjust the offset of the score + // to align with the last stat of the compressed label + let fuzzyScore = node.filterData as FuzzyScore | undefined; + if (fuzzyScore && fuzzyScore.length > 2) { + const filterDataOffset = labels.join('/').length - labels[labels.length - 1].length; + fuzzyScore = [fuzzyScore[0], fuzzyScore[1] + filterDataOffset, ...fuzzyScore.slice(2)]; + } - const label = node.element.elements.map(e => e.name); - this.renderStat(stat, label, id, node.filterData, templateData); + this.renderStat(stat, labels, id, fuzzyScore, templateData); const compressedNavigationController = new CompressedNavigationController(id, node.element.elements, templateData, node.depth, node.collapsed); templateData.elementDisposables.add(compressedNavigationController); @@ -473,10 +981,19 @@ export class FilesRenderer implements ICompressibleTreeRenderer 0) { + const badge = new CountBadge(templateData.label.element.lastElementChild as HTMLElement, {}, { ...defaultCountBadgeStyles, badgeBackground: asCssVariable(listFilterMatchHighlight), badgeBorder: asCssVariable(listFilterMatchHighlightBorder) }); + badge.setCount(highlightResults); + badge.setTitleFormat(localize('explorerHighlightFolderBadgeTitle', "Directory contains {0} matches", highlightResults)); + templateData.elementDisposables.add(badge); + } + templateData.label.element.classList.toggle('highlight-badge', highlightResults > 0); } private renderInputBox(container: HTMLElement, stat: ExplorerItem, editableData: IEditableData): IDisposable { @@ -523,7 +1040,7 @@ export class FilesRenderer implements ICompressibleTreeRenderer child.unmarkItemAndChildren()); + } } export class NewExplorerItem extends ExplorerItem { diff --git a/src/vs/workbench/contrib/files/common/files.ts b/src/vs/workbench/contrib/files/common/files.ts index c7351f2e1778a..56715560f15ec 100644 --- a/src/vs/workbench/contrib/files/common/files.ts +++ b/src/vs/workbench/contrib/files/common/files.ts @@ -53,6 +53,7 @@ export const ExplorerResourceMoveableToTrash = new RawContextKey('explo export const FilesExplorerFocusedContext = new RawContextKey('filesExplorerFocus', true, { type: 'boolean', description: localize('filesExplorerFocus', "True when the focus is inside the EXPLORER view.") }); export const OpenEditorsFocusedContext = new RawContextKey('openEditorsFocus', true, { type: 'boolean', description: localize('openEditorsFocus', "True when the focus is inside the OPEN EDITORS view.") }); export const ExplorerFocusedContext = new RawContextKey('explorerViewletFocus', true, { type: 'boolean', description: localize('explorerViewletFocus', "True when the focus is inside the EXPLORER viewlet.") }); +export const ExplorerFindProviderActive = new RawContextKey('explorerFindProviderActive', false, { type: 'boolean', description: localize('explorerFindProviderActive', "True when the explorer tree is using the explorer find provider.") }); // compressed nodes export const ExplorerCompressedFocusContext = new RawContextKey('explorerViewletCompressedFocus', true, { type: 'boolean', description: localize('explorerViewletCompressedFocus', "True when the focused item in the EXPLORER view is a compact item.") }); diff --git a/src/vs/workbench/contrib/files/test/browser/explorerFindProvider.test.ts b/src/vs/workbench/contrib/files/test/browser/explorerFindProvider.test.ts new file mode 100644 index 0000000000000..c4b21ae4321bd --- /dev/null +++ b/src/vs/workbench/contrib/files/test/browser/explorerFindProvider.test.ts @@ -0,0 +1,252 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ICompressibleTreeRenderer } from '../../../../../base/browser/ui/tree/objectTree.js'; +import { IIdentityProvider, IKeyboardNavigationLabelProvider, IListVirtualDelegate } from '../../../../../base/browser/ui/list/list.js'; +import { ICompressedTreeNode } from '../../../../../base/browser/ui/tree/compressedObjectTreeModel.js'; +import { ExplorerItem } from '../../common/explorerModel.js'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; +import { ITreeCompressionDelegate } from '../../../../../base/browser/ui/tree/asyncDataTree.js'; +import { ITreeNode, IAsyncDataSource } from '../../../../../base/browser/ui/tree/tree.js'; +import { DisposableStore } from '../../../../../base/common/lifecycle.js'; +import { TestFileService, workbenchInstantiationService } from '../../../../test/browser/workbenchTestServices.js'; +import { NullFilesConfigurationService } from '../../../../test/common/workbenchTestServices.js'; +import { ExplorerFindProvider } from '../../browser/views/explorerViewer.js'; +import { TestConfigurationService } from '../../../../../platform/configuration/test/common/testConfigurationService.js'; +import { IWorkbenchCompressibleAsyncDataTreeOptions, WorkbenchCompressibleAsyncDataTree } from '../../../../../platform/list/browser/listService.js'; +import { IListAccessibilityProvider } from '../../../../../base/browser/ui/list/listWidget.js'; +import { FuzzyScore } from '../../../../../base/common/filters.js'; +import { CancellationToken, CancellationTokenSource } from '../../../../../base/common/cancellation.js'; +import { TestInstantiationService } from '../../../../../platform/instantiation/test/common/instantiationServiceMock.js'; +import { IFileMatch, IFileQuery, ISearchComplete, ISearchService } from '../../../../services/search/common/search.js'; +import { URI } from '../../../../../base/common/uri.js'; +import assert from 'assert'; +import { IExplorerService } from '../../browser/files.js'; +import { basename } from '../../../../../base/common/resources.js'; +import { TreeFindMatchType, TreeFindMode } from '../../../../../base/browser/ui/tree/abstractTree.js'; + +function find(element: ExplorerItem, id: string): ExplorerItem | undefined { + if (element.name === id) { + return element; + } + + if (!element.children) { + return undefined; + } + + for (const child of element.children.values()) { + const result = find(child, id); + + if (result) { + return result; + } + } + + return undefined; +} + +class Renderer implements ICompressibleTreeRenderer { + readonly templateId = 'default'; + renderTemplate(container: HTMLElement): HTMLElement { + return container; + } + renderElement(element: ITreeNode, index: number, templateData: HTMLElement): void { + templateData.textContent = element.element.name; + } + disposeTemplate(templateData: HTMLElement): void { + // noop + } + renderCompressedElements(node: ITreeNode, FuzzyScore>, index: number, templateData: HTMLElement, height: number | undefined): void { + const result: string[] = []; + + for (const element of node.element.elements) { + result.push(element.name); + } + + templateData.textContent = result.join('/'); + } +} + +class IdentityProvider implements IIdentityProvider { + getId(element: ExplorerItem) { + return { + toString: () => { return element.name; } + }; + } +} + +class VirtualDelegate implements IListVirtualDelegate { + getHeight() { return 20; } + getTemplateId(element: ExplorerItem): string { return 'default'; } +} + +class DataSource implements IAsyncDataSource { + hasChildren(element: ExplorerItem): boolean { + return !!element.children && element.children.size > 0; + } + getChildren(element: ExplorerItem): Promise { + return Promise.resolve(Array.from(element.children.values()) || []); + } + getParent(element: ExplorerItem): ExplorerItem { + return element.parent!; + } + +} + +class AccessibilityProvider implements IListAccessibilityProvider { + getWidgetAriaLabel(): string { + return ''; + } + getAriaLabel(stat: ExplorerItem): string { + return stat.name; + } +} + +class KeyboardNavigationLabelProvider implements IKeyboardNavigationLabelProvider { + getKeyboardNavigationLabel(stat: ExplorerItem): string { + return stat.name; + } + getCompressedNodeKeyboardNavigationLabel(stats: ExplorerItem[]): string { + return stats.map(stat => stat.name).join('/'); + } +} + +class CompressionDelegate implements ITreeCompressionDelegate { + constructor(private dataSource: DataSource) { } + isIncompressible(element: ExplorerItem): boolean { + return !this.dataSource.hasChildren(element); + } +} + +suite('Files - ExplorerView', () => { + const disposables = ensureNoDisposablesAreLeakedInTestSuite(); + + const fileService = new TestFileService(); + const configService = new TestConfigurationService(); + + function createStat(this: any, path: string, isFolder: boolean): ExplorerItem { + return new ExplorerItem(URI.from({ scheme: 'file', path }), fileService, configService, NullFilesConfigurationService, undefined, isFolder); + } + + let root: ExplorerItem; + + let instantiationService: TestInstantiationService; + + const searchMappings = new Map([ + ['bb', [URI.file('/root/b/bb/bbb.txt'), URI.file('/root/a/ab/abb.txt'), URI.file('/root/b/bb/bba.txt')]], + ]); + + setup(() => { + root = createStat.call(this, '/root', true); + const a = createStat.call(this, '/root/a', true); + const aa = createStat.call(this, '/root/a/aa', true); + const ab = createStat.call(this, '/root/a/ab', true); + const aba = createStat.call(this, '/root/a/ab/aba.txt', false); + const abb = createStat.call(this, '/root/a/ab/abb.txt', false); + const b = createStat.call(this, '/root/b', true); + const ba = createStat.call(this, '/root/b/ba', true); + const baa = createStat.call(this, '/root/b/ba/baa.txt', false); + const bab = createStat.call(this, '/root/b/ba/bab.txt', false); + const bb = createStat.call(this, '/root/b/bb', true); + + root.addChild(a); + a.addChild(aa); + a.addChild(ab); + ab.addChild(aba); + ab.addChild(abb); + root.addChild(b); + b.addChild(ba); + ba.addChild(baa); + ba.addChild(bab); + b.addChild(bb); + + instantiationService = workbenchInstantiationService(undefined, disposables); + instantiationService.stub(IExplorerService, { + roots: [root], + refresh: () => Promise.resolve(), + findClosest: (resource: URI) => { + return find(root, basename(resource)) ?? null; + }, + }); + instantiationService.stub(ISearchService, { + fileSearch(query: IFileQuery, token?: CancellationToken): Promise { + const filePattern = query.filePattern?.replace(/\//g, '') + .replace(/\*/g, '') + .replace(/\[/g, '') + .replace(/\]/g, '') + .replace(/[A-Z]/g, '') ?? ''; + const fileMatches: IFileMatch[] = (searchMappings.get(filePattern) ?? []).map(u => ({ resource: u })); + return Promise.resolve({ results: fileMatches, messages: [] }); + }, + schemeHasFileSearchProvider(): boolean { + return true; + } + }); + }); + + test('find provider', async function () { + const disposables = new DisposableStore(); + + // Tree Stuff + const container = document.createElement('div'); + + const dataSource = new DataSource(); + const compressionDelegate = new CompressionDelegate(dataSource); + const keyboardNavigationLabelProvider = new KeyboardNavigationLabelProvider(); + const accessibilityProvider = new AccessibilityProvider(); + + const options: IWorkbenchCompressibleAsyncDataTreeOptions = { identityProvider: new IdentityProvider(), keyboardNavigationLabelProvider, accessibilityProvider }; + const tree = disposables.add(instantiationService.createInstance(WorkbenchCompressibleAsyncDataTree, 'test', container, new VirtualDelegate(), compressionDelegate, [new Renderer()], dataSource, options)); + tree.layout(200); + + await tree.setInput(root); + + const findProvider = instantiationService.createInstance(ExplorerFindProvider, () => tree); + + findProvider.startSession(); + + assert.strictEqual(find(root, 'abb.txt') !== undefined, true); + assert.strictEqual(find(root, 'bba.txt') !== undefined, false); + assert.strictEqual(find(root, 'bbb.txt') !== undefined, false); + + assert.strictEqual(find(root, 'abb.txt')?.isMarkedAsFiltered(), false); + assert.strictEqual(find(root, 'a')?.isMarkedAsFiltered(), false); + assert.strictEqual(find(root, 'ab')?.isMarkedAsFiltered(), false); + + await findProvider.find('bb', { matchType: TreeFindMatchType.Contiguous, findMode: TreeFindMode.Filter }, new CancellationTokenSource().token); + + assert.strictEqual(find(root, 'abb.txt') !== undefined, true); + assert.strictEqual(find(root, 'bba.txt') !== undefined, true); + assert.strictEqual(find(root, 'bbb.txt') !== undefined, true); + + assert.strictEqual(find(root, 'abb.txt')?.isMarkedAsFiltered(), true); + assert.strictEqual(find(root, 'bba.txt')?.isMarkedAsFiltered(), true); + assert.strictEqual(find(root, 'bbb.txt')?.isMarkedAsFiltered(), true); + + assert.strictEqual(find(root, 'a')?.isMarkedAsFiltered(), true); + assert.strictEqual(find(root, 'ab')?.isMarkedAsFiltered(), true); + assert.strictEqual(find(root, 'b')?.isMarkedAsFiltered(), true); + assert.strictEqual(find(root, 'bb')?.isMarkedAsFiltered(), true); + + assert.strictEqual(find(root, 'aa')?.isMarkedAsFiltered(), false); + assert.strictEqual(find(root, 'ba')?.isMarkedAsFiltered(), false); + assert.strictEqual(find(root, 'aba.txt')?.isMarkedAsFiltered(), false); + + await findProvider.endSession(); + + assert.strictEqual(find(root, 'abb.txt') !== undefined, true); + assert.strictEqual(find(root, 'baa.txt') !== undefined, true); + assert.strictEqual(find(root, 'baa.txt') !== undefined, true); + assert.strictEqual(find(root, 'bba.txt') !== undefined, false); + assert.strictEqual(find(root, 'bbb.txt') !== undefined, false); + + assert.strictEqual(find(root, 'a')?.isMarkedAsFiltered(), false); + assert.strictEqual(find(root, 'ab')?.isMarkedAsFiltered(), false); + assert.strictEqual(find(root, 'b')?.isMarkedAsFiltered(), false); + assert.strictEqual(find(root, 'bb')?.isMarkedAsFiltered(), false); + + disposables.dispose(); + }); +}); diff --git a/src/vs/workbench/contrib/files/test/browser/explorerView.test.ts b/src/vs/workbench/contrib/files/test/browser/explorerView.test.ts index 0027c63f0513d..7e65d5e557fb6 100644 --- a/src/vs/workbench/contrib/files/test/browser/explorerView.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/explorerView.test.ts @@ -95,7 +95,7 @@ suite('Files - ExplorerView', () => { label: { container: label, onDidRender: emitter.event - } + }, }, 1, false); ds.add(navigationController); diff --git a/src/vs/workbench/contrib/format/browser/formatActionsNone.ts b/src/vs/workbench/contrib/format/browser/formatActionsNone.ts index a01e47e17c03c..e12080faaf89b 100644 --- a/src/vs/workbench/contrib/format/browser/formatActionsNone.ts +++ b/src/vs/workbench/contrib/format/browser/formatActionsNone.ts @@ -21,8 +21,7 @@ registerEditorAction(class FormatDocumentMultipleAction extends EditorAction { constructor() { super({ id: 'editor.action.formatDocument.none', - label: nls.localize('formatDocument.label.multiple', "Format Document"), - alias: 'Format Document', + label: nls.localize2('formatDocument.label.multiple', "Format Document"), precondition: ContextKeyExpr.and(EditorContextKeys.writable, EditorContextKeys.hasDocumentFormattingProvider.toNegated()), kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, diff --git a/src/vs/workbench/contrib/format/browser/formatModified.ts b/src/vs/workbench/contrib/format/browser/formatModified.ts index f59c7cc29ef7e..bddd514e821e1 100644 --- a/src/vs/workbench/contrib/format/browser/formatModified.ts +++ b/src/vs/workbench/contrib/format/browser/formatModified.ts @@ -25,8 +25,7 @@ registerEditorAction(class FormatModifiedAction extends EditorAction { constructor() { super({ id: 'editor.action.formatChanges', - label: nls.localize('formatChanges', "Format Modified Lines"), - alias: 'Format Modified Lines', + label: nls.localize2('formatChanges', "Format Modified Lines"), precondition: ContextKeyExpr.and(EditorContextKeys.writable, EditorContextKeys.hasDocumentSelectionFormattingProvider), }); } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts index bf80d6d241b40..2cc76db29aa94 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts @@ -19,9 +19,9 @@ import { IInlineChatSavingService } from './inlineChatSavingService.js'; import { IInlineChatSessionService } from './inlineChatSessionService.js'; import { InlineChatEnabler, InlineChatSessionServiceImpl } from './inlineChatSessionServiceImpl.js'; import { AccessibleViewRegistry } from '../../../../platform/accessibility/browser/accessibleViewRegistry.js'; -import { CancelAction, SubmitAction } from '../../chat/browser/actions/chatExecuteActions.js'; +import { CancelAction, ChatSubmitAction } from '../../chat/browser/actions/chatExecuteActions.js'; import { localize } from '../../../../nls.js'; -import { CONTEXT_CHAT_INPUT_HAS_TEXT } from '../../chat/common/chatContextKeys.js'; +import { ChatContextKeys } from '../../chat/common/chatContextKeys.js'; import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js'; import { InlineChatAccessibilityHelp } from './inlineChatAccessibilityHelp.js'; import { InlineChatExansionContextKey, InlineChatExpandLineAction } from './inlineChatCurrentLine.js'; @@ -43,11 +43,11 @@ const editActionMenuItem: IMenuItem = { group: '0_main', order: 0, command: { - id: SubmitAction.ID, + id: ChatSubmitAction.ID, title: localize('send.edit', "Edit Code"), }, when: ContextKeyExpr.and( - CONTEXT_CHAT_INPUT_HAS_TEXT, + ChatContextKeys.inputHasText, CTX_INLINE_CHAT_REQUEST_IN_PROGRESS.toNegated(), CTX_INLINE_CHAT_EDITING ), @@ -57,11 +57,11 @@ const generateActionMenuItem: IMenuItem = { group: '0_main', order: 0, command: { - id: SubmitAction.ID, + id: ChatSubmitAction.ID, title: localize('send.generate', "Generate"), }, when: ContextKeyExpr.and( - CONTEXT_CHAT_INPUT_HAS_TEXT, + ChatContextKeys.inputHasText, CTX_INLINE_CHAT_REQUEST_IN_PROGRESS.toNegated(), CTX_INLINE_CHAT_EDITING.toNegated() ), diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatAccessibilityHelp.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatAccessibilityHelp.ts index e44fe66d93f98..338697c4b7bd9 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatAccessibilityHelp.ts @@ -9,14 +9,14 @@ import { AccessibleViewType } from '../../../../platform/accessibility/browser/a import { IAccessibleViewImplentation } from '../../../../platform/accessibility/browser/accessibleViewRegistry.js'; import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js'; import { getChatAccessibilityHelpProvider } from '../../chat/browser/actions/chatAccessibilityHelp.js'; -import { CONTEXT_CHAT_INPUT_HAS_FOCUS } from '../../chat/common/chatContextKeys.js'; +import { ChatContextKeys } from '../../chat/common/chatContextKeys.js'; import { CTX_INLINE_CHAT_RESPONSE_FOCUSED } from '../common/inlineChat.js'; export class InlineChatAccessibilityHelp implements IAccessibleViewImplentation { readonly priority = 106; readonly name = 'inlineChat'; readonly type = AccessibleViewType.Help; - readonly when = ContextKeyExpr.or(CTX_INLINE_CHAT_RESPONSE_FOCUSED, CONTEXT_CHAT_INPUT_HAS_FOCUS); + readonly when = ContextKeyExpr.or(CTX_INLINE_CHAT_RESPONSE_FOCUSED, ChatContextKeys.inputHasFocus); getProvider(accessor: ServicesAccessor) { const codeEditor = accessor.get(ICodeEditorService).getActiveCodeEditor() || accessor.get(ICodeEditorService).getFocusedCodeEditor(); if (!codeEditor) { diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts index 030b9bda36596..84f00a76493a5 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts @@ -25,7 +25,7 @@ import { registerIcon } from '../../../../platform/theme/common/iconRegistry.js' import { IPreferencesService } from '../../../services/preferences/common/preferences.js'; import { ILogService } from '../../../../platform/log/common/log.js'; import { IChatService } from '../../chat/common/chatService.js'; -import { CONTEXT_CHAT_INPUT_HAS_TEXT, CONTEXT_IN_CHAT_INPUT } from '../../chat/common/chatContextKeys.js'; +import { ChatContextKeys } from '../../chat/common/chatContextKeys.js'; import { HunkInformation } from './inlineChatSession.js'; import { IChatWidgetService } from '../../chat/browser/chat.js'; @@ -67,7 +67,7 @@ export class StartSessionAction extends EditorAction2 { icon: START_INLINE_CHAT, menu: { id: MenuId.ChatCommandCenter, - group: 'b_inlineChat', + group: 'd_inlineChat', order: 10, } }); @@ -255,7 +255,7 @@ export class AcceptChanges extends AbstractInlineChatAction { group: '0_main', order: 1, when: ContextKeyExpr.and( - CONTEXT_CHAT_INPUT_HAS_TEXT.toNegated(), + ChatContextKeys.inputHasText.toNegated(), CTX_INLINE_CHAT_REQUEST_IN_PROGRESS.toNegated(), CTX_INLINE_CHAT_RESPONSE_TYPE.isEqualTo(InlineChatResponseType.MessagesAndEdits) ), @@ -285,7 +285,7 @@ export class DiscardHunkAction extends AbstractInlineChatAction { group: '0_main', order: 2, when: ContextKeyExpr.and( - CONTEXT_CHAT_INPUT_HAS_TEXT.toNegated(), + ChatContextKeys.inputHasText.toNegated(), CTX_INLINE_CHAT_REQUEST_IN_PROGRESS.negate(), CTX_INLINE_CHAT_RESPONSE_TYPE.isEqualTo(InlineChatResponseType.MessagesAndEdits), CTX_INLINE_CHAT_EDIT_MODE.isEqualTo(EditMode.Live) @@ -322,7 +322,7 @@ export class RerunAction extends AbstractInlineChatAction { group: '0_main', order: 5, when: ContextKeyExpr.and( - CONTEXT_CHAT_INPUT_HAS_TEXT.toNegated(), + ChatContextKeys.inputHasText.toNegated(), CTX_INLINE_CHAT_REQUEST_IN_PROGRESS.negate(), CTX_INLINE_CHAT_RESPONSE_TYPE.notEqualsTo(InlineChatResponseType.None) ) @@ -465,7 +465,7 @@ export class ViewInChatAction extends AbstractInlineChatAction { group: '0_main', order: 1, when: ContextKeyExpr.and( - CONTEXT_CHAT_INPUT_HAS_TEXT.toNegated(), + ChatContextKeys.inputHasText.toNegated(), CTX_INLINE_CHAT_RESPONSE_TYPE.isEqualTo(InlineChatResponseType.Messages), CTX_INLINE_CHAT_REQUEST_IN_PROGRESS.negate() ) @@ -473,7 +473,7 @@ export class ViewInChatAction extends AbstractInlineChatAction { keybinding: { weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.CtrlCmd | KeyCode.DownArrow, - when: CONTEXT_IN_CHAT_INPUT + when: ChatContextKeys.inChatInput } }); } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index fed6152dba7e8..17d1b391b3b0d 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -38,7 +38,7 @@ import { IChatViewState, IChatWidgetLocationOptions } from '../../chat/browser/c import { ChatAgentLocation } from '../../chat/common/chatAgents.js'; import { ChatModel, ChatRequestRemovalReason, IChatRequestModel, IChatTextEditGroup, IChatTextEditGroupState, IResponse } from '../../chat/common/chatModel.js'; import { IChatService } from '../../chat/common/chatService.js'; -import { HunkInformation, HunkState, Session, StashedSession } from './inlineChatSession.js'; +import { HunkInformation, Session, StashedSession } from './inlineChatSession.js'; import { InlineChatError } from './inlineChatSessionServiceImpl.js'; import { EditModeStrategy, HunkAction, IEditObserver, LiveStrategy, PreviewStrategy, ProgressingEditsOptions } from './inlineChatStrategies.js'; import { CTX_INLINE_CHAT_EDITING, CTX_INLINE_CHAT_REQUEST_IN_PROGRESS, CTX_INLINE_CHAT_RESPONSE_TYPE, CTX_INLINE_CHAT_USER_DID_EDIT, CTX_INLINE_CHAT_VISIBLE, EditMode, INLINE_CHAT_ID, InlineChatConfigKeys, InlineChatResponseType } from '../common/inlineChat.js'; @@ -48,7 +48,8 @@ import { IViewsService } from '../../../services/views/common/viewsService.js'; import { IInlineChatSavingService } from './inlineChatSavingService.js'; import { IInlineChatSessionService } from './inlineChatSessionService.js'; import { InlineChatZoneWidget } from './inlineChatZoneWidget.js'; -import { CONTEXT_RESPONSE, CONTEXT_RESPONSE_ERROR } from '../../chat/common/chatContextKeys.js'; +import { ChatContextKeys } from '../../chat/common/chatContextKeys.js'; +import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; export const enum State { CREATE_SESSION = 'CREATE_SESSION', @@ -103,11 +104,12 @@ export class InlineChatController implements IEditorContribution { return editor.getContribution(INLINE_CHAT_ID); } + private static readonly _storageKey = 'inlineChatController.state'; + private _isDisposed: boolean = false; private readonly _store = new DisposableStore(); private readonly _ui: Lazy; - private _uiInitViewState: IChatViewState | undefined; private readonly _ctxVisible: IContextKey; private readonly _ctxEditing: IContextKey; @@ -145,6 +147,7 @@ export class InlineChatController implements IEditorContribution { @IContextKeyService contextKeyService: IContextKeyService, @IChatService private readonly _chatService: IChatService, @IEditorService private readonly _editorService: IEditorService, + @IStorageService private readonly _storageService: IStorageService, @INotebookEditorService notebookEditorService: INotebookEditorService, ) { this._ctxVisible = CTX_INLINE_CHAT_VISIBLE.bindTo(contextKeyService); @@ -153,8 +156,8 @@ export class InlineChatController implements IEditorContribution { this._ctxResponseType = CTX_INLINE_CHAT_RESPONSE_TYPE.bindTo(contextKeyService); this._ctxRequestInProgress = CTX_INLINE_CHAT_REQUEST_IN_PROGRESS.bindTo(contextKeyService); - this._ctxResponse = CONTEXT_RESPONSE.bindTo(contextKeyService); - CONTEXT_RESPONSE_ERROR.bindTo(contextKeyService); + this._ctxResponse = ChatContextKeys.isResponse.bindTo(contextKeyService); + ChatContextKeys.responseHasError.bindTo(contextKeyService); this._ui = new Lazy(() => { @@ -185,7 +188,16 @@ export class InlineChatController implements IEditorContribution { } } - return this._store.add(_instaService.createInstance(InlineChatZoneWidget, location, this._editor)); + const zone = _instaService.createInstance(InlineChatZoneWidget, location, this._editor); + this._store.add(zone); + this._store.add(zone.widget.chatWidget.onDidClear(async () => { + const r = this.joinCurrentRun(); + this.cancelSession(); + await r; + this.run(); + })); + + return zone; }); this._store.add(this._editor.onDidChangeModel(async e => { @@ -230,19 +242,6 @@ export class InlineChatController implements IEditorContribution { this._log('DISPOSED controller'); } - saveViewState(): any { - if (!this._ui.rawValue) { - return undefined; - } - // only take select lm - const { selectedLanguageModelId } = this._ui.rawValue.widget.chatWidget.getViewState(); - return { selectedLanguageModelId }; - } - - restoreViewState(state: any): void { - this._uiInitViewState = state; - } - private _log(message: string | Error, ...more: any[]): void { if (message instanceof Error) { this._logService.error(message, ...more); @@ -394,7 +393,7 @@ export class InlineChatController implements IEditorContribution { assertType(this._strategy); // hide/cancel inline completions when invoking IE - InlineCompletionsController.get(this._editor)?.hide(); + InlineCompletionsController.get(this._editor)?.reject(); this._sessionStore.clear(); @@ -412,11 +411,9 @@ export class InlineChatController implements IEditorContribution { this._sessionStore.add(this._session.wholeRange.onDidChange(handleWholeRangeChange)); handleWholeRangeChange(); - this._ui.value.widget.setChatModel(this._session.chatModel, this._uiInitViewState); - this._uiInitViewState = undefined; + this._ui.value.widget.setChatModel(this._session.chatModel, this._retrieveWidgetState()); this._updatePlaceholder(); - const isModelEmpty = !this._session.chatModel.hasRequests; this._ui.value.widget.updateToolbar(true); this._ui.value.widget.toggleStatus(!isModelEmpty); @@ -557,7 +554,7 @@ export class InlineChatController implements IEditorContribution { } if (message & Message.ACCEPT_SESSION) { - this._ui.value.widget.selectAll(false); + this._ui.value.widget.selectAll(); return State.ACCEPT; } @@ -584,7 +581,7 @@ export class InlineChatController implements IEditorContribution { assertType(request.response); this._showWidget(this._session.headless, false); - this._ui.value.widget.selectAll(false); + this._ui.value.widget.selectAll(); this._ui.value.widget.updateInfo(''); this._ui.value.widget.toggleStatus(true); @@ -744,7 +741,7 @@ export class InlineChatController implements IEditorContribution { await responsePromise.p; await progressiveEditsQueue.whenIdle(); - if (response.result?.errorDetails) { + if (response.result?.errorDetails && !response.result.errorDetails.responseIsFiltered) { await this._session.undoChangesUntil(response.requestId); } @@ -890,11 +887,19 @@ export class InlineChatController implements IEditorContribution { } private _resetWidget() { + this._sessionStore.clear(); this._ctxVisible.reset(); this._ctxUserDidEdit.reset(); - this._ui.rawValue?.hide(); + if (this._ui.rawValue) { + // persist selected LM in memento + const { selectedLanguageModelId } = this._ui.rawValue.widget.chatWidget.getViewState(); + const state = { selectedLanguageModelId }; + this._storageService.store(InlineChatController._storageKey, state, StorageScope.PROFILE, StorageTarget.USER); + + this._ui.rawValue.hide(); + } // Return focus to the editor only if the current focus is within the editor widget if (this._editor.hasWidgetFocus()) { @@ -902,6 +907,15 @@ export class InlineChatController implements IEditorContribution { } } + private _retrieveWidgetState(): IChatViewState | undefined { + try { + const state = JSON.parse(this._storageService.get(InlineChatController._storageKey, StorageScope.PROFILE) ?? '{}'); + return state; + } catch { + return undefined; + } + } + private _updateCtxResponseType(): void { if (!this._session) { @@ -970,36 +984,12 @@ export class InlineChatController implements IEditorContribution { } } - private _forcedPlaceholder: string | undefined = undefined; - private _updatePlaceholder(): void { - this._ui.value.widget.placeholder = this._getPlaceholderText(); - } - - private _getPlaceholderText(): string { - return this._forcedPlaceholder ?? this._session?.agent.description ?? ''; + this._ui.value.widget.placeholder = this._session?.agent.description ?? ''; } // ---- controller API - showSaveHint(): void { - if (!this._session) { - return; - } - - const status = localize('savehint', "Accept or discard changes to continue saving."); - this._ui.value.widget.updateStatus(status, { classes: ['warn'] }); - - if (this._ui.value.position) { - this._editor.revealLineInCenterIfOutsideViewport(this._ui.value.position.lineNumber); - } else { - const hunk = this._session.hunkData.getInfo().find(info => info.getState() === HunkState.Pending); - if (hunk) { - this._editor.revealLineInCenterIfOutsideViewport(hunk.getRangesN()[0].startLineNumber); - } - } - } - acceptInput() { return this.chatWidget.acceptInput(); } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index 9fab09b8f489e..c947e3ed8562e 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -8,12 +8,10 @@ import { IActionViewItemOptions } from '../../../../base/browser/ui/actionbar/ac import { getDefaultHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js'; import { renderLabelWithIcons } from '../../../../base/browser/ui/iconLabel/iconLabels.js'; import { IAction } from '../../../../base/common/actions.js'; -import { isNonEmptyArray, tail } from '../../../../base/common/arrays.js'; +import { isNonEmptyArray } from '../../../../base/common/arrays.js'; import { Emitter, Event } from '../../../../base/common/event.js'; -import { IMarkdownString } from '../../../../base/common/htmlContent.js'; import { DisposableStore, MutableDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; -import { constObservable, derived, ISettableObservable, observableValue } from '../../../../base/common/observable.js'; -import './media/inlineChat.css'; +import { constObservable, derived, IObservable, ISettableObservable, observableValue } from '../../../../base/common/observable.js'; import { ICodeEditor } from '../../../../editor/browser/editorBrowser.js'; import { AccessibleDiffViewer, IAccessibleDiffViewerModel } from '../../../../editor/browser/widget/diffEditor/components/accessibleDiffViewer.js'; import { EditorOption, IComputedEditorOptions } from '../../../../editor/common/config/editorOptions.js'; @@ -24,7 +22,7 @@ import { Selection } from '../../../../editor/common/core/selection.js'; import { DetailedLineRangeMapping, RangeMapping } from '../../../../editor/common/diff/rangeMapping.js'; import { ICodeEditorViewState, ScrollType } from '../../../../editor/common/editorCommon.js'; import { ITextModel } from '../../../../editor/common/model.js'; -import { IResolvedTextEditorModel, ITextModelService } from '../../../../editor/common/services/resolverService.js'; +import { ITextModelService } from '../../../../editor/common/services/resolverService.js'; import { localize } from '../../../../nls.js'; import { IAccessibleViewService } from '../../../../platform/accessibility/browser/accessibleView.js'; import { IAccessibilityService } from '../../../../platform/accessibility/common/accessibility.js'; @@ -38,7 +36,9 @@ import { IHoverService } from '../../../../platform/hover/browser/hover.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { ServiceCollection } from '../../../../platform/instantiation/common/serviceCollection.js'; import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; +import { ILayoutService } from '../../../../platform/layout/browser/layoutService.js'; import { asCssVariable, asCssVariableName, editorBackground, inputBackground } from '../../../../platform/theme/common/colorRegistry.js'; +import { EDITOR_DRAG_AND_DROP_BACKGROUND } from '../../../common/theme.js'; import { AccessibilityVerbositySettingId } from '../../accessibility/browser/accessibilityConfiguration.js'; import { AccessibilityCommandId } from '../../accessibility/common/accessibilityCommands.js'; import { MarkUnhelpfulActionId } from '../../chat/browser/actions/chatTitleActions.js'; @@ -46,14 +46,13 @@ import { IChatWidgetViewOptions } from '../../chat/browser/chat.js'; import { ChatVoteDownButton } from '../../chat/browser/chatListRenderer.js'; import { ChatWidget, IChatViewState, IChatWidgetLocationOptions } from '../../chat/browser/chatWidget.js'; import { chatRequestBackground } from '../../chat/common/chatColors.js'; -import { CONTEXT_CHAT_RESPONSE_SUPPORT_ISSUE_REPORTING, CONTEXT_RESPONSE, CONTEXT_RESPONSE_ERROR, CONTEXT_RESPONSE_FILTERED, CONTEXT_RESPONSE_VOTE } from '../../chat/common/chatContextKeys.js'; +import { ChatContextKeys } from '../../chat/common/chatContextKeys.js'; import { IChatModel } from '../../chat/common/chatModel.js'; import { ChatAgentVoteDirection, IChatService } from '../../chat/common/chatService.js'; import { isResponseVM } from '../../chat/common/chatViewModel.js'; -import { HunkInformation, Session } from './inlineChatSession.js'; import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_RESPONSE_FOCUSED, inlineChatBackground, inlineChatForeground } from '../common/inlineChat.js'; -import { EDITOR_DRAG_AND_DROP_BACKGROUND } from '../../../common/theme.js'; -import { ILayoutService } from '../../../../platform/layout/browser/layoutService.js'; +import { HunkInformation, Session } from './inlineChatSession.js'; +import './media/inlineChat.css'; export interface InlineChatWidgetViewState { @@ -79,17 +78,6 @@ export interface IInlineChatWidgetConstructionOptions { inZoneWidget?: boolean; } -export interface IInlineChatMessage { - message: IMarkdownString; - requestId: string; -} - -export interface IInlineChatMessageAppender { - appendContent(fragment: string): void; - cancel(): void; - complete(): void; -} - export class InlineChatWidget { protected readonly _elements = h( @@ -116,8 +104,8 @@ export class InlineChatWidget { protected readonly _onDidChangeHeight = this._store.add(new Emitter()); readonly onDidChangeHeight: Event = Event.filter(this._onDidChangeHeight.event, _ => !this._isLayouting); - private readonly _onDidChangeInput = this._store.add(new Emitter()); - readonly onDidChangeInput: Event = this._onDidChangeInput.event; + private readonly _requestInProgress = observableValue(this, false); + readonly requestInProgress: IObservable = this._requestInProgress; private _isLayouting: boolean = false; @@ -157,18 +145,17 @@ export class InlineChatWidget { renderFollowups: true, supportsFileReferences: true, filter: item => { - if (isResponseVM(item) && item.isComplete && !item.errorDetails) { - // filter responses that - // - are just text edits(prevents the "Made Edits") - // - are all empty - if (item.response.value.length > 0 && item.response.value.every(item => item.kind === 'textEditGroup' && _options.chatWidgetViewOptions?.rendererOptions?.renderTextEditsAsSummary?.(item.uri))) { - return false; - } - if (item.response.value.length === 0) { - return false; - } + if (!isResponseVM(item) || item.errorDetails) { + // show all requests and errors return true; } + const emptyResponse = item.response.value.length === 0; + if (emptyResponse) { + return false; + } + if (item.response.value.every(item => item.kind === 'textEditGroup' && _options.chatWidgetViewOptions?.rendererOptions?.renderTextEditsAsSummary?.(item.uri))) { + return false; + } return true; }, ..._options.chatWidgetViewOptions @@ -187,11 +174,11 @@ export class InlineChatWidget { this._chatWidget.setVisible(true); this._store.add(this._chatWidget); - const ctxResponse = CONTEXT_RESPONSE.bindTo(this.scopedContextKeyService); - const ctxResponseVote = CONTEXT_RESPONSE_VOTE.bindTo(this.scopedContextKeyService); - const ctxResponseSupportIssues = CONTEXT_CHAT_RESPONSE_SUPPORT_ISSUE_REPORTING.bindTo(this.scopedContextKeyService); - const ctxResponseError = CONTEXT_RESPONSE_ERROR.bindTo(this.scopedContextKeyService); - const ctxResponseErrorFiltered = CONTEXT_RESPONSE_FILTERED.bindTo(this.scopedContextKeyService); + const ctxResponse = ChatContextKeys.isResponse.bindTo(this.scopedContextKeyService); + const ctxResponseVote = ChatContextKeys.responseVote.bindTo(this.scopedContextKeyService); + const ctxResponseSupportIssues = ChatContextKeys.responseSupportsIssueReporting.bindTo(this.scopedContextKeyService); + const ctxResponseError = ChatContextKeys.responseHasError.bindTo(this.scopedContextKeyService); + const ctxResponseErrorFiltered = ChatContextKeys.responseIsFiltered.bindTo(this.scopedContextKeyService); const viewModelStore = this._store.add(new DisposableStore()); this._store.add(this._chatWidget.onDidChangeViewModel(() => { @@ -213,6 +200,8 @@ export class InlineChatWidget { viewModelStore.add(viewModel.onDidChange(() => { + this._requestInProgress.set(viewModel.requestInProgress, undefined); + const last = viewModel.getItems().at(-1); toolbar2.context = last; @@ -324,11 +313,16 @@ export class InlineChatWidget { } layout(widgetDim: Dimension) { + const contentHeight = this.contentHeight; this._isLayouting = true; try { this._doLayout(widgetDim); } finally { this._isLayouting = false; + + if (this.contentHeight !== contentHeight) { + this._onDidChangeHeight.fire(); + } } } @@ -390,18 +384,8 @@ export class InlineChatWidget { this._chatWidget.setInput(value); } - - selectAll(includeSlashCommand: boolean = true) { - // DEBT@jrieken - // REMOVE when agents are adopted - let startColumn = 1; - if (!includeSlashCommand) { - const match = /^(\/\w+)\s*/.exec(this._chatWidget.inputEditor.getModel()!.getLineContent(1)); - if (match) { - startColumn = match[1].length + 1; - } - } - this._chatWidget.inputEditor.setSelection(new Selection(1, startColumn, Number.MAX_SAFE_INTEGER, 1)); + selectAll() { + this._chatWidget.inputEditor.setSelection(new Selection(1, 1, Number.MAX_SAFE_INTEGER, 1)); } set placeholder(value: string) { @@ -425,16 +409,16 @@ export class InlineChatWidget { this._onDidChangeHeight.fire(); } - async getCodeBlockInfo(codeBlockIndex: number): Promise { + async getCodeBlockInfo(codeBlockIndex: number): Promise { const { viewModel } = this._chatWidget; if (!viewModel) { return undefined; } const items = viewModel.getItems().filter(i => isResponseVM(i)); - if (!items.length) { + const item = items.at(-1); + if (!item) { return; } - const item = items[items.length - 1]; return viewModel.codeBlockModelCollection.get(viewModel.sessionId, item, codeBlockIndex)?.model; } @@ -443,7 +427,7 @@ export class InlineChatWidget { if (!isNonEmptyArray(requests)) { return undefined; } - return tail(requests)?.response?.response.toString(); + return requests.at(-1)?.response?.response.toString(); } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatZoneWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatZoneWidget.ts index c487376229c28..07734037b8d8f 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatZoneWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatZoneWidget.ts @@ -5,11 +5,12 @@ import { addDisposableListener, Dimension } from '../../../../base/browser/dom.js'; import * as aria from '../../../../base/browser/ui/aria/aria.js'; import { MutableDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; +import { autorun } from '../../../../base/common/observable.js'; import { isEqual } from '../../../../base/common/resources.js'; import { assertType } from '../../../../base/common/types.js'; import { ICodeEditor } from '../../../../editor/browser/editorBrowser.js'; import { StableEditorBottomScrollState } from '../../../../editor/browser/stableEditorScroll.js'; -import { EditorLayoutInfo, EditorOption } from '../../../../editor/common/config/editorOptions.js'; +import { EditorOption } from '../../../../editor/common/config/editorOptions.js'; import { Position } from '../../../../editor/common/core/position.js'; import { Range } from '../../../../editor/common/core/range.js'; import { ScrollType } from '../../../../editor/common/editorCommon.js'; @@ -30,7 +31,7 @@ export class InlineChatZoneWidget extends ZoneWidget { showFrame: true, frameWidth: 1, // frameColor: 'var(--vscode-inlineChat-border)', - // isResizeable: true, + isResizeable: true, showArrow: false, isAccessible: true, className: 'inline-chat-widget', @@ -100,7 +101,7 @@ export class InlineChatZoneWidget extends ZoneWidget { } })); this._disposables.add(this.widget.onDidChangeHeight(() => { - if (this.position) { + if (this.position && !this._usesResizeHeight) { // only relayout when visible revealFn ??= this._createZoneAndScrollRestoreFn(this.position); const height = this._computeHeight(); @@ -112,6 +113,11 @@ export class InlineChatZoneWidget extends ZoneWidget { this.create(); + this._disposables.add(autorun(r => { + const isBusy = this.widget.requestInProgress.read(r); + this.domNode.firstElementChild?.classList.toggle('busy', isBusy); + })); + this._disposables.add(addDisposableListener(this.domNode, 'click', e => { if (!this.editor.hasWidgetFocus() && !this.widget.hasFocus()) { this.editor.focus(); @@ -145,9 +151,11 @@ export class InlineChatZoneWidget extends ZoneWidget { protected override _doLayout(heightInPixel: number): void { + this._updatePadding(); + const info = this.editor.getLayoutInfo(); - let width = info.contentWidth - (info.glyphMarginWidth + info.decorationsWidth); - width = Math.min(640, width); + let width = info.contentWidth - info.verticalScrollbarWidth; + width = Math.min(850, width); this._dimension = new Dimension(width, heightInPixel); this.widget.layout(this._dimension); @@ -162,6 +170,19 @@ export class InlineChatZoneWidget extends ZoneWidget { return { linesValue: heightInLines, pixelsValue: contentHeight }; } + protected override _getResizeBounds(): { minLines: number; maxLines: number } { + const lineHeight = this.editor.getOption(EditorOption.lineHeight); + const decoHeight = this._decoratingElementsHeight(); + + const minHeightPx = decoHeight + this.widget.minHeight; + const maxHeightPx = decoHeight + this.widget.contentHeight; + + return { + minLines: minHeightPx / lineHeight, + maxLines: maxHeightPx / lineHeight + }; + } + protected override _onWidth(_widthInPixel: number): void { if (this._dimension) { this._doLayout(this._dimension.height); @@ -171,9 +192,7 @@ export class InlineChatZoneWidget extends ZoneWidget { override show(position: Position): void { assertType(this.container); - const info = this.editor.getLayoutInfo(); - const marginWithoutIndentation = info.glyphMarginWidth + info.lineNumbersWidth; - this.container.style.paddingLeft = `${marginWithoutIndentation}px`; + this._updatePadding(); const revealZone = this._createZoneAndScrollRestoreFn(position); super.show(position, this._computeHeight().linesValue); @@ -184,6 +203,14 @@ export class InlineChatZoneWidget extends ZoneWidget { this._scrollUp.enable(); } + private _updatePadding() { + assertType(this.container); + + const info = this.editor.getLayoutInfo(); + const marginWithoutIndentation = info.glyphMarginWidth + info.lineNumbersWidth + info.decorationsWidth; + this.container.style.paddingLeft = `${marginWithoutIndentation}px`; + } + reveal(position: Position) { const stickyScroll = this.editor.getOption(EditorOption.stickyScroll); const magicValue = stickyScroll.enabled ? stickyScroll.maxLineCount : 0; @@ -194,7 +221,7 @@ export class InlineChatZoneWidget extends ZoneWidget { override updatePositionAndHeight(position: Position): void { const revealZone = this._createZoneAndScrollRestoreFn(position); - super.updatePositionAndHeight(position, this._computeHeight().linesValue); + super.updatePositionAndHeight(position, !this._usesResizeHeight ? this._computeHeight().linesValue : undefined); revealZone(); } @@ -249,10 +276,6 @@ export class InlineChatZoneWidget extends ZoneWidget { // noop } - protected override _getWidth(info: EditorLayoutInfo): number { - return info.width - info.minimap.minimapWidth; - } - override hide(): void { const scrollState = StableEditorBottomScrollState.capture(this.editor); this._scrollUp.disable(); diff --git a/src/vs/workbench/contrib/inlineChat/browser/media/inlineChat.css b/src/vs/workbench/contrib/inlineChat/browser/media/inlineChat.css index 897697cf87580..c4435f516b1ef 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/media/inlineChat.css +++ b/src/vs/workbench/contrib/inlineChat/browser/media/inlineChat.css @@ -38,6 +38,30 @@ background: var(--vscode-inlineChat-background); } +@property --inline-chat-frame-progress { + syntax: ''; + initial-value: 0%; + inherits: false; +} + +@keyframes shift { + 0% { + --inline-chat-frame-progress: 0%; + } + 50% { + --inline-chat-frame-progress: 100%; + } + 100% { + --inline-chat-frame-progress: 0%; + } +} + +.monaco-workbench .zone-widget.inline-chat-widget > .zone-widget-container.busy { + --inline-chat-frame-progress: 0%; + border-image: linear-gradient(90deg, var(--vscode-editorGutter-addedBackground) var(--inline-chat-frame-progress), var(--vscode-button-background)) 1; + animation: 3s shift linear infinite; +} + .monaco-workbench .zone-widget.inline-chat-widget > .zone-widget-container > .inline-chat { color: inherit; border-radius: unset; @@ -49,7 +73,7 @@ } .monaco-workbench .inline-chat .chat-widget .interactive-session .interactive-input-part { - padding: 4px 8px 0 8px; + padding: 4px 0 0 0; } .monaco-workbench .inline-chat .chat-widget .interactive-session .interactive-input-part .interactive-execute-toolbar { diff --git a/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts b/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts index 9ecf03b632635..249ee28a2f51a 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts @@ -39,7 +39,7 @@ import { EditorExtensions, EditorsOrder, IEditorControl, IEditorFactoryRegistry, import { EditorInput } from '../../../common/editor/editorInput.js'; import { PANEL_BORDER } from '../../../common/theme.js'; import { ResourceNotebookCellEdit } from '../../bulkEdit/browser/bulkCellEdits.js'; -import { InteractiveWindowSetting, INTERACTIVE_INPUT_CURSOR_BOUNDARY } from './interactiveCommon.js'; +import { ReplEditorSettings, INTERACTIVE_INPUT_CURSOR_BOUNDARY } from './interactiveCommon.js'; import { IInteractiveDocumentService, InteractiveDocumentService } from './interactiveDocumentService.js'; import { InteractiveEditor } from './interactiveEditor.js'; import { InteractiveEditorInput } from './interactiveEditorInput.js'; @@ -752,13 +752,8 @@ registerAction2(class extends Action2 { category: interactiveWindowCategory, menu: { id: MenuId.CommandPalette, - when: InteractiveWindowOpen, + when: InteractiveWindowOpen }, - keybinding: { - when: ContextKeyExpr.and(IS_COMPOSITE_NOTEBOOK, NOTEBOOK_EDITOR_FOCUSED), - weight: KeybindingWeight.WorkbenchContrib + 5, - primary: KeyMod.CtrlCmd | KeyCode.DownArrow - } }); } @@ -844,7 +839,7 @@ Registry.as(ConfigurationExtensions.Configuration).regis order: 100, type: 'object', 'properties': { - [InteractiveWindowSetting.interactiveWindowAlwaysScrollOnNewCell]: { + [ReplEditorSettings.interactiveWindowAlwaysScrollOnNewCell]: { type: 'boolean', default: true, markdownDescription: localize('interactiveWindow.alwaysScrollOnNewCell', "Automatically scroll the interactive window to show the output of the last statement executed. If this value is false, the window will only scroll if the last cell was already the one scrolled to.") @@ -854,13 +849,13 @@ Registry.as(ConfigurationExtensions.Configuration).regis default: false, markdownDescription: localize('interactiveWindow.promptToSaveOnClose', "Prompt to save the interactive window when it is closed. Only new interactive windows will be affected by this setting change.") }, - [InteractiveWindowSetting.executeWithShiftEnter]: { + [ReplEditorSettings.executeWithShiftEnter]: { type: 'boolean', default: false, markdownDescription: localize('interactiveWindow.executeWithShiftEnter', "Execute the Interactive Window (REPL) input box with shift+enter, so that enter can be used to create a newline."), tags: ['replExecute'] }, - [InteractiveWindowSetting.showExecutionHint]: { + [ReplEditorSettings.showExecutionHint]: { type: 'boolean', default: true, markdownDescription: localize('interactiveWindow.showExecutionHint', "Display a hint in the Interactive Window (REPL) input box to indicate how to execute code."), diff --git a/src/vs/workbench/contrib/interactive/browser/interactiveCommon.ts b/src/vs/workbench/contrib/interactive/browser/interactiveCommon.ts index f42d31dc7d9fb..e7796663c9601 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactiveCommon.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactiveCommon.ts @@ -7,8 +7,8 @@ import { RawContextKey } from '../../../../platform/contextkey/common/contextkey export const INTERACTIVE_INPUT_CURSOR_BOUNDARY = new RawContextKey<'none' | 'top' | 'bottom' | 'both'>('interactiveInputCursorAtBoundary', 'none'); -export const InteractiveWindowSetting = { +export const ReplEditorSettings = { interactiveWindowAlwaysScrollOnNewCell: 'interactiveWindow.alwaysScrollOnNewCell', executeWithShiftEnter: 'interactiveWindow.executeWithShiftEnter', - showExecutionHint: 'interactiveWindow.showExecutionHint' + showExecutionHint: 'interactiveWindow.showExecutionHint', }; diff --git a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts index 0c6bb6a516f68..669b094d675f0 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts @@ -5,6 +5,7 @@ import './media/interactive.css'; import * as DOM from '../../../../base/browser/dom.js'; +import * as domStylesheets from '../../../../base/browser/domStylesheets.js'; import { CancellationToken } from '../../../../base/common/cancellation.js'; import { Emitter, Event } from '../../../../base/common/event.js'; import { DisposableStore, MutableDisposable } from '../../../../base/common/lifecycle.js'; @@ -31,13 +32,12 @@ import { PLAINTEXT_LANGUAGE_ID } from '../../../../editor/common/languages/modes import { ILanguageService } from '../../../../editor/common/languages/language.js'; import { IMenuService, MenuId } from '../../../../platform/actions/common/actions.js'; import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; -import { InteractiveWindowSetting, INTERACTIVE_INPUT_CURSOR_BOUNDARY } from './interactiveCommon.js'; +import { ReplEditorSettings, INTERACTIVE_INPUT_CURSOR_BOUNDARY } from './interactiveCommon.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { NotebookOptions } from '../../notebook/browser/notebookOptions.js'; import { ToolBar } from '../../../../base/browser/ui/toolbar/toolbar.js'; import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js'; -import { createActionViewItem, createAndFillInActionBarActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; -import { IAction } from '../../../../base/common/actions.js'; +import { createActionViewItem, getActionBarActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { EditorExtensionsRegistry } from '../../../../editor/browser/editorExtensions.js'; import { ParameterHintsController } from '../../../../editor/contrib/parameterHints/browser/parameterHints.js'; import { MenuPreventer } from '../../codeEditor/browser/menuPreventer.js'; @@ -218,16 +218,12 @@ export class InteractiveEditor extends EditorPane implements IEditorPaneWithScro renderDropdownAsChildElement: true })); - const primary: IAction[] = []; - const secondary: IAction[] = []; - const result = { primary, secondary }; - - createAndFillInActionBarActions(menu, { shouldForwardArgs: true }, result); + const { primary, secondary } = getActionBarActions(menu.getActions({ shouldForwardArgs: true })); this._runbuttonToolbar.setActions([...primary, ...secondary]); } private _createLayoutStyles(): void { - this._styleElement = DOM.createStyleSheet(this._rootElement); + this._styleElement = domStylesheets.createStyleSheet(this._rootElement); const styleSheets: string[] = []; const { @@ -517,7 +513,7 @@ export class InteractiveEditor extends EditorPane implements IEditorPaneWithScro })); this._configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration(InteractiveWindowSetting.showExecutionHint)) { + if (e.affectsConfiguration(ReplEditorSettings.showExecutionHint)) { this._updateInputHint(); } }); @@ -596,7 +592,7 @@ export class InteractiveEditor extends EditorPane implements IEditorPaneWithScro const index = this._notebookWidget.value!.getCellIndex(cvm); if (index === this._notebookWidget.value!.getLength() - 1) { // If we're already at the bottom or auto scroll is enabled, scroll to the bottom - if (this._configurationService.getValue(InteractiveWindowSetting.interactiveWindowAlwaysScrollOnNewCell) || this._cellAtBottom(cvm)) { + if (this._configurationService.getValue(ReplEditorSettings.interactiveWindowAlwaysScrollOnNewCell) || this._cellAtBottom(cvm)) { this._notebookWidget.value!.scrollToBottom(); } } @@ -678,7 +674,7 @@ export class InteractiveEditor extends EditorPane implements IEditorPaneWithScro const shouldHide = !this._codeEditorWidget.hasModel() || - this._configurationService.getValue(InteractiveWindowSetting.showExecutionHint) === false || + this._configurationService.getValue(ReplEditorSettings.showExecutionHint) === false || this._codeEditorWidget.getModel()!.getValueLength() !== 0 || this._hasConflictingDecoration(); diff --git a/src/vs/workbench/contrib/interactive/browser/replInputHintContentWidget.ts b/src/vs/workbench/contrib/interactive/browser/replInputHintContentWidget.ts index 4b83bb64f0105..8b17007145fb2 100644 --- a/src/vs/workbench/contrib/interactive/browser/replInputHintContentWidget.ts +++ b/src/vs/workbench/contrib/interactive/browser/replInputHintContentWidget.ts @@ -17,7 +17,7 @@ import { IConfigurationService } from '../../../../platform/configuration/common import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; import { AccessibilityVerbositySettingId } from '../../accessibility/browser/accessibilityConfiguration.js'; import { AccessibilityCommandId } from '../../accessibility/common/accessibilityCommands.js'; -import { InteractiveWindowSetting } from './interactiveCommon.js'; +import { ReplEditorSettings } from './interactiveCommon.js'; export class ReplInputHintContentWidget extends Disposable implements IContentWidget { @@ -41,12 +41,12 @@ export class ReplInputHintContentWidget extends Disposable implements IContentWi })); const onDidFocusEditorText = Event.debounce(this.editor.onDidFocusEditorText, () => undefined, 500); this._register(onDidFocusEditorText(() => { - if (this.editor.hasTextFocus() && this.ariaLabel && configurationService.getValue(AccessibilityVerbositySettingId.ReplInputHint)) { + if (this.editor.hasTextFocus() && this.ariaLabel && configurationService.getValue(AccessibilityVerbositySettingId.ReplEditor)) { status(this.ariaLabel); } })); this._register(configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration(InteractiveWindowSetting.executeWithShiftEnter)) { + if (e.affectsConfiguration(ReplEditorSettings.executeWithShiftEnter)) { this.setHint(); } })); @@ -121,13 +121,13 @@ export class ReplInputHintContentWidget extends Disposable implements IContentWi ? localize('ReplInputAriaLabelHelp', "Use {0} for accessibility help. ", helpKeybinding) : localize('ReplInputAriaLabelHelpNoKb', "Run the Open Accessibility Help command for more information. "); - this.ariaLabel = helpInfo.concat(actionPart, localize('disableHint', ' Toggle {0} in settings to disable this hint.', AccessibilityVerbositySettingId.ReplInputHint)); + this.ariaLabel = actionPart.concat(helpInfo, localize('disableHint', ' Toggle {0} in settings to disable this hint.', AccessibilityVerbositySettingId.ReplEditor)); } } private getKeybinding() { const keybindings = this.keybindingService.lookupKeybindings('interactive.execute'); - const shiftEnterConfig = this.configurationService.getValue(InteractiveWindowSetting.executeWithShiftEnter); + const shiftEnterConfig = this.configurationService.getValue(ReplEditorSettings.executeWithShiftEnter); const hasEnterChord = (kb: ResolvedKeybinding, modifier: string = '') => { const chords = kb.getDispatchChords(); const chord = modifier + 'Enter'; diff --git a/src/vs/workbench/contrib/issue/browser/baseIssueReporterService.ts b/src/vs/workbench/contrib/issue/browser/baseIssueReporterService.ts index 0fbc0b71baa4f..4e0b80d97f8dd 100644 --- a/src/vs/workbench/contrib/issue/browser/baseIssueReporterService.ts +++ b/src/vs/workbench/contrib/issue/browser/baseIssueReporterService.ts @@ -2,7 +2,8 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { $, createStyleSheet, isHTMLInputElement, isHTMLTextAreaElement, reset, windowOpenNoOpener } from '../../../../base/browser/dom.js'; +import { $, isHTMLInputElement, isHTMLTextAreaElement, reset, windowOpenNoOpener } from '../../../../base/browser/dom.js'; +import { createStyleSheet } from '../../../../base/browser/domStylesheets.js'; import { Button, unthemedButtonStyles } from '../../../../base/browser/ui/button/button.js'; import { renderIcon } from '../../../../base/browser/ui/iconLabel/iconLabels.js'; import { mainWindow } from '../../../../base/browser/window.js'; @@ -18,7 +19,6 @@ import { escape } from '../../../../base/common/strings.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; import { URI } from '../../../../base/common/uri.js'; import { localize } from '../../../../nls.js'; -import { OldIssueReporterData } from '../../../../platform/issue/common/issue.js'; import { getIconsStyleSheet } from '../../../../platform/theme/browser/iconsStyleSheet.js'; import { IThemeService } from '../../../../platform/theme/common/themeService.js'; import { IssueReporterModel, IssueReporterData as IssueReporterModelData } from './issueReporterModel.js'; @@ -57,7 +57,7 @@ export class BaseIssueReporterService extends Disposable { constructor( public disableExtensions: boolean, - public data: IssueReporterData | OldIssueReporterData, + public data: IssueReporterData, public os: { type: string; arch: string; diff --git a/src/vs/workbench/contrib/issue/browser/issueFormService.ts b/src/vs/workbench/contrib/issue/browser/issueFormService.ts index f2e9cad950f68..c2449a6a10213 100644 --- a/src/vs/workbench/contrib/issue/browser/issueFormService.ts +++ b/src/vs/workbench/contrib/issue/browser/issueFormService.ts @@ -111,8 +111,8 @@ export class IssueFormService implements IIssueFormService { const actions = menu.getActions({ renderShortTitle: true }).flatMap(entry => entry[1]); for (const action of actions) { try { - if (action.item && 'source' in action.item && action.item.source?.id === extensionId) { - this.extensionIdentifierSet.add(extensionId); + if (action.item && 'source' in action.item && action.item.source?.id.toLowerCase() === extensionId.toLowerCase()) { + this.extensionIdentifierSet.add(extensionId.toLowerCase()); await action.run(); } } catch (error) { diff --git a/src/vs/workbench/contrib/issue/browser/issueReporterModel.ts b/src/vs/workbench/contrib/issue/browser/issueReporterModel.ts index 97d1199d1e6c7..0bbd8acf09aeb 100644 --- a/src/vs/workbench/contrib/issue/browser/issueReporterModel.ts +++ b/src/vs/workbench/contrib/issue/browser/issueReporterModel.ts @@ -5,11 +5,10 @@ import { mainWindow } from '../../../../base/browser/window.js'; import { isRemoteDiagnosticError, SystemInfo } from '../../../../platform/diagnostics/common/diagnostics.js'; -import { OldIssueType } from '../../../../platform/issue/common/issue.js'; import { ISettingSearchResult, IssueReporterExtensionData, IssueType } from '../common/issue.js'; export interface IssueReporterData { - issueType: IssueType | OldIssueType; + issueType: IssueType; issueDescription?: string; issueTitle?: string; extensionData?: string; diff --git a/src/vs/workbench/contrib/issue/browser/issueReporterPage.ts b/src/vs/workbench/contrib/issue/browser/issueReporterPage.ts index b54b661ff448d..5cbc70936ffb2 100644 --- a/src/vs/workbench/contrib/issue/browser/issueReporterPage.ts +++ b/src/vs/workbench/contrib/issue/browser/issueReporterPage.ts @@ -105,7 +105,7 @@ export default (): string => ` ${sendSystemInfoLabel} (${escape(localize('show', "show"))}) - @@ -115,7 +115,7 @@ export default (): string => ` ${sendProcessInfoLabel} (${escape(localize('show', "show"))}) -

]: 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 getLiteralTypeFromPropertyName(name: PropertyName | JsxAttributeName) { + if (isPrivateIdentifier(name)) { + return neverType; + } + if (isNumericLiteral(name)) { + return getRegularTypeOfLiteralType(checkExpression(name)); + } + if (isComputedPropertyName(name)) { + return getRegularTypeOfLiteralType(checkComputedPropertyName(name)); + } + const propertyName = getPropertyNameForPropertyNameNode(name); + if (propertyName !== undefined) { + return getStringLiteralType(unescapeLeadingUnderscores(propertyName)); + } + if (isExpression(name)) { + return getRegularTypeOfLiteralType(checkExpression(name)); + } + return neverType; + } + + 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 | JsxAttributeName; + type = prop.escapedName === InternalSymbolName.Default ? getStringLiteralType("default") : + name && getLiteralTypeFromPropertyName(name) || (!isKnownSymbol(prop) ? getStringLiteralType(symbolName(prop)) : undefined); + } + if (type && type.flags & include) { + return type; + } + } + return neverType; + } + + 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 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 shouldDeferIndexType(type: Type, indexFlags = IndexFlags.None) { + return !!(type.flags & TypeFlags.InstantiableNonPrimitive || + isGenericTupleType(type) || + isGenericMappedType(type) && (!hasDistributiveNameType(type) || getMappedTypeNameTypeKind(type) === MappedTypeNameTypeKind.Remapping) || + type.flags & TypeFlags.Union && !(indexFlags & IndexFlags.NoReducibleCheck) && isGenericReducibleType(type) || + type.flags & TypeFlags.Intersection && maybeTypeOfKind(type, TypeFlags.Instantiable) && some((type as IntersectionType).types, isEmptyAnonymousObjectType)); + } + + function getIndexType(type: Type, indexFlags = IndexFlags.None): Type { + type = getReducedType(type); + return isNoInferType(type) ? getNoInferType(getIndexType((type as SubstitutionType).baseType, indexFlags)) : + shouldDeferIndexType(type, indexFlags) ? getIndexTypeForGenericType(type as InstantiableType | UnionOrIntersectionType, indexFlags) : + type.flags & TypeFlags.Union ? getIntersectionType(map((type as UnionType).types, t => getIndexType(t, indexFlags))) : + type.flags & TypeFlags.Intersection ? getUnionType(map((type as IntersectionType).types, t => getIndexType(t, indexFlags))) : + getObjectFlags(type) & ObjectFlags.Mapped ? getIndexTypeForMappedType(type as MappedType, indexFlags) : + type === wildcardType ? wildcardType : + type.flags & TypeFlags.Unknown ? neverType : + type.flags & (TypeFlags.Any | TypeFlags.Never) ? stringNumberSymbolType : + getLiteralTypeFromProperties(type, (indexFlags & IndexFlags.NoIndexSignatures ? TypeFlags.StringLiteral : TypeFlags.StringLike) | (indexFlags & IndexFlags.StringsOnly ? 0 : TypeFlags.NumberLike | TypeFlags.ESSymbolLike), indexFlags === IndexFlags.None); + } + + function getExtractStringType(type: Type) { + const extractTypeAlias = getGlobalExtractSymbol(); + return extractTypeAlias ? getTypeAliasInstantiation(extractTypeAlias, [type, stringType]) : stringType; + } + + function getIndexTypeOrString(type: Type): Type { + const indexType = getExtractStringType(getIndexType(type)); + return indexType.flags & TypeFlags.Never ? stringType : indexType; + } + + 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: + Debug.assertNever(node.operator); + } + } + 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 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); + if (!type) { + templateLiteralTypes.set(id, type = createTemplateLiteralType(newTexts, newTypes)); + } + return type; + + function addSpans(texts: readonly string[], types: readonly Type[]): boolean { + for (let i = 0; i < types.length; i++) { + const t = types[i]; + if (t.flags & (TypeFlags.Literal | TypeFlags.Null | TypeFlags.Undefined)) { + text += getTemplateStringForType(t) || ""; + text += texts[i + 1]; + } + 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 += texts[i + 1]; + } + else if (isGenericIndexType(t) || isPatternLiteralPlaceholderType(t)) { + newTypes.push(t); + newTexts.push(text); + text = texts[i + 1]; + } + else { + return false; + } + } + return true; + } + } + + 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 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]; + } + + 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 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 getPropertyNameFromIndex(indexType: Type, accessNode: PropertyName | ObjectBindingPattern | ArrayBindingPattern | 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 every(symbol.declarations, d => !isFunctionLike(d) || isDeprecatedDeclaration(d)); + } + 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); + + 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); + } + 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; + } + } + const propType = accessFlags & AccessFlags.Writing ? getWriteTypeOfSymbol(prop) : getTypeOfSymbol(prop); + return accessExpression && getAssignmentTargetKind(accessExpression) !== AssignmentKind.Definite ? getFlowTypeOfReference(accessExpression, propType) : + accessNode && isIndexedAccessTypeNode(accessNode) && containsMissingType(propType) ? getUnionType([propType, undefinedType]) : + propType; + } + if (everyType(objectType, isTupleType) && isNumericLiteralName(propName)) { + const index = +propName; + if (accessNode && everyType(objectType, t => !((t as TupleTypeReference).target.combinedFlags & ElementFlags.Variable)) && !(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)); + } + } + if (index >= 0) { + errorIfWritingToReadonlyIndex(getIndexInfoOfType(objectType, numberType)); + return getTupleElementTypeOutOfStartCount(objectType, index, accessFlags & AccessFlags.IncludeUndefined ? missingType : 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) { + if (accessFlags & AccessFlags.Writing) { + error(accessExpression, Diagnostics.Type_0_is_generic_and_can_only_be_indexed_for_reading, typeToString(originalObjectType)); + } + else { + error(accessExpression, Diagnostics.Type_0_cannot_be_used_to_index_type_1, typeToString(indexType), typeToString(originalObjectType)); + } + } + return undefined; + } + 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, missingType]) : 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, missingType]); + } + 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)); + } + else if (noImplicitAny && !(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 { + 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 { + 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 { + 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(getSourceFileOfNode(accessExpression), accessExpression, errorInfo)); + } + } + } + } + return undefined; + } + } + 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)); + } + 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)); + } + } + 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 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 { + if (type.flags & TypeFlags.Intersection) { + // Return true if the intersection consists of one or more placeholders and zero or + // more object type tags. + let seenPlaceholder = false; + for (const t of (type as IntersectionType).types) { + if (t.flags & (TypeFlags.Literal | TypeFlags.Nullable) || isPatternLiteralPlaceholderType(t)) { + seenPlaceholder = true; + } + else if (!(t.flags & TypeFlags.Object)) { + return false; + } + } + return seenPlaceholder; + } + return !!(type.flags & (TypeFlags.Any | TypeFlags.String | TypeFlags.Number | TypeFlags.BigInt)) || isPatternLiteralType(type); + } + + function isPatternLiteralType(type: Type) { + // A pattern literal type is a template literal or a string mapping type that contains only + // non-generic pattern literal placeholders. + return !!(type.flags & TypeFlags.TemplateLiteral) && every((type as TemplateLiteralType).types, isPatternLiteralPlaceholderType) || + !!(type.flags & TypeFlags.StringMapping) && isPatternLiteralPlaceholderType((type as StringMappingType).type); + } + + function isGenericStringLikeType(type: Type) { + return !!(type.flags & (TypeFlags.TemplateLiteral | TypeFlags.StringMapping)) && !isPatternLiteralType(type); + } + + function isGenericType(type: Type): boolean { + return !!getGenericObjectFlags(type); + } + + function isGenericObjectType(type: Type): boolean { + return !!(getGenericObjectFlags(type) & ObjectFlags.IsGenericObjectType); + } + + 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; + } + 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.flags & TypeFlags.InstantiableNonPrimitive || isGenericMappedType(type) || isGenericTupleType(type) ? ObjectFlags.IsGenericObjectType : 0) | + (type.flags & (TypeFlags.InstantiableNonPrimitive | TypeFlags.Index) || isGenericStringLikeType(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 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); + } + } + + // 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; + } + } + // 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] + + // 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)) { + if (getMappedTypeNameTypeKind(objectType) !== MappedTypeNameTypeKind.Remapping) { + return type[cache] = mapType(substituteIndexedMappedType(objectType, type.indexType), t => getSimplifiedType(t, writing)); + } + } + return type[cache] = type; + } + + function getSimplifiedConditionalType(type: ConditionalType, writing: boolean) { + const checkType = type.checkType; + const extendsType = type.extendsType; + const trueType = getTrueTypeFromConditionalType(type); + const falseType = getFalseTypeFromConditionalType(type); + // Simplifications for types of the form `T extends U ? T : never` and `T extends U ? never : T`. + if (falseType.flags & TypeFlags.Never && getActualTypeVariable(trueType) === getActualTypeVariable(checkType)) { + if (checkType.flags & TypeFlags.Any || isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(extendsType))) { // Always true + return getSimplifiedType(trueType, writing); + } + else if (isIntersectionEmpty(checkType, extendsType)) { // Always false + return neverType; + } + } + else if (trueType.flags & TypeFlags.Never && getActualTypeVariable(falseType) === getActualTypeVariable(checkType)) { + if (!(checkType.flags & TypeFlags.Any) && isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(extendsType))) { // Always true + return neverType; + } + else if (checkType.flags & TypeFlags.Any || isIntersectionEmpty(checkType, extendsType)) { // Always false + return getSimplifiedType(falseType, writing); + } + } + return type; + } + + /** + * Invokes union simplification logic to determine if an intersection is considered empty as a union constituent + */ + function isIntersectionEmpty(type1: Type, type2: Type) { + return !!(getUnionType([intersectTypes(type1, type2), neverType]).flags & TypeFlags.Never); + } + + // Given an indexed access on a mapped type of the form { [P in K]: E }[X], return an instantiation of E where P is + // replaced with X. Since this simplification doesn't account for mapped type modifiers, add 'undefined' to the + // resulting type if the mapped type includes a '?' modifier or if the modifiers type indicates that some properties + // are optional. If the modifiers type is generic, conservatively estimate optionality by recursively looking for + // mapped types that include '?' modifiers. + function substituteIndexedMappedType(objectType: MappedType, index: Type) { + const mapper = createTypeMapper([getTypeParameterFromMappedType(objectType)], [index]); + const templateMapper = combineTypeMappers(objectType.mapper, mapper); + const instantiatedTemplateType = instantiateType(getTemplateTypeFromMappedType(objectType.target as MappedType || objectType), templateMapper); + const isOptional = getMappedTypeOptionality(objectType) > 0 || (isGenericType(objectType) ? + getCombinedMappedTypeOptionality(getModifiersTypeFromMappedType(objectType)) > 0 : + couldAccessOptionalProperty(objectType, index)); + return addOptionality(instantiatedTemplateType, /*isProperty*/ true, isOptional); + } + + // Return true if an indexed access with the given object and index types could access an optional property. + function couldAccessOptionalProperty(objectType: Type, indexType: Type) { + const indexConstraint = getBaseConstraintOfType(indexType); + return !!indexConstraint && some(getPropertiesOfType(objectType), p => + !!(p.flags & SymbolFlags.Optional) && + isTypeAssignableTo(getLiteralTypeFromProperty(p, TypeFlags.StringOrNumberLiteralOrUnique), indexConstraint)); + } + + 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); + } + + 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 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; + } + objectType = getReducedType(objectType); + // 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, getTotalFixedElementCount(objectType.target)) : + isGenericObjectType(objectType) && !(isTupleType(objectType) && indexTypeLessThan(indexType, getTotalFixedElementCount(objectType.target))) || isGenericReducibleType(objectType)) + ) { + 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)); + } + + 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; + } + return accessFlags & AccessFlags.Writing + ? getIntersectionType(propTypes, IntersectionFlags.None, aliasSymbol, aliasTypeArguments) + : getUnionType(propTypes, UnionReduction.Literal, aliasSymbol, aliasTypeArguments); + } + return getPropertyTypeForIndexType(objectType, apparentObjectType, indexType, indexType, accessNode, accessFlags | AccessFlags.CacheSymbol | AccessFlags.ReportDeprecated); + } + + 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 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 getActualTypeVariable(type: Type): Type { + if (type.flags & TypeFlags.Substitution) { + return getActualTypeVariable((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 isSimpleTupleType(node: TypeNode): boolean { + return isTupleTypeNode(node) && length(node.elements) > 0 && + !some(node.elements, e => isOptionalTypeNode(e) || isRestTypeNode(e) || isNamedTupleMember(e) && !!(e.questionToken || e.dotDotDotToken)); + } + + function isDeferredType(type: Type, checkTuples: boolean) { + return isGenericType(type) || checkTuples && isTupleType(type) && some(getElementTypes(type), isGenericType); + } + + function getConditionalType(root: ConditionalRoot, mapper: TypeMapper | undefined, forConstraint: boolean, 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); + return errorType; + } + const checkType = instantiateType(getActualTypeVariable(root.checkType), mapper); + const extendsType = instantiateType(root.extendsType, mapper); + if (checkType === errorType || extendsType === errorType) { + return errorType; + } + if (checkType === wildcardType || extendsType === wildcardType) { + return wildcardType; + } + const checkTypeNode = skipTypeParentheses(root.node.checkType); + const extendsTypeNode = skipTypeParentheses(root.node.extendsType); + // When the check and extends types are simple tuple types of the same arity, we defer resolution of the + // conditional type when any tuple elements are generic. This is such that non-distributable conditional + // types can be written `[X] extends [Y] ? ...` and be deferred similarly to `X extends Y ? ...`. + const checkTuples = isSimpleTupleType(checkTypeNode) && isSimpleTupleType(extendsTypeNode) && + length((checkTypeNode as TupleTypeNode).elements) === length((extendsTypeNode as TupleTypeNode).elements); + const checkTypeDeferred = isDeferredType(checkType, checkTuples); + 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 context - 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: + // * combine `context.nonFixingMapper` with `mapper` so their constraints can be instantiated in the context of `mapper` (otherwise they'd only get inference context information) + // * incorporate all of the component mappers into the combined mapper for the true and false members + // This means we have two mappers that need applying: + // * The original `mapper` used to create this conditional + // * The mapper that maps the infer type parameter to its inference result (`context.mapper`) + const context = createInferenceContext(root.inferTypeParameters, /*signature*/ undefined, InferenceFlags.None); + if (mapper) { + context.nonFixingMapper = combineTypeMappers(context.nonFixingMapper, mapper); + } + if (!checkTypeDeferred) { + // 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, extendsType, InferencePriority.NoConstraints | InferencePriority.AlwaysStrict); + } + // 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(context.mapper, mapper) : context.mapper; + } + // Instantiate the extends type including inferences for 'infer T' type parameters + const inferredExtendsType = combinedMapper ? instantiateType(root.extendsType, combinedMapper) : extendsType; + // We attempt to resolve the conditional type only when the check and extends types are non-generic + if (!checkTypeDeferred && !isDeferredType(inferredExtendsType, checkTuples)) { + // 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 || !isTypeAssignableTo(getPermissiveInstantiation(checkType), getPermissiveInstantiation(inferredExtendsType)))) { + // Return union of trueType and falseType for 'any' since it matches anything. Furthermore, for a + // distributive conditional type applied to the constraint of a type variable, include trueType if + // there are possible values of the check type that are also possible values of the extends type. + // We use a reverse assignability check as it is less expensive than the comparable relationship + // and avoids false positives of a non-empty intersection check. + if (checkType.flags & TypeFlags.Any || forConstraint && !(inferredExtendsType.flags & TypeFlags.Never) && someType(getPermissiveInstantiation(inferredExtendsType), t => isTypeAssignableTo(t, getPermissiveInstantiation(checkType)))) { + (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; + } + } + // 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; + } + } + + function getTrueTypeFromConditionalType(type: ConditionalType) { + return type.resolvedTrueType || (type.resolvedTrueType = instantiateType(getTypeFromTypeNode(type.root.node.trueType), type.mapper)); + } + + function getFalseTypeFromConditionalType(type: ConditionalType) { + return type.resolvedFalseType || (type.resolvedFalseType = instantiateType(getTypeFromTypeNode(type.root.node.falseType), type.mapper)); + } + + function getInferredTrueTypeFromConditionalType(type: ConditionalType) { + return type.resolvedInferredTrueType || (type.resolvedInferredTrueType = type.combinedMapper ? instantiateType(getTypeFromTypeNode(type.root.node.trueType), type.combinedMapper) : getTrueTypeFromConditionalType(type)); + } + + 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 isDistributionDependent(root: ConditionalRoot) { + return root.isDistributive && ( + isTypeParameterPossiblyReferenced(root.checkType as TypeParameter, root.node.trueType) || + isTypeParameterPossiblyReferenced(root.checkType as TypeParameter, root.node.falseType) + ); + } + + 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, /*forConstraint*/ false); + if (outerTypeParameters) { + root.instantiations = new Map(); + root.instantiations.set(getTypeListId(outerTypeParameters), links.resolvedType); + } + } + return links.resolvedType; + } + + function getTypeFromInferTypeNode(node: InferTypeNode): Type { + const links = getNodeLinks(node); + if (!links.resolvedType) { + links.resolvedType = getDeclaredTypeOfTypeParameter(getSymbolOfDeclaration(node.typeParameter)); + } + return links.resolvedType; + } + + function getIdentifierChain(node: EntityName): Identifier[] { + if (isIdentifier(node)) { + return [node]; + } + else { + return append(getIdentifierChain(node.left), node.right); + } + } + + function getTypeFromImportTypeNode(node: ImportTypeNode): Type { + const links = getNodeLinks(node); + if (!links.resolvedType) { + 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); + } + 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 links.resolvedType; + } + + function resolveImportSymbolType(node: ImportTypeNode, links: NodeLinks, symbol: Symbol, meaning: SymbolFlags) { + const resolvedSymbol = resolveSymbol(symbol); + links.resolvedSymbol = resolvedSymbol; + if (meaning === SymbolFlags.Value) { + return getInstantiationExpressionType(getTypeOfSymbol(symbol), node); // 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: TypeLiteralNode | FunctionOrConstructorTypeNode | JSDocTypeLiteral | JSDocFunctionType | JSDocSignature): 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; + } + } + return links.resolvedType; + } + + 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) ? getSymbolOfDeclaration(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 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 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.links.type = isSetonlyAccessor ? undefinedType : addOptionality(getTypeOfSymbol(prop), /*isProperty*/ true); + result.declarations = prop.declarations; + result.links.nameType = getSymbolLinks(prop).nameType; + result.links.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; + } + } + + /** + * 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]); + } + + const members = createSymbolTable(); + const skippedPrivateMembers = new Set<__String>(); + const indexInfos = left === emptyObjectType ? getIndexInfosOfType(right) : getUnionIndexInfos([left, 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)); + } + } + + 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); + // Optimization: avoid calculating the union type if spreading into the exact same type. + // This is common, e.g. spreading one options bag into another where the bags have the + // same type, or have properties which overlap. If the unions are large, it may turn out + // to be expensive to perform subtype reduction. + const leftType = getTypeOfSymbol(leftProp); + const leftTypeWithoutUndefined = removeMissingOrUndefinedType(leftType); + const rightTypeWithoutUndefined = removeMissingOrUndefinedType(rightType); + result.links.type = leftTypeWithoutUndefined === rightTypeWithoutUndefined ? leftType : getUnionType([leftType, rightTypeWithoutUndefined], UnionReduction.Subtype); + result.links.leftSpread = leftProp; + result.links.rightSpread = rightProp; + result.declarations = declarations; + result.links.nameType = getSymbolLinks(leftProp).nameType; + members.set(leftProp.escapedName, result); + } + } + else { + members.set(leftProp.escapedName, getSpreadSymbol(leftProp, readonly)); + } + } + + const spread = createAnonymousType(symbol, members, emptyArray, emptyArray, sameMap(indexInfos, info => getIndexInfoWithReadonly(info, readonly))); + spread.objectFlags |= ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral | ObjectFlags.ContainsSpread | objectFlags; + return spread; + } + + /** 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 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.links.type = isSetonlyAccessor ? undefinedType : getTypeOfSymbol(prop); + result.declarations = prop.declarations; + result.links.nameType = getSymbolLinks(prop).nameType; + result.links.syntheticOrigin = prop; + return result; + } + + function getIndexInfoWithReadonly(info: IndexInfo, readonly: boolean) { + return info.isReadonly !== readonly ? createIndexInfo(info.keyType, info.type, readonly, info.declaration) : info; + } + + 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 getFreshTypeOfLiteralType(type: Type): Type { + if (type.flags & TypeFlags.Freshable) { + if (!(type as FreshableType).freshType) { + const freshType = createLiteralType(type.flags, (type as LiteralType).value, (type as LiteralType).symbol, type as LiteralType); + freshType.freshType = freshType; + (type as FreshableType).freshType = freshType; + } + return (type as FreshableType).freshType; + } + return type; + } + + function getRegularTypeOfLiteralType(type: Type): Type { + return type.flags & TypeFlags.Freshable ? (type as FreshableType).regularType : + type.flags & TypeFlags.Union ? ((type as UnionType).regularType || ((type as UnionType).regularType = mapType(type, getRegularTypeOfLiteralType) as UnionType)) : + type; + } + + function isFreshLiteralType(type: Type) { + return !!(type.flags & TypeFlags.Freshable) && (type as LiteralType).freshType === type; + } + + function getStringLiteralType(value: string): StringLiteralType { + let type; + return stringLiteralTypes.get(value) || + (stringLiteralTypes.set(value, type = createLiteralType(TypeFlags.StringLiteral, value) as StringLiteralType), type); + } + + function getNumberLiteralType(value: number): NumberLiteralType { + let type; + return numberLiteralTypes.get(value) || + (numberLiteralTypes.set(value, type = createLiteralType(TypeFlags.NumberLiteral, value) as NumberLiteralType), type); + } + + 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 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 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; + } + + 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 getESSymbolLikeTypeForNode(node: Node) { + if (isInJSFile(node) && isJSDocTypeExpression(node)) { + const host = getJSDocHost(node); + if (host) { + node = getSingleVariableOfVariableStatement(host) || host; + } + } + 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 getThisType(node: Node): Type { + const container = getThisContainer(node, /*includeArrowFunctions*/ false, /*includeClassComputedPropertyName*/ 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(getSymbolOfDeclaration(parent as ClassLikeDeclaration | InterfaceDeclaration)).thisType!; + } + } + + // inside x.prototype = { ... } + if (parent && isObjectLiteralExpression(parent) && isBinaryExpression(parent.parent) && getAssignmentDeclarationKind(parent.parent) === AssignmentDeclarationKind.Prototype) { + return getDeclaredTypeOfClassOrInterface(getSymbolOfNode(parent.parent.left)!.parent!).thisType!; + } + // /** @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!; + } + // inside constructor function C() { ... } + if (isJSConstructor(container) && isNodeDescendantOf(node, container.body)) { + return getDeclaredTypeOfClassOrInterface(getSymbolOfDeclaration(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); + } + return links.resolvedType; + } + + function getTypeFromRestTypeNode(node: RestTypeNode | NamedTupleMember) { + return getTypeFromTypeNode(getArrayElementTypeNode(node.type) || node.type); + } + + 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 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 getTypeFromTypeNode(node: TypeNode): Type { + return getConditionalFlowTypeOfType(getTypeFromTypeNodeWorker(node), node); + } + + 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 as TypeLiteralNode | FunctionOrConstructorTypeNode | JSDocTypeLiteral | JSDocFunctionType | JSDocSignature); + 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 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 items; + } + + 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 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 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 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 the type of the symbol is already resolved, and if that type could not possibly + // be affected by instantiation, simply return the symbol itself. + if (links.type && !couldContainTypeVariables(links.type)) { + if (!(symbol.flags & SymbolFlags.SetAccessor)) { + return symbol; + } + // If we're a setter, check writeType. + if (links.writeType && !couldContainTypeVariables(links.writeType)) { + 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.links.target = symbol; + result.links.mapper = mapper; + if (symbol.valueDeclaration) { + result.valueDeclaration = symbol.valueDeclaration; + } + if (links.nameType) { + result.links.nameType = links.nameType; + } + return result; + } + + 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 = type.objectFlags & ObjectFlags.SingleSignatureType ? (type as SingleSignatureType).outerTypeParameters : 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 = (type.objectFlags & ObjectFlags.SingleSignatureType ? "S" : "") + 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) { + if (type.objectFlags & ObjectFlags.SingleSignatureType) { + result = instantiateAnonymousType(type, mapper); + target.instantiations.set(id, result); + return 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); // Set cached result early in case we recursively invoke instantiation while eagerly computing type variable visibility below + const resultObjectFlags = getObjectFlags(result); + if (result.flags & TypeFlags.ObjectFlagsType && !(resultObjectFlags & ObjectFlags.CouldContainTypeVariablesComputed)) { + const resultCouldContainTypeVariables = some(typeArguments, couldContainTypeVariables); // one of the input type arguments might be or contain the result + if (!(getObjectFlags(result) & ObjectFlags.CouldContainTypeVariablesComputed)) { + // if `result` is one of the object types we tried to make (it may not be, due to how `instantiateMappedType` works), we can carry forward the type variable containment check from the input type arguments + if (resultObjectFlags & (ObjectFlags.Mapped | ObjectFlags.Anonymous | ObjectFlags.Reference)) { + (result as ObjectFlagsType).objectFlags |= ObjectFlags.CouldContainTypeVariablesComputed | (resultCouldContainTypeVariables ? ObjectFlags.CouldContainTypeVariables : 0); + } + // If none of the type arguments for the outer type parameters contain type variables, it follows + // that the instantiated type doesn't reference type variables. + // Intrinsics have `CouldContainTypeVariablesComputed` pre-set, so this should only cover unions and intersections resulting from `instantiateMappedType` + else { + (result as ObjectFlagsType).objectFlags |= !resultCouldContainTypeVariables ? ObjectFlags.CouldContainTypeVariablesComputed : 0; + } + } + } + } + return result; + } + return type; + } + + 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; + } + } + 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); + if (!isThisIdentifier(firstIdentifier)) { // Don't attempt to analyze typeof this.xxx + const firstIdentifierSymbol = getResolvedSymbol(firstIdentifier); + const tpDeclaration = tp.symbol.declarations![0]; // There is exactly one declaration, otherwise `containsReference` is not called + const tpScope = tpDeclaration.kind === SyntaxKind.TypeParameter ? tpDeclaration.parent : // Type parameter is a regular type parameter, e.g. foo + tp.isThisType ? tpDeclaration : // Type parameter is the this type, and its declaration is the class declaration. + undefined; // Type parameter's declaration was unrecognized, e.g. comes from JSDoc annotation. + if (firstIdentifierSymbol.declarations && tpScope) { + 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 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 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. + // * If T is an intersection of array or tuple types we map to an intersection of transformed array or tuple types. + // * 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), instantiateConstituent, aliasSymbol, aliasTypeArguments); + } + } + // 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 instantiateConstituent(t: Type): Type { + 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 (isTupleType(t)) { + return instantiateMappedTupleType(t, type, typeVariable!, mapper); + } + if (isArrayOrTupleOrIntersection(t)) { + return getIntersectionType(map((t as IntersectionType).types, instantiateConstituent)); + } + } + return instantiateAnonymousType(type, prependTypeMapping(typeVariable!, t, mapper)); + } + return t; + } + } + + function getModifiedReadonlyState(state: boolean, modifiers: MappedTypeModifiers) { + return modifiers & MappedTypeModifiers.IncludeReadonly ? true : modifiers & MappedTypeModifiers.ExcludeReadonly ? false : state; + } + + function instantiateMappedTupleType(tupleType: TupleTypeReference, mappedType: MappedType, typeVariable: TypeVariable, mapper: TypeMapper) { + // We apply the mapped type's template type to each of the fixed part elements. For variadic elements, we + // apply the mapped type itself to the variadic element type. For other elements in the variable part of the + // tuple, we surround the element type with an array type and apply the mapped type to that. This ensures + // that we get sequential property key types for the fixed part of the tuple, and property key type number + // for the remaining elements. For example + // + // type Keys = { [K in keyof T]: K }; + // type Foo = Keys<[string, string, ...T, string]>; // ["0", "1", ...Keys, number] + // + const elementFlags = tupleType.target.elementFlags; + const fixedLength = tupleType.target.fixedLength; + const fixedMapper = fixedLength ? prependTypeMapping(typeVariable, tupleType, mapper) : mapper; + const newElementTypes = map(getElementTypes(tupleType), (type, i) => { + const flags = elementFlags[i]; + return i < fixedLength ? instantiateMappedTypeTemplate(mappedType, getStringLiteralType("" + i), !!(flags & ElementFlags.Optional), fixedMapper) : + flags & ElementFlags.Variadic ? instantiateType(mappedType, prependTypeMapping(typeVariable, type, mapper)) : + getElementTypeOfArrayType(instantiateType(mappedType, prependTypeMapping(typeVariable, createArrayType(type), mapper))) ?? unknownType; + }); + const modifiers = getMappedTypeModifiers(mappedType); + const newElementFlags = 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, getMappedTypeModifiers(mappedType)); + return contains(newElementTypes, errorType) ? errorType : + createTupleType(newElementTypes, newElementFlags, newReadonly, tupleType.target.labeledElementDeclarations); + } + + 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 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 { + Debug.assert(type.symbol, "anonymous type must have symbol to be instantiated"); + const result = createObjectType(type.objectFlags & ~(ObjectFlags.CouldContainTypeVariablesComputed | ObjectFlags.CouldContainTypeVariables) | 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; + } + if (type.objectFlags & ObjectFlags.SingleSignatureType) { + (result as SingleSignatureType).outerTypeParameters = (type as SingleSignatureType).outerTypeParameters; + } + 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, forConstraint: boolean, 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 = (forConstraint ? "C" : "") + 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 ? getReducedType(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(distributionType, t => getConditionalType(root, prependTypeMapping(checkType, t, newMapper), forConstraint), aliasSymbol, aliasTypeArguments) : + getConditionalType(root, newMapper, forConstraint, aliasSymbol, aliasTypeArguments); + root.instantiations!.set(id, result); + } + return result; + } + 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; + } + + 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); + } + 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, IntersectionFlags.None, 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), /*forConstraint*/ false, aliasSymbol, aliasTypeArguments); + } + if (flags & TypeFlags.Substitution) { + const newBaseType = instantiateType((type as SubstitutionType).baseType, mapper); + if (isNoInferType(type)) { + return getNoInferType(newBaseType); + } + 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 (newConstraint.flags & TypeFlags.AnyOrUnknown || isTypeAssignableTo(getRestrictiveInstantiation(newBaseType), getRestrictiveInstantiation(newConstraint))) { + return newBaseType; + } + return newBaseType.flags & TypeFlags.TypeVariable ? getSubstitutionType(newBaseType, newConstraint) : getIntersectionType([newConstraint, newBaseType]); + } + 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)) { + 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 + } + + function getPermissiveInstantiation(type: Type) { + return type.flags & (TypeFlags.Primitive | TypeFlags.AnyOrUnknown | TypeFlags.Never) ? type : + type.permissiveInstantiation || (type.permissiveInstantiation = instantiateType(type, permissiveMapper)); + } + + function getRestrictiveInstantiation(type: Type) { + if (type.flags & (TypeFlags.Primitive | TypeFlags.AnyOrUnknown | TypeFlags.Never)) { + return type; + } + 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 instantiateIndexInfo(info: IndexInfo, mapper: TypeMapper) { + return createIndexInfo(info.keyType, instantiateType(info.type, mapper), info.isReadonly, info.declaration); + } + + // 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

]: 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 getLiteralTypeFromPropertyName(name: PropertyName | JsxAttributeName) { + if (isPrivateIdentifier(name)) { + return neverType; + } + if (isNumericLiteral(name)) { + return getRegularTypeOfLiteralType(checkExpression(name)); + } + if (isComputedPropertyName(name)) { + return getRegularTypeOfLiteralType(checkComputedPropertyName(name)); + } + const propertyName = getPropertyNameForPropertyNameNode(name); + if (propertyName !== undefined) { + return getStringLiteralType(unescapeLeadingUnderscores(propertyName)); + } + if (isExpression(name)) { + return getRegularTypeOfLiteralType(checkExpression(name)); + } + return neverType; + } + + 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 | JsxAttributeName; + type = prop.escapedName === InternalSymbolName.Default ? getStringLiteralType("default") : + name && getLiteralTypeFromPropertyName(name) || (!isKnownSymbol(prop) ? getStringLiteralType(symbolName(prop)) : undefined); + } + if (type && type.flags & include) { + return type; + } + } + return neverType; + } + + 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 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 shouldDeferIndexType(type: Type, indexFlags = IndexFlags.None) { + return !!(type.flags & TypeFlags.InstantiableNonPrimitive || + isGenericTupleType(type) || + isGenericMappedType(type) && (!hasDistributiveNameType(type) || getMappedTypeNameTypeKind(type) === MappedTypeNameTypeKind.Remapping) || + type.flags & TypeFlags.Union && !(indexFlags & IndexFlags.NoReducibleCheck) && isGenericReducibleType(type) || + type.flags & TypeFlags.Intersection && maybeTypeOfKind(type, TypeFlags.Instantiable) && some((type as IntersectionType).types, isEmptyAnonymousObjectType)); + } + + function getIndexType(type: Type, indexFlags = IndexFlags.None): Type { + type = getReducedType(type); + return isNoInferType(type) ? getNoInferType(getIndexType((type as SubstitutionType).baseType, indexFlags)) : + shouldDeferIndexType(type, indexFlags) ? getIndexTypeForGenericType(type as InstantiableType | UnionOrIntersectionType, indexFlags) : + type.flags & TypeFlags.Union ? getIntersectionType(map((type as UnionType).types, t => getIndexType(t, indexFlags))) : + type.flags & TypeFlags.Intersection ? getUnionType(map((type as IntersectionType).types, t => getIndexType(t, indexFlags))) : + getObjectFlags(type) & ObjectFlags.Mapped ? getIndexTypeForMappedType(type as MappedType, indexFlags) : + type === wildcardType ? wildcardType : + type.flags & TypeFlags.Unknown ? neverType : + type.flags & (TypeFlags.Any | TypeFlags.Never) ? stringNumberSymbolType : + getLiteralTypeFromProperties(type, (indexFlags & IndexFlags.NoIndexSignatures ? TypeFlags.StringLiteral : TypeFlags.StringLike) | (indexFlags & IndexFlags.StringsOnly ? 0 : TypeFlags.NumberLike | TypeFlags.ESSymbolLike), indexFlags === IndexFlags.None); + } + + function getExtractStringType(type: Type) { + const extractTypeAlias = getGlobalExtractSymbol(); + return extractTypeAlias ? getTypeAliasInstantiation(extractTypeAlias, [type, stringType]) : stringType; + } + + function getIndexTypeOrString(type: Type): Type { + const indexType = getExtractStringType(getIndexType(type)); + return indexType.flags & TypeFlags.Never ? stringType : indexType; + } + + 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: + Debug.assertNever(node.operator); + } + } + 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 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); + if (!type) { + templateLiteralTypes.set(id, type = createTemplateLiteralType(newTexts, newTypes)); + } + return type; + + function addSpans(texts: readonly string[], types: readonly Type[]): boolean { + for (let i = 0; i < types.length; i++) { + const t = types[i]; + if (t.flags & (TypeFlags.Literal | TypeFlags.Null | TypeFlags.Undefined)) { + text += getTemplateStringForType(t) || ""; + text += texts[i + 1]; + } + 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 += texts[i + 1]; + } + else if (isGenericIndexType(t) || isPatternLiteralPlaceholderType(t)) { + newTypes.push(t); + newTexts.push(text); + text = texts[i + 1]; + } + else { + return false; + } + } + return true; + } + } + + 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 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]; + } + + 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 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 getPropertyNameFromIndex(indexType: Type, accessNode: PropertyName | ObjectBindingPattern | ArrayBindingPattern | 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 every(symbol.declarations, d => !isFunctionLike(d) || isDeprecatedDeclaration(d)); + } + 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); + + 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); + } + 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; + } + } + const propType = accessFlags & AccessFlags.Writing ? getWriteTypeOfSymbol(prop) : getTypeOfSymbol(prop); + return accessExpression && getAssignmentTargetKind(accessExpression) !== AssignmentKind.Definite ? getFlowTypeOfReference(accessExpression, propType) : + accessNode && isIndexedAccessTypeNode(accessNode) && containsMissingType(propType) ? getUnionType([propType, undefinedType]) : + propType; + } + if (everyType(objectType, isTupleType) && isNumericLiteralName(propName)) { + const index = +propName; + if (accessNode && everyType(objectType, t => !((t as TupleTypeReference).target.combinedFlags & ElementFlags.Variable)) && !(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)); + } + } + if (index >= 0) { + errorIfWritingToReadonlyIndex(getIndexInfoOfType(objectType, numberType)); + return getTupleElementTypeOutOfStartCount(objectType, index, accessFlags & AccessFlags.IncludeUndefined ? missingType : 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) { + if (accessFlags & AccessFlags.Writing) { + error(accessExpression, Diagnostics.Type_0_is_generic_and_can_only_be_indexed_for_reading, typeToString(originalObjectType)); + } + else { + error(accessExpression, Diagnostics.Type_0_cannot_be_used_to_index_type_1, typeToString(indexType), typeToString(originalObjectType)); + } + } + return undefined; + } + 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, missingType]) : 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, missingType]); + } + 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)); + } + else if (noImplicitAny && !(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 { + 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 { + 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 { + 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(getSourceFileOfNode(accessExpression), accessExpression, errorInfo)); + } + } + } + } + return undefined; + } + } + 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)); + } + 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)); + } + } + 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 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 { + if (type.flags & TypeFlags.Intersection) { + // Return true if the intersection consists of one or more placeholders and zero or + // more object type tags. + let seenPlaceholder = false; + for (const t of (type as IntersectionType).types) { + if (t.flags & (TypeFlags.Literal | TypeFlags.Nullable) || isPatternLiteralPlaceholderType(t)) { + seenPlaceholder = true; + } + else if (!(t.flags & TypeFlags.Object)) { + return false; + } + } + return seenPlaceholder; + } + return !!(type.flags & (TypeFlags.Any | TypeFlags.String | TypeFlags.Number | TypeFlags.BigInt)) || isPatternLiteralType(type); + } + + function isPatternLiteralType(type: Type) { + // A pattern literal type is a template literal or a string mapping type that contains only + // non-generic pattern literal placeholders. + return !!(type.flags & TypeFlags.TemplateLiteral) && every((type as TemplateLiteralType).types, isPatternLiteralPlaceholderType) || + !!(type.flags & TypeFlags.StringMapping) && isPatternLiteralPlaceholderType((type as StringMappingType).type); + } + + function isGenericStringLikeType(type: Type) { + return !!(type.flags & (TypeFlags.TemplateLiteral | TypeFlags.StringMapping)) && !isPatternLiteralType(type); + } + + function isGenericType(type: Type): boolean { + return !!getGenericObjectFlags(type); + } + + function isGenericObjectType(type: Type): boolean { + return !!(getGenericObjectFlags(type) & ObjectFlags.IsGenericObjectType); + } + + 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; + } + 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.flags & TypeFlags.InstantiableNonPrimitive || isGenericMappedType(type) || isGenericTupleType(type) ? ObjectFlags.IsGenericObjectType : 0) | + (type.flags & (TypeFlags.InstantiableNonPrimitive | TypeFlags.Index) || isGenericStringLikeType(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 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); + } + } + + // 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; + } + } + // 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] + + // 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)) { + if (getMappedTypeNameTypeKind(objectType) !== MappedTypeNameTypeKind.Remapping) { + return type[cache] = mapType(substituteIndexedMappedType(objectType, type.indexType), t => getSimplifiedType(t, writing)); + } + } + return type[cache] = type; + } + + function getSimplifiedConditionalType(type: ConditionalType, writing: boolean) { + const checkType = type.checkType; + const extendsType = type.extendsType; + const trueType = getTrueTypeFromConditionalType(type); + const falseType = getFalseTypeFromConditionalType(type); + // Simplifications for types of the form `T extends U ? T : never` and `T extends U ? never : T`. + if (falseType.flags & TypeFlags.Never && getActualTypeVariable(trueType) === getActualTypeVariable(checkType)) { + if (checkType.flags & TypeFlags.Any || isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(extendsType))) { // Always true + return getSimplifiedType(trueType, writing); + } + else if (isIntersectionEmpty(checkType, extendsType)) { // Always false + return neverType; + } + } + else if (trueType.flags & TypeFlags.Never && getActualTypeVariable(falseType) === getActualTypeVariable(checkType)) { + if (!(checkType.flags & TypeFlags.Any) && isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(extendsType))) { // Always true + return neverType; + } + else if (checkType.flags & TypeFlags.Any || isIntersectionEmpty(checkType, extendsType)) { // Always false + return getSimplifiedType(falseType, writing); + } + } + return type; + } + + /** + * Invokes union simplification logic to determine if an intersection is considered empty as a union constituent + */ + function isIntersectionEmpty(type1: Type, type2: Type) { + return !!(getUnionType([intersectTypes(type1, type2), neverType]).flags & TypeFlags.Never); + } + + // Given an indexed access on a mapped type of the form { [P in K]: E }[X], return an instantiation of E where P is + // replaced with X. Since this simplification doesn't account for mapped type modifiers, add 'undefined' to the + // resulting type if the mapped type includes a '?' modifier or if the modifiers type indicates that some properties + // are optional. If the modifiers type is generic, conservatively estimate optionality by recursively looking for + // mapped types that include '?' modifiers. + function substituteIndexedMappedType(objectType: MappedType, index: Type) { + const mapper = createTypeMapper([getTypeParameterFromMappedType(objectType)], [index]); + const templateMapper = combineTypeMappers(objectType.mapper, mapper); + const instantiatedTemplateType = instantiateType(getTemplateTypeFromMappedType(objectType.target as MappedType || objectType), templateMapper); + const isOptional = getMappedTypeOptionality(objectType) > 0 || (isGenericType(objectType) ? + getCombinedMappedTypeOptionality(getModifiersTypeFromMappedType(objectType)) > 0 : + couldAccessOptionalProperty(objectType, index)); + return addOptionality(instantiatedTemplateType, /*isProperty*/ true, isOptional); + } + + // Return true if an indexed access with the given object and index types could access an optional property. + function couldAccessOptionalProperty(objectType: Type, indexType: Type) { + const indexConstraint = getBaseConstraintOfType(indexType); + return !!indexConstraint && some(getPropertiesOfType(objectType), p => + !!(p.flags & SymbolFlags.Optional) && + isTypeAssignableTo(getLiteralTypeFromProperty(p, TypeFlags.StringOrNumberLiteralOrUnique), indexConstraint)); + } + + 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); + } + + 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 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; + } + objectType = getReducedType(objectType); + // 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, getTotalFixedElementCount(objectType.target)) : + isGenericObjectType(objectType) && !(isTupleType(objectType) && indexTypeLessThan(indexType, getTotalFixedElementCount(objectType.target))) || isGenericReducibleType(objectType)) + ) { + 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)); + } + + 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; + } + return accessFlags & AccessFlags.Writing + ? getIntersectionType(propTypes, IntersectionFlags.None, aliasSymbol, aliasTypeArguments) + : getUnionType(propTypes, UnionReduction.Literal, aliasSymbol, aliasTypeArguments); + } + return getPropertyTypeForIndexType(objectType, apparentObjectType, indexType, indexType, accessNode, accessFlags | AccessFlags.CacheSymbol | AccessFlags.ReportDeprecated); + } + + 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 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 getActualTypeVariable(type: Type): Type { + if (type.flags & TypeFlags.Substitution) { + return getActualTypeVariable((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 isSimpleTupleType(node: TypeNode): boolean { + return isTupleTypeNode(node) && length(node.elements) > 0 && + !some(node.elements, e => isOptionalTypeNode(e) || isRestTypeNode(e) || isNamedTupleMember(e) && !!(e.questionToken || e.dotDotDotToken)); + } + + function isDeferredType(type: Type, checkTuples: boolean) { + return isGenericType(type) || checkTuples && isTupleType(type) && some(getElementTypes(type), isGenericType); + } + + function getConditionalType(root: ConditionalRoot, mapper: TypeMapper | undefined, forConstraint: boolean, 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); + return errorType; + } + const checkType = instantiateType(getActualTypeVariable(root.checkType), mapper); + const extendsType = instantiateType(root.extendsType, mapper); + if (checkType === errorType || extendsType === errorType) { + return errorType; + } + if (checkType === wildcardType || extendsType === wildcardType) { + return wildcardType; + } + const checkTypeNode = skipTypeParentheses(root.node.checkType); + const extendsTypeNode = skipTypeParentheses(root.node.extendsType); + // When the check and extends types are simple tuple types of the same arity, we defer resolution of the + // conditional type when any tuple elements are generic. This is such that non-distributable conditional + // types can be written `[X] extends [Y] ? ...` and be deferred similarly to `X extends Y ? ...`. + const checkTuples = isSimpleTupleType(checkTypeNode) && isSimpleTupleType(extendsTypeNode) && + length((checkTypeNode as TupleTypeNode).elements) === length((extendsTypeNode as TupleTypeNode).elements); + const checkTypeDeferred = isDeferredType(checkType, checkTuples); + 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 context - 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: + // * combine `context.nonFixingMapper` with `mapper` so their constraints can be instantiated in the context of `mapper` (otherwise they'd only get inference context information) + // * incorporate all of the component mappers into the combined mapper for the true and false members + // This means we have two mappers that need applying: + // * The original `mapper` used to create this conditional + // * The mapper that maps the infer type parameter to its inference result (`context.mapper`) + const context = createInferenceContext(root.inferTypeParameters, /*signature*/ undefined, InferenceFlags.None); + if (mapper) { + context.nonFixingMapper = combineTypeMappers(context.nonFixingMapper, mapper); + } + if (!checkTypeDeferred) { + // 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, extendsType, InferencePriority.NoConstraints | InferencePriority.AlwaysStrict); + } + // 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(context.mapper, mapper) : context.mapper; + } + // Instantiate the extends type including inferences for 'infer T' type parameters + const inferredExtendsType = combinedMapper ? instantiateType(root.extendsType, combinedMapper) : extendsType; + // We attempt to resolve the conditional type only when the check and extends types are non-generic + if (!checkTypeDeferred && !isDeferredType(inferredExtendsType, checkTuples)) { + // 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 || !isTypeAssignableTo(getPermissiveInstantiation(checkType), getPermissiveInstantiation(inferredExtendsType)))) { + // Return union of trueType and falseType for 'any' since it matches anything. Furthermore, for a + // distributive conditional type applied to the constraint of a type variable, include trueType if + // there are possible values of the check type that are also possible values of the extends type. + // We use a reverse assignability check as it is less expensive than the comparable relationship + // and avoids false positives of a non-empty intersection check. + if (checkType.flags & TypeFlags.Any || forConstraint && !(inferredExtendsType.flags & TypeFlags.Never) && someType(getPermissiveInstantiation(inferredExtendsType), t => isTypeAssignableTo(t, getPermissiveInstantiation(checkType)))) { + (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; + } + } + // 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; + } + } + + function getTrueTypeFromConditionalType(type: ConditionalType) { + return type.resolvedTrueType || (type.resolvedTrueType = instantiateType(getTypeFromTypeNode(type.root.node.trueType), type.mapper)); + } + + function getFalseTypeFromConditionalType(type: ConditionalType) { + return type.resolvedFalseType || (type.resolvedFalseType = instantiateType(getTypeFromTypeNode(type.root.node.falseType), type.mapper)); + } + + function getInferredTrueTypeFromConditionalType(type: ConditionalType) { + return type.resolvedInferredTrueType || (type.resolvedInferredTrueType = type.combinedMapper ? instantiateType(getTypeFromTypeNode(type.root.node.trueType), type.combinedMapper) : getTrueTypeFromConditionalType(type)); + } + + 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 isDistributionDependent(root: ConditionalRoot) { + return root.isDistributive && ( + isTypeParameterPossiblyReferenced(root.checkType as TypeParameter, root.node.trueType) || + isTypeParameterPossiblyReferenced(root.checkType as TypeParameter, root.node.falseType) + ); + } + + 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, /*forConstraint*/ false); + if (outerTypeParameters) { + root.instantiations = new Map(); + root.instantiations.set(getTypeListId(outerTypeParameters), links.resolvedType); + } + } + return links.resolvedType; + } + + function getTypeFromInferTypeNode(node: InferTypeNode): Type { + const links = getNodeLinks(node); + if (!links.resolvedType) { + links.resolvedType = getDeclaredTypeOfTypeParameter(getSymbolOfDeclaration(node.typeParameter)); + } + return links.resolvedType; + } + + function getIdentifierChain(node: EntityName): Identifier[] { + if (isIdentifier(node)) { + return [node]; + } + else { + return append(getIdentifierChain(node.left), node.right); + } + } + + function getTypeFromImportTypeNode(node: ImportTypeNode): Type { + const links = getNodeLinks(node); + if (!links.resolvedType) { + 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); + } + 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 links.resolvedType; + } + + function resolveImportSymbolType(node: ImportTypeNode, links: NodeLinks, symbol: Symbol, meaning: SymbolFlags) { + const resolvedSymbol = resolveSymbol(symbol); + links.resolvedSymbol = resolvedSymbol; + if (meaning === SymbolFlags.Value) { + return getInstantiationExpressionType(getTypeOfSymbol(symbol), node); // 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: TypeLiteralNode | FunctionOrConstructorTypeNode | JSDocTypeLiteral | JSDocFunctionType | JSDocSignature): 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; + } + } + return links.resolvedType; + } + + 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) ? getSymbolOfDeclaration(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 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 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.links.type = isSetonlyAccessor ? undefinedType : addOptionality(getTypeOfSymbol(prop), /*isProperty*/ true); + result.declarations = prop.declarations; + result.links.nameType = getSymbolLinks(prop).nameType; + result.links.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; + } + } + + /** + * 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]); + } + + const members = createSymbolTable(); + const skippedPrivateMembers = new Set<__String>(); + const indexInfos = left === emptyObjectType ? getIndexInfosOfType(right) : getUnionIndexInfos([left, 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)); + } + } + + 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); + // Optimization: avoid calculating the union type if spreading into the exact same type. + // This is common, e.g. spreading one options bag into another where the bags have the + // same type, or have properties which overlap. If the unions are large, it may turn out + // to be expensive to perform subtype reduction. + const leftType = getTypeOfSymbol(leftProp); + const leftTypeWithoutUndefined = removeMissingOrUndefinedType(leftType); + const rightTypeWithoutUndefined = removeMissingOrUndefinedType(rightType); + result.links.type = leftTypeWithoutUndefined === rightTypeWithoutUndefined ? leftType : getUnionType([leftType, rightTypeWithoutUndefined], UnionReduction.Subtype); + result.links.leftSpread = leftProp; + result.links.rightSpread = rightProp; + result.declarations = declarations; + result.links.nameType = getSymbolLinks(leftProp).nameType; + members.set(leftProp.escapedName, result); + } + } + else { + members.set(leftProp.escapedName, getSpreadSymbol(leftProp, readonly)); + } + } + + const spread = createAnonymousType(symbol, members, emptyArray, emptyArray, sameMap(indexInfos, info => getIndexInfoWithReadonly(info, readonly))); + spread.objectFlags |= ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral | ObjectFlags.ContainsSpread | objectFlags; + return spread; + } + + /** 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 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.links.type = isSetonlyAccessor ? undefinedType : getTypeOfSymbol(prop); + result.declarations = prop.declarations; + result.links.nameType = getSymbolLinks(prop).nameType; + result.links.syntheticOrigin = prop; + return result; + } + + function getIndexInfoWithReadonly(info: IndexInfo, readonly: boolean) { + return info.isReadonly !== readonly ? createIndexInfo(info.keyType, info.type, readonly, info.declaration) : info; + } + + 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 getFreshTypeOfLiteralType(type: Type): Type { + if (type.flags & TypeFlags.Freshable) { + if (!(type as FreshableType).freshType) { + const freshType = createLiteralType(type.flags, (type as LiteralType).value, (type as LiteralType).symbol, type as LiteralType); + freshType.freshType = freshType; + (type as FreshableType).freshType = freshType; + } + return (type as FreshableType).freshType; + } + return type; + } + + function getRegularTypeOfLiteralType(type: Type): Type { + return type.flags & TypeFlags.Freshable ? (type as FreshableType).regularType : + type.flags & TypeFlags.Union ? ((type as UnionType).regularType || ((type as UnionType).regularType = mapType(type, getRegularTypeOfLiteralType) as UnionType)) : + type; + } + + function isFreshLiteralType(type: Type) { + return !!(type.flags & TypeFlags.Freshable) && (type as LiteralType).freshType === type; + } + + function getStringLiteralType(value: string): StringLiteralType { + let type; + return stringLiteralTypes.get(value) || + (stringLiteralTypes.set(value, type = createLiteralType(TypeFlags.StringLiteral, value) as StringLiteralType), type); + } + + function getNumberLiteralType(value: number): NumberLiteralType { + let type; + return numberLiteralTypes.get(value) || + (numberLiteralTypes.set(value, type = createLiteralType(TypeFlags.NumberLiteral, value) as NumberLiteralType), type); + } + + 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 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 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; + } + + 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 getESSymbolLikeTypeForNode(node: Node) { + if (isInJSFile(node) && isJSDocTypeExpression(node)) { + const host = getJSDocHost(node); + if (host) { + node = getSingleVariableOfVariableStatement(host) || host; + } + } + 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 getThisType(node: Node): Type { + const container = getThisContainer(node, /*includeArrowFunctions*/ false, /*includeClassComputedPropertyName*/ 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(getSymbolOfDeclaration(parent as ClassLikeDeclaration | InterfaceDeclaration)).thisType!; + } + } + + // inside x.prototype = { ... } + if (parent && isObjectLiteralExpression(parent) && isBinaryExpression(parent.parent) && getAssignmentDeclarationKind(parent.parent) === AssignmentDeclarationKind.Prototype) { + return getDeclaredTypeOfClassOrInterface(getSymbolOfNode(parent.parent.left)!.parent!).thisType!; + } + // /** @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!; + } + // inside constructor function C() { ... } + if (isJSConstructor(container) && isNodeDescendantOf(node, container.body)) { + return getDeclaredTypeOfClassOrInterface(getSymbolOfDeclaration(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); + } + return links.resolvedType; + } + + function getTypeFromRestTypeNode(node: RestTypeNode | NamedTupleMember) { + return getTypeFromTypeNode(getArrayElementTypeNode(node.type) || node.type); + } + + 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 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 getTypeFromTypeNode(node: TypeNode): Type { + return getConditionalFlowTypeOfType(getTypeFromTypeNodeWorker(node), node); + } + + 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 as TypeLiteralNode | FunctionOrConstructorTypeNode | JSDocTypeLiteral | JSDocFunctionType | JSDocSignature); + 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 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 items; + } + + 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 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 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 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 the type of the symbol is already resolved, and if that type could not possibly + // be affected by instantiation, simply return the symbol itself. + if (links.type && !couldContainTypeVariables(links.type)) { + if (!(symbol.flags & SymbolFlags.SetAccessor)) { + return symbol; + } + // If we're a setter, check writeType. + if (links.writeType && !couldContainTypeVariables(links.writeType)) { + 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.links.target = symbol; + result.links.mapper = mapper; + if (symbol.valueDeclaration) { + result.valueDeclaration = symbol.valueDeclaration; + } + if (links.nameType) { + result.links.nameType = links.nameType; + } + return result; + } + + 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 = type.objectFlags & ObjectFlags.SingleSignatureType ? (type as SingleSignatureType).outerTypeParameters : 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 = (type.objectFlags & ObjectFlags.SingleSignatureType ? "S" : "") + 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) { + if (type.objectFlags & ObjectFlags.SingleSignatureType) { + result = instantiateAnonymousType(type, mapper); + target.instantiations.set(id, result); + return 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); // Set cached result early in case we recursively invoke instantiation while eagerly computing type variable visibility below + const resultObjectFlags = getObjectFlags(result); + if (result.flags & TypeFlags.ObjectFlagsType && !(resultObjectFlags & ObjectFlags.CouldContainTypeVariablesComputed)) { + const resultCouldContainTypeVariables = some(typeArguments, couldContainTypeVariables); // one of the input type arguments might be or contain the result + if (!(getObjectFlags(result) & ObjectFlags.CouldContainTypeVariablesComputed)) { + // if `result` is one of the object types we tried to make (it may not be, due to how `instantiateMappedType` works), we can carry forward the type variable containment check from the input type arguments + if (resultObjectFlags & (ObjectFlags.Mapped | ObjectFlags.Anonymous | ObjectFlags.Reference)) { + (result as ObjectFlagsType).objectFlags |= ObjectFlags.CouldContainTypeVariablesComputed | (resultCouldContainTypeVariables ? ObjectFlags.CouldContainTypeVariables : 0); + } + // If none of the type arguments for the outer type parameters contain type variables, it follows + // that the instantiated type doesn't reference type variables. + // Intrinsics have `CouldContainTypeVariablesComputed` pre-set, so this should only cover unions and intersections resulting from `instantiateMappedType` + else { + (result as ObjectFlagsType).objectFlags |= !resultCouldContainTypeVariables ? ObjectFlags.CouldContainTypeVariablesComputed : 0; + } + } + } + } + return result; + } + return type; + } + + 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; + } + } + 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); + if (!isThisIdentifier(firstIdentifier)) { // Don't attempt to analyze typeof this.xxx + const firstIdentifierSymbol = getResolvedSymbol(firstIdentifier); + const tpDeclaration = tp.symbol.declarations![0]; // There is exactly one declaration, otherwise `containsReference` is not called + const tpScope = tpDeclaration.kind === SyntaxKind.TypeParameter ? tpDeclaration.parent : // Type parameter is a regular type parameter, e.g. foo + tp.isThisType ? tpDeclaration : // Type parameter is the this type, and its declaration is the class declaration. + undefined; // Type parameter's declaration was unrecognized, e.g. comes from JSDoc annotation. + if (firstIdentifierSymbol.declarations && tpScope) { + 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 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 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. + // * If T is an intersection of array or tuple types we map to an intersection of transformed array or tuple types. + // * 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), instantiateConstituent, aliasSymbol, aliasTypeArguments); + } + } + // 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 instantiateConstituent(t: Type): Type { + 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 (isTupleType(t)) { + return instantiateMappedTupleType(t, type, typeVariable!, mapper); + } + if (isArrayOrTupleOrIntersection(t)) { + return getIntersectionType(map((t as IntersectionType).types, instantiateConstituent)); + } + } + return instantiateAnonymousType(type, prependTypeMapping(typeVariable!, t, mapper)); + } + return t; + } + } + + function getModifiedReadonlyState(state: boolean, modifiers: MappedTypeModifiers) { + return modifiers & MappedTypeModifiers.IncludeReadonly ? true : modifiers & MappedTypeModifiers.ExcludeReadonly ? false : state; + } + + function instantiateMappedTupleType(tupleType: TupleTypeReference, mappedType: MappedType, typeVariable: TypeVariable, mapper: TypeMapper) { + // We apply the mapped type's template type to each of the fixed part elements. For variadic elements, we + // apply the mapped type itself to the variadic element type. For other elements in the variable part of the + // tuple, we surround the element type with an array type and apply the mapped type to that. This ensures + // that we get sequential property key types for the fixed part of the tuple, and property key type number + // for the remaining elements. For example + // + // type Keys = { [K in keyof T]: K }; + // type Foo = Keys<[string, string, ...T, string]>; // ["0", "1", ...Keys, number] + // + const elementFlags = tupleType.target.elementFlags; + const fixedLength = tupleType.target.fixedLength; + const fixedMapper = fixedLength ? prependTypeMapping(typeVariable, tupleType, mapper) : mapper; + const newElementTypes = map(getElementTypes(tupleType), (type, i) => { + const flags = elementFlags[i]; + return i < fixedLength ? instantiateMappedTypeTemplate(mappedType, getStringLiteralType("" + i), !!(flags & ElementFlags.Optional), fixedMapper) : + flags & ElementFlags.Variadic ? instantiateType(mappedType, prependTypeMapping(typeVariable, type, mapper)) : + getElementTypeOfArrayType(instantiateType(mappedType, prependTypeMapping(typeVariable, createArrayType(type), mapper))) ?? unknownType; + }); + const modifiers = getMappedTypeModifiers(mappedType); + const newElementFlags = 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, getMappedTypeModifiers(mappedType)); + return contains(newElementTypes, errorType) ? errorType : + createTupleType(newElementTypes, newElementFlags, newReadonly, tupleType.target.labeledElementDeclarations); + } + + 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 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 { + Debug.assert(type.symbol, "anonymous type must have symbol to be instantiated"); + const result = createObjectType(type.objectFlags & ~(ObjectFlags.CouldContainTypeVariablesComputed | ObjectFlags.CouldContainTypeVariables) | 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; + } + if (type.objectFlags & ObjectFlags.SingleSignatureType) { + (result as SingleSignatureType).outerTypeParameters = (type as SingleSignatureType).outerTypeParameters; + } + 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, forConstraint: boolean, 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 = (forConstraint ? "C" : "") + 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 ? getReducedType(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(distributionType, t => getConditionalType(root, prependTypeMapping(checkType, t, newMapper), forConstraint), aliasSymbol, aliasTypeArguments) : + getConditionalType(root, newMapper, forConstraint, aliasSymbol, aliasTypeArguments); + root.instantiations!.set(id, result); + } + return result; + } + 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; + } + + 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); + } + 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, IntersectionFlags.None, 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), /*forConstraint*/ false, aliasSymbol, aliasTypeArguments); + } + if (flags & TypeFlags.Substitution) { + const newBaseType = instantiateType((type as SubstitutionType).baseType, mapper); + if (isNoInferType(type)) { + return getNoInferType(newBaseType); + } + 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 (newConstraint.flags & TypeFlags.AnyOrUnknown || isTypeAssignableTo(getRestrictiveInstantiation(newBaseType), getRestrictiveInstantiation(newConstraint))) { + return newBaseType; + } + return newBaseType.flags & TypeFlags.TypeVariable ? getSubstitutionType(newBaseType, newConstraint) : getIntersectionType([newConstraint, newBaseType]); + } + 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)) { + 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 + } + + function getPermissiveInstantiation(type: Type) { + return type.flags & (TypeFlags.Primitive | TypeFlags.AnyOrUnknown | TypeFlags.Never) ? type : + type.permissiveInstantiation || (type.permissiveInstantiation = instantiateType(type, permissiveMapper)); + } + + function getRestrictiveInstantiation(type: Type) { + if (type.flags & (TypeFlags.Primitive | TypeFlags.AnyOrUnknown | TypeFlags.Never)) { + return type; + } + 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 instantiateIndexInfo(info: IndexInfo, mapper: TypeMapper) { + return createIndexInfo(info.keyType, instantiateType(info.type, mapper), info.isReadonly, info.declaration); + } + + // 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