diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index f96ffd6c02a..fc67bdcb482 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -7,7 +7,13 @@ } }, "settings": { - "terminal.integrated.shell.linux": "/bin/bash" + "terminal.integrated.defaultProfile.linux": "bash", + "terminal.integrated.profiles.linux": { + "bash": { + "path": "/bin/bash", + "icon": "terminal-bash", + }, + }, }, "extensions": [ "dbaeumer.vscode-eslint" diff --git a/.github/pr_owners.txt b/.github/pr_owners.txt index 6177279468b..60ba7e99b7a 100644 --- a/.github/pr_owners.txt +++ b/.github/pr_owners.txt @@ -10,4 +10,7 @@ ahejlsberg amcasey jessetrinity minestarks -uniqueiniquity +armanio123 +gabritto +jakebailey +DanielRosenwasser diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index b02c231fb4f..12b9aeb21e1 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -9,6 +9,13 @@ Please verify that: Refer to CONTRIBUTING.MD for more details. https://github.com/Microsoft/TypeScript/blob/main/CONTRIBUTING.md + +** Please don't send typo fixes! ** +Please don't send a PR solely for the purpose of fixing a typo, unless that +typo truly hurts understanding of the text. Each PR represents work for the +maintainers, and that work should provide commensurate value. + +If you're interested in sending a PR, the issue tracker has many issues marked `help wanted`. --> Fixes # diff --git a/.github/workflows/accept-baselines-fix-lints.yaml b/.github/workflows/accept-baselines-fix-lints.yaml index d92b1ae1836..0038167e350 100644 --- a/.github/workflows/accept-baselines-fix-lints.yaml +++ b/.github/workflows/accept-baselines-fix-lints.yaml @@ -20,6 +20,7 @@ jobs: git config user.email "typescriptbot@microsoft.com" git config user.name "TypeScript Bot" npm install + git rm -r --quiet tests/baselines/reference :^tests/baselines/reference/docker :^tests/baselines/reference/user gulp runtests-parallel --ci --fix || true gulp baseline-accept git add ./src diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index df96f73376b..aefe86e2ef8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: strategy: matrix: - node-version: [10.x, 12.x, 14.x] + node-version: [12.x, 14.x, 16.x] steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/ensure-related-repos-run-crons.yml b/.github/workflows/ensure-related-repos-run-crons.yml new file mode 100644 index 00000000000..f6faff6b67f --- /dev/null +++ b/.github/workflows/ensure-related-repos-run-crons.yml @@ -0,0 +1,46 @@ +# Ensures that repos which are related to TypeScript but may not have regular commits +# have their GitHub Actions scheduled jobs still active due to the 6 week timeout +# on OSS repos. This has already triggered a few times with microsoft/TypeScript-Make-Monaco-Builds +# so, better to automate keeping on top of it. + +name: Related Repo Commit Bumps + +on: + schedule: + # Monthly, https://crontab.guru/#0_0_*_1-12_* + - cron: '0 0 1 * *' + workflow_dispatch: {} + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Configure git + run: | + git config --global user.email "typescriptbot@microsoft.com" + git config --global user.name "TypeScript Bot" + + - uses: actions/checkout@v2 + with: + repository: 'microsoft/TypeScript-Website' + path: 'ts-site' + + - name: Push Commit to TS Website + run: | + cd ts-site + git commit --allow-empty -m "Monthly Bump" + git config --unset-all http.https://github.com/.extraheader + git push https://${{ secrets.TS_BOT_GITHUB_TOKEN }}@github.com/microsoft/TypeScript-Website.git + + - uses: actions/checkout@v2 + with: + repository: 'microsoft/TypeScript-Make-Monaco-Builds' + path: 'monaco-builds' + + - name: Push Commit to TS Make Monaco Builds + run: | + cd monaco-builds + git commit --allow-empty -m "Monthly Bump" + git config --unset-all http.https://github.com/.extraheader + git push https://${{ secrets.TS_BOT_GITHUB_TOKEN }}@github.com/microsoft/TypeScript-Make-Monaco-Builds.git diff --git a/.vscode/launch.template.json b/.vscode/launch.template.json index ca3ac8d2b88..e4301b81e87 100644 --- a/.vscode/launch.template.json +++ b/.vscode/launch.template.json @@ -48,11 +48,7 @@ "outFiles": [ "${workspaceRoot}/built/local/run.js" ], - - // NOTE: To use this, you must switch the "type" above to "pwa-node". You may also need to follow the instructions - // here: https://github.com/microsoft/vscode-js-debug#nightly-extension to use the js-debug nightly until - // this feature is shipping in insiders or to a release: - // "customDescriptionGenerator": "'__tsDebuggerDisplay' in this ? this.__tsDebuggerDisplay(defaultValue) : defaultValue" + "customDescriptionGenerator": "'__tsDebuggerDisplay' in this ? this.__tsDebuggerDisplay(defaultValue) : defaultValue", }, { // See: https://github.com/microsoft/TypeScript/wiki/Debugging-Language-Service-in-VS-Code @@ -60,11 +56,7 @@ "request": "attach", "name": "Attach to VS Code TS Server via Port", "processId": "${command:PickProcess}", - - // NOTE: To use this, you must switch the "type" above to "pwa-node". You may also need to follow the instructions - // here: https://github.com/microsoft/vscode-js-debug#nightly-extension to use the js-debug nightly until - // this feature is shipping in insiders or to a release: - // "customDescriptionGenerator": "'__tsDebuggerDisplay' in this ? this.__tsDebuggerDisplay(defaultValue) : defaultValue" + "customDescriptionGenerator": "'__tsDebuggerDisplay' in this ? this.__tsDebuggerDisplay(defaultValue) : defaultValue", } ] } diff --git a/.vscode/settings.template.json b/.vscode/settings.template.json index 1806a93ecbc..3359472330b 100644 --- a/.vscode/settings.template.json +++ b/.vscode/settings.template.json @@ -2,10 +2,7 @@ // contents into your existing settings. { "eslint.validate": [ - { - "language": "typescript", - "autoFix": true - } + "typescript" ], "eslint.options": { "rulePaths": ["./scripts/eslint/built/rules/"], @@ -16,4 +13,4 @@ // To use the locally built compiler, after 'npm run build': // "typescript.tsdk": "built/local" -} \ No newline at end of file +} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 391a92de5cd..9d32388a1ff 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -20,7 +20,7 @@ Some search tips: ## 3. Do you have a question? The issue tracker is for **issues**, in other words, bugs and suggestions. -If you have a *question*, please use [Stack Overflow](http://stackoverflow.com/questions/tagged/typescript), [Gitter](https://gitter.im/Microsoft/TypeScript), your favorite search engine, or other resources. +If you have a *question*, please use [Stack Overflow](https://stackoverflow.com/questions/tagged/typescript), [Gitter](https://gitter.im/Microsoft/TypeScript), your favorite search engine, or other resources. Due to increased traffic, we can no longer answer questions in the issue tracker. ## 4. Did you find a bug? diff --git a/lib/zh-cn/diagnosticMessages.generated.json b/lib/zh-cn/diagnosticMessages.generated.json index 9c79f7feb06..824ee7b5c06 100644 --- a/lib/zh-cn/diagnosticMessages.generated.json +++ b/lib/zh-cn/diagnosticMessages.generated.json @@ -130,7 +130,7 @@ "Add_await_to_initializer_for_0_95084": "将 \"await\" 添加到 \"{0}\" 的初始值设定项", "Add_await_to_initializers_95089": "将 \"await\" 添加到初始值设定项", "Add_braces_to_arrow_function_95059": "向箭头函数添加大括号", - "Add_class_tag_95102": "添加“@类”标记", + "Add_class_tag_95102": "添加“@class”标记", "Add_const_to_all_unresolved_variables_95082": "将 \"const\" 添加到所有未解析变量", "Add_const_to_unresolved_variable_95081": "将 \"const\" 添加到未解析的变量", "Add_default_import_0_to_existing_import_declaration_from_1_90033": "将默认导入 \"{0}\" 从 \"{1}\" 添加到现有导入声明。", @@ -154,7 +154,7 @@ "Add_parameter_name_90034": "添加参数名称", "Add_qualifier_to_all_unresolved_variables_matching_a_member_name_95037": "将限定符添加到匹配成员名称的所有未解析变量", "Add_this_parameter_95104": "添加“此”参数。", - "Add_this_tag_95103": "添加“@此”标记", + "Add_this_tag_95103": "添加“@this”标记", "Add_to_all_uncalled_decorators_95044": "将 \"()\" 添加到所有未调用的修饰器", "Add_ts_ignore_to_all_error_messages_95042": "将 \"@ts-ignore\" 添加到所有错误消息", "Add_undefined_to_a_type_when_accessed_using_an_index_6674": "使用索引访问时,将 “undefined” 添加到类型。", @@ -1784,4 +1784,4 @@ "with_statements_are_not_allowed_in_strict_mode_1101": "严格模式下不允许使用 \"with\" 语句。", "yield_expression_implicitly_results_in_an_any_type_because_its_containing_generator_lacks_a_return_t_7057": "\"yield\" 表达式隐式导致 \"any\" 类型,因为它的包含生成器缺少返回类型批注。", "yield_expressions_cannot_be_used_in_a_parameter_initializer_2523": "不能在参数初始化表达式中使用 \"yield\" 表达式。" -} \ No newline at end of file +} diff --git a/package-lock.json b/package-lock.json index 4fe981b3a24..3dffad674d8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "typescript", - "version": "4.5.0", + "version": "4.7.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -279,9 +279,9 @@ } }, "@octokit/auth-token": { - "version": "2.4.5", - "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.4.5.tgz", - "integrity": "sha512-BpGYsPgJt05M7/L/5FoE1PiAbdxXFZkX/3kDYcsvd1v6UhlnE5e96dTDr0ezX/EFwciQxf3cNV0loipsURU+WA==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz", + "integrity": "sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g==", "dev": true, "requires": { "@octokit/types": "^6.0.3" @@ -325,18 +325,18 @@ } }, "@octokit/openapi-types": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-10.1.1.tgz", - "integrity": "sha512-ygp/6r25Ezb1CJuVMnFfOsScEtPF0zosdTJDZ7mZ+I8IULl7DP1BS5ZvPRwglcarGPXOvS5sHdR0bjnVDDfQdQ==", + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-11.2.0.tgz", + "integrity": "sha512-PBsVO+15KSlGmiI8QAzaqvsNlZlrDlyAJYcrXBCvVUxCp7VnXjkwPoFHgjEJXx3WF9BAwkA6nfCUA7i9sODzKA==", "dev": true }, "@octokit/plugin-paginate-rest": { - "version": "2.16.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.16.0.tgz", - "integrity": "sha512-8YYzALPMvEZ35kgy5pdYvQ22Roz+BIuEaedO575GwE2vb/ACDqQn0xQrTJR4tnZCJn7pi8+AWPVjrFDaERIyXQ==", + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.17.0.tgz", + "integrity": "sha512-tzMbrbnam2Mt4AhuyCHvpRkS0oZ5MvwwcQPYGtMv4tUa5kkzG58SVB0fcsLulOZQeRnOgdkZWkRUiyBlh0Bkyw==", "dev": true, "requires": { - "@octokit/types": "^6.26.0" + "@octokit/types": "^6.34.0" } }, "@octokit/plugin-request-log": { @@ -346,26 +346,26 @@ "dev": true }, "@octokit/plugin-rest-endpoint-methods": { - "version": "5.10.1", - "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.10.1.tgz", - "integrity": "sha512-Rf1iMl40I0dIxjh1g32qZ6Ym/uT8QWZMm2vYGG5Vi8SX1MwZvbuxEGXYgmzTUWSD3PYWSLilE2+4L8kmdLGTMg==", + "version": "5.13.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.13.0.tgz", + "integrity": "sha512-uJjMTkN1KaOIgNtUPMtIXDOjx6dGYysdIFhgA52x4xSadQCz3b/zJexvITDVpANnfKPW/+E0xkOvLntqMYpviA==", "dev": true, "requires": { - "@octokit/types": "^6.27.0", + "@octokit/types": "^6.34.0", "deprecation": "^2.3.1" } }, "@octokit/request": { - "version": "5.6.1", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.1.tgz", - "integrity": "sha512-Ls2cfs1OfXaOKzkcxnqw5MR6drMA/zWX/LIS/p8Yjdz7QKTPQLMsB3R+OvoxE6XnXeXEE2X7xe4G4l4X0gRiKQ==", + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.3.tgz", + "integrity": "sha512-bFJl0I1KVc9jYTe9tdGGpAMPy32dLBXXo1dS/YwSCTL/2nd9XeHsY616RE3HPXDVk+a+dBuzyz5YdlXwcDTr2A==", "dev": true, "requires": { "@octokit/endpoint": "^6.0.1", "@octokit/request-error": "^2.1.0", "@octokit/types": "^6.16.1", "is-plain-object": "^5.0.0", - "node-fetch": "^2.6.1", + "node-fetch": "^2.6.7", "universal-user-agent": "^6.0.0" } }, @@ -381,24 +381,24 @@ } }, "@octokit/rest": { - "version": "18.10.0", - "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-18.10.0.tgz", - "integrity": "sha512-esHR5OKy38bccL/sajHqZudZCvmv4yjovMJzyXlphaUo7xykmtOdILGJ3aAm0mFHmMLmPFmDMJXf39cAjNJsrw==", + "version": "18.12.0", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-18.12.0.tgz", + "integrity": "sha512-gDPiOHlyGavxr72y0guQEhLsemgVjwRePayJ+FcKc2SJqKUbxbkvf5kAZEWA/MKvsfYlQAMVzNJE3ezQcxMJ2Q==", "dev": true, "requires": { "@octokit/core": "^3.5.1", - "@octokit/plugin-paginate-rest": "^2.16.0", + "@octokit/plugin-paginate-rest": "^2.16.8", "@octokit/plugin-request-log": "^1.0.4", - "@octokit/plugin-rest-endpoint-methods": "^5.9.0" + "@octokit/plugin-rest-endpoint-methods": "^5.12.0" } }, "@octokit/types": { - "version": "6.27.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.27.0.tgz", - "integrity": "sha512-ha27f8DToxXBPEJdzHCCuqpw7AgKfjhWGdNf3yIlBAhAsaexBXTfWw36zNSsncALXGvJq4EjLy1p3Wz45Aqb4A==", + "version": "6.34.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.34.0.tgz", + "integrity": "sha512-s1zLBjWhdEI2zwaoSgyOFoKSl109CUcVBCc7biPJ3aAf6LGLU6szDvi31JPU7bxfla2lqfhjbbg/5DdFNxOwHw==", "dev": true, "requires": { - "@octokit/openapi-types": "^10.1.0" + "@octokit/openapi-types": "^11.2.0" } }, "@types/browserify": { @@ -412,9 +412,9 @@ } }, "@types/chai": { - "version": "4.2.21", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.21.tgz", - "integrity": "sha512-yd+9qKmJxm496BOV9CMNaey8TWsikaZOwMRwPHQIjcOJM9oV+fi9ZMNw3JsVnbEEbo2gRTDnGEBv8pjyn67hNg==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.0.tgz", + "integrity": "sha512-/ceqdqeRraGolFTcfoXNiqjyQhZzbINDngeoAq9GoHa8PPK1yNzTaxWjA6BFWp5Ua9JpXEMSS4s5i9tS0hOJtw==", "dev": true }, "@types/convert-source-map": { @@ -430,9 +430,9 @@ "dev": true }, "@types/glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-w+LsMxKyYQm347Otw+IfBXOv9UWVjpHpCDdbBMt8Kz/xbvCYNjP+0qPh91Km3iKfSRLBB0P7fAMf0KHrPu+MyA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==", "dev": true, "requires": { "@types/minimatch": "*", @@ -664,9 +664,9 @@ } }, "@types/mocha": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.0.0.tgz", - "integrity": "sha512-scN0hAWyLVAvLR9AyW7HoFF5sJZglyBsbPuHO4fv7JRvfmPBMfp1ozWqOf/e4wwPNxezBZXRfWzMb6iFLgEVRA==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.1.0.tgz", + "integrity": "sha512-QCWHkbMv4Y5U9oW10Uxbr45qMMSzl4OzijsozynUAgx3kEHUdXB00udx2dWDQ7f2TU2a2uuiFaRZjCe3unPpeg==", "dev": true }, "@types/ms": { @@ -676,9 +676,9 @@ "dev": true }, "@types/node": { - "version": "16.9.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.0.tgz", - "integrity": "sha512-nmP+VR4oT0pJUPFbKE4SXj3Yb4Q/kz3M9dSAO1GGMebRKWHQxLfDNmU/yh3xxCJha3N60nQ/JwXWwOE/ZSEVag==", + "version": "17.0.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.18.tgz", + "integrity": "sha512-eKj4f/BsN/qcculZiRSujogjvp5O/k4lOW5m35NopjZM/QwLOR075a8pJW5hD+Rtdm2DaCVPENS6KtSQnUD6BA==", "dev": true }, "@types/node-fetch": { @@ -1316,9 +1316,9 @@ "dev": true }, "async": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.1.tgz", - "integrity": "sha512-XdD5lRO/87udXCMC9meWdYiR+Nq6ZjUfXidViUZGu2F1MO4T3XwZ1et0hb2++BgLfhyJwy44BGB/yx80ABx8hg==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz", + "integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==", "dev": true }, "async-done": { @@ -1646,21 +1646,22 @@ }, "dependencies": { "resolve": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", - "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", + "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", "dev": true, "requires": { - "is-core-module": "^2.2.0", - "path-parse": "^1.0.6" + "is-core-module": "^2.8.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" } } } }, "is-core-module": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.6.0.tgz", - "integrity": "sha512-wShG8vs60jKfPWpF2KZRaAtvt3a20OAn7+IJ6hLPECpSABLcKtFKTTI4ZtH5QcBruBHlq+WsdHWyz0BCZW7svQ==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz", + "integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==", "dev": true, "requires": { "has": "^1.0.3" @@ -1822,9 +1823,9 @@ } }, "cached-path-relative": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/cached-path-relative/-/cached-path-relative-1.0.2.tgz", - "integrity": "sha512-5r2GqsoEb4qMTTN9J+WzXfjov+hjxT+j3u5K+kIVNIwAd99DLCJE9pBIMP1qVeybV6JiijL385Oz0DcYxfbOIg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/cached-path-relative/-/cached-path-relative-1.1.0.tgz", + "integrity": "sha512-WF0LihfemtesFcJgO7xfOoOcnWzY/QHR4qeDqV44jPU3HTI54+LnfXK3SA27AVVGCdZFgjjFFaqUA9Jx7dMJZA==", "dev": true }, "call-bind": { @@ -1850,15 +1851,16 @@ "dev": true }, "chai": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.4.tgz", - "integrity": "sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA==", + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.6.tgz", + "integrity": "sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==", "dev": true, "requires": { "assertion-error": "^1.1.0", "check-error": "^1.0.2", "deep-eql": "^3.0.1", "get-func-name": "^2.0.0", + "loupe": "^2.3.1", "pathval": "^1.1.1", "type-detect": "^4.0.5" } @@ -2173,24 +2175,13 @@ "dev": true }, "copy-props": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/copy-props/-/copy-props-2.0.4.tgz", - "integrity": "sha512-7cjuUME+p+S3HZlbllgsn2CDwS+5eCCX16qBgNC4jgSTf49qR1VKy/Zhl400m0IQXl/bPGEVqncgUUMjrr4s8A==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/copy-props/-/copy-props-2.0.5.tgz", + "integrity": "sha512-XBlx8HSqrT0ObQwmSzM7WE5k8FxTV75h1DX1Z3n6NhQ/UYYAvInWYmG06vFt7hQZArE2fuO62aihiWIVQwh1sw==", "dev": true, "requires": { - "each-props": "^1.3.0", - "is-plain-object": "^2.0.1" - }, - "dependencies": { - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } - } + "each-props": "^1.3.2", + "is-plain-object": "^5.0.0" } }, "core-util-is": { @@ -2860,6 +2851,16 @@ "v8-compile-cache": "^2.0.3" }, "dependencies": { + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -2982,6 +2983,18 @@ "requires": { "chalk": "^4.1.0", "plur": "^4.0.0" + }, + "dependencies": { + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } } }, "eslint-import-resolver-node": { @@ -3392,15 +3405,12 @@ } }, "fancy-log": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz", - "integrity": "sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-2.0.0.tgz", + "integrity": "sha512-9CzxZbACXMUXW13tS0tI8XsGGmxWzO2DmYrGuBJOJ8k8q2K7hwfJA5qHjuPPe8wtsco33YR9wc+Rlr5wYFvhSA==", "dev": true, "requires": { - "ansi-gray": "^0.1.1", - "color-support": "^1.1.3", - "parse-node-version": "^1.0.0", - "time-stamp": "^1.0.0" + "color-support": "^1.1.3" } }, "fast-deep-equal": { @@ -3788,9 +3798,9 @@ "dev": true }, "glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -3961,6 +3971,20 @@ "semver-greatest-satisfied-range": "^1.1.0", "v8flags": "^3.2.0", "yargs": "^7.1.0" + }, + "dependencies": { + "fancy-log": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz", + "integrity": "sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw==", + "dev": true, + "requires": { + "ansi-gray": "^0.1.1", + "color-support": "^1.1.3", + "parse-node-version": "^1.0.0", + "time-stamp": "^1.0.0" + } + } } } } @@ -4694,6 +4718,12 @@ "is-unc-path": "^1.0.0" } }, + "is-shared-array-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz", + "integrity": "sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA==", + "dev": true + }, "is-string": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", @@ -4723,9 +4753,9 @@ }, "dependencies": { "es-abstract": { - "version": "1.18.6", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.6.tgz", - "integrity": "sha512-kAeIT4cku5eNLNuUKhlmtuk1/TRZvQoYccn6TO0cSVdf1kzB0T7+dYuVK9MWM7l+/53W2Q8M7N2c6MQvhXFcUQ==", + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.1.tgz", + "integrity": "sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==", "dev": true, "requires": { "call-bind": "^1.0.2", @@ -4739,7 +4769,9 @@ "is-callable": "^1.2.4", "is-negative-zero": "^2.0.1", "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.1", "is-string": "^1.0.7", + "is-weakref": "^1.0.1", "object-inspect": "^1.11.0", "object-keys": "^1.1.1", "object.assign": "^4.1.2", @@ -4761,9 +4793,9 @@ "dev": true }, "is-negative-zero": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", - "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", "dev": true }, "is-regex": { @@ -4786,9 +4818,9 @@ } }, "object-inspect": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz", - "integrity": "sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", + "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==", "dev": true }, "object.assign": { @@ -4852,6 +4884,15 @@ "integrity": "sha1-Kb8+/3Ab4tTTFdusw5vDn+j2Aao=", "dev": true }, + "is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2" + } + }, "is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", @@ -5071,6 +5112,15 @@ "is-unicode-supported": "^0.1.0" } }, + "loupe": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.4.tgz", + "integrity": "sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==", + "dev": true, + "requires": { + "get-func-name": "^2.0.0" + } + }, "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -5322,33 +5372,32 @@ "dev": true }, "mocha": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.1.1.tgz", - "integrity": "sha512-0wE74YMgOkCgBUj8VyIDwmLUjTsS13WV1Pg7l0SHea2qzZzlq7MDnfbPsHKcELBRk3+izEVkRofjmClpycudCA==", + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.0.tgz", + "integrity": "sha512-kNn7E8g2SzVcq0a77dkphPsDSN7P+iYkqE0ZsGCYWRsoiKjOt+NvXfaagik8vuDa6W5Zw3qxe8Jfpt5qKf+6/Q==", "dev": true, "requires": { "@ungap/promise-all-settled": "1.1.2", "ansi-colors": "4.1.1", "browser-stdout": "1.3.1", - "chokidar": "3.5.2", - "debug": "4.3.1", + "chokidar": "3.5.3", + "debug": "4.3.3", "diff": "5.0.0", "escape-string-regexp": "4.0.0", "find-up": "5.0.0", - "glob": "7.1.7", + "glob": "7.2.0", "growl": "1.10.5", "he": "1.2.0", "js-yaml": "4.1.0", "log-symbols": "4.1.0", "minimatch": "3.0.4", "ms": "2.1.3", - "nanoid": "3.1.23", + "nanoid": "3.2.0", "serialize-javascript": "6.0.0", "strip-json-comments": "3.1.1", "supports-color": "8.1.1", "which": "2.0.2", - "wide-align": "1.1.3", - "workerpool": "6.1.5", + "workerpool": "6.2.0", "yargs": "16.2.0", "yargs-parser": "20.2.4", "yargs-unparser": "2.0.0" @@ -5360,6 +5409,12 @@ "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", "dev": true }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, "anymatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", @@ -5392,9 +5447,9 @@ } }, "chokidar": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", - "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", "dev": true, "requires": { "anymatch": "~3.1.2", @@ -5419,9 +5474,9 @@ } }, "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", "dev": true, "requires": { "ms": "2.1.2" @@ -5567,14 +5622,25 @@ } }, "string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" + "strip-ansi": "^6.0.1" + }, + "dependencies": { + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + } } }, "supports-color": { @@ -5683,22 +5749,23 @@ } }, "is-core-module": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.6.0.tgz", - "integrity": "sha512-wShG8vs60jKfPWpF2KZRaAtvt3a20OAn7+IJ6hLPECpSABLcKtFKTTI4ZtH5QcBruBHlq+WsdHWyz0BCZW7svQ==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz", + "integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==", "dev": true, "requires": { "has": "^1.0.3" } }, "resolve": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", - "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", + "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", "dev": true, "requires": { - "is-core-module": "^2.2.0", - "path-parse": "^1.0.6" + "is-core-module": "^2.8.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" } }, "through2": { @@ -5733,9 +5800,9 @@ "optional": true }, "nanoid": { - "version": "3.1.23", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.23.tgz", - "integrity": "sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.2.0.tgz", + "integrity": "sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==", "dev": true }, "nanomatch": { @@ -5770,10 +5837,13 @@ "dev": true }, "node-fetch": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", - "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", - "dev": true + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dev": true, + "requires": { + "whatwg-url": "^5.0.0" + } }, "normalize-package-data": { "version": "2.5.0", @@ -6173,6 +6243,12 @@ "sha.js": "^2.4.8" } }, + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "dev": true + }, "picomatch": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", @@ -6237,82 +6313,20 @@ "dev": true }, "postcss": { - "version": "7.0.36", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", - "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", "dev": true, "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" + "picocolors": "^0.2.1", + "source-map": "^0.6.1" }, "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "dependencies": { - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true - }, - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } } } }, @@ -6806,9 +6820,9 @@ } }, "shell-quote": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.2.tgz", - "integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==", + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.3.tgz", + "integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==", "dev": true }, "side-channel": { @@ -7006,9 +7020,9 @@ } }, "source-map-support": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", - "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, "requires": { "buffer-from": "^1.0.0", @@ -7394,6 +7408,12 @@ "has-flag": "^4.0.0" } }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true + }, "sver-compat": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/sver-compat/-/sver-compat-1.5.0.tgz", @@ -7579,6 +7599,12 @@ } } }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", + "dev": true + }, "tsconfig-paths": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz", @@ -7654,9 +7680,9 @@ "dev": true }, "typescript": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.3.tgz", - "integrity": "sha512-qOcYwxaByStAWrBf4x0fibwZvMRG+r4cQoTjbPtUlrWjBHbmCAww1i448U0GJ+3cNNEtebDteo/cHOR3xJ4wEw==", + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz", + "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==", "dev": true }, "umd": { @@ -8012,6 +8038,22 @@ "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", "dev": true }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=", + "dev": true + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "dev": true, + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", @@ -8055,9 +8097,9 @@ }, "dependencies": { "es-abstract": { - "version": "1.18.6", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.6.tgz", - "integrity": "sha512-kAeIT4cku5eNLNuUKhlmtuk1/TRZvQoYccn6TO0cSVdf1kzB0T7+dYuVK9MWM7l+/53W2Q8M7N2c6MQvhXFcUQ==", + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.1.tgz", + "integrity": "sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==", "dev": true, "requires": { "call-bind": "^1.0.2", @@ -8071,7 +8113,9 @@ "is-callable": "^1.2.4", "is-negative-zero": "^2.0.1", "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.1", "is-string": "^1.0.7", + "is-weakref": "^1.0.1", "object-inspect": "^1.11.0", "object-keys": "^1.1.1", "object.assign": "^4.1.2", @@ -8093,9 +8137,9 @@ "dev": true }, "is-negative-zero": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", - "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", "dev": true }, "is-regex": { @@ -8118,9 +8162,9 @@ } }, "object-inspect": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz", - "integrity": "sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", + "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==", "dev": true }, "object.assign": { @@ -8157,42 +8201,6 @@ } } }, - "wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", - "dev": true, - "requires": { - "string-width": "^1.0.2 || 2" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - } - } - }, "word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", @@ -8200,9 +8208,9 @@ "dev": true }, "workerpool": { - "version": "6.1.5", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.5.tgz", - "integrity": "sha512-XdKkCK0Zqc6w3iTxLckiuJ81tiD/o5rBE/m+nXpRCB+/Sq4DqkfXZ/x0jW02DG1tGsfUGXbTJyZDP+eu67haSw==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz", + "integrity": "sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==", "dev": true }, "wrap-ansi": { @@ -8466,9 +8474,9 @@ }, "dependencies": { "camelcase": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true }, "decamelize": { diff --git a/package.json b/package.json index fe5093903af..92afe19ddf3 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "typescript", "author": "Microsoft Corp.", "homepage": "https://www.typescriptlang.org/", - "version": "4.5.0", + "version": "4.7.0", "license": "Apache-2.0", "description": "TypeScript is a language for application scale JavaScript development", "keywords": [ @@ -28,6 +28,7 @@ "engines": { "node": ">=4.2.0" }, + "packageManager": "npm@6.14.15", "devDependencies": { "@octokit/rest": "latest", "@types/browserify": "latest", @@ -61,7 +62,7 @@ "browser-resolve": "^1.11.2", "browserify": "latest", "chai": "latest", - "chalk": "latest", + "chalk": "^4.1.2", "convert-source-map": "latest", "del": "5.1.0", "diff": "^4.0.2", @@ -84,7 +85,7 @@ "mkdirp": "latest", "mocha": "latest", "mocha-fivemat-progress-reporter": "latest", - "ms": "latest", + "ms": "^2.1.3", "node-fetch": "^2.6.1", "plugin-error": "latest", "pretty-hrtime": "^1.0.3", @@ -92,7 +93,7 @@ "q": "latest", "source-map-support": "latest", "through2": "latest", - "typescript": "^4.2.3", + "typescript": "^4.5.5", "vinyl": "latest", "vinyl-sourcemaps-apply": "latest", "xml2js": "^0.4.19" diff --git a/scripts/build/findUpDir.js b/scripts/build/findUpDir.js new file mode 100644 index 00000000000..fab301c6dcd --- /dev/null +++ b/scripts/build/findUpDir.js @@ -0,0 +1,21 @@ +const { join, resolve, dirname } = require("path"); +const { existsSync } = require("fs"); + +// search directories upward to avoid hard-wired paths based on the +// build tree (same as src/harness/findUpDir.ts) + +function findUpFile(name) { + let dir = __dirname; + while (true) { + const fullPath = join(dir, name); + if (existsSync(fullPath)) return fullPath; + const up = resolve(dir, ".."); + if (up === dir) return name; // it'll fail anyway + dir = up; + } +} +exports.findUpFile = findUpFile; + +const findUpRoot = () => + findUpRoot.cached || (findUpRoot.cached = dirname(findUpFile("Gulpfile.js"))); +exports.findUpRoot = findUpRoot; diff --git a/scripts/build/projects.js b/scripts/build/projects.js index 954f37af84e..6f3da08c86e 100644 --- a/scripts/build/projects.js +++ b/scripts/build/projects.js @@ -1,5 +1,7 @@ // @ts-check const { exec, Debouncer } = require("./utils"); +const { resolve } = require("path"); +const { findUpRoot } = require("./findUpDir"); class ProjectQueue { /** @@ -33,7 +35,13 @@ class ProjectQueue { } } -const projectBuilder = new ProjectQueue((projects, lkg, force) => exec(process.execPath, [lkg ? "./lib/tsc" : "./built/local/tsc", "-b", ...(force ? ["--force"] : []), ...projects], { hidePrompt: true })); +const execTsc = (lkg, ...args) => + exec(process.execPath, + [resolve(findUpRoot(), lkg ? "./lib/tsc" : "./built/local/tsc"), + "-b", ...args], + { hidePrompt: true }); + +const projectBuilder = new ProjectQueue((projects, lkg, force) => execTsc(lkg, ...(force ? ["--force"] : []), ...projects)); /** * @param {string} project @@ -43,14 +51,14 @@ const projectBuilder = new ProjectQueue((projects, lkg, force) => exec(process.e */ exports.buildProject = (project, { lkg, force } = {}) => projectBuilder.enqueue(project, { lkg, force }); -const projectCleaner = new ProjectQueue((projects, lkg) => exec(process.execPath, [lkg ? "./lib/tsc" : "./built/local/tsc", "-b", "--clean", ...projects], { hidePrompt: true })); +const projectCleaner = new ProjectQueue((projects, lkg) => execTsc(lkg, "--clean", ...projects)); /** * @param {string} project */ exports.cleanProject = (project) => projectCleaner.enqueue(project); -const projectWatcher = new ProjectQueue((projects) => exec(process.execPath, ["./lib/tsc", "-b", "--watch", ...projects], { hidePrompt: true })); +const projectWatcher = new ProjectQueue((projects) => execTsc(true, "--watch", ...projects)); /** * @param {string} project diff --git a/scripts/build/tests.js b/scripts/build/tests.js index 7fee32566dd..10c6db79cf1 100644 --- a/scripts/build/tests.js +++ b/scripts/build/tests.js @@ -9,6 +9,7 @@ const log = require("fancy-log"); const cmdLineOptions = require("./options"); const { CancellationToken } = require("prex"); const { exec } = require("./utils"); +const { findUpFile } = require("./findUpDir"); const mochaJs = require.resolve("mocha/bin/_mocha"); exports.localBaseline = "tests/baselines/local/"; @@ -73,11 +74,11 @@ async function runConsoleTests(runJs, defaultReporter, runInParallel, watchMode, /** @type {string[]} */ let args = []; - // timeout normally isn"t necessary but Travis-CI has been timing out on compiler baselines occasionally + // timeout normally isn't necessary but Travis-CI has been timing out on compiler baselines occasionally // default timeout is 2sec which really should be enough, but maybe we just need a small amount longer if (!runInParallel) { args.push(mochaJs); - args.push("-R", "scripts/failed-tests"); + args.push("-R", findUpFile("scripts/failed-tests.js")); args.push("-O", '"reporter=' + reporter + (keepFailed ? ",keepFailed=true" : "") + '"'); if (tests) { args.push("-g", `"${tests}"`); diff --git a/scripts/build/utils.js b/scripts/build/utils.js index 6664a2fa67a..3bcad441775 100644 --- a/scripts/build/utils.js +++ b/scripts/build/utils.js @@ -9,12 +9,11 @@ const del = require("del"); const File = require("vinyl"); const ts = require("../../lib/typescript"); const chalk = require("chalk"); +const which = require("which"); const { spawn } = require("child_process"); const { CancellationToken, CancelError, Deferred } = require("prex"); const { Readable, Duplex } = require("stream"); -const isWindows = /^win/.test(process.platform); - /** * Executes the provided command once with the supplied arguments. * @param {string} cmd @@ -27,17 +26,13 @@ const isWindows = /^win/.test(process.platform); * @property {boolean} [hidePrompt] * @property {boolean} [waitForExit=true] */ -function exec(cmd, args, options = {}) { +async function exec(cmd, args, options = {}) { return /**@type {Promise<{exitCode: number}>}*/(new Promise((resolve, reject) => { const { ignoreExitCode, cancelToken = CancellationToken.none, waitForExit = true } = options; cancelToken.throwIfCancellationRequested(); - // TODO (weswig): Update child_process types to add windowsVerbatimArguments to the type definition - const subshellFlag = isWindows ? "/c" : "-c"; - const command = isWindows ? [possiblyQuote(cmd), ...args] : [`${cmd} ${args.join(" ")}`]; - if (!options.hidePrompt) log(`> ${chalk.green(cmd)} ${args.join(" ")}`); - const proc = spawn(isWindows ? "cmd" : "/bin/sh", [subshellFlag, ...command], { stdio: waitForExit ? "inherit" : "ignore", windowsVerbatimArguments: true }); + const proc = spawn(which.sync(cmd), args, { stdio: waitForExit ? "inherit" : "ignore" }); const registration = cancelToken.register(() => { log(`${chalk.red("killing")} '${chalk.green(cmd)} ${args.join(" ")}'...`); proc.kill("SIGINT"); @@ -68,13 +63,6 @@ function exec(cmd, args, options = {}) { } exports.exec = exec; -/** - * @param {string} cmd - */ -function possiblyQuote(cmd) { - return cmd.indexOf(" ") >= 0 ? `"${cmd}"` : cmd; -} - /** * @param {ts.Diagnostic[]} diagnostics * @param {{ cwd?: string, pretty?: boolean }} [options] @@ -440,4 +428,4 @@ class Debouncer { } } } -exports.Debouncer = Debouncer; \ No newline at end of file +exports.Debouncer = Debouncer; diff --git a/scripts/open-cherry-pick-pr.ts b/scripts/open-cherry-pick-pr.ts index 0227a90dc23..07e61b8e3bf 100644 --- a/scripts/open-cherry-pick-pr.ts +++ b/scripts/open-cherry-pick-pr.ts @@ -33,9 +33,9 @@ async function main() { const inputPR = (await gh.pulls.get({ pull_number: +process.env.SOURCE_ISSUE, owner: "microsoft", repo: "TypeScript" })).data; let remoteName = "origin"; - if (inputPR.base.repo.git_url !== `git:github.com/microsoft/TypeScript`) { + if (inputPR.base.repo.git_url !== `git:github.com/microsoft/TypeScript` && inputPR.base.repo.git_url !== `git://github.com/microsoft/TypeScript`) { runSequence([ - ["git", ["remote", "add", "nonlocal", inputPR.base.repo.git_url]] + ["git", ["remote", "add", "nonlocal", inputPR.base.repo.git_url.replace(/^git:(?:\/\/)?/, "https://")]] ]); remoteName = "nonlocal"; } diff --git a/scripts/processDiagnosticMessages.ts b/scripts/processDiagnosticMessages.ts index 53d1ca75db8..fd5f825b848 100644 --- a/scripts/processDiagnosticMessages.ts +++ b/scripts/processDiagnosticMessages.ts @@ -41,7 +41,7 @@ function main(): void { const outputFilesDir = path.dirname(inputFilePath); const thisFilePathRel = path.relative(process.cwd(), outputFilesDir); - const infoFileOutput = buildInfoFileOutput(diagnosticMessages, "./diagnosticInformationMap.generated.ts", thisFilePathRel); + const infoFileOutput = buildInfoFileOutput(diagnosticMessages, `./${path.basename(inputFilePath)}`, thisFilePathRel); checkForUniqueCodes(diagnosticMessages); writeFile("diagnosticInformationMap.generated.ts", infoFileOutput); @@ -62,7 +62,7 @@ function checkForUniqueCodes(diagnosticTable: InputDiagnosticMessageTable) { function buildInfoFileOutput(messageTable: InputDiagnosticMessageTable, inputFilePathRel: string, thisFilePathRel: string): string { let result = "// \r\n" + - "// generated from '" + inputFilePathRel + "' by '" + thisFilePathRel.replace(/\\/g, "/") + "'\r\n" + + "// generated from '" + inputFilePathRel + "' in '" + thisFilePathRel.replace(/\\/g, "/") + "'\r\n" + "/* @internal */\r\n" + "namespace ts {\r\n" + " function diag(code: number, category: DiagnosticCategory, key: string, message: string, reportsUnnecessary?: {}, elidedInCompatabilityPyramid?: boolean, reportsDeprecated?: {}): DiagnosticMessage {\r\n" + diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 9087823617b..1f36ffff644 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -174,14 +174,12 @@ namespace ts { const binder = createBinder(); export function bindSourceFile(file: SourceFile, options: CompilerOptions) { - tracing?.push(tracing.Phase.Bind, "bindSourceFile", { path: file.path }, /*separateBeginAndEnd*/ true); performance.mark("beforeBind"); perfLogger.logStartBindFile("" + file.fileName); binder(file, options); perfLogger.logStopBindFile(); performance.mark("afterBind"); performance.measure("Bind", "beforeBind", "afterBind"); - tracing?.pop(); } function createBinder(): (file: SourceFile, options: CompilerOptions) => void { @@ -253,7 +251,9 @@ namespace ts { Debug.attachFlowNodeDebugInfo(reportedUnreachableFlow); if (!file.locals) { + tracing?.push(tracing.Phase.Bind, "bindSourceFile", { path: file.path }, /*separateBeginAndEnd*/ true); bind(file); + tracing?.pop(); file.symbolCount = symbolCount; file.classifiableNames = classifiableNames; delayedBindJSDocTypedefTag(); @@ -1365,7 +1365,7 @@ namespace ts { } function maybeBindExpressionFlowIfCall(node: Expression) { - // A top level or LHS of comma expression call expression with a dotted function name and at least one argument + // A top level or comma expression call expression with a dotted function name and at least one argument // is potentially an assertion and is therefore included in the control flow. if (node.kind === SyntaxKind.CallExpression) { const call = node as CallExpression; @@ -1550,24 +1550,29 @@ namespace ts { return state; } - function onLeft(left: Expression, state: WorkArea, _node: BinaryExpression) { + function onLeft(left: Expression, state: WorkArea, node: BinaryExpression) { if (!state.skip) { - return maybeBind(left); + const maybeBound = maybeBind(left); + if (node.operatorToken.kind === SyntaxKind.CommaToken) { + maybeBindExpressionFlowIfCall(left); + } + return maybeBound; } } - function onOperator(operatorToken: BinaryOperatorToken, state: WorkArea, node: BinaryExpression) { + function onOperator(operatorToken: BinaryOperatorToken, state: WorkArea, _node: BinaryExpression) { if (!state.skip) { - if (operatorToken.kind === SyntaxKind.CommaToken) { - maybeBindExpressionFlowIfCall(node.left); - } bind(operatorToken); } } - function onRight(right: Expression, state: WorkArea, _node: BinaryExpression) { + function onRight(right: Expression, state: WorkArea, node: BinaryExpression) { if (!state.skip) { - return maybeBind(right); + const maybeBound = maybeBind(right); + if (node.operatorToken.kind === SyntaxKind.CommaToken) { + maybeBindExpressionFlowIfCall(right); + } + return maybeBound; } } @@ -2066,12 +2071,6 @@ namespace ts { seen.set(identifier.escapedText, currentKind); continue; } - - if (currentKind === ElementKind.Property && existingKind === ElementKind.Property) { - const span = getErrorSpanForNode(file, identifier); - file.bindDiagnostics.push(createFileDiagnostic(file, span.start, span.length, - Diagnostics.An_object_literal_cannot_have_multiple_properties_with_the_same_name_in_strict_mode)); - } } } @@ -2341,7 +2340,7 @@ namespace ts { } function checkStrictModeNumericLiteral(node: NumericLiteral) { - if (inStrictMode && node.numericLiteralFlags & TokenFlags.Octal) { + if (languageVersion < ScriptTarget.ES5 && inStrictMode && node.numericLiteralFlags & TokenFlags.Octal) { file.bindDiagnostics.push(createDiagnosticForNode(node, Diagnostics.Octal_literals_are_not_allowed_in_strict_mode)); } } @@ -2374,7 +2373,7 @@ namespace ts { function checkStrictModeLabeledStatement(node: LabeledStatement) { // Grammar checking for labeledStatement - if (inStrictMode && options.target! >= ScriptTarget.ES2015) { + if (inStrictMode && getEmitScriptTarget(options) >= ScriptTarget.ES2015) { if (isDeclarationStatement(node.statement) || isVariableStatement(node.statement)) { errorOnFirstToken(node.label, Diagnostics.A_label_is_not_allowed_here); } @@ -2409,6 +2408,7 @@ namespace ts { return; } setParent(node, parent); + if (tracing) (node as TracingNode).tracingPath = file.path; const saveInStrictMode = inStrictMode; // Even though in the AST the jsdoc @typedef node belongs to the current node, @@ -2947,6 +2947,7 @@ namespace ts { case SyntaxKind.MethodDeclaration: case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: + case SyntaxKind.ClassStaticBlockDeclaration: // this.foo assignment in a JavaScript class // Bind this property to the containing class const containingClass = thisContainer.parent; @@ -3287,7 +3288,7 @@ namespace ts { } if (!isBindingPattern(node.name)) { - if (isInJSFile(node) && isRequireVariableDeclaration(node) && !getJSDocTypeTag(node)) { + if (isInJSFile(node) && isVariableDeclarationInitializedToBareOrAccessedRequire(node) && !getJSDocTypeTag(node)) { declareSymbolAndAddToSymbolTable(node as Declaration, SymbolFlags.Alias, SymbolFlags.AliasExcludes); } else if (isBlockOrCatchScoped(node)) { diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index 2b578cecfc0..aec8cae3cea 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -258,6 +258,14 @@ namespace ts { Debug.assert(!state.seenAffectedFiles || !state.seenAffectedFiles.size); state.seenAffectedFiles = state.seenAffectedFiles || new Set(); } + if (useOldState) { + // Any time the interpretation of a source file changes, mark it as changed + forEachEntry(oldState!.fileInfos, (info, sourceFilePath) => { + if (state.fileInfos.has(sourceFilePath) && state.fileInfos.get(sourceFilePath)!.impliedFormat !== info.impliedFormat) { + state.changedFilesSet.add(sourceFilePath); + } + }); + } state.buildInfoEmitPending = !!state.changedFilesSet.size; return state; @@ -744,13 +752,13 @@ namespace ts { const actualSignature = signature ?? value.signature; return value.version === actualSignature ? value.affectsGlobalScope ? - { version: value.version, signature: undefined, affectsGlobalScope: true } : + { version: value.version, signature: undefined, affectsGlobalScope: true, impliedFormat: value.impliedFormat } : value.version : actualSignature !== undefined ? signature === undefined ? value : - { version: value.version, signature, affectsGlobalScope: value.affectsGlobalScope } : - { version: value.version, signature: false, affectsGlobalScope: value.affectsGlobalScope }; + { version: value.version, signature, affectsGlobalScope: value.affectsGlobalScope, impliedFormat: value.impliedFormat } : + { version: value.version, signature: false, affectsGlobalScope: value.affectsGlobalScope, impliedFormat: value.impliedFormat }; }); let referencedMap: ProgramBuildInfoReferencedMap | undefined; @@ -1243,10 +1251,10 @@ namespace ts { export function toBuilderStateFileInfo(fileInfo: ProgramBuildInfoFileInfo): BuilderState.FileInfo { return isString(fileInfo) ? - { version: fileInfo, signature: fileInfo, affectsGlobalScope: undefined } : + { version: fileInfo, signature: fileInfo, affectsGlobalScope: undefined, impliedFormat: undefined } : isString(fileInfo.signature) ? fileInfo as BuilderState.FileInfo : - { version: fileInfo.version, signature: fileInfo.signature === false ? undefined : fileInfo.version, affectsGlobalScope: fileInfo.affectsGlobalScope }; + { version: fileInfo.version, signature: fileInfo.signature === false ? undefined : fileInfo.version, affectsGlobalScope: fileInfo.affectsGlobalScope, impliedFormat: fileInfo.impliedFormat }; } export function createBuildProgramUsingProgramBuildInfo(program: ProgramBuildInfo, buildInfoPath: string, host: ReadBuildProgramHost): EmitAndSemanticDiagnosticsBuilderProgram { diff --git a/src/compiler/builderState.ts b/src/compiler/builderState.ts index 6578962ebf0..a64a6c13ed1 100644 --- a/src/compiler/builderState.ts +++ b/src/compiler/builderState.ts @@ -82,6 +82,7 @@ namespace ts { readonly version: string; signature: string | undefined; affectsGlobalScope: boolean | undefined; + impliedFormat: number | undefined; } export interface ReadonlyManyToManyPathMap { @@ -332,7 +333,7 @@ namespace ts { } } } - fileInfos.set(sourceFile.resolvedPath, { version, signature: oldInfo && oldInfo.signature, affectsGlobalScope: isFileAffectingGlobalScope(sourceFile) || undefined }); + fileInfos.set(sourceFile.resolvedPath, { version, signature: oldInfo && oldInfo.signature, affectsGlobalScope: isFileAffectingGlobalScope(sourceFile) || undefined, impliedFormat: sourceFile.impliedNodeFormat }); } return { @@ -433,7 +434,7 @@ namespace ts { ); const firstDts = firstOrUndefined(emitOutput.outputFiles); if (firstDts) { - Debug.assert(fileExtensionIs(firstDts.name, Extension.Dts), "File extension for signature expected to be dts", () => `Found: ${getAnyExtensionFromPath(firstDts.name)} for ${firstDts.name}:: All output files: ${JSON.stringify(emitOutput.outputFiles.map(f => f.name))}`); + Debug.assert(fileExtensionIsOneOf(firstDts.name, [Extension.Dts, Extension.Dmts, Extension.Dcts]), "File extension for signature expected to be dts", () => `Found: ${getAnyExtensionFromPath(firstDts.name)} for ${firstDts.name}:: All output files: ${JSON.stringify(emitOutput.outputFiles.map(f => f.name))}`); latestSignature = (computeHash || generateDjb2Hash)(firstDts.text); if (exportedModulesMapCache && latestSignature !== prevSignature) { updateExportedModules(sourceFile, emitOutput.exportedModulesFromDeclarationEmit, exportedModulesMapCache); diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 53bd6e14c23..c15a0ad2e5e 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -134,6 +134,9 @@ namespace ts { EmptyObjectStrictFacts = All & ~(EQUndefined | EQNull | EQUndefinedOrNull), AllTypeofNE = TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | NEUndefined, EmptyObjectFacts = All, + // Masks + OrFactsMask = TypeofEQFunction | TypeofNEObject, + AndFactsMask = All & ~OrFactsMask, } const typeofEQFacts: ReadonlyESMap = new Map(getEntries({ @@ -172,12 +175,15 @@ namespace ts { } 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 + 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 } const enum SignatureCheckMode { @@ -193,8 +199,14 @@ namespace ts { Source = 1 << 0, Target = 1 << 1, PropertyCheck = 1 << 2, - UnionIntersectionCheck = 1 << 3, - InPropertyCheck = 1 << 4, + InPropertyCheck = 1 << 3, + } + + const enum RecursionFlags { + None = 0, + Source = 1 << 0, + Target = 1 << 1, + Both = Source | Target, } const enum MappedTypeModifiers { @@ -303,7 +315,7 @@ namespace ts { host.getSourceFiles().forEach(sf => { if (!sf.resolvedModules) return; - forEachEntry(sf.resolvedModules, r => { + sf.resolvedModules.forEach(r => { if (r && r.packageId) map.set(r.packageId.name, r.extension === Extension.Dts || !!map.get(r.packageId.name)); }); }); @@ -333,6 +345,7 @@ namespace ts { let totalInstantiationCount = 0; let instantiationCount = 0; let instantiationDepth = 0; + let inlineLevel = 0; let currentNode: Node | undefined; const emptySymbols = createSymbolTable(); @@ -402,6 +415,7 @@ namespace ts { 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."); @@ -424,6 +438,7 @@ namespace ts { getIndexInfosOfType, getSignaturesOfType, getIndexTypeOfType: (type, kind) => getIndexTypeOfType(type, kind === IndexKind.String ? stringType : numberType), + getIndexType: type => getIndexType(type), getBaseTypes, getBaseTypeOfLiteralType, getWidenedType, @@ -720,6 +735,8 @@ namespace ts { getLocalTypeParametersOfClassOrInterfaceOrTypeAlias, isDeclarationVisible, isPropertyAccessible, + getTypeOnlyAliasDeclaration, + getMemberOverrideModifierStatus, }; function getResolvedSignatureWorker(nodeIn: CallLikeExpression, candidatesOutArray: Signature[] | undefined, argumentCount: number | undefined, checkMode: CheckMode): Signature | undefined { @@ -747,14 +764,18 @@ namespace ts { const unknownSymbol = createSymbol(SymbolFlags.Property, "unknown" as __String); const resolvingSymbol = createSymbol(0, InternalSymbolName.Resolving); + const unresolvedSymbols = new Map(); + const errorTypes = new Map(); const anyType = createIntrinsicType(TypeFlags.Any, "any"); const autoType = createIntrinsicType(TypeFlags.Any, "any"); const wildcardType = createIntrinsicType(TypeFlags.Any, "any"); const errorType = createIntrinsicType(TypeFlags.Any, "error"); + const unresolvedType = createIntrinsicType(TypeFlags.Any, "unresolved"); const nonInferrableAnyType = createIntrinsicType(TypeFlags.Any, "any", ObjectFlags.ContainsWideningType); const intrinsicMarkerType = createIntrinsicType(TypeFlags.Any, "intrinsic"); const unknownType = createIntrinsicType(TypeFlags.Unknown, "unknown"); + const nonNullUnknownType = createIntrinsicType(TypeFlags.Unknown, "unknown"); const undefinedType = createIntrinsicType(TypeFlags.Undefined, "undefined"); const undefinedWideningType = strictNullChecks ? undefinedType : createIntrinsicType(TypeFlags.Undefined, "undefined", ObjectFlags.ContainsWideningType); const optionalType = createIntrinsicType(TypeFlags.Undefined, "undefined"); @@ -912,28 +933,30 @@ namespace ts { // and they will not get an error from not having unrelated library files let deferredGlobalESSymbolConstructorSymbol: Symbol | undefined; let deferredGlobalESSymbolConstructorTypeSymbol: Symbol | undefined; - let deferredGlobalESSymbolType: ObjectType; + let deferredGlobalESSymbolType: ObjectType | undefined; let deferredGlobalTypedPropertyDescriptorType: GenericType; - let deferredGlobalPromiseType: GenericType; - let deferredGlobalPromiseLikeType: GenericType; + let deferredGlobalPromiseType: GenericType | undefined; + let deferredGlobalPromiseLikeType: GenericType | undefined; let deferredGlobalPromiseConstructorSymbol: Symbol | undefined; - let deferredGlobalPromiseConstructorLikeType: ObjectType; - let deferredGlobalIterableType: GenericType; - let deferredGlobalIteratorType: GenericType; - let deferredGlobalIterableIteratorType: GenericType; - let deferredGlobalGeneratorType: GenericType; - let deferredGlobalIteratorYieldResultType: GenericType; - let deferredGlobalIteratorReturnResultType: GenericType; - let deferredGlobalAsyncIterableType: GenericType; - let deferredGlobalAsyncIteratorType: GenericType; - let deferredGlobalAsyncIterableIteratorType: GenericType; - let deferredGlobalAsyncGeneratorType: GenericType; - let deferredGlobalTemplateStringsArrayType: ObjectType; + let deferredGlobalPromiseConstructorLikeType: ObjectType | undefined; + let deferredGlobalIterableType: GenericType | undefined; + let deferredGlobalIteratorType: GenericType | undefined; + let deferredGlobalIterableIteratorType: GenericType | undefined; + let deferredGlobalGeneratorType: GenericType | undefined; + let deferredGlobalIteratorYieldResultType: GenericType | undefined; + let deferredGlobalIteratorReturnResultType: GenericType | undefined; + let deferredGlobalAsyncIterableType: GenericType | undefined; + let deferredGlobalAsyncIteratorType: GenericType | undefined; + let deferredGlobalAsyncIterableIteratorType: GenericType | undefined; + let deferredGlobalAsyncGeneratorType: GenericType | undefined; + let deferredGlobalTemplateStringsArrayType: ObjectType | undefined; let deferredGlobalImportMetaType: ObjectType; let deferredGlobalImportMetaExpressionType: ObjectType; - let deferredGlobalExtractSymbol: Symbol; - let deferredGlobalOmitSymbol: Symbol; - let deferredGlobalBigIntType: ObjectType; + let deferredGlobalImportCallOptionsType: ObjectType | undefined; + let deferredGlobalExtractSymbol: Symbol | undefined; + let deferredGlobalOmitSymbol: Symbol | undefined; + let deferredGlobalAwaitedSymbol: Symbol | undefined; + let deferredGlobalBigIntType: ObjectType | undefined; const allPotentiallyUnusedIdentifiers = new Map(); // key is file name @@ -1000,6 +1023,22 @@ namespace ts { const builtinGlobals = createSymbolTable(); builtinGlobals.set(undefinedSymbol.escapedName, undefinedSymbol); + // Extensions suggested for path imports when module resolution is node12 or higher. + // The first element of each tuple is the extension a file has. + // The second element of each tuple is the extension that should be used in a path import. + // e.g. if we want to import file `foo.mts`, we should write `import {} from "./foo.mjs". + const suggestedExtensions: [string, string][] = [ + [".mts", ".mjs"], + [".ts", ".js"], + [".cts", ".cjs"], + [".mjs", ".mjs"], + [".js", ".js"], + [".cjs", ".cjs"], + [".tsx", compilerOptions.jsx === JsxEmit.Preserve ? ".jsx" : ".js"], + [".jsx", ".jsx"], + [".json", ".json"], + ]; + initializeTypeChecker(); return checker; @@ -1028,17 +1067,9 @@ namespace ts { } } else { - if (file.localJsxNamespace) { - return file.localJsxNamespace; - } - const jsxPragma = file.pragmas.get("jsx"); - if (jsxPragma) { - const chosenPragma = isArray(jsxPragma) ? jsxPragma[0] : jsxPragma; - file.localJsxFactory = parseIsolatedEntityName(chosenPragma.arguments.factory, languageVersion); - visitNode(file.localJsxFactory, markAsSynthetic); - if (file.localJsxFactory) { - return file.localJsxNamespace = getFirstIdentifier(file.localJsxFactory).escapedText; - } + const localJsxNamespace = getLocalJsxNamespace(file); + if (localJsxNamespace) { + return file.localJsxNamespace = localJsxNamespace; } } } @@ -1060,11 +1091,26 @@ namespace ts { _jsxFactoryEntity = factory.createQualifiedName(factory.createIdentifier(unescapeLeadingUnderscores(_jsxNamespace)), "createElement"); } return _jsxNamespace; + } - function markAsSynthetic(node: Node): VisitResult { - setTextRangePosEnd(node, -1, -1); - return visitEachChild(node, markAsSynthetic, nullTransformationContext); + function getLocalJsxNamespace(file: SourceFile): __String | undefined { + if (file.localJsxNamespace) { + return file.localJsxNamespace; } + const jsxPragma = file.pragmas.get("jsx"); + if (jsxPragma) { + const chosenPragma = isArray(jsxPragma) ? jsxPragma[0] : jsxPragma; + file.localJsxFactory = parseIsolatedEntityName(chosenPragma.arguments.factory, languageVersion); + visitNode(file.localJsxFactory, markAsSynthetic); + if (file.localJsxFactory) { + return file.localJsxNamespace = getFirstIdentifier(file.localJsxFactory).escapedText; + } + } + } + + function markAsSynthetic(node: Node): VisitResult { + setTextRangePosEnd(node, -1, -1); + return visitEachChild(node, markAsSynthetic, nullTransformationContext); } function getEmitResolver(sourceFile: SourceFile, cancellationToken: CancellationToken) { @@ -1154,6 +1200,10 @@ namespace ts { return diagnostic; } + function isDeprecatedSymbol(symbol: Symbol) { + return !!(getDeclarationNodeFlagsFromSymbol(symbol) & NodeFlags.Deprecated); + } + function addDeprecatedSuggestion(location: Node, declarations: Node[], deprecatedEntity: string) { const diagnostic = createDiagnosticForNode(location, Diagnostics._0_is_deprecated, deprecatedEntity); return addDeprecatedSuggestionWorker(declarations, diagnostic); @@ -1269,13 +1319,14 @@ namespace ts { else { // error const isEitherEnum = !!(target.flags & SymbolFlags.Enum || source.flags & SymbolFlags.Enum); const isEitherBlockScoped = !!(target.flags & SymbolFlags.BlockScopedVariable || source.flags & SymbolFlags.BlockScopedVariable); - const message = isEitherEnum - ? Diagnostics.Enum_declarations_can_only_merge_with_namespace_or_other_enum_declarations - : isEitherBlockScoped - ? Diagnostics.Cannot_redeclare_block_scoped_variable_0 - : Diagnostics.Duplicate_identifier_0; + const 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 @@ -1286,12 +1337,12 @@ namespace ts { ({ firstFile, secondFile, conflictingSymbols: new Map() } as DuplicateInfoForFiles)); const conflictingSymbolInfo = getOrUpdate(filesDuplicates.conflictingSymbols, symbolName, () => ({ isBlockScoped: isEitherBlockScoped, firstFileLocations: [], secondFileLocations: [] } as DuplicateInfoForSymbol)); - addDuplicateLocations(conflictingSymbolInfo.firstFileLocations, source); - addDuplicateLocations(conflictingSymbolInfo.secondFileLocations, target); + if (!isSourcePlainJs) addDuplicateLocations(conflictingSymbolInfo.firstFileLocations, source); + if (!isTargetPlainJs) addDuplicateLocations(conflictingSymbolInfo.secondFileLocations, target); } else { - addDuplicateDeclarationErrorsForSymbols(source, message, symbolName, target); - addDuplicateDeclarationErrorsForSymbols(target, message, symbolName, source); + if (!isSourcePlainJs) addDuplicateDeclarationErrorsForSymbols(source, message, symbolName, target); + if (!isTargetPlainJs) addDuplicateDeclarationErrorsForSymbols(target, message, symbolName, source); } } return target; @@ -1519,7 +1570,7 @@ namespace ts { } else if (isParameterPropertyDeclaration(declaration, declaration.parent)) { // foo = this.bar is illegal in esnext+useDefineForClassFields when bar is a parameter property - return !(compilerOptions.target === ScriptTarget.ESNext && useDefineForClassFields + return !(getEmitScriptTarget(compilerOptions) === ScriptTarget.ESNext && useDefineForClassFields && getContainingClass(declaration) === getContainingClass(usage) && isUsedInFunctionOrInstanceProperty(usage, declaration)); } @@ -1550,7 +1601,7 @@ namespace ts { return true; } if (isUsedInFunctionOrInstanceProperty(usage, declaration)) { - if (compilerOptions.target === ScriptTarget.ESNext && useDefineForClassFields + if (getEmitScriptTarget(compilerOptions) === ScriptTarget.ESNext && useDefineForClassFields && getContainingClass(declaration) && (isPropertyDeclaration(declaration) || isParameterPropertyDeclaration(declaration, declaration.parent))) { return !isPropertyImmediatelyReferencedWithinDeclaration(declaration, usage, /*stopAtAnyPropertyDeclaration*/ true); @@ -1743,8 +1794,8 @@ namespace ts { nameArg: __String | Identifier | undefined, isUse: boolean, excludeGlobals = false, - issueSuggestions?: boolean): Symbol | undefined { - return resolveNameHelper(location, name, meaning, nameNotFoundMessage, nameArg, isUse, excludeGlobals, getSymbol, issueSuggestions); + getSpellingSuggstions = true): Symbol | undefined { + return resolveNameHelper(location, name, meaning, nameNotFoundMessage, nameArg, isUse, excludeGlobals, getSpellingSuggstions, getSymbol); } function resolveNameHelper( @@ -1755,7 +1806,8 @@ namespace ts { nameArg: __String | Identifier | undefined, isUse: boolean, excludeGlobals: boolean, - lookup: typeof getSymbol, issueSuggestions?: boolean): Symbol | undefined { + getSpellingSuggestions: boolean, + lookup: typeof getSymbol): Symbol | undefined { const originalLocation = location; // needed for did-you-mean error reporting, which gathers candidates starting from the original location let result: Symbol | undefined; let lastLocation: Node | undefined; @@ -1784,6 +1836,8 @@ namespace ts { // type parameters are visible in parameter list, return type and type parameter list ? lastLocation === (location as FunctionLikeDeclaration).type || lastLocation.kind === SyntaxKind.Parameter || + lastLocation.kind === SyntaxKind.JSDocParameterTag || + lastLocation.kind === SyntaxKind.JSDocReturnTag || lastLocation.kind === SyntaxKind.TypeParameter // local types not visible outside the function body : false; @@ -1828,7 +1882,7 @@ namespace ts { isInExternalModule = true; // falls through case SyntaxKind.ModuleDeclaration: - const moduleExports = getSymbolOfNode(location as SourceFile | ModuleDeclaration).exports || emptySymbols; + const moduleExports = getSymbolOfNode(location as SourceFile | ModuleDeclaration)?.exports || emptySymbols; if (location.kind === SyntaxKind.SourceFile || (isModuleDeclaration(location) && location.flags & NodeFlags.Ambient && !isGlobalScopeAugmentation(location))) { // It's an external module. First see if the module has an export default and if the local @@ -1871,7 +1925,7 @@ namespace ts { } break; case SyntaxKind.EnumDeclaration: - if (result = lookup(getSymbolOfNode(location)!.exports!, name, meaning & SymbolFlags.EnumMember)) { + if (result = lookup(getSymbolOfNode(location)?.exports || emptySymbols, name, meaning & SymbolFlags.EnumMember)) { break loop; } break; @@ -1954,7 +2008,7 @@ namespace ts { case SyntaxKind.ArrowFunction: // when targeting ES6 or higher there is no 'arguments' in an arrow function // for lower compile targets the resolved symbol is used to emit an error - if (compilerOptions.target! >= ScriptTarget.ES2015) { + if (getEmitScriptTarget(compilerOptions) >= ScriptTarget.ES2015) { break; } // falls through @@ -2052,8 +2106,8 @@ namespace ts { lastSelfReferenceLocation = location; } lastLocation = location; - location = isJSDocTemplateTag(location) ? - getEffectiveContainerForJSDocTemplateTag(location) || location.parent : + location = isJSDocTemplateTag(location) ? getEffectiveContainerForJSDocTemplateTag(location) || location.parent : + isJSDocParameterTag(location) || isJSDocReturnTag(location) ? getHostSignatureFromJSDoc(location) || location.parent : location.parent; } @@ -2084,7 +2138,7 @@ namespace ts { } } if (!result) { - if (nameNotFoundMessage) { + if (nameNotFoundMessage && produceDiagnostics) { if (!errorLocation || !checkAndReportErrorForMissingPrefix(errorLocation, name, nameArg!) && // TODO: GH#18217 !checkAndReportErrorForExtendingInterface(errorLocation) && @@ -2094,7 +2148,7 @@ namespace ts { !checkAndReportErrorForUsingNamespaceModuleAsValue(errorLocation, name, meaning) && !checkAndReportErrorForUsingValueAsType(errorLocation, name, meaning)) { let suggestion: Symbol | undefined; - if (issueSuggestions && suggestionCount < maximumSuggestionCount) { + if (getSpellingSuggestions && suggestionCount < maximumSuggestionCount) { suggestion = getSuggestedSymbolForNonexistentSymbol(originalLocation, name, meaning); const isGlobalScopeAugmentationDeclaration = suggestion?.valueDeclaration && isAmbientModule(suggestion.valueDeclaration) && isGlobalScopeAugmentation(suggestion.valueDeclaration); if (isGlobalScopeAugmentationDeclaration) { @@ -2103,10 +2157,11 @@ namespace ts { if (suggestion) { const suggestionName = symbolToString(suggestion); const isUncheckedJS = isUncheckedJSSuggestion(originalLocation, suggestion, /*excludeClasses*/ false); - const message = isUncheckedJS ? Diagnostics.Could_not_find_name_0_Did_you_mean_1 : Diagnostics.Cannot_find_name_0_Did_you_mean_1; + const message = meaning === SymbolFlags.Namespace || nameArg && typeof nameArg !== "string" && nodeIsSynthesized(nameArg) ? Diagnostics.Cannot_find_namespace_0_Did_you_mean_1 + : isUncheckedJS ? Diagnostics.Could_not_find_name_0_Did_you_mean_1 + : Diagnostics.Cannot_find_name_0_Did_you_mean_1; const diagnostic = createError(errorLocation, message, diagnosticName(nameArg!), suggestionName); addErrorOrSuggestion(!isUncheckedJS, diagnostic); - if (suggestion.valueDeclaration) { addRelatedInfo( diagnostic, @@ -2133,8 +2188,8 @@ namespace ts { } // Perform extra checks only if error reporting was requested - if (nameNotFoundMessage) { - if (propertyWithInvalidInitializer && !(compilerOptions.target === ScriptTarget.ESNext && useDefineForClassFields)) { + if (nameNotFoundMessage && produceDiagnostics) { + if (propertyWithInvalidInitializer && !(getEmitScriptTarget(compilerOptions) === ScriptTarget.ESNext && useDefineForClassFields)) { // We have a match, but the reference occurred within a property initializer and the identifier also binds // to a local variable in the constructor where the code will be emitted. Note that this is actually allowed // with ESNext+useDefineForClassFields because the scope semantics are different. @@ -2185,37 +2240,30 @@ namespace ts { error(errorLocation, Diagnostics.Parameter_0_cannot_reference_identifier_1_declared_after_it, declarationNameToString(associatedDeclarationForContainingInitializerOrBindingName.name), declarationNameToString(errorLocation as Identifier)); } } - if (result && errorLocation && meaning & SymbolFlags.Value && result.flags & SymbolFlags.Alias) { - checkSymbolUsageInExpressionContext(result, name, errorLocation); + if (result && errorLocation && meaning & SymbolFlags.Value && result.flags & SymbolFlags.Alias && !(result.flags & SymbolFlags.Value) && !isValidTypeOnlyAliasUseSite(errorLocation)) { + const typeOnlyDeclaration = getTypeOnlyAliasDeclaration(result); + if (typeOnlyDeclaration) { + const message = typeOnlyDeclaration.kind === SyntaxKind.ExportSpecifier + ? Diagnostics._0_cannot_be_used_as_a_value_because_it_was_exported_using_export_type + : Diagnostics._0_cannot_be_used_as_a_value_because_it_was_imported_using_import_type; + const unescapedName = unescapeLeadingUnderscores(name); + addTypeOnlyDeclarationRelatedInfo( + error(errorLocation, message, unescapedName), + typeOnlyDeclaration, + unescapedName); + } } } return result; } - function checkSymbolUsageInExpressionContext(symbol: Symbol, name: __String, useSite: Node) { - if (!isValidTypeOnlyAliasUseSite(useSite)) { - const typeOnlyDeclaration = getTypeOnlyAliasDeclaration(symbol); - if (typeOnlyDeclaration) { - const isExport = typeOnlyDeclarationIsExport(typeOnlyDeclaration); - const message = isExport - ? 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(useSite, message, unescapedName), - typeOnlyDeclaration, - unescapedName); - } - } - } - function addTypeOnlyDeclarationRelatedInfo(diagnostic: Diagnostic, typeOnlyDeclaration: TypeOnlyCompatibleAliasDeclaration | undefined, unescapedName: string) { if (!typeOnlyDeclaration) return diagnostic; return addRelatedInfo( diagnostic, createDiagnosticForNode( typeOnlyDeclaration, - typeOnlyDeclarationIsExport(typeOnlyDeclaration) ? Diagnostics._0_was_exported_here : Diagnostics._0_was_imported_here, + typeOnlyDeclaration.kind === SyntaxKind.ExportSpecifier ? Diagnostics._0_was_exported_here : Diagnostics._0_was_imported_here, unescapedName)); } @@ -2495,10 +2543,12 @@ namespace ts { /* 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 === stopAt || isFunctionLike(n) ? "quit" : n === parent); + return !!parent && !!findAncestor(initial, n => n === parent + || (n === stopAt || isFunctionLike(n) && !getImmediatelyInvokedFunctionExpression(n) ? "quit" : false)); } function getAnyImportSyntax(node: Node): AnyImportSyntax | undefined { @@ -2552,7 +2602,7 @@ namespace ts { && isAliasableOrJsExpression(node.parent.right) || node.kind === SyntaxKind.ShorthandPropertyAssignment || node.kind === SyntaxKind.PropertyAssignment && isAliasableOrJsExpression((node as PropertyAssignment).initializer) - || isRequireVariableDeclaration(node); + || isVariableDeclarationInitializedToBareOrAccessedRequire(node); } function isAliasableOrJsExpression(e: Expression) { @@ -2583,7 +2633,7 @@ namespace ts { function checkAndReportErrorForResolvingImportAliasToTypeOnlySymbol(node: ImportEqualsDeclaration, resolved: Symbol | undefined) { if (markSymbolOfAliasDeclarationIfTypeOnly(node, /*immediateTarget*/ undefined, resolved, /*overwriteEmpty*/ false) && !node.isTypeOnly) { const typeOnlyDeclaration = getTypeOnlyAliasDeclaration(getSymbolOfNode(node))!; - const isExport = typeOnlyDeclarationIsExport(typeOnlyDeclaration); + const isExport = typeOnlyDeclaration.kind === SyntaxKind.ExportSpecifier; const message = isExport ? Diagnostics.An_import_alias_cannot_reference_a_declaration_that_was_exported_using_export_type : Diagnostics.An_import_alias_cannot_reference_a_declaration_that_was_imported_using_import_type; @@ -2591,9 +2641,7 @@ namespace ts { ? Diagnostics._0_was_exported_here : Diagnostics._0_was_imported_here; - // Non-null assertion is safe because the optionality comes from ImportClause, - // but if an ImportClause was the typeOnlyDeclaration, it had to have a `name`. - const name = unescapeLeadingUnderscores(typeOnlyDeclaration.name!.escapedText); + const name = unescapeLeadingUnderscores(typeOnlyDeclaration.name.escapedText); addRelatedInfo(error(node.moduleReference, message), createDiagnosticForNode(typeOnlyDeclaration, relatedMessage, name)); } } @@ -2610,7 +2658,28 @@ namespace ts { return ((isExportAssignment(node) && !node.isExportEquals) || hasSyntacticModifier(node, ModifierFlags.Default) || isExportSpecifier(node)); } - function canHaveSyntheticDefault(file: SourceFile | undefined, moduleSymbol: Symbol, dontResolveAlias: boolean) { + function getUsageModeForExpression(usage: Expression) { + return isStringLiteralLike(usage) ? getModeForUsageLocation(getSourceFileOfNode(usage), usage) : undefined; + } + + function isESMFormatImportImportingCommonjsFormatFile(usageMode: SourceFile["impliedNodeFormat"], targetMode: SourceFile["impliedNodeFormat"]) { + return usageMode === ModuleKind.ESNext && targetMode === ModuleKind.CommonJS; + } + + function isOnlyImportedAsDefault(usage: Expression) { + const usageMode = getUsageModeForExpression(usage); + return usageMode === ModuleKind.ESNext && endsWith((usage as StringLiteralLike).text, Extension.Json); + } + + function canHaveSyntheticDefault(file: SourceFile | undefined, moduleSymbol: Symbol, dontResolveAlias: boolean, usage: Expression) { + const usageMode = file && getUsageModeForExpression(usage); + if (file && usageMode !== undefined) { + const result = isESMFormatImportImportingCommonjsFormatFile(usageMode, file.impliedNodeFormat); + if (usageMode === ModuleKind.ESNext || result) { + return result; + } + // fallthrough on cjs usages so we imply defaults for interop'd imports, too + } if (!allowSyntheticDefaultImports) { return false; } @@ -2653,8 +2722,9 @@ namespace ts { } const file = moduleSymbol.declarations?.find(isSourceFile); - const hasSyntheticDefault = canHaveSyntheticDefault(file, moduleSymbol, dontResolveAlias); - if (!exportDefaultSymbol && !hasSyntheticDefault) { + const hasDefaultOnly = isOnlyImportedAsDefault(node.parent.moduleSpecifier); + const hasSyntheticDefault = canHaveSyntheticDefault(file, moduleSymbol, dontResolveAlias, node.parent.moduleSpecifier); + if (!exportDefaultSymbol && !hasSyntheticDefault && !hasDefaultOnly) { if (hasExportAssignmentSymbol(moduleSymbol)) { const compilerOptionName = moduleKind >= ModuleKind.ES2015 ? "allowSyntheticDefaultImports" : "esModuleInterop"; const exportEqualsSymbol = moduleSymbol.exports!.get(InternalSymbolName.ExportEquals); @@ -2673,7 +2743,7 @@ namespace ts { reportNonDefaultExport(moduleSymbol, node); } } - else if (hasSyntheticDefault) { + else if (hasSyntheticDefault || hasDefaultOnly) { // per emit behavior, a synthetic default overrides a "real" .default member if `__esModule` is not present const resolved = resolveExternalModuleSymbol(moduleSymbol, dontResolveAlias) || resolveSymbol(moduleSymbol, dontResolveAlias); markSymbolOfAliasDeclarationIfTypeOnly(node, moduleSymbol, resolved, /*overwriteTypeOnly*/ false); @@ -2783,7 +2853,7 @@ namespace ts { if (!isIdentifier(name)) { return undefined; } - const suppressInteropError = name.escapedText === InternalSymbolName.Default && !!(compilerOptions.allowSyntheticDefaultImports || compilerOptions.esModuleInterop); + const suppressInteropError = name.escapedText === InternalSymbolName.Default && !!(compilerOptions.allowSyntheticDefaultImports || getESModuleInterop(compilerOptions)); const targetSymbol = resolveESModuleSymbol(moduleSymbol, moduleSpecifier, /*dontResolveAlias*/ false, suppressInteropError); if (targetSymbol) { if (name.escapedText) { @@ -2805,7 +2875,7 @@ namespace ts { let symbolFromModule = getExportOfModule(targetSymbol, name, specifier, dontResolveAlias); if (symbolFromModule === undefined && name.escapedText === InternalSymbolName.Default) { const file = moduleSymbol.declarations?.find(isSourceFile); - if (canHaveSyntheticDefault(file, moduleSymbol, dontResolveAlias)) { + if (isOnlyImportedAsDefault(moduleSpecifier) || canHaveSyntheticDefault(file, moduleSymbol, dontResolveAlias, moduleSpecifier)) { symbolFromModule = resolveExternalModuleSymbol(moduleSymbol, dontResolveAlias) || resolveSymbol(moduleSymbol, dontResolveAlias); } } @@ -2872,18 +2942,18 @@ namespace ts { function reportInvalidImportEqualsExportMember(node: ImportDeclaration | ExportDeclaration | VariableDeclaration, name: Identifier, declarationName: string, moduleName: string) { if (moduleKind >= ModuleKind.ES2015) { - const message = compilerOptions.esModuleInterop ? Diagnostics._0_can_only_be_imported_by_using_a_default_import : + 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 = compilerOptions.esModuleInterop ? Diagnostics._0_can_only_be_imported_by_using_a_require_call_or_by_using_a_default_import : + 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 = compilerOptions.esModuleInterop ? Diagnostics._0_can_only_be_imported_by_using_import_1_require_2_or_a_default_import : + 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); } @@ -3047,13 +3117,13 @@ namespace ts { * 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. - * @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` */ function markSymbolOfAliasDeclarationIfTypeOnly( aliasDeclaration: Declaration | undefined, @@ -3087,7 +3157,7 @@ namespace ts { } /** Indicates that a symbol directly or indirectly resolves to a type-only import or export. */ - function getTypeOnlyAliasDeclaration(symbol: Symbol): TypeOnlyCompatibleAliasDeclaration | undefined { + function getTypeOnlyAliasDeclaration(symbol: Symbol): TypeOnlyAliasDeclaration | undefined { if (!(symbol.flags & SymbolFlags.Alias)) { return undefined; } @@ -3203,7 +3273,7 @@ namespace ts { if (name.kind === SyntaxKind.Identifier) { const message = meaning === namespaceMeaning || nodeIsSynthesized(name) ? Diagnostics.Cannot_find_namespace_0 : getCannotFindNameDiagnosticForName(getFirstIdentifier(name)); const symbolFromJSPrototype = isInJSFile(name) && !nodeIsSynthesized(name) ? resolveEntityNameFromAssignmentDeclaration(name, meaning) : undefined; - symbol = getMergedSymbol(resolveName(location || name, name.escapedText, meaning, ignoreErrors || symbolFromJSPrototype ? undefined : message, name, /*isUse*/ true)); + symbol = getMergedSymbol(resolveName(location || name, name.escapedText, meaning, ignoreErrors || symbolFromJSPrototype ? undefined : message, name, /*isUse*/ true, false)); if (!symbol) { return getMergedSymbol(symbolFromJSPrototype); } @@ -3240,21 +3310,40 @@ namespace ts { const namespaceName = getFullyQualifiedName(namespace); const declarationName = declarationNameToString(right); const suggestionForNonexistentModule = getSuggestedSymbolForNonexistentModule(right, namespace); - const exportedTypeSymbol = getMergedSymbol(getSymbol(getExportsOfSymbol(namespace), right.escapedText, SymbolFlags.Type)); - const containingQualifiedName = isQualifiedName(name) && getContainingQualifiedNameNode(name); - const canSuggestTypeof = containingQualifiedName && !isTypeOfExpression(containingQualifiedName.parent) && tryGetQualifiedNameAsValue(containingQualifiedName); if (suggestionForNonexistentModule) { error(right, Diagnostics._0_has_no_exported_member_named_1_Did_you_mean_2, namespaceName, declarationName, symbolToString(suggestionForNonexistentModule)); + return undefined; } - else 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)); + + 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; } - else if (meaning & SymbolFlags.Namespace && exportedTypeSymbol && isQualifiedName(name.parent)) { - 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)); - } - else { - error(right, Diagnostics.Namespace_0_has_no_exported_member_1, namespaceName, declarationName); + + 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; } @@ -3290,16 +3379,20 @@ namespace ts { return; } const host = getJSDocHost(node); - if (host && - isExpressionStatement(host) && - isBinaryExpression(host.expression) && - getAssignmentDeclarationKind(host.expression) === AssignmentDeclarationKind.PrototypeProperty) { - // X.prototype.m = /** @param {K} p */ function () { } <-- look for K on X's declaration + if (host && isExpressionStatement(host) && isPrototypePropertyAssignment(host.expression)) { + // /** @param {K} p */ X.prototype.m = function () { } <-- look for K on X's declaration const symbol = getSymbolOfNode(host.expression.left); if (symbol) { return getDeclarationOfJSPrototypeContainer(symbol); } } + if (host && isFunctionExpression(host) && isPrototypePropertyAssignment(host.parent) && isExpressionStatement(host.parent.parent)) { + // X.prototype.m = /** @param {K} p */ function () { } <-- look for K on X's declaration + const symbol = getSymbolOfNode(host.parent.left); + if (symbol) { + return getDeclarationOfJSPrototypeContainer(symbol); + } + } if (host && (isObjectLiteralMethod(host) || isPropertyAssignment(host)) && isBinaryExpression(host.parent.parent) && getAssignmentDeclarationKind(host.parent.parent) === AssignmentDeclarationKind.Prototype) { @@ -3373,14 +3466,38 @@ namespace ts { return ambientModule; } const currentSourceFile = getSourceFileOfNode(location); - const resolvedModule = getResolvedModule(currentSourceFile, moduleReference)!; // TODO: GH#18217 + const contextSpecifier = isStringLiteralLike(location) + ? location + : findAncestor(location, isImportCall)?.arguments[0] || + findAncestor(location, isImportDeclaration)?.moduleSpecifier || + findAncestor(location, isExternalModuleImportEqualsDeclaration)?.moduleReference.expression || + findAncestor(location, isExportDeclaration)?.moduleSpecifier || + (isModuleDeclaration(location) ? location : location.parent && isModuleDeclaration(location.parent) && location.parent.name === location ? location.parent : undefined)?.name || + (isLiteralImportTypeNode(location) ? location : undefined)?.argument.literal; + const mode = contextSpecifier && isStringLiteralLike(contextSpecifier) ? getModeForUsageLocation(currentSourceFile, contextSpecifier) : currentSourceFile.impliedNodeFormat; + const resolvedModule = getResolvedModule(currentSourceFile, moduleReference, mode); const resolutionDiagnostic = resolvedModule && getResolutionDiagnostic(compilerOptions, resolvedModule); - const sourceFile = resolvedModule && !resolutionDiagnostic && host.getSourceFile(resolvedModule.resolvedFileName); + const sourceFile = resolvedModule + && (!resolutionDiagnostic || resolutionDiagnostic === Diagnostics.Module_0_was_resolved_to_1_but_jsx_is_not_set) + && host.getSourceFile(resolvedModule.resolvedFileName); if (sourceFile) { + // If there's a resolutionDiagnostic we need to report it even if a sourceFile is found. + if (resolutionDiagnostic) { + error(errorNode, resolutionDiagnostic, moduleReference, resolvedModule.resolvedFileName); + } if (sourceFile.symbol) { if (resolvedModule.isExternalLibraryImport && !resolutionExtensionIsTSOrJson(resolvedModule.extension)) { errorOnImplicitAnyModule(/*isError*/ false, errorNode, resolvedModule, moduleReference); } + if (getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.Node12 || getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.NodeNext) { + const isSyncImport = (currentSourceFile.impliedNodeFormat === ModuleKind.CommonJS && !findAncestor(location, isImportCall)) || !!findAncestor(location, isImportEqualsDeclaration); + if (isSyncImport && sourceFile.impliedNodeFormat === ModuleKind.ESNext) { + error(errorNode, Diagnostics.Module_0_cannot_be_imported_using_this_construct_The_specifier_only_resolves_to_an_ES_module_which_cannot_be_imported_synchronously_Use_dynamic_import_instead, moduleReference); + } + if (mode === ModuleKind.ESNext && compilerOptions.resolveJsonModule && resolvedModule.extension === Extension.Json) { + error(errorNode, Diagnostics.JSON_imports_are_experimental_in_ES_module_mode_imports); + } + } // merged symbol is module declaration symbol combined with all augmentations return getMergedSymbol(sourceFile.symbol); } @@ -3410,10 +3527,10 @@ namespace ts { 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); + error(errorNode, diag, moduleReference, resolvedModule!.resolvedFileName); } else { - errorOnImplicitAnyModule(/*isError*/ noImplicitAny && !!moduleNotFoundError, errorNode, resolvedModule, moduleReference); + errorOnImplicitAnyModule(/*isError*/ noImplicitAny && !!moduleNotFoundError, errorNode, resolvedModule!, moduleReference); } // Failed imports and untyped modules are both treated in an untyped manner; only difference is whether we give a diagnostic first. return undefined; @@ -3434,6 +3551,10 @@ namespace ts { } else { const tsExtension = tryExtractTSExtension(moduleReference); + const isExtensionlessRelativePathImport = pathIsRelative(moduleReference) && !hasExtension(moduleReference); + const moduleResolutionKind = getEmitModuleResolutionKind(compilerOptions); + const resolutionIsNode12OrNext = moduleResolutionKind === ModuleResolutionKind.Node12 || + moduleResolutionKind === ModuleResolutionKind.NodeNext; if (tsExtension) { const diag = Diagnostics.An_import_path_cannot_end_with_a_0_extension_Consider_importing_1_instead; const importSourceWithoutExtension = removeExtension(moduleReference, tsExtension); @@ -3442,18 +3563,29 @@ namespace ts { * Direct users to import source with .js extension if outputting an ES module. * @see https://github.com/microsoft/TypeScript/issues/42151 */ - const moduleKind = getEmitModuleKind(compilerOptions); if (moduleKind >= ModuleKind.ES2015) { - replacedImportSource += ".js"; + replacedImportSource += tsExtension === Extension.Mts ? ".mjs" : tsExtension === Extension.Cts ? ".cjs" : ".js"; } error(errorNode, diag, tsExtension, replacedImportSource); } else if (!compilerOptions.resolveJsonModule && fileExtensionIs(moduleReference, Extension.Json) && - getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.NodeJs && + getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.Classic && hasJsonModuleEmitEnabled(compilerOptions)) { error(errorNode, Diagnostics.Cannot_find_module_0_Consider_using_resolveJsonModule_to_import_module_with_json_extension, moduleReference); } + else if (mode === ModuleKind.ESNext && resolutionIsNode12OrNext && 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_node12_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_node12_or_nodenext_Consider_adding_an_extension_to_the_import_path); + } + } else { error(errorNode, moduleNotFoundError, moduleReference); } @@ -3543,32 +3675,26 @@ namespace ts { return symbol; } - if (compilerOptions.esModuleInterop) { - const referenceParent = referencingLocation.parent; - if ( - (isImportDeclaration(referenceParent) && getNamespaceDeclarationNode(referenceParent)) || - isImportCall(referenceParent) - ) { - const type = getTypeOfSymbol(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); + } + + if (getESModuleInterop(compilerOptions)) { let sigs = getSignaturesOfStructuredType(type, SignatureKind.Call); if (!sigs || !sigs.length) { sigs = getSignaturesOfStructuredType(type, SignatureKind.Construct); } - if (sigs && sigs.length) { - const moduleType = getTypeWithSyntheticDefaultImportType(type, symbol, moduleSymbol!); - // Create a new symbol which has the module's type less the call and construct signatures - const result = createSymbol(symbol.flags, symbol.escapedName); - result.declarations = symbol.declarations ? symbol.declarations.slice() : []; - result.parent = symbol.parent; - result.target = symbol; - result.originatingImport = referenceParent; - if (symbol.valueDeclaration) result.valueDeclaration = symbol.valueDeclaration; - if (symbol.constEnumOnlyModule) result.constEnumOnlyModule = true; - if (symbol.members) result.members = new Map(symbol.members); - if (symbol.exports) result.exports = new Map(symbol.exports); - const resolvedModuleType = resolveStructuredTypeMembers(moduleType as StructuredType); // Should already be resolved from the signature checks above - result.type = createAnonymousType(result, resolvedModuleType.members, emptyArray, emptyArray, resolvedModuleType.indexInfos); - return result; + if ((sigs && sigs.length) || getPropertyOfType(type, InternalSymbolName.Default, /*skipObjectFunctionPropertyAugment*/ true)) { + const moduleType = getTypeWithSyntheticDefaultImportType(type, symbol, moduleSymbol!, reference); + return cloneTypeAsModuleType(symbol, moduleType, referenceParent); } } } @@ -3576,6 +3702,24 @@ namespace ts { 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.target = symbol; + result.originatingImport = referenceParent; + if (symbol.valueDeclaration) result.valueDeclaration = symbol.valueDeclaration; + if (symbol.constEnumOnlyModule) result.constEnumOnlyModule = true; + if (symbol.members) result.members = new Map(symbol.members); + if (symbol.exports) result.exports = new Map(symbol.exports); + const resolvedModuleType = resolveStructuredTypeMembers(moduleType as StructuredType); // Should already be resolved from the signature checks above + result.type = createAnonymousType(result, resolvedModuleType.members, emptyArray, emptyArray, resolvedModuleType.indexInfos); + return result; + } + function hasExportAssignmentSymbol(moduleSymbol: Symbol): boolean { return moduleSymbol.exports!.get(InternalSymbolName.ExportEquals) !== undefined; } @@ -3607,8 +3751,8 @@ namespace ts { if (exportEquals !== moduleSymbol) { const type = getTypeOfSymbol(exportEquals); if (shouldTreatPropertiesOfExternalModuleAsExports(type)) { - getPropertiesOfType(type).forEach(symbol => { - cb(symbol, symbol.escapedName); + forEachPropertyOfType(type, (symbol, escapedName) => { + cb(symbol, escapedName); }); } } @@ -3836,8 +3980,15 @@ namespace ts { return res; } const candidates = mapDefined(symbol.declarations, d => { - if (!isAmbientModule(d) && d.parent && hasNonGlobalAugmentationExternalModuleSymbol(d.parent)) { - return getSymbolOfNode(d.parent); + if (!isAmbientModule(d) && d.parent){ + // direct children of a module + if (hasNonGlobalAugmentationExternalModuleSymbol(d.parent)) { + return getSymbolOfNode(d.parent); + } + // export ='d member of an ambient module + if (isModuleBlock(d.parent) && d.parent.parent && resolveExternalModuleSymbol(getSymbolOfNode(d.parent.parent)) === symbol) { + return getSymbolOfNode(d.parent.parent); + } } if (isClassExpression(d) && isBinaryExpression(d.parent) && d.parent.operatorToken.kind === SyntaxKind.EqualsToken && isAccessExpression(d.parent.left) && isEntityNameExpression(d.parent.left.expression)) { if (isModuleExportsAccessExpression(d.parent.left) || isExportsIdentifier(d.parent.left.expression)) { @@ -3984,13 +4135,17 @@ namespace ts { function getNamedMembers(members: SymbolTable): Symbol[] { let result: Symbol[] | undefined; members.forEach((symbol, id) => { - if (!isReservedMemberName(id) && symbolIsValue(symbol)) { + 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); @@ -4437,7 +4592,7 @@ namespace ts { // get symbol of the first identifier of the entityName let meaning: SymbolFlags; if (entityName.parent.kind === SyntaxKind.TypeQuery || - isExpressionWithTypeArgumentsInClassExtendsClause(entityName.parent) || + entityName.parent.kind === SyntaxKind.ExpressionWithTypeArguments && !isPartOfTypeNode(entityName.parent) || entityName.parent.kind === SyntaxKind.ComputedPropertyName) { // Typeof value meaning = SymbolFlags.Value | SymbolFlags.ExportValue; @@ -4517,7 +4672,9 @@ namespace ts { const noTruncation = compilerOptions.noErrorTruncation || flags & TypeFormatFlags.NoTruncation; const typeNode = nodeBuilder.typeToTypeNode(type, enclosingDeclaration, toNodeBuilderFlags(flags) | NodeBuilderFlags.IgnoreErrors | (noTruncation ? NodeBuilderFlags.NoTruncation : 0), writer); if (typeNode === undefined) return Debug.fail("should always get typenode"); - const options = { removeComments: true }; + // The unresolved type gets a synthesized comment on `any` to hint to users that it's not a plain `any`. + // Otherwise, we always strip comments out. + const options = { removeComments: type !== unresolvedType }; const printer = createPrinter(options); const sourceFile = enclosingDeclaration && getSourceFileOfNode(enclosingDeclaration); printer.writeNode(EmitHint.Unspecified, typeNode, /*sourceFile*/ sourceFile, writer); @@ -4588,12 +4745,14 @@ namespace ts { getCommonSourceDirectory: !!(host as Program).getCommonSourceDirectory ? () => (host as Program).getCommonSourceDirectory() : () => "", getCurrentDirectory: () => host.getCurrentDirectory(), getSymlinkCache: maybeBind(host, host.getSymlinkCache), + getPackageJsonInfoCache: () => host.getPackageJsonInfoCache?.(), useCaseSensitiveFileNames: maybeBind(host, host.useCaseSensitiveFileNames), redirectTargetsMap: host.redirectTargetsMap, getProjectReferenceRedirect: fileName => host.getProjectReferenceRedirect(fileName), isSourceOfProjectReferenceRedirect: fileName => host.isSourceOfProjectReferenceRedirect(fileName), fileExists: fileName => host.fileExists(fileName), getFileIncludeReasons: () => host.getFileIncludeReasons(), + readFile: host.readFile ? (fileName => host.readFile!(fileName)) : undefined, } : undefined }, encounteredError: false, reportedDiagnostic: false, @@ -4667,6 +4826,12 @@ namespace ts { } 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); } @@ -4915,7 +5080,7 @@ namespace ts { 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); + const mappedTypeNode = factory.createMappedTypeNode(readonlyToken, typeParameterNode, nameTypeNode, questionToken, templateTypeNode, /*members*/ undefined); context.approximateLength += 10; return setEmitFlags(mappedTypeNode, EmitFlags.SingleLine); } @@ -5522,8 +5687,8 @@ namespace ts { const expandedParams = getExpandedParameters(signature, /*skipUnionExpanding*/ true)[0]; // If the expanded parameter list had a variadic in a non-trailing position, don't expand it const parameters = (some(expandedParams, p => p !== expandedParams[expandedParams.length - 1] && !!(getCheckFlags(p) & CheckFlags.RestParameter)) ? signature.parameters : expandedParams).map(parameter => symbolToParameterDeclaration(parameter, context, kind === SyntaxKind.Constructor, options?.privateSymbolVisitor, options?.bundledImports)); - if (signature.thisParameter) { - const thisParameter = symbolToParameterDeclaration(signature.thisParameter, context); + const thisParameter = tryGetThisParameterDeclaration(signature, context); + if (thisParameter) { parameters.unshift(thisParameter); } @@ -5578,6 +5743,25 @@ namespace ts { return node; } + function tryGetThisParameterDeclaration(signature: Signature, context: NodeBuilderContext) { + if (signature.thisParameter) { + return symbolToParameterDeclaration(signature.thisParameter, context); + } + if (signature.declaration) { + const thisTag = getJSDocThisTag(signature.declaration); + if (thisTag && thisTag.typeExpression) { + return factory.createParameterDeclaration( + /* decorators */ undefined, + /* modifiers */ undefined, + /* dotDotDotToken */ undefined, + "this", + /* questionToken */ undefined, + typeToTypeNodeHelper(getTypeFromTypeNode(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 @@ -5852,6 +6036,11 @@ namespace ts { 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 @@ -5861,7 +6050,7 @@ namespace ts { const nonRootParts = chain.length > 1 ? createAccessFromSymbolChain(chain, chain.length - 1, 1) : undefined; const typeParameterNodes = overrideTypeArguments || lookupTypeParameterNodes(chain, 0, context); const specifier = getSpecifierForModuleSymbol(chain[0], context); - if (!(context.flags & NodeBuilderFlags.AllowNodeModulesRelativePaths) && getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.NodeJs && specifier.indexOf("/node_modules/") >= 0) { + if (!(context.flags & NodeBuilderFlags.AllowNodeModulesRelativePaths) && getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.Classic && specifier.indexOf("/node_modules/") >= 0) { // 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; @@ -6064,12 +6253,8 @@ namespace ts { firstChar = symbolName.charCodeAt(0); } let expression: Expression | undefined; - if (isSingleOrDoubleQuote(firstChar)) { - expression = factory.createStringLiteral( - symbolName - .substring(1, symbolName.length - 1) - .replace(/\\./g, s => s.substring(1)), - firstChar === CharacterCodes.singleQuote); + 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); @@ -6101,7 +6286,7 @@ namespace ts { } const rawName = unescapeLeadingUnderscores(symbol.escapedName); const stringNamed = !!length(symbol.declarations) && every(symbol.declarations, isStringNamed); - return createPropertyNameNodeForIdentifierOrLiteral(rawName, stringNamed, singleQuote); + return createPropertyNameNodeForIdentifierOrLiteral(rawName, getEmitScriptTarget(compilerOptions), singleQuote, stringNamed); } // See getNameForSymbolFromNameType for a stringy equivalent @@ -6110,13 +6295,13 @@ namespace ts { if (nameType) { if (nameType.flags & TypeFlags.StringOrNumberLiteral) { const name = "" + (nameType as StringLiteralType | NumberLiteralType).value; - if (!isIdentifierText(name, compilerOptions.target) && !isNumericLiteralName(name)) { + if (!isIdentifierText(name, getEmitScriptTarget(compilerOptions)) && !isNumericLiteralName(name)) { return factory.createStringLiteral(name, !!singleQuote); } if (isNumericLiteralName(name) && startsWith(name, "-")) { return factory.createComputedPropertyName(factory.createNumericLiteral(+name)); } - return createPropertyNameNodeForIdentifierOrLiteral(name); + return createPropertyNameNodeForIdentifierOrLiteral(name, getEmitScriptTarget(compilerOptions)); } if (nameType.flags & TypeFlags.UniqueESSymbol) { return factory.createComputedPropertyName(symbolToExpression((nameType as UniqueESSymbolType).symbol, context, SymbolFlags.Value)); @@ -6124,12 +6309,6 @@ namespace ts { } } - function createPropertyNameNodeForIdentifierOrLiteral(name: string, stringNamed?: boolean, singleQuote?: boolean) { - return isIdentifierText(name, compilerOptions.target) ? factory.createIdentifier(name) : - !stringNamed && isNumericLiteralName(name) && +name >= 0 ? factory.createNumericLiteral(+name) : - factory.createStringLiteral(name, !!singleQuote); - } - function cloneNodeBuilderContext(context: NodeBuilderContext): NodeBuilderContext { const initial: NodeBuilderContext = { ...context }; // Make type parameters created within this context not consume the name outside this context @@ -6171,7 +6350,7 @@ namespace ts { * so a `unique symbol` is returned when appropriate for the input symbol, rather than `typeof sym` */ function serializeTypeForDeclaration(context: NodeBuilderContext, type: Type, symbol: Symbol, enclosingDeclaration: Node | undefined, includePrivateSymbol?: (s: Symbol) => void, bundled?: boolean) { - if (type !== errorType && enclosingDeclaration) { + if (!isErrorType(type) && enclosingDeclaration) { const declWithExistingAnnotation = getDeclarationWithTypeAnnotation(symbol, enclosingDeclaration); if (declWithExistingAnnotation && !isFunctionLikeDeclaration(declWithExistingAnnotation) && !isGetAccessorDeclaration(declWithExistingAnnotation)) { // try to reuse the existing annotation @@ -6195,7 +6374,7 @@ namespace ts { } function serializeReturnTypeForSignature(context: NodeBuilderContext, type: Type, signature: Signature, includePrivateSymbol?: (s: Symbol) => void, bundled?: boolean) { - if (type !== errorType && context.enclosingDeclaration) { + if (!isErrorType(type) && context.enclosingDeclaration) { const annotation = signature.declaration && getEffectiveReturnTypeNode(signature.declaration); if (!!findAncestor(annotation, n => n === context.enclosingDeclaration) && annotation) { const annotated = getTypeFromTypeNode(annotation); @@ -6228,7 +6407,8 @@ namespace ts { includePrivateSymbol?.(sym); } if (isIdentifier(node)) { - const name = sym.flags & SymbolFlags.TypeParameter ? typeParameterToName(getDeclaredTypeOfSymbol(sym), context) : factory.cloneNode(node); + const type = getDeclaredTypeOfSymbol(sym); + const name = sym.flags & SymbolFlags.TypeParameter && !isTypeSymbolAccessible(type.symbol, context.enclosingDeclaration) ? typeParameterToName(type, context) : factory.cloneNode(node); name.symbol = sym; // for quickinfo, which uses identifier symbol information return { introducesError, node: setEmitFlags(setOriginalNode(name, node), EmitFlags.NoAsciiEscaping) }; } @@ -6335,7 +6515,7 @@ namespace ts { ); } } - if (isTypeReferenceNode(node) && isInJSDoc(node) && (!existingTypeNodeIsNotReferenceOrIsReferenceWithCompatibleTypeArgumentCount(node, getTypeFromTypeNode(node)) || getIntendedTypeFromJSDocTypeReference(node) || unknownSymbol === resolveTypeReferenceName(getTypeReferenceName(node), SymbolFlags.Type, /*ignoreErrors*/ true))) { + if (isTypeReferenceNode(node) && isInJSDoc(node) && (!existingTypeNodeIsNotReferenceOrIsReferenceWithCompatibleTypeArgumentCount(node, getTypeFromTypeNode(node)) || getIntendedTypeFromJSDocTypeReference(node) || unknownSymbol === resolveTypeReferenceName(node, SymbolFlags.Type, /*ignoreErrors*/ true))) { return setOriginalNode(typeToTypeNodeHelper(getTypeFromTypeNode(node), context), node); } if (isLiteralImportTypeNode(node)) { @@ -6502,7 +6682,7 @@ namespace ts { /*decorators*/ undefined, /*modifiers*/ undefined, /*isTypeOnly*/ false, - factory.createNamedExports(map(flatMap(excessExports, e => getNamesOfDeclaration(e)), id => factory.createExportSpecifier(/*alias*/ undefined, id))), + factory.createNamedExports(map(flatMap(excessExports, e => getNamesOfDeclaration(e)), id => factory.createExportSpecifier(/*isTypeOnly*/ false, /*alias*/ undefined, id))), /*moduleSpecifier*/ undefined )]) ) @@ -6565,7 +6745,7 @@ namespace ts { 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.exportClause && isNamedExports(d.exportClause)); + const index = findIndex(statements, d => isExportDeclaration(d) && !d.moduleSpecifier && !d.assertClause && !!d.exportClause && isNamedExports(d.exportClause)); if (index >= 0) { const exportDecl = statements[index] as ExportDeclaration & { readonly exportClause: NamedExports }; const replacements = mapDefined(exportDecl.exportClause.elements, e => { @@ -6597,7 +6777,8 @@ namespace ts { exportDecl.exportClause, replacements ), - exportDecl.moduleSpecifier + exportDecl.moduleSpecifier, + exportDecl.assertClause ); } } @@ -6759,7 +6940,7 @@ namespace ts { /*decorators*/ undefined, /*modifiers*/ undefined, /*isTypeOnly*/ false, - factory.createNamedExports([factory.createExportSpecifier(alias, localName)]) + factory.createNamedExports([factory.createExportSpecifier(/*isTypeOnly*/ false, alias, localName)]) ), ModifierFlags.None ); @@ -6797,7 +6978,7 @@ namespace ts { /*decorators*/ undefined, /*modifiers*/ undefined, /*isTypeOnly*/ false, - factory.createNamedExports([factory.createExportSpecifier(name, localName)]) + factory.createNamedExports([factory.createExportSpecifier(/*isTypeOnly*/ false, name, localName)]) ), ModifierFlags.None ); @@ -6857,7 +7038,7 @@ namespace ts { /*decorators*/ undefined, /*modifiers*/ undefined, /*isTypeOnly*/ false, - factory.createNamedExports([factory.createExportSpecifier(getInternalSymbolName(symbol, symbolName), symbolName)]) + factory.createNamedExports([factory.createExportSpecifier(/*isTypeOnly*/ false, getInternalSymbolName(symbol, symbolName), symbolName)]) ), ModifierFlags.None); } } @@ -6996,7 +7177,7 @@ namespace ts { const target = aliasDecl && getTargetOfAliasDeclaration(aliasDecl, /*dontRecursivelyResolve*/ true); includePrivateSymbol(target || s); const targetName = target ? getInternalSymbolName(target, unescapeLeadingUnderscores(target.escapedName)) : localName; - return factory.createExportSpecifier(name === targetName ? undefined : targetName, name); + return factory.createExportSpecifier(/*isTypeOnly*/ false, name === targetName ? undefined : targetName, name); })) )]); addResult(factory.createModuleDeclaration( @@ -7102,7 +7283,7 @@ namespace ts { /*decorators*/ undefined, /*modifiers*/ undefined, /*isTypeOnly*/ false, - factory.createNamedExports([factory.createExportSpecifier(d.expression, factory.createIdentifier(InternalSymbolName.Default))]) + factory.createNamedExports([factory.createExportSpecifier(/*isTypeOnly*/ false, d.expression, factory.createIdentifier(InternalSymbolName.Default))]) ) : d); const exportModifierStripped = every(defaultReplaced, d => hasSyntacticModifier(d, ModifierFlags.Export)) ? map(defaultReplaced, removeExportModifier) : defaultReplaced; fakespace = factory.updateModuleDeclaration( @@ -7227,6 +7408,28 @@ namespace ts { ), symbol.declarations && filter(symbol.declarations, d => isClassDeclaration(d) || isClassExpression(d))[0]), modifierFlags); } + function getSomeTargetNameFromDeclarations(declarations: Declaration[] | undefined) { + return firstDefined(declarations, d => { + if (isImportSpecifier(d) || isExportSpecifier(d)) { + return idText(d.propertyName || d.name); + } + if (isBinaryExpression(d) || isExportAssignment(d)) { + const expression = isExportAssignment(d) ? d.expression : d.right; + if (isPropertyAccessExpression(expression)) { + return idText(expression.name); + } + } + if (isAliasSymbolDeclaration(d)) { + // This is... heuristic, at best. But it's probably better than always printing the name of the shorthand ambient module. + const name = getNameOfDeclaration(d); + if (name && isIdentifier(name)) { + return idText(name); + } + } + return undefined; + }); + } + function serializeAsAlias(symbol: Symbol, localName: string, modifierFlags: ModifierFlags) { // synthesize an alias, eg `export { symbolName as Name }` // need to mark the alias `symbol` points at @@ -7237,8 +7440,10 @@ namespace ts { if (!target) { return; } - let verbatimTargetName = unescapeLeadingUnderscores(target.escapedName); - if (verbatimTargetName === InternalSymbolName.ExportEquals && (compilerOptions.esModuleInterop || compilerOptions.allowSyntheticDefaultImports)) { + // If `target` refers to a shorthand module symbol, the name we're trying to pull out isn;t recoverable from the target symbol + // In such a scenario, we must fall back to looking for an alias declaration on `symbol` and pulling the target name from that + let verbatimTargetName = isShorthandAmbientModuleSymbol(target) && getSomeTargetNameFromDeclarations(symbol.declarations) || unescapeLeadingUnderscores(target.escapedName); + if (verbatimTargetName === InternalSymbolName.ExportEquals && (getESModuleInterop(compilerOptions) || compilerOptions.allowSyntheticDefaultImports)) { // target refers to an `export=` symbol that was hoisted into a synthetic default - rename here to match verbatimTargetName = InternalSymbolName.Default; } @@ -7254,10 +7459,12 @@ namespace ts { /*decorators*/ undefined, /*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) + factory.createStringLiteral(specifier), + /*importClause*/ undefined ), ModifierFlags.None); break; } @@ -7333,7 +7540,8 @@ namespace ts { // We use `target.parent || target` below as `target.parent` is unset when the target is a module which has been export assigned // And then made into a default by the `esModuleInterop` or `allowSyntheticDefaultImports` flag // In such cases, the `target` refers to the module itself already - factory.createStringLiteral(getSpecifierForModuleSymbol(target.parent || target, context)) + factory.createStringLiteral(getSpecifierForModuleSymbol(target.parent || target, context)), + /*assertClause*/ undefined ), ModifierFlags.None); break; case SyntaxKind.NamespaceImport: @@ -7341,7 +7549,8 @@ namespace ts { /*decorators*/ undefined, /*modifiers*/ undefined, factory.createImportClause(/*isTypeOnly*/ false, /*importClause*/ undefined, factory.createNamespaceImport(factory.createIdentifier(localName))), - factory.createStringLiteral(getSpecifierForModuleSymbol(target, context)) + factory.createStringLiteral(getSpecifierForModuleSymbol(target, context)), + /*assertClause*/ undefined ), ModifierFlags.None); break; case SyntaxKind.NamespaceExport: @@ -7362,11 +7571,13 @@ namespace ts { /*importClause*/ undefined, factory.createNamedImports([ factory.createImportSpecifier( + /*isTypeOnly*/ false, localName !== verbatimTargetName ? factory.createIdentifier(verbatimTargetName) : undefined, factory.createIdentifier(localName) ) ])), - factory.createStringLiteral(getSpecifierForModuleSymbol(target.parent || target, context)) + factory.createStringLiteral(getSpecifierForModuleSymbol(target.parent || target, context)), + /*assertClause*/ undefined ), ModifierFlags.None); break; case SyntaxKind.ExportSpecifier: @@ -7407,7 +7618,7 @@ namespace ts { /*decorators*/ undefined, /*modifiers*/ undefined, /*isTypeOnly*/ false, - factory.createNamedExports([factory.createExportSpecifier(localName !== targetName ? targetName : undefined, localName)]), + factory.createNamedExports([factory.createExportSpecifier(/*isTypeOnly*/ false, localName !== targetName ? targetName : undefined, localName)]), specifier ), ModifierFlags.None); } @@ -7923,7 +8134,7 @@ namespace ts { if (nameType) { if (nameType.flags & TypeFlags.StringOrNumberLiteral) { const name = "" + (nameType as StringLiteralType | NumberLiteralType).value; - if (!isIdentifierText(name, compilerOptions.target) && !isNumericLiteralName(name)) { + if (!isIdentifierText(name, getEmitScriptTarget(compilerOptions)) && !isNumericLiteralName(name)) { return `"${escapeString(name, CharacterCodes.doubleQuote)}"`; } if (isNumericLiteralName(name) && startsWith(name, "-")) { @@ -8252,11 +8463,20 @@ namespace ts { 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) { + function getTypeForBindingElementParent(node: BindingElementGrandparent, checkMode: CheckMode) { + if (checkMode !== CheckMode.Normal) { + return getTypeForVariableLikeDeclaration(node, /*includeOptionality*/ false, checkMode); + } const symbol = getSymbolOfNode(node); - return symbol && getSymbolLinks(symbol).type || getTypeForVariableLikeDeclaration(node, /*includeOptionality*/ false); + return symbol && getSymbolLinks(symbol).type || getTypeForVariableLikeDeclaration(node, /*includeOptionality*/ false, checkMode); } function getRestType(source: Type, properties: PropertyName[], symbol: Symbol | undefined): Type { @@ -8267,8 +8487,32 @@ namespace ts { if (source.flags & TypeFlags.Union) { return mapType(source, t => getRestType(t, properties, symbol)); } - const omitKeyType = getUnionType(map(properties, getLiteralTypeFromPropertyName)); + + 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; } @@ -8280,12 +8524,8 @@ namespace ts { return getTypeAliasInstantiation(omitTypeAlias, [source, omitKeyType]); } const members = createSymbolTable(); - for (const prop of getPropertiesOfType(source)) { - if (!isTypeAssignableTo(getLiteralTypeFromProperty(prop, TypeFlags.StringOrNumberLiteralOrUnique), omitKeyType) - && !(getDeclarationModifierFlagsFromSymbol(prop) & (ModifierFlags.Private | ModifierFlags.Protected)) - && isSpreadableProperty(prop)) { - members.set(prop.escapedName, getSpreadSymbol(prop, /*readonly*/ false)); - } + 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; @@ -8367,12 +8607,17 @@ namespace ts { /** Return the inferred type for a binding element */ function getTypeForBindingElement(declaration: BindingElement): Type | undefined { - const pattern = declaration.parent; - let parentType = getTypeForBindingElementParent(pattern.parent); - // If no type or an any type was inferred for parent, infer that for the binding element - if (!parentType || isTypeAny(parentType)) { + const checkMode = declaration.dotDotDotToken ? CheckMode.RestBindingElement : CheckMode.Normal; + const parentType = getTypeForBindingElementParent(declaration.parent.parent, checkMode); + return parentType && getBindingElementTypeFromParentType(declaration, parentType); + } + + function getBindingElementTypeFromParentType(declaration: BindingElement, parentType: Type): Type { + // If an any type was inferred for parent, infer that for the binding element + if (isTypeAny(parentType)) { return parentType; } + const pattern = declaration.parent; // Relax null check on ambient destructuring parameters, since the parameters have no implementation and are just documentation if (strictNullChecks && declaration.flags & NodeFlags.Ambient && isParameterDeclaration(declaration)) { parentType = getNonNullableType(parentType); @@ -8436,9 +8681,9 @@ namespace ts { 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 && !(getFalsyFlags(checkDeclarationInitializer(declaration)) & TypeFlags.Undefined) ? getNonUndefinedType(type) : type; + return strictNullChecks && !(getFalsyFlags(checkDeclarationInitializer(declaration, CheckMode.Normal)) & TypeFlags.Undefined) ? getNonUndefinedType(type) : type; } - return widenTypeInferredFromInitializer(declaration, getUnionType([getNonUndefinedType(type), checkDeclarationInitializer(declaration)], UnionReduction.Subtype)); + return widenTypeInferredFromInitializer(declaration, getUnionType([getNonUndefinedType(type), checkDeclarationInitializer(declaration, CheckMode.Normal)], UnionReduction.Subtype)); } function getTypeForDeclarationFromJSDocComment(declaration: Node) { @@ -8464,11 +8709,15 @@ namespace ts { } // Return the inferred type for a variable, parameter, or property declaration - function getTypeForVariableLikeDeclaration(declaration: ParameterDeclaration | PropertyDeclaration | PropertySignature | VariableDeclaration | BindingElement | JSDocPropertyLikeTag, includeOptionality: boolean): Type | undefined { + 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))); + const indexType = getIndexType(getNonNullableTypeIfNeeded(checkExpression(declaration.parent.parent.expression, /*checkMode*/ checkMode))); return indexType.flags & (TypeFlags.TypeParameter | TypeFlags.Index) ? getExtractStringType(indexType) : stringType; } @@ -8487,7 +8736,7 @@ namespace ts { const isProperty = isPropertyDeclaration(declaration) || isPropertySignature(declaration); const isOptional = includeOptionality && ( - isProperty && !!(declaration as PropertyDeclaration | PropertySignature).questionToken || + isProperty && !!declaration.questionToken || isParameter(declaration) && (!!declaration.questionToken || isJSDocOptionalParameter(declaration)) || isOptionalJSDocPropertyLikeTag(declaration)); @@ -8530,12 +8779,8 @@ namespace ts { } } if (isInJSFile(declaration)) { - const typeTag = getJSDocType(func); - if (typeTag && isFunctionTypeNode(typeTag)) { - const signature = getSignatureFromDeclaration(typeTag); - const pos = func.parameters.indexOf(declaration); - return declaration.dotDotDotToken ? getRestTypeAtPosition(signature, pos) : getTypeAtPosition(signature, pos); - } + const type = getParameterTypeOfTypeTag(func, declaration); + if (type) return type; } // Use contextual parameter type if one is available const type = declaration.symbol.escapedName === InternalSymbolName.This ? getContextualThisParameterType(func) : getContextuallyTypedParameterType(declaration); @@ -8553,7 +8798,7 @@ namespace ts { return containerObjectType; } } - const type = widenTypeInferredFromInitializer(declaration, checkDeclarationInitializer(declaration)); + const type = widenTypeInferredFromInitializer(declaration, checkDeclarationInitializer(declaration, checkMode)); return addOptionality(type, isProperty, isOptional); } @@ -8797,7 +9042,7 @@ namespace ts { if (!declaredType) { return type; } - else if (declaredType !== errorType && type !== errorType && !isTypeIdenticalTo(declaredType, type)) { + else if (!isErrorType(declaredType) && !isErrorType(type) && !isTypeIdenticalTo(declaredType, type)) { errorNextVariableOrPropertyDeclarationMustHaveSameType(/*firstDeclaration*/ undefined, declaredType, declaration, type); } } @@ -8946,7 +9191,7 @@ namespace ts { // 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, contextualType))); + return addOptionality(widenTypeInferredFromInitializer(element, checkDeclarationInitializer(element, CheckMode.Normal, contextualType))); } if (isBindingPattern(element.name)) { return getTypeFromBindingPattern(element.name, includePatternInType, reportErrors); @@ -9038,7 +9283,7 @@ namespace ts { // 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), declaration, reportErrors); + return widenTypeForVariableLikeDeclaration(getTypeForVariableLikeDeclaration(declaration, /*includeOptionality*/ true, CheckMode.Normal), declaration, reportErrors); } function isGlobalSymbolConstructor(node: Node) { @@ -9286,11 +9531,12 @@ namespace ts { const getter = getDeclarationOfKind(symbol, SyntaxKind.GetAccessor); const setter = getDeclarationOfKind(symbol, SyntaxKind.SetAccessor); - const setterType = getAnnotatedAccessorType(setter); - // For write operations, prioritize type annotations on the setter - if (writing && setterType) { - return instantiateTypeIfNeeded(setterType, symbol); + if (writing) { + const setterType = getAnnotatedAccessorType(setter); + if (setterType) { + return instantiateTypeIfNeeded(setterType, symbol); + } } // Else defer to the getter type @@ -9308,6 +9554,7 @@ namespace ts { } // If the user didn't specify a return type, try to use the set-accessor's parameter type. + const setterType = getAnnotatedAccessorType(setter); if (setterType) { return setterType; } @@ -9471,6 +9718,41 @@ namespace ts { 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 union and intersection + * properties deriving from set accessors will either pre-compute or defer the + * union or intersection of the writeTypes of their constituents. To account for + * this, we will assume that any deferred type or transient symbol may have a + * `writeType` (or a deferred write type ready to be computed) that should be + * used before looking for set accessor declarations. + */ + function getWriteTypeOfSymbol(symbol: Symbol): Type { + const checkFlags = getCheckFlags(symbol); + if (checkFlags & CheckFlags.DeferredType) { + const writeType = getWriteTypeOfSymbolWithDeferredType(symbol); + if (writeType) { + return writeType; + } + } + if (symbol.flags & SymbolFlags.Transient) { + const { writeType } = symbol as TransientSymbol; + if (writeType) { + return writeType; + } + } + return getSetAccessorTypeOfSymbol(symbol); + } + function getSetAccessorTypeOfSymbol(symbol: Symbol): Type { if (symbol.flags & SymbolFlags.Accessor) { const type = getTypeOfSetAccessor(symbol); @@ -9679,7 +9961,8 @@ namespace ts { } function getBaseTypeNodeOfClass(type: InterfaceType): ExpressionWithTypeArguments | undefined { - return getEffectiveBaseTypeNode(type.symbol.valueDeclaration as ClassLikeDeclaration); + const decl = getClassLikeDeclarationOfSymbol(type.symbol); + return decl && getEffectiveBaseTypeNode(decl); } function getConstructorsForTypeArguments(type: Type, typeArgumentNodes: readonly TypeNode[] | undefined, location: Node): readonly Signature[] { @@ -9705,8 +9988,8 @@ namespace ts { */ function getBaseConstructorTypeOfClass(type: InterfaceType): Type { if (!type.resolvedBaseConstructorType) { - const decl = type.symbol.valueDeclaration as ClassLikeDeclaration; - const extended = getEffectiveBaseTypeNode(decl); + const decl = getClassLikeDeclarationOfSymbol(type.symbol); + const extended = decl && getEffectiveBaseTypeNode(decl); const baseTypeNode = getBaseTypeNodeOfClass(type); if (!baseTypeNode) { return type.resolvedBaseConstructorType = undefinedType; @@ -9758,7 +10041,7 @@ namespace ts { if (!implementsTypeNodes) continue; for (const node of implementsTypeNodes) { const implementsType = getTypeFromTypeNode(node); - if (implementsType !== errorType) { + if (!isErrorType(implementsType)) { if (resolvedImplementsTypes === emptyArray) { resolvedImplementsTypes = [implementsType as ObjectType]; } @@ -9842,7 +10125,7 @@ namespace ts { baseType = getReturnTypeOfSignature(constructors[0]); } - if (baseType === errorType) { + if (isErrorType(baseType)) { return type.resolvedBaseTypes = emptyArray; } const reducedBaseType = getReducedType(baseType); @@ -9900,7 +10183,7 @@ namespace ts { if (declaration.kind === SyntaxKind.InterfaceDeclaration && getInterfaceBaseTypeNodes(declaration as InterfaceDeclaration)) { for (const node of getInterfaceBaseTypeNodes(declaration as InterfaceDeclaration)!) { const baseType = getReducedType(getTypeFromTypeNode(node)); - if (baseType !== errorType) { + if (!isErrorType(baseType)) { if (isValidBaseType(baseType)) { if (type !== baseType && !hasBaseType(baseType, type)) { if (type.resolvedBaseTypes === emptyArray) { @@ -11037,7 +11320,6 @@ namespace ts { * Converts an AnonymousType to a ResolvedType. */ function resolveAnonymousTypeMembers(type: AnonymousType) { - const symbol = getMergedSymbol(type.symbol); if (type.target) { setStructuredTypeMembers(type, emptySymbols, emptyArray, emptyArray, emptyArray); const members = createInstantiatedSymbolTable(getPropertiesOfObjectType(type.target), type.mapper!, /*mappingThisOnly*/ false); @@ -11045,82 +11327,83 @@ namespace ts { 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; } - else if (symbol.flags & SymbolFlags.TypeLiteral) { + const symbol = getMergedSymbol(type.symbol); + if (symbol.flags & SymbolFlags.TypeLiteral) { setStructuredTypeMembers(type, emptySymbols, emptyArray, emptyArray, emptyArray); const members = getMembersOfSymbol(symbol); const callSignatures = getSignaturesOfSymbol(members.get(InternalSymbolName.Call)); const constructSignatures = getSignaturesOfSymbol(members.get(InternalSymbolName.New)); const indexInfos = getIndexInfosOfSymbol(symbol); setStructuredTypeMembers(type, members, callSignatures, constructSignatures, indexInfos); + return; + } + // Combinations of function, class, enum and module + let members = emptySymbols; + let indexInfos: IndexInfo[] | undefined; + if (symbol.exports) { + members = getExportsOfSymbol(symbol); + if (symbol === globalThisSymbol) { + const varsOnly = new Map() as SymbolTable; + members.forEach(p => { + if (!(p.flags & SymbolFlags.BlockScoped)) { + 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 { - // Combinations of function, class, enum and module - let members = emptySymbols; - let indexInfos: IndexInfo[] | undefined; - if (symbol.exports) { - members = getExportsOfSymbol(symbol); - if (symbol === globalThisSymbol) { - const varsOnly = new Map() as SymbolTable; - members.forEach(p => { - if (!(p.flags & SymbolFlags.BlockScoped)) { - varsOnly.set(p.escapedName, p); - } - }); - members = varsOnly; - } + if (baseConstructorIndexInfo) { + indexInfos = append(indexInfos, baseConstructorIndexInfo); } - 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); - } + if (symbol.flags & SymbolFlags.Enum && (getDeclaredTypeOfSymbol(symbol).flags & TypeFlags.Enum || + some(type.properties, prop => !!(getTypeOfSymbol(prop).flags & TypeFlags.NumberLike)))) { + indexInfos = append(indexInfos, enumNumberIndexInfo); } - - const indexSymbol = getIndexSymbolFromSymbolTable(members); - if (indexSymbol) { - indexInfos = getIndexInfosOfIndexSymbol(indexSymbol); + } + 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)); } - 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; + if (!constructSignatures.length) { + constructSignatures = getDefaultConstructSignatures(classType); } + type.constructSignatures = constructSignatures; } } @@ -11196,6 +11479,22 @@ namespace ts { 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(); @@ -11213,19 +11512,7 @@ namespace ts { const include = keyofStringsOnly ? TypeFlags.StringLiteral : TypeFlags.StringOrNumberLiteralOrUnique; if (isMappedTypeWithKeyofConstraintDeclaration(type)) { // We have a { [P in keyof T]: X } - for (const prop of getPropertiesOfType(modifiersType)) { - addMemberForKeyType(getLiteralTypeFromProperty(prop, include)); - } - if (modifiersType.flags & TypeFlags.Any) { - addMemberForKeyType(stringType); - } - else { - for (const info of getIndexInfosOfType(modifiersType)) { - if (!keyofStringsOnly || info.keyType.flags & (TypeFlags.String | TypeFlags.TemplateLiteral)) { - addMemberForKeyType(info.keyType); - } - } - } + forEachMappedTypePropertyKeyTypeAndIndexSignatureKeyType(modifiersType, include, keyofStringsOnly, addMemberForKeyType); } else { forEachType(getLowerBoundOfKeyType(constraintType), addMemberForKeyType); @@ -11467,6 +11754,17 @@ namespace ts { 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 => { @@ -11517,6 +11815,11 @@ namespace ts { } 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); @@ -11730,6 +12033,11 @@ namespace ts { return constraint ? 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); @@ -11812,13 +12120,18 @@ namespace ts { return type; } + function isMappedTypeGenericIndexedAccess(type: Type) { + return type.flags & TypeFlags.IndexedAccess && getObjectFlags((type as IndexedAccessType).objectType) & ObjectFlags.Mapped && + !isGenericMappedType((type as IndexedAccessType).objectType) && isGenericIndexType((type as IndexedAccessType).indexType); + } + /** * 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 t = !(type.flags & TypeFlags.Instantiable) ? type : getBaseConstraintOfType(type) || unknownType; return getObjectFlags(t) & ObjectFlags.Mapped ? getApparentTypeOfMappedType(t as MappedType) : t.flags & TypeFlags.Intersection ? getApparentTypeOfIntersectionType(t as IntersectionType) : t.flags & TypeFlags.StringLike ? globalStringType : @@ -11852,7 +12165,7 @@ namespace ts { let mergedInstantiations = false; for (const current of containingType.types) { const type = getApparentType(current); - if (!(type === errorType || type.flags & TypeFlags.Never)) { + if (!(isErrorType(type) || type.flags & TypeFlags.Never)) { const prop = getPropertyOfType(type, name, skipObjectFunctionPropertyAugment); const modifiers = prop ? getDeclarationModifierFlagsFromSymbol(prop) : 0; if (prop) { @@ -11942,6 +12255,7 @@ namespace ts { 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) { @@ -11957,10 +12271,14 @@ namespace ts { firstType = type; nameType = getSymbolLinks(prop).nameType; } + const writeType = getWriteTypeOfSymbol(prop); + if (writeTypes || writeType !== type) { + writeTypes = append(!writeTypes ? propTypes.slice() : writeTypes, writeType); + } else if (type !== firstType) { checkFlags |= CheckFlags.HasNonUniformType; } - if (isLiteralType(type)) { + if (isLiteralType(type) || isPatternLiteralType(type)) { checkFlags |= CheckFlags.HasLiteralType; } if (type.flags & TypeFlags.Never) { @@ -11987,9 +12305,13 @@ namespace ts { result.checkFlags |= CheckFlags.DeferredType; result.deferralParent = containingType; result.deferralConstituents = propTypes; + result.deferralWriteConstituents = writeTypes; } else { result.type = isUnion ? getUnionType(propTypes) : getIntersectionType(propTypes); + if (writeTypes) { + result.writeType = isUnion ? getUnionType(writeTypes) : getIntersectionType(writeTypes); + } } return result; } @@ -12456,6 +12778,13 @@ namespace ts { 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); @@ -12477,7 +12806,7 @@ namespace ts { if (!node) return false; switch (node.kind) { case SyntaxKind.Identifier: - return (node as Identifier).escapedText === argumentsSymbol.escapedName && getResolvedSymbol(node as Identifier) === argumentsSymbol; + return (node as Identifier).escapedText === argumentsSymbol.escapedName && getReferencedValueSymbol(node as Identifier) === argumentsSymbol; case SyntaxKind.PropertyDeclaration: case SyntaxKind.MethodDeclaration: @@ -12490,6 +12819,9 @@ namespace ts { 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); } @@ -12745,8 +13077,11 @@ namespace ts { // 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 ? signature.declaration.kind : SyntaxKind.Unknown; - const isConstructor = kind === SyntaxKind.Constructor || kind === SyntaxKind.ConstructSignature || kind === SyntaxKind.ConstructorType; + 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; + const type = createObjectType(ObjectFlags.Anonymous); type.members = emptySymbols; type.properties = emptyArray; @@ -12889,7 +13224,7 @@ namespace ts { } else { let type = getTypeFromTypeNode(constraintDeclaration); - if (type.flags & TypeFlags.Any && type !== errorType) { // Allow errorType to propegate to keep downstream errors suppressed + if (type.flags & TypeFlags.Any && !isErrorType(type)) { // Allow errorType to propegate to keep downstream errors suppressed // use keyofConstraintType as the base constraint for mapped type key constraints (unknown isn;t assignable to that, but `any` was), // use unknown otherwise type = constraintDeclaration.parent.parent.kind === SyntaxKind.MappedType ? keyofConstraintType : unknownType; @@ -13084,6 +13419,18 @@ namespace ts { * declared type. Instantiations are cached using the type identities of the type arguments as the key. */ function getTypeFromTypeAliasReference(node: NodeWithTypeArguments, symbol: Symbol): Type { + if (getCheckFlags(symbol) & CheckFlags.Unresolved) { + const typeArguments = typeArgumentsFromTypeReferenceNode(node); + const id = getAliasId(symbol, typeArguments); + let errorType = errorTypes.get(id); + if (!errorType) { + errorType = createIntrinsicType(TypeFlags.Any, "error"); + errorType.aliasSymbol = symbol; + errorType.aliasTypeArguments = typeArguments; + errorTypes.set(id, errorType); + } + return errorType; + } const type = getDeclaredTypeOfSymbol(symbol); const typeParameters = getSymbolLinks(symbol).typeParameters; if (typeParameters) { @@ -13132,12 +13479,39 @@ namespace ts { return undefined; } - function resolveTypeReferenceName(typeReferenceName: EntityNameExpression | EntityName | undefined, meaning: SymbolFlags, ignoreErrors?: boolean) { - if (!typeReferenceName) { + function getSymbolPath(symbol: Symbol): string { + return symbol.parent ? `${getSymbolPath(symbol.parent)}.${symbol.escapedName}` : symbol.escapedName as string; + } + + function getUnresolvedSymbolForEntityName(name: EntityNameOrEntityNameExpression) { + const identifier = name.kind === SyntaxKind.QualifiedName ? name.right : + name.kind === SyntaxKind.PropertyAccessExpression ? name.name : + name; + const text = identifier.escapedText; + if (text) { + const parentSymbol = name.kind === SyntaxKind.QualifiedName ? getUnresolvedSymbolForEntityName(name.left) : + name.kind === SyntaxKind.PropertyAccessExpression ? getUnresolvedSymbolForEntityName(name.expression) : + undefined; + const path = parentSymbol ? `${getSymbolPath(parentSymbol)}.${text}` : text as string; + let result = unresolvedSymbols.get(path); + if (!result) { + unresolvedSymbols.set(path, result = createSymbol(SymbolFlags.TypeAlias, text, CheckFlags.Unresolved)); + result.parent = parentSymbol; + result.declaredType = unresolvedType; + } + return result; + } + return unknownSymbol; + } + + function resolveTypeReferenceName(typeReference: TypeReferenceType, meaning: SymbolFlags, ignoreErrors?: boolean) { + const name = getTypeReferenceName(typeReference); + if (!name) { return unknownSymbol; } - - return resolveEntityName(typeReferenceName, meaning, ignoreErrors) || unknownSymbol; + const symbol = resolveEntityName(name, meaning, ignoreErrors); + return symbol && symbol !== unknownSymbol ? symbol : + ignoreErrors ? unknownSymbol : getUnresolvedSymbolForEntityName(name); } function getTypeReferenceType(node: NodeWithTypeArguments, symbol: Symbol): Type { @@ -13163,7 +13537,7 @@ namespace ts { } else { // Resolve the type reference as a Type for the purpose of reporting errors. - resolveTypeReferenceName(getTypeReferenceName(node), SymbolFlags.Type); + resolveTypeReferenceName(node, SymbolFlags.Type); return getTypeOfSymbol(symbol); } } @@ -13213,7 +13587,7 @@ namespace ts { 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)) === type ? getTypeFromTypeNode(extendsNode) : + getActualTypeVariable(getTypeFromTypeNode(checkNode)) === getActualTypeVariable(type) ? getTypeFromTypeNode(extendsNode) : undefined; } @@ -13317,18 +13691,18 @@ namespace ts { if (isJSDocTypeReference(node)) { type = getIntendedTypeFromJSDocTypeReference(node); if (!type) { - symbol = resolveTypeReferenceName(getTypeReferenceName(node), meaning, /*ignoreErrors*/ true); + symbol = resolveTypeReferenceName(node, meaning, /*ignoreErrors*/ true); if (symbol === unknownSymbol) { - symbol = resolveTypeReferenceName(getTypeReferenceName(node), meaning | SymbolFlags.Value); + symbol = resolveTypeReferenceName(node, meaning | SymbolFlags.Value); } else { - resolveTypeReferenceName(getTypeReferenceName(node), meaning); // Resolve again to mark errors, if any + resolveTypeReferenceName(node, meaning); // Resolve again to mark errors, if any } type = getTypeReferenceType(node, symbol); } } if (!type) { - symbol = resolveTypeReferenceName(getTypeReferenceName(node), meaning); + 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 @@ -13350,7 +13724,7 @@ namespace ts { // 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 = isThisIdentifier(node.exprName) ? checkThisExpression(node.exprName) : checkExpression(node.exprName); + const type = checkExpressionWithTypeArguments(node); links.resolvedType = getRegularTypeOfLiteralType(getWidenedType(type)); } return links.resolvedType; @@ -13395,28 +13769,48 @@ namespace ts { return getGlobalSymbol(name, SymbolFlags.Type, reportErrors ? Diagnostics.Cannot_find_global_type_0 : undefined); } - function getGlobalSymbol(name: __String, meaning: SymbolFlags, diagnostic: DiagnosticMessage | undefined): Symbol | undefined { - // Don't track references for global symbols anyway, so value if `isReference` is arbitrary - return resolveName(undefined, name, meaning, diagnostic, name, /*isUse*/ false); + 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 getGlobalType(name: __String, arity: 0, reportErrors: boolean): ObjectType; - function getGlobalType(name: __String, arity: number, reportErrors: boolean): GenericType; + function getGlobalSymbol(name: __String, meaning: SymbolFlags, diagnostic: DiagnosticMessage | undefined): Symbol | undefined { + // Don't track references for global symbols anyway, so value if `isReference` is arbitrary + return resolveName(undefined, name, meaning, diagnostic, name, /*isUse*/ false, /*excludeGlobals*/ false, /*getSpellingSuggestions*/ false); + } + + 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() { - return deferredGlobalTypedPropertyDescriptorType || (deferredGlobalTypedPropertyDescriptorType = getGlobalType("TypedPropertyDescriptor" as __String, /*arity*/ 1, /*reportErrors*/ true)) || emptyGenericType; + // 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() { - return deferredGlobalTemplateStringsArrayType || (deferredGlobalTemplateStringsArrayType = getGlobalType("TemplateStringsArray" as __String, /*arity*/ 0, /*reportErrors*/ true)) || emptyObjectType; + // 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() { - return deferredGlobalImportMetaType || (deferredGlobalImportMetaType = getGlobalType("ImportMeta" as __String, /*arity*/ 0, /*reportErrors*/ true)) || emptyObjectType; + // 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() { @@ -13437,72 +13831,76 @@ namespace ts { return deferredGlobalImportMetaExpressionType; } - function getGlobalESSymbolConstructorSymbol(reportErrors: boolean) { - return deferredGlobalESSymbolConstructorSymbol || (deferredGlobalESSymbolConstructorSymbol = getGlobalValueSymbol("Symbol" as __String, reportErrors)); + function getGlobalImportCallOptionsType(reportErrors: boolean) { + return (deferredGlobalImportCallOptionsType ||= getGlobalType("ImportCallOptions" as __String, /*arity*/ 0, reportErrors)) || emptyObjectType; } - function getGlobalESSymbolConstructorTypeSymbol(reportErrors: boolean) { - return deferredGlobalESSymbolConstructorTypeSymbol || (deferredGlobalESSymbolConstructorTypeSymbol = getGlobalTypeSymbol("SymbolConstructor" as __String, reportErrors)); + 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(reportErrors: boolean) { - return deferredGlobalESSymbolType || (deferredGlobalESSymbolType = getGlobalType("Symbol" as __String, /*arity*/ 0, reportErrors)) || emptyObjectType; + return (deferredGlobalESSymbolType ||= getGlobalType("Symbol" as __String, /*arity*/ 0, reportErrors)) || emptyObjectType; } function getGlobalPromiseType(reportErrors: boolean) { - return deferredGlobalPromiseType || (deferredGlobalPromiseType = getGlobalType("Promise" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; + return (deferredGlobalPromiseType ||= getGlobalType("Promise" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; } function getGlobalPromiseLikeType(reportErrors: boolean) { - return deferredGlobalPromiseLikeType || (deferredGlobalPromiseLikeType = getGlobalType("PromiseLike" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; + return (deferredGlobalPromiseLikeType ||= getGlobalType("PromiseLike" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; } function getGlobalPromiseConstructorSymbol(reportErrors: boolean): Symbol | undefined { - return deferredGlobalPromiseConstructorSymbol || (deferredGlobalPromiseConstructorSymbol = getGlobalValueSymbol("Promise" as __String, reportErrors)); + return deferredGlobalPromiseConstructorSymbol ||= getGlobalValueSymbol("Promise" as __String, reportErrors); } function getGlobalPromiseConstructorLikeType(reportErrors: boolean) { - return deferredGlobalPromiseConstructorLikeType || (deferredGlobalPromiseConstructorLikeType = getGlobalType("PromiseConstructorLike" as __String, /*arity*/ 0, reportErrors)) || emptyObjectType; + return (deferredGlobalPromiseConstructorLikeType ||= getGlobalType("PromiseConstructorLike" as __String, /*arity*/ 0, reportErrors)) || emptyObjectType; } function getGlobalAsyncIterableType(reportErrors: boolean) { - return deferredGlobalAsyncIterableType || (deferredGlobalAsyncIterableType = getGlobalType("AsyncIterable" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; + return (deferredGlobalAsyncIterableType ||= getGlobalType("AsyncIterable" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; } function getGlobalAsyncIteratorType(reportErrors: boolean) { - return deferredGlobalAsyncIteratorType || (deferredGlobalAsyncIteratorType = getGlobalType("AsyncIterator" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType; + return (deferredGlobalAsyncIteratorType ||= getGlobalType("AsyncIterator" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType; } function getGlobalAsyncIterableIteratorType(reportErrors: boolean) { - return deferredGlobalAsyncIterableIteratorType || (deferredGlobalAsyncIterableIteratorType = getGlobalType("AsyncIterableIterator" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; + return (deferredGlobalAsyncIterableIteratorType ||= getGlobalType("AsyncIterableIterator" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; } function getGlobalAsyncGeneratorType(reportErrors: boolean) { - return deferredGlobalAsyncGeneratorType || (deferredGlobalAsyncGeneratorType = getGlobalType("AsyncGenerator" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType; + return (deferredGlobalAsyncGeneratorType ||= getGlobalType("AsyncGenerator" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType; } function getGlobalIterableType(reportErrors: boolean) { - return deferredGlobalIterableType || (deferredGlobalIterableType = getGlobalType("Iterable" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; + return (deferredGlobalIterableType ||= getGlobalType("Iterable" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; } function getGlobalIteratorType(reportErrors: boolean) { - return deferredGlobalIteratorType || (deferredGlobalIteratorType = getGlobalType("Iterator" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType; + return (deferredGlobalIteratorType ||= getGlobalType("Iterator" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType; } function getGlobalIterableIteratorType(reportErrors: boolean) { - return deferredGlobalIterableIteratorType || (deferredGlobalIterableIteratorType = getGlobalType("IterableIterator" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; + return (deferredGlobalIterableIteratorType ||= getGlobalType("IterableIterator" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; } function getGlobalGeneratorType(reportErrors: boolean) { - return deferredGlobalGeneratorType || (deferredGlobalGeneratorType = getGlobalType("Generator" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType; + return (deferredGlobalGeneratorType ||= getGlobalType("Generator" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType; } function getGlobalIteratorYieldResultType(reportErrors: boolean) { - return deferredGlobalIteratorYieldResultType || (deferredGlobalIteratorYieldResultType = getGlobalType("IteratorYieldResult" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; + return (deferredGlobalIteratorYieldResultType ||= getGlobalType("IteratorYieldResult" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; } function getGlobalIteratorReturnResultType(reportErrors: boolean) { - return deferredGlobalIteratorReturnResultType || (deferredGlobalIteratorReturnResultType = getGlobalType("IteratorReturnResult" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; + return (deferredGlobalIteratorReturnResultType ||= getGlobalType("IteratorReturnResult" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; } function getGlobalTypeOrUndefined(name: __String, arity = 0): ObjectType | undefined { @@ -13510,16 +13908,26 @@ namespace ts { return symbol && getTypeOfGlobalSymbol(symbol, arity) as GenericType; } - function getGlobalExtractSymbol(): Symbol { - return deferredGlobalExtractSymbol || (deferredGlobalExtractSymbol = getGlobalSymbol("Extract" as __String, SymbolFlags.TypeAlias, Diagnostics.Cannot_find_global_type_0)!); // TODO: GH#18217 + 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 { - return deferredGlobalOmitSymbol || (deferredGlobalOmitSymbol = getGlobalSymbol("Omit" as __String, SymbolFlags.TypeAlias, Diagnostics.Cannot_find_global_type_0)!); // TODO: GH#18217 + 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(reportErrors: boolean) { - return deferredGlobalBigIntType || (deferredGlobalBigIntType = getGlobalType("BigInt" as __String, /*arity*/ 0, reportErrors)) || emptyObjectType; + return (deferredGlobalBigIntType ||= getGlobalType("BigInt" as __String, /*arity*/ 0, reportErrors)) || emptyObjectType; } /** @@ -13608,7 +14016,7 @@ namespace ts { function mayResolveTypeAlias(node: Node): boolean { switch (node.kind) { case SyntaxKind.TypeReference: - return isJSDocTypeReference(node) || !!(resolveTypeReferenceName((node as TypeReferenceNode).typeName, SymbolFlags.Type).flags & SymbolFlags.TypeAlias); + return isJSDocTypeReference(node) || !!(resolveTypeReferenceName(node as TypeReferenceNode, SymbolFlags.Type).flags & SymbolFlags.TypeAlias); case SyntaxKind.TypeQuery: return true; case SyntaxKind.TypeOperator: @@ -13709,7 +14117,7 @@ namespace ts { } } const fixedLength = properties.length; - const lengthSymbol = createSymbol(SymbolFlags.Property, "length" as __String); + const lengthSymbol = createSymbol(SymbolFlags.Property, "length" as __String, readonly ? CheckFlags.Readonly : 0); if (combinedFlags & ElementFlags.Variable) { lengthSymbol.type = numberType; } @@ -13893,7 +14301,7 @@ namespace ts { // We ignore 'never' types in unions if (!(flags & TypeFlags.Never)) { includes |= flags & TypeFlags.IncludesMask; - if (flags & TypeFlags.StructuredOrInstantiable) includes |= TypeFlags.IncludesStructuredOrInstantiable; + if (flags & TypeFlags.Instantiable) includes |= TypeFlags.IncludesInstantiable; if (type === wildcardType) includes |= TypeFlags.IncludesWildcard; if (!strictNullChecks && flags & TypeFlags.Nullable) { if (!(getObjectFlags(type) & ObjectFlags.ContainsWideningType)) includes |= TypeFlags.IncludesNonWideningType; @@ -13998,13 +14406,13 @@ namespace ts { } function removeStringLiteralsMatchedByTemplateLiterals(types: Type[]) { - const templates = filter(types, isPatternLiteralType); + const templates = filter(types, isPatternLiteralType) as TemplateLiteralType[]; if (templates.length) { let i = types.length; while (i > 0) { i--; const t = types[i]; - if (t.flags & TypeFlags.StringLiteral && some(templates, template => isTypeSubtypeOf(t, template))) { + if (t.flags & TypeFlags.StringLiteral && some(templates, template => isTypeMatchedByTemplateLiteralType(t, template))) { orderedRemoveItemAt(types, i); } } @@ -14053,7 +14461,9 @@ namespace ts { const includes = addTypesToUnion(typeSet, 0, types); if (unionReduction !== UnionReduction.None) { if (includes & TypeFlags.AnyOrUnknown) { - return includes & TypeFlags.Any ? includes & TypeFlags.IncludesWildcard ? wildcardType : anyType : unknownType; + return includes & TypeFlags.Any ? + includes & TypeFlags.IncludesWildcard ? wildcardType : anyType : + includes & TypeFlags.Null || containsType(typeSet, unknownType) ? unknownType : nonNullUnknownType; } if (exactOptionalPropertyTypes && includes & TypeFlags.Undefined) { const missingIndex = binarySearch(typeSet, missingType, getTypeId, compareValues); @@ -14198,13 +14608,19 @@ namespace ts { if (flags & TypeFlags.AnyOrUnknown) { if (type === wildcardType) includes |= TypeFlags.IncludesWildcard; } - else if ((strictNullChecks || !(flags & TypeFlags.Nullable)) && !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; + else if (strictNullChecks || !(flags & TypeFlags.Nullable)) { + if (exactOptionalPropertyTypes && type === missingType) { + includes |= TypeFlags.IncludesMissingType; + type = undefinedType; + } + if (!typeSet.has(type.id.toString())) { + if (type.flags & TypeFlags.Unit && includes & TypeFlags.Unit) { + // We have seen two distinct unit types which means we should reduce to an + // empty intersection. Adding TypeFlags.NonPrimitive causes that to happen. + includes |= TypeFlags.NonPrimitive; + } + typeSet.set(type.id.toString(), type); } - typeSet.set(type.id.toString(), type); } includes |= flags & TypeFlags.IncludesMask; } @@ -14279,14 +14695,14 @@ namespace ts { return false; } - function extractIrreducible(types: Type[], flag: TypeFlags) { - if (every(types, t => !!(t.flags & TypeFlags.Union) && some((t as UnionType).types, tt => !!(tt.flags & flag)))) { - for (let i = 0; i < types.length; i++) { - types[i] = filterType(types[i], t => !(t.flags & flag)); - } - return true; + function eachIsUnionContaining(types: Type[], flag: TypeFlags) { + return every(types, t => !!(t.flags & TypeFlags.Union) && some((t as UnionType).types, tt => !!(tt.flags & flag))); + } + + function removeFromEach(types: Type[], flag: TypeFlags) { + for (let i = 0; i < types.length; i++) { + types[i] = filterType(types[i], t => !(t.flags & flag)); } - return false; } // If the given list of types contains more than one union of primitive types, replace the @@ -14396,6 +14812,9 @@ namespace ts { if (includes & TypeFlags.IncludesEmptyObject && includes & TypeFlags.Object) { orderedRemoveItemAt(typeSet, findIndex(typeSet, isEmptyAnonymousObjectType)); } + if (includes & TypeFlags.IncludesMissingType) { + typeSet[typeSet.indexOf(undefinedType)] = missingType; + } if (typeSet.length === 0) { return unknownType; } @@ -14412,10 +14831,13 @@ namespace ts { // reduced we'll never reduce again, so this occurs at most once. result = getIntersectionType(typeSet, aliasSymbol, aliasTypeArguments); } - else if (extractIrreducible(typeSet, TypeFlags.Undefined)) { - result = getUnionType([getIntersectionType(typeSet), undefinedType], UnionReduction.Literal, aliasSymbol, aliasTypeArguments); + else if (eachIsUnionContaining(typeSet, TypeFlags.Undefined)) { + const undefinedOrMissingType = exactOptionalPropertyTypes && some(typeSet, t => containsType((t as UnionType).types, missingType)) ? missingType : undefinedType; + removeFromEach(typeSet, TypeFlags.Undefined); + result = getUnionType([getIntersectionType(typeSet), undefinedOrMissingType], UnionReduction.Literal, aliasSymbol, aliasTypeArguments); } - else if (extractIrreducible(typeSet, TypeFlags.Null)) { + else if (eachIsUnionContaining(typeSet, TypeFlags.Null)) { + removeFromEach(typeSet, TypeFlags.Null); result = getUnionType([getIntersectionType(typeSet), nullType], UnionReduction.Literal, aliasSymbol, aliasTypeArguments); } else { @@ -14503,19 +14925,58 @@ namespace ts { type.resolvedIndexType || (type.resolvedIndexType = createIndexType(type, /*stringsOnly*/ false)); } - function instantiateTypeAsMappedNameType(nameType: Type, type: MappedType, t: Type) { - return instantiateType(nameType, appendTypeMapping(type.mapper, getTypeParameterFromMappedType(type), t)); - } + /** + * This roughly mirrors `resolveMappedTypeMembers` in the nongeneric case, except only reports a union of the keys calculated, + * rather than manufacturing the properties. We can't just fetch the `constraintType` since that would ignore mappings + * and mapping the `constraintType` directly ignores how mapped types map _properties_ and not keys (thus ignoring subtype + * reduction in the constraintType) when possible. + * @param noIndexSignatures Indicates if _string_ index signatures should be elided. (other index signatures are always reported) + */ + function getIndexTypeForMappedType(type: MappedType, stringsOnly: boolean, noIndexSignatures: boolean | undefined) { + const typeParameter = getTypeParameterFromMappedType(type); + const constraintType = getConstraintTypeFromMappedType(type); + const nameType = getNameTypeFromMappedType(type.target as MappedType || type); + if (!nameType && !noIndexSignatures) { + // no mapping and no filtering required, just quickly bail to returning the constraint in the common case + return constraintType; + } + const keyTypes: Type[] = []; + if (isMappedTypeWithKeyofConstraintDeclaration(type)) { + // We have a { [P in keyof T]: X } - function getIndexTypeForMappedType(type: MappedType, noIndexSignatures: boolean | undefined) { - const constraint = filterType(getConstraintTypeFromMappedType(type), t => !(noIndexSignatures && t.flags & (TypeFlags.Any | TypeFlags.String))); - const nameType = type.declaration.nameType && getTypeFromTypeNode(type.declaration.nameType); - // If the constraint is exclusively string/number/never type(s), we need to pull the property names from the modified type and run them through the `nameType` mapper as well - // since they won't appear in the constraint, due to subtype reducing with the string/number index types - const properties = nameType && everyType(constraint, t => !!(t.flags & (TypeFlags.String | TypeFlags.Number | TypeFlags.Never))) && getPropertiesOfType(getApparentType(getModifiersTypeFromMappedType(type))); - return nameType ? - getUnionType([mapType(constraint, t => instantiateTypeAsMappedNameType(nameType, type, t)), mapType(getUnionType(map(properties || emptyArray, p => getLiteralTypeFromProperty(p, TypeFlags.StringOrNumberLiteralOrUnique))), t => instantiateTypeAsMappedNameType(nameType, type, t))]): - constraint; + // `getApparentType` on the T in a generic mapped type can trigger a circularity + // (conditionals and `infer` types create a circular dependency in the constraint resolution) + // so we only eagerly manifest the keys if the constraint is nongeneric + if (!isGenericIndexType(constraintType)) { + const modifiersType = getApparentType(getModifiersTypeFromMappedType(type)); // The 'T' in 'keyof T' + forEachMappedTypePropertyKeyTypeAndIndexSignatureKeyType(modifiersType, TypeFlags.StringOrNumberLiteralOrUnique, stringsOnly, addMemberForKeyType); + } + else { + // we have a generic index and a homomorphic mapping (but a distributive key remapping) - we need to defer the whole `keyof whatever` for later + // since it's not safe to resolve the shape of modifier type + return getIndexTypeForGenericType(type, stringsOnly); + } + } + else { + forEachType(getLowerBoundOfKeyType(constraintType), addMemberForKeyType); + } + if (isGenericIndexType(constraintType)) { // include the generic component in the resulting type + forEachType(constraintType, addMemberForKeyType); + } + // we had to pick apart the constraintType to potentially map/filter it - compare the final resulting list with the original constraintType, + // so we can return the union that preserves aliases/origin data if possible + const result = noIndexSignatures ? filterType(getUnionType(keyTypes), t => !(t.flags & (TypeFlags.Any | TypeFlags.String))) : getUnionType(keyTypes); + if (result.flags & TypeFlags.Union && constraintType.flags & TypeFlags.Union && getTypeListId((result as UnionType).types) === getTypeListId((constraintType as UnionType).types)){ + return constraintType; + } + return result; + + function addMemberForKeyType(keyType: Type) { + const propNameType = nameType ? instantiateType(nameType, appendTypeMapping(type.mapper, typeParameter, keyType)) : keyType; + // `keyof` currently always returns `string | number` for concrete `string` index signatures - the below ternary keeps that behavior for mapped types + // See `getLiteralTypeFromProperties` where there's a similar ternary to cause the same behavior. + keyTypes.push(propNameType === stringType ? stringOrNumberType : propNameType); + } } // Ordinarily we reduce a keyof M, where M is a mapped type { [P in K as N

]: X }, to simply N. This however presumes @@ -14578,7 +15039,7 @@ namespace ts { return type.flags & TypeFlags.Union ? getIntersectionType(map((type as UnionType).types, t => getIndexType(t, stringsOnly, noIndexSignatures))) : type.flags & TypeFlags.Intersection ? getUnionType(map((type as IntersectionType).types, t => getIndexType(t, stringsOnly, noIndexSignatures))) : type.flags & TypeFlags.InstantiableNonPrimitive || isGenericTupleType(type) || isGenericMappedType(type) && !hasDistributiveNameType(type) ? getIndexTypeForGenericType(type as InstantiableType | UnionOrIntersectionType, stringsOnly) : - getObjectFlags(type) & ObjectFlags.Mapped ? getIndexTypeForMappedType(type as MappedType, noIndexSignatures) : + getObjectFlags(type) & ObjectFlags.Mapped ? getIndexTypeForMappedType(type as MappedType, stringsOnly, noIndexSignatures) : type === wildcardType ? wildcardType : type.flags & TypeFlags.Unknown ? neverType : type.flags & (TypeFlags.Any | TypeFlags.Never) ? keyofConstraintType : @@ -14803,7 +15264,7 @@ namespace ts { } const prop = getPropertyOfType(objectType, propName); if (prop) { - if (accessFlags & AccessFlags.ReportDeprecated && accessNode && prop.declarations && getDeclarationNodeFlagsFromSymbol(prop) & NodeFlags.Deprecated && isUncalledFunctionReference(accessNode, 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); } @@ -15012,10 +15473,6 @@ namespace ts { (type.flags & (TypeFlags.InstantiableNonPrimitive | TypeFlags.Index | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) && !isPatternLiteralType(type) ? ObjectFlags.IsGenericIndexType : 0); } - function isThisTypeParameter(type: Type): boolean { - return !!(type.flags & TypeFlags.TypeParameter && (type as TypeParameter).isThisType); - } - 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) : @@ -15091,13 +15548,6 @@ namespace ts { return type[cache] = type; } - function isConditionalTypeAlwaysTrueDisregardingInferTypes(type: ConditionalType) { - const extendsInferParamMapper = type.root.inferTypeParameters && createTypeMapper(type.root.inferTypeParameters, map(type.root.inferTypeParameters, () => wildcardType)); - const checkType = type.checkType; - const extendsType = type.extendsType; - return isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(instantiateType(extendsType, extendsInferParamMapper))); - } - function getSimplifiedConditionalType(type: ConditionalType, writing: boolean) { const checkType = type.checkType; const extendsType = type.extendsType; @@ -15421,6 +15871,12 @@ namespace ts { 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) { @@ -15697,7 +16153,7 @@ namespace ts { const declarations = concatenate(leftProp.declarations, rightProp.declarations); const flags = SymbolFlags.Property | (leftProp.flags & SymbolFlags.Optional); const result = createSymbol(flags, leftProp.escapedName); - result.type = getUnionType([getTypeOfSymbol(leftProp), removeMissingOrUndefinedType(rightType)]); + result.type = getUnionType([getTypeOfSymbol(leftProp), removeMissingOrUndefinedType(rightType)], UnionReduction.Subtype); result.leftSpread = leftProp; result.rightSpread = rightProp; result.declarations = declarations; @@ -16176,7 +16632,9 @@ namespace ts { } function getObjectTypeInstantiation(type: AnonymousType | DeferredTypeReference, mapper: TypeMapper, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]) { - const declaration = type.objectFlags & ObjectFlags.Reference ? (type as TypeReference).node! : type.symbol.declarations![0]; + 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; @@ -16192,8 +16650,8 @@ namespace ts { outerTypeParameters = addRange(outerTypeParameters, templateTagParameters); } typeParameters = outerTypeParameters || emptyArray; - const allDeclarations = type.objectFlags & ObjectFlags.Reference ? [declaration] : type.symbol.declarations!; - typeParameters = (target.objectFlags & ObjectFlags.Reference || target.symbol.flags & SymbolFlags.Method || target.symbol.flags & SymbolFlags.TypeLiteral) && !target.aliasTypeArguments ? + 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; @@ -16290,9 +16748,11 @@ namespace ts { const mappedTypeVariable = instantiateType(typeVariable, mapper); if (typeVariable !== mappedTypeVariable) { return mapTypeWithAlias(getReducedType(mappedTypeVariable), t => { - if (t.flags & (TypeFlags.AnyOrUnknown | TypeFlags.InstantiableNonPrimitive | TypeFlags.Object | TypeFlags.Intersection) && t !== wildcardType && t !== errorType) { + if (t.flags & (TypeFlags.AnyOrUnknown | TypeFlags.InstantiableNonPrimitive | TypeFlags.Object | TypeFlags.Intersection) && t !== wildcardType && !isErrorType(t)) { if (!type.declaration.nameType) { - if (isArrayType(t)) { + let constraint; + if (isArrayType(t) || t.flags & TypeFlags.Any && findResolutionCycleStartIndex(typeVariable, TypeSystemPropertyName.ImmediateBaseConstraint) < 0 && + (constraint = getConstraintOfTypeParameter(typeVariable)) && everyType(constraint, or(isArrayType, isTupleType))) { return instantiateMappedArrayType(t, type, prependTypeMapping(typeVariable, t, mapper)); } if (isGenericTupleType(t)) { @@ -16335,7 +16795,7 @@ namespace ts { function instantiateMappedArrayType(arrayType: Type, mappedType: MappedType, mapper: TypeMapper) { const elementType = instantiateMappedTypeTemplate(mappedType, numberType, /*isOptional*/ true, mapper); - return elementType === errorType ? errorType : + return isErrorType(elementType) ? errorType : createArrayType(elementType, getModifiedReadonlyState(isReadonlyArrayType(arrayType), getMappedTypeModifiers(mappedType))); } @@ -16372,6 +16832,9 @@ namespace ts { mapper = combineTypeMappers(makeUnaryTypeMapper(origTypeParameter, freshTypeParameter), mapper); freshTypeParameter.mapper = mapper; } + if (type.objectFlags & ObjectFlags.InstantiationExpressionType) { + (result as InstantiationExpressionType).node = (type as InstantiationExpressionType).node; + } result.target = type; result.mapper = mapper; result.aliasSymbol = aliasSymbol || type.aliasSymbol; @@ -17213,10 +17676,6 @@ namespace ts { if (sourceRestType || targetRestType) { void instantiateType(sourceRestType || targetRestType, reportUnreliableMarkers); } - if (sourceRestType && targetRestType && sourceCount !== targetCount) { - // We're not able to relate misaligned complex rest parameters - return Ternary.False; - } const kind = target.declaration ? target.declaration.kind : SyntaxKind.Unknown; const strictVariance = !(checkMode & SignatureCheckMode.Callback) && strictFunctionTypes && kind !== SyntaxKind.MethodDeclaration && @@ -17456,8 +17915,10 @@ namespace ts { (source as LiteralType).value === (target as LiteralType).value && isEnumTypeRelatedTo(getParentOfSymbol(source.symbol)!, getParentOfSymbol(target.symbol)!, errorReporter)) return true; } - if (s & TypeFlags.Undefined && (!strictNullChecks || t & (TypeFlags.Undefined | TypeFlags.Void))) return true; - if (s & TypeFlags.Null && (!strictNullChecks || t & TypeFlags.Null)) 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) return true; if (relation === assignableRelation || relation === comparableRelation) { if (s & TypeFlags.Any) return true; @@ -17492,7 +17953,7 @@ namespace ts { // We skip the cache lookup shortcut when we're in a variance computation so the outofbandVarianceMarkerHandler // will get called for cached results that are unreliable or unmeasurable. if (source.flags & TypeFlags.Object && target.flags & TypeFlags.Object && !outofbandVarianceMarkerHandler) { - const related = relation.get(getRelationKey(source, target, IntersectionState.None, relation)); + const related = relation.get(getRelationKey(source, target, IntersectionState.None, relation, /*ignoreConstraints*/ false)); if (related !== undefined) { return !!(related & RelationComparisonResult.Succeeded); } @@ -17549,22 +18010,23 @@ namespace ts { let sourceStack: Type[]; let targetStack: Type[]; let maybeCount = 0; - let depth = 0; + let sourceDepth = 0; + let targetDepth = 0; let expandingFlags = ExpandingFlags.None; let overflow = false; let overrideNextErrorInfo = 0; // How many `reportRelationError` calls should be skipped in the elaboration pyramid let lastSkippedInfo: [Type, Type] | undefined; - let incompatibleStack: [DiagnosticMessage, (string | number)?, (string | number)?, (string | number)?, (string | number)?][] = []; + let incompatibleStack: [DiagnosticMessage, (string | number)?, (string | number)?, (string | number)?, (string | number)?][] | undefined; let inPropertyCheck = false; Debug.assert(relation !== identityRelation || !errorNode, "no error reporting in identity checking"); - const result = isRelatedTo(source, target, /*reportErrors*/ !!errorNode, headMessage); - if (incompatibleStack.length) { + const result = isRelatedTo(source, target, RecursionFlags.Both, /*reportErrors*/ !!errorNode, headMessage); + if (incompatibleStack) { reportIncompatibleStack(); } if (overflow) { - tracing?.instant(tracing.Phase.CheckTypes, "checkTypeRelatedTo_DepthLimit", { sourceId: source.id, targetId: target.id, depth }); + tracing?.instant(tracing.Phase.CheckTypes, "checkTypeRelatedTo_DepthLimit", { sourceId: source.id, targetId: target.id, depth: sourceDepth, targetDepth }); const diag = error(errorNode || currentNode, Diagnostics.Excessive_stack_depth_comparing_types_0_and_1, typeToString(source), typeToString(target)); if (errorOutputContainer) { (errorOutputContainer.errors || (errorOutputContainer.errors = [])).push(diag); @@ -17622,21 +18084,21 @@ namespace ts { return { errorInfo, lastSkippedInfo, - incompatibleStack: incompatibleStack.slice(), + incompatibleStack: incompatibleStack?.slice(), overrideNextErrorInfo, - relatedInfo: !relatedInfo ? undefined : relatedInfo.slice() as ([DiagnosticRelatedInformation, ...DiagnosticRelatedInformation[]] | undefined) + relatedInfo: relatedInfo?.slice() as [DiagnosticRelatedInformation, ...DiagnosticRelatedInformation[]] | undefined, }; } function reportIncompatibleError(message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number) { overrideNextErrorInfo++; // Suppress the next relation error lastSkippedInfo = undefined; // Reset skipped info cache - incompatibleStack.push([message, arg0, arg1, arg2, arg3]); + (incompatibleStack ||= []).push([message, arg0, arg1, arg2, arg3]); } function reportIncompatibleStack() { - const stack = incompatibleStack; - incompatibleStack = []; + const stack = incompatibleStack || []; + incompatibleStack = undefined; const info = lastSkippedInfo; lastSkippedInfo = undefined; if (stack.length === 1) { @@ -17650,7 +18112,7 @@ namespace ts { // 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: typeof incompatibleStack = []; + const secondaryRootErrors: [DiagnosticMessage, (string | number)?, (string | number)?, (string | number)?, (string | number)?][] = []; while (stack.length) { const [msg, ...args] = stack.pop()!; switch (msg.code) { @@ -17665,7 +18127,7 @@ namespace ts { path = `${str}`; } // Otherwise write a dotted name if possible - else if (isIdentifierText(str, compilerOptions.target)) { + else if (isIdentifierText(str, getEmitScriptTarget(compilerOptions))) { path = `${path}.${str}`; } // Failing that, check if the name is already a computed name @@ -17744,7 +18206,7 @@ namespace ts { function reportError(message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): void { Debug.assert(!!errorNode); - if (incompatibleStack.length) reportIncompatibleStack(); + if (incompatibleStack) reportIncompatibleStack(); if (message.elidedInCompatabilityPyramid) return; errorInfo = chainDiagnosticMessages(errorInfo, message, arg0, arg1, arg2, arg3); } @@ -17760,7 +18222,7 @@ namespace ts { } function reportRelationError(message: DiagnosticMessage | undefined, source: Type, target: Type) { - if (incompatibleStack.length) reportIncompatibleStack(); + if (incompatibleStack) reportIncompatibleStack(); const [sourceType, targetType] = getTypeNamesForErrorDisplay(source, target); let generalizedSource = source; let generalizedSourceType = sourceType; @@ -17803,6 +18265,13 @@ namespace ts { 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; } } @@ -17862,20 +18331,26 @@ namespace ts { 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, reportErrors = false, headMessage?: DiagnosticMessage, intersectionState = IntersectionState.None): Ternary { + function isRelatedTo(originalSource: Type, originalTarget: Type, recursionFlags: RecursionFlags = RecursionFlags.Both, reportErrors = false, headMessage?: DiagnosticMessage, intersectionState = IntersectionState.None): Ternary { // Before normalization: if `source` is type an object type, and `target` is primitive, // skip all the checks we don't need and just return `isSimpleTypeRelatedTo` result if (originalSource.flags & TypeFlags.Object && originalTarget.flags & TypeFlags.Primitive) { if (isSimpleTypeRelatedTo(originalSource, originalTarget, relation, reportErrors ? reportError : undefined)) { return Ternary.True; } - reportErrorResults(originalSource, originalTarget, Ternary.False, !!(getObjectFlags(originalSource) & ObjectFlags.JsxAttributes)); + if (reportErrors) { + reportErrorResults(originalSource, originalTarget, originalSource, originalTarget, headMessage); + } return Ternary.False; } @@ -17889,7 +18364,10 @@ namespace ts { if (source === target) return Ternary.True; if (relation === identityRelation) { - return isIdenticalTo(source, target); + 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, @@ -17901,172 +18379,136 @@ namespace ts { return Ternary.True; } - // Try to see if we're relating something like `Foo` -> `Bar | null | undefined`. - // If so, reporting the `null` and `undefined` in the type is hardly useful. - // First, see if we're even relating an object type to a union. - // Then see if the target is stripped down to a single non-union type. - // Note - // * We actually want to remove null and undefined naively here (rather than using getNonNullableType), - // since we don't want to end up with a worse error like "`Foo` is not assignable to `NonNullable`" - // when dealing with generics. - // * We also don't deal with primitive source types, since we already halt elaboration below. - if (target.flags & TypeFlags.Union && source.flags & TypeFlags.Object && - (target as UnionType).types.length <= 3 && maybeTypeOfKind(target, TypeFlags.Nullable)) { - const nullStrippedTarget = extractTypesOfKind(target, ~TypeFlags.Nullable); - if (!(nullStrippedTarget.flags & (TypeFlags.Union | TypeFlags.Never))) { - target = getNormalizedType(nullStrippedTarget, /*writing*/ 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 (source === nullStrippedTarget) return Ternary.True; } if (relation === comparableRelation && !(target.flags & TypeFlags.Never) && isSimpleTypeRelatedTo(target, source, relation) || isSimpleTypeRelatedTo(source, target, relation, reportErrors ? reportError : undefined)) return Ternary.True; - const isComparingJsxAttributes = !!(getObjectFlags(source) & ObjectFlags.JsxAttributes); - const isPerformingExcessPropertyChecks = !(intersectionState & IntersectionState.Target) && (isObjectLiteralType(source) && getObjectFlags(source) & ObjectFlags.FreshLiteral); - if (isPerformingExcessPropertyChecks) { - if (hasExcessProperties(source as FreshObjectLiteralType, target, reportErrors)) { + 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 && !(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) { - reportRelationError(headMessage, source, originalTarget.aliasSymbol ? originalTarget : target); + 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; } - } - const isPerformingCommonPropertyChecks = relation !== comparableRelation && !(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)); - 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, /*reportErrors*/ false) || - constructs.length > 0 && isRelatedTo(getReturnTypeOfSignature(constructs[0]), target, /*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); - } + 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); + let result = skipCaching ? + unionOrIntersectionRelatedTo(source, target, reportErrors, intersectionState) : + recursiveTypeRelatedTo(source, target, reportErrors, intersectionState, recursionFlags); + // For certain combinations involving intersections and optional, excess, or mismatched properties we need + // an extra property check where the intersection is viewed as a single object. The following are motivating + // examples that all should be errors, but aren't without this extra property check: + // + // let obj: { a: { x: string } } & { c: number } = { a: { x: 'hello', y: 2 }, c: 5 }; // Nested excess property + // + // declare let wrong: { a: { y: string } }; + // let weak: { a?: { x?: number } } & { c?: string } = wrong; // Nested weak object type + // + // function foo(x: { a?: string }, y: T & { a: boolean }) { + // x = y; // Mismatched property in source intersection + // } + // + // We suppress recursive intersection property checks because they can generate lots of work when relating + // recursive intersections that are structurally similar but not exactly identical. See #37854. + if (result && !inPropertyCheck && ( + target.flags & TypeFlags.Intersection && (isPerformingExcessPropertyChecks || isPerformingCommonPropertyChecks) || + isNonGenericObjectType(target) && !isArrayType(target) && !isTupleType(target) && source.flags & TypeFlags.Intersection && getApparentType(source).flags & TypeFlags.StructuredType && !some((source as IntersectionType).types, t => !!(getObjectFlags(t) & ObjectFlags.NonInferrableType)))) { + inPropertyCheck = true; + result &= recursiveTypeRelatedTo(source, target, reportErrors, IntersectionState.PropertyCheck, recursionFlags); + inPropertyCheck = false; } - return Ternary.False; - } - - traceUnionsOrIntersectionsTooLarge(source, target); - - let result = Ternary.False; - const saveErrorInfo = captureErrorCalculationState(); - - // 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.UnionOrIntersection || target.flags & TypeFlags.UnionOrIntersection) { - result = getConstituentCount(source) * getConstituentCount(target) >= 4 ? - recursiveTypeRelatedTo(source, target, reportErrors, intersectionState | IntersectionState.UnionIntersectionCheck) : - structuredTypeRelatedTo(source, target, reportErrors, intersectionState | IntersectionState.UnionIntersectionCheck); - } - if (!result && !(source.flags & TypeFlags.Union) && (source.flags & (TypeFlags.StructuredOrInstantiable) || target.flags & TypeFlags.StructuredOrInstantiable)) { - if (result = recursiveTypeRelatedTo(source, target, reportErrors, intersectionState)) { - resetErrorInfo(saveErrorInfo); - } - } - if (!result && source.flags & (TypeFlags.Intersection | TypeFlags.TypeParameter)) { - // 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)` - const constraint = getEffectiveConstraintOfIntersection(source.flags & TypeFlags.Intersection ? (source as IntersectionType).types: [source], !!(target.flags & TypeFlags.Union)); - if (constraint && (source.flags & TypeFlags.Intersection || target.flags & TypeFlags.Union)) { - if (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 - if (result = isRelatedTo(constraint, target, /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState)) { - resetErrorInfo(saveErrorInfo); - } - } + if (result) { + return result; } } - // For certain combinations involving intersections and optional, excess, or mismatched properties we need - // an extra property check where the intersection is viewed as a single object. The following are motivating - // examples that all should be errors, but aren't without this extra property check: - // - // let obj: { a: { x: string } } & { c: number } = { a: { x: 'hello', y: 2 }, c: 5 }; // Nested excess property - // - // declare let wrong: { a: { y: string } }; - // let weak: { a?: { x?: number } } & { c?: string } = wrong; // Nested weak object type - // - // function foo(x: { a?: string }, y: T & { a: boolean }) { - // x = y; // Mismatched property in source intersection - // } - // - // We suppress recursive intersection property checks because they can generate lots of work when relating - // recursive intersections that are structurally similar but not exactly identical. See #37854. - if (result && !inPropertyCheck && ( - target.flags & TypeFlags.Intersection && (isPerformingExcessPropertyChecks || isPerformingCommonPropertyChecks) || - isNonGenericObjectType(target) && !isArrayType(target) && !isTupleType(target) && source.flags & TypeFlags.Intersection && getApparentType(source).flags & TypeFlags.StructuredType && !some((source as IntersectionType).types, t => !!(getObjectFlags(t) & ObjectFlags.NonInferrableType)))) { - inPropertyCheck = true; - result &= recursiveTypeRelatedTo(source, target, reportErrors, IntersectionState.PropertyCheck); - inPropertyCheck = false; + if (reportErrors) { + reportErrorResults(originalSource, originalTarget, source, target, headMessage); } + return Ternary.False; + } - reportErrorResults(source, target, result, isComparingJsxAttributes); - return result; - - function reportErrorResults(source: Type, target: Type, result: Ternary, isComparingJsxAttributes: boolean) { - if (!result && reportErrors) { - 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); - 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 (isComparingJsxAttributes && target.flags & TypeFlags.Intersection) { - const targetTypes = (target as IntersectionType).types; - const intrinsicAttributes = getJsxType(JsxNames.IntrinsicAttributes, errorNode); - const intrinsicClassAttributes = getJsxType(JsxNames.IntrinsicClassAttributes, errorNode); - if (intrinsicAttributes !== errorType && intrinsicClassAttributes !== errorType && - (contains(targetTypes, intrinsicAttributes) || contains(targetTypes, intrinsicClassAttributes))) { - // do not report top error - return result; - } - } - else { - errorInfo = elaborateNeverIntersection(errorInfo, originalTarget); - } - if (!headMessage && maybeSuppress) { - lastSkippedInfo = [source, target]; - // Used by, eg, missing property checking to replace the top-level message with a more informative one - return result; - } - reportRelationError(headMessage, source, target); + function reportErrorResults(originalSource: Type, originalTarget: Type, source: Type, target: Type, headMessage: DiagnosticMessage | undefined) { + const sourceHasBase = !!getSingleBaseForNonAugmentingSubtype(originalSource); + const targetHasBase = !!getSingleBaseForNonAugmentingSubtype(originalTarget); + source = (originalSource.aliasSymbol || sourceHasBase) ? originalSource : source; + target = (originalTarget.aliasSymbol || targetHasBase) ? originalTarget : target; + let maybeSuppress = overrideNextErrorInfo > 0; + if (maybeSuppress) { + overrideNextErrorInfo--; + } + if (source.flags & TypeFlags.Object && target.flags & TypeFlags.Object) { + const currentError = errorInfo; + tryElaborateArrayLikeErrors(source, target, /*reportErrors*/ true); + if (errorInfo !== currentError) { + maybeSuppress = !!errorInfo; } } + if (source.flags & TypeFlags.Object && target.flags & TypeFlags.Primitive) { + tryElaborateErrorsForPrimitivesAndObjects(source, target); + } + else if (source.symbol && source.flags & TypeFlags.Object && globalObjectType === source) { + reportError(Diagnostics.The_Object_type_is_assignable_to_very_few_other_types_Did_you_mean_to_use_the_any_type_instead); + } + else if (getObjectFlags(source) & ObjectFlags.JsxAttributes && target.flags & TypeFlags.Intersection) { + const targetTypes = (target as IntersectionType).types; + const intrinsicAttributes = getJsxType(JsxNames.IntrinsicAttributes, errorNode); + const intrinsicClassAttributes = getJsxType(JsxNames.IntrinsicClassAttributes, errorNode); + if (!isErrorType(intrinsicAttributes) && !isErrorType(intrinsicClassAttributes) && + (contains(targetTypes, intrinsicAttributes) || contains(targetTypes, intrinsicClassAttributes))) { + // do not report top error + return; + } + } + else { + errorInfo = elaborateNeverIntersection(errorInfo, originalTarget); + } + if (!headMessage && maybeSuppress) { + lastSkippedInfo = [source, target]; + // Used by, eg, missing property checking to replace the top-level message with a more informative one + return; + } + reportRelationError(headMessage, source, target); } function traceUnionsOrIntersectionsTooLarge(source: Type, target: Type): void { @@ -18098,20 +18540,6 @@ namespace ts { } } - function isIdenticalTo(source: Type, target: Type): Ternary { - if (source.flags !== target.flags) return Ternary.False; - if (source.flags & TypeFlags.Singleton) return Ternary.True; - traceUnionsOrIntersectionsTooLarge(source, target); - if (source.flags & TypeFlags.UnionOrIntersection) { - let result = eachTypeRelatedToSomeType(source as UnionOrIntersectionType, target as UnionOrIntersectionType); - if (result) { - result &= eachTypeRelatedToSomeType(target as UnionOrIntersectionType, source as UnionOrIntersectionType); - } - return result; - } - return recursiveTypeRelatedTo(source, target, /*reportErrors*/ false, IntersectionState.None); - } - function getTypeOfPropertyInTypes(types: Type[], name: __String) { const appendPropType = (propTypes: Type[] | undefined, type: Type) => { type = getApparentType(type); @@ -18193,7 +18621,7 @@ namespace ts { } return true; } - if (checkTypes && !isRelatedTo(getTypeOfSymbol(prop), getTypeOfPropertyInTypes(checkTypes, prop.escapedName), reportErrors)) { + if (checkTypes && !isRelatedTo(getTypeOfSymbol(prop), getTypeOfPropertyInTypes(checkTypes, prop.escapedName), RecursionFlags.Both, reportErrors)) { if (reportErrors) { reportIncompatibleError(Diagnostics.Types_of_property_0_are_incompatible, symbolToString(prop)); } @@ -18208,6 +18636,42 @@ namespace ts { return prop.valueDeclaration && container.valueDeclaration && prop.valueDeclaration.parent === container.valueDeclaration; } + function unionOrIntersectionRelatedTo(source: Type, target: Type, reportErrors: boolean, intersectionState: IntersectionState): Ternary { + // Note that these checks are specifically ordered to produce correct results. In particular, + // we need to deconstruct unions before intersections (because unions are always at the top), + // and we need to handle "each" relations before "some" relations for the same kind of type. + if (source.flags & TypeFlags.Union) { + return relation === comparableRelation ? + someTypeRelatedToType(source as UnionType, target, reportErrors && !(source.flags & TypeFlags.Primitive), intersectionState) : + eachTypeRelatedToType(source as UnionType, target, reportErrors && !(source.flags & TypeFlags.Primitive), intersectionState); + } + if (target.flags & TypeFlags.Union) { + return typeRelatedToSomeType(getRegularTypeOfObjectLiteral(source), target as UnionType, reportErrors && !(source.flags & TypeFlags.Primitive) && !(target.flags & TypeFlags.Primitive)); + } + if (target.flags & TypeFlags.Intersection) { + return typeRelatedToEachType(getRegularTypeOfObjectLiteral(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, getBaseConstraintOrType); + if (constraints !== (source as IntersectionType).types) { + source = getIntersectionType(constraints); + if (!(source.flags & TypeFlags.Intersection)) { + return isRelatedTo(source, target, 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; @@ -18229,21 +18693,24 @@ namespace ts { } const match = getMatchingUnionConstituentForType(target as UnionType, source); if (match) { - const related = isRelatedTo(source, match, /*reportErrors*/ false); + const related = isRelatedTo(source, match, RecursionFlags.Target, /*reportErrors*/ false); if (related) { return related; } } } for (const type of targetTypes) { - const related = isRelatedTo(source, type, /*reportErrors*/ false); + const related = isRelatedTo(source, type, RecursionFlags.Target, /*reportErrors*/ false); if (related) { return related; } } if (reportErrors) { + // Elaborate only if we can find a best matching type in the target union const bestMatchingType = getBestMatchingType(source, target, isRelatedTo); - isRelatedTo(source, bestMatchingType || targetTypes[targetTypes.length - 1], /*reportErrors*/ true); + if (bestMatchingType) { + isRelatedTo(source, bestMatchingType, RecursionFlags.Target, /*reportErrors*/ true); + } } return Ternary.False; } @@ -18252,7 +18719,7 @@ namespace ts { let result = Ternary.True; const targetTypes = target.types; for (const targetType of targetTypes) { - const related = isRelatedTo(source, targetType, reportErrors, /*headMessage*/ undefined, intersectionState); + const related = isRelatedTo(source, targetType, RecursionFlags.Target, reportErrors, /*headMessage*/ undefined, intersectionState); if (!related) { return Ternary.False; } @@ -18268,7 +18735,7 @@ namespace ts { } const len = sourceTypes.length; for (let i = 0; i < len; i++) { - const related = isRelatedTo(sourceTypes[i], target, reportErrors && i === len - 1, /*headMessage*/ undefined, intersectionState); + const related = isRelatedTo(sourceTypes[i], target, RecursionFlags.Source, reportErrors && i === len - 1, /*headMessage*/ undefined, intersectionState); if (related) { return related; } @@ -18300,13 +18767,13 @@ namespace ts { // 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], /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState); + 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, reportErrors, /*headMessage*/ undefined, intersectionState); + const related = isRelatedTo(sourceType, target, RecursionFlags.Source, reportErrors, /*headMessage*/ undefined, intersectionState); if (!related) { return Ternary.False; } @@ -18336,31 +18803,31 @@ namespace ts { // 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, /*reportErrors*/ false) : compareTypesIdentical(s, t); + related = relation === identityRelation ? isRelatedTo(s, t, RecursionFlags.Both, /*reportErrors*/ false) : compareTypesIdentical(s, t); } else if (variance === VarianceFlags.Covariant) { - related = isRelatedTo(s, t, reportErrors, /*headMessage*/ undefined, intersectionState); + related = isRelatedTo(s, t, RecursionFlags.Both, reportErrors, /*headMessage*/ undefined, intersectionState); } else if (variance === VarianceFlags.Contravariant) { - related = isRelatedTo(t, s, reportErrors, /*headMessage*/ undefined, intersectionState); + 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, /*reportErrors*/ false); + related = isRelatedTo(t, s, RecursionFlags.Both, /*reportErrors*/ false); if (!related) { - related = isRelatedTo(s, t, reportErrors, /*headMessage*/ undefined, intersectionState); + 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, reportErrors, /*headMessage*/ undefined, intersectionState); + related = isRelatedTo(s, t, RecursionFlags.Both, reportErrors, /*headMessage*/ undefined, intersectionState); if (related) { - related &= isRelatedTo(t, s, reportErrors, /*headMessage*/ undefined, intersectionState); + related &= isRelatedTo(t, s, RecursionFlags.Both, reportErrors, /*headMessage*/ undefined, intersectionState); } } if (!related) { @@ -18377,11 +18844,12 @@ namespace ts { // 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): Ternary { + function recursiveTypeRelatedTo(source: Type, target: Type, reportErrors: boolean, intersectionState: IntersectionState, recursionFlags: RecursionFlags): Ternary { if (overflow) { return Ternary.False; } - const id = getRelationKey(source, target, intersectionState | (inPropertyCheck ? IntersectionState.InPropertyCheck : 0), relation); + const keyIntersectionState = intersectionState | (inPropertyCheck ? IntersectionState.InPropertyCheck : 0); + const id = getRelationKey(source, target, keyIntersectionState, relation, /*ingnoreConstraints*/ false); const entry = relation.get(id); if (entry !== undefined) { if (reportErrors && entry & RelationComparisonResult.Failed && !(entry & RelationComparisonResult.Reported)) { @@ -18408,20 +18876,17 @@ namespace ts { targetStack = []; } else { - // generate a key where all type parameter id positions are replaced with unconstrained type parameter ids - // this isn't perfect - nested type references passed as type arguments will muck up the indexes and thus - // prevent finding matches- but it should hit up the common cases - const broadestEquivalentId = id.split(",").map(i => i.replace(/-\d+/g, (_match, offset: number) => { - const index = length(id.slice(0, offset).match(/[-=]/g) || undefined); - return `=${index}`; - })).join(","); + // 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, keyIntersectionState, relation, /*ignoreConstraints*/ true) : undefined; for (let i = 0; i < maybeCount; i++) { // If source and target are already being compared, consider them related with assumptions - if (id === maybeKeys[i] || broadestEquivalentId === maybeKeys[i]) { + if (id === maybeKeys[i] || broadestEquivalentId && broadestEquivalentId === maybeKeys[i]) { return Ternary.Maybe; } } - if (depth === 100) { + if (sourceDepth === 100 || targetDepth === 100) { overflow = true; return Ternary.False; } @@ -18429,12 +18894,17 @@ namespace ts { const maybeStart = maybeCount; maybeKeys[maybeCount] = id; maybeCount++; - sourceStack[depth] = source; - targetStack[depth] = target; - depth++; const saveExpandingFlags = expandingFlags; - if (!(expandingFlags & ExpandingFlags.Source) && isDeeplyNestedType(source, sourceStack, depth)) expandingFlags |= ExpandingFlags.Source; - if (!(expandingFlags & ExpandingFlags.Target) && isDeeplyNestedType(target, targetStack, depth)) expandingFlags |= ExpandingFlags.Target; + if (recursionFlags & RecursionFlags.Source) { + sourceStack[sourceDepth] = source; + sourceDepth++; + if (!(expandingFlags & ExpandingFlags.Source) && isDeeplyNestedType(source, sourceStack, sourceDepth)) expandingFlags |= ExpandingFlags.Source; + } + if (recursionFlags & RecursionFlags.Target) { + targetStack[targetDepth] = target; + targetDepth++; + if (!(expandingFlags & ExpandingFlags.Target) && isDeeplyNestedType(target, targetStack, targetDepth)) expandingFlags |= ExpandingFlags.Target; + } let originalHandler: typeof outofbandVarianceMarkerHandler; let propagatingVarianceFlags: RelationComparisonResult = 0; if (outofbandVarianceMarkerHandler) { @@ -18445,24 +18915,36 @@ namespace ts { }; } + 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, + 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(); } - const result = expandingFlags !== ExpandingFlags.Both ? structuredTypeRelatedTo(source, target, reportErrors, intersectionState) : Ternary.Maybe; if (outofbandVarianceMarkerHandler) { outofbandVarianceMarkerHandler = originalHandler; } + if (recursionFlags & RecursionFlags.Source) { + sourceDepth--; + } + if (recursionFlags & RecursionFlags.Target) { + targetDepth--; + } expandingFlags = saveExpandingFlags; - depth--; if (result) { - if (result === Ternary.True || depth === 0) { + 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. @@ -18483,79 +18965,40 @@ namespace ts { } function structuredTypeRelatedTo(source: Type, target: Type, reportErrors: boolean, intersectionState: IntersectionState): Ternary { - tracing?.push(tracing.Phase.CheckTypes, "structuredTypeRelatedTo", { sourceId: source.id, targetId: target.id }); - const result = structuredTypeRelatedToWorker(source, target, reportErrors, intersectionState); - tracing?.pop(); - return result; - } - - function structuredTypeRelatedToWorker(source: Type, target: Type, reportErrors: boolean, intersectionState: IntersectionState): Ternary { if (intersectionState & IntersectionState.PropertyCheck) { return propertiesRelatedTo(source, target, reportErrors, /*excludedProperties*/ undefined, IntersectionState.None); } - if (intersectionState & IntersectionState.UnionIntersectionCheck) { - // Note that these checks are specifically ordered to produce correct results. In particular, - // we need to deconstruct unions before intersections (because unions are always at the top), - // and we need to handle "each" relations before "some" relations for the same kind of type. - if (source.flags & TypeFlags.Union) { - return relation === comparableRelation ? - someTypeRelatedToType(source as UnionType, target, reportErrors && !(source.flags & TypeFlags.Primitive), intersectionState & ~IntersectionState.UnionIntersectionCheck) : - eachTypeRelatedToType(source as UnionType, target, reportErrors && !(source.flags & TypeFlags.Primitive), intersectionState & ~IntersectionState.UnionIntersectionCheck); - } - if (target.flags & TypeFlags.Union) { - return typeRelatedToSomeType(getRegularTypeOfObjectLiteral(source), target as UnionType, reportErrors && !(source.flags & TypeFlags.Primitive) && !(target.flags & TypeFlags.Primitive)); - } - if (target.flags & TypeFlags.Intersection) { - return typeRelatedToEachType(getRegularTypeOfObjectLiteral(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, getBaseConstraintOrType); - if (constraints !== (source as IntersectionType).types) { - source = getIntersectionType(constraints); - if (!(source.flags & TypeFlags.Intersection)) { - return isRelatedTo(source, target, /*reportErrors*/ false); - } + let result: Ternary; + let originalErrorInfo: DiagnosticMessageChain | undefined; + let varianceCheckFailed = false; + const saveErrorInfo = captureErrorCalculationState(); + 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; } - // Check to see if any constituents of the intersection are immediately related to the target. - // - // Don't report errors though. Checking whether a constituent is related to the source is not actually - // useful and leads to some confusing error messages. Instead it is better to let the below checks - // take care of this, or to not elaborate at all. For instance, - // - // - For an object type (such as 'C = A & B'), users are usually more interested in structural errors. - // - // - For a union type (such as '(A | B) = (C & D)'), it's better to hold onto the whole intersection - // than to report that 'D' is not assignable to 'A' or 'B'. - // - // - For a primitive type or type parameter (such as 'number = A & B') there is no point in - // breaking the intersection apart. - return someTypeRelatedToType(source as IntersectionType, target, /*reportErrors*/ false, IntersectionState.Source); - } - const flags = source.flags & target.flags; - if (relation === identityRelation && !(flags & TypeFlags.Object)) { - if (flags & TypeFlags.Index) { - return isRelatedTo((source as IndexType).type, (target as IndexType).type, /*reportErrors*/ false); + if (sourceFlags & TypeFlags.Index) { + return isRelatedTo((source as IndexType).type, (target as IndexType).type, RecursionFlags.Both, /*reportErrors*/ false); } - let result = Ternary.False; - if (flags & TypeFlags.IndexedAccess) { - if (result = isRelatedTo((source as IndexedAccessType).objectType, (target as IndexedAccessType).objectType, /*reportErrors*/ false)) { - if (result &= isRelatedTo((source as IndexedAccessType).indexType, (target as IndexedAccessType).indexType, /*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 (flags & TypeFlags.Conditional) { + 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, /*reportErrors*/ false)) { - if (result &= isRelatedTo((source as ConditionalType).extendsType, (target as ConditionalType).extendsType, /*reportErrors*/ false)) { - if (result &= isRelatedTo(getTrueTypeFromConditionalType(source as ConditionalType), getTrueTypeFromConditionalType(target as ConditionalType), /*reportErrors*/ false)) { - if (result &= isRelatedTo(getFalseTypeFromConditionalType(source as ConditionalType), getFalseTypeFromConditionalType(target as ConditionalType), /*reportErrors*/ false)) { + 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; } } @@ -18563,21 +19006,57 @@ namespace ts { } } } - if (flags & TypeFlags.Substitution) { - return isRelatedTo((source as SubstitutionType).substitute, (target as SubstitutionType).substitute, /*reportErrors*/ false); + if (sourceFlags & TypeFlags.Substitution) { + return isRelatedTo((source as SubstitutionType).substitute, (target as SubstitutionType).substitute, RecursionFlags.Both, /*reportErrors*/ false); + } + if (!(sourceFlags & TypeFlags.Object)) { + return Ternary.False; + } + } + else if (sourceFlags & TypeFlags.UnionOrIntersection || targetFlags & TypeFlags.UnionOrIntersection) { + if (result = unionOrIntersectionRelatedTo(source, target, reportErrors, intersectionState)) { + return result; + } + if (source.flags & TypeFlags.Intersection || source.flags & TypeFlags.TypeParameter && target.flags & TypeFlags.Union) { + // 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)` + 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 + if (result = isRelatedTo(constraint, target, RecursionFlags.Source, /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState)) { + resetErrorInfo(saveErrorInfo); + return result; + } + } + } + // The ordered decomposition above doesn't handle all cases. Specifically, we also need to handle: + // Source is instantiable (e.g. source has union or intersection constraint). + // Source is an object, target is a union (e.g. { a, b: boolean } <=> { a, b: true } | { a, b: false }). + // Source is an intersection, target is an object (e.g. { a } & { b } <=> { a, b }). + // Source is an intersection, target is a union (e.g. { a } & { b: boolean } <=> { a, b: true } | { a, b: false }). + // Source is an intersection, target instantiable (e.g. string & { tag } <=> T["a"] constrained to string & { tag }). + if (!(sourceFlags & TypeFlags.Instantiable || + sourceFlags & TypeFlags.Object && targetFlags & TypeFlags.Union || + sourceFlags & TypeFlags.Intersection && targetFlags & (TypeFlags.Object | TypeFlags.Union | TypeFlags.Instantiable))) { + return Ternary.False; } - return Ternary.False; } - - let result: Ternary; - let originalErrorInfo: DiagnosticMessageChain | undefined; - let varianceCheckFailed = false; - const saveErrorInfo = captureErrorCalculationState(); // 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 (source.flags & (TypeFlags.Object | TypeFlags.Conditional) && source.aliasSymbol && + if (sourceFlags & (TypeFlags.Object | TypeFlags.Conditional) && source.aliasSymbol && source.aliasTypeArguments && source.aliasSymbol === target.aliasSymbol && !(containsMarkerType(source.aliasTypeArguments) || containsMarkerType(target.aliasTypeArguments))) { const variances = getAliasVariances(source.aliasSymbol); @@ -18592,36 +19071,36 @@ namespace ts { // 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)) || - isSingleElementGenericTupleType(target) && (target.target.readonly || isMutableArrayOrTuple(getBaseConstraintOfType(source) || source)) && (result = isRelatedTo(source, getTypeArguments(target)[0]))) { + 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 (target.flags & TypeFlags.TypeParameter) { + 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))) { + 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, reportErrors)) { + if (result = isRelatedTo(templateType, indexedAccessType, RecursionFlags.Both, reportErrors)) { return result; } } } } - else if (target.flags & TypeFlags.Index) { + 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 (source.flags & TypeFlags.Index) { - if (result = isRelatedTo(targetType, (source as IndexType).type, /*reportErrors*/ false)) { + 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), reportErrors)) { + if (result = isRelatedTo(source, getKnownKeysOfTupleType(targetType), RecursionFlags.Target, reportErrors)) { return result; } } @@ -18634,18 +19113,47 @@ namespace ts { // false positives. For example, given 'T extends { [K in keyof T]: string }', // 'keyof T' has itself as its constraint and produces a Ternary.Maybe when // related to other types. - if (isRelatedTo(source, getIndexType(constraint, (target as IndexType).stringsOnly), reportErrors) === Ternary.True) { + if (isRelatedTo(source, getIndexType(constraint, (target as IndexType).stringsOnly), RecursionFlags.Target, reportErrors) === Ternary.True) { + return Ternary.True; + } + } + else if (isGenericMappedType(targetType)) { + // generic mapped types that don't simplify or have a constraint still have a very simple set of keys we can compare against + // - their nameType or constraintType. + // In many ways, this comparison is a deferred version of what `getIndexTypeForMappedType` does to actually resolve the keys for _non_-generic types + + const nameType = getNameTypeFromMappedType(targetType); + const constraintType = getConstraintTypeFromMappedType(targetType); + let targetKeys; + if (nameType && isMappedTypeWithKeyofConstraintDeclaration(targetType)) { + // we need to get the apparent mappings and union them with the generic mappings, since some properties may be + // missing from the `constraintType` which will otherwise be mapped in the object + const modifiersType = getApparentType(getModifiersTypeFromMappedType(targetType)); + const mappedKeys: Type[] = []; + forEachMappedTypePropertyKeyTypeAndIndexSignatureKeyType( + modifiersType, + TypeFlags.StringOrNumberLiteralOrUnique, + /*stringsOnly*/ false, + t => void mappedKeys.push(instantiateType(nameType, appendTypeMapping(targetType.mapper, getTypeParameterFromMappedType(targetType), t))) + ); + // We still need to include the non-apparent (and thus still generic) keys in the target side of the comparison (in case they're in the source side) + targetKeys = getUnionType([...mappedKeys, nameType]); + } + else { + targetKeys = nameType || constraintType; + } + if (isRelatedTo(source, targetKeys, RecursionFlags.Target, reportErrors) === Ternary.True) { return Ternary.True; } } } } - else if (target.flags & TypeFlags.IndexedAccess) { - if (source.flags & TypeFlags.IndexedAccess) { + 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, reportErrors)) { - result &= isRelatedTo((source as IndexedAccessType).indexType, (target as IndexedAccessType).indexType, reportErrors); + 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) { resetErrorInfo(saveErrorInfo); @@ -18670,7 +19178,7 @@ namespace ts { // create a new chain for the constraint error resetErrorInfo(saveErrorInfo); } - if (result = isRelatedTo(source, constraint, reportErrors)) { + if (result = isRelatedTo(source, constraint, RecursionFlags.Target, reportErrors)) { return result; } // prefer the shorter chain of the constraint comparison chain, and the direct comparison chain @@ -18684,7 +19192,7 @@ namespace ts { originalErrorInfo = undefined; } } - else if (isGenericMappedType(target)) { + 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); @@ -18710,14 +19218,15 @@ namespace ts { // 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)) { + : 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, reportErrors)) { + if (result = isRelatedTo(source, (nonNullComponent as IndexedAccessType).objectType, RecursionFlags.Target, reportErrors)) { return result; } } @@ -18738,7 +19247,7 @@ namespace ts { : 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, reportErrors)) { + if (result = isRelatedTo(indexedAccessType, templateType, RecursionFlags.Both, reportErrors)) { return result; } } @@ -18748,104 +19257,117 @@ namespace ts { } } } - else if (target.flags & TypeFlags.Conditional) { - const c = target as ConditionalType; - // 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 && isConditionalTypeAlwaysTrueDisregardingInferTypes(c); - - // Instantiate with a replacement mapper if the conditional is distributive, replacing the check type with a clone of itself, - // this way {x: string | number, y: string | number} -> (T extends T ? { x: T, y: T } : never) appropriately _fails_ when - // T = string | number (since that will end up distributing and producing `{x: string, y: string} | {x: number, y: number}`, - // to which `{x: string | number, y: string | number}` isn't assignable) - let distributionMapper: TypeMapper | undefined; - const checkVar = getActualTypeVariable(c.root.checkType); - if (c.root.isDistributive && checkVar.flags & TypeFlags.TypeParameter) { - const newParam = cloneTypeParameter(checkVar); - distributionMapper = prependTypeMapping(checkVar, newParam, c.mapper); - newParam.mapper = distributionMapper; + 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)) { + resetErrorInfo(saveErrorInfo); + return Ternary.Maybe; } - - // TODO: Find a nice way to include potential conditional type breakdowns in error output, if they seem good (they usually don't) - let localResult: Ternary | undefined; - if (skipTrue || (localResult = isRelatedTo(source, distributionMapper ? instantiateType(getTypeFromTypeNode(c.root.node.trueType), distributionMapper) : getTrueTypeFromConditionalType(c), /*reportErrors*/ false))) { - if (!skipFalse) { - localResult = (localResult || Ternary.Maybe) & isRelatedTo(source, distributionMapper ? instantiateType(getTypeFromTypeNode(c.root.node.falseType), distributionMapper) : getFalseTypeFromConditionalType(c), /*reportErrors*/ false); + const c = target as ConditionalType; + // We check for a relationship to a conditional type target only when the conditional type has no + // 'infer' positions and is not distributive or is distributive but doesn't reference the check type + // parameter in either of the result types. + if (!c.root.inferTypeParameters && !isDistributionDependent(c.root)) { + // Check if the conditional is always true or always false but still deferred for distribution purposes. + const skipTrue = !isTypeAssignableTo(getPermissiveInstantiation(c.checkType), getPermissiveInstantiation(c.extendsType)); + const skipFalse = !skipTrue && isTypeAssignableTo(getRestrictiveInstantiation(c.checkType), getRestrictiveInstantiation(c.extendsType)); + // TODO: Find a nice way to include potential conditional type breakdowns in error output, if they seem good (they usually don't) + if (result = skipTrue ? Ternary.True : isRelatedTo(source, getTrueTypeFromConditionalType(c), RecursionFlags.Target, /*reportErrors*/ false)) { + result &= skipFalse ? Ternary.True : isRelatedTo(source, getFalseTypeFromConditionalType(c), RecursionFlags.Target, /*reportErrors*/ false); + if (result) { + resetErrorInfo(saveErrorInfo); + return result; + } } } - if (localResult) { - resetErrorInfo(saveErrorInfo); - return localResult; - } } - else if (target.flags & TypeFlags.TemplateLiteral) { - if (source.flags & TypeFlags.TemplateLiteral) { + 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, makeFunctionTypeMapper(reportUnreliableMarkers)); } - const result = inferTypesFromTemplateLiteralType(source, target as TemplateLiteralType); - if (result && every(result, (r, i) => isValidTypeForTemplateLiteralPlaceholder(r, (target as TemplateLiteralType).types[i]))) { + if (isTypeMatchedByTemplateLiteralType(source, target as TemplateLiteralType)) { return Ternary.True; } } - if (source.flags & TypeFlags.TypeVariable) { - // IndexedAccess comparisons are handled above in the `target.flags & TypeFlage.IndexedAccess` branch - if (!(source.flags & TypeFlags.IndexedAccess && target.flags & TypeFlags.IndexedAccess)) { + 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); - if (!constraint || (source.flags & TypeFlags.TypeParameter && constraint.flags & TypeFlags.Any)) { + if (!constraint || (sourceFlags & TypeFlags.TypeParameter && constraint.flags & TypeFlags.Any)) { // A type variable with no constraint is not related to the non-primitive object type. - if (result = isRelatedTo(emptyObjectType, extractTypesOfKind(target, ~TypeFlags.NonPrimitive))) { + if (result = isRelatedTo(emptyObjectType, extractTypesOfKind(target, ~TypeFlags.NonPrimitive), RecursionFlags.Both)) { resetErrorInfo(saveErrorInfo); return result; } } // 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 - else if (result = isRelatedTo(constraint, target, /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState)) { + else if (result = isRelatedTo(constraint, target, RecursionFlags.Source, /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState)) { resetErrorInfo(saveErrorInfo); 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, reportErrors && !(target.flags & source.flags & TypeFlags.TypeParameter), /*headMessage*/ undefined, intersectionState)) { + else if (result = isRelatedTo(getTypeWithThisArgument(constraint, source), target, RecursionFlags.Source, reportErrors && !(targetFlags & sourceFlags & TypeFlags.TypeParameter), /*headMessage*/ undefined, intersectionState)) { resetErrorInfo(saveErrorInfo); 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)) { + resetErrorInfo(saveErrorInfo); + return result; + } + } + } } } - else if (source.flags & TypeFlags.Index) { - if (result = isRelatedTo(keyofConstraintType, target, reportErrors)) { + else if (sourceFlags & TypeFlags.Index) { + if (result = isRelatedTo(keyofConstraintType, target, RecursionFlags.Source, reportErrors)) { resetErrorInfo(saveErrorInfo); return result; } } - else if (source.flags & TypeFlags.TemplateLiteral) { - if (!(target.flags & TypeFlags.TemplateLiteral)) { - const baseConstraint = getBaseConstraintOfType(source); - const constraint = baseConstraint && baseConstraint !== source ? baseConstraint : stringType; - if (result = isRelatedTo(constraint, target, reportErrors)) { + 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))) { resetErrorInfo(saveErrorInfo); return result; } } } - else if (source.flags & TypeFlags.StringMapping) { - if (target.flags & TypeFlags.StringMapping && (source as StringMappingType).symbol === (target as StringMappingType).symbol) { - if (result = isRelatedTo((source as StringMappingType).type, (target as StringMappingType).type, reportErrors)) { + else if (sourceFlags & TypeFlags.StringMapping) { + if (targetFlags & TypeFlags.StringMapping && (source as StringMappingType).symbol === (target as StringMappingType).symbol) { + if (result = isRelatedTo((source as StringMappingType).type, (target as StringMappingType).type, RecursionFlags.Both, reportErrors)) { resetErrorInfo(saveErrorInfo); return result; } } else { const constraint = getBaseConstraintOfType(source); - if (constraint && (result = isRelatedTo(constraint, target, reportErrors))) { + if (constraint && (result = isRelatedTo(constraint, target, RecursionFlags.Source, reportErrors))) { resetErrorInfo(saveErrorInfo); return result; } } } - else if (source.flags & TypeFlags.Conditional) { - if (target.flags & TypeFlags.Conditional) { + 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)) { + resetErrorInfo(saveErrorInfo); + 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. @@ -18854,15 +19376,15 @@ namespace ts { 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, isRelatedTo); + 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) || isRelatedTo((target as ConditionalType).checkType, (source as ConditionalType).checkType))) { - if (result = isRelatedTo(instantiateType(getTrueTypeFromConditionalType(source as ConditionalType), mapper), getTrueTypeFromConditionalType(target as ConditionalType), reportErrors)) { - result &= isRelatedTo(getFalseTypeFromConditionalType(source as ConditionalType), getFalseTypeFromConditionalType(target as ConditionalType), reportErrors); + (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) { resetErrorInfo(saveErrorInfo); @@ -18873,19 +19395,20 @@ namespace ts { else { // conditionals aren't related to one another via distributive constraint as it is much too inaccurate and allows way // more assignments than are desirable (since it maps the source check type to its constraint, it loses information) - const distributiveConstraint = getConstraintOfDistributiveConditionalType(source as ConditionalType); + const distributiveConstraint = hasNonCircularBaseConstraint(source) ? getConstraintOfDistributiveConditionalType(source as ConditionalType) : undefined; if (distributiveConstraint) { - if (result = isRelatedTo(distributiveConstraint, target, reportErrors)) { + if (result = isRelatedTo(distributiveConstraint, target, RecursionFlags.Source, reportErrors)) { resetErrorInfo(saveErrorInfo); 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 aissgnable to `O`, as is `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, reportErrors)) { + if (result = isRelatedTo(defaultConstraint, target, RecursionFlags.Source, reportErrors)) { resetErrorInfo(saveErrorInfo); return result; } @@ -18905,15 +19428,21 @@ namespace ts { } return Ternary.False; } - const sourceIsPrimitive = !!(source.flags & TypeFlags.Primitive); + 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) && !(getObjectFlags(source) & ObjectFlags.MarkerType || getObjectFlags(target) & ObjectFlags.MarkerType)) { + // 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. @@ -18931,7 +19460,7 @@ namespace ts { } else if (isReadonlyArrayType(target) ? isArrayType(source) || isTupleType(source) : isArrayType(target) && isTupleType(source) && !source.target.readonly) { if (relation !== identityRelation) { - return isRelatedTo(getIndexTypeOfType(source, numberType) || anyType, getIndexTypeOfType(target, numberType) || anyType, reportErrors); + 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 @@ -18949,7 +19478,7 @@ namespace ts { // 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 (source.flags & (TypeFlags.Object | TypeFlags.Intersection) && target.flags & TypeFlags.Object) { + if (sourceFlags & (TypeFlags.Object | TypeFlags.Intersection) && targetFlags & TypeFlags.Object) { // Report structural errors only if we haven't reported any errors yet const reportStructuralErrors = reportErrors && errorInfo === saveErrorInfo.errorInfo && !sourceIsPrimitive; result = propertiesRelatedTo(source, target, reportStructuralErrors, /*excludedProperties*/ undefined, intersectionState); @@ -18973,7 +19502,7 @@ namespace ts { // 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 (source.flags & (TypeFlags.Object | TypeFlags.Intersection) && target.flags & TypeFlags.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); @@ -19057,10 +19586,10 @@ namespace ts { let result: Ternary; const targetConstraint = getConstraintTypeFromMappedType(target); const sourceConstraint = instantiateType(getConstraintTypeFromMappedType(source), makeFunctionTypeMapper(getCombinedMappedTypeOptionality(source) < 0 ? reportUnmeasurableMarkers : reportUnreliableMarkers)); - if (result = isRelatedTo(targetConstraint, sourceConstraint, reportErrors)) { + 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), reportErrors); + return result & isRelatedTo(instantiateType(getTemplateTypeFromMappedType(source), mapper), getTemplateTypeFromMappedType(target), RecursionFlags.Both, reportErrors); } } } @@ -19182,7 +19711,7 @@ namespace ts { const targetIsOptional = strictNullChecks && !!(getCheckFlags(targetProp) & CheckFlags.Partial); const effectiveTarget = addOptionality(getNonMissingTypeOfSymbol(targetProp), /*isProperty*/ false, targetIsOptional); const effectiveSource = getTypeOfSourceProperty(sourceProp); - return isRelatedTo(effectiveSource, effectiveTarget, reportErrors, /*headMessage*/ undefined, intersectionState); + 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 { @@ -19219,6 +19748,18 @@ namespace ts { } 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. + if ( + (relation === subtypeRelation || relation === strictSubtypeRelation) && + !!(sourcePropFlags & ModifierFlags.Readonly) && !(targetPropFlags & ModifierFlags.Readonly) + ) { + 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) { @@ -19376,7 +19917,7 @@ namespace ts { const targetType = targetTypeArguments[i]; const targetCheckType = sourceFlags & ElementFlags.Variadic && targetFlags & ElementFlags.Rest ? createArrayType(targetType) : removeMissingType(targetType, !!(targetFlags & ElementFlags.Optional)); - const related = isRelatedTo(sourceType, targetCheckType, reportErrors, /*headMessage*/ undefined, intersectionState); + const related = isRelatedTo(sourceType, targetCheckType, RecursionFlags.Both, reportErrors, /*headMessage*/ undefined, intersectionState); if (!related) { if (reportErrors && (targetArity > 1 || sourceArity > 1)) { if (i < startCount || i >= targetArity - endCount || sourceArity - startCount - endCount === 1) { @@ -19496,11 +20037,11 @@ namespace ts { } let result = Ternary.True; - const saveErrorInfo = captureErrorCalculationState(); 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) { + 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 @@ -19534,6 +20075,7 @@ namespace ts { } 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) { @@ -19545,7 +20087,6 @@ namespace ts { } shouldElaborateErrors = false; } - if (shouldElaborateErrors) { reportError(Diagnostics.Type_0_provides_no_match_for_the_signature_1, typeToString(source), @@ -19576,7 +20117,7 @@ namespace ts { */ function signatureRelatedTo(source: Signature, target: Signature, erase: boolean, reportErrors: boolean, incompatibleReporter: (source: Type, target: Type) => void): Ternary { return compareSignaturesRelated(erase ? getErasedSignature(source) : source, erase ? getErasedSignature(target) : target, - relation === strictSubtypeRelation ? SignatureCheckMode.StrictArity : 0, reportErrors, reportError, incompatibleReporter, isRelatedTo, makeFunctionTypeMapper(reportUnreliableMarkers)); + relation === strictSubtypeRelation ? SignatureCheckMode.StrictArity : 0, reportErrors, reportError, incompatibleReporter, isRelatedToWorker, makeFunctionTypeMapper(reportUnreliableMarkers)); } function signaturesIdenticalTo(source: Type, target: Type, kind: SignatureKind): Ternary { @@ -19610,7 +20151,7 @@ namespace ts { const type = exactOptionalPropertyTypes || propType.flags & TypeFlags.Undefined || keyType === numberType || !(prop.flags & SymbolFlags.Optional) ? propType : getTypeWithFacts(propType, TypeFacts.NEUndefined); - const related = isRelatedTo(type, targetInfo.type, reportErrors); + const related = isRelatedTo(type, targetInfo.type, RecursionFlags.Both, reportErrors); if (!related) { if (reportErrors) { reportError(Diagnostics.Property_0_is_incompatible_with_index_signature, symbolToString(prop)); @@ -19633,7 +20174,7 @@ namespace ts { } function indexInfoRelatedTo(sourceInfo: IndexInfo, targetInfo: IndexInfo, reportErrors: boolean) { - const related = isRelatedTo(sourceInfo.type, targetInfo.type, reportErrors); + const related = isRelatedTo(sourceInfo.type, targetInfo.type, RecursionFlags.Both, reportErrors); if (!related && reportErrors) { if (sourceInfo.keyType === targetInfo.keyType) { reportError(Diagnostics._0_index_signatures_are_incompatible, typeToString(sourceInfo.keyType)); @@ -19654,7 +20195,7 @@ namespace ts { let result = Ternary.True; for (const targetInfo of indexInfos) { const related = !sourceIsPrimitive && targetHasStringIndex && targetInfo.type.flags & TypeFlags.Any ? Ternary.True : - isGenericMappedType(source) && targetHasStringIndex ? isRelatedTo(getTemplateTypeFromMappedType(source), targetInfo.type, reportErrors) : + isGenericMappedType(source) && targetHasStringIndex ? isRelatedTo(getTemplateTypeFromMappedType(source), targetInfo.type, RecursionFlags.Both, reportErrors) : typeRelatedToIndexInfo(source, targetInfo, reportErrors, intersectionState); if (!related) { return Ternary.False; @@ -19687,7 +20228,7 @@ namespace ts { } for (const targetInfo of targetInfos) { const sourceInfo = getIndexInfoOfType(source, targetInfo.keyType); - if (!(sourceInfo && isRelatedTo(sourceInfo.type, targetInfo.type) && sourceInfo.isReadonly === targetInfo.isReadonly)) { + if (!(sourceInfo && isRelatedTo(sourceInfo.type, targetInfo.type, RecursionFlags.Both) && sourceInfo.isReadonly === targetInfo.isReadonly)) { return Ternary.False; } } @@ -19924,47 +20465,55 @@ namespace ts { return isNonDeferredTypeReference(type) && some(getTypeArguments(type), t => !!(t.flags & TypeFlags.TypeParameter) || isTypeReferenceWithGenericArguments(t)); } - /** - * getTypeReferenceId(A) returns "111=0-12=1" - * where A.id=111 and number.id=12 - */ - function getTypeReferenceId(type: TypeReference, typeParameters: Type[], depth = 0) { - let result = "" + type.target.id; - for (const t of getTypeArguments(type)) { - if (isUnconstrainedTypeParameter(t)) { - let index = typeParameters.indexOf(t); - if (index < 0) { - index = typeParameters.length; - typeParameters.push(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 += "=" + index; - } - else if (depth < 4 && isTypeReferenceWithGenericArguments(t)) { - result += "<" + getTypeReferenceId(t as TypeReference, typeParameters, depth + 1) + ">"; - } - else { result += "-" + t.id; } + return result; } - return result; } /** * To improve caching, the relation key for two generic types uses the target's id plus ids of the type parameters. * For other cases, the types ids are used. */ - function getRelationKey(source: Type, target: Type, intersectionState: IntersectionState, relation: ESMap) { + function getRelationKey(source: Type, target: Type, intersectionState: IntersectionState, relation: ESMap, ignoreConstraints: boolean) { if (relation === identityRelation && source.id > target.id) { const temp = source; source = target; target = temp; } const postFix = intersectionState ? ":" + intersectionState : ""; - if (isTypeReferenceWithGenericArguments(source) && isTypeReferenceWithGenericArguments(target)) { - const typeParameters: Type[] = []; - return getTypeReferenceId(source as TypeReference, typeParameters) + "," + getTypeReferenceId(target as TypeReference, typeParameters) + postFix; - } - return source.id + "," + target.id + postFix; + 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 @@ -20018,27 +20567,34 @@ namespace ts { } // Return true if the given type is deeply nested. We consider this to be the case when structural type comparisons - // for 5 or more occurrences or instantiations of the type have been recorded on the given stack. It is possible, + // for maxDepth or more occurrences or instantiations of the type have been recorded on the given stack. It is possible, // though highly unlikely, for this test to be true in a situation where a chain of instantiations is not infinitely - // expanding. Effectively, we will generate a false positive when two types are structurally equal to at least 5 + // expanding. Effectively, we will generate a false positive when two types are structurally equal to at least maxDepth // levels, but unequal at some level beyond that. - // In addition, this will also detect when an indexed access has been chained off of 5 or more times (which is essentially - // the dual of the structural comparison), and likewise mark the type as deeply nested, potentially adding false positives - // for finite but deeply expanding indexed accesses (eg, for `Q[P1][P2][P3][P4][P5]`). - // It also detects when a recursive type reference has expanded 5 or more times, eg, if the true branch of + // In addition, this will also detect when an indexed access has been chained off of maxDepth more times (which is + // essentially the dual of the structural comparison), and likewise mark the type as deeply nested, potentially adding + // false positives for finite but deeply expanding indexed accesses (eg, for `Q[P1][P2][P3][P4][P5]`). + // It also detects when a recursive type reference has expanded maxDepth or more times, e.g. if the true branch of // `type A = null extends T ? [A>] : [T]` - // has expanded into `[A>>>>>]` - // in such cases we need to terminate the expansion, and we do so here. - function isDeeplyNestedType(type: Type, stack: Type[], depth: number): boolean { - if (depth >= 5) { + // has expanded into `[A>>>>>]`. In such cases we need + // to terminate the expansion, and we do so here. + function isDeeplyNestedType(type: Type, stack: Type[], depth: number, maxDepth = 3): boolean { + if (depth >= maxDepth) { const identity = getRecursionIdentity(type); let count = 0; + let lastTypeId = 0; for (let i = 0; i < depth; i++) { - if (getRecursionIdentity(stack[i]) === identity) { - count++; - if (count >= 5) { - return true; + const t = stack[i]; + if (getRecursionIdentity(t) === identity) { + // We only count occurrences with a higher type id than the previous occurrence, since higher + // type ids are an indicator of newer instantiations caused by recursion. + if (t.id >= lastTypeId) { + count++; + if (count >= maxDepth) { + return true; + } } + lastTypeId = t.id; } } } @@ -20359,7 +20915,7 @@ namespace ts { function getBaseTypeOfLiteralType(type: Type): Type { return type.flags & TypeFlags.EnumLiteral ? getBaseTypeOfEnumLiteralType(type as LiteralType) : - type.flags & TypeFlags.StringLiteral ? stringType : + type.flags & (TypeFlags.StringLiteral | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) ? stringType : type.flags & TypeFlags.NumberLiteral ? numberType : type.flags & TypeFlags.BigIntLiteral ? bigintType : type.flags & TypeFlags.BooleanLiteral ? booleanType : @@ -20596,9 +21152,14 @@ namespace ts { * with no call or construct signatures. */ function isObjectTypeWithInferableIndex(type: Type): boolean { - 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 && - !typeHasCallOrConstructSignatures(type)) || !!(getObjectFlags(type) & ObjectFlags.ReverseMapped && isObjectTypeWithInferableIndex((type as ReverseMappedType).source)); + 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) + ) || !!(getObjectFlags(type) & ObjectFlags.ReverseMapped && isObjectTypeWithInferableIndex((type as ReverseMappedType).source)); } function createSymbolWithType(source: Symbol, type: Type | undefined) { @@ -20833,9 +21394,10 @@ namespace ts { (isCallSignatureDeclaration(param.parent) || isMethodSignature(param.parent) || isFunctionTypeNode(param.parent)) && param.parent.parameters.indexOf(param) > -1 && (resolveName(param, param.name.escapedText, SymbolFlags.Type, undefined, param.name.escapedText, /*isUse*/ true) || - param.name.originalKeywordKind && isTypeNodeKind(param.name.originalKeywordKind))) { + param.name.originalKeywordKind && isTypeNodeKind(param.name.originalKeywordKind))) { const newName = "arg" + param.parent.parameters.indexOf(param); - errorOrSuggestion(noImplicitAny, declaration, Diagnostics.Parameter_has_a_name_but_no_type_Did_you_mean_0_Colon_1, newName, declarationNameToString(param.name)); + 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 ? @@ -21017,7 +21579,7 @@ namespace ts { type.flags & TypeFlags.Object && !isNonGenericTopLevelType(type) && ( objectFlags & ObjectFlags.Reference && ((type as TypeReference).node || forEach(getTypeArguments(type as TypeReference), couldContainTypeVariables)) || objectFlags & ObjectFlags.Anonymous && type.symbol && type.symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.TypeLiteral | SymbolFlags.ObjectLiteral) && type.symbol.declarations || - objectFlags & (ObjectFlags.Mapped | ObjectFlags.ReverseMapped | ObjectFlags.ObjectRestType)) || + objectFlags & (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); @@ -21190,6 +21752,18 @@ namespace ts { return !!(type.symbol && some(type.symbol.declarations, hasSkipDirectInferenceFlag)); } + function templateLiteralTypesDefinitelyUnrelated(source: TemplateLiteralType, target: TemplateLiteralType) { + // Two template literal types with diffences in their starting or ending text spans are definitely unrelated. + const sourceStart = source.texts[0]; + const targetStart = target.texts[0]; + const sourceEnd = source.texts[source.texts.length - 1]; + const targetEnd = target.texts[target.texts.length - 1]; + const startLen = Math.min(sourceStart.length, targetStart.length); + const endLen = Math.min(sourceEnd.length, targetEnd.length); + return sourceStart.slice(0, startLen) !== targetStart.slice(0, startLen) || + sourceEnd.slice(sourceEnd.length - endLen) !== targetEnd.slice(targetEnd.length - endLen); + } + function isValidBigIntString(s: string): boolean { const scanner = createScanner(ScriptTarget.ESNext, /*skipTrivia*/ false); let success = true; @@ -21233,6 +21807,11 @@ namespace ts { 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]); } @@ -21403,12 +21982,14 @@ namespace ts { // not contain anyFunctionType when we come back to this argument for its second round // of inference. Also, we exclude inferences for silentNeverType (which is used as a wildcard // when constructing types from type parameters that had no inference candidates). - if (getObjectFlags(source) & ObjectFlags.NonInferrableType || source === nonInferrableAnyType || source === silentNeverType || - (priority & InferencePriority.ReturnType && (source === autoType || source === autoArrayType)) || isFromInferenceBlockedSource(source)) { + if (source === nonInferrableAnyType || source === silentNeverType || (priority & InferencePriority.ReturnType && (source === autoType || source === autoArrayType)) || isFromInferenceBlockedSource(source)) { return; } const inference = getInferenceInfoForType(target); if (inference) { + if (getObjectFlags(source) & ObjectFlags.NonInferrableType) { + return; + } if (!inference.isFixed) { if (inference.priority === undefined || priority < inference.priority) { inference.candidates = undefined; @@ -21439,21 +22020,19 @@ namespace ts { inferencePriority = Math.min(inferencePriority, priority); return; } - else { - // 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) { - invokeOnce(source, simplified, inferFromTypes); - } - 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) { - invokeOnce(source, simplified, inferFromTypes); - } + // 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); } } } @@ -21780,8 +22359,16 @@ namespace ts { function inferToTemplateLiteralType(source: Type, target: TemplateLiteralType) { const matches = inferTypesFromTemplateLiteralType(source, target); const types = target.types; - for (let i = 0; i < types.length; i++) { - inferFromTypes(matches ? matches[i] : neverType, types[i]); + // 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++) { + inferFromTypes(matches ? matches[i] : neverType, types[i]); + } } } @@ -22014,12 +22601,11 @@ namespace ts { if (signature) { const inferredCovariantType = inference.candidates ? getCovariantInference(inference, signature) : undefined; if (inference.contraCandidates) { - const inferredContravariantType = getContravariantInference(inference); // If we have both co- and contra-variant inferences, we prefer the contra-variant inference - // unless the co-variant inference is a subtype and not 'never'. + // unless the co-variant inference is a subtype of some contra-variant inference and not 'never'. inferredType = inferredCovariantType && !(inferredCovariantType.flags & TypeFlags.Never) && - isTypeSubtypeOf(inferredCovariantType, inferredContravariantType) ? - inferredCovariantType : inferredContravariantType; + some(inference.contraCandidates, t => isTypeSubtypeOf(inferredCovariantType, t)) ? + inferredCovariantType : getContravariantInference(inference); } else if (inferredCovariantType) { inferredType = inferredCovariantType; @@ -22116,6 +22702,11 @@ namespace ts { 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; @@ -22137,8 +22728,7 @@ namespace ts { getCannotFindNameDiagnosticForName(node), node, !isWriteOnlyAccess(node), - /*excludeGlobals*/ false, - /*issueSuggestions*/ true) || unknownSymbol; + /*excludeGlobals*/ false) || unknownSymbol; } return links.resolvedSymbol; } @@ -22159,8 +22749,11 @@ namespace ts { function getFlowCacheKey(node: Node, declaredType: Type, initialType: Type, flowContainer: Node | undefined): string | undefined { switch (node.kind) { case SyntaxKind.Identifier: - const symbol = getResolvedSymbol(node as Identifier); - return symbol !== unknownSymbol ? `${flowContainer ? getNodeId(flowContainer) : "-1"}|${getTypeId(declaredType)}|${getTypeId(initialType)}|${getSymbolId(symbol)}` : undefined; + 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: @@ -22223,42 +22816,12 @@ namespace ts { return false; } - // Given a source x, check if target matches x or is an && operation with an operand that matches x. - function containsTruthyCheck(source: Node, target: Node): boolean { - return isMatchingReference(source, target) || - (target.kind === SyntaxKind.BinaryExpression && (target as BinaryExpression).operatorToken.kind === SyntaxKind.AmpersandAmpersandToken && - (containsTruthyCheck(source, (target as BinaryExpression).left) || containsTruthyCheck(source, (target as BinaryExpression).right))); - } - - function getPropertyAccess(expr: Expression) { - if (isAccessExpression(expr)) { - return expr; - } - if (isIdentifier(expr)) { - const symbol = getResolvedSymbol(expr); - if (isConstVariable(symbol)) { - const declaration = symbol.valueDeclaration!; - // Given 'const x = obj.kind', allow 'x' as an alias for 'obj.kind' - if (isVariableDeclaration(declaration) && !declaration.type && declaration.initializer && isAccessExpression(declaration.initializer)) { - 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))) { - return declaration; - } - } - } - } - return undefined; - } - - function getAccessedPropertyName(access: AccessExpression | BindingElement): __String | undefined { + function getAccessedPropertyName(access: AccessExpression | BindingElement | ParameterDeclaration): __String | undefined { let propertyName; return access.kind === SyntaxKind.PropertyAccessExpression ? access.name.escapedText : access.kind === SyntaxKind.ElementAccessExpression && isStringOrNumericLiteralLike(access.argumentExpression) ? escapeLeadingUnderscores(access.argumentExpression.text) : access.kind === SyntaxKind.BindingElement && (propertyName = getDestructuringPropertyName(access)) ? escapeLeadingUnderscores(propertyName) : + access.kind === SyntaxKind.Parameter ? ("" + access.parent.parameters.indexOf(access)) as __String : undefined; } @@ -22289,7 +22852,7 @@ namespace ts { if ((prop as TransientSymbol).isDiscriminantProperty === undefined) { (prop as TransientSymbol).isDiscriminantProperty = ((prop as TransientSymbol).checkFlags & CheckFlags.Discriminant) === CheckFlags.Discriminant && - !maybeTypeOfKind(getTypeOfSymbol(prop), TypeFlags.Instantiable & ~TypeFlags.TemplateLiteral); + !isGenericType(getTypeOfSymbol(prop)); } return !!(prop as TransientSymbol).isDiscriminantProperty; } @@ -22348,8 +22911,9 @@ namespace ts { // 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 large unions with non-primitive constituents. - if (types.length < 10 || getObjectFlags(unionType) & ObjectFlags.PrimitiveUnion) { + // 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) { @@ -22383,7 +22947,7 @@ namespace ts { 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 && getTypeOfExpression((propNode as PropertyAssignment).initializer); + const propType = propNode && getContextFreeTypeOfExpression((propNode as PropertyAssignment).initializer); return propType && getConstituentTypeForKeyType(unionType, propType); } @@ -22494,7 +23058,10 @@ namespace ts { (type === falseType || type === regularFalseType) ? TypeFacts.FalseStrictFacts : TypeFacts.TrueStrictFacts : (type === falseType || type === regularFalseType) ? TypeFacts.FalseFacts : TypeFacts.TrueFacts; } - if (flags & TypeFlags.Object && !ignoreObjects) { + if (flags & TypeFlags.Object) { + if (ignoreObjects) { + return TypeFacts.AndFactsMask; // This is the identity element for computing type facts of intersection. + } return getObjectFlags(type) & ObjectFlags.Anonymous && isEmptyObjectType(type as ObjectType) ? strictNullChecks ? TypeFacts.EmptyObjectStrictFacts : TypeFacts.EmptyObjectFacts : isFunctionObjectType(type as ObjectType) ? @@ -22527,11 +23094,24 @@ namespace ts { // 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. ignoreObjects ||= maybeTypeOfKind(type, TypeFlags.Primitive); - return reduceLeft((type as UnionType).types, (facts, t) => facts & getTypeFacts(t, ignoreObjects), TypeFacts.All); + return getIntersectionTypeFacts(type as IntersectionType, ignoreObjects); } return TypeFacts.All; } + function getIntersectionTypeFacts(type: IntersectionType, ignoreObjects: boolean): TypeFacts { + // 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) { + const f = getTypeFacts(t, ignoreObjects); + oredFacts |= f; + andedFacts &= f; + } + return oredFacts & TypeFacts.OrFactsMask | andedFacts & TypeFacts.AndFactsMask; + } + function getTypeWithFacts(type: Type, include: TypeFacts) { return filterType(type, t => (getTypeFacts(t) & include) !== 0); } @@ -22840,23 +23420,21 @@ namespace ts { mapType(type, mapper); } - function getConstituentCount(type: Type) { - return type.flags & TypeFlags.UnionOrIntersection ? (type as UnionOrIntersectionType).types.length : 1; - } - function extractTypesOfKind(type: Type, kind: TypeFlags) { return filterType(type, t => (t.flags & kind) !== 0); } - // Return a new type in which occurrences of the string and number primitive types in - // typeWithPrimitives have been replaced with occurrences of string literals and numeric - // literals in typeWithLiterals, respectively. + // 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 (isTypeSubsetOf(stringType, typeWithPrimitives) && maybeTypeOfKind(typeWithLiterals, TypeFlags.StringLiteral) || - isTypeSubsetOf(numberType, typeWithPrimitives) && maybeTypeOfKind(typeWithLiterals, TypeFlags.NumberLiteral) || - isTypeSubsetOf(bigintType, typeWithPrimitives) && maybeTypeOfKind(typeWithLiterals, TypeFlags.BigIntLiteral)) { + 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) : + 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); } @@ -22951,10 +23529,10 @@ namespace ts { return isLengthPushOrUnshift || isElementAssignment; } - function isDeclarationWithExplicitTypeAnnotation(declaration: Declaration) { - return (declaration.kind === SyntaxKind.VariableDeclaration || declaration.kind === SyntaxKind.Parameter || - declaration.kind === SyntaxKind.PropertyDeclaration || declaration.kind === SyntaxKind.PropertySignature) && - !!getEffectiveTypeAnnotationNode(declaration as VariableDeclaration | ParameterDeclaration | PropertyDeclaration | PropertySignature); + 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) { @@ -23208,9 +23786,10 @@ namespace ts { function isConstantReference(node: Node): boolean { switch (node.kind) { - case SyntaxKind.Identifier: + case SyntaxKind.Identifier: { const symbol = getResolvedSymbol(node as Identifier); - return isConstVariable(symbol) || !!symbol.valueDeclaration && getRootDeclaration(symbol.valueDeclaration).kind === SyntaxKind.Parameter && !isParameterAssigned(symbol); + return isConstVariable(symbol) || isParameterOrCatchClauseVariable(symbol) && !isSymbolAssigned(symbol); + } case SyntaxKind.PropertyAccessExpression: case SyntaxKind.ElementAccessExpression: // The resolvedSymbol property is initialized by checkPropertyAccess or checkElementAccess before we get here. @@ -23219,20 +23798,19 @@ namespace ts { return false; } - function getFlowTypeOfReference(reference: Node, declaredType: Type, initialType = declaredType, flowContainer?: Node) { + function getFlowTypeOfReference(reference: Node, declaredType: Type, initialType = declaredType, flowContainer?: Node, flowNode = reference.flowNode) { let key: string | undefined; let isKeySet = false; let flowDepth = 0; - let inlineLevel = 0; if (flowAnalysisDisabled) { return errorType; } - if (!reference.flowNode) { + if (!flowNode) { return declaredType; } flowInvocationCount++; const sharedFlowStart = sharedFlowCount; - const evolvedType = getTypeFromFlowType(getTypeAtFlowNode(reference.flowNode)); + 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 @@ -23242,7 +23820,8 @@ namespace ts { 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; + // The non-null unknown type should never escape control flow analysis. + return resultType === nonNullUnknownType ? unknownType : resultType; function getOrSetCacheKey() { if (isKeySet) { @@ -23675,16 +24254,62 @@ namespace ts { return result; } - function getDiscriminantPropertyAccess(expr: Expression, computedType: Type) { - let access, name; - const type = declaredType.flags & TypeFlags.Union ? declaredType : computedType; - return type.flags & TypeFlags.Union && (access = getPropertyAccess(expr)) && (name = getAccessedPropertyName(access)) && - isMatchingReference(reference, isAccessExpression(access) ? access.expression : access.parent.parent.initializer!) && - isDiscriminantProperty(type, name) ? - access : undefined; + function getCandidateDiscriminantPropertyAccess(expr: Expression) { + if (isBindingPattern(reference) || isFunctionExpressionOrArrowFunction(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 (isConstVariable(symbol)) { + const declaration = symbol.valueDeclaration!; + // Given 'const x = obj.kind', allow 'x' as an alias for 'obj.kind' + if (isVariableDeclaration(declaration) && !declaration.type && declaration.initializer && isAccessExpression(declaration.initializer) && + isMatchingReference(reference, declaration.initializer.expression)) { + return declaration.initializer; + } + // Given 'const { kind: x } = obj', allow 'x' as an alias for 'obj.kind' + if (isBindingElement(declaration) && !declaration.initializer) { + const parent = declaration.parent.parent; + if (isVariableDeclaration(parent) && !parent.type && parent.initializer && (isIdentifier(parent.initializer) || isAccessExpression(parent.initializer)) && + isMatchingReference(reference, parent.initializer)) { + return declaration; + } + } + } + } + return undefined; } - function narrowTypeByDiscriminant(type: Type, access: AccessExpression | BindingElement, narrowType: (t: Type) => Type): Type { + function getDiscriminantPropertyAccess(expr: Expression, computedType: Type) { + const type = declaredType.flags & TypeFlags.Union ? declaredType : computedType; + if (type.flags & TypeFlags.Union) { + const access = getCandidateDiscriminantPropertyAccess(expr); + if (access) { + const name = getAccessedPropertyName(access); + if (name && isDiscriminantProperty(type, name)) { + return access; + } + } + } + return undefined; + } + + function narrowTypeByDiscriminant(type: Type, access: AccessExpression | BindingElement | ParameterDeclaration, narrowType: (t: Type) => Type): Type { const propName = getAccessedPropertyName(access); if (propName === undefined) { return type; @@ -23698,11 +24323,11 @@ namespace ts { const narrowedPropType = narrowType(propType); return filterType(type, t => { const discriminantType = getTypeOfPropertyOrIndexSignature(t, propName); - return !(discriminantType.flags & TypeFlags.Never) && isTypeComparableTo(discriminantType, narrowedPropType); + return !(narrowedPropType.flags & TypeFlags.Never) && isTypeComparableTo(narrowedPropType, discriminantType); }); } - function narrowTypeByDiscriminantProperty(type: Type, access: AccessExpression | BindingElement, operator: SyntaxKind, value: Expression, assumeTrue: boolean) { + 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)) { @@ -23717,7 +24342,7 @@ namespace ts { return narrowTypeByDiscriminant(type, access, t => narrowTypeByEquality(t, operator, value, assumeTrue)); } - function narrowTypeBySwitchOnDiscriminantProperty(type: Type, access: AccessExpression | BindingElement, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number) { + function narrowTypeBySwitchOnDiscriminantProperty(type: Type, access: AccessExpression | BindingElement | ParameterDeclaration, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number) { if (clauseStart < clauseEnd && type.flags & TypeFlags.Union && getKeyPropertyName(type as UnionType) === getAccessedPropertyName(access)) { const clauseTypes = getSwitchClauseTypes(switchStatement).slice(clauseStart, clauseEnd); const candidate = getUnionType(map(clauseTypes, t => getConstituentTypeForKeyType(type as UnionType, t) || unknownType)); @@ -23730,7 +24355,8 @@ namespace ts { function narrowTypeByTruthiness(type: Type, expr: Expression, assumeTrue: boolean): Type { if (isMatchingReference(reference, expr)) { - return getTypeWithFacts(type, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy); + return type.flags & TypeFlags.Unknown && assumeTrue ? nonNullUnknownType : + getTypeWithFacts(type, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy); } if (strictNullChecks && assumeTrue && optionalChainContainsReference(expr, reference)) { type = getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull); @@ -23812,6 +24438,9 @@ namespace ts { case SyntaxKind.InstanceOfKeyword: return narrowTypeByInstanceof(type, expr, assumeTrue); case SyntaxKind.InKeyword: + if (isPrivateIdentifier(expr.left)) { + return narrowTypeByPrivateIdentifierInInExpression(type, expr, assumeTrue); + } const target = getReferenceCandidate(expr.right); const leftType = getTypeOfNode(expr.left); if (leftType.flags & TypeFlags.StringLiteral) { @@ -23842,6 +24471,24 @@ namespace ts { 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, isTypeDerivedFrom); + } + 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. @@ -23869,6 +24516,9 @@ namespace ts { assumeTrue = !assumeTrue; } const valueType = getTypeOfExpression(value); + if (assumeTrue && (type.flags & TypeFlags.Unknown) && (operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsToken) && (valueType.flags & TypeFlags.Null)) { + return getUnionType([nullType, undefinedType]); + } if ((type.flags & TypeFlags.Unknown) && assumeTrue && (operator === SyntaxKind.EqualsEqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken)) { if (valueType.flags & (TypeFlags.Primitive | TypeFlags.NonPrimitive)) { return valueType; @@ -23888,7 +24538,7 @@ namespace ts { valueType.flags & TypeFlags.Null ? assumeTrue ? TypeFacts.EQNull : TypeFacts.NENull : assumeTrue ? TypeFacts.EQUndefined : TypeFacts.NEUndefined; - return getTypeWithFacts(type, facts); + return type.flags & TypeFlags.Unknown && facts & (TypeFacts.NENull | TypeFacts.NEUndefinedOrNull) ? nonNullUnknownType : getTypeWithFacts(type, facts); } if (assumeTrue) { const filterFn: (t: Type) => boolean = operator === SyntaxKind.EqualsEqualsToken ? @@ -23918,15 +24568,10 @@ namespace ts { return type; } if (assumeTrue && type.flags & TypeFlags.Unknown && literal.text === "object") { - // The pattern x && typeof x === 'object', where x is of type unknown, narrows x to type object. We don't - // need to check for the reverse typeof x === 'object' && x since that already narrows correctly. - if (typeOfExpr.parent.parent.kind === SyntaxKind.BinaryExpression) { - const expr = typeOfExpr.parent.parent as BinaryExpression; - if (expr.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken && expr.right === typeOfExpr.parent && containsTruthyCheck(reference, expr.left)) { - return nonPrimitiveType; - } - } - return getUnionType([nonPrimitiveType, nullType]); + // The non-null unknown type is used to track whether a previous narrowing operation has removed the null type + // from the unknown type. For example, the expression `x && typeof x === 'object'` first narrows x to the non-null + // unknown type, and then narrows that to the non-primitive type. + return type === nonNullUnknownType ? nonPrimitiveType : getUnionType([nonPrimitiveType, nullType]); } const facts = assumeTrue ? typeofEQFacts.get(literal.text) || TypeFacts.TypeofEQHostObject : @@ -24180,16 +24825,7 @@ namespace ts { function getNarrowedType(type: Type, candidate: Type, assumeTrue: boolean, isRelated: (source: Type, target: Type) => boolean) { if (!assumeTrue) { - return filterType(type, t => { - if (!isRelated(t, candidate)) { - return true; - } - const constraint = getBaseConstraintOfType(t); - if (constraint && constraint !== t) { - return !isRelated(constraint, candidate); - } - return false; - }); + return filterType(type, t => !isRelated(t, candidate)); } // If the current type is a union type, remove all constituents that couldn't be instances of // the candidate type. If one or more constituents remain, return a union of those. @@ -24348,37 +24984,38 @@ namespace ts { node.kind === SyntaxKind.PropertyDeclaration)!; } - // Check if a parameter is assigned anywhere within its declaring function. - function isParameterAssigned(symbol: Symbol) { + // Check if a parameter or catch variable is assigned anywhere + function isSymbolAssigned(symbol: Symbol) { if (!symbol.valueDeclaration) { return false; } - const func = getRootDeclaration(symbol.valueDeclaration).parent as FunctionLikeDeclaration; - const links = getNodeLinks(func); + const parent = getRootDeclaration(symbol.valueDeclaration).parent; + const links = getNodeLinks(parent); if (!(links.flags & NodeCheckFlags.AssignmentsMarked)) { links.flags |= NodeCheckFlags.AssignmentsMarked; - if (!hasParentWithAssignmentsMarked(func)) { - markParameterAssignments(func); + if (!hasParentWithAssignmentsMarked(parent)) { + markNodeAssignments(parent); } } return symbol.isAssigned || false; } function hasParentWithAssignmentsMarked(node: Node) { - return !!findAncestor(node.parent, node => isFunctionLike(node) && !!(getNodeLinks(node).flags & NodeCheckFlags.AssignmentsMarked)); + return !!findAncestor(node.parent, node => + (isFunctionLike(node) || isCatchClause(node)) && !!(getNodeLinks(node).flags & NodeCheckFlags.AssignmentsMarked)); } - function markParameterAssignments(node: Node) { + function markNodeAssignments(node: Node) { if (node.kind === SyntaxKind.Identifier) { if (isAssignmentTarget(node)) { const symbol = getResolvedSymbol(node as Identifier); - if (symbol.valueDeclaration && getRootDeclaration(symbol.valueDeclaration).kind === SyntaxKind.Parameter) { + if (isParameterOrCatchClauseVariable(symbol)) { symbol.isAssigned = true; } } } else { - forEachChild(node, markParameterAssignments); + forEachChild(node, markNodeAssignments); } } @@ -24412,7 +25049,7 @@ namespace ts { return parent.kind === SyntaxKind.PropertyAccessExpression || parent.kind === SyntaxKind.CallExpression && (parent as CallExpression).expression === node || parent.kind === SyntaxKind.ElementAccessExpression && (parent as ElementAccessExpression).expression === node && - !(isGenericTypeWithoutNullableConstraint(type) && isGenericIndexType(getTypeOfExpression((parent as ElementAccessExpression).argumentExpression))); + !(someType(type, isGenericTypeWithoutNullableConstraint) && isGenericIndexType(getTypeOfExpression((parent as ElementAccessExpression).argumentExpression))); } function isGenericTypeWithUnionConstraint(type: Type) { @@ -24423,12 +25060,16 @@ namespace ts { return !!(type.flags & TypeFlags.Instantiable && !maybeTypeOfKind(getBaseConstraintOrType(type), TypeFlags.Nullable)); } - function hasNonBindingPatternContextualTypeWithNoGenericTypes(node: Node) { + 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) && - getContextualType(node, ContextFlags.SkipBindingPatterns); + (checkMode && checkMode & CheckMode.RestBindingElement ? + getContextualType(node, ContextFlags.SkipBindingPatterns) + : getContextualType(node)); return contextualType && !isGenericType(contextualType); } @@ -24442,12 +25083,24 @@ namespace ts { // '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) || hasNonBindingPatternContextualTypeWithNoGenericTypes(reference)); - return substituteConstraints ? mapType(type, t => t.flags & TypeFlags.Instantiable ? getBaseConstraintOrType(t) : t) : type; + (isConstraintPosition(type, reference) || hasContextualTypeWithNoGenericTypes(reference, checkMode)); + return substituteConstraints ? mapType(type, t => t.flags & TypeFlags.Instantiable && !isMappedTypeGenericIndexedAccess(t) ? getBaseConstraintOrType(t) : t) : type; } function isExportOrExportExpression(location: Node) { - return !!findAncestor(location, e => e.parent && isExportAssignment(e.parent) && e.parent.expression === e && isEntityNameExpression(e)); + 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; + }); } function markAliasReferenced(symbol: Symbol, location: Node) { @@ -24470,7 +25123,91 @@ namespace ts { } } + function getNarrowedTypeOfSymbol(symbol: Symbol, location: Identifier) { + const declaration = symbol.valueDeclaration; + if (declaration) { + // If we have a non-rest binding element with no initializer declared as a const variable or a const-like + // parameter (a parameter for which there are no assignments in the function body), and if the parent type + // for the destructuring is a union type, one or more of the binding elements may represent discriminant + // properties, and we want the effects of conditional checks on such discriminants to affect the types of + // other binding elements from the same destructuring. Consider: + // + // type Action = + // | { kind: 'A', payload: number } + // | { kind: 'B', payload: string }; + // + // function f({ kind, payload }: Action) { + // if (kind === 'A') { + // payload.toFixed(); + // } + // if (kind === 'B') { + // payload.toUpperCase(); + // } + // } + // + // Above, we want the conditional checks on 'kind' to affect the type of 'payload'. To facilitate this, we use + // the binding pattern AST instance for '{ kind, payload }' as a pseudo-reference and narrow this reference + // as if it occurred in the specified location. We then recompute the narrowed binding element type by + // destructuring from the narrowed parent type. + if (isBindingElement(declaration) && !declaration.initializer && !declaration.dotDotDotToken && declaration.parent.elements.length >= 2) { + const parent = declaration.parent.parent; + if (parent.kind === SyntaxKind.VariableDeclaration && getCombinedNodeFlags(declaration) & NodeFlags.Const || parent.kind === SyntaxKind.Parameter) { + const links = getNodeLinks(location); + if (!(links.flags & NodeCheckFlags.InCheckIdentifier)) { + links.flags |= NodeCheckFlags.InCheckIdentifier; + const parentType = getTypeForBindingElementParent(parent, CheckMode.Normal); + links.flags &= ~NodeCheckFlags.InCheckIdentifier; + if (parentType && parentType.flags & TypeFlags.Union && !(parent.kind === SyntaxKind.Parameter && isSymbolAssigned(symbol))) { + const pattern = declaration.parent; + const narrowedType = getFlowTypeOfReference(pattern, parentType, parentType, /*flowContainer*/ undefined, location.flowNode); + return getBindingElementTypeFromParentType(declaration, narrowedType); + } + } + } + } + // If we have a const-like parameter with no type annotation or initializer, and if the parameter is contextually + // typed by a signature with a single rest parameter of a union of tuple types, one or more of the parameters may + // represent discriminant tuple elements, and we want the effects of conditional checks on such discriminants to + // affect the types of other parameters in the same parameter list. Consider: + // + // type Action = [kind: 'A', payload: number] | [kind: 'B', payload: string]; + // + // const f: (...args: Action) => void = (kind, payload) => { + // if (kind === 'A') { + // payload.toFixed(); + // } + // if (kind === 'B') { + // payload.toUpperCase(); + // } + // } + // + // Above, we want the conditional checks on 'kind' to affect the type of 'payload'. To facilitate this, we use + // the arrow function AST node for '(kind, payload) => ...' as a pseudo-reference and narrow this reference as + // if it occurred in the specified location. We then recompute the narrowed parameter type by indexing into the + // narrowed tuple type. + if (isParameter(declaration) && !declaration.type && !declaration.initializer && !declaration.dotDotDotToken) { + const func = declaration.parent; + if (func.parameters.length >= 2 && isContextSensitiveFunctionOrObjectLiteralMethod(func)) { + const contextualSignature = getContextualSignature(func); + if (contextualSignature && contextualSignature.parameters.length === 1 && signatureHasRestParameter(contextualSignature)) { + const restType = getTypeOfSymbol(contextualSignature.parameters[0]); + if (restType.flags & TypeFlags.Union && everyType(restType, isTupleType) && !isSymbolAssigned(symbol)) { + const narrowedType = getFlowTypeOfReference(func, restType, restType, /*flowContainer*/ undefined, location.flowNode); + const index = func.parameters.indexOf(declaration) - (getThisParameter(func) ? 1 : 0); + return getIndexedAccessType(narrowedType, getNumberLiteralType(index)); + } + } + } + } + } + return getTypeOfSymbol(symbol); + } + function checkIdentifier(node: Identifier, checkMode: CheckMode | undefined): Type { + if (isThisInTypeQuery(node)) { + return checkThisExpression(node); + } + const symbol = getResolvedSymbol(node); if (symbol === unknownSymbol) { return errorType; @@ -24509,9 +25246,9 @@ namespace ts { } const localOrExportSymbol = getExportSymbolOfValueSymbolIfExported(symbol); - const sourceSymbol = localOrExportSymbol.flags & SymbolFlags.Alias ? resolveAlias(localOrExportSymbol) : localOrExportSymbol; - if (sourceSymbol.declarations && getDeclarationNodeFlagsFromSymbol(sourceSymbol) & NodeFlags.Deprecated && isUncalledFunctionReference(node, sourceSymbol)) { - addDeprecatedSuggestion(node, sourceSymbol.declarations, node.escapedText as string); + const targetSymbol = checkDeprecatedAliasedSymbol(localOrExportSymbol, node); + if (isDeprecatedSymbol(targetSymbol) && isUncalledFunctionReference(node, targetSymbol) && targetSymbol.declarations) { + addDeprecatedSuggestion(node, targetSymbol.declarations, node.escapedText as string); } let declaration = localOrExportSymbol.valueDeclaration; @@ -24553,7 +25290,7 @@ namespace ts { checkNestedBlockScopedBinding(node, symbol); - let type = getTypeOfSymbol(localOrExportSymbol); + let type = getNarrowedTypeOfSymbol(localOrExportSymbol, node); const assignmentKind = getAssignmentTargetKind(node); if (assignmentKind) { @@ -24616,7 +25353,7 @@ namespace ts { // analysis to include the immediately enclosing function. while (flowContainer !== declarationContainer && (flowContainer.kind === SyntaxKind.FunctionExpression || flowContainer.kind === SyntaxKind.ArrowFunction || isObjectLiteralOrClassExpressionMethodOrAccessor(flowContainer)) && - (isConstVariable(localOrExportSymbol) && type !== autoArrayType || isParameter && !isParameterAssigned(localOrExportSymbol))) { + (isConstVariable(localOrExportSymbol) && type !== autoArrayType || isParameter && !isSymbolAssigned(localOrExportSymbol))) { flowContainer = getControlFlowContainer(flowContainer); } // We only look for uninitialized variables in strict null checking mode, and only when we can analyze @@ -25355,7 +26092,7 @@ namespace ts { const parent = declaration.parent.parent; const name = declaration.propertyName || declaration.name; const parentType = getContextualTypeForVariableLikeDeclaration(parent) || - parent.kind !== SyntaxKind.BindingElement && parent.initializer && checkDeclarationInitializer(parent); + 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); @@ -25390,8 +26127,7 @@ namespace ts { if (result) { return result; } - if (!(contextFlags! & ContextFlags.SkipBindingPatterns) && isBindingPattern(declaration.name)) { - // This is less a contextual type and more an implied shape - in some cases, this may be undesirable + if (!(contextFlags! & ContextFlags.SkipBindingPatterns) && isBindingPattern(declaration.name)) { // This is less a contextual type and more an implied shape - in some cases, this may be undesirable return getTypeFromBindingPattern(declaration.name, /*includePatternInType*/ true, /*reportErrors*/ false); } } @@ -25415,7 +26151,8 @@ namespace ts { } if (functionFlags & FunctionFlags.Async) { // Async function or AsyncGenerator function - const contextualAwaitedType = mapType(contextualReturnType, getAwaitedType); + // Get the awaited type without the `Awaited` alias + const contextualAwaitedType = mapType(contextualReturnType, getAwaitedTypeNoAlias); return contextualAwaitedType && getUnionType([contextualAwaitedType, createPromiseLikeType(contextualAwaitedType)]); } @@ -25428,7 +26165,7 @@ namespace ts { function getContextualTypeForAwaitOperand(node: AwaitExpression, contextFlags?: ContextFlags): Type | undefined { const contextualType = getContextualType(node, contextFlags); if (contextualType) { - const contextualAwaitedType = getAwaitedType(contextualType); + const contextualAwaitedType = getAwaitedTypeNoAlias(contextualType); return contextualAwaitedType && getUnionType([contextualAwaitedType, createPromiseLikeType(contextualAwaitedType)]); } return undefined; @@ -25504,6 +26241,12 @@ namespace ts { } 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); @@ -25685,12 +26428,12 @@ namespace ts { return !!(getCheckFlags(symbol) & CheckFlags.Mapped && !(symbol as MappedSymbol).type && findResolutionCycleStartIndex(symbol, TypeSystemPropertyName.Type) >= 0); } - function getTypeOfPropertyOfContextualType(type: Type, name: __String) { + function getTypeOfPropertyOfContextualType(type: Type, name: __String, nameType?: Type) { return mapType(type, t => { if (isGenericMappedType(t)) { const constraint = getConstraintTypeFromMappedType(t); const constraintOfConstraint = getBaseConstraintOfType(constraint) || constraint; - const propertyNameType = getStringLiteralType(unescapeLeadingUnderscores(name)); + const propertyNameType = nameType || getStringLiteralType(unescapeLeadingUnderscores(name)); if (isTypeAssignableTo(propertyNameType, constraintOfConstraint)) { return substituteIndexedMappedType(t, propertyNameType); } @@ -25706,7 +26449,7 @@ namespace ts { return restType; } } - return findApplicableIndexInfo(getIndexInfosOfStructuredType(t), getStringLiteralType(unescapeLeadingUnderscores(name)))?.type; + return findApplicableIndexInfo(getIndexInfosOfStructuredType(t), nameType || getStringLiteralType(unescapeLeadingUnderscores(name)))?.type; } return undefined; }, /*noReductions*/ true); @@ -25736,7 +26479,8 @@ namespace ts { // 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. - return getTypeOfPropertyOfContextualType(type, getSymbolOfNode(element).escapedName); + const symbol = getSymbolOfNode(element); + return getTypeOfPropertyOfContextualType(type, symbol.escapedName, getSymbolLinks(symbol).nameType); } if (element.name) { const nameType = getLiteralTypeFromPropertyName(element.name); @@ -25857,7 +26601,7 @@ namespace ts { 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) : (() => checkExpression((prop as JsxAttribute).initializer!)), prop.symbol.escapedName] as [() => Type, __String]) + prop => ([!(prop as JsxAttribute).initializer ? (() => trueType) : (() => getContextFreeTypeOfExpression((prop as JsxAttribute).initializer!)), prop.symbol.escapedName] as [() => Type, __String]) ), map( filter(getPropertiesOfType(contextualType), s => !!(s.flags & SymbolFlags.Optional) && !!node?.symbol?.members && !node.symbol.members.has(s.escapedName) && isDiscriminantProperty(contextualType, s.escapedName)), @@ -25964,10 +26708,6 @@ namespace ts { case SyntaxKind.AwaitExpression: return getContextualTypeForAwaitOperand(parent as AwaitExpression, contextFlags); case SyntaxKind.CallExpression: - if ((parent as CallExpression).expression.kind === SyntaxKind.ImportKeyword) { - return stringType; - } - /* falls through */ case SyntaxKind.NewExpression: return getContextualTypeForArgument(parent as CallExpression | NewExpression, node); case SyntaxKind.TypeAssertionExpression: @@ -26040,7 +26780,7 @@ namespace ts { let propsType = getTypeOfFirstParameterOfSignatureWithFallback(sig, unknownType); propsType = getJsxManagedAttributesFromLocatedAttributes(context, getJsxNamespaceAt(context), propsType); const intrinsicAttribs = getJsxType(JsxNames.IntrinsicAttributes, context); - if (intrinsicAttribs !== errorType) { + if (!isErrorType(intrinsicAttribs)) { propsType = intersectTypes(intrinsicAttribs, propsType); } return propsType; @@ -26139,7 +26879,7 @@ namespace ts { // Normal case -- add in IntrinsicClassElements and IntrinsicElements let apparentAttributesType = attributesType; const intrinsicClassAttribs = getJsxType(JsxNames.IntrinsicClassAttributes, context); - if (intrinsicClassAttribs !== errorType) { + if (!isErrorType(intrinsicClassAttribs)) { const typeParams = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(intrinsicClassAttribs.symbol); const hostClassType = getReturnTypeOfSignature(sig); apparentAttributesType = intersectTypes( @@ -26151,7 +26891,7 @@ namespace ts { } const intrinsicAttribs = getJsxType(JsxNames.IntrinsicAttributes, context); - if (intrinsicAttribs !== errorType) { + if (!isErrorType(intrinsicAttribs)) { apparentAttributesType = intersectTypes(intrinsicAttribs, apparentAttributesType); } @@ -26279,10 +27019,6 @@ namespace ts { return !hasEffectiveRestParameter(signature) && getParameterCount(signature) < targetParameterCount; } - function isFunctionExpressionOrArrowFunction(node: Node): node is FunctionExpression | ArrowFunction { - return node.kind === SyntaxKind.FunctionExpression || node.kind === SyntaxKind.ArrowFunction; - } - function getContextualSignatureForFunctionLikeDeclaration(node: FunctionLikeDeclaration): Signature | undefined { // Only function expressions, arrow functions, and object literal methods are contextually typed. return isFunctionExpressionOrArrowFunction(node) || isObjectLiteralMethod(node) @@ -26450,34 +27186,14 @@ namespace ts { return isTypeAssignableToKind(checkComputedPropertyName(name), TypeFlags.NumberLike); } - function isNumericLiteralName(name: string | __String) { - // The intent of numeric names is that - // - they are names with text in a numeric form, and that - // - setting properties/indexing with them is always equivalent to doing so with the numeric literal 'numLit', - // acquired by applying the abstract 'ToNumber' operation on the name's text. - // - // The subtlety is in the latter portion, as we cannot reliably say that anything that looks like a numeric literal is a numeric name. - // In fact, it is the case that the text of the name must be equal to 'ToString(numLit)' for this to hold. - // - // Consider the property name '"0xF00D"'. When one indexes with '0xF00D', they are actually indexing with the value of 'ToString(0xF00D)' - // according to the ECMAScript specification, so it is actually as if the user indexed with the string '"61453"'. - // Thus, the text of all numeric literals equivalent to '61543' such as '0xF00D', '0xf00D', '0170015', etc. are not valid numeric names - // because their 'ToString' representation is not equal to their original text. - // This is motivated by ECMA-262 sections 9.3.1, 9.8.1, 11.1.5, and 11.2.1. - // - // Here, we test whether 'ToString(ToNumber(name))' is exactly equal to 'name'. - // The '+' prefix operator is equivalent here to applying the abstract ToNumber operation. - // Applying the 'toString()' method on a number gives us the abstract ToString operation on a number. - // - // Note that this accepts the values 'Infinity', '-Infinity', and 'NaN', and that this is intentional. - // This is desired behavior, because when indexing with them as numeric entities, you are indexing - // with the strings '"Infinity"', '"-Infinity"', and '"NaN"' respectively. - return (+name).toString() === name; - } - 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.) @@ -26662,7 +27378,7 @@ namespace ts { checkSpreadPropOverrides(mergedType, allPropertiesTable, memberDecl); } offset = propertiesArray.length; - if (spread === errorType) { + if (isErrorType(spread)) { continue; } spread = getSpreadType(spread, mergedType, node.symbol, objectFlags, inConstContext); @@ -26722,7 +27438,7 @@ namespace ts { } } - if (spread === errorType) { + if (isErrorType(spread)) { return errorType; } @@ -26761,15 +27477,9 @@ namespace ts { } function isValidSpreadType(type: Type): boolean { - if (type.flags & TypeFlags.Instantiable) { - const constraint = getBaseConstraintOfType(type); - if (constraint !== undefined) { - return isValidSpreadType(constraint); - } - } - return !!(type.flags & (TypeFlags.Any | TypeFlags.NonPrimitive | TypeFlags.Object | TypeFlags.InstantiableNonPrimitive) || - getFalsyFlags(type) & TypeFlags.DefinitelyFalsy && isValidSpreadType(removeDefinitelyFalsyTypes(type)) || - type.flags & TypeFlags.UnionOrIntersection && every((type as UnionOrIntersectionType).types, isValidSpreadType)); + 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) { @@ -27016,7 +27726,7 @@ namespace ts { const links = getNodeLinks(node); if (!links.resolvedSymbol) { const intrinsicElementsType = getJsxType(JsxNames.IntrinsicElements, node); - if (intrinsicElementsType !== errorType) { + if (!isErrorType(intrinsicElementsType)) { // Property case if (!isIdentifier(node.tagName)) return Debug.fail(); const intrinsicProp = getPropertyOfType(intrinsicElementsType, node.tagName.escapedText); @@ -27190,7 +27900,7 @@ namespace ts { // var CustomTag: "h1" = "h1"; // Hello World const intrinsicElementsType = getJsxType(JsxNames.IntrinsicElements, location); - if (intrinsicElementsType !== errorType) { + if (!isErrorType(intrinsicElementsType)) { const stringLiteralTypeName = type.value; const intrinsicProp = getPropertyOfType(intrinsicElementsType, escapeLeadingUnderscores(stringLiteralTypeName)); if (intrinsicProp) { @@ -27262,7 +27972,7 @@ namespace ts { function getJsxElementClassTypeAt(location: Node): Type | undefined { const type = getJsxType(JsxNames.ElementClass, location); - if (type === errorType) return undefined; + if (isErrorType(type)) return undefined; return type; } @@ -27302,7 +28012,7 @@ namespace ts { const isNodeOpeningLikeElement = isJsxOpeningLikeElement(node); if (isNodeOpeningLikeElement) { - checkGrammarJsxElement(node as JsxOpeningLikeElement); + checkGrammarJsxElement(node); } checkJsxPreconditions(node); @@ -27312,7 +28022,7 @@ namespace ts { // And if there is no reactNamespace/jsxFactory's symbol in scope when targeting React emit, we should issue an error. const jsxFactoryRefErr = diagnostics && compilerOptions.jsx === JsxEmit.React ? Diagnostics.Cannot_find_name_0 : undefined; const jsxFactoryNamespace = getJsxNamespace(node); - const jsxFactoryLocation = isNodeOpeningLikeElement ? (node as JsxOpeningLikeElement).tagName : node; + const jsxFactoryLocation = isNodeOpeningLikeElement ? node.tagName : node; // allow null as jsxFragmentFactory let jsxFactorySym: Symbol | undefined; @@ -27330,12 +28040,21 @@ namespace ts { 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, localJsxNamespace, /*isUse*/ true); + } + } } if (isNodeOpeningLikeElement) { - const jsxOpeningLikeNode = node as JsxOpeningLikeElement; + const jsxOpeningLikeNode = node ; const sig = getResolvedSignature(jsxOpeningLikeNode); - checkDeprecatedSignature(sig, node as JsxOpeningLikeElement); + checkDeprecatedSignature(sig, node); checkJsxReturnAssignableToAppropriateBound(getJsxReferenceKind(jsxOpeningLikeNode), getReturnTypeOfSignature(sig), jsxOpeningLikeNode); } } @@ -27631,7 +28350,7 @@ namespace ts { function checkNonNullNonVoidType(type: Type, node: Node): Type { const nonNullType = checkNonNullType(type, node); - if (nonNullType !== errorType && nonNullType.flags & TypeFlags.Void) { + if (nonNullType.flags & TypeFlags.Void) { error(node, Diagnostics.Object_is_possibly_undefined); } return nonNullType; @@ -27672,6 +28391,46 @@ namespace ts { } } + 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, /* isThisAccess: */ 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); } @@ -27763,32 +28522,9 @@ namespace ts { grammarErrorOnNode(right, Diagnostics.Cannot_assign_to_private_method_0_Private_methods_are_not_writable, idText(right)); } - if (lexicallyScopedSymbol?.valueDeclaration && (compilerOptions.target === ScriptTarget.ESNext && !useDefineForClassFields)) { - const lexicalClass = getContainingClass(lexicallyScopedSymbol.valueDeclaration); - const parentStaticFieldInitializer = findAncestor(node, (n) => { - if (n === lexicalClass) return "quit"; - if (isPropertyDeclaration(n.parent) && hasStaticModifier(n.parent) && n.parent.initializer === n && n.parent.parent === lexicalClass) { - return true; - } - return false; - }); - if (parentStaticFieldInitializer) { - const parentStaticFieldInitializerSymbol = getSymbolOfNode(parentStaticFieldInitializer.parent); - Debug.assert(parentStaticFieldInitializerSymbol, "Initializer without declaration symbol"); - const diagnostic = error(node, - Diagnostics.Property_0_may_not_be_used_in_a_static_property_s_initializer_in_the_same_class_when_target_is_esnext_and_useDefineForClassFields_is_false, - symbolName(lexicallyScopedSymbol)); - addRelatedInfo(diagnostic, - createDiagnosticForNode(parentStaticFieldInitializer.parent, - Diagnostics.Initializer_for_property_0, - symbolName(parentStaticFieldInitializerSymbol)) - ); - } - } - if (isAnyLike) { if (lexicallyScopedSymbol) { - return apparentType; + return isErrorType(apparentType) ? errorType : apparentType; } if (!getContainingClass(right)) { grammarErrorOnNode(right, Diagnostics.Private_identifiers_are_not_allowed_outside_class_bodies); @@ -27812,7 +28548,7 @@ namespace ts { if (isIdentifier(left) && parentSymbol) { markAliasReferenced(parentSymbol, node); } - return apparentType; + return isErrorType(apparentType) ? errorType : apparentType;; } prop = getPropertyOfType(apparentType, right.escapedText); } @@ -27855,9 +28591,12 @@ namespace ts { 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 && getCombinedNodeFlags(indexInfo.declaration) & NodeFlags.Deprecated) { + addDeprecatedSuggestion(right, [indexInfo.declaration], right.escapedText as string); + } } else { - if (prop.declarations && getDeclarationNodeFlagsFromSymbol(prop) & NodeFlags.Deprecated && isUncalledFunctionReference(node, prop)) { + if (isDeprecatedSymbol(prop) && isUncalledFunctionReference(node, prop) && prop.declarations) { addDeprecatedSuggestion(right, prop.declarations, right.escapedText as string); } checkPropertyNotUsedBeforeDeclaration(prop, node, right); @@ -27870,7 +28609,7 @@ namespace ts { return errorType; } - propType = isThisPropertyAccessInConstructor(node, prop) ? autoType : writing ? getSetAccessorTypeOfSymbol(prop) : getTypeOfSymbol(prop); + propType = isThisPropertyAccessInConstructor(node, prop) ? autoType : writing ? getWriteTypeOfSymbol(prop) : getTypeOfSymbol(prop); } return getFlowTypeOfAccessExpression(node, prop, propType, right, checkMode); @@ -28082,7 +28821,7 @@ namespace ts { if (relatedInfo) { addRelatedInfo(resultDiagnostic, relatedInfo); } - addErrorOrSuggestion(!isUncheckedJS, resultDiagnostic); + addErrorOrSuggestion(!isUncheckedJS || errorInfo.code !== Diagnostics.Property_0_may_not_exist_on_type_1_Did_you_mean_2.code, resultDiagnostic); } function containerSeemsToBeEmptyDomElement(containingType: Type) { @@ -28156,13 +28895,26 @@ namespace ts { function getSuggestedSymbolForNonexistentSymbol(location: Node | undefined, outerName: __String, meaning: SymbolFlags): Symbol | undefined { Debug.assert(outerName !== undefined, "outername should always be defined"); - const result = resolveNameHelper(location, outerName, meaning, /*nameNotFoundMessage*/ undefined, outerName, /*isUse*/ false, /*excludeGlobals*/ false, (symbols, name, meaning) => { + const result = resolveNameHelper(location, outerName, meaning, /*nameNotFoundMessage*/ undefined, outerName, /*isUse*/ false, /*excludeGlobals*/ false, /*getSpellingSuggestions*/ true, (symbols, name, meaning) => { Debug.assertEqual(outerName, name, "name should equal outerName"); const symbol = getSymbol(symbols, name, meaning); // Sometimes the symbol is found when location is a return type of a function: `typeof x` and `x` is declared in the body of the function // So the table *contains* `x` but `x` isn't actually in scope. // However, resolveNameHelper will continue and call this callback again, so we'll eventually get a correct suggestion. - return symbol || getSpellingSuggestionForName(unescapeLeadingUnderscores(name), arrayFrom(symbols.values()), meaning); + if (symbol) return symbol; + let candidates: Symbol[]; + if (symbols === globals) { + const primitives = mapDefined( + ["string", "number", "boolean", "object", "bigint", "symbol"], + s => symbols.has((s.charAt(0).toUpperCase() + s.slice(1)) as __String) + ? createSymbol(SymbolFlags.TypeAlias, s as __String) as Symbol + : undefined); + candidates = primitives.concat(arrayFrom(symbols.values())); + } + else { + candidates = arrayFrom(symbols.values()); + } + return getSpellingSuggestionForName(unescapeLeadingUnderscores(name), candidates, meaning); }); return result; } @@ -28208,6 +28960,11 @@ namespace ts { 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. @@ -28313,7 +29070,7 @@ namespace ts { type: Type): boolean { // Short-circuiting for improved performance. - if (type === errorType || isTypeAny(type)) { + if (isTypeAny(type)) { return true; } @@ -28339,7 +29096,7 @@ namespace ts { property: Symbol): boolean { // Short-circuiting for improved performance. - if (containingType === errorType || isTypeAny(containingType)) { + if (isTypeAny(containingType)) { return true; } @@ -28419,7 +29176,7 @@ namespace ts { const indexExpression = node.argumentExpression; const indexType = checkExpression(indexExpression); - if (objectType === errorType || objectType === silentNeverType) { + if (isErrorType(objectType) || objectType === silentNeverType) { return objectType; } @@ -28690,7 +29447,7 @@ namespace ts { // 'let f: (x: string) => number = wrap(s => s.length)', we infer from the declared type of 'f' to the // return type of 'wrap'. if (node.kind !== SyntaxKind.Decorator) { - const contextualType = getContextualType(node, ContextFlags.SkipBindingPatterns); + const contextualType = getContextualType(node, every(signature.typeParameters, p => !!getDefaultFromTypeParameter(p)) ? ContextFlags.SkipBindingPatterns : ContextFlags.None); if (contextualType) { // 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 @@ -29443,7 +30200,7 @@ namespace ts { const diags = max > 1 ? allDiagnostics[minIndex] : flatten(allDiagnostics); Debug.assert(diags.length > 0, "No errors reported for 3 or fewer overload signatures"); const chain = chainDiagnosticMessages( - map(diags, d => typeof d.messageText === "string" ? (d as DiagnosticMessageChain) : d.messageText), + map(diags, createDiagnosticMessageChainFromDiagnostic), Diagnostics.No_overload_matches_this_call); // The below is a spread to guarantee we get a new (mutable) array - our `flatMap` helper tries to do "smart" optimizations where it reuses input // arrays and the emptyArray singleton where possible, which is decidedly not what we want while we're still constructing this diagnostic @@ -29683,7 +30440,7 @@ namespace ts { typeArguments.pop(); } while (typeArguments.length < typeParameters.length) { - typeArguments.push(getConstraintOfTypeParameter(typeParameters[typeArguments.length]) || getDefaultTypeArgumentType(isJs)); + typeArguments.push(getDefaultFromTypeParameter(typeParameters[typeArguments.length]) || getConstraintOfTypeParameter(typeParameters[typeArguments.length]) || getDefaultTypeArgumentType(isJs)); } return typeArguments; } @@ -29722,7 +30479,7 @@ namespace ts { } return anySignature; } - if (superType !== errorType) { + 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)!); @@ -29758,7 +30515,7 @@ namespace ts { } const apparentType = getApparentType(funcType); - if (apparentType === errorType) { + if (isErrorType(apparentType)) { // Another error has already been reported return resolveErrorCall(node); } @@ -29776,7 +30533,7 @@ namespace ts { 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 (funcType !== errorType && node.typeArguments) { + if (!isErrorType(funcType) && node.typeArguments) { error(node, Diagnostics.Untyped_function_calls_may_not_accept_type_arguments); } return resolveUntypedCall(node); @@ -29859,7 +30616,7 @@ namespace ts { // signatures for overload resolution. The result type of the function call becomes // the result type of the operation. expressionType = getApparentType(expressionType); - if (expressionType === errorType) { + if (isErrorType(expressionType)) { // Another error has already been reported return resolveErrorCall(node); } @@ -30112,7 +30869,7 @@ namespace ts { const tagType = checkExpression(node.tag); const apparentType = getApparentType(tagType); - if (apparentType === errorType) { + if (isErrorType(apparentType)) { // Another error has already been reported return resolveErrorCall(node); } @@ -30169,7 +30926,7 @@ namespace ts { function resolveDecorator(node: Decorator, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature { const funcType = checkExpression(node.expression); const apparentType = getApparentType(funcType); - if (apparentType === errorType) { + if (isErrorType(apparentType)) { return resolveErrorCall(node); } @@ -30239,7 +30996,7 @@ namespace ts { } const exprTypes = checkExpression(node.tagName); const apparentType = getApparentType(exprTypes); - if (apparentType === errorType) { + if (isErrorType(apparentType)) { return resolveErrorCall(node); } @@ -30433,7 +31190,7 @@ namespace ts { * @returns On success, the expression's signature's return type. On failure, anyType. */ function checkCallExpression(node: CallExpression | NewExpression, checkMode?: CheckMode): Type { - if (!checkGrammarTypeArguments(node, node.typeArguments)) checkGrammarArguments(node.arguments); + checkGrammarTypeArguments(node, node.typeArguments); const signature = getResolvedSignature(node, /*candidatesOutArray*/ undefined, checkMode); if (signature === resolvingSignature) { @@ -30553,15 +31310,17 @@ namespace ts { function checkImportCallExpression(node: ImportCall): Type { // Check grammar of dynamic import - if (!checkGrammarArguments(node.arguments)) checkGrammarImportCallExpression(node); + 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 = 1; i < node.arguments.length; ++i) { + for (let i = 2; i < node.arguments.length; ++i) { checkExpressionCached(node.arguments[i]); } @@ -30569,32 +31328,59 @@ namespace ts { 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, /*dontRecursivelyResolve*/ true, /*suppressUsageError*/ false); if (esModuleSymbol) { - return createPromiseReturnType(node, getTypeWithSyntheticDefaultImportType(getTypeOfSymbol(esModuleSymbol), esModuleSymbol, moduleSymbol)); + return createPromiseReturnType(node, + getTypeWithSyntheticDefaultOnly(getTypeOfSymbol(esModuleSymbol), esModuleSymbol, moduleSymbol, specifier) || + getTypeWithSyntheticDefaultImportType(getTypeOfSymbol(esModuleSymbol), esModuleSymbol, moduleSymbol, specifier) + ); } } return createPromiseReturnType(node, anyType); } - function getTypeWithSyntheticDefaultImportType(type: Type, symbol: Symbol, originalSymbol: Symbol): Type { - if (allowSyntheticDefaultImports && type && type !== errorType) { + function createDefaultPropertyWrapperForModule(symbol: Symbol, originalSymbol: Symbol, anonymousSymbol?: Symbol | undefined) { + const memberTable = createSymbolTable(); + const newSymbol = createSymbol(SymbolFlags.Alias, InternalSymbolName.Default); + newSymbol.parent = originalSymbol; + newSymbol.nameType = getStringLiteralType("default"); + newSymbol.target = 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); + const hasSyntheticDefault = canHaveSyntheticDefault(file, originalSymbol, /*dontResolveAlias*/ false, moduleSpecifier); if (hasSyntheticDefault) { - const memberTable = createSymbolTable(); - const newSymbol = createSymbol(SymbolFlags.Alias, InternalSymbolName.Default); - newSymbol.parent = originalSymbol; - newSymbol.nameType = getStringLiteralType("default"); - newSymbol.target = resolveSymbol(symbol); - memberTable.set(InternalSymbolName.Default, newSymbol); const anonymousSymbol = createSymbol(SymbolFlags.TypeLiteral, InternalSymbolName.Type); - const defaultContainingObject = createAnonymousType(anonymousSymbol, memberTable, emptyArray, emptyArray, emptyArray); + const defaultContainingObject = createDefaultPropertyWrapperForModule(symbol, originalSymbol, anonymousSymbol); anonymousSymbol.type = defaultContainingObject; synthType.syntheticType = isValidSpreadType(type) ? getSpreadType(type, defaultContainingObject, anonymousSymbol, /*objectFlags*/ 0, /*readonly*/ false) : defaultContainingObject; } @@ -30647,6 +31433,12 @@ namespace ts { } function checkAssertion(node: AssertionExpression) { + if (node.kind === SyntaxKind.TypeAssertionExpression) { + const file = getSourceFileOfNode(node); + if (file && fileExtensionIsOneOf(file.fileName, [Extension.Cts, Extension.Mts])) { + grammarErrorOnNode(node, Diagnostics.This_syntax_is_reserved_in_files_with_the_mts_or_cts_extension_Use_an_as_expression_instead); + } + } return checkAssertionWorker(node, node.type, node.expression); } @@ -30672,13 +31464,11 @@ namespace ts { case SyntaxKind.PropertyAccessExpression: case SyntaxKind.ElementAccessExpression: const expr = (node as PropertyAccessExpression | ElementAccessExpression).expression; - if (isIdentifier(expr)) { - let symbol = getSymbolAtLocation(expr); - if (symbol && symbol.flags & SymbolFlags.Alias) { - symbol = resolveAlias(symbol); - } - return !!(symbol && (symbol.flags & SymbolFlags.Enum) && getEnumKind(symbol) === EnumKind.Literal); + let symbol = getTypeOfNode(expr).symbol; + if (symbol && symbol.flags & SymbolFlags.Alias) { + symbol = resolveAlias(symbol); } + return !!(symbol && (symbol.flags & SymbolFlags.Enum) && getEnumKind(symbol) === EnumKind.Literal); } return false; } @@ -30694,7 +31484,7 @@ namespace ts { checkSourceElement(type); exprType = getRegularTypeOfObjectLiteral(getBaseTypeOfLiteralType(exprType)); const targetType = getTypeFromTypeNode(type); - if (produceDiagnostics && targetType !== errorType) { + if (produceDiagnostics && !isErrorType(targetType)) { const widenedType = getWidenedType(exprType); if (!isTypeComparableTo(targetType, widenedType)) { checkTypeComparableTo(exprType, targetType, errNode, @@ -30715,6 +31505,76 @@ namespace ts { getNonNullableType(checkExpression(node.expression)); } + function checkExpressionWithTypeArguments(node: ExpressionWithTypeArguments | TypeQueryNode) { + checkGrammarExpressionWithTypeArguments(node); + const exprType = node.kind === SyntaxKind.ExpressionWithTypeArguments ? checkExpression(node.expression) : + isThisIdentifier(node.exprName) ? checkThisExpression(node.exprName) : + checkExpression(node.exprName); + const typeArguments = node.typeArguments; + if (exprType === silentNeverType || isErrorType(exprType) || !some(typeArguments)) { + return exprType; + } + let hasSomeApplicableSignature = false; + let nonApplicableType: Type | undefined; + const result = getInstantiatedType(exprType); + const errorType = hasSomeApplicableSignature ? nonApplicableType : exprType; + if (errorType) { + diagnostics.add(createDiagnosticForNodeArray(getSourceFileOfNode(node), typeArguments, Diagnostics.Type_0_has_no_signatures_for_which_the_type_argument_list_is_applicable, typeToString(errorType))); + } + return result; + + function 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(undefined, resolved.members, callSignatures, constructSignatures, resolved.indexInfos) as ResolvedType & InstantiationExpressionType; + result.objectFlags |= ObjectFlags.InstantiationExpressionType; + result.node = node; + return result; + } + } + else if (type.flags & TypeFlags.InstantiableNonPrimitive) { + const constraint = getBaseConstraintOfType(type); + if (constraint) { + const instantiated = getInstantiatedTypePart(constraint); + if (instantiated !== constraint) { + return instantiated; + } + } + } + else if (type.flags & TypeFlags.Union) { + return mapType(type, getInstantiatedType); + } + else if (type.flags & TypeFlags.Intersection) { + return getIntersectionType(sameMap((type as IntersectionType).types, getInstantiatedTypePart)); + } + return type; + } + } + + function 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 checkMetaProperty(node: MetaProperty): Type { checkGrammarMetaProperty(node); @@ -30735,7 +31595,7 @@ namespace ts { return getGlobalImportMetaExpressionType(); case SyntaxKind.NewKeyword: const type = checkNewTargetMetaProperty(node); - return type === errorType ? errorType : createNewTargetExpressionType(type); + return isErrorType(type) ? errorType : createNewTargetExpressionType(type); default: Debug.assertNever(node.keywordToken); } @@ -30758,8 +31618,13 @@ namespace ts { } function checkImportMetaProperty(node: MetaProperty) { - if (moduleKind !== ModuleKind.ES2020 && moduleKind !== ModuleKind.ESNext && moduleKind !== ModuleKind.System) { - error(node, Diagnostics.The_import_meta_meta_property_is_only_allowed_when_the_module_option_is_es2020_esnext_or_system); + if (moduleKind === ModuleKind.Node12 || 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_node12_or_nodenext); } const file = getSourceFileOfNode(node); Debug.assert(!!(file.flags & NodeFlags.PossiblyContainsImportMeta), "Containing file is missing import meta node flag."); @@ -30798,6 +31663,9 @@ namespace ts { } function getParameterIdentifierNameAtPosition(signature: Signature, pos: number): [parameterName: __String, isRestParameter: boolean] | undefined { + if (signature.declaration?.kind === SyntaxKind.JSDocFunctionType) { + return undefined; + } const paramCount = signature.parameters.length - (signatureHasRestParameter(signature) ? 1 : 0); if (pos < paramCount) { const param = signature.parameters[pos]; @@ -31057,27 +31925,28 @@ namespace ts { const links = getSymbolLinks(parameter); if (!links.type) { const declaration = parameter.valueDeclaration as ParameterDeclaration; - links.type = type || getWidenedTypeForVariableLikeDeclaration(declaration, /*includeOptionality*/ true); + links.type = type || getWidenedTypeForVariableLikeDeclaration(declaration, /*reportErrors*/ true); if (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); + assignBindingElementTypes(declaration.name, links.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) { + function assignBindingElementTypes(pattern: BindingPattern, parentType: Type) { for (const element of pattern.elements) { if (!isOmittedExpression(element)) { + const type = getBindingElementTypeFromParentType(element, parentType); if (element.name.kind === SyntaxKind.Identifier) { - getSymbolLinks(getSymbolOfNode(element)).type = getTypeForBindingElement(element); + getSymbolLinks(getSymbolOfNode(element)).type = type; } else { - assignBindingElementTypes(element.name); + assignBindingElementTypes(element.name, type); } } } @@ -31088,7 +31957,8 @@ namespace ts { 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 - promisedType = getAwaitedType(promisedType) || unknownType; + // Unwrap an `Awaited` to `T` to improve inference. + promisedType = getAwaitedTypeNoAlias(unwrapAwaitedType(promisedType)) || unknownType; return createTypeReference(globalPromiseType, [promisedType]); } @@ -31100,7 +31970,8 @@ namespace ts { 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 - promisedType = getAwaitedType(promisedType) || unknownType; + // Unwrap an `Awaited` to `T` to improve inference. + promisedType = getAwaitedTypeNoAlias(unwrapAwaitedType(promisedType)) || unknownType; return createTypeReference(globalPromiseLikeType, [promisedType]); } @@ -31157,7 +32028,7 @@ namespace ts { // 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 = checkAwaitedType(returnType, /*errorNode*/ func, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member); + 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 @@ -31390,7 +32261,7 @@ namespace ts { // 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 = checkAwaitedType(type, func, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member); + 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; @@ -31592,7 +32463,7 @@ namespace ts { const returnOrPromisedType = returnType && unwrapReturnType(returnType, functionFlags); if (returnOrPromisedType) { if ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.Async) { // Async function - const awaitedType = checkAwaitedType(exprType, node.body, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member); + const awaitedType = checkAwaitedType(exprType, /*withAlias*/ false, node.body, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member); checkTypeAssignableToAndOptionallyElaborate(awaitedType, returnOrPromisedType, node.body, node.body); } else { // Normal function @@ -31780,10 +32651,10 @@ namespace ts { Diagnostics.await_expressions_are_only_allowed_at_the_top_level_of_a_file_when_that_file_is_a_module_but_this_file_has_no_imports_or_exports_Consider_adding_an_empty_export_to_make_this_file_a_module); diagnostics.add(diagnostic); } - if ((moduleKind !== ModuleKind.ESNext && moduleKind !== ModuleKind.System) || languageVersion < ScriptTarget.ES2017) { + if ((moduleKind !== ModuleKind.ES2022 && moduleKind !== ModuleKind.ESNext && moduleKind !== ModuleKind.System && !(moduleKind === ModuleKind.NodeNext && getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.ESNext)) || languageVersion < ScriptTarget.ES2017) { span = getSpanOfTokenAtPosition(sourceFile, node.pos); const diagnostic = createFileDiagnostic(sourceFile, span.start, span.length, - Diagnostics.Top_level_await_expressions_are_only_allowed_when_the_module_option_is_set_to_esnext_or_system_and_the_target_option_is_set_to_es2017_or_higher); + Diagnostics.Top_level_await_expressions_are_only_allowed_when_the_module_option_is_set_to_es2022_esnext_system_or_nodenext_and_the_target_option_is_set_to_es2017_or_higher); diagnostics.add(diagnostic); } } @@ -31809,8 +32680,8 @@ namespace ts { } const operandType = checkExpression(node.expression); - const awaitedType = checkAwaitedType(operandType, node, Diagnostics.Type_of_await_operand_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member); - if (awaitedType === operandType && awaitedType !== errorType && !(operandType.flags & TypeFlags.AnyOrUnknown)) { + 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; @@ -31978,11 +32849,29 @@ namespace ts { if (leftType === silentNeverType || rightType === silentNeverType) { return silentNeverType; } - leftType = checkNonNullType(leftType, left); + if (isPrivateIdentifier(left)) { + if (languageVersion < ScriptTarget.ESNext) { + checkExternalEmitHelpers(left, ExternalEmitHelpers.ClassPrivateFieldIn); + } + // Unlike in 'checkPrivateIdentifierExpression' we now have access to the RHS type + // which provides us with the opportunity to emit more detailed errors + if (!getNodeLinks(left).resolvedSymbol && getContainingClass(left)) { + const isUncheckedJS = isUncheckedJSSuggestion(left, rightType.symbol, /*excludeClasses*/ true); + reportNonexistentProperty(left, rightType, isUncheckedJS); + } + } + else { + leftType = checkNonNullType(leftType, left); + // TypeScript 1.0 spec (April 2014): 4.15.5 + // Require the left operand to be of type Any, the String primitive type, or the Number primitive type. + if (!(allTypesAssignableToKind(leftType, TypeFlags.StringLike | TypeFlags.NumberLike | TypeFlags.ESSymbolLike) || + isTypeAssignableToKind(leftType, TypeFlags.Index | TypeFlags.TemplateLiteral | TypeFlags.StringMapping | TypeFlags.TypeParameter))) { + error(left, Diagnostics.The_left_hand_side_of_an_in_expression_must_be_a_private_identifier_or_of_type_any_string_number_or_symbol); + } + } rightType = checkNonNullType(rightType, right); // TypeScript 1.0 spec (April 2014): 4.15.5 - // The in operator requires the left operand to be of type Any, the String primitive type, or the Number primitive type, - // and the right operand to be + // The in operator requires the right operand to be // // 1. assignable to the non-primitive type, // 2. an unconstrained type parameter, @@ -32000,10 +32889,6 @@ namespace ts { // unless *all* instantiations would result in an error. // // The result is always of the Boolean primitive type. - if (!(allTypesAssignableToKind(leftType, TypeFlags.StringLike | TypeFlags.NumberLike | TypeFlags.ESSymbolLike) || - isTypeAssignableToKind(leftType, TypeFlags.Index | TypeFlags.TemplateLiteral | TypeFlags.StringMapping | TypeFlags.TypeParameter))) { - error(left, Diagnostics.The_left_hand_side_of_an_in_expression_must_be_of_type_any_string_number_or_symbol); - } const rightTypeConstraint = getConstraintOfType(rightType); if (!allTypesAssignableToKind(rightType, TypeFlags.NonPrimitive | TypeFlags.InstantiableNonPrimitive) || rightTypeConstraint && ( @@ -32533,7 +33418,7 @@ namespace ts { 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 = leftType === errorType || rightType === errorType ? errorType : anyType; + resultType = isErrorType(leftType) || isErrorType(rightType) ? errorType : anyType; } // Symbols are not allowed at all in arithmetic expressions @@ -32761,8 +33646,8 @@ namespace ts { let wouldWorkWithAwait = false; const errNode = errorNode || operatorToken; if (isRelated) { - const awaitedLeftType = getAwaitedType(leftType); - const awaitedRightType = getAwaitedType(rightType); + const awaitedLeftType = getAwaitedTypeNoAlias(leftType); + const awaitedRightType = getAwaitedTypeNoAlias(rightType); wouldWorkWithAwait = !(awaitedLeftType === leftType && awaitedRightType === rightType) && !!(awaitedLeftType && awaitedRightType) && isRelated(awaitedLeftType, awaitedRightType); @@ -32958,11 +33843,11 @@ namespace ts { } function checkExpressionCached(node: Expression | QualifiedName, checkMode?: CheckMode): Type { + if (checkMode && checkMode !== CheckMode.Normal) { + return checkExpression(node, checkMode); + } const links = getNodeLinks(node); if (!links.resolvedType) { - if (checkMode && checkMode !== CheckMode.Normal) { - return checkExpression(node, checkMode); - } // 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. @@ -32984,10 +33869,16 @@ namespace ts { isJSDocTypeAssertion(node); } - function checkDeclarationInitializer(declaration: HasExpressionInitializer, contextualType?: Type | undefined) { + function checkDeclarationInitializer( + declaration: HasExpressionInitializer, + checkMode: CheckMode, + contextualType?: Type | undefined + ) { const initializer = getEffectiveInitializer(declaration)!; const type = getQuickTypeOfExpression(initializer) || - (contextualType ? checkExpressionWithContextualType(initializer, contextualType, /*inferenceContext*/ undefined, CheckMode.Normal) : checkExpressionCached(initializer)); + (contextualType ? + checkExpressionWithContextualType(initializer, contextualType, /*inferenceContext*/ undefined, checkMode || CheckMode.Normal) + : checkExpressionCached(initializer, checkMode)); return isParameter(declaration) && declaration.name.kind === SyntaxKind.ArrayBindingPattern && isTupleType(type) && !type.target.hasRestElement && getTypeReferenceArity(type) < declaration.name.elements.length ? padTupleType(type, declaration.name) : type; @@ -33323,7 +34214,7 @@ namespace ts { } 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 }); + 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; @@ -33363,7 +34254,7 @@ namespace ts { } function checkParenthesizedExpression(node: ParenthesizedExpression, checkMode?: CheckMode): Type { - if (isJSDocTypeAssertion(node)) { + if (hasJSDocNodes(node) && isJSDocTypeAssertion(node)) { const type = getJSDocTypeAssertionType(node); return checkAssertionWorker(type, type, node.expression, checkMode); } @@ -33385,6 +34276,8 @@ namespace ts { 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: @@ -33444,6 +34337,8 @@ namespace ts { return checkAssertion(node as AssertionExpression); case SyntaxKind.NonNullExpression: return checkNonNullAssertion(node as NonNullExpression); + case SyntaxKind.ExpressionWithTypeArguments: + return checkExpressionWithTypeArguments(node as ExpressionWithTypeArguments); case SyntaxKind.MetaProperty: return checkMetaProperty(node as MetaProperty); case SyntaxKind.DeleteExpression: @@ -33679,6 +34574,7 @@ namespace ts { } checkTypeParameters(getEffectiveTypeParameterDeclarations(node)); + checkUnmatchedJSDocParameters(node); forEach(node.parameters, checkParameter); @@ -33917,9 +34813,7 @@ namespace ts { checkVariableLikeDeclaration(node); setNodeLinksForPrivateIdentifierScope(node); - if (isPrivateIdentifier(node.name) && hasStaticModifier(node) && node.initializer && languageVersion === ScriptTarget.ESNext && !compilerOptions.useDefineForClassFields) { - error(node.initializer, Diagnostics.Static_fields_with_private_names_can_t_have_initializers_when_the_useDefineForClassFields_flag_is_not_specified_with_a_target_of_esnext_Consider_adding_the_useDefineForClassFields_flag); - } + // 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)); @@ -34025,33 +34919,41 @@ namespace ts { error(superCall, Diagnostics.A_constructor_cannot_contain_a_super_call_when_its_class_extends_null); } - // The first statement in the body of a constructor (excluding prologue directives) must be a super call - // if both of the following are true: + // 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 superCallShouldBeFirst = - (compilerOptions.target !== ScriptTarget.ESNext || !useDefineForClassFields) && + + const superCallShouldBeRootLevel = + (getEmitScriptTarget(compilerOptions) !== ScriptTarget.ESNext || !useDefineForClassFields) && (some((node.parent as ClassDeclaration).members, isInstancePropertyWithInitializerOrPrivateIdentifierProperty) || some(node.parameters, p => hasSyntacticModifier(p, ModifierFlags.ParameterPropertyModifier))); - // Skip past any prologue directives to find the first statement - // to ensure that it was a super call. - if (superCallShouldBeFirst) { - const statements = node.body!.statements; - let superCallStatement: ExpressionStatement | undefined; - - for (const statement of statements) { - if (statement.kind === SyntaxKind.ExpressionStatement && isSuperCall((statement as ExpressionStatement).expression)) { - superCallStatement = statement as ExpressionStatement; - break; - } - if (!isPrologueDirective(statement)) { - break; - } + if (superCallShouldBeRootLevel) { + // Until we have better flow analysis, it is an error to place the super call within any kind of block or conditional + // See GH #8277 + if (!superCallIsRootLevelInConstructor(superCall, node.body!)) { + error(superCall, Diagnostics.A_super_call_must_be_a_root_level_statement_within_a_constructor_of_a_derived_class_that_contains_initialized_properties_parameter_properties_or_private_identifiers); } - if (!superCallStatement) { - error(node, Diagnostics.A_super_call_must_be_the_first_statement_in_the_constructor_when_a_class_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 (!isPrologueDirective(statement) && 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); + } } } } @@ -34061,6 +34963,23 @@ namespace ts { } } + 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 (produceDiagnostics) { // Grammar checking accessors @@ -34150,7 +35069,7 @@ namespace ts { function getTypeParametersForTypeReference(node: TypeReferenceNode | ExpressionWithTypeArguments) { const type = getTypeFromTypeReference(node); - if (type !== errorType) { + if (!isErrorType(type)) { const symbol = getNodeLinks(node).resolvedSymbol; if (symbol) { return symbol.flags & SymbolFlags.TypeAlias && getSymbolLinks(symbol).typeParameters || @@ -34167,7 +35086,7 @@ namespace ts { } forEach(node.typeArguments, checkSourceElement); const type = getTypeFromTypeReference(node); - if (type !== errorType) { + if (!isErrorType(type)) { if (node.typeArguments && produceDiagnostics) { const typeParameters = getTypeParametersForTypeReference(node); if (typeParameters) { @@ -34193,7 +35112,8 @@ namespace ts { function getTypeArgumentConstraint(node: TypeNode): Type | undefined { const typeReferenceNode = tryCast(node.parent, isTypeReferenceType); if (!typeReferenceNode) return undefined; - const typeParameters = getTypeParametersForTypeReference(typeReferenceNode)!; // TODO: GH#18217 + const typeParameters = getTypeParametersForTypeReference(typeReferenceNode); + if (!typeParameters) return undefined; const constraint = getConstraintOfTypeParameter(typeParameters[typeReferenceNode.typeArguments!.indexOf(node)]); return constraint && instantiateType(constraint, createTypeMapper(typeParameters, getEffectiveTypeArguments(typeReferenceNode, typeParameters))); } @@ -34206,7 +35126,7 @@ namespace ts { forEach(node.members, checkSourceElement); if (produceDiagnostics) { const type = getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node); - checkIndexConstraints(type); + checkIndexConstraints(type, type.symbol); checkTypeForDuplicateIndexSignatures(node); checkObjectTypeForDuplicateDeclarations(node); } @@ -34306,6 +35226,7 @@ namespace ts { } function checkMappedType(node: MappedTypeNode) { + checkGrammarMappedType(node); checkSourceElement(node.typeParameter); checkSourceElement(node.nameType); checkSourceElement(node.type); @@ -34325,6 +35246,12 @@ namespace ts { } } + 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); } @@ -34807,6 +35734,11 @@ namespace ts { return typeAsPromise.promisedTypeOfPromise = getTypeArguments(type as GenericType)[0]; } + // primitives with a `{ then() }` won't be unwrapped/adopted. + if (allTypesAssignableToKind(type, TypeFlags.Primitive | TypeFlags.Never)) { + return undefined; + } + const thenFunction = getTypeOfPropertyOfType(type, "then" as __String)!; // TODO: GH#18217 if (isTypeAny(thenFunction)) { return undefined; @@ -34839,23 +35771,92 @@ namespace ts { /** * 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, errorNode: Node, diagnosticMessage: DiagnosticMessage, arg0?: string | number): Type { - const awaitedType = getAwaitedType(type, errorNode, diagnosticMessage, arg0); + function checkAwaitedType(type: Type, withAlias: boolean, errorNode: Node, diagnosticMessage: DiagnosticMessage, arg0?: string | number): Type { + const awaitedType = withAlias ? + getAwaitedType(type, errorNode, diagnosticMessage, arg0) : + getAwaitedTypeNoAlias(type, errorNode, diagnosticMessage, arg0); return awaitedType || errorType; } /** - * Determines whether a type has a callable `then` member. + * Determines whether a type is an object with a callable `then` member. */ function isThenableType(type: Type): boolean { + if (allTypesAssignableToKind(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 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 (isTypeAny(type)) { + return type; + } + + // If this is already an `Awaited`, just return it. This helps to avoid `Awaited>` in higher-order. + if (isAwaitedTypeInstantiation(type)) { + return type; + } + + // Only instantiate `Awaited` if `T` contains possibly non-primitive types. + if (isGenericObjectType(type)) { + const baseConstraint = getBaseConstraintOfType(type); + // Only instantiate `Awaited` if `T` 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) || isThenableType(baseConstraint)) { + // 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)]); + } + } + } + + Debug.assert(getPromisedTypeOfPromise(type) === undefined, "type provided should not be a non-generic 'promise'-like."); + return type; + } + /** * Gets the "awaited type" of a type. * @@ -34867,25 +35868,35 @@ namespace ts { * This is used to reflect the runtime behavior of the `await` keyword. */ function getAwaitedType(type: Type, errorNode?: Node, diagnosticMessage?: DiagnosticMessage, arg0?: string | number): Type | undefined { + const awaitedType = getAwaitedTypeNoAlias(type, errorNode, diagnosticMessage, arg0); + return awaitedType && createAwaitedTypeIfNeeded(awaitedType); + } + + /** + * Gets the "awaited type" of a type without introducing an `Awaited` wrapper. + * + * @see {@link getAwaitedType} + */ + function getAwaitedTypeNoAlias(type: Type, errorNode?: Node, diagnosticMessage?: DiagnosticMessage, arg0?: string | number): Type | undefined { if (isTypeAny(type)) { return type; } + // If 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. - // - return typeAsAwaitable.awaitedTypeOfType = - mapType(type, errorNode ? constituentType => getAwaitedTypeWorker(constituentType, errorNode, diagnosticMessage, arg0) : getAwaitedTypeWorker); - } - - function getAwaitedTypeWorker(type: Type, errorNode?: Node, diagnosticMessage?: DiagnosticMessage, arg0?: string | number): Type | undefined { - const typeAsAwaitable = type as PromiseOrAwaitableType; - if (typeAsAwaitable.awaitedTypeOfType) { - return typeAsAwaitable.awaitedTypeOfType; + if (type.flags & TypeFlags.Union) { + const mapper = errorNode ? (constituentType: Type) => getAwaitedTypeNoAlias(constituentType, errorNode, diagnosticMessage, arg0) : getAwaitedTypeNoAlias; + return typeAsAwaitable.awaitedTypeOfType = mapType(type, mapper); } const promisedType = getPromisedTypeOfPromise(type); @@ -34933,7 +35944,7 @@ namespace ts { // 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 = getAwaitedType(promisedType, errorNode, diagnosticMessage, arg0); + const awaitedType = getAwaitedTypeNoAlias(promisedType, errorNode, diagnosticMessage, arg0); awaitedTypeStack.pop(); if (!awaitedType) { @@ -34960,7 +35971,7 @@ namespace ts { // be treated as a promise, they can cast to . if (isThenableType(type)) { if (errorNode) { - if (!diagnosticMessage) return Debug.fail(); + Debug.assertIsDefined(diagnosticMessage); error(errorNode, diagnosticMessage, arg0); } return undefined; @@ -35009,14 +36020,14 @@ namespace ts { const returnType = getTypeFromTypeNode(returnTypeNode); if (languageVersion >= ScriptTarget.ES2015) { - if (returnType === errorType) { + if (isErrorType(returnType)) { return; } const globalPromiseType = getGlobalPromiseType(/*reportErrors*/ true); if (globalPromiseType !== emptyGenericType && !isReferenceToType(returnType, globalPromiseType)) { // The promise type was not a valid type reference to the global promise type, so we // report an error and return the unknown type. - error(returnTypeNode, Diagnostics.The_return_type_of_an_async_function_or_method_must_be_the_global_Promise_T_type_Did_you_mean_to_write_Promise_0, typeToString(getAwaitedType(returnType) || voidType)); + error(returnTypeNode, Diagnostics.The_return_type_of_an_async_function_or_method_must_be_the_global_Promise_T_type_Did_you_mean_to_write_Promise_0, typeToString(getAwaitedTypeNoAlias(returnType) || voidType)); return; } } @@ -35024,7 +36035,7 @@ namespace ts { // Always mark the type node as referenced if it points to a value markTypeNodeAsReferenced(returnTypeNode); - if (returnType === errorType) { + if (isErrorType(returnType)) { return; } @@ -35036,7 +36047,7 @@ namespace ts { const promiseConstructorSymbol = resolveEntityName(promiseConstructorName, SymbolFlags.Value, /*ignoreErrors*/ true); const promiseConstructorType = promiseConstructorSymbol ? getTypeOfSymbol(promiseConstructorSymbol) : errorType; - if (promiseConstructorType === errorType) { + if (isErrorType(promiseConstructorType)) { if (promiseConstructorName.kind === SyntaxKind.Identifier && promiseConstructorName.escapedText === "Promise" && getTargetType(returnType) === getGlobalPromiseType(/*reportErrors*/ false)) { error(returnTypeNode, Diagnostics.An_async_function_or_method_in_ES5_SlashES3_requires_the_Promise_constructor_Make_sure_you_have_a_declaration_for_the_Promise_constructor_or_include_ES2015_in_your_lib_option); } @@ -35069,7 +36080,7 @@ namespace ts { return; } } - checkAwaitedType(returnType, node, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member); + checkAwaitedType(returnType, /*withAlias*/ false, node, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member); } /** Check a decorator */ @@ -35081,34 +36092,26 @@ namespace ts { return; } + let headMessage: DiagnosticMessage; let expectedReturnType: Type; - const headMessage = getDiagnosticHeadMessageForDecoratorResolution(node); - let errorInfo: DiagnosticMessageChain | undefined; switch (node.parent.kind) { case SyntaxKind.ClassDeclaration: + headMessage = Diagnostics.Decorator_function_return_type_0_is_not_assignable_to_type_1; const classSymbol = getSymbolOfNode(node.parent); const classConstructorType = getTypeOfSymbol(classSymbol); expectedReturnType = getUnionType([classConstructorType, voidType]); break; - case SyntaxKind.Parameter: - expectedReturnType = voidType; - errorInfo = chainDiagnosticMessages( - /*details*/ undefined, - Diagnostics.The_return_type_of_a_parameter_decorator_function_must_be_either_void_or_any); - - break; - case SyntaxKind.PropertyDeclaration: + case SyntaxKind.Parameter: + headMessage = Diagnostics.Decorator_function_return_type_is_0_but_is_expected_to_be_void_or_any; expectedReturnType = voidType; - errorInfo = chainDiagnosticMessages( - /*details*/ undefined, - Diagnostics.The_return_type_of_a_property_decorator_function_must_be_either_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; const methodType = getTypeOfNode(node.parent); const descriptorType = createTypedPropertyDescriptorType(methodType); expectedReturnType = getUnionType([descriptorType, voidType]); @@ -35122,8 +36125,7 @@ namespace ts { returnType, expectedReturnType, node, - headMessage, - () => errorInfo); + headMessage); } /** @@ -35327,40 +36329,7 @@ namespace ts { function checkJSDocParameterTag(node: JSDocParameterTag) { checkSourceElement(node.typeExpression); - if (!getParameterSymbolFromJSDoc(node)) { - const decl = getHostSignatureFromJSDoc(node); - // don't issue an error for invalid hosts -- just functions -- - // and give a better error message when the host function mentions `arguments` - // but the tag doesn't have an array type - if (decl) { - const i = getJSDocTags(decl).filter(isJSDocParameterTag).indexOf(node); - if (i > -1 && i < decl.parameters.length && isBindingPattern(decl.parameters[i].name)) { - return; - } - if (!containsArgumentsReference(decl)) { - if (isQualifiedName(node.name)) { - error(node.name, - Diagnostics.Qualified_name_0_is_not_allowed_without_a_leading_param_object_1, - entityNameToString(node.name), - entityNameToString(node.name.left)); - } - else { - error(node.name, - Diagnostics.JSDoc_param_tag_has_name_0_but_there_is_no_parameter_with_that_name, - idText(node.name)); - } - } - else if (findLast(getJSDocTags(decl), isJSDocParameterTag) === node && - node.typeExpression && node.typeExpression.type && - !isArrayType(getTypeFromTypeNode(node.typeExpression.type))) { - error(node.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(node.name.kind === SyntaxKind.QualifiedName ? node.name.right : node.name)); - } - } - } } - function checkJSDocPropertyTag(node: JSDocPropertyTag) { checkSourceElement(node.typeExpression); } @@ -35402,6 +36371,13 @@ namespace ts { } } + 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 { @@ -35898,7 +36874,7 @@ namespace ts { function checkCollisionWithRequireExportsInGeneratedCode(node: Node, name: Identifier | undefined) { // No need to check for require or exports for ES6 modules and later - if (moduleKind >= ModuleKind.ES2015) { + if (moduleKind >= ModuleKind.ES2015 && !(moduleKind >= ModuleKind.Node12 && getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS)) { return; } @@ -36121,7 +37097,8 @@ namespace ts { // check private/protected variable access const parent = node.parent.parent; - const parentType = getTypeForBindingElementParent(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); @@ -36157,7 +37134,7 @@ namespace ts { // Don't validate for-in initializer as it is already an error const widenedType = getWidenedTypeForVariableLikeDeclaration(node); if (needCheckInitializer) { - const initializerType = checkExpressionCached(node.initializer!); + const initializerType = checkExpressionCached(node.initializer); if (strictNullChecks && needCheckWidenedType) { checkNonNullNonVoidType(initializerType, node); } @@ -36179,7 +37156,7 @@ namespace ts { } // For a commonjs `const x = require`, validate the alias and exit const symbol = getSymbolOfNode(node); - if (symbol.flags & SymbolFlags.Alias && isRequireVariableDeclaration(node)) { + if (symbol.flags & SymbolFlags.Alias && isVariableDeclarationInitializedToBareOrAccessedRequire(node)) { checkAliasSymbol(node); return; } @@ -36209,7 +37186,7 @@ namespace ts { // initializer is consistent with type associated with the node const declarationType = convertAutoToAny(getWidenedTypeForVariableLikeDeclaration(node)); - if (type !== errorType && declarationType !== errorType && + if (!isErrorType(type) && !isErrorType(declarationType) && !isTypeIdenticalTo(type, declarationType) && !(symbol.flags & SymbolFlags.Assignment)) { errorNextVariableOrPropertyDeclarationMustHaveSameType(symbol.valueDeclaration, type, node, declarationType); @@ -36273,7 +37250,7 @@ namespace ts { } function checkVariableDeclaration(node: VariableDeclaration) { - tracing?.push(tracing.Phase.Check, "checkVariableDeclaration", { kind: node.kind, pos: node.pos, end: node.end }); + 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(); @@ -36888,6 +37865,10 @@ namespace ts { 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, @@ -36914,7 +37895,9 @@ namespace ts { getIterationTypesOfIterableCached(type, asyncIterationTypesResolver) || getIterationTypesOfIterableFast(type, asyncIterationTypesResolver); if (iterationTypes) { - return iterationTypes; + return use & IterationUse.ForOfFlag ? + getAsyncFromSyncIterationTypes(iterationTypes, errorNode) : + iterationTypes; } } @@ -37003,7 +37986,7 @@ namespace ts { // 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(yieldType, returnType, nextType)); + 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 @@ -37011,7 +37994,7 @@ namespace ts { // - `Generator` or `AsyncGenerator` if (isReferenceToType(type, resolver.getGlobalGeneratorType(/*reportErrors*/ false))) { const [yieldType, returnType, nextType] = getTypeArguments(type as GenericType); - return setCachedIterationTypes(type, resolver.iterableCacheKey, createIterationTypes(yieldType, returnType, nextType)); + return setCachedIterationTypes(type, resolver.iterableCacheKey, createIterationTypes(resolver.resolveIterationType(yieldType, /*errorNode*/ undefined) || yieldType, resolver.resolveIterationType(returnType, /*errorNode*/ undefined) || returnType, nextType)); } } @@ -37343,8 +38326,8 @@ namespace ts { function unwrapReturnType(returnType: Type, functionFlags: FunctionFlags) { const isGenerator = !!(functionFlags & FunctionFlags.Generator); const isAsync = !!(functionFlags & FunctionFlags.Async); - return isGenerator ? getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Return, returnType, isAsync) ?? errorType : - isAsync ? getAwaitedType(returnType) ?? errorType : + return isGenerator ? getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Return, returnType, isAsync) || errorType : + isAsync ? getAwaitedTypeNoAlias(returnType) || errorType : returnType; } @@ -37388,7 +38371,7 @@ namespace ts { else if (getReturnTypeFromAnnotation(container)) { const unwrappedReturnType = unwrapReturnType(returnType, functionFlags) ?? returnType; const unwrappedExprType = functionFlags & FunctionFlags.Async - ? checkAwaitedType(exprType, node, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member) + ? 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 @@ -37513,7 +38496,7 @@ namespace ts { const declaration = catchClause.variableDeclaration; const typeNode = getEffectiveTypeAnnotationNode(getRootDeclaration(declaration)); if (typeNode) { - const type = getTypeForVariableLikeDeclaration(declaration, /*includeOptionality*/ false); + const type = getTypeForVariableLikeDeclaration(declaration, /*includeOptionality*/ false, CheckMode.Normal); if (type && !(type.flags & TypeFlags.AnyOrUnknown)) { grammarErrorOnFirstToken(typeNode, Diagnostics.Catch_clause_variable_type_annotation_must_be_any_or_unknown_if_specified); } @@ -37542,7 +38525,7 @@ namespace ts { } } - function checkIndexConstraints(type: Type, isStaticIndex?: boolean) { + function checkIndexConstraints(type: Type, symbol: Symbol, isStaticIndex?: boolean) { const indexInfos = getIndexInfosOfType(type); if (indexInfos.length === 0) { return; @@ -37552,7 +38535,7 @@ namespace ts { checkIndexConstraintForProperty(type, prop, getLiteralTypeFromProperty(prop, TypeFlags.StringOrNumberLiteralOrUnique, /*includeNonPublic*/ true), getNonMissingTypeOfSymbol(prop)); } } - const typeDeclaration = type.symbol.valueDeclaration; + 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, @@ -37636,12 +38619,51 @@ namespace ts { * 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) { + 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 lastJSDocParam = lastOrUndefined(jsdocParameters); + if (lastJSDocParam && isIdentifier(lastJSDocParam.name) && lastJSDocParam.typeExpression && + lastJSDocParam.typeExpression.type && !parameters.has(lastJSDocParam.name.escapedText) && !isArrayType(getTypeFromTypeNode(lastJSDocParam.typeExpression.type))) { + errorOrSuggestion(isJs, 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 }, index) => { + if (excludedParameters.has(index) || isIdentifier(name) && parameters.has(name.escapedText)) { + return; + } + if (isQualifiedName(name)) { + errorOrSuggestion(isJs, name, Diagnostics.Qualified_name_0_is_not_allowed_without_a_leading_param_object_1, entityNameToString(name), entityNameToString(name.left)); + } + else { + 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 */ @@ -37863,7 +38885,7 @@ namespace ts { } } - checkMembersForMissingOverrideModifier(node, type, typeWithThis, staticType); + checkMembersForOverrideModifier(node, type, typeWithThis, staticType); const implementedTypeNodes = getEffectiveImplementsTypeNodes(node); if (implementedTypeNodes) { @@ -37874,7 +38896,7 @@ namespace ts { checkTypeReferenceNode(typeRefNode); if (produceDiagnostics) { const t = getReducedType(getTypeFromTypeNode(typeRefNode)); - if (t !== errorType) { + 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 : @@ -37893,15 +38915,14 @@ namespace ts { } if (produceDiagnostics) { - checkIndexConstraints(type); - checkIndexConstraints(staticType, /*isStaticIndex*/ true); + checkIndexConstraints(type, symbol); + checkIndexConstraints(staticType, symbol, /*isStaticIndex*/ true); checkTypeForDuplicateIndexSignatures(node); checkPropertyInitialization(node); } } - function checkMembersForMissingOverrideModifier(node: ClassLikeDeclaration, type: InterfaceType, typeWithThis: Type, staticType: ObjectType) { - const nodeInAmbientContext = !!(node.flags & NodeFlags.Ambient); + 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; @@ -37915,56 +38936,163 @@ namespace ts { if (isConstructorDeclaration(member)) { forEach(member.parameters, param => { if (isParameterPropertyDeclaration(param, member)) { - checkClassMember(param, /*memberIsParameterProperty*/ true); + checkExistingMemberForOverrideModifier( + node, + staticType, + baseStaticType, + baseWithThis, + type, + typeWithThis, + param, + /* memberIsParameterProperty */ true + ); } }); } - checkClassMember(member); + 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; } - function checkClassMember(member: ClassElement | ParameterPropertyDeclaration, memberIsParameterProperty?: boolean) { - const hasOverride = hasOverrideModifier(member); - const hasStatic = isStatic(member); - if (baseWithThis && (hasOverride || compilerOptions.noImplicitOverride)) { - const declaredProp = member.name && getSymbolAtLocation(member.name) || getSymbolAtLocation(member); - if (!declaredProp) { - return; - } + return checkMemberForOverrideModifier( + node, + staticType, + baseStaticType, + baseWithThis, + type, + typeWithThis, + hasOverrideModifier(member), + hasAbstractModifier(member), + isStatic(member), + memberIsParameterProperty, + symbolName(declaredProp), + reportErrors ? member : undefined, + ); + } - const thisType = hasStatic ? staticType : typeWithThis; - const baseType = hasStatic ? baseStaticType : baseWithThis; - const prop = getPropertyOfType(thisType, declaredProp.escapedName); - const baseProp = getPropertyOfType(baseType, declaredProp.escapedName); + /** + * Checks a class member declaration for either a missing or an invalid `override` modifier. + * Note: this function can be used for speculative checking, + * i.e. checking a member that does not yet exist in the program. + * An example of that would be to call this function in a completions scenario, + * when offering a method declaration as completion. + * @param errorNode The node where we should report an error, or undefined if we should not report errors. + */ + function checkMemberForOverrideModifier( + node: ClassLikeDeclaration, + staticType: ObjectType, + baseStaticType: Type, + baseWithThis: Type | undefined, + type: InterfaceType, + typeWithThis: Type, + memberHasOverrideModifier: boolean, + memberHasAbstractModifier: boolean, + memberIsStatic: boolean, + memberIsParameterProperty: boolean, + memberName: string, + errorNode?: Node, + ): MemberOverrideStatus { + const isJs = isInJSFile(node); + const nodeInAmbientContext = !!(node.flags & NodeFlags.Ambient); + if (baseWithThis && (memberHasOverrideModifier || compilerOptions.noImplicitOverride)) { + const memberEscapedName = escapeLeadingUnderscores(memberName); + const thisType = memberIsStatic ? staticType : typeWithThis; + const baseType = memberIsStatic ? baseStaticType : baseWithThis; + const prop = getPropertyOfType(thisType, memberEscapedName); + const baseProp = getPropertyOfType(baseType, memberEscapedName); - const baseClassName = typeToString(baseWithThis); - if (prop && !baseProp && hasOverride) { - const suggestion = getSuggestedSymbolForNonexistentClassMember(symbolName(declaredProp), baseType); + const baseClassName = typeToString(baseWithThis); + if (prop && !baseProp && memberHasOverrideModifier) { + if (errorNode) { + const suggestion = getSuggestedSymbolForNonexistentClassMember(memberName, baseType); // Again, using symbol name: note that's different from `symbol.escapedName` suggestion ? - error(member, 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(member, Diagnostics.This_member_cannot_have_an_override_modifier_because_it_is_not_declared_in_the_base_class_0, baseClassName); - } - else if (prop && baseProp?.declarations && compilerOptions.noImplicitOverride && !nodeInAmbientContext) { - const baseHasAbstract = some(baseProp.declarations, hasAbstractModifier); - if (hasOverride) { - return; - } - - if (!baseHasAbstract) { - const diag = memberIsParameterProperty ? - Diagnostics.This_parameter_property_must_have_an_override_modifier_because_it_overrides_a_member_in_base_class_0 : - Diagnostics.This_member_must_have_an_override_modifier_because_it_overrides_a_member_in_the_base_class_0; - error(member, diag, baseClassName); - } - else if (hasAbstractModifier(member) && baseHasAbstract) { - error(member, Diagnostics.This_member_must_have_an_override_modifier_because_it_overrides_an_abstract_method_that_is_declared_in_the_base_class_0, baseClassName); - } + 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 (hasOverride) { - const className = typeToString(type); - error(member, Diagnostics.This_member_cannot_have_an_override_modifier_because_its_containing_class_0_does_not_extend_another_class, className); + 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) { @@ -38011,6 +39139,48 @@ namespace ts { } } + /** + * Checks a member declaration node to see if has a missing or invalid `override` modifier. + * @param node Class-like node where the member is declared. + * @param member Member declaration node. + * Note: `member` can be a synthetic node without a parent. + */ + function getMemberOverrideModifierStatus(node: ClassLikeDeclaration, member: ClassElement): MemberOverrideStatus { + if (!member.name) { + return MemberOverrideStatus.Ok; + } + + const symbol = getSymbolOfNode(node); + const type = getDeclaredTypeOfSymbol(symbol) as InterfaceType; + const typeWithThis = getTypeWithThisArgument(type); + const staticType = getTypeOfSymbol(symbol) as ObjectType; + + const baseTypeNode = getEffectiveBaseTypeNode(node); + const baseTypes = baseTypeNode && getBaseTypes(type); + const baseWithThis = baseTypes?.length ? getTypeWithThisArgument(first(baseTypes), type.thisType) : undefined; + const baseStaticType = getBaseConstructorTypeOfClass(type); + + const memberHasOverrideModifier = member.parent + ? hasOverrideModifier(member) + : hasSyntacticModifier(member, ModifierFlags.Override); + + const memberName = unescapeLeadingUnderscores(getTextOfPropertyName(member.name)); + + return checkMemberForOverrideModifier( + node, + staticType, + baseStaticType, + baseWithThis, + type, + typeWithThis, + memberHasOverrideModifier, + hasAbstractModifier(member), + isStatic(member), + /* memberIsParameterProperty */ false, + memberName, + ); + } + function getTargetSymbol(s: Symbol) { // if symbol is instantiated its flags are not copied from the 'target' // so we'll need to get back original 'target' symbol to work with correct set of flags @@ -38174,7 +39344,7 @@ namespace ts { const properties = getPropertiesOfType(getTypeWithThisArgument(base, type.thisType)); for (const prop of properties) { const existing = seen.get(prop.escapedName); - if (existing && !isPropertyIdenticalTo(existing, prop)) { + if (existing && prop.parent === existing.parent) { seen.delete(prop.escapedName); } } @@ -38300,7 +39470,7 @@ namespace ts { for (const baseType of getBaseTypes(type)) { checkTypeAssignableTo(typeWithThis, getTypeWithThisArgument(baseType, type.thisType), node.name, Diagnostics.Interface_0_incorrectly_extends_interface_1); } - checkIndexConstraints(type); + checkIndexConstraints(type, symbol); } } checkObjectTypeForDuplicateDeclarations(node); @@ -38464,16 +39634,15 @@ namespace ts { return nodeIsMissing(expr) ? 0 : evaluateEnumMember(expr, getSymbolOfNode(member.parent), identifier.escapedText); case SyntaxKind.ElementAccessExpression: case SyntaxKind.PropertyAccessExpression: - const ex = expr as AccessExpression; - if (isConstantMemberAccess(ex)) { - const type = getTypeOfExpression(ex.expression); + if (isConstantMemberAccess(expr)) { + const type = getTypeOfExpression(expr.expression); if (type.symbol && type.symbol.flags & SymbolFlags.Enum) { let name: __String; - if (ex.kind === SyntaxKind.PropertyAccessExpression) { - name = ex.name.escapedText; + if (expr.kind === SyntaxKind.PropertyAccessExpression) { + name = expr.name.escapedText; } else { - name = escapeLeadingUnderscores(cast(ex.argumentExpression, isLiteralExpression).text); + name = escapeLeadingUnderscores(cast(expr.argumentExpression, isLiteralExpression).text); } return evaluateEnumMember(expr, type.symbol, name); } @@ -38488,7 +39657,7 @@ namespace ts { if (memberSymbol) { const declaration = memberSymbol.valueDeclaration; if (declaration !== member) { - if (declaration && isBlockScopedNameDeclaredBeforeUse(declaration, member)) { + if (declaration && isBlockScopedNameDeclaredBeforeUse(declaration, member) && isEnumDeclaration(declaration.parent)) { return getEnumMemberValue(declaration as EnumMember); } error(expr, Diagnostics.A_member_initializer_in_a_enum_declaration_cannot_reference_members_declared_after_it_including_members_defined_in_other_enums); @@ -38502,7 +39671,12 @@ namespace ts { } } - function isConstantMemberAccess(node: Expression): boolean { + function isConstantMemberAccess(node: Expression): node is AccessExpression { + const type = getTypeOfExpression(node); + if(type === errorType) { + return false; + } + return node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.PropertyAccessExpression && isConstantMemberAccess((node as PropertyAccessExpression).expression) || node.kind === SyntaxKind.ElementAccessExpression && isConstantMemberAccess((node as ElementAccessExpression).expression) && @@ -38613,7 +39787,7 @@ namespace ts { 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_in_a_namespace_or_module; + : 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; @@ -38801,6 +39975,16 @@ namespace ts { return false; } } + if (!isImportEqualsDeclaration(node) && node.assertClause) { + let hasError = false; + for (const clause of node.assertClause.elements) { + if (!isStringLiteral(clause.value)) { + hasError = true; + error(clause.value, Diagnostics.Import_assertion_values_must_be_string_literal_expressions); + } + } + return !hasError; + } return true; } @@ -38849,6 +40033,9 @@ namespace ts { name ); } + if (isType && node.kind === SyntaxKind.ImportEqualsDeclaration && hasEffectiveModifier(node, ModifierFlags.Export)) { + error(node, Diagnostics.Cannot_use_export_import_on_a_type_or_type_only_namespace_when_the_isolatedModules_flag_is_provided); + } break; } case SyntaxKind.ExportSpecifier: { @@ -38872,25 +40059,76 @@ namespace ts { } } - if (isImportSpecifier(node) && target.declarations?.every(d => !!(getCombinedNodeFlags(d) & NodeFlags.Deprecated))) { - addDeprecatedSuggestion(node.name, target.declarations, symbol.escapedName as string); + if (isImportSpecifier(node)) { + const targetSymbol = checkDeprecatedAliasedSymbol(symbol, node); + if (isDeprecatedAliasedSymbol(targetSymbol) && targetSymbol.declarations) { + addDeprecatedSuggestion(node, targetSymbol.declarations, targetSymbol.escapedName as string); + } } } } + function isDeprecatedAliasedSymbol(symbol: Symbol) { + return !!symbol.declarations && every(symbol.declarations, d => !!(getCombinedNodeFlags(d) & NodeFlags.Deprecated)); + } + + function checkDeprecatedAliasedSymbol(symbol: Symbol, location: Node) { + if (!(symbol.flags & SymbolFlags.Alias)) return symbol; + + const targetSymbol = resolveAlias(symbol); + if (targetSymbol === unknownSymbol) return targetSymbol; + + while (symbol.flags & SymbolFlags.Alias) { + const target = getImmediateAliasedSymbol(symbol); + if (target) { + if (target === targetSymbol) break; + if (target.declarations && length(target.declarations)) { + if (isDeprecatedAliasedSymbol(target)) { + addDeprecatedSuggestion(location, target.declarations, target.escapedName as string); + break; + } + else { + if (symbol === targetSymbol) break; + symbol = target; + } + } + } + else { + break; + } + } + return targetSymbol; + } + function checkImportBinding(node: ImportEqualsDeclaration | ImportClause | NamespaceImport | ImportSpecifier) { checkCollisionsForDeclarationName(node, node.name); checkAliasSymbol(node); if (node.kind === SyntaxKind.ImportSpecifier && idText(node.propertyName || node.name) === "default" && - compilerOptions.esModuleInterop && - moduleKind !== ModuleKind.System && moduleKind < ModuleKind.ES2015) { + getESModuleInterop(compilerOptions) && + moduleKind !== ModuleKind.System && (moduleKind < ModuleKind.ES2015 || getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS)) { checkExternalEmitHelpers(node, ExternalEmitHelpers.ImportDefault); } } + function checkAssertClause(declaration: ImportDeclaration | ExportDeclaration) { + if (declaration.assertClause) { + const mode = (moduleKind === ModuleKind.NodeNext) && declaration.moduleSpecifier && getUsageModeForExpression(declaration.moduleSpecifier); + if (mode !== ModuleKind.ESNext && moduleKind !== ModuleKind.ESNext) { + return grammarErrorOnNode(declaration.assertClause, + moduleKind === ModuleKind.NodeNext + ? Diagnostics.Import_assertions_are_not_allowed_on_statements_that_transpile_to_commonjs_require_calls + : Diagnostics.Import_assertions_are_only_supported_when_the_module_option_is_set_to_esnext_or_nodenext); + } + + if (isImportDeclaration(declaration) ? declaration.importClause?.isTypeOnly : declaration.isTypeOnly) { + return grammarErrorOnNode(declaration.assertClause, Diagnostics.Import_assertions_cannot_be_used_with_type_only_imports_or_exports); + } + } + } + function checkImportDeclaration(node: ImportDeclaration) { - if (checkGrammarModuleElementContext(node, Diagnostics.An_import_declaration_can_only_be_used_in_a_namespace_or_module)) { + 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; } @@ -38906,7 +40144,7 @@ namespace ts { if (importClause.namedBindings) { if (importClause.namedBindings.kind === SyntaxKind.NamespaceImport) { checkImportBinding(importClause.namedBindings); - if (moduleKind !== ModuleKind.System && moduleKind < ModuleKind.ES2015 && compilerOptions.esModuleInterop) { + if (moduleKind !== ModuleKind.System && (moduleKind < ModuleKind.ES2015 || getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS) && getESModuleInterop(compilerOptions)) { // import * as ns from "foo"; checkExternalEmitHelpers(node, ExternalEmitHelpers.ImportStar); } @@ -38920,11 +40158,11 @@ namespace ts { } } } - + checkAssertClause(node); } function checkImportEqualsDeclaration(node: ImportEqualsDeclaration) { - if (checkGrammarModuleElementContext(node, Diagnostics.An_import_declaration_can_only_be_used_in_a_namespace_or_module)) { + 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; } @@ -38954,7 +40192,7 @@ namespace ts { } } else { - if (moduleKind >= ModuleKind.ES2015 && !node.isTypeOnly && !(node.flags & NodeFlags.Ambient)) { + if (moduleKind >= ModuleKind.ES2015 && getSourceFileOfNode(node).impliedNodeFormat === undefined && !node.isTypeOnly && !(node.flags & NodeFlags.Ambient)) { // Import equals declaration is deprecated in es6 or above grammarErrorOnNode(node, Diagnostics.Import_assignment_cannot_be_used_when_targeting_ECMAScript_modules_Consider_using_import_Asterisk_as_ns_from_mod_import_a_from_mod_import_d_from_mod_or_another_module_format_instead); } @@ -38963,7 +40201,7 @@ namespace ts { } function checkExportDeclaration(node: ExportDeclaration) { - if (checkGrammarModuleElementContext(node, Diagnostics.An_export_declaration_can_only_be_used_in_a_module)) { + 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; } @@ -38999,12 +40237,12 @@ namespace ts { else if (node.exportClause) { checkAliasSymbol(node.exportClause); } - if (moduleKind !== ModuleKind.System && moduleKind < ModuleKind.ES2015) { + 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 (compilerOptions.esModuleInterop) { + if (getESModuleInterop(compilerOptions)) { checkExternalEmitHelpers(node, ExternalEmitHelpers.ImportStar); } } @@ -39015,14 +40253,19 @@ namespace ts { } } } + checkAssertClause(node); } function checkGrammarExportDeclaration(node: ExportDeclaration): boolean { - const isTypeOnlyExportStar = node.isTypeOnly && node.exportClause?.kind !== SyntaxKind.NamedExports; - if (isTypeOnlyExportStar) { - grammarErrorOnNode(node, Diagnostics.Only_named_exports_may_use_export_type); + if (node.isTypeOnly) { + if (node.exportClause?.kind === SyntaxKind.NamedExports) { + return checkGrammarNamedImportsOrExports(node.exportClause); + } + else { + return grammarErrorOnNode(node, Diagnostics.Only_named_exports_may_use_export_type); + } } - return !isTypeOnlyExportStar; + return false; } function checkGrammarModuleElementContext(node: Statement, errorMessage: DiagnosticMessage): boolean { @@ -39095,9 +40338,9 @@ namespace ts { } } else { - if (compilerOptions.esModuleInterop && + if (getESModuleInterop(compilerOptions) && moduleKind !== ModuleKind.System && - moduleKind < ModuleKind.ES2015 && + (moduleKind < ModuleKind.ES2015 || getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS) && idText(node.propertyName || node.name) === "default") { checkExternalEmitHelpers(node, ExternalEmitHelpers.ImportDefault); } @@ -39165,7 +40408,7 @@ namespace ts { } if (node.isExportEquals && !(node.flags & NodeFlags.Ambient)) { - if (moduleKind >= ModuleKind.ES2015) { + if (moduleKind >= ModuleKind.ES2015 && getSourceFileOfNode(node).impliedNodeFormat !== ModuleKind.CommonJS) { // export assignment is not supported in es6 modules grammarErrorOnNode(node, Diagnostics.Export_assignment_cannot_be_used_when_targeting_ECMAScript_modules_Consider_using_export_default_or_another_module_format_instead); } @@ -39351,6 +40594,10 @@ namespace ts { return; case SyntaxKind.JSDocTypeExpression: return checkSourceElement((node as JSDocTypeExpression).type); + case SyntaxKind.JSDocPublicTag: + case SyntaxKind.JSDocProtectedTag: + case SyntaxKind.JSDocPrivateTag: + return checkJSDocAccessibilityModifiers(node as JSDocPublicTag | JSDocProtectedTag | JSDocPrivateTag); case SyntaxKind.IndexedAccessType: return checkIndexedAccessType(node as IndexedAccessTypeNode); case SyntaxKind.MappedType: @@ -39508,9 +40755,8 @@ namespace ts { const enclosingFile = getSourceFileOfNode(node); const links = getNodeLinks(enclosingFile); if (!(links.flags & NodeCheckFlags.TypeChecked)) { - links.deferredNodes = links.deferredNodes || new Map(); - const id = getNodeId(node); - links.deferredNodes.set(id, node); + links.deferredNodes ||= new Set(); + links.deferredNodes.add(node); } } @@ -39522,7 +40768,7 @@ namespace ts { } function checkDeferredNode(node: Node) { - tracing?.push(tracing.Phase.Check, "checkDeferredNode", { kind: node.kind, pos: node.pos, end: node.end }); + 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; @@ -40040,6 +41286,9 @@ namespace ts { } 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) { @@ -40063,7 +41312,8 @@ namespace ts { } else if (isTypeReferenceIdentifier(name as EntityName)) { const meaning = name.parent.kind === SyntaxKind.TypeReference ? SymbolFlags.Type : SymbolFlags.Namespace; - return resolveEntityName(name as EntityName, meaning, /*ignoreErrors*/ false, /*dontResolveAlias*/ true); + 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); @@ -40155,7 +41405,10 @@ namespace ts { case SyntaxKind.PrivateIdentifier: case SyntaxKind.PropertyAccessExpression: case SyntaxKind.QualifiedName: - return getSymbolOfNameOrPropertyAccessExpression(node as EntityName | PrivateIdentifier | PropertyAccessExpression); + if (!isThisInTypeQuery(node)) { + return getSymbolOfNameOrPropertyAccessExpression(node as EntityName | PrivateIdentifier | PropertyAccessExpression); + } + // falls through case SyntaxKind.ThisKeyword: const container = getThisContainer(node, /*includeArrowFunctions*/ false); @@ -40316,14 +41569,14 @@ namespace ts { } if (isBindingPattern(node)) { - return getTypeForVariableLikeDeclaration(node.parent, /*includeOptionality*/ true) || errorType; + 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 declaredType !== errorType ? declaredType : getTypeOfSymbol(symbol); + return !isErrorType(declaredType) ? declaredType : getTypeOfSymbol(symbol); } } @@ -40686,7 +41939,7 @@ namespace ts { if (!symbol) { return false; } - const target = resolveAlias(symbol); + const target = getExportSymbolOfValueSymbolIfExported(resolveAlias(symbol)); if (target === unknownSymbol) { return true; } @@ -40867,7 +42120,7 @@ namespace ts { return isTypeOnly ? TypeReferenceSerializationKind.ObjectType : TypeReferenceSerializationKind.Unknown; } const type = getDeclaredTypeOfSymbol(typeSymbol); - if (type === errorType) { + if (isErrorType(type)) { return isTypeOnly ? TypeReferenceSerializationKind.ObjectType : TypeReferenceSerializationKind.Unknown; } else if (type.flags & TypeFlags.AnyOrUnknown) { @@ -41031,11 +42284,11 @@ namespace ts { // this variable and functions that use it are deliberately moved here from the outer scope // to avoid scope pollution const resolvedTypeReferenceDirectives = host.getResolvedTypeReferenceDirectives(); - let fileToDirective: ESMap; + let fileToDirective: ESMap; if (resolvedTypeReferenceDirectives) { // populate reverse mapping: file path -> type reference directive that was resolved to this file - fileToDirective = new Map(); - resolvedTypeReferenceDirectives.forEach((resolvedDirective, key) => { + fileToDirective = new Map(); + resolvedTypeReferenceDirectives.forEach((resolvedDirective, key, mode) => { if (!resolvedDirective || !resolvedDirective.resolvedFileName) { return; } @@ -41043,7 +42296,7 @@ namespace ts { if (file) { // Add the transitive closure of path references loaded by this file (as long as they are not) // part of an existing type reference. - addReferencedFilesToTypeDirective(file, key); + addReferencedFilesToTypeDirective(file, key, mode); } }); } @@ -41166,7 +42419,7 @@ namespace ts { } // defined here to avoid outer scope pollution - function getTypeReferenceDirectivesForEntityName(node: EntityNameOrEntityNameExpression): string[] | undefined { + function getTypeReferenceDirectivesForEntityName(node: EntityNameOrEntityNameExpression): [specifier: string, mode: SourceFile["impliedNodeFormat"] | undefined][] | undefined { // program does not have any files with type reference directives - bail out if (!fileToDirective) { return undefined; @@ -41184,13 +42437,13 @@ namespace ts { } // defined here to avoid outer scope pollution - function getTypeReferenceDirectivesForSymbol(symbol: Symbol, meaning?: SymbolFlags): string[] | undefined { + function getTypeReferenceDirectivesForSymbol(symbol: Symbol, meaning?: SymbolFlags): [specifier: string, mode: SourceFile["impliedNodeFormat"] | undefined][] | undefined { // program does not have any files with type reference directives - bail out if (!fileToDirective || !isSymbolFromTypeDeclarationFile(symbol)) { return undefined; } // check what declarations in the symbol can contribute to the target meaning - let typeReferenceDirectives: string[] | undefined; + let typeReferenceDirectives: [specifier: string, mode: SourceFile["impliedNodeFormat"] | undefined][] | undefined; for (const decl of symbol.declarations!) { // check meaning of the local symbol to see if declaration needs to be analyzed further if (decl.symbol && decl.symbol.flags & meaning!) { @@ -41241,14 +42494,14 @@ namespace ts { return false; } - function addReferencedFilesToTypeDirective(file: SourceFile, key: string) { + function addReferencedFilesToTypeDirective(file: SourceFile, key: string, mode: SourceFile["impliedNodeFormat"] | undefined) { if (fileToDirective.has(file.path)) return; - fileToDirective.set(file.path, key); - for (const { fileName } of file.referencedFiles) { + fileToDirective.set(file.path, [key, mode]); + for (const { fileName, resolutionMode } of file.referencedFiles) { const resolvedFile = resolveTripleslashReference(fileName, file.fileName); const referencedFile = host.getSourceFile(resolvedFile); if (referencedFile) { - addReferencedFilesToTypeDirective(referencedFile, key); + addReferencedFilesToTypeDirective(referencedFile, key, resolutionMode || file.impliedNodeFormat); } } } @@ -41455,6 +42708,7 @@ namespace ts { case ExternalEmitHelpers.MakeTemplateObject: return "__makeTemplateObject"; case ExternalEmitHelpers.ClassPrivateFieldGet: return "__classPrivateFieldGet"; case ExternalEmitHelpers.ClassPrivateFieldSet: return "__classPrivateFieldSet"; + case ExternalEmitHelpers.ClassPrivateFieldIn: return "__classPrivateFieldIn"; case ExternalEmitHelpers.CreateBinding: return "__createBinding"; default: return Debug.fail("Unrecognized helper"); } @@ -41499,7 +42753,7 @@ namespace ts { return quickResult; } - let lastStatic: Node | undefined, lastDeclare: Node | undefined, lastAsync: Node | undefined, lastReadonly: Node | undefined, lastOverride: Node | undefined; + let lastStatic: Node | undefined, lastDeclare: Node | undefined, lastAsync: Node | undefined, lastOverride: Node | undefined; let flags = ModifierFlags.None; for (const modifier of node.modifiers!) { if (modifier.kind !== SyntaxKind.ReadonlyKeyword) { @@ -41606,7 +42860,6 @@ namespace ts { return grammarErrorOnNode(modifier, Diagnostics.readonly_modifier_can_only_appear_on_a_property_declaration_or_index_signature); } flags |= ModifierFlags.Readonly; - lastReadonly = modifier; break; case SyntaxKind.ExportKeyword: @@ -41725,18 +42978,12 @@ namespace ts { if (flags & ModifierFlags.Static) { return grammarErrorOnNode(lastStatic!, Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration, "static"); } - if (flags & ModifierFlags.Abstract) { - return grammarErrorOnNode(lastStatic!, Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration, "abstract"); // TODO: GH#18217 - } if (flags & ModifierFlags.Override) { return grammarErrorOnNode(lastOverride!, Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration, "override"); // TODO: GH#18217 } - else if (flags & ModifierFlags.Async) { + if (flags & ModifierFlags.Async) { return grammarErrorOnNode(lastAsync!, Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration, "async"); } - else if (flags & ModifierFlags.Readonly) { - return grammarErrorOnNode(lastReadonly!, Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration, "readonly"); - } return false; } else if ((node.kind === SyntaxKind.ImportDeclaration || node.kind === SyntaxKind.ImportEqualsDeclaration) && flags & ModifierFlags.Ambient) { @@ -41921,6 +43168,12 @@ namespace ts { 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; @@ -41993,21 +43246,6 @@ namespace ts { return false; } - function checkGrammarForOmittedArgument(args: NodeArray | undefined): boolean { - if (args) { - for (const arg of args) { - if (arg.kind === SyntaxKind.OmittedExpression) { - return grammarErrorAtPos(arg, arg.pos, 0, Diagnostics.Argument_expression_expected); - } - } - } - return false; - } - - function checkGrammarArguments(args: NodeArray | undefined): boolean { - return checkGrammarForOmittedArgument(args); - } - function checkGrammarHeritageClause(node: HeritageClause): boolean { const types = node.types; if (checkGrammarForDisallowedTrailingComma(types)) { @@ -42020,7 +43258,7 @@ namespace ts { return some(types, checkGrammarExpressionWithTypeArguments); } - function checkGrammarExpressionWithTypeArguments(node: ExpressionWithTypeArguments) { + function checkGrammarExpressionWithTypeArguments(node: ExpressionWithTypeArguments | TypeQueryNode) { return checkGrammarTypeArguments(node, node.typeArguments); } @@ -42143,7 +43381,7 @@ namespace ts { if (prop.kind === SyntaxKind.ShorthandPropertyAssignment && !inDestructuring && prop.objectAssignmentInitializer) { // having objectAssignmentInitializer is only valid in ObjectAssignmentPattern // outside of destructuring it is a syntax error - return 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); + 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) { @@ -42152,8 +43390,7 @@ namespace ts { // Modifiers are never allowed on properties except for 'async' on a method declaration if (prop.modifiers) { - // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion - for (const mod of prop.modifiers!) { // TODO: GH#19955 + for (const mod of prop.modifiers) { if (mod.kind !== SyntaxKind.AsyncKeyword || prop.kind !== SyntaxKind.MethodDeclaration) { grammarErrorOnNode(mod, Diagnostics._0_modifier_cannot_be_used_here, getTextOfNode(mod)); } @@ -42205,9 +43442,12 @@ namespace ts { seen.set(effectiveName, currentKind); } else { - if ((currentKind & DeclarationMeaning.PropertyAssignmentOrMethod) && (existingKind & DeclarationMeaning.PropertyAssignmentOrMethod)) { + 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); @@ -42291,9 +43531,9 @@ namespace ts { 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)); } - if ((moduleKind !== ModuleKind.ESNext && moduleKind !== ModuleKind.System) || languageVersion < ScriptTarget.ES2017) { + if ((moduleKind !== ModuleKind.ES2022 && moduleKind !== ModuleKind.ESNext && moduleKind !== ModuleKind.System && !(moduleKind === ModuleKind.NodeNext && getSourceFileOfNode(forInOrOfStatement).impliedNodeFormat === ModuleKind.ESNext)) || languageVersion < ScriptTarget.ES2017) { diagnostics.add(createDiagnosticForNode(forInOrOfStatement.awaitModifier, - Diagnostics.Top_level_for_await_loops_are_only_allowed_when_the_module_option_is_set_to_esnext_or_system_and_the_target_option_is_set_to_es2017_or_higher)); + Diagnostics.Top_level_for_await_loops_are_only_allowed_when_the_module_option_is_set_to_es2022_esnext_system_or_nodenext_and_the_target_option_is_set_to_es2017_or_higher)); } } } @@ -42672,8 +43912,7 @@ namespace ts { return grammarErrorOnNode(node.exclamationToken, message); } - const moduleKind = getEmitModuleKind(compilerOptions); - if (moduleKind < ModuleKind.ES2015 && moduleKind !== ModuleKind.System && + 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); } @@ -42838,6 +44077,11 @@ namespace ts { } function checkGrammarProperty(node: PropertyDeclaration | PropertySignature) { + if (isComputedPropertyName(node.name) && isBinaryExpression(node.name.expression) && node.name.expression.operatorToken.kind === SyntaxKind.InKeyword) { + return grammarErrorOnNode( + (node.parent as ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode).members[0], + Diagnostics.A_mapped_type_may_not_declare_properties_or_methods); + } if (isClassLike(node.parent)) { if (isStringLiteral(node.name) && node.name.text === "constructor") { return grammarErrorOnNode(node.name, Diagnostics.Classes_may_not_have_a_field_named_constructor); @@ -42857,7 +44101,7 @@ namespace ts { return grammarErrorOnNode(node.initializer, Diagnostics.An_interface_property_cannot_have_an_initializer); } } - else if (node.parent.kind === SyntaxKind.TypeLiteral) { + 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; } @@ -42979,19 +44223,24 @@ namespace ts { } function checkNumericLiteralValueSize(node: NumericLiteral) { + // We should test against `getTextOfNode(node)` rather than `node.text`, because `node.text` for large numeric literals can contain "." + // e.g. `node.text` for numeric literal `1100000000000000000000` is `1.1e21`. + const isFractional = getTextOfNode(node).indexOf(".") !== -1; + const isScientific = node.numericLiteralFlags & TokenFlags.Scientific; + // Scientific notation (e.g. 2e54 and 1e00000000010) can't be converted to bigint - // Literals with 15 or fewer characters aren't long enough to reach past 2^53 - 1 // Fractional numbers (e.g. 9000000000000000.001) are inherently imprecise anyway - if (node.numericLiteralFlags & TokenFlags.Scientific || node.text.length <= 15 || node.text.indexOf(".") !== -1) { + if (isFractional || isScientific) { return; } - // We can't rely on the runtime to accurately store and compare extremely large numeric values - // Even for internal use, we use getTextOfNode: https://github.com/microsoft/TypeScript/issues/33298 - // Thus, if the runtime claims a too-large number is lower than Number.MAX_SAFE_INTEGER, - // it's likely addition operations on it will fail too - const apparentValue = +getTextOfNode(node); - if (apparentValue <= 2 ** 53 - 1 && apparentValue + 1 > apparentValue) { + // 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; } @@ -43038,12 +44287,27 @@ namespace ts { 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 (moduleKind === ModuleKind.ES2015) { - return grammarErrorOnNode(node, Diagnostics.Dynamic_imports_are_only_supported_when_the_module_flag_is_set_to_es2020_esnext_commonjs_amd_system_or_umd); + return grammarErrorOnNode(node, Diagnostics.Dynamic_imports_are_only_supported_when_the_module_flag_is_set_to_es2020_es2022_esnext_commonjs_amd_system_umd_node12_or_nodenext); } if (node.typeArguments) { @@ -43051,14 +44315,25 @@ namespace ts { } const nodeArguments = node.arguments; - if (nodeArguments.length !== 1) { - return grammarErrorOnNode(node, Diagnostics.Dynamic_import_must_have_one_specifier_as_an_argument); + if (moduleKind !== ModuleKind.ESNext && moduleKind !== ModuleKind.NodeNext) { + // We are allowed trailing comma after proposal-import-assertions. + checkGrammarForDisallowedTrailingComma(nodeArguments); + + if (nodeArguments.length > 1) { + const assertionArgument = nodeArguments[1]; + return grammarErrorOnNode(assertionArgument, Diagnostics.Dynamic_imports_only_support_a_second_argument_when_the_module_option_is_set_to_esnext_or_nodenext); + } } - checkGrammarForDisallowedTrailingComma(nodeArguments); + + if (nodeArguments.length === 0 || nodeArguments.length > 2) { + return grammarErrorOnNode(node, Diagnostics.Dynamic_imports_can_only_accept_a_module_specifier_and_an_optional_assertion_as_arguments); + } + // see: parseArgumentOrArrayLiteralElement...we use this function which parse arguments of callExpression to parse specifier for dynamic import. // parseArgumentOrArrayLiteralElement allows spread element to be in an argument list which is not allowed as specifier in dynamic import. - if (isSpreadElement(nodeArguments[0])) { - return grammarErrorOnNode(nodeArguments[0], Diagnostics.Specifier_of_dynamic_import_cannot_be_spread_element); + const spreadElement = find(nodeArguments, isSpreadElement); + if (spreadElement) { + return grammarErrorOnNode(spreadElement, Diagnostics.Argument_of_dynamic_import_cannot_be_spread_element); } return false; } @@ -43098,28 +44373,27 @@ namespace ts { function findMostOverlappyType(source: Type, unionTarget: UnionOrIntersectionType) { let bestMatch: Type | undefined; - let matchingCount = 0; - for (const target of unionTarget.types) { - const overlap = getIntersectionType([getIndexType(source), getIndexType(target)]); - if (overlap.flags & TypeFlags.Index) { - // perfect overlap of keys - bestMatch = target; - matchingCount = Infinity; - } - else if (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 = length(filter((overlap as UnionType).types, isUnitType)); - if (len >= matchingCount) { - bestMatch = target; - matchingCount = len; + 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; + } + } } } - else if (isUnitType(overlap) && 1 >= matchingCount) { - bestMatch = target; - matchingCount = 1; - } } return bestMatch; } diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index b76e6f72268..89d131508cb 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -1,6 +1,10 @@ namespace ts { /* @internal */ - export const compileOnSaveCommandLineOption: CommandLineOption = { name: "compileOnSave", type: "boolean" }; + export const compileOnSaveCommandLineOption: CommandLineOption = { + name: "compileOnSave", + type: "boolean", + defaultValueDescription: false, + }; const jsxOptionMap = new Map(getEntries({ "preserve": JsxEmit.Preserve, @@ -29,6 +33,7 @@ namespace ts { ["es2019", "lib.es2019.d.ts"], ["es2020", "lib.es2020.d.ts"], ["es2021", "lib.es2021.d.ts"], + ["es2022", "lib.es2022.d.ts"], ["esnext", "lib.esnext.d.ts"], // Host only ["dom", "lib.dom.d.ts"], @@ -72,12 +77,16 @@ namespace ts { ["es2021.string", "lib.es2021.string.d.ts"], ["es2021.weakref", "lib.es2021.weakref.d.ts"], ["es2021.intl", "lib.es2021.intl.d.ts"], - ["esnext.array", "lib.es2019.array.d.ts"], + ["es2022.array", "lib.es2022.array.d.ts"], + ["es2022.error", "lib.es2022.error.d.ts"], + ["es2022.object", "lib.es2022.object.d.ts"], + ["es2022.string", "lib.es2022.string.d.ts"], + ["esnext.array", "lib.es2022.array.d.ts"], ["esnext.symbol", "lib.es2019.symbol.d.ts"], ["esnext.asynciterable", "lib.es2018.asynciterable.d.ts"], ["esnext.intl", "lib.esnext.intl.d.ts"], ["esnext.bigint", "lib.es2020.bigint.d.ts"], - ["esnext.string", "lib.es2021.string.d.ts"], + ["esnext.string", "lib.es2022.string.d.ts"], ["esnext.promise", "lib.es2021.promise.d.ts"], ["esnext.weakref", "lib.es2021.weakref.d.ts"] ]; @@ -112,6 +121,7 @@ namespace ts { })), category: Diagnostics.Watch_and_Build_Modes, description: Diagnostics.Specify_how_the_TypeScript_watch_mode_works, + defaultValueDescription: WatchFileKind.UseFsEvents, }, { name: "watchDirectory", @@ -123,6 +133,7 @@ namespace ts { })), category: Diagnostics.Watch_and_Build_Modes, description: Diagnostics.Specify_how_directories_are_watched_on_systems_that_lack_recursive_file_watching_functionality, + defaultValueDescription: WatchDirectoryKind.UseFsEvents, }, { name: "fallbackPolling", @@ -134,12 +145,14 @@ namespace ts { })), category: Diagnostics.Watch_and_Build_Modes, description: Diagnostics.Specify_what_approach_the_watcher_should_use_if_the_system_runs_out_of_native_file_watchers, + defaultValueDescription: PollingWatchKind.PriorityInterval, }, { name: "synchronousWatchDirectory", type: "boolean", category: Diagnostics.Watch_and_Build_Modes, description: Diagnostics.Synchronously_call_callbacks_and_update_the_state_of_directory_watchers_on_platforms_that_don_t_support_recursive_watching_natively, + defaultValueDescription: false, }, { name: "excludeDirectories", @@ -176,11 +189,13 @@ namespace ts { showInSimplifiedHelpView: true, category: Diagnostics.Command_line_Options, description: Diagnostics.Print_this_message, + defaultValueDescription: false, }, { name: "help", shortName: "?", - type: "boolean" + type: "boolean", + defaultValueDescription: false, }, { name: "watch", @@ -190,6 +205,7 @@ namespace ts { isCommandLineOnly: true, category: Diagnostics.Command_line_Options, description: Diagnostics.Watch_input_files, + defaultValueDescription: false, }, { name: "preserveWatchOutput", @@ -197,27 +213,28 @@ namespace ts { showInSimplifiedHelpView: false, category: Diagnostics.Output_Formatting, description: Diagnostics.Disable_wiping_the_console_in_watch_mode, - defaultValueDescription: "n/a" + defaultValueDescription: false, }, { name: "listFiles", type: "boolean", category: Diagnostics.Compiler_Diagnostics, description: Diagnostics.Print_all_of_the_files_read_during_the_compilation, - defaultValueDescription: "false" + defaultValueDescription: false, }, { name: "explainFiles", type: "boolean", category: Diagnostics.Compiler_Diagnostics, - description: Diagnostics.Print_files_read_during_the_compilation_including_why_it_was_included + description: Diagnostics.Print_files_read_during_the_compilation_including_why_it_was_included, + defaultValueDescription: false, }, { name: "listEmittedFiles", type: "boolean", category: Diagnostics.Compiler_Diagnostics, description: Diagnostics.Print_the_names_of_emitted_files_after_a_compilation, - defaultValueDescription: "false" + defaultValueDescription: false, }, { name: "pretty", @@ -225,28 +242,28 @@ namespace ts { showInSimplifiedHelpView: true, category: Diagnostics.Output_Formatting, description: Diagnostics.Enable_color_and_formatting_in_TypeScript_s_output_to_make_compiler_errors_easier_to_read, - defaultValueDescription: "true" + defaultValueDescription: true, }, { name: "traceResolution", type: "boolean", category: Diagnostics.Compiler_Diagnostics, description: Diagnostics.Log_paths_used_during_the_moduleResolution_process, - defaultValueDescription: "false" + defaultValueDescription: false, }, { name: "diagnostics", type: "boolean", category: Diagnostics.Compiler_Diagnostics, description: Diagnostics.Output_compiler_performance_information_after_building, - defaultValueDescription: "false" + defaultValueDescription: false, }, { name: "extendedDiagnostics", type: "boolean", category: Diagnostics.Compiler_Diagnostics, description: Diagnostics.Output_more_detailed_compiler_performance_information_after_building, - defaultValueDescription: "false" + defaultValueDescription: false, }, { name: "generateCpuProfile", @@ -281,7 +298,8 @@ namespace ts { affectsSemanticDiagnostics: true, affectsEmit: true, category: Diagnostics.Watch_and_Build_Modes, - description: Diagnostics.Have_recompiles_in_projects_that_use_incremental_and_watch_mode_assume_that_changes_within_a_file_will_only_affect_files_directly_depending_on_it + description: Diagnostics.Have_recompiles_in_projects_that_use_incremental_and_watch_mode_assume_that_changes_within_a_file_will_only_affect_files_directly_depending_on_it, + defaultValueDescription: false, }, { name: "locale", @@ -308,6 +326,7 @@ namespace ts { es2019: ScriptTarget.ES2019, es2020: ScriptTarget.ES2020, es2021: ScriptTarget.ES2021, + es2022: ScriptTarget.ES2022, esnext: ScriptTarget.ESNext, })), affectsSourceFile: true, @@ -317,7 +336,7 @@ namespace ts { showInSimplifiedHelpView: true, category: Diagnostics.Language_and_Environment, description: Diagnostics.Set_the_JavaScript_language_version_for_emitted_JavaScript_and_include_compatible_library_declarations, - defaultValueDescription: "ES3" + defaultValueDescription: ScriptTarget.ES3, }; const commandOptionsWithoutBuild: CommandLineOption[] = [ @@ -328,6 +347,7 @@ namespace ts { showInSimplifiedHelpView: true, category: Diagnostics.Command_line_Options, description: Diagnostics.Show_all_compiler_options, + defaultValueDescription: false, }, { name: "version", @@ -336,6 +356,7 @@ namespace ts { showInSimplifiedHelpView: true, category: Diagnostics.Command_line_Options, description: Diagnostics.Print_the_compiler_s_version, + defaultValueDescription: false, }, { name: "init", @@ -343,6 +364,7 @@ namespace ts { showInSimplifiedHelpView: true, category: Diagnostics.Command_line_Options, description: Diagnostics.Initializes_a_TypeScript_project_and_creates_a_tsconfig_json_file, + defaultValueDescription: false, }, { name: "project", @@ -360,7 +382,8 @@ namespace ts { shortName: "b", showInSimplifiedHelpView: true, category: Diagnostics.Command_line_Options, - description: Diagnostics.Build_one_or_more_projects_and_their_dependencies_if_out_of_date + description: Diagnostics.Build_one_or_more_projects_and_their_dependencies_if_out_of_date, + defaultValueDescription: false, }, { name: "showConfig", @@ -368,7 +391,8 @@ namespace ts { showInSimplifiedHelpView: true, category: Diagnostics.Command_line_Options, isCommandLineOnly: true, - description: Diagnostics.Print_the_final_configuration_instead_of_building + description: Diagnostics.Print_the_final_configuration_instead_of_building, + defaultValueDescription: false, }, { name: "listFilesOnly", @@ -377,7 +401,8 @@ namespace ts { affectsSemanticDiagnostics: true, affectsEmit: true, isCommandLineOnly: true, - description: Diagnostics.Print_names_of_files_that_are_part_of_the_compilation_and_then_stop_processing + description: Diagnostics.Print_names_of_files_that_are_part_of_the_compilation_and_then_stop_processing, + defaultValueDescription: false, }, // Basic @@ -394,7 +419,10 @@ namespace ts { es6: ModuleKind.ES2015, es2015: ModuleKind.ES2015, es2020: ModuleKind.ES2020, - esnext: ModuleKind.ESNext + es2022: ModuleKind.ES2022, + esnext: ModuleKind.ESNext, + node12: ModuleKind.Node12, + nodenext: ModuleKind.NodeNext, })), affectsModuleResolution: true, affectsEmit: true, @@ -402,13 +430,15 @@ namespace ts { showInSimplifiedHelpView: true, category: Diagnostics.Modules, description: Diagnostics.Specify_what_module_code_is_generated, + defaultValueDescription: undefined, }, { name: "lib", type: "list", element: { name: "lib", - type: libMap + type: libMap, + defaultValueDescription: undefined, }, affectsProgramStructure: true, showInSimplifiedHelpView: true, @@ -423,7 +453,7 @@ namespace ts { showInSimplifiedHelpView: true, category: Diagnostics.JavaScript_Support, description: Diagnostics.Allow_JavaScript_files_to_be_a_part_of_your_program_Use_the_checkJS_option_to_get_errors_from_these_files, - defaultValueDescription: "false" + defaultValueDescription: false, }, { name: "checkJs", @@ -431,7 +461,7 @@ namespace ts { showInSimplifiedHelpView: true, category: Diagnostics.JavaScript_Support, description: Diagnostics.Enable_error_reporting_in_type_checked_JavaScript_files, - defaultValueDescription: "false" + defaultValueDescription: false, }, { name: "jsx", @@ -443,7 +473,7 @@ namespace ts { showInSimplifiedHelpView: true, category: Diagnostics.Language_and_Environment, description: Diagnostics.Specify_what_JSX_code_is_generated, - defaultValueDescription: "undefined" + defaultValueDescription: undefined, }, { name: "declaration", @@ -463,7 +493,7 @@ namespace ts { showInSimplifiedHelpView: true, category: Diagnostics.Emit, transpileOptionValue: undefined, - defaultValueDescription: "false", + defaultValueDescription: false, description: Diagnostics.Create_sourcemaps_for_d_ts_files }, { @@ -475,7 +505,7 @@ namespace ts { category: Diagnostics.Emit, description: Diagnostics.Only_output_d_ts_files_and_not_JavaScript_files, transpileOptionValue: undefined, - defaultValueDescription: "false", + defaultValueDescription: false, }, { name: "sourceMap", @@ -483,7 +513,7 @@ namespace ts { affectsEmit: true, showInSimplifiedHelpView: true, category: Diagnostics.Emit, - defaultValueDescription: "false", + defaultValueDescription: false, description: Diagnostics.Create_source_map_files_for_emitted_JavaScript_files, }, { @@ -496,7 +526,6 @@ namespace ts { category: Diagnostics.Emit, description: Diagnostics.Specify_a_file_that_bundles_all_outputs_into_one_JavaScript_file_If_declaration_is_true_also_designates_a_file_that_bundles_all_d_ts_output, transpileOptionValue: undefined, - defaultValueDescription: "n/a" }, { name: "outDir", @@ -507,7 +536,6 @@ namespace ts { showInSimplifiedHelpView: true, category: Diagnostics.Emit, description: Diagnostics.Specify_an_output_folder_for_all_emitted_files, - defaultValueDescription: "n/a" }, { name: "rootDir", @@ -526,7 +554,7 @@ namespace ts { isTSConfigOnly: true, category: Diagnostics.Projects, transpileOptionValue: undefined, - defaultValueDescription: "false", + defaultValueDescription: false, description: Diagnostics.Enable_constraints_that_allow_a_TypeScript_project_to_be_used_with_project_references, }, { @@ -546,7 +574,7 @@ namespace ts { affectsEmit: true, showInSimplifiedHelpView: true, category: Diagnostics.Emit, - defaultValueDescription: "false", + defaultValueDescription: false, description: Diagnostics.Disable_emitting_comments, }, { @@ -556,7 +584,7 @@ namespace ts { category: Diagnostics.Emit, description: Diagnostics.Disable_emitting_files_from_a_compilation, transpileOptionValue: undefined, - defaultValueDescription: "false" + defaultValueDescription: false, }, { name: "importHelpers", @@ -564,7 +592,7 @@ namespace ts { affectsEmit: true, category: Diagnostics.Emit, description: Diagnostics.Allow_importing_helper_functions_from_tslib_once_per_project_instead_of_including_them_per_file, - defaultValueDescription: "false" + defaultValueDescription: false, }, { name: "importsNotUsedAsValues", @@ -576,7 +604,8 @@ namespace ts { affectsEmit: true, affectsSemanticDiagnostics: true, category: Diagnostics.Emit, - description: Diagnostics.Specify_emit_Slashchecking_behavior_for_imports_that_are_only_used_for_types + description: Diagnostics.Specify_emit_Slashchecking_behavior_for_imports_that_are_only_used_for_types, + defaultValueDescription: ImportsNotUsedAsValues.Remove, }, { name: "downlevelIteration", @@ -584,7 +613,7 @@ namespace ts { affectsEmit: true, category: Diagnostics.Emit, description: Diagnostics.Emit_more_compliant_but_verbose_and_less_performant_JavaScript_for_iteration, - defaultValueDescription: "false" + defaultValueDescription: false, }, { name: "isolatedModules", @@ -592,7 +621,7 @@ namespace ts { category: Diagnostics.Interop_Constraints, description: Diagnostics.Ensure_that_each_file_can_be_safely_transpiled_without_relying_on_other_imports, transpileOptionValue: true, - defaultValueDescription: "false" + defaultValueDescription: false, }, // Strict Type Checks @@ -604,7 +633,7 @@ namespace ts { showInSimplifiedHelpView: true, category: Diagnostics.Type_Checking, description: Diagnostics.Enable_all_strict_type_checking_options, - defaultValueDescription: "false" + defaultValueDescription: false, }, { name: "noImplicitAny", @@ -665,6 +694,7 @@ namespace ts { strictFlag: true, category: Diagnostics.Type_Checking, description: Diagnostics.Type_catch_clause_variables_as_unknown_instead_of_any, + defaultValueDescription: false, }, { name: "alwaysStrict", @@ -683,7 +713,7 @@ namespace ts { affectsSemanticDiagnostics: true, category: Diagnostics.Type_Checking, description: Diagnostics.Enable_error_reporting_when_a_local_variables_aren_t_read, - defaultValueDescription: "false" + defaultValueDescription: false, }, { name: "noUnusedParameters", @@ -691,14 +721,15 @@ namespace ts { affectsSemanticDiagnostics: true, category: Diagnostics.Type_Checking, description: Diagnostics.Raise_an_error_when_a_function_parameter_isn_t_read, - defaultValueDescription: "false" + defaultValueDescription: false, }, { name: "exactOptionalPropertyTypes", type: "boolean", affectsSemanticDiagnostics: true, category: Diagnostics.Type_Checking, - description: Diagnostics.Interpret_optional_property_types_as_written_rather_than_adding_undefined + description: Diagnostics.Interpret_optional_property_types_as_written_rather_than_adding_undefined, + defaultValueDescription: false, }, { name: "noImplicitReturns", @@ -706,7 +737,7 @@ namespace ts { affectsSemanticDiagnostics: true, category: Diagnostics.Type_Checking, description: Diagnostics.Enable_error_reporting_for_codepaths_that_do_not_explicitly_return_in_a_function, - defaultValueDescription: "false" + defaultValueDescription: false, }, { name: "noFallthroughCasesInSwitch", @@ -714,21 +745,24 @@ namespace ts { affectsBindDiagnostics: true, affectsSemanticDiagnostics: true, category: Diagnostics.Type_Checking, - description: Diagnostics.Enable_error_reporting_for_fallthrough_cases_in_switch_statements + description: Diagnostics.Enable_error_reporting_for_fallthrough_cases_in_switch_statements, + defaultValueDescription: false, }, { name: "noUncheckedIndexedAccess", type: "boolean", affectsSemanticDiagnostics: true, category: Diagnostics.Type_Checking, - description: Diagnostics.Include_undefined_in_index_signature_results + description: Diagnostics.Include_undefined_in_index_signature_results, + defaultValueDescription: false, }, { name: "noImplicitOverride", type: "boolean", affectsSemanticDiagnostics: true, category: Diagnostics.Type_Checking, - description: Diagnostics.Ensure_overriding_members_in_derived_classes_are_marked_with_an_override_modifier + description: Diagnostics.Ensure_overriding_members_in_derived_classes_are_marked_with_an_override_modifier, + defaultValueDescription: false, }, { name: "noPropertyAccessFromIndexSignature", @@ -736,7 +770,7 @@ namespace ts { showInSimplifiedHelpView: false, category: Diagnostics.Type_Checking, description: Diagnostics.Enforces_using_indexed_accessors_for_keys_declared_using_an_indexed_type, - defaultValueDescription: "false" + defaultValueDescription: false, }, // Module Resolution @@ -745,6 +779,8 @@ namespace ts { type: new Map(getEntries({ node: ModuleResolutionKind.NodeJs, classic: ModuleResolutionKind.Classic, + node12: ModuleResolutionKind.Node12, + nodenext: ModuleResolutionKind.NodeNext, })), affectsModuleResolution: true, paramType: Diagnostics.STRATEGY, @@ -829,14 +865,14 @@ namespace ts { showInSimplifiedHelpView: true, category: Diagnostics.Interop_Constraints, description: Diagnostics.Emit_additional_JavaScript_to_ease_support_for_importing_CommonJS_modules_This_enables_allowSyntheticDefaultImports_for_type_compatibility, - defaultValueDescription: "false" + defaultValueDescription: false, }, { name: "preserveSymlinks", type: "boolean", category: Diagnostics.Interop_Constraints, description: Diagnostics.Disable_resolving_symlinks_to_their_realpath_This_correlates_to_the_same_flag_in_node, - defaultValueDescription: "n/a" + defaultValueDescription: false, }, { name: "allowUmdGlobalAccess", @@ -844,7 +880,7 @@ namespace ts { affectsSemanticDiagnostics: true, category: Diagnostics.Modules, description: Diagnostics.Allow_accessing_UMD_globals_from_modules, - defaultValueDescription: "false" + defaultValueDescription: false, }, // Source Maps @@ -870,7 +906,7 @@ namespace ts { affectsEmit: true, category: Diagnostics.Emit, description: Diagnostics.Include_sourcemap_files_inside_the_emitted_JavaScript, - defaultValueDescription: "false" + defaultValueDescription: false, }, { name: "inlineSources", @@ -878,7 +914,7 @@ namespace ts { affectsEmit: true, category: Diagnostics.Emit, description: Diagnostics.Include_source_code_in_the_sourcemaps_inside_the_emitted_JavaScript, - defaultValueDescription: "false" + defaultValueDescription: false, }, // Experimental @@ -887,7 +923,8 @@ namespace ts { type: "boolean", affectsSemanticDiagnostics: true, category: Diagnostics.Language_and_Environment, - description: Diagnostics.Enable_experimental_support_for_TC39_stage_2_draft_decorators + description: Diagnostics.Enable_experimental_support_for_TC39_stage_2_draft_decorators, + defaultValueDescription: false, }, { name: "emitDecoratorMetadata", @@ -895,7 +932,8 @@ namespace ts { affectsSemanticDiagnostics: true, affectsEmit: true, category: Diagnostics.Language_and_Environment, - description: Diagnostics.Emit_design_type_metadata_for_decorated_declarations_in_source_files + description: Diagnostics.Emit_design_type_metadata_for_decorated_declarations_in_source_files, + defaultValueDescription: false, }, // Advanced @@ -928,7 +966,7 @@ namespace ts { affectsModuleResolution: true, category: Diagnostics.Modules, description: Diagnostics.Enable_importing_json_files, - defaultValueDescription: "false" + defaultValueDescription: false, }, { @@ -940,7 +978,6 @@ namespace ts { category: Diagnostics.Backwards_Compatibility, paramType: Diagnostics.FILE, transpileOptionValue: undefined, - defaultValueDescription: "n/a", description: Diagnostics.Deprecated_setting_Use_outFile_instead, }, { @@ -956,7 +993,7 @@ namespace ts { type: "boolean", category: Diagnostics.Completeness, description: Diagnostics.Skip_type_checking_d_ts_files_that_are_included_with_TypeScript, - defaultValueDescription: "false", + defaultValueDescription: false, }, { name: "charset", @@ -971,7 +1008,7 @@ namespace ts { affectsEmit: true, category: Diagnostics.Emit, description: Diagnostics.Emit_a_UTF_8_Byte_Order_Mark_BOM_in_the_beginning_of_output_files, - defaultValueDescription: "false" + defaultValueDescription: false, }, { name: "newLine", @@ -991,7 +1028,7 @@ namespace ts { affectsSemanticDiagnostics: true, category: Diagnostics.Output_Formatting, description: Diagnostics.Disable_truncating_types_in_error_messages, - defaultValueDescription: "false" + defaultValueDescription: false, }, { name: "noLib", @@ -1002,7 +1039,7 @@ namespace ts { // We are not returning a sourceFile for lib file when asked by the program, // so pass --noLib to avoid reporting a file not found error. transpileOptionValue: true, - defaultValueDescription: "false" + defaultValueDescription: false, }, { name: "noResolve", @@ -1013,7 +1050,7 @@ namespace ts { // We are not doing a full typecheck, we are not resolving the whole context, // so pass --noResolve to avoid reporting missing file errors. transpileOptionValue: true, - defaultValueDescription: "false" + defaultValueDescription: false, }, { name: "stripInternal", @@ -1021,6 +1058,7 @@ namespace ts { affectsEmit: true, category: Diagnostics.Emit, description: Diagnostics.Disable_emitting_declarations_that_have_internal_in_their_JSDoc_comments, + defaultValueDescription: false, }, { name: "disableSizeLimit", @@ -1028,28 +1066,31 @@ namespace ts { affectsProgramStructure: true, category: Diagnostics.Editor_Support, description: Diagnostics.Remove_the_20mb_cap_on_total_source_code_size_for_JavaScript_files_in_the_TypeScript_language_server, - defaultValueDescription: "false" + defaultValueDescription: false, }, { name: "disableSourceOfProjectReferenceRedirect", type: "boolean", isTSConfigOnly: true, category: Diagnostics.Projects, - description: Diagnostics.Disable_preferring_source_files_instead_of_declaration_files_when_referencing_composite_projects + description: Diagnostics.Disable_preferring_source_files_instead_of_declaration_files_when_referencing_composite_projects, + defaultValueDescription: false, }, { name: "disableSolutionSearching", type: "boolean", isTSConfigOnly: true, category: Diagnostics.Projects, - description: Diagnostics.Opt_a_project_out_of_multi_project_reference_checking_when_editing + description: Diagnostics.Opt_a_project_out_of_multi_project_reference_checking_when_editing, + defaultValueDescription: false, }, { name: "disableReferencedProjectLoad", type: "boolean", isTSConfigOnly: true, category: Diagnostics.Projects, - description: Diagnostics.Reduce_the_number_of_projects_loaded_automatically_by_TypeScript + description: Diagnostics.Reduce_the_number_of_projects_loaded_automatically_by_TypeScript, + defaultValueDescription: false, }, { name: "noImplicitUseStrict", @@ -1057,7 +1098,7 @@ namespace ts { affectsSemanticDiagnostics: true, category: Diagnostics.Backwards_Compatibility, description: Diagnostics.Disable_adding_use_strict_directives_in_emitted_JavaScript_files, - defaultValueDescription: "false" + defaultValueDescription: false, }, { name: "noEmitHelpers", @@ -1065,7 +1106,7 @@ namespace ts { affectsEmit: true, category: Diagnostics.Emit, description: Diagnostics.Disable_generating_custom_helper_functions_like_extends_in_compiled_output, - defaultValueDescription: "false" + defaultValueDescription: false, }, { name: "noEmitOnError", @@ -1074,7 +1115,7 @@ namespace ts { category: Diagnostics.Emit, transpileOptionValue: undefined, description: Diagnostics.Disable_emitting_files_if_any_type_checking_errors_are_reported, - defaultValueDescription: "false" + defaultValueDescription: false, }, { name: "preserveConstEnums", @@ -1082,7 +1123,7 @@ namespace ts { affectsEmit: true, category: Diagnostics.Emit, description: Diagnostics.Disable_erasing_const_enum_declarations_in_generated_code, - defaultValueDescription: "n/a" + defaultValueDescription: false, }, { name: "declarationDir", @@ -1093,14 +1134,13 @@ namespace ts { category: Diagnostics.Emit, transpileOptionValue: undefined, description: Diagnostics.Specify_the_output_directory_for_generated_declaration_files, - defaultValueDescription: "n/a" }, { name: "skipLibCheck", type: "boolean", category: Diagnostics.Completeness, description: Diagnostics.Skip_type_checking_all_d_ts_files, - defaultValueDescription: "false" + defaultValueDescription: false, }, { name: "allowUnusedLabels", @@ -1109,7 +1149,7 @@ namespace ts { affectsSemanticDiagnostics: true, category: Diagnostics.Type_Checking, description: Diagnostics.Disable_error_reporting_for_unused_labels, - defaultValueDescription: "undefined" + defaultValueDescription: undefined, }, { name: "allowUnreachableCode", @@ -1118,7 +1158,7 @@ namespace ts { affectsSemanticDiagnostics: true, category: Diagnostics.Type_Checking, description: Diagnostics.Disable_error_reporting_for_unreachable_code, - defaultValueDescription: "undefined" + defaultValueDescription: undefined, }, { name: "suppressExcessPropertyErrors", @@ -1126,7 +1166,7 @@ namespace ts { affectsSemanticDiagnostics: true, category: Diagnostics.Backwards_Compatibility, description: Diagnostics.Disable_reporting_of_excess_property_errors_during_the_creation_of_object_literals, - defaultValueDescription: "false" + defaultValueDescription: false, }, { name: "suppressImplicitAnyIndexErrors", @@ -1134,7 +1174,7 @@ namespace ts { affectsSemanticDiagnostics: true, category: Diagnostics.Backwards_Compatibility, description: Diagnostics.Suppress_noImplicitAny_errors_when_indexing_objects_that_lack_index_signatures, - defaultValueDescription: "false" + defaultValueDescription: false, }, { name: "forceConsistentCasingInFileNames", @@ -1142,7 +1182,7 @@ namespace ts { affectsModuleResolution: true, category: Diagnostics.Interop_Constraints, description: Diagnostics.Ensure_that_casing_is_correct_in_imports, - defaultValueDescription: "false" + defaultValueDescription: false, }, { name: "maxNodeModuleJsDepth", @@ -1150,7 +1190,7 @@ namespace ts { affectsModuleResolution: true, category: Diagnostics.JavaScript_Support, description: Diagnostics.Specify_the_maximum_folder_depth_used_for_checking_JavaScript_files_from_node_modules_Only_applicable_with_allowJs, - defaultValueDescription: "0" + defaultValueDescription: 0, }, { name: "noStrictGenericChecks", @@ -1158,7 +1198,7 @@ namespace ts { affectsSemanticDiagnostics: true, category: Diagnostics.Backwards_Compatibility, description: Diagnostics.Disable_strict_checking_of_generic_signatures_in_function_types, - defaultValueDescription: "false" + defaultValueDescription: false, }, { name: "useDefineForClassFields", @@ -1175,6 +1215,7 @@ namespace ts { affectsEmit: true, category: Diagnostics.Emit, description: Diagnostics.Preserve_unused_imported_values_in_the_JavaScript_output_that_would_otherwise_be_removed, + defaultValueDescription: false, }, { @@ -1182,7 +1223,7 @@ namespace ts { type: "boolean", category: Diagnostics.Backwards_Compatibility, description: Diagnostics.Make_keyof_only_return_strings_instead_of_string_numbers_or_symbols_Legacy_option, - defaultValueDescription: "false" + defaultValueDescription: false, }, { // A list of plugins to load in the language service @@ -1237,27 +1278,31 @@ namespace ts { shortName: "v", category: Diagnostics.Command_line_Options, description: Diagnostics.Enable_verbose_logging, - type: "boolean" + type: "boolean", + defaultValueDescription: false, }, { name: "dry", shortName: "d", category: Diagnostics.Command_line_Options, description: Diagnostics.Show_what_would_be_built_or_deleted_if_specified_with_clean, - type: "boolean" + type: "boolean", + defaultValueDescription: false, }, { name: "force", shortName: "f", category: Diagnostics.Command_line_Options, description: Diagnostics.Build_all_projects_including_those_that_appear_to_be_up_to_date, - type: "boolean" + type: "boolean", + defaultValueDescription: false, }, { name: "clean", category: Diagnostics.Command_line_Options, description: Diagnostics.Delete_the_outputs_of_all_projects, - type: "boolean" + type: "boolean", + defaultValueDescription: false, } ]; @@ -1275,10 +1320,12 @@ namespace ts { */ name: "enableAutoDiscovery", type: "boolean", + defaultValueDescription: false, }, { name: "enable", type: "boolean", + defaultValueDescription: false, }, { name: "include", @@ -1299,6 +1346,7 @@ namespace ts { { name: "disableFilenameBasedTypeAcquisition", type: "boolean", + defaultValueDescription: false, }, ]; @@ -1337,7 +1385,7 @@ namespace ts { /* @internal */ export const defaultInitCompilerOptions: CompilerOptions = { module: ModuleKind.CommonJS, - target: ScriptTarget.ES5, + target: ScriptTarget.ES2016, strict: true, esModuleInterop: true, forceConsistentCasingInFileNames: true, @@ -2280,7 +2328,7 @@ namespace ts { return getCustomTypeMapOfCommandLineOption(optionDefinition.element); } else { - return (optionDefinition as CommandLineOptionOfCustomType).type; + return optionDefinition.type; } } @@ -2348,6 +2396,47 @@ namespace ts { return result; } + /** + * Generate a list of the compiler options whose value is not the default. + * @param options compilerOptions to be evaluated. + /** @internal */ + export function getCompilerOptionsDiffValue(options: CompilerOptions, newLine: string): string { + const compilerOptionsMap = getSerializedCompilerOption(options); + return getOverwrittenDefaultOptions(); + + function makePadding(paddingLength: number): string { + return Array(paddingLength + 1).join(" "); + } + + function getOverwrittenDefaultOptions() { + const result: string[] = []; + const tab = makePadding(2); + commandOptionsWithoutBuild.forEach(cmd => { + if (!compilerOptionsMap.has(cmd.name)) { + return; + } + + const newValue = compilerOptionsMap.get(cmd.name); + const defaultValue = getDefaultValueForOption(cmd); + if (newValue !== defaultValue) { + result.push(`${tab}${cmd.name}: ${newValue}`); + } + else if (hasProperty(defaultInitCompilerOptions, cmd.name)) { + result.push(`${tab}${cmd.name}: ${defaultValue}`); + } + }); + return result.join(newLine) + newLine; + } + } + + /** + * Get the compiler options to be written into the tsconfig.json. + * @param options commandlineOptions to be included in the compileOptions. + */ + function getSerializedCompilerOption(options: CompilerOptions): ESMap { + const compilerOptions = extend(options, defaultInitCompilerOptions); + return serializeCompilerOptions(compilerOptions); + } /** * Generate tsconfig configuration when running command line "--init" * @param options commandlineOptions to be generated into tsconfig.json @@ -2355,29 +2444,9 @@ namespace ts { */ /* @internal */ export function generateTSConfig(options: CompilerOptions, fileNames: readonly string[], newLine: string): string { - const compilerOptions = extend(options, defaultInitCompilerOptions); - const compilerOptionsMap = serializeCompilerOptions(compilerOptions); + const compilerOptionsMap = getSerializedCompilerOption(options); return writeConfigurations(); - function getDefaultValueForOption(option: CommandLineOption) { - switch (option.type) { - case "number": - return 1; - case "boolean": - return true; - case "string": - return option.isFilePath ? "./" : ""; - case "list": - return []; - case "object": - return {}; - default: - const iterResult = option.type.keys().next(); - if (!iterResult.done) return iterResult.value; - return Debug.fail("Expected 'option.type' to have entries."); - } - } - function makePadding(paddingLength: number): string { return Array(paddingLength + 1).join(" "); } @@ -3087,7 +3156,7 @@ namespace ts { if (isCompilerOptionsValue(opt, value)) { const optType = opt.type; if (optType === "list" && isArray(value)) { - return convertJsonOptionOfListType(opt as CommandLineOptionOfListType, value, basePath, errors); + return convertJsonOptionOfListType(opt , value, basePath, errors); } else if (!isString(optType)) { return convertJsonOptionOfCustomType(opt as CommandLineOptionOfCustomType, value as string, errors); @@ -3216,7 +3285,7 @@ namespace ts { // Rather than re-query this for each file and filespec, we query the supported extensions // once and store it on the expansion context. const supportedExtensions = getSupportedExtensions(options, extraFileExtensions); - const supportedExtensionsWithJsonIfResolveJsonModule = getSuppoertedExtensionsWithJsonIfResolveJsonModule(options, supportedExtensions); + const supportedExtensionsWithJsonIfResolveJsonModule = getSupportedExtensionsWithJsonIfResolveJsonModule(options, supportedExtensions); // Literal files are always included verbatim. An "include" or "exclude" specification cannot // remove a literal file. @@ -3229,7 +3298,7 @@ namespace ts { let jsonOnlyIncludeRegexes: readonly RegExp[] | undefined; if (validatedIncludeSpecs && validatedIncludeSpecs.length > 0) { - for (const file of host.readDirectory(basePath, supportedExtensionsWithJsonIfResolveJsonModule, validatedExcludeSpecs, validatedIncludeSpecs, /*depth*/ undefined)) { + for (const file of host.readDirectory(basePath, flatten(supportedExtensionsWithJsonIfResolveJsonModule), validatedExcludeSpecs, validatedIncludeSpecs, /*depth*/ undefined)) { if (fileExtensionIs(file, Extension.Json)) { // Valid only if *.json specified if (!jsonOnlyIncludeRegexes) { @@ -3440,7 +3509,7 @@ namespace ts { ? WatchDirectoryFlags.Recursive : WatchDirectoryFlags.None }; } - if (isImplicitGlob(spec)) { + if (isImplicitGlob(spec.substring(spec.lastIndexOf(directorySeparator) + 1))) { return { key: useCaseSensitiveFileNames ? spec : toFileNameLowerCase(spec), flags: WatchDirectoryFlags.Recursive @@ -3454,16 +3523,24 @@ namespace ts { * extension priority. * * @param file The path to the file. - * @param extensionPriority The priority of the extension. - * @param context The expansion context. */ - function hasFileWithHigherPriorityExtension(file: string, literalFiles: ESMap, wildcardFiles: ESMap, extensions: readonly string[], keyMapper: (value: string) => string) { - const extensionPriority = getExtensionPriority(file, extensions); - const adjustedExtensionPriority = adjustExtensionPriority(extensionPriority, extensions); - for (let i = ExtensionPriority.Highest; i < adjustedExtensionPriority; i++) { - const higherPriorityExtension = extensions[i]; - const higherPriorityPath = keyMapper(changeExtension(file, higherPriorityExtension)); + function hasFileWithHigherPriorityExtension(file: string, literalFiles: ESMap, wildcardFiles: ESMap, extensions: readonly string[][], keyMapper: (value: string) => string) { + const extensionGroup = forEach(extensions, group => fileExtensionIsOneOf(file, group) ? group : undefined); + if (!extensionGroup) { + return false; + } + for (const ext of extensionGroup) { + if (fileExtensionIs(file, ext)) { + return false; + } + const higherPriorityPath = keyMapper(changeExtension(file, ext)); if (literalFiles.has(higherPriorityPath) || wildcardFiles.has(higherPriorityPath)) { + if (ext === Extension.Dts && (fileExtensionIs(file, Extension.Js) || fileExtensionIs(file, Extension.Jsx))) { + // LEGACY BEHAVIOR: An off-by-one bug somewhere in the extension priority system for wildcard module loading allowed declaration + // files to be loaded alongside their js(x) counterparts. We regard this as generally undesirable, but retain the behavior to + // prevent breakage. + continue; + } return true; } } @@ -3476,15 +3553,18 @@ namespace ts { * already been included. * * @param file The path to the file. - * @param extensionPriority The priority of the extension. - * @param context The expansion context. */ - function removeWildcardFilesWithLowerPriorityExtension(file: string, wildcardFiles: ESMap, extensions: readonly string[], keyMapper: (value: string) => string) { - const extensionPriority = getExtensionPriority(file, extensions); - const nextExtensionPriority = getNextLowestExtensionPriority(extensionPriority, extensions); - for (let i = nextExtensionPriority; i < extensions.length; i++) { - const lowerPriorityExtension = extensions[i]; - const lowerPriorityPath = keyMapper(changeExtension(file, lowerPriorityExtension)); + function removeWildcardFilesWithLowerPriorityExtension(file: string, wildcardFiles: ESMap, extensions: readonly string[][], keyMapper: (value: string) => string) { + const extensionGroup = forEach(extensions, group => fileExtensionIsOneOf(file, group) ? group : undefined); + if (!extensionGroup) { + return; + } + for (let i = extensionGroup.length - 1; i >= 0; i--) { + const ext = extensionGroup[i]; + if (fileExtensionIs(file, ext)) { + return; + } + const lowerPriorityPath = keyMapper(changeExtension(file, ext)); wildcardFiles.delete(lowerPriorityPath); } } @@ -3528,4 +3608,24 @@ namespace ts { })!; // TODO: GH#18217 } } + + + function getDefaultValueForOption(option: CommandLineOption) { + switch (option.type) { + case "number": + return 1; + case "boolean": + return true; + case "string": + return option.isFilePath ? "./" : ""; + case "list": + return []; + case "object": + return {}; + default: + const iterResult = option.type.keys().next(); + if (!iterResult.done) return iterResult.value; + return Debug.fail("Expected 'option.type' to have entries."); + } + } } diff --git a/src/compiler/core.ts b/src/compiler/core.ts index eb07a11cbac..cceaa2abd50 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1,25 +1,5 @@ /* @internal */ namespace ts { - type GetIteratorCallback = | ReadonlyESMap | undefined>(iterable: I) => Iterator< - I extends ReadonlyESMap ? [K, V] : - I extends ReadonlySet ? T : - I extends readonly (infer T)[] ? T : - I extends undefined ? undefined : - never>; - - function getCollectionImplementation< - K1 extends MatchingKeys any>, - K2 extends MatchingKeys ReturnType<(typeof NativeCollections)[K1]>> - >(name: string, nativeFactory: K1, shimFactory: K2): NonNullable> { - // NOTE: ts.ShimCollections will be defined for typescriptServices.js but not for tsc.js, so we must test for it. - const constructor = NativeCollections[nativeFactory]() ?? ShimCollections?.[shimFactory](getIterator); - if (constructor) return constructor as NonNullable>; - throw new Error(`TypeScript requires an environment that provides a compatible native ${name} implementation.`); - } - - export const Map = getCollectionImplementation("Map", "tryGetNativeMap", "createMapShim"); - export const Set = getCollectionImplementation("Set", "tryGetNativeSet", "createSetShim"); - export function getIterator | ReadonlyESMap | undefined>(iterable: I): Iterator< I extends ReadonlyESMap ? [K, V] : I extends ReadonlySet ? T : @@ -43,34 +23,6 @@ namespace ts { export const emptyMap: ReadonlyESMap = new Map(); export const emptySet: ReadonlySet = new Set(); - /** - * Create a new map. - * @deprecated Use `new Map()` instead. - */ - export function createMap(): ESMap; - export function createMap(): ESMap; - export function createMap(): ESMap { - return new Map(); - } - - /** - * Create a new map from a template object is provided, the map will copy entries from it. - * @deprecated Use `new Map(getEntries(template))` instead. - */ - export function createMapFromTemplate(template: MapLike): ESMap { - const map: ESMap = new Map(); - - // Copies keys/values from template. Note that for..in will not throw if - // template is undefined, and instead will just exit the loop. - for (const key in template) { - if (hasOwnProperty.call(template, key)) { - map.set(key, template[key]); - } - } - - return map; - } - export function length(array: readonly any[] | undefined): number { return array ? array.length : 0; } @@ -818,7 +770,11 @@ namespace ts { return deduplicated as any as SortedReadonlyArray; } - export function insertSorted(array: SortedArray, insert: T, compare: Comparer): void { + export function createSortedArray(): SortedArray { + return [] as any as SortedArray; // TODO: GH#19873 + } + + export function insertSorted(array: SortedArray, insert: T, compare: Comparer, allowDuplicates?: boolean): void { if (array.length === 0) { array.push(insert); return; @@ -828,6 +784,9 @@ namespace ts { if (insertIndex < 0) { array.splice(~insertIndex, 0, insert); } + else if (allowDuplicates) { + array.splice(insertIndex, 0, insert); + } } export function sortAndDeduplicate(array: readonly string[]): SortedReadonlyArray; @@ -1293,11 +1252,11 @@ namespace ts { return result; } - export function getOwnValues(sparseArray: T[]): T[] { + export function getOwnValues(collection: MapLike | T[]): T[] { const values: T[] = []; - for (const key in sparseArray) { - if (hasOwnProperty.call(sparseArray, key)) { - values.push(sparseArray[key]); + for (const key in collection) { + if (hasOwnProperty.call(collection, key)) { + values.push((collection as MapLike)[key]); } } diff --git a/src/compiler/corePublic.ts b/src/compiler/corePublic.ts index 58d53aec456..1d84b6726e6 100644 --- a/src/compiler/corePublic.ts +++ b/src/compiler/corePublic.ts @@ -1,7 +1,7 @@ namespace ts { // WARNING: The script `configurePrerelease.ts` uses a regexp to parse out these values. // If changing the text in this section, be sure to test `configurePrerelease` too. - export const versionMajorMinor = "4.5"; + export const versionMajorMinor = "4.7"; // The following is baselined as a literal template type without intervention /** The version of the TypeScript compiler release */ // eslint-disable-next-line @typescript-eslint/no-inferrable-types @@ -113,17 +113,22 @@ namespace ts { } /* @internal */ - export namespace NativeCollections { - declare const Map: MapConstructor | undefined; - declare const Set: SetConstructor | undefined; + namespace NativeCollections { + declare const self: any; + + const globals = typeof globalThis !== "undefined" ? globalThis : + typeof global !== "undefined" ? global : + typeof self !== "undefined" ? self : + undefined; /** * Returns the native Map implementation if it is available and compatible (i.e. supports iteration). */ export function tryGetNativeMap(): MapConstructor | undefined { // Internet Explorer's Map doesn't support iteration, so don't use it. + const gMap = globals?.Map; // eslint-disable-next-line no-in-operator - return typeof Map !== "undefined" && "entries" in Map.prototype && new Map([[0, 0]]).size === 1 ? Map : undefined; + return typeof gMap !== "undefined" && "entries" in gMap.prototype && new gMap([[0, 0]]).size === 1 ? gMap : undefined; } /** @@ -131,8 +136,33 @@ namespace ts { */ export function tryGetNativeSet(): SetConstructor | undefined { // Internet Explorer's Set doesn't support iteration, so don't use it. + const gSet = globals?.Set; // eslint-disable-next-line no-in-operator - return typeof Set !== "undefined" && "entries" in Set.prototype && new Set([0]).size === 1 ? Set : undefined; + return typeof gSet !== "undefined" && "entries" in gSet.prototype && new gSet([0]).size === 1 ? gSet : undefined; } } -} \ No newline at end of file + + /* @internal */ + export const Map = getCollectionImplementation("Map", "tryGetNativeMap", "createMapShim"); + /* @internal */ + export const Set = getCollectionImplementation("Set", "tryGetNativeSet", "createSetShim"); + + /* @internal */ + type GetIteratorCallback = | ReadonlyESMap | undefined>(iterable: I) => Iterator< + I extends ReadonlyESMap ? [K, V] : + I extends ReadonlySet ? T : + I extends readonly (infer T)[] ? T : + I extends undefined ? undefined : + never>; + + /* @internal */ + function getCollectionImplementation< + K1 extends MatchingKeys any>, + K2 extends MatchingKeys ReturnType<(typeof NativeCollections)[K1]>> + >(name: string, nativeFactory: K1, shimFactory: K2): NonNullable> { + // NOTE: ts.ShimCollections will be defined for typescriptServices.js but not for tsc.js, so we must test for it. + const constructor = NativeCollections[nativeFactory]() ?? ShimCollections?.[shimFactory](getIterator); + if (constructor) return constructor as NonNullable>; + throw new Error(`TypeScript requires an environment that provides a compatible native ${name} implementation.`); + } +} diff --git a/src/compiler/debug.ts b/src/compiler/debug.ts index 2e6b87a30e6..36a402b3340 100644 --- a/src/compiler/debug.ts +++ b/src/compiler/debug.ts @@ -171,12 +171,6 @@ namespace ts { return value; } - /** - * @deprecated Use `checkDefined` to check whether a value is defined inline. Use `assertIsDefined` to check whether - * a value is defined at the statement level. - */ - export const assertDefined = checkDefined; - export function assertEachIsDefined(value: NodeArray, message?: string, stackCrawlMark?: AnyFunction): asserts value is NodeArray; export function assertEachIsDefined(value: readonly T[], message?: string, stackCrawlMark?: AnyFunction): asserts value is readonly NonNullable[]; export function assertEachIsDefined(value: readonly T[], message?: string, stackCrawlMark?: AnyFunction) { @@ -190,12 +184,6 @@ namespace ts { return value; } - /** - * @deprecated Use `checkEachDefined` to check whether the elements of an array are defined inline. Use `assertEachIsDefined` to check whether - * the elements of an array are defined at the statement level. - */ - export const assertEachDefined = checkEachDefined; - export function assertNever(member: never, message = "Illegal value:", stackCrawlMark?: AnyFunction): never { const detail = typeof member === "object" && hasProperty(member, "kind") && hasProperty(member, "pos") && formatSyntaxKind ? "SyntaxKind: " + formatSyntaxKind((member as Node).kind) : JSON.stringify(member); return fail(`${message} ${detail}`, stackCrawlMark || assertNever); @@ -351,6 +339,10 @@ namespace ts { return formatEnum(kind, (ts as any).SyntaxKind, /*isFlags*/ false); } + export function formatSnippetKind(kind: SnippetKind | undefined): string { + return formatEnum(kind, (ts as any).SnippetKind, /*isFlags*/ false); + } + export function formatNodeFlags(flags: NodeFlags | undefined): string { return formatEnum(flags, (ts as any).NodeFlags, /*isFlags*/ true); } @@ -662,7 +654,7 @@ namespace ts { if (text === undefined) { const parseNode = getParseTreeNode(this); const sourceFile = parseNode && getSourceFileOfNode(parseNode); - text = sourceFile ? getSourceTextOfNodeFromSourceFile(sourceFile, parseNode!, includeTrivia) : ""; + text = sourceFile ? getSourceTextOfNodeFromSourceFile(sourceFile, parseNode, includeTrivia) : ""; map?.set(this, text); } return text; diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index f29fde71ca0..0418b54a2ed 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -335,7 +335,7 @@ "category": "Error", "code": 1116 }, - "An object literal cannot have multiple properties with the same name in strict mode.": { + "An object literal cannot have multiple properties with the same name.": { "category": "Error", "code": 1117 }, @@ -727,11 +727,11 @@ "category": "Error", "code": 1231 }, - "An import declaration can only be used in a namespace or module.": { + "An import declaration can only be used at the top level of a namespace or module.": { "category": "Error", "code": 1232 }, - "An export declaration can only be used in a module.": { + "An export declaration can only be used at the top level of a namespace or module.": { "category": "Error", "code": 1233 }, @@ -739,7 +739,7 @@ "category": "Error", "code": 1234 }, - "A namespace declaration is only allowed in a namespace or module.": { + "A namespace declaration is only allowed at the top level of a namespace or module.": { "category": "Error", "code": 1235 }, @@ -867,6 +867,18 @@ "category": "Error", "code": 1268 }, + "Cannot use 'export import' on a type or type-only namespace when the '--isolatedModules' flag is provided.": { + "category": "Error", + "code": 1269 + }, + "Decorator function return type '{0}' is not assignable to type '{1}'.": { + "category": "Error", + "code": 1270 + }, + "Decorator function return type is '{0}' but is expected to be 'void' or 'any'.": { + "category": "Error", + "code": 1271 + }, "'with' statements are not allowed in an async function block.": { "category": "Error", @@ -920,15 +932,15 @@ "category": "Error", "code": 1322 }, - "Dynamic imports are only supported when the '--module' flag is set to 'es2020', 'esnext', 'commonjs', 'amd', 'system', or 'umd'.": { + "Dynamic imports are only supported when the '--module' flag is set to 'es2020', 'es2022', 'esnext', 'commonjs', 'amd', 'system', 'umd', 'node12', or 'nodenext'.": { "category": "Error", "code": 1323 }, - "Dynamic import must have one specifier as an argument.": { + "Dynamic imports only support a second argument when the '--module' option is set to 'esnext' or 'nodenext'.": { "category": "Error", "code": 1324 }, - "Specifier of dynamic import cannot be spread element.": { + "Argument of dynamic import cannot be spread element.": { "category": "Error", "code": 1325 }, @@ -992,7 +1004,7 @@ "category": "Error", "code": 1342 }, - "The 'import.meta' meta-property is only allowed when the '--module' option is 'es2020', 'esnext', or 'system'.": { + "The 'import.meta' meta-property is only allowed when the '--module' option is 'es2020', 'es2022', 'esnext', 'system', 'node12', or 'nodenext'.": { "category": "Error", "code": 1343 }, @@ -1116,7 +1128,7 @@ "category": "Message", "code": 1377 }, - "Top-level 'await' expressions are only allowed when the 'module' option is set to 'esnext' or 'system', and the 'target' option is set to 'es2017' or higher.": { + "Top-level 'await' expressions are only allowed when the 'module' option is set to 'es2022', 'esnext', 'system', or 'nodenext', and the 'target' option is set to 'es2017' or higher.": { "category": "Error", "code": 1378 }, @@ -1140,10 +1152,6 @@ "category": "Error", "code": 1383 }, - "A 'new' expression with type arguments must always be followed by a parenthesized argument list.": { - "category": "Error", - "code": 1384 - }, "Function type notation must be parenthesized when used in a union type.": { "category": "Error", "code": 1385 @@ -1164,6 +1172,10 @@ "category": "Error", "code": 1389 }, + "'{0}' is not allowed as a parameter name.": { + "category": "Error", + "code": 1390 + }, "An import alias cannot use 'import type'": { "category": "Error", "code": 1392 @@ -1324,7 +1336,7 @@ "category": "Error", "code": 1431 }, - "Top-level 'for await' loops are only allowed when the 'module' option is set to 'esnext' or 'system', and the 'target' option is set to 'es2017' or higher.": { + "Top-level 'for await' loops are only allowed when the 'module' option is set to 'es2022', 'esnext', 'system', or 'nodenext', and the 'target' option is set to 'es2017' or higher.": { "category": "Error", "code": 1432 }, @@ -1388,6 +1400,43 @@ "category": "Message", "code": 1449 }, + "Dynamic imports can only accept a module specifier and an optional assertion as arguments": { + "category": "Message", + "code": 1450 + }, + "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": { + "category": "Error", + "code": 1451 + }, + "Resolution modes are only supported when `moduleResolution` is `node12` or `nodenext`.": { + "category": "Error", + "code": 1452 + }, + "`resolution-mode` should be either `require` or `import`.": { + "category": "Error", + "code": 1453 + }, + + "The 'import.meta' meta-property is not allowed in files which will build into CommonJS output.": { + "category": "Error", + "code": 1470 + }, + "Module '{0}' cannot be imported using this construct. The specifier only resolves to an ES module, which cannot be imported synchronously. Use dynamic import instead.": { + "category": "Error", + "code": 1471 + }, + "'catch' or 'finally' expected.": { + "category": "Error", + "code": 1472 + }, + "An import declaration can only be used at the top level of a module.": { + "category": "Error", + "code": 1473 + }, + "An export declaration can only be used at the top level of a module.": { + "category": "Error", + "code": 1474 + }, "The types of '{0}' are incompatible between these types.": { "category": "Error", @@ -1417,6 +1466,14 @@ "code": 2205, "elidedInCompatabilityPyramid": true }, + "The 'type' modifier cannot be used on a named import when 'import type' is used on its import statement.": { + "category": "Error", + "code": 2206 + }, + "The 'type' modifier cannot be used on a named export when 'export type' is used on its export statement.": { + "category": "Error", + "code": 2207 + }, "Duplicate identifier '{0}'.": { "category": "Error", @@ -1462,6 +1519,10 @@ "category": "Error", "code": 2310 }, + "Cannot find name '{0}'. Did you mean to write this in an async function?": { + "category": "Error", + "code": 2311 + }, "An interface can only extend an object type or intersection of object types with statically known members.": { "category": "Error", "code": 2312 @@ -1650,7 +1711,7 @@ "category": "Error", "code": 2359 }, - "The left-hand side of an 'in' expression must be of type 'any', 'string', 'number', or 'symbol'.": { + "The left-hand side of an 'in' expression must be a private identifier or of type 'any', 'string', 'number', or 'symbol'.": { "category": "Error", "code": 2360 }, @@ -1714,7 +1775,7 @@ "category": "Error", "code": 2375 }, - "A 'super' call must be the first statement in the constructor when a class contains initialized properties, parameter properties, or private identifiers.": { + "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.": { "category": "Error", "code": 2376 }, @@ -1806,6 +1867,10 @@ "category": "Error", "code": 2400 }, + "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.": { + "category": "Error", + "code": 2401 + }, "Expression resolves to '_super' that compiler uses to capture base class reference.": { "category": "Error", "code": 2402 @@ -2634,6 +2699,10 @@ "category": "Error", "code": 2634 }, + "Type '{0}' has no signatures for which the type argument list is applicable.": { + "category": "Error", + "code": 2635 + }, "Cannot augment module '{0}' with value exports because it resolves to a non-module entity.": { "category": "Error", @@ -3240,10 +3309,6 @@ "category": "Error", "code": 2804 }, - "Static fields with private names can't have initializers when the '--useDefineForClassFields' flag is not specified with a '--target' of 'esnext'. Consider adding the '--useDefineForClassFields' flag.": { - "category": "Error", - "code": 2805 - }, "Private accessor was defined without a getter.": { "category": "Error", "code": 2806 @@ -3260,10 +3325,6 @@ "category": "Error", "code": 2809 }, - "Property '{0}' may not be used in a static property's initializer in the same class when 'target' is 'esnext' and 'useDefineForClassFields' is 'false'.": { - "category": "Error", - "code": 2810 - }, "Initializer for property '{0}'": { "category": "Error", "code": 2811 @@ -3300,6 +3361,38 @@ "category": "Error", "code": 2819 }, + "Type '{0}' is not assignable to type '{1}'. Did you mean '{2}'?": { + "category": "Error", + "code": 2820 + }, + "Import assertions are only supported when the '--module' option is set to 'esnext' or 'nodenext'.": { + "category": "Error", + "code": 2821 + }, + "Import assertions cannot be used with type-only imports or exports.": { + "category": "Error", + "code": 2822 + }, + "Cannot find namespace '{0}'. Did you mean '{1}'?": { + "category": "Error", + "code": 2833 + }, + "Relative import paths need explicit file extensions in EcmaScript imports when '--moduleResolution' is 'node12' or 'nodenext'. Consider adding an extension to the import path.": { + "category": "Error", + "code": 2834 + }, + "Relative import paths need explicit file extensions in EcmaScript imports when '--moduleResolution' is 'node12' or 'nodenext'. Did you mean '{0}'?": { + "category": "Error", + "code": 2835 + }, + "Import assertions are not allowed on statements that transpile to commonjs 'require' calls.": { + "category": "Error", + "code": 2836 + }, + "Import assertion values must be string literal expressions.": { + "category": "Error", + "code": 2837 + }, "Import declaration '{0}' is using private name '{1}'.": { "category": "Error", @@ -3705,6 +3798,30 @@ "category": "Error", "code": 4118 }, + "This member must have a JSDoc comment with an '@override' tag because it overrides a member in the base class '{0}'.": { + "category": "Error", + "code": 4119 + }, + "This parameter property must have a JSDoc comment with an '@override' tag because it overrides a member in the base class '{0}'.": { + "category": "Error", + "code": 4120 + }, + "This member cannot have a JSDoc comment with an '@override' tag because its containing class '{0}' does not extend another class.": { + "category": "Error", + "code": 4121 + }, + "This member cannot have a JSDoc comment with an '@override' tag because it is not declared in the base class '{0}'.": { + "category": "Error", + "code": 4122 + }, + "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}'?": { + "category": "Error", + "code": 4123 + }, + "Compiler option '{0}' of value '{1}' is unstable. Use nightly TypeScript to silence this error. Try updating with 'npm install -D typescript@next'.": { + "category": "Error", + "code": 4124 + }, "The current host does not support the '{0}' option.": { "category": "Error", @@ -3943,10 +4060,6 @@ "category": "Message", "code": 6002 }, - "Specify the location where debugger should locate map files instead of generated locations.": { - "category": "Message", - "code": 6003 - }, "Specify the location where debugger should locate TypeScript files instead of source locations.": { "category": "Message", "code": 6004 @@ -4075,6 +4188,11 @@ "category": "Message", "code": 6040 }, + "Errors Files": { + "_locale_notes": "There is a double space, and the order cannot be changed (they're table headings) ^", + "category": "Message", + "code": 6041 + }, "Generates corresponding '.map' file.": { "category": "Message", "code": 6043 @@ -4513,10 +4631,6 @@ "category": "Message", "code": 6163 }, - "Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files.": { - "category": "Message", - "code": 6164 - }, "Do not truncate error messages.": { "category": "Message", "code": 6165 @@ -4858,6 +4972,47 @@ "category": "Error", "code": 6258 }, + "Found 1 error in {1}": { + "category": "Message", + "code": 6259 + }, + "Found {0} errors in the same file, starting at: {1}": { + "category": "Message", + "code": 6260 + }, + "Found {0} errors in {1} files.": { + "category": "Message", + "code": 6261 + }, + + "Directory '{0}' has no containing package.json scope. Imports will not resolve.": { + "category": "Message", + "code": 6270 + }, + "Import specifier '{0}' does not exist in package.json scope at path '{1}'.": { + "category": "Message", + "code": 6271 + }, + "Invalid import specifier '{0}' has no possible resolutions.": { + "category": "Message", + "code": 6272 + }, + "package.json scope '{0}' has no imports defined.": { + "category": "Message", + "code": 6273 + }, + "package.json scope '{0}' explicitly maps specifier '{1}' to null.": { + "category": "Message", + "code": 6274 + }, + "package.json scope '{0}' has invalid type for target of specifier '{1}'": { + "category": "Message", + "code": 6275 + }, + "Export specifier '{0}' does not exist in package.json scope at path '{1}'.": { + "category": "Message", + "code": 6276 + }, "Enable project compilation": { "category": "Message", @@ -5904,7 +6059,22 @@ "category": "Error", "code": 7058 }, - + "This syntax is reserved in files with the .mts or .cts extension. Use an `as` expression instead.": { + "category": "Error", + "code": 7059 + }, + "This syntax is reserved in files with the .mts or .cts extension. Add a trailing comma or explicit constraint.": { + "category": "Error", + "code": 7060 + }, + "A mapped type may not declare properties or methods.": { + "category": "Error", + "code": 7061 + }, + "JSON imports are experimental in ES module mode imports.": { + "category": "Error", + "code": 7062 + }, "You cannot rename this element.": { "category": "Error", @@ -6127,7 +6297,7 @@ "code": 18003 }, - "File is a CommonJS module; it may be converted to an ES6 module.": { + "File is a CommonJS module; it may be converted to an ES module.": { "category": "Suggestion", "code": 80001 }, @@ -6204,7 +6374,7 @@ "category": "Message", "code": 90012 }, - "Import '{0}' from module \"{1}\"": { + "Import '{0}' from \"{1}\"": { "category": "Message", "code": 90013 }, @@ -6212,10 +6382,6 @@ "category": "Message", "code": 90014 }, - "Add '{0}' to existing import declaration from \"{1}\"": { - "category": "Message", - "code": 90015 - }, "Declare property '{0}'": { "category": "Message", "code": 90016 @@ -6280,14 +6446,6 @@ "category": "Message", "code": 90031 }, - "Import default '{0}' from module \"{1}\"": { - "category": "Message", - "code": 90032 - }, - "Add default import '{0}' to existing import declaration from \"{1}\"": { - "category": "Message", - "code": 90033 - }, "Add parameter name": { "category": "Message", "code": 90034 @@ -6320,6 +6478,27 @@ "category": "Message", "code": 90053 }, + "Includes imports of types referenced by '{0}'": { + "category": "Message", + "code": 90054 + }, + "Remove 'type' from import declaration from \"{0}\"": { + "category": "Message", + "code": 90055 + }, + "Remove 'type' from import of '{0}' from \"{1}\"": { + "category": "Message", + "code": 90056 + }, + "Add import from \"{0}\"": { + "category": "Message", + "code": 90057 + }, + "Update import from \"{0}\"": { + "category": "Message", + "code": 90058 + }, + "Convert function to an ES2015 class": { "category": "Message", "code": 95001 @@ -6376,7 +6555,7 @@ "category": "Message", "code": 95016 }, - "Convert to ES6 module": { + "Convert to ES module": { "category": "Message", "code": 95017 }, @@ -6972,6 +7151,22 @@ "category": "Message", "code": 95169 }, + "Convert named imports to default import": { + "category": "Message", + "code": 95170 + }, + "Delete unused '@param' tag '{0}'": { + "category": "Message", + "code": 95171 + }, + "Delete all unused '@param' tags": { + "category": "Message", + "code": 95172 + }, + "Rename '@param' tag name '{0}' to '{1}'": { + "category": "Message", + "code": 95173 + }, "No value exists in scope for the shorthand property '{0}'. Either declare one or provide an initializer.": { "category": "Error", diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 5667e792b46..9e5b39bd7a8 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -89,7 +89,7 @@ namespace ts { return getOutputPathsForBundle(options, forceDtsPaths); } else { - const ownOutputFilePath = getOwnEmitOutputFilePath(sourceFile.fileName, host, getOutputExtension(sourceFile, options)); + const ownOutputFilePath = getOwnEmitOutputFilePath(sourceFile.fileName, host, getOutputExtension(sourceFile.fileName, options)); const isJsonFile = isJsonSourceFile(sourceFile); // If json file emits to the same location skip writing it, if emitDeclarationOnly skip writing it const isJsonEmittedToSameLocation = isJsonFile && @@ -106,27 +106,13 @@ namespace ts { return (options.sourceMap && !options.inlineSourceMap) ? jsFilePath + ".map" : undefined; } - // JavaScript files are always LanguageVariant.JSX, as JSX syntax is allowed in .js files also. - // So for JavaScript files, '.jsx' is only emitted if the input was '.jsx', and JsxEmit.Preserve. - // For TypeScript, the only time to emit with a '.jsx' extension, is on JSX input, and JsxEmit.Preserve /* @internal */ - export function getOutputExtension(sourceFile: SourceFile, options: CompilerOptions): Extension { - if (isJsonSourceFile(sourceFile)) { - return Extension.Json; - } - - if (options.jsx === JsxEmit.Preserve) { - if (isSourceFileJS(sourceFile)) { - if (fileExtensionIs(sourceFile.fileName, Extension.Jsx)) { - return Extension.Jsx; - } - } - else if (sourceFile.languageVariant === LanguageVariant.JSX) { - // TypeScript source file preserving JSX syntax - return Extension.Jsx; - } - } - return Extension.Js; + export function getOutputExtension(fileName: string, options: CompilerOptions): Extension { + return fileExtensionIs(fileName, Extension.Json) ? Extension.Json : + options.jsx === JsxEmit.Preserve && fileExtensionIsOneOf(fileName, [Extension.Jsx, Extension.Tsx]) ? Extension.Jsx : + fileExtensionIsOneOf(fileName, [Extension.Mts, Extension.Mjs]) ? Extension.Mjs : + fileExtensionIsOneOf(fileName, [Extension.Cts, Extension.Cjs]) ? Extension.Cjs : + Extension.Js; } function getOutputPathWithoutChangingExt(inputFileName: string, configFile: ParsedCommandLine, ignoreCase: boolean, outputDir: string | undefined, getCommonSourceDirectory?: () => string) { @@ -140,10 +126,9 @@ namespace ts { /* @internal */ export function getOutputDeclarationFileName(inputFileName: string, configFile: ParsedCommandLine, ignoreCase: boolean, getCommonSourceDirectory?: () => string) { - Debug.assert(!fileExtensionIs(inputFileName, Extension.Dts) && !fileExtensionIs(inputFileName, Extension.Json)); return changeExtension( getOutputPathWithoutChangingExt(inputFileName, configFile, ignoreCase, configFile.options.declarationDir || configFile.options.outDir, getCommonSourceDirectory), - Extension.Dts + getDeclarationEmitExtensionForPath(inputFileName) ); } @@ -152,11 +137,7 @@ namespace ts { const isJsonFile = fileExtensionIs(inputFileName, Extension.Json); const outputFileName = changeExtension( getOutputPathWithoutChangingExt(inputFileName, configFile, ignoreCase, configFile.options.outDir, getCommonSourceDirectory), - isJsonFile ? - Extension.Json : - configFile.options.jsx === JsxEmit.Preserve && (fileExtensionIs(inputFileName, Extension.Tsx) || fileExtensionIs(inputFileName, Extension.Jsx)) ? - Extension.Jsx : - Extension.Js + getOutputExtension(inputFileName, configFile.options) ); return !isJsonFile || comparePaths(inputFileName, outputFileName, Debug.checkDefined(configFile.options.configFilePath), ignoreCase) !== Comparison.EqualTo ? outputFileName : @@ -238,7 +219,7 @@ namespace ts { export function getCommonSourceDirectoryOfConfig({ options, fileNames }: ParsedCommandLine, ignoreCase: boolean): string { return getCommonSourceDirectory( options, - () => filter(fileNames, file => !(options.noEmitForJsFiles && fileExtensionIsOneOf(file, supportedJSExtensions)) && !fileExtensionIs(file, Extension.Dts)), + () => filter(fileNames, file => !(options.noEmitForJsFiles && fileExtensionIsOneOf(file, supportedJSExtensionsFlat)) && !fileExtensionIs(file, Extension.Dts)), getDirectoryPath(normalizeSlashes(Debug.checkDefined(options.configFilePath))), createGetCanonicalFileName(!ignoreCase) ); @@ -1070,7 +1051,7 @@ namespace ts { writeLine(); const pos = writer.getTextPos(); const savedSections = bundleFileInfo && bundleFileInfo.sections; - if (savedSections) bundleFileInfo!.sections = []; + if (savedSections) bundleFileInfo.sections = []; print(EmitHint.Unspecified, prepend, /*sourceFile*/ undefined); if (bundleFileInfo) { const newSections = bundleFileInfo.sections; @@ -1302,7 +1283,13 @@ namespace ts { currentParenthesizerRule = undefined; } - function pipelineEmitWithHintWorker(hint: EmitHint, node: Node): void { + function pipelineEmitWithHintWorker(hint: EmitHint, node: Node, allowSnippets = true): void { + if (allowSnippets) { + const snippet = getSnippetElement(node); + if (snippet) { + return emitSnippetNode(hint, node, snippet); + } + } if (hint === EmitHint.SourceFile) return emitSourceFile(cast(node, isSourceFile)); if (hint === EmitHint.IdentifierName) return emitIdentifier(cast(node, isIdentifier)); if (hint === EmitHint.JsxAttributeValue) return emitLiteral(cast(node, isStringLiteral), /*jsxAttributeEscape*/ true); @@ -1515,6 +1502,10 @@ namespace ts { return emitNamedExports(node as NamedExports); case SyntaxKind.ExportSpecifier: return emitExportSpecifier(node as ExportSpecifier); + case SyntaxKind.AssertClause: + return emitAssertClause(node as AssertClause); + case SyntaxKind.AssertEntry: + return emitAssertEntry(node as AssertEntry); case SyntaxKind.MissingDeclaration: return; @@ -1613,6 +1604,7 @@ namespace ts { return emitJSDocSignature(node as JSDocSignature); case SyntaxKind.JSDocTag: case SyntaxKind.JSDocClassTag: + case SyntaxKind.JSDocOverrideTag: return emitJSDocSimpleTag(node as JSDocTag); case SyntaxKind.JSDocAugmentsTag: case SyntaxKind.JSDocImplementsTag: @@ -1625,7 +1617,6 @@ namespace ts { case SyntaxKind.JSDocPrivateTag: case SyntaxKind.JSDocProtectedTag: case SyntaxKind.JSDocReadonlyTag: - case SyntaxKind.JSDocOverrideTag: return; case SyntaxKind.JSDocCallbackTag: return emitJSDocCallbackTag(node as JSDocCallbackTag); @@ -1680,6 +1671,8 @@ namespace ts { // Identifiers case SyntaxKind.Identifier: return emitIdentifier(node as Identifier); + case SyntaxKind.PrivateIdentifier: + return emitPrivateIdentifier(node as PrivateIdentifier); // Expressions case SyntaxKind.ArrayLiteralExpression: @@ -1734,6 +1727,8 @@ namespace ts { return emitAsExpression(node as AsExpression); case SyntaxKind.NonNullExpression: return emitNonNullExpression(node as NonNullExpression); + case SyntaxKind.ExpressionWithTypeArguments: + return emitExpressionWithTypeArguments(node as ExpressionWithTypeArguments); case SyntaxKind.MetaProperty: return emitMetaProperty(node as MetaProperty); case SyntaxKind.SyntheticExpression: @@ -1937,6 +1932,37 @@ namespace ts { } } + // + // Snippet Elements + // + + function emitSnippetNode(hint: EmitHint, node: Node, snippet: SnippetElement) { + switch (snippet.kind) { + case SnippetKind.Placeholder: + emitPlaceholder(hint, node, snippet); + break; + case SnippetKind.TabStop: + emitTabStop(hint, node, snippet); + break; + } + } + + function emitPlaceholder(hint: EmitHint, node: Node, snippet: Placeholder) { + nonEscapingWrite(`\$\{${snippet.order}:`); // `${2:` + pipelineEmitWithHintWorker(hint, node, /*allowSnippets*/ false); // `...` + nonEscapingWrite(`\}`); // `}` + // `${2:...}` + } + + function emitTabStop(hint: EmitHint, node: Node, snippet: TabStop) { + // A tab stop should only be attached to an empty node, i.e. a node that doesn't emit any text. + Debug.assert(node.kind === SyntaxKind.EmptyStatement, + `A tab stop cannot be attached to a node of kind ${Debug.formatSyntaxKind(node.kind)}.`); + Debug.assert(hint !== EmitHint.EmbeddedStatement, + `A tab stop cannot be attached to an embedded statement.`); + nonEscapingWrite(`\$${snippet.order}`); + } + // // Identifiers // @@ -2203,6 +2229,7 @@ namespace ts { writeKeyword("typeof"); writeSpace(); emit(node.exprName); + emitTypeArguments(node, node.typeArguments); } function emitTypeLiteral(node: TypeLiteralNode) { @@ -2747,7 +2774,7 @@ namespace ts { function emitYieldExpression(node: YieldExpression) { emitTokenWithComment(SyntaxKind.YieldKeyword, node.pos, writeKeyword, node); emit(node.asteriskToken); - emitExpressionWithLeadingSpace(node.expression, parenthesizer.parenthesizeExpressionForDisallowedComma); + emitExpressionWithLeadingSpace(node.expression && parenthesizeExpressionForNoAsi(node.expression), parenthesizeExpressionForNoAsiAndDisallowedComma); } function emitSpreadElement(node: SpreadElement) { @@ -2971,9 +2998,49 @@ namespace ts { return pos; } + function commentWillEmitNewLine(node: CommentRange) { + return node.kind === SyntaxKind.SingleLineCommentTrivia || !!node.hasTrailingNewLine; + } + + function willEmitLeadingNewLine(node: Expression): boolean { + if (!currentSourceFile) return false; + if (some(getLeadingCommentRanges(currentSourceFile.text, node.pos), commentWillEmitNewLine)) return true; + if (some(getSyntheticLeadingComments(node), commentWillEmitNewLine)) return true; + if (isPartiallyEmittedExpression(node)) { + if (node.pos !== node.expression.pos) { + if (some(getTrailingCommentRanges(currentSourceFile.text, node.expression.pos), commentWillEmitNewLine)) return true; + } + return willEmitLeadingNewLine(node.expression); + } + return false; + } + + /** + * Wraps an expression in parens if we would emit a leading comment that would introduce a line separator + * between the node and its parent. + */ + function parenthesizeExpressionForNoAsi(node: Expression) { + if (!commentsDisabled && isPartiallyEmittedExpression(node) && willEmitLeadingNewLine(node)) { + const parseNode = getParseTreeNode(node); + if (parseNode && isParenthesizedExpression(parseNode)) { + // If the original node was a parenthesized expression, restore it to preserve comment and source map emit + const parens = factory.createParenthesizedExpression(node.expression); + setOriginalNode(parens, node); + setTextRange(parens, parseNode); + return parens; + } + return factory.createParenthesizedExpression(node); + } + return node; + } + + function parenthesizeExpressionForNoAsiAndDisallowedComma(node: Expression) { + return parenthesizeExpressionForNoAsi(parenthesizer.parenthesizeExpressionForDisallowedComma(node)); + } + function emitReturnStatement(node: ReturnStatement) { emitTokenWithComment(SyntaxKind.ReturnKeyword, node.pos, writeKeyword, /*contextNode*/ node); - emitExpressionWithLeadingSpace(node.expression); + emitExpressionWithLeadingSpace(node.expression && parenthesizeExpressionForNoAsi(node.expression), parenthesizeExpressionForNoAsi); writeTrailingSemicolon(); } @@ -3005,7 +3072,7 @@ namespace ts { function emitThrowStatement(node: ThrowStatement) { emitTokenWithComment(SyntaxKind.ThrowKeyword, node.pos, writeKeyword, node); - emitExpressionWithLeadingSpace(node.expression); + emitExpressionWithLeadingSpace(parenthesizeExpressionForNoAsi(node.expression), parenthesizeExpressionForNoAsi); writeTrailingSemicolon(); } @@ -3322,6 +3389,9 @@ namespace ts { writeSpace(); } emitExpression(node.moduleSpecifier); + if (node.assertClause) { + emitWithLeadingSpace(node.assertClause); + } writeTrailingSemicolon(); } @@ -3390,9 +3460,33 @@ namespace ts { writeSpace(); emitExpression(node.moduleSpecifier); } + if (node.assertClause) { + emitWithLeadingSpace(node.assertClause); + } writeTrailingSemicolon(); } + function emitAssertClause(node: AssertClause) { + emitTokenWithComment(SyntaxKind.AssertKeyword, node.pos, writeKeyword, node); + writeSpace(); + const elements = node.elements; + emitList(node, elements, ListFormat.ImportClauseEntries); + } + + function emitAssertEntry(node: AssertEntry) { + emit(node.name); + writePunctuation(":"); + writeSpace(); + + const value = node.value; + /** @see {emitPropertyAssignment} */ + if ((getEmitFlags(value) & EmitFlags.NoLeadingComments) === 0) { + const commentRange = getCommentRange(value); + emitTrailingCommentsOfPosition(commentRange.pos); + } + emit(value); + } + function emitNamespaceExportDeclaration(node: NamespaceExportDeclaration) { let nextPos = emitTokenWithComment(SyntaxKind.ExportKeyword, node.pos, writeKeyword, node); writeSpace(); @@ -3427,6 +3521,10 @@ namespace ts { } function emitImportOrExportSpecifier(node: ImportOrExportSpecifier) { + if (node.isTypeOnly) { + writeKeyword("type"); + writeSpace(); + } if (node.propertyName) { emit(node.propertyName); writeSpace(); @@ -3898,8 +3996,11 @@ namespace ts { } for (const directive of types) { const pos = writer.getTextPos(); - writeComment(`/// `); - if (bundleFileInfo) bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: BundleFileSectionKind.Type, data: directive.fileName }); + const resolutionMode = directive.resolutionMode && directive.resolutionMode !== currentSourceFile?.impliedNodeFormat + ? `resolution-mode="${directive.resolutionMode === ModuleKind.ESNext ? "import" : "require"}"` + : ""; + writeComment(`/// `); + if (bundleFileInfo) bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: !directive.resolutionMode ? BundleFileSectionKind.Type : directive.resolutionMode === ModuleKind.ESNext ? BundleFileSectionKind.TypeResolutionModeImport : BundleFileSectionKind.TypeResolutionModeRequire, data: directive.fileName }); writeLine(); } for (const directive of libs) { @@ -3924,7 +4025,14 @@ namespace ts { // Transformation nodes function emitPartiallyEmittedExpression(node: PartiallyEmittedExpression) { + const emitFlags = getEmitFlags(node); + if (!(emitFlags & EmitFlags.NoLeadingComments) && node.pos !== node.expression.pos) { + emitTrailingCommentsOfPosition(node.expression.pos); + } emitExpression(node.expression); + if (!(emitFlags & EmitFlags.NoTrailingComments) && node.end !== node.expression.end) { + emitLeadingCommentsOfPosition(node.expression.end); + } } function emitCommaList(node: CommaListExpression) { @@ -4312,10 +4420,8 @@ namespace ts { // Emit this child. previousSourceFileTextKind = recordBundleFileInternalSectionStart(child); if (shouldEmitInterveningComments) { - if (emitTrailingCommentsOfPosition) { - const commentRange = getCommentRange(child); - emitTrailingCommentsOfPosition(commentRange.pos); - } + const commentRange = getCommentRange(child); + emitTrailingCommentsOfPosition(commentRange.pos); } else { shouldEmitInterveningComments = mayEmitInterveningComments; @@ -4439,6 +4545,16 @@ namespace ts { writer.writeProperty(s); } + function nonEscapingWrite(s: string) { + // This should be defined in a snippet-escaping text writer. + if (writer.nonEscapingWrite) { + writer.nonEscapingWrite(s); + } + else { + writer.write(s); + } + } + function writeLine(count = 1) { for (let i = 0; i < count; i++) { writer.writeLine(i > 0); @@ -4683,7 +4799,7 @@ namespace ts { function writeLineSeparatorsAndIndentBefore(node: Node, parent: Node): boolean { const leadingNewlines = preserveSourceNewlines && getLeadingLineTerminatorCount(parent, [node], ListFormat.None); if (leadingNewlines) { - writeLinesAndIndent(leadingNewlines, /*writeLinesIfNotIndenting*/ false); + writeLinesAndIndent(leadingNewlines, /*writeSpaceIfNotIndenting*/ false); } return !!leadingNewlines; } diff --git a/src/compiler/factory/emitHelpers.ts b/src/compiler/factory/emitHelpers.ts index 85b00a5ef3e..85a12581965 100644 --- a/src/compiler/factory/emitHelpers.ts +++ b/src/compiler/factory/emitHelpers.ts @@ -34,6 +34,7 @@ namespace ts { // Class Fields Helpers createClassPrivateFieldGetHelper(receiver: Expression, state: Identifier, kind: PrivateIdentifierKind, f: Identifier | undefined): Expression; createClassPrivateFieldSetHelper(receiver: Expression, state: Identifier, value: Expression, kind: PrivateIdentifierKind, f: Identifier | undefined): Expression; + createClassPrivateFieldInHelper(state: Identifier, receiver: Expression): Expression; } export function createEmitHelperFactory(context: TransformationContext): EmitHelperFactory { @@ -75,6 +76,7 @@ namespace ts { // Class Fields Helpers createClassPrivateFieldGetHelper, createClassPrivateFieldSetHelper, + createClassPrivateFieldInHelper }; /** @@ -136,7 +138,7 @@ namespace ts { // ES2018 Helpers function createAssignHelper(attributesSegments: Expression[]) { - if (context.getCompilerOptions().target! >= ScriptTarget.ES2015) { + if (getEmitScriptTarget(context.getCompilerOptions()) >= ScriptTarget.ES2015) { return factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("Object"), "assign"), /*typeArguments*/ undefined, attributesSegments); @@ -395,6 +397,10 @@ namespace ts { return factory.createCallExpression(getUnscopedHelperName("__classPrivateFieldSet"), /*typeArguments*/ undefined, args); } + function createClassPrivateFieldInHelper(state: Identifier, receiver: Expression) { + context.requestEmitHelper(classPrivateFieldInHelper); + return factory.createCallExpression(getUnscopedHelperName("__classPrivateFieldIn"), /* typeArguments*/ undefined, [state, receiver]); + } } /* @internal */ @@ -777,7 +783,11 @@ namespace ts { text: ` var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; - Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; @@ -961,6 +971,29 @@ namespace ts { };` }; + /** + * Parameters: + * @param state — One of the following: + * - A WeakMap when the member is a private instance field. + * - A WeakSet when the member is a private instance method or accessor. + * - A function value that should be the undecorated class constructor when the member is a private static field, method, or accessor. + * @param receiver — The object being checked if it has the private member. + * + * Usage: + * This helper is used to transform `#field in expression` to + * `__classPrivateFieldIn(, expression)` + */ + export const classPrivateFieldInHelper: UnscopedEmitHelper = { + name: "typescript:classPrivateFieldIn", + importName: "__classPrivateFieldIn", + scoped: false, + text: ` + var __classPrivateFieldIn = (this && this.__classPrivateFieldIn) || function(state, receiver) { + if (receiver === null || (typeof receiver !== "object" && typeof receiver !== "function")) throw new TypeError("Cannot use 'in' operator on non-object"); + return typeof state === "function" ? receiver === state : state.has(receiver); + };` + }; + let allUnscopedEmitHelpers: ReadonlyESMap | undefined; export function getAllUnscopedEmitHelpers() { @@ -986,6 +1019,7 @@ namespace ts { exportStarHelper, classPrivateFieldGetHelper, classPrivateFieldSetHelper, + classPrivateFieldInHelper, createBindingHelper, setModuleDefaultHelper ], helper => helper.name)); diff --git a/src/compiler/factory/emitNode.ts b/src/compiler/factory/emitNode.ts index 8f032fd8b76..6c207994331 100644 --- a/src/compiler/factory/emitNode.ts +++ b/src/compiler/factory/emitNode.ts @@ -127,7 +127,7 @@ namespace ts { /** * Gets a custom text range to use when emitting comments. */ - export function getCommentRange(node: Node) { + export function getCommentRange(node: Node): TextRange { return node.emitNode?.commentRange ?? node; } @@ -256,6 +256,24 @@ namespace ts { } } + /** + * Gets the SnippetElement of a node. + */ + /* @internal */ + export function getSnippetElement(node: Node): SnippetElement | undefined { + return node.emitNode?.snippetElement; + } + + /** + * Sets the SnippetElement of a node. + */ + /* @internal */ + export function setSnippetElement(node: T, snippet: SnippetElement): T { + const emitNode = getOrCreateEmitNode(node); + emitNode.snippetElement = snippet; + return node; + } + /* @internal */ export function ignoreSourceNewlines(node: T): T { getOrCreateEmitNode(node).flags |= EmitFlags.IgnoreSourceNewlines; diff --git a/src/compiler/factory/nodeFactory.ts b/src/compiler/factory/nodeFactory.ts index c697a04860b..cbddf4fb324 100644 --- a/src/compiler/factory/nodeFactory.ts +++ b/src/compiler/factory/nodeFactory.ts @@ -289,6 +289,10 @@ namespace ts { updateImportDeclaration, createImportClause, updateImportClause, + createAssertClause, + updateAssertClause, + createAssertEntry, + updateAssertEntry, createNamespaceImport, updateNamespaceImport, createNamespaceExport, @@ -1071,7 +1075,7 @@ namespace ts { if (flags & ModifierFlags.Override) result.push(createModifier(SyntaxKind.OverrideKeyword)); if (flags & ModifierFlags.Readonly) result.push(createModifier(SyntaxKind.ReadonlyKeyword)); if (flags & ModifierFlags.Async) result.push(createModifier(SyntaxKind.AsyncKeyword)); - return result; + return result.length ? result : undefined; } // @@ -1835,17 +1839,19 @@ namespace ts { } // @api - function createTypeQueryNode(exprName: EntityName) { + function createTypeQueryNode(exprName: EntityName, typeArguments?: readonly TypeNode[]) { const node = createBaseNode(SyntaxKind.TypeQuery); node.exprName = exprName; + node.typeArguments = typeArguments && parenthesizerRules().parenthesizeTypeArguments(typeArguments); node.transformFlags = TransformFlags.ContainsTypeScript; return node; } // @api - function updateTypeQueryNode(node: TypeQueryNode, exprName: EntityName) { + function updateTypeQueryNode(node: TypeQueryNode, exprName: EntityName, typeArguments?: readonly TypeNode[]) { return node.exprName !== exprName - ? update(createTypeQueryNode(exprName), node) + || node.typeArguments !== typeArguments + ? update(createTypeQueryNode(exprName, typeArguments), node) : node; } @@ -2108,25 +2114,27 @@ namespace ts { } // @api - function createMappedTypeNode(readonlyToken: ReadonlyKeyword | PlusToken | MinusToken | undefined, typeParameter: TypeParameterDeclaration, nameType: TypeNode | undefined, questionToken: QuestionToken | PlusToken | MinusToken | undefined, type: TypeNode | undefined): MappedTypeNode { + function createMappedTypeNode(readonlyToken: ReadonlyKeyword | PlusToken | MinusToken | undefined, typeParameter: TypeParameterDeclaration, nameType: TypeNode | undefined, questionToken: QuestionToken | PlusToken | MinusToken | undefined, type: TypeNode | undefined, members: readonly TypeElement[] | undefined): MappedTypeNode { const node = createBaseNode(SyntaxKind.MappedType); node.readonlyToken = readonlyToken; node.typeParameter = typeParameter; node.nameType = nameType; node.questionToken = questionToken; node.type = type; + node.members = members && createNodeArray(members); node.transformFlags = TransformFlags.ContainsTypeScript; return node; } // @api - function updateMappedTypeNode(node: MappedTypeNode, readonlyToken: ReadonlyKeyword | PlusToken | MinusToken | undefined, typeParameter: TypeParameterDeclaration, nameType: TypeNode | undefined, questionToken: QuestionToken | PlusToken | MinusToken | undefined, type: TypeNode | undefined): MappedTypeNode { + function updateMappedTypeNode(node: MappedTypeNode, readonlyToken: ReadonlyKeyword | PlusToken | MinusToken | undefined, typeParameter: TypeParameterDeclaration, nameType: TypeNode | undefined, questionToken: QuestionToken | PlusToken | MinusToken | undefined, type: TypeNode | undefined, members: readonly TypeElement[] | undefined): MappedTypeNode { return node.readonlyToken !== readonlyToken || node.typeParameter !== typeParameter || node.nameType !== nameType || node.questionToken !== questionToken || node.type !== type - ? update(createMappedTypeNode(readonlyToken, typeParameter, nameType, questionToken, type), node) + || node.members !== members + ? update(createMappedTypeNode(readonlyToken, typeParameter, nameType, questionToken, type, members), node) : node; } @@ -3939,7 +3947,8 @@ namespace ts { decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, importClause: ImportClause | undefined, - moduleSpecifier: Expression + moduleSpecifier: Expression, + assertClause: AssertClause | undefined ): ImportDeclaration { const node = createBaseDeclaration( SyntaxKind.ImportDeclaration, @@ -3948,6 +3957,7 @@ namespace ts { ); node.importClause = importClause; node.moduleSpecifier = moduleSpecifier; + node.assertClause = assertClause; node.transformFlags |= propagateChildFlags(node.importClause) | propagateChildFlags(node.moduleSpecifier); @@ -3961,13 +3971,15 @@ namespace ts { decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, importClause: ImportClause | undefined, - moduleSpecifier: Expression + moduleSpecifier: Expression, + assertClause: AssertClause | undefined ) { return node.decorators !== decorators || node.modifiers !== modifiers || node.importClause !== importClause || node.moduleSpecifier !== moduleSpecifier - ? update(createImportDeclaration(decorators, modifiers, importClause, moduleSpecifier), node) + || node.assertClause !== assertClause + ? update(createImportDeclaration(decorators, modifiers, importClause, moduleSpecifier, assertClause), node) : node; } @@ -3996,6 +4008,40 @@ namespace ts { : node; } + // @api + function createAssertClause(elements: readonly AssertEntry[], multiLine?: boolean): AssertClause { + const node = createBaseNode(SyntaxKind.AssertClause); + node.elements = createNodeArray(elements); + node.multiLine = multiLine; + node.transformFlags |= TransformFlags.ContainsESNext; + return node; + } + + // @api + function updateAssertClause(node: AssertClause, elements: readonly AssertEntry[], multiLine?: boolean): AssertClause { + return node.elements !== elements + || node.multiLine !== multiLine + ? update(createAssertClause(elements, multiLine), node) + : node; + } + + // @api + function createAssertEntry(name: AssertionKey, value: Expression): AssertEntry { + const node = createBaseNode(SyntaxKind.AssertEntry); + node.name = name; + node.value = value; + node.transformFlags |= TransformFlags.ContainsESNext; + return node; + } + + // @api + function updateAssertEntry(node: AssertEntry, name: AssertionKey, value: Expression): AssertEntry { + return node.name !== name + || node.value !== value + ? update(createAssertEntry(name, value), node) + : node; + } + // @api function createNamespaceImport(name: Identifier): NamespaceImport { const node = createBaseNode(SyntaxKind.NamespaceImport); @@ -4047,8 +4093,9 @@ namespace ts { } // @api - function createImportSpecifier(propertyName: Identifier | undefined, name: Identifier) { + function createImportSpecifier(isTypeOnly: boolean, propertyName: Identifier | undefined, name: Identifier) { const node = createBaseNode(SyntaxKind.ImportSpecifier); + node.isTypeOnly = isTypeOnly; node.propertyName = propertyName; node.name = name; node.transformFlags |= @@ -4059,10 +4106,11 @@ namespace ts { } // @api - function updateImportSpecifier(node: ImportSpecifier, propertyName: Identifier | undefined, name: Identifier) { - return node.propertyName !== propertyName + function updateImportSpecifier(node: ImportSpecifier, isTypeOnly: boolean, propertyName: Identifier | undefined, name: Identifier) { + return node.isTypeOnly !== isTypeOnly + || node.propertyName !== propertyName || node.name !== name - ? update(createImportSpecifier(propertyName, name), node) + ? update(createImportSpecifier(isTypeOnly, propertyName, name), node) : node; } @@ -4107,7 +4155,8 @@ namespace ts { modifiers: readonly Modifier[] | undefined, isTypeOnly: boolean, exportClause: NamedExportBindings | undefined, - moduleSpecifier?: Expression + moduleSpecifier?: Expression, + assertClause?: AssertClause ) { const node = createBaseDeclaration( SyntaxKind.ExportDeclaration, @@ -4117,6 +4166,7 @@ namespace ts { node.isTypeOnly = isTypeOnly; node.exportClause = exportClause; node.moduleSpecifier = moduleSpecifier; + node.assertClause = assertClause; node.transformFlags |= propagateChildFlags(node.exportClause) | propagateChildFlags(node.moduleSpecifier); @@ -4131,14 +4181,16 @@ namespace ts { modifiers: readonly Modifier[] | undefined, isTypeOnly: boolean, exportClause: NamedExportBindings | undefined, - moduleSpecifier: Expression | undefined + moduleSpecifier: Expression | undefined, + assertClause: AssertClause | undefined ) { return node.decorators !== decorators || node.modifiers !== modifiers || node.isTypeOnly !== isTypeOnly || node.exportClause !== exportClause || node.moduleSpecifier !== moduleSpecifier - ? update(createExportDeclaration(decorators, modifiers, isTypeOnly, exportClause, moduleSpecifier), node) + || node.assertClause !== assertClause + ? update(createExportDeclaration(decorators, modifiers, isTypeOnly, exportClause, moduleSpecifier, assertClause), node) : node; } @@ -4159,8 +4211,9 @@ namespace ts { } // @api - function createExportSpecifier(propertyName: string | Identifier | undefined, name: string | Identifier) { + function createExportSpecifier(isTypeOnly: boolean, propertyName: string | Identifier | undefined, name: string | Identifier) { const node = createBaseNode(SyntaxKind.ExportSpecifier); + node.isTypeOnly = isTypeOnly; node.propertyName = asName(propertyName); node.name = asName(name); node.transformFlags |= @@ -4171,10 +4224,11 @@ namespace ts { } // @api - function updateExportSpecifier(node: ExportSpecifier, propertyName: Identifier | undefined, name: Identifier) { - return node.propertyName !== propertyName + function updateExportSpecifier(node: ExportSpecifier, isTypeOnly: boolean, propertyName: Identifier | undefined, name: Identifier) { + return node.isTypeOnly !== isTypeOnly + || node.propertyName !== propertyName || node.name !== name - ? update(createExportSpecifier(propertyName, name), node) + ? update(createExportSpecifier(isTypeOnly, propertyName, name), node) : node; } @@ -5129,6 +5183,7 @@ namespace ts { node.transformFlags = propagateChildrenFlags(node.statements) | propagateChildFlags(node.endOfFileToken); + node.impliedNodeFormat = source.impliedNodeFormat; return node; } @@ -5441,7 +5496,7 @@ namespace ts { /*modifiers*/ undefined, /*isTypeOnly*/ false, createNamedExports([ - createExportSpecifier(/*propertyName*/ undefined, exportName) + createExportSpecifier(/*isTypeOnly*/ false, /*propertyName*/ undefined, exportName) ]) ); } @@ -5832,7 +5887,7 @@ namespace ts { * @param visitor Optional callback used to visit any custom prologue directives. */ function copyPrologue(source: readonly Statement[], target: Push, ensureUseStrict?: boolean, visitor?: (node: Node) => VisitResult): number { - const offset = copyStandardPrologue(source, target, ensureUseStrict); + const offset = copyStandardPrologue(source, target, 0, ensureUseStrict); return copyCustomPrologue(source, target, offset, visitor); } @@ -5848,12 +5903,13 @@ namespace ts { * Copies only the standard (string-expression) prologue-directives into the target statement-array. * @param source origin statements array * @param target result statements array + * @param statementOffset The offset at which to begin the copy. * @param ensureUseStrict boolean determining whether the function need to add prologue-directives + * @returns Count of how many directive statements were copied. */ - function copyStandardPrologue(source: readonly Statement[], target: Push, ensureUseStrict?: boolean): number { + function copyStandardPrologue(source: readonly Statement[], target: Push, statementOffset = 0, ensureUseStrict?: boolean): number { Debug.assert(target.length === 0, "Prologue directives should be at the first statement in the target statements array"); let foundUseStrict = false; - let statementOffset = 0; const numStatements = source.length; while (statementOffset < numStatements) { const statement = source[statementOffset]; @@ -6027,32 +6083,36 @@ namespace ts { function updateModifiers(node: T, modifiers: readonly Modifier[] | ModifierFlags): T; function updateModifiers(node: HasModifiers, modifiers: readonly Modifier[] | ModifierFlags) { + let modifierArray; if (typeof modifiers === "number") { - modifiers = createModifiersFromModifierFlags(modifiers); + modifierArray = createModifiersFromModifierFlags(modifiers); } - return isParameter(node) ? updateParameterDeclaration(node, node.decorators, modifiers, node.dotDotDotToken, node.name, node.questionToken, node.type, node.initializer) : - isPropertySignature(node) ? updatePropertySignature(node, modifiers, node.name, node.questionToken, node.type) : - isPropertyDeclaration(node) ? updatePropertyDeclaration(node, node.decorators, modifiers, node.name, node.questionToken ?? node.exclamationToken, node.type, node.initializer) : - isMethodSignature(node) ? updateMethodSignature(node, modifiers, node.name, node.questionToken, node.typeParameters, node.parameters, node.type) : - isMethodDeclaration(node) ? updateMethodDeclaration(node, node.decorators, modifiers, node.asteriskToken, node.name, node.questionToken, node.typeParameters, node.parameters, node.type, node.body) : - isConstructorDeclaration(node) ? updateConstructorDeclaration(node, node.decorators, modifiers, node.parameters, node.body) : - isGetAccessorDeclaration(node) ? updateGetAccessorDeclaration(node, node.decorators, modifiers, node.name, node.parameters, node.type, node.body) : - isSetAccessorDeclaration(node) ? updateSetAccessorDeclaration(node, node.decorators, modifiers, node.name, node.parameters, node.body) : - isIndexSignatureDeclaration(node) ? updateIndexSignature(node, node.decorators, modifiers, node.parameters, node.type) : - isFunctionExpression(node) ? updateFunctionExpression(node, modifiers, node.asteriskToken, node.name, node.typeParameters, node.parameters, node.type, node.body) : - isArrowFunction(node) ? updateArrowFunction(node, modifiers, node.typeParameters, node.parameters, node.type, node.equalsGreaterThanToken, node.body) : - isClassExpression(node) ? updateClassExpression(node, node.decorators, modifiers, node.name, node.typeParameters, node.heritageClauses, node.members) : - isVariableStatement(node) ? updateVariableStatement(node, modifiers, node.declarationList) : - isFunctionDeclaration(node) ? updateFunctionDeclaration(node, node.decorators, modifiers, node.asteriskToken, node.name, node.typeParameters, node.parameters, node.type, node.body) : - isClassDeclaration(node) ? updateClassDeclaration(node, node.decorators, modifiers, node.name, node.typeParameters, node.heritageClauses, node.members) : - isInterfaceDeclaration(node) ? updateInterfaceDeclaration(node, node.decorators, modifiers, node.name, node.typeParameters, node.heritageClauses, node.members) : - isTypeAliasDeclaration(node) ? updateTypeAliasDeclaration(node, node.decorators, modifiers, node.name, node.typeParameters, node.type) : - isEnumDeclaration(node) ? updateEnumDeclaration(node, node.decorators, modifiers, node.name, node.members) : - isModuleDeclaration(node) ? updateModuleDeclaration(node, node.decorators, modifiers, node.name, node.body) : - isImportEqualsDeclaration(node) ? updateImportEqualsDeclaration(node, node.decorators, modifiers, node.isTypeOnly, node.name, node.moduleReference) : - isImportDeclaration(node) ? updateImportDeclaration(node, node.decorators, modifiers, node.importClause, node.moduleSpecifier) : - isExportAssignment(node) ? updateExportAssignment(node, node.decorators, modifiers, node.expression) : - isExportDeclaration(node) ? updateExportDeclaration(node, node.decorators, modifiers, node.isTypeOnly, node.exportClause, node.moduleSpecifier) : + else { + modifierArray = modifiers; + } + return isParameter(node) ? updateParameterDeclaration(node, node.decorators, modifierArray, node.dotDotDotToken, node.name, node.questionToken, node.type, node.initializer) : + isPropertySignature(node) ? updatePropertySignature(node, modifierArray, node.name, node.questionToken, node.type) : + isPropertyDeclaration(node) ? updatePropertyDeclaration(node, node.decorators, modifierArray, node.name, node.questionToken ?? node.exclamationToken, node.type, node.initializer) : + isMethodSignature(node) ? updateMethodSignature(node, modifierArray, node.name, node.questionToken, node.typeParameters, node.parameters, node.type) : + isMethodDeclaration(node) ? updateMethodDeclaration(node, node.decorators, modifierArray, node.asteriskToken, node.name, node.questionToken, node.typeParameters, node.parameters, node.type, node.body) : + isConstructorDeclaration(node) ? updateConstructorDeclaration(node, node.decorators, modifierArray, node.parameters, node.body) : + isGetAccessorDeclaration(node) ? updateGetAccessorDeclaration(node, node.decorators, modifierArray, node.name, node.parameters, node.type, node.body) : + isSetAccessorDeclaration(node) ? updateSetAccessorDeclaration(node, node.decorators, modifierArray, node.name, node.parameters, node.body) : + isIndexSignatureDeclaration(node) ? updateIndexSignature(node, node.decorators, modifierArray, node.parameters, node.type) : + isFunctionExpression(node) ? updateFunctionExpression(node, modifierArray, node.asteriskToken, node.name, node.typeParameters, node.parameters, node.type, node.body) : + isArrowFunction(node) ? updateArrowFunction(node, modifierArray, node.typeParameters, node.parameters, node.type, node.equalsGreaterThanToken, node.body) : + isClassExpression(node) ? updateClassExpression(node, node.decorators, modifierArray, node.name, node.typeParameters, node.heritageClauses, node.members) : + isVariableStatement(node) ? updateVariableStatement(node, modifierArray, node.declarationList) : + isFunctionDeclaration(node) ? updateFunctionDeclaration(node, node.decorators, modifierArray, node.asteriskToken, node.name, node.typeParameters, node.parameters, node.type, node.body) : + isClassDeclaration(node) ? updateClassDeclaration(node, node.decorators, modifierArray, node.name, node.typeParameters, node.heritageClauses, node.members) : + isInterfaceDeclaration(node) ? updateInterfaceDeclaration(node, node.decorators, modifierArray, node.name, node.typeParameters, node.heritageClauses, node.members) : + isTypeAliasDeclaration(node) ? updateTypeAliasDeclaration(node, node.decorators, modifierArray, node.name, node.typeParameters, node.type) : + isEnumDeclaration(node) ? updateEnumDeclaration(node, node.decorators, modifierArray, node.name, node.members) : + isModuleDeclaration(node) ? updateModuleDeclaration(node, node.decorators, modifierArray, node.name, node.body) : + isImportEqualsDeclaration(node) ? updateImportEqualsDeclaration(node, node.decorators, modifierArray, node.isTypeOnly, node.name, node.moduleReference) : + isImportDeclaration(node) ? updateImportDeclaration(node, node.decorators, modifierArray, node.importClause, node.moduleSpecifier, node.assertClause) : + isExportAssignment(node) ? updateExportAssignment(node, node.decorators, modifierArray, node.expression) : + isExportDeclaration(node) ? updateExportDeclaration(node, node.decorators, modifierArray, node.isTypeOnly, node.exportClause, node.moduleSpecifier, node.assertClause) : Debug.assertNever(node); } @@ -6334,7 +6394,7 @@ namespace ts { sourceMapText = mapTextOrStripInternal as string; } const node = oldFileOfCurrentEmit ? - parseOldFileOfCurrentEmit(Debug.assertDefined(bundleFileInfo)) : + parseOldFileOfCurrentEmit(Debug.checkDefined(bundleFileInfo)) : parseUnparsedSourceFile(bundleFileInfo, stripInternal, length); node.fileName = fileName; node.sourceMapPath = sourceMapPath; @@ -6356,7 +6416,7 @@ namespace ts { let prologues: UnparsedPrologue[] | undefined; let helpers: UnscopedEmitHelper[] | undefined; let referencedFiles: FileReference[] | undefined; - let typeReferenceDirectives: string[] | undefined; + let typeReferenceDirectives: FileReference[] | undefined; let libReferenceDirectives: FileReference[] | undefined; let prependChildren: UnparsedTextLike[] | undefined; let texts: UnparsedSourceText[] | undefined; @@ -6377,7 +6437,13 @@ namespace ts { referencedFiles = append(referencedFiles, { pos: -1, end: -1, fileName: section.data }); break; case BundleFileSectionKind.Type: - typeReferenceDirectives = append(typeReferenceDirectives, section.data); + typeReferenceDirectives = append(typeReferenceDirectives, { pos: -1, end: -1, fileName: section.data }); + break; + case BundleFileSectionKind.TypeResolutionModeImport: + typeReferenceDirectives = append(typeReferenceDirectives, { pos: -1, end: -1, fileName: section.data, resolutionMode: ModuleKind.ESNext }); + break; + case BundleFileSectionKind.TypeResolutionModeRequire: + typeReferenceDirectives = append(typeReferenceDirectives, { pos: -1, end: -1, fileName: section.data, resolutionMode: ModuleKind.CommonJS }); break; case BundleFileSectionKind.Lib: libReferenceDirectives = append(libReferenceDirectives, { pos: -1, end: -1, fileName: section.data }); @@ -6438,6 +6504,8 @@ namespace ts { case BundleFileSectionKind.NoDefaultLib: case BundleFileSectionKind.Reference: case BundleFileSectionKind.Type: + case BundleFileSectionKind.TypeResolutionModeImport: + case BundleFileSectionKind.TypeResolutionModeRequire: case BundleFileSectionKind.Lib: syntheticReferences = append(syntheticReferences, setTextRange(factory.createUnparsedSyntheticReference(section), section)); break; @@ -6534,13 +6602,13 @@ namespace ts { }; node.javascriptPath = declarationTextOrJavascriptPath; node.javascriptMapPath = javascriptMapPath; - node.declarationPath = Debug.assertDefined(javascriptMapTextOrDeclarationPath); + node.declarationPath = Debug.checkDefined(javascriptMapTextOrDeclarationPath); node.declarationMapPath = declarationMapPath; node.buildInfoPath = declarationMapTextOrBuildInfoPath; Object.defineProperties(node, { javascriptText: { get() { return definedTextGetter(declarationTextOrJavascriptPath); } }, javascriptMapText: { get() { return textGetter(javascriptMapPath); } }, // TODO:: if there is inline sourceMap in jsFile, use that - declarationText: { get() { return definedTextGetter(Debug.assertDefined(javascriptMapTextOrDeclarationPath)); } }, + declarationText: { get() { return definedTextGetter(Debug.checkDefined(javascriptMapTextOrDeclarationPath)); } }, declarationMapText: { get() { return textGetter(declarationMapPath); } }, // TODO:: if there is inline sourceMap in dtsFile, use that buildInfo: { get() { return getAndCacheBuildInfo(() => textGetter(declarationMapTextOrBuildInfoPath)); } } }); diff --git a/src/compiler/factory/nodeTests.ts b/src/compiler/factory/nodeTests.ts index 7f5fda1994c..274ade1886d 100644 --- a/src/compiler/factory/nodeTests.ts +++ b/src/compiler/factory/nodeTests.ts @@ -597,6 +597,14 @@ namespace ts { return node.kind === SyntaxKind.ImportClause; } + export function isAssertClause(node: Node): node is AssertClause { + return node.kind === SyntaxKind.AssertClause; + } + + export function isAssertEntry(node: Node): node is AssertEntry { + return node.kind === SyntaxKind.AssertEntry; + } + export function isNamespaceImport(node: Node): node is NamespaceImport { return node.kind === SyntaxKind.NamespaceImport; } diff --git a/src/compiler/factory/parenthesizerRules.ts b/src/compiler/factory/parenthesizerRules.ts index 782a0110aba..1e2cb936582 100644 --- a/src/compiler/factory/parenthesizerRules.ts +++ b/src/compiler/factory/parenthesizerRules.ts @@ -452,4 +452,4 @@ namespace ts { parenthesizeConstituentTypesOfUnionOrIntersectionType: nodes => cast(nodes, isNodeArray), parenthesizeTypeArguments: nodes => nodes && cast(nodes, isNodeArray), }; -} \ No newline at end of file +} diff --git a/src/compiler/factory/utilities.ts b/src/compiler/factory/utilities.ts index fdd3200d572..7daa31c5227 100644 --- a/src/compiler/factory/utilities.ts +++ b/src/compiler/factory/utilities.ts @@ -481,7 +481,7 @@ namespace ts { if (compilerOptions.importHelpers && isEffectiveExternalModule(sourceFile, compilerOptions)) { let namedBindings: NamedImportBindings | undefined; const moduleKind = getEmitModuleKind(compilerOptions); - if (moduleKind >= ModuleKind.ES2015 && moduleKind <= ModuleKind.ESNext) { + if ((moduleKind >= ModuleKind.ES2015 && moduleKind <= ModuleKind.ESNext) || sourceFile.impliedNodeFormat === ModuleKind.ESNext) { // use named imports const helpers = getEmitHelpers(sourceFile); if (helpers) { @@ -500,8 +500,8 @@ namespace ts { // NOTE: We don't need to care about global import collisions as this is a module. namedBindings = nodeFactory.createNamedImports( map(helperNames, name => isFileLevelUniqueName(sourceFile, name) - ? nodeFactory.createImportSpecifier(/*propertyName*/ undefined, nodeFactory.createIdentifier(name)) - : nodeFactory.createImportSpecifier(nodeFactory.createIdentifier(name), helperFactory.getUnscopedHelperName(name)) + ? nodeFactory.createImportSpecifier(/*isTypeOnly*/ false, /*propertyName*/ undefined, nodeFactory.createIdentifier(name)) + : nodeFactory.createImportSpecifier(/*isTypeOnly*/ false, nodeFactory.createIdentifier(name), helperFactory.getUnscopedHelperName(name)) ) ); const parseNode = getOriginalNode(sourceFile, isSourceFile); @@ -522,7 +522,8 @@ namespace ts { /*decorators*/ undefined, /*modifiers*/ undefined, nodeFactory.createImportClause(/*isTypeOnly*/ false, /*name*/ undefined, namedBindings), - nodeFactory.createStringLiteral(externalHelpersModuleNameText) + nodeFactory.createStringLiteral(externalHelpersModuleNameText), + /*assertClause*/ undefined ); addEmitFlags(externalHelpersImportDeclaration, EmitFlags.NeverApplyImportHelper); return externalHelpersImportDeclaration; @@ -538,9 +539,9 @@ namespace ts { } const moduleKind = getEmitModuleKind(compilerOptions); - let create = (hasExportStarsToExportValues || (compilerOptions.esModuleInterop && hasImportStarOrImportDefault)) + let create = (hasExportStarsToExportValues || (getESModuleInterop(compilerOptions) && hasImportStarOrImportDefault)) && moduleKind !== ModuleKind.System - && moduleKind < ModuleKind.ES2015; + && (moduleKind < ModuleKind.ES2015 || node.impliedNodeFormat === ModuleKind.CommonJS); if (!create) { const helpers = getEmitHelpers(node); if (helpers) { diff --git a/src/compiler/moduleNameResolver.ts b/src/compiler/moduleNameResolver.ts index b5f83ae0b71..c63cc791782 100644 --- a/src/compiler/moduleNameResolver.ts +++ b/src/compiler/moduleNameResolver.ts @@ -96,6 +96,7 @@ namespace ts { }; } + /*@internal*/ interface ModuleResolutionState { host: ModuleResolutionHost; compilerOptions: CompilerOptions; @@ -103,6 +104,8 @@ namespace ts { failedLookupLocations: Push; resultFromCache?: ResolvedModuleWithFailedLookupLocations; packageJsonInfoCache: PackageJsonInfoCache | undefined; + features: NodeResolutionFeatures; + conditions: string[]; } /** Just the fields that we use for module resolution. */ @@ -113,6 +116,10 @@ namespace ts { typesVersions?: MapLike>; main?: string; tsconfig?: string; + type?: string; + imports?: object; + exports?: object; + name?: string; } interface PackageJson extends PackageJsonPathFields { @@ -290,7 +297,7 @@ namespace ts { * This is possible in case if resolution is performed for directives specified via 'types' parameter. In this case initial path for secondary lookups * is assumed to be the same as root directory of the project. */ - export function resolveTypeReferenceDirective(typeReferenceDirectiveName: string, containingFile: string | undefined, options: CompilerOptions, host: ModuleResolutionHost, redirectedReference?: ResolvedProjectReference, cache?: TypeReferenceDirectiveResolutionCache): ResolvedTypeReferenceDirectiveWithFailedLookupLocations { + export function resolveTypeReferenceDirective(typeReferenceDirectiveName: string, containingFile: string | undefined, options: CompilerOptions, host: ModuleResolutionHost, redirectedReference?: ResolvedProjectReference, cache?: TypeReferenceDirectiveResolutionCache, resolutionMode?: SourceFile["impliedNodeFormat"]): ResolvedTypeReferenceDirectiveWithFailedLookupLocations { const traceEnabled = isTraceEnabled(options, host); if (redirectedReference) { options = redirectedReference.commandLine.options; @@ -298,7 +305,7 @@ namespace ts { const containingDirectory = containingFile ? getDirectoryPath(containingFile) : undefined; const perFolderCache = containingDirectory ? cache && cache.getOrCreateCacheForDirectory(containingDirectory, redirectedReference) : undefined; - let result = perFolderCache && perFolderCache.get(typeReferenceDirectiveName); + let result = perFolderCache && perFolderCache.get(typeReferenceDirectiveName, /*mode*/ resolutionMode); if (result) { if (traceEnabled) { trace(host, Diagnostics.Resolving_type_reference_directive_0_containing_file_1, typeReferenceDirectiveName, containingFile); @@ -333,7 +340,19 @@ namespace ts { } const failedLookupLocations: string[] = []; - const moduleResolutionState: ModuleResolutionState = { compilerOptions: options, host, traceEnabled, failedLookupLocations, packageJsonInfoCache: cache }; + let features = getDefaultNodeResolutionFeatures(options); + // Unlike `import` statements, whose mode-calculating APIs are all guaranteed to return `undefined` if we're in an un-mode-ed module resolution + // setting, type references will return their target mode regardless of options because of how the parser works, so we guard against the mode being + // set in a non-modal module resolution setting here. Do note that our behavior is not particularly well defined when these mode-overriding imports + // are present in a non-modal project; while in theory we'd like to either ignore the mode or provide faithful modern resolution, depending on what we feel is best, + // in practice, not every cache has the options available to intelligently make the choice to ignore the mode request, and it's unclear how modern "faithful modern + // resolution" should be (`node12`? `nodenext`?). As such, witnessing a mode-overriding triple-slash reference in a non-modal module resolution + // context should _probably_ be an error - and that should likely be handled by the `Program` (which is what we do). + if (resolutionMode === ModuleKind.ESNext && (getEmitModuleResolutionKind(options) === ModuleResolutionKind.Node12 || getEmitModuleResolutionKind(options) === ModuleResolutionKind.NodeNext)) { + features |= NodeResolutionFeatures.EsmMode; + } + const conditions = features & NodeResolutionFeatures.Exports ? features & NodeResolutionFeatures.EsmMode ? ["node", "import", "types"] : ["node", "require", "types"] : []; + const moduleResolutionState: ModuleResolutionState = { compilerOptions: options, host, traceEnabled, failedLookupLocations, packageJsonInfoCache: cache, features, conditions }; let resolved = primaryLookup(); let primary = true; if (!resolved) { @@ -354,7 +373,7 @@ namespace ts { }; } result = { resolvedTypeReferenceDirective, failedLookupLocations }; - perFolderCache?.set(typeReferenceDirectiveName, result); + perFolderCache?.set(typeReferenceDirectiveName, /*mode*/ resolutionMode, result); if (traceEnabled) traceResult(result); return result; @@ -409,7 +428,7 @@ namespace ts { result = searchResult && searchResult.value; } else { - const { path: candidate } = normalizePathAndParts(combinePaths(initialLocationForSecondaryLookup, typeReferenceDirectiveName)); + const { path: candidate } = normalizePathForCJSResolution(initialLocationForSecondaryLookup, typeReferenceDirectiveName); result = nodeLoadModuleByRelativeName(Extensions.DtsOnly, candidate, /*onlyRecordFailures*/ false, moduleResolutionState, /*considerPackageJson*/ true); } return resolvedTypeScriptOnly(result); @@ -422,6 +441,42 @@ namespace ts { } } + function getDefaultNodeResolutionFeatures(options: CompilerOptions) { + return getEmitModuleResolutionKind(options) === ModuleResolutionKind.Node12 ? NodeResolutionFeatures.Node12Default : + getEmitModuleResolutionKind(options) === ModuleResolutionKind.NodeNext ? NodeResolutionFeatures.NodeNextDefault : + NodeResolutionFeatures.None; + } + + /** + * @internal + * Does not try `@types/${packageName}` - use a second pass if needed. + */ + export function resolvePackageNameToPackageJson( + packageName: string, + containingDirectory: string, + options: CompilerOptions, + host: ModuleResolutionHost, + cache: ModuleResolutionCache | undefined, + ): PackageJsonInfo | undefined { + const moduleResolutionState: ModuleResolutionState = { + compilerOptions: options, + host, + traceEnabled: isTraceEnabled(options, host), + failedLookupLocations: [], + packageJsonInfoCache: cache?.getPackageJsonInfoCache(), + conditions: emptyArray, + features: NodeResolutionFeatures.None, + }; + + return forEachAncestorDirectory(containingDirectory, ancestorDirectory => { + if (getBaseFileName(ancestorDirectory) !== "node_modules") { + const nodeModulesFolder = combinePaths(ancestorDirectory, "node_modules"); + const candidate = combinePaths(nodeModulesFolder, packageName); + return getPackageJsonInfo(candidate, /*onlyRecordFailures*/ false, moduleResolutionState); + } + }); + } + /** * Given a set of options, returns the set of type directive names * that should be included for this program automatically. @@ -470,12 +525,21 @@ namespace ts { export interface TypeReferenceDirectiveResolutionCache extends PerDirectoryResolutionCache, PackageJsonInfoCache { } + export interface ModeAwareCache { + get(key: string, mode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined): T | undefined; + set(key: string, mode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined, value: T): this; + delete(key: string, mode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined): this; + has(key: string, mode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined): boolean; + forEach(cb: (elem: T, key: string, mode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined) => void): void; + size(): number; + } + /** * Cached resolutions per containing directory. * This assumes that any module id will have the same resolution for sibling files located in the same folder. */ export interface PerDirectoryResolutionCache { - getOrCreateCacheForDirectory(directoryName: string, redirectedReference?: ResolvedProjectReference): Map; + getOrCreateCacheForDirectory(directoryName: string, redirectedReference?: ResolvedProjectReference): ModeAwareCache; clear(): void; /** * Updates with the current compilerOptions the cache will operate with. @@ -493,7 +557,7 @@ namespace ts { * We support only non-relative module names because resolution of relative module names is usually more deterministic and thus less expensive. */ export interface NonRelativeModuleNameResolutionCache extends PackageJsonInfoCache { - getOrCreateCacheForModuleName(nonRelativeModuleName: string, redirectedReference?: ResolvedProjectReference): PerModuleNameCache; + getOrCreateCacheForModuleName(nonRelativeModuleName: string, mode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined, redirectedReference?: ResolvedProjectReference): PerModuleNameCache; } export interface PackageJsonInfoCache { @@ -593,7 +657,7 @@ namespace ts { function updateRedirectsMap( options: CompilerOptions, - directoryToModuleNameMap: CacheWithRedirects>, + directoryToModuleNameMap: CacheWithRedirects>, moduleNameToDirectoryMap?: CacheWithRedirects ) { if (!options.configFile) return; @@ -619,7 +683,7 @@ namespace ts { moduleNameToDirectoryMap?.setOwnOptions(options); } - function createPerDirectoryResolutionCache(currentDirectory: string, getCanonicalFileName: GetCanonicalFileName, directoryToModuleNameMap: CacheWithRedirects>): PerDirectoryResolutionCache { + function createPerDirectoryResolutionCache(currentDirectory: string, getCanonicalFileName: GetCanonicalFileName, directoryToModuleNameMap: CacheWithRedirects>): PerDirectoryResolutionCache { return { getOrCreateCacheForDirectory, clear, @@ -636,10 +700,63 @@ namespace ts { function getOrCreateCacheForDirectory(directoryName: string, redirectedReference?: ResolvedProjectReference) { const path = toPath(directoryName, currentDirectory, getCanonicalFileName); - return getOrCreateCache>(directoryToModuleNameMap, redirectedReference, path, () => new Map()); + return getOrCreateCache>(directoryToModuleNameMap, redirectedReference, path, () => createModeAwareCache()); } } + /* @internal */ + export function createModeAwareCache(): ModeAwareCache { + const underlying = new Map(); + const memoizedReverseKeys = new Map(); + + const cache: ModeAwareCache = { + get(specifier, mode) { + return underlying.get(getUnderlyingCacheKey(specifier, mode)); + }, + set(specifier, mode, value) { + underlying.set(getUnderlyingCacheKey(specifier, mode), value); + return cache; + }, + delete(specifier, mode) { + underlying.delete(getUnderlyingCacheKey(specifier, mode)); + return cache; + }, + has(specifier, mode) { + return underlying.has(getUnderlyingCacheKey(specifier, mode)); + }, + forEach(cb) { + return underlying.forEach((elem, key) => { + const [specifier, mode] = memoizedReverseKeys.get(key)!; + return cb(elem, specifier, mode); + }); + }, + size() { + return underlying.size; + } + }; + return cache; + + function getUnderlyingCacheKey(specifier: string, mode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined) { + const result = mode === undefined ? specifier : `${mode}|${specifier}`; + memoizedReverseKeys.set(result, [specifier, mode]); + return result; + } + } + + /* @internal */ + export function zipToModeAwareCache(file: SourceFile, keys: readonly string[] | readonly FileReference[], values: readonly V[]): ModeAwareCache { + Debug.assert(keys.length === values.length); + const map = createModeAwareCache(); + for (let i = 0; i < keys.length; ++i) { + const entry = keys[i]; + // We lower-case all type references because npm automatically lowercases all packages. See GH#9824. + const name = !isString(entry) ? entry.fileName.toLowerCase() : entry; + const mode = !isString(entry) ? entry.resolutionMode || file.impliedNodeFormat : getModeForResolutionAtIndex(file, i); + map.set(name, mode, values[i]); + } + return map; + } + export function createModuleResolutionCache( currentDirectory: string, getCanonicalFileName: (s: string) => string, @@ -650,14 +767,14 @@ namespace ts { currentDirectory: string, getCanonicalFileName: GetCanonicalFileName, options: undefined, - directoryToModuleNameMap: CacheWithRedirects>, + directoryToModuleNameMap: CacheWithRedirects>, moduleNameToDirectoryMap: CacheWithRedirects, ): ModuleResolutionCache; export function createModuleResolutionCache( currentDirectory: string, getCanonicalFileName: GetCanonicalFileName, options?: CompilerOptions, - directoryToModuleNameMap?: CacheWithRedirects>, + directoryToModuleNameMap?: CacheWithRedirects>, moduleNameToDirectoryMap?: CacheWithRedirects, ): ModuleResolutionCache { const preDirectoryResolutionCache = createPerDirectoryResolutionCache(currentDirectory, getCanonicalFileName, directoryToModuleNameMap ||= createCacheWithRedirects(options)); @@ -683,9 +800,9 @@ namespace ts { updateRedirectsMap(options, directoryToModuleNameMap!, moduleNameToDirectoryMap); } - function getOrCreateCacheForModuleName(nonRelativeModuleName: string, redirectedReference?: ResolvedProjectReference): PerModuleNameCache { + function getOrCreateCacheForModuleName(nonRelativeModuleName: string, mode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined, redirectedReference?: ResolvedProjectReference): PerModuleNameCache { Debug.assert(!isExternalModuleNameRelative(nonRelativeModuleName)); - return getOrCreateCache(moduleNameToDirectoryMap!, redirectedReference, nonRelativeModuleName, createPerModuleNameCache); + return getOrCreateCache(moduleNameToDirectoryMap!, redirectedReference, mode === undefined ? nonRelativeModuleName : `${mode}|${nonRelativeModuleName}`, createPerModuleNameCache); } function createPerModuleNameCache(): PerModuleNameCache { @@ -773,14 +890,14 @@ namespace ts { getCanonicalFileName: GetCanonicalFileName, options: undefined, packageJsonInfoCache: PackageJsonInfoCache | undefined, - directoryToModuleNameMap: CacheWithRedirects>, + directoryToModuleNameMap: CacheWithRedirects>, ): TypeReferenceDirectiveResolutionCache; export function createTypeReferenceDirectiveResolutionCache( currentDirectory: string, getCanonicalFileName: GetCanonicalFileName, options?: CompilerOptions, packageJsonInfoCache?: PackageJsonInfoCache | undefined, - directoryToModuleNameMap?: CacheWithRedirects>, + directoryToModuleNameMap?: CacheWithRedirects>, ): TypeReferenceDirectiveResolutionCache { const preDirectoryResolutionCache = createPerDirectoryResolutionCache(currentDirectory, getCanonicalFileName, directoryToModuleNameMap ||= createCacheWithRedirects(options)); packageJsonInfoCache ||= createPackageJsonInfoCache(currentDirectory, getCanonicalFileName); @@ -797,13 +914,14 @@ namespace ts { } } - export function resolveModuleNameFromCache(moduleName: string, containingFile: string, cache: ModuleResolutionCache): ResolvedModuleWithFailedLookupLocations | undefined { + export function resolveModuleNameFromCache(moduleName: string, containingFile: string, cache: ModuleResolutionCache, mode?: ModuleKind.CommonJS | ModuleKind.ESNext): ResolvedModuleWithFailedLookupLocations | undefined { const containingDirectory = getDirectoryPath(containingFile); const perFolderCache = cache && cache.getOrCreateCacheForDirectory(containingDirectory); - return perFolderCache && perFolderCache.get(moduleName); + if (!perFolderCache) return undefined; + return perFolderCache.get(moduleName, mode); } - export function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference): ResolvedModuleWithFailedLookupLocations { + export function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference, resolutionMode?: ModuleKind.CommonJS | ModuleKind.ESNext): ResolvedModuleWithFailedLookupLocations { const traceEnabled = isTraceEnabled(compilerOptions, host); if (redirectedReference) { compilerOptions = redirectedReference.commandLine.options; @@ -816,7 +934,7 @@ namespace ts { } const containingDirectory = getDirectoryPath(containingFile); const perFolderCache = cache && cache.getOrCreateCacheForDirectory(containingDirectory, redirectedReference); - let result = perFolderCache && perFolderCache.get(moduleName); + let result = perFolderCache && perFolderCache.get(moduleName, resolutionMode); if (result) { if (traceEnabled) { @@ -826,7 +944,20 @@ namespace ts { else { let moduleResolution = compilerOptions.moduleResolution; if (moduleResolution === undefined) { - moduleResolution = getEmitModuleKind(compilerOptions) === ModuleKind.CommonJS ? ModuleResolutionKind.NodeJs : ModuleResolutionKind.Classic; + switch (getEmitModuleKind(compilerOptions)) { + case ModuleKind.CommonJS: + moduleResolution = ModuleResolutionKind.NodeJs; + break; + case ModuleKind.Node12: + moduleResolution = ModuleResolutionKind.Node12; + break; + case ModuleKind.NodeNext: + moduleResolution = ModuleResolutionKind.NodeNext; + break; + default: + moduleResolution = ModuleResolutionKind.Classic; + break; + } if (traceEnabled) { trace(host, Diagnostics.Module_resolution_kind_is_not_specified_using_0, ModuleResolutionKind[moduleResolution]); } @@ -839,6 +970,12 @@ namespace ts { perfLogger.logStartResolveModule(moduleName /* , containingFile, ModuleResolutionKind[moduleResolution]*/); switch (moduleResolution) { + case ModuleResolutionKind.Node12: + result = node12ModuleNameResolver(moduleName, containingFile, compilerOptions, host, cache, redirectedReference, resolutionMode); + break; + case ModuleResolutionKind.NodeNext: + result = nodeNextModuleNameResolver(moduleName, containingFile, compilerOptions, host, cache, redirectedReference, resolutionMode); + break; case ModuleResolutionKind.NodeJs: result = nodeModuleNameResolver(moduleName, containingFile, compilerOptions, host, cache, redirectedReference); break; @@ -852,10 +989,10 @@ namespace ts { perfLogger.logStopResolveModule((result && result.resolvedModule) ? "" + result.resolvedModule.resolvedFileName : "null"); if (perFolderCache) { - perFolderCache.set(moduleName, result); + perFolderCache.set(moduleName, resolutionMode, result); if (!isExternalModuleNameRelative(moduleName)) { // put result in per-module name cache - cache!.getOrCreateCacheForModuleName(moduleName, redirectedReference).set(containingDirectory, result); + cache.getOrCreateCacheForModuleName(moduleName, resolutionMode, redirectedReference).set(containingDirectory, result); } } } @@ -1083,8 +1220,62 @@ namespace ts { } /* @internal */ - export function tryResolveJSModule(moduleName: string, initialDir: string, host: ModuleResolutionHost) { - return tryResolveJSModuleWorker(moduleName, initialDir, host).resolvedModule; + enum NodeResolutionFeatures { + None = 0, + // resolving `#local` names in your own package.json + Imports = 1 << 1, + // resolving `your-own-name` from your own package.json + SelfName = 1 << 2, + // respecting the `.exports` member of packages' package.json files and its (conditional) mappings of export names + Exports = 1 << 3, + // allowing `*` in the LHS of an export to be followed by more content, eg `"./whatever/*.js"` + // not currently backported to node 12 - https://github.com/nodejs/Release/issues/690 + ExportsPatternTrailers = 1 << 4, + AllFeatures = Imports | SelfName | Exports | ExportsPatternTrailers, + + Node12Default = Imports | SelfName | Exports, + + NodeNextDefault = AllFeatures, + + EsmMode = 1 << 5, + } + + function node12ModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, + host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference, + resolutionMode?: ModuleKind.CommonJS | ModuleKind.ESNext): ResolvedModuleWithFailedLookupLocations { + return nodeNextModuleNameResolverWorker( + NodeResolutionFeatures.Node12Default, + moduleName, + containingFile, + compilerOptions, + host, + cache, + redirectedReference, + resolutionMode + ); + } + + function nodeNextModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, + host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference, + resolutionMode?: ModuleKind.CommonJS | ModuleKind.ESNext): ResolvedModuleWithFailedLookupLocations { + return nodeNextModuleNameResolverWorker( + NodeResolutionFeatures.NodeNextDefault, + moduleName, + containingFile, + compilerOptions, + host, + cache, + redirectedReference, + resolutionMode + ); + } + + function nodeNextModuleNameResolverWorker(features: NodeResolutionFeatures, moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference, resolutionMode?: ModuleKind.CommonJS | ModuleKind.ESNext): ResolvedModuleWithFailedLookupLocations { + const containingDirectory = getDirectoryPath(containingFile); + + // es module file or cjs-like input file, use a variant of the legacy cjs resolver that supports the selected modern features + const esmMode = resolutionMode === ModuleKind.ESNext ? NodeResolutionFeatures.EsmMode : 0; + return nodeModuleNameResolverWorker(features | esmMode, moduleName, containingDirectory, compilerOptions, host, cache, compilerOptions.resolveJsonModule ? tsPlusJsonExtensions : tsExtensions, redirectedReference); } const jsOnlyExtensions = [Extensions.JavaScript]; @@ -1092,20 +1283,30 @@ namespace ts { const tsPlusJsonExtensions = [...tsExtensions, Extensions.Json]; const tsconfigExtensions = [Extensions.TSConfig]; function tryResolveJSModuleWorker(moduleName: string, initialDir: string, host: ModuleResolutionHost): ResolvedModuleWithFailedLookupLocations { - return nodeModuleNameResolverWorker(moduleName, initialDir, { moduleResolution: ModuleResolutionKind.NodeJs, allowJs: true }, host, /*cache*/ undefined, jsOnlyExtensions, /*redirectedReferences*/ undefined); + return nodeModuleNameResolverWorker(NodeResolutionFeatures.None, moduleName, initialDir, { moduleResolution: ModuleResolutionKind.NodeJs, allowJs: true }, host, /*cache*/ undefined, jsOnlyExtensions, /*redirectedReferences*/ undefined); } export function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference): ResolvedModuleWithFailedLookupLocations; /* @internal */ export function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference, lookupConfig?: boolean): ResolvedModuleWithFailedLookupLocations; // eslint-disable-line @typescript-eslint/unified-signatures export function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference, lookupConfig?: boolean): ResolvedModuleWithFailedLookupLocations { - return nodeModuleNameResolverWorker(moduleName, getDirectoryPath(containingFile), compilerOptions, host, cache, lookupConfig ? tsconfigExtensions : (compilerOptions.resolveJsonModule ? tsPlusJsonExtensions : tsExtensions), redirectedReference); + return nodeModuleNameResolverWorker(NodeResolutionFeatures.None, moduleName, getDirectoryPath(containingFile), compilerOptions, host, cache, lookupConfig ? tsconfigExtensions : (compilerOptions.resolveJsonModule ? tsPlusJsonExtensions : tsExtensions), redirectedReference); } - function nodeModuleNameResolverWorker(moduleName: string, containingDirectory: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache: ModuleResolutionCache | undefined, extensions: Extensions[], redirectedReference: ResolvedProjectReference | undefined): ResolvedModuleWithFailedLookupLocations { + function nodeModuleNameResolverWorker(features: NodeResolutionFeatures, moduleName: string, containingDirectory: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache: ModuleResolutionCache | undefined, extensions: Extensions[], redirectedReference: ResolvedProjectReference | undefined): ResolvedModuleWithFailedLookupLocations { const traceEnabled = isTraceEnabled(compilerOptions, host); const failedLookupLocations: string[] = []; - const state: ModuleResolutionState = { compilerOptions, host, traceEnabled, failedLookupLocations, packageJsonInfoCache: cache }; + // conditions are only used by the node12/nodenext resolver - there's no priority order in the list, + //it's essentially a set (priority is determined by object insertion order in the object we look at). + const state: ModuleResolutionState = { + compilerOptions, + host, + traceEnabled, + failedLookupLocations, + packageJsonInfoCache: cache, + features, + conditions: features & NodeResolutionFeatures.EsmMode ? ["node", "import", "types"] : ["node", "require", "types"] + }; const result = forEach(extensions, ext => tryResolve(ext)); return createResolvedModuleWithFailedLookupLocations(result?.value?.resolved, result?.value?.isExternalLibraryImport, failedLookupLocations, state.resultFromCache); @@ -1118,10 +1319,19 @@ namespace ts { } if (!isExternalModuleNameRelative(moduleName)) { - if (traceEnabled) { - trace(host, Diagnostics.Loading_module_0_from_node_modules_folder_target_file_type_1, moduleName, Extensions[extensions]); + let resolved: SearchResult | undefined; + if (features & NodeResolutionFeatures.Imports && startsWith(moduleName, "#")) { + resolved = loadModuleFromImports(extensions, moduleName, containingDirectory, state, cache, redirectedReference); + } + if (!resolved && features & NodeResolutionFeatures.SelfName) { + resolved = loadModuleFromSelfNameReference(extensions, moduleName, containingDirectory, state, cache, redirectedReference); + } + if (!resolved) { + if (traceEnabled) { + trace(host, Diagnostics.Loading_module_0_from_node_modules_folder_target_file_type_1, moduleName, Extensions[extensions]); + } + resolved = loadModuleFromNearestNodeModulesDirectory(extensions, moduleName, containingDirectory, state, cache, redirectedReference); } - const resolved = loadModuleFromNearestNodeModulesDirectory(extensions, moduleName, containingDirectory, state, cache, redirectedReference); if (!resolved) return undefined; let resolvedValue = resolved.value; @@ -1134,12 +1344,26 @@ namespace ts { return { value: resolvedValue && { resolved: resolvedValue, isExternalLibraryImport: true } }; } else { - const { path: candidate, parts } = normalizePathAndParts(combinePaths(containingDirectory, moduleName)); + const { path: candidate, parts } = normalizePathForCJSResolution(containingDirectory, moduleName); const resolved = nodeLoadModuleByRelativeName(extensions, candidate, /*onlyRecordFailures*/ false, state, /*considerPackageJson*/ true); // Treat explicit "node_modules" import as an external library import. return resolved && toSearchResult({ resolved, isExternalLibraryImport: contains(parts, "node_modules") }); } } + + } + + // If you import from "." inside a containing directory "/foo", the result of `normalizePath` + // would be "/foo", but this loses the information that `foo` is a directory and we intended + // to look inside of it. The Node CommonJS resolution algorithm doesn't call this out + // (https://nodejs.org/api/modules.html#all-together), but it seems that module paths ending + // in `.` are actually normalized to `./` before proceeding with the resolution algorithm. + function normalizePathForCJSResolution(containingDirectory: string, moduleName: string) { + const combined = combinePaths(containingDirectory, moduleName); + const parts = getPathComponents(combined); + const lastPart = lastOrUndefined(parts); + const path = lastPart === "." || lastPart === ".." ? ensureTrailingDirectorySeparator(normalizePath(combined)) : normalizePath(combined); + return { path, parts }; } function realPath(path: string, host: ModuleResolutionHost, traceEnabled: boolean): string { @@ -1185,7 +1409,13 @@ namespace ts { onlyRecordFailures = true; } } - return loadNodeModuleFromDirectory(extensions, candidate, onlyRecordFailures, state, considerPackageJson); + // esm mode relative imports shouldn't do any directory lookups (either inside `package.json` + // files or implicit `index.js`es). This is a notable depature from cjs norms, where `./foo/pkg` + // could have been redirected by `./foo/pkg/package.json` to an arbitrary location! + if (!(state.features & NodeResolutionFeatures.EsmMode)) { + return loadNodeModuleFromDirectory(extensions, candidate, onlyRecordFailures, state, considerPackageJson); + } + return undefined; } /*@internal*/ @@ -1237,29 +1467,46 @@ namespace ts { function loadModuleFromFile(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState): PathAndExtension | undefined { if (extensions === Extensions.Json || extensions === Extensions.TSConfig) { const extensionLess = tryRemoveExtension(candidate, Extension.Json); - return (extensionLess === undefined && extensions === Extensions.Json) ? undefined : tryAddingExtensions(extensionLess || candidate, extensions, onlyRecordFailures, state); + const extension = extensionLess ? candidate.substring(extensionLess.length) : ""; + return (extensionLess === undefined && extensions === Extensions.Json) ? undefined : tryAddingExtensions(extensionLess || candidate, extensions, extension, onlyRecordFailures, state); } - // First, try adding an extension. An import of "foo" could be matched by a file "foo.ts", or "foo.js" by "foo.js.ts" - const resolvedByAddingExtension = tryAddingExtensions(candidate, extensions, onlyRecordFailures, state); - if (resolvedByAddingExtension) { - return resolvedByAddingExtension; + // esm mode resolutions don't include automatic extension lookup (without additional flags, at least) + if (!(state.features & NodeResolutionFeatures.EsmMode)) { + // First, try adding an extension. An import of "foo" could be matched by a file "foo.ts", or "foo.js" by "foo.js.ts" + const resolvedByAddingExtension = tryAddingExtensions(candidate, extensions, "", onlyRecordFailures, state); + if (resolvedByAddingExtension) { + return resolvedByAddingExtension; + } } + return loadModuleFromFileNoImplicitExtensions(extensions, candidate, onlyRecordFailures, state); + } + + function loadModuleFromFileNoImplicitExtensions(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState): PathAndExtension | undefined { // If that didn't work, try stripping a ".js" or ".jsx" extension and replacing it with a TypeScript one; // e.g. "./foo.js" can be matched by "./foo.ts" or "./foo.d.ts" - if (hasJSFileExtension(candidate)) { + if (hasJSFileExtension(candidate) || (fileExtensionIs(candidate, Extension.Json) && state.compilerOptions.resolveJsonModule)) { const extensionless = removeFileExtension(candidate); + const extension = candidate.substring(extensionless.length); if (state.traceEnabled) { - const extension = candidate.substring(extensionless.length); trace(state.host, Diagnostics.File_name_0_has_a_1_extension_stripping_it, candidate, extension); } - return tryAddingExtensions(extensionless, extensions, onlyRecordFailures, state); + return tryAddingExtensions(extensionless, extensions, extension, onlyRecordFailures, state); } } + function loadJSOrExactTSFileName(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState): PathAndExtension | undefined { + if ((extensions === Extensions.TypeScript || extensions === Extensions.DtsOnly) && fileExtensionIsOneOf(candidate, [Extension.Dts, Extension.Dcts, Extension.Dmts])) { + const result = tryFile(candidate, onlyRecordFailures, state); + return result !== undefined ? { path: candidate, ext: forEach([Extension.Dts, Extension.Dcts, Extension.Dmts], e => fileExtensionIs(candidate, e) ? e : undefined)! } : undefined; + } + + return loadModuleFromFileNoImplicitExtensions(extensions, candidate, onlyRecordFailures, state); + } + /** Try to return an existing file that adds one of the `extensions` to `candidate`. */ - function tryAddingExtensions(candidate: string, extensions: Extensions, onlyRecordFailures: boolean, state: ModuleResolutionState): PathAndExtension | undefined { + function tryAddingExtensions(candidate: string, extensions: Extensions, originalExtension: string, onlyRecordFailures: boolean, state: ModuleResolutionState): PathAndExtension | undefined { if (!onlyRecordFailures) { // check if containing folder exists - if it doesn't then just record failures for all supported extensions without disk probing const directory = getDirectoryPath(candidate); @@ -1270,11 +1517,51 @@ namespace ts { switch (extensions) { case Extensions.DtsOnly: - return tryExtension(Extension.Dts); + switch (originalExtension) { + case Extension.Mjs: + case Extension.Mts: + case Extension.Dmts: + return tryExtension(Extension.Dmts); + case Extension.Cjs: + case Extension.Cts: + case Extension.Dcts: + return tryExtension(Extension.Dcts); + case Extension.Json: + candidate += Extension.Json; + return tryExtension(Extension.Dts); + default: return tryExtension(Extension.Dts); + } case Extensions.TypeScript: - return tryExtension(Extension.Ts) || tryExtension(Extension.Tsx) || tryExtension(Extension.Dts); + switch (originalExtension) { + case Extension.Mjs: + case Extension.Mts: + case Extension.Dmts: + return tryExtension(Extension.Mts) || tryExtension(Extension.Dmts); + case Extension.Cjs: + case Extension.Cts: + case Extension.Dcts: + return tryExtension(Extension.Cts) || tryExtension(Extension.Dcts); + case Extension.Json: + candidate += Extension.Json; + return tryExtension(Extension.Dts); + default: + return tryExtension(Extension.Ts) || tryExtension(Extension.Tsx) || tryExtension(Extension.Dts); + } case Extensions.JavaScript: - return tryExtension(Extension.Js) || tryExtension(Extension.Jsx); + switch (originalExtension) { + case Extension.Mjs: + case Extension.Mts: + case Extension.Dmts: + return tryExtension(Extension.Mjs); + case Extension.Cjs: + case Extension.Cts: + case Extension.Dcts: + return tryExtension(Extension.Cjs); + case Extension.Json: + return tryExtension(Extension.Json); + default: + return tryExtension(Extension.Js) || tryExtension(Extension.Jsx); + } case Extensions.TSConfig: case Extensions.Json: return tryExtension(Extension.Json); @@ -1312,14 +1599,163 @@ namespace ts { return withPackageId(packageInfo, loadNodeModuleFromDirectoryWorker(extensions, candidate, onlyRecordFailures, state, packageJsonContent, versionPaths)); } + /* @internal */ + export function getEntrypointsFromPackageJsonInfo( + packageJsonInfo: PackageJsonInfo, + options: CompilerOptions, + host: ModuleResolutionHost, + cache: ModuleResolutionCache | undefined, + resolveJs?: boolean, + ): string[] | false { + if (!resolveJs && packageJsonInfo.resolvedEntrypoints !== undefined) { + // Cached value excludes resolutions to JS files - those could be + // cached separately, but they're used rarely. + return packageJsonInfo.resolvedEntrypoints; + } + + let entrypoints: string[] | undefined; + const extensions = resolveJs ? Extensions.JavaScript : Extensions.TypeScript; + const features = getDefaultNodeResolutionFeatures(options); + const requireState: ModuleResolutionState = { + compilerOptions: options, + host, + traceEnabled: isTraceEnabled(options, host), + failedLookupLocations: [], + packageJsonInfoCache: cache?.getPackageJsonInfoCache(), + conditions: ["node", "require", "types"], + features, + }; + const requireResolution = loadNodeModuleFromDirectoryWorker( + extensions, + packageJsonInfo.packageDirectory, + /*onlyRecordFailures*/ false, + requireState, + packageJsonInfo.packageJsonContent, + packageJsonInfo.versionPaths); + entrypoints = append(entrypoints, requireResolution?.path); + + if (features & NodeResolutionFeatures.Exports && packageJsonInfo.packageJsonContent.exports) { + for (const conditions of [["node", "import", "types"], ["node", "require", "types"]]) { + const exportState = { ...requireState, failedLookupLocations: [], conditions }; + const exportResolutions = loadEntrypointsFromExportMap( + packageJsonInfo, + packageJsonInfo.packageJsonContent.exports, + exportState, + extensions); + if (exportResolutions) { + for (const resolution of exportResolutions) { + entrypoints = appendIfUnique(entrypoints, resolution.path); + } + } + } + } + + return packageJsonInfo.resolvedEntrypoints = entrypoints || false; + } + + function loadEntrypointsFromExportMap( + scope: PackageJsonInfo, + exports: object, + state: ModuleResolutionState, + extensions: Extensions, + ): PathAndExtension[] | undefined { + let entrypoints: PathAndExtension[] | undefined; + if (isArray(exports)) { + for (const target of exports) { + loadEntrypointsFromTargetExports(target); + } + } + // eslint-disable-next-line no-null/no-null + else if (typeof exports === "object" && exports !== null && allKeysStartWithDot(exports as MapLike)) { + for (const key in exports) { + loadEntrypointsFromTargetExports((exports as MapLike)[key]); + } + } + else { + loadEntrypointsFromTargetExports(exports); + } + return entrypoints; + + function loadEntrypointsFromTargetExports(target: unknown): boolean | undefined { + if (typeof target === "string" && startsWith(target, "./") && target.indexOf("*") === -1) { + const partsAfterFirst = getPathComponents(target).slice(2); + if (partsAfterFirst.indexOf("..") >= 0 || partsAfterFirst.indexOf(".") >= 0 || partsAfterFirst.indexOf("node_modules") >= 0) { + return false; + } + const resolvedTarget = combinePaths(scope.packageDirectory, target); + const finalPath = getNormalizedAbsolutePath(resolvedTarget, state.host.getCurrentDirectory?.()); + const result = loadJSOrExactTSFileName(extensions, finalPath, /*recordOnlyFailures*/ false, state); + if (result) { + entrypoints = appendIfUnique(entrypoints, result, (a, b) => a.path === b.path); + return true; + } + } + else if (Array.isArray(target)) { + for (const t of target) { + const success = loadEntrypointsFromTargetExports(t); + if (success) { + return true; + } + } + } + // eslint-disable-next-line no-null/no-null + else if (typeof target === "object" && target !== null) { + return forEach(getOwnKeys(target as MapLike), key => { + if (key === "default" || contains(state.conditions, key) || isApplicableVersionedTypesKey(state.conditions, key)) { + loadEntrypointsFromTargetExports((target as MapLike)[key]); + return true; + } + }); + } + } + } + /*@internal*/ interface PackageJsonInfo { packageDirectory: string; packageJsonContent: PackageJsonPathFields; versionPaths: VersionPaths | undefined; + /** false: resolved to nothing. undefined: not yet resolved */ + resolvedEntrypoints: string[] | false | undefined; } - function getPackageJsonInfo(packageDirectory: string, onlyRecordFailures: boolean, state: ModuleResolutionState): PackageJsonInfo | undefined { + /** + * A function for locating the package.json scope for a given path + */ + /*@internal*/ + export function getPackageScopeForPath(fileName: Path, packageJsonInfoCache: PackageJsonInfoCache | undefined, host: ModuleResolutionHost, options: CompilerOptions): PackageJsonInfo | undefined { + const state: { + host: ModuleResolutionHost; + compilerOptions: CompilerOptions; + traceEnabled: boolean; + failedLookupLocations: Push; + resultFromCache?: ResolvedModuleWithFailedLookupLocations; + packageJsonInfoCache: PackageJsonInfoCache | undefined; + features: number; + conditions: never[]; + } = { + host, + compilerOptions: options, + traceEnabled: isTraceEnabled(options, host), + failedLookupLocations: [], + packageJsonInfoCache, + features: 0, + conditions: [], + }; + const parts = getPathComponents(fileName); + parts.pop(); + while (parts.length > 0) { + const pkg = getPackageJsonInfo(getPathFromPathComponents(parts), /*onlyRecordFailures*/ false, state); + if (pkg) { + return pkg; + } + parts.pop(); + } + return undefined; + } + + /*@internal*/ + export function getPackageJsonInfo(packageDirectory: string, onlyRecordFailures: boolean, state: ModuleResolutionState): PackageJsonInfo | undefined { const { host, traceEnabled } = state; const packageJsonPath = combinePaths(packageDirectory, "package.json"); if (onlyRecordFailures) { @@ -1346,7 +1782,7 @@ namespace ts { trace(host, Diagnostics.Found_package_json_at_0, packageJsonPath); } const versionPaths = readPackageJsonTypesVersionPaths(packageJsonContent, state); - const result = { packageDirectory, packageJsonContent, versionPaths }; + const result = { packageDirectory, packageJsonContent, versionPaths, resolvedEntrypoints: undefined }; state.packageJsonInfoCache?.setPackageJsonInfo(packageJsonPath, result); return result; } @@ -1398,7 +1834,17 @@ namespace ts { // Even if extensions is DtsOnly, we can still look up a .ts file as a result of package.json "types" const nextExtensions = extensions === Extensions.DtsOnly ? Extensions.TypeScript : extensions; // Don't do package.json lookup recursively, because Node.js' package lookup doesn't. - return nodeLoadModuleByRelativeName(nextExtensions, candidate, onlyRecordFailures, state, /*considerPackageJson*/ false); + + // Disable `EsmMode` for the resolution of the package path for cjs-mode packages (so the `main` field can omit extensions) + // (technically it only emits a deprecation warning in esm packages right now, but that's probably + // enough to mean we don't need to support it) + const features = state.features; + if (jsonContent?.type !== "module") { + state.features &= ~NodeResolutionFeatures.EsmMode; + } + const result = nodeLoadModuleByRelativeName(nextExtensions, candidate, onlyRecordFailures, state, /*considerPackageJson*/ false); + state.features = features; + return result; }; const onlyRecordFailuresForPackageFile = packageFile ? !directoryProbablyExists(getDirectoryPath(packageFile), state.host) : undefined; @@ -1420,7 +1866,10 @@ namespace ts { const packageFileResult = packageFile && removeIgnoredPackageId(loader(extensions, packageFile, onlyRecordFailuresForPackageFile!, state)); if (packageFileResult) return packageFileResult; - return loadModuleFromFile(extensions, indexPath, onlyRecordFailuresForIndex, state); + // esm mode resolutions don't do package `index` lookups + if (!(state.features & NodeResolutionFeatures.EsmMode)) { + return loadModuleFromFile(extensions, indexPath, onlyRecordFailuresForIndex, state); + } } /** Resolve from an arbitrarily specified file. Return `undefined` if it has an unsupported extension. */ @@ -1453,7 +1902,237 @@ namespace ts { return idx === -1 ? { packageName: moduleName, rest: "" } : { packageName: moduleName.slice(0, idx), rest: moduleName.slice(idx + 1) }; } - function loadModuleFromNearestNodeModulesDirectory(extensions: Extensions, moduleName: string, directory: string, state: ModuleResolutionState, cache: NonRelativeModuleNameResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined): SearchResult { + /* @internal */ + export function allKeysStartWithDot(obj: MapLike) { + return every(getOwnKeys(obj), k => startsWith(k, ".")); + } + + function noKeyStartsWithDot(obj: MapLike) { + return !some(getOwnKeys(obj), k => startsWith(k, ".")); + } + + function loadModuleFromSelfNameReference(extensions: Extensions, moduleName: string, directory: string, state: ModuleResolutionState, cache: ModuleResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined): SearchResult { + const useCaseSensitiveFileNames = typeof state.host.useCaseSensitiveFileNames === "function" ? state.host.useCaseSensitiveFileNames() : state.host.useCaseSensitiveFileNames; + const directoryPath = toPath(combinePaths(directory, "dummy"), state.host.getCurrentDirectory?.(), createGetCanonicalFileName(useCaseSensitiveFileNames === undefined ? true : useCaseSensitiveFileNames)); + const scope = getPackageScopeForPath(directoryPath, state.packageJsonInfoCache, state.host, state.compilerOptions); + if (!scope || !scope.packageJsonContent.exports) { + return undefined; + } + if (typeof scope.packageJsonContent.name !== "string") { + return undefined; + } + const parts = getPathComponents(moduleName); // unrooted paths should have `""` as their 0th entry + const nameParts = getPathComponents(scope.packageJsonContent.name); + if (!every(nameParts, (p, i) => parts[i] === p)) { + return undefined; + } + const trailingParts = parts.slice(nameParts.length); + return loadModuleFromExports(scope, extensions, !length(trailingParts) ? "." : `.${directorySeparator}${trailingParts.join(directorySeparator)}`, state, cache, redirectedReference); + } + + function loadModuleFromExports(scope: PackageJsonInfo, extensions: Extensions, subpath: string, state: ModuleResolutionState, cache: ModuleResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined): SearchResult { + if (!scope.packageJsonContent.exports) { + return undefined; + } + + if (subpath === ".") { + let mainExport; + if (typeof scope.packageJsonContent.exports === "string" || Array.isArray(scope.packageJsonContent.exports) || (typeof scope.packageJsonContent.exports === "object" && noKeyStartsWithDot(scope.packageJsonContent.exports as MapLike))) { + mainExport = scope.packageJsonContent.exports; + } + else if (hasProperty(scope.packageJsonContent.exports as MapLike, ".")) { + mainExport = (scope.packageJsonContent.exports as MapLike)["."]; + } + if (mainExport) { + const loadModuleFromTargetImportOrExport = getLoadModuleFromTargetImportOrExport(extensions, state, cache, redirectedReference, subpath, scope, /*isImports*/ false); + return loadModuleFromTargetImportOrExport(mainExport, "", /*pattern*/ false); + } + } + else if (allKeysStartWithDot(scope.packageJsonContent.exports as MapLike)) { + if (typeof scope.packageJsonContent.exports !== "object") { + if (state.traceEnabled) { + trace(state.host, Diagnostics.Export_specifier_0_does_not_exist_in_package_json_scope_at_path_1, subpath, scope.packageDirectory); + } + return toSearchResult(/*value*/ undefined); + } + const result = loadModuleFromImportsOrExports(extensions, state, cache, redirectedReference, subpath, scope.packageJsonContent.exports, scope, /*isImports*/ false); + if (result) { + return result; + } + } + + if (state.traceEnabled) { + trace(state.host, Diagnostics.Export_specifier_0_does_not_exist_in_package_json_scope_at_path_1, subpath, scope.packageDirectory); + } + return toSearchResult(/*value*/ undefined); + } + + function loadModuleFromImports(extensions: Extensions, moduleName: string, directory: string, state: ModuleResolutionState, cache: ModuleResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined): SearchResult { + if (moduleName === "#" || startsWith(moduleName, "#/")) { + if (state.traceEnabled) { + trace(state.host, Diagnostics.Invalid_import_specifier_0_has_no_possible_resolutions, moduleName); + } + return toSearchResult(/*value*/ undefined); + } + const useCaseSensitiveFileNames = typeof state.host.useCaseSensitiveFileNames === "function" ? state.host.useCaseSensitiveFileNames() : state.host.useCaseSensitiveFileNames; + const directoryPath = toPath(combinePaths(directory, "dummy"), state.host.getCurrentDirectory?.(), createGetCanonicalFileName(useCaseSensitiveFileNames === undefined ? true : useCaseSensitiveFileNames)); + const scope = getPackageScopeForPath(directoryPath, state.packageJsonInfoCache, state.host, state.compilerOptions); + if (!scope) { + if (state.traceEnabled) { + trace(state.host, Diagnostics.Directory_0_has_no_containing_package_json_scope_Imports_will_not_resolve, directoryPath); + } + return toSearchResult(/*value*/ undefined); + } + if (!scope.packageJsonContent.imports) { + if (state.traceEnabled) { + trace(state.host, Diagnostics.package_json_scope_0_has_no_imports_defined, scope.packageDirectory); + } + return toSearchResult(/*value*/ undefined); + } + + const result = loadModuleFromImportsOrExports(extensions, state, cache, redirectedReference, moduleName, scope.packageJsonContent.imports, scope, /*isImports*/ true); + if (result) { + return result; + } + + if (state.traceEnabled) { + trace(state.host, Diagnostics.Import_specifier_0_does_not_exist_in_package_json_scope_at_path_1, moduleName, scope.packageDirectory); + } + return toSearchResult(/*value*/ undefined); + } + + function loadModuleFromImportsOrExports(extensions: Extensions, state: ModuleResolutionState, cache: ModuleResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined, moduleName: string, lookupTable: object, scope: PackageJsonInfo, isImports: boolean): SearchResult | undefined { + const loadModuleFromTargetImportOrExport = getLoadModuleFromTargetImportOrExport(extensions, state, cache, redirectedReference, moduleName, scope, isImports); + + if (!endsWith(moduleName, directorySeparator) && moduleName.indexOf("*") === -1 && hasProperty(lookupTable, moduleName)) { + const target = (lookupTable as {[idx: string]: unknown})[moduleName]; + return loadModuleFromTargetImportOrExport(target, /*subpath*/ "", /*pattern*/ false); + } + const expandingKeys = sort(filter(getOwnKeys(lookupTable as MapLike), k => k.indexOf("*") !== -1 || endsWith(k, "/")), (a, b) => a.length - b.length); + for (const potentialTarget of expandingKeys) { + if (state.features & NodeResolutionFeatures.ExportsPatternTrailers && matchesPatternWithTrailer(potentialTarget, moduleName)) { + const target = (lookupTable as {[idx: string]: unknown})[potentialTarget]; + const starPos = potentialTarget.indexOf("*"); + const subpath = moduleName.substring(potentialTarget.substring(0, starPos).length, moduleName.length - (potentialTarget.length - 1 - starPos)); + return loadModuleFromTargetImportOrExport(target, subpath, /*pattern*/ true); + } + else if (endsWith(potentialTarget, "*") && startsWith(moduleName, potentialTarget.substring(0, potentialTarget.length - 1))) { + const target = (lookupTable as {[idx: string]: unknown})[potentialTarget]; + const subpath = moduleName.substring(potentialTarget.length - 1); + return loadModuleFromTargetImportOrExport(target, subpath, /*pattern*/ true); + } + else if (startsWith(moduleName, potentialTarget)) { + const target = (lookupTable as {[idx: string]: unknown})[potentialTarget]; + const subpath = moduleName.substring(potentialTarget.length); + return loadModuleFromTargetImportOrExport(target, subpath, /*pattern*/ false); + } + } + + function matchesPatternWithTrailer(target: string, name: string) { + if (endsWith(target, "*")) return false; // handled by next case in loop + const starPos = target.indexOf("*"); + if (starPos === -1) return false; // handled by last case in loop + return startsWith(name, target.substring(0, starPos)) && endsWith(name, target.substring(starPos + 1)); + } + } + + /** + * Gets the self-recursive function specialized to retrieving the targeted import/export element for the given resolution configuration + */ + function getLoadModuleFromTargetImportOrExport(extensions: Extensions, state: ModuleResolutionState, cache: ModuleResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined, moduleName: string, scope: PackageJsonInfo, isImports: boolean) { + return loadModuleFromTargetImportOrExport; + function loadModuleFromTargetImportOrExport(target: unknown, subpath: string, pattern: boolean): SearchResult | undefined { + if (typeof target === "string") { + if (!pattern && subpath.length > 0 && !endsWith(target, "/")) { + if (state.traceEnabled) { + trace(state.host, Diagnostics.package_json_scope_0_has_invalid_type_for_target_of_specifier_1, scope.packageDirectory, moduleName); + } + return toSearchResult(/*value*/ undefined); + } + if (!startsWith(target, "./")) { + if (isImports && !startsWith(target, "../") && !startsWith(target, "/") && !isRootedDiskPath(target)) { + const combinedLookup = pattern ? target.replace(/\*/g, subpath) : target + subpath; + const result = nodeModuleNameResolverWorker(state.features, combinedLookup, scope.packageDirectory + "/", state.compilerOptions, state.host, cache, [extensions], redirectedReference); + return toSearchResult(result.resolvedModule ? { path: result.resolvedModule.resolvedFileName, extension: result.resolvedModule.extension, packageId: result.resolvedModule.packageId, originalPath: result.resolvedModule.originalPath } : undefined); + } + if (state.traceEnabled) { + trace(state.host, Diagnostics.package_json_scope_0_has_invalid_type_for_target_of_specifier_1, scope.packageDirectory, moduleName); + } + return toSearchResult(/*value*/ undefined); + } + const parts = pathIsRelative(target) ? getPathComponents(target).slice(1) : getPathComponents(target); + const partsAfterFirst = parts.slice(1); + if (partsAfterFirst.indexOf("..") >= 0 || partsAfterFirst.indexOf(".") >= 0 || partsAfterFirst.indexOf("node_modules") >= 0) { + if (state.traceEnabled) { + trace(state.host, Diagnostics.package_json_scope_0_has_invalid_type_for_target_of_specifier_1, scope.packageDirectory, moduleName); + } + return toSearchResult(/*value*/ undefined); + } + const resolvedTarget = combinePaths(scope.packageDirectory, target); + // TODO: Assert that `resolvedTarget` is actually within the package directory? That's what the spec says.... but I'm not sure we need + // to be in the business of validating everyone's import and export map correctness. + const subpathParts = getPathComponents(subpath); + if (subpathParts.indexOf("..") >= 0 || subpathParts.indexOf(".") >= 0 || subpathParts.indexOf("node_modules") >= 0) { + if (state.traceEnabled) { + trace(state.host, Diagnostics.package_json_scope_0_has_invalid_type_for_target_of_specifier_1, scope.packageDirectory, moduleName); + } + return toSearchResult(/*value*/ undefined); + } + const finalPath = getNormalizedAbsolutePath(pattern ? resolvedTarget.replace(/\*/g, subpath) : resolvedTarget + subpath, state.host.getCurrentDirectory?.()); + + return toSearchResult(withPackageId(scope, loadJSOrExactTSFileName(extensions, finalPath, /*onlyRecordFailures*/ false, state))); + } + else if (typeof target === "object" && target !== null) { // eslint-disable-line no-null/no-null + if (!Array.isArray(target)) { + for (const key of getOwnKeys(target as MapLike)) { + if (key === "default" || state.conditions.indexOf(key) >= 0 || isApplicableVersionedTypesKey(state.conditions, key)) { + const subTarget = (target as MapLike)[key]; + const result = loadModuleFromTargetImportOrExport(subTarget, subpath, pattern); + if (result) { + return result; + } + } + } + return undefined; + } + else { + if (!length(target)) { + if (state.traceEnabled) { + trace(state.host, Diagnostics.package_json_scope_0_has_invalid_type_for_target_of_specifier_1, scope.packageDirectory, moduleName); + } + return toSearchResult(/*value*/ undefined); + } + for (const elem of target) { + const result = loadModuleFromTargetImportOrExport(elem, subpath, pattern); + if (result) { + return result; + } + } + } + } + else if (target === null) { // eslint-disable-line no-null/no-null + if (state.traceEnabled) { + trace(state.host, Diagnostics.package_json_scope_0_explicitly_maps_specifier_1_to_null, scope.packageDirectory, moduleName); + } + return toSearchResult(/*value*/ undefined); + } + if (state.traceEnabled) { + trace(state.host, Diagnostics.package_json_scope_0_has_invalid_type_for_target_of_specifier_1, scope.packageDirectory, moduleName); + } + return toSearchResult(/*value*/ undefined); + } + } + + /* @internal */ + export function isApplicableVersionedTypesKey(conditions: string[], key: string) { + if (conditions.indexOf("types") === -1) return false; // only apply versioned types conditions if the types condition is applied + if (!startsWith(key, "types@")) return false; + const range = VersionRange.tryParse(key.substring("types@".length)); + if (!range) return false; + return range.test(version); + } + + function loadModuleFromNearestNodeModulesDirectory(extensions: Extensions, moduleName: string, directory: string, state: ModuleResolutionState, cache: ModuleResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined): SearchResult { return loadModuleFromNearestNodeModulesDirectoryWorker(extensions, moduleName, directory, state, /*typesScopeOnly*/ false, cache, redirectedReference); } @@ -1462,27 +2141,27 @@ namespace ts { return loadModuleFromNearestNodeModulesDirectoryWorker(Extensions.DtsOnly, moduleName, directory, state, /*typesScopeOnly*/ true, /*cache*/ undefined, /*redirectedReference*/ undefined); } - function loadModuleFromNearestNodeModulesDirectoryWorker(extensions: Extensions, moduleName: string, directory: string, state: ModuleResolutionState, typesScopeOnly: boolean, cache: NonRelativeModuleNameResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined): SearchResult { - const perModuleNameCache = cache && cache.getOrCreateCacheForModuleName(moduleName, redirectedReference); + function loadModuleFromNearestNodeModulesDirectoryWorker(extensions: Extensions, moduleName: string, directory: string, state: ModuleResolutionState, typesScopeOnly: boolean, cache: ModuleResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined): SearchResult { + const perModuleNameCache = cache && cache.getOrCreateCacheForModuleName(moduleName, state.features === 0 ? undefined : state.features & NodeResolutionFeatures.EsmMode ? ModuleKind.ESNext : ModuleKind.CommonJS, redirectedReference); return forEachAncestorDirectory(normalizeSlashes(directory), ancestorDirectory => { if (getBaseFileName(ancestorDirectory) !== "node_modules") { const resolutionFromCache = tryFindNonRelativeModuleNameInCache(perModuleNameCache, moduleName, ancestorDirectory, state); if (resolutionFromCache) { return resolutionFromCache; } - return toSearchResult(loadModuleFromImmediateNodeModulesDirectory(extensions, moduleName, ancestorDirectory, state, typesScopeOnly)); + return toSearchResult(loadModuleFromImmediateNodeModulesDirectory(extensions, moduleName, ancestorDirectory, state, typesScopeOnly, cache, redirectedReference)); } }); } - function loadModuleFromImmediateNodeModulesDirectory(extensions: Extensions, moduleName: string, directory: string, state: ModuleResolutionState, typesScopeOnly: boolean): Resolved | undefined { + function loadModuleFromImmediateNodeModulesDirectory(extensions: Extensions, moduleName: string, directory: string, state: ModuleResolutionState, typesScopeOnly: boolean, cache: ModuleResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined): Resolved | undefined { const nodeModulesFolder = combinePaths(directory, "node_modules"); const nodeModulesFolderExists = directoryProbablyExists(nodeModulesFolder, state.host); if (!nodeModulesFolderExists && state.traceEnabled) { trace(state.host, Diagnostics.Directory_0_does_not_exist_skipping_all_lookups_in_it, nodeModulesFolder); } - const packageResult = typesScopeOnly ? undefined : loadModuleFromSpecificNodeModulesDirectory(extensions, moduleName, nodeModulesFolder, nodeModulesFolderExists, state); + const packageResult = typesScopeOnly ? undefined : loadModuleFromSpecificNodeModulesDirectory(extensions, moduleName, nodeModulesFolder, nodeModulesFolderExists, state, cache, redirectedReference); if (packageResult) { return packageResult; } @@ -1495,34 +2174,42 @@ namespace ts { } nodeModulesAtTypesExists = false; } - return loadModuleFromSpecificNodeModulesDirectory(Extensions.DtsOnly, mangleScopedPackageNameWithTrace(moduleName, state), nodeModulesAtTypes, nodeModulesAtTypesExists, state); + return loadModuleFromSpecificNodeModulesDirectory(Extensions.DtsOnly, mangleScopedPackageNameWithTrace(moduleName, state), nodeModulesAtTypes, nodeModulesAtTypesExists, state, cache, redirectedReference); } } - function loadModuleFromSpecificNodeModulesDirectory(extensions: Extensions, moduleName: string, nodeModulesDirectory: string, nodeModulesDirectoryExists: boolean, state: ModuleResolutionState): Resolved | undefined { + function loadModuleFromSpecificNodeModulesDirectory(extensions: Extensions, moduleName: string, nodeModulesDirectory: string, nodeModulesDirectoryExists: boolean, state: ModuleResolutionState, cache: ModuleResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined): Resolved | undefined { const candidate = normalizePath(combinePaths(nodeModulesDirectory, moduleName)); // First look for a nested package.json, as in `node_modules/foo/bar/package.json`. let packageInfo = getPackageJsonInfo(candidate, !nodeModulesDirectoryExists, state); - if (packageInfo) { - const fromFile = loadModuleFromFile(extensions, candidate, !nodeModulesDirectoryExists, state); - if (fromFile) { - return noPackageId(fromFile); - } + // But only if we're not respecting export maps (if we are, we might redirect around this location) + if (!(state.features & NodeResolutionFeatures.Exports)) { + if (packageInfo) { + const fromFile = loadModuleFromFile(extensions, candidate, !nodeModulesDirectoryExists, state); + if (fromFile) { + return noPackageId(fromFile); + } - const fromDirectory = loadNodeModuleFromDirectoryWorker( - extensions, - candidate, - !nodeModulesDirectoryExists, - state, - packageInfo.packageJsonContent, - packageInfo.versionPaths - ); - return withPackageId(packageInfo, fromDirectory); + const fromDirectory = loadNodeModuleFromDirectoryWorker( + extensions, + candidate, + !nodeModulesDirectoryExists, + state, + packageInfo.packageJsonContent, + packageInfo.versionPaths + ); + return withPackageId(packageInfo, fromDirectory); + } } + const { packageName, rest } = parsePackageName(moduleName); const loader: ResolutionKindSpecificLoader = (extensions, candidate, onlyRecordFailures, state) => { - const pathAndExtension = + // package exports are higher priority than file/directory lookups (and, if there's exports present, blocks them) + if (packageInfo && packageInfo.packageJsonContent.exports && state.features & NodeResolutionFeatures.Exports) { + return loadModuleFromExports(packageInfo, extensions, combinePaths(".", rest), state, cache, redirectedReference)?.value; + } + let pathAndExtension = loadModuleFromFile(extensions, candidate, onlyRecordFailures, state) || loadNodeModuleFromDirectoryWorker( extensions, @@ -1532,10 +2219,19 @@ namespace ts { packageInfo && packageInfo.packageJsonContent, packageInfo && packageInfo.versionPaths ); + if ( + !pathAndExtension && packageInfo + && packageInfo.packageJsonContent.exports === undefined + && packageInfo.packageJsonContent.main === undefined + && state.features & NodeResolutionFeatures.EsmMode + ) { + // EsmMode disables index lookup in `loadNodeModuleFromDirectoryWorker` generally, however non-relative package resolutions still assume + // a default `index.js` entrypoint if no `main` or `exports` are present + pathAndExtension = loadModuleFromFile(extensions, combinePaths(candidate, "index.js"), onlyRecordFailures, state); + } return withPackageId(packageInfo, pathAndExtension); }; - const { packageName, rest } = parsePackageName(moduleName); if (rest !== "") { // If "rest" is empty, we just did this search above. const packageDirectory = combinePaths(nodeModulesDirectory, packageName); @@ -1644,7 +2340,7 @@ namespace ts { export function classicNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: NonRelativeModuleNameResolutionCache, redirectedReference?: ResolvedProjectReference): ResolvedModuleWithFailedLookupLocations { const traceEnabled = isTraceEnabled(compilerOptions, host); const failedLookupLocations: string[] = []; - const state: ModuleResolutionState = { compilerOptions, host, traceEnabled, failedLookupLocations, packageJsonInfoCache: cache }; + const state: ModuleResolutionState = { compilerOptions, host, traceEnabled, failedLookupLocations, packageJsonInfoCache: cache, features: NodeResolutionFeatures.None, conditions: [] }; const containingDirectory = getDirectoryPath(containingFile); const resolved = tryResolve(Extensions.TypeScript) || tryResolve(Extensions.JavaScript); @@ -1658,7 +2354,7 @@ namespace ts { } if (!isExternalModuleNameRelative(moduleName)) { - const perModuleNameCache = cache && cache.getOrCreateCacheForModuleName(moduleName, redirectedReference); + const perModuleNameCache = cache && cache.getOrCreateCacheForModuleName(moduleName, /*mode*/ undefined, redirectedReference); // Climb up parent directories looking for a module. const resolved = forEachAncestorDirectory(containingDirectory, directory => { const resolutionFromCache = tryFindNonRelativeModuleNameInCache(perModuleNameCache, moduleName, directory, state); @@ -1694,8 +2390,8 @@ namespace ts { trace(host, Diagnostics.Auto_discovery_for_typings_is_enabled_in_project_0_Running_extra_resolution_pass_for_module_1_using_cache_location_2, projectName, moduleName, globalCache); } const failedLookupLocations: string[] = []; - const state: ModuleResolutionState = { compilerOptions, host, traceEnabled, failedLookupLocations, packageJsonInfoCache }; - const resolved = loadModuleFromImmediateNodeModulesDirectory(Extensions.DtsOnly, moduleName, globalCache, state, /*typesScopeOnly*/ false); + const state: ModuleResolutionState = { compilerOptions, host, traceEnabled, failedLookupLocations, packageJsonInfoCache, features: NodeResolutionFeatures.None, conditions: [] }; + const resolved = loadModuleFromImmediateNodeModulesDirectory(Extensions.DtsOnly, moduleName, globalCache, state, /*typesScopeOnly*/ false, /*cache*/ undefined, /*redirectedReference*/ undefined); return createResolvedModuleWithFailedLookupLocations(resolved, /*isExternalLibraryImport*/ true, failedLookupLocations, state.resultFromCache); } diff --git a/src/compiler/moduleSpecifiers.ts b/src/compiler/moduleSpecifiers.ts index f246f44c013..c740f8996ee 100644 --- a/src/compiler/moduleSpecifiers.ts +++ b/src/compiler/moduleSpecifiers.ts @@ -11,7 +11,7 @@ namespace ts.moduleSpecifiers { readonly ending: Ending; } - function getPreferences({ importModuleSpecifierPreference, importModuleSpecifierEnding }: UserPreferences, compilerOptions: CompilerOptions, importingSourceFile: SourceFile): Preferences { + function getPreferences(host: ModuleSpecifierResolutionHost, { importModuleSpecifierPreference, importModuleSpecifierEnding }: UserPreferences, compilerOptions: CompilerOptions, importingSourceFile: SourceFile): Preferences { return { relativePreference: importModuleSpecifierPreference === "relative" ? RelativePreference.Relative : @@ -25,34 +25,63 @@ namespace ts.moduleSpecifiers { case "minimal": return Ending.Minimal; case "index": return Ending.Index; case "js": return Ending.JsExtension; - default: return usesJsExtensionOnImports(importingSourceFile) ? Ending.JsExtension + default: return usesJsExtensionOnImports(importingSourceFile) || isFormatRequiringExtensions(compilerOptions, importingSourceFile.path, host) ? Ending.JsExtension : getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.NodeJs ? Ending.Index : Ending.Minimal; } } } - function getPreferencesForUpdate(compilerOptions: CompilerOptions, oldImportSpecifier: string): Preferences { + function getPreferencesForUpdate(compilerOptions: CompilerOptions, oldImportSpecifier: string, importingSourceFileName: Path, host: ModuleSpecifierResolutionHost): Preferences { return { relativePreference: isExternalModuleNameRelative(oldImportSpecifier) ? RelativePreference.Relative : RelativePreference.NonRelative, - ending: hasJSFileExtension(oldImportSpecifier) ? + ending: hasJSFileExtension(oldImportSpecifier) || isFormatRequiringExtensions(compilerOptions, importingSourceFileName, host) ? Ending.JsExtension : getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.NodeJs || endsWith(oldImportSpecifier, "index") ? Ending.Index : Ending.Minimal, }; } + function isFormatRequiringExtensions(compilerOptions: CompilerOptions, importingSourceFileName: Path, host: ModuleSpecifierResolutionHost) { + if (getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.Node12 + && getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.NodeNext) { + return false; + } + return getImpliedNodeFormatForFile(importingSourceFileName, host.getPackageJsonInfoCache?.(), getModuleResolutionHost(host), compilerOptions) !== ModuleKind.CommonJS; + } + + function getModuleResolutionHost(host: ModuleSpecifierResolutionHost): ModuleResolutionHost { + return { + fileExists: host.fileExists, + readFile: Debug.checkDefined(host.readFile), + directoryExists: host.directoryExists, + getCurrentDirectory: host.getCurrentDirectory, + realpath: host.realpath, + useCaseSensitiveFileNames: host.useCaseSensitiveFileNames?.(), + }; + } + + // `importingSourceFile` and `importingSourceFileName`? Why not just use `importingSourceFile.path`? + // Because when this is called by the file renamer, `importingSourceFile` is the file being renamed, + // while `importingSourceFileName` its *new* name. We need a source file just to get its + // `impliedNodeFormat` and to detect certain preferences from existing import module specifiers. export function updateModuleSpecifier( compilerOptions: CompilerOptions, + importingSourceFile: SourceFile, importingSourceFileName: Path, toFileName: string, host: ModuleSpecifierResolutionHost, oldImportSpecifier: string, ): string | undefined { - const res = getModuleSpecifierWorker(compilerOptions, importingSourceFileName, toFileName, host, getPreferencesForUpdate(compilerOptions, oldImportSpecifier), {}); + const res = getModuleSpecifierWorker(compilerOptions, importingSourceFile, importingSourceFileName, toFileName, host, getPreferencesForUpdate(compilerOptions, oldImportSpecifier, importingSourceFileName, host), {}); if (res === oldImportSpecifier) return undefined; return res; } - // Note: importingSourceFile is just for usesJsExtensionOnImports + // `importingSourceFile` and `importingSourceFileName`? Why not just use `importingSourceFile.path`? + // Because when this is called by the declaration emitter, `importingSourceFile` is the implementation + // file, but `importingSourceFileName` and `toFileName` refer to declaration files (the former to the + // one currently being produced; the latter to the one being imported). We need an implementation file + // just to get its `impliedNodeFormat` and to detect certain preferences from existing import module + // specifiers. export function getModuleSpecifier( compilerOptions: CompilerOptions, importingSourceFile: SourceFile, @@ -60,24 +89,25 @@ namespace ts.moduleSpecifiers { toFileName: string, host: ModuleSpecifierResolutionHost, ): string { - return getModuleSpecifierWorker(compilerOptions, importingSourceFileName, toFileName, host, getPreferences({}, compilerOptions, importingSourceFile), {}); + return getModuleSpecifierWorker(compilerOptions, importingSourceFile, importingSourceFileName, toFileName, host, getPreferences(host, {}, compilerOptions, importingSourceFile), {}); } export function getNodeModulesPackageName( compilerOptions: CompilerOptions, - importingSourceFileName: Path, + importingSourceFile: SourceFile, nodeModulesFileName: string, host: ModuleSpecifierResolutionHost, preferences: UserPreferences, ): string | undefined { - const info = getInfo(importingSourceFileName, host); - const modulePaths = getAllModulePaths(importingSourceFileName, nodeModulesFileName, host, preferences); + const info = getInfo(importingSourceFile.path, host); + const modulePaths = getAllModulePaths(importingSourceFile.path, nodeModulesFileName, host, preferences); return firstDefined(modulePaths, - modulePath => tryGetModuleNameAsNodeModule(modulePath, info, host, compilerOptions, /*packageNameOnly*/ true)); + modulePath => tryGetModuleNameAsNodeModule(modulePath, info, importingSourceFile, host, compilerOptions, /*packageNameOnly*/ true)); } function getModuleSpecifierWorker( compilerOptions: CompilerOptions, + importingSourceFile: SourceFile, importingSourceFileName: Path, toFileName: string, host: ModuleSpecifierResolutionHost, @@ -86,7 +116,7 @@ namespace ts.moduleSpecifiers { ): string { const info = getInfo(importingSourceFileName, host); const modulePaths = getAllModulePaths(importingSourceFileName, toFileName, host, userPreferences); - return firstDefined(modulePaths, modulePath => tryGetModuleNameAsNodeModule(modulePath, info, host, compilerOptions)) || + return firstDefined(modulePaths, modulePath => tryGetModuleNameAsNodeModule(modulePath, info, importingSourceFile, host, compilerOptions)) || getLocalModuleSpecifier(toFileName, info, compilerOptions, host, preferences); } @@ -175,7 +205,7 @@ namespace ts.moduleSpecifiers { userPreferences: UserPreferences, ): readonly string[] { const info = getInfo(importingSourceFile.path, host); - const preferences = getPreferences(userPreferences, compilerOptions, importingSourceFile); + const preferences = getPreferences(host, userPreferences, compilerOptions, importingSourceFile); const existingSpecifier = forEach(modulePaths, modulePath => forEach( host.getFileIncludeReasons().get(toPath(modulePath.path, host.getCurrentDirectory(), info.getCanonicalFileName)), reason => { @@ -203,7 +233,7 @@ namespace ts.moduleSpecifiers { let pathsSpecifiers: string[] | undefined; let relativeSpecifiers: string[] | undefined; for (const modulePath of modulePaths) { - const specifier = tryGetModuleNameAsNodeModule(modulePath, info, host, compilerOptions); + const specifier = tryGetModuleNameAsNodeModule(modulePath, info, importingSourceFile, host, compilerOptions); nodeModulesSpecifiers = append(nodeModulesSpecifiers, specifier); if (specifier && modulePath.isRedirect) { // If we got a specifier for a redirect, it was a bare package specifier (e.g. "@foo/bar", @@ -536,6 +566,77 @@ namespace ts.moduleSpecifiers { } } + const enum MatchingMode { + Exact, + Directory, + Pattern + } + + function tryGetModuleNameFromExports(options: CompilerOptions, targetFilePath: string, packageDirectory: string, packageName: string, exports: unknown, conditions: string[], mode = MatchingMode.Exact): { moduleFileToTry: string } | undefined { + if (typeof exports === "string") { + const pathOrPattern = getNormalizedAbsolutePath(combinePaths(packageDirectory, exports), /*currentDirectory*/ undefined); + const extensionSwappedTarget = hasTSFileExtension(targetFilePath) ? removeFileExtension(targetFilePath) + tryGetJSExtensionForFile(targetFilePath, options) : undefined; + switch (mode) { + case MatchingMode.Exact: + if (comparePaths(targetFilePath, pathOrPattern) === Comparison.EqualTo || (extensionSwappedTarget && comparePaths(extensionSwappedTarget, pathOrPattern) === Comparison.EqualTo)) { + return { moduleFileToTry: packageName }; + } + break; + case MatchingMode.Directory: + if (containsPath(pathOrPattern, targetFilePath)) { + const fragment = getRelativePathFromDirectory(pathOrPattern, targetFilePath, /*ignoreCase*/ false); + return { moduleFileToTry: getNormalizedAbsolutePath(combinePaths(combinePaths(packageName, exports), fragment), /*currentDirectory*/ undefined) }; + } + break; + case MatchingMode.Pattern: + const starPos = pathOrPattern.indexOf("*"); + const leadingSlice = pathOrPattern.slice(0, starPos); + const trailingSlice = pathOrPattern.slice(starPos + 1); + if (startsWith(targetFilePath, leadingSlice) && endsWith(targetFilePath, trailingSlice)) { + const starReplacement = targetFilePath.slice(leadingSlice.length, targetFilePath.length - trailingSlice.length); + return { moduleFileToTry: packageName.replace("*", starReplacement) }; + } + if (extensionSwappedTarget && startsWith(extensionSwappedTarget, leadingSlice) && endsWith(extensionSwappedTarget, trailingSlice)) { + const starReplacement = extensionSwappedTarget.slice(leadingSlice.length, extensionSwappedTarget.length - trailingSlice.length); + return { moduleFileToTry: packageName.replace("*", starReplacement) }; + } + break; + } + } + else if (Array.isArray(exports)) { + return forEach(exports, e => tryGetModuleNameFromExports(options, targetFilePath, packageDirectory, packageName, e, conditions)); + } + else if (typeof exports === "object" && exports !== null) { // eslint-disable-line no-null/no-null + if (allKeysStartWithDot(exports as MapLike)) { + // sub-mappings + // 3 cases: + // * directory mappings (legacyish, key ends with / (technically allows index/extension resolution under cjs mode)) + // * pattern mappings (contains a *) + // * exact mappings (no *, does not end with /) + return forEach(getOwnKeys(exports as MapLike), k => { + const subPackageName = getNormalizedAbsolutePath(combinePaths(packageName, k), /*currentDirectory*/ undefined); + const mode = endsWith(k, "/") ? MatchingMode.Directory + : stringContains(k, "*") ? MatchingMode.Pattern + : MatchingMode.Exact; + return tryGetModuleNameFromExports(options, targetFilePath, packageDirectory, subPackageName, (exports as MapLike)[k], conditions, mode); + }); + } + else { + // conditional mapping + for (const key of getOwnKeys(exports as MapLike)) { + if (key === "default" || conditions.indexOf(key) >= 0 || isApplicableVersionedTypesKey(conditions, key)) { + const subTarget = (exports as MapLike)[key]; + const result = tryGetModuleNameFromExports(options, targetFilePath, packageDirectory, packageName, subTarget, conditions); + if (result) { + return result; + } + } + } + } + } + return undefined; + } + function tryGetModuleNameFromRootDirs(rootDirs: readonly string[], moduleFileName: string, sourceDirectory: string, getCanonicalFileName: (file: string) => string, ending: Ending, compilerOptions: CompilerOptions): string | undefined { const normalizedTargetPath = getPathRelativeToRootDirs(moduleFileName, rootDirs, getCanonicalFileName); if (normalizedTargetPath === undefined) { @@ -549,7 +650,7 @@ namespace ts.moduleSpecifiers { : removeFileExtension(relativePath); } - function tryGetModuleNameAsNodeModule({ path, isRedirect }: ModulePath, { getCanonicalFileName, sourceDirectory }: Info, host: ModuleSpecifierResolutionHost, options: CompilerOptions, packageNameOnly?: boolean): string | undefined { + function tryGetModuleNameAsNodeModule({ path, isRedirect }: ModulePath, { getCanonicalFileName, sourceDirectory }: Info, importingSourceFile: SourceFile , host: ModuleSpecifierResolutionHost, options: CompilerOptions, packageNameOnly?: boolean): string | undefined { if (!host.fileExists || !host.readFile) { return undefined; } @@ -567,7 +668,15 @@ namespace ts.moduleSpecifiers { let moduleFileNameForExtensionless: string | undefined; while (true) { // If the module could be imported by a directory name, use that directory's name - const { moduleFileToTry, packageRootPath } = tryDirectoryWithPackageJson(packageRootIndex); + const { moduleFileToTry, packageRootPath, blockedByExports, verbatimFromExports } = tryDirectoryWithPackageJson(packageRootIndex); + if (getEmitModuleResolutionKind(options) !== ModuleResolutionKind.Classic) { + if (blockedByExports) { + return undefined; // File is under this package.json, but is not publicly exported - there's no way to name it via `node_modules` resolution + } + if (verbatimFromExports) { + return moduleFileToTry; + } + } if (packageRootPath) { moduleSpecifier = packageRootPath; isPackageRootPath = true; @@ -600,14 +709,34 @@ namespace ts.moduleSpecifiers { const nodeModulesDirectoryName = moduleSpecifier.substring(parts.topLevelPackageNameIndex + 1); const packageName = getPackageNameFromTypesPackageName(nodeModulesDirectoryName); // For classic resolution, only allow importing from node_modules/@types, not other node_modules - return getEmitModuleResolutionKind(options) !== ModuleResolutionKind.NodeJs && packageName === nodeModulesDirectoryName ? undefined : packageName; + return getEmitModuleResolutionKind(options) === ModuleResolutionKind.Classic && packageName === nodeModulesDirectoryName ? undefined : packageName; - function tryDirectoryWithPackageJson(packageRootIndex: number) { + function tryDirectoryWithPackageJson(packageRootIndex: number): { moduleFileToTry: string, packageRootPath?: string, blockedByExports?: true, verbatimFromExports?: true } { const packageRootPath = path.substring(0, packageRootIndex); const packageJsonPath = combinePaths(packageRootPath, "package.json"); let moduleFileToTry = path; - if (host.fileExists(packageJsonPath)) { - const packageJsonContent = JSON.parse(host.readFile!(packageJsonPath)!); + const cachedPackageJson = host.getPackageJsonInfoCache?.()?.getPackageJsonInfo(packageJsonPath); + if (typeof cachedPackageJson === "object" || cachedPackageJson === undefined && host.fileExists(packageJsonPath)) { + const packageJsonContent = cachedPackageJson?.packageJsonContent || JSON.parse(host.readFile!(packageJsonPath)!); + if (getEmitModuleResolutionKind(options) === ModuleResolutionKind.Node12 || getEmitModuleResolutionKind(options) === ModuleResolutionKind.NodeNext) { + // `conditions` *could* be made to go against `importingSourceFile.impliedNodeFormat` if something wanted to generate + // an ImportEqualsDeclaration in an ESM-implied file or an ImportCall in a CJS-implied file. But since this function is + // usually called to conjure an import out of thin air, we don't have an existing usage to call `getModeForUsageAtIndex` + // with, so for now we just stick with the mode of the file. + const conditions = ["node", importingSourceFile.impliedNodeFormat === ModuleKind.ESNext ? "import" : "require", "types"]; + const fromExports = packageJsonContent.exports && typeof packageJsonContent.name === "string" + ? tryGetModuleNameFromExports(options, path, packageRootPath, getPackageNameFromTypesPackageName(packageJsonContent.name), packageJsonContent.exports, conditions) + : undefined; + if (fromExports) { + const withJsExtension = !hasTSFileExtension(fromExports.moduleFileToTry) + ? fromExports + : { moduleFileToTry: removeFileExtension(fromExports.moduleFileToTry) + tryGetJSExtensionForFile(fromExports.moduleFileToTry, options) }; + return { ...withJsExtension, verbatimFromExports: true }; + } + if (packageJsonContent.exports) { + return { moduleFileToTry: path, blockedByExports: true }; + } + } const versionPaths = packageJsonContent.typesVersions ? getPackageJsonTypesVersionsPaths(packageJsonContent.typesVersions) : undefined; @@ -622,7 +751,6 @@ namespace ts.moduleSpecifiers { moduleFileToTry = combinePaths(packageRootPath, fromPaths); } } - // If the file is the main module, it can be imported by the package name const mainFileRelative = packageJsonContent.typings || packageJsonContent.types || packageJsonContent.main; if (isString(mainFileRelative)) { @@ -652,7 +780,7 @@ namespace ts.moduleSpecifiers { function tryGetAnyFileFromPath(host: ModuleSpecifierResolutionHost, path: string) { if (!host.fileExists) return; // We check all js, `node` and `json` extensions in addition to TS, since node module resolution would also choose those over the directory - const extensions = getSupportedExtensions({ allowJs: true }, [{ extension: "node", isMixedContent: false }, { extension: "json", isMixedContent: false, scriptKind: ScriptKind.JSON }]); + const extensions = flatten(getSupportedExtensions({ allowJs: true }, [{ extension: "node", isMixedContent: false }, { extension: "json", isMixedContent: false, scriptKind: ScriptKind.JSON }])); for (const e of extensions) { const fullPath = path + e; if (host.fileExists(fullPath)) { @@ -661,80 +789,17 @@ namespace ts.moduleSpecifiers { } } - interface NodeModulePathParts { - readonly topLevelNodeModulesIndex: number; - readonly topLevelPackageNameIndex: number; - readonly packageRootIndex: number; - readonly fileNameIndex: number; - } - function getNodeModulePathParts(fullPath: string): NodeModulePathParts | undefined { - // If fullPath can't be valid module file within node_modules, returns undefined. - // Example of expected pattern: /base/path/node_modules/[@scope/otherpackage/@otherscope/node_modules/]package/[subdirectory/]file.js - // Returns indices: ^ ^ ^ ^ - - let topLevelNodeModulesIndex = 0; - let topLevelPackageNameIndex = 0; - let packageRootIndex = 0; - let fileNameIndex = 0; - - const enum States { - BeforeNodeModules, - NodeModules, - Scope, - PackageContent - } - - let partStart = 0; - let partEnd = 0; - let state = States.BeforeNodeModules; - - while (partEnd >= 0) { - partStart = partEnd; - partEnd = fullPath.indexOf("/", partStart + 1); - switch (state) { - case States.BeforeNodeModules: - if (fullPath.indexOf(nodeModulesPathPart, partStart) === partStart) { - topLevelNodeModulesIndex = partStart; - topLevelPackageNameIndex = partEnd; - state = States.NodeModules; - } - break; - case States.NodeModules: - case States.Scope: - if (state === States.NodeModules && fullPath.charAt(partStart + 1) === "@") { - state = States.Scope; - } - else { - packageRootIndex = partEnd; - state = States.PackageContent; - } - break; - case States.PackageContent: - if (fullPath.indexOf(nodeModulesPathPart, partStart) === partStart) { - state = States.NodeModules; - } - else { - state = States.PackageContent; - } - break; - } - } - - fileNameIndex = partStart; - - return state > States.NodeModules ? { topLevelNodeModulesIndex, topLevelPackageNameIndex, packageRootIndex, fileNameIndex } : undefined; - } - function getPathRelativeToRootDirs(path: string, rootDirs: readonly string[], getCanonicalFileName: GetCanonicalFileName): string | undefined { return firstDefined(rootDirs, rootDir => { - const relativePath = getRelativePathIfInDirectory(path, rootDir, getCanonicalFileName)!; // TODO: GH#18217 - return isPathRelativeToParent(relativePath) ? undefined : relativePath; + const relativePath = getRelativePathIfInDirectory(path, rootDir, getCanonicalFileName); + return relativePath !== undefined && isPathRelativeToParent(relativePath) ? undefined : relativePath; }); } function removeExtensionAndIndexPostFix(fileName: string, ending: Ending, options: CompilerOptions): string { - if (fileExtensionIs(fileName, Extension.Json)) return fileName; + if (fileExtensionIsOneOf(fileName, [Extension.Json, Extension.Mjs, Extension.Cjs])) return fileName; const noExtension = removeFileExtension(fileName); + if (fileExtensionIsOneOf(fileName, [Extension.Dmts, Extension.Mts, Extension.Dcts, Extension.Cts])) return noExtension + getJSExtensionForFile(fileName, options); switch (ending) { case Ending.Minimal: return removeSuffix(noExtension, "/index"); @@ -763,6 +828,14 @@ namespace ts.moduleSpecifiers { case Extension.Jsx: case Extension.Json: return ext; + case Extension.Dmts: + case Extension.Mts: + case Extension.Mjs: + return Extension.Mjs; + case Extension.Dcts: + case Extension.Cts: + case Extension.Cjs: + return Extension.Cjs; default: return undefined; } diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index dbe5394908d..cd7ed6285cb 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -180,7 +180,8 @@ namespace ts { visitNode(cbNode, (node as TypePredicateNode).parameterName) || visitNode(cbNode, (node as TypePredicateNode).type); case SyntaxKind.TypeQuery: - return visitNode(cbNode, (node as TypeQueryNode).exprName); + return visitNode(cbNode, (node as TypeQueryNode).exprName) || + visitNodes(cbNode, cbNodes, (node as TypeQueryNode).typeArguments); case SyntaxKind.TypeLiteral: return visitNodes(cbNode, cbNodes, (node as TypeLiteralNode).members); case SyntaxKind.ArrayType: @@ -212,7 +213,8 @@ namespace ts { visitNode(cbNode, (node as MappedTypeNode).typeParameter) || visitNode(cbNode, (node as MappedTypeNode).nameType) || visitNode(cbNode, (node as MappedTypeNode).questionToken) || - visitNode(cbNode, (node as MappedTypeNode).type); + visitNode(cbNode, (node as MappedTypeNode).type) || + visitNodes(cbNode, cbNodes, (node as MappedTypeNode).members); case SyntaxKind.LiteralType: return visitNode(cbNode, (node as LiteralTypeNode).literal); case SyntaxKind.NamedTupleMember: @@ -398,13 +400,18 @@ namespace ts { return visitNodes(cbNode, cbNodes, node.decorators) || visitNodes(cbNode, cbNodes, node.modifiers) || visitNode(cbNode, (node as ImportDeclaration).importClause) || - visitNode(cbNode, (node as ImportDeclaration).moduleSpecifier); + visitNode(cbNode, (node as ImportDeclaration).moduleSpecifier) || + visitNode(cbNode, (node as ImportDeclaration).assertClause); case SyntaxKind.ImportClause: return visitNode(cbNode, (node as ImportClause).name) || visitNode(cbNode, (node as ImportClause).namedBindings); + case SyntaxKind.AssertClause: + return visitNodes(cbNode, cbNodes, (node as AssertClause).elements); + case SyntaxKind.AssertEntry: + return visitNode(cbNode, (node as AssertEntry).name) || + visitNode(cbNode, (node as AssertEntry).value); case SyntaxKind.NamespaceExportDeclaration: return visitNode(cbNode, (node as NamespaceExportDeclaration).name); - case SyntaxKind.NamespaceImport: return visitNode(cbNode, (node as NamespaceImport).name); case SyntaxKind.NamespaceExport: @@ -416,7 +423,8 @@ namespace ts { return visitNodes(cbNode, cbNodes, node.decorators) || visitNodes(cbNode, cbNodes, node.modifiers) || visitNode(cbNode, (node as ExportDeclaration).exportClause) || - visitNode(cbNode, (node as ExportDeclaration).moduleSpecifier); + visitNode(cbNode, (node as ExportDeclaration).moduleSpecifier) || + visitNode(cbNode, (node as ExportDeclaration).assertClause); case SyntaxKind.ImportSpecifier: case SyntaxKind.ExportSpecifier: return visitNode(cbNode, (node as ImportOrExportSpecifier).propertyName) || @@ -868,7 +876,7 @@ namespace ts { initializeState("", content, languageVersion, /*syntaxCursor*/ undefined, ScriptKind.JS); // Prime the scanner. nextToken(); - const entityName = parseEntityName(/*allowReservedWords*/ true); + const entityName = parseEntityName(/*allowReservedWords*/ true, /*allowPrivateIdentifiers*/ false); const isInvalid = token() === SyntaxKind.EndOfFileToken && !parseDiagnostics.length; clearState(); return isInvalid ? entityName : undefined; @@ -1633,7 +1641,7 @@ namespace ts { parseErrorAtCurrentToken(blankDiagnostic); } else { - parseErrorAtCurrentToken(nameDiagnostic, tokenToString(token())); + parseErrorAtCurrentToken(nameDiagnostic, scanner.getTokenValue()); } } @@ -1886,6 +1894,11 @@ namespace ts { token() === SyntaxKind.NumericLiteral; } + function isAssertionKey(): boolean { + return tokenIsIdentifierOrKeyword(token()) || + token() === SyntaxKind.StringLiteral; + } + function parsePropertyNameWorker(allowComputedPropertyNames: boolean): PropertyName { if (token() === SyntaxKind.StringLiteral || token() === SyntaxKind.NumericLiteral) { const node = parseLiteralNode() as StringLiteral | NumericLiteral; @@ -1963,7 +1976,6 @@ namespace ts { case SyntaxKind.DefaultKeyword: return nextTokenCanFollowDefaultKeyword(); case SyntaxKind.StaticKeyword: - return nextTokenIsOnSameLineAndCanFollowModifier(); case SyntaxKind.GetKeyword: case SyntaxKind.SetKeyword: nextToken(); @@ -2051,6 +2063,8 @@ namespace ts { return isLiteralPropertyName(); case ParsingContext.ObjectBindingElements: return token() === SyntaxKind.OpenBracketToken || token() === SyntaxKind.DotDotDotToken || isLiteralPropertyName(); + case ParsingContext.AssertEntries: + return isAssertionKey(); case ParsingContext.HeritageClauseElement: // If we see `{ ... }` then only consume it as an expression if it is followed by `,` or `{` // That way we won't consume the body of a class in its heritage clause. @@ -2171,6 +2185,7 @@ namespace ts { case ParsingContext.ObjectLiteralMembers: case ParsingContext.ObjectBindingElements: case ParsingContext.ImportOrExportSpecifiers: + case ParsingContext.AssertEntries: return token() === SyntaxKind.CloseBraceToken; case ParsingContext.SwitchClauseStatements: return token() === SyntaxKind.CloseBraceToken || token() === SyntaxKind.CaseKeyword || token() === SyntaxKind.DefaultKeyword; @@ -2597,7 +2612,10 @@ namespace ts { case ParsingContext.ObjectLiteralMembers: return parseErrorAtCurrentToken(Diagnostics.Property_assignment_expected); case ParsingContext.ArrayLiteralMembers: return parseErrorAtCurrentToken(Diagnostics.Expression_or_comma_expected); case ParsingContext.JSDocParameters: return parseErrorAtCurrentToken(Diagnostics.Parameter_declaration_expected); - case ParsingContext.Parameters: return parseErrorAtCurrentToken(Diagnostics.Parameter_declaration_expected); + case ParsingContext.Parameters: + return isKeyword(token()) + ? parseErrorAtCurrentToken(Diagnostics._0_is_not_allowed_as_a_parameter_name, tokenToString(token())) + : parseErrorAtCurrentToken(Diagnostics.Parameter_declaration_expected); case ParsingContext.TypeParameters: return parseErrorAtCurrentToken(Diagnostics.Type_parameter_declaration_expected); case ParsingContext.TypeArguments: return parseErrorAtCurrentToken(Diagnostics.Type_argument_expected); case ParsingContext.TupleElementTypes: return parseErrorAtCurrentToken(Diagnostics.Type_expected); @@ -2702,7 +2720,7 @@ namespace ts { return createMissingList(); } - function parseEntityName(allowReservedWords: boolean, diagnosticMessage?: DiagnosticMessage): EntityName { + function parseEntityName(allowReservedWords: boolean, allowPrivateIdentifiers: boolean, diagnosticMessage?: DiagnosticMessage): EntityName { const pos = getNodePos(); let entity: EntityName = allowReservedWords ? parseIdentifierName(diagnosticMessage) : parseIdentifier(diagnosticMessage); let dotPos = getNodePos(); @@ -2716,7 +2734,7 @@ namespace ts { entity = finishNode( factory.createQualifiedName( entity, - parseRightSideOfDot(allowReservedWords, /* allowPrivateIdentifiers */ false) as Identifier + parseRightSideOfDot(allowReservedWords, allowPrivateIdentifiers) as Identifier ), pos ); @@ -2901,7 +2919,7 @@ namespace ts { // TYPES function parseEntityNameOfTypeReference() { - return parseEntityName(/*allowReservedWords*/ true, Diagnostics.Type_expected); + return parseEntityName(/*allowReservedWords*/ true, /*allowPrivateIdentifiers*/ false, Diagnostics.Type_expected); } function parseTypeArgumentsOfTypeReference() { @@ -3061,7 +3079,9 @@ namespace ts { function parseTypeQuery(): TypeQueryNode { const pos = getNodePos(); parseExpected(SyntaxKind.TypeOfKeyword); - return finishNode(factory.createTypeQueryNode(parseEntityName(/*allowReservedWords*/ true)), pos); + const entityName = parseEntityName(/*allowReservedWords*/ true, /*allowPrivateIdentifiers*/ true); + const typeArguments = tryParseTypeArguments(); + return finishNode(factory.createTypeQueryNode(entityName, typeArguments), pos); } function parseTypeParameter(): TypeParameterDeclaration { @@ -3521,8 +3541,9 @@ namespace ts { } const type = parseTypeAnnotation(); parseSemicolon(); + const members = parseList(ParsingContext.TypeMembers, parseTypeMember); parseExpected(SyntaxKind.CloseBraceToken); - return finishNode(factory.createMappedTypeNode(readonlyToken, typeParameter, nameType, questionToken, type), pos); + return finishNode(factory.createMappedTypeNode(readonlyToken, typeParameter, nameType, questionToken, type, members), pos); } function parseTupleElementType() { @@ -4415,7 +4436,7 @@ namespace ts { return true; } } - else if (third === SyntaxKind.CommaToken) { + else if (third === SyntaxKind.CommaToken || third === SyntaxKind.EqualsToken) { return true; } return false; @@ -4521,9 +4542,16 @@ namespace ts { // - "(x,y)" is a comma expression parsed as a signature with two parameters. // - "a ? (b): c" will have "(b):" parsed as a signature with a return type annotation. // - "a ? (b): function() {}" will too, since function() is a valid JSDoc function type. + // - "a ? (b): (function() {})" as well, but inside of a parenthesized type with an arbitrary amount of nesting. // // So we need just a bit of lookahead to ensure that it can only be a signature. - const hasJSDocFunctionType = type && isJSDocFunctionType(type); + + let unwrappedType = type; + while (unwrappedType?.kind === SyntaxKind.ParenthesizedType) { + unwrappedType = (unwrappedType as ParenthesizedTypeNode).type; // Skip parens if need be + } + + const hasJSDocFunctionType = unwrappedType && isJSDocFunctionType(unwrappedType); if (!allowAmbiguity && token() !== SyntaxKind.EqualsGreaterThanToken && (hasJSDocFunctionType || token() !== SyntaxKind.OpenBraceToken)) { // Returning undefined here will cause our caller to rewind to where we started from. return undefined; @@ -5042,12 +5070,12 @@ namespace ts { && !tagNamesAreEquivalent(lastChild.openingElement.tagName, lastChild.closingElement.tagName) && tagNamesAreEquivalent(opening.tagName, lastChild.closingElement.tagName)) { // when an unclosed JsxOpeningElement incorrectly parses its parent's JsxClosingElement, - // restructure (

(...
)) --> (
(...)
) + // restructure (
(......
)) --> (
(......)
) // (no need to error; the parent will error) - const end = lastChild.openingElement.end; // newly-created children and closing are both zero-width end/end + const end = lastChild.children.end; const newLast = finishNode(factory.createJsxElement( lastChild.openingElement, - createNodeArray([], end, end), + lastChild.children, finishNode(factory.createJsxClosingElement(finishNode(factory.createIdentifier(""), end, end)), end, end)), lastChild.openingElement.pos, end); @@ -5403,12 +5431,6 @@ namespace ts { continue; } - if (!questionDotToken && token() === SyntaxKind.ExclamationToken && !scanner.hasPrecedingLineBreak()) { - nextToken(); - expression = finishNode(factory.createNonNullExpression(expression), pos); - continue; - } - // when in the [Decorator] context, we do not parse ElementAccess as it could be part of a ComputedPropertyName if ((questionDotToken || !inDecoratorContext()) && parseOptional(SyntaxKind.OpenBracketToken)) { expression = parseElementAccessExpressionRest(pos, expression, questionDotToken); @@ -5416,10 +5438,26 @@ namespace ts { } if (isTemplateStartOfTaggedTemplate()) { - expression = parseTaggedTemplateRest(pos, expression, questionDotToken, /*typeArguments*/ undefined); + // Absorb type arguments into TemplateExpression when preceding expression is ExpressionWithTypeArguments + expression = !questionDotToken && expression.kind === SyntaxKind.ExpressionWithTypeArguments ? + parseTaggedTemplateRest(pos, (expression as ExpressionWithTypeArguments).expression, questionDotToken, (expression as ExpressionWithTypeArguments).typeArguments) : + parseTaggedTemplateRest(pos, expression, questionDotToken, /*typeArguments*/ undefined); continue; } + if (!questionDotToken) { + if (token() === SyntaxKind.ExclamationToken && !scanner.hasPrecedingLineBreak()) { + nextToken(); + expression = finishNode(factory.createNonNullExpression(expression), pos); + continue; + } + const typeArguments = tryParse(parseTypeArgumentsInExpression); + if (typeArguments) { + expression = finishNode(factory.createExpressionWithTypeArguments(expression, typeArguments), pos); + continue; + } + } + return expression as MemberExpression; } } @@ -5446,39 +5484,30 @@ namespace ts { function parseCallExpressionRest(pos: number, expression: LeftHandSideExpression): LeftHandSideExpression { while (true) { expression = parseMemberExpressionRest(pos, expression, /*allowOptionalChain*/ true); + let typeArguments: NodeArray | undefined; const questionDotToken = parseOptionalToken(SyntaxKind.QuestionDotToken); - // handle 'foo<()' - // parse template arguments only in TypeScript files (not in JavaScript files). - if ((contextFlags & NodeFlags.JavaScriptFile) === 0 && (token() === SyntaxKind.LessThanToken || token() === SyntaxKind.LessThanLessThanToken)) { - // See if this is the start of a generic invocation. If so, consume it and - // keep checking for postfix expressions. Otherwise, it's just a '<' that's - // part of an arithmetic expression. Break out so we consume it higher in the - // stack. - const typeArguments = tryParse(parseTypeArgumentsInExpression); - if (typeArguments) { - if (isTemplateStartOfTaggedTemplate()) { - expression = parseTaggedTemplateRest(pos, expression, questionDotToken, typeArguments); - continue; - } - - const argumentList = parseArgumentList(); - const callExpr = questionDotToken || tryReparseOptionalChain(expression) ? - factory.createCallChain(expression, questionDotToken, typeArguments, argumentList) : - factory.createCallExpression(expression, typeArguments, argumentList); - expression = finishNode(callExpr, pos); + if (questionDotToken) { + typeArguments = tryParse(parseTypeArgumentsInExpression); + if (isTemplateStartOfTaggedTemplate()) { + expression = parseTaggedTemplateRest(pos, expression, questionDotToken, typeArguments); continue; } } - else if (token() === SyntaxKind.OpenParenToken) { + if (typeArguments || token() === SyntaxKind.OpenParenToken) { + // Absorb type arguments into CallExpression when preceding expression is ExpressionWithTypeArguments + if (!questionDotToken && expression.kind === SyntaxKind.ExpressionWithTypeArguments) { + typeArguments = (expression as ExpressionWithTypeArguments).typeArguments; + expression = (expression as ExpressionWithTypeArguments).expression; + } const argumentList = parseArgumentList(); const callExpr = questionDotToken || tryReparseOptionalChain(expression) ? - factory.createCallChain(expression, questionDotToken, /*typeArguments*/ undefined, argumentList) : - factory.createCallExpression(expression, /*typeArguments*/ undefined, argumentList); + factory.createCallChain(expression, questionDotToken, typeArguments, argumentList) : + factory.createCallExpression(expression, typeArguments, argumentList); expression = finishNode(callExpr, pos); continue; } if (questionDotToken) { - // We failed to parse anything, so report a missing identifier here. + // We parsed `?.` but then failed to parse anything, so report a missing identifier here. const name = createMissingNode(SyntaxKind.Identifier, /*reportAtCurrentPosition*/ false, Diagnostics.Identifier_expected); expression = finishNode(factory.createPropertyAccessChain(expression, questionDotToken, name), pos); } @@ -5511,22 +5540,26 @@ namespace ts { return undefined; } - // If we have a '<', then only parse this as a argument list if the type arguments - // are complete and we have an open paren. if we don't, rewind and return nothing. - return typeArguments && canFollowTypeArgumentsInExpression() - ? typeArguments - : undefined; + // We successfully parsed a type argument list. The next token determines whether we want to + // treat it as such. If the type argument list is followed by `(` or a template literal, as in + // `f(42)`, we favor the type argument interpretation even though JavaScript would view + // it as a relational expression. + return typeArguments && canFollowTypeArgumentsInExpression() ? typeArguments : undefined; } function canFollowTypeArgumentsInExpression(): boolean { switch (token()) { + // These tokens can follow a type argument list in a call expression. case SyntaxKind.OpenParenToken: // foo( case SyntaxKind.NoSubstitutionTemplateLiteral: // foo `...` case SyntaxKind.TemplateHead: // foo `...${100}...` - // these are the only tokens can legally follow a type argument - // list. So we definitely want to treat them as type arg lists. + // These tokens can't follow in a call expression, nor can they start an + // expression. So, consider the type argument list part of an instantiation + // expression. // falls through + case SyntaxKind.CommaToken: // foo, case SyntaxKind.DotToken: // foo. + case SyntaxKind.QuestionDotToken: // foo?. case SyntaxKind.CloseParenToken: // foo) case SyntaxKind.CloseBracketToken: // foo] case SyntaxKind.ColonToken: // foo: @@ -5544,21 +5577,10 @@ namespace ts { case SyntaxKind.BarToken: // foo | case SyntaxKind.CloseBraceToken: // foo } case SyntaxKind.EndOfFileToken: // foo - // these cases can't legally follow a type arg list. However, they're not legal - // expressions either. The user is probably in the middle of a generic type. So - // treat it as such. return true; - - case SyntaxKind.CommaToken: // foo, - case SyntaxKind.OpenBraceToken: // foo { - // We don't want to treat these as type arguments. Otherwise we'll parse this - // as an invocation expression. Instead, we want to parse out the expression - // in isolation from the type arguments. - // falls through - default: - // Anything else treat as an expression. - return false; } + // Treat anything else as an expression. + return false; } function parsePrimaryExpression(): PrimaryExpression { @@ -5603,6 +5625,8 @@ namespace ts { break; case SyntaxKind.TemplateHead: return parseTemplateExpression(/* isTaggedTemplate */ false); + case SyntaxKind.PrivateIdentifier: + return parsePrivateIdentifier(); } return parseIdentifier(Diagnostics.Expression_expected); @@ -5763,30 +5787,16 @@ namespace ts { const name = parseIdentifierName(); return finishNode(factory.createMetaProperty(SyntaxKind.NewKeyword, name), pos); } - const expressionPos = getNodePos(); - let expression: MemberExpression = parsePrimaryExpression(); - let typeArguments; - while (true) { - expression = parseMemberExpressionRest(expressionPos, expression, /*allowOptionalChain*/ false); - typeArguments = tryParse(parseTypeArgumentsInExpression); - if (isTemplateStartOfTaggedTemplate()) { - Debug.assert(!!typeArguments, - "Expected a type argument list; all plain tagged template starts should be consumed in 'parseMemberExpressionRest'"); - expression = parseTaggedTemplateRest(expressionPos, expression, /*optionalChain*/ undefined, typeArguments); - typeArguments = undefined; - } - break; + let expression: LeftHandSideExpression = parseMemberExpressionRest(expressionPos, parsePrimaryExpression(), /*allowOptionalChain*/ false); + let typeArguments: NodeArray | undefined; + // Absorb type arguments into NewExpression when preceding expression is ExpressionWithTypeArguments + if (expression.kind === SyntaxKind.ExpressionWithTypeArguments) { + typeArguments = (expression as ExpressionWithTypeArguments).typeArguments; + expression = (expression as ExpressionWithTypeArguments).expression; } - - let argumentsArray: NodeArray | undefined; - if (token() === SyntaxKind.OpenParenToken) { - argumentsArray = parseArgumentList(); - } - else if (typeArguments) { - parseErrorAt(pos, scanner.getStartPos(), Diagnostics.A_new_expression_with_type_arguments_must_always_be_followed_by_a_parenthesized_argument_list); - } - return finishNode(factory.createNewExpression(expression, typeArguments, argumentsArray), pos); + const argumentList = token() === SyntaxKind.OpenParenToken ? parseArgumentList() : undefined; + return finishNode(factory.createNewExpression(expression, typeArguments, argumentList), pos); } // STATEMENTS @@ -5978,11 +5988,12 @@ namespace ts { function parseCaseClause(): CaseClause { const pos = getNodePos(); + const hasJSDoc = hasPrecedingJSDocComment(); parseExpected(SyntaxKind.CaseKeyword); const expression = allowInAnd(parseExpression); parseExpected(SyntaxKind.ColonToken); const statements = parseList(ParsingContext.SwitchClauseStatements, parseStatement); - return finishNode(factory.createCaseClause(expression, statements), pos); + return withJSDoc(finishNode(factory.createCaseClause(expression, statements), pos), hasJSDoc); } function parseDefaultClause(): DefaultClause { @@ -6053,7 +6064,7 @@ namespace ts { // one out no matter what. let finallyBlock: Block | undefined; if (!catchClause || token() === SyntaxKind.FinallyKeyword) { - parseExpected(SyntaxKind.FinallyKeyword); + parseExpected(SyntaxKind.FinallyKeyword, Diagnostics.catch_or_finally_expected); finallyBlock = parseBlock(/*ignoreMissingOpenBrace*/ false); } @@ -6854,7 +6865,7 @@ namespace ts { return list && createNodeArray(list, pos); } - function tryParseModifier(permitInvalidConstAsModifier?: boolean, stopOnStartOfClassStaticBlock?: boolean): Modifier | undefined { + function tryParseModifier(permitInvalidConstAsModifier?: boolean, stopOnStartOfClassStaticBlock?: boolean, hasSeenStaticModifier?: boolean): Modifier | undefined { const pos = getNodePos(); const kind = token(); @@ -6868,6 +6879,9 @@ namespace ts { else if (stopOnStartOfClassStaticBlock && token() === SyntaxKind.StaticKeyword && lookAhead(nextTokenIsOpenBrace)) { return undefined; } + else if (hasSeenStaticModifier && token() === SyntaxKind.StaticKeyword) { + return undefined; + } else { if (!parseAnyContextualModifier()) { return undefined; @@ -6886,8 +6900,9 @@ namespace ts { */ function parseModifiers(permitInvalidConstAsModifier?: boolean, stopOnStartOfClassStaticBlock?: boolean): NodeArray | undefined { const pos = getNodePos(); - let list, modifier; - while (modifier = tryParseModifier(permitInvalidConstAsModifier, stopOnStartOfClassStaticBlock)) { + let list, modifier, hasSeenStatic = false; + while (modifier = tryParseModifier(permitInvalidConstAsModifier, stopOnStartOfClassStaticBlock, hasSeenStatic)) { + if (modifier.kind === SyntaxKind.StaticKeyword) hasSeenStatic = true; list = append(list, modifier); } return list && createNodeArray(list, pos); @@ -7039,6 +7054,9 @@ namespace ts { function parseExpressionWithTypeArguments(): ExpressionWithTypeArguments { const pos = getNodePos(); const expression = parseLeftHandSideExpressionOrHigher(); + if (expression.kind === SyntaxKind.ExpressionWithTypeArguments) { + return expression as ExpressionWithTypeArguments; + } const typeArguments = tryParseTypeArguments(); return finishNode(factory.createExpressionWithTypeArguments(expression, typeArguments), pos); } @@ -7234,13 +7252,50 @@ namespace ts { importClause = parseImportClause(identifier, afterImportPos, isTypeOnly); parseExpected(SyntaxKind.FromKeyword); } - const moduleSpecifier = parseModuleSpecifier(); + + let assertClause: AssertClause | undefined; + if (token() === SyntaxKind.AssertKeyword && !scanner.hasPrecedingLineBreak()) { + assertClause = parseAssertClause(); + } + parseSemicolon(); - const node = factory.createImportDeclaration(decorators, modifiers, importClause, moduleSpecifier); + const node = factory.createImportDeclaration(decorators, modifiers, importClause, moduleSpecifier, assertClause); return withJSDoc(finishNode(node, pos), hasJSDoc); } + function parseAssertEntry() { + const pos = getNodePos(); + const name = tokenIsIdentifierOrKeyword(token()) ? parseIdentifierName() : parseLiteralLikeNode(SyntaxKind.StringLiteral) as StringLiteral; + parseExpected(SyntaxKind.ColonToken); + const value = parseAssignmentExpressionOrHigher(); + return finishNode(factory.createAssertEntry(name, value), pos); + } + + function parseAssertClause() { + const pos = getNodePos(); + parseExpected(SyntaxKind.AssertKeyword); + const openBracePosition = scanner.getTokenPos(); + if (parseExpected(SyntaxKind.OpenBraceToken)) { + const multiLine = scanner.hasPrecedingLineBreak(); + const elements = parseDelimitedList(ParsingContext.AssertEntries, parseAssertEntry, /*considerSemicolonAsDelimiter*/ true); + if (!parseExpected(SyntaxKind.CloseBraceToken)) { + const lastError = lastOrUndefined(parseDiagnostics); + if (lastError && lastError.code === Diagnostics._0_expected.code) { + addRelatedInfo( + lastError, + createDetachedDiagnostic(fileName, openBracePosition, 1, Diagnostics.The_parser_expected_to_find_a_to_match_the_token_here) + ); + } + } + return finishNode(factory.createAssertClause(elements, multiLine), pos); + } + else { + const elements = createNodeArray([], getNodePos(), /*end*/ undefined, /*hasTrailingComma*/ false); + return finishNode(factory.createAssertClause(elements, /*multiLine*/ false), pos); + } + } + function tokenAfterImportDefinitelyProducesImportDeclaration() { return token() === SyntaxKind.AsteriskToken || token() === SyntaxKind.OpenBraceToken; } @@ -7282,7 +7337,7 @@ namespace ts { function parseModuleReference() { return isExternalModuleReference() ? parseExternalModuleReference() - : parseEntityName(/*allowReservedWords*/ false); + : parseEntityName(/*allowReservedWords*/ false, /*allowPrivateIdentifiers*/ false); } function parseExternalModuleReference() { @@ -7338,7 +7393,8 @@ namespace ts { } function parseExportSpecifier() { - return parseImportOrExportSpecifier(SyntaxKind.ExportSpecifier) as ExportSpecifier; + const hasJSDoc = hasPrecedingJSDocComment(); + return withJSDoc(parseImportOrExportSpecifier(SyntaxKind.ExportSpecifier) as ExportSpecifier, hasJSDoc); } function parseImportSpecifier() { @@ -7356,27 +7412,76 @@ namespace ts { let checkIdentifierIsKeyword = isKeyword(token()) && !isIdentifier(); let checkIdentifierStart = scanner.getTokenPos(); let checkIdentifierEnd = scanner.getTextPos(); - const identifierName = parseIdentifierName(); + let isTypeOnly = false; let propertyName: Identifier | undefined; - let name: Identifier; - if (token() === SyntaxKind.AsKeyword) { - propertyName = identifierName; - parseExpected(SyntaxKind.AsKeyword); - checkIdentifierIsKeyword = isKeyword(token()) && !isIdentifier(); - checkIdentifierStart = scanner.getTokenPos(); - checkIdentifierEnd = scanner.getTextPos(); - name = parseIdentifierName(); + let canParseAsKeyword = true; + let name = parseIdentifierName(); + if (name.escapedText === "type") { + // If the first token of an import specifier is 'type', there are a lot of possibilities, + // especially if we see 'as' afterwards: + // + // import { type } from "mod"; - isTypeOnly: false, name: type + // import { type as } from "mod"; - isTypeOnly: true, name: as + // import { type as as } from "mod"; - isTypeOnly: false, name: as, propertyName: type + // import { type as as as } from "mod"; - isTypeOnly: true, name: as, propertyName: as + if (token() === SyntaxKind.AsKeyword) { + // { type as ...? } + const firstAs = parseIdentifierName(); + if (token() === SyntaxKind.AsKeyword) { + // { type as as ...? } + const secondAs = parseIdentifierName(); + if (tokenIsIdentifierOrKeyword(token())) { + // { type as as something } + isTypeOnly = true; + propertyName = firstAs; + name = parseNameWithKeywordCheck(); + canParseAsKeyword = false; + } + else { + // { type as as } + propertyName = name; + name = secondAs; + canParseAsKeyword = false; + } + } + else if (tokenIsIdentifierOrKeyword(token())) { + // { type as something } + propertyName = name; + canParseAsKeyword = false; + name = parseNameWithKeywordCheck(); + } + else { + // { type as } + isTypeOnly = true; + name = firstAs; + } + } + else if (tokenIsIdentifierOrKeyword(token())) { + // { type something ...? } + isTypeOnly = true; + name = parseNameWithKeywordCheck(); + } } - else { - name = identifierName; + + if (canParseAsKeyword && token() === SyntaxKind.AsKeyword) { + propertyName = name; + parseExpected(SyntaxKind.AsKeyword); + name = parseNameWithKeywordCheck(); } if (kind === SyntaxKind.ImportSpecifier && checkIdentifierIsKeyword) { parseErrorAt(checkIdentifierStart, checkIdentifierEnd, Diagnostics.Identifier_expected); } const node = kind === SyntaxKind.ImportSpecifier - ? factory.createImportSpecifier(propertyName, name) - : factory.createExportSpecifier(propertyName, name); + ? factory.createImportSpecifier(isTypeOnly, propertyName, name) + : factory.createExportSpecifier(isTypeOnly, propertyName, name); return finishNode(node, pos); + + function parseNameWithKeywordCheck() { + checkIdentifierIsKeyword = isKeyword(token()) && !isIdentifier(); + checkIdentifierStart = scanner.getTokenPos(); + checkIdentifierEnd = scanner.getTextPos(); + return parseIdentifierName(); + } } function parseNamespaceExport(pos: number): NamespaceExport { @@ -7388,6 +7493,7 @@ namespace ts { setAwaitContext(/*value*/ true); let exportClause: NamedExportBindings | undefined; let moduleSpecifier: Expression | undefined; + let assertClause: AssertClause | undefined; const isTypeOnly = parseOptional(SyntaxKind.TypeKeyword); const namespaceExportPos = getNodePos(); if (parseOptional(SyntaxKind.AsteriskToken)) { @@ -7407,9 +7513,12 @@ namespace ts { moduleSpecifier = parseModuleSpecifier(); } } + if (moduleSpecifier && token() === SyntaxKind.AssertKeyword && !scanner.hasPrecedingLineBreak()) { + assertClause = parseAssertClause(); + } parseSemicolon(); setAwaitContext(savedAwaitContext); - const node = factory.createExportDeclaration(decorators, modifiers, isTypeOnly, exportClause, moduleSpecifier); + const node = factory.createExportDeclaration(decorators, modifiers, isTypeOnly, exportClause, moduleSpecifier, assertClause); return withJSDoc(finishNode(node, pos), hasJSDoc); } @@ -7489,7 +7598,8 @@ namespace ts { TypeArguments, // Type arguments in type argument list TupleElementTypes, // Element types in tuple element type list HeritageClauses, // Heritage clauses for a class or interface declaration. - ImportOrExportSpecifiers, // Named import clause's import specifier list + ImportOrExportSpecifiers, // Named import clause's import specifier list, + AssertEntries, // Import entries list. Count // Number of parsing contexts } @@ -7535,7 +7645,7 @@ namespace ts { const pos = getNodePos(); const hasBrace = parseOptional(SyntaxKind.OpenBraceToken); const p2 = getNodePos(); - let entityName: EntityName | JSDocMemberName = parseEntityName(/* allowReservedWords*/ false); + let entityName: EntityName | JSDocMemberName = parseEntityName(/* allowReservedWords*/ false, /*allowPrivateIdentifiers*/ false); while (token() === SyntaxKind.PrivateIdentifier) { reScanHashToken(); // rescan #id as # id nextTokenJSDoc(); // then skip the # @@ -7998,7 +8108,7 @@ namespace ts { // parseEntityName logs an error for non-identifier, so create a MissingNode ourselves to avoid the error const p2 = getNodePos(); let name: EntityName | JSDocMemberName | undefined = tokenIsIdentifierOrKeyword(token()) - ? parseEntityName(/*allowReservedWords*/ true) + ? parseEntityName(/*allowReservedWords*/ true, /*allowPrivateIdentifiers*/ false) : undefined; if (name) { while (token() === SyntaxKind.PrivateIdentifier) { @@ -8024,12 +8134,14 @@ namespace ts { && nextTokenJSDoc() === SyntaxKind.AtToken && tokenIsIdentifierOrKeyword(nextTokenJSDoc())) { const kind = scanner.getTokenValue(); - if(kind === "link" || kind === "linkcode" || kind === "linkplain") { - return kind; - } + if (isJSDocLinkTag(kind)) return kind; } } + function isJSDocLinkTag(kind: string) { + return kind === "link" || kind === "linkcode" || kind === "linkplain"; + } + function parseUnknownTag(start: number, tagName: Identifier, indent: number, indentText: string) { return finishNode(factory.createJSDocUnknownTag(tagName, parseTrailingTagComments(start, getNodePos(), indent, indentText)), start); } @@ -8151,8 +8263,9 @@ namespace ts { } function parseSeeTag(start: number, tagName: Identifier, indent?: number, indentText?: string): JSDocSeeTag { - const isLink = lookAhead(() => nextTokenJSDoc() === SyntaxKind.AtToken && tokenIsIdentifierOrKeyword(nextTokenJSDoc()) && scanner.getTokenValue() === "link"); - const nameExpression = isLink ? undefined : parseJSDocNameReference(); + const isMarkdownOrJSDocLink = token() === SyntaxKind.OpenBracketToken + || lookAhead(() => nextTokenJSDoc() === SyntaxKind.AtToken && tokenIsIdentifierOrKeyword(nextTokenJSDoc()) && isJSDocLinkTag(scanner.getTokenValue())); + const nameExpression = isMarkdownOrJSDocLink ? undefined : parseJSDocNameReference(); const comments = indent !== undefined && indentText !== undefined ? parseTrailingTagComments(start, getNodePos(), indent, indentText) : undefined; return finishNode(factory.createJSDocSeeTag(tagName, nameExpression, comments), start); } @@ -8624,6 +8737,7 @@ namespace ts { newText, aggressiveChecks ); + result.impliedNodeFormat = sourceFile.impliedNodeFormat; return result; } @@ -9164,7 +9278,7 @@ namespace ts { /** @internal */ export function isDeclarationFileName(fileName: string): boolean { - return fileExtensionIs(fileName, Extension.Dts); + return fileExtensionIsOneOf(fileName, [Extension.Dts, Extension.Dmts, Extension.Dcts]); } /*@internal*/ @@ -9180,6 +9294,20 @@ namespace ts { moduleName?: string; } + function parseResolutionMode(mode: string | undefined, pos: number, end: number, reportDiagnostic: PragmaDiagnosticReporter): ModuleKind.ESNext | ModuleKind.CommonJS | undefined { + if (!mode) { + return undefined; + } + if (mode === "import") { + return ModuleKind.ESNext; + } + if (mode === "require") { + return ModuleKind.CommonJS; + } + reportDiagnostic(pos, end - pos, Diagnostics.resolution_mode_should_be_either_require_or_import); + return undefined; + } + /*@internal*/ export function processCommentPragmas(context: PragmaContext, sourceText: string): void { const pragmas: PragmaPseudoMapEntry[] = []; @@ -9225,12 +9353,13 @@ namespace ts { const typeReferenceDirectives = context.typeReferenceDirectives; const libReferenceDirectives = context.libReferenceDirectives; forEach(toArray(entryOrList) as PragmaPseudoMap["reference"][], arg => { - const { types, lib, path } = arg.arguments; + const { types, lib, path, ["resolution-mode"]: res } = arg.arguments; if (arg.arguments["no-default-lib"]) { context.hasNoDefaultLib = true; } else if (types) { - typeReferenceDirectives.push({ pos: types.pos, end: types.end, fileName: types.value }); + const parsed = parseResolutionMode(res, types.pos, types.end, reportDiagnostic); + typeReferenceDirectives.push({ pos: types.pos, end: types.end, fileName: types.value, ...(parsed ? { resolutionMode: parsed } : {}) }); } else if (lib) { libReferenceDirectives.push({ pos: lib.pos, end: lib.end, fileName: lib.value }); diff --git a/src/compiler/path.ts b/src/compiler/path.ts index d09108d34c7..ebc837feb33 100644 --- a/src/compiler/path.ts +++ b/src/compiler/path.ts @@ -585,18 +585,6 @@ namespace ts { return getCanonicalFileName(nonCanonicalizedPath) as Path; } - export function normalizePathAndParts(path: string): { path: string, parts: string[] } { - path = normalizeSlashes(path); - const [root, ...parts] = reducePathComponents(getPathComponents(path)); - if (parts.length) { - const joinedParts = root + parts.join(directorySeparator); - return { path: hasTrailingDirectorySeparator(path) ? ensureTrailingDirectorySeparator(joinedParts) : joinedParts, parts }; - } - else { - return { path: root, parts }; - } - } - //// Path Mutation /** diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 4ee8195cbd5..004986b55db 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -510,7 +510,7 @@ namespace ts { } /* @internal */ - export function loadWithLocalCache(names: string[], containingFile: string, redirectedReference: ResolvedProjectReference | undefined, loader: (name: string, containingFile: string, redirectedReference: ResolvedProjectReference | undefined) => T): T[] { + export function loadWithTypeDirectiveCache(names: string[] | readonly FileReference[], containingFile: string, redirectedReference: ResolvedProjectReference | undefined, containingFileMode: SourceFile["impliedNodeFormat"], loader: (name: string, containingFile: string, redirectedReference: ResolvedProjectReference | undefined, resolutionMode: SourceFile["impliedNodeFormat"]) => T): T[] { if (names.length === 0) { return []; } @@ -518,11 +518,72 @@ namespace ts { const cache = new Map(); for (const name of names) { let result: T; - if (cache.has(name)) { - result = cache.get(name)!; + const mode = getModeForFileReference(name, containingFileMode); + // We lower-case all type references because npm automatically lowercases all packages. See GH#9824. + const strName = isString(name) ? name : name.fileName.toLowerCase(); + const cacheKey = mode !== undefined ? `${mode}|${strName}` : strName; + if (cache.has(cacheKey)) { + result = cache.get(cacheKey)!; } else { - cache.set(name, result = loader(name, containingFile, redirectedReference)); + cache.set(cacheKey, result = loader(strName, containingFile, redirectedReference, mode)); + } + resolutions.push(result); + } + return resolutions; + } + + /* @internal */ + interface SourceFileImportsList { + imports: SourceFile["imports"]; + moduleAugmentations: SourceFile["moduleAugmentations"]; + impliedNodeFormat?: SourceFile["impliedNodeFormat"]; + }; + + /* @internal */ + export function getModeForFileReference(ref: FileReference | string, containingFileMode: SourceFile["impliedNodeFormat"]) { + return (isString(ref) ? containingFileMode : ref.resolutionMode) || containingFileMode; + } + + /* @internal */ + export function getModeForResolutionAtIndex(file: SourceFileImportsList, index: number) { + if (file.impliedNodeFormat === undefined) return undefined; + // we ensure all elements of file.imports and file.moduleAugmentations have the relevant parent pointers set during program setup, + // so it's safe to use them even pre-bind + return getModeForUsageLocation(file, getModuleNameStringLiteralAt(file, index)); + } + + /* @internal */ + export function getModeForUsageLocation(file: {impliedNodeFormat?: SourceFile["impliedNodeFormat"]}, usage: StringLiteralLike) { + if (file.impliedNodeFormat === undefined) return undefined; + if (file.impliedNodeFormat !== ModuleKind.ESNext) { + // in cjs files, import call expressions are esm format, otherwise everything is cjs + return isImportCall(walkUpParenthesizedExpressions(usage.parent)) ? ModuleKind.ESNext : ModuleKind.CommonJS; + } + // in esm files, import=require statements are cjs format, otherwise everything is esm + // imports are only parent'd up to their containing declaration/expression, so access farther parents with care + const exprParentParent = walkUpParenthesizedExpressions(usage.parent)?.parent; + return exprParentParent && isImportEqualsDeclaration(exprParentParent) ? ModuleKind.CommonJS : ModuleKind.ESNext; + } + + /* @internal */ + export function loadWithModeAwareCache(names: string[], containingFile: SourceFile, containingFileName: string, redirectedReference: ResolvedProjectReference | undefined, loader: (name: string, resolverMode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined, containingFileName: string, redirectedReference: ResolvedProjectReference | undefined) => T): T[] { + if (names.length === 0) { + return []; + } + const resolutions: T[] = []; + const cache = new Map(); + let i = 0; + for (const name of names) { + let result: T; + const mode = getModeForResolutionAtIndex(containingFile, i); + i++; + const cacheKey = mode !== undefined ? `${mode}|${name}` : name; + if (cache.has(cacheKey)) { + result = cache.get(cacheKey)!; + } + else { + cache.set(cacheKey, result = loader(name, mode, containingFileName, redirectedReference)); } resolutions.push(result); } @@ -619,11 +680,11 @@ namespace ts { export function getReferencedFileLocation(getSourceFileByPath: (path: Path) => SourceFile | undefined, ref: ReferencedFile): ReferenceFileLocation | SyntheticReferenceFileLocation { const file = Debug.checkDefined(getSourceFileByPath(ref.file)); const { kind, index } = ref; - let pos: number | undefined, end: number | undefined, packageId: PackageId | undefined; + let pos: number | undefined, end: number | undefined, packageId: PackageId | undefined, resolutionMode: FileReference["resolutionMode"] | undefined; switch (kind) { case FileIncludeKind.Import: const importLiteral = getModuleNameStringLiteralAt(file, index); - packageId = file.resolvedModules?.get(importLiteral.text)?.packageId; + packageId = file.resolvedModules?.get(importLiteral.text, getModeForResolutionAtIndex(file, index))?.packageId; if (importLiteral.pos === -1) return { file, packageId, text: importLiteral.text }; pos = skipTrivia(file.text, importLiteral.pos); end = importLiteral.end; @@ -632,8 +693,8 @@ namespace ts { ({ pos, end } = file.referencedFiles[index]); break; case FileIncludeKind.TypeReferenceDirective: - ({ pos, end } = file.typeReferenceDirectives[index]); - packageId = file.resolvedTypeReferenceDirectiveNames?.get(toFileNameLowerCase(file.typeReferenceDirectives[index].fileName))?.packageId; + ({ pos, end, resolutionMode } = file.typeReferenceDirectives[index]); + packageId = file.resolvedTypeReferenceDirectiveNames?.get(toFileNameLowerCase(file.typeReferenceDirectives[index].fileName), resolutionMode || file.impliedNodeFormat)?.packageId; break; case FileIncludeKind.LibReferenceDirective: ({ pos, end } = file.libReferenceDirectives[index]); @@ -738,6 +799,129 @@ namespace ts { configFileParseResult.errors; } + /** + * A function for determining if a given file is esm or cjs format, assuming modern node module resolution rules, as configured by the + * `options` parameter. + * + * @param fileName The normalized absolute path to check the format of (it need not exist on disk) + * @param [packageJsonInfoCache] A cache for package file lookups - it's best to have a cache when this function is called often + * @param host The ModuleResolutionHost which can perform the filesystem lookups for package json data + * @param options The compiler options to perform the analysis under - relevant options are `moduleResolution` and `traceResolution` + * @returns `undefined` if the path has no relevant implied format, `ModuleKind.ESNext` for esm format, and `ModuleKind.CommonJS` for cjs format + */ + export function getImpliedNodeFormatForFile(fileName: Path, packageJsonInfoCache: PackageJsonInfoCache | undefined, host: ModuleResolutionHost, options: CompilerOptions): ModuleKind.ESNext | ModuleKind.CommonJS | undefined { + switch (getEmitModuleResolutionKind(options)) { + case ModuleResolutionKind.Node12: + case ModuleResolutionKind.NodeNext: + return fileExtensionIsOneOf(fileName, [Extension.Dmts, Extension.Mts, Extension.Mjs]) ? ModuleKind.ESNext : + fileExtensionIsOneOf(fileName, [Extension.Dcts, Extension.Cts, Extension.Cjs]) ? ModuleKind.CommonJS : + fileExtensionIsOneOf(fileName, [Extension.Dts, Extension.Ts, Extension.Tsx, Extension.Js, Extension.Jsx]) ? lookupFromPackageJson() : + undefined; // other extensions, like `json` or `tsbuildinfo`, are set as `undefined` here but they should never be fed through the transformer pipeline + default: + return undefined; + } + function lookupFromPackageJson(): ModuleKind.ESNext | ModuleKind.CommonJS { + const scope = getPackageScopeForPath(fileName, packageJsonInfoCache, host, options); + return scope?.packageJsonContent.type === "module" ? ModuleKind.ESNext : ModuleKind.CommonJS; + + } + } + + /** @internal */ + export const plainJSErrors: Set = new Set([ + // binder errors + Diagnostics.Cannot_redeclare_block_scoped_variable_0.code, + Diagnostics.A_module_cannot_have_multiple_default_exports.code, + Diagnostics.Another_export_default_is_here.code, + Diagnostics.The_first_export_default_is_here.code, + Diagnostics.Identifier_expected_0_is_a_reserved_word_at_the_top_level_of_a_module.code, + Diagnostics.Identifier_expected_0_is_a_reserved_word_in_strict_mode_Modules_are_automatically_in_strict_mode.code, + Diagnostics.Identifier_expected_0_is_a_reserved_word_that_cannot_be_used_here.code, + Diagnostics.constructor_is_a_reserved_word.code, + Diagnostics.delete_cannot_be_called_on_an_identifier_in_strict_mode.code, + Diagnostics.Code_contained_in_a_class_is_evaluated_in_JavaScript_s_strict_mode_which_does_not_allow_this_use_of_0_For_more_information_see_https_Colon_Slash_Slashdeveloper_mozilla_org_Slashen_US_Slashdocs_SlashWeb_SlashJavaScript_SlashReference_SlashStrict_mode.code, + Diagnostics.Invalid_use_of_0_Modules_are_automatically_in_strict_mode.code, + Diagnostics.Invalid_use_of_0_in_strict_mode.code, + Diagnostics.A_label_is_not_allowed_here.code, + Diagnostics.Octal_literals_are_not_allowed_in_strict_mode.code, + Diagnostics.with_statements_are_not_allowed_in_strict_mode.code, + // grammar errors + Diagnostics.A_break_statement_can_only_be_used_within_an_enclosing_iteration_or_switch_statement.code, + Diagnostics.A_break_statement_can_only_jump_to_a_label_of_an_enclosing_statement.code, + Diagnostics.A_class_declaration_without_the_default_modifier_must_have_a_name.code, + Diagnostics.A_class_member_cannot_have_the_0_keyword.code, + Diagnostics.A_comma_expression_is_not_allowed_in_a_computed_property_name.code, + Diagnostics.A_continue_statement_can_only_be_used_within_an_enclosing_iteration_statement.code, + Diagnostics.A_continue_statement_can_only_jump_to_a_label_of_an_enclosing_iteration_statement.code, + Diagnostics.A_continue_statement_can_only_jump_to_a_label_of_an_enclosing_iteration_statement.code, + Diagnostics.A_default_clause_cannot_appear_more_than_once_in_a_switch_statement.code, + Diagnostics.A_default_export_must_be_at_the_top_level_of_a_file_or_module_declaration.code, + Diagnostics.A_definite_assignment_assertion_is_not_permitted_in_this_context.code, + Diagnostics.A_destructuring_declaration_must_have_an_initializer.code, + Diagnostics.A_get_accessor_cannot_have_parameters.code, + Diagnostics.A_rest_element_cannot_contain_a_binding_pattern.code, + Diagnostics.A_rest_element_cannot_have_a_property_name.code, + Diagnostics.A_rest_element_cannot_have_an_initializer.code, + Diagnostics.A_rest_element_must_be_last_in_a_destructuring_pattern.code, + Diagnostics.A_rest_parameter_cannot_have_an_initializer.code, + Diagnostics.A_rest_parameter_must_be_last_in_a_parameter_list.code, + Diagnostics.A_rest_parameter_or_binding_pattern_may_not_have_a_trailing_comma.code, + Diagnostics.A_return_statement_can_only_be_used_within_a_function_body.code, + Diagnostics.A_return_statement_cannot_be_used_inside_a_class_static_block.code, + Diagnostics.A_set_accessor_cannot_have_rest_parameter.code, + Diagnostics.A_set_accessor_must_have_exactly_one_parameter.code, + Diagnostics.An_export_declaration_can_only_be_used_at_the_top_level_of_a_module.code, + Diagnostics.An_export_declaration_cannot_have_modifiers.code, + Diagnostics.An_import_declaration_can_only_be_used_at_the_top_level_of_a_module.code, + Diagnostics.An_import_declaration_cannot_have_modifiers.code, + Diagnostics.An_object_member_cannot_be_declared_optional.code, + Diagnostics.Argument_of_dynamic_import_cannot_be_spread_element.code, + Diagnostics.Cannot_assign_to_private_method_0_Private_methods_are_not_writable.code, + Diagnostics.Cannot_redeclare_identifier_0_in_catch_clause.code, + Diagnostics.Catch_clause_variable_cannot_have_an_initializer.code, + Diagnostics.Class_decorators_can_t_be_used_with_static_private_identifier_Consider_removing_the_experimental_decorator.code, + Diagnostics.Classes_can_only_extend_a_single_class.code, + Diagnostics.Classes_may_not_have_a_field_named_constructor.code, + Diagnostics.Did_you_mean_to_use_a_Colon_An_can_only_follow_a_property_name_when_the_containing_object_literal_is_part_of_a_destructuring_pattern.code, + Diagnostics.Duplicate_label_0.code, + Diagnostics.Dynamic_imports_can_only_accept_a_module_specifier_and_an_optional_assertion_as_arguments.code, + Diagnostics.For_await_loops_cannot_be_used_inside_a_class_static_block.code, + Diagnostics.JSX_attributes_must_only_be_assigned_a_non_empty_expression.code, + Diagnostics.JSX_elements_cannot_have_multiple_attributes_with_the_same_name.code, + Diagnostics.JSX_expressions_may_not_use_the_comma_operator_Did_you_mean_to_write_an_array.code, + Diagnostics.JSX_property_access_expressions_cannot_include_JSX_namespace_names.code, + Diagnostics.Jump_target_cannot_cross_function_boundary.code, + Diagnostics.Line_terminator_not_permitted_before_arrow.code, + Diagnostics.Modifiers_cannot_appear_here.code, + Diagnostics.Only_a_single_variable_declaration_is_allowed_in_a_for_in_statement.code, + Diagnostics.Only_a_single_variable_declaration_is_allowed_in_a_for_of_statement.code, + Diagnostics.Private_identifiers_are_not_allowed_outside_class_bodies.code, + Diagnostics.Private_identifiers_are_only_allowed_in_class_bodies_and_may_only_be_used_as_part_of_a_class_member_declaration_property_access_or_on_the_left_hand_side_of_an_in_expression.code, + Diagnostics.Property_0_is_not_accessible_outside_class_1_because_it_has_a_private_identifier.code, + Diagnostics.Tagged_template_expressions_are_not_permitted_in_an_optional_chain.code, + Diagnostics.The_left_hand_side_of_a_for_of_statement_may_not_be_async.code, + Diagnostics.The_variable_declaration_of_a_for_in_statement_cannot_have_an_initializer.code, + Diagnostics.The_variable_declaration_of_a_for_of_statement_cannot_have_an_initializer.code, + Diagnostics.Trailing_comma_not_allowed.code, + Diagnostics.Variable_declaration_list_cannot_be_empty.code, + Diagnostics._0_and_1_operations_cannot_be_mixed_without_parentheses.code, + Diagnostics._0_expected.code, + Diagnostics._0_is_not_a_valid_meta_property_for_keyword_1_Did_you_mean_2.code, + Diagnostics._0_list_cannot_be_empty.code, + Diagnostics._0_modifier_already_seen.code, + Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration.code, + Diagnostics._0_modifier_cannot_appear_on_a_module_or_namespace_element.code, + Diagnostics._0_modifier_cannot_appear_on_a_parameter.code, + Diagnostics._0_modifier_cannot_appear_on_class_elements_of_this_kind.code, + Diagnostics._0_modifier_cannot_be_used_here.code, + Diagnostics._0_modifier_must_precede_1_modifier.code, + Diagnostics.const_declarations_can_only_be_declared_inside_a_block.code, + Diagnostics.const_declarations_must_be_initialized.code, + Diagnostics.extends_clause_already_seen.code, + Diagnostics.let_declarations_can_only_be_declared_inside_a_block.code, + Diagnostics.let_is_not_allowed_to_be_used_as_a_name_in_let_or_const_declarations.code, + ]); + /** * Determine if source file needs to be re-created even if its text hasn't changed */ @@ -802,7 +986,7 @@ namespace ts { const cachedBindAndCheckDiagnosticsForFile: DiagnosticCache = {}; const cachedDeclarationDiagnosticsForFile: DiagnosticCache = {}; - let resolvedTypeReferenceDirectives = new Map(); + let resolvedTypeReferenceDirectives = createModeAwareCache(); let fileProcessingDiagnostics: FilePreprocessingDiagnostics[] | undefined; // The below settings are to track if a .js file should be add to the program if loaded via searching under node_modules. @@ -834,7 +1018,7 @@ namespace ts { const programDiagnostics = createDiagnosticCollection(); const currentDirectory = host.getCurrentDirectory(); const supportedExtensions = getSupportedExtensions(options); - const supportedExtensionsWithJsonIfResolveJsonModule = getSuppoertedExtensionsWithJsonIfResolveJsonModule(options, supportedExtensions); + const supportedExtensionsWithJsonIfResolveJsonModule = getSupportedExtensionsWithJsonIfResolveJsonModule(options, supportedExtensions); // Map storing if there is emit blocking diagnostics for given input const hasEmitBlockingDiagnostics = new Map(); @@ -842,10 +1026,10 @@ namespace ts { let moduleResolutionCache: ModuleResolutionCache | undefined; let typeReferenceDirectiveResolutionCache: TypeReferenceDirectiveResolutionCache | undefined; - let actualResolveModuleNamesWorker: (moduleNames: string[], containingFile: string, reusedNames?: string[], redirectedReference?: ResolvedProjectReference) => ResolvedModuleFull[]; + let actualResolveModuleNamesWorker: (moduleNames: string[], containingFile: SourceFile, containingFileName: string, reusedNames?: string[], redirectedReference?: ResolvedProjectReference) => ResolvedModuleFull[]; const hasInvalidatedResolution = host.hasInvalidatedResolution || returnFalse; if (host.resolveModuleNames) { - actualResolveModuleNamesWorker = (moduleNames, containingFile, reusedNames, redirectedReference) => host.resolveModuleNames!(Debug.checkEachDefined(moduleNames), containingFile, reusedNames, redirectedReference, options).map(resolved => { + actualResolveModuleNamesWorker = (moduleNames, containingFile, containingFileName, reusedNames, redirectedReference) => host.resolveModuleNames!(Debug.checkEachDefined(moduleNames), containingFileName, reusedNames, redirectedReference, options, containingFile).map(resolved => { // An older host may have omitted extension, in which case we should infer it from the file extension of resolvedFileName. if (!resolved || (resolved as ResolvedModuleFull).extension !== undefined) { return resolved as ResolvedModuleFull; @@ -854,28 +1038,30 @@ namespace ts { withExtension.extension = extensionFromPath(resolved.resolvedFileName); return withExtension; }); + moduleResolutionCache = host.getModuleResolutionCache?.(); } else { moduleResolutionCache = createModuleResolutionCache(currentDirectory, getCanonicalFileName, options); - const loader = (moduleName: string, containingFile: string, redirectedReference: ResolvedProjectReference | undefined) => resolveModuleName(moduleName, containingFile, options, host, moduleResolutionCache, redirectedReference).resolvedModule!; // TODO: GH#18217 - actualResolveModuleNamesWorker = (moduleNames, containingFile, _reusedNames, redirectedReference) => loadWithLocalCache(Debug.checkEachDefined(moduleNames), containingFile, redirectedReference, loader); + const loader = (moduleName: string, resolverMode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined, containingFileName: string, redirectedReference: ResolvedProjectReference | undefined) => resolveModuleName(moduleName, containingFileName, options, host, moduleResolutionCache, redirectedReference, resolverMode).resolvedModule!; // TODO: GH#18217 + actualResolveModuleNamesWorker = (moduleNames, containingFile, containingFileName, _reusedNames, redirectedReference) => loadWithModeAwareCache(Debug.checkEachDefined(moduleNames), containingFile, containingFileName, redirectedReference, loader); } - let actualResolveTypeReferenceDirectiveNamesWorker: (typeDirectiveNames: string[], containingFile: string, redirectedReference?: ResolvedProjectReference) => (ResolvedTypeReferenceDirective | undefined)[]; + let actualResolveTypeReferenceDirectiveNamesWorker: (typeDirectiveNames: string[] | readonly FileReference[], containingFile: string, redirectedReference?: ResolvedProjectReference, containingFileMode?: SourceFile["impliedNodeFormat"] | undefined) => (ResolvedTypeReferenceDirective | undefined)[]; if (host.resolveTypeReferenceDirectives) { - actualResolveTypeReferenceDirectiveNamesWorker = (typeDirectiveNames, containingFile, redirectedReference) => host.resolveTypeReferenceDirectives!(Debug.checkEachDefined(typeDirectiveNames), containingFile, redirectedReference, options); + actualResolveTypeReferenceDirectiveNamesWorker = (typeDirectiveNames, containingFile, redirectedReference, containingFileMode) => host.resolveTypeReferenceDirectives!(Debug.checkEachDefined(typeDirectiveNames), containingFile, redirectedReference, options, containingFileMode); } else { typeReferenceDirectiveResolutionCache = createTypeReferenceDirectiveResolutionCache(currentDirectory, getCanonicalFileName, /*options*/ undefined, moduleResolutionCache?.getPackageJsonInfoCache()); - const loader = (typesRef: string, containingFile: string, redirectedReference: ResolvedProjectReference | undefined) => resolveTypeReferenceDirective( + const loader = (typesRef: string, containingFile: string, redirectedReference: ResolvedProjectReference | undefined, resolutionMode: SourceFile["impliedNodeFormat"] | undefined) => resolveTypeReferenceDirective( typesRef, containingFile, options, host, redirectedReference, typeReferenceDirectiveResolutionCache, + resolutionMode, ).resolvedTypeReferenceDirective!; // TODO: GH#18217 - actualResolveTypeReferenceDirectiveNamesWorker = (typeReferenceDirectiveNames, containingFile, redirectedReference) => loadWithLocalCache(Debug.checkEachDefined(typeReferenceDirectiveNames), containingFile, redirectedReference, loader); + actualResolveTypeReferenceDirectiveNamesWorker = (typeReferenceDirectiveNames, containingFile, redirectedReference, containingFileMode) => loadWithTypeDirectiveCache(Debug.checkEachDefined(typeReferenceDirectiveNames), containingFile, redirectedReference, containingFileMode, loader); } // Map from a stringified PackageId to the source file with that id. @@ -917,6 +1103,7 @@ namespace ts { getSourceOfProjectReferenceRedirect, forEachResolvedProjectReference }); + const readFile = host.readFile.bind(host) as typeof host.readFile; tracing?.push(tracing.Phase.Program, "shouldProgramCreateNewSourceFiles", { hasOldProgram: !!oldProgram }); const shouldCreateNewSourceFile = shouldProgramCreateNewSourceFiles(oldProgram, options); @@ -977,7 +1164,8 @@ namespace ts { const containingFilename = combinePaths(containingDirectory, inferredTypesContainingFile); const resolutions = resolveTypeReferenceDirectiveNamesWorker(typeReferences, containingFilename); for (let i = 0; i < typeReferences.length; i++) { - processTypeReferenceDirective(typeReferences[i], resolutions[i], { kind: FileIncludeKind.AutomaticTypeDirectiveFile, typeReference: typeReferences[i], packageId: resolutions[i]?.packageId }); + // under node12/nodenext module resolution, load `types`/ata include names as cjs resolution results by passing an `undefined` mode + processTypeReferenceDirective(typeReferences[i], /*mode*/ undefined, resolutions[i], { kind: FileIncludeKind.AutomaticTypeDirectiveFile, typeReference: typeReferences[i], packageId: resolutions[i]?.packageId }); } tracing?.pop(); } @@ -995,7 +1183,7 @@ namespace ts { } else { forEach(options.lib, (libFileName, index) => { - processRootFile(combinePaths(defaultLibraryPath, libFileName), /*isDefaultLib*/ true, /*ignoreNoDefaultLib*/ false, { kind: FileIncludeKind.LibFile, index }); + processRootFile(pathForLibFile(libFileName), /*isDefaultLib*/ true, /*ignoreNoDefaultLib*/ false, { kind: FileIncludeKind.LibFile, index }); }); } } @@ -1101,6 +1289,7 @@ namespace ts { isSourceOfProjectReferenceRedirect, emitBuildInfo, fileExists, + readFile, directoryExists, getSymlinkCache, realpath: host.realpath?.bind(host), @@ -1137,20 +1326,21 @@ namespace ts { const redirectedReference = getRedirectReferenceForResolution(containingFile); tracing?.push(tracing.Phase.Program, "resolveModuleNamesWorker", { containingFileName }); performance.mark("beforeResolveModule"); - const result = actualResolveModuleNamesWorker(moduleNames, containingFileName, reusedNames, redirectedReference); + const result = actualResolveModuleNamesWorker(moduleNames, containingFile, containingFileName, reusedNames, redirectedReference); performance.mark("afterResolveModule"); performance.measure("ResolveModule", "beforeResolveModule", "afterResolveModule"); tracing?.pop(); return result; } - function resolveTypeReferenceDirectiveNamesWorker(typeDirectiveNames: string[], containingFile: string | SourceFile): readonly (ResolvedTypeReferenceDirective | undefined)[] { + function resolveTypeReferenceDirectiveNamesWorker(typeDirectiveNames: string[] | readonly FileReference[], containingFile: string | SourceFile): readonly (ResolvedTypeReferenceDirective | undefined)[] { if (!typeDirectiveNames.length) return []; const containingFileName = !isString(containingFile) ? getNormalizedAbsolutePath(containingFile.originalFileName, currentDirectory) : containingFile; const redirectedReference = !isString(containingFile) ? getRedirectReferenceForResolution(containingFile) : undefined; + const containingFileMode = !isString(containingFile) ? containingFile.impliedNodeFormat : undefined; tracing?.push(tracing.Phase.Program, "resolveTypeReferenceDirectiveNamesWorker", { containingFileName }); performance.mark("beforeResolveTypeReference"); - const result = actualResolveTypeReferenceDirectiveNamesWorker(typeDirectiveNames, containingFileName, redirectedReference); + const result = actualResolveTypeReferenceDirectiveNamesWorker(typeDirectiveNames, containingFileName, redirectedReference, containingFileMode); performance.mark("afterResolveTypeReference"); performance.measure("ResolveTypeReference", "beforeResolveTypeReference", "afterResolveTypeReference"); tracing?.pop(); @@ -1159,7 +1349,7 @@ namespace ts { function getRedirectReferenceForResolution(file: SourceFile) { const redirect = getResolvedProjectReferenceToRedirect(file.originalFileName); - if (redirect || !fileExtensionIs(file.originalFileName, Extension.Dts)) return redirect; + if (redirect || !fileExtensionIsOneOf(file.originalFileName, [Extension.Dts, Extension.Dcts, Extension.Dmts])) return redirect; // The originalFileName could not be actual source file name if file found was d.ts from referecned project // So in this case try to look up if this is output from referenced project, if it is use the redirected project in that case @@ -1202,8 +1392,8 @@ namespace ts { return libs.length + 2; } - function getResolvedModuleWithFailedLookupLocationsFromCache(moduleName: string, containingFile: string): ResolvedModuleWithFailedLookupLocations | undefined { - return moduleResolutionCache && resolveModuleNameFromCache(moduleName, containingFile, moduleResolutionCache); + function getResolvedModuleWithFailedLookupLocationsFromCache(moduleName: string, containingFile: string, mode?: ModuleKind.CommonJS | ModuleKind.ESNext): ResolvedModuleWithFailedLookupLocations | undefined { + return moduleResolutionCache && resolveModuleNameFromCache(moduleName, containingFile, moduleResolutionCache, mode); } function toPath(fileName: string): Path { @@ -1256,8 +1446,10 @@ namespace ts { // Since we assume the filesystem does not change during program creation, // it is safe to reuse resolutions from the earlier call. const result: ResolvedModuleFull[] = []; + let i = 0; for (const moduleName of moduleNames) { - const resolvedModule = file.resolvedModules.get(moduleName)!; + const resolvedModule = file.resolvedModules.get(moduleName, getModeForResolutionAtIndex(file, i))!; + i++; result.push(resolvedModule); } return result; @@ -1287,7 +1479,7 @@ namespace ts { const moduleName = moduleNames[i]; // If the source file is unchanged and doesnt have invalidated resolution, reuse the module resolutions if (file === oldSourceFile && !hasInvalidatedResolution(oldSourceFile.path)) { - const oldResolvedModule = getResolvedModule(oldSourceFile, moduleName); + const oldResolvedModule = getResolvedModule(oldSourceFile, moduleName, getModeForResolutionAtIndex(oldSourceFile, i)); if (oldResolvedModule) { if (isTraceEnabled(options, host)) { trace(host, @@ -1317,7 +1509,7 @@ namespace ts { } } else { - resolvesToAmbientModuleInNonModifiedFile = moduleNameResolvesToAmbientModuleInNonModifiedFile(moduleName); + resolvesToAmbientModuleInNonModifiedFile = moduleNameResolvesToAmbientModuleInNonModifiedFile(moduleName, i); } if (resolvesToAmbientModuleInNonModifiedFile) { @@ -1360,8 +1552,9 @@ namespace ts { // If we change our policy of rechecking failed lookups on each program create, // we should adjust the value returned here. - function moduleNameResolvesToAmbientModuleInNonModifiedFile(moduleName: string): boolean { - const resolutionToFile = getResolvedModule(oldSourceFile, moduleName); + function moduleNameResolvesToAmbientModuleInNonModifiedFile(moduleName: string, index: number): boolean { + if (index >= length(oldSourceFile?.imports) + length(oldSourceFile?.moduleAugmentations)) return false; // mode index out of bounds, don't reuse resolution + const resolutionToFile = getResolvedModule(oldSourceFile, moduleName, oldSourceFile && getModeForResolutionAtIndex(oldSourceFile, index)); const resolvedFile = resolutionToFile && oldProgram!.getSourceFile(resolutionToFile.resolvedFileName); if (resolutionToFile && resolvedFile) { // In the old program, we resolved to an ambient module that was in the same @@ -1455,8 +1648,8 @@ namespace ts { for (const oldSourceFile of oldSourceFiles) { let newSourceFile = host.getSourceFileByPath - ? host.getSourceFileByPath(oldSourceFile.fileName, oldSourceFile.resolvedPath, options.target!, /*onError*/ undefined, shouldCreateNewSourceFile) - : host.getSourceFile(oldSourceFile.fileName, options.target!, /*onError*/ undefined, shouldCreateNewSourceFile); // TODO: GH#18217 + ? host.getSourceFileByPath(oldSourceFile.fileName, oldSourceFile.resolvedPath, getEmitScriptTarget(options), /*onError*/ undefined, shouldCreateNewSourceFile) + : host.getSourceFile(oldSourceFile.fileName, getEmitScriptTarget(options), /*onError*/ undefined, shouldCreateNewSourceFile); // TODO: GH#18217 if (!newSourceFile) { return StructureIsReused.Not; @@ -1491,6 +1684,7 @@ namespace ts { newSourceFile.originalFileName = oldSourceFile.originalFileName; newSourceFile.resolvedPath = oldSourceFile.resolvedPath; newSourceFile.fileName = oldSourceFile.fileName; + newSourceFile.impliedNodeFormat = oldSourceFile.impliedNodeFormat; const packageName = oldProgram.sourceFileToPackageName.get(oldSourceFile.path); if (packageName !== undefined) { @@ -1576,22 +1770,21 @@ namespace ts { const moduleNames = getModuleNames(newSourceFile); const resolutions = resolveModuleNamesReusingOldState(moduleNames, newSourceFile); // ensure that module resolution results are still correct - const resolutionsChanged = hasChangesInResolutions(moduleNames, resolutions, oldSourceFile.resolvedModules, moduleResolutionIsEqualTo); + const resolutionsChanged = hasChangesInResolutions(moduleNames, resolutions, oldSourceFile.resolvedModules, oldSourceFile, moduleResolutionIsEqualTo); if (resolutionsChanged) { structureIsReused = StructureIsReused.SafeModules; - newSourceFile.resolvedModules = zipToMap(moduleNames, resolutions); + newSourceFile.resolvedModules = zipToModeAwareCache(newSourceFile, moduleNames, resolutions); } else { newSourceFile.resolvedModules = oldSourceFile.resolvedModules; } - // We lower-case all type references because npm automatically lowercases all packages. See GH#9824. - const typesReferenceDirectives = map(newSourceFile.typeReferenceDirectives, ref => toFileNameLowerCase(ref.fileName)); + const typesReferenceDirectives = newSourceFile.typeReferenceDirectives; const typeReferenceResolutions = resolveTypeReferenceDirectiveNamesWorker(typesReferenceDirectives, newSourceFile); // ensure that types resolutions are still correct - const typeReferenceEesolutionsChanged = hasChangesInResolutions(typesReferenceDirectives, typeReferenceResolutions, oldSourceFile.resolvedTypeReferenceDirectiveNames, typeDirectiveIsEqualTo); - if (typeReferenceEesolutionsChanged) { + const typeReferenceResolutionsChanged = hasChangesInResolutions(typesReferenceDirectives, typeReferenceResolutions, oldSourceFile.resolvedTypeReferenceDirectiveNames, oldSourceFile, typeDirectiveIsEqualTo); + if (typeReferenceResolutionsChanged) { structureIsReused = StructureIsReused.SafeModules; - newSourceFile.resolvedTypeReferenceDirectiveNames = zipToMap(typesReferenceDirectives, typeReferenceResolutions); + newSourceFile.resolvedTypeReferenceDirectiveNames = zipToModeAwareCache(newSourceFile, typesReferenceDirectives, typeReferenceResolutions); } else { newSourceFile.resolvedTypeReferenceDirectiveNames = oldSourceFile.resolvedTypeReferenceDirectiveNames; @@ -1737,7 +1930,7 @@ namespace ts { return equalityComparer(file.fileName, getDefaultLibraryFileName()); } else { - return some(options.lib, libFileName => equalityComparer(file.fileName, combinePaths(defaultLibraryPath, libFileName))); + return some(options.lib, libFileName => equalityComparer(file.fileName, pathForLibFile(libFileName))); } } @@ -1918,15 +2111,25 @@ namespace ts { Debug.assert(!!sourceFile.bindDiagnostics); - const isCheckJs = isCheckJsEnabledForFile(sourceFile, options); + const isJs = sourceFile.scriptKind === ScriptKind.JS || sourceFile.scriptKind === ScriptKind.JSX; + const isCheckJs = isJs && isCheckJsEnabledForFile(sourceFile, options); + const isPlainJs = isPlainJsFile(sourceFile, options.checkJs); const isTsNoCheck = !!sourceFile.checkJsDirective && sourceFile.checkJsDirective.enabled === false; - // By default, only type-check .ts, .tsx, 'Deferred' and 'External' files (external files are added by plugins) - const includeBindAndCheckDiagnostics = !isTsNoCheck && (sourceFile.scriptKind === ScriptKind.TS || sourceFile.scriptKind === ScriptKind.TSX - || sourceFile.scriptKind === ScriptKind.External || isCheckJs || sourceFile.scriptKind === ScriptKind.Deferred); - const bindDiagnostics: readonly Diagnostic[] = includeBindAndCheckDiagnostics ? sourceFile.bindDiagnostics : emptyArray; - const checkDiagnostics = includeBindAndCheckDiagnostics ? typeChecker.getDiagnostics(sourceFile, cancellationToken) : emptyArray; - return getMergedBindAndCheckDiagnostics(sourceFile, includeBindAndCheckDiagnostics, bindDiagnostics, checkDiagnostics, isCheckJs ? sourceFile.jsDocDiagnostics : undefined); + // By default, only type-check .ts, .tsx, Deferred, plain JS, checked JS and External + // - plain JS: .js files with no // ts-check and checkJs: undefined + // - check JS: .js files with either // ts-check or checkJs: true + // - external: files that are added by plugins + const includeBindAndCheckDiagnostics = !isTsNoCheck && (sourceFile.scriptKind === ScriptKind.TS || sourceFile.scriptKind === ScriptKind.TSX + || sourceFile.scriptKind === ScriptKind.External || isPlainJs || isCheckJs || sourceFile.scriptKind === ScriptKind.Deferred); + let bindDiagnostics: readonly Diagnostic[] = includeBindAndCheckDiagnostics ? sourceFile.bindDiagnostics : emptyArray; + let checkDiagnostics = includeBindAndCheckDiagnostics ? typeChecker.getDiagnostics(sourceFile, cancellationToken) : emptyArray; + if (isPlainJs) { + bindDiagnostics = filter(bindDiagnostics, d => plainJSErrors.has(d.code)); + checkDiagnostics = filter(checkDiagnostics, d => plainJSErrors.has(d.code)); + } + // skip ts-expect-error errors in plain JS files, and skip JSDoc errors except in checked JS + return getMergedBindAndCheckDiagnostics(sourceFile, includeBindAndCheckDiagnostics && !isPlainJs, bindDiagnostics, checkDiagnostics, isCheckJs ? sourceFile.jsDocDiagnostics : undefined); }); } @@ -2269,7 +2472,7 @@ namespace ts { function createSyntheticImport(text: string, file: SourceFile) { const externalHelpersModuleReference = factory.createStringLiteral(text); - const importDecl = factory.createImportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, /*importClause*/ undefined, externalHelpersModuleReference); + const importDecl = factory.createImportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, /*importClause*/ undefined, externalHelpersModuleReference, /*assertClause*/ undefined); addEmitFlags(importDecl, EmitFlags.NeverApplyImportHelper); setParent(externalHelpersModuleReference, importDecl); setParent(importDecl, file); @@ -2328,6 +2531,7 @@ namespace ts { // An ExternalImportDeclaration in an AmbientExternalModuleDeclaration may reference other external modules // only through top - level external module names. Relative external module names are not permitted. if (moduleNameExpr && isStringLiteral(moduleNameExpr) && moduleNameExpr.text && (!inAmbientModule || !isExternalModuleNameRelative(moduleNameExpr.text))) { + setParentRecursive(node, /*incremental*/ false); // we need parent data on imports before the program is fully bound, so we ensure it's set here imports = append(imports, moduleNameExpr); if (!usesUriStyleNodeCoreModules && currentNodeModulesDepth === 0 && !file.isDeclarationFile) { usesUriStyleNodeCoreModules = startsWith(moduleNameExpr.text, "node:"); @@ -2336,6 +2540,7 @@ namespace ts { } else if (isModuleDeclaration(node)) { if (isAmbientModule(node) && (inAmbientModule || hasSyntacticModifier(node, ModifierFlags.Ambient) || file.isDeclarationFile)) { + (node.name as Mutable).parent = node; const nameText = getTextOfIdentifierOrLiteral(node.name); // Ambient module declarations can be interpreted as augmentations for some existing external modules. // This will happen in two cases: @@ -2372,13 +2577,16 @@ namespace ts { while (r.exec(file.text) !== null) { // eslint-disable-line no-null/no-null const node = getNodeAtPosition(file, r.lastIndex); if (isJavaScriptFile && isRequireCall(node, /*checkArgumentIsStringLiteralLike*/ true)) { + setParentRecursive(node, /*incremental*/ false); // we need parent data on imports before the program is fully bound, so we ensure it's set here imports = append(imports, node.arguments[0]); } - // we have to check the argument list has length of 1. We will still have to process these even though we have parsing error. - else if (isImportCall(node) && node.arguments.length === 1 && isStringLiteralLike(node.arguments[0])) { + // we have to check the argument list has length of at least 1. We will still have to process these even though we have parsing error. + else if (isImportCall(node) && node.arguments.length >= 1 && isStringLiteralLike(node.arguments[0])) { + setParentRecursive(node, /*incremental*/ false); // we need parent data on imports before the program is fully bound, so we ensure it's set here imports = append(imports, node.arguments[0]); } else if (isLiteralImportTypeNode(node)) { + setParentRecursive(node, /*incremental*/ false); // we need parent data on imports before the program is fully bound, so we ensure it's set here imports = append(imports, node.argument.literal); } } @@ -2406,7 +2614,7 @@ namespace ts { const libName = toFileNameLowerCase(ref.fileName); const libFileName = libMap.get(libName); if (libFileName) { - return getSourceFile(combinePaths(defaultLibraryPath, libFileName)); + return getSourceFile(pathForLibFile(libFileName)); } } @@ -2423,13 +2631,13 @@ namespace ts { if (hasExtension(fileName)) { const canonicalFileName = host.getCanonicalFileName(fileName); - if (!options.allowNonTsExtensions && !forEach(supportedExtensionsWithJsonIfResolveJsonModule, extension => fileExtensionIs(canonicalFileName, extension))) { + if (!options.allowNonTsExtensions && !forEach(flatten(supportedExtensionsWithJsonIfResolveJsonModule), extension => fileExtensionIs(canonicalFileName, extension))) { if (fail) { if (hasJSFileExtension(canonicalFileName)) { fail(Diagnostics.File_0_is_a_JavaScript_file_Did_you_mean_to_enable_the_allowJs_option, fileName); } else { - fail(Diagnostics.File_0_has_an_unsupported_extension_The_only_supported_extensions_are_1, fileName, "'" + supportedExtensions.join("', '") + "'"); + fail(Diagnostics.File_0_has_an_unsupported_extension_The_only_supported_extensions_are_1, fileName, "'" + flatten(supportedExtensions).join("', '") + "'"); } } return undefined; @@ -2461,8 +2669,9 @@ namespace ts { return undefined; } - const sourceFileWithAddedExtension = forEach(supportedExtensions, extension => getSourceFile(fileName + extension)); - if (fail && !sourceFileWithAddedExtension) fail(Diagnostics.Could_not_resolve_the_path_0_with_the_extensions_Colon_1, fileName, "'" + supportedExtensions.join("', '") + "'"); + // Only try adding extensions from the first supported group (which should be .ts/.tsx/.d.ts) + const sourceFileWithAddedExtension = forEach(supportedExtensions[0], extension => getSourceFile(fileName + extension)); + if (fail && !sourceFileWithAddedExtension) fail(Diagnostics.Could_not_resolve_the_path_0_with_the_extensions_Colon_1, fileName, "'" + flatten(supportedExtensions).join("', '") + "'"); return sourceFileWithAddedExtension; } } @@ -2616,7 +2825,7 @@ namespace ts { // We haven't looked for this file, do so now and cache result const file = host.getSourceFile( fileName, - options.target!, + getEmitScriptTarget(options), hostErrorMessage => addFilePreprocessingFileExplainingDiagnostic(/*file*/ undefined, reason, Diagnostics.Cannot_read_file_0_Colon_1, [fileName, hostErrorMessage]), shouldCreateNewSourceFile ); @@ -2631,14 +2840,14 @@ namespace ts { redirectTargetsMap.add(fileFromPackageId.path, fileName); addFileToFilesByName(dupFile, path, redirectedPath); addFileIncludeReason(dupFile, reason); - sourceFileToPackageName.set(path, packageId.name); + sourceFileToPackageName.set(path, packageIdToPackageName(packageId)); processingOtherFiles!.push(dupFile); return dupFile; } else if (file) { // This is the first source file to have this packageId. packageIdToSourceFile.set(packageIdKey, file); - sourceFileToPackageName.set(path, packageId.name); + sourceFileToPackageName.set(path, packageIdToPackageName(packageId)); } } addFileToFilesByName(file, path, redirectedPath); @@ -2649,6 +2858,10 @@ namespace ts { file.path = path; file.resolvedPath = toPath(fileName); file.originalFileName = originalFileName; + // It's a _little odd_ that we can't set `impliedNodeFormat` until the program step - but it's the first and only time we have a resolution cache + // and a freshly made source file node on hand at the same time, and we need both to set the field. Persisting the resolution cache all the way + // to the check and emit steps would be bad - so we much prefer detecting and storing the format information on the source file node upfront. + file.impliedNodeFormat = getImpliedNodeFormatForFile(file.resolvedPath, moduleResolutionCache?.getPackageJsonInfoCache(), host, options); addFileIncludeReason(file, reason); if (host.useCaseSensitiveFileNames()) { @@ -2800,8 +3013,7 @@ namespace ts { } function processTypeReferenceDirectives(file: SourceFile) { - // We lower-case all type references because npm automatically lowercases all packages. See GH#9824. - const typeDirectives = map(file.typeReferenceDirectives, ref => toFileNameLowerCase(ref.fileName)); + const typeDirectives = file.typeReferenceDirectives; if (!typeDirectives) { return; } @@ -2813,28 +3025,34 @@ namespace ts { // store resolved type directive on the file const fileName = toFileNameLowerCase(ref.fileName); setResolvedTypeReferenceDirective(file, fileName, resolvedTypeReferenceDirective); - processTypeReferenceDirective(fileName, resolvedTypeReferenceDirective, { kind: FileIncludeKind.TypeReferenceDirective, file: file.path, index, }); + const mode = ref.resolutionMode || file.impliedNodeFormat; + if (mode && getEmitModuleResolutionKind(options) !== ModuleResolutionKind.Node12 && getEmitModuleResolutionKind(options) !== ModuleResolutionKind.NodeNext) { + programDiagnostics.add(createDiagnosticForRange(file, ref, Diagnostics.Resolution_modes_are_only_supported_when_moduleResolution_is_node12_or_nodenext)); + } + processTypeReferenceDirective(fileName, mode, resolvedTypeReferenceDirective, { kind: FileIncludeKind.TypeReferenceDirective, file: file.path, index, }); } } function processTypeReferenceDirective( typeReferenceDirective: string, + mode: SourceFile["impliedNodeFormat"] | undefined, resolvedTypeReferenceDirective: ResolvedTypeReferenceDirective | undefined, reason: FileIncludeReason ): void { tracing?.push(tracing.Phase.Program, "processTypeReferenceDirective", { directive: typeReferenceDirective, hasResolved: !!resolveModuleNamesReusingOldState, refKind: reason.kind, refPath: isReferencedFile(reason) ? reason.file : undefined }); - processTypeReferenceDirectiveWorker(typeReferenceDirective, resolvedTypeReferenceDirective, reason); + processTypeReferenceDirectiveWorker(typeReferenceDirective, mode, resolvedTypeReferenceDirective, reason); tracing?.pop(); } function processTypeReferenceDirectiveWorker( typeReferenceDirective: string, + mode: SourceFile["impliedNodeFormat"] | undefined, resolvedTypeReferenceDirective: ResolvedTypeReferenceDirective | undefined, reason: FileIncludeReason ): void { // If we already found this library as a primary reference - nothing to do - const previousResolution = resolvedTypeReferenceDirectives.get(typeReferenceDirective); + const previousResolution = resolvedTypeReferenceDirectives.get(typeReferenceDirective, mode); if (previousResolution && previousResolution.primary) { return; } @@ -2879,17 +3097,36 @@ namespace ts { } if (saveResolution) { - resolvedTypeReferenceDirectives.set(typeReferenceDirective, resolvedTypeReferenceDirective); + resolvedTypeReferenceDirectives.set(typeReferenceDirective, mode, resolvedTypeReferenceDirective); } } + function pathForLibFile(libFileName: string): string { + // Support resolving to lib.dom.d.ts -> @typescript/lib-dom, and + // lib.dom.iterable.d.ts -> @typescript/lib-dom/iterable + // lib.es2015.symbol.wellknown.d.ts -> @typescript/lib-es2015/symbol-wellknown + const components = libFileName.split("."); + let path = components[1]; + let i = 2; + while (components[i] && components[i] !== "d") { + path += (i === 2 ? "/" : "-") + components[i]; + i++; + } + const resolveFrom = combinePaths(currentDirectory, `__lib_node_modules_lookup_${libFileName}__.ts`); + const localOverrideModuleResult = resolveModuleName("@typescript/lib-" + path, resolveFrom, { moduleResolution: ModuleResolutionKind.NodeJs }, host, moduleResolutionCache); + if (localOverrideModuleResult?.resolvedModule) { + return localOverrideModuleResult.resolvedModule.resolvedFileName; + } + return combinePaths(defaultLibraryPath, libFileName); + } + function processLibReferenceDirectives(file: SourceFile) { forEach(file.libReferenceDirectives, (libReference, index) => { const libName = toFileNameLowerCase(libReference.fileName); const libFileName = libMap.get(libName); if (libFileName) { // we ignore any 'no-default-lib' reference set on this file. - processRootFile(combinePaths(defaultLibraryPath, libFileName), /*isDefaultLib*/ true, /*ignoreNoDefaultLib*/ true, { kind: FileIncludeKind.LibReferenceDirective, file: file.path, index, }); + processRootFile(pathForLibFile(libFileName), /*isDefaultLib*/ true, /*ignoreNoDefaultLib*/ true, { kind: FileIncludeKind.LibReferenceDirective, file: file.path, index, }); } else { const unqualifiedLibName = removeSuffix(removePrefix(libName, "lib."), ".d.ts"); @@ -2919,7 +3156,7 @@ namespace ts { const optionsForFile = (useSourceOfProjectReferenceRedirect ? getRedirectReferenceForResolution(file)?.commandLine.options : undefined) || options; for (let index = 0; index < moduleNames.length; index++) { const resolution = resolutions[index]; - setResolvedModule(file, moduleNames[index], resolution); + setResolvedModule(file, moduleNames[index], resolution, getModeForResolutionAtIndex(file, index)); if (!resolution) { continue; @@ -3045,6 +3282,21 @@ namespace ts { } function verifyCompilerOptions() { + const isNightly = stringContains(version, "-dev") || stringContains(version, "-insiders"); + if (!isNightly) { + if (getEmitModuleKind(options) === ModuleKind.Node12) { + createOptionValueDiagnostic("module", Diagnostics.Compiler_option_0_of_value_1_is_unstable_Use_nightly_TypeScript_to_silence_this_error_Try_updating_with_npm_install_D_typescript_next, "module", "node12"); + } + else if (getEmitModuleKind(options) === ModuleKind.NodeNext) { + createOptionValueDiagnostic("module", Diagnostics.Compiler_option_0_of_value_1_is_unstable_Use_nightly_TypeScript_to_silence_this_error_Try_updating_with_npm_install_D_typescript_next, "module", "nodenext"); + } + else if (getEmitModuleResolutionKind(options) === ModuleResolutionKind.Node12) { + createOptionValueDiagnostic("moduleResolution", Diagnostics.Compiler_option_0_of_value_1_is_unstable_Use_nightly_TypeScript_to_silence_this_error_Try_updating_with_npm_install_D_typescript_next, "moduleResolution", "node12"); + } + else if (getEmitModuleResolutionKind(options) === ModuleResolutionKind.NodeNext) { + createOptionValueDiagnostic("moduleResolution", Diagnostics.Compiler_option_0_of_value_1_is_unstable_Use_nightly_TypeScript_to_silence_this_error_Try_updating_with_npm_install_D_typescript_next, "moduleResolution", "nodenext"); + } + } if (options.strictPropertyInitialization && !getStrictOptionValue(options, "strictNullChecks")) { createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "strictPropertyInitialization", "strictNullChecks"); } @@ -3181,7 +3433,7 @@ namespace ts { createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "noImplicitUseStrict", "alwaysStrict"); } - const languageVersion = options.target || ScriptTarget.ES3; + const languageVersion = getEmitScriptTarget(options); const firstNonAmbientExternalModuleSourceFile = find(files, f => isExternalModule(f) && !f.isDeclarationFile); if (options.isolatedModules) { @@ -3218,7 +3470,9 @@ namespace ts { } if (options.resolveJsonModule) { - if (getEmitModuleResolutionKind(options) !== ModuleResolutionKind.NodeJs) { + if (getEmitModuleResolutionKind(options) !== ModuleResolutionKind.NodeJs && + getEmitModuleResolutionKind(options) !== ModuleResolutionKind.Node12 && + getEmitModuleResolutionKind(options) !== ModuleResolutionKind.NodeNext) { createDiagnosticForOptionName(Diagnostics.Option_resolveJsonModule_cannot_be_specified_without_node_module_resolution_strategy, "resolveJsonModule"); } // Any emit other than common js, amd, es2015 or esnext is error @@ -3470,7 +3724,7 @@ namespace ts { message = Diagnostics.File_is_library_specified_here; break; } - const target = forEachEntry(targetOptionDeclaration.type, (value, key) => value === options.target ? key : undefined); + const target = forEachEntry(targetOptionDeclaration.type, (value, key) => value === getEmitScriptTarget(options) ? key : undefined); configFileNode = target ? getOptionsSyntaxByValue("target", target) : undefined; message = Diagnostics.File_is_default_library_for_target_specified_here; break; @@ -3579,8 +3833,8 @@ namespace ts { createDiagnosticForOption(/*onKey*/ true, option1, option2, message, option1, option2, option3); } - function createOptionValueDiagnostic(option1: string, message: DiagnosticMessage, arg0?: string) { - createDiagnosticForOption(/*onKey*/ false, option1, /*option2*/ undefined, message, arg0); + function createOptionValueDiagnostic(option1: string, message: DiagnosticMessage, arg0?: string, arg1?: string) { + createDiagnosticForOption(/*onKey*/ false, option1, /*option2*/ undefined, message, arg0, arg1); } function createDiagnosticForReference(sourceFile: JsonSourceFile | undefined, index: number, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number) { @@ -3660,7 +3914,7 @@ namespace ts { return containsPath(options.outDir, filePath, currentDirectory, !host.useCaseSensitiveFileNames()); } - if (fileExtensionIsOneOf(filePath, supportedJSExtensions) || fileExtensionIs(filePath, Extension.Dts)) { + if (fileExtensionIsOneOf(filePath, supportedJSExtensionsFlat) || fileExtensionIs(filePath, Extension.Dts)) { // Otherwise just check if sourceFile with the name exists const filePathWithoutExtension = removeFileExtension(filePath); return !!getSourceFileByPath((filePathWithoutExtension + Extension.Ts) as Path) || @@ -4014,7 +4268,7 @@ namespace ts { } /* @internal */ - export function getModuleNameStringLiteralAt({ imports, moduleAugmentations }: SourceFile, index: number): StringLiteralLike { + export function getModuleNameStringLiteralAt({ imports, moduleAugmentations }: SourceFileImportsList, index: number): StringLiteralLike { if (index < imports.length) return imports[index]; let augIndex = imports.length; for (const aug of moduleAugmentations) { diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index f15cc9766d5..c42d99c33c4 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -5,9 +5,9 @@ namespace ts { startRecordingFilesWithChangedResolutions(): void; finishRecordingFilesWithChangedResolutions(): Path[] | undefined; - resolveModuleNames(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference?: ResolvedProjectReference): (ResolvedModuleFull | undefined)[]; - getResolvedModuleWithFailedLookupLocationsFromCache(moduleName: string, containingFile: string): CachedResolvedModuleWithFailedLookupLocations | undefined; - resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string, redirectedReference?: ResolvedProjectReference): (ResolvedTypeReferenceDirective | undefined)[]; + resolveModuleNames(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference?: ResolvedProjectReference, containingSourceFile?: SourceFile): (ResolvedModuleFull | undefined)[]; + getResolvedModuleWithFailedLookupLocationsFromCache(moduleName: string, containingFile: string, resolutionMode?: ModuleKind.CommonJS | ModuleKind.ESNext): CachedResolvedModuleWithFailedLookupLocations | undefined; + resolveTypeReferenceDirectives(typeDirectiveNames: string[] | readonly FileReference[], containingFile: string, redirectedReference?: ResolvedProjectReference, containingFileMode?: SourceFile["impliedNodeFormat"]): (ResolvedTypeReferenceDirective | undefined)[]; invalidateResolutionsOfFailedLookupLocations(): boolean; invalidateResolutionOfFile(filePath: Path): void; @@ -66,6 +66,7 @@ namespace ts { getCurrentProgram(): Program | undefined; fileIsOpen(filePath: Path): boolean; getCompilerHost?(): CompilerHost | undefined; + onDiscoveredSymlink?(): void; } interface DirectoryWatchesOfFailedLookup { @@ -166,8 +167,8 @@ namespace ts { // The resolvedModuleNames and resolvedTypeReferenceDirectives are the cache of resolutions per file. // The key in the map is source file's path. // The values are Map of resolutions with key being name lookedup. - const resolvedModuleNames = new Map>(); - const perDirectoryResolvedModuleNames: CacheWithRedirects> = createCacheWithRedirects(); + const resolvedModuleNames = new Map>(); + const perDirectoryResolvedModuleNames: CacheWithRedirects> = createCacheWithRedirects(); const nonRelativeModuleNameCache: CacheWithRedirects = createCacheWithRedirects(); const moduleResolutionCache = createModuleResolutionCache( getCurrentDirectory(), @@ -177,8 +178,8 @@ namespace ts { nonRelativeModuleNameCache, ); - const resolvedTypeReferenceDirectives = new Map>(); - const perDirectoryResolvedTypeReferenceDirectives: CacheWithRedirects> = createCacheWithRedirects(); + const resolvedTypeReferenceDirectives = new Map>(); + const perDirectoryResolvedTypeReferenceDirectives: CacheWithRedirects> = createCacheWithRedirects(); const typeReferenceDirectiveResolutionCache = createTypeReferenceDirectiveResolutionCache( getCurrentDirectory(), resolutionHost.getCanonicalFileName, @@ -314,8 +315,8 @@ namespace ts { hasChangedAutomaticTypeDirectiveNames = false; } - function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, redirectedReference?: ResolvedProjectReference): CachedResolvedModuleWithFailedLookupLocations { - const primaryResult = ts.resolveModuleName(moduleName, containingFile, compilerOptions, host, moduleResolutionCache, redirectedReference); + function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, redirectedReference?: ResolvedProjectReference, _containingSourceFile?: never, mode?: ModuleKind.CommonJS | ModuleKind.ESNext | undefined): CachedResolvedModuleWithFailedLookupLocations { + const primaryResult = ts.resolveModuleName(moduleName, containingFile, compilerOptions, host, moduleResolutionCache, redirectedReference, mode); // return result immediately only if global cache support is not enabled or if it is .ts, .tsx or .d.ts if (!resolutionHost.getGlobalCache) { return primaryResult; @@ -346,35 +347,37 @@ namespace ts { return primaryResult; } - function resolveTypeReferenceDirective(typeReferenceDirectiveName: string, containingFile: string | undefined, options: CompilerOptions, host: ModuleResolutionHost, redirectedReference?: ResolvedProjectReference): CachedResolvedTypeReferenceDirectiveWithFailedLookupLocations { - return ts.resolveTypeReferenceDirective(typeReferenceDirectiveName, containingFile, options, host, redirectedReference, typeReferenceDirectiveResolutionCache); + function resolveTypeReferenceDirective(typeReferenceDirectiveName: string, containingFile: string | undefined, options: CompilerOptions, host: ModuleResolutionHost, redirectedReference?: ResolvedProjectReference, _containingSourceFile?: SourceFile, resolutionMode?: SourceFile["impliedNodeFormat"] | undefined): CachedResolvedTypeReferenceDirectiveWithFailedLookupLocations { + return ts.resolveTypeReferenceDirective(typeReferenceDirectiveName, containingFile, options, host, redirectedReference, typeReferenceDirectiveResolutionCache, resolutionMode); } interface ResolveNamesWithLocalCacheInput { - names: readonly string[]; + names: readonly string[] | readonly FileReference[]; containingFile: string; redirectedReference: ResolvedProjectReference | undefined; - cache: ESMap>; - perDirectoryCacheWithRedirects: CacheWithRedirects>; - loader: (name: string, containingFile: string, options: CompilerOptions, host: ModuleResolutionHost, redirectedReference?: ResolvedProjectReference) => T; + cache: ESMap>; + perDirectoryCacheWithRedirects: CacheWithRedirects>; + loader: (name: string, containingFile: string, options: CompilerOptions, host: ModuleResolutionHost, redirectedReference?: ResolvedProjectReference, containingSourceFile?: SourceFile, resolutionMode?: ModuleKind.CommonJS | ModuleKind.ESNext | undefined) => T; getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName; shouldRetryResolution: (t: T) => boolean; reusedNames?: readonly string[]; logChanges?: boolean; + containingSourceFile?: SourceFile; + containingSourceFileMode?: SourceFile["impliedNodeFormat"]; } function resolveNamesWithLocalCache({ names, containingFile, redirectedReference, cache, perDirectoryCacheWithRedirects, loader, getResolutionWithResolvedFileName, - shouldRetryResolution, reusedNames, logChanges + shouldRetryResolution, reusedNames, logChanges, containingSourceFile, containingSourceFileMode }: ResolveNamesWithLocalCacheInput): (R | undefined)[] { const path = resolutionHost.toPath(containingFile); - const resolutionsInFile = cache.get(path) || cache.set(path, new Map()).get(path)!; + const resolutionsInFile = cache.get(path) || cache.set(path, createModeAwareCache()).get(path)!; const dirPath = getDirectoryPath(path); const perDirectoryCache = perDirectoryCacheWithRedirects.getOrCreateMapOfCacheRedirects(redirectedReference); let perDirectoryResolution = perDirectoryCache.get(dirPath); if (!perDirectoryResolution) { - perDirectoryResolution = new Map(); + perDirectoryResolution = createModeAwareCache(); perDirectoryCache.set(dirPath, perDirectoryResolution); } const resolvedModules: (R | undefined)[] = []; @@ -388,16 +391,26 @@ namespace ts { !redirectedReference || redirectedReference.sourceFile.path !== oldRedirect.sourceFile.path : !!redirectedReference; - const seenNamesInFile = new Map(); - for (const name of names) { - let resolution = resolutionsInFile.get(name); + const seenNamesInFile = createModeAwareCache(); + let i = 0; + for (const entry of names) { + const name = isString(entry) ? entry : entry.fileName.toLowerCase(); + // Imports supply a `containingSourceFile` but no `containingSourceFileMode` - it would be redundant + // they require calculating the mode for a given import from it's position in the resolution table, since a given + // import's syntax may override the file's default mode. + // Type references instead supply a `containingSourceFileMode` and a non-string entry which contains + // a default file mode override if applicable. + const mode = !isString(entry) ? getModeForFileReference(entry, containingSourceFileMode) : + containingSourceFile ? getModeForResolutionAtIndex(containingSourceFile, i) : undefined; + i++; + let resolution = resolutionsInFile.get(name, mode); // Resolution is valid if it is present and not invalidated - if (!seenNamesInFile.has(name) && + if (!seenNamesInFile.has(name, mode) && unmatchedRedirects || !resolution || resolution.isInvalidated || // If the name is unresolved import that was invalidated, recalculate (hasInvalidatedNonRelativeUnresolvedImport && !isExternalModuleNameRelative(name) && shouldRetryResolution(resolution))) { const existingResolution = resolution; - const resolutionInDirectory = perDirectoryResolution.get(name); + const resolutionInDirectory = perDirectoryResolution.get(name, mode); if (resolutionInDirectory) { resolution = resolutionInDirectory; const host = resolutionHost.getCompilerHost?.() || resolutionHost; @@ -425,10 +438,13 @@ namespace ts { } } else { - resolution = loader(name, containingFile, compilerOptions, resolutionHost.getCompilerHost?.() || resolutionHost, redirectedReference); - perDirectoryResolution.set(name, resolution); + resolution = loader(name, containingFile, compilerOptions, resolutionHost.getCompilerHost?.() || resolutionHost, redirectedReference, containingSourceFile, mode); + perDirectoryResolution.set(name, mode, resolution); + if (resolutionHost.onDiscoveredSymlink && resolutionIsSymlink(resolution)) { + resolutionHost.onDiscoveredSymlink(); + } } - resolutionsInFile.set(name, resolution); + resolutionsInFile.set(name, mode, resolution); watchFailedLookupLocationsOfExternalModuleResolutions(name, resolution, path, getResolutionWithResolvedFileName); if (existingResolution) { stopWatchFailedLookupLocationOfResolution(existingResolution, path, getResolutionWithResolvedFileName); @@ -442,7 +458,7 @@ namespace ts { } else { const host = resolutionHost.getCompilerHost?.() || resolutionHost; - if (isTraceEnabled(compilerOptions, host) && !seenNamesInFile.has(name)) { + if (isTraceEnabled(compilerOptions, host) && !seenNamesInFile.has(name, mode)) { const resolved = getResolutionWithResolvedFileName(resolution); trace( host, @@ -465,15 +481,15 @@ namespace ts { } } Debug.assert(resolution !== undefined && !resolution.isInvalidated); - seenNamesInFile.set(name, true); + seenNamesInFile.set(name, mode, true); resolvedModules.push(getResolutionWithResolvedFileName(resolution)); } // Stop watching and remove the unused name - resolutionsInFile.forEach((resolution, name) => { - if (!seenNamesInFile.has(name) && !contains(reusedNames, name)) { + resolutionsInFile.forEach((resolution, name, mode) => { + if (!seenNamesInFile.has(name, mode) && !contains(reusedNames, name)) { stopWatchFailedLookupLocationOfResolution(resolution, path, getResolutionWithResolvedFileName); - resolutionsInFile.delete(name); + resolutionsInFile.delete(name, mode); } }); @@ -498,7 +514,7 @@ namespace ts { } } - function resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string, redirectedReference?: ResolvedProjectReference): (ResolvedTypeReferenceDirective | undefined)[] { + function resolveTypeReferenceDirectives(typeDirectiveNames: string[] | readonly FileReference[], containingFile: string, redirectedReference?: ResolvedProjectReference, containingFileMode?: SourceFile["impliedNodeFormat"]): (ResolvedTypeReferenceDirective | undefined)[] { return resolveNamesWithLocalCache({ names: typeDirectiveNames, containingFile, @@ -508,10 +524,11 @@ namespace ts { loader: resolveTypeReferenceDirective, getResolutionWithResolvedFileName: getResolvedTypeReferenceDirective, shouldRetryResolution: resolution => resolution.resolvedTypeReferenceDirective === undefined, + containingSourceFileMode: containingFileMode }); } - function resolveModuleNames(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference?: ResolvedProjectReference): (ResolvedModuleFull | undefined)[] { + function resolveModuleNames(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference?: ResolvedProjectReference, containingSourceFile?: SourceFile): (ResolvedModuleFull | undefined)[] { return resolveNamesWithLocalCache({ names: moduleNames, containingFile, @@ -523,12 +540,14 @@ namespace ts { shouldRetryResolution: resolution => !resolution.resolvedModule || !resolutionExtensionIsTSOrJson(resolution.resolvedModule.extension), reusedNames, logChanges: logChangesWhenResolvingModule, + containingSourceFile, }); } - function getResolvedModuleWithFailedLookupLocationsFromCache(moduleName: string, containingFile: string): CachedResolvedModuleWithFailedLookupLocations | undefined { + function getResolvedModuleWithFailedLookupLocationsFromCache(moduleName: string, containingFile: string, resolutionMode?: ModuleKind.CommonJS | ModuleKind.ESNext): CachedResolvedModuleWithFailedLookupLocations | undefined { const cache = resolvedModuleNames.get(resolutionHost.toPath(containingFile)); - return cache && cache.get(moduleName); + if (!cache) return undefined; + return cache.get(moduleName, resolutionMode); } function isNodeModulesAtTypesDirectory(dirPath: Path) { @@ -609,7 +628,7 @@ namespace ts { ) { if (resolution.refCount) { resolution.refCount++; - Debug.assertDefined(resolution.files); + Debug.assertIsDefined(resolution.files); } else { resolution.refCount = 1; @@ -686,7 +705,7 @@ namespace ts { filePath: Path, getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName, ) { - unorderedRemoveItem(Debug.assertDefined(resolution.files), filePath); + unorderedRemoveItem(Debug.checkDefined(resolution.files), filePath); resolution.refCount!--; if (resolution.refCount) { return; @@ -751,7 +770,7 @@ namespace ts { } function removeResolutionsOfFileFromCache( - cache: ESMap>, + cache: ESMap>, filePath: Path, getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName, ) { @@ -788,7 +807,7 @@ namespace ts { for (const resolution of resolutions) { if (resolution.isInvalidated || !canInvalidate(resolution)) continue; resolution.isInvalidated = invalidated = true; - for (const containingFilePath of Debug.assertDefined(resolution.files)) { + for (const containingFilePath of Debug.checkDefined(resolution.files)) { (filesWithInvalidatedResolutions || (filesWithInvalidatedResolutions = new Set())).add(containingFilePath); // When its a file with inferred types resolution, invalidate type reference directive resolution hasChangedAutomaticTypeDirectiveNames = hasChangedAutomaticTypeDirectiveNames || endsWith(containingFilePath, inferredTypesContainingFile); @@ -959,4 +978,11 @@ namespace ts { return dirPath === rootPath || canWatchDirectory(dirPath); } } + + function resolutionIsSymlink(resolution: ResolutionWithFailedLookupLocations) { + return !!( + (resolution as ResolvedModuleWithFailedLookupLocations).resolvedModule?.originalPath || + (resolution as ResolvedTypeReferenceDirectiveWithFailedLookupLocations).resolvedTypeReferenceDirective?.originalPath + ); + } } diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index 8e31a88589b..6cac0cb1936 100644 --- a/src/compiler/scanner.ts +++ b/src/compiler/scanner.ts @@ -83,6 +83,7 @@ namespace ts { any: SyntaxKind.AnyKeyword, as: SyntaxKind.AsKeyword, asserts: SyntaxKind.AssertsKeyword, + assert: SyntaxKind.AssertKeyword, bigint: SyntaxKind.BigIntKeyword, boolean: SyntaxKind.BooleanKeyword, break: SyntaxKind.BreakKeyword, @@ -2373,6 +2374,7 @@ namespace ts { tokenValue = tokenValue.slice(0, -1); pos--; } + return getIdentifierToken(); } return token; } diff --git a/src/compiler/sourcemap.ts b/src/compiler/sourcemap.ts index ed75cf081d9..e3aebc849b6 100644 --- a/src/compiler/sourcemap.ts +++ b/src/compiler/sourcemap.ts @@ -322,7 +322,8 @@ namespace ts { } // Sometimes tools can see the following line as a source mapping url comment, so we mangle it a bit (the [M]) - const sourceMapCommentRegExp = /^\/\/[@#] source[M]appingURL=(.+)$/; + const sourceMapCommentRegExp = /^\/\/[@#] source[M]appingURL=(.+)\r?\n?$/; + const whitespaceOrMapCommentRegExp = /^\s*(\/\/[@#] .*)?$/; export interface LineInfo { diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index 4f853cbb146..48ac670c8be 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -499,12 +499,16 @@ namespace ts { /*@internal*/ export const ignoredPaths = ["/node_modules/.", "/.git", "/.#"]; + let curSysLog: (s: string) => void = noop; // eslint-disable-line prefer-const + /*@internal*/ - export let sysLog: (s: string) => void = noop; // eslint-disable-line prefer-const + export function sysLog(s: string) { + return curSysLog(s); + } /*@internal*/ export function setSysLog(logger: typeof sysLog) { - sysLog = logger; + curSysLog = logger; } /*@internal*/ @@ -1625,7 +1629,6 @@ namespace ts { sysLog(`sysLog:: ${fileOrDirectory}:: Defaulting to fsWatchFile`); return watchPresentFileSystemEntryWithFsWatchFile(); } - try { const presentWatcher = _fs.watch( fileOrDirectory, @@ -1804,7 +1807,7 @@ namespace ts { } function readDirectory(path: string, extensions?: readonly string[], excludes?: readonly string[], includes?: readonly string[], depth?: number): string[] { - return matchFiles(path, extensions, excludes, includes, useCaseSensitiveFileNames, process.cwd(), depth, getAccessibleFileSystemEntries, realpath, directoryExists); + return matchFiles(path, extensions, excludes, includes, useCaseSensitiveFileNames, process.cwd(), depth, getAccessibleFileSystemEntries, realpath); } function fileSystemEntryExists(path: string, entryKind: FileSystemEntryKind): boolean { @@ -1900,6 +1903,11 @@ namespace ts { return sys!; })(); + /*@internal*/ + export function setSys(s: System) { + sys = s; + } + if (sys && sys.getEnvironmentVariable) { setCustomPollingValues(sys); Debug.setAssertionLevel(/^development$/i.test(sys.getEnvironmentVariable("NODE_ENV")) diff --git a/src/compiler/tracing.ts b/src/compiler/tracing.ts index 31c1c8910cf..4757ac72e1b 100644 --- a/src/compiler/tracing.ts +++ b/src/compiler/tracing.ts @@ -341,4 +341,8 @@ namespace ts { // eslint-disable-line one-namespace-per-file // define after tracingEnabled is initialized export const startTracing = tracingEnabled.startTracing; export const dumpTracingLegend = tracingEnabled.dumpLegend; + + export interface TracingNode { + tracingPath?: Path; + } } diff --git a/src/compiler/transformer.ts b/src/compiler/transformer.ts index 732205c00a3..7207d8a60d7 100644 --- a/src/compiler/transformer.ts +++ b/src/compiler/transformer.ts @@ -3,11 +3,15 @@ namespace ts { function getModuleTransformer(moduleKind: ModuleKind): TransformerFactory { switch (moduleKind) { case ModuleKind.ESNext: + case ModuleKind.ES2022: case ModuleKind.ES2020: case ModuleKind.ES2015: return transformECMAScriptModule; case ModuleKind.System: return transformSystemModule; + case ModuleKind.Node12: + case ModuleKind.NodeNext: + return transformNodeModule; default: return transformModule; } @@ -562,7 +566,7 @@ namespace ts { } export const nullTransformationContext: TransformationContext = { - factory, + factory: factory, // eslint-disable-line object-shorthand getCompilerOptions: () => ({}), getEmitResolver: notImplemented, getEmitHost: notImplemented, diff --git a/src/compiler/transformers/classFields.ts b/src/compiler/transformers/classFields.ts index 935c549dbce..3ac842e8aba 100644 --- a/src/compiler/transformers/classFields.ts +++ b/src/compiler/transformers/classFields.ts @@ -133,12 +133,15 @@ namespace ts { const languageVersion = getEmitScriptTarget(compilerOptions); const useDefineForClassFields = getUseDefineForClassFields(compilerOptions); - const shouldTransformPrivateElementsOrClassStaticBlocks = languageVersion < ScriptTarget.ESNext; + const shouldTransformPrivateElementsOrClassStaticBlocks = languageVersion < ScriptTarget.ES2022; + + // We need to transform `this` in a static initializer into a reference to the class + // when targeting < ES2022 since the assignment will be moved outside of the class body. + const shouldTransformThisInStaticInitializers = languageVersion < ScriptTarget.ES2022; // We don't need to transform `super` property access when targeting ES5, ES3 because // the es2015 transformation handles those. - const shouldTransformSuperInStaticInitializers = (languageVersion <= ScriptTarget.ES2021 || !useDefineForClassFields) && languageVersion >= ScriptTarget.ES2015; - const shouldTransformThisInStaticInitializers = languageVersion <= ScriptTarget.ES2021 || !useDefineForClassFields; + const shouldTransformSuperInStaticInitializers = shouldTransformThisInStaticInitializers && languageVersion >= ScriptTarget.ES2015; const previousOnSubstituteNode = context.onSubstituteNode; context.onSubstituteNode = onSubstituteNode; @@ -172,7 +175,7 @@ namespace ts { function transformSourceFile(node: SourceFile) { const options = context.getCompilerOptions(); if (node.isDeclarationFile - || useDefineForClassFields && options.target === ScriptTarget.ESNext) { + || useDefineForClassFields && getEmitScriptTarget(options) >= ScriptTarget.ES2022) { return node; } const visited = visitEachChild(node, visitor, context); @@ -266,15 +269,44 @@ namespace ts { /** * If we visit a private name, this means it is an undeclared private name. - * Replace it with an empty identifier to indicate a problem with the code. + * Replace it with an empty identifier to indicate a problem with the code, + * unless we are in a statement position - otherwise this will not trigger + * a SyntaxError. */ function visitPrivateIdentifier(node: PrivateIdentifier) { if (!shouldTransformPrivateElementsOrClassStaticBlocks) { return node; } + if (isStatement(node.parent)) { + return node; + } return setOriginalNode(factory.createIdentifier(""), node); } + /** + * Visits `#id in expr` + */ + function visitPrivateIdentifierInInExpression(node: BinaryExpression) { + if (!shouldTransformPrivateElementsOrClassStaticBlocks) { + return node; + } + const privId = node.left; + Debug.assertNode(privId, isPrivateIdentifier); + Debug.assert(node.operatorToken.kind === SyntaxKind.InKeyword); + const info = accessPrivateIdentifier(privId); + if (info) { + const receiver = visitNode(node.right, visitor, isExpression); + + return setOriginalNode( + context.getEmitHelperFactory().createClassPrivateFieldInHelper(info.brandCheckIdentifier, receiver), + node + ); + } + + // Private name has not been declared. Subsequent transformers will handle this error + return visitEachChild(node, visitor, context); + } + /** * Visits the members of a class that has fields. * @@ -393,6 +425,11 @@ namespace ts { if (isPrivateIdentifier(node.name)) { if (!shouldTransformPrivateElementsOrClassStaticBlocks) { + if (isStatic(node)) { + // static fields are left as is + return visitEachChild(node, visitor, context); + } + // Initializer is elided as the field is initialized in transformConstructor. return factory.updatePropertyDeclaration( node, @@ -419,6 +456,28 @@ namespace ts { if (expr && !isSimpleInlineableExpression(expr)) { getPendingExpressions().push(expr); } + + if (isStatic(node) && !shouldTransformPrivateElementsOrClassStaticBlocks && !useDefineForClassFields) { + const initializerStatement = transformPropertyOrClassStaticBlock(node, factory.createThis()); + if (initializerStatement) { + const staticBlock = factory.createClassStaticBlockDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + factory.createBlock([initializerStatement]) + ); + + setOriginalNode(staticBlock, node); + setCommentRange(staticBlock, node); + + // Set the comment range for the statement to an empty synthetic range + // and drop synthetic comments from the statement to avoid printing them twice. + setCommentRange(initializerStatement, { pos: -1, end: -1 }); + setSyntheticLeadingComments(initializerStatement, undefined); + setSyntheticTrailingComments(initializerStatement, undefined); + return staticBlock; + } + } + return undefined; } @@ -827,6 +886,9 @@ namespace ts { } } } + if (node.operatorToken.kind === SyntaxKind.InKeyword && isPrivateIdentifier(node.left)) { + return visitPrivateIdentifierInInExpression(node); + } return visitEachChild(node, visitor, context); } @@ -974,8 +1036,6 @@ namespace ts { enableSubstitutionForClassStaticThisOrSuperReference(); } - const staticProperties = getStaticPropertiesAndClassStaticBlock(node); - // If a class has private static fields, or a static field has a `this` or `super` reference, // then we need to allocate a temp variable to hold on to that reference. let pendingClassReferenceAssignment: BinaryExpression | undefined; @@ -1015,6 +1075,7 @@ namespace ts { // HasLexicalDeclaration (N) : Determines if the argument identifier has a binding in this environment record that was created using // a lexical declaration such as a LexicalDeclaration or a ClassDeclaration. + const staticProperties = getStaticPropertiesAndClassStaticBlock(node); if (some(staticProperties)) { addPropertyOrClassStaticBlockStatements(statements, staticProperties, factory.getInternalName(node)); } @@ -1070,7 +1131,7 @@ namespace ts { transformClassMembers(node, isDerivedClass) ); - const hasTransformableStatics = some(staticPropertiesOrClassStaticBlocks, p => isClassStaticBlockDeclaration(p) || !!p.initializer || (shouldTransformPrivateElementsOrClassStaticBlocks && isPrivateIdentifier(p.name))); + const hasTransformableStatics = shouldTransformPrivateElementsOrClassStaticBlocks && some(staticPropertiesOrClassStaticBlocks, p => isClassStaticBlockDeclaration(p) || !!p.initializer || isPrivateIdentifier(p.name)); if (hasTransformableStatics || some(pendingExpressions)) { if (isDecoratedClassDeclaration) { Debug.assertIsDefined(pendingStatements, "Decorated classes transformed by TypeScript are expected to be within a variable declaration."); @@ -1124,6 +1185,7 @@ namespace ts { } function transformClassMembers(node: ClassDeclaration | ClassExpression, isDerivedClass: boolean) { + const members: ClassElement[] = []; if (shouldTransformPrivateElementsOrClassStaticBlocks) { // Declare private names. for (const member of node.members) { @@ -1137,12 +1199,26 @@ namespace ts { } } - const members: ClassElement[] = []; const constructor = transformConstructor(node, isDerivedClass); + const visitedMembers = visitNodes(node.members, classElementVisitor, isClassElement); + if (constructor) { members.push(constructor); } - addRange(members, visitNodes(node.members, classElementVisitor, isClassElement)); + + if (!shouldTransformPrivateElementsOrClassStaticBlocks && some(pendingExpressions)) { + members.push(factory.createClassStaticBlockDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + factory.createBlock([ + factory.createExpressionStatement(factory.inlineExpressions(pendingExpressions)) + ]) + )); + pendingExpressions = undefined; + } + + addRange(members, visitedMembers); + return setTextRange(factory.createNodeArray(members), /*location*/ node.members); } @@ -1169,7 +1245,7 @@ namespace ts { if (useDefineForClassFields) { // If we are using define semantics and targeting ESNext or higher, // then we don't need to transform any class properties. - return languageVersion < ScriptTarget.ESNext; + return languageVersion < ScriptTarget.ES2022; } return isInitializedProperty(member) || shouldTransformPrivateElementsOrClassStaticBlocks && isPrivateIdentifierClassElementDeclaration(member); } @@ -1217,10 +1293,28 @@ namespace ts { resumeLexicalEnvironment(); - let indexOfFirstStatement = 0; + const needsSyntheticConstructor = !constructor && isDerivedClass; + let indexOfFirstStatementAfterSuper = 0; + let prologueStatementCount = 0; + let superStatementIndex = -1; let statements: Statement[] = []; - if (!constructor && isDerivedClass) { + if (constructor?.body?.statements) { + prologueStatementCount = factory.copyPrologue(constructor.body.statements, statements, /*ensureUseStrict*/ false, visitor); + superStatementIndex = findSuperStatementIndex(constructor.body.statements, prologueStatementCount); + + // If there was a super call, visit existing statements up to and including it + if (superStatementIndex >= 0) { + indexOfFirstStatementAfterSuper = superStatementIndex + 1; + statements = [ + ...statements.slice(0, prologueStatementCount), + ...visitNodes(constructor.body.statements, visitor, isStatement, prologueStatementCount, indexOfFirstStatementAfterSuper - prologueStatementCount), + ...statements.slice(prologueStatementCount), + ]; + } + } + + if (needsSyntheticConstructor) { // Add a synthetic `super` call: // // super(...arguments); @@ -1236,9 +1330,6 @@ namespace ts { ); } - if (constructor) { - indexOfFirstStatement = addPrologueDirectivesAndInitialSuperCall(factory, constructor, statements, visitor); - } // Add the property initializers. Transforms this: // // public x = 1; @@ -1249,26 +1340,52 @@ namespace ts { // this.x = 1; // } // + // If we do useDefineForClassFields, they'll be converted elsewhere. + // We instead *remove* them from the transformed output at this stage. + let parameterPropertyDeclarationCount = 0; if (constructor?.body) { - let afterParameterProperties = findIndex(constructor.body.statements, s => !isParameterPropertyDeclaration(getOriginalNode(s), constructor), indexOfFirstStatement); - if (afterParameterProperties === -1) { - afterParameterProperties = constructor.body.statements.length; + if (useDefineForClassFields) { + statements = statements.filter(statement => !isParameterPropertyDeclaration(getOriginalNode(statement), constructor)); } - if (afterParameterProperties > indexOfFirstStatement) { - if (!useDefineForClassFields) { - addRange(statements, visitNodes(constructor.body.statements, visitor, isStatement, indexOfFirstStatement, afterParameterProperties - indexOfFirstStatement)); + else { + for (const statement of constructor.body.statements) { + if (isParameterPropertyDeclaration(getOriginalNode(statement), constructor)) { + parameterPropertyDeclarationCount++; + } + } + if (parameterPropertyDeclarationCount > 0) { + const parameterProperties = visitNodes(constructor.body.statements, visitor, isStatement, indexOfFirstStatementAfterSuper, parameterPropertyDeclarationCount); + + // If there was a super() call found, add parameter properties immediately after it + if (superStatementIndex >= 0) { + addRange(statements, parameterProperties); + } + // If a synthetic super() call was added, add them just after it + else if (needsSyntheticConstructor) { + statements = [ + statements[0], + ...parameterProperties, + ...statements.slice(1), + ]; + } + // Since there wasn't a super() call, add them to the top of the constructor + else { + statements = [...parameterProperties, ...statements]; + } + + indexOfFirstStatementAfterSuper += parameterPropertyDeclarationCount; } - indexOfFirstStatement = afterParameterProperties; } } + const receiver = factory.createThis(); // private methods can be called in property initializers, they should execute first. addMethodStatements(statements, privateMethodsAndAccessors, receiver); addPropertyOrClassStaticBlockStatements(statements, properties, receiver); - // Add existing statements, skipping the initial super call. + // Add existing statements after the initial prologues and super call if (constructor) { - addRange(statements, visitNodes(constructor.body!.statements, visitor, isStatement, indexOfFirstStatement)); + addRange(statements, visitNodes(constructor.body!.statements, visitBodyStatement, isStatement, indexOfFirstStatementAfterSuper + prologueStatementCount)); } statements = factory.mergeLexicalEnvironment(statements, endLexicalEnvironment()); @@ -1283,6 +1400,14 @@ namespace ts { ), /*location*/ constructor ? constructor.body : undefined ); + + function visitBodyStatement(statement: Node) { + if (useDefineForClassFields && isParameterPropertyDeclaration(getOriginalNode(statement), constructor!)) { + return undefined; + } + + return visitor(statement); + } } /** @@ -1293,20 +1418,41 @@ namespace ts { */ function addPropertyOrClassStaticBlockStatements(statements: Statement[], properties: readonly (PropertyDeclaration | ClassStaticBlockDeclaration)[], receiver: LeftHandSideExpression) { for (const property of properties) { - const expression = isClassStaticBlockDeclaration(property) ? - transformClassStaticBlockDeclaration(property) : - transformProperty(property, receiver); - if (!expression) { + if (isStatic(property) && !shouldTransformPrivateElementsOrClassStaticBlocks && !useDefineForClassFields) { continue; } - const statement = factory.createExpressionStatement(expression); - setSourceMapRange(statement, moveRangePastModifiers(property)); - setCommentRange(statement, property); - setOriginalNode(statement, property); + + const statement = transformPropertyOrClassStaticBlock(property, receiver); + if (!statement) { + continue; + } + statements.push(statement); } } + function transformPropertyOrClassStaticBlock(property: PropertyDeclaration | ClassStaticBlockDeclaration, receiver: LeftHandSideExpression) { + const expression = isClassStaticBlockDeclaration(property) ? + transformClassStaticBlockDeclaration(property) : + transformProperty(property, receiver); + if (!expression) { + return undefined; + } + + const statement = factory.createExpressionStatement(expression); + setSourceMapRange(statement, moveRangePastModifiers(property)); + setCommentRange(statement, property); + setOriginalNode(statement, property); + + // `setOriginalNode` *copies* the `emitNode` from `property`, so now both + // `statement` and `expression` have a copy of the synthesized comments. + // Drop the comments from expression to avoid printing them twice. + setSyntheticLeadingComments(expression, undefined); + setSyntheticTrailingComments(expression, undefined); + + return statement; + } + /** * Generates assignment expressions for property initializers. * diff --git a/src/compiler/transformers/declarations.ts b/src/compiler/transformers/declarations.ts index e76debaa785..f91ad7641d3 100644 --- a/src/compiler/transformers/declarations.ts +++ b/src/compiler/transformers/declarations.ts @@ -58,7 +58,7 @@ namespace ts { let needsScopeFixMarker = false; let resultHasScopeMarker = false; let enclosingDeclaration: Node; - let necessaryTypeReferences: Set | undefined; + let necessaryTypeReferences: Set<[specifier: string, mode: SourceFile["impliedNodeFormat"] | undefined]> | undefined; let lateMarkedStatements: LateVisibilityPaintedStatement[] | undefined; let lateStatementReplacementMap: ESMap>; let suppressNewDiagnosticContexts: boolean; @@ -92,7 +92,7 @@ namespace ts { const { noResolve, stripInternal } = options; return transformRoot; - function recordTypeReferenceDirectivesIfNecessary(typeReferenceDirectives: readonly string[] | undefined): void { + function recordTypeReferenceDirectivesIfNecessary(typeReferenceDirectives: readonly [specifier: string, mode: SourceFile["impliedNodeFormat"] | undefined][] | undefined): void { if (!typeReferenceDirectives) { return; } @@ -296,7 +296,7 @@ namespace ts { const sourceFile = createUnparsedSourceFile(prepend, "dts", stripInternal); hasNoDefaultLib = hasNoDefaultLib || !!sourceFile.hasNoDefaultLib; collectReferences(sourceFile, refs); - recordTypeReferenceDirectivesIfNecessary(sourceFile.typeReferenceDirectives); + recordTypeReferenceDirectivesIfNecessary(map(sourceFile.typeReferenceDirectives, ref => [ref.fileName, ref.resolutionMode])); collectLibs(sourceFile, libs); return sourceFile; } @@ -354,10 +354,10 @@ namespace ts { } function getFileReferencesForUsedTypeReferences() { - return necessaryTypeReferences ? mapDefined(arrayFrom(necessaryTypeReferences.keys()), getFileReferenceForTypeName) : []; + return necessaryTypeReferences ? mapDefined(arrayFrom(necessaryTypeReferences.keys()), getFileReferenceForSpecifierModeTuple) : []; } - function getFileReferenceForTypeName(typeName: string): FileReference | undefined { + function getFileReferenceForSpecifierModeTuple([typeName, mode]: [specifier: string, mode: SourceFile["impliedNodeFormat"] | undefined]): FileReference | undefined { // Elide type references for which we have imports if (emittedImports) { for (const importStatement of emittedImports) { @@ -372,7 +372,7 @@ namespace ts { } } } - return { fileName: typeName, pos: -1, end: -1 }; + return { fileName: typeName, pos: -1, end: -1, ...(mode ? { resolutionMode: mode } : undefined) }; } function mapReferencesIntoArray(references: FileReference[], outputFilePath: string): (file: SourceFile) => void { @@ -399,7 +399,7 @@ namespace ts { // If some compiler option/symlink/whatever allows access to the file containing the ambient module declaration // via a non-relative name, emit a type reference directive to that non-relative name, rather than // a relative path to the declaration file - recordTypeReferenceDirectivesIfNecessary([specifier]); + recordTypeReferenceDirectivesIfNecessary([[specifier, /*mode*/ undefined]]); return; } @@ -732,7 +732,8 @@ namespace ts { /*decorators*/ undefined, decl.modifiers, decl.importClause, - rewriteModuleSpecifier(decl, decl.moduleSpecifier) + rewriteModuleSpecifier(decl, decl.moduleSpecifier), + /*assertClause*/ undefined ); } // The `importClause` visibility corresponds to the default's visibility. @@ -744,7 +745,7 @@ namespace ts { decl.importClause.isTypeOnly, visibleDefaultBinding, /*namedBindings*/ undefined, - ), rewriteModuleSpecifier(decl, decl.moduleSpecifier)); + ), rewriteModuleSpecifier(decl, decl.moduleSpecifier), /*assertClause*/ undefined); } if (decl.importClause.namedBindings.kind === SyntaxKind.NamespaceImport) { // Namespace import (optionally with visible default) @@ -754,7 +755,7 @@ namespace ts { decl.importClause.isTypeOnly, visibleDefaultBinding, namedBindings, - ), rewriteModuleSpecifier(decl, decl.moduleSpecifier)) : undefined; + ), rewriteModuleSpecifier(decl, decl.moduleSpecifier), /*assertClause*/ undefined) : undefined; } // Named imports (optionally with visible default) const bindingList = mapDefined(decl.importClause.namedBindings.elements, b => resolver.isDeclarationVisible(b) ? b : undefined); @@ -769,7 +770,8 @@ namespace ts { visibleDefaultBinding, bindingList && bindingList.length ? factory.updateNamedImports(decl.importClause.namedBindings, bindingList) : undefined, ), - rewriteModuleSpecifier(decl, decl.moduleSpecifier) + rewriteModuleSpecifier(decl, decl.moduleSpecifier), + /*assertClause*/ undefined ); } // Augmentation of export depends on import @@ -779,7 +781,8 @@ namespace ts { /*decorators*/ undefined, decl.modifiers, /*importClause*/ undefined, - rewriteModuleSpecifier(decl, decl.moduleSpecifier) + rewriteModuleSpecifier(decl, decl.moduleSpecifier), + /*assertClause*/ undefined ); } // Nothing visible @@ -875,7 +878,7 @@ namespace ts { } if (canProduceDiagnostic && !suppressNewDiagnosticContexts) { - getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(input as DeclarationDiagnosticProducing); + getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(input); } if (isTypeQueryNode(input)) { @@ -1070,7 +1073,7 @@ namespace ts { function cleanup(returnValue: T | undefined): T | undefined { if (returnValue && canProduceDiagnostic && hasDynamicName(input as Declaration)) { - checkName(input as DeclarationDiagnosticProducing); + checkName(input); } if (isEnclosingDeclaration(input)) { enclosingDeclaration = previousEnclosingDeclaration; @@ -1114,6 +1117,7 @@ namespace ts { input.isTypeOnly, input.exportClause, rewriteModuleSpecifier(input, input.moduleSpecifier), + /*assertClause*/ undefined ); } case SyntaxKind.ExportAssignment: { @@ -1252,7 +1256,7 @@ namespace ts { /*modifiers*/ undefined, /*isTypeOnly*/ false, factory.createNamedExports(map(exportMappings, ([gen, exp]) => { - return factory.createExportSpecifier(gen, exp); + return factory.createExportSpecifier(/*isTypeOnly*/ false, gen, exp); })) )); } @@ -1606,7 +1610,7 @@ namespace ts { } // Elide "public" modifier, as it is the default - function maskModifiers(node: Node, modifierMask?: ModifierFlags, modifierAdditions?: ModifierFlags): Modifier[] { + function maskModifiers(node: Node, modifierMask?: ModifierFlags, modifierAdditions?: ModifierFlags): Modifier[] | undefined { return factory.createModifiersFromModifierFlags(maskModifierFlags(node, modifierMask, modifierAdditions)); } diff --git a/src/compiler/transformers/es2015.ts b/src/compiler/transformers/es2015.ts index 52963c530ab..36a0f29d109 100644 --- a/src/compiler/transformers/es2015.ts +++ b/src/compiler/transformers/es2015.ts @@ -1017,40 +1017,44 @@ namespace ts { const statements: Statement[] = []; resumeLexicalEnvironment(); + // In derived classes, there may be code before the necessary super() call + // We'll remove pre-super statements to be tacked on after the rest of the body + const existingPrologue = takeWhile(constructor.body.statements, isPrologueDirective); + const { superCall, superStatementIndex } = findSuperCallAndStatementIndex(constructor.body.statements, existingPrologue); + const postSuperStatementsStart = superStatementIndex === -1 ? existingPrologue.length : superStatementIndex + 1; + // If a super call has already been synthesized, // we're going to assume that we should just transform everything after that. // The assumption is that no prior step in the pipeline has added any prologue directives. - let statementOffset = 0; - if (!hasSynthesizedSuper) statementOffset = factory.copyStandardPrologue(constructor.body.statements, prologue, /*ensureUseStrict*/ false); - addDefaultValueAssignmentsIfNeeded(statements, constructor); - addRestParameterIfNeeded(statements, constructor, hasSynthesizedSuper); - if (!hasSynthesizedSuper) statementOffset = factory.copyCustomPrologue(constructor.body.statements, statements, statementOffset, visitor); + let statementOffset = postSuperStatementsStart; + if (!hasSynthesizedSuper) statementOffset = factory.copyStandardPrologue(constructor.body.statements, prologue, statementOffset, /*ensureUseStrict*/ false); + if (!hasSynthesizedSuper) statementOffset = factory.copyCustomPrologue(constructor.body.statements, statements, statementOffset, visitor, /*filter*/ undefined); - // If the first statement is a call to `super()`, visit the statement directly + // If there already exists a call to `super()`, visit the statement directly let superCallExpression: Expression | undefined; if (hasSynthesizedSuper) { superCallExpression = createDefaultSuperCallOrThis(); } - else if (isDerivedClass && statementOffset < constructor.body.statements.length) { - const firstStatement = constructor.body.statements[statementOffset]; - if (isExpressionStatement(firstStatement) && isSuperCall(firstStatement.expression)) { - superCallExpression = visitImmediateSuperCallInBody(firstStatement.expression); - } + else if (superCall) { + superCallExpression = visitSuperCallInBody(superCall); } if (superCallExpression) { hierarchyFacts |= HierarchyFacts.ConstructorWithCapturedSuper; - statementOffset++; // skip this statement, we will add it after visiting the rest of the body. } + // Add parameter defaults at the beginning of the output, with prologue statements + addDefaultValueAssignmentsIfNeeded(prologue, constructor); + addRestParameterIfNeeded(prologue, constructor, hasSynthesizedSuper); + // visit the remaining statements addRange(statements, visitNodes(constructor.body.statements, visitor, isStatement, /*start*/ statementOffset)); factory.mergeLexicalEnvironment(prologue, endLexicalEnvironment()); insertCaptureNewTargetIfNeeded(prologue, constructor, /*copyOnWrite*/ false); - if (isDerivedClass) { - if (superCallExpression && statementOffset === constructor.body.statements.length && !(constructor.body.transformFlags & TransformFlags.ContainsLexicalThis)) { + if (isDerivedClass || superCallExpression) { + if (superCallExpression && postSuperStatementsStart === constructor.body.statements.length && !(constructor.body.transformFlags & TransformFlags.ContainsLexicalThis)) { // If the subclass constructor does *not* contain `this` and *ends* with a `super()` call, we will use the // following representation: // @@ -1099,9 +1103,19 @@ namespace ts { // })(Base); // ``` - // Since the `super()` call was the first statement, we insert the `this` capturing call to - // `super()` at the top of the list of `statements` (after any pre-existing custom prologues). - insertCaptureThisForNode(statements, constructor, superCallExpression || createActualThis()); + // If the super() call is the first statement, we can directly create and assign its result to `_this` + if (superStatementIndex <= existingPrologue.length) { + insertCaptureThisForNode(statements, constructor, superCallExpression || createActualThis()); + } + // Since the `super()` call isn't the first statement, it's split across 1-2 statements: + // * A prologue `var _this = this;`, in case the constructor accesses this before super() + // * If it exists, a reassignment to that `_this` of the super() call + else { + insertCaptureThisForNode(prologue, constructor, createActualThis()); + if (superCallExpression) { + insertSuperThisCaptureThisForNode(statements, superCallExpression); + } + } if (!isSufficientlyCoveredByReturnStatements(constructor.body)) { statements.push(factory.createReturnStatement(factory.createUniqueName("_this", GeneratedIdentifierFlags.Optimistic | GeneratedIdentifierFlags.FileLevel))); @@ -1126,19 +1140,41 @@ namespace ts { insertCaptureThisForNodeIfNeeded(prologue, constructor); } - const block = factory.createBlock( + const body = factory.createBlock( setTextRange( factory.createNodeArray( - concatenate(prologue, statements) + [ + ...existingPrologue, + ...prologue, + ...(superStatementIndex <= existingPrologue.length ? emptyArray : visitNodes(constructor.body.statements, visitor, isStatement, existingPrologue.length, superStatementIndex)), + ...statements + ] ), /*location*/ constructor.body.statements ), /*multiLine*/ true ); - setTextRange(block, constructor.body); + setTextRange(body, constructor.body); + return body; + } - return block; + function findSuperCallAndStatementIndex(originalBodyStatements: NodeArray, existingPrologue: Statement[]) { + for (let i = existingPrologue.length; i < originalBodyStatements.length; i += 1) { + const superCall = getSuperCallFromStatement(originalBodyStatements[i]); + if (superCall) { + // With a super() call, split the statements into pre-super() and 'body' (post-super()) + return { + superCall, + superStatementIndex: i, + }; + } + } + + // Since there was no super() call found, consider all statements to be in the main 'body' (post-super()) + return { + superStatementIndex: -1, + }; } /** @@ -1511,6 +1547,25 @@ namespace ts { return false; } + /** + * Assigns the `this` in a constructor to the result of its `super()` call. + * + * @param statements Statements in the constructor body. + * @param superExpression Existing `super()` call for the constructor. + */ + function insertSuperThisCaptureThisForNode(statements: Statement[], superExpression: Expression): void { + enableSubstitutionsForCapturedThis(); + const assignSuperExpression = factory.createExpressionStatement( + factory.createBinaryExpression( + factory.createThis(), + SyntaxKind.EqualsToken, + superExpression + ) + ); + insertStatementAfterCustomPrologue(statements, assignSuperExpression); + setCommentRange(assignSuperExpression, getOriginalNode(superExpression).parent); + } + function insertCaptureThisForNode(statements: Statement[], node: Node, initializer: Expression | undefined): void { enableSubstitutionsForCapturedThis(); const captureThisStatement = factory.createVariableStatement( @@ -1919,7 +1974,7 @@ namespace ts { if (isBlock(body)) { // ensureUseStrict is false because no new prologue-directive should be added. // addStandardPrologue will put already-existing directives at the beginning of the target statement-array - statementOffset = factory.copyStandardPrologue(body.statements, prologue, /*ensureUseStrict*/ false); + statementOffset = factory.copyStandardPrologue(body.statements, prologue, 0, /*ensureUseStrict*/ false); statementOffset = factory.copyCustomPrologue(body.statements, statements, statementOffset, visitor, isHoistedFunction); statementOffset = factory.copyCustomPrologue(body.statements, statements, statementOffset, visitor, isHoistedVariableStatement); } @@ -2899,9 +2954,11 @@ namespace ts { // variables declared in the loop initializer that will be changed inside the loop const loopOutParameters: LoopOutParameter[] = []; if (loopInitializer && (getCombinedNodeFlags(loopInitializer) & NodeFlags.BlockScoped)) { - const hasCapturedBindingsInForInitializer = shouldConvertInitializerOfForStatement(node); + const hasCapturedBindingsInForHead = shouldConvertInitializerOfForStatement(node) || + shouldConvertConditionOfForStatement(node) || + shouldConvertIncrementorOfForStatement(node); for (const decl of loopInitializer.declarations) { - processLoopVariableDeclaration(node, decl, loopParameters, loopOutParameters, hasCapturedBindingsInForInitializer); + processLoopVariableDeclaration(node, decl, loopParameters, loopOutParameters, hasCapturedBindingsInForHead); } } @@ -3379,26 +3436,32 @@ namespace ts { }); } - function processLoopVariableDeclaration(container: IterationStatement, decl: VariableDeclaration | BindingElement, loopParameters: ParameterDeclaration[], loopOutParameters: LoopOutParameter[], hasCapturedBindingsInForInitializer: boolean) { + function processLoopVariableDeclaration(container: IterationStatement, decl: VariableDeclaration | BindingElement, loopParameters: ParameterDeclaration[], loopOutParameters: LoopOutParameter[], hasCapturedBindingsInForHead: boolean) { const name = decl.name; if (isBindingPattern(name)) { for (const element of name.elements) { if (!isOmittedExpression(element)) { - processLoopVariableDeclaration(container, element, loopParameters, loopOutParameters, hasCapturedBindingsInForInitializer); + processLoopVariableDeclaration(container, element, loopParameters, loopOutParameters, hasCapturedBindingsInForHead); } } } else { loopParameters.push(factory.createParameterDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, /*dotDotDotToken*/ undefined, name)); const checkFlags = resolver.getNodeCheckFlags(decl); - if (checkFlags & NodeCheckFlags.NeedsLoopOutParameter || hasCapturedBindingsInForInitializer) { + if (checkFlags & NodeCheckFlags.NeedsLoopOutParameter || hasCapturedBindingsInForHead) { const outParamName = factory.createUniqueName("out_" + idText(name)); let flags: LoopOutParameterFlags = 0; if (checkFlags & NodeCheckFlags.NeedsLoopOutParameter) { flags |= LoopOutParameterFlags.Body; } - if (isForStatement(container) && container.initializer && resolver.isBindingCapturedByNode(container.initializer, decl)) { - flags |= LoopOutParameterFlags.Initializer; + if (isForStatement(container)) { + if (container.initializer && resolver.isBindingCapturedByNode(container.initializer, decl)) { + flags |= LoopOutParameterFlags.Initializer; + } + if (container.condition && resolver.isBindingCapturedByNode(container.condition, decl) || + container.incrementor && resolver.isBindingCapturedByNode(container.incrementor, decl)) { + flags |= LoopOutParameterFlags.Body; + } } loopOutParameters.push({ flags, originalName: name, outParamName }); } @@ -3822,7 +3885,7 @@ namespace ts { ); } - function visitImmediateSuperCallInBody(node: CallExpression) { + function visitSuperCallInBody(node: CallExpression) { return visitCallExpressionWithPotentialCapturedThisAssignment(node, /*assignToCapturedThis*/ false); } @@ -4102,86 +4165,22 @@ namespace ts { * @param node A TemplateExpression node. */ function visitTemplateExpression(node: TemplateExpression): Expression { - const expressions: Expression[] = []; - addTemplateHead(expressions, node); - addTemplateSpans(expressions, node); - - // createAdd will check if each expression binds less closely than binary '+'. - // If it does, it wraps the expression in parentheses. Otherwise, something like - // `abc${ 1 << 2 }` - // becomes - // "abc" + 1 << 2 + "" - // which is really - // ("abc" + 1) << (2 + "") - // rather than - // "abc" + (1 << 2) + "" - const expression = reduceLeft(expressions, factory.createAdd)!; - if (nodeIsSynthesized(expression)) { - setTextRange(expression, node); - } - - return expression; - } - - /** - * Gets a value indicating whether we need to include the head of a TemplateExpression. - * - * @param node A TemplateExpression node. - */ - function shouldAddTemplateHead(node: TemplateExpression) { - // If this expression has an empty head literal and the first template span has a non-empty - // literal, then emitting the empty head literal is not necessary. - // `${ foo } and ${ bar }` - // can be emitted as - // foo + " and " + bar - // This is because it is only required that one of the first two operands in the emit - // output must be a string literal, so that the other operand and all following operands - // are forced into strings. - // - // If the first template span has an empty literal, then the head must still be emitted. - // `${ foo }${ bar }` - // must still be emitted as - // "" + foo + bar - - // There is always atleast one templateSpan in this code path, since - // NoSubstitutionTemplateLiterals are directly emitted via emitLiteral() - Debug.assert(node.templateSpans.length !== 0); - - return node.head.text.length !== 0 || node.templateSpans[0].literal.text.length === 0; - } - - /** - * Adds the head of a TemplateExpression to an array of expressions. - * - * @param expressions An array of expressions. - * @param node A TemplateExpression node. - */ - function addTemplateHead(expressions: Expression[], node: TemplateExpression): void { - if (!shouldAddTemplateHead(node)) { - return; - } - - expressions.push(factory.createStringLiteral(node.head.text)); - } - - /** - * Visits and adds the template spans of a TemplateExpression to an array of expressions. - * - * @param expressions An array of expressions. - * @param node A TemplateExpression node. - */ - function addTemplateSpans(expressions: Expression[], node: TemplateExpression): void { + let expression: Expression = factory.createStringLiteral(node.head.text); for (const span of node.templateSpans) { - expressions.push(visitNode(span.expression, visitor, isExpression)); + const args = [visitNode(span.expression, visitor, isExpression)]; - // Only emit if the literal is non-empty. - // The binary '+' operator is left-associative, so the first string concatenation - // with the head will force the result up to this point to be a string. - // Emitting a '+ ""' has no semantic effect for middles and tails. - if (span.literal.text.length !== 0) { - expressions.push(factory.createStringLiteral(span.literal.text)); + if (span.literal.text.length > 0) { + args.push(factory.createStringLiteral(span.literal.text)); } + + expression = factory.createCallExpression( + factory.createPropertyAccessExpression(expression, "concat"), + /*typeArguments*/ undefined, + args, + ); } + + return setTextRange(expression, node); } /** diff --git a/src/compiler/transformers/es2017.ts b/src/compiler/transformers/es2017.ts index ade1d41b010..50c4fa62c0d 100644 --- a/src/compiler/transformers/es2017.ts +++ b/src/compiler/transformers/es2017.ts @@ -528,7 +528,7 @@ namespace ts { inHasLexicalThisContext(), hasLexicalArguments, promiseConstructor, - transformAsyncFunctionBodyWorker(node.body!) + transformAsyncFunctionBodyWorker(node.body) ); const declarations = endLexicalEnvironment(); diff --git a/src/compiler/transformers/es2018.ts b/src/compiler/transformers/es2018.ts index 846fd992ff2..e3923c79d9f 100644 --- a/src/compiler/transformers/es2018.ts +++ b/src/compiler/transformers/es2018.ts @@ -59,6 +59,7 @@ namespace ts { let exportedVariableStatement = false; let enabledSubstitutions: ESNextSubstitutionFlags; let enclosingFunctionFlags: FunctionFlags; + let parametersWithPrecedingObjectRestOrSpread: Set | undefined; let enclosingSuperContainerFlags: NodeCheckFlags = 0; let hierarchyFacts: HierarchyFacts = 0; @@ -282,7 +283,7 @@ namespace ts { function visitYieldExpression(node: YieldExpression) { if (enclosingFunctionFlags & FunctionFlags.Async && enclosingFunctionFlags & FunctionFlags.Generator) { if (node.asteriskToken) { - const expression = visitNode(Debug.assertDefined(node.expression), visitor, isExpression); + const expression = visitNode(Debug.checkDefined(node.expression), visitor, isExpression); return setOriginalNode( setTextRange( @@ -721,6 +722,7 @@ namespace ts { ), EmitFlags.NoTokenTrailingSourceMaps ); + setOriginalNode(forStatement, node); return factory.createTryStatement( factory.createBlock([ @@ -785,7 +787,24 @@ namespace ts { ); } + function parameterVisitor(node: Node) { + Debug.assertNode(node, isParameter); + return visitParameter(node); + } + function visitParameter(node: ParameterDeclaration): ParameterDeclaration { + if (parametersWithPrecedingObjectRestOrSpread?.has(node)) { + return factory.updateParameterDeclaration( + node, + /*decorators*/ undefined, + /*modifiers*/ undefined, + node.dotDotDotToken, + isBindingPattern(node.name) ? factory.getGeneratedNameForNode(node) : node.name, + /*questionToken*/ undefined, + /*type*/ undefined, + /*initializer*/ undefined + ); + } if (node.transformFlags & TransformFlags.ContainsObjectRestOrSpread) { // Binding patterns are converted into a generated name and are // evaluated inside the function body. @@ -803,54 +822,78 @@ namespace ts { return visitEachChild(node, visitor, context); } + function collectParametersWithPrecedingObjectRestOrSpread(node: SignatureDeclaration) { + let parameters: Set | undefined; + for (const parameter of node.parameters) { + if (parameters) { + parameters.add(parameter); + } + else if (parameter.transformFlags & TransformFlags.ContainsObjectRestOrSpread) { + parameters = new Set(); + } + } + return parameters; + } + function visitConstructorDeclaration(node: ConstructorDeclaration) { const savedEnclosingFunctionFlags = enclosingFunctionFlags; - enclosingFunctionFlags = FunctionFlags.Normal; + const savedParametersWithPrecedingObjectRestOrSpread = parametersWithPrecedingObjectRestOrSpread; + enclosingFunctionFlags = getFunctionFlags(node); + parametersWithPrecedingObjectRestOrSpread = collectParametersWithPrecedingObjectRestOrSpread(node); const updated = factory.updateConstructorDeclaration( node, /*decorators*/ undefined, node.modifiers, - visitParameterList(node.parameters, visitor, context), + visitParameterList(node.parameters, parameterVisitor, context), transformFunctionBody(node) ); enclosingFunctionFlags = savedEnclosingFunctionFlags; + parametersWithPrecedingObjectRestOrSpread = savedParametersWithPrecedingObjectRestOrSpread; return updated; } function visitGetAccessorDeclaration(node: GetAccessorDeclaration) { const savedEnclosingFunctionFlags = enclosingFunctionFlags; - enclosingFunctionFlags = FunctionFlags.Normal; + const savedParametersWithPrecedingObjectRestOrSpread = parametersWithPrecedingObjectRestOrSpread; + enclosingFunctionFlags = getFunctionFlags(node); + parametersWithPrecedingObjectRestOrSpread = collectParametersWithPrecedingObjectRestOrSpread(node); const updated = factory.updateGetAccessorDeclaration( node, /*decorators*/ undefined, node.modifiers, visitNode(node.name, visitor, isPropertyName), - visitParameterList(node.parameters, visitor, context), + visitParameterList(node.parameters, parameterVisitor, context), /*type*/ undefined, transformFunctionBody(node) ); enclosingFunctionFlags = savedEnclosingFunctionFlags; + parametersWithPrecedingObjectRestOrSpread = savedParametersWithPrecedingObjectRestOrSpread; return updated; } function visitSetAccessorDeclaration(node: SetAccessorDeclaration) { const savedEnclosingFunctionFlags = enclosingFunctionFlags; - enclosingFunctionFlags = FunctionFlags.Normal; + const savedParametersWithPrecedingObjectRestOrSpread = parametersWithPrecedingObjectRestOrSpread; + enclosingFunctionFlags = getFunctionFlags(node); + parametersWithPrecedingObjectRestOrSpread = collectParametersWithPrecedingObjectRestOrSpread(node); const updated = factory.updateSetAccessorDeclaration( node, /*decorators*/ undefined, node.modifiers, visitNode(node.name, visitor, isPropertyName), - visitParameterList(node.parameters, visitor, context), + visitParameterList(node.parameters, parameterVisitor, context), transformFunctionBody(node) ); enclosingFunctionFlags = savedEnclosingFunctionFlags; + parametersWithPrecedingObjectRestOrSpread = savedParametersWithPrecedingObjectRestOrSpread; return updated; } function visitMethodDeclaration(node: MethodDeclaration) { const savedEnclosingFunctionFlags = enclosingFunctionFlags; + const savedParametersWithPrecedingObjectRestOrSpread = parametersWithPrecedingObjectRestOrSpread; enclosingFunctionFlags = getFunctionFlags(node); + parametersWithPrecedingObjectRestOrSpread = collectParametersWithPrecedingObjectRestOrSpread(node); const updated = factory.updateMethodDeclaration( node, /*decorators*/ undefined, @@ -863,19 +906,22 @@ namespace ts { visitNode(node.name, visitor, isPropertyName), visitNode>(/*questionToken*/ undefined, visitor, isToken), /*typeParameters*/ undefined, - visitParameterList(node.parameters, visitor, context), + visitParameterList(node.parameters, parameterVisitor, context), /*type*/ undefined, enclosingFunctionFlags & FunctionFlags.Async && enclosingFunctionFlags & FunctionFlags.Generator ? transformAsyncGeneratorFunctionBody(node) : transformFunctionBody(node) ); enclosingFunctionFlags = savedEnclosingFunctionFlags; + parametersWithPrecedingObjectRestOrSpread = savedParametersWithPrecedingObjectRestOrSpread; return updated; } function visitFunctionDeclaration(node: FunctionDeclaration) { const savedEnclosingFunctionFlags = enclosingFunctionFlags; + const savedParametersWithPrecedingObjectRestOrSpread = parametersWithPrecedingObjectRestOrSpread; enclosingFunctionFlags = getFunctionFlags(node); + parametersWithPrecedingObjectRestOrSpread = collectParametersWithPrecedingObjectRestOrSpread(node); const updated = factory.updateFunctionDeclaration( node, /*decorators*/ undefined, @@ -887,35 +933,41 @@ namespace ts { : node.asteriskToken, node.name, /*typeParameters*/ undefined, - visitParameterList(node.parameters, visitor, context), + visitParameterList(node.parameters, parameterVisitor, context), /*type*/ undefined, enclosingFunctionFlags & FunctionFlags.Async && enclosingFunctionFlags & FunctionFlags.Generator ? transformAsyncGeneratorFunctionBody(node) : transformFunctionBody(node) ); enclosingFunctionFlags = savedEnclosingFunctionFlags; + parametersWithPrecedingObjectRestOrSpread = savedParametersWithPrecedingObjectRestOrSpread; return updated; } function visitArrowFunction(node: ArrowFunction) { const savedEnclosingFunctionFlags = enclosingFunctionFlags; + const savedParametersWithPrecedingObjectRestOrSpread = parametersWithPrecedingObjectRestOrSpread; enclosingFunctionFlags = getFunctionFlags(node); + parametersWithPrecedingObjectRestOrSpread = collectParametersWithPrecedingObjectRestOrSpread(node); const updated = factory.updateArrowFunction( node, node.modifiers, /*typeParameters*/ undefined, - visitParameterList(node.parameters, visitor, context), + visitParameterList(node.parameters, parameterVisitor, context), /*type*/ undefined, node.equalsGreaterThanToken, transformFunctionBody(node), ); enclosingFunctionFlags = savedEnclosingFunctionFlags; + parametersWithPrecedingObjectRestOrSpread = savedParametersWithPrecedingObjectRestOrSpread; return updated; } function visitFunctionExpression(node: FunctionExpression) { const savedEnclosingFunctionFlags = enclosingFunctionFlags; + const savedParametersWithPrecedingObjectRestOrSpread = parametersWithPrecedingObjectRestOrSpread; enclosingFunctionFlags = getFunctionFlags(node); + parametersWithPrecedingObjectRestOrSpread = collectParametersWithPrecedingObjectRestOrSpread(node); const updated = factory.updateFunctionExpression( node, enclosingFunctionFlags & FunctionFlags.Generator @@ -926,13 +978,14 @@ namespace ts { : node.asteriskToken, node.name, /*typeParameters*/ undefined, - visitParameterList(node.parameters, visitor, context), + visitParameterList(node.parameters, parameterVisitor, context), /*type*/ undefined, enclosingFunctionFlags & FunctionFlags.Async && enclosingFunctionFlags & FunctionFlags.Generator ? transformAsyncGeneratorFunctionBody(node) : transformFunctionBody(node) ); enclosingFunctionFlags = savedEnclosingFunctionFlags; + parametersWithPrecedingObjectRestOrSpread = savedParametersWithPrecedingObjectRestOrSpread; return updated; } @@ -1007,6 +1060,7 @@ namespace ts { statementOffset = factory.copyPrologue(body.statements, statements, /*ensureUseStrict*/ false, visitor); } addRange(statements, appendObjectRestAssignmentsIfNeeded(/*statements*/ undefined, node)); + const leadingStatements = endLexicalEnvironment(); if (statementOffset > 0 || some(statements) || some(leadingStatements)) { const block = factory.converters.convertToFunctionBlock(body, /*multiLine*/ true); @@ -1018,25 +1072,86 @@ namespace ts { } function appendObjectRestAssignmentsIfNeeded(statements: Statement[] | undefined, node: FunctionLikeDeclaration): Statement[] | undefined { + let containsPrecedingObjectRestOrSpread = false; for (const parameter of node.parameters) { - if (parameter.transformFlags & TransformFlags.ContainsObjectRestOrSpread) { - const temp = factory.getGeneratedNameForNode(parameter); + if (containsPrecedingObjectRestOrSpread) { + if (isBindingPattern(parameter.name)) { + // In cases where a binding pattern is simply '[]' or '{}', + // we usually don't want to emit a var declaration; however, in the presence + // of an initializer, we must emit that expression to preserve side effects. + // + // NOTE: see `insertDefaultValueAssignmentForBindingPattern` in es2015.ts + if (parameter.name.elements.length > 0) { + const declarations = flattenDestructuringBinding( + parameter, + visitor, + context, + FlattenLevel.All, + factory.getGeneratedNameForNode(parameter)); + if (some(declarations)) { + const declarationList = factory.createVariableDeclarationList(declarations); + const statement = factory.createVariableStatement(/*modifiers*/ undefined, declarationList); + setEmitFlags(statement, EmitFlags.CustomPrologue); + statements = append(statements, statement); + } + } + else if (parameter.initializer) { + const name = factory.getGeneratedNameForNode(parameter); + const initializer = visitNode(parameter.initializer, visitor, isExpression); + const assignment = factory.createAssignment(name, initializer); + const statement = factory.createExpressionStatement(assignment); + setEmitFlags(statement, EmitFlags.CustomPrologue); + statements = append(statements, statement); + } + } + else if (parameter.initializer) { + // Converts a parameter initializer into a function body statement, i.e.: + // + // function f(x = 1) { } + // + // becomes + // + // function f(x) { + // if (typeof x === "undefined") { x = 1; } + // } + + const name = factory.cloneNode(parameter.name); + setTextRange(name, parameter.name); + setEmitFlags(name, EmitFlags.NoSourceMap); + + const initializer = visitNode(parameter.initializer, visitor, isExpression); + addEmitFlags(initializer, EmitFlags.NoSourceMap | EmitFlags.NoComments); + + const assignment = factory.createAssignment(name, initializer); + setTextRange(assignment, parameter); + setEmitFlags(assignment, EmitFlags.NoComments); + + const block = factory.createBlock([factory.createExpressionStatement(assignment)]); + setTextRange(block, parameter); + setEmitFlags(block, EmitFlags.SingleLine | EmitFlags.NoTrailingSourceMap | EmitFlags.NoTokenSourceMaps | EmitFlags.NoComments); + + const typeCheck = factory.createTypeCheck(factory.cloneNode(parameter.name), "undefined"); + const statement = factory.createIfStatement(typeCheck, block); + startOnNewLine(statement); + setTextRange(statement, parameter); + setEmitFlags(statement, EmitFlags.NoTokenSourceMaps | EmitFlags.NoTrailingSourceMap | EmitFlags.CustomPrologue | EmitFlags.NoComments); + statements = append(statements, statement); + } + } + else if (parameter.transformFlags & TransformFlags.ContainsObjectRestOrSpread) { + containsPrecedingObjectRestOrSpread = true; const declarations = flattenDestructuringBinding( parameter, visitor, context, FlattenLevel.ObjectRest, - temp, + factory.getGeneratedNameForNode(parameter), /*doNotRecordTempVariablesInLine*/ false, /*skipInitializer*/ true, ); if (some(declarations)) { - const statement = factory.createVariableStatement( - /*modifiers*/ undefined, - factory.createVariableDeclarationList( - declarations - ) - ); + const declarationList = factory.createVariableDeclarationList(declarations); + const statement = factory.createVariableStatement(/*modifiers*/ undefined, declarationList); setEmitFlags(statement, EmitFlags.CustomPrologue); statements = append(statements, statement); } diff --git a/src/compiler/transformers/es2020.ts b/src/compiler/transformers/es2020.ts index 5e2311a2d27..328cc2376d6 100644 --- a/src/compiler/transformers/es2020.ts +++ b/src/compiler/transformers/es2020.ts @@ -122,11 +122,11 @@ namespace ts { function visitOptionalExpression(node: OptionalChain, captureThisArg: boolean, isDelete: boolean): Expression { const { expression, chain } = flattenChain(node); - const left = visitNonOptionalExpression(expression, isCallChain(chain[0]), /*isDelete*/ false); - const leftThisArg = isSyntheticReference(left) ? left.thisArg : undefined; - let leftExpression = isSyntheticReference(left) ? left.expression : left; - let capturedLeft: Expression = leftExpression; - if (!isSimpleCopiableExpression(leftExpression)) { + const left = visitNonOptionalExpression(skipPartiallyEmittedExpressions(expression), isCallChain(chain[0]), /*isDelete*/ false); + let leftThisArg = isSyntheticReference(left) ? left.thisArg : undefined; + let capturedLeft = isSyntheticReference(left) ? left.expression : left; + let leftExpression = factory.restoreOuterExpressions(expression, capturedLeft, OuterExpressionKinds.PartiallyEmittedExpressions); + if (!isSimpleCopiableExpression(capturedLeft)) { capturedLeft = factory.createTempVariable(hoistVariableDeclaration); leftExpression = factory.createAssignment(capturedLeft, leftExpression); } @@ -152,6 +152,10 @@ namespace ts { break; case SyntaxKind.CallExpression: if (i === 0 && leftThisArg) { + if (!isGeneratedIdentifier(leftThisArg)) { + leftThisArg = factory.cloneNode(leftThisArg); + addEmitFlags(leftThisArg, EmitFlags.NoComments); + } rightExpression = factory.createFunctionCallCall( rightExpression, leftThisArg.kind === SyntaxKind.SuperKeyword ? factory.createThis() : leftThisArg, diff --git a/src/compiler/transformers/jsx.ts b/src/compiler/transformers/jsx.ts index 55c606f8832..d321e74817b 100644 --- a/src/compiler/transformers/jsx.ts +++ b/src/compiler/transformers/jsx.ts @@ -26,12 +26,12 @@ namespace ts { return currentFileState.filenameDeclaration.name; } - function getJsxFactoryCalleePrimitive(childrenLength: number): "jsx" | "jsxs" | "jsxDEV" { - return compilerOptions.jsx === JsxEmit.ReactJSXDev ? "jsxDEV" : childrenLength > 1 ? "jsxs" : "jsx"; + function getJsxFactoryCalleePrimitive(isStaticChildren: boolean): "jsx" | "jsxs" | "jsxDEV" { + return compilerOptions.jsx === JsxEmit.ReactJSXDev ? "jsxDEV" : isStaticChildren ? "jsxs" : "jsx"; } - function getJsxFactoryCallee(childrenLength: number) { - const type = getJsxFactoryCalleePrimitive(childrenLength); + function getJsxFactoryCallee(isStaticChildren: boolean) { + const type = getJsxFactoryCalleePrimitive(isStaticChildren); return getImplicitImportForName(type); } @@ -48,15 +48,15 @@ namespace ts { return existing.name; } if (!currentFileState.utilizedImplicitRuntimeImports) { - currentFileState.utilizedImplicitRuntimeImports = createMap(); + currentFileState.utilizedImplicitRuntimeImports = new Map(); } let specifierSourceImports = currentFileState.utilizedImplicitRuntimeImports.get(importSource); if (!specifierSourceImports) { - specifierSourceImports = createMap(); + specifierSourceImports = new Map(); currentFileState.utilizedImplicitRuntimeImports.set(importSource, specifierSourceImports); } const generatedName = factory.createUniqueName(`_${name}`, GeneratedIdentifierFlags.Optimistic | GeneratedIdentifierFlags.FileLevel | GeneratedIdentifierFlags.AllowNameSubstitution); - const specifier = factory.createImportSpecifier(factory.createIdentifier(name), generatedName); + const specifier = factory.createImportSpecifier(/*isTypeOnly*/ false, factory.createIdentifier(name), generatedName); generatedName.generatedImportReference = specifier; specifierSourceImports.set(name, specifier); return generatedName; @@ -85,7 +85,7 @@ namespace ts { for (const [importSource, importSpecifiersMap] of arrayFrom(currentFileState.utilizedImplicitRuntimeImports.entries())) { if (isExternalModule(node)) { // Add `import` statement - const importStatement = factory.createImportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, factory.createImportClause(/*typeOnly*/ false, /*name*/ undefined, factory.createNamedImports(arrayFrom(importSpecifiersMap.values()))), factory.createStringLiteral(importSource)); + const importStatement = factory.createImportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, factory.createImportClause(/*typeOnly*/ false, /*name*/ undefined, factory.createNamedImports(arrayFrom(importSpecifiersMap.values()))), factory.createStringLiteral(importSource), /*assertClause*/ undefined); setParentRecursive(importStatement, /*incremental*/ false); statements = insertStatementAfterCustomPrologue(statements.slice(), importStatement); } @@ -200,69 +200,63 @@ namespace ts { } function convertJsxChildrenToChildrenPropObject(children: readonly JsxChild[]) { + const prop = convertJsxChildrenToChildrenPropAssignment(children); + return prop && factory.createObjectLiteralExpression([prop]); + } + + function convertJsxChildrenToChildrenPropAssignment(children: readonly JsxChild[]) { const nonWhitespaceChildren = getSemanticJsxChildren(children); - if (length(nonWhitespaceChildren) === 1) { + if (length(nonWhitespaceChildren) === 1 && !(nonWhitespaceChildren[0] as JsxExpression).dotDotDotToken) { const result = transformJsxChildToExpression(nonWhitespaceChildren[0]); - return result && factory.createObjectLiteralExpression([ - factory.createPropertyAssignment("children", result) - ]); + return result && factory.createPropertyAssignment("children", result); } const result = mapDefined(children, transformJsxChildToExpression); - return !result.length ? undefined : factory.createObjectLiteralExpression([ - factory.createPropertyAssignment("children", factory.createArrayLiteralExpression(result)) - ]); + return length(result) ? factory.createPropertyAssignment("children", factory.createArrayLiteralExpression(result)) : undefined; } function visitJsxOpeningLikeElementJSX(node: JsxOpeningLikeElement, children: readonly JsxChild[] | undefined, isChild: boolean, location: TextRange) { const tagName = getTagName(node); - let objectProperties: Expression; + const childrenProp = children && children.length ? convertJsxChildrenToChildrenPropAssignment(children) : undefined; const keyAttr = find(node.attributes.properties, p => !!p.name && isIdentifier(p.name) && p.name.escapedText === "key") as JsxAttribute | undefined; const attrs = keyAttr ? filter(node.attributes.properties, p => p !== keyAttr) : node.attributes.properties; - - let segments: Expression[] = []; - if (attrs.length) { - // Map spans of JsxAttribute nodes into object literals and spans - // of JsxSpreadAttribute nodes into expressions. - segments = flatten( - spanMap(attrs, isJsxSpreadAttribute, (attrs, isSpread) => isSpread - ? map(attrs, transformJsxSpreadAttributeToExpression) - : factory.createObjectLiteralExpression(map(attrs, transformJsxAttributeToObjectLiteralElement)) - ) - ); - - if (isJsxSpreadAttribute(attrs[0])) { - // We must always emit at least one object literal before a spread - // argument.factory.createObjectLiteral - segments.unshift(factory.createObjectLiteralExpression()); - } - } - if (children && children.length) { - const result = convertJsxChildrenToChildrenPropObject(children); - if (result) { - segments.push(result); - } - } - - if (segments.length === 0) { - objectProperties = factory.createObjectLiteralExpression([]); - // When there are no attributes, React wants {} - } - else { - // Either emit one big object literal (no spread attribs), or - // a call to the __assign helper. - objectProperties = singleOrUndefined(segments) || emitHelpers().createAssignHelper(segments); - } - - return visitJsxOpeningLikeElementOrFragmentJSX(tagName, objectProperties, keyAttr, length(getSemanticJsxChildren(children || emptyArray)), isChild, location); + const objectProperties = length(attrs) ? transformJsxAttributesToObjectProps(attrs, childrenProp) : + factory.createObjectLiteralExpression(childrenProp ? [childrenProp] : emptyArray); // When there are no attributes, React wants {} + return visitJsxOpeningLikeElementOrFragmentJSX( + tagName, + objectProperties, + keyAttr, + children || emptyArray, + isChild, + location + ); } - function visitJsxOpeningLikeElementOrFragmentJSX(tagName: Expression, objectProperties: Expression, keyAttr: JsxAttribute | undefined, childrenLength: number, isChild: boolean, location: TextRange) { - const args: Expression[] = [tagName, objectProperties, !keyAttr ? factory.createVoidZero() : transformJsxAttributeInitializer(keyAttr.initializer)]; + function visitJsxOpeningLikeElementOrFragmentJSX( + tagName: Expression, + objectProperties: Expression, + keyAttr: JsxAttribute | undefined, + children: readonly JsxChild[], + isChild: boolean, + location: TextRange + ) { + const nonWhitespaceChildren = getSemanticJsxChildren(children); + const isStaticChildren = + length(nonWhitespaceChildren) > 1 || !!(nonWhitespaceChildren[0] as JsxExpression)?.dotDotDotToken; + const args: Expression[] = [tagName, objectProperties]; + // function jsx(type, config, maybeKey) {} + // "maybeKey" is optional. It is acceptable to use "_jsx" without a third argument + if (keyAttr) { + args.push(transformJsxAttributeInitializer(keyAttr.initializer)); + } if (compilerOptions.jsx === JsxEmit.ReactJSXDev) { const originalFile = getOriginalNode(currentSourceFile); if (originalFile && isSourceFile(originalFile)) { + // "maybeKey" has to be replaced with "void 0" to not break the jsxDEV signature + if (keyAttr === undefined) { + args.push(factory.createVoidZero()); + } // isStaticChildren development flag - args.push(childrenLength > 1 ? factory.createTrue() : factory.createFalse()); + args.push(isStaticChildren ? factory.createTrue() : factory.createFalse()); // __source development flag const lineCol = getLineAndCharacterOfPosition(originalFile, location.pos); args.push(factory.createObjectLiteralExpression([ @@ -274,7 +268,11 @@ namespace ts { args.push(factory.createThis()); } } - const element = setTextRange(factory.createCallExpression(getJsxFactoryCallee(childrenLength), /*typeArguments*/ undefined, args), location); + + const element = setTextRange( + factory.createCallExpression(getJsxFactoryCallee(isStaticChildren), /*typeArguments*/ undefined, args), + location + ); if (isChild) { startOnNewLine(element); @@ -285,47 +283,9 @@ namespace ts { function visitJsxOpeningLikeElementCreateElement(node: JsxOpeningLikeElement, children: readonly JsxChild[] | undefined, isChild: boolean, location: TextRange) { const tagName = getTagName(node); - let objectProperties: Expression | undefined; const attrs = node.attributes.properties; - if (attrs.length === 0) { - objectProperties = factory.createNull(); - // When there are no attributes, React wants "null" - } - else { - const target = compilerOptions.target; - if (target && target >= ScriptTarget.ES2018) { - objectProperties = factory.createObjectLiteralExpression( - flatten( - spanMap(attrs, isJsxSpreadAttribute, (attrs, isSpread) => - isSpread ? map(attrs, transformJsxSpreadAttributeToSpreadAssignment) : map(attrs, transformJsxAttributeToObjectLiteralElement) - ) - ) - ); - } - else { - // Map spans of JsxAttribute nodes into object literals and spans - // of JsxSpreadAttribute nodes into expressions. - const segments = flatten( - spanMap(attrs, isJsxSpreadAttribute, (attrs, isSpread) => isSpread - ? map(attrs, transformJsxSpreadAttributeToExpression) - : factory.createObjectLiteralExpression(map(attrs, transformJsxAttributeToObjectLiteralElement)) - ) - ); - - if (isJsxSpreadAttribute(attrs[0])) { - // We must always emit at least one object literal before a spread - // argument.factory.createObjectLiteral - segments.unshift(factory.createObjectLiteralExpression()); - } - - // Either emit one big object literal (no spread attribs), or - // a call to the __assign helper. - objectProperties = singleOrUndefined(segments); - if (!objectProperties) { - objectProperties = emitHelpers().createAssignHelper(segments); - } - } - } + const objectProperties = length(attrs) ? transformJsxAttributesToObjectProps(attrs) : + factory.createNull(); // When there are no attributes, React wants "null" const callee = currentFileState.importSpecifier === undefined ? createJsxFactoryExpression( @@ -364,7 +324,7 @@ namespace ts { getImplicitJsxFragmentReference(), childrenProps || factory.createObjectLiteralExpression([]), /*keyAttr*/ undefined, - length(getSemanticJsxChildren(children)), + children, isChild, location ); @@ -392,6 +352,44 @@ namespace ts { return factory.createSpreadAssignment(visitNode(node.expression, visitor, isExpression)); } + function transformJsxAttributesToObjectProps(attrs: readonly(JsxSpreadAttribute | JsxAttribute)[], children?: PropertyAssignment) { + const target = getEmitScriptTarget(compilerOptions); + return target && target >= ScriptTarget.ES2018 ? factory.createObjectLiteralExpression(transformJsxAttributesToProps(attrs, children)) : + transformJsxAttributesToExpression(attrs, children); + } + + function transformJsxAttributesToProps(attrs: readonly(JsxSpreadAttribute | JsxAttribute)[], children?: PropertyAssignment) { + const props = flatten(spanMap(attrs, isJsxSpreadAttribute, (attrs, isSpread) => + map(attrs, attr => isSpread ? transformJsxSpreadAttributeToSpreadAssignment(attr as JsxSpreadAttribute) : transformJsxAttributeToObjectLiteralElement(attr as JsxAttribute)))); + if (children) { + props.push(children); + } + return props; + } + + function transformJsxAttributesToExpression(attrs: readonly(JsxSpreadAttribute | JsxAttribute)[], children?: PropertyAssignment) { + // Map spans of JsxAttribute nodes into object literals and spans + // of JsxSpreadAttribute nodes into expressions. + const expressions = flatten( + spanMap(attrs, isJsxSpreadAttribute, (attrs, isSpread) => isSpread + ? map(attrs, transformJsxSpreadAttributeToExpression) + : factory.createObjectLiteralExpression(map(attrs, transformJsxAttributeToObjectLiteralElement)) + ) + ); + + if (isJsxSpreadAttribute(attrs[0])) { + // We must always emit at least one object literal before a spread + // argument.factory.createObjectLiteral + expressions.unshift(factory.createObjectLiteralExpression()); + } + + if (children) { + expressions.push(factory.createObjectLiteralExpression([children])); + } + + return singleOrUndefined(expressions) || emitHelpers().createAssignHelper(expressions); + } + function transformJsxSpreadAttributeToExpression(node: JsxSpreadAttribute) { return visitNode(node.expression, visitor, isExpression); } @@ -547,7 +545,8 @@ namespace ts { } function visitJsxExpression(node: JsxExpression) { - return visitNode(node.expression, visitor, isExpression); + const expression = visitNode(node.expression, visitor, isExpression); + return node.dotDotDotToken ? factory.createSpreadElement(expression!) : expression; } } diff --git a/src/compiler/transformers/module/esnextAnd2015.ts b/src/compiler/transformers/module/esnextAnd2015.ts index 4a7e297722e..5e755e5083c 100644 --- a/src/compiler/transformers/module/esnextAnd2015.ts +++ b/src/compiler/transformers/module/esnextAnd2015.ts @@ -5,7 +5,10 @@ namespace ts { factory, getEmitHelperFactory: emitHelpers, } = context; + const host = context.getEmitHost(); + const resolver = context.getEmitResolver(); const compilerOptions = context.getCompilerOptions(); + const languageVersion = getEmitScriptTarget(compilerOptions); const previousOnEmitNode = context.onEmitNode; const previousOnSubstituteNode = context.onSubstituteNode; context.onEmitNode = onEmitNode; @@ -14,6 +17,8 @@ namespace ts { context.enableSubstitution(SyntaxKind.Identifier); let helperNameSubstitutions: ESMap | undefined; + let currentSourceFile: SourceFile | undefined; + let importRequireStatements: [ImportDeclaration, VariableStatement] | undefined; return chainBundle(context, transformSourceFile); function transformSourceFile(node: SourceFile) { @@ -22,7 +27,16 @@ namespace ts { } if (isExternalModule(node) || compilerOptions.isolatedModules) { - const result = updateExternalModule(node); + currentSourceFile = node; + importRequireStatements = undefined; + let result = updateExternalModule(node); + currentSourceFile = undefined; + if (importRequireStatements) { + result = factory.updateSourceFile( + result, + setTextRange(factory.createNodeArray(insertStatementsAfterCustomPrologue(result.statements.slice(), importRequireStatements)), result.statements), + ); + } if (!isExternalModule(node) || some(result.statements, isExternalModuleIndicator)) { return result; } @@ -55,8 +69,10 @@ namespace ts { function visitor(node: Node): VisitResult { switch (node.kind) { case SyntaxKind.ImportEqualsDeclaration: - // Elide `import=` as it is not legal with --module ES6 - return undefined; + // Though an error in es2020 modules, in node-flavor es2020 modules, we can helpfully transform this to a synthetic `require` call + // To give easy access to a synchronous `require` in node-flavor esm. We do the transform even in scenarios where we error, but `import.meta.url` + // is available, just because the output is reasonable for a node-like runtime. + return getEmitScriptTarget(compilerOptions) >= ModuleKind.ES2020 ? visitImportEqualsDeclaration(node as ImportEqualsDeclaration) : undefined; case SyntaxKind.ExportAssignment: return visitExportAssignment(node as ExportAssignment); case SyntaxKind.ExportDeclaration: @@ -67,6 +83,106 @@ namespace ts { return node; } + /** + * Creates a `require()` call to import an external module. + * + * @param importNode The declaration to import. + */ + function createRequireCall(importNode: ImportDeclaration | ImportEqualsDeclaration | ExportDeclaration) { + const moduleName = getExternalModuleNameLiteral(factory, importNode, Debug.checkDefined(currentSourceFile), host, resolver, compilerOptions); + const args: Expression[] = []; + if (moduleName) { + args.push(moduleName); + } + + if (!importRequireStatements) { + const createRequireName = factory.createUniqueName("_createRequire", GeneratedIdentifierFlags.Optimistic | GeneratedIdentifierFlags.FileLevel); + const importStatement = factory.createImportDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + factory.createImportClause( + /*isTypeOnly*/ false, + /*name*/ undefined, + factory.createNamedImports([ + factory.createImportSpecifier(/*isTypeOnly*/ false, factory.createIdentifier("createRequire"), createRequireName) + ]) + ), + factory.createStringLiteral("module") + ); + const requireHelperName = factory.createUniqueName("__require", GeneratedIdentifierFlags.Optimistic | GeneratedIdentifierFlags.FileLevel); + const requireStatement = factory.createVariableStatement( + /*modifiers*/ undefined, + factory.createVariableDeclarationList( + [ + factory.createVariableDeclaration( + requireHelperName, + /*exclamationToken*/ undefined, + /*type*/ undefined, + factory.createCallExpression(factory.cloneNode(createRequireName), /*typeArguments*/ undefined, [ + factory.createPropertyAccessExpression(factory.createMetaProperty(SyntaxKind.ImportKeyword, factory.createIdentifier("meta")), factory.createIdentifier("url")) + ]) + ) + ], + /*flags*/ languageVersion >= ScriptTarget.ES2015 ? NodeFlags.Const : NodeFlags.None + ) + ); + importRequireStatements = [importStatement, requireStatement]; + + } + + const name = importRequireStatements[1].declarationList.declarations[0].name; + Debug.assertNode(name, isIdentifier); + return factory.createCallExpression(factory.cloneNode(name), /*typeArguments*/ undefined, args); + } + + /** + * Visits an ImportEqualsDeclaration node. + * + * @param node The node to visit. + */ + function visitImportEqualsDeclaration(node: ImportEqualsDeclaration): VisitResult { + Debug.assert(isExternalModuleImportEqualsDeclaration(node), "import= for internal module references should be handled in an earlier transformer."); + + let statements: Statement[] | undefined; + statements = append(statements, + setOriginalNode( + setTextRange( + factory.createVariableStatement( + /*modifiers*/ undefined, + factory.createVariableDeclarationList( + [ + factory.createVariableDeclaration( + factory.cloneNode(node.name), + /*exclamationToken*/ undefined, + /*type*/ undefined, + createRequireCall(node) + ) + ], + /*flags*/ languageVersion >= ScriptTarget.ES2015 ? NodeFlags.Const : NodeFlags.None + ) + ), + node), + node + ) + ); + + statements = appendExportsOfImportEqualsDeclaration(statements, node); + + return singleOrMany(statements); + } + + function appendExportsOfImportEqualsDeclaration(statements: Statement[] | undefined, node: ImportEqualsDeclaration) { + if (hasSyntacticModifier(node, ModifierFlags.Export)) { + statements = append(statements, factory.createExportDeclaration( + /*decorators*/ undefined, + /*modifiers*/ undefined, + node.isTypeOnly, + factory.createNamedExports([factory.createExportSpecifier(/*isTypeOnly*/ false, /*propertyName*/ undefined, idText(node.name))]) + )); + } + return statements; + } + function visitExportAssignment(node: ExportAssignment): VisitResult { // Elide `export=` as it is not legal with --module ES6 return node.isExportEquals ? undefined : node; @@ -96,6 +212,7 @@ namespace ts { ) ), node.moduleSpecifier, + node.assertClause ); setOriginalNode(importDecl, node.exportClause); @@ -103,7 +220,7 @@ namespace ts { /*decorators*/ undefined, /*modifiers*/ undefined, /*isTypeOnly*/ false, - factory.createNamedExports([factory.createExportSpecifier(synthName, oldIdentifier)]), + factory.createNamedExports([factory.createExportSpecifier(/*isTypeOnly*/ false, synthName, oldIdentifier)]), ); setOriginalNode(exportDecl, node); diff --git a/src/compiler/transformers/module/module.ts b/src/compiler/transformers/module/module.ts index 225939a9df8..2b4978306e8 100644 --- a/src/compiler/transformers/module/module.ts +++ b/src/compiler/transformers/module/module.ts @@ -556,7 +556,7 @@ namespace ts { case SyntaxKind.PartiallyEmittedExpression: return visitPartiallyEmittedExpression(node as PartiallyEmittedExpression, valueIsDiscarded); case SyntaxKind.CallExpression: - if (isImportCall(node)) { + if (isImportCall(node) && currentSourceFile.impliedNodeFormat === undefined) { return visitImportCallExpression(node); } break; @@ -814,7 +814,7 @@ namespace ts { } const promise = factory.createNewExpression(factory.createIdentifier("Promise"), /*typeArguments*/ undefined, [func]); - if (compilerOptions.esModuleInterop) { + if (getESModuleInterop(compilerOptions)) { return factory.createCallExpression(factory.createPropertyAccessExpression(promise, factory.createIdentifier("then")), /*typeArguments*/ undefined, [emitHelpers().createImportStarCallbackHelper()]); } return promise; @@ -828,7 +828,7 @@ namespace ts { // if we simply do require in resolve callback in Promise constructor. We will execute the loading immediately const promiseResolveCall = factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("Promise"), "resolve"), /*typeArguments*/ undefined, /*argumentsArray*/ []); let requireCall: Expression = factory.createCallExpression(factory.createIdentifier("require"), /*typeArguments*/ undefined, arg ? [arg] : []); - if (compilerOptions.esModuleInterop) { + if (getESModuleInterop(compilerOptions)) { requireCall = emitHelpers().createImportStarHelper(requireCall); } @@ -864,7 +864,7 @@ namespace ts { } function getHelperExpressionForExport(node: ExportDeclaration, innerExpr: Expression) { - if (!compilerOptions.esModuleInterop || getEmitFlags(node) & EmitFlags.NeverApplyImportHelper) { + if (!getESModuleInterop(compilerOptions) || getEmitFlags(node) & EmitFlags.NeverApplyImportHelper) { return innerExpr; } if (getExportNeedsImportStarHelper(node)) { @@ -874,7 +874,7 @@ namespace ts { } function getHelperExpressionForImport(node: ImportDeclaration, innerExpr: Expression) { - if (!compilerOptions.esModuleInterop || getEmitFlags(node) & EmitFlags.NeverApplyImportHelper) { + if (!getESModuleInterop(compilerOptions) || getEmitFlags(node) & EmitFlags.NeverApplyImportHelper) { return innerExpr; } if (getImportNeedsImportStarHelper(node)) { @@ -1134,7 +1134,7 @@ namespace ts { } else { const exportNeedsImportDefault = - !!compilerOptions.esModuleInterop && + !!getESModuleInterop(compilerOptions) && !(getEmitFlags(node) & EmitFlags.NeverApplyImportHelper) && idText(specifier.propertyName || specifier.name) === "default"; const exportedValue = factory.createPropertyAccessExpression( @@ -1853,7 +1853,7 @@ namespace ts { if (isIdentifier(node.expression)) { const expression = substituteExpressionIdentifier(node.expression); noSubstitution[getNodeId(expression)] = true; - if (!isIdentifier(expression)) { + if (!isIdentifier(expression) && !(getEmitFlags(node.expression) & EmitFlags.HelperName)) { return addEmitFlags( factory.updateCallExpression(node, expression, @@ -1872,7 +1872,7 @@ namespace ts { if (isIdentifier(node.tag)) { const tag = substituteExpressionIdentifier(node.tag); noSubstitution[getNodeId(tag)] = true; - if (!isIdentifier(tag)) { + if (!isIdentifier(tag) && !(getEmitFlags(node.tag) & EmitFlags.HelperName)) { return addEmitFlags( factory.updateTaggedTemplateExpression(node, tag, diff --git a/src/compiler/transformers/module/node.ts b/src/compiler/transformers/module/node.ts new file mode 100644 index 00000000000..b1addf4f3ec --- /dev/null +++ b/src/compiler/transformers/module/node.ts @@ -0,0 +1,84 @@ +/*@internal*/ +namespace ts { + export function transformNodeModule(context: TransformationContext) { + const previousOnSubstituteNode = context.onSubstituteNode; + const previousOnEmitNode = context.onEmitNode; + + const esmTransform = transformECMAScriptModule(context); + + const esmOnSubstituteNode = context.onSubstituteNode; + const esmOnEmitNode = context.onEmitNode; + + context.onSubstituteNode = previousOnSubstituteNode; + context.onEmitNode = previousOnEmitNode; + + const cjsTransform = transformModule(context); + + const cjsOnSubstituteNode = context.onSubstituteNode; + const cjsOnEmitNode = context.onEmitNode; + + context.onSubstituteNode = onSubstituteNode; + context.onEmitNode = onEmitNode; + context.enableSubstitution(SyntaxKind.SourceFile); + context.enableEmitNotification(SyntaxKind.SourceFile); + + let currentSourceFile: SourceFile | undefined; + return transformSourceFileOrBundle; + + function onSubstituteNode(hint: EmitHint, node: Node) { + if (isSourceFile(node)) { + currentSourceFile = node; + // Neither component transform wants substitution notifications for `SourceFile`s, and, in fact, relies on + // the source file emit notification to setup scope variables for substitutions (so we _cannot_ call their substitute + // functions on source files safely, as that context only gets setup in a later pipeline phase!) + return previousOnSubstituteNode(hint, node); + } + else { + if (!currentSourceFile) { + return previousOnSubstituteNode(hint, node); + } + if (currentSourceFile.impliedNodeFormat === ModuleKind.ESNext) { + return esmOnSubstituteNode(hint, node); + } + return cjsOnSubstituteNode(hint, node); + } + } + + function onEmitNode(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void): void { + if (isSourceFile(node)) { + currentSourceFile = node; + } + if (!currentSourceFile) { + return previousOnEmitNode(hint, node, emitCallback); + } + if (currentSourceFile.impliedNodeFormat === ModuleKind.ESNext) { + return esmOnEmitNode(hint, node, emitCallback); + } + return cjsOnEmitNode(hint, node, emitCallback); + } + + function getModuleTransformForFile(file: SourceFile): (typeof esmTransform) { + return file.impliedNodeFormat === ModuleKind.ESNext ? esmTransform : cjsTransform; + } + + function transformSourceFile(node: SourceFile) { + if (node.isDeclarationFile) { + return node; + } + + currentSourceFile = node; + const result = getModuleTransformForFile(node)(node); + currentSourceFile = undefined; + Debug.assert(isSourceFile(result)); + return result; + } + + function transformSourceFileOrBundle(node: SourceFile | Bundle) { + return node.kind === SyntaxKind.SourceFile ? transformSourceFile(node) : transformBundle(node); + } + + function transformBundle(node: Bundle) { + return context.factory.createBundle(map(node.sourceFiles, transformSourceFile), node.prepends); + } + } +} diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index 012be39dadb..f8cec499a8f 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -1189,7 +1189,7 @@ namespace ts { // const prefix = getClassMemberPrefix(node, member); - const memberName = getExpressionForPropertyName(member, /*generateNameForComputedPropertyName*/ true); + const memberName = getExpressionForPropertyName(member, /*generateNameForComputedPropertyName*/ !hasSyntacticModifier(member, ModifierFlags.Ambient)); const descriptor = languageVersion > ScriptTarget.ES3 ? member.kind === SyntaxKind.PropertyDeclaration // We emit `void 0` here to indicate to `__decorate` that it can invoke `Object.defineProperty` directly, but that it @@ -1511,6 +1511,7 @@ namespace ts { case SyntaxKind.BooleanKeyword: return factory.createIdentifier("Boolean"); + case SyntaxKind.TemplateLiteralType: case SyntaxKind.StringKeyword: return factory.createIdentifier("String"); @@ -1932,13 +1933,21 @@ namespace ts { } let statements: Statement[] = []; - let indexOfFirstStatement = 0; resumeLexicalEnvironment(); - indexOfFirstStatement = addPrologueDirectivesAndInitialSuperCall(factory, constructor, statements, visitor); + const indexAfterLastPrologueStatement = factory.copyPrologue(body.statements, statements, /*ensureUseStrict*/ false, visitor); + const superStatementIndex = findSuperStatementIndex(body.statements, indexAfterLastPrologueStatement); - // Add parameters with property assignments. Transforms this: + // If there was a super call, visit existing statements up to and including it + if (superStatementIndex >= 0) { + addRange( + statements, + visitNodes(body.statements, visitor, isStatement, indexAfterLastPrologueStatement, superStatementIndex + 1 - indexAfterLastPrologueStatement), + ); + } + + // Transform parameters into property assignments. Transforms this: // // constructor (public x, public y) { // } @@ -1950,10 +1959,19 @@ namespace ts { // this.y = y; // } // - addRange(statements, map(parametersWithPropertyAssignments, transformParameterWithPropertyAssignment)); + const parameterPropertyAssignments = mapDefined(parametersWithPropertyAssignments, transformParameterWithPropertyAssignment); - // Add the existing statements, skipping the initial super call. - addRange(statements, visitNodes(body.statements, visitor, isStatement, indexOfFirstStatement)); + // If there is a super() call, the parameter properties go immediately after it + if (superStatementIndex >= 0) { + addRange(statements, parameterPropertyAssignments); + } + // Since there was no super() call, parameter properties are the first statements in the constructor + else { + statements = addRange(parameterPropertyAssignments, statements); + } + + // Add remaining statements from the body, skipping the super() call if it was found + addRange(statements, visitNodes(body.statements, visitor, isStatement, superStatementIndex + 1)); // End the lexical environment. statements = factory.mergeLexicalEnvironment(statements, endLexicalEnvironment()); @@ -2237,11 +2255,10 @@ namespace ts { // we can safely elide the parentheses here, as a new synthetic // ParenthesizedExpression will be inserted if we remove parentheses too // aggressively. - // HOWEVER - if there are leading comments on the expression itself, to handle ASI - // correctly for return and throw, we must keep the parenthesis - if (length(getLeadingCommentRangesOfNode(expression, currentSourceFile))) { - return factory.updateParenthesizedExpression(node, expression); - } + // + // If there are leading comments on the expression itself, the emitter will handle ASI + // for return, throw, and yield by re-introducing parenthesis during emit on an as-need + // basis. return factory.createPartiallyEmittedExpression(expression, node); } @@ -2507,6 +2524,7 @@ namespace ts { || (isExternalModuleExport(node) && moduleKind !== ModuleKind.ES2015 && moduleKind !== ModuleKind.ES2020 + && moduleKind !== ModuleKind.ES2022 && moduleKind !== ModuleKind.ESNext && moduleKind !== ModuleKind.System); } @@ -2816,7 +2834,8 @@ namespace ts { /*decorators*/ undefined, /*modifiers*/ undefined, importClause, - node.moduleSpecifier) + node.moduleSpecifier, + node.assertClause) : undefined; } @@ -2844,9 +2863,12 @@ namespace ts { return shouldEmitAliasDeclaration(node) ? node : undefined; } else { - // Elide named imports if all of its import specifiers are elided. + // Elide named imports if all of its import specifiers are elided and settings allow. + const allowEmpty = compilerOptions.preserveValueImports && ( + compilerOptions.importsNotUsedAsValues === ImportsNotUsedAsValues.Preserve || + compilerOptions.importsNotUsedAsValues === ImportsNotUsedAsValues.Error); const elements = visitNodes(node.elements, visitImportSpecifier, isImportSpecifier); - return some(elements) ? factory.updateNamedImports(node, elements) : undefined; + return allowEmpty || some(elements) ? factory.updateNamedImports(node, elements) : undefined; } } @@ -2856,7 +2878,7 @@ namespace ts { * @param node The import specifier node. */ function visitImportSpecifier(node: ImportSpecifier): VisitResult { - return shouldEmitAliasDeclaration(node) ? node : undefined; + return !node.isTypeOnly && shouldEmitAliasDeclaration(node) ? node : undefined; } /** @@ -2889,13 +2911,15 @@ namespace ts { return node; } - if (!resolver.isValueAliasDeclaration(node)) { - // Elide the export declaration if it does not export a value. - return undefined; - } - // Elide the export declaration if all of its named exports are elided. - const exportClause = visitNode(node.exportClause, visitNamedExportBindings, isNamedExportBindings); + const allowEmpty = !!node.moduleSpecifier && ( + compilerOptions.importsNotUsedAsValues === ImportsNotUsedAsValues.Preserve || + compilerOptions.importsNotUsedAsValues === ImportsNotUsedAsValues.Error); + const exportClause = visitNode( + node.exportClause, + (bindings: NamedExportBindings) => visitNamedExportBindings(bindings, allowEmpty), + isNamedExportBindings); + return exportClause ? factory.updateExportDeclaration( node, @@ -2903,7 +2927,8 @@ namespace ts { /*modifiers*/ undefined, node.isTypeOnly, exportClause, - node.moduleSpecifier) + node.moduleSpecifier, + node.assertClause) : undefined; } @@ -2913,18 +2938,18 @@ namespace ts { * * @param node The named exports node. */ - function visitNamedExports(node: NamedExports): VisitResult { + function visitNamedExports(node: NamedExports, allowEmpty: boolean): VisitResult { // Elide the named exports if all of its export specifiers were elided. const elements = visitNodes(node.elements, visitExportSpecifier, isExportSpecifier); - return some(elements) ? factory.updateNamedExports(node, elements) : undefined; + return allowEmpty || some(elements) ? factory.updateNamedExports(node, elements) : undefined; } function visitNamespaceExports(node: NamespaceExport): VisitResult { return factory.updateNamespaceExport(node, visitNode(node.name, visitor, isIdentifier)); } - function visitNamedExportBindings(node: NamedExportBindings): VisitResult { - return isNamespaceExport(node) ? visitNamespaceExports(node) : visitNamedExports(node); + function visitNamedExportBindings(node: NamedExportBindings, allowEmpty: boolean): VisitResult { + return isNamespaceExport(node) ? visitNamespaceExports(node) : visitNamedExports(node, allowEmpty); } /** @@ -2934,7 +2959,7 @@ namespace ts { */ function visitExportSpecifier(node: ExportSpecifier): VisitResult { // Elide an export specifier if it does not reference a value. - return resolver.isValueAliasDeclaration(node) ? node : undefined; + return !node.isTypeOnly && resolver.isValueAliasDeclaration(node) ? node : undefined; } /** @@ -2973,6 +2998,7 @@ namespace ts { /*modifiers*/ undefined, /*importClause*/ undefined, node.moduleReference.expression, + /*assertClause*/ undefined ), node, ), diff --git a/src/compiler/transformers/utilities.ts b/src/compiler/transformers/utilities.ts index 276d034c7b9..7a714befd82 100644 --- a/src/compiler/transformers/utilities.ts +++ b/src/compiler/transformers/utilities.ts @@ -299,35 +299,32 @@ namespace ts { } /** - * Adds super call and preceding prologue directives into the list of statements. - * - * @param ctor The constructor node. - * @param result The list of statements. - * @param visitor The visitor to apply to each node added to the result array. - * @returns index of the statement that follows super call + * @returns Contained super() call from descending into the statement ignoring parentheses, if that call exists. */ - export function addPrologueDirectivesAndInitialSuperCall(factory: NodeFactory, ctor: ConstructorDeclaration, result: Statement[], visitor: Visitor): number { - if (ctor.body) { - const statements = ctor.body.statements; - // add prologue directives to the list (if any) - const index = factory.copyPrologue(statements, result, /*ensureUseStrict*/ false, visitor); - if (index === statements.length) { - // list contains nothing but prologue directives (or empty) - exit - return index; - } - - const superIndex = findIndex(statements, s => isExpressionStatement(s) && isSuperCall(s.expression), index); - if (superIndex > -1) { - for (let i = index; i <= superIndex; i++) { - result.push(visitNode(statements[i], visitor, isStatement)); - } - return superIndex + 1; - } - - return index; + export function getSuperCallFromStatement(statement: Statement) { + if (!isExpressionStatement(statement)) { + return undefined; } - return 0; + const expression = skipParentheses(statement.expression); + return isSuperCall(expression) + ? expression + : undefined; + } + + /** + * @returns The index (after prologue statements) of a super call, or -1 if not found. + */ + export function findSuperStatementIndex(statements: NodeArray, indexAfterLastPrologueStatement: number) { + for (let i = indexAfterLastPrologueStatement; i < statements.length; i += 1) { + const statement = statements[i]; + + if (getSuperCallFromStatement(statement)) { + return i; + } + } + + return -1; } /** diff --git a/src/compiler/tsbuildPublic.ts b/src/compiler/tsbuildPublic.ts index b92741a3de8..ad9f97f2dd8 100644 --- a/src/compiler/tsbuildPublic.ts +++ b/src/compiler/tsbuildPublic.ts @@ -76,7 +76,12 @@ namespace ts { return fileExtensionIs(fileName, Extension.Dts); } - export type ReportEmitErrorSummary = (errorCount: number) => void; + export type ReportEmitErrorSummary = (errorCount: number, filesInError: (ReportFileInError | undefined)[]) => void; + + export interface ReportFileInError { + fileName: string; + line: number; + } export interface SolutionBuilderHostBase extends ProgramHost { createDirectory?(path: string): void; @@ -281,14 +286,15 @@ namespace ts { const moduleResolutionCache = !compilerHost.resolveModuleNames ? createModuleResolutionCache(currentDirectory, getCanonicalFileName) : undefined; const typeReferenceDirectiveResolutionCache = !compilerHost.resolveTypeReferenceDirectives ? createTypeReferenceDirectiveResolutionCache(currentDirectory, getCanonicalFileName, /*options*/ undefined, moduleResolutionCache?.getPackageJsonInfoCache()) : undefined; if (!compilerHost.resolveModuleNames) { - const loader = (moduleName: string, containingFile: string, redirectedReference: ResolvedProjectReference | undefined) => resolveModuleName(moduleName, containingFile, state.projectCompilerOptions, compilerHost, moduleResolutionCache, redirectedReference).resolvedModule!; - compilerHost.resolveModuleNames = (moduleNames, containingFile, _reusedNames, redirectedReference) => - loadWithLocalCache(Debug.checkEachDefined(moduleNames), containingFile, redirectedReference, loader); + const loader = (moduleName: string, resolverMode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined, containingFile: string, redirectedReference: ResolvedProjectReference | undefined) => resolveModuleName(moduleName, containingFile, state.projectCompilerOptions, compilerHost, moduleResolutionCache, redirectedReference, resolverMode).resolvedModule!; + compilerHost.resolveModuleNames = (moduleNames, containingFile, _reusedNames, redirectedReference, _options, containingSourceFile) => + loadWithModeAwareCache(Debug.checkEachDefined(moduleNames), Debug.checkDefined(containingSourceFile), containingFile, redirectedReference, loader); + compilerHost.getModuleResolutionCache = () => moduleResolutionCache; } if (!compilerHost.resolveTypeReferenceDirectives) { - const loader = (moduleName: string, containingFile: string, redirectedReference: ResolvedProjectReference | undefined) => resolveTypeReferenceDirective(moduleName, containingFile, state.projectCompilerOptions, compilerHost, redirectedReference, state.typeReferenceDirectiveResolutionCache).resolvedTypeReferenceDirective!; - compilerHost.resolveTypeReferenceDirectives = (typeReferenceDirectiveNames, containingFile, redirectedReference) => - loadWithLocalCache(Debug.checkEachDefined(typeReferenceDirectiveNames), containingFile, redirectedReference, loader); + const loader = (moduleName: string, containingFile: string, redirectedReference: ResolvedProjectReference | undefined, containingFileMode: SourceFile["impliedNodeFormat"] | undefined) => resolveTypeReferenceDirective(moduleName, containingFile, state.projectCompilerOptions, compilerHost, redirectedReference, state.typeReferenceDirectiveResolutionCache, containingFileMode).resolvedTypeReferenceDirective!; + compilerHost.resolveTypeReferenceDirectives = (typeReferenceDirectiveNames, containingFile, redirectedReference, _options, containingFileMode) => + loadWithTypeDirectiveCache(Debug.checkEachDefined(typeReferenceDirectiveNames), containingFile, redirectedReference, containingFileMode, loader); } const { watchFile, watchDirectory, writeLog } = createWatchFactory(hostWithWatch, options); @@ -871,12 +877,12 @@ namespace ts { getConfigFileParsingDiagnostics(config), config.projectReferences ); - state.lastCachedPackageJsonLookups.set(projectPath, state.moduleResolutionCache && map( - state.moduleResolutionCache.getPackageJsonInfoCache().entries(), - ([path, data]) => ([state.host.realpath ? toPath(state, state.host.realpath(path)) : path, data] as const) - )); - if (state.watch) { + state.lastCachedPackageJsonLookups.set(projectPath, state.moduleResolutionCache && map( + state.moduleResolutionCache.getPackageJsonInfoCache().entries(), + ([path, data]) => ([state.host.realpath && data ? toPath(state, state.host.realpath(path)) : path, data] as const) + )); + state.builderPrograms.set(projectPath, program); } step++; @@ -2002,10 +2008,12 @@ namespace ts { const canReportSummary = state.watch || !!state.host.reportErrorSummary; const { diagnostics } = state; let totalErrors = 0; + let filesInError: (ReportFileInError | undefined)[] = []; if (isCircularBuildOrder(buildOrder)) { reportBuildQueue(state, buildOrder.buildOrder); reportErrors(state, buildOrder.circularDiagnostics); if (canReportSummary) totalErrors += getErrorCountForSummary(buildOrder.circularDiagnostics); + if (canReportSummary) filesInError = [...filesInError, ...getFilesInErrorForSummary(buildOrder.circularDiagnostics)]; } else { // Report errors from the other projects @@ -2016,13 +2024,14 @@ namespace ts { } }); if (canReportSummary) diagnostics.forEach(singleProjectErrors => totalErrors += getErrorCountForSummary(singleProjectErrors)); + if (canReportSummary) diagnostics.forEach(singleProjectErrors => [...filesInError, ...getFilesInErrorForSummary(singleProjectErrors)]); } if (state.watch) { reportWatchStatus(state, getWatchErrorSummaryDiagnosticMessage(totalErrors), totalErrors); } else if (state.host.reportErrorSummary) { - state.host.reportErrorSummary(totalErrors); + state.host.reportErrorSummary(totalErrors, filesInError); } } diff --git a/src/compiler/tsconfig.json b/src/compiler/tsconfig.json index 5f118cf1e47..c5db068d120 100644 --- a/src/compiler/tsconfig.json +++ b/src/compiler/tsconfig.json @@ -63,6 +63,7 @@ "transformers/module/module.ts", "transformers/module/system.ts", "transformers/module/esnextAnd2015.ts", + "transformers/module/node.ts", "transformers/declarations/diagnostics.ts", "transformers/declarations.ts", "transformer.ts", diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 35aac901641..9c4b2cf7f51 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -161,6 +161,7 @@ namespace ts { AbstractKeyword, AsKeyword, AssertsKeyword, + AssertKeyword, AnyKeyword, AsyncKeyword, AwaitKeyword, @@ -341,6 +342,8 @@ namespace ts { DefaultClause, HeritageClause, CatchClause, + AssertClause, + AssertEntry, // Property assignments PropertyAssignment, @@ -544,6 +547,7 @@ namespace ts { | SyntaxKind.AnyKeyword | SyntaxKind.AsKeyword | SyntaxKind.AssertsKeyword + | SyntaxKind.AssertKeyword | SyntaxKind.AsyncKeyword | SyntaxKind.AwaitKeyword | SyntaxKind.BigIntKeyword @@ -925,6 +929,8 @@ namespace ts { | JSDocFunctionType | ExportDeclaration | NamedTupleMember + | ExportSpecifier + | CaseClause | EndOfFileToken ; @@ -1039,6 +1045,7 @@ namespace ts { } export type AssertsKeyword = KeywordToken; + export type AssertKeyword = KeywordToken; export type AwaitKeyword = KeywordToken; /** @deprecated Use `AwaitKeyword` instead. */ @@ -1209,7 +1216,8 @@ namespace ts { readonly expression: Expression; } - export interface PrivateIdentifier extends Node { + // Typed as a PrimaryExpression due to its presence in BinaryExpressions (#field in expr) + export interface PrivateIdentifier extends PrimaryExpression { readonly kind: SyntaxKind.PrivateIdentifier; // escaping not strictly necessary // avoids gotchas in transforms and utils @@ -1603,12 +1611,12 @@ namespace ts { export interface TypePredicateNode extends TypeNode { readonly kind: SyntaxKind.TypePredicate; readonly parent: SignatureDeclaration | JSDocTypeExpression; - readonly assertsModifier?: AssertsToken; + readonly assertsModifier?: AssertsKeyword; readonly parameterName: Identifier | ThisTypeNode; readonly type?: TypeNode; } - export interface TypeQueryNode extends TypeNode { + export interface TypeQueryNode extends NodeWithTypeArguments { readonly kind: SyntaxKind.TypeQuery; readonly exprName: EntityName; } @@ -1696,11 +1704,13 @@ namespace ts { export interface MappedTypeNode extends TypeNode, Declaration { readonly kind: SyntaxKind.MappedType; - readonly readonlyToken?: ReadonlyToken | PlusToken | MinusToken; + readonly readonlyToken?: ReadonlyKeyword | PlusToken | MinusToken; readonly typeParameter: TypeParameterDeclaration; readonly nameType?: TypeNode; readonly questionToken?: QuestionToken | PlusToken | MinusToken; readonly type?: TypeNode; + /** Used only to produce grammar errors */ + readonly members?: NodeArray; } export interface LiteralTypeNode extends TypeNode { @@ -2436,9 +2446,8 @@ namespace ts { readonly expression: ImportExpression; } - export interface ExpressionWithTypeArguments extends NodeWithTypeArguments { + export interface ExpressionWithTypeArguments extends MemberExpression, NodeWithTypeArguments { readonly kind: SyntaxKind.ExpressionWithTypeArguments; - readonly parent: HeritageClause | JSDocAugmentsTag | JSDocImplementsTag; readonly expression: LeftHandSideExpression; } @@ -2748,7 +2757,7 @@ namespace ts { export interface ForOfStatement extends IterationStatement { readonly kind: SyntaxKind.ForOfStatement; - readonly awaitModifier?: AwaitKeywordToken; + readonly awaitModifier?: AwaitKeyword; readonly initializer: ForInitializer; readonly expression: Expression; } @@ -2792,7 +2801,7 @@ namespace ts { readonly clauses: NodeArray; } - export interface CaseClause extends Node { + export interface CaseClause extends Node, JSDocContainer { readonly kind: SyntaxKind.CaseClause; readonly parent: CaseBlock; readonly expression: Expression; @@ -3014,6 +3023,7 @@ namespace ts { readonly importClause?: ImportClause; /** If this is not a StringLiteral it will be a grammar error. */ readonly moduleSpecifier: Expression; + readonly assertClause?: AssertClause; } export type NamedImportBindings = @@ -3040,6 +3050,22 @@ namespace ts { readonly namedBindings?: NamedImportBindings; } + export type AssertionKey = Identifier | StringLiteral; + + export interface AssertEntry extends Node { + readonly kind: SyntaxKind.AssertEntry; + readonly parent: AssertClause; + readonly name: AssertionKey; + readonly value: Expression; + } + + export interface AssertClause extends Node { + readonly kind: SyntaxKind.AssertClause; + readonly parent: ImportDeclaration | ExportDeclaration + readonly elements: NodeArray; + readonly multiLine?: boolean; + } + export interface NamespaceImport extends NamedDeclaration { readonly kind: SyntaxKind.NamespaceImport; readonly parent: ImportClause; @@ -3067,6 +3093,7 @@ namespace ts { readonly exportClause?: NamedExportBindings; /** If this is not a StringLiteral it will be a grammar error. */ readonly moduleSpecifier?: Expression; + readonly assertClause?: AssertClause; } export interface NamedImports extends Node { @@ -3088,11 +3115,13 @@ namespace ts { readonly parent: NamedImports; readonly propertyName?: Identifier; // Name preceding "as" keyword (or undefined when "as" is absent) readonly name: Identifier; // Declared name + readonly isTypeOnly: boolean; } - export interface ExportSpecifier extends NamedDeclaration { + export interface ExportSpecifier extends NamedDeclaration, JSDocContainer { readonly kind: SyntaxKind.ExportSpecifier; readonly parent: NamedExports; + readonly isTypeOnly: boolean; readonly propertyName?: Identifier; // Name preceding "as" keyword (or undefined when "as" is absent) readonly name: Identifier; // Declared name } @@ -3113,8 +3142,8 @@ namespace ts { | ImportClause & { readonly isTypeOnly: true, readonly name: Identifier } | ImportEqualsDeclaration & { readonly isTypeOnly: true } | NamespaceImport & { readonly parent: ImportClause & { readonly isTypeOnly: true } } - | ImportSpecifier & { readonly parent: NamedImports & { readonly parent: ImportClause & { readonly isTypeOnly: true } } } - | ExportSpecifier & { readonly parent: NamedExports & { readonly parent: ExportDeclaration & { readonly isTypeOnly: true } } } + | ImportSpecifier & ({ readonly isTypeOnly: true } | { readonly parent: NamedImports & { readonly parent: ImportClause & { readonly isTypeOnly: true } } }) + | ExportSpecifier & ({ readonly isTypeOnly: true } | { readonly parent: NamedExports & { readonly parent: ExportDeclaration & { readonly isTypeOnly: true } } }) ; /** @@ -3130,6 +3159,7 @@ namespace ts { export interface FileReference extends TextRange { fileName: string; + resolutionMode?: SourceFile["impliedNodeFormat"]; } export interface CheckJsDirective extends TextRange { @@ -3560,6 +3590,20 @@ namespace ts { hasNoDefaultLib: boolean; languageVersion: ScriptTarget; + + /** + * When `module` is `Node12` or `NodeNext`, this field controls whether the + * source file in question is an ESNext-output-format file, or a CommonJS-output-format + * module. This is derived by the module resolver as it looks up the file, since + * it is derived from either the file extension of the module, or the containing + * `package.json` context, and affects both checking and emit. + * + * It is _public_ so that (pre)transformers can set this field, + * since it switches the builtin `node` module transform. Generally speaking, if unset, + * the field is treated as though it is `ModuleKind.CommonJS`. + */ + impliedNodeFormat?: ModuleKind.ESNext | ModuleKind.CommonJS; + /* @internal */ scriptKind: ScriptKind; /** @@ -3601,8 +3645,8 @@ namespace ts { // Stores a mapping 'external module reference text' -> 'resolved file name' | undefined // It is used to resolve module names in the checker. // Content of this field should never be used directly - use getResolvedModuleFileName/setResolvedModuleFileName functions instead - /* @internal */ resolvedModules?: ESMap; - /* @internal */ resolvedTypeReferenceDirectiveNames: ESMap; + /* @internal */ resolvedModules?: ModeAwareCache; + /* @internal */ resolvedTypeReferenceDirectiveNames: ModeAwareCache; /* @internal */ imports: readonly StringLiteralLike[]; // Identifier only if `declare global` /* @internal */ moduleAugmentations: readonly (StringLiteral | Identifier)[]; @@ -3669,7 +3713,7 @@ namespace ts { // References and noDefaultLibAre Dts only referencedFiles: readonly FileReference[]; - typeReferenceDirectives: readonly string[] | undefined; + typeReferenceDirectives: readonly FileReference[] | undefined; libReferenceDirectives: readonly FileReference[]; hasNoDefaultLib?: boolean; @@ -3962,7 +4006,7 @@ namespace ts { getRelationCacheSizes(): { assignable: number, identity: number, subtype: number, strictSubtype: number }; /* @internal */ getFileProcessingDiagnostics(): FilePreprocessingDiagnostics[] | undefined; - /* @internal */ getResolvedTypeReferenceDirectives(): ESMap; + /* @internal */ getResolvedTypeReferenceDirectives(): ModeAwareCache; isSourceFileFromExternalLibrary(file: SourceFile): boolean; isSourceFileDefaultLibrary(file: SourceFile): boolean; @@ -3984,7 +4028,7 @@ namespace ts { /* @internal */ getFileIncludeReasons(): MultiMap; /* @internal */ useCaseSensitiveFileNames(): boolean; - /* @internal */ getResolvedModuleWithFailedLookupLocationsFromCache(moduleName: string, containingFile: string): ResolvedModuleWithFailedLookupLocations | undefined; + /* @internal */ getResolvedModuleWithFailedLookupLocationsFromCache(moduleName: string, containingFile: string, mode?: ModuleKind.CommonJS | ModuleKind.ESNext): ResolvedModuleWithFailedLookupLocations | undefined; getProjectReferences(): readonly ProjectReference[] | undefined; getResolvedProjectReferences(): readonly (ResolvedProjectReference | undefined)[] | undefined; @@ -4102,7 +4146,7 @@ namespace ts { getSourceFiles(): readonly SourceFile[]; getSourceFile(fileName: string): SourceFile | undefined; - getResolvedTypeReferenceDirectives(): ReadonlyESMap; + getResolvedTypeReferenceDirectives(): ModeAwareCache; getProjectReferenceRedirect(fileName: string): string | undefined; isSourceOfProjectReferenceRedirect(fileName: string): boolean; @@ -4111,6 +4155,7 @@ namespace ts { export interface TypeChecker { getTypeOfSymbolAtLocation(symbol: Symbol, node: Node): Type; + /* @internal */ getTypeOfSymbol(symbol: Symbol): Type; getDeclaredTypeOfSymbol(symbol: Symbol): Type; getPropertiesOfType(type: Type): Symbol[]; getPropertyOfType(type: Type, propertyName: string): Symbol | undefined; @@ -4120,6 +4165,7 @@ namespace ts { getIndexInfosOfType(type: Type): readonly IndexInfo[]; getSignaturesOfType(type: Type, kind: SignatureKind): readonly Signature[]; getIndexTypeOfType(type: Type, kind: IndexKind): Type | undefined; + /* @internal */ getIndexType(type: Type): Type; getBaseTypes(type: InterfaceType): BaseType[]; getBaseTypeOfLiteralType(type: Type): Type; getWidenedType(type: Type): Type; @@ -4371,6 +4417,15 @@ namespace ts { /* @internal */ getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol: Symbol): readonly TypeParameter[] | undefined; /* @internal */ isDeclarationVisible(node: Declaration | AnyImportSyntax): boolean; /* @internal */ isPropertyAccessible(node: Node, isSuper: boolean, isWrite: boolean, containingType: Type, property: Symbol): boolean; + /* @internal */ getTypeOnlyAliasDeclaration(symbol: Symbol): TypeOnlyAliasDeclaration | undefined; + /* @internal */ getMemberOverrideModifierStatus(node: ClassLikeDeclaration, member: ClassElement): MemberOverrideStatus; + } + + /* @internal */ + export const enum MemberOverrideStatus { + Ok, + NeedsOverride, + HasInvalidOverride } /* @internal */ @@ -4586,7 +4641,7 @@ namespace ts { export type AnyImportSyntax = ImportDeclaration | ImportEqualsDeclaration; /* @internal */ - export type AnyImportOrRequire = AnyImportSyntax | RequireVariableDeclaration; + export type AnyImportOrRequire = AnyImportSyntax | VariableDeclarationInitializedTo; /* @internal */ export type AnyImportOrRequireStatement = AnyImportSyntax | RequireVariableStatement; @@ -4611,8 +4666,8 @@ namespace ts { export type RequireOrImportCall = CallExpression & { expression: Identifier, arguments: [StringLiteralLike] }; /* @internal */ - export interface RequireVariableDeclaration extends VariableDeclaration { - readonly initializer: RequireOrImportCall; + export interface VariableDeclarationInitializedTo extends VariableDeclaration { + readonly initializer: T; } /* @internal */ @@ -4622,7 +4677,7 @@ namespace ts { /* @internal */ export interface RequireVariableDeclarationList extends VariableDeclarationList { - readonly declarations: NodeArray; + readonly declarations: NodeArray>; } /* @internal */ @@ -4734,8 +4789,8 @@ namespace ts { moduleExportsSomeValue(moduleReferenceExpression: Expression): boolean; isArgumentsLocalBinding(node: Identifier): boolean; getExternalModuleFileFromDeclaration(declaration: ImportEqualsDeclaration | ImportDeclaration | ExportDeclaration | ModuleDeclaration | ImportTypeNode | ImportCall): SourceFile | undefined; - getTypeReferenceDirectivesForEntityName(name: EntityNameOrEntityNameExpression): string[] | undefined; - getTypeReferenceDirectivesForSymbol(symbol: Symbol, meaning?: SymbolFlags): string[] | undefined; + getTypeReferenceDirectivesForEntityName(name: EntityNameOrEntityNameExpression): [specifier: string, mode: SourceFile["impliedNodeFormat"] | undefined][] | undefined; + getTypeReferenceDirectivesForSymbol(symbol: Symbol, meaning?: SymbolFlags): [specifier: string, mode: SourceFile["impliedNodeFormat"] | undefined][] | undefined; isLiteralConstDeclaration(node: VariableDeclaration | PropertyDeclaration | PropertySignature | ParameterDeclaration): boolean; getJsxFactoryEntity(location?: Node): EntityName | undefined; getJsxFragmentFactoryEntity(location?: Node): EntityName | undefined; @@ -4898,9 +4953,10 @@ namespace ts { extendedContainersByFile?: ESMap; // Containers (other than the parent) which this symbol is aliased in variances?: VarianceFlags[]; // Alias symbol type argument variance cache deferralConstituents?: Type[]; // Calculated list of constituents for a deferred type + deferralWriteConstituents?: Type[]; // Constituents of a deferred `writeType` deferralParent?: Type; // Source union/intersection of a deferred type cjsExportMerged?: Symbol; // Version of the symbol with all non export= exports merged with the export= target - typeOnlyDeclaration?: TypeOnlyCompatibleAliasDeclaration | false; // First resolved alias declaration that makes the symbol only usable in type constructs + typeOnlyDeclaration?: TypeOnlyAliasDeclaration | false; // First resolved alias declaration that makes the symbol only usable in type constructs isConstructorDeclaredProperty?: boolean; // Property declared through 'this.x = ...' assignment in constructor tupleLabelDeclaration?: NamedTupleMember | ParameterDeclaration; // Declaration associated with the tuple's label accessibleChainCache?: ESMap; @@ -4934,6 +4990,7 @@ namespace ts { HasNeverType = 1 << 17, // Synthetic property with at least one never type in constituents Mapped = 1 << 18, // Property of mapped type StripOptional = 1 << 19, // Strip optionality in mapped property + Unresolved = 1 << 20, // Unresolved type alias symbol Synthetic = SyntheticProperty | SyntheticMethod, Discriminant = HasNonUniformType | HasLiteralType, Partial = ReadPartial | WritePartial @@ -5031,6 +5088,7 @@ namespace ts { ConstructorReferenceInClass = 0x02000000, // Binding to a class constructor inside of the class's body. ContainsClassWithPrivateIdentifiers = 0x04000000, // Marked on all block-scoped containers containing a class with private identifiers. ContainsSuperPropertyInStaticInitializer = 0x08000000, // Marked on all block-scoped containers containing a static initializer with 'super.x' or 'super[x]'. + InCheckIdentifier = 0x10000000, } /* @internal */ @@ -5054,7 +5112,7 @@ namespace ts { jsxNamespace?: Symbol | false; // Resolved jsx namespace symbol for this node jsxImplicitImportContainer?: Symbol | false; // Resolved module symbol the implicit jsx import of this file should refer to contextFreeType?: Type; // Cached context-free type used by the first pass of inference; used when a function's return is partially contextually sensitive - deferredNodes?: ESMap; // Set of nodes whose checking has been deferred + deferredNodes?: Set; // Set of nodes whose checking has been deferred capturedBlockScopeBindings?: Symbol[]; // Block-scoped bindings captured beneath this part of an IterationStatement outerTypeParameters?: TypeParameter[]; // Outer type parameters of anonymous object type isExhaustive?: boolean; // Is node an exhaustive switch statement @@ -5118,6 +5176,8 @@ namespace ts { ESSymbolLike = ESSymbol | UniqueESSymbol, VoidLike = Void | Undefined, /* @internal */ + DefinitelyNonNullable = StringLike | NumberLike | BigIntLike | BooleanLike | EnumLike | ESSymbolLike | Object | NonPrimitive, + /* @internal */ DisjointDomains = NonPrimitive | StringLike | NumberLike | BigIntLike | BooleanLike | ESSymbolLike | VoidLike | Null, UnionOrIntersection = Union | Intersection, StructuredType = Object | Union | Intersection, @@ -5135,20 +5195,22 @@ namespace ts { // 'Narrowable' types are types where narrowing actually narrows. // This *should* be every type other than null, undefined, void, and never Narrowable = Any | Unknown | StructuredOrInstantiable | StringLike | NumberLike | BigIntLike | BooleanLike | ESSymbol | UniqueESSymbol | NonPrimitive, - /* @internal */ - NotPrimitiveUnion = Any | Unknown | Enum | Void | Never | Object | Intersection | Instantiable, // The following flags are aggregated during union and intersection type construction /* @internal */ IncludesMask = Any | Unknown | Primitive | Never | Object | Union | Intersection | NonPrimitive | TemplateLiteral, // The following flags are used for different purposes during union and intersection type construction /* @internal */ - IncludesStructuredOrInstantiable = TypeParameter, + IncludesMissingType = TypeParameter, /* @internal */ IncludesNonWideningType = Index, /* @internal */ IncludesWildcard = IndexedAccess, /* @internal */ IncludesEmptyObject = Conditional, + /* @internal */ + IncludesInstantiable = Substitution, + /* @internal */ + NotPrimitiveUnion = Any | Unknown | Enum | Void | Never | Object | Intersection | IncludesInstantiable, } export type DestructuringPattern = BindingPattern | ObjectLiteralExpression | ArrayLiteralExpression; @@ -5272,13 +5334,14 @@ namespace ts { // Flags that require TypeFlags.Object ContainsSpread = 1 << 22, // Object literal contains spread operation ObjectRestType = 1 << 23, // Originates in object rest declaration + InstantiationExpressionType = 1 << 24, // Originates in instantiation expression /* @internal */ - IsClassInstanceClone = 1 << 24, // Type is a clone of a class instance type + IsClassInstanceClone = 1 << 25, // Type is a clone of a class instance type // Flags that require TypeFlags.Object and ObjectFlags.Reference /* @internal */ - IdenticalBaseTypeCalculated = 1 << 25, // has had `getSingleBaseForNonAugmentingSubtype` invoked on it already + IdenticalBaseTypeCalculated = 1 << 26, // has had `getSingleBaseForNonAugmentingSubtype` invoked on it already /* @internal */ - IdenticalBaseTypeExists = 1 << 26, // has a defined cachedEquivalentBaseType member + IdenticalBaseTypeExists = 1 << 27, // has a defined cachedEquivalentBaseType member // Flags that require TypeFlags.UnionOrIntersection or TypeFlags.Substitution /* @internal */ @@ -5463,6 +5526,11 @@ namespace ts { instantiations?: ESMap; // Instantiations of generic type alias (undefined if non-generic) } + /* @internal */ + export interface InstantiationExpressionType extends AnonymousType { + node: ExpressionWithTypeArguments | TypeQueryNode; + } + /* @internal */ export interface MappedType extends AnonymousType { declaration: MappedTypeNode; @@ -5534,6 +5602,7 @@ namespace ts { /* @internal */ export interface SyntheticDefaultModuleType extends Type { syntheticType?: Type; + defaultOnlyType?: Type; } export interface InstantiableType extends Type { @@ -5615,8 +5684,8 @@ namespace ts { root: ConditionalRoot; checkType: Type; extendsType: Type; - resolvedTrueType: Type; - resolvedFalseType: Type; + resolvedTrueType?: Type; + resolvedFalseType?: Type; /* @internal */ resolvedInferredTrueType?: Type; // The `trueType` instantiated with the `combinedMapper`, if present /* @internal */ @@ -5937,7 +6006,13 @@ namespace ts { export enum ModuleResolutionKind { Classic = 1, - NodeJs = 2 + NodeJs = 2, + // Starting with node12, node's module resolver has significant departures from traditional cjs resolution + // to better support ecmascript modules and their use within node - more features are still being added, so + // we can expect it to change over time, and as such, offer both a `NodeNext` moving resolution target, and a `Node12` + // version-anchored resolution target + Node12 = 3, + NodeNext = 99, // Not simply `Node12` so that compiled code linked against TS can use the `Next` value reliably (same as with `ModuleKind`) } export interface PluginImport { @@ -6091,7 +6166,7 @@ namespace ts { suppressExcessPropertyErrors?: boolean; suppressImplicitAnyIndexErrors?: boolean; /* @internal */ suppressOutputPathCheck?: boolean; - target?: ScriptTarget; // TODO: GH#18217 frequently asserted as defined + target?: ScriptTarget; traceResolution?: boolean; useUnknownInCatchVariables?: boolean; resolveJsonModule?: boolean; @@ -6143,7 +6218,12 @@ namespace ts { // module kind). ES2015 = 5, ES2020 = 6, - ESNext = 99 + ES2022 = 7, + ESNext = 99, + + // Node12+ is an amalgam of commonjs (albeit updated) and es2020+, and represents a distinct module system from es2020/esnext + Node12 = 100, + NodeNext = 199, } export const enum JsxEmit { @@ -6200,6 +6280,7 @@ namespace ts { ES2019 = 6, ES2020 = 7, ES2021 = 8, + ES2022 = 9, ESNext = 99, JSON = 100, Latest = ESNext, @@ -6266,7 +6347,7 @@ namespace ts { isFilePath?: boolean; // True if option value is a path or fileName shortName?: string; // A short mnemonic for convenience - for instance, 'h' can be used in place of 'help' description?: DiagnosticMessage; // The message describing what the command line switch does. - defaultValueDescription?: string | DiagnosticMessage; // The message describing what the dafault value is. string type is prepared for fixed chosen like "false" which do not need I18n. + defaultValueDescription?: string | number | boolean | DiagnosticMessage; // The message describing what the dafault value is. string type is prepared for fixed chosen like "false" which do not need I18n. paramType?: DiagnosticMessage; // The name to be used for a non-boolean option's parameter isTSConfigOnly?: boolean; // True if option can only be specified via tsconfig.json file isCommandLineOnly?: boolean; @@ -6284,13 +6365,27 @@ namespace ts { } /* @internal */ - export interface CommandLineOptionOfPrimitiveType extends CommandLineOptionBase { - type: "string" | "number" | "boolean"; + export interface CommandLineOptionOfStringType extends CommandLineOptionBase { + type: "string"; + defaultValueDescription?: string | undefined | DiagnosticMessage; + } + + /* @internal */ + export interface CommandLineOptionOfNumberType extends CommandLineOptionBase { + type: "number"; + defaultValueDescription: number | undefined | DiagnosticMessage; + } + + /* @internal */ + export interface CommandLineOptionOfBooleanType extends CommandLineOptionBase { + type: "boolean"; + defaultValueDescription: boolean | undefined | DiagnosticMessage; } /* @internal */ export interface CommandLineOptionOfCustomType extends CommandLineOptionBase { type: ESMap; // an object literal mapping named values to actual values + defaultValueDescription: number | string | undefined | DiagnosticMessage; } /* @internal */ @@ -6317,11 +6412,11 @@ namespace ts { /* @internal */ export interface CommandLineOptionOfListType extends CommandLineOptionBase { type: "list"; - element: CommandLineOptionOfCustomType | CommandLineOptionOfPrimitiveType | TsConfigOnlyOption; + element: CommandLineOptionOfCustomType | CommandLineOptionOfStringType | CommandLineOptionOfNumberType | CommandLineOptionOfBooleanType | TsConfigOnlyOption; } /* @internal */ - export type CommandLineOption = CommandLineOptionOfCustomType | CommandLineOptionOfPrimitiveType | TsConfigOnlyOption | CommandLineOptionOfListType; + export type CommandLineOption = CommandLineOptionOfCustomType | CommandLineOptionOfStringType | CommandLineOptionOfNumberType | CommandLineOptionOfBooleanType | TsConfigOnlyOption | CommandLineOptionOfListType; /* @internal */ export const enum CharacterCodes { @@ -6539,7 +6634,13 @@ namespace ts { Js = ".js", Jsx = ".jsx", Json = ".json", - TsBuildInfo = ".tsbuildinfo" + TsBuildInfo = ".tsbuildinfo", + Mjs = ".mjs", + Mts = ".mts", + Dmts = ".d.mts", + Cjs = ".cjs", + Cts = ".cts", + Dcts = ".d.cts", } export interface ResolvedModuleWithFailedLookupLocations { @@ -6594,11 +6695,15 @@ namespace ts { * If resolveModuleNames is implemented then implementation for members from ModuleResolutionHost can be just * 'throw new Error("NotImplemented")' */ - resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions): (ResolvedModule | undefined)[]; + resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions, containingSourceFile?: SourceFile): (ResolvedModule | undefined)[]; + /** + * Returns the module resolution cache used by a provided `resolveModuleNames` implementation so that any non-name module resolution operations (eg, package.json lookup) can reuse it + */ + getModuleResolutionCache?(): ModuleResolutionCache | undefined; /** * This method is a companion for 'resolveModuleNames' and is used to resolve 'types' references to actual type declaration files */ - resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions): (ResolvedTypeReferenceDirective | undefined)[]; + resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[] | readonly FileReference[], containingFile: string, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions, containingFileMode?: SourceFile["impliedNodeFormat"] | undefined): (ResolvedTypeReferenceDirective | undefined)[]; getEnvironmentVariable?(name: string): string | undefined; /* @internal */ onReleaseOldSourceFile?(oldSourceFile: SourceFile, oldOptions: CompilerOptions, hasSourceFileByPath: boolean): void; /* @internal */ onReleaseParsedCommandLine?(configFileName: string, oldResolvedRef: ResolvedProjectReference | undefined, optionOptions: CompilerOptions): void; @@ -6635,15 +6740,16 @@ namespace ts { ContainsTypeScript = 1 << 0, ContainsJsx = 1 << 1, ContainsESNext = 1 << 2, - ContainsES2021 = 1 << 3, - ContainsES2020 = 1 << 4, - ContainsES2019 = 1 << 5, - ContainsES2018 = 1 << 6, - ContainsES2017 = 1 << 7, - ContainsES2016 = 1 << 8, - ContainsES2015 = 1 << 9, - ContainsGenerator = 1 << 10, - ContainsDestructuringAssignment = 1 << 11, + ContainsES2022 = 1 << 3, + ContainsES2021 = 1 << 4, + ContainsES2020 = 1 << 5, + ContainsES2019 = 1 << 6, + ContainsES2018 = 1 << 7, + ContainsES2017 = 1 << 8, + ContainsES2016 = 1 << 9, + ContainsES2015 = 1 << 10, + ContainsGenerator = 1 << 11, + ContainsDestructuringAssignment = 1 << 12, // Markers // - Flags used to indicate that a subtree contains a specific transformation. @@ -6672,6 +6778,7 @@ namespace ts { AssertTypeScript = ContainsTypeScript, AssertJsx = ContainsJsx, AssertESNext = ContainsESNext, + AssertES2022 = ContainsES2022, AssertES2021 = ContainsES2021, AssertES2020 = ContainsES2020, AssertES2019 = ContainsES2019, @@ -6737,6 +6844,31 @@ namespace ts { externalHelpers?: boolean; helpers?: EmitHelper[]; // Emit helpers for the node startsOnNewLine?: boolean; // If the node should begin on a new line + snippetElement?: SnippetElement; // Snippet element of the node + } + + /* @internal */ + export type SnippetElement = TabStop | Placeholder; + + /* @internal */ + export interface TabStop { + kind: SnippetKind.TabStop; + order: number; + } + + /* @internal */ + export interface Placeholder { + kind: SnippetKind.Placeholder; + order: number; + } + + // Reference: https://code.visualstudio.com/docs/editor/userdefinedsnippets#_snippet-syntax + /* @internal */ + export const enum SnippetKind { + TabStop, // `$1`, `$2` + Placeholder, // `${1:foo}` + Choice, // `${1|one,two,three|}` + Variable, // `$name`, `${name:default}` } export const enum EmitFlags { @@ -6829,7 +6961,8 @@ namespace ts { MakeTemplateObject = 1 << 18, // __makeTemplateObject (used for constructing template string array objects) ClassPrivateFieldGet = 1 << 19, // __classPrivateFieldGet (used by the class private field transformation) ClassPrivateFieldSet = 1 << 20, // __classPrivateFieldSet (used by the class private field transformation) - CreateBinding = 1 << 21, // __createBinding (use by the module transform for (re)exports and namespace imports) + ClassPrivateFieldIn = 1 << 21, // __classPrivateFieldIn (used by the class private field transformation) + CreateBinding = 1 << 22, // __createBinding (use by the module transform for (re)exports and namespace imports) FirstEmitHelper = Extends, LastEmitHelper = CreateBinding, @@ -7047,7 +7180,7 @@ namespace ts { // createModifier(kind: T): ModifierToken; - createModifiersFromModifierFlags(flags: ModifierFlags): Modifier[]; + createModifiersFromModifierFlags(flags: ModifierFlags): Modifier[] | undefined; // // Names @@ -7116,8 +7249,8 @@ namespace ts { updateConstructorTypeNode(node: ConstructorTypeNode, modifiers: readonly Modifier[] | undefined, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode): ConstructorTypeNode; /** @deprecated */ updateConstructorTypeNode(node: ConstructorTypeNode, typeParameters: NodeArray | undefined, parameters: NodeArray, type: TypeNode): ConstructorTypeNode; - createTypeQueryNode(exprName: EntityName): TypeQueryNode; - updateTypeQueryNode(node: TypeQueryNode, exprName: EntityName): TypeQueryNode; + createTypeQueryNode(exprName: EntityName, typeArguments?: readonly TypeNode[]): TypeQueryNode; + updateTypeQueryNode(node: TypeQueryNode, exprName: EntityName, typeArguments?: readonly TypeNode[]): TypeQueryNode; createTypeLiteralNode(members: readonly TypeElement[] | undefined): TypeLiteralNode; updateTypeLiteralNode(node: TypeLiteralNode, members: NodeArray): TypeLiteralNode; createArrayTypeNode(elementType: TypeNode): ArrayTypeNode; @@ -7147,8 +7280,8 @@ namespace ts { updateTypeOperatorNode(node: TypeOperatorNode, type: TypeNode): TypeOperatorNode; createIndexedAccessTypeNode(objectType: TypeNode, indexType: TypeNode): IndexedAccessTypeNode; updateIndexedAccessTypeNode(node: IndexedAccessTypeNode, objectType: TypeNode, indexType: TypeNode): IndexedAccessTypeNode; - createMappedTypeNode(readonlyToken: ReadonlyKeyword | PlusToken | MinusToken | undefined, typeParameter: TypeParameterDeclaration, nameType: TypeNode | undefined, questionToken: QuestionToken | PlusToken | MinusToken | undefined, type: TypeNode | undefined): MappedTypeNode; - updateMappedTypeNode(node: MappedTypeNode, readonlyToken: ReadonlyKeyword | PlusToken | MinusToken | undefined, typeParameter: TypeParameterDeclaration, nameType: TypeNode | undefined, questionToken: QuestionToken | PlusToken | MinusToken | undefined, type: TypeNode | undefined): MappedTypeNode; + createMappedTypeNode(readonlyToken: ReadonlyKeyword | PlusToken | MinusToken | undefined, typeParameter: TypeParameterDeclaration, nameType: TypeNode | undefined, questionToken: QuestionToken | PlusToken | MinusToken | undefined, type: TypeNode | undefined, members: NodeArray | undefined): MappedTypeNode; + updateMappedTypeNode(node: MappedTypeNode, readonlyToken: ReadonlyKeyword | PlusToken | MinusToken | undefined, typeParameter: TypeParameterDeclaration, nameType: TypeNode | undefined, questionToken: QuestionToken | PlusToken | MinusToken | undefined, type: TypeNode | undefined, members: NodeArray | undefined): MappedTypeNode; createLiteralTypeNode(literal: LiteralTypeNode["literal"]): LiteralTypeNode; updateLiteralTypeNode(node: LiteralTypeNode, literal: LiteralTypeNode["literal"]): LiteralTypeNode; createTemplateLiteralType(head: TemplateHead, templateSpans: readonly TemplateLiteralTypeSpan[]): TemplateLiteralTypeNode; @@ -7317,26 +7450,30 @@ namespace ts { updateNamespaceExportDeclaration(node: NamespaceExportDeclaration, name: Identifier): NamespaceExportDeclaration; createImportEqualsDeclaration(decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, isTypeOnly: boolean, name: string | Identifier, moduleReference: ModuleReference): ImportEqualsDeclaration; updateImportEqualsDeclaration(node: ImportEqualsDeclaration, decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, isTypeOnly: boolean, name: Identifier, moduleReference: ModuleReference): ImportEqualsDeclaration; - createImportDeclaration(decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, importClause: ImportClause | undefined, moduleSpecifier: Expression): ImportDeclaration; - updateImportDeclaration(node: ImportDeclaration, decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, importClause: ImportClause | undefined, moduleSpecifier: Expression): ImportDeclaration; + createImportDeclaration(decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, importClause: ImportClause | undefined, moduleSpecifier: Expression, assertClause?: AssertClause): ImportDeclaration; + updateImportDeclaration(node: ImportDeclaration, decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, importClause: ImportClause | undefined, moduleSpecifier: Expression, assertClause: AssertClause | undefined): ImportDeclaration; createImportClause(isTypeOnly: boolean, name: Identifier | undefined, namedBindings: NamedImportBindings | undefined): ImportClause; updateImportClause(node: ImportClause, isTypeOnly: boolean, name: Identifier | undefined, namedBindings: NamedImportBindings | undefined): ImportClause; + createAssertClause(elements: NodeArray, multiLine?: boolean): AssertClause; + updateAssertClause(node: AssertClause, elements: NodeArray, multiLine?: boolean): AssertClause; + createAssertEntry(name: AssertionKey, value: Expression): AssertEntry; + updateAssertEntry(node: AssertEntry, name: AssertionKey, value: Expression): AssertEntry; createNamespaceImport(name: Identifier): NamespaceImport; updateNamespaceImport(node: NamespaceImport, name: Identifier): NamespaceImport; createNamespaceExport(name: Identifier): NamespaceExport; updateNamespaceExport(node: NamespaceExport, name: Identifier): NamespaceExport; createNamedImports(elements: readonly ImportSpecifier[]): NamedImports; updateNamedImports(node: NamedImports, elements: readonly ImportSpecifier[]): NamedImports; - createImportSpecifier(propertyName: Identifier | undefined, name: Identifier): ImportSpecifier; - updateImportSpecifier(node: ImportSpecifier, propertyName: Identifier | undefined, name: Identifier): ImportSpecifier; + createImportSpecifier(isTypeOnly: boolean, propertyName: Identifier | undefined, name: Identifier): ImportSpecifier; + updateImportSpecifier(node: ImportSpecifier, isTypeOnly: boolean, propertyName: Identifier | undefined, name: Identifier): ImportSpecifier; createExportAssignment(decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, isExportEquals: boolean | undefined, expression: Expression): ExportAssignment; updateExportAssignment(node: ExportAssignment, decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, expression: Expression): ExportAssignment; - createExportDeclaration(decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, isTypeOnly: boolean, exportClause: NamedExportBindings | undefined, moduleSpecifier?: Expression): ExportDeclaration; - updateExportDeclaration(node: ExportDeclaration, decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, isTypeOnly: boolean, exportClause: NamedExportBindings | undefined, moduleSpecifier: Expression | undefined): ExportDeclaration; + createExportDeclaration(decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, isTypeOnly: boolean, exportClause: NamedExportBindings | undefined, moduleSpecifier?: Expression, assertClause?: AssertClause): ExportDeclaration; + updateExportDeclaration(node: ExportDeclaration, decorators: readonly Decorator[] | undefined, modifiers: readonly Modifier[] | undefined, isTypeOnly: boolean, exportClause: NamedExportBindings | undefined, moduleSpecifier: Expression | undefined, assertClause: AssertClause | undefined): ExportDeclaration; createNamedExports(elements: readonly ExportSpecifier[]): NamedExports; updateNamedExports(node: NamedExports, elements: readonly ExportSpecifier[]): NamedExports; - createExportSpecifier(propertyName: string | Identifier | undefined, name: string | Identifier): ExportSpecifier; - updateExportSpecifier(node: ExportSpecifier, propertyName: Identifier | undefined, name: Identifier): ExportSpecifier; + createExportSpecifier(isTypeOnly: boolean, propertyName: string | Identifier | undefined, name: string | Identifier): ExportSpecifier; + updateExportSpecifier(node: ExportSpecifier, isTypeOnly: boolean, propertyName: Identifier | undefined, name: Identifier): ExportSpecifier; /* @internal*/ createMissingDeclaration(): MissingDeclaration; // @@ -7686,9 +7823,10 @@ namespace ts { * Copies only the standard (string-expression) prologue-directives into the target statement-array. * @param source origin statements array * @param target result statements array + * @param statementOffset The offset at which to begin the copy. * @param ensureUseStrict boolean determining whether the function need to add prologue-directives */ - /* @internal */ copyStandardPrologue(source: readonly Statement[], target: Push, ensureUseStrict?: boolean): number; + /* @internal */ copyStandardPrologue(source: readonly Statement[], target: Push, statementOffset: number | undefined, ensureUseStrict?: boolean): number; /** * Copies only the custom prologue-directives into target statement-array. * @param source origin statements array @@ -7715,7 +7853,7 @@ namespace ts { * - *DO NOT USE THIS* if a more appropriate function is available. */ /* @internal */ cloneNode(node: T): T; - /* @internal */ updateModifiers(node: T, modifiers: readonly Modifier[] | ModifierFlags): T; + /* @internal */ updateModifiers(node: T, modifiers: readonly Modifier[] | ModifierFlags | undefined): T; } /* @internal */ @@ -7919,6 +8057,8 @@ namespace ts { NoDefaultLib = "no-default-lib", Reference = "reference", Type = "type", + TypeResolutionModeRequire = "type-require", + TypeResolutionModeImport = "type-import", Lib = "lib", Prepend = "prepend", Text = "text", @@ -7951,7 +8091,7 @@ namespace ts { /*@internal*/ export interface BundleFileReference extends BundleFileSectionBase { - kind: BundleFileSectionKind.Reference | BundleFileSectionKind.Type | BundleFileSectionKind.Lib; + kind: BundleFileSectionKind.Reference | BundleFileSectionKind.Type | BundleFileSectionKind.Lib | BundleFileSectionKind.TypeResolutionModeImport | BundleFileSectionKind.TypeResolutionModeRequire; data: string; } @@ -8194,6 +8334,7 @@ namespace ts { hasTrailingComment(): boolean; hasTrailingWhitespace(): boolean; getTextPosWithWriteLine?(): number; + nonEscapingWrite?(text: string): void; } export interface GetEffectiveTypeRootsHost { @@ -8201,6 +8342,11 @@ namespace ts { getCurrentDirectory?(): string; } + /* @internal */ + export interface HasCurrentDirectory { + getCurrentDirectory(): string; + } + /*@internal*/ export interface ModuleSpecifierResolutionHost { useCaseSensitiveFileNames?(): boolean; @@ -8211,6 +8357,7 @@ namespace ts { realpath?(path: string): string; getSymlinkCache?(): SymlinkCache; getModuleSpecifierCache?(): ModuleSpecifierCache; + getPackageJsonInfoCache?(): PackageJsonInfoCache | undefined; getGlobalTypingsCacheLocation?(): string | undefined; getNearestAncestorDirectoryWithPackageJson?(fileName: string, rootDir?: string): string | undefined; @@ -8354,6 +8501,7 @@ namespace ts { ObjectBindingPatternElements = SingleLine | AllowTrailingComma | SpaceBetweenBraces | CommaDelimited | SpaceBetweenSiblings | NoSpaceIfEmpty, ArrayBindingPatternElements = SingleLine | AllowTrailingComma | CommaDelimited | SpaceBetweenSiblings | NoSpaceIfEmpty, ObjectLiteralExpressionProperties = PreserveLines | CommaDelimited | SpaceBetweenSiblings | SpaceBetweenBraces | Indented | Braces | NoSpaceIfEmpty, + ImportClauseEntries = PreserveLines | CommaDelimited | SpaceBetweenSiblings | SpaceBetweenBraces | Indented | Braces | NoSpaceIfEmpty, ArrayLiteralExpressionElements = PreserveLines | CommaDelimited | SpaceBetweenSiblings | AllowTrailingComma | Indented | SquareBrackets, CommaListElements = CommaDelimited | SpaceBetweenSiblings | SingleLine, CallExpressionArguments = CommaDelimited | SpaceBetweenSiblings | SingleLine | Parenthesis, @@ -8434,7 +8582,8 @@ namespace ts { { name: "types", optional: true, captureSpan: true }, { name: "lib", optional: true, captureSpan: true }, { name: "path", optional: true, captureSpan: true }, - { name: "no-default-lib", optional: true } + { name: "no-default-lib", optional: true }, + { name: "resolution-mode", optional: true } ], kind: PragmaKindFlags.TripleSlashXML }, @@ -8538,6 +8687,7 @@ namespace ts { readonly includeCompletionsWithSnippetText?: boolean; readonly includeAutomaticOptionalChainCompletions?: boolean; readonly includeCompletionsWithInsertText?: boolean; + readonly includeCompletionsWithClassMemberSnippets?: boolean; readonly allowIncompleteCompletions?: boolean; readonly importModuleSpecifierPreference?: "shortest" | "project-relative" | "relative" | "non-relative"; /** Determines whether we import `foo/index.ts` as "foo", "foo/index", or "foo/index.js" */ @@ -8546,6 +8696,14 @@ namespace ts { readonly providePrefixAndSuffixTextForRename?: boolean; readonly includePackageJsonAutoImports?: "auto" | "on" | "off"; readonly provideRefactorNotApplicableReason?: boolean; + readonly jsxAttributeCompletionStyle?: "auto" | "braces" | "none"; + readonly includeInlayParameterNameHints?: "none" | "literals" | "all"; + readonly includeInlayParameterNameHintsWhenArgumentMatchesName?: boolean; + readonly includeInlayFunctionParameterTypeHints?: boolean, + readonly includeInlayVariableTypeHints?: boolean; + readonly includeInlayPropertyDeclarationTypeHints?: boolean; + readonly includeInlayFunctionLikeReturnTypeHints?: boolean; + readonly includeInlayEnumMemberValueHints?: boolean; } /** Represents a bigint literal value without requiring bigint support */ diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 64f822a8422..651b29d0dc9 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -20,19 +20,8 @@ namespace ts { return undefined; } - /** - * Create a new escaped identifier map. - * @deprecated Use `new Map<__String, T>()` instead. - */ - export function createUnderscoreEscapedMap(): UnderscoreEscapedMap { - return new Map<__String, T>(); - } - - /** - * @deprecated Use `!!map?.size` instead - */ - export function hasEntries(map: ReadonlyCollection | undefined): map is ReadonlyCollection { - return !!map && !!map.size; + export function getDeclarationsOfKind(symbol: Symbol, kind: T["kind"]): T[] { + return filter(symbol.declarations || emptyArray, d => d.kind === kind) as T[]; } export function createSymbolTable(symbols?: readonly Symbol[]): SymbolTable { @@ -169,24 +158,24 @@ namespace ts { return node.end - node.pos; } - export function getResolvedModule(sourceFile: SourceFile | undefined, moduleNameText: string): ResolvedModuleFull | undefined { - return sourceFile && sourceFile.resolvedModules && sourceFile.resolvedModules.get(moduleNameText); + export function getResolvedModule(sourceFile: SourceFile | undefined, moduleNameText: string, mode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined): ResolvedModuleFull | undefined { + return sourceFile && sourceFile.resolvedModules && sourceFile.resolvedModules.get(moduleNameText, mode); } - export function setResolvedModule(sourceFile: SourceFile, moduleNameText: string, resolvedModule: ResolvedModuleFull): void { + export function setResolvedModule(sourceFile: SourceFile, moduleNameText: string, resolvedModule: ResolvedModuleFull, mode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined): void { if (!sourceFile.resolvedModules) { - sourceFile.resolvedModules = new Map(); + sourceFile.resolvedModules = createModeAwareCache(); } - sourceFile.resolvedModules.set(moduleNameText, resolvedModule); + sourceFile.resolvedModules.set(moduleNameText, mode, resolvedModule); } export function setResolvedTypeReferenceDirective(sourceFile: SourceFile, typeReferenceDirectiveName: string, resolvedTypeReferenceDirective?: ResolvedTypeReferenceDirective): void { if (!sourceFile.resolvedTypeReferenceDirectiveNames) { - sourceFile.resolvedTypeReferenceDirectiveNames = new Map(); + sourceFile.resolvedTypeReferenceDirectiveNames = createModeAwareCache(); } - sourceFile.resolvedTypeReferenceDirectiveNames.set(typeReferenceDirectiveName, resolvedTypeReferenceDirective); + sourceFile.resolvedTypeReferenceDirectiveNames.set(typeReferenceDirectiveName, /*mode*/ undefined, resolvedTypeReferenceDirective); } export function projectReferenceIsEqualTo(oldRef: ProjectReference, newRef: ProjectReference) { @@ -207,9 +196,12 @@ namespace ts { return a === b || !!a && !!b && a.name === b.name && a.subModuleName === b.subModuleName && a.version === b.version; } - export function packageIdToString({ name, subModuleName, version }: PackageId): string { - const fullName = subModuleName ? `${name}/${subModuleName}` : name; - return `${fullName}@${version}`; + export function packageIdToPackageName({ name, subModuleName }: PackageId): string { + return subModuleName ? `${name}/${subModuleName}` : name; + } + + export function packageIdToString(packageId: PackageId): string { + return `${packageIdToPackageName(packageId)}@${packageId.version}`; } export function typeDirectiveIsEqualTo(oldResolution: ResolvedTypeReferenceDirective, newResolution: ResolvedTypeReferenceDirective): boolean { @@ -219,15 +211,20 @@ namespace ts { } export function hasChangesInResolutions( - names: readonly string[], + names: readonly string[] | readonly FileReference[], newResolutions: readonly T[], - oldResolutions: ReadonlyESMap | undefined, + oldResolutions: ModeAwareCache | undefined, + oldSourceFile: SourceFile | undefined, comparer: (oldResolution: T, newResolution: T) => boolean): boolean { Debug.assert(names.length === newResolutions.length); for (let i = 0; i < names.length; i++) { const newResolution = newResolutions[i]; - const oldResolution = oldResolutions && oldResolutions.get(names[i]); + const entry = names[i]; + // We lower-case all type references because npm automatically lowercases all packages. See GH#9824. + const name = !isString(entry) ? entry.fileName.toLowerCase() : entry; + const mode = !isString(entry) ? getModeForFileReference(entry, oldSourceFile?.impliedNodeFormat) : oldSourceFile && getModeForResolutionAtIndex(oldSourceFile, i); + const oldResolution = oldResolutions && oldResolutions.get(name, mode); const changed = oldResolution ? !newResolution || !comparer(oldResolution, newResolution) @@ -278,6 +275,10 @@ namespace ts { return getSourceFileOfNode(module.valueDeclaration || getNonAugmentationDeclaration(module)); } + export function isPlainJsFile(file: SourceFile | undefined, checkJs: boolean | undefined): boolean { + return !!file && (file.scriptKind === ScriptKind.JS || file.scriptKind === ScriptKind.JSX) && !file.checkJsDirective && checkJs === undefined; + } + export function isStatementWithLocals(node: Node) { switch (node.kind) { case SyntaxKind.Block: @@ -605,6 +606,7 @@ namespace ts { AsyncIterableIterator: emptyArray, AsyncGenerator: emptyArray, AsyncGeneratorFunction: emptyArray, + NumberFormat: ["formatToParts"] }, es2019: { Array: ["flat", "flatMap"], @@ -626,8 +628,22 @@ namespace ts { PromiseConstructor: ["any"], String: ["replaceAll"] }, - esnext: { - NumberFormat: ["formatToParts"] + es2022: { + Array: ["at"], + String: ["at"], + Int8Array: ["at"], + Uint8Array: ["at"], + Uint8ClampedArray: ["at"], + Int16Array: ["at"], + Uint16Array: ["at"], + Int32Array: ["at"], + Uint32Array: ["at"], + Float32Array: ["at"], + Float64Array: ["at"], + BigInt64Array: ["at"], + BigUint64Array: ["at"], + ObjectConstructor: ["hasOwn"], + Error: ["cause"] } }; } @@ -791,8 +807,12 @@ namespace ts { return symbol.declarations?.find(d => !isExternalModuleAugmentation(d) && !(isModuleDeclaration(d) && isGlobalScopeAugmentation(d))); } + function isCommonJSContainingModuleKind(kind: ModuleKind) { + return kind === ModuleKind.CommonJS || kind === ModuleKind.Node12 || kind === ModuleKind.NodeNext; + } + export function isEffectiveExternalModule(node: SourceFile, compilerOptions: CompilerOptions) { - return isExternalModule(node) || compilerOptions.isolatedModules || ((getEmitModuleKind(compilerOptions) === ModuleKind.CommonJS) && !!node.commonJsModuleIndicator); + return isExternalModule(node) || compilerOptions.isolatedModules || (isCommonJSContainingModuleKind(getEmitModuleKind(compilerOptions)) && !!node.commonJsModuleIndicator); } /** @@ -1063,6 +1083,15 @@ namespace ts { }; } + export function createDiagnosticMessageChainFromDiagnostic(diagnostic: DiagnosticRelatedInformation): DiagnosticMessageChain { + return typeof diagnostic.messageText === "string" ? { + code: diagnostic.code, + category: diagnostic.category, + messageText: diagnostic.messageText, + next: (diagnostic as DiagnosticMessageChain).next, + } : diagnostic.messageText; + } + export function createDiagnosticForRange(sourceFile: SourceFile, range: TextRange, message: DiagnosticMessage): DiagnosticWithLocation { return { file: sourceFile, @@ -1239,7 +1268,8 @@ namespace ts { node.kind === SyntaxKind.FunctionExpression || node.kind === SyntaxKind.ArrowFunction || node.kind === SyntaxKind.ParenthesizedExpression || - node.kind === SyntaxKind.VariableDeclaration) ? + node.kind === SyntaxKind.VariableDeclaration || + node.kind === SyntaxKind.ExportSpecifier) ? concatenate(getTrailingCommentRanges(text, node.pos), getLeadingCommentRanges(text, node.pos)) : getLeadingCommentRanges(text, node.pos); // True if the comment starts with '/**' but not if it is '/**/' @@ -1274,7 +1304,7 @@ namespace ts { case SyntaxKind.VoidKeyword: return node.parent.kind !== SyntaxKind.VoidExpression; case SyntaxKind.ExpressionWithTypeArguments: - return !isExpressionWithTypeArgumentsInClassExtendsClause(node); + return isHeritageClause(node.parent) && !isExpressionWithTypeArgumentsInClassExtendsClause(node); case SyntaxKind.TypeParameter: return node.parent.kind === SyntaxKind.MappedType || node.parent.kind === SyntaxKind.InferType; @@ -1314,7 +1344,7 @@ namespace ts { } switch (parent.kind) { case SyntaxKind.ExpressionWithTypeArguments: - return !isExpressionWithTypeArgumentsInClassExtendsClause(parent); + return isHeritageClause(parent.parent) && !isExpressionWithTypeArgumentsInClassExtendsClause(parent); case SyntaxKind.TypeParameter: return node === (parent as TypeParameterDeclaration).constraint; case SyntaxKind.JSDocTemplateTag: @@ -1664,6 +1694,34 @@ namespace ts { } } + /** + * @returns Whether the node creates a new 'this' scope for its children. + */ + export function isThisContainerOrFunctionBlock(node: Node): boolean { + switch (node.kind) { + // Arrow functions use the same scope, but may do so in a "delayed" manner + // For example, `const getThis = () => this` may be before a super() call in a derived constructor + case SyntaxKind.ArrowFunction: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + case SyntaxKind.PropertyDeclaration: + return true; + case SyntaxKind.Block: + switch (node.parent.kind) { + case SyntaxKind.Constructor: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + // Object properties can have computed names; only method-like bodies start a new scope + return true; + default: + return false; + } + default: + return false; + } + } + export function isInTopLevelContext(node: Node) { // The name of a class or function declaration is a BindingIdentifier in its surrounding scope. if (isIdentifier(node) && (isClassDeclaration(node.parent) || isFunctionDeclaration(node.parent)) && node.parent.name === node) { @@ -1949,6 +2007,8 @@ namespace ts { node = node.parent; } return node.parent.kind === SyntaxKind.TypeQuery || isJSDocLinkLike(node.parent) || isJSDocNameReference(node.parent) || isJSDocMemberName(node.parent) || isJSXTagName(node); + case SyntaxKind.PrivateIdentifier: + return isBinaryExpression(node.parent) && node.parent.left === node && node.parent.operatorToken.kind === SyntaxKind.InKeyword; case SyntaxKind.Identifier: if (node.parent.kind === SyntaxKind.TypeQuery || isJSDocLinkLike(node.parent) || isJSDocNameReference(node.parent) || isJSDocMemberName(node.parent) || isJSXTagName(node)) { return true; @@ -2010,7 +2070,7 @@ namespace ts { case SyntaxKind.SpreadAssignment: return true; case SyntaxKind.ExpressionWithTypeArguments: - return (parent as ExpressionWithTypeArguments).expression === node && isExpressionWithTypeArgumentsInClassExtendsClause(parent); + return (parent as ExpressionWithTypeArguments).expression === node && !isPartOfTypeNode(parent); case SyntaxKind.ShorthandPropertyAssignment: return (parent as ShorthandPropertyAssignment).objectAssignmentInitializer === node; default: @@ -2039,7 +2099,7 @@ namespace ts { } export function getExternalModuleRequireArgument(node: Node) { - return isRequireVariableDeclaration(node) && (getLeftmostAccessExpression(node.initializer) as CallExpression).arguments[0] as StringLiteral; + return isVariableDeclarationInitializedToBareOrAccessedRequire(node) && (getLeftmostAccessExpression(node.initializer) as CallExpression).arguments[0] as StringLiteral; } export function isInternalModuleImportEqualsDeclaration(node: Node): node is ImportEqualsDeclaration { @@ -2106,17 +2166,30 @@ namespace ts { * Returns true if the node is a VariableDeclaration initialized to a require call (see `isRequireCall`). * This function does not test if the node is in a JavaScript file or not. */ - export function isRequireVariableDeclaration(node: Node): node is RequireVariableDeclaration { + export function isVariableDeclarationInitializedToRequire(node: Node): node is VariableDeclarationInitializedTo { + return isVariableDeclarationInitializedWithRequireHelper(node, /*allowAccessedRequire*/ false); + } + + /** + * Like {@link isVariableDeclarationInitializedToRequire} but allows things like `require("...").foo.bar` or `require("...")["baz"]`. + */ + export function isVariableDeclarationInitializedToBareOrAccessedRequire(node: Node): node is VariableDeclarationInitializedTo { + return isVariableDeclarationInitializedWithRequireHelper(node, /*allowAccessedRequire*/ true); + } + + function isVariableDeclarationInitializedWithRequireHelper(node: Node, allowAccessedRequire: boolean) { if (node.kind === SyntaxKind.BindingElement) { node = node.parent.parent; } - return isVariableDeclaration(node) && !!node.initializer && isRequireCall(getLeftmostAccessExpression(node.initializer), /*requireStringLiteralLikeArgument*/ true); + return isVariableDeclaration(node) && + !!node.initializer && + isRequireCall(allowAccessedRequire ? getLeftmostAccessExpression(node.initializer) : node.initializer, /*requireStringLiteralLikeArgument*/ true); } export function isRequireVariableStatement(node: Node): node is RequireVariableStatement { return isVariableStatement(node) && node.declarationList.declarations.length > 0 - && every(node.declarationList.declarations, decl => isRequireVariableDeclaration(decl)); + && every(node.declarationList.declarations, decl => isVariableDeclarationInitializedToRequire(decl)); } export function isSingleOrDoubleQuote(charCode: number) { @@ -2212,7 +2285,7 @@ namespace ts { const e = isBinaryExpression(initializer) && (initializer.operatorToken.kind === SyntaxKind.BarBarToken || initializer.operatorToken.kind === SyntaxKind.QuestionQuestionToken) && getExpandoInitializer(initializer.right, isPrototypeAssignment); - if (e && isSameEntityName(name, (initializer as BinaryExpression).left)) { + if (e && isSameEntityName(name, initializer.left)) { return e; } } @@ -2445,7 +2518,7 @@ namespace ts { return expr.right; } - export function isPrototypePropertyAssignment(node: Node): boolean { + export function isPrototypePropertyAssignment(node: Node): node is BinaryExpression { return isBinaryExpression(node) && getAssignmentDeclarationKind(node) === AssignmentDeclarationKind.PrototypeProperty; } @@ -3154,7 +3227,7 @@ namespace ts { return undefined; } - export function isKeyword(token: SyntaxKind): boolean { + export function isKeyword(token: SyntaxKind): token is KeywordSyntaxKind { return SyntaxKind.FirstKeyword <= token && token <= SyntaxKind.LastKeyword; } @@ -3339,7 +3412,7 @@ namespace ts { return node.escapedText === "push" || node.escapedText === "unshift"; } - export function isParameterDeclaration(node: VariableLikeDeclaration) { + export function isParameterDeclaration(node: VariableLikeDeclaration): boolean { const root = getRootDeclaration(node); return root.kind === SyntaxKind.Parameter; } @@ -3706,6 +3779,7 @@ namespace ts { case SyntaxKind.ThisKeyword: case SyntaxKind.SuperKeyword: case SyntaxKind.Identifier: + case SyntaxKind.PrivateIdentifier: case SyntaxKind.NullKeyword: case SyntaxKind.TrueKeyword: case SyntaxKind.FalseKeyword: @@ -4270,7 +4344,15 @@ namespace ts { const path = outputDir ? getSourceFilePathInNewDirWorker(fileName, outputDir, currentDirectory, commonSourceDirectory, getCanonicalFileName) : fileName; - return removeFileExtension(path) + Extension.Dts; + const declarationExtension = getDeclarationEmitExtensionForPath(path); + return removeFileExtension(path) + declarationExtension; + } + + export function getDeclarationEmitExtensionForPath(path: string) { + return fileExtensionIsOneOf(path, [Extension.Mjs, Extension.Mts]) ? Extension.Dmts : + fileExtensionIsOneOf(path, [Extension.Cjs, Extension.Cts]) ? Extension.Dcts : + fileExtensionIsOneOf(path, [Extension.Json]) ? `.json.d.ts` : // Drive-by redefinition of json declaration file output name so if it's ever enabled, it behaves well + Extension.Dts; } export function outFile(options: CompilerOptions) { @@ -5842,7 +5924,7 @@ namespace ts { } // eslint-disable-next-line prefer-const - export let objectAllocator: ObjectAllocator = { + export const objectAllocator: ObjectAllocator = { getNodeConstructor: () => Node as any, getTokenConstructor: () => Token as any, getIdentifierConstructor: () => Identifier as any, @@ -5855,20 +5937,29 @@ namespace ts { }; export function setObjectAllocator(alloc: ObjectAllocator) { - objectAllocator = alloc; + Object.assign(objectAllocator, alloc); } export function formatStringFromArgs(text: string, args: ArrayLike, baseIndex = 0): string { return text.replace(/{(\d+)}/g, (_match, index: string) => "" + Debug.checkDefined(args[+index + baseIndex])); } - export let localizedDiagnosticMessages: MapLike | undefined; + let localizedDiagnosticMessages: MapLike | undefined; /* @internal */ export function setLocalizedDiagnosticMessages(messages: typeof localizedDiagnosticMessages) { localizedDiagnosticMessages = messages; } + /* @internal */ + // If the localized messages json is unset, and if given function use it to set the json + + export function maybeSetLocalizedDiagnosticMessages(getMessages: undefined | (() => typeof localizedDiagnosticMessages)) { + if (!localizedDiagnosticMessages && getMessages) { + localizedDiagnosticMessages = getMessages(); + } + } + export function getLocaleSpecificMessage(message: DiagnosticMessage) { return localizedDiagnosticMessages && localizedDiagnosticMessages[message.key] || message.message; } @@ -6110,8 +6201,11 @@ namespace ts { return scriptKind === ScriptKind.TSX || scriptKind === ScriptKind.JSX || scriptKind === ScriptKind.JS || scriptKind === ScriptKind.JSON ? LanguageVariant.JSX : LanguageVariant.Standard; } - export function getEmitScriptTarget(compilerOptions: CompilerOptions) { - return compilerOptions.target || ScriptTarget.ES3; + export function getEmitScriptTarget(compilerOptions: {module?: CompilerOptions["module"], target?: CompilerOptions["target"]}) { + return compilerOptions.target || + (compilerOptions.module === ModuleKind.Node12 && ScriptTarget.ES2020) || + (compilerOptions.module === ModuleKind.NodeNext && ScriptTarget.ESNext) || + ScriptTarget.ES3; } export function getEmitModuleKind(compilerOptions: {module?: CompilerOptions["module"], target?: CompilerOptions["target"]}) { @@ -6123,7 +6217,20 @@ namespace ts { export function getEmitModuleResolutionKind(compilerOptions: CompilerOptions) { let moduleResolution = compilerOptions.moduleResolution; if (moduleResolution === undefined) { - moduleResolution = getEmitModuleKind(compilerOptions) === ModuleKind.CommonJS ? ModuleResolutionKind.NodeJs : ModuleResolutionKind.Classic; + switch (getEmitModuleKind(compilerOptions)) { + case ModuleKind.CommonJS: + moduleResolution = ModuleResolutionKind.NodeJs; + break; + case ModuleKind.Node12: + moduleResolution = ModuleResolutionKind.Node12; + break; + case ModuleKind.NodeNext: + moduleResolution = ModuleResolutionKind.NodeNext; + break; + default: + moduleResolution = ModuleResolutionKind.Classic; + break; + } } return moduleResolution; } @@ -6134,7 +6241,10 @@ namespace ts { case ModuleKind.AMD: case ModuleKind.ES2015: case ModuleKind.ES2020: + case ModuleKind.ES2022: case ModuleKind.ESNext: + case ModuleKind.Node12: + case ModuleKind.NodeNext: return true; default: return false; @@ -6153,11 +6263,23 @@ namespace ts { return !!(getEmitDeclarations(options) && options.declarationMap); } + export function getESModuleInterop(compilerOptions: CompilerOptions) { + if (compilerOptions.esModuleInterop !== undefined) { + return compilerOptions.esModuleInterop; + } + switch (getEmitModuleKind(compilerOptions)) { + case ModuleKind.Node12: + case ModuleKind.NodeNext: + return true; + } + return undefined; + } + export function getAllowSyntheticDefaultImports(compilerOptions: CompilerOptions) { const moduleKind = getEmitModuleKind(compilerOptions); return compilerOptions.allowSyntheticDefaultImports !== undefined ? compilerOptions.allowSyntheticDefaultImports - : compilerOptions.esModuleInterop || + : getESModuleInterop(compilerOptions) || moduleKind === ModuleKind.System; } @@ -6193,7 +6315,7 @@ namespace ts { } export function getUseDefineForClassFields(compilerOptions: CompilerOptions): boolean { - return compilerOptions.useDefineForClassFields === undefined ? compilerOptions.target === ScriptTarget.ESNext : compilerOptions.useDefineForClassFields; + return compilerOptions.useDefineForClassFields === undefined ? getEmitScriptTarget(compilerOptions) >= ScriptTarget.ES2022 : compilerOptions.useDefineForClassFields; } export function compilerOptionsAffectSemanticDiagnostics(newOptions: CompilerOptions, oldOptions: CompilerOptions): boolean { @@ -6260,15 +6382,13 @@ namespace ts { getSymlinkedFiles(): ReadonlyESMap | undefined; setSymlinkedDirectory(symlink: string, real: SymlinkedDirectory | false): void; setSymlinkedFile(symlinkPath: Path, real: string): void; - /*@internal*/ - setSymlinkedDirectoryFromSymlinkedFile(symlink: string, real: string): void; /** * @internal * Uses resolvedTypeReferenceDirectives from program instead of from files, since files * don't include automatic type reference directives. Must be called only when * `hasProcessedResolutions` returns false (once per cache instance). */ - setSymlinksFromResolutions(files: readonly SourceFile[], typeReferenceDirectives: ReadonlyESMap | undefined): void; + setSymlinksFromResolutions(files: readonly SourceFile[], typeReferenceDirectives: ModeAwareCache | undefined): void; /** * @internal * Whether `setSymlinksFromResolutions` has already been called. @@ -6299,16 +6419,6 @@ namespace ts { (symlinkedDirectories || (symlinkedDirectories = new Map())).set(symlinkPath, real); } }, - setSymlinkedDirectoryFromSymlinkedFile(symlink, real) { - this.setSymlinkedFile(toPath(symlink, cwd, getCanonicalFileName), real); - const [commonResolved, commonOriginal] = guessDirectorySymlink(real, symlink, cwd, getCanonicalFileName) || emptyArray; - if (commonResolved && commonOriginal) { - this.setSymlinkedDirectory(commonOriginal, { - real: commonResolved, - realPath: toPath(commonResolved, cwd, getCanonicalFileName), - }); - } - }, setSymlinksFromResolutions(files, typeReferenceDirectives) { Debug.assert(!hasProcessedResolutions); hasProcessedResolutions = true; @@ -6337,9 +6447,12 @@ namespace ts { const aParts = getPathComponents(getNormalizedAbsolutePath(a, cwd)); const bParts = getPathComponents(getNormalizedAbsolutePath(b, cwd)); let isDirectory = false; - while (!isNodeModulesOrScopedPackageDirectory(aParts[aParts.length - 2], getCanonicalFileName) && + while ( + aParts.length >= 2 && bParts.length >= 2 && + !isNodeModulesOrScopedPackageDirectory(aParts[aParts.length - 2], getCanonicalFileName) && !isNodeModulesOrScopedPackageDirectory(bParts[bParts.length - 2], getCanonicalFileName) && - getCanonicalFileName(aParts[aParts.length - 1]) === getCanonicalFileName(bParts[bParts.length - 1])) { + getCanonicalFileName(aParts[aParts.length - 1]) === getCanonicalFileName(bParts[bParts.length - 1]) + ) { aParts.pop(); bParts.pop(); isDirectory = true; @@ -6349,8 +6462,8 @@ namespace ts { // KLUDGE: Don't assume one 'node_modules' links to another. More likely a single directory inside the node_modules is the symlink. // ALso, don't assume that an `@foo` directory is linked. More likely the contents of that are linked. - function isNodeModulesOrScopedPackageDirectory(s: string, getCanonicalFileName: GetCanonicalFileName): boolean { - return getCanonicalFileName(s) === "node_modules" || startsWith(s, "@"); + function isNodeModulesOrScopedPackageDirectory(s: string | undefined, getCanonicalFileName: GetCanonicalFileName): boolean { + return s !== undefined && (getCanonicalFileName(s) === "node_modules" || startsWith(s, "@")); } function stripLeadingDirectorySeparator(s: string): string | undefined { @@ -6574,7 +6687,7 @@ namespace ts { } /** @param path directory of the tsconfig.json */ - export function matchFiles(path: string, extensions: readonly string[] | undefined, excludes: readonly string[] | undefined, includes: readonly string[] | undefined, useCaseSensitiveFileNames: boolean, currentDirectory: string, depth: number | undefined, getFileSystemEntries: (path: string) => FileSystemEntries, realpath: (path: string) => string, directoryExists: (path: string) => boolean): string[] { + export function matchFiles(path: string, extensions: readonly string[] | undefined, excludes: readonly string[] | undefined, includes: readonly string[] | undefined, useCaseSensitiveFileNames: boolean, currentDirectory: string, depth: number | undefined, getFileSystemEntries: (path: string) => FileSystemEntries, realpath: (path: string) => string): string[] { path = normalizePath(path); currentDirectory = normalizePath(currentDirectory); @@ -6590,9 +6703,7 @@ namespace ts { const visited = new Map(); const toCanonical = createGetCanonicalFileName(useCaseSensitiveFileNames); for (const basePath of patterns.basePaths) { - if (directoryExists(basePath)) { - visitDirectory(basePath, combinePaths(currentDirectory, basePath), depth); - } + visitDirectory(basePath, combinePaths(currentDirectory, basePath), depth); } return flatten(results); @@ -6695,10 +6806,14 @@ namespace ts { const ext = fileName.substr(fileName.lastIndexOf(".")); switch (ext.toLowerCase()) { case Extension.Js: + case Extension.Cjs: + case Extension.Mjs: return ScriptKind.JS; case Extension.Jsx: return ScriptKind.JSX; case Extension.Ts: + case Extension.Cts: + case Extension.Mts: return ScriptKind.TS; case Extension.Tsx: return ScriptKind.TSX; @@ -6710,39 +6825,44 @@ namespace ts { } /** - * List of supported extensions in order of file resolution precedence. + * Groups of supported extensions in order of file resolution precedence. (eg, TS > TSX > DTS and seperately, CTS > DCTS) */ - export const supportedTSExtensions: readonly Extension[] = [Extension.Ts, Extension.Tsx, Extension.Dts]; - export const supportedTSExtensionsWithJson: readonly Extension[] = [Extension.Ts, Extension.Tsx, Extension.Dts, Extension.Json]; + export const supportedTSExtensions: readonly Extension[][] = [[Extension.Ts, Extension.Tsx, Extension.Dts], [Extension.Cts, Extension.Dcts], [Extension.Mts, Extension.Dmts]]; + export const supportedTSExtensionsFlat: readonly Extension[] = flatten(supportedTSExtensions); + const supportedTSExtensionsWithJson: readonly Extension[][] = [...supportedTSExtensions, [Extension.Json]]; /** Must have ".d.ts" first because if ".ts" goes first, that will be detected as the extension instead of ".d.ts". */ - export const supportedTSExtensionsForExtractExtension: readonly Extension[] = [Extension.Dts, Extension.Ts, Extension.Tsx]; - export const supportedJSExtensions: readonly Extension[] = [Extension.Js, Extension.Jsx]; - export const supportedJSAndJsonExtensions: readonly Extension[] = [Extension.Js, Extension.Jsx, Extension.Json]; - const allSupportedExtensions: readonly Extension[] = [...supportedTSExtensions, ...supportedJSExtensions]; - const allSupportedExtensionsWithJson: readonly Extension[] = [...supportedTSExtensions, ...supportedJSExtensions, Extension.Json]; + const supportedTSExtensionsForExtractExtension: readonly Extension[] = [Extension.Dts, Extension.Dcts, Extension.Dmts, Extension.Cts, Extension.Mts, Extension.Ts, Extension.Tsx, Extension.Cts, Extension.Mts]; + export const supportedJSExtensions: readonly Extension[][] = [[Extension.Js, Extension.Jsx], [Extension.Mjs], [Extension.Cjs]]; + export const supportedJSExtensionsFlat: readonly Extension[] = flatten(supportedJSExtensions); + const allSupportedExtensions: readonly Extension[][] = [[Extension.Ts, Extension.Tsx, Extension.Dts, Extension.Js, Extension.Jsx], [Extension.Cts, Extension.Dcts, Extension.Cjs], [Extension.Mts, Extension.Dmts, Extension.Mjs]]; + const allSupportedExtensionsWithJson: readonly Extension[][] = [...allSupportedExtensions, [Extension.Json]]; - export function getSupportedExtensions(options?: CompilerOptions): readonly Extension[]; - export function getSupportedExtensions(options?: CompilerOptions, extraFileExtensions?: readonly FileExtensionInfo[]): readonly string[]; - export function getSupportedExtensions(options?: CompilerOptions, extraFileExtensions?: readonly FileExtensionInfo[]): readonly string[] { + export function getSupportedExtensions(options?: CompilerOptions): readonly Extension[][]; + export function getSupportedExtensions(options?: CompilerOptions, extraFileExtensions?: readonly FileExtensionInfo[]): readonly string[][]; + export function getSupportedExtensions(options?: CompilerOptions, extraFileExtensions?: readonly FileExtensionInfo[]): readonly string[][] { const needJsExtensions = options && getAllowJSCompilerOption(options); if (!extraFileExtensions || extraFileExtensions.length === 0) { return needJsExtensions ? allSupportedExtensions : supportedTSExtensions; } + const builtins = needJsExtensions ? allSupportedExtensions : supportedTSExtensions; + const flatBuiltins = flatten(builtins); const extensions = [ - ...needJsExtensions ? allSupportedExtensions : supportedTSExtensions, - ...mapDefined(extraFileExtensions, x => x.scriptKind === ScriptKind.Deferred || needJsExtensions && isJSLike(x.scriptKind) ? x.extension : undefined) + ...builtins, + ...mapDefined(extraFileExtensions, x => x.scriptKind === ScriptKind.Deferred || needJsExtensions && isJSLike(x.scriptKind) && flatBuiltins.indexOf(x.extension as Extension) === -1 ? [x.extension] : undefined) ]; - return deduplicate(extensions, equateStringsCaseSensitive, compareStringsCaseSensitive); + return extensions; } - export function getSuppoertedExtensionsWithJsonIfResolveJsonModule(options: CompilerOptions | undefined, supportedExtensions: readonly string[]): readonly string[] { + export function getSupportedExtensionsWithJsonIfResolveJsonModule(options: CompilerOptions | undefined, supportedExtensions: readonly Extension[][]): readonly Extension[][]; + export function getSupportedExtensionsWithJsonIfResolveJsonModule(options: CompilerOptions | undefined, supportedExtensions: readonly string[][]): readonly string[][]; + export function getSupportedExtensionsWithJsonIfResolveJsonModule(options: CompilerOptions | undefined, supportedExtensions: readonly string[][]): readonly string[][] { if (!options || !options.resolveJsonModule) return supportedExtensions; if (supportedExtensions === allSupportedExtensions) return allSupportedExtensionsWithJson; if (supportedExtensions === supportedTSExtensions) return supportedTSExtensionsWithJson; - return [...supportedExtensions, Extension.Json]; + return [...supportedExtensions, [Extension.Json]]; } function isJSLike(scriptKind: ScriptKind | undefined): boolean { @@ -6750,18 +6870,18 @@ namespace ts { } export function hasJSFileExtension(fileName: string): boolean { - return some(supportedJSExtensions, extension => fileExtensionIs(fileName, extension)); + return some(supportedJSExtensionsFlat, extension => fileExtensionIs(fileName, extension)); } export function hasTSFileExtension(fileName: string): boolean { - return some(supportedTSExtensions, extension => fileExtensionIs(fileName, extension)); + return some(supportedTSExtensionsFlat, extension => fileExtensionIs(fileName, extension)); } export function isSupportedSourceFileName(fileName: string, compilerOptions?: CompilerOptions, extraFileExtensions?: readonly FileExtensionInfo[]) { if (!fileName) return false; const supportedExtensions = getSupportedExtensions(compilerOptions, extraFileExtensions); - for (const extension of getSuppoertedExtensionsWithJsonIfResolveJsonModule(compilerOptions, supportedExtensions)) { + for (const extension of flatten(getSupportedExtensionsWithJsonIfResolveJsonModule(compilerOptions, supportedExtensions))) { if (fileExtensionIs(fileName, extension)) { return true; } @@ -6781,59 +6901,7 @@ namespace ts { ); } - /** - * Extension boundaries by priority. Lower numbers indicate higher priorities, and are - * aligned to the offset of the highest priority extension in the - * allSupportedExtensions array. - */ - export const enum ExtensionPriority { - TypeScriptFiles = 0, - DeclarationAndJavaScriptFiles = 2, - - Highest = TypeScriptFiles, - Lowest = DeclarationAndJavaScriptFiles, - } - - export function getExtensionPriority(path: string, supportedExtensions: readonly string[]): ExtensionPriority { - for (let i = supportedExtensions.length - 1; i >= 0; i--) { - if (fileExtensionIs(path, supportedExtensions[i])) { - return adjustExtensionPriority(i as ExtensionPriority, supportedExtensions); - } - } - - // If its not in the list of supported extensions, this is likely a - // TypeScript file with a non-ts extension - return ExtensionPriority.Highest; - } - - /** - * Adjusts an extension priority to be the highest priority within the same range. - */ - export function adjustExtensionPriority(extensionPriority: ExtensionPriority, supportedExtensions: readonly string[]): ExtensionPriority { - if (extensionPriority < ExtensionPriority.DeclarationAndJavaScriptFiles) { - return ExtensionPriority.TypeScriptFiles; - } - else if (extensionPriority < supportedExtensions.length) { - return ExtensionPriority.DeclarationAndJavaScriptFiles; - } - else { - return supportedExtensions.length; - } - } - - /** - * Gets the next lowest extension priority for a given priority. - */ - export function getNextLowestExtensionPriority(extensionPriority: ExtensionPriority, supportedExtensions: readonly string[]): ExtensionPriority { - if (extensionPriority < ExtensionPriority.DeclarationAndJavaScriptFiles) { - return ExtensionPriority.DeclarationAndJavaScriptFiles; - } - else { - return supportedExtensions.length; - } - } - - const extensionsToRemove = [Extension.Dts, Extension.Ts, Extension.Js, Extension.Tsx, Extension.Jsx, Extension.Json]; + const extensionsToRemove = [Extension.Dts, Extension.Dmts, Extension.Dcts, Extension.Mjs, Extension.Mts, Extension.Cjs, Extension.Cts, Extension.Ts, Extension.Js, Extension.Tsx, Extension.Jsx, Extension.Json]; export function removeFileExtension(path: string): string { for (const ext of extensionsToRemove) { const extensionless = tryRemoveExtension(path, ext); @@ -6885,7 +6953,7 @@ namespace ts { /** True if an extension is one of the supported TypeScript extensions. */ export function extensionIsTS(ext: Extension): boolean { - return ext === Extension.Ts || ext === Extension.Tsx || ext === Extension.Dts; + return ext === Extension.Ts || ext === Extension.Tsx || ext === Extension.Dts || ext === Extension.Cts || ext === Extension.Mts || ext === Extension.Dmts || ext === Extension.Dcts; } export function resolutionExtensionIsTSOrJson(ext: Extension) { @@ -6975,18 +7043,6 @@ namespace ts { return { min, max }; } - /** @deprecated Use `ReadonlySet` instead. */ - export type ReadonlyNodeSet = ReadonlySet; - - /** @deprecated Use `Set` instead. */ - export type NodeSet = Set; - - /** @deprecated Use `ReadonlyMap` instead. */ - export type ReadonlyNodeMap = ReadonlyESMap; - - /** @deprecated Use `Map` instead. */ - export type NodeMap = ESMap; - export function rangeOfNode(node: Node): TextRange { return { pos: getTokenPosOfNode(node), end: node.end }; } @@ -7098,10 +7154,6 @@ namespace ts { || !(isExpressionNode(useSite) || isShorthandPropertyNameUseSite(useSite)); } - export function typeOnlyDeclarationIsExport(typeOnlyDeclaration: Node) { - return typeOnlyDeclaration.kind === SyntaxKind.ExportSpecifier; - } - function isShorthandPropertyNameUseSite(useSite: Node) { return isIdentifier(useSite) && isShorthandPropertyAssignment(useSite.parent) && useSite.parent.name === useSite; } @@ -7409,4 +7461,120 @@ namespace ts { export function isInfinityOrNaNString(name: string | __String): boolean { return name === "Infinity" || name === "-Infinity" || name === "NaN"; } + + export function isCatchClauseVariableDeclaration(node: Node) { + return node.kind === SyntaxKind.VariableDeclaration && node.parent.kind === SyntaxKind.CatchClause; + } + + export function isParameterOrCatchClauseVariable(symbol: Symbol) { + const declaration = symbol.valueDeclaration && getRootDeclaration(symbol.valueDeclaration); + return !!declaration && (isParameter(declaration) || isCatchClauseVariableDeclaration(declaration)); + } + + export function isFunctionExpressionOrArrowFunction(node: Node): node is FunctionExpression | ArrowFunction { + return node.kind === SyntaxKind.FunctionExpression || node.kind === SyntaxKind.ArrowFunction; + } + + export function escapeSnippetText(text: string): string { + return text.replace(/\$/gm, () => "\\$"); + } + + export function isNumericLiteralName(name: string | __String) { + // The intent of numeric names is that + // - they are names with text in a numeric form, and that + // - setting properties/indexing with them is always equivalent to doing so with the numeric literal 'numLit', + // acquired by applying the abstract 'ToNumber' operation on the name's text. + // + // The subtlety is in the latter portion, as we cannot reliably say that anything that looks like a numeric literal is a numeric name. + // In fact, it is the case that the text of the name must be equal to 'ToString(numLit)' for this to hold. + // + // Consider the property name '"0xF00D"'. When one indexes with '0xF00D', they are actually indexing with the value of 'ToString(0xF00D)' + // according to the ECMAScript specification, so it is actually as if the user indexed with the string '"61453"'. + // Thus, the text of all numeric literals equivalent to '61543' such as '0xF00D', '0xf00D', '0170015', etc. are not valid numeric names + // because their 'ToString' representation is not equal to their original text. + // This is motivated by ECMA-262 sections 9.3.1, 9.8.1, 11.1.5, and 11.2.1. + // + // Here, we test whether 'ToString(ToNumber(name))' is exactly equal to 'name'. + // The '+' prefix operator is equivalent here to applying the abstract ToNumber operation. + // Applying the 'toString()' method on a number gives us the abstract ToString operation on a number. + // + // Note that this accepts the values 'Infinity', '-Infinity', and 'NaN', and that this is intentional. + // This is desired behavior, because when indexing with them as numeric entities, you are indexing + // with the strings '"Infinity"', '"-Infinity"', and '"NaN"' respectively. + return (+name).toString() === name; + } + + export function createPropertyNameNodeForIdentifierOrLiteral(name: string, target: ScriptTarget, singleQuote?: boolean, stringNamed?: boolean) { + return isIdentifierText(name, target) ? factory.createIdentifier(name) : + !stringNamed && isNumericLiteralName(name) && +name >= 0 ? factory.createNumericLiteral(+name) : + factory.createStringLiteral(name, !!singleQuote); + } + + export function isThisTypeParameter(type: Type): boolean { + return !!(type.flags & TypeFlags.TypeParameter && (type as TypeParameter).isThisType); + } + + export interface NodeModulePathParts { + readonly topLevelNodeModulesIndex: number; + readonly topLevelPackageNameIndex: number; + readonly packageRootIndex: number; + readonly fileNameIndex: number; + } + export function getNodeModulePathParts(fullPath: string): NodeModulePathParts | undefined { + // If fullPath can't be valid module file within node_modules, returns undefined. + // Example of expected pattern: /base/path/node_modules/[@scope/otherpackage/@otherscope/node_modules/]package/[subdirectory/]file.js + // Returns indices: ^ ^ ^ ^ + + let topLevelNodeModulesIndex = 0; + let topLevelPackageNameIndex = 0; + let packageRootIndex = 0; + let fileNameIndex = 0; + + const enum States { + BeforeNodeModules, + NodeModules, + Scope, + PackageContent + } + + let partStart = 0; + let partEnd = 0; + let state = States.BeforeNodeModules; + + while (partEnd >= 0) { + partStart = partEnd; + partEnd = fullPath.indexOf("/", partStart + 1); + switch (state) { + case States.BeforeNodeModules: + if (fullPath.indexOf(nodeModulesPathPart, partStart) === partStart) { + topLevelNodeModulesIndex = partStart; + topLevelPackageNameIndex = partEnd; + state = States.NodeModules; + } + break; + case States.NodeModules: + case States.Scope: + if (state === States.NodeModules && fullPath.charAt(partStart + 1) === "@") { + state = States.Scope; + } + else { + packageRootIndex = partEnd; + state = States.PackageContent; + } + break; + case States.PackageContent: + if (fullPath.indexOf(nodeModulesPathPart, partStart) === partStart) { + state = States.NodeModules; + } + else { + state = States.PackageContent; + } + break; + } + } + + fileNameIndex = partStart; + + return state > States.NodeModules ? { topLevelNodeModulesIndex, topLevelPackageNameIndex, packageRootIndex, fileNameIndex } : undefined; + } } diff --git a/src/compiler/utilitiesPublic.ts b/src/compiler/utilitiesPublic.ts index 91b0a1cc9a9..4e1ff57f08b 100644 --- a/src/compiler/utilitiesPublic.ts +++ b/src/compiler/utilitiesPublic.ts @@ -11,9 +11,11 @@ namespace ts { } export function getDefaultLibFileName(options: CompilerOptions): string { - switch (options.target) { + switch (getEmitScriptTarget(options)) { case ScriptTarget.ESNext: return "lib.esnext.full.d.ts"; + case ScriptTarget.ES2022: + return "lib.es2022.full.d.ts"; case ScriptTarget.ES2021: return "lib.es2021.full.d.ts"; case ScriptTarget.ES2020: @@ -903,9 +905,16 @@ namespace ts { /** Gets the text of a jsdoc comment, flattening links to their text. */ export function getTextOfJSDocComment(comment?: string | NodeArray) { return typeof comment === "string" ? comment - : comment?.map(c => - // TODO: Other kinds here - c.kind === SyntaxKind.JSDocText ? c.text : `{@link ${c.name ? entityNameToString(c.name) + " " : ""}${c.text}}`).join(""); + : comment?.map(c => c.kind === SyntaxKind.JSDocText ? c.text : formatJSDocLink(c)).join(""); + } + + function formatJSDocLink(link: JSDocLink | JSDocLinkCode | JSDocLinkPlain) { + const kind = link.kind === SyntaxKind.JSDocLink ? "link" + : link.kind === SyntaxKind.JSDocLinkCode ? "linkcode" + : "linkplain"; + const name = link.name ? entityNameToString(link.name) : ""; + const space = link.name && link.text.startsWith("://") ? "" : " "; + return `{@${kind} ${name}${space}${link.text}}`; } /** @@ -1130,7 +1139,7 @@ namespace ts { switch (node.kind) { case SyntaxKind.ImportSpecifier: case SyntaxKind.ExportSpecifier: - return (node as ImportOrExportSpecifier).parent.parent.isTypeOnly; + return (node as ImportOrExportSpecifier).isTypeOnly || (node as ImportOrExportSpecifier).parent.parent.isTypeOnly; case SyntaxKind.NamespaceImport: return (node as NamespaceImport).parent.isTypeOnly; case SyntaxKind.ImportClause: @@ -1141,6 +1150,10 @@ namespace ts { } } + export function isAssertionKey(node: Node): node is AssertionKey { + return isStringLiteral(node) || isIdentifier(node); + } + export function isStringTextContainingNode(node: Node): node is StringLiteral | TemplateLiteralToken { return node.kind === SyntaxKind.StringLiteral || isTemplateLiteralKind(node.kind); } @@ -1512,6 +1525,7 @@ namespace ts { case SyntaxKind.ClassExpression: case SyntaxKind.FunctionExpression: case SyntaxKind.Identifier: + case SyntaxKind.PrivateIdentifier: // technically this is only an Expression if it's in a `#field in expr` BinaryExpression case SyntaxKind.RegularExpressionLiteral: case SyntaxKind.NumericLiteral: case SyntaxKind.BigIntLiteral: @@ -1524,6 +1538,7 @@ namespace ts { case SyntaxKind.TrueKeyword: case SyntaxKind.SuperKeyword: case SyntaxKind.NonNullExpression: + case SyntaxKind.ExpressionWithTypeArguments: case SyntaxKind.MetaProperty: case SyntaxKind.ImportKeyword: // technically this is only an Expression if it's in a CallExpression return true; diff --git a/src/compiler/visitorPublic.ts b/src/compiler/visitorPublic.ts index 94ab1afbb8a..66840b76047 100644 --- a/src/compiler/visitorPublic.ts +++ b/src/compiler/visitorPublic.ts @@ -538,7 +538,8 @@ namespace ts { case SyntaxKind.TypeQuery: Debug.type(node); return factory.updateTypeQueryNode(node, - nodeVisitor(node.exprName, visitor, isEntityName)); + nodeVisitor(node.exprName, visitor, isEntityName), + nodesVisitor(node.typeArguments, visitor, isTypeNode)); case SyntaxKind.TypeLiteral: Debug.type(node); @@ -629,7 +630,8 @@ namespace ts { nodeVisitor(node.typeParameter, visitor, isTypeParameterDeclaration), nodeVisitor(node.nameType, visitor, isTypeNode), nodeVisitor(node.questionToken, tokenVisitor, isQuestionOrPlusOrMinusToken), - nodeVisitor(node.type, visitor, isTypeNode)); + nodeVisitor(node.type, visitor, isTypeNode), + nodesVisitor(node.members, visitor, isTypeElement)); case SyntaxKind.LiteralType: Debug.type(node); @@ -1076,7 +1078,20 @@ namespace ts { nodesVisitor(node.decorators, visitor, isDecorator), nodesVisitor(node.modifiers, visitor, isModifier), nodeVisitor(node.importClause, visitor, isImportClause), - nodeVisitor(node.moduleSpecifier, visitor, isExpression)); + nodeVisitor(node.moduleSpecifier, visitor, isExpression), + nodeVisitor(node.assertClause, visitor, isAssertClause)); + + case SyntaxKind.AssertClause: + Debug.type(node); + return factory.updateAssertClause(node, + nodesVisitor(node.elements, visitor, isAssertEntry), + node.multiLine); + + case SyntaxKind.AssertEntry: + Debug.type(node); + return factory.updateAssertEntry(node, + nodeVisitor(node.name, visitor, isAssertionKey), + nodeVisitor(node.value, visitor, isExpressionNode)); case SyntaxKind.ImportClause: Debug.type(node); @@ -1103,6 +1118,7 @@ namespace ts { case SyntaxKind.ImportSpecifier: Debug.type(node); return factory.updateImportSpecifier(node, + node.isTypeOnly, nodeVisitor(node.propertyName, visitor, isIdentifier), nodeVisitor(node.name, visitor, isIdentifier)); @@ -1120,7 +1136,8 @@ namespace ts { nodesVisitor(node.modifiers, visitor, isModifier), node.isTypeOnly, nodeVisitor(node.exportClause, visitor, isNamedExportBindings), - nodeVisitor(node.moduleSpecifier, visitor, isExpression)); + nodeVisitor(node.moduleSpecifier, visitor, isExpression), + nodeVisitor(node.assertClause, visitor, isAssertClause)); case SyntaxKind.NamedExports: Debug.type(node); @@ -1130,6 +1147,7 @@ namespace ts { case SyntaxKind.ExportSpecifier: Debug.type(node); return factory.updateExportSpecifier(node, + node.isTypeOnly, nodeVisitor(node.propertyName, visitor, isIdentifier), nodeVisitor(node.name, visitor, isIdentifier)); diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index 3f63422a7fa..5e8c950da4e 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -7,7 +7,7 @@ namespace ts { } : undefined; /** - * Create a function that reports error by writing to the system and handles the formating of the diagnostic + * Create a function that reports error by writing to the system and handles the formatting of the diagnostic */ export function createDiagnosticReporter(system: System, pretty?: boolean): DiagnosticReporter { const host: FormatDiagnosticsHost = system === sys && sysFormatDiagnosticsHost ? sysFormatDiagnosticsHost : { @@ -64,7 +64,7 @@ namespace ts { } /** - * Create a function that reports watch status by writing to the system and handles the formating of the diagnostic + * Create a function that reports watch status by writing to the system and handles the formatting of the diagnostic */ export function createWatchStatusReporter(system: System, pretty?: boolean): WatchStatusReporter { return pretty ? @@ -101,16 +101,104 @@ namespace ts { return countWhere(diagnostics, diagnostic => diagnostic.category === DiagnosticCategory.Error); } + export function getFilesInErrorForSummary(diagnostics: readonly Diagnostic[]): (ReportFileInError | undefined)[] { + const filesInError = + filter(diagnostics, diagnostic => diagnostic.category === DiagnosticCategory.Error) + .map( + errorDiagnostic => { + if(errorDiagnostic.file === undefined) return; + return `${errorDiagnostic.file.fileName}`; + }); + return filesInError.map((fileName: string) => { + const diagnosticForFileName = find(diagnostics, diagnostic => + diagnostic.file !== undefined && diagnostic.file.fileName === fileName + ); + + if(diagnosticForFileName !== undefined) { + const { line } = getLineAndCharacterOfPosition(diagnosticForFileName.file!, diagnosticForFileName.start!); + return { + fileName, + line: line + 1, + }; + } + }); + } + export function getWatchErrorSummaryDiagnosticMessage(errorCount: number) { return errorCount === 1 ? Diagnostics.Found_1_error_Watching_for_file_changes : Diagnostics.Found_0_errors_Watching_for_file_changes; } - export function getErrorSummaryText(errorCount: number, newLine: string) { + function prettyPathForFileError(error: ReportFileInError, cwd: string) { + const line = formatColorAndReset(":" + error.line, ForegroundColorEscapeSequences.Grey); + if (pathIsAbsolute(error.fileName) && pathIsAbsolute(cwd)) { + return getRelativePathFromDirectory(cwd, error.fileName, /* ignoreCase */ false) + line; + } + + return error.fileName + line; + } + + export function getErrorSummaryText( + errorCount: number, + filesInError: readonly (ReportFileInError | undefined)[], + newLine: string, + host: HasCurrentDirectory + ) { if (errorCount === 0) return ""; - const d = createCompilerDiagnostic(errorCount === 1 ? Diagnostics.Found_1_error : Diagnostics.Found_0_errors, errorCount); - return `${newLine}${flattenDiagnosticMessageText(d.messageText, newLine)}${newLine}${newLine}`; + const nonNilFiles = filesInError.filter(fileInError => fileInError !== undefined); + const distinctFileNamesWithLines = nonNilFiles.map(fileInError => `${fileInError!.fileName}:${fileInError!.line}`) + .filter((value, index, self) => self.indexOf(value) === index); + + const firstFileReference = nonNilFiles[0] && prettyPathForFileError(nonNilFiles[0], host.getCurrentDirectory()); + + const d = errorCount === 1 ? + createCompilerDiagnostic( + filesInError[0] !== undefined ? + Diagnostics.Found_1_error_in_1 : + Diagnostics.Found_1_error, + errorCount, + firstFileReference) : + createCompilerDiagnostic( + distinctFileNamesWithLines.length === 0 ? + Diagnostics.Found_0_errors : + distinctFileNamesWithLines.length === 1 ? + Diagnostics.Found_0_errors_in_the_same_file_starting_at_Colon_1 : + Diagnostics.Found_0_errors_in_1_files, + errorCount, + distinctFileNamesWithLines.length === 1 ? firstFileReference : distinctFileNamesWithLines.length); + + const suffix = distinctFileNamesWithLines.length > 1 ? createTabularErrorsDisplay(nonNilFiles, host) : ""; + return `${newLine}${flattenDiagnosticMessageText(d.messageText, newLine)}${newLine}${newLine}${suffix}`; + } + + function createTabularErrorsDisplay(filesInError: (ReportFileInError | undefined)[], host: HasCurrentDirectory) { + const distinctFiles = filesInError.filter((value, index, self) => index === self.findIndex(file => file?.fileName === value?.fileName)); + if (distinctFiles.length === 0) return ""; + + const numberLength = (num: number) => Math.log(num) * Math.LOG10E + 1; + const fileToErrorCount = distinctFiles.map(file => ([file, countWhere(filesInError, fileInError => fileInError!.fileName === file!.fileName)] as const)); + const maxErrors = fileToErrorCount.reduce((acc, value) => Math.max(acc, value[1] || 0), 0); + + const headerRow = Diagnostics.Errors_Files.message; + const leftColumnHeadingLength = headerRow.split(" ")[0].length; + const leftPaddingGoal = Math.max(leftColumnHeadingLength, numberLength(maxErrors)); + const headerPadding = Math.max(numberLength(maxErrors) - leftColumnHeadingLength, 0); + + let tabularData = ""; + tabularData += " ".repeat(headerPadding) + headerRow + "\n"; + fileToErrorCount.forEach((row) => { + const [file, errorCount] = row; + const errorCountDigitsLength = Math.log(errorCount) * Math.LOG10E + 1 | 0; + const leftPadding = errorCountDigitsLength < leftPaddingGoal ? + " ".repeat(leftPaddingGoal - errorCountDigitsLength) + : ""; + + const fileRef = prettyPathForFileError(file!, host.getCurrentDirectory()); + tabularData += `${leftPadding}${errorCount} ${fileRef}\n`; + }); + + return tabularData; } export function isBuilderProgram(program: Program | BuilderProgram): program is BuilderProgram { @@ -279,7 +367,7 @@ namespace ts { ); case FileIncludeKind.LibFile: if (reason.index !== undefined) return chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Library_0_specified_in_compilerOptions, options.lib![reason.index]); - const target = forEachEntry(targetOptionDeclaration.type, (value, key) => value === options.target ? key : undefined); + const target = forEachEntry(targetOptionDeclaration.type, (value, key) => value === getEmitScriptTarget(options) ? key : undefined); return chainDiagnosticMessages( /*details*/ undefined, target ? @@ -350,7 +438,7 @@ namespace ts { } if (reportSummary) { - reportSummary(getErrorCountForSummary(diagnostics)); + reportSummary(getErrorCountForSummary(diagnostics), getFilesInErrorForSummary(diagnostics)); } return { @@ -656,7 +744,7 @@ namespace ts { builderProgram, input.reportDiagnostic || createDiagnosticReporter(system), s => host.trace && host.trace(s), - input.reportErrorSummary || input.options.pretty ? errorCount => system.write(getErrorSummaryText(errorCount, system.newLine)) : undefined + input.reportErrorSummary || input.options.pretty ? (errorCount, filesInError) => system.write(getErrorSummaryText(errorCount, filesInError, system.newLine, host)) : undefined ); if (input.afterProgramEmitAndDiagnostics) input.afterProgramEmitAndDiagnostics(builderProgram); return exitStatus; diff --git a/src/compiler/watchPublic.ts b/src/compiler/watchPublic.ts index 1cc21a0bc94..1f6ec39df4e 100644 --- a/src/compiler/watchPublic.ts +++ b/src/compiler/watchPublic.ts @@ -101,9 +101,9 @@ namespace ts { getEnvironmentVariable?(name: string): string | undefined; /** If provided, used to resolve the module names, otherwise typescript's default module resolution */ - resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions): (ResolvedModule | undefined)[]; + resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames: string[] | undefined, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions, containingSourceFile?: SourceFile): (ResolvedModule | undefined)[]; /** If provided, used to resolve type reference directives, otherwise typescript's default resolution */ - resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions): (ResolvedTypeReferenceDirective | undefined)[]; + resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[] | readonly FileReference[], containingFile: string, redirectedReference: ResolvedProjectReference | undefined, options: CompilerOptions, containingFileMode?: SourceFile["impliedNodeFormat"] | undefined): (ResolvedTypeReferenceDirective | undefined)[]; } /** Internal interface used to wire emit through same host */ @@ -273,6 +273,7 @@ namespace ts { let sharedExtendedConfigFileWatchers: ESMap>; // Map of file watchers for extended files, shared between different referenced projects let extendedConfigCache = host.extendedConfigCache; // Cache for extended config evaluation let changesAffectResolution = false; // Flag for indicating non-config changes affect module resolution + let reportFileChangeDetectedOnCreateProgram = false; // True if synchronizeProgram should report "File change detected..." when a new program is created const sourceFilesCache = new Map(); // Cache that stores the source file and version info let missingFilePathsRequestedForRelease: Path[] | undefined; // These paths are held temporarily so that we can remove the entry from source file cache if the file is not tracked by missing files @@ -349,10 +350,10 @@ namespace ts { // Resolve module using host module resolution strategy if provided otherwise use resolution cache to resolve module names compilerHost.resolveModuleNames = host.resolveModuleNames ? ((...args) => host.resolveModuleNames!(...args)) : - ((moduleNames, containingFile, reusedNames, redirectedReference) => resolutionCache.resolveModuleNames(moduleNames, containingFile, reusedNames, redirectedReference)); + ((moduleNames, containingFile, reusedNames, redirectedReference, _options, sourceFile) => resolutionCache.resolveModuleNames(moduleNames, containingFile, reusedNames, redirectedReference, sourceFile)); compilerHost.resolveTypeReferenceDirectives = host.resolveTypeReferenceDirectives ? ((...args) => host.resolveTypeReferenceDirectives!(...args)) : - ((typeDirectiveNames, containingFile, redirectedReference) => resolutionCache.resolveTypeReferenceDirectives(typeDirectiveNames, containingFile, redirectedReference)); + ((typeDirectiveNames, containingFile, redirectedReference, _options, containingFileMode) => resolutionCache.resolveTypeReferenceDirectives(typeDirectiveNames, containingFile, redirectedReference, containingFileMode)); const userProvidedResolution = !!host.resolveModuleNames || !!host.resolveTypeReferenceDirectives; builderProgram = readBuilderProgram(compilerOptions, compilerHost) as any as T; @@ -434,15 +435,22 @@ namespace ts { const hasInvalidatedResolution = resolutionCache.createHasInvalidatedResolution(userProvidedResolution || changesAffectResolution); if (isProgramUptoDate(getCurrentProgram(), rootFileNames, compilerOptions, getSourceVersion, fileExists, hasInvalidatedResolution, hasChangedAutomaticTypeDirectiveNames, getParsedCommandLine, projectReferences)) { if (hasChangedConfigFileParsingErrors) { + if (reportFileChangeDetectedOnCreateProgram) { + reportWatchDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation); + } builderProgram = createProgram(/*rootNames*/ undefined, /*options*/ undefined, compilerHost, builderProgram, configFileParsingDiagnostics, projectReferences); hasChangedConfigFileParsingErrors = false; } } else { + if (reportFileChangeDetectedOnCreateProgram) { + reportWatchDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation); + } createNewProgram(hasInvalidatedResolution); } changesAffectResolution = false; // reset for next sync + reportFileChangeDetectedOnCreateProgram = false; if (host.afterProgramCreate && program !== builderProgram) { host.afterProgramCreate(builderProgram); @@ -560,6 +568,9 @@ namespace ts { sourceFilesCache.set(path, false); } } + if (sourceFile) { + sourceFile.impliedNodeFormat = getImpliedNodeFormatForFile(path, resolutionCache.getModuleResolutionCache().getPackageJsonInfoCache(), compilerHost, compilerHost.getCompilationSettings()); + } return sourceFile; } return hostSourceFile.sourceFile; @@ -662,7 +673,7 @@ namespace ts { function updateProgramWithWatchStatus() { timerToUpdateProgram = undefined; - reportWatchDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation); + reportFileChangeDetectedOnCreateProgram = true; updateProgram(); } diff --git a/src/compiler/watchUtilities.ts b/src/compiler/watchUtilities.ts index aac36908bad..adbf9f1287b 100644 --- a/src/compiler/watchUtilities.ts +++ b/src/compiler/watchUtilities.ts @@ -184,7 +184,7 @@ namespace ts { const rootResult = tryReadDirectory(rootDir, rootDirPath); let rootSymLinkResult: FileSystemEntries | undefined; if (rootResult !== undefined) { - return matchFiles(rootDir, extensions, excludes, includes, useCaseSensitiveFileNames, currentDirectory, depth, getFileSystemEntries, realpath, directoryExists); + return matchFiles(rootDir, extensions, excludes, includes, useCaseSensitiveFileNames, currentDirectory, depth, getFileSystemEntries, realpath); } return host.readDirectory!(rootDir, extensions, excludes, includes, depth); @@ -499,7 +499,7 @@ namespace ts { // If its declaration directory: its not ignored if not excluded by config if (options.declarationDir) return false; } - else if (!fileExtensionIsOneOf(fileOrDirectoryPath, supportedJSExtensions)) { + else if (!fileExtensionIsOneOf(fileOrDirectoryPath, supportedJSExtensionsFlat)) { return false; } diff --git a/src/deprecatedCompat/deprecations.ts b/src/deprecatedCompat/deprecations.ts index a70db0d3442..292840166b3 100644 --- a/src/deprecatedCompat/deprecations.ts +++ b/src/deprecatedCompat/deprecations.ts @@ -1224,7 +1224,7 @@ namespace ts { exportClause: NamedExportBindings | undefined, moduleSpecifier: Expression | undefined, isTypeOnly: boolean) { - return factory.updateExportDeclaration(node, decorators, modifiers, isTypeOnly, exportClause, moduleSpecifier); + return factory.updateExportDeclaration(node, decorators, modifiers, isTypeOnly, exportClause, moduleSpecifier, node.assertClause); }, factoryDeprecation); /** @deprecated Use `factory.createJSDocParameterTag` or the factory supplied by your transformation context instead. */ diff --git a/src/executeCommandLine/executeCommandLine.ts b/src/executeCommandLine/executeCommandLine.ts index 87084d4f4bb..cbdba05b3ac 100644 --- a/src/executeCommandLine/executeCommandLine.ts +++ b/src/executeCommandLine/executeCommandLine.ts @@ -43,10 +43,10 @@ namespace ts { } const path = file.path; - if (fileExtensionIsOneOf(path, supportedTSExtensions)) { + if (fileExtensionIsOneOf(path, supportedTSExtensionsFlat)) { return "TypeScript"; } - else if (fileExtensionIsOneOf(path, supportedJSExtensions)) { + else if (fileExtensionIsOneOf(path, supportedJSExtensionsFlat)) { return "JavaScript"; } else if (fileExtensionIs(path, Extension.Json)) { @@ -160,7 +160,13 @@ namespace ts { // value type and possible value const valueCandidates = getValueCandidate(option); - const defaultValueDescription = typeof option.defaultValueDescription === "object" ? getDiagnosticText(option.defaultValueDescription) : option.defaultValueDescription; + const defaultValueDescription = + typeof option.defaultValueDescription === "object" + ? getDiagnosticText(option.defaultValueDescription) + : formatDefaultValue( + option.defaultValueDescription, + option.type === "list" ? option.element.type : option.type + ); const terminalWidth = sys.getWidthOfTerminal?.() ?? 0; // Note: child_process might return `terminalWidth` as undefined. @@ -203,6 +209,19 @@ namespace ts { } return text; + function formatDefaultValue( + defaultValue: CommandLineOption["defaultValueDescription"], + type: CommandLineOption["type"] + ) { + return defaultValue !== undefined && typeof type === "object" + // e.g. ScriptTarget.ES2015 -> "es6/es2015" + ? arrayFrom(type.entries()) + .filter(([, value]) => value === defaultValue) + .map(([name]) => name) + .join("/") + : String(defaultValue); + } + function showAdditionalInfoOutput(valueCandidates: ValueCandidate | undefined, option: CommandLineOption): boolean { const ignoreValues = ["string"]; const ignoredDescriptions = [undefined, "false", "n/a"]; @@ -286,8 +305,14 @@ namespace ts { break; default: // ESMap - const keys = arrayFrom(option.type.keys()); - possibleValues = keys.join(", "); + // Group synonyms: es6/es2015 + const inverted: { [value: string]: string[] } = {}; + option.type.forEach((value, name) => { + (inverted[value] ||= []).push(name); + }); + return getEntries(inverted) + .map(([, synonyms]) => synonyms.join("/")) + .join(", "); } return possibleValues; } @@ -354,7 +379,7 @@ namespace ts { function printEasyHelp(sys: System, simpleOptions: readonly CommandLineOption[]) { const colors = createColors(sys); - let output: string[] = [...getHelpHeader(sys)]; + let output: string[] = [...getHeader(sys,`${getDiagnosticText(Diagnostics.tsc_Colon_The_TypeScript_Compiler)} - ${getDiagnosticText(Diagnostics.Version_0, version)}`)]; output.push(colors.bold(getDiagnosticText(Diagnostics.COMMON_COMMANDS)) + sys.newLine + sys.newLine); example("tsc", Diagnostics.Compiles_the_current_project_tsconfig_json_in_the_working_directory); @@ -388,7 +413,7 @@ namespace ts { } function printAllHelp(sys: System, compilerOptions: readonly CommandLineOption[], buildOptions: readonly CommandLineOption[], watchOptions: readonly CommandLineOption[]) { - let output: string[] = [...getHelpHeader(sys)]; + let output: string[] = [...getHeader(sys,`${getDiagnosticText(Diagnostics.tsc_Colon_The_TypeScript_Compiler)} - ${getDiagnosticText(Diagnostics.Version_0, version)}`)]; output = [...output, ...generateSectionOptionsOutput(sys, getDiagnosticText(Diagnostics.ALL_COMPILER_OPTIONS), compilerOptions, /*subCategory*/ true, /* beforeOptionsDescription */ undefined, formatMessage(/*_dummy*/ undefined, Diagnostics.You_can_learn_about_all_of_the_compiler_options_at_0, "https://aka.ms/tsconfig-reference"))]; output = [...output, ...generateSectionOptionsOutput(sys, getDiagnosticText(Diagnostics.WATCH_OPTIONS), watchOptions, /*subCategory*/ false, getDiagnosticText(Diagnostics.Including_watch_w_will_start_watching_the_current_project_for_the_file_changes_Once_set_you_can_config_watch_mode_with_Colon))]; output = [...output, ...generateSectionOptionsOutput(sys, getDiagnosticText(Diagnostics.BUILD_OPTIONS), buildOptions, /*subCategory*/ false, formatMessage(/*_dummy*/ undefined, Diagnostics.Using_build_b_will_make_tsc_behave_more_like_a_build_orchestrator_than_a_compiler_This_is_used_to_trigger_building_composite_projects_which_you_can_learn_more_about_at_0, "https://aka.ms/tsc-composite-builds"))]; @@ -398,32 +423,31 @@ namespace ts { } function printBuildHelp(sys: System, buildOptions: readonly CommandLineOption[]) { - let output: string[] = [...getHelpHeader(sys)]; + let output: string[] = [...getHeader(sys,`${getDiagnosticText(Diagnostics.tsc_Colon_The_TypeScript_Compiler)} - ${getDiagnosticText(Diagnostics.Version_0, version)}`)]; output = [...output, ...generateSectionOptionsOutput(sys, getDiagnosticText(Diagnostics.BUILD_OPTIONS), buildOptions, /*subCategory*/ false, formatMessage(/*_dummy*/ undefined, Diagnostics.Using_build_b_will_make_tsc_behave_more_like_a_build_orchestrator_than_a_compiler_This_is_used_to_trigger_building_composite_projects_which_you_can_learn_more_about_at_0, "https://aka.ms/tsc-composite-builds"))]; for (const line of output) { sys.write(line); } } - function getHelpHeader(sys: System) { + function getHeader(sys: System, message: string) { const colors = createColors(sys); const header: string[] = []; - const tscExplanation = `${getDiagnosticText(Diagnostics.tsc_Colon_The_TypeScript_Compiler)} - ${getDiagnosticText(Diagnostics.Version_0, version)}`; const terminalWidth = sys.getWidthOfTerminal?.() ?? 0;; const tsIconLength = 5; const tsIconFirstLine = colors.blueBackground(padLeft("", tsIconLength)); const tsIconSecondLine = colors.blueBackground(colors.brightWhite(padLeft("TS ", tsIconLength))); // If we have enough space, print TS icon. - if (terminalWidth >= tscExplanation.length + tsIconLength) { + if (terminalWidth >= message.length + tsIconLength) { // right align of the icon is 120 at most. const rightAlign = terminalWidth > 120 ? 120 : terminalWidth; const leftAlign = rightAlign - tsIconLength; - header.push(padRight(tscExplanation, leftAlign) + tsIconFirstLine + sys.newLine); + header.push(padRight(message, leftAlign) + tsIconFirstLine + sys.newLine); header.push(padLeft("", leftAlign) + tsIconSecondLine + sys.newLine); } else { - header.push(tscExplanation + sys.newLine); + header.push(message + sys.newLine); header.push(sys.newLine); } return header; @@ -749,7 +773,7 @@ namespace ts { function createReportErrorSummary(sys: System, options: CompilerOptions | BuildOptions): ReportEmitErrorSummary | undefined { return shouldBePretty(sys, options) ? - errorCount => sys.write(getErrorSummaryText(errorCount, sys.newLine)) : + (errorCount, filesInError) => sys.write(getErrorSummaryText(errorCount, filesInError, sys.newLine, sys)) : undefined; } @@ -1035,7 +1059,12 @@ namespace ts { } else { sys.writeFile(file, generateTSConfig(options, fileNames, sys.newLine)); - reportDiagnostic(createCompilerDiagnostic(Diagnostics.Successfully_created_a_tsconfig_json_file)); + const output: string[] = [sys.newLine, ...getHeader(sys,"Created a new tsconfig.json with:")]; + output.push(getCompilerOptionsDiffValue(options, sys.newLine) + sys.newLine + sys.newLine); + output.push(`You can learn more at https://aka.ms/tsconfig.json` + sys.newLine); + for (const line of output) { + sys.write(line); + } } return; diff --git a/src/harness/compilerImpl.ts b/src/harness/compilerImpl.ts index 41003356fc2..40e3994b1c9 100644 --- a/src/harness/compilerImpl.ts +++ b/src/harness/compilerImpl.ts @@ -118,11 +118,11 @@ namespace compiler { const input = new documents.TextDocument(sourceFile.fileName, sourceFile.text); this._inputs.push(input); if (!vpath.isDeclaration(sourceFile.fileName)) { - const extname = ts.getOutputExtension(sourceFile, this.options); + const extname = ts.getOutputExtension(sourceFile.fileName, this.options); const outputs: CompilationOutput = { inputs: [input], js: js.get(this.getOutputPath(sourceFile.fileName, extname)), - dts: dts.get(this.getOutputPath(sourceFile.fileName, ".d.ts")), + dts: dts.get(this.getOutputPath(sourceFile.fileName, ts.getDeclarationEmitExtensionForPath(sourceFile.fileName))), map: maps.get(this.getOutputPath(sourceFile.fileName, extname + ".map")) }; @@ -205,7 +205,7 @@ namespace compiler { } else { path = vpath.resolve(this.vfs.cwd(), path); - const outDir = ext === ".d.ts" ? this.options.declarationDir || this.options.outDir : this.options.outDir; + const outDir = ext === ".d.ts" || ext === ".json.d.ts" || ext === ".d.mts" || ext === ".d.cts" ? this.options.declarationDir || this.options.outDir : this.options.outDir; if (outDir) { const common = this.commonSourceDirectory; if (common) { @@ -249,7 +249,7 @@ namespace compiler { } // establish defaults (aligns with old harness) - if (compilerOptions.target === undefined) compilerOptions.target = ts.ScriptTarget.ES3; + if (compilerOptions.target === undefined && compilerOptions.module !== ts.ModuleKind.Node12 && compilerOptions.module !== ts.ModuleKind.NodeNext) compilerOptions.target = ts.ScriptTarget.ES3; if (compilerOptions.newLine === undefined) compilerOptions.newLine = ts.NewLineKind.CarriageReturnLineFeed; if (compilerOptions.skipDefaultLibCheck === undefined) compilerOptions.skipDefaultLibCheck = true; if (compilerOptions.noErrorTruncation === undefined) compilerOptions.noErrorTruncation = true; @@ -264,7 +264,9 @@ namespace compiler { const program = ts.createProgram(rootFiles || [], compilerOptions, host); const emitResult = program.emit(); const postErrors = ts.getPreEmitDiagnostics(program); - const errors = preErrors && (preErrors.length !== postErrors.length) ? [...postErrors, + const longerErrors = ts.length(preErrors) > postErrors.length ? preErrors : postErrors; + const shorterErrors = longerErrors === preErrors ? postErrors : preErrors; + const errors = preErrors && (preErrors.length !== postErrors.length) ? [...shorterErrors!, ts.addRelatedInfo( ts.createCompilerDiagnostic({ category: ts.DiagnosticCategory.Error, @@ -278,7 +280,7 @@ namespace compiler { key: "-1", message: `The excess diagnostics are:` }), - ...ts.filter(postErrors, p => !ts.some(preErrors, p2 => ts.compareDiagnostics(p, p2) === ts.Comparison.EqualTo)) + ...ts.filter(longerErrors!, p => !ts.some(shorterErrors, p2 => ts.compareDiagnostics(p, p2) === ts.Comparison.EqualTo)) ) ] : postErrors; return new CompilationResult(host, compilerOptions, program, emitResult, errors); diff --git a/src/harness/fakesHosts.ts b/src/harness/fakesHosts.ts index 898cfc048c3..58b29a4c7ef 100644 --- a/src/harness/fakesHosts.ts +++ b/src/harness/fakesHosts.ts @@ -95,7 +95,7 @@ namespace fakes { } public readDirectory(path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[], depth?: number): string[] { - return ts.matchFiles(path, extensions, exclude, include, this.useCaseSensitiveFileNames, this.getCurrentDirectory(), depth, path => this.getAccessibleFileSystemEntries(path), path => this.realpath(path), path => this.directoryExists(path)); + return ts.matchFiles(path, extensions, exclude, include, this.useCaseSensitiveFileNames, this.getCurrentDirectory(), depth, path => this.getAccessibleFileSystemEntries(path), path => this.realpath(path)); } public getAccessibleFileSystemEntries(path: string): ts.FileSystemEntries { diff --git a/src/harness/findUpDir.ts b/src/harness/findUpDir.ts new file mode 100644 index 00000000000..3078554f61b --- /dev/null +++ b/src/harness/findUpDir.ts @@ -0,0 +1,21 @@ +namespace Utils { + const { join, resolve, dirname } = require("path") as typeof import("path"); + const { existsSync } = require("fs") as typeof import("fs"); + + // search directories upward to avoid hard-wired paths based on the + // build tree (same as scripts/build/findUpDir.js) + + export function findUpFile(name: string): string { + let dir = __dirname; + while (true) { + const fullPath = join(dir, name); + if (existsSync(fullPath)) return fullPath; + const up = resolve(dir, ".."); + if (up === dir) return name; // it'll fail anyway + dir = up; + } + } + + export const findUpRoot: { (): string; cached?: string; } = () => + findUpRoot.cached ||= dirname(findUpFile("Gulpfile.js")); +} diff --git a/src/harness/fourslashImpl.ts b/src/harness/fourslashImpl.ts index 9e4db5e973b..47ed0e4a1ae 100644 --- a/src/harness/fourslashImpl.ts +++ b/src/harness/fourslashImpl.ts @@ -313,12 +313,13 @@ namespace FourSlash { this.addMatchedInputFile(referenceFilePath, /* extensions */ undefined); }); + const exts = ts.flatten(ts.getSupportedExtensions(compilationOptions)); // Add import files into language-service host ts.forEach(importedFiles, importedFile => { // Fourslash insert tests/cases/fourslash into inputFile.unitName and import statement doesn't require ".ts" // so convert them before making appropriate comparison const importedFilePath = this.basePath + "/" + importedFile.fileName; - this.addMatchedInputFile(importedFilePath, ts.getSupportedExtensions(compilationOptions)); + this.addMatchedInputFile(importedFilePath, exts); }); // Check if no-default-lib flag is false and if so add default library @@ -636,7 +637,9 @@ namespace FourSlash { ts.forEachKey(this.inputFiles, fileName => { if (!ts.isAnySupportedFileExtension(fileName) || Harness.getConfigNameFromFileName(fileName) - || !ts.getAllowJSCompilerOption(this.getProgram().getCompilerOptions()) && !ts.resolutionExtensionIsTSOrJson(ts.extensionFromPath(fileName))) return; + // Can't get a Program in Server tests + || this.testType !== FourSlashTestType.Server && !ts.getAllowJSCompilerOption(this.getProgram().getCompilerOptions()) && !ts.resolutionExtensionIsTSOrJson(ts.extensionFromPath(fileName)) + || ts.getBaseFileName(fileName) === "package.json") return; const errors = this.getDiagnostics(fileName).filter(e => e.category !== ts.DiagnosticCategory.Suggestion); if (errors.length) { this.printErrorLog(/*expectErrors*/ false, errors); @@ -838,7 +841,7 @@ namespace FourSlash { }); } - public verifyInlayHints(expected: readonly FourSlashInterface.VerifyInlayHintsOptions[], span: ts.TextSpan = { start: 0, length: this.activeFile.content.length }, preference?: ts.InlayHintsOptions) { + public verifyInlayHints(expected: readonly FourSlashInterface.VerifyInlayHintsOptions[], span: ts.TextSpan = { start: 0, length: this.activeFile.content.length }, preference?: ts.UserPreferences) { const hints = this.languageService.provideInlayHints(this.activeFile.fileName, span, preference); assert.equal(hints.length, expected.length, "Number of hints"); @@ -911,9 +914,26 @@ namespace FourSlash { } if (ts.hasProperty(options, "exact")) { - ts.Debug.assert(!ts.hasProperty(options, "includes") && !ts.hasProperty(options, "excludes")); + ts.Debug.assert(!ts.hasProperty(options, "includes") && !ts.hasProperty(options, "excludes") && !ts.hasProperty(options, "unsorted")); if (options.exact === undefined) throw this.raiseError("Expected no completions"); - this.verifyCompletionsAreExactly(actualCompletions.entries, toArray(options.exact), options.marker); + this.verifyCompletionsAreExactly(actualCompletions.entries, options.exact, options.marker); + } + else if (options.unsorted) { + ts.Debug.assert(!ts.hasProperty(options, "includes") && !ts.hasProperty(options, "excludes")); + for (const expectedEntry of options.unsorted) { + const name = typeof expectedEntry === "string" ? expectedEntry : expectedEntry.name; + const found = nameToEntries.get(name); + if (!found) throw this.raiseError(`Unsorted: completion '${name}' not found.`); + if (!found.length) throw this.raiseError(`Unsorted: no completions with name '${name}' remain unmatched.`); + this.verifyCompletionEntry(found.shift()!, expectedEntry); + } + if (actualCompletions.entries.length !== options.unsorted.length) { + const unmatched: string[] = []; + nameToEntries.forEach(entries => { + unmatched.push(...entries.map(e => e.name)); + }); + this.raiseError(`Additional completions found not included in 'unsorted': ${unmatched.join("\n")}`); + } } else { if (options.includes) { @@ -940,7 +960,7 @@ namespace FourSlash { expected = typeof expected === "string" ? { name: expected } : expected; if (actual.insertText !== expected.insertText) { - this.raiseError(`Expected completion insert text to be ${expected.insertText}, got ${actual.insertText}`); + this.raiseError(`Completion insert text did not match: ${showTextDiff(expected.insertText || "", actual.insertText || "")}`); } const convertedReplacementSpan = expected.replacementSpan && ts.createTextSpanFromRange(expected.replacementSpan); if (convertedReplacementSpan?.length) { @@ -990,7 +1010,11 @@ namespace FourSlash { } } - private verifyCompletionsAreExactly(actual: readonly ts.CompletionEntry[], expected: readonly FourSlashInterface.ExpectedCompletionEntry[], marker?: ArrayOrSingle) { + private verifyCompletionsAreExactly(actual: readonly ts.CompletionEntry[], expected: ArrayOrSingle | FourSlashInterface.ExpectedExactCompletionsPlus, marker?: ArrayOrSingle) { + if (!ts.isArray(expected)) { + expected = [expected]; + } + // First pass: test that names are right. Then we'll test details. assert.deepEqual(actual.map(a => a.name), expected.map(e => typeof e === "string" ? e : e.name), marker ? "At marker " + JSON.stringify(marker) : undefined); @@ -1001,6 +1025,16 @@ namespace FourSlash { } this.verifyCompletionEntry(completion, expectedCompletion); }); + + // All completions were correct in the sort order given. If that order was produced by a function + // like `completion.globalsPlus`, ensure the "plus" array was sorted in the same way. + const { plusArgument, plusFunctionName } = expected as FourSlashInterface.ExpectedExactCompletionsPlus; + if (plusArgument) { + assert.deepEqual( + plusArgument, + expected.filter(entry => plusArgument.includes(entry)), + `At marker ${JSON.stringify(marker)}: Argument to '${plusFunctionName}' was incorrectly sorted.`); + } } /** Use `getProgram` instead of accessing this directly. */ @@ -1244,20 +1278,6 @@ namespace FourSlash { } } - public verifyNoReferences(markerNameOrRange?: string | Range) { - if (markerNameOrRange !== undefined) this.goToMarkerOrRange(markerNameOrRange); - const refs = this.getReferencesAtCaret(); - if (refs && refs.length) { - this.raiseError(`Expected getReferences to fail, but saw references: ${stringify(refs)}`); - } - } - - /** @deprecated - use `verify.baselineFindAllReferences()` instead. */ - public verifyGetReferencesForServerTest(expected: readonly ts.ReferenceEntry[]): void { - const refs = this.getReferencesAtCaret(); - assert.deepEqual(refs, expected); - } - public verifySingleReferenceGroup(definition: FourSlashInterface.ReferenceGroupDefinition, ranges?: Range[] | string) { ranges = ts.isString(ranges) ? this.rangesByText().get(ranges)! : ranges || this.getRanges(); this.verifyReferenceGroups(ranges, [{ definition, ranges }]); @@ -1328,7 +1348,11 @@ namespace FourSlash { if (options) { this.configure(options); } - return this.languageService.getCompletionsAtPosition(this.activeFile.fileName, this.currentCaretPosition, options); + return this.languageService.getCompletionsAtPosition( + this.activeFile.fileName, + this.currentCaretPosition, + options, + this.formatCodeSettings); } private getCompletionEntryDetails(entryName: string, source: string | undefined, data: ts.CompletionEntryData | undefined, preferences?: ts.UserPreferences): ts.CompletionEntryDetails | undefined { @@ -1338,10 +1362,6 @@ namespace FourSlash { return this.languageService.getCompletionEntryDetails(this.activeFile.fileName, this.currentCaretPosition, entryName, this.formatCodeSettings, source, preferences, data); } - private getReferencesAtCaret() { - return this.languageService.getReferencesAtPosition(this.activeFile.fileName, this.currentCaretPosition); - } - private findReferencesAtCaret() { return this.languageService.findReferences(this.activeFile.fileName, this.currentCaretPosition); } @@ -1377,11 +1397,11 @@ namespace FourSlash { })); } - public verifyQuickInfoAt(markerName: string | Range, expectedText: string, expectedDocumentation?: string) { + public verifyQuickInfoAt(markerName: string | Range, expectedText: string, expectedDocumentation?: string, expectedTags?: {name: string; text: string;}[]) { if (typeof markerName === "string") this.goToMarker(markerName); else this.goToRangeStart(markerName); - this.verifyQuickInfoString(expectedText, expectedDocumentation); + this.verifyQuickInfoString(expectedText, expectedDocumentation, expectedTags); } public verifyQuickInfos(namesAndTexts: { [name: string]: string | [string, string] }) { @@ -1400,16 +1420,29 @@ namespace FourSlash { } } - public verifyQuickInfoString(expectedText: string, expectedDocumentation?: string) { + public verifyQuickInfoString(expectedText: string, expectedDocumentation?: string, expectedTags?: { name: string; text: string; }[]) { if (expectedDocumentation === "") { - throw new Error("Use 'undefined' instead"); + throw new Error("Use 'undefined' instead of empty string for `expectedDocumentation`"); } const actualQuickInfo = this.languageService.getQuickInfoAtPosition(this.activeFile.fileName, this.currentCaretPosition); - const actualQuickInfoText = actualQuickInfo ? ts.displayPartsToString(actualQuickInfo.displayParts) : ""; - const actualQuickInfoDocumentation = actualQuickInfo ? ts.displayPartsToString(actualQuickInfo.documentation) : ""; + const actualQuickInfoText = ts.displayPartsToString(actualQuickInfo?.displayParts); + const actualQuickInfoDocumentation = ts.displayPartsToString(actualQuickInfo?.documentation); + const actualQuickInfoTags = actualQuickInfo?.tags?.map(tag => ({ name: tag.name, text: ts.displayPartsToString(tag.text) })); assert.equal(actualQuickInfoText, expectedText, this.messageAtLastKnownMarker("quick info text")); assert.equal(actualQuickInfoDocumentation, expectedDocumentation || "", this.assertionMessageAtLastKnownMarker("quick info doc")); + if (!expectedTags) { + // Skip if `expectedTags` is not given + } + else if (!actualQuickInfoTags) { + assert.equal(actualQuickInfoTags, expectedTags, this.messageAtLastKnownMarker("QuickInfo tags")); + } + else { + ts.zipWith(expectedTags, actualQuickInfoTags, (expectedTag, actualTag) => { + assert.equal(expectedTag.name, actualTag.name); + assert.equal(expectedTag.text, actualTag.text, this.messageAtLastKnownMarker("QuickInfo tag " + actualTag.name)); + }); + } } public verifyQuickInfoDisplayParts(kind: string, kindModifiers: string, textSpan: TextSpan, @@ -1927,7 +1960,7 @@ namespace FourSlash { } public baselineSyntacticAndSemanticDiagnostics() { - const files = this.getCompilerTestFiles(); + const files = ts.filter(this.getCompilerTestFiles(), f => !ts.endsWith(f.unitName, ".json")); const result = this.getSyntacticDiagnosticBaselineText(files) + Harness.IO.newLine() + Harness.IO.newLine() @@ -3113,11 +3146,12 @@ namespace FourSlash { }); } - public verifyImportFixModuleSpecifiers(markerName: string, moduleSpecifiers: string[]) { + public verifyImportFixModuleSpecifiers(markerName: string, moduleSpecifiers: string[], preferences?: ts.UserPreferences) { const marker = this.getMarkerByName(markerName); const codeFixes = this.getCodeFixes(marker.fileName, ts.Diagnostics.Cannot_find_name_0.code, { includeCompletionsForModuleExports: true, - includeCompletionsWithInsertText: true + includeCompletionsWithInsertText: true, + ...preferences, }, marker.position).filter(f => f.fixName === ts.codefix.importFixName); const actualModuleSpecifiers = ts.mapDefined(codeFixes, fix => { diff --git a/src/harness/fourslashInterfaceImpl.ts b/src/harness/fourslashInterfaceImpl.ts index c7a2f1d0f03..98b61cf2fb5 100644 --- a/src/harness/fourslashInterfaceImpl.ts +++ b/src/harness/fourslashInterfaceImpl.ts @@ -255,16 +255,16 @@ namespace FourSlashInterface { } } - public getInlayHints(expected: readonly VerifyInlayHintsOptions[], span: ts.TextSpan, preference?: ts.InlayHintsOptions) { + public getInlayHints(expected: readonly VerifyInlayHintsOptions[], span: ts.TextSpan, preference?: ts.UserPreferences) { this.state.verifyInlayHints(expected, span, preference); } - public quickInfoIs(expectedText: string, expectedDocumentation?: string) { - this.state.verifyQuickInfoString(expectedText, expectedDocumentation); + public quickInfoIs(expectedText: string, expectedDocumentation?: string, expectedTags?: { name: string; text: string; }[]) { + this.state.verifyQuickInfoString(expectedText, expectedDocumentation, expectedTags); } - public quickInfoAt(markerName: string | FourSlash.Range, expectedText: string, expectedDocumentation?: string) { - this.state.verifyQuickInfoAt(markerName, expectedText, expectedDocumentation); + public quickInfoAt(markerName: string | FourSlash.Range, expectedText: string, expectedDocumentation?: string, expectedTags?: { name: string; text: string; }[]) { + this.state.verifyQuickInfoAt(markerName, expectedText, expectedDocumentation, expectedTags); } public quickInfos(namesAndTexts: { [name: string]: string }) { @@ -356,18 +356,6 @@ namespace FourSlashInterface { this.state.verifyBaselineGetFileReferences(fileName); } - public referenceGroups(starts: ArrayOrSingle | ArrayOrSingle, parts: ReferenceGroup[]) { - this.state.verifyReferenceGroups(starts, parts); - } - - public noReferences(markerNameOrRange?: string | FourSlash.Range) { - this.state.verifyNoReferences(markerNameOrRange); - } - - public getReferencesForServerTest(expected: readonly ts.ReferenceEntry[]) { - this.state.verifyGetReferencesForServerTest(expected); - } - public singleReferenceGroup(definition: ReferenceGroupDefinition, ranges?: FourSlash.Range[] | string) { this.state.verifySingleReferenceGroup(definition, ranges); } @@ -490,8 +478,8 @@ namespace FourSlashInterface { this.state.verifyImportFixAtPosition(expectedTextArray, errorCode, preferences); } - public importFixModuleSpecifiers(marker: string, moduleSpecifiers: string[]) { - this.state.verifyImportFixModuleSpecifiers(marker, moduleSpecifiers); + public importFixModuleSpecifiers(marker: string, moduleSpecifiers: string[], preferences?: ts.UserPreferences) { + this.state.verifyImportFixModuleSpecifiers(marker, moduleSpecifiers, preferences); } public navigationBar(json: any, options?: { checkSpans?: boolean }) { @@ -1036,8 +1024,48 @@ namespace FourSlashInterface { export const keywordsWithUndefined: readonly ExpectedCompletionEntryObject[] = res; export const keywords: readonly ExpectedCompletionEntryObject[] = keywordsWithUndefined.filter(k => k.name !== "undefined"); - export const typeKeywords: readonly ExpectedCompletionEntryObject[] = - ["false", "null", "true", "void", "asserts", "any", "boolean", "infer", "keyof", "never", "readonly", "number", "object", "string", "symbol", "undefined", "unique", "unknown", "bigint"].map(keywordEntry); + export const typeKeywords: readonly ExpectedCompletionEntryObject[] = [ + "any", + "asserts", + "bigint", + "boolean", + "false", + "infer", + "keyof", + "never", + "null", + "number", + "object", + "readonly", + "string", + "symbol", + "true", + "undefined", + "unique", + "unknown", + "void", + ].map(keywordEntry); + + export function sorted(entries: readonly ExpectedCompletionEntry[]): readonly ExpectedCompletionEntry[] { + return ts.stableSort(entries, compareExpectedCompletionEntries); + } + + // If you want to use a function like `globalsPlus`, that function needs to sort + // the concatted array since the entries provided as "plus" could be interleaved + // among the "globals." However, we still want to assert that the "plus" array + // was internally sorted correctly, so we tack it onto the sorted concatted array + // so `verify.completions` can assert that it represents the same order as the response. + function combineExpectedCompletionEntries( + functionName: string, + providedByHarness: readonly ExpectedCompletionEntry[], + providedByTest: readonly ExpectedCompletionEntry[], + ): ExpectedExactCompletionsPlus { + return Object.assign(sorted([...providedByHarness, ...providedByTest]), { plusFunctionName: functionName, plusArgument: providedByTest }); + } + + export function typeKeywordsPlus(plus: readonly ExpectedCompletionEntry[]) { + return combineExpectedCompletionEntries("typeKeywordsPlus", typeKeywords, plus); + } const globalTypeDecls: readonly ExpectedCompletionEntryObject[] = [ interfaceEntry("Symbol"), @@ -1061,6 +1089,8 @@ namespace FourSlashInterface { interfaceEntry("NumberConstructor"), interfaceEntry("TemplateStringsArray"), interfaceEntry("ImportMeta"), + interfaceEntry("ImportCallOptions"), + interfaceEntry("ImportAssertions"), varEntry("Math"), varEntry("Date"), interfaceEntry("DateConstructor"), @@ -1095,6 +1125,7 @@ namespace FourSlashInterface { typeEntry("PromiseConstructorLike"), interfaceEntry("PromiseLike"), interfaceEntry("Promise"), + typeEntry("Awaited"), interfaceEntry("ArrayLike"), typeEntry("Partial"), typeEntry("Required"), @@ -1148,13 +1179,12 @@ namespace FourSlashInterface { sortText: SortText.GlobalsOrKeywords }; export const globalTypes = globalTypesPlus([]); - export function globalTypesPlus(plus: readonly ExpectedCompletionEntry[]): readonly ExpectedCompletionEntry[] { - return [ - globalThisEntry, - ...globalTypeDecls, - ...plus, - ...typeKeywords, - ]; + export function globalTypesPlus(plus: readonly ExpectedCompletionEntry[]) { + return combineExpectedCompletionEntries( + "globalTypesPlus", + [globalThisEntry, ...globalTypeDecls, ...typeKeywords], + plus + ); } export const typeAssertionKeywords: readonly ExpectedCompletionEntry[] = @@ -1197,13 +1227,25 @@ namespace FourSlashInterface { }); } - export const classElementKeywords: readonly ExpectedCompletionEntryObject[] = - ["private", "protected", "public", "static", "abstract", "async", "constructor", "declare", "get", "readonly", "set", "override"].map(keywordEntry); + export const classElementKeywords: readonly ExpectedCompletionEntryObject[] = [ + "abstract", + "async", + "constructor", + "declare", + "get", + "override", + "private", + "protected", + "public", + "readonly", + "set", + "static", + ].map(keywordEntry); export const classElementInJsKeywords = getInJsKeywords(classElementKeywords); export const constructorParameterKeywords: readonly ExpectedCompletionEntryObject[] = - ["private", "protected", "public", "readonly", "override"].map((name): ExpectedCompletionEntryObject => ({ + ["override", "private", "protected", "public", "readonly"].map((name): ExpectedCompletionEntryObject => ({ name, kind: "keyword", sortText: SortText.GlobalsOrKeywords @@ -1217,7 +1259,11 @@ namespace FourSlashInterface { propertyEntry("length"), { name: "arguments", kind: "property", kindModifiers: "declare", text: "(property) Function.arguments: any" }, propertyEntry("caller"), - ]; + ].sort(compareExpectedCompletionEntries); + + export function functionMembersPlus(plus: readonly ExpectedCompletionEntryObject[]) { + return combineExpectedCompletionEntries("functionMembersPlus", functionMembers, plus); + } export const stringMembers: readonly ExpectedCompletionEntryObject[] = [ methodEntry("toString"), @@ -1241,16 +1287,27 @@ namespace FourSlashInterface { propertyEntry("length"), deprecatedMethodEntry("substr"), methodEntry("valueOf"), - ]; + ].sort(compareExpectedCompletionEntries); export const functionMembersWithPrototype: readonly ExpectedCompletionEntryObject[] = [ - ...functionMembers.slice(0, 4), + ...functionMembers, propertyEntry("prototype"), - ...functionMembers.slice(4), - ]; + ].sort(compareExpectedCompletionEntries); + + export function functionMembersWithPrototypePlus(plus: readonly ExpectedCompletionEntryObject[]) { + return [...functionMembersWithPrototype, ...plus].sort(compareExpectedCompletionEntries); + } // TODO: Shouldn't propose type keywords in statement position export const statementKeywordsWithTypes: readonly ExpectedCompletionEntryObject[] = [ + "abstract", + "any", + "as", + "asserts", + "async", + "await", + "bigint", + "boolean", "break", "case", "catch", @@ -1258,6 +1315,7 @@ namespace FourSlashInterface { "const", "continue", "debugger", + "declare", "default", "delete", "do", @@ -1270,50 +1328,41 @@ namespace FourSlashInterface { "for", "function", "if", + "implements", "import", "in", + "infer", "instanceof", + "interface", + "keyof", + "let", + "module", + "namespace", + "never", "new", "null", + "number", + "object", + "package", + "readonly", "return", + "string", "super", "switch", + "symbol", "this", "throw", "true", "try", + "type", "typeof", + "unique", + "unknown", "var", "void", "while", "with", - "implements", - "interface", - "let", - "package", "yield", - "abstract", - "as", - "asserts", - "any", - "async", - "await", - "boolean", - "declare", - "infer", - "keyof", - "module", - "namespace", - "never", - "readonly", - "number", - "object", - "string", - "symbol", - "type", - "unique", - "unknown", - "bigint", ].map(keywordEntry); export const statementKeywords: readonly ExpectedCompletionEntryObject[] = statementKeywordsWithTypes.filter(k => { @@ -1335,51 +1384,54 @@ namespace FourSlashInterface { export const statementInJsKeywords = getInJsKeywords(statementKeywords); export const globalsVars: readonly ExpectedCompletionEntryObject[] = [ - functionEntry("eval"), - functionEntry("parseInt"), - functionEntry("parseFloat"), - functionEntry("isNaN"), - functionEntry("isFinite"), + varEntry("Array"), + varEntry("ArrayBuffer"), + varEntry("Boolean"), + varEntry("DataView"), + varEntry("Date"), functionEntry("decodeURI"), functionEntry("decodeURIComponent"), functionEntry("encodeURI"), functionEntry("encodeURIComponent"), - deprecatedFunctionEntry("escape"), - deprecatedFunctionEntry("unescape"), - varEntry("NaN"), - varEntry("Infinity"), - varEntry("Object"), - varEntry("Function"), - varEntry("String"), - varEntry("Boolean"), - varEntry("Number"), - varEntry("Math"), - varEntry("Date"), - varEntry("RegExp"), varEntry("Error"), + deprecatedFunctionEntry("escape"), + functionEntry("eval"), varEntry("EvalError"), - varEntry("RangeError"), - varEntry("ReferenceError"), - varEntry("SyntaxError"), - varEntry("TypeError"), - varEntry("URIError"), - varEntry("JSON"), - varEntry("Array"), - varEntry("ArrayBuffer"), - varEntry("DataView"), - varEntry("Int8Array"), - varEntry("Uint8Array"), - varEntry("Uint8ClampedArray"), - varEntry("Int16Array"), - varEntry("Uint16Array"), - varEntry("Int32Array"), - varEntry("Uint32Array"), varEntry("Float32Array"), varEntry("Float64Array"), + varEntry("Function"), + varEntry("Infinity"), moduleEntry("Intl"), + varEntry("Int16Array"), + varEntry("Int32Array"), + varEntry("Int8Array"), + functionEntry("isFinite"), + functionEntry("isNaN"), + varEntry("JSON"), + varEntry("Math"), + varEntry("NaN"), + varEntry("Number"), + varEntry("Object"), + functionEntry("parseFloat"), + functionEntry("parseInt"), + varEntry("RangeError"), + varEntry("ReferenceError"), + varEntry("RegExp"), + varEntry("String"), + varEntry("SyntaxError"), + varEntry("TypeError"), + varEntry("Uint16Array"), + varEntry("Uint32Array"), + varEntry("Uint8Array"), + varEntry("Uint8ClampedArray"), + deprecatedFunctionEntry("unescape"), + varEntry("URIError"), ]; const globalKeywordsInsideFunction: readonly ExpectedCompletionEntryObject[] = [ + "as", + "async", + "await", "break", "case", "catch", @@ -1399,11 +1451,15 @@ namespace FourSlashInterface { "for", "function", "if", + "implements", "import", "in", "instanceof", + "interface", + "let", "new", "null", + "package", "return", "super", "switch", @@ -1416,45 +1472,54 @@ namespace FourSlashInterface { "void", "while", "with", - "implements", - "interface", - "let", - "package", "yield", - "as", - "async", - "await", ].map(keywordEntry); + function compareExpectedCompletionEntries(a: ExpectedCompletionEntry, b: ExpectedCompletionEntry) { + const aSortText = typeof a !== "string" && a.sortText || ts.Completions.SortText.LocationPriority; + const bSortText = typeof b !== "string" && b.sortText || ts.Completions.SortText.LocationPriority; + const bySortText = ts.compareStringsCaseSensitiveUI(aSortText, bSortText); + if (bySortText !== ts.Comparison.EqualTo) return bySortText; + return ts.compareStringsCaseSensitiveUI(typeof a === "string" ? a : a.name, typeof b === "string" ? b : b.name); + } + export const undefinedVarEntry: ExpectedCompletionEntryObject = { name: "undefined", kind: "var", sortText: SortText.GlobalsOrKeywords }; // TODO: many of these are inappropriate to always provide - export const globalsInsideFunction = (plus: readonly ExpectedCompletionEntry[]): readonly ExpectedCompletionEntry[] => [ + export const globalsInsideFunction = (plus: readonly ExpectedCompletionEntry[], options?: { noLib?: boolean }): readonly ExpectedCompletionEntry[] => [ { name: "arguments", kind: "local var" }, ...plus, globalThisEntry, - ...globalsVars, + ...options?.noLib ? [] : globalsVars, undefinedVarEntry, ...globalKeywordsInsideFunction, - ]; + ].sort(compareExpectedCompletionEntries); const globalInJsKeywordsInsideFunction = getInJsKeywords(globalKeywordsInsideFunction); // TODO: many of these are inappropriate to always provide - export const globalsInJsInsideFunction = (plus: readonly ExpectedCompletionEntry[]): readonly ExpectedCompletionEntry[] => [ + export const globalsInJsInsideFunction = (plus: readonly ExpectedCompletionEntry[], options?: { noLib?: boolean }): readonly ExpectedCompletionEntry[] => [ { name: "arguments", kind: "local var" }, globalThisEntry, - ...globalsVars, + ...options?.noLib ? [] : globalsVars, ...plus, undefinedVarEntry, ...globalInJsKeywordsInsideFunction, - ]; + ].sort(compareExpectedCompletionEntries); // TODO: many of these are inappropriate to always provide export const globalKeywords: readonly ExpectedCompletionEntryObject[] = [ + "abstract", + "any", + "as", + "asserts", + "async", + "await", + "bigint", + "boolean", "break", "case", "catch", @@ -1462,6 +1527,7 @@ namespace FourSlashInterface { "const", "continue", "debugger", + "declare", "default", "delete", "do", @@ -1474,55 +1540,49 @@ namespace FourSlashInterface { "for", "function", "if", + "implements", "import", "in", + "infer", "instanceof", + "interface", + "keyof", + "let", + "module", + "namespace", + "never", "new", "null", + "number", + "object", + "package", + "readonly", "return", + "string", "super", "switch", + "symbol", "this", "throw", "true", "try", + "type", "typeof", + "unique", + "unknown", "var", "void", "while", "with", - "implements", - "interface", - "let", - "package", "yield", - "abstract", - "as", - "asserts", - "any", - "async", - "await", - "boolean", - "declare", - "infer", - "keyof", - "module", - "namespace", - "never", - "readonly", - "number", - "object", - "string", - "symbol", - "type", - "unique", - "unknown", - "bigint", ].map(keywordEntry); export const globalInJsKeywords = getInJsKeywords(globalKeywords); export const insideMethodKeywords: readonly ExpectedCompletionEntryObject[] = [ + "as", + "async", + "await", "break", "case", "catch", @@ -1542,11 +1602,15 @@ namespace FourSlashInterface { "for", "function", "if", + "implements", "import", "in", "instanceof", + "interface", + "let", "new", "null", + "package", "return", "super", "switch", @@ -1559,14 +1623,7 @@ namespace FourSlashInterface { "void", "while", "with", - "implements", - "interface", - "let", - "package", "yield", - "as", - "async", - "await", ].map(keywordEntry); export const insideMethodInJsKeywords = getInJsKeywords(insideMethodKeywords); @@ -1576,34 +1633,31 @@ namespace FourSlashInterface { ...globalsVars, undefinedVarEntry, ...globalKeywords - ]; + ].sort(compareExpectedCompletionEntries); export const globalsInJs: readonly ExpectedCompletionEntryObject[] = [ globalThisEntry, ...globalsVars, undefinedVarEntry, ...globalInJsKeywords - ]; + ].sort(compareExpectedCompletionEntries); - export function globalsPlus(plus: readonly ExpectedCompletionEntry[]): readonly ExpectedCompletionEntry[] { - const firstEntry = plus[0]; - const afterUndefined = typeof firstEntry !== "string" && firstEntry.sortText! > undefinedVarEntry.sortText!; - return [ + export function globalsPlus(plus: readonly ExpectedCompletionEntry[], options?: { noLib?: boolean }) { + return combineExpectedCompletionEntries("globalsPlus", [ globalThisEntry, - ...globalsVars, - ...afterUndefined ? ts.emptyArray : plus, + ...options?.noLib ? [] : globalsVars, undefinedVarEntry, - ...afterUndefined ? plus : ts.emptyArray, - ...globalKeywords]; + ...globalKeywords, + ], plus); } - export function globalsInJsPlus(plus: readonly ExpectedCompletionEntry[]): readonly ExpectedCompletionEntry[] { - return [ + export function globalsInJsPlus(plus: readonly ExpectedCompletionEntry[], options?: { noLib?: boolean }) { + return combineExpectedCompletionEntries("globalsInJsPlus", [ globalThisEntry, - ...globalsVars, - ...plus, + ...options?.noLib ? [] : globalsVars, undefinedVarEntry, - ...globalInJsKeywords]; + ...globalInJsKeywords, + ], plus); } } @@ -1642,12 +1696,18 @@ namespace FourSlashInterface { readonly sortText?: ts.Completions.SortText; } + export type ExpectedExactCompletionsPlus = readonly ExpectedCompletionEntry[] & { + plusFunctionName: string, + plusArgument: readonly ExpectedCompletionEntry[] + }; + export interface VerifyCompletionsOptions { readonly marker?: ArrayOrSingle; readonly isNewIdentifierLocation?: boolean; // Always tested readonly isGlobalCompletion?: boolean; // Only tested if set readonly optionalReplacementSpan?: FourSlash.Range; // Only tested if set - readonly exact?: ArrayOrSingle; + readonly exact?: ArrayOrSingle | ExpectedExactCompletionsPlus; + readonly unsorted?: readonly ExpectedCompletionEntry[]; readonly includes?: ArrayOrSingle; readonly excludes?: ArrayOrSingle; readonly preferences?: ts.UserPreferences; diff --git a/src/harness/harnessIO.ts b/src/harness/harnessIO.ts index e4730a2e9fd..99da90f44c3 100644 --- a/src/harness/harnessIO.ts +++ b/src/harness/harnessIO.ts @@ -41,6 +41,7 @@ namespace Harness { export const virtualFileSystemRoot = "/"; function createNodeIO(): IO { + const workspaceRoot = Utils.findUpRoot(); let fs: any, pathModule: any; if (require) { fs = require("fs"); @@ -154,7 +155,7 @@ namespace Harness { log: s => console.log(s), args: () => ts.sys.args, getExecutingFilePath: () => ts.sys.getExecutingFilePath(), - getWorkspaceRoot: () => vpath.resolve(__dirname, "../.."), + getWorkspaceRoot: () => workspaceRoot, exit: exitCode => ts.sys.exit(exitCode), readDirectory: (path, extension, exclude, include, depth) => ts.sys.readDirectory(path, extension, exclude, include, depth), getAccessibleFileSystemEntries, @@ -271,7 +272,7 @@ namespace Harness { } export function getDefaultLibFileName(options: ts.CompilerOptions): string { - switch (options.target) { + switch (ts.getEmitScriptTarget(options)) { case ts.ScriptTarget.ESNext: case ts.ScriptTarget.ES2017: return "lib.es2017.d.ts"; @@ -303,21 +304,21 @@ namespace Harness { // Additional options not already in ts.optionDeclarations const harnessOptionDeclarations: ts.CommandLineOption[] = [ - { name: "allowNonTsExtensions", type: "boolean" }, - { name: "useCaseSensitiveFileNames", type: "boolean" }, + { name: "allowNonTsExtensions", type: "boolean", defaultValueDescription: false }, + { name: "useCaseSensitiveFileNames", type: "boolean", defaultValueDescription: false }, { name: "baselineFile", type: "string" }, { name: "includeBuiltFile", type: "string" }, { name: "fileName", type: "string" }, { name: "libFiles", type: "string" }, - { name: "noErrorTruncation", type: "boolean" }, - { name: "suppressOutputPathCheck", type: "boolean" }, - { name: "noImplicitReferences", type: "boolean" }, + { name: "noErrorTruncation", type: "boolean", defaultValueDescription: false }, + { name: "suppressOutputPathCheck", type: "boolean", defaultValueDescription: false }, + { name: "noImplicitReferences", type: "boolean", defaultValueDescription: false }, { name: "currentDirectory", type: "string" }, { name: "symlink", type: "string" }, { name: "link", type: "string" }, - { name: "noTypesAndSymbols", type: "boolean" }, + { name: "noTypesAndSymbols", type: "boolean", defaultValueDescription: false }, // Emitted js baseline will print full paths for every output file - { name: "fullEmitPaths", type: "boolean" } + { name: "fullEmitPaths", type: "boolean", defaultValueDescription: false }, ]; let optionsIndex: ts.ESMap; @@ -391,7 +392,7 @@ namespace Harness { symlinks?: vfs.FileSet ): compiler.CompilationResult { const options: ts.CompilerOptions & HarnessOptions = compilerOptions ? ts.cloneCompilerOptions(compilerOptions) : { noResolve: false }; - options.target = options.target || ts.ScriptTarget.ES3; + options.target = ts.getEmitScriptTarget(options); options.newLine = options.newLine || ts.NewLineKind.CarriageReturnLineFeed; options.noErrorTruncation = true; options.skipDefaultLibCheck = typeof options.skipDefaultLibCheck === "undefined" ? true : options.skipDefaultLibCheck; @@ -505,7 +506,7 @@ namespace Harness { sourceFileName = outFile; } - const dTsFileName = ts.removeFileExtension(sourceFileName) + ts.Extension.Dts; + const dTsFileName = ts.removeFileExtension(sourceFileName) + ts.getDeclarationEmitExtensionForPath(sourceFileName); return result.dts.get(dTsFileName); } @@ -536,7 +537,7 @@ namespace Harness { outputLines += content; } if (pretty) { - outputLines += ts.getErrorSummaryText(ts.getErrorCountForSummary(diagnostics), IO.newLine()); + outputLines += ts.getErrorSummaryText(ts.getErrorCountForSummary(diagnostics), ts.getFilesInErrorForSummary(diagnostics), IO.newLine(), { getCurrentDirectory: () => "" }); } return outputLines; } @@ -897,7 +898,7 @@ namespace Harness { jsCode += "\r\n"; } if (!result.diagnostics.length && !ts.endsWith(file.file, ts.Extension.Json)) { - const fileParseResult = ts.createSourceFile(file.file, file.text, options.target || ts.ScriptTarget.ES3, /*parentNodes*/ false, ts.endsWith(file.file, "x") ? ts.ScriptKind.JSX : ts.ScriptKind.JS); + const fileParseResult = ts.createSourceFile(file.file, file.text, ts.getEmitScriptTarget(options), /*parentNodes*/ false, ts.endsWith(file.file, "x") ? ts.ScriptKind.JSX : ts.ScriptKind.JS); if (ts.length(fileParseResult.parseDiagnostics)) { jsCode += getErrorBaseline([file.asTestFile()], fileParseResult.parseDiagnostics); return; diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 6f7245b01a9..dc41722f26c 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -472,8 +472,8 @@ namespace Harness.LanguageService { const responseFormat = format || ts.SemanticClassificationFormat.Original; return unwrapJSONCallResult(this.shim.getEncodedSemanticClassifications(fileName, span.start, span.length, responseFormat)); } - getCompletionsAtPosition(fileName: string, position: number, preferences: ts.UserPreferences | undefined): ts.CompletionInfo { - return unwrapJSONCallResult(this.shim.getCompletionsAtPosition(fileName, position, preferences)); + getCompletionsAtPosition(fileName: string, position: number, preferences: ts.UserPreferences | undefined, formattingSettings: ts.FormatCodeSettings | undefined): ts.CompletionInfo { + return unwrapJSONCallResult(this.shim.getCompletionsAtPosition(fileName, position, preferences, formattingSettings)); } getCompletionEntryDetails(fileName: string, position: number, entryName: string, formatOptions: ts.FormatCodeOptions | undefined, source: string | undefined, preferences: ts.UserPreferences | undefined, data: ts.CompletionEntryData | undefined): ts.CompletionEntryDetails { return unwrapJSONCallResult(this.shim.getCompletionEntryDetails(fileName, position, entryName, JSON.stringify(formatOptions), source, preferences, data)); @@ -600,7 +600,7 @@ namespace Harness.LanguageService { provideCallHierarchyOutgoingCalls(fileName: string, position: number) { return unwrapJSONCallResult(this.shim.provideCallHierarchyOutgoingCalls(fileName, position)); } - provideInlayHints(fileName: string, span: ts.TextSpan, preference: ts.InlayHintsOptions) { + provideInlayHints(fileName: string, span: ts.TextSpan, preference: ts.UserPreferences) { return unwrapJSONCallResult(this.shim.provideInlayHints(fileName, span, preference)); } getEmitOutput(fileName: string): ts.EmitOutput { diff --git a/src/harness/tsconfig.json b/src/harness/tsconfig.json index 08b9a0bc65e..37c24527730 100644 --- a/src/harness/tsconfig.json +++ b/src/harness/tsconfig.json @@ -28,6 +28,7 @@ "evaluatorImpl.ts", "fakesHosts.ts", "client.ts", + "findUpDir.ts", "runnerbase.ts", "sourceMapRecorder.ts", diff --git a/src/harness/virtualFileSystemWithWatch.ts b/src/harness/virtualFileSystemWithWatch.ts index 77e3741d1fc..62c6cd042ef 100644 --- a/src/harness/virtualFileSystemWithWatch.ts +++ b/src/harness/virtualFileSystemWithWatch.ts @@ -922,7 +922,7 @@ interface Array { length: number; [n: number]: T; }` }); } return { directories, files }; - }, path => this.realpath(path), path => this.directoryExists(path)); + }, path => this.realpath(path)); } createHash(s: string): string { diff --git a/src/harness/vpathUtil.ts b/src/harness/vpathUtil.ts index 31419e976ff..44ef5b0f6af 100644 --- a/src/harness/vpathUtil.ts +++ b/src/harness/vpathUtil.ts @@ -108,7 +108,7 @@ namespace vpath { } export function isDeclaration(path: string) { - return extname(path, ".d.ts", /*ignoreCase*/ false).length > 0; + return ts.fileExtensionIsOneOf(path, [ts.Extension.Dmts, ts.Extension.Dcts, ts.Extension.Dts]); } export function isSourceMap(path: string) { diff --git a/src/jsTyping/jsTyping.ts b/src/jsTyping/jsTyping.ts index e23622ee9fc..141212eca20 100644 --- a/src/jsTyping/jsTyping.ts +++ b/src/jsTyping/jsTyping.ts @@ -9,7 +9,6 @@ namespace ts.JsTyping { } interface PackageJson { - _requiredBy?: string[]; dependencies?: MapLike; devDependencies?: MapLike; name?: string; @@ -152,17 +151,8 @@ namespace ts.JsTyping { const possibleSearchDirs = new Set(fileNames.map(getDirectoryPath)); possibleSearchDirs.add(projectRootPath); possibleSearchDirs.forEach((searchDir) => { - const packageJsonPath = combinePaths(searchDir, "package.json"); - getTypingNamesFromJson(packageJsonPath, filesToWatch); - - const bowerJsonPath = combinePaths(searchDir, "bower.json"); - getTypingNamesFromJson(bowerJsonPath, filesToWatch); - - const bowerComponentsPath = combinePaths(searchDir, "bower_components"); - getTypingNamesFromPackagesFolder(bowerComponentsPath, filesToWatch); - - const nodeModulesPath = combinePaths(searchDir, "node_modules"); - getTypingNamesFromPackagesFolder(nodeModulesPath, filesToWatch); + getTypingNames(searchDir, "bower.json", "bower_components", filesToWatch); + getTypingNames(searchDir, "package.json", "node_modules", filesToWatch); }); if(!typeAcquisition.disableFilenameBasedTypeAcquisition) { getTypingNamesFromSourceFileNames(fileNames); @@ -214,17 +204,104 @@ namespace ts.JsTyping { } /** - * Get the typing info from common package manager json files like package.json or bower.json + * Adds inferred typings from manifest/module pairs (think package.json + node_modules) + * + * @param projectRootPath is the path to the directory where to look for package.json, bower.json and other typing information + * @param manifestName is the name of the manifest (package.json or bower.json) + * @param modulesDirName is the directory name for modules (node_modules or bower_components). Should be lowercase! + * @param filesToWatch are the files to watch for changes. We will push things into this array. */ - function getTypingNamesFromJson(jsonPath: string, filesToWatch: Push) { - if (!host.fileExists(jsonPath)) { + function getTypingNames(projectRootPath: string, manifestName: string, modulesDirName: string, filesToWatch: string[]): void { + // First, we check the manifests themselves. They're not + // _required_, but they allow us to do some filtering when dealing + // with big flat dep directories. + const manifestPath = combinePaths(projectRootPath, manifestName); + let manifest; + let manifestTypingNames; + if (host.fileExists(manifestPath)) { + filesToWatch.push(manifestPath); + manifest = readConfigFile(manifestPath, path => host.readFile(path)).config; + manifestTypingNames = flatMap([manifest.dependencies, manifest.devDependencies, manifest.optionalDependencies, manifest.peerDependencies], getOwnKeys); + addInferredTypings(manifestTypingNames, `Typing names in '${manifestPath}' dependencies`); + } + + // Now we scan the directories for typing information in + // already-installed dependencies (if present). Note that this + // step happens regardless of whether a manifest was present, + // which is certainly a valid configuration, if an unusual one. + const packagesFolderPath = combinePaths(projectRootPath, modulesDirName); + filesToWatch.push(packagesFolderPath); + if (!host.directoryExists(packagesFolderPath)) { return; } - filesToWatch.push(jsonPath); - const jsonConfig: PackageJson = readConfigFile(jsonPath, path => host.readFile(path)).config; - const jsonTypingNames = flatMap([jsonConfig.dependencies, jsonConfig.devDependencies, jsonConfig.optionalDependencies, jsonConfig.peerDependencies], getOwnKeys); - addInferredTypings(jsonTypingNames, `Typing names in '${jsonPath}' dependencies`); + // There's two cases we have to take into account here: + // 1. If manifest is undefined, then we're not using a manifest. + // That means that we should scan _all_ dependencies at the top + // level of the modulesDir. + // 2. If manifest is defined, then we can do some special + // filtering to reduce the amount of scanning we need to do. + // + // Previous versions of this algorithm checked for a `_requiredBy` + // field in the package.json, but that field is only present in + // `npm@>=3 <7`. + + // Package names that do **not** provide their own typings, so + // we'll look them up. + const packageNames: string[] = []; + + const dependencyManifestNames = manifestTypingNames + // This is #1 described above. + ? manifestTypingNames.map(typingName => combinePaths(packagesFolderPath, typingName, manifestName)) + // And #2. Depth = 3 because scoped packages look like `node_modules/@foo/bar/package.json` + : host.readDirectory(packagesFolderPath, [Extension.Json], /*excludes*/ undefined, /*includes*/ undefined, /*depth*/ 3) + .filter(manifestPath => { + if (getBaseFileName(manifestPath) !== manifestName) { + return false; + } + // It's ok to treat + // `node_modules/@foo/bar/package.json` as a manifest, + // but not `node_modules/jquery/nested/package.json`. + // We only assume depth 3 is ok for formally scoped + // packages. So that needs this dance here. + const pathComponents = getPathComponents(normalizePath(manifestPath)); + const isScoped = pathComponents[pathComponents.length - 3][0] === "@"; + return isScoped && pathComponents[pathComponents.length - 4].toLowerCase() === modulesDirName || // `node_modules/@foo/bar` + !isScoped && pathComponents[pathComponents.length - 3].toLowerCase() === modulesDirName; // `node_modules/foo` + }); + + if (log) log(`Searching for typing names in ${packagesFolderPath}; all files: ${JSON.stringify(dependencyManifestNames)}`); + + // Once we have the names of things to look up, we iterate over + // and either collect their included typings, or add them to the + // list of typings we need to look up separately. + for (const manifestPath of dependencyManifestNames) { + const normalizedFileName = normalizePath(manifestPath); + const result = readConfigFile(normalizedFileName, (path: string) => host.readFile(path)); + const manifest: PackageJson = result.config; + + // If the package has its own d.ts typings, those will take precedence. Otherwise the package name will be used + // to download d.ts files from DefinitelyTyped + if (!manifest.name) { + continue; + } + const ownTypes = manifest.types || manifest.typings; + if (ownTypes) { + const absolutePath = getNormalizedAbsolutePath(ownTypes, getDirectoryPath(normalizedFileName)); + if (host.fileExists(absolutePath)) { + if (log) log(` Package '${manifest.name}' provides its own types.`); + inferredTypings.set(manifest.name, absolutePath); + } + else { + if (log) log(` Package '${manifest.name}' provides its own types but they are missing.`); + } + } + else { + packageNames.push(manifest.name); + } + } + + addInferredTypings(packageNames, " Found package names"); } /** @@ -251,58 +328,6 @@ namespace ts.JsTyping { addInferredTyping("react"); } } - - /** - * Infer typing names from packages folder (ex: node_module, bower_components) - * @param packagesFolderPath is the path to the packages folder - */ - function getTypingNamesFromPackagesFolder(packagesFolderPath: string, filesToWatch: Push) { - filesToWatch.push(packagesFolderPath); - - // Todo: add support for ModuleResolutionHost too - if (!host.directoryExists(packagesFolderPath)) { - return; - } - - // depth of 2, so we access `node_modules/foo` but not `node_modules/foo/bar` - const fileNames = host.readDirectory(packagesFolderPath, [Extension.Json], /*excludes*/ undefined, /*includes*/ undefined, /*depth*/ 2); - if (log) log(`Searching for typing names in ${packagesFolderPath}; all files: ${JSON.stringify(fileNames)}`); - const packageNames: string[] = []; - for (const fileName of fileNames) { - const normalizedFileName = normalizePath(fileName); - const baseFileName = getBaseFileName(normalizedFileName); - if (baseFileName !== "package.json" && baseFileName !== "bower.json") { - continue; - } - const result = readConfigFile(normalizedFileName, (path: string) => host.readFile(path)); - const packageJson: PackageJson = result.config; - - // npm 3's package.json contains a "_requiredBy" field - // we should include all the top level module names for npm 2, and only module names whose - // "_requiredBy" field starts with "#" or equals "/" for npm 3. - if (baseFileName === "package.json" && packageJson._requiredBy && - filter(packageJson._requiredBy, (r: string) => r[0] === "#" || r === "/").length === 0) { - continue; - } - - // If the package has its own d.ts typings, those will take precedence. Otherwise the package name will be used - // to download d.ts files from DefinitelyTyped - if (!packageJson.name) { - continue; - } - const ownTypes = packageJson.types || packageJson.typings; - if (ownTypes) { - const absolutePath = getNormalizedAbsolutePath(ownTypes, getDirectoryPath(normalizedFileName)); - if (log) log(` Package '${packageJson.name}' provides its own types.`); - inferredTypings.set(packageJson.name, absolutePath); - } - else { - packageNames.push(packageJson.name); - } - } - addInferredTypings(packageNames, " Found package names"); - } - } export const enum NameValidationResult { diff --git a/src/lib/dom.generated.d.ts b/src/lib/dom.generated.d.ts index f8fd23d0fba..2ef38a96af1 100644 --- a/src/lib/dom.generated.d.ts +++ b/src/lib/dom.generated.d.ts @@ -53,8 +53,8 @@ interface AnimationEventInit extends EventInit { } interface AnimationPlaybackEventInit extends EventInit { - currentTime?: number | null; - timelineTime?: number | null; + currentTime?: CSSNumberish | null; + timelineTime?: CSSNumberish | null; } interface AssignedNodesOptions { @@ -207,7 +207,7 @@ interface ComputedEffectTiming extends EffectTiming { currentIteration?: number | null; endTime?: CSSNumberish; localTime?: CSSNumberish | null; - progress?: CSSNumberish | null; + progress?: number | null; startTime?: CSSNumberish; } @@ -264,8 +264,8 @@ interface CredentialRequestOptions { } interface CryptoKeyPair { - privateKey?: CryptoKey; - publicKey?: CryptoKey; + privateKey: CryptoKey; + publicKey: CryptoKey; } interface CustomEventInit extends EventInit { @@ -467,6 +467,18 @@ interface FileSystemFlags { exclusive?: boolean; } +interface FileSystemGetDirectoryOptions { + create?: boolean; +} + +interface FileSystemGetFileOptions { + create?: boolean; +} + +interface FileSystemRemoveOptions { + recursive?: boolean; +} + interface FocusEventInit extends UIEventInit { relatedTarget?: EventTarget | null; } @@ -671,6 +683,24 @@ interface KeyframeEffectOptions extends EffectTiming { pseudoElement?: string | null; } +interface LockInfo { + clientId?: string; + mode?: LockMode; + name?: string; +} + +interface LockManagerSnapshot { + held?: LockInfo[]; + pending?: LockInfo[]; +} + +interface LockOptions { + ifAvailable?: boolean; + mode?: LockMode; + signal?: AbortSignal; + steal?: boolean; +} + interface MediaCapabilitiesDecodingInfo extends MediaCapabilitiesInfo { configuration?: MediaDecodingConfiguration; } @@ -777,6 +807,7 @@ interface MediaStreamAudioSourceOptions { interface MediaStreamConstraints { audio?: boolean | MediaTrackConstraints; peerIdentity?: string; + preferCurrentTab?: boolean; video?: boolean | MediaTrackConstraints; } @@ -807,6 +838,7 @@ interface MediaTrackCapabilities { interface MediaTrackConstraintSet { aspectRatio?: ConstrainDouble; + autoGainControl?: ConstrainBoolean; channelCount?: ConstrainULong; deviceId?: ConstrainDOMString; echoCancellation?: ConstrainBoolean; @@ -815,6 +847,7 @@ interface MediaTrackConstraintSet { groupId?: ConstrainDOMString; height?: ConstrainULong; latency?: ConstrainDouble; + noiseSuppression?: ConstrainBoolean; sampleRate?: ConstrainULong; sampleSize?: ConstrainULong; suppressLocalAudioPlayback?: ConstrainBoolean; @@ -827,12 +860,14 @@ interface MediaTrackConstraints extends MediaTrackConstraintSet { interface MediaTrackSettings { aspectRatio?: number; + autoGainControl?: boolean; deviceId?: string; echoCancellation?: boolean; facingMode?: string; frameRate?: number; groupId?: string; height?: number; + noiseSuppression?: boolean; restrictOwnAudio?: boolean; sampleRate?: number; sampleSize?: number; @@ -841,12 +876,14 @@ interface MediaTrackSettings { interface MediaTrackSupportedConstraints { aspectRatio?: boolean; + autoGainControl?: boolean; deviceId?: boolean; echoCancellation?: boolean; facingMode?: boolean; frameRate?: boolean; groupId?: boolean; height?: boolean; + noiseSuppression?: boolean; sampleRate?: boolean; sampleSize?: boolean; suppressLocalAudioPlayback?: boolean; @@ -878,33 +915,19 @@ interface MultiCacheQueryOptions extends CacheQueryOptions { } interface MutationObserverInit { - /** - * Set to a list of attribute local names (without namespace) if not all attribute mutations need to be observed and attributes is true or omitted. - */ + /** Set to a list of attribute local names (without namespace) if not all attribute mutations need to be observed and attributes is true or omitted. */ attributeFilter?: string[]; - /** - * Set to true if attributes is true or omitted and target's attribute value before the mutation needs to be recorded. - */ + /** Set to true if attributes is true or omitted and target's attribute value before the mutation needs to be recorded. */ attributeOldValue?: boolean; - /** - * Set to true if mutations to target's attributes are to be observed. Can be omitted if attributeOldValue or attributeFilter is specified. - */ + /** Set to true if mutations to target's attributes are to be observed. Can be omitted if attributeOldValue or attributeFilter is specified. */ attributes?: boolean; - /** - * Set to true if mutations to target's data are to be observed. Can be omitted if characterDataOldValue is specified. - */ + /** Set to true if mutations to target's data are to be observed. Can be omitted if characterDataOldValue is specified. */ characterData?: boolean; - /** - * Set to true if characterData is set to true or omitted and target's data before the mutation needs to be recorded. - */ + /** Set to true if characterData is set to true or omitted and target's data before the mutation needs to be recorded. */ characterDataOldValue?: boolean; - /** - * Set to true if mutations to target's children are to be observed. - */ + /** Set to true if mutations to target's children are to be observed. */ childList?: boolean; - /** - * Set to true if mutations to not just target, but also target's descendants are to be observed. - */ + /** Set to true if mutations to not just target, but also target's descendants are to be observed. */ subtree?: boolean; } @@ -927,7 +950,7 @@ interface NotificationOptions { requireInteraction?: boolean; silent?: boolean; tag?: string; - timestamp?: DOMTimeStamp; + timestamp?: EpochTimeStamp; vibrate?: VibratePattern; } @@ -1094,10 +1117,6 @@ interface PositionOptions { timeout?: number; } -interface PostMessageOptions { - transfer?: any[]; -} - interface ProgressEventInit extends EventInit { lengthComputable?: boolean; loaded?: number; @@ -1163,7 +1182,7 @@ interface PublicKeyCredentialUserEntity extends PublicKeyCredentialEntity { interface PushSubscriptionJSON { endpoint?: string; - expirationTime?: DOMTimeStamp | null; + expirationTime?: EpochTimeStamp | null; keys?: Record; } @@ -1190,7 +1209,7 @@ interface RTCAnswerOptions extends RTCOfferAnswerOptions { } interface RTCCertificateExpiration { - expires?: DOMTimeStamp; + expires?: number; } interface RTCConfiguration { @@ -1455,58 +1474,32 @@ interface RegistrationOptions { } interface RequestInit { - /** - * A BodyInit object or null to set request's body. - */ + /** A BodyInit object or null to set request's body. */ body?: BodyInit | null; - /** - * A string indicating how the request will interact with the browser's cache to set request's cache. - */ + /** A string indicating how the request will interact with the browser's cache to set request's cache. */ cache?: RequestCache; - /** - * A string indicating whether credentials will be sent with the request always, never, or only when sent to a same-origin URL. Sets request's credentials. - */ + /** A string indicating whether credentials will be sent with the request always, never, or only when sent to a same-origin URL. Sets request's credentials. */ credentials?: RequestCredentials; - /** - * A Headers object, an object literal, or an array of two-item arrays to set request's headers. - */ + /** A Headers object, an object literal, or an array of two-item arrays to set request's headers. */ headers?: HeadersInit; - /** - * A cryptographic hash of the resource to be fetched by request. Sets request's integrity. - */ + /** A cryptographic hash of the resource to be fetched by request. Sets request's integrity. */ integrity?: string; - /** - * A boolean to set request's keepalive. - */ + /** A boolean to set request's keepalive. */ keepalive?: boolean; - /** - * A string to set request's method. - */ + /** A string to set request's method. */ method?: string; - /** - * A string to indicate whether the request will use CORS, or will be restricted to same-origin URLs. Sets request's mode. - */ + /** A string to indicate whether the request will use CORS, or will be restricted to same-origin URLs. Sets request's mode. */ mode?: RequestMode; - /** - * A string indicating whether request follows redirects, results in an error upon encountering a redirect, or returns the redirect (in an opaque fashion). Sets request's redirect. - */ + /** A string indicating whether request follows redirects, results in an error upon encountering a redirect, or returns the redirect (in an opaque fashion). Sets request's redirect. */ redirect?: RequestRedirect; - /** - * A string whose value is a same-origin URL, "about:client", or the empty string, to set request's referrer. - */ + /** A string whose value is a same-origin URL, "about:client", or the empty string, to set request's referrer. */ referrer?: string; - /** - * A referrer policy to set request's referrerPolicy. - */ + /** A referrer policy to set request's referrerPolicy. */ referrerPolicy?: ReferrerPolicy; - /** - * An AbortSignal to set request's signal. - */ + /** An AbortSignal to set request's signal. */ signal?: AbortSignal | null; - /** - * Can only be null. Used to disassociate request from any Window. - */ - window?: any; + /** Can only be null. Used to disassociate request from any Window. */ + window?: null; } interface ResizeObserverOptions { @@ -1664,6 +1657,10 @@ interface StreamPipeOptions { signal?: AbortSignal; } +interface StructuredSerializeOptions { + transfer?: Transferable[]; +} + interface SubmitEventInit extends EventInit { submitter?: HTMLElement | null; } @@ -1791,7 +1788,7 @@ interface WheelEventInit extends MouseEventInit { deltaZ?: number; } -interface WindowPostMessageOptions extends PostMessageOptions { +interface WindowPostMessageOptions extends StructuredSerializeOptions { targetOrigin?: string; } @@ -1837,54 +1834,50 @@ interface ANGLE_instanced_arrays { } interface ARIAMixin { - ariaAtomic: string; - ariaAutoComplete: string; - ariaBusy: string; - ariaChecked: string; - ariaColCount: string; - ariaColIndex: string; - ariaColSpan: string; - ariaCurrent: string; - ariaDisabled: string; - ariaExpanded: string; - ariaHasPopup: string; - ariaHidden: string; - ariaKeyShortcuts: string; - ariaLabel: string; - ariaLevel: string; - ariaLive: string; - ariaModal: string; - ariaMultiLine: string; - ariaMultiSelectable: string; - ariaOrientation: string; - ariaPlaceholder: string; - ariaPosInSet: string; - ariaPressed: string; - ariaReadOnly: string; - ariaRequired: string; - ariaRoleDescription: string; - ariaRowCount: string; - ariaRowIndex: string; - ariaRowSpan: string; - ariaSelected: string; - ariaSetSize: string; - ariaSort: string; - ariaValueMax: string; - ariaValueMin: string; - ariaValueNow: string; - ariaValueText: string; + ariaAtomic: string | null; + ariaAutoComplete: string | null; + ariaBusy: string | null; + ariaChecked: string | null; + ariaColCount: string | null; + ariaColIndex: string | null; + ariaColSpan: string | null; + ariaCurrent: string | null; + ariaDisabled: string | null; + ariaExpanded: string | null; + ariaHasPopup: string | null; + ariaHidden: string | null; + ariaKeyShortcuts: string | null; + ariaLabel: string | null; + ariaLevel: string | null; + ariaLive: string | null; + ariaModal: string | null; + ariaMultiLine: string | null; + ariaMultiSelectable: string | null; + ariaOrientation: string | null; + ariaPlaceholder: string | null; + ariaPosInSet: string | null; + ariaPressed: string | null; + ariaReadOnly: string | null; + ariaRequired: string | null; + ariaRoleDescription: string | null; + ariaRowCount: string | null; + ariaRowIndex: string | null; + ariaRowSpan: string | null; + ariaSelected: string | null; + ariaSetSize: string | null; + ariaSort: string | null; + ariaValueMax: string | null; + ariaValueMin: string | null; + ariaValueNow: string | null; + ariaValueText: string | null; } /** A controller object that allows you to abort one or more DOM requests as and when desired. */ interface AbortController { - /** - * Returns the AbortSignal object associated with this object. - */ + /** Returns the AbortSignal object associated with this object. */ readonly signal: AbortSignal; - /** - * Invoking this method will set this object's AbortSignal's aborted flag and signal to any observers that the associated activity is to be aborted. - */ - abort(): void; + /** Invoking this method will set this object's AbortSignal's aborted flag and signal to any observers that the associated activity is to be aborted. */ + abort(reason?: any): void; } declare var AbortController: { @@ -1898,9 +1891,7 @@ interface AbortSignalEventMap { /** A signal object that allows you to communicate with a DOM request (such as a Fetch) and abort it if required via an AbortController object. */ interface AbortSignal extends EventTarget { - /** - * Returns true if this AbortSignal's AbortController has signaled to abort, and false otherwise. - */ + /** Returns true if this AbortSignal's AbortController has signaled to abort, and false otherwise. */ readonly aborted: boolean; onabort: ((this: AbortSignal, ev: Event) => any) | null; addEventListener(type: K, listener: (this: AbortSignal, ev: AbortSignalEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; @@ -1912,28 +1903,19 @@ interface AbortSignal extends EventTarget { declare var AbortSignal: { prototype: AbortSignal; new(): AbortSignal; + // abort(): AbortSignal; - To be re-added in the future }; interface AbstractRange { - /** - * Returns true if range is collapsed, and false otherwise. - */ + /** Returns true if range is collapsed, and false otherwise. */ readonly collapsed: boolean; - /** - * Returns range's end node. - */ + /** Returns range's end node. */ readonly endContainer: Node; - /** - * Returns range's end offset. - */ + /** Returns range's end offset. */ readonly endOffset: number; - /** - * Returns range's start node. - */ + /** Returns range's start node. */ readonly startContainer: Node; - /** - * Returns range's start offset. - */ + /** Returns range's start offset. */ readonly startOffset: number; } @@ -1984,7 +1966,7 @@ interface AnimationEventMap { } interface Animation extends EventTarget { - currentTime: number | null; + currentTime: CSSNumberish | null; effect: AnimationEffect | null; readonly finished: Promise; id: string; @@ -1996,7 +1978,7 @@ interface Animation extends EventTarget { playbackRate: number; readonly ready: Promise; readonly replaceState: AnimationReplaceState; - startTime: number | null; + startTime: CSSNumberish | null; timeline: AnimationTimeline | null; cancel(): void; commitStyles(): void; @@ -2046,8 +2028,8 @@ interface AnimationFrameProvider { } interface AnimationPlaybackEvent extends Event { - readonly currentTime: number | null; - readonly timelineTime: number | null; + readonly currentTime: CSSNumberish | null; + readonly timelineTime: CSSNumberish | null; } declare var AnimationPlaybackEvent: { @@ -2262,6 +2244,7 @@ declare var AudioScheduledSourceNode: { new(): AudioScheduledSourceNode; }; +/** Available only in secure contexts. */ interface AudioWorklet extends Worklet { } @@ -2274,6 +2257,7 @@ interface AudioWorkletNodeEventMap { "processorerror": Event; } +/** Available only in secure contexts. */ interface AudioWorkletNode extends AudioNode { onprocessorerror: ((this: AudioWorkletNode, ev: Event) => any) | null; readonly parameters: AudioParamMap; @@ -2289,6 +2273,7 @@ declare var AudioWorkletNode: { new(context: BaseAudioContext, name: string, options?: AudioWorkletNodeOptions): AudioWorkletNode; }; +/** Available only in secure contexts. */ interface AuthenticatorAssertionResponse extends AuthenticatorResponse { readonly authenticatorData: ArrayBuffer; readonly signature: ArrayBuffer; @@ -2300,6 +2285,7 @@ declare var AuthenticatorAssertionResponse: { new(): AuthenticatorAssertionResponse; }; +/** Available only in secure contexts. */ interface AuthenticatorAttestationResponse extends AuthenticatorResponse { readonly attestationObject: ArrayBuffer; } @@ -2309,6 +2295,7 @@ declare var AuthenticatorAttestationResponse: { new(): AuthenticatorAttestationResponse; }; +/** Available only in secure contexts. */ interface AuthenticatorResponse { readonly clientDataJSON: ArrayBuffer; } @@ -2332,6 +2319,7 @@ interface BaseAudioContextEventMap { } interface BaseAudioContext extends EventTarget { + /** Available only in secure contexts. */ readonly audioWorklet: AudioWorklet; readonly currentTime: number; readonly destination: AudioDestinationNode; @@ -2436,19 +2424,13 @@ interface BroadcastChannelEventMap { } interface BroadcastChannel extends EventTarget { - /** - * Returns the channel name (as passed to the constructor). - */ + /** Returns the channel name (as passed to the constructor). */ readonly name: string; onmessage: ((this: BroadcastChannel, ev: MessageEvent) => any) | null; onmessageerror: ((this: BroadcastChannel, ev: MessageEvent) => any) | null; - /** - * Closes the BroadcastChannel object, opening it up to garbage collection. - */ + /** Closes the BroadcastChannel object, opening it up to garbage collection. */ close(): void; - /** - * Sends the given message to other BroadcastChannel objects set up for this channel. Messages can be structured objects, e.g. nested objects and arrays. - */ + /** Sends the given message to other BroadcastChannel objects set up for this channel. Messages can be structured objects, e.g. nested objects and arrays. */ postMessage(message: any): void; addEventListener(type: K, listener: (this: BroadcastChannel, ev: BroadcastChannelEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; @@ -2660,6 +2642,7 @@ declare var CSSRuleList: { /** An object that is a CSS declaration block, and exposes style information and various style-related methods and properties. */ interface CSSStyleDeclaration { + accentColor: string; alignContent: string; alignItems: string; alignSelf: string; @@ -2833,11 +2816,14 @@ interface CSSStyleDeclaration { gridAutoRows: string; gridColumn: string; gridColumnEnd: string; + /** @deprecated This is a legacy alias of `columnGap`. */ gridColumnGap: string; gridColumnStart: string; + /** @deprecated This is a legacy alias of `gap`. */ gridGap: string; gridRow: string; gridRowEnd: string; + /** @deprecated This is a legacy alias of `rowGap`. */ gridRowGap: string; gridRowStart: string; gridTemplate: string; @@ -2978,6 +2964,7 @@ interface CSSStyleDeclaration { scrollSnapAlign: string; scrollSnapStop: string; scrollSnapType: string; + scrollbarGutter: string; shapeImageThreshold: string; shapeMargin: string; shapeOutside: string; @@ -3076,15 +3063,15 @@ interface CSSStyleDeclaration { webkitBorderTopLeftRadius: string; /** @deprecated This is a legacy alias of `borderTopRightRadius`. */ webkitBorderTopRightRadius: string; - /** @deprecated */ + /** @deprecated This is a legacy alias of `boxAlign`. */ webkitBoxAlign: string; - /** @deprecated */ + /** @deprecated This is a legacy alias of `boxFlex`. */ webkitBoxFlex: string; - /** @deprecated */ + /** @deprecated This is a legacy alias of `boxOrdinalGroup`. */ webkitBoxOrdinalGroup: string; - /** @deprecated */ + /** @deprecated This is a legacy alias of `boxOrient`. */ webkitBoxOrient: string; - /** @deprecated */ + /** @deprecated This is a legacy alias of `boxPack`. */ webkitBoxPack: string; /** @deprecated This is a legacy alias of `boxShadow`. */ webkitBoxShadow: string; @@ -3239,7 +3226,10 @@ declare var CSSTransition: { new(): CSSTransition; }; -/** Provides a storage mechanism for Request / Response object pairs that are cached, for example as part of the ServiceWorker life cycle. Note that the Cache interface is exposed to windowed scopes as well as workers. You don't have to use it in conjunction with service workers, even though it is defined in the service worker spec. */ +/** + * Provides a storage mechanism for Request / Response object pairs that are cached, for example as part of the ServiceWorker life cycle. Note that the Cache interface is exposed to windowed scopes as well as workers. You don't have to use it in conjunction with service workers, even though it is defined in the service worker spec. + * Available only in secure contexts. + */ interface Cache { add(request: RequestInfo): Promise; addAll(requests: RequestInfo[]): Promise; @@ -3255,7 +3245,10 @@ declare var Cache: { new(): Cache; }; -/** The storage for Cache objects. */ +/** + * The storage for Cache objects. + * Available only in secure contexts. + */ interface CacheStorage { delete(cacheName: string): Promise; has(cacheName: string): Promise; @@ -3269,9 +3262,23 @@ declare var CacheStorage: { new(): CacheStorage; }; +interface CanvasCaptureMediaStreamTrack extends MediaStreamTrack { + readonly canvas: HTMLCanvasElement; + requestFrame(): void; + addEventListener(type: K, listener: (this: CanvasCaptureMediaStreamTrack, ev: MediaStreamTrackEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; + removeEventListener(type: K, listener: (this: CanvasCaptureMediaStreamTrack, ev: MediaStreamTrackEventMap[K]) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void; +} + +declare var CanvasCaptureMediaStreamTrack: { + prototype: CanvasCaptureMediaStreamTrack; + new(): CanvasCaptureMediaStreamTrack; +}; + interface CanvasCompositing { globalAlpha: number; - globalCompositeOperation: string; + globalCompositeOperation: GlobalCompositeOperation; } interface CanvasDrawImage { @@ -3297,6 +3304,7 @@ interface CanvasDrawPath { interface CanvasFillStrokeStyles { fillStyle: string | CanvasGradient | CanvasPattern; strokeStyle: string | CanvasGradient | CanvasPattern; + createConicGradient(startAngle: number, x: number, y: number): CanvasGradient; createLinearGradient(x0: number, y0: number, x1: number, y1: number): CanvasGradient; createPattern(image: CanvasImageSource, repetition: string | null): CanvasPattern | null; createRadialGradient(x0: number, y0: number, r0: number, x1: number, y1: number, r1: number): CanvasGradient; @@ -3358,9 +3366,7 @@ interface CanvasPathDrawingStyles { /** An opaque object describing a pattern, based on an image, a canvas, or a video, created by the CanvasRenderingContext2D.createPattern() method. */ interface CanvasPattern { - /** - * Sets the transformation matrix that will be used when rendering the pattern during a fill or stroke painting operation. - */ + /** Sets the transformation matrix that will be used when rendering the pattern during a fill or stroke painting operation. */ setTransform(transform?: DOMMatrix2DInit): void; } @@ -3475,9 +3481,7 @@ interface ChildNode extends Node { * Throws a "HierarchyRequestError" DOMException if the constraints of the node tree are violated. */ before(...nodes: (Node | string)[]): void; - /** - * Removes node. - */ + /** Removes node. */ remove(): void; /** * Replaces node with nodes, while replacing strings in nodes with equivalent Text nodes. @@ -3491,6 +3495,7 @@ interface ChildNode extends Node { interface ClientRect extends DOMRect { } +/** Available only in secure contexts. */ interface Clipboard extends EventTarget { read(): Promise; readText(): Promise; @@ -3520,22 +3525,16 @@ interface ClipboardItem { declare var ClipboardItem: { prototype: ClipboardItem; - new(items: Record, options?: ClipboardItemOptions): ClipboardItem; + new(items: Record>, options?: ClipboardItemOptions): ClipboardItem; }; /** A CloseEvent is sent to clients using WebSockets when the connection is closed. This is delivered to the listener indicated by the WebSocket object's onclose attribute. */ interface CloseEvent extends Event { - /** - * Returns the WebSocket connection close code provided by the server. - */ + /** Returns the WebSocket connection close code provided by the server. */ readonly code: number; - /** - * Returns the WebSocket connection close reason provided by the server. - */ + /** Returns the WebSocket connection close reason provided by the server. */ readonly reason: string; - /** - * Returns true if the connection closed cleanly; false otherwise. - */ + /** Returns true if the connection closed cleanly; false otherwise. */ readonly wasClean: boolean; } @@ -3600,6 +3599,7 @@ declare var CountQueuingStrategy: { new(init: QueuingStrategyInit): CountQueuingStrategy; }; +/** Available only in secure contexts. */ interface Credential { readonly id: string; readonly type: string; @@ -3610,6 +3610,7 @@ declare var Credential: { new(): Credential; }; +/** Available only in secure contexts. */ interface CredentialsContainer { create(options?: CredentialCreationOptions): Promise; get(options?: CredentialRequestOptions): Promise; @@ -3624,8 +3625,11 @@ declare var CredentialsContainer: { /** Basic cryptography features available in the current context. It allows access to a cryptographically strong random number generator and to cryptographic primitives. */ interface Crypto { + /** Available only in secure contexts. */ readonly subtle: SubtleCrypto; getRandomValues(array: T): T; + /** Available only in secure contexts. */ + randomUUID(): string; } declare var Crypto: { @@ -3633,7 +3637,10 @@ declare var Crypto: { new(): Crypto; }; -/** The CryptoKey dictionary of the Web Crypto API represents a cryptographic key. */ +/** + * The CryptoKey dictionary of the Web Crypto API represents a cryptographic key. + * Available only in secure contexts. + */ interface CryptoKey { readonly algorithm: KeyAlgorithm; readonly extractable: boolean; @@ -3659,9 +3666,7 @@ declare var CustomElementRegistry: { }; interface CustomEvent extends Event { - /** - * Returns any custom data event was created with. Typically used for synthetic events. - */ + /** Returns any custom data event was created with. Typically used for synthetic events. */ readonly detail: T; /** @deprecated */ initCustomEvent(type: string, bubbles?: boolean, cancelable?: boolean, detail?: T): void; @@ -3673,7 +3678,7 @@ declare var CustomEvent: { }; /** An abnormal event (called an exception) which occurs as a result of calling a method or accessing a property of a web API. */ -interface DOMException { +interface DOMException extends Error { readonly code: number; readonly message: string; readonly name: string; @@ -3967,17 +3972,11 @@ declare var DOMRectReadOnly: { /** A type returned by some APIs which contains a list of DOMString (strings). */ interface DOMStringList { - /** - * Returns the number of strings in strings. - */ + /** Returns the number of strings in strings. */ readonly length: number; - /** - * Returns true if strings contains string, and false otherwise. - */ + /** Returns true if strings contains string, and false otherwise. */ contains(string: string): boolean; - /** - * Returns the string with index index from strings. - */ + /** Returns the string with index index from strings. */ item(index: number): string | null; [index: number]: string; } @@ -3999,9 +3998,7 @@ declare var DOMStringMap: { /** A set of space-separated tokens. Such a set is returned by Element.classList, HTMLLinkElement.relList, HTMLAnchorElement.relList, HTMLAreaElement.relList, HTMLIframeElement.sandbox, or HTMLOutputElement.htmlFor. It is indexed beginning with 0 as with JavaScript Array objects. DOMTokenList is always case-sensitive. */ interface DOMTokenList { - /** - * Returns the number of tokens. - */ + /** Returns the number of tokens. */ readonly length: number; /** * Returns the associated set as string. @@ -4018,13 +4015,9 @@ interface DOMTokenList { * Throws an "InvalidCharacterError" DOMException if one of the arguments contains any ASCII whitespace. */ add(...tokens: string[]): void; - /** - * Returns true if token is present, and false otherwise. - */ + /** Returns true if token is present, and false otherwise. */ contains(token: string): boolean; - /** - * Returns the token with index index. - */ + /** Returns the token with index index. */ item(index: number): string | null; /** * Removes arguments passed, if they are present. @@ -4087,33 +4080,19 @@ interface DataTransfer { * The possible values are "none", "copy", "copyLink", "copyMove", "link", "linkMove", "move", "all", and "uninitialized", */ effectAllowed: "none" | "copy" | "copyLink" | "copyMove" | "link" | "linkMove" | "move" | "all" | "uninitialized"; - /** - * Returns a FileList of the files being dragged, if any. - */ + /** Returns a FileList of the files being dragged, if any. */ readonly files: FileList; - /** - * Returns a DataTransferItemList object, with the drag data. - */ + /** Returns a DataTransferItemList object, with the drag data. */ readonly items: DataTransferItemList; - /** - * Returns a frozen array listing the formats that were set in the dragstart event. In addition, if any files are being dragged, then one of the types will be the string "Files". - */ + /** Returns a frozen array listing the formats that were set in the dragstart event. In addition, if any files are being dragged, then one of the types will be the string "Files". */ readonly types: ReadonlyArray; - /** - * Removes the data of the specified formats. Removes all data if the argument is omitted. - */ + /** Removes the data of the specified formats. Removes all data if the argument is omitted. */ clearData(format?: string): void; - /** - * Returns the specified data. If there is no such data, returns the empty string. - */ + /** Returns the specified data. If there is no such data, returns the empty string. */ getData(format: string): string; - /** - * Adds the specified data. - */ + /** Adds the specified data. */ setData(format: string, data: string): void; - /** - * Uses the given element to update the drag feedback, replacing any previously specified feedback. - */ + /** Uses the given element to update the drag feedback, replacing any previously specified feedback. */ setDragImage(image: Element, x: number, y: number): void; } @@ -4124,21 +4103,13 @@ declare var DataTransfer: { /** One drag data item. During a drag operation, each drag event has a dataTransfer property which contains a list of drag data items. Each item in the list is a DataTransferItem object. */ interface DataTransferItem { - /** - * Returns the drag data item kind, one of: "string", "file". - */ + /** Returns the drag data item kind, one of: "string", "file". */ readonly kind: string; - /** - * Returns the drag data item type string. - */ + /** Returns the drag data item type string. */ readonly type: string; - /** - * Returns a File object, if the drag data item kind is File. - */ + /** Returns a File object, if the drag data item kind is File. */ getAsFile(): File | null; - /** - * Invokes the callback with the string data as the argument, if the drag data item kind is text. - */ + /** Invokes the callback with the string data as the argument, if the drag data item kind is text. */ getAsString(callback: FunctionStringCallback | null): void; webkitGetAsEntry(): FileSystemEntry | null; } @@ -4150,22 +4121,14 @@ declare var DataTransferItem: { /** A list of DataTransferItem objects representing items being dragged. During a drag operation, each DragEvent has a dataTransfer property and that property is a DataTransferItemList. */ interface DataTransferItemList { - /** - * Returns the number of items in the drag data store. - */ + /** Returns the number of items in the drag data store. */ readonly length: number; - /** - * Adds a new entry for the given data to the drag data store. If the data is plain text then a type string has to be provided also. - */ + /** Adds a new entry for the given data to the drag data store. If the data is plain text then a type string has to be provided also. */ add(data: string, type: string): DataTransferItem | null; add(data: File): DataTransferItem | null; - /** - * Removes all the entries in the drag data store. - */ + /** Removes all the entries in the drag data store. */ clear(): void; - /** - * Removes the indexth entry in the drag data store. - */ + /** Removes the indexth entry in the drag data store. */ remove(index: number): void; [index: number]: DataTransferItem; } @@ -4185,7 +4148,10 @@ declare var DelayNode: { new(context: BaseAudioContext, options?: DelayOptions): DelayNode; }; -/** The DeviceMotionEvent provides web developers with information about the speed of changes for the device's position and orientation. */ +/** + * The DeviceMotionEvent provides web developers with information about the speed of changes for the device's position and orientation. + * Available only in secure contexts. + */ interface DeviceMotionEvent extends Event { readonly acceleration: DeviceMotionEventAcceleration | null; readonly accelerationIncludingGravity: DeviceMotionEventAcceleration | null; @@ -4198,19 +4164,24 @@ declare var DeviceMotionEvent: { new(type: string, eventInitDict?: DeviceMotionEventInit): DeviceMotionEvent; }; +/** Available only in secure contexts. */ interface DeviceMotionEventAcceleration { readonly x: number | null; readonly y: number | null; readonly z: number | null; } +/** Available only in secure contexts. */ interface DeviceMotionEventRotationRate { readonly alpha: number | null; readonly beta: number | null; readonly gamma: number | null; } -/** The DeviceOrientationEvent provides web developers with information from the physical orientation of the device running the web page. */ +/** + * The DeviceOrientationEvent provides web developers with information from the physical orientation of the device running the web page. + * Available only in secure contexts. + */ interface DeviceOrientationEvent extends Event { readonly absolute: boolean; readonly alpha: number | null; @@ -4234,9 +4205,7 @@ interface DocumentEventMap extends DocumentAndElementEventHandlersEventMap, Glob /** Any web page loaded in the browser and serves as an entry point into the web page's content, which is the DOM tree. */ interface Document extends Node, DocumentAndElementEventHandlers, DocumentOrShadowRoot, FontFaceSource, GlobalEventHandlers, NonElementParentNode, ParentNode, XPathEvaluatorBase { - /** - * Sets or gets the URL for the current document. - */ + /** Sets or gets the URL for the current document. */ readonly URL: string; /** * Sets or gets the color of all active links in the document. @@ -4263,26 +4232,18 @@ interface Document extends Node, DocumentAndElementEventHandlers, DocumentOrShad * @deprecated */ bgColor: string; - /** - * Specifies the beginning and end of the document body. - */ + /** Specifies the beginning and end of the document body. */ body: HTMLElement; - /** - * Returns document's encoding. - */ + /** Returns document's encoding. */ readonly characterSet: string; /** * Gets or sets the character set used to encode the object. * @deprecated This is a legacy alias of `characterSet`. */ readonly charset: string; - /** - * Gets a value that indicates whether standards-compliant mode is switched on for the object. - */ + /** Gets a value that indicates whether standards-compliant mode is switched on for the object. */ readonly compatMode: string; - /** - * Returns document's content type. - */ + /** Returns document's content type. */ readonly contentType: string; /** * Returns the HTTP cookies that apply to the Document. If there are no cookies or cookies can't be applied to this resource, the empty string will be returned. @@ -4298,87 +4259,55 @@ interface Document extends Node, DocumentAndElementEventHandlers, DocumentOrShad * Returns null if the Document is not currently executing a script or SVG script element (e.g., because the running script is an event handler, or a timeout), or if the currently executing script or SVG script element represents a module script. */ readonly currentScript: HTMLOrSVGScriptElement | null; - /** - * Returns the Window object of the active document. - */ + /** Returns the Window object of the active document. */ readonly defaultView: (WindowProxy & typeof globalThis) | null; - /** - * Sets or gets a value that indicates whether the document can be edited. - */ + /** Sets or gets a value that indicates whether the document can be edited. */ designMode: string; - /** - * Sets or retrieves a value that indicates the reading order of the object. - */ + /** Sets or retrieves a value that indicates the reading order of the object. */ dir: string; - /** - * Gets an object representing the document type declaration associated with the current document. - */ + /** Gets an object representing the document type declaration associated with the current document. */ readonly doctype: DocumentType | null; - /** - * Gets a reference to the root node of the document. - */ + /** Gets a reference to the root node of the document. */ readonly documentElement: HTMLElement; - /** - * Returns document's URL. - */ + /** Returns document's URL. */ readonly documentURI: string; - /** - * Sets or gets the security domain of the document. - */ + /** Sets or gets the security domain of the document. */ domain: string; - /** - * Retrieves a collection of all embed objects in the document. - */ + /** Retrieves a collection of all embed objects in the document. */ readonly embeds: HTMLCollectionOf; /** * Sets or gets the foreground (text) color of the document. * @deprecated */ fgColor: string; - /** - * Retrieves a collection, in source order, of all form objects in the document. - */ + /** Retrieves a collection, in source order, of all form objects in the document. */ readonly forms: HTMLCollectionOf; /** @deprecated */ readonly fullscreen: boolean; - /** - * Returns true if document has the ability to display elements fullscreen and fullscreen is supported, or false otherwise. - */ + /** Returns true if document has the ability to display elements fullscreen and fullscreen is supported, or false otherwise. */ readonly fullscreenEnabled: boolean; - /** - * Returns the head element. - */ + /** Returns the head element. */ readonly head: HTMLHeadElement; readonly hidden: boolean; - /** - * Retrieves a collection, in source order, of img objects in the document. - */ + /** Retrieves a collection, in source order, of img objects in the document. */ readonly images: HTMLCollectionOf; - /** - * Gets the implementation object of the current document. - */ + /** Gets the implementation object of the current document. */ readonly implementation: DOMImplementation; /** * Returns the character encoding used to create the webpage that is loaded into the document object. * @deprecated This is a legacy alias of `characterSet`. */ readonly inputEncoding: string; - /** - * Gets the date that the page was last modified, if the page supplies one. - */ + /** Gets the date that the page was last modified, if the page supplies one. */ readonly lastModified: string; /** * Sets or gets the color of the document links. * @deprecated */ linkColor: string; - /** - * Retrieves a collection of all a objects that specify the href property and all area objects in the document. - */ + /** Retrieves a collection of all a objects that specify the href property and all area objects in the document. */ readonly links: HTMLCollectionOf; - /** - * Contains information about the current URL. - */ + /** Contains information about the current URL. */ get location(): Location; set location(href: string | Location); onfullscreenchange: ((this: Document, ev: Event) => any) | null; @@ -4393,31 +4322,21 @@ interface Document extends Node, DocumentAndElementEventHandlers, DocumentOrShad onvisibilitychange: ((this: Document, ev: Event) => any) | null; readonly ownerDocument: null; readonly pictureInPictureEnabled: boolean; - /** - * Return an HTMLCollection of the embed elements in the Document. - */ + /** Return an HTMLCollection of the embed elements in the Document. */ readonly plugins: HTMLCollectionOf; - /** - * Retrieves a value that indicates the current state of the object. - */ + /** Retrieves a value that indicates the current state of the object. */ readonly readyState: DocumentReadyState; - /** - * Gets the URL of the location that referred the user to the current page. - */ + /** Gets the URL of the location that referred the user to the current page. */ readonly referrer: string; /** @deprecated */ readonly rootElement: SVGSVGElement | null; - /** - * Retrieves a collection of all script objects in the document. - */ + /** Retrieves a collection of all script objects in the document. */ readonly scripts: HTMLCollectionOf; readonly scrollingElement: Element | null; readonly timeline: DocumentTimeline; - /** - * Contains the title of the document. - */ + /** Contains the title of the document. */ title: string; - readonly visibilityState: VisibilityState; + readonly visibilityState: DocumentVisibilityState; /** * Sets or gets the color of the links that the user has visited. * @deprecated @@ -4435,9 +4354,7 @@ interface Document extends Node, DocumentAndElementEventHandlers, DocumentOrShad caretRangeFromPoint(x: number, y: number): Range | null; /** @deprecated */ clear(): void; - /** - * Closes an output stream and forces the sent data to display. - */ + /** Closes an output stream and forces the sent data to display. */ close(): void; /** * Creates an attribute object with a specified name. @@ -4445,18 +4362,14 @@ interface Document extends Node, DocumentAndElementEventHandlers, DocumentOrShad */ createAttribute(localName: string): Attr; createAttributeNS(namespace: string | null, qualifiedName: string): Attr; - /** - * Returns a CDATASection node whose data is data. - */ + /** Returns a CDATASection node whose data is data. */ createCDATASection(data: string): CDATASection; /** * Creates a comment object with the specified data. * @param data Sets the comment object's data. */ createComment(data: string): Comment; - /** - * Creates a new document. - */ + /** Creates a new document. */ createDocumentFragment(): DocumentFragment; /** * Creates an instance of the element for the specified tag. @@ -4550,13 +4463,9 @@ interface Document extends Node, DocumentAndElementEventHandlers, DocumentOrShad * @param filter A custom NodeFilter function to use. For more information, see filter. Use null for no filter. */ createNodeIterator(root: Node, whatToShow?: number, filter?: NodeFilter | null): NodeIterator; - /** - * Returns a ProcessingInstruction node whose target is target and data is data. If target does not match the Name production an "InvalidCharacterError" DOMException will be thrown. If data contains "?>" an "InvalidCharacterError" DOMException will be thrown. - */ + /** Returns a ProcessingInstruction node whose target is target and data is data. If target does not match the Name production an "InvalidCharacterError" DOMException will be thrown. If data contains "?>" an "InvalidCharacterError" DOMException will be thrown. */ createProcessingInstruction(target: string, data: string): ProcessingInstruction; - /** - * Returns an empty range object that has both of its boundary points positioned at the beginning of the document. - */ + /** Returns an empty range object that has both of its boundary points positioned at the beginning of the document. */ createRange(): Range; /** * Creates a text string from the specified value. @@ -4570,13 +4479,6 @@ interface Document extends Node, DocumentAndElementEventHandlers, DocumentOrShad * @param filter A custom NodeFilter function to use. */ createTreeWalker(root: Node, whatToShow?: number, filter?: NodeFilter | null): TreeWalker; - /** - * Returns the element for the specified x coordinate and the specified y coordinate. - * @param x The x-offset - * @param y The y-offset - */ - elementFromPoint(x: number, y: number): Element | null; - elementsFromPoint(x: number, y: number): Element[]; /** * Executes a command on the current document, current selection, or the given range. * @param commandId String that specifies the command to execute. This command can be any of the command identifiers that can be executed in script. @@ -4585,9 +4487,7 @@ interface Document extends Node, DocumentAndElementEventHandlers, DocumentOrShad * @deprecated */ execCommand(commandId: string, showUI?: boolean, value?: string): boolean; - /** - * Stops document's fullscreen element from being displayed fullscreen and resolves promise when done. - */ + /** Stops document's fullscreen element from being displayed fullscreen and resolves promise when done. */ exitFullscreen(): Promise; exitPictureInPicture(): Promise; exitPointerLock(): void; @@ -4596,9 +4496,7 @@ interface Document extends Node, DocumentAndElementEventHandlers, DocumentOrShad * @param elementId String that specifies the ID value. */ getElementById(elementId: string): HTMLElement | null; - /** - * Returns a HTMLCollection of the elements in the object on which the method was invoked (a document or an element) that have all the classes given by classNames. The classNames argument is interpreted as a space-separated list of classes. - */ + /** Returns a HTMLCollection of the elements in the object on which the method was invoked (a document or an element) that have all the classes given by classNames. The classNames argument is interpreted as a space-separated list of classes. */ getElementsByClassName(classNames: string): HTMLCollectionOf; /** * Gets a collection of objects based on the value of the NAME or ID attribute. @@ -4624,13 +4522,9 @@ interface Document extends Node, DocumentAndElementEventHandlers, DocumentOrShad getElementsByTagNameNS(namespaceURI: "http://www.w3.org/1999/xhtml", localName: string): HTMLCollectionOf; getElementsByTagNameNS(namespaceURI: "http://www.w3.org/2000/svg", localName: string): HTMLCollectionOf; getElementsByTagNameNS(namespace: string | null, localName: string): HTMLCollectionOf; - /** - * Returns an object representing the current selection of the document that is loaded into the object displaying a webpage. - */ + /** Returns an object representing the current selection of the document that is loaded into the object displaying a webpage. */ getSelection(): Selection | null; - /** - * Gets a value indicating whether the object currently has focus. - */ + /** Gets a value indicating whether the object currently has focus. */ hasFocus(): boolean; hasStorageAccess(): Promise; /** @@ -4736,16 +4630,19 @@ interface DocumentOrShadowRoot { * Similarly, when the focused element is in a different node tree than documentOrShadowRoot, the element returned will be the host that's located in the same node tree as documentOrShadowRoot if documentOrShadowRoot is a shadow-including inclusive ancestor of the focused element, and null if not. */ readonly activeElement: Element | null; - /** - * Returns document's fullscreen element. - */ + /** Returns document's fullscreen element. */ readonly fullscreenElement: Element | null; readonly pictureInPictureElement: Element | null; readonly pointerLockElement: Element | null; - /** - * Retrieves a collection of styleSheet objects representing the style sheets that correspond to each instance of a link or style object in the document. - */ + /** Retrieves a collection of styleSheet objects representing the style sheets that correspond to each instance of a link or style object in the document. */ readonly styleSheets: StyleSheetList; + /** + * Returns the element for the specified x coordinate and the specified y coordinate. + * @param x The x-offset + * @param y The y-offset + */ + elementFromPoint(x: number, y: number): Element | null; + elementsFromPoint(x: number, y: number): Element[]; getAnimations(): Animation[]; } @@ -4772,9 +4669,7 @@ declare var DocumentType: { /** A DOM event that represents a drag and drop interaction. The user initiates a drag by placing a pointer device (such as a mouse) on the touch surface and then dragging the pointer to a new location (such as another DOM element). Applications are free to interpret a drag and drop interaction in an application-specific way. */ interface DragEvent extends MouseEvent { - /** - * Returns the DataTransfer object for the event. - */ + /** Returns the DataTransfer object for the event. */ readonly dataTransfer: DataTransfer | null; } @@ -4851,84 +4746,54 @@ interface ElementEventMap { /** Element is the most general base class from which all objects in a Document inherit. It only has methods and properties common to all kinds of elements. More specific classes inherit from Element. */ interface Element extends Node, ARIAMixin, Animatable, ChildNode, InnerHTML, NonDocumentTypeChildNode, ParentNode, Slottable { readonly attributes: NamedNodeMap; - /** - * Allows for manipulation of element's class content attribute as a set of whitespace-separated tokens through a DOMTokenList object. - */ + /** Allows for manipulation of element's class content attribute as a set of whitespace-separated tokens through a DOMTokenList object. */ readonly classList: DOMTokenList; - /** - * Returns the value of element's class content attribute. Can be set to change it. - */ + /** Returns the value of element's class content attribute. Can be set to change it. */ className: string; readonly clientHeight: number; readonly clientLeft: number; readonly clientTop: number; readonly clientWidth: number; - /** - * Returns the value of element's id content attribute. Can be set to change it. - */ + /** Returns the value of element's id content attribute. Can be set to change it. */ id: string; - /** - * Returns the local name. - */ + /** Returns the local name. */ readonly localName: string; - /** - * Returns the namespace. - */ + /** Returns the namespace. */ readonly namespaceURI: string | null; onfullscreenchange: ((this: Element, ev: Event) => any) | null; onfullscreenerror: ((this: Element, ev: Event) => any) | null; outerHTML: string; readonly ownerDocument: Document; readonly part: DOMTokenList; - /** - * Returns the namespace prefix. - */ + /** Returns the namespace prefix. */ readonly prefix: string | null; readonly scrollHeight: number; scrollLeft: number; scrollTop: number; readonly scrollWidth: number; - /** - * Returns element's shadow root, if any, and if shadow root's mode is "open", and null otherwise. - */ + /** Returns element's shadow root, if any, and if shadow root's mode is "open", and null otherwise. */ readonly shadowRoot: ShadowRoot | null; - /** - * Returns the value of element's slot content attribute. Can be set to change it. - */ + /** Returns the value of element's slot content attribute. Can be set to change it. */ slot: string; - /** - * Returns the HTML-uppercased qualified name. - */ + /** Returns the HTML-uppercased qualified name. */ readonly tagName: string; - /** - * Creates a shadow root for element and returns it. - */ + /** Creates a shadow root for element and returns it. */ attachShadow(init: ShadowRootInit): ShadowRoot; - /** - * Returns the first (starting at element) inclusive ancestor that matches selectors, and null otherwise. - */ + /** Returns the first (starting at element) inclusive ancestor that matches selectors, and null otherwise. */ closest(selector: K): HTMLElementTagNameMap[K] | null; closest(selector: K): SVGElementTagNameMap[K] | null; closest(selectors: string): E | null; - /** - * Returns element's first attribute whose qualified name is qualifiedName, and null if there is no such attribute otherwise. - */ + /** Returns element's first attribute whose qualified name is qualifiedName, and null if there is no such attribute otherwise. */ getAttribute(qualifiedName: string): string | null; - /** - * Returns element's attribute whose namespace is namespace and local name is localName, and null if there is no such attribute otherwise. - */ + /** Returns element's attribute whose namespace is namespace and local name is localName, and null if there is no such attribute otherwise. */ getAttributeNS(namespace: string | null, localName: string): string | null; - /** - * Returns the qualified names of all element's attributes. Can contain duplicates. - */ + /** Returns the qualified names of all element's attributes. Can contain duplicates. */ getAttributeNames(): string[]; getAttributeNode(qualifiedName: string): Attr | null; getAttributeNodeNS(namespace: string | null, localName: string): Attr | null; getBoundingClientRect(): DOMRect; getClientRects(): DOMRectList; - /** - * Returns a HTMLCollection of the elements in the object on which the method was invoked (a document or an element) that have all the classes given by classNames. The classNames argument is interpreted as a space-separated list of classes. - */ + /** Returns a HTMLCollection of the elements in the object on which the method was invoked (a document or an element) that have all the classes given by classNames. The classNames argument is interpreted as a space-separated list of classes. */ getElementsByClassName(classNames: string): HTMLCollectionOf; getElementsByTagName(qualifiedName: K): HTMLCollectionOf; getElementsByTagName(qualifiedName: K): HTMLCollectionOf; @@ -4936,34 +4801,22 @@ interface Element extends Node, ARIAMixin, Animatable, ChildNode, InnerHTML, Non getElementsByTagNameNS(namespaceURI: "http://www.w3.org/1999/xhtml", localName: string): HTMLCollectionOf; getElementsByTagNameNS(namespaceURI: "http://www.w3.org/2000/svg", localName: string): HTMLCollectionOf; getElementsByTagNameNS(namespace: string | null, localName: string): HTMLCollectionOf; - /** - * Returns true if element has an attribute whose qualified name is qualifiedName, and false otherwise. - */ + /** Returns true if element has an attribute whose qualified name is qualifiedName, and false otherwise. */ hasAttribute(qualifiedName: string): boolean; - /** - * Returns true if element has an attribute whose namespace is namespace and local name is localName. - */ + /** Returns true if element has an attribute whose namespace is namespace and local name is localName. */ hasAttributeNS(namespace: string | null, localName: string): boolean; - /** - * Returns true if element has attributes, and false otherwise. - */ + /** Returns true if element has attributes, and false otherwise. */ hasAttributes(): boolean; hasPointerCapture(pointerId: number): boolean; insertAdjacentElement(where: InsertPosition, element: Element): Element | null; insertAdjacentHTML(position: InsertPosition, text: string): void; insertAdjacentText(where: InsertPosition, data: string): void; - /** - * Returns true if matching selectors against element's root yields element, and false otherwise. - */ + /** Returns true if matching selectors against element's root yields element, and false otherwise. */ matches(selectors: string): boolean; releasePointerCapture(pointerId: number): void; - /** - * Removes element's first attribute whose qualified name is qualifiedName. - */ + /** Removes element's first attribute whose qualified name is qualifiedName. */ removeAttribute(qualifiedName: string): void; - /** - * Removes element's attribute whose namespace is namespace and local name is localName. - */ + /** Removes element's attribute whose namespace is namespace and local name is localName. */ removeAttributeNS(namespace: string | null, localName: string): void; removeAttributeNode(attr: Attr): Attr; /** @@ -4980,13 +4833,9 @@ interface Element extends Node, ARIAMixin, Animatable, ChildNode, InnerHTML, Non scrollIntoView(arg?: boolean | ScrollIntoViewOptions): void; scrollTo(options?: ScrollToOptions): void; scrollTo(x: number, y: number): void; - /** - * Sets the value of element's first attribute whose qualified name is qualifiedName to value. - */ + /** Sets the value of element's first attribute whose qualified name is qualifiedName to value. */ setAttribute(qualifiedName: string, value: string): void; - /** - * Sets the value of element's attribute whose namespace is namespace and local name is localName to value. - */ + /** Sets the value of element's attribute whose namespace is namespace and local name is localName to value. */ setAttributeNS(namespace: string | null, qualifiedName: string, value: string): void; setAttributeNode(attr: Attr): Attr | null; setAttributeNodeNS(attr: Attr): Attr | null; @@ -5021,6 +4870,16 @@ interface ElementContentEditable { readonly isContentEditable: boolean; } +interface ElementInternals extends ARIAMixin { + /** Returns the ShadowRoot for internals's target element, if the target element is a shadow host, or null otherwise. */ + readonly shadowRoot: ShadowRoot | null; +} + +declare var ElementInternals: { + prototype: ElementInternals; + new(): ElementInternals; +}; + /** Events providing information related to errors in scripts or in files. */ interface ErrorEvent extends Event { readonly colno: number; @@ -5037,68 +4896,40 @@ declare var ErrorEvent: { /** An event which takes place in the DOM. */ interface Event { - /** - * Returns true or false depending on how event was initialized. True if event goes through its target's ancestors in reverse tree order, and false otherwise. - */ + /** Returns true or false depending on how event was initialized. True if event goes through its target's ancestors in reverse tree order, and false otherwise. */ readonly bubbles: boolean; cancelBubble: boolean; - /** - * Returns true or false depending on how event was initialized. Its return value does not always carry meaning, but true can indicate that part of the operation during which event was dispatched, can be canceled by invoking the preventDefault() method. - */ + /** Returns true or false depending on how event was initialized. Its return value does not always carry meaning, but true can indicate that part of the operation during which event was dispatched, can be canceled by invoking the preventDefault() method. */ readonly cancelable: boolean; - /** - * Returns true or false depending on how event was initialized. True if event invokes listeners past a ShadowRoot node that is the root of its target, and false otherwise. - */ + /** Returns true or false depending on how event was initialized. True if event invokes listeners past a ShadowRoot node that is the root of its target, and false otherwise. */ readonly composed: boolean; - /** - * Returns the object whose event listener's callback is currently being invoked. - */ + /** Returns the object whose event listener's callback is currently being invoked. */ readonly currentTarget: EventTarget | null; - /** - * Returns true if preventDefault() was invoked successfully to indicate cancelation, and false otherwise. - */ + /** Returns true if preventDefault() was invoked successfully to indicate cancelation, and false otherwise. */ readonly defaultPrevented: boolean; - /** - * Returns the event's phase, which is one of NONE, CAPTURING_PHASE, AT_TARGET, and BUBBLING_PHASE. - */ + /** Returns the event's phase, which is one of NONE, CAPTURING_PHASE, AT_TARGET, and BUBBLING_PHASE. */ readonly eventPhase: number; - /** - * Returns true if event was dispatched by the user agent, and false otherwise. - */ + /** Returns true if event was dispatched by the user agent, and false otherwise. */ readonly isTrusted: boolean; /** @deprecated */ returnValue: boolean; /** @deprecated */ readonly srcElement: EventTarget | null; - /** - * Returns the object to which event is dispatched (its target). - */ + /** Returns the object to which event is dispatched (its target). */ readonly target: EventTarget | null; - /** - * Returns the event's timestamp as the number of milliseconds measured relative to the time origin. - */ + /** Returns the event's timestamp as the number of milliseconds measured relative to the time origin. */ readonly timeStamp: DOMHighResTimeStamp; - /** - * Returns the type of event, e.g. "click", "hashchange", or "submit". - */ + /** Returns the type of event, e.g. "click", "hashchange", or "submit". */ readonly type: string; - /** - * Returns the invocation target objects of event's path (objects on which listeners will be invoked), except for any nodes in shadow trees of which the shadow root's mode is "closed" that are not reachable from event's currentTarget. - */ + /** Returns the invocation target objects of event's path (objects on which listeners will be invoked), except for any nodes in shadow trees of which the shadow root's mode is "closed" that are not reachable from event's currentTarget. */ composedPath(): EventTarget[]; /** @deprecated */ initEvent(type: string, bubbles?: boolean, cancelable?: boolean): void; - /** - * If invoked when the cancelable attribute value is true, and while executing a listener for the event with passive set to false, signals to the operation that caused event to be dispatched that it needs to be canceled. - */ + /** If invoked when the cancelable attribute value is true, and while executing a listener for the event with passive set to false, signals to the operation that caused event to be dispatched that it needs to be canceled. */ preventDefault(): void; - /** - * Invoking this method prevents event from reaching any registered event listeners after the current one finishes running and, when dispatched in a tree, also prevents event from reaching any other objects. - */ + /** Invoking this method prevents event from reaching any registered event listeners after the current one finishes running and, when dispatched in a tree, also prevents event from reaching any other objects. */ stopImmediatePropagation(): void; - /** - * When dispatched in a tree, invoking this method prevents event from reaching any objects other than the current object. - */ + /** When dispatched in a tree, invoking this method prevents event from reaching any objects other than the current object. */ stopPropagation(): void; readonly AT_TARGET: number; readonly BUBBLING_PHASE: number; @@ -5133,28 +4964,22 @@ interface EventSource extends EventTarget { onerror: ((this: EventSource, ev: Event) => any) | null; onmessage: ((this: EventSource, ev: MessageEvent) => any) | null; onopen: ((this: EventSource, ev: Event) => any) | null; - /** - * Returns the state of this EventSource object's connection. It can have the values described below. - */ + /** Returns the state of this EventSource object's connection. It can have the values described below. */ readonly readyState: number; - /** - * Returns the URL providing the event stream. - */ + /** Returns the URL providing the event stream. */ readonly url: string; - /** - * Returns true if the credentials mode for connection requests to the URL providing the event stream is set to "include", and false otherwise. - */ + /** Returns true if the credentials mode for connection requests to the URL providing the event stream is set to "include", and false otherwise. */ readonly withCredentials: boolean; - /** - * Aborts any instances of the fetch algorithm started for this EventSource object, and sets the readyState attribute to CLOSED. - */ + /** Aborts any instances of the fetch algorithm started for this EventSource object, and sets the readyState attribute to CLOSED. */ close(): void; readonly CLOSED: number; readonly CONNECTING: number; readonly OPEN: number; addEventListener(type: K, listener: (this: EventSource, ev: EventSourceEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: string, listener: (this: EventSource, event: MessageEvent) => any, options?: boolean | AddEventListenerOptions): void; addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; removeEventListener(type: K, listener: (this: EventSource, ev: EventSourceEventMap[K]) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: string, listener: (this: EventSource, event: MessageEvent) => any, options?: boolean | EventListenerOptions): void; removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void; } @@ -5184,13 +5009,9 @@ interface EventTarget { * The event listener is appended to target's event listener list and is not appended if it has the same type, callback, and capture. */ addEventListener(type: string, callback: EventListenerOrEventListenerObject | null, options?: AddEventListenerOptions | boolean): void; - /** - * Dispatches a synthetic event event to target and returns true if either event's cancelable attribute value is false or its preventDefault() method was not invoked, and false otherwise. - */ + /** Dispatches a synthetic event event to target and returns true if either event's cancelable attribute value is false or its preventDefault() method was not invoked, and false otherwise. */ dispatchEvent(event: Event): boolean; - /** - * Removes the event listener in target's event listener list with the same type, callback, and options. - */ + /** Removes the event listener in target's event listener list with the same type, callback, and options. */ removeEventListener(type: string, callback: EventListenerOrEventListenerObject | null, options?: EventListenerOptions | boolean): void; } @@ -5300,13 +5121,24 @@ declare var FileSystemDirectoryEntry: { new(): FileSystemDirectoryEntry; }; -/** @deprecated */ +/** Available only in secure contexts. */ +interface FileSystemDirectoryHandle extends FileSystemHandle { + readonly kind: "directory"; + getDirectoryHandle(name: string, options?: FileSystemGetDirectoryOptions): Promise; + getFileHandle(name: string, options?: FileSystemGetFileOptions): Promise; + removeEntry(name: string, options?: FileSystemRemoveOptions): Promise; + resolve(possibleDescendant: FileSystemHandle): Promise; +} + +declare var FileSystemDirectoryHandle: { + prototype: FileSystemDirectoryHandle; + new(): FileSystemDirectoryHandle; +}; + interface FileSystemDirectoryReader { - /** @deprecated */ readEntries(successCallback: FileSystemEntriesCallback, errorCallback?: ErrorCallback): void; } -/** @deprecated */ declare var FileSystemDirectoryReader: { prototype: FileSystemDirectoryReader; new(): FileSystemDirectoryReader; @@ -5335,6 +5167,29 @@ declare var FileSystemFileEntry: { new(): FileSystemFileEntry; }; +/** Available only in secure contexts. */ +interface FileSystemFileHandle extends FileSystemHandle { + readonly kind: "file"; + getFile(): Promise; +} + +declare var FileSystemFileHandle: { + prototype: FileSystemFileHandle; + new(): FileSystemFileHandle; +}; + +/** Available only in secure contexts. */ +interface FileSystemHandle { + readonly kind: FileSystemHandleKind; + readonly name: string; + isSameEntry(other: FileSystemHandle): Promise; +} + +declare var FileSystemHandle: { + prototype: FileSystemHandle; + new(): FileSystemHandle; +}; + /** Focus-related events like focus, blur, focusin, or focusout. */ interface FocusEvent extends UIEvent { readonly relatedTarget: EventTarget | null; @@ -5424,9 +5279,7 @@ declare var FormData: { }; interface FormDataEvent extends Event { - /** - * Returns a FormData object representing names and values of elements associated to the target form. Operations on the FormData object will affect form data to be submitted. - */ + /** Returns a FormData object representing names and values of elements associated to the target form. Operations on the FormData object will affect form data to be submitted. */ readonly formData: FormData; } @@ -5445,7 +5298,10 @@ declare var GainNode: { new(context: BaseAudioContext, options?: GainOptions): GainNode; }; -/** This Gamepad API interface defines an individual gamepad or other controller, allowing access to information such as button presses, axis positions, and id. */ +/** + * This Gamepad API interface defines an individual gamepad or other controller, allowing access to information such as button presses, axis positions, and id. + * Available only in secure contexts. + */ interface Gamepad { readonly axes: ReadonlyArray; readonly buttons: ReadonlyArray; @@ -5462,7 +5318,10 @@ declare var Gamepad: { new(): Gamepad; }; -/** An individual button of a gamepad or other controller, allowing access to the current state of different types of buttons available on the control device. */ +/** + * An individual button of a gamepad or other controller, allowing access to the current state of different types of buttons available on the control device. + * Available only in secure contexts. + */ interface GamepadButton { readonly pressed: boolean; readonly touched: boolean; @@ -5474,7 +5333,10 @@ declare var GamepadButton: { new(): GamepadButton; }; -/** This Gamepad API interface contains references to gamepads connected to the system, which is what the gamepad events Window.gamepadconnected and Window.gamepaddisconnected are fired in response to. */ +/** + * This Gamepad API interface contains references to gamepads connected to the system, which is what the gamepad events Window.gamepadconnected and Window.gamepaddisconnected are fired in response to. + * Available only in secure contexts. + */ interface GamepadEvent extends Event { readonly gamepad: Gamepad; } @@ -5511,6 +5373,7 @@ declare var Geolocation: { new(): Geolocation; }; +/** Available only in secure contexts. */ interface GeolocationCoordinates { readonly accuracy: number; readonly altitude: number | null; @@ -5526,9 +5389,10 @@ declare var GeolocationCoordinates: { new(): GeolocationCoordinates; }; +/** Available only in secure contexts. */ interface GeolocationPosition { readonly coords: GeolocationCoordinates; - readonly timestamp: DOMTimeStamp; + readonly timestamp: EpochTimeStamp; } declare var GeolocationPosition: { @@ -5627,8 +5491,9 @@ interface GlobalEventHandlersEventMap { "select": Event; "selectionchange": Event; "selectstart": Event; + "slotchange": Event; "stalled": Event; - "submit": Event; + "submit": SubmitEvent; "suspend": Event; "timeupdate": Event; "toggle": Event; @@ -5861,6 +5726,7 @@ interface GlobalEventHandlers { * @param ev The event. */ onscroll: ((this: GlobalEventHandlers, ev: Event) => any) | null; + onsecuritypolicyviolation: ((this: GlobalEventHandlers, ev: SecurityPolicyViolationEvent) => any) | null; /** * Occurs when the seek operation ends. * @param ev The event. @@ -5878,12 +5744,13 @@ interface GlobalEventHandlers { onselect: ((this: GlobalEventHandlers, ev: Event) => any) | null; onselectionchange: ((this: GlobalEventHandlers, ev: Event) => any) | null; onselectstart: ((this: GlobalEventHandlers, ev: Event) => any) | null; + onslotchange: ((this: GlobalEventHandlers, ev: Event) => any) | null; /** * Occurs when the download has stopped. * @param ev The event. */ onstalled: ((this: GlobalEventHandlers, ev: Event) => any) | null; - onsubmit: ((this: GlobalEventHandlers, ev: Event) => any) | null; + onsubmit: ((this: GlobalEventHandlers, ev: SubmitEvent) => any) | null; /** * Occurs if the load operation has been intentionally halted. * @param ev The event. @@ -5913,9 +5780,13 @@ interface GlobalEventHandlers { * @param ev The event. */ onwaiting: ((this: GlobalEventHandlers, ev: Event) => any) | null; + /** @deprecated This is a legacy alias of `onanimationend`. */ onwebkitanimationend: ((this: GlobalEventHandlers, ev: Event) => any) | null; + /** @deprecated This is a legacy alias of `onanimationiteration`. */ onwebkitanimationiteration: ((this: GlobalEventHandlers, ev: Event) => any) | null; + /** @deprecated This is a legacy alias of `onanimationstart`. */ onwebkitanimationstart: ((this: GlobalEventHandlers, ev: Event) => any) | null; + /** @deprecated This is a legacy alias of `ontransitionend`. */ onwebkittransitionend: ((this: GlobalEventHandlers, ev: Event) => any) | null; onwheel: ((this: GlobalEventHandlers, ev: WheelEvent) => any) | null; addEventListener(type: K, listener: (this: GlobalEventHandlers, ev: GlobalEventHandlersEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; @@ -5925,13 +5796,9 @@ interface GlobalEventHandlers { } interface HTMLAllCollection { - /** - * Returns the number of elements in the collection. - */ + /** Returns the number of elements in the collection. */ readonly length: number; - /** - * Returns the item with index index from the collection (determined by tree order). - */ + /** Returns the item with index index from the collection (determined by tree order). */ item(nameOrIndex?: string): HTMLCollection | Element | null; /** * Returns the item with ID or name name from the collection. @@ -5962,9 +5829,7 @@ interface HTMLAnchorElement extends HTMLElement, HTMLHyperlinkElementUtils { */ coords: string; download: string; - /** - * Sets or retrieves the language code of the object. - */ + /** Sets or retrieves the language code of the object. */ hreflang: string; /** * Sets or retrieves the shape of the object. @@ -5973,9 +5838,7 @@ interface HTMLAnchorElement extends HTMLElement, HTMLHyperlinkElementUtils { name: string; ping: string; referrerPolicy: string; - /** - * Sets or retrieves the relationship between the object and the destination of the link. - */ + /** Sets or retrieves the relationship between the object and the destination of the link. */ rel: string; readonly relList: DOMTokenList; /** @@ -5988,13 +5851,9 @@ interface HTMLAnchorElement extends HTMLElement, HTMLHyperlinkElementUtils { * @deprecated */ shape: string; - /** - * Sets or retrieves the window or frame at which to target content. - */ + /** Sets or retrieves the window or frame at which to target content. */ target: string; - /** - * Retrieves or sets the text of the object as a string. - */ + /** Retrieves or sets the text of the object as a string. */ text: string; type: string; addEventListener(type: K, listener: (this: HTMLAnchorElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; @@ -6010,13 +5869,9 @@ declare var HTMLAnchorElement: { /** Provides special properties and methods (beyond those of the regular object HTMLElement interface it also has available to it by inheritance) for manipulating the layout and presentation of elements. */ interface HTMLAreaElement extends HTMLElement, HTMLHyperlinkElementUtils { - /** - * Sets or retrieves a text alternative to the graphic. - */ + /** Sets or retrieves a text alternative to the graphic. */ alt: string; - /** - * Sets or retrieves the coordinates of the object. - */ + /** Sets or retrieves the coordinates of the object. */ coords: string; download: string; /** @@ -6028,13 +5883,9 @@ interface HTMLAreaElement extends HTMLElement, HTMLHyperlinkElementUtils { referrerPolicy: string; rel: string; readonly relList: DOMTokenList; - /** - * Sets or retrieves the shape of the object. - */ + /** Sets or retrieves the shape of the object. */ shape: string; - /** - * Sets or retrieves the window or frame at which to target content. - */ + /** Sets or retrieves the window or frame at which to target content. */ target: string; addEventListener(type: K, listener: (this: HTMLAreaElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; @@ -6080,13 +5931,9 @@ declare var HTMLBRElement: { /** Contains the base URI for a document. This object inherits all of the properties and methods as described in the HTMLElement interface. */ interface HTMLBaseElement extends HTMLElement { - /** - * Gets or sets the baseline URL on which relative links are based. - */ + /** Gets or sets the baseline URL on which relative links are based. */ href: string; - /** - * Sets or retrieves the window or frame at which to target content. - */ + /** Sets or retrieves the window or frame at which to target content. */ target: string; addEventListener(type: K, listener: (this: HTMLBaseElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; @@ -6133,58 +5980,32 @@ declare var HTMLBodyElement: { /** Provides properties and methods (beyond the regular HTMLElement interface it also has available to it by inheritance) for manipulating