mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
Merge remote-tracking branch 'origin/main' into release-5.3
This commit is contained in:
+7
-3
@@ -38,6 +38,10 @@
|
||||
"yml.singleQuote": true,
|
||||
"yaml.singleQuote": true
|
||||
},
|
||||
"json": {
|
||||
// This would be good to do in known-JSONC files, but VS Code warns on trailing commas.
|
||||
"trailingCommas": "never"
|
||||
},
|
||||
"excludes": [
|
||||
"**/node_modules",
|
||||
"**/*-lock.json",
|
||||
@@ -51,8 +55,8 @@
|
||||
],
|
||||
// Note: if adding new languages, make sure settings.template.json is updated too.
|
||||
"plugins": [
|
||||
"https://plugins.dprint.dev/typescript-0.88.1.wasm",
|
||||
"https://plugins.dprint.dev/prettier-0.27.0.json@3557a62b4507c55a47d8cde0683195b14d13c41dda66d0f0b0e111aed107e2fe",
|
||||
"https://plugins.dprint.dev/json-0.17.4.wasm"
|
||||
"https://plugins.dprint.dev/typescript-0.88.3.wasm",
|
||||
"https://plugins.dprint.dev/json-0.19.0.wasm",
|
||||
"https://plugins.dprint.dev/prettier-0.27.0.json@3557a62b4507c55a47d8cde0683195b14d13c41dda66d0f0b0e111aed107e2fe"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -13,8 +13,7 @@ body:
|
||||
|
||||
List them here so people in the future can find this one more easily.
|
||||
placeholder: |
|
||||
List of keywords you searched for before creating this issue.
|
||||
Write them down here so that others can find this bug more easily and help provide feedback.
|
||||
List of keywords you searched for before creating this issue. Write them down here so that others can find this bug more easily and help provide feedback.
|
||||
|
||||
e.g. "function inference any", "jsx attribute spread", "move to file duplicate imports", "discriminated union inference", "ts2822"
|
||||
validations:
|
||||
@@ -26,21 +25,19 @@ body:
|
||||
description: |
|
||||
When did you start seeing this bug occur?
|
||||
|
||||
"Bugs" that have existed in TS for a long time are very likely to be FAQs; refer to
|
||||
https://github.com/Microsoft/TypeScript/wiki/FAQ#common-bugs-that-arent-bugs
|
||||
"Bugs" that have existed in TS for a long time are very likely to be FAQs; please refer to the [FAQ wiki page](https://github.com/Microsoft/TypeScript/wiki/FAQ#common-bugs-that-arent-bugs).
|
||||
|
||||
Please try the nightly version of TS to see if it's already been fixed. Install `typescript@next` or use the [Playground](http://www.typescriptlang.org/play/?ts=Nightly).
|
||||
|
||||
If possible, please try testing the nightly version of TS to see if it's already been fixed. For npm: `typescript@next`
|
||||
If possible, try bisecting the issue using [every-ts](https://www.npmjs.com/package/every-ts#bisecting), which should narrow down the problem to a specific change.
|
||||
|
||||
This is also the 'Nightly' version in the playground: http://www.typescriptlang.org/play/?ts=Nightly
|
||||
|
||||
|
||||
Note: The TypeScript Playground can be used to try older versions of TypeScript.
|
||||
The Playground also supports versions of TypeScript back to TypeScript 3.3.
|
||||
|
||||
Please keep and fill in the line that best applies.
|
||||
value: |
|
||||
- This is a crash
|
||||
- This changed between versions ______ and _______
|
||||
- This changed in commit or PR _______
|
||||
- This is the behavior in every version I tried, and I reviewed the FAQ for entries about _________
|
||||
- I was unable to test this on prior versions because _______
|
||||
validations:
|
||||
@@ -54,7 +51,6 @@ body:
|
||||
|
||||
This should have the same code as the code snippet below, and use whichever settings are relevant to your report.
|
||||
|
||||
|
||||
As a last resort, you can link to a repo, but these will be slower for us to investigate.
|
||||
placeholder: 'Playground link with relevant code: https://www.typescriptlang.org/play?#code/PTAEFkE9QYwewCYFNQHM5IM6gBZIE5JA'
|
||||
validations:
|
||||
@@ -65,6 +61,7 @@ body:
|
||||
label: 💻 Code
|
||||
description: |
|
||||
Please post the relevant code sample here as well.
|
||||
|
||||
This code and the Playground code should be the same, do not use separate examples.
|
||||
|
||||
We can quickly address your report if:
|
||||
@@ -74,6 +71,7 @@ body:
|
||||
- The incorrectness of the behavior is readily apparent from reading the sample.
|
||||
|
||||
Reports are slower to investigate if:
|
||||
|
||||
- We have to pare too much extraneous code.
|
||||
- We have to clone a large repo and validate that the problem isn't elsewhere.
|
||||
- The sample is confusing or doesn't clearly demonstrate what's wrong.
|
||||
|
||||
@@ -15,8 +15,7 @@ body:
|
||||
|
||||
The "Common Feature Requests" section of the FAQ lists many popular requests: https://github.com/Microsoft/TypeScript/wiki/FAQ#common-feature-requests
|
||||
placeholder: |
|
||||
List of keywords you searched for before creating this issue.
|
||||
Write them down here so that others can find this suggestion more easily and help provide feedback.
|
||||
List of keywords you searched for before creating this issue. Write them down here so that others can find this suggestion more easily and help provide feedback.
|
||||
|
||||
e.g. "isArray readonly", "regex string types", "json const assertion import"
|
||||
validations:
|
||||
@@ -26,9 +25,7 @@ body:
|
||||
attributes:
|
||||
label: '✅ Viability Checklist'
|
||||
description: |
|
||||
Suggestions that don't meet all these criteria are very, very unlikely to be accepted.
|
||||
We always recommend reviewing the TypeScript design goals before investing time writing
|
||||
a proposal for ideas outside the scope of the project.
|
||||
Suggestions that don't meet all these criteria are very, very unlikely to be accepted. We always recommend reviewing the TypeScript design goals before investing time writing a proposal for ideas outside the scope of the project.
|
||||
|
||||
My suggestion meets the following guidelines.
|
||||
options:
|
||||
@@ -54,8 +51,7 @@ body:
|
||||
attributes:
|
||||
label: '📃 Motivating Example'
|
||||
description: |
|
||||
If you were announcing this feature in a blog post, what's a short
|
||||
explanation that shows a developer why this feature improves the language?
|
||||
If you were announcing this feature in a blog post, what's a short explanation that shows a developer why this feature improves the language?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
|
||||
@@ -12,18 +12,13 @@ body:
|
||||
* Array methods - see https://github.com/microsoft/TypeScript/issues/36554
|
||||
* `parseInt`, `parseFloat`, `isFinite`, `isNaN`, etc. - see https://github.com/microsoft/TypeScript/issues/4002
|
||||
|
||||
|
||||
The DOM lib is maintained elsewhere and you can skip a step by filing issues/PRs for the DOM at that repo.
|
||||
See https://github.com/microsoft/TypeScript-DOM-lib-generator
|
||||
The DOM lib is maintained elsewhere and you can skip a step by filing issues/PRs for the DOM at that repo. See https://github.com/microsoft/TypeScript-DOM-lib-generator
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
If you're missing common new methods like `Array.includes`, you may have a misconfigured project.
|
||||
Try setting `lib: "es2020"` and checking whether the type you want is present.
|
||||
You can diagnose further by running `tsc` with `--listFilesOnly` or `--showConfig`.
|
||||
If you're missing common new methods like `Array.includes`, you may have a misconfigured project. Try setting `lib: "es2020"` and checking whether the type you want is present. You can diagnose further by running `tsc` with `--listFilesOnly` or `--showConfig`.
|
||||
|
||||
Conversely, if you are seeing built-in methods you expect to *not* see, check your 'lib' setting or review your dependencies for lib/reference directives that might be polluting
|
||||
your global scope. This is common when using the 'node' type library. See https://github.com/microsoft/TypeScript/issues/40184
|
||||
Conversely, if you are seeing built-in methods you expect to *not* see, check your 'lib' setting or review your dependencies for lib/reference directives that might be polluting your global scope. This is common when using the 'node' type library. See https://github.com/microsoft/TypeScript/issues/40184
|
||||
- type: input
|
||||
id: compilation_target
|
||||
attributes:
|
||||
@@ -58,5 +53,4 @@ body:
|
||||
attributes:
|
||||
label: 'Documentation Link'
|
||||
description: |
|
||||
Link to relevant documentation (e.g. MDN, W3C, ECMAScript Spec) to consult for this property.
|
||||
Note that lib.dom.d.ts intentionally does not include browser-specific extensions or early experimental features.
|
||||
Link to relevant documentation (e.g. MDN, W3C, ECMAScript Spec) to consult for this property. Note that lib.dom.d.ts intentionally does not include browser-specific extensions or early experimental features.
|
||||
|
||||
@@ -27,8 +27,7 @@ body:
|
||||
4. The configuration of the target module
|
||||
5. A difference in runtime behavior
|
||||
|
||||
You will also be required to post a cloneable repository.
|
||||
This repo must involve running `tsc`, not a third-party tool (e.g. vue-tsc, ngc, expo, ...)
|
||||
You will also be required to post a cloneable repository. This repo must involve running `tsc`, not a third-party tool (e.g. vue-tsc, ngc, expo, ...)
|
||||
|
||||
- type: input
|
||||
id: repo-url
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: 'github-actions'
|
||||
directory: '/'
|
||||
schedule:
|
||||
interval: 'weekly'
|
||||
groups:
|
||||
github-actions:
|
||||
patterns:
|
||||
- '*'
|
||||
@@ -13,3 +13,4 @@ jakebailey
|
||||
DanielRosenwasser
|
||||
navya9singh
|
||||
iisaduan
|
||||
dependabot
|
||||
|
||||
@@ -16,12 +16,11 @@ jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
with:
|
||||
token: ${{ secrets.TS_BOT_GITHUB_TOKEN }}
|
||||
- uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0
|
||||
|
||||
- name: Configure Git, Run Tests, Update Baselines, Apply Fixes
|
||||
run: |
|
||||
|
||||
+22
-22
@@ -39,9 +39,9 @@ jobs:
|
||||
name: Test Node ${{ matrix.node-version }} with --bundle=${{ matrix.bundle }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- name: Use node version ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
check-latest: true
|
||||
@@ -55,8 +55,8 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0
|
||||
with:
|
||||
node-version: '*'
|
||||
check-latest: true
|
||||
@@ -69,14 +69,14 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0
|
||||
with:
|
||||
node-version: '*'
|
||||
check-latest: true
|
||||
- run: npm ci
|
||||
|
||||
- uses: actions/cache@v3
|
||||
- uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3.3.2
|
||||
with:
|
||||
path: ~/.cache/dprint
|
||||
key: ${{ runner.os }}-dprint-${{ hashFiles('package-lock.json', '.dprint.jsonc') }}
|
||||
@@ -90,8 +90,8 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0
|
||||
with:
|
||||
node-version: '*'
|
||||
check-latest: true
|
||||
@@ -107,8 +107,8 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0
|
||||
with:
|
||||
node-version: '*'
|
||||
check-latest: true
|
||||
@@ -121,9 +121,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0
|
||||
with:
|
||||
node-version: '*'
|
||||
check-latest: true
|
||||
@@ -162,16 +162,16 @@ jobs:
|
||||
if: github.event_name == 'pull_request'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
with:
|
||||
path: pr
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
with:
|
||||
path: base
|
||||
ref: ${{ github.base_ref }}
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0
|
||||
with:
|
||||
node-version: '*'
|
||||
check-latest: true
|
||||
@@ -204,8 +204,8 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0
|
||||
with:
|
||||
node-version: '*'
|
||||
check-latest: true
|
||||
@@ -221,8 +221,8 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0
|
||||
with:
|
||||
node-version: '*'
|
||||
check-latest: true
|
||||
@@ -241,8 +241,8 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0
|
||||
with:
|
||||
node-version: '*'
|
||||
check-latest: true
|
||||
|
||||
@@ -42,11 +42,11 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
uses: github/codeql-action/init@49abf0ba24d0b7953cb586944e918a0b92074c80 # v2.22.4
|
||||
with:
|
||||
config-file: ./.github/codeql/codeql-configuration.yml
|
||||
# Override language selection by uncommenting this and choosing your languages
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below).
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
uses: github/codeql-action/autobuild@49abf0ba24d0b7953cb586944e918a0b92074c80 # v2.22.4
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||
@@ -70,4 +70,4 @@ jobs:
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
uses: github/codeql-action/analyze@49abf0ba24d0b7953cb586944e918a0b92074c80 # v2.22.4
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
# 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: {}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
# Ensure scripts are run with pipefail. See:
|
||||
# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#exit-codes-and-error-action-preference
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'microsoft/TypeScript'
|
||||
|
||||
steps:
|
||||
- name: Configure git
|
||||
run: |
|
||||
git config --global user.email "typescriptbot@microsoft.com"
|
||||
git config --global user.name "TypeScript Bot"
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
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@v3
|
||||
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
|
||||
@@ -17,14 +17,13 @@ jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
with:
|
||||
fetch-depth: 5
|
||||
- uses: actions/setup-node@v3
|
||||
filter: blob:none # https://github.blog/2020-12-21-get-up-to-speed-with-partial-clone-and-shallow-clone/
|
||||
fetch-depth: 0 # Default is 1; need to set to 0 to get the benefits of blob:none.
|
||||
token: ${{ secrets.TS_BOT_GITHUB_TOKEN }}
|
||||
- uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0
|
||||
- run: |
|
||||
npm --version
|
||||
# corepack enable npm
|
||||
@@ -38,10 +37,11 @@ jobs:
|
||||
sed -i -e 's/const versionMajorMinor = ".*"/const versionMajorMinor = "${{ github.event.client_payload.core_major_minor }}"/g' tests/baselines/reference/api/tsserverlibrary.d.ts
|
||||
sed -i -e 's/const version\(: string\)\{0,1\} = `${versionMajorMinor}.0-.*`/const version = `${versionMajorMinor}.0-${{ github.event.client_payload.core_tag || 'dev' }}`/g' src/compiler/corePublic.ts
|
||||
npm ci
|
||||
npm install # update package-lock.json to ensure the version bump is included
|
||||
npx hereby LKG
|
||||
npm test
|
||||
git diff
|
||||
git add package.json
|
||||
git add package.json package-lock.json
|
||||
git add src/compiler/corePublic.ts
|
||||
git add tests/baselines/reference/api/typescript.d.ts
|
||||
git add tests/baselines/reference/api/tsserverlibrary.d.ts
|
||||
|
||||
@@ -23,8 +23,8 @@ jobs:
|
||||
if: github.repository == 'microsoft/TypeScript'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0
|
||||
with:
|
||||
# Use NODE_AUTH_TOKEN environment variable to authenticate to this registry.
|
||||
registry-url: https://registry.npmjs.org/
|
||||
|
||||
@@ -19,8 +19,8 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0
|
||||
- run: |
|
||||
npm --version
|
||||
# corepack enable npm
|
||||
@@ -41,7 +41,7 @@ jobs:
|
||||
npm pack ./
|
||||
mv typescript-*.tgz typescript.tgz
|
||||
- name: Upload built tarfile
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
|
||||
with:
|
||||
name: tgz
|
||||
path: typescript.tgz
|
||||
|
||||
@@ -24,16 +24,14 @@ jobs:
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 5
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- uses: microsoft/RichCodeNavIndexer@v0.1
|
||||
- uses: microsoft/RichCodeNavIndexer@e444937494b8941cc2bcccf2bcf2cd595d402f6b # v0.1
|
||||
with:
|
||||
languages: typescript
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -29,12 +29,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: 'Checkout code'
|
||||
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: 'Run analysis'
|
||||
uses: ossf/scorecard-action@80e868c13c90f172d68d1f4501dee99e2479f7af # v2.1.3
|
||||
uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1
|
||||
with:
|
||||
results_file: results.sarif
|
||||
results_format: sarif
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
|
||||
# format to the repository Actions tab.
|
||||
- name: 'Upload artifact'
|
||||
uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # v3.1.0
|
||||
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
|
||||
with:
|
||||
name: SARIF file
|
||||
path: results.sarif
|
||||
@@ -55,6 +55,6 @@ jobs:
|
||||
|
||||
# Upload the results to GitHub's code scanning dashboard.
|
||||
- name: 'Upload to code-scanning'
|
||||
uses: github/codeql-action/upload-sarif@807578363a7869ca324a79039e6db9c843e0e100 # v2.1.27
|
||||
uses: github/codeql-action/upload-sarif@49abf0ba24d0b7953cb586944e918a0b92074c80 # v2.22.4
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
|
||||
@@ -16,15 +16,12 @@ defaults:
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
with:
|
||||
ref: ${{ github.event.client_payload.branch_name }}
|
||||
- uses: actions/setup-node@v3
|
||||
token: ${{ secrets.TS_BOT_GITHUB_TOKEN }}
|
||||
- uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0
|
||||
- run: |
|
||||
npm --version
|
||||
# corepack enable npm
|
||||
@@ -44,10 +41,11 @@ jobs:
|
||||
sed -i -e 's/const versionMajorMinor = ".*"/const versionMajorMinor = "${{ github.event.client_payload.core_major_minor }}"/g' tests/baselines/reference/api/tsserverlibrary.d.ts
|
||||
sed -i -e 's/const version\(: string\)\{0,1\} = .*;/const version = "${{ github.event.client_payload.package_version }}" as string;/g' src/compiler/corePublic.ts
|
||||
npm ci
|
||||
npm install # update package-lock.json to ensure the version bump is included
|
||||
npx hereby LKG
|
||||
npm test
|
||||
git diff
|
||||
git add package.json
|
||||
git add package.json package-lock.json
|
||||
git add src/compiler/corePublic.ts
|
||||
git add tests/baselines/reference/api/typescript.d.ts
|
||||
git add tests/baselines/reference/api/tsserverlibrary.d.ts
|
||||
|
||||
@@ -22,16 +22,14 @@ jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
with:
|
||||
ref: ${{ github.event.inputs.branch_name || github.event.client_payload.branch_name }}
|
||||
fetch-depth: 0
|
||||
# This does a test post-merge and only pushes the result if the test succeeds
|
||||
filter: blob:none # https://github.blog/2020-12-21-get-up-to-speed-with-partial-clone-and-shallow-clone/
|
||||
fetch-depth: 0 # Default is 1; need to set to 0 to get the benefits of blob:none.
|
||||
token: ${{ secrets.TS_BOT_GITHUB_TOKEN }}
|
||||
# required client_payload members:
|
||||
# branch_name - the target branch
|
||||
- run: |
|
||||
@@ -40,5 +38,7 @@ jobs:
|
||||
git fetch origin main
|
||||
git merge origin/main --no-ff
|
||||
npm ci
|
||||
npm test
|
||||
npx hereby LKG
|
||||
git add --force ./lib
|
||||
git commit -m 'Update LKG'
|
||||
git push
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
- name: Get repo name
|
||||
run: R=${GITHUB_REPOSITORY%?wiki}; echo "BASENAME=${R##*/}" >> $GITHUB_ENV
|
||||
- name: Checkout ${{ env.BASENAME }}-wiki
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
with:
|
||||
repository: '${{ GITHUB.repository_owner }}/${{ env.BASENAME }}-wiki'
|
||||
token: ${{ secrets.TS_BOT_GITHUB_TOKEN }}
|
||||
|
||||
@@ -34,13 +34,14 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- if: ${{ github.event.inputs.bisect }}
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
filter: blob:none # https://github.blog/2020-12-21-get-up-to-speed-with-partial-clone-and-shallow-clone/
|
||||
fetch-depth: 0 # Default is 1; need to set to 0 to get the benefits of blob:none.
|
||||
- if: ${{ !github.event.inputs.bisect }}
|
||||
uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: microsoft/TypeScript-Twoslash-Repro-Action@master
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0
|
||||
- uses: microsoft/TypeScript-Twoslash-Repro-Action@80178415feb0ebd3ed88d09e263a7cb8c8d6e1d2 # master
|
||||
with:
|
||||
github-token: ${{ secrets.TS_BOT_GITHUB_TOKEN }}
|
||||
issue: ${{ github.event.inputs.issue }}
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
name: Update LKG
|
||||
|
||||
on:
|
||||
workflow_dispatch: {}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
# Ensure scripts are run with pipefail. See:
|
||||
# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#exit-codes-and-error-action-preference
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
|
||||
- name: Configure Git and Update LKG
|
||||
run: |
|
||||
git config user.email "typescriptbot@microsoft.com"
|
||||
git config user.name "TypeScript Bot"
|
||||
npm ci
|
||||
npx hereby LKG
|
||||
npm test
|
||||
git diff
|
||||
git add --force ./lib
|
||||
git commit -m "Update LKG"
|
||||
git push
|
||||
@@ -21,14 +21,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'microsoft/TypeScript'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
with:
|
||||
token: ${{ secrets.TS_BOT_GITHUB_TOKEN }}
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0
|
||||
with:
|
||||
node-version: 16
|
||||
- run: |
|
||||
@@ -46,7 +43,7 @@ jobs:
|
||||
echo "No change."
|
||||
else
|
||||
npm test
|
||||
npx hereby lkg
|
||||
npx hereby LKG
|
||||
git config user.email "typescriptbot@microsoft.com"
|
||||
git config user.name "TypeScript Bot"
|
||||
git add -f package-lock.json
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
# TypeScript
|
||||
|
||||
[](https://github.com/microsoft/TypeScript/actions?query=workflow%3ACI)
|
||||
[](https://dev.azure.com/typescript/TypeScript/_build?definitionId=7)
|
||||
[](https://www.npmjs.com/package/typescript)
|
||||
[](https://www.npmjs.com/package/typescript)
|
||||
[](https://api.securityscorecards.dev/projects/github.com/microsoft/TypeScript)
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
# Support
|
||||
|
||||
## How to file issues and get help
|
||||
|
||||
This project uses GitHub issues to track bugs and feature requests. Please search the [existing issues](https://github.com/microsoft/TypeScript/issues) before filing new issues to avoid duplicates. For new issues, file your bug or feature request as a new issue.
|
||||
|
||||
For help and questions about using this project, please see the [`typescript` label on Stack Overflow](https://stackoverflow.com/questions/tagged/typescript) or the [`#typescript` channel on Discord](https://discord.com/invite/typescript).
|
||||
|
||||
## Microsoft Support Policy
|
||||
|
||||
TypeScript releases may include new features, fixes (security and/or non-security), or a combination of both. Some critical issues may be fixed in a servicing update to the latest release. When included with a Microsoft product, TypeScript support and servicing is offered under the [Modern Support Policy](https://learn.microsoft.com/lifecycle/policies/modern). For Visual Studio, servicing fixes are limited to security fixes for versions of TypeScript included in under-support releases of Visual Studio. Community support for this project is limited to the resources listed above. Assisted support is available from a professional support engineer by opening a ticket with the [Microsoft assisted support team](https://support.serviceshub.microsoft.com/supportforbusiness/onboarding).
|
||||
Generated
+712
-559
File diff suppressed because it is too large
Load Diff
+3
-2
@@ -59,7 +59,7 @@
|
||||
"chalk": "^4.1.2",
|
||||
"chokidar": "^3.5.3",
|
||||
"diff": "^5.1.0",
|
||||
"dprint": "^0.41.0",
|
||||
"dprint": "^0.42.3",
|
||||
"esbuild": "^0.19.0",
|
||||
"eslint": "^8.22.0",
|
||||
"eslint-formatter-autolinkable-stylish": "^1.2.0",
|
||||
@@ -106,7 +106,8 @@
|
||||
"buffer": false,
|
||||
"@microsoft/typescript-etw": false,
|
||||
"source-map-support": false,
|
||||
"inspector": false
|
||||
"inspector": false,
|
||||
"perf_hooks": false
|
||||
},
|
||||
"packageManager": "npm@8.19.4",
|
||||
"volta": {
|
||||
|
||||
@@ -280,7 +280,7 @@ export function getBuilderFileEmit(options: CompilerOptions) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Determing what all is pending to be emitted based on previous options or previous file emit flags
|
||||
* Determining what all is pending to be emitted based on previous options or previous file emit flags
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
@@ -1588,7 +1588,7 @@ export function createBuilderProgram(kind: BuilderProgramKind, { newProgram, hos
|
||||
info.signature = signature;
|
||||
}
|
||||
else {
|
||||
// These are directly commited
|
||||
// These are directly committed
|
||||
info.signature = signature;
|
||||
state.oldExportedModulesMap?.clear();
|
||||
}
|
||||
|
||||
@@ -84,11 +84,11 @@ export interface BuilderState {
|
||||
*/
|
||||
hasCalledUpdateShapeSignature?: Set<Path>;
|
||||
/**
|
||||
* Stores signatures before before the update till affected file is commited
|
||||
* Stores signatures before before the update till affected file is committed
|
||||
*/
|
||||
oldSignatures?: Map<Path, string | false>;
|
||||
/**
|
||||
* Stores exportedModulesMap before the update till affected file is commited
|
||||
* Stores exportedModulesMap before the update till affected file is committed
|
||||
*/
|
||||
oldExportedModulesMap?: Map<Path, ReadonlySet<Path> | false>;
|
||||
/**
|
||||
|
||||
+270
-103
@@ -780,6 +780,7 @@ import {
|
||||
JSDocSatisfiesTag,
|
||||
JSDocSignature,
|
||||
JSDocTemplateTag,
|
||||
JSDocThisTag,
|
||||
JSDocTypeAssertion,
|
||||
JSDocTypedefTag,
|
||||
JSDocTypeExpression,
|
||||
@@ -1009,6 +1010,7 @@ import {
|
||||
tokenToString,
|
||||
tracing,
|
||||
TracingNode,
|
||||
TrackedSymbol,
|
||||
TransientSymbol,
|
||||
TransientSymbolLinks,
|
||||
tryAddToSet,
|
||||
@@ -3788,8 +3790,19 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
function checkAndReportErrorForUsingTypeAsValue(errorLocation: Node, name: __String, meaning: SymbolFlags): boolean {
|
||||
if (meaning & SymbolFlags.Value) {
|
||||
if (isPrimitiveTypeName(name)) {
|
||||
if (isExtendedByInterface(errorLocation)) {
|
||||
error(errorLocation, Diagnostics.An_interface_cannot_extend_a_primitive_type_like_0_an_interface_can_only_extend_named_types_and_classes, unescapeLeadingUnderscores(name));
|
||||
const grandparent = errorLocation.parent.parent;
|
||||
if (grandparent && grandparent.parent && isHeritageClause(grandparent)) {
|
||||
const heritageKind = grandparent.token;
|
||||
const containerKind = grandparent.parent.kind;
|
||||
if (containerKind === SyntaxKind.InterfaceDeclaration && heritageKind === SyntaxKind.ExtendsKeyword) {
|
||||
error(errorLocation, Diagnostics.An_interface_cannot_extend_a_primitive_type_like_0_It_can_only_extend_other_named_object_types, unescapeLeadingUnderscores(name));
|
||||
}
|
||||
else if (containerKind === SyntaxKind.ClassDeclaration && heritageKind === SyntaxKind.ExtendsKeyword) {
|
||||
error(errorLocation, Diagnostics.A_class_cannot_extend_a_primitive_type_like_0_Classes_can_only_extend_constructable_values, unescapeLeadingUnderscores(name));
|
||||
}
|
||||
else if (containerKind === SyntaxKind.ClassDeclaration && heritageKind === SyntaxKind.ImplementsKeyword) {
|
||||
error(errorLocation, Diagnostics.A_class_cannot_implement_a_primitive_type_like_0_It_can_only_implement_other_named_object_types, unescapeLeadingUnderscores(name));
|
||||
}
|
||||
}
|
||||
else {
|
||||
error(errorLocation, Diagnostics._0_only_refers_to_a_type_but_is_being_used_as_a_value_here, unescapeLeadingUnderscores(name));
|
||||
@@ -3815,17 +3828,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
return false;
|
||||
}
|
||||
|
||||
function isExtendedByInterface(node: Node): boolean {
|
||||
const grandparent = node.parent.parent;
|
||||
const parentOfGrandparent = grandparent.parent;
|
||||
if (grandparent && parentOfGrandparent) {
|
||||
const isExtending = isHeritageClause(grandparent) && grandparent.token === SyntaxKind.ExtendsKeyword;
|
||||
const isInterface = isInterfaceDeclaration(parentOfGrandparent);
|
||||
return isExtending && isInterface;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function maybeMappedType(node: Node, symbol: Symbol) {
|
||||
const container = findAncestor(node.parent, n => isComputedPropertyName(n) || isPropertySignature(n) ? false : isTypeLiteralNode(n) || "quit") as TypeLiteralNode | undefined;
|
||||
if (container && container.members.length === 1) {
|
||||
@@ -4022,7 +4024,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
}
|
||||
|
||||
function isSyntacticDefault(node: Node) {
|
||||
return ((isExportAssignment(node) && !node.isExportEquals) || hasSyntacticModifier(node, ModifierFlags.Default) || isExportSpecifier(node));
|
||||
return ((isExportAssignment(node) && !node.isExportEquals)
|
||||
|| hasSyntacticModifier(node, ModifierFlags.Default)
|
||||
|| isExportSpecifier(node)
|
||||
|| isNamespaceExport(node));
|
||||
}
|
||||
|
||||
function getUsageModeForExpression(usage: Expression) {
|
||||
@@ -6420,6 +6425,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
symbolDepth: undefined,
|
||||
inferTypeParameters: undefined,
|
||||
approximateLength: 0,
|
||||
trackedSymbols: undefined,
|
||||
};
|
||||
context.tracker = new SymbolTrackerImpl(context, tracker, moduleResolverHost);
|
||||
const resultingNode = cb(context);
|
||||
@@ -6918,6 +6924,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
}
|
||||
const cachedResult = links?.serializedTypes?.get(key);
|
||||
if (cachedResult) {
|
||||
// TODO:: check if we instead store late painted statements associated with this?
|
||||
cachedResult.trackedSymbols?.forEach(
|
||||
([symbol, enclosingDeclaration, meaning]) =>
|
||||
context.tracker.trackSymbol(
|
||||
symbol,
|
||||
enclosingDeclaration,
|
||||
meaning,
|
||||
),
|
||||
);
|
||||
if (cachedResult.truncating) {
|
||||
context.truncating = true;
|
||||
}
|
||||
@@ -6938,7 +6953,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
const result = transform(type);
|
||||
const addedLength = context.approximateLength - startLength;
|
||||
if (!context.reportedDiagnostic && !context.encounteredError) {
|
||||
links?.serializedTypes?.set(key, { node: result, truncating: context.truncating, addedLength });
|
||||
links?.serializedTypes?.set(key, {
|
||||
node: result,
|
||||
truncating: context.truncating,
|
||||
addedLength,
|
||||
trackedSymbols: context.trackedSymbols,
|
||||
});
|
||||
}
|
||||
context.visitedTypes.delete(typeId);
|
||||
if (id) {
|
||||
@@ -7289,6 +7309,30 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
const propertyName = getPropertyNameNodeForSymbol(propertySymbol, context);
|
||||
context.enclosingDeclaration = saveEnclosingDeclaration;
|
||||
context.approximateLength += symbolName(propertySymbol).length + 1;
|
||||
|
||||
if (propertySymbol.flags & SymbolFlags.Accessor) {
|
||||
const writeType = getWriteTypeOfSymbol(propertySymbol);
|
||||
if (propertyType !== writeType) {
|
||||
const getterDeclaration = getDeclarationOfKind<GetAccessorDeclaration>(propertySymbol, SyntaxKind.GetAccessor)!;
|
||||
const getterSignature = getSignatureFromDeclaration(getterDeclaration);
|
||||
typeElements.push(
|
||||
setCommentRange(
|
||||
signatureToSignatureDeclarationHelper(getterSignature, SyntaxKind.GetAccessor, context, { name: propertyName }) as GetAccessorDeclaration,
|
||||
getterDeclaration,
|
||||
),
|
||||
);
|
||||
const setterDeclaration = getDeclarationOfKind<SetAccessorDeclaration>(propertySymbol, SyntaxKind.SetAccessor)!;
|
||||
const setterSignature = getSignatureFromDeclaration(setterDeclaration);
|
||||
typeElements.push(
|
||||
setCommentRange(
|
||||
signatureToSignatureDeclarationHelper(setterSignature, SyntaxKind.SetAccessor, context, { name: propertyName }) as SetAccessorDeclaration,
|
||||
setterDeclaration,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const optionalToken = propertySymbol.flags & SymbolFlags.Optional ? factory.createToken(SyntaxKind.QuestionToken) : undefined;
|
||||
if (propertySymbol.flags & (SymbolFlags.Function | SymbolFlags.Method) && !getPropertiesOfObjectType(propertyType).length && !isReadonlySymbol(propertySymbol)) {
|
||||
const signatures = getSignaturesOfType(filterType(propertyType, t => !(t.flags & TypeFlags.Undefined)), SignatureKind.Call);
|
||||
@@ -7329,9 +7373,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
typeElements.push(preserveCommentsOn(propertySignature));
|
||||
|
||||
function preserveCommentsOn<T extends Node>(node: T) {
|
||||
if (some(propertySymbol.declarations, d => d.kind === SyntaxKind.JSDocPropertyTag)) {
|
||||
const d = propertySymbol.declarations?.find(d => d.kind === SyntaxKind.JSDocPropertyTag)! as JSDocPropertyTag;
|
||||
const commentText = getTextOfJSDocComment(d.comment);
|
||||
const jsdocPropertyTag = propertySymbol.declarations?.find((d): d is JSDocPropertyTag => d.kind === SyntaxKind.JSDocPropertyTag);
|
||||
if (jsdocPropertyTag) {
|
||||
const commentText = getTextOfJSDocComment(jsdocPropertyTag.comment);
|
||||
if (commentText) {
|
||||
setSyntheticLeadingComments(node, [{ kind: SyntaxKind.MultiLineCommentTrivia, text: "*\n * " + commentText.replace(/\n/g, "\n * ") + "\n ", pos: -1, end: -1, hasTrailingNewLine: true }]);
|
||||
}
|
||||
@@ -8789,12 +8833,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
|
||||
function addExportModifier(node: Extract<HasModifiers, Statement>) {
|
||||
const flags = (getEffectiveModifierFlags(node) | ModifierFlags.Export) & ~ModifierFlags.Ambient;
|
||||
return factory.updateModifiers(node, flags);
|
||||
return factory.replaceModifiers(node, flags);
|
||||
}
|
||||
|
||||
function removeExportModifier(node: Extract<HasModifiers, Statement>) {
|
||||
const flags = getEffectiveModifierFlags(node) & ~ModifierFlags.Export;
|
||||
return factory.updateModifiers(node, flags);
|
||||
return factory.replaceModifiers(node, flags);
|
||||
}
|
||||
|
||||
function visitSymbolTable(symbolTable: SymbolTable, suppressNewPrivateContext?: boolean, propertyAsAlias?: boolean) {
|
||||
@@ -8832,6 +8876,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
if (context.reportedDiagnostic) {
|
||||
oldcontext.reportedDiagnostic = context.reportedDiagnostic; // hoist diagnostic result into outer context
|
||||
}
|
||||
if (context.trackedSymbols) {
|
||||
if (!oldContext.trackedSymbols) oldContext.trackedSymbols = context.trackedSymbols;
|
||||
else Debug.assert(context.trackedSymbols === oldContext.trackedSymbols);
|
||||
}
|
||||
context = oldContext;
|
||||
}
|
||||
}
|
||||
@@ -9093,7 +9141,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
newModifierFlags |= ModifierFlags.Default;
|
||||
}
|
||||
if (newModifierFlags) {
|
||||
node = factory.updateModifiers(node, newModifierFlags | getEffectiveModifierFlags(node));
|
||||
node = factory.replaceModifiers(node, newModifierFlags | getEffectiveModifierFlags(node));
|
||||
}
|
||||
}
|
||||
results.push(node);
|
||||
@@ -9795,7 +9843,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
!(typeToSerialize.symbol && some(typeToSerialize.symbol.declarations, d => getSourceFileOfNode(d) !== ctxSrc)) &&
|
||||
!some(getPropertiesOfType(typeToSerialize), p => isLateBoundName(p.escapedName)) &&
|
||||
!some(getPropertiesOfType(typeToSerialize), p => some(p.declarations, d => getSourceFileOfNode(d) !== ctxSrc)) &&
|
||||
every(getPropertiesOfType(typeToSerialize), p => isIdentifierText(symbolName(p), languageVersion));
|
||||
every(getPropertiesOfType(typeToSerialize), p => {
|
||||
if (!isIdentifierText(symbolName(p), languageVersion)) {
|
||||
return false;
|
||||
}
|
||||
if (!(p.flags & SymbolFlags.Accessor)) {
|
||||
return true;
|
||||
}
|
||||
return getNonMissingTypeOfSymbol(p) === getWriteTypeOfSymbol(p);
|
||||
});
|
||||
}
|
||||
|
||||
function makeSerializePropertySymbol<T extends Node>(
|
||||
@@ -11776,9 +11832,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
function getTypeOfAlias(symbol: Symbol): Type {
|
||||
const links = getSymbolLinks(symbol);
|
||||
if (!links.type) {
|
||||
if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) {
|
||||
return errorType;
|
||||
}
|
||||
const targetSymbol = resolveAlias(symbol);
|
||||
const exportSymbol = symbol.declarations && getTargetOfAliasDeclaration(getDeclarationOfAliasSymbol(symbol)!, /*dontRecursivelyResolve*/ true);
|
||||
const declaredType = firstDefined(exportSymbol?.declarations, d => isExportAssignment(d) ? tryGetTypeFromEffectiveTypeNode(d) : undefined);
|
||||
|
||||
// It only makes sense to get the type of a value symbol. If the result of resolving
|
||||
// the alias is not a value, then it has no type. To get the type associated with a
|
||||
// type symbol, call getDeclaredTypeOfSymbol.
|
||||
@@ -11789,6 +11849,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
: declaredType ? declaredType
|
||||
: getSymbolFlags(targetSymbol) & SymbolFlags.Value ? getTypeOfSymbol(targetSymbol)
|
||||
: errorType;
|
||||
|
||||
if (!popTypeResolution()) {
|
||||
reportCircularityError(exportSymbol ?? symbol);
|
||||
return links.type = errorType;
|
||||
}
|
||||
}
|
||||
return links.type;
|
||||
}
|
||||
@@ -11804,15 +11869,23 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
}
|
||||
|
||||
function reportCircularityError(symbol: Symbol) {
|
||||
const declaration = symbol.valueDeclaration as VariableLikeDeclaration;
|
||||
const declaration = symbol.valueDeclaration;
|
||||
// Check if variable has type annotation that circularly references the variable itself
|
||||
if (getEffectiveTypeAnnotationNode(declaration)) {
|
||||
error(symbol.valueDeclaration, Diagnostics._0_is_referenced_directly_or_indirectly_in_its_own_type_annotation, symbolToString(symbol));
|
||||
return errorType;
|
||||
if (declaration) {
|
||||
if (getEffectiveTypeAnnotationNode(declaration)) {
|
||||
error(symbol.valueDeclaration, Diagnostics._0_is_referenced_directly_or_indirectly_in_its_own_type_annotation, symbolToString(symbol));
|
||||
return errorType;
|
||||
}
|
||||
// Check if variable has initializer that circularly references the variable itself
|
||||
if (noImplicitAny && (declaration.kind !== SyntaxKind.Parameter || (declaration as HasInitializer).initializer)) {
|
||||
error(symbol.valueDeclaration, Diagnostics._0_implicitly_has_type_any_because_it_does_not_have_a_type_annotation_and_is_referenced_directly_or_indirectly_in_its_own_initializer, symbolToString(symbol));
|
||||
}
|
||||
}
|
||||
// Check if variable has initializer that circularly references the variable itself
|
||||
if (noImplicitAny && (declaration.kind !== SyntaxKind.Parameter || (declaration as HasInitializer).initializer)) {
|
||||
error(symbol.valueDeclaration, Diagnostics._0_implicitly_has_type_any_because_it_does_not_have_a_type_annotation_and_is_referenced_directly_or_indirectly_in_its_own_initializer, symbolToString(symbol));
|
||||
else if (symbol.flags & SymbolFlags.Alias) {
|
||||
const node = getDeclarationOfAliasSymbol(symbol);
|
||||
if (node) {
|
||||
error(node, Diagnostics.Circular_definition_of_import_alias_0, symbolToString(symbol));
|
||||
}
|
||||
}
|
||||
// Circularities could also result from parameters in function expressions that end up
|
||||
// having themselves as contextual types following type argument inference. In those cases
|
||||
@@ -17727,7 +17800,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
return false;
|
||||
}
|
||||
|
||||
function getPropertyNameFromIndex(indexType: Type, accessNode: StringLiteral | Identifier | PrivateIdentifier | ObjectBindingPattern | ArrayBindingPattern | ComputedPropertyName | NumericLiteral | IndexedAccessTypeNode | ElementAccessExpression | SyntheticExpression | undefined) {
|
||||
function getPropertyNameFromIndex(indexType: Type, accessNode: PropertyName | ObjectBindingPattern | ArrayBindingPattern | IndexedAccessTypeNode | ElementAccessExpression | SyntheticExpression | undefined) {
|
||||
return isTypeUsableAsPropertyName(indexType) ?
|
||||
getPropertyNameFromType(indexType) :
|
||||
accessNode && isPropertyName(accessNode) ?
|
||||
@@ -17810,7 +17883,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
if (indexInfo) {
|
||||
if (accessFlags & AccessFlags.NoIndexSignatures && indexInfo.keyType !== numberType) {
|
||||
if (accessExpression) {
|
||||
error(accessExpression, Diagnostics.Type_0_cannot_be_used_to_index_type_1, typeToString(indexType), typeToString(originalObjectType));
|
||||
if (accessFlags & AccessFlags.Writing) {
|
||||
error(accessExpression, Diagnostics.Type_0_is_generic_and_can_only_be_indexed_for_reading, typeToString(originalObjectType));
|
||||
}
|
||||
else {
|
||||
error(accessExpression, Diagnostics.Type_0_cannot_be_used_to_index_type_1, typeToString(indexType), typeToString(originalObjectType));
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
@@ -17947,7 +18025,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
|
||||
function isPatternLiteralPlaceholderType(type: Type): boolean {
|
||||
if (type.flags & TypeFlags.Intersection) {
|
||||
return some((type as IntersectionType).types, t => !!(t.flags & (TypeFlags.Literal | TypeFlags.Null | TypeFlags.Undefined)) || isPatternLiteralPlaceholderType(t));
|
||||
return !isGenericType(type) && some((type as IntersectionType).types, t => !!(t.flags & (TypeFlags.Literal | TypeFlags.Nullable)) || isPatternLiteralPlaceholderType(t));
|
||||
}
|
||||
return !!(type.flags & (TypeFlags.Any | TypeFlags.String | TypeFlags.Number | TypeFlags.BigInt)) || isPatternLiteralType(type);
|
||||
}
|
||||
@@ -23528,22 +23606,22 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
!hasBaseType(checkClass, getDeclaringClass(p)) : false) ? undefined : checkClass;
|
||||
}
|
||||
|
||||
// Return true if the given type is deeply nested. We consider this to be the case when structural type comparisons
|
||||
// for maxDepth or more occurrences or instantiations of the same type have been recorded on the given stack. The
|
||||
// "sameness" of instantiations is determined by the getRecursionIdentity function. An intersection is considered
|
||||
// deeply nested if any constituent of the intersection is deeply nested. It is possible, though highly unlikely, for
|
||||
// the deeply nested check to be true in a situation where a chain of instantiations is not infinitely expanding.
|
||||
// Effectively, we will generate a false positive when two types are structurally equal to at least maxDepth levels,
|
||||
// but unequal at some level beyond that.
|
||||
// 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<T> = null extends T ? [A<NonNullable<T>>] : [T]`
|
||||
// has expanded into `[A<NonNullable<NonNullable<NonNullable<NonNullable<NonNullable<T>>>>>>]`. In such cases we need
|
||||
// to terminate the expansion, and we do so here.
|
||||
// Return true if the given type is deeply nested. We consider this to be the case when the given stack contains
|
||||
// maxDepth or more occurrences of types with the same recursion identity as the given type. The recursion identity
|
||||
// provides a shared identity for type instantiations that repeat in some (possibly infinite) pattern. For example,
|
||||
// in `type Deep<T> = { next: Deep<Deep<T>> }`, repeatedly referencing the `next` property leads to an infinite
|
||||
// sequence of ever deeper instantiations with the same recursion identity (in this case the symbol associated with
|
||||
// the object type literal).
|
||||
// A homomorphic mapped type is considered deeply nested if its target type is deeply nested, and an intersection is
|
||||
// considered deeply nested if any constituent of the intersection is deeply nested.
|
||||
// It is possible, though highly unlikely, for the deeply nested check to be true in a situation where a chain of
|
||||
// instantiations is not infinitely expanding. Effectively, we will generate a false positive when two types are
|
||||
// structurally equal to at least maxDepth levels, but unequal at some level beyond that.
|
||||
function isDeeplyNestedType(type: Type, stack: Type[], depth: number, maxDepth = 3): boolean {
|
||||
if (depth >= maxDepth) {
|
||||
if ((getObjectFlags(type) & ObjectFlags.InstantiatedMapped) === ObjectFlags.InstantiatedMapped) {
|
||||
type = getMappedTargetWithSymbol(type);
|
||||
}
|
||||
if (type.flags & TypeFlags.Intersection) {
|
||||
return some((type as IntersectionType).types, t => isDeeplyNestedType(t, stack, depth, maxDepth));
|
||||
}
|
||||
@@ -23552,7 +23630,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
let lastTypeId = 0;
|
||||
for (let i = 0; i < depth; i++) {
|
||||
const t = stack[i];
|
||||
if (t.flags & TypeFlags.Intersection ? some((t as IntersectionType).types, u => getRecursionIdentity(u) === identity) : getRecursionIdentity(t) === identity) {
|
||||
if (hasMatchingRecursionIdentity(t, identity)) {
|
||||
// We only count occurrences with a higher type id than the previous occurrence, since higher
|
||||
// type ids are an indicator of newer instantiations caused by recursion.
|
||||
if (t.id >= lastTypeId) {
|
||||
@@ -23568,6 +23646,32 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Unwrap nested homomorphic mapped types and return the deepest target type that has a symbol. This better
|
||||
// preserves unique type identities for mapped types applied to explicitly written object literals. For example
|
||||
// in `Mapped<{ x: Mapped<{ x: Mapped<{ x: string }>}>}>`, each of the mapped type applications will have a
|
||||
// unique recursion identity (that of their target object type literal) and thus avoid appearing deeply nested.
|
||||
function getMappedTargetWithSymbol(type: Type) {
|
||||
let target;
|
||||
while (
|
||||
(getObjectFlags(type) & ObjectFlags.InstantiatedMapped) === ObjectFlags.InstantiatedMapped &&
|
||||
(target = getModifiersTypeFromMappedType(type as MappedType)) &&
|
||||
(target.symbol || target.flags & TypeFlags.Intersection && some((target as IntersectionType).types, t => !!t.symbol))
|
||||
) {
|
||||
type = target;
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
function hasMatchingRecursionIdentity(type: Type, identity: object): boolean {
|
||||
if ((getObjectFlags(type) & ObjectFlags.InstantiatedMapped) === ObjectFlags.InstantiatedMapped) {
|
||||
type = getMappedTargetWithSymbol(type);
|
||||
}
|
||||
if (type.flags & TypeFlags.Intersection) {
|
||||
return some((type as IntersectionType).types, t => hasMatchingRecursionIdentity(t, identity));
|
||||
}
|
||||
return getRecursionIdentity(type) === identity;
|
||||
}
|
||||
|
||||
// The recursion identity of a type is an object identity that is shared among multiple instantiations of the type.
|
||||
// We track recursion identities in order to identify deeply nested and possibly infinite type instantiations with
|
||||
// the same origin. For example, when type parameters are in scope in an object type such as { x: T }, all
|
||||
@@ -23583,28 +23687,22 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
// unique AST node.
|
||||
return (type as TypeReference).node!;
|
||||
}
|
||||
if (type.symbol) {
|
||||
// We track object types that have a symbol by that symbol (representing the origin of the type).
|
||||
if (getObjectFlags(type) & ObjectFlags.Mapped) {
|
||||
// When a homomorphic mapped type is applied to a type with a symbol, we use the symbol of that
|
||||
// type as the recursion identity. This is a better strategy than using the symbol of the mapped
|
||||
// type, which doesn't work well for recursive mapped types.
|
||||
type = getMappedTargetWithSymbol(type);
|
||||
}
|
||||
if (!(getObjectFlags(type) & ObjectFlags.Anonymous && type.symbol.flags & SymbolFlags.Class)) {
|
||||
// We exclude the static side of a class since it shares its symbol with the instance side.
|
||||
return type.symbol;
|
||||
}
|
||||
if (type.symbol && !(getObjectFlags(type) & ObjectFlags.Anonymous && type.symbol.flags & SymbolFlags.Class)) {
|
||||
// We track object types that have a symbol by that symbol (representing the origin of the type), but
|
||||
// exclude the static side of a class since it shares its symbol with the instance side.
|
||||
return type.symbol;
|
||||
}
|
||||
if (isTupleType(type)) {
|
||||
return type.target;
|
||||
}
|
||||
}
|
||||
if (type.flags & TypeFlags.TypeParameter) {
|
||||
// We use the symbol of the type parameter such that all "fresh" instantiations of that type parameter
|
||||
// have the same recursion identity.
|
||||
return type.symbol;
|
||||
}
|
||||
if (type.flags & TypeFlags.IndexedAccess) {
|
||||
// Identity is the leftmost object type in a chain of indexed accesses, eg, in A[P][Q] it is A
|
||||
// Identity is the leftmost object type in a chain of indexed accesses, eg, in A[P1][P2][P3] it is A.
|
||||
do {
|
||||
type = (type as IndexedAccessType).objectType;
|
||||
}
|
||||
@@ -23618,14 +23716,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
return type;
|
||||
}
|
||||
|
||||
function getMappedTargetWithSymbol(type: Type) {
|
||||
let target = type;
|
||||
while ((getObjectFlags(target) & ObjectFlags.InstantiatedMapped) === ObjectFlags.InstantiatedMapped && isMappedTypeWithKeyofConstraintDeclaration(target as MappedType)) {
|
||||
target = getModifiersTypeFromMappedType(target as MappedType);
|
||||
}
|
||||
return target.symbol ? target : type;
|
||||
}
|
||||
|
||||
function isPropertyIdenticalTo(sourceProp: Symbol, targetProp: Symbol): boolean {
|
||||
return compareProperties(sourceProp, targetProp, compareTypesIdentical) !== Ternary.False;
|
||||
}
|
||||
@@ -27411,7 +27501,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
}
|
||||
|
||||
function getTypeAtSwitchClause(flow: FlowSwitchClause): FlowType {
|
||||
const expr = flow.switchStatement.expression;
|
||||
const expr = skipParentheses(flow.switchStatement.expression);
|
||||
const flowType = getTypeAtFlowNode(flow.antecedent);
|
||||
let type = getTypeFromFlowType(flowType);
|
||||
if (isMatchingReference(reference, expr)) {
|
||||
@@ -27421,11 +27511,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
type = narrowTypeBySwitchOnTypeOf(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd);
|
||||
}
|
||||
else if (expr.kind === SyntaxKind.TrueKeyword) {
|
||||
const clause = flow.switchStatement.caseBlock.clauses.find((_, index) => index === flow.clauseStart);
|
||||
const clauseExpression = clause && clause.kind === SyntaxKind.CaseClause ? clause.expression : undefined;
|
||||
if (clauseExpression) {
|
||||
type = narrowType(type, clauseExpression, /*assumeTrue*/ true);
|
||||
}
|
||||
type = narrowTypeBySwitchOnTrue(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd);
|
||||
}
|
||||
else {
|
||||
if (strictNullChecks) {
|
||||
@@ -27641,13 +27727,18 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
}
|
||||
|
||||
function getDiscriminantPropertyAccess(expr: Expression, computedType: Type) {
|
||||
const type = !(computedType.flags & TypeFlags.Union) && declaredType.flags & TypeFlags.Union ? declaredType : computedType;
|
||||
if (type.flags & TypeFlags.Union) {
|
||||
// As long as the computed type is a subset of the declared type, we use the full declared type to detect
|
||||
// a discriminant property. In cases where the computed type isn't a subset, e.g because of a preceding type
|
||||
// predicate narrowing, we use the actual computed type.
|
||||
if (declaredType.flags & TypeFlags.Union || computedType.flags & TypeFlags.Union) {
|
||||
const access = getCandidateDiscriminantPropertyAccess(expr);
|
||||
if (access) {
|
||||
const name = getAccessedPropertyName(access);
|
||||
if (name && isDiscriminantProperty(type, name)) {
|
||||
return access;
|
||||
if (name) {
|
||||
const type = declaredType.flags & TypeFlags.Union && isTypeSubsetOf(computedType, declaredType) ? declaredType : computedType;
|
||||
if (isDiscriminantProperty(type, name)) {
|
||||
return access;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28040,6 +28131,36 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
return getUnionType(map(clauseWitnesses, text => text ? narrowTypeByTypeName(type, text) : neverType));
|
||||
}
|
||||
|
||||
function narrowTypeBySwitchOnTrue(type: Type, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number): Type {
|
||||
const defaultIndex = findIndex(switchStatement.caseBlock.clauses, clause => clause.kind === SyntaxKind.DefaultClause);
|
||||
const hasDefaultClause = clauseStart === clauseEnd || (defaultIndex >= clauseStart && defaultIndex < clauseEnd);
|
||||
|
||||
// First, narrow away all of the cases that preceded this set of cases.
|
||||
for (let i = 0; i < clauseStart; i++) {
|
||||
const clause = switchStatement.caseBlock.clauses[i];
|
||||
if (clause.kind === SyntaxKind.CaseClause) {
|
||||
type = narrowType(type, clause.expression, /*assumeTrue*/ false);
|
||||
}
|
||||
}
|
||||
|
||||
// If our current set has a default, then none the other cases were hit either.
|
||||
// There's no point in narrowing by the the other cases in the set, since we can
|
||||
// get here through other paths.
|
||||
if (hasDefaultClause) {
|
||||
for (let i = clauseEnd; i < switchStatement.caseBlock.clauses.length; i++) {
|
||||
const clause = switchStatement.caseBlock.clauses[i];
|
||||
if (clause.kind === SyntaxKind.CaseClause) {
|
||||
type = narrowType(type, clause.expression, /*assumeTrue*/ false);
|
||||
}
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
// Now, narrow based on the cases in this set.
|
||||
const clauses = switchStatement.caseBlock.clauses.slice(clauseStart, clauseEnd);
|
||||
return getUnionType(map(clauses, clause => clause.kind === SyntaxKind.CaseClause ? narrowType(type, clause.expression, /*assumeTrue*/ true) : neverType));
|
||||
}
|
||||
|
||||
function isMatchingConstructorReference(expr: Expression) {
|
||||
return (isPropertyAccessExpression(expr) && idText(expr.name) === "constructor" ||
|
||||
isElementAccessExpression(expr) && isStringLiteralLike(expr.argumentExpression) && expr.argumentExpression.text === "constructor") &&
|
||||
@@ -29462,8 +29583,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
// In an assignment of the form 'obj.xxx = function(...)' or 'obj[xxx] = function(...)', the
|
||||
// contextual type for 'this' is 'obj'.
|
||||
const parent = walkUpParenthesizedExpressions(func.parent);
|
||||
if (parent.kind === SyntaxKind.BinaryExpression && (parent as BinaryExpression).operatorToken.kind === SyntaxKind.EqualsToken) {
|
||||
const target = (parent as BinaryExpression).left;
|
||||
if (isAssignmentExpression(parent)) {
|
||||
const target = parent.left;
|
||||
if (isAccessExpression(target)) {
|
||||
const { expression } = target;
|
||||
// Don't contextually type `this` as `exports` in `exports.Point = function(x, y) { this.x = x; this.y = y; }`
|
||||
@@ -29673,7 +29794,19 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
// and that call signature is non-generic, return statements are contextually typed by the return type of the signature
|
||||
const signature = getContextualSignatureForFunctionLikeDeclaration(functionDecl as FunctionExpression);
|
||||
if (signature && !isResolvingReturnTypeOfSignature(signature)) {
|
||||
return getReturnTypeOfSignature(signature);
|
||||
const returnType = getReturnTypeOfSignature(signature);
|
||||
const functionFlags = getFunctionFlags(functionDecl);
|
||||
if (functionFlags & FunctionFlags.Generator) {
|
||||
return filterType(returnType, t => {
|
||||
return !!(t.flags & (TypeFlags.AnyOrUnknown | TypeFlags.Void | TypeFlags.InstantiableNonPrimitive)) || checkGeneratorInstantiationAssignabilityToReturnType(t, functionFlags, /*errorNode*/ undefined);
|
||||
});
|
||||
}
|
||||
if (functionFlags & FunctionFlags.Async) {
|
||||
return filterType(returnType, t => {
|
||||
return !!(t.flags & (TypeFlags.AnyOrUnknown | TypeFlags.Void | TypeFlags.InstantiableNonPrimitive)) || !!getAwaitedTypeOfPromise(t);
|
||||
});
|
||||
}
|
||||
return returnType;
|
||||
}
|
||||
const iife = getImmediatelyInvokedFunctionExpression(functionDecl);
|
||||
if (iife) {
|
||||
@@ -31261,7 +31394,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
// Handle children attribute
|
||||
const parent = openingLikeElement.parent.kind === SyntaxKind.JsxElement ? openingLikeElement.parent as JsxElement : undefined;
|
||||
// We have to check that openingElement of the parent is the one we are visiting as this may not be true for selfClosingElement
|
||||
if (parent && parent.openingElement === openingLikeElement && parent.children.length > 0) {
|
||||
if (parent && parent.openingElement === openingLikeElement && getSemanticJsxChildren(parent.children).length > 0) {
|
||||
const childrenTypes: Type[] = checkJsxChildren(parent, checkMode);
|
||||
|
||||
if (!hasSpreadAnyType && jsxChildrenPropertyName && jsxChildrenPropertyName !== "") {
|
||||
@@ -33356,8 +33489,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
if (arg.kind === SyntaxKind.SyntheticExpression && (arg as SyntheticExpression).tupleNameSource) {
|
||||
names.push((arg as SyntheticExpression).tupleNameSource!);
|
||||
}
|
||||
else {
|
||||
names.push(undefined);
|
||||
}
|
||||
}
|
||||
return createTupleType(types, flags, inConstContext && !someType(restType, isMutableArrayLikeType), length(names) === length(types) ? names : undefined);
|
||||
return createTupleType(types, flags, inConstContext && !someType(restType, isMutableArrayLikeType), names);
|
||||
}
|
||||
|
||||
function checkTypeArguments(signature: Signature, typeArgumentNodes: readonly TypeNode[], reportErrors: boolean, headMessage?: DiagnosticMessage): Type[] | undefined {
|
||||
@@ -35741,12 +35877,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
types.push(restType);
|
||||
flags.push(ElementFlags.Variadic);
|
||||
}
|
||||
const name = getNameableDeclarationAtPosition(source, i);
|
||||
if (name) {
|
||||
names.push(name);
|
||||
}
|
||||
names.push(getNameableDeclarationAtPosition(source, i));
|
||||
}
|
||||
return createTupleType(types, flags, readonly, length(names) === length(types) ? names : undefined);
|
||||
return createTupleType(types, flags, readonly, names);
|
||||
}
|
||||
|
||||
// Return the number of parameters in a signature. The rest parameter, if present, counts as one
|
||||
@@ -36613,9 +36746,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
let hasReturnWithNoExpression = functionHasImplicitReturn(func);
|
||||
let hasReturnOfTypeNever = false;
|
||||
forEachReturnStatement(func.body as Block, returnStatement => {
|
||||
const expr = returnStatement.expression;
|
||||
let expr = returnStatement.expression;
|
||||
if (expr) {
|
||||
expr = skipParentheses(expr, /*excludeJSDocTypeAssertions*/ true);
|
||||
// Bare calls to this same function don't contribute to inference
|
||||
// and `return await` is also safe to unwrap here
|
||||
if (functionFlags & FunctionFlags.Async && expr.kind === SyntaxKind.AwaitExpression) {
|
||||
expr = skipParentheses((expr as AwaitExpression).expression, /*excludeJSDocTypeAssertions*/ true);
|
||||
}
|
||||
if (
|
||||
expr.kind === SyntaxKind.CallExpression &&
|
||||
(expr as CallExpression).expression.kind === SyntaxKind.Identifier &&
|
||||
@@ -37019,7 +37157,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
}
|
||||
|
||||
function checkVoidExpression(node: VoidExpression): Type {
|
||||
checkExpression(node.expression);
|
||||
checkNodeDeferred(node);
|
||||
return undefinedWideningType;
|
||||
}
|
||||
|
||||
@@ -38250,7 +38388,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
// There is no point in doing an assignability check if the function
|
||||
// has no explicit return type because the return type is directly computed
|
||||
// from the yield expressions.
|
||||
const returnType = getReturnTypeFromAnnotation(func);
|
||||
let returnType = getReturnTypeFromAnnotation(func);
|
||||
if (returnType && returnType.flags & TypeFlags.Union) {
|
||||
returnType = filterType(returnType, t => checkGeneratorInstantiationAssignabilityToReturnType(t, functionFlags, /*errorNode*/ undefined));
|
||||
}
|
||||
const iterationTypes = returnType && getIterationTypesOfGeneratorFunctionReturnType(returnType, isAsync);
|
||||
const signatureYieldType = iterationTypes && iterationTypes.yieldType || anyType;
|
||||
const signatureNextType = iterationTypes && iterationTypes.nextType || anyType;
|
||||
@@ -39178,17 +39319,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
error(returnTypeErrorLocation, Diagnostics.A_generator_cannot_have_a_void_type_annotation);
|
||||
}
|
||||
else {
|
||||
// Naively, one could check that Generator<any, any, any> is assignable to the return type annotation.
|
||||
// However, that would not catch the error in the following case.
|
||||
//
|
||||
// interface BadGenerator extends Iterable<number>, Iterator<string> { }
|
||||
// function* g(): BadGenerator { } // Iterable and Iterator have different types!
|
||||
//
|
||||
const generatorYieldType = getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Yield, returnType, (functionFlags & FunctionFlags.Async) !== 0) || anyType;
|
||||
const generatorReturnType = getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Return, returnType, (functionFlags & FunctionFlags.Async) !== 0) || generatorYieldType;
|
||||
const generatorNextType = getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Next, returnType, (functionFlags & FunctionFlags.Async) !== 0) || unknownType;
|
||||
const generatorInstantiation = createGeneratorReturnType(generatorYieldType, generatorReturnType, generatorNextType, !!(functionFlags & FunctionFlags.Async));
|
||||
checkTypeAssignableTo(generatorInstantiation, returnType, returnTypeErrorLocation);
|
||||
checkGeneratorInstantiationAssignabilityToReturnType(returnType, functionFlags, returnTypeErrorLocation);
|
||||
}
|
||||
}
|
||||
else if ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.Async) {
|
||||
@@ -39201,6 +39332,21 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
}
|
||||
}
|
||||
|
||||
function checkGeneratorInstantiationAssignabilityToReturnType(returnType: Type, functionFlags: FunctionFlags, errorNode?: TypeNode) {
|
||||
// Naively, one could check that Generator<any, any, any> is assignable to the return type annotation.
|
||||
// However, that would not catch the error in the following case.
|
||||
//
|
||||
// interface BadGenerator extends Iterable<number>, Iterator<string> { }
|
||||
// function* g(): BadGenerator { } // Iterable and Iterator have different types!
|
||||
//
|
||||
const generatorYieldType = getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Yield, returnType, (functionFlags & FunctionFlags.Async) !== 0) || anyType;
|
||||
const generatorReturnType = getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Return, returnType, (functionFlags & FunctionFlags.Async) !== 0) || generatorYieldType;
|
||||
const generatorNextType = getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Next, returnType, (functionFlags & FunctionFlags.Async) !== 0) || unknownType;
|
||||
const generatorInstantiation = createGeneratorReturnType(generatorYieldType, generatorReturnType, generatorNextType, !!(functionFlags & FunctionFlags.Async));
|
||||
|
||||
return checkTypeAssignableTo(generatorInstantiation, returnType, errorNode);
|
||||
}
|
||||
|
||||
function checkClassForDuplicateDeclarations(node: ClassLikeDeclaration) {
|
||||
const instanceNames = new Map<__String, DeclarationMeaning>();
|
||||
const staticNames = new Map<__String, DeclarationMeaning>();
|
||||
@@ -41152,6 +41298,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
function checkJSDocParameterTag(node: JSDocParameterTag) {
|
||||
checkSourceElement(node.typeExpression);
|
||||
}
|
||||
|
||||
function checkJSDocPropertyTag(node: JSDocPropertyTag) {
|
||||
checkSourceElement(node.typeExpression);
|
||||
}
|
||||
@@ -41167,6 +41314,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
}
|
||||
}
|
||||
|
||||
function checkJSDocThisTag(node: JSDocThisTag) {
|
||||
const host = getEffectiveJSDocHost(node);
|
||||
if (host && isArrowFunction(host)) {
|
||||
error(node.tagName, Diagnostics.An_arrow_function_cannot_have_a_this_parameter);
|
||||
}
|
||||
}
|
||||
|
||||
function checkJSDocImplementsTag(node: JSDocImplementsTag): void {
|
||||
const classLike = getEffectiveJSDocHost(node);
|
||||
if (!classLike || !isClassDeclaration(classLike) && !isClassExpression(classLike)) {
|
||||
@@ -41998,7 +42152,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
forEach(node.name.elements, checkSourceElement);
|
||||
}
|
||||
// For a parameter declaration with an initializer, error and exit if the containing function doesn't have a body
|
||||
if (isParameter(node) && node.initializer && nodeIsMissing((getContainingFunction(node) as FunctionLikeDeclaration).body)) {
|
||||
if (node.initializer && isParameterDeclaration(node) && nodeIsMissing((getContainingFunction(node) as FunctionLikeDeclaration).body)) {
|
||||
error(node, Diagnostics.A_parameter_initializer_is_only_allowed_in_a_function_or_constructor_implementation);
|
||||
return;
|
||||
}
|
||||
@@ -45885,6 +46039,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
return checkJSDocAccessibilityModifiers(node as JSDocPublicTag | JSDocProtectedTag | JSDocPrivateTag);
|
||||
case SyntaxKind.JSDocSatisfiesTag:
|
||||
return checkJSDocSatisfiesTag(node as JSDocSatisfiesTag);
|
||||
case SyntaxKind.JSDocThisTag:
|
||||
return checkJSDocThisTag(node as JSDocThisTag);
|
||||
case SyntaxKind.IndexedAccessType:
|
||||
return checkIndexedAccessType(node as IndexedAccessTypeNode);
|
||||
case SyntaxKind.MappedType:
|
||||
@@ -46132,6 +46288,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
case SyntaxKind.ParenthesizedExpression:
|
||||
checkAssertionDeferred(node as AssertionExpression | JSDocTypeAssertion);
|
||||
break;
|
||||
case SyntaxKind.VoidExpression:
|
||||
checkExpression((node as VoidExpression).expression);
|
||||
break;
|
||||
case SyntaxKind.BinaryExpression:
|
||||
if (isInstanceOfExpression(node)) {
|
||||
resolveUntypedCall(node);
|
||||
@@ -47869,6 +48028,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
return !sym.exports ? [] : nodeBuilder.symbolTableToDeclarationStatements(sym.exports, node, flags, tracker, bundled);
|
||||
},
|
||||
isImportRequiredByAugmentation,
|
||||
tryFindAmbientModule: moduleReferenceExpression => {
|
||||
const node = getParseTreeNode(moduleReferenceExpression);
|
||||
const moduleSpecifier = node && isStringLiteralLike(node) ? node.text : undefined;
|
||||
return moduleSpecifier !== undefined ? tryFindAmbientModule(moduleSpecifier, /*withAugmentations*/ true) : undefined;
|
||||
},
|
||||
};
|
||||
|
||||
function isImportRequiredByAugmentation(node: ImportDeclaration) {
|
||||
@@ -50271,6 +50435,7 @@ interface NodeBuilderContext {
|
||||
// State
|
||||
encounteredError: boolean;
|
||||
reportedDiagnostic: boolean;
|
||||
trackedSymbols: TrackedSymbol[] | undefined;
|
||||
visitedTypes: Set<number> | undefined;
|
||||
symbolDepth: Map<string, number> | undefined;
|
||||
inferTypeParameters: TypeParameter[] | undefined;
|
||||
@@ -50311,6 +50476,8 @@ class SymbolTrackerImpl implements SymbolTracker {
|
||||
this.onDiagnosticReported();
|
||||
return true;
|
||||
}
|
||||
// Skip recording type parameters as they dont contribute to late painted statements
|
||||
if (!(symbol.flags & SymbolFlags.TypeParameter)) (this.context.trackedSymbols ??= []).push([symbol, enclosingDeclaration, meaning]);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1249,7 +1249,7 @@ const commandOptionsWithoutBuild: CommandLineOption[] = [
|
||||
affectsEmit: true,
|
||||
affectsBuildInfo: true,
|
||||
affectsDeclarationPath: true,
|
||||
isFilePath: false, // This is intentionally broken to support compatability with existing tsconfig files
|
||||
isFilePath: false, // This is intentionally broken to support compatibility with existing tsconfig files
|
||||
// for correct behaviour, please use outFile
|
||||
category: Diagnostics.Backwards_Compatibility,
|
||||
paramType: Diagnostics.FILE,
|
||||
@@ -2384,7 +2384,7 @@ export function convertToJson(
|
||||
// Currently having element option declaration in the tsconfig with type "object"
|
||||
// determines if it needs onSetValidOptionKeyValueInParent callback or not
|
||||
// At moment there are only "compilerOptions", "typeAcquisition" and "typingOptions"
|
||||
// that satifies it and need it to modify options set in them (for normalizing file paths)
|
||||
// that satisfies it and need it to modify options set in them (for normalizing file paths)
|
||||
// vs what we set in the json
|
||||
// If need arises, we can modify this interface and callbacks as needed
|
||||
return convertObjectLiteralExpressionToJson(objectLiteralExpression, option as TsConfigOnlyOption);
|
||||
|
||||
@@ -3619,7 +3619,7 @@
|
||||
"category": "Error",
|
||||
"code": 2839
|
||||
},
|
||||
"An interface cannot extend a primitive type like '{0}'; an interface can only extend named types and classes": {
|
||||
"An interface cannot extend a primitive type like '{0}'. It can only extend other named object types.": {
|
||||
"category": "Error",
|
||||
"code": 2840
|
||||
},
|
||||
@@ -3699,6 +3699,18 @@
|
||||
"category": "Error",
|
||||
"code": 2861
|
||||
},
|
||||
"Type '{0}' is generic and can only be indexed for reading.": {
|
||||
"category": "Error",
|
||||
"code": 2862
|
||||
},
|
||||
"A class cannot extend a primitive type like '{0}'. Classes can only extend constructable values.": {
|
||||
"category": "Error",
|
||||
"code": 2863
|
||||
},
|
||||
"A class cannot implement a primitive type like '{0}'. It can only implement other named object types.": {
|
||||
"category": "Error",
|
||||
"code": 2864
|
||||
},
|
||||
|
||||
"Import declaration '{0}' is using private name '{1}'.": {
|
||||
"category": "Error",
|
||||
|
||||
@@ -1182,6 +1182,7 @@ export const notImplementedResolver: EmitResolver = {
|
||||
isBindingCapturedByNode: notImplemented,
|
||||
getDeclarationStatementsForSourceFile: notImplemented,
|
||||
isImportRequiredByAugmentation: notImplemented,
|
||||
tryFindAmbientModule: notImplemented,
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -854,17 +854,18 @@ export const asyncGeneratorHelper: UnscopedEmitHelper = {
|
||||
scoped: false,
|
||||
dependencies: [awaitHelper],
|
||||
text: `
|
||||
var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) {
|
||||
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
||||
var g = generator.apply(thisArg, _arguments || []), i, q = [];
|
||||
return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i;
|
||||
function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; }
|
||||
function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
|
||||
function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
|
||||
function fulfill(value) { resume("next", value); }
|
||||
function reject(value) { resume("throw", value); }
|
||||
function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
|
||||
};`,
|
||||
var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) {
|
||||
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
||||
var g = generator.apply(thisArg, _arguments || []), i, q = [];
|
||||
return i = {}, verb("next"), verb("throw"), verb("return", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i;
|
||||
function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; }
|
||||
function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } }
|
||||
function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
|
||||
function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
|
||||
function fulfill(value) { resume("next", value); }
|
||||
function reject(value) { resume("throw", value); }
|
||||
function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
|
||||
};`,
|
||||
};
|
||||
|
||||
/** @internal */
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
__String,
|
||||
AccessorDeclaration,
|
||||
addRange,
|
||||
append,
|
||||
appendIfUnique,
|
||||
@@ -1178,8 +1179,9 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
|
||||
ensureUseStrict,
|
||||
liftToBlock,
|
||||
mergeLexicalEnvironment,
|
||||
updateModifiers,
|
||||
updateModifierLike,
|
||||
replaceModifiers,
|
||||
replaceDecoratorsAndModifiers,
|
||||
replacePropertyName,
|
||||
};
|
||||
|
||||
forEach(nodeFactoryPatchers, fn => fn(factory));
|
||||
@@ -7100,8 +7102,8 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
|
||||
return statements;
|
||||
}
|
||||
|
||||
function updateModifiers<T extends HasModifiers>(node: T, modifiers: readonly Modifier[] | ModifierFlags): T;
|
||||
function updateModifiers(node: HasModifiers, modifiers: readonly Modifier[] | ModifierFlags) {
|
||||
function replaceModifiers<T extends HasModifiers>(node: T, modifiers: readonly Modifier[] | ModifierFlags): T;
|
||||
function replaceModifiers(node: HasModifiers, modifiers: readonly Modifier[] | ModifierFlags) {
|
||||
let modifierArray;
|
||||
if (typeof modifiers === "number") {
|
||||
modifierArray = createModifiersFromModifierFlags(modifiers);
|
||||
@@ -7137,8 +7139,8 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
|
||||
Debug.assertNever(node);
|
||||
}
|
||||
|
||||
function updateModifierLike<T extends HasModifiers & HasDecorators>(node: T, modifiers: readonly ModifierLike[]): T;
|
||||
function updateModifierLike(node: HasModifiers & HasDecorators, modifierArray: readonly ModifierLike[]) {
|
||||
function replaceDecoratorsAndModifiers<T extends HasModifiers & HasDecorators>(node: T, modifiers: readonly ModifierLike[]): T;
|
||||
function replaceDecoratorsAndModifiers(node: HasModifiers & HasDecorators, modifierArray: readonly ModifierLike[]) {
|
||||
return isParameter(node) ? updateParameterDeclaration(node, modifierArray, node.dotDotDotToken, node.name, node.questionToken, node.type, node.initializer) :
|
||||
isPropertyDeclaration(node) ? updatePropertyDeclaration(node, modifierArray, node.name, node.questionToken ?? node.exclamationToken, node.type, node.initializer) :
|
||||
isMethodDeclaration(node) ? updateMethodDeclaration(node, modifierArray, node.asteriskToken, node.name, node.questionToken, node.typeParameters, node.parameters, node.type, node.body) :
|
||||
@@ -7149,6 +7151,26 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
|
||||
Debug.assertNever(node);
|
||||
}
|
||||
|
||||
function replacePropertyName<T extends AccessorDeclaration | MethodDeclaration | MethodSignature | PropertyDeclaration | PropertySignature | PropertyAssignment>(node: T, name: T["name"]): T;
|
||||
function replacePropertyName(node: AccessorDeclaration | MethodDeclaration | MethodSignature | PropertyDeclaration | PropertySignature | PropertyAssignment, name: PropertyName) {
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.GetAccessor:
|
||||
return updateGetAccessorDeclaration(node, node.modifiers, name, node.parameters, node.type, node.body);
|
||||
case SyntaxKind.SetAccessor:
|
||||
return updateSetAccessorDeclaration(node, node.modifiers, name, node.parameters, node.body);
|
||||
case SyntaxKind.MethodDeclaration:
|
||||
return updateMethodDeclaration(node, node.modifiers, node.asteriskToken, name, node.questionToken, node.typeParameters, node.parameters, node.type, node.body);
|
||||
case SyntaxKind.MethodSignature:
|
||||
return updateMethodSignature(node, node.modifiers, name, node.questionToken, node.typeParameters, node.parameters, node.type);
|
||||
case SyntaxKind.PropertyDeclaration:
|
||||
return updatePropertyDeclaration(node, node.modifiers, name, node.questionToken ?? node.exclamationToken, node.type, node.initializer);
|
||||
case SyntaxKind.PropertySignature:
|
||||
return updatePropertySignature(node, node.modifiers, name, node.questionToken, node.type);
|
||||
case SyntaxKind.PropertyAssignment:
|
||||
return updatePropertyAssignment(node, name, node.initializer);
|
||||
}
|
||||
}
|
||||
|
||||
function asNodeArray<T extends Node>(array: readonly T[]): NodeArray<T>;
|
||||
function asNodeArray<T extends Node>(array: readonly T[] | undefined): NodeArray<T> | undefined;
|
||||
function asNodeArray<T extends Node>(array: readonly T[] | undefined): NodeArray<T> | undefined {
|
||||
|
||||
@@ -225,6 +225,7 @@ function createResolvedModuleWithFailedLookupLocationsHandlingSymlink(
|
||||
affectingLocations: string[],
|
||||
diagnostics: Diagnostic[],
|
||||
state: ModuleResolutionState,
|
||||
cache: ModuleResolutionCache | NonRelativeModuleNameResolutionCache | undefined,
|
||||
legacyResult?: string,
|
||||
): ResolvedModuleWithFailedLookupLocations {
|
||||
// If this is from node_modules for non relative name, always respect preserveSymlinks
|
||||
@@ -246,6 +247,7 @@ function createResolvedModuleWithFailedLookupLocationsHandlingSymlink(
|
||||
affectingLocations,
|
||||
diagnostics,
|
||||
state.resultFromCache,
|
||||
cache,
|
||||
legacyResult,
|
||||
);
|
||||
}
|
||||
@@ -257,13 +259,24 @@ function createResolvedModuleWithFailedLookupLocations(
|
||||
affectingLocations: string[],
|
||||
diagnostics: Diagnostic[],
|
||||
resultFromCache: ResolvedModuleWithFailedLookupLocations | undefined,
|
||||
cache: ModuleResolutionCache | NonRelativeModuleNameResolutionCache | undefined,
|
||||
legacyResult?: string,
|
||||
): ResolvedModuleWithFailedLookupLocations {
|
||||
if (resultFromCache) {
|
||||
resultFromCache.failedLookupLocations = updateResolutionField(resultFromCache.failedLookupLocations, failedLookupLocations);
|
||||
resultFromCache.affectingLocations = updateResolutionField(resultFromCache.affectingLocations, affectingLocations);
|
||||
resultFromCache.resolutionDiagnostics = updateResolutionField(resultFromCache.resolutionDiagnostics, diagnostics);
|
||||
return resultFromCache;
|
||||
if (!cache?.isReadonly) {
|
||||
resultFromCache.failedLookupLocations = updateResolutionField(resultFromCache.failedLookupLocations, failedLookupLocations);
|
||||
resultFromCache.affectingLocations = updateResolutionField(resultFromCache.affectingLocations, affectingLocations);
|
||||
resultFromCache.resolutionDiagnostics = updateResolutionField(resultFromCache.resolutionDiagnostics, diagnostics);
|
||||
return resultFromCache;
|
||||
}
|
||||
else {
|
||||
return {
|
||||
...resultFromCache,
|
||||
failedLookupLocations: initializeResolutionFieldForReadonlyCache(resultFromCache.failedLookupLocations, failedLookupLocations),
|
||||
affectingLocations: initializeResolutionFieldForReadonlyCache(resultFromCache.affectingLocations, affectingLocations),
|
||||
resolutionDiagnostics: initializeResolutionFieldForReadonlyCache(resultFromCache.resolutionDiagnostics, diagnostics),
|
||||
};
|
||||
}
|
||||
}
|
||||
return {
|
||||
resolvedModule: resolved && {
|
||||
@@ -291,6 +304,12 @@ export function updateResolutionField<T>(to: T[] | undefined, value: T[] | undef
|
||||
return to;
|
||||
}
|
||||
|
||||
function initializeResolutionFieldForReadonlyCache<T>(fromCache: T[] | undefined, value: T[]): T[] | undefined {
|
||||
if (!fromCache?.length) return initializeResolutionField(value);
|
||||
if (!value.length) return fromCache.slice();
|
||||
return [...fromCache, ...value];
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export interface ModuleResolutionState {
|
||||
host: ModuleResolutionHost;
|
||||
@@ -566,10 +585,8 @@ export function resolveTypeReferenceDirective(typeReferenceDirectiveName: string
|
||||
if (resolutionMode === ModuleKind.ESNext && (ModuleResolutionKind.Node16 <= moduleResolution && moduleResolution <= ModuleResolutionKind.NodeNext)) {
|
||||
features |= NodeResolutionFeatures.EsmMode;
|
||||
}
|
||||
// true: "import" / false: "require" / undefined: default based on settings
|
||||
const useImportCondition = resolutionMode === ModuleKind.ESNext || (resolutionMode !== undefined ? false : undefined);
|
||||
const conditions = (features & NodeResolutionFeatures.Exports)
|
||||
? getConditions(options, useImportCondition)
|
||||
? getConditions(options, resolutionMode)
|
||||
: [];
|
||||
const diagnostics: Diagnostic[] = [];
|
||||
const moduleResolutionState: ModuleResolutionState = {
|
||||
@@ -612,10 +629,10 @@ export function resolveTypeReferenceDirective(typeReferenceDirectiveName: string
|
||||
affectingLocations: initializeResolutionField(affectingLocations),
|
||||
resolutionDiagnostics: initializeResolutionField(diagnostics),
|
||||
};
|
||||
if (containingDirectory) {
|
||||
cache?.getOrCreateCacheForDirectory(containingDirectory, redirectedReference).set(typeReferenceDirectiveName, /*mode*/ resolutionMode, result);
|
||||
if (containingDirectory && cache && !cache.isReadonly) {
|
||||
cache.getOrCreateCacheForDirectory(containingDirectory, redirectedReference).set(typeReferenceDirectiveName, /*mode*/ resolutionMode, result);
|
||||
if (!isExternalModuleNameRelative(typeReferenceDirectiveName)) {
|
||||
cache?.getOrCreateCacheForNonRelativeName(typeReferenceDirectiveName, resolutionMode, redirectedReference).set(containingDirectory, result);
|
||||
cache.getOrCreateCacheForNonRelativeName(typeReferenceDirectiveName, resolutionMode, redirectedReference).set(containingDirectory, result);
|
||||
}
|
||||
}
|
||||
if (traceEnabled) traceResult(result);
|
||||
@@ -725,16 +742,13 @@ function getNodeResolutionFeatures(options: CompilerOptions) {
|
||||
return features;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param overrideResolutionModeAttribute
|
||||
* @internal
|
||||
*/
|
||||
export function getConditions(options: CompilerOptions, esmMode?: boolean) {
|
||||
/** @internal */
|
||||
export function getConditions(options: CompilerOptions, resolutionMode?: ResolutionMode) {
|
||||
const moduleResolution = getEmitModuleResolutionKind(options);
|
||||
if (esmMode === undefined) {
|
||||
if (resolutionMode === undefined) {
|
||||
if (moduleResolution === ModuleResolutionKind.Bundler) {
|
||||
// bundler always uses `import` unless explicitly overridden
|
||||
esmMode ??= moduleResolution === ModuleResolutionKind.Bundler;
|
||||
resolutionMode = ModuleKind.ESNext;
|
||||
}
|
||||
else if (moduleResolution === ModuleResolutionKind.Node10) {
|
||||
// node10 does not support package.json imports/exports without
|
||||
@@ -745,7 +759,7 @@ export function getConditions(options: CompilerOptions, esmMode?: boolean) {
|
||||
}
|
||||
// conditions are only used by the node16/nodenext/bundler resolvers - there's no priority order in the list,
|
||||
// it's essentially a set (priority is determined by object insertion order in the object we look at).
|
||||
const conditions = esmMode
|
||||
const conditions = resolutionMode === ModuleKind.ESNext
|
||||
? ["import"]
|
||||
: ["require"];
|
||||
if (!options.noDtsResolution) {
|
||||
@@ -850,6 +864,8 @@ export interface PerDirectoryResolutionCache<T> {
|
||||
* This updates the redirects map as well if needed so module resolutions are cached if they can across the projects
|
||||
*/
|
||||
update(options: CompilerOptions): void;
|
||||
/** @internal */ directoryToModuleNameMap: CacheWithRedirects<Path, ModeAwareCache<T>>;
|
||||
/** @internal */ isReadonly?: boolean;
|
||||
}
|
||||
|
||||
export interface NonRelativeNameResolutionCache<T> {
|
||||
@@ -861,11 +877,13 @@ export interface NonRelativeNameResolutionCache<T> {
|
||||
* This updates the redirects map as well if needed so module resolutions are cached if they can across the projects
|
||||
*/
|
||||
update(options: CompilerOptions): void;
|
||||
/** @internal */ isReadonly?: boolean;
|
||||
}
|
||||
|
||||
export interface PerNonRelativeNameCache<T> {
|
||||
get(directory: string): T | undefined;
|
||||
set(directory: string, result: T): void;
|
||||
/** @internal */ isReadonly?: boolean;
|
||||
}
|
||||
|
||||
export interface ModuleResolutionCache extends PerDirectoryResolutionCache<ResolvedModuleWithFailedLookupLocations>, NonRelativeModuleNameResolutionCache, PackageJsonInfoCache {
|
||||
@@ -889,6 +907,7 @@ export interface PackageJsonInfoCache {
|
||||
/** @internal */ entries(): [Path, PackageJsonInfo | boolean][];
|
||||
/** @internal */ getInternalMap(): Map<Path, PackageJsonInfo | boolean> | undefined;
|
||||
clear(): void;
|
||||
/** @internal */ isReadonly?: boolean;
|
||||
}
|
||||
|
||||
export type PerModuleNameCache = PerNonRelativeNameCache<ResolvedModuleWithFailedLookupLocations>;
|
||||
@@ -920,6 +939,7 @@ export interface CacheWithRedirects<K, V> {
|
||||
getOrCreateMapOfCacheRedirects(redirectedReference: ResolvedProjectReference | undefined): Map<K, V>;
|
||||
update(newOptions: CompilerOptions): void;
|
||||
clear(): void;
|
||||
getOwnMap(): Map<K, V>;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
@@ -936,6 +956,7 @@ export function createCacheWithRedirects<K, V>(ownOptions: CompilerOptions | und
|
||||
getOrCreateMapOfCacheRedirects,
|
||||
update,
|
||||
clear,
|
||||
getOwnMap: () => ownMap,
|
||||
};
|
||||
|
||||
function getMapOfCacheRedirects(redirectedReference: ResolvedProjectReference | undefined): Map<K, V> | undefined {
|
||||
@@ -1042,6 +1063,7 @@ function createPerDirectoryResolutionCache<T>(
|
||||
getOrCreateCacheForDirectory,
|
||||
clear,
|
||||
update,
|
||||
directoryToModuleNameMap,
|
||||
};
|
||||
|
||||
function clear() {
|
||||
@@ -1412,13 +1434,13 @@ export function resolveModuleName(moduleName: string, containingFile: string, co
|
||||
result = nodeNextModuleNameResolver(moduleName, containingFile, compilerOptions, host, cache, redirectedReference, resolutionMode);
|
||||
break;
|
||||
case ModuleResolutionKind.Node10:
|
||||
result = nodeModuleNameResolver(moduleName, containingFile, compilerOptions, host, cache, redirectedReference, resolutionMode ? getConditions(compilerOptions, resolutionMode === ModuleKind.ESNext) : undefined);
|
||||
result = nodeModuleNameResolver(moduleName, containingFile, compilerOptions, host, cache, redirectedReference, resolutionMode ? getConditions(compilerOptions, resolutionMode) : undefined);
|
||||
break;
|
||||
case ModuleResolutionKind.Classic:
|
||||
result = classicNameResolver(moduleName, containingFile, compilerOptions, host, cache, redirectedReference);
|
||||
break;
|
||||
case ModuleResolutionKind.Bundler:
|
||||
result = bundlerModuleNameResolver(moduleName, containingFile, compilerOptions, host, cache, redirectedReference, resolutionMode ? getConditions(compilerOptions, resolutionMode === ModuleKind.ESNext) : undefined);
|
||||
result = bundlerModuleNameResolver(moduleName, containingFile, compilerOptions, host, cache, redirectedReference, resolutionMode ? getConditions(compilerOptions, resolutionMode) : undefined);
|
||||
break;
|
||||
default:
|
||||
return Debug.fail(`Unexpected moduleResolution: ${moduleResolution}`);
|
||||
@@ -1426,10 +1448,12 @@ export function resolveModuleName(moduleName: string, containingFile: string, co
|
||||
if (result && result.resolvedModule) perfLogger?.logInfoEvent(`Module "${moduleName}" resolved to "${result.resolvedModule.resolvedFileName}"`);
|
||||
perfLogger?.logStopResolveModule((result && result.resolvedModule) ? "" + result.resolvedModule.resolvedFileName : "null");
|
||||
|
||||
cache?.getOrCreateCacheForDirectory(containingDirectory, redirectedReference).set(moduleName, resolutionMode, result);
|
||||
if (!isExternalModuleNameRelative(moduleName)) {
|
||||
// put result in per-module name cache
|
||||
cache?.getOrCreateCacheForNonRelativeName(moduleName, resolutionMode, redirectedReference).set(containingDirectory, result);
|
||||
if (cache && !cache.isReadonly) {
|
||||
cache.getOrCreateCacheForDirectory(containingDirectory, redirectedReference).set(moduleName, resolutionMode, result);
|
||||
if (!isExternalModuleNameRelative(moduleName)) {
|
||||
// put result in per-module name cache
|
||||
cache.getOrCreateCacheForNonRelativeName(moduleName, resolutionMode, redirectedReference).set(containingDirectory, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1784,7 +1808,7 @@ function nodeModuleNameResolverWorker(
|
||||
compilerOptions,
|
||||
moduleResolution === ModuleResolutionKind.Bundler || moduleResolution === ModuleResolutionKind.Node10
|
||||
? undefined
|
||||
: !!(features & NodeResolutionFeatures.EsmMode),
|
||||
: (features & NodeResolutionFeatures.EsmMode) ? ModuleKind.ESNext : ModuleKind.CommonJS,
|
||||
);
|
||||
|
||||
const diagnostics: Diagnostic[] = [];
|
||||
@@ -1850,6 +1874,7 @@ function nodeModuleNameResolverWorker(
|
||||
affectingLocations,
|
||||
diagnostics,
|
||||
state,
|
||||
cache,
|
||||
legacyResult,
|
||||
);
|
||||
|
||||
@@ -2195,7 +2220,7 @@ export function getEntrypointsFromPackageJsonInfo(
|
||||
|
||||
if (features & NodeResolutionFeatures.Exports && packageJsonInfo.contents.packageJsonContent.exports) {
|
||||
const conditionSets = deduplicate(
|
||||
[getConditions(options, /*esmMode*/ true), getConditions(options, /*esmMode*/ false)],
|
||||
[getConditions(options, ModuleKind.ESNext), getConditions(options, ModuleKind.CommonJS)],
|
||||
arrayIsEqualTo,
|
||||
);
|
||||
for (const conditions of conditionSets) {
|
||||
@@ -2386,7 +2411,7 @@ export function getPackageJsonInfo(packageDirectory: string, onlyRecordFailures:
|
||||
trace(host, Diagnostics.Found_package_json_at_0, packageJsonPath);
|
||||
}
|
||||
const result: PackageJsonInfo = { packageDirectory, contents: { packageJsonContent, versionPaths: undefined, resolvedEntrypoints: undefined } };
|
||||
state.packageJsonInfoCache?.setPackageJsonInfo(packageJsonPath, result);
|
||||
if (state.packageJsonInfoCache && !state.packageJsonInfoCache.isReadonly) state.packageJsonInfoCache.setPackageJsonInfo(packageJsonPath, result);
|
||||
state.affectingLocations?.push(packageJsonPath);
|
||||
return result;
|
||||
}
|
||||
@@ -2394,7 +2419,7 @@ export function getPackageJsonInfo(packageDirectory: string, onlyRecordFailures:
|
||||
if (directoryExists && traceEnabled) {
|
||||
trace(host, Diagnostics.File_0_does_not_exist, packageJsonPath);
|
||||
}
|
||||
state.packageJsonInfoCache?.setPackageJsonInfo(packageJsonPath, directoryExists);
|
||||
if (state.packageJsonInfoCache && !state.packageJsonInfoCache.isReadonly) state.packageJsonInfoCache.setPackageJsonInfo(packageJsonPath, directoryExists);
|
||||
// record package json as one of failed lookup locations - in the future if this file will appear it will invalidate resolution results
|
||||
state.failedLookupLocations?.push(packageJsonPath);
|
||||
}
|
||||
@@ -3178,6 +3203,7 @@ export function classicNameResolver(moduleName: string, containingFile: string,
|
||||
affectingLocations,
|
||||
diagnostics,
|
||||
state,
|
||||
cache,
|
||||
);
|
||||
|
||||
function tryResolve(extensions: Extensions): SearchResult<Resolved> {
|
||||
@@ -3273,6 +3299,7 @@ export function loadModuleFromGlobalCache(moduleName: string, projectName: strin
|
||||
affectingLocations,
|
||||
diagnostics,
|
||||
state.resultFromCache,
|
||||
/*cache*/ undefined,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -315,6 +315,7 @@ export function getModuleSpecifiers(
|
||||
host,
|
||||
userPreferences,
|
||||
options,
|
||||
/*forAutoImport*/ false,
|
||||
).moduleSpecifiers;
|
||||
}
|
||||
|
||||
@@ -327,6 +328,7 @@ export function getModuleSpecifiersWithCacheInfo(
|
||||
host: ModuleSpecifierResolutionHost,
|
||||
userPreferences: UserPreferences,
|
||||
options: ModuleSpecifierOptions = {},
|
||||
forAutoImport: boolean,
|
||||
): { moduleSpecifiers: readonly string[]; computedWithoutCache: boolean; } {
|
||||
let computedWithoutCache = false;
|
||||
const ambient = tryGetModuleNameFromAmbientModule(moduleSymbol, checker);
|
||||
@@ -345,7 +347,15 @@ export function getModuleSpecifiersWithCacheInfo(
|
||||
|
||||
computedWithoutCache = true;
|
||||
modulePaths ||= getAllModulePathsWorker(importingSourceFile.path, moduleSourceFile.originalFileName, host);
|
||||
const result = computeModuleSpecifiers(modulePaths, compilerOptions, importingSourceFile, host, userPreferences, options);
|
||||
const result = computeModuleSpecifiers(
|
||||
modulePaths,
|
||||
compilerOptions,
|
||||
importingSourceFile,
|
||||
host,
|
||||
userPreferences,
|
||||
options,
|
||||
forAutoImport,
|
||||
);
|
||||
cache?.set(importingSourceFile.path, moduleSourceFile.path, userPreferences, options, modulePaths, result);
|
||||
return { moduleSpecifiers: result, computedWithoutCache };
|
||||
}
|
||||
@@ -357,6 +367,7 @@ function computeModuleSpecifiers(
|
||||
host: ModuleSpecifierResolutionHost,
|
||||
userPreferences: UserPreferences,
|
||||
options: ModuleSpecifierOptions = {},
|
||||
forAutoImport: boolean,
|
||||
): readonly string[] {
|
||||
const info = getInfo(importingSourceFile.path, host);
|
||||
const preferences = getPreferences(userPreferences, compilerOptions, importingSourceFile);
|
||||
@@ -421,7 +432,7 @@ function computeModuleSpecifiers(
|
||||
else if (pathIsBareSpecifier(local)) {
|
||||
pathsSpecifiers = append(pathsSpecifiers, local);
|
||||
}
|
||||
else if (!importedFileIsInNodeModules || modulePath.isInNodeModules) {
|
||||
else if (forAutoImport || !importedFileIsInNodeModules || modulePath.isInNodeModules) {
|
||||
// Why this extra conditional, not just an `else`? If some path to the file contained
|
||||
// 'node_modules', but we can't create a non-relative specifier (e.g. "@foo/bar/path/to/file"),
|
||||
// that means we had to go through a *sibling's* node_modules, not one we can access directly.
|
||||
@@ -981,7 +992,7 @@ function tryGetModuleNameAsNodeModule({ path, isRedirect }: ModulePath, { getCan
|
||||
// use the actual directory name, so don't look at `packageJsonContent.name` here.
|
||||
const nodeModulesDirectoryName = packageRootPath.substring(parts.topLevelPackageNameIndex + 1);
|
||||
const packageName = getPackageNameFromTypesPackageName(nodeModulesDirectoryName);
|
||||
const conditions = getConditions(options, importMode === ModuleKind.ESNext);
|
||||
const conditions = getConditions(options, importMode);
|
||||
const fromExports = packageJsonContent.exports
|
||||
? tryGetModuleNameFromExports(options, path, packageRootPath, packageName, packageJsonContent.exports, conditions)
|
||||
: undefined;
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
arrayFrom,
|
||||
arrayIsEqualTo,
|
||||
AsExpression,
|
||||
AssertClause,
|
||||
BuilderProgram,
|
||||
CancellationToken,
|
||||
canHaveDecorators,
|
||||
@@ -918,7 +917,7 @@ export function getModeForUsageLocation(file: { impliedNodeFormat?: ResolutionMo
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function getResolutionModeOverride(node: AssertClause | ImportAttributes | undefined, grammarErrorOnNode?: (node: Node, diagnostic: DiagnosticMessage) => void) {
|
||||
export function getResolutionModeOverride(node: ImportAttributes | undefined, grammarErrorOnNode?: (node: Node, diagnostic: DiagnosticMessage) => void) {
|
||||
if (!node) return undefined;
|
||||
if (length(node.elements) !== 1) {
|
||||
grammarErrorOnNode?.(
|
||||
|
||||
@@ -205,6 +205,18 @@ export interface ResolutionCacheHost extends MinimalResolutionCacheHost {
|
||||
getCurrentProgram(): Program | undefined;
|
||||
fileIsOpen(filePath: Path): boolean;
|
||||
onDiscoveredSymlink?(): void;
|
||||
|
||||
// For incremental testing
|
||||
beforeResolveSingleModuleNameWithoutWatching?(
|
||||
moduleResolutionCache: ModuleResolutionCache,
|
||||
): any;
|
||||
afterResolveSingleModuleNameWithoutWatching?(
|
||||
moduleResolutionCache: ModuleResolutionCache,
|
||||
moduleName: string,
|
||||
containingFile: string,
|
||||
result: ResolvedModuleWithFailedLookupLocations,
|
||||
data: any,
|
||||
): any;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
@@ -439,6 +451,10 @@ export function getRootPathSplitLength(rootPath: Path) {
|
||||
return rootPath.split(directorySeparator).length - (hasTrailingDirectorySeparator(rootPath) ? 1 : 0);
|
||||
}
|
||||
|
||||
function getModuleResolutionHost(resolutionHost: ResolutionCacheHost) {
|
||||
return resolutionHost.getCompilerHost?.() || resolutionHost;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function createModuleResolutionLoaderUsingGlobalCache(
|
||||
containingFile: string,
|
||||
@@ -471,7 +487,7 @@ function resolveModuleNameUsingGlobalCache(
|
||||
redirectedReference?: ResolvedProjectReference,
|
||||
mode?: ResolutionMode,
|
||||
): ResolvedModuleWithFailedLookupLocations {
|
||||
const host = resolutionHost.getCompilerHost?.() || resolutionHost;
|
||||
const host = getModuleResolutionHost(resolutionHost);
|
||||
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) {
|
||||
@@ -686,6 +702,10 @@ export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootD
|
||||
}
|
||||
|
||||
function startCachingPerDirectoryResolution() {
|
||||
moduleResolutionCache.isReadonly = undefined;
|
||||
typeReferenceDirectiveResolutionCache.isReadonly = undefined;
|
||||
libraryResolutionCache.isReadonly = undefined;
|
||||
moduleResolutionCache.getPackageJsonInfoCache().isReadonly = undefined;
|
||||
moduleResolutionCache.clearAllExceptPackageJsonInfoCache();
|
||||
typeReferenceDirectiveResolutionCache.clearAllExceptPackageJsonInfoCache();
|
||||
libraryResolutionCache.clearAllExceptPackageJsonInfoCache();
|
||||
@@ -740,6 +760,10 @@ export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootD
|
||||
directoryWatchesOfFailedLookups.forEach(closeDirectoryWatchesOfFailedLookup);
|
||||
fileWatchesOfAffectingLocations.forEach(closeFileWatcherOfAffectingLocation);
|
||||
hasChangedAutomaticTypeDirectiveNames = false;
|
||||
moduleResolutionCache.isReadonly = true;
|
||||
typeReferenceDirectiveResolutionCache.isReadonly = true;
|
||||
libraryResolutionCache.isReadonly = true;
|
||||
moduleResolutionCache.getPackageJsonInfoCache().isReadonly = true;
|
||||
}
|
||||
|
||||
function closeDirectoryWatchesOfFailedLookup(watcher: DirectoryWatchesOfFailedLookup, path: Path) {
|
||||
@@ -828,7 +852,7 @@ export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootD
|
||||
}
|
||||
}
|
||||
else {
|
||||
const host = resolutionHost.getCompilerHost?.() || resolutionHost;
|
||||
const host = getModuleResolutionHost(resolutionHost);
|
||||
if (isTraceEnabled(options, host) && !seenNamesInFile.has(name, mode)) {
|
||||
const resolved = getResolutionWithResolvedFileName(resolution!);
|
||||
trace(
|
||||
@@ -912,7 +936,7 @@ export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootD
|
||||
containingFile,
|
||||
redirectedReference,
|
||||
options,
|
||||
resolutionHost.getCompilerHost?.() || resolutionHost,
|
||||
getModuleResolutionHost(resolutionHost),
|
||||
typeReferenceDirectiveResolutionCache,
|
||||
),
|
||||
getResolutionWithResolvedFileName: getResolvedTypeReferenceDirective,
|
||||
@@ -957,7 +981,7 @@ export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootD
|
||||
options: CompilerOptions,
|
||||
libFileName: string,
|
||||
) {
|
||||
const host = resolutionHost.getCompilerHost?.() || resolutionHost;
|
||||
const host = getModuleResolutionHost(resolutionHost);
|
||||
let resolution = resolvedLibraries?.get(libFileName);
|
||||
if (!resolution || resolution.isInvalidated) {
|
||||
const existingResolution = resolution;
|
||||
@@ -994,7 +1018,18 @@ export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootD
|
||||
const resolutionsInFile = resolvedModuleNames.get(path);
|
||||
const resolution = resolutionsInFile?.get(moduleName, /*mode*/ undefined);
|
||||
if (resolution && !resolution.isInvalidated) return resolution;
|
||||
return resolveModuleNameUsingGlobalCache(resolutionHost, moduleResolutionCache, moduleName, containingFile, resolutionHost.getCompilationSettings());
|
||||
const data = resolutionHost.beforeResolveSingleModuleNameWithoutWatching?.(moduleResolutionCache);
|
||||
const host = getModuleResolutionHost(resolutionHost);
|
||||
// We are not resolving d.ts so just normal resolution instead of doing resolution pass to global cache
|
||||
const result = ts_resolveModuleName(
|
||||
moduleName,
|
||||
containingFile,
|
||||
resolutionHost.getCompilationSettings(),
|
||||
host,
|
||||
moduleResolutionCache,
|
||||
);
|
||||
resolutionHost.afterResolveSingleModuleNameWithoutWatching?.(moduleResolutionCache, moduleName, containingFile, result, data);
|
||||
return result;
|
||||
}
|
||||
|
||||
function isNodeModulesAtTypesDirectory(dirPath: Path) {
|
||||
|
||||
@@ -212,6 +212,7 @@ import {
|
||||
TransformationContext,
|
||||
transformNodes,
|
||||
tryCast,
|
||||
tryGetModuleSpecifierFromDeclaration,
|
||||
TypeAliasDeclaration,
|
||||
TypeNode,
|
||||
TypeParameterDeclaration,
|
||||
@@ -345,6 +346,18 @@ export function transformDeclarations(context: TransformationContext) {
|
||||
refs.set(getOriginalNodeId(container), container);
|
||||
}
|
||||
|
||||
function trackReferencedAmbientModuleFromImport(node: ImportDeclaration | ExportDeclaration | ImportEqualsDeclaration | ImportTypeNode) {
|
||||
const moduleSpecifier = tryGetModuleSpecifierFromDeclaration(node);
|
||||
const symbol = moduleSpecifier && resolver.tryFindAmbientModule(moduleSpecifier);
|
||||
if (symbol?.declarations) {
|
||||
for (const decl of symbol.declarations) {
|
||||
if (isAmbientModule(decl) && getSourceFileOfNode(decl) !== currentSourceFile) {
|
||||
trackReferencedAmbientModule(decl, symbol);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleSymbolAccessibilityError(symbolAccessibilityResult: SymbolAccessibilityResult) {
|
||||
if (symbolAccessibilityResult.accessibility === SymbolAccessibility.Accessible) {
|
||||
// Add aliases back onto the possible imports list if they're not there so we can try them again with updated visibility info
|
||||
@@ -1312,6 +1325,7 @@ export function transformDeclarations(context: TransformationContext) {
|
||||
}
|
||||
case SyntaxKind.ImportType: {
|
||||
if (!isLiteralImportTypeNode(input)) return cleanup(input);
|
||||
trackReferencedAmbientModuleFromImport(input);
|
||||
return cleanup(factory.updateImportTypeNode(
|
||||
input,
|
||||
factory.updateLiteralTypeNode(input.argument, rewriteModuleSpecifier(input, input.argument.literal)),
|
||||
@@ -1370,6 +1384,7 @@ export function transformDeclarations(context: TransformationContext) {
|
||||
}
|
||||
resultHasScopeMarker = true;
|
||||
// Always visible if the parent node isn't dropped for being not visible
|
||||
trackReferencedAmbientModuleFromImport(input);
|
||||
// Rewrite external module names if necessary
|
||||
return factory.updateExportDeclaration(
|
||||
input,
|
||||
@@ -1421,7 +1436,7 @@ export function transformDeclarations(context: TransformationContext) {
|
||||
}
|
||||
|
||||
const modifiers = factory.createModifiersFromModifierFlags(getEffectiveModifierFlags(statement) & (ModifierFlags.All ^ ModifierFlags.Export));
|
||||
return factory.updateModifiers(statement, modifiers);
|
||||
return factory.replaceModifiers(statement, modifiers);
|
||||
}
|
||||
|
||||
function updateModuleDeclarationAndKeyword(
|
||||
@@ -1456,10 +1471,18 @@ export function transformDeclarations(context: TransformationContext) {
|
||||
if (shouldStripInternal(input)) return;
|
||||
switch (input.kind) {
|
||||
case SyntaxKind.ImportEqualsDeclaration: {
|
||||
return transformImportEqualsDeclaration(input);
|
||||
const transformed = transformImportEqualsDeclaration(input);
|
||||
if (transformed) {
|
||||
trackReferencedAmbientModuleFromImport(input);
|
||||
}
|
||||
return transformed;
|
||||
}
|
||||
case SyntaxKind.ImportDeclaration: {
|
||||
return transformImportDeclaration(input);
|
||||
const transformed = transformImportDeclaration(input);
|
||||
if (transformed) {
|
||||
trackReferencedAmbientModuleFromImport(input);
|
||||
}
|
||||
return transformed;
|
||||
}
|
||||
}
|
||||
if (isDeclaration(input) && isDeclarationAndNotVisible(input)) return;
|
||||
@@ -1543,7 +1566,7 @@ export function transformDeclarations(context: TransformationContext) {
|
||||
return factory.createVariableStatement(isNonContextualKeywordName ? undefined : [factory.createToken(SyntaxKind.ExportKeyword)], factory.createVariableDeclarationList([varDecl]));
|
||||
});
|
||||
if (!exportMappings.length) {
|
||||
declarations = mapDefined(declarations, declaration => factory.updateModifiers(declaration, ModifierFlags.None));
|
||||
declarations = mapDefined(declarations, declaration => factory.replaceModifiers(declaration, ModifierFlags.None));
|
||||
}
|
||||
else {
|
||||
declarations.push(factory.createExportDeclaration(
|
||||
|
||||
+555
-247
File diff suppressed because it is too large
Load Diff
@@ -36,6 +36,7 @@ import {
|
||||
isJsxSelfClosingElement,
|
||||
isJsxSpreadAttribute,
|
||||
isLineBreak,
|
||||
isObjectLiteralElementLike,
|
||||
isObjectLiteralExpression,
|
||||
isPropertyAssignment,
|
||||
isSourceFile,
|
||||
@@ -63,6 +64,7 @@ import {
|
||||
ObjectLiteralElementLike,
|
||||
ObjectLiteralExpression,
|
||||
PropertyAssignment,
|
||||
sameMap,
|
||||
ScriptTarget,
|
||||
setIdentifierGeneratedImportReference,
|
||||
setParentRecursive,
|
||||
@@ -444,7 +446,7 @@ export function transformJsx(context: TransformationContext): (x: SourceFile | B
|
||||
|
||||
function transformJsxSpreadAttributeToProps(node: JsxSpreadAttribute) {
|
||||
if (isObjectLiteralExpression(node.expression) && !hasProto(node.expression)) {
|
||||
return node.expression.properties;
|
||||
return sameMap(node.expression.properties, p => Debug.checkDefined(visitNode(p, visitor, isObjectLiteralElementLike)));
|
||||
}
|
||||
return factory.createSpreadAssignment(Debug.checkDefined(visitNode(node.expression, visitor, isExpression)));
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ import {
|
||||
CompilerHost,
|
||||
CompilerOptions,
|
||||
CompilerOptionsValue,
|
||||
ConfigFileProgramReloadLevel,
|
||||
convertToRelativePath,
|
||||
copyProperties,
|
||||
createCompilerDiagnostic,
|
||||
@@ -95,6 +94,7 @@ import {
|
||||
ProgramBundleEmitBuildInfo,
|
||||
ProgramHost,
|
||||
ProgramMultiFileEmitBuildInfo,
|
||||
ProgramUpdateLevel,
|
||||
readBuilderProgram,
|
||||
ReadBuildProgramHost,
|
||||
resolveConfigFileProjectName,
|
||||
@@ -287,7 +287,7 @@ export interface SolutionBuilder<T extends BuilderProgram> {
|
||||
|
||||
// Testing only
|
||||
/** @internal */ getUpToDateStatusOfProject(project: string): UpToDateStatus;
|
||||
/** @internal */ invalidateProject(configFilePath: ResolvedConfigFilePath, reloadLevel?: ConfigFileProgramReloadLevel): void;
|
||||
/** @internal */ invalidateProject(configFilePath: ResolvedConfigFilePath, updateLevel?: ProgramUpdateLevel): void;
|
||||
/** @internal */ close(): void;
|
||||
}
|
||||
|
||||
@@ -389,7 +389,7 @@ interface SolutionBuilderState<T extends BuilderProgram> extends WatchFactory<Wa
|
||||
|
||||
readonly builderPrograms: Map<ResolvedConfigFilePath, T>;
|
||||
readonly diagnostics: Map<ResolvedConfigFilePath, readonly Diagnostic[]>;
|
||||
readonly projectPendingBuild: Map<ResolvedConfigFilePath, ConfigFileProgramReloadLevel>;
|
||||
readonly projectPendingBuild: Map<ResolvedConfigFilePath, ProgramUpdateLevel>;
|
||||
readonly projectErrorsReported: Map<ResolvedConfigFilePath, true>;
|
||||
|
||||
readonly compilerHost: CompilerHost & ReadBuildProgramHost;
|
||||
@@ -795,13 +795,13 @@ function clearProjectStatus<T extends BuilderProgram>(state: SolutionBuilderStat
|
||||
state.diagnostics.delete(resolved);
|
||||
}
|
||||
|
||||
function addProjToQueue<T extends BuilderProgram>({ projectPendingBuild }: SolutionBuilderState<T>, proj: ResolvedConfigFilePath, reloadLevel: ConfigFileProgramReloadLevel) {
|
||||
function addProjToQueue<T extends BuilderProgram>({ projectPendingBuild }: SolutionBuilderState<T>, proj: ResolvedConfigFilePath, updateLevel: ProgramUpdateLevel) {
|
||||
const value = projectPendingBuild.get(proj);
|
||||
if (value === undefined) {
|
||||
projectPendingBuild.set(proj, reloadLevel);
|
||||
projectPendingBuild.set(proj, updateLevel);
|
||||
}
|
||||
else if (value < reloadLevel) {
|
||||
projectPendingBuild.set(proj, reloadLevel);
|
||||
else if (value < updateLevel) {
|
||||
projectPendingBuild.set(proj, updateLevel);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -815,7 +815,7 @@ function setupInitialBuild<T extends BuilderProgram>(state: SolutionBuilderState
|
||||
buildOrder.forEach(configFileName =>
|
||||
state.projectPendingBuild.set(
|
||||
toResolvedConfigFilePath(state, configFileName),
|
||||
ConfigFileProgramReloadLevel.None,
|
||||
ProgramUpdateLevel.Update,
|
||||
)
|
||||
);
|
||||
|
||||
@@ -1402,8 +1402,8 @@ function getNextInvalidatedProjectCreateInfo<T extends BuilderProgram>(
|
||||
for (let projectIndex = 0; projectIndex < buildOrder.length; projectIndex++) {
|
||||
const project = buildOrder[projectIndex];
|
||||
const projectPath = toResolvedConfigFilePath(state, project);
|
||||
const reloadLevel = state.projectPendingBuild.get(projectPath);
|
||||
if (reloadLevel === undefined) continue;
|
||||
const updateLevel = state.projectPendingBuild.get(projectPath);
|
||||
if (updateLevel === undefined) continue;
|
||||
|
||||
if (reportQueue) {
|
||||
reportQueue = false;
|
||||
@@ -1417,14 +1417,14 @@ function getNextInvalidatedProjectCreateInfo<T extends BuilderProgram>(
|
||||
continue;
|
||||
}
|
||||
|
||||
if (reloadLevel === ConfigFileProgramReloadLevel.Full) {
|
||||
if (updateLevel === ProgramUpdateLevel.Full) {
|
||||
watchConfigFile(state, project, projectPath, config);
|
||||
watchExtendedConfigFiles(state, projectPath, config);
|
||||
watchWildCardDirectories(state, project, projectPath, config);
|
||||
watchInputFiles(state, project, projectPath, config);
|
||||
watchPackageJsonFiles(state, project, projectPath, config);
|
||||
}
|
||||
else if (reloadLevel === ConfigFileProgramReloadLevel.Partial) {
|
||||
else if (updateLevel === ProgramUpdateLevel.RootNamesAndUpdate) {
|
||||
// Update file names
|
||||
config.fileNames = getFileNamesFromConfigSpecs(config.options.configFile!.configFileSpecs!, getDirectoryPath(project), config.options, state.parseConfigFileHost);
|
||||
updateErrorForNoInputFiles(config.fileNames, project, config.options.configFile!.configFileSpecs!, config.errors, canJsonReportNoInputFiles(config.raw));
|
||||
@@ -2169,7 +2169,7 @@ function queueReferencingProjects<T extends BuilderProgram>(
|
||||
break;
|
||||
}
|
||||
}
|
||||
addProjToQueue(state, nextProjectPath, ConfigFileProgramReloadLevel.None);
|
||||
addProjToQueue(state, nextProjectPath, ProgramUpdateLevel.Update);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -2251,7 +2251,7 @@ function cleanWorker<T extends BuilderProgram>(state: SolutionBuilderState<T>, p
|
||||
}
|
||||
else {
|
||||
host.deleteFile(output);
|
||||
invalidateProject(state, resolvedPath, ConfigFileProgramReloadLevel.None);
|
||||
invalidateProject(state, resolvedPath, ProgramUpdateLevel.Update);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2264,24 +2264,24 @@ function cleanWorker<T extends BuilderProgram>(state: SolutionBuilderState<T>, p
|
||||
return ExitStatus.Success;
|
||||
}
|
||||
|
||||
function invalidateProject<T extends BuilderProgram>(state: SolutionBuilderState<T>, resolved: ResolvedConfigFilePath, reloadLevel: ConfigFileProgramReloadLevel) {
|
||||
function invalidateProject<T extends BuilderProgram>(state: SolutionBuilderState<T>, resolved: ResolvedConfigFilePath, updateLevel: ProgramUpdateLevel) {
|
||||
// If host implements getParsedCommandLine, we cant get list of files from parseConfigFileHost
|
||||
if (state.host.getParsedCommandLine && reloadLevel === ConfigFileProgramReloadLevel.Partial) {
|
||||
reloadLevel = ConfigFileProgramReloadLevel.Full;
|
||||
if (state.host.getParsedCommandLine && updateLevel === ProgramUpdateLevel.RootNamesAndUpdate) {
|
||||
updateLevel = ProgramUpdateLevel.Full;
|
||||
}
|
||||
if (reloadLevel === ConfigFileProgramReloadLevel.Full) {
|
||||
if (updateLevel === ProgramUpdateLevel.Full) {
|
||||
state.configFileCache.delete(resolved);
|
||||
state.buildOrder = undefined;
|
||||
}
|
||||
state.needsSummary = true;
|
||||
clearProjectStatus(state, resolved);
|
||||
addProjToQueue(state, resolved, reloadLevel);
|
||||
addProjToQueue(state, resolved, updateLevel);
|
||||
enableCache(state);
|
||||
}
|
||||
|
||||
function invalidateProjectAndScheduleBuilds<T extends BuilderProgram>(state: SolutionBuilderState<T>, resolvedPath: ResolvedConfigFilePath, reloadLevel: ConfigFileProgramReloadLevel) {
|
||||
function invalidateProjectAndScheduleBuilds<T extends BuilderProgram>(state: SolutionBuilderState<T>, resolvedPath: ResolvedConfigFilePath, updateLevel: ProgramUpdateLevel) {
|
||||
state.reportFileChangeDetected = true;
|
||||
invalidateProject(state, resolvedPath, reloadLevel);
|
||||
invalidateProject(state, resolvedPath, updateLevel);
|
||||
scheduleBuildInvalidatedProject(state, 250, /*changeDetected*/ true);
|
||||
}
|
||||
|
||||
@@ -2344,7 +2344,7 @@ function watchConfigFile<T extends BuilderProgram>(state: SolutionBuilderState<T
|
||||
watchFile(
|
||||
state,
|
||||
resolved,
|
||||
() => invalidateProjectAndScheduleBuilds(state, resolvedPath, ConfigFileProgramReloadLevel.Full),
|
||||
() => invalidateProjectAndScheduleBuilds(state, resolvedPath, ProgramUpdateLevel.Full),
|
||||
PollingInterval.High,
|
||||
parsed?.watchOptions,
|
||||
WatchType.ConfigFile,
|
||||
@@ -2362,7 +2362,7 @@ function watchExtendedConfigFiles<T extends BuilderProgram>(state: SolutionBuild
|
||||
watchFile(
|
||||
state,
|
||||
extendedConfigFileName,
|
||||
() => state.allWatchedExtendedConfigFiles.get(extendedConfigFilePath)?.projects.forEach(projectConfigFilePath => invalidateProjectAndScheduleBuilds(state, projectConfigFilePath, ConfigFileProgramReloadLevel.Full)),
|
||||
() => state.allWatchedExtendedConfigFiles.get(extendedConfigFilePath)?.projects.forEach(projectConfigFilePath => invalidateProjectAndScheduleBuilds(state, projectConfigFilePath, ProgramUpdateLevel.Full)),
|
||||
PollingInterval.High,
|
||||
parsed?.watchOptions,
|
||||
WatchType.ExtendedConfigFile,
|
||||
@@ -2395,7 +2395,7 @@ function watchWildCardDirectories<T extends BuilderProgram>(state: SolutionBuild
|
||||
})
|
||||
) return;
|
||||
|
||||
invalidateProjectAndScheduleBuilds(state, resolvedPath, ConfigFileProgramReloadLevel.Partial);
|
||||
invalidateProjectAndScheduleBuilds(state, resolvedPath, ProgramUpdateLevel.RootNamesAndUpdate);
|
||||
},
|
||||
flags,
|
||||
parsed?.watchOptions,
|
||||
@@ -2415,7 +2415,7 @@ function watchInputFiles<T extends BuilderProgram>(state: SolutionBuilderState<T
|
||||
watchFile(
|
||||
state,
|
||||
input,
|
||||
() => invalidateProjectAndScheduleBuilds(state, resolvedPath, ConfigFileProgramReloadLevel.None),
|
||||
() => invalidateProjectAndScheduleBuilds(state, resolvedPath, ProgramUpdateLevel.Update),
|
||||
PollingInterval.Low,
|
||||
parsed?.watchOptions,
|
||||
WatchType.SourceFile,
|
||||
@@ -2436,7 +2436,7 @@ function watchPackageJsonFiles<T extends BuilderProgram>(state: SolutionBuilderS
|
||||
watchFile(
|
||||
state,
|
||||
path,
|
||||
() => invalidateProjectAndScheduleBuilds(state, resolvedPath, ConfigFileProgramReloadLevel.None),
|
||||
() => invalidateProjectAndScheduleBuilds(state, resolvedPath, ProgramUpdateLevel.Update),
|
||||
PollingInterval.High,
|
||||
parsed?.watchOptions,
|
||||
WatchType.PackageJson,
|
||||
@@ -2503,7 +2503,7 @@ function createSolutionBuilderWorker<T extends BuilderProgram>(watch: boolean, h
|
||||
const configFilePath = toResolvedConfigFilePath(state, configFileName);
|
||||
return getUpToDateStatus(state, parseConfigFile(state, configFileName, configFilePath), configFilePath);
|
||||
},
|
||||
invalidateProject: (configFilePath, reloadLevel) => invalidateProject(state, configFilePath, reloadLevel || ConfigFileProgramReloadLevel.None),
|
||||
invalidateProject: (configFilePath, updateLevel) => invalidateProject(state, configFilePath, updateLevel || ProgramUpdateLevel.Update),
|
||||
close: () => stopWatching(state),
|
||||
};
|
||||
}
|
||||
|
||||
+57
-22
@@ -851,25 +851,45 @@ export const enum NodeFlags {
|
||||
// dprint-ignore
|
||||
export const enum ModifierFlags {
|
||||
None = 0,
|
||||
Export = 1 << 0, // Declarations
|
||||
Ambient = 1 << 1, // Declarations
|
||||
Public = 1 << 2, // Property/Method
|
||||
Private = 1 << 3, // Property/Method
|
||||
Protected = 1 << 4, // Property/Method
|
||||
Static = 1 << 5, // Property/Method
|
||||
Readonly = 1 << 6, // Property/Method
|
||||
Accessor = 1 << 7, // Property
|
||||
Abstract = 1 << 8, // Class/Method/ConstructSignature
|
||||
Async = 1 << 9, // Property/Method/Function
|
||||
Default = 1 << 10, // Function/Class (export default declaration)
|
||||
Const = 1 << 11, // Const enum
|
||||
HasComputedJSDocModifiers = 1 << 12, // Indicates the computed modifier flags include modifiers from JSDoc.
|
||||
|
||||
Deprecated = 1 << 13, // Deprecated tag.
|
||||
Override = 1 << 14, // Override method.
|
||||
In = 1 << 15, // Contravariance modifier
|
||||
Out = 1 << 16, // Covariance modifier
|
||||
Decorator = 1 << 17, // Contains a decorator.
|
||||
// Syntactic/JSDoc modifiers
|
||||
Public = 1 << 0, // Property/Method
|
||||
Private = 1 << 1, // Property/Method
|
||||
Protected = 1 << 2, // Property/Method
|
||||
Readonly = 1 << 3, // Property/Method
|
||||
Override = 1 << 4, // Override method.
|
||||
|
||||
// Syntactic-only modifiers
|
||||
Export = 1 << 5, // Declarations
|
||||
Abstract = 1 << 6, // Class/Method/ConstructSignature
|
||||
Ambient = 1 << 7, // Declarations
|
||||
Static = 1 << 8, // Property/Method
|
||||
Accessor = 1 << 9, // Property
|
||||
Async = 1 << 10, // Property/Method/Function
|
||||
Default = 1 << 11, // Function/Class (export default declaration)
|
||||
Const = 1 << 12, // Const enum
|
||||
In = 1 << 13, // Contravariance modifier
|
||||
Out = 1 << 14, // Covariance modifier
|
||||
Decorator = 1 << 15, // Contains a decorator.
|
||||
|
||||
// JSDoc-only modifiers
|
||||
Deprecated = 1 << 16, // Deprecated tag.
|
||||
|
||||
// Cache-only JSDoc-modifiers. Should match order of Syntactic/JSDoc modifiers, above.
|
||||
/** @internal */ JSDocPublic = 1 << 23, // if this value changes, `selectEffectiveModifierFlags` must change accordingly
|
||||
/** @internal */ JSDocPrivate = 1 << 24,
|
||||
/** @internal */ JSDocProtected = 1 << 25,
|
||||
/** @internal */ JSDocReadonly = 1 << 26,
|
||||
/** @internal */ JSDocOverride = 1 << 27,
|
||||
|
||||
/** @internal */ SyntacticOrJSDocModifiers = Public | Private | Protected | Readonly | Override,
|
||||
/** @internal */ SyntacticOnlyModifiers = Export | Ambient | Abstract | Static | Accessor | Async | Default | Const | In | Out | Decorator,
|
||||
/** @internal */ SyntacticModifiers = SyntacticOrJSDocModifiers | SyntacticOnlyModifiers,
|
||||
/** @internal */ JSDocCacheOnlyModifiers = JSDocPublic | JSDocPrivate | JSDocProtected | JSDocReadonly | JSDocOverride,
|
||||
/** @internal */ JSDocOnlyModifiers = Deprecated,
|
||||
/** @internal */ NonCacheOnlyModifiers = SyntacticOrJSDocModifiers | SyntacticOnlyModifiers | JSDocOnlyModifiers,
|
||||
|
||||
HasComputedJSDocModifiers = 1 << 28, // Indicates the computed modifier flags include modifiers from JSDoc.
|
||||
HasComputedFlags = 1 << 29, // Modifier flags have been computed
|
||||
|
||||
AccessibilityModifier = Public | Private | Protected,
|
||||
@@ -1692,7 +1712,7 @@ export interface QualifiedName extends Node, FlowContainer {
|
||||
|
||||
export type EntityName = Identifier | QualifiedName;
|
||||
|
||||
export type PropertyName = Identifier | StringLiteral | NumericLiteral | ComputedPropertyName | PrivateIdentifier;
|
||||
export type PropertyName = Identifier | StringLiteral | NoSubstitutionTemplateLiteral | NumericLiteral | ComputedPropertyName | PrivateIdentifier;
|
||||
|
||||
export type MemberName = Identifier | PrivateIdentifier;
|
||||
|
||||
@@ -5043,7 +5063,7 @@ export interface TypeChecker {
|
||||
isUndefinedSymbol(symbol: Symbol): boolean;
|
||||
isArgumentsSymbol(symbol: Symbol): boolean;
|
||||
isUnknownSymbol(symbol: Symbol): boolean;
|
||||
/** @internal */ getMergedSymbol(symbol: Symbol): Symbol;
|
||||
getMergedSymbol(symbol: Symbol): Symbol;
|
||||
|
||||
getConstantValue(node: EnumMember | PropertyAccessExpression | ElementAccessExpression): string | number | undefined;
|
||||
isValidPropertyAccess(node: PropertyAccessExpression | QualifiedName | ImportTypeNode, propertyName: string): boolean;
|
||||
@@ -5675,6 +5695,7 @@ export interface EmitResolver {
|
||||
isBindingCapturedByNode(node: Node, decl: VariableDeclaration | BindingElement): boolean;
|
||||
getDeclarationStatementsForSourceFile(node: SourceFile, flags: NodeBuilderFlags, tracker: SymbolTracker, bundled?: boolean): Statement[] | undefined;
|
||||
isImportRequiredByAugmentation(decl: ImportDeclaration): boolean;
|
||||
tryFindAmbientModule(moduleReferenceExpression: Expression): Symbol | undefined;
|
||||
}
|
||||
|
||||
// dprint-ignore
|
||||
@@ -6030,11 +6051,14 @@ export interface NodeLinks {
|
||||
assertionExpressionType?: Type; // Cached type of the expression of a type assertion
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export type TrackedSymbol = [symbol: Symbol, enclosingDeclaration: Node | undefined, meaning: SymbolFlags];
|
||||
/** @internal */
|
||||
export interface SerializedTypeEntry {
|
||||
node: TypeNode;
|
||||
truncating?: boolean;
|
||||
addedLength: number;
|
||||
trackedSymbols: readonly TrackedSymbol[] | undefined;
|
||||
}
|
||||
|
||||
// dprint-ignore
|
||||
@@ -9071,8 +9095,18 @@ export interface NodeFactory {
|
||||
* @internal
|
||||
*/
|
||||
cloneNode<T extends Node | undefined>(node: T): T;
|
||||
/** @internal */ updateModifiers<T extends HasModifiers>(node: T, modifiers: readonly Modifier[] | ModifierFlags | undefined): T;
|
||||
/** @internal */ updateModifierLike<T extends HasModifiers & HasDecorators>(node: T, modifierLike: readonly ModifierLike[] | undefined): T;
|
||||
/**
|
||||
* Updates a node that may contain modifiers, replacing only the modifiers of the node.
|
||||
*/
|
||||
replaceModifiers<T extends HasModifiers>(node: T, modifiers: readonly Modifier[] | ModifierFlags | undefined): T;
|
||||
/**
|
||||
* Updates a node that may contain decorators or modifiers, replacing only the decorators and modifiers of the node.
|
||||
*/
|
||||
replaceDecoratorsAndModifiers<T extends HasModifiers & HasDecorators>(node: T, modifiers: readonly ModifierLike[] | undefined): T;
|
||||
/**
|
||||
* Updates a node that contains a property name, replacing only the name of the node.
|
||||
*/
|
||||
replacePropertyName<T extends AccessorDeclaration | MethodDeclaration | MethodSignature | PropertyDeclaration | PropertySignature | PropertyAssignment>(node: T, name: T["name"]): T;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
@@ -10013,6 +10047,7 @@ export interface UserPreferences {
|
||||
readonly interactiveInlayHints?: boolean;
|
||||
readonly allowRenameOfImportPath?: boolean;
|
||||
readonly autoImportFileExcludePatterns?: string[];
|
||||
readonly preferTypeOnlyAutoImports?: boolean;
|
||||
readonly organizeImportsIgnoreCase?: "auto" | boolean;
|
||||
readonly organizeImportsCollation?: "ordinal" | "unicode";
|
||||
readonly organizeImportsLocale?: string;
|
||||
|
||||
+32
-13
@@ -3975,12 +3975,13 @@ export function isFunctionSymbol(symbol: Symbol | undefined) {
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function tryGetModuleSpecifierFromDeclaration(node: AnyImportOrBareOrAccessedRequire | AliasDeclarationNode): StringLiteralLike | undefined {
|
||||
export function tryGetModuleSpecifierFromDeclaration(node: AnyImportOrBareOrAccessedRequire | AliasDeclarationNode | ExportDeclaration | ImportTypeNode): StringLiteralLike | undefined {
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.VariableDeclaration:
|
||||
case SyntaxKind.BindingElement:
|
||||
return findAncestor(node.initializer, (node): node is RequireOrImportCall => isRequireCall(node, /*requireStringLiteralLikeArgument*/ true))?.arguments[0];
|
||||
case SyntaxKind.ImportDeclaration:
|
||||
case SyntaxKind.ExportDeclaration:
|
||||
return tryCast(node.moduleSpecifier, isStringLiteralLike);
|
||||
case SyntaxKind.ImportEqualsDeclaration:
|
||||
return tryCast(tryCast(node.moduleReference, isExternalModuleReference)?.expression, isStringLiteralLike);
|
||||
@@ -3992,6 +3993,8 @@ export function tryGetModuleSpecifierFromDeclaration(node: AnyImportOrBareOrAcce
|
||||
return tryCast(node.parent.parent.moduleSpecifier, isStringLiteralLike);
|
||||
case SyntaxKind.ImportSpecifier:
|
||||
return tryCast(node.parent.parent.parent.moduleSpecifier, isStringLiteralLike);
|
||||
case SyntaxKind.ImportType:
|
||||
return isLiteralImportTypeNode(node) ? node.argument.literal : undefined;
|
||||
default:
|
||||
Debug.assertNever(node);
|
||||
}
|
||||
@@ -4331,7 +4334,7 @@ export function getNextJSDocCommentLocation(node: Node) {
|
||||
parent.kind === SyntaxKind.ExpressionStatement && node.kind === SyntaxKind.PropertyAccessExpression ||
|
||||
parent.kind === SyntaxKind.ReturnStatement ||
|
||||
getNestedModuleDeclaration(parent) ||
|
||||
isBinaryExpression(node) && node.operatorToken.kind === SyntaxKind.EqualsToken
|
||||
isAssignmentExpression(node)
|
||||
) {
|
||||
return parent;
|
||||
}
|
||||
@@ -4343,8 +4346,7 @@ export function getNextJSDocCommentLocation(node: Node) {
|
||||
// var x = function(name) { return name.length; }
|
||||
else if (
|
||||
parent.parent &&
|
||||
(getSingleVariableOfVariableStatement(parent.parent) === node ||
|
||||
isBinaryExpression(parent) && parent.operatorToken.kind === SyntaxKind.EqualsToken)
|
||||
(getSingleVariableOfVariableStatement(parent.parent) === node || isAssignmentExpression(parent))
|
||||
) {
|
||||
return parent.parent;
|
||||
}
|
||||
@@ -5062,6 +5064,7 @@ export function getPropertyNameForPropertyNameNode(name: PropertyName | JsxAttri
|
||||
case SyntaxKind.PrivateIdentifier:
|
||||
return name.escapedText;
|
||||
case SyntaxKind.StringLiteral:
|
||||
case SyntaxKind.NoSubstitutionTemplateLiteral:
|
||||
case SyntaxKind.NumericLiteral:
|
||||
return escapeLeadingUnderscores(name.text);
|
||||
case SyntaxKind.ComputedPropertyName:
|
||||
@@ -6925,11 +6928,14 @@ function getModifierFlagsWorker(node: Node, includeJSDoc: boolean, alwaysInclude
|
||||
node.modifierFlagsCache = getSyntacticModifierFlagsNoCache(node) | ModifierFlags.HasComputedFlags;
|
||||
}
|
||||
|
||||
if (includeJSDoc && !(node.modifierFlagsCache & ModifierFlags.HasComputedJSDocModifiers) && (alwaysIncludeJSDoc || isInJSFile(node)) && node.parent) {
|
||||
node.modifierFlagsCache |= getJSDocModifierFlagsNoCache(node) | ModifierFlags.HasComputedJSDocModifiers;
|
||||
if (alwaysIncludeJSDoc || includeJSDoc && isInJSFile(node)) {
|
||||
if (!(node.modifierFlagsCache & ModifierFlags.HasComputedJSDocModifiers) && node.parent) {
|
||||
node.modifierFlagsCache |= getRawJSDocModifierFlagsNoCache(node) | ModifierFlags.HasComputedJSDocModifiers;
|
||||
}
|
||||
return selectEffectiveModifierFlags(node.modifierFlagsCache);
|
||||
}
|
||||
|
||||
return node.modifierFlagsCache & ~(ModifierFlags.HasComputedFlags | ModifierFlags.HasComputedJSDocModifiers);
|
||||
return selectSyntacticModifierFlags(node.modifierFlagsCache);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -6959,15 +6965,15 @@ export function getSyntacticModifierFlags(node: Node): ModifierFlags {
|
||||
return getModifierFlagsWorker(node, /*includeJSDoc*/ false);
|
||||
}
|
||||
|
||||
function getJSDocModifierFlagsNoCache(node: Node): ModifierFlags {
|
||||
function getRawJSDocModifierFlagsNoCache(node: Node): ModifierFlags {
|
||||
let flags = ModifierFlags.None;
|
||||
if (!!node.parent && !isParameter(node)) {
|
||||
if (isInJSFile(node)) {
|
||||
if (getJSDocPublicTagNoCache(node)) flags |= ModifierFlags.Public;
|
||||
if (getJSDocPrivateTagNoCache(node)) flags |= ModifierFlags.Private;
|
||||
if (getJSDocProtectedTagNoCache(node)) flags |= ModifierFlags.Protected;
|
||||
if (getJSDocReadonlyTagNoCache(node)) flags |= ModifierFlags.Readonly;
|
||||
if (getJSDocOverrideTagNoCache(node)) flags |= ModifierFlags.Override;
|
||||
if (getJSDocPublicTagNoCache(node)) flags |= ModifierFlags.JSDocPublic;
|
||||
if (getJSDocPrivateTagNoCache(node)) flags |= ModifierFlags.JSDocPrivate;
|
||||
if (getJSDocProtectedTagNoCache(node)) flags |= ModifierFlags.JSDocProtected;
|
||||
if (getJSDocReadonlyTagNoCache(node)) flags |= ModifierFlags.JSDocReadonly;
|
||||
if (getJSDocOverrideTagNoCache(node)) flags |= ModifierFlags.JSDocOverride;
|
||||
}
|
||||
if (getJSDocDeprecatedTagNoCache(node)) flags |= ModifierFlags.Deprecated;
|
||||
}
|
||||
@@ -6975,6 +6981,19 @@ function getJSDocModifierFlagsNoCache(node: Node): ModifierFlags {
|
||||
return flags;
|
||||
}
|
||||
|
||||
function selectSyntacticModifierFlags(flags: ModifierFlags) {
|
||||
return flags & ModifierFlags.SyntacticModifiers;
|
||||
}
|
||||
|
||||
function selectEffectiveModifierFlags(flags: ModifierFlags) {
|
||||
return (flags & ModifierFlags.NonCacheOnlyModifiers) |
|
||||
((flags & ModifierFlags.JSDocCacheOnlyModifiers) >>> 23); // shift ModifierFlags.JSDoc* to match ModifierFlags.*
|
||||
}
|
||||
|
||||
function getJSDocModifierFlagsNoCache(node: Node): ModifierFlags {
|
||||
return selectEffectiveModifierFlags(getRawJSDocModifierFlagsNoCache(node));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the effective ModifierFlags for the provided node, including JSDoc modifiers. The modifier flags cache on the node is ignored.
|
||||
*
|
||||
|
||||
+21
-21
@@ -12,7 +12,6 @@ import {
|
||||
CompilerHost,
|
||||
CompilerOptions,
|
||||
ConfigFileDiagnosticsReporter,
|
||||
ConfigFileProgramReloadLevel,
|
||||
createBuilderProgramUsingProgramBuildInfo,
|
||||
createCachedDirectoryStructureHost,
|
||||
createCompilerDiagnostic,
|
||||
@@ -64,6 +63,7 @@ import {
|
||||
Path,
|
||||
perfLogger,
|
||||
PollingInterval,
|
||||
ProgramUpdateLevel,
|
||||
ProjectReference,
|
||||
ResolutionCache,
|
||||
ResolutionCacheHost,
|
||||
@@ -399,8 +399,8 @@ interface ParsedConfig {
|
||||
watcher?: FileWatcher;
|
||||
/** Wild card directories watched from this config file */
|
||||
watchedDirectories?: Map<string, WildcardDirectoryWatcher>;
|
||||
/** Reload to be done for this config file */
|
||||
reloadLevel?: ConfigFileProgramReloadLevel.Partial | ConfigFileProgramReloadLevel.Full;
|
||||
/** Level of program update to be done for this config file */
|
||||
updateLevel?: ProgramUpdateLevel.RootNamesAndUpdate | ProgramUpdateLevel.Full;
|
||||
}
|
||||
|
||||
// All of one and partial of the other, or vice versa.
|
||||
@@ -431,7 +431,7 @@ export function createWatchProgram<T extends BuilderProgram>(host: WatchCompiler
|
||||
type HostFileInfo = FilePresentOnHost | FileMissingOnHost | FilePresenceUnknownOnHost;
|
||||
|
||||
let builderProgram: T;
|
||||
let reloadLevel: ConfigFileProgramReloadLevel; // level to indicate if the program needs to be reloaded from config file/just filenames etc
|
||||
let updateLevel: ProgramUpdateLevel; // level to indicate if the program needs to be reloaded from config file/just filenames etc
|
||||
let missingFilesMap: Map<Path, FileWatcher>; // Map of file watchers for the missing files
|
||||
let watchedWildcardDirectories: Map<string, WildcardDirectoryWatcher>; // map of watchers for the wild card directories in the config file
|
||||
let timerToUpdateProgram: any; // timer callback to recompile the program
|
||||
@@ -872,7 +872,7 @@ export function createWatchProgram<T extends BuilderProgram>(host: WatchCompiler
|
||||
|
||||
function scheduleProgramReload() {
|
||||
Debug.assert(!!configFileName);
|
||||
reloadLevel = ConfigFileProgramReloadLevel.Full;
|
||||
updateLevel = ProgramUpdateLevel.Full;
|
||||
scheduleProgramUpdate();
|
||||
}
|
||||
|
||||
@@ -883,12 +883,12 @@ export function createWatchProgram<T extends BuilderProgram>(host: WatchCompiler
|
||||
}
|
||||
|
||||
function updateProgram() {
|
||||
switch (reloadLevel) {
|
||||
case ConfigFileProgramReloadLevel.Partial:
|
||||
switch (updateLevel) {
|
||||
case ProgramUpdateLevel.RootNamesAndUpdate:
|
||||
perfLogger?.logStartUpdateProgram("PartialConfigReload");
|
||||
reloadFileNamesFromConfigFile();
|
||||
break;
|
||||
case ConfigFileProgramReloadLevel.Full:
|
||||
case ProgramUpdateLevel.Full:
|
||||
perfLogger?.logStartUpdateProgram("FullConfigReload");
|
||||
reloadConfigFile();
|
||||
break;
|
||||
@@ -907,7 +907,7 @@ export function createWatchProgram<T extends BuilderProgram>(host: WatchCompiler
|
||||
Debug.assert(compilerOptions);
|
||||
Debug.assert(configFileName);
|
||||
|
||||
reloadLevel = ConfigFileProgramReloadLevel.None;
|
||||
updateLevel = ProgramUpdateLevel.Update;
|
||||
rootFileNames = getFileNamesFromConfigSpecs(compilerOptions.configFile!.configFileSpecs!, getNormalizedAbsolutePath(getDirectoryPath(configFileName), currentDirectory), compilerOptions, parseConfigFileHost, extraFileExtensions);
|
||||
if (updateErrorForNoInputFiles(rootFileNames, getNormalizedAbsolutePath(configFileName, currentDirectory), compilerOptions.configFile!.configFileSpecs!, configFileParsingDiagnostics!, canConfigFileJsonReportNoInputFiles)) {
|
||||
hasChangedConfigFileParsingErrors = true;
|
||||
@@ -920,7 +920,7 @@ export function createWatchProgram<T extends BuilderProgram>(host: WatchCompiler
|
||||
function reloadConfigFile() {
|
||||
Debug.assert(configFileName);
|
||||
writeLog(`Reloading config file: ${configFileName}`);
|
||||
reloadLevel = ConfigFileProgramReloadLevel.None;
|
||||
updateLevel = ProgramUpdateLevel.Update;
|
||||
|
||||
if (cachedDirectoryStructureHost) {
|
||||
cachedDirectoryStructureHost.clearCache();
|
||||
@@ -965,9 +965,9 @@ export function createWatchProgram<T extends BuilderProgram>(host: WatchCompiler
|
||||
const configPath = toPath(configFileName);
|
||||
let config = parsedConfigs?.get(configPath);
|
||||
if (config) {
|
||||
if (!config.reloadLevel) return config.parsedCommandLine;
|
||||
if (!config.updateLevel) return config.parsedCommandLine;
|
||||
// With host implementing getParsedCommandLine we cant just update file names
|
||||
if (config.parsedCommandLine && config.reloadLevel === ConfigFileProgramReloadLevel.Partial && !host.getParsedCommandLine) {
|
||||
if (config.parsedCommandLine && config.updateLevel === ProgramUpdateLevel.RootNamesAndUpdate && !host.getParsedCommandLine) {
|
||||
writeLog("Reloading new file names and options");
|
||||
Debug.assert(compilerOptions);
|
||||
const fileNames = getFileNamesFromConfigSpecs(
|
||||
@@ -977,7 +977,7 @@ export function createWatchProgram<T extends BuilderProgram>(host: WatchCompiler
|
||||
parseConfigFileHost,
|
||||
);
|
||||
config.parsedCommandLine = { ...config.parsedCommandLine, fileNames };
|
||||
config.reloadLevel = undefined;
|
||||
config.updateLevel = undefined;
|
||||
return config.parsedCommandLine;
|
||||
}
|
||||
}
|
||||
@@ -988,7 +988,7 @@ export function createWatchProgram<T extends BuilderProgram>(host: WatchCompiler
|
||||
getParsedCommandLineFromConfigFileHost(configFileName);
|
||||
if (config) {
|
||||
config.parsedCommandLine = parsedCommandLine;
|
||||
config.reloadLevel = undefined;
|
||||
config.updateLevel = undefined;
|
||||
}
|
||||
else {
|
||||
(parsedConfigs ||= new Map()).set(configPath, config = { parsedCommandLine });
|
||||
@@ -1120,8 +1120,8 @@ export function createWatchProgram<T extends BuilderProgram>(host: WatchCompiler
|
||||
) return;
|
||||
|
||||
// Reload is pending, do the reload
|
||||
if (reloadLevel !== ConfigFileProgramReloadLevel.Full) {
|
||||
reloadLevel = ConfigFileProgramReloadLevel.Partial;
|
||||
if (updateLevel !== ProgramUpdateLevel.Full) {
|
||||
updateLevel = ProgramUpdateLevel.RootNamesAndUpdate;
|
||||
|
||||
// Schedule Update the program
|
||||
scheduleProgramUpdate();
|
||||
@@ -1152,12 +1152,12 @@ export function createWatchProgram<T extends BuilderProgram>(host: WatchCompiler
|
||||
projects.forEach(projectPath => {
|
||||
if (configFileName && toPath(configFileName) === projectPath) {
|
||||
// If this is the config file of the project, reload completely
|
||||
reloadLevel = ConfigFileProgramReloadLevel.Full;
|
||||
updateLevel = ProgramUpdateLevel.Full;
|
||||
}
|
||||
else {
|
||||
// Reload config for the referenced projects and remove the resolutions from referenced projects since the config file changed
|
||||
const config = parsedConfigs?.get(projectPath);
|
||||
if (config) config.reloadLevel = ConfigFileProgramReloadLevel.Full;
|
||||
if (config) config.updateLevel = ProgramUpdateLevel.Full;
|
||||
resolutionCache.removeResolutionsFromProjectReferenceRedirects(projectPath);
|
||||
}
|
||||
scheduleProgramUpdate();
|
||||
@@ -1178,7 +1178,7 @@ export function createWatchProgram<T extends BuilderProgram>(host: WatchCompiler
|
||||
(_fileName, eventKind) => {
|
||||
updateCachedSystemWithFile(configFileName, configPath, eventKind);
|
||||
const config = parsedConfigs?.get(configPath);
|
||||
if (config) config.reloadLevel = ConfigFileProgramReloadLevel.Full;
|
||||
if (config) config.updateLevel = ProgramUpdateLevel.Full;
|
||||
resolutionCache.removeResolutionsFromProjectReferenceRedirects(configPath);
|
||||
scheduleProgramUpdate();
|
||||
},
|
||||
@@ -1220,8 +1220,8 @@ export function createWatchProgram<T extends BuilderProgram>(host: WatchCompiler
|
||||
) return;
|
||||
|
||||
// Reload is pending, do the reload
|
||||
if (config.reloadLevel !== ConfigFileProgramReloadLevel.Full) {
|
||||
config.reloadLevel = ConfigFileProgramReloadLevel.Partial;
|
||||
if (config.updateLevel !== ProgramUpdateLevel.Full) {
|
||||
config.updateLevel = ProgramUpdateLevel.RootNamesAndUpdate;
|
||||
|
||||
// Schedule Update the program
|
||||
scheduleProgramUpdate();
|
||||
|
||||
@@ -365,12 +365,18 @@ export function createCachedDirectoryStructureHost(host: DirectoryStructureHost,
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export enum ConfigFileProgramReloadLevel {
|
||||
None,
|
||||
/** Update the file name list from the disk */
|
||||
Partial,
|
||||
/** Reload completely by re-reading contents of config file from disk and updating program */
|
||||
export enum ProgramUpdateLevel {
|
||||
/** Program is updated with same root file names and options */
|
||||
Update,
|
||||
/** Loads program after updating root file names from the disk */
|
||||
RootNamesAndUpdate,
|
||||
/**
|
||||
* Loads program completely, including:
|
||||
* - re-reading contents of config file from disk
|
||||
* - calculating root file names for the program
|
||||
* - Updating the program
|
||||
*/
|
||||
|
||||
Full,
|
||||
}
|
||||
|
||||
|
||||
+301
-259
@@ -5,6 +5,9 @@ import * as ts from "./_namespaces/ts";
|
||||
import * as Utils from "./_namespaces/Utils";
|
||||
import * as vfs from "./_namespaces/vfs";
|
||||
import * as vpath from "./_namespaces/vpath";
|
||||
import {
|
||||
Logger,
|
||||
} from "./tsserverLogger";
|
||||
|
||||
import ArrayOrSingle = FourSlashInterface.ArrayOrSingle;
|
||||
|
||||
@@ -226,6 +229,12 @@ function realizeDiagnostic(diagnostic: ts.Diagnostic, newLine: string): Realized
|
||||
};
|
||||
}
|
||||
|
||||
interface BaselineTest {
|
||||
command: string;
|
||||
actual: string;
|
||||
ext: string | undefined;
|
||||
}
|
||||
|
||||
export class TestState {
|
||||
// Language service instance
|
||||
private languageServiceAdapterHost: Harness.LanguageService.LanguageServiceAdapterHost;
|
||||
@@ -250,6 +259,8 @@ export class TestState {
|
||||
|
||||
private inputFiles = new Map<string, string>(); // Map between inputFile's fileName and its content for easily looking up when resolving references
|
||||
|
||||
private logger: Logger | undefined;
|
||||
|
||||
private static getDisplayPartsJson(displayParts: ts.SymbolDisplayPart[] | undefined) {
|
||||
let result = "";
|
||||
ts.forEach(displayParts, part => {
|
||||
@@ -358,6 +369,7 @@ export class TestState {
|
||||
}
|
||||
|
||||
const languageServiceAdapter = this.getLanguageServiceAdapter(testType, this.cancellationToken, compilationOptions);
|
||||
this.logger = languageServiceAdapter.getLogger();
|
||||
this.languageServiceAdapterHost = languageServiceAdapter.getHost();
|
||||
this.languageService = memoWrap(languageServiceAdapter.getLanguageService(), this); // Wrap the LS to cache some expensive operations certain tests call repeatedly
|
||||
if (this.testType === FourSlashTestType.Server) {
|
||||
@@ -445,6 +457,11 @@ export class TestState {
|
||||
|
||||
this.formatCodeSettings = ts.testFormatSettings;
|
||||
|
||||
if (this.logger?.loggingEnabled()) {
|
||||
const patch = this.languageServiceAdapterHost.vfs.diff();
|
||||
this.logger.log(vfs.formatPatch(patch) || "");
|
||||
}
|
||||
|
||||
// Open the first file by default
|
||||
this.openFile(0);
|
||||
|
||||
@@ -482,6 +499,31 @@ export class TestState {
|
||||
}
|
||||
}
|
||||
|
||||
private baselineFromTest: BaselineTest[] | undefined;
|
||||
|
||||
private baseline(command: string, actual: string, ext?: string) {
|
||||
if (!this.baselineFromTest) this.baselineFromTest = [{ command, actual, ext }];
|
||||
else this.baselineFromTest.push({ command, actual, ext });
|
||||
}
|
||||
|
||||
baselineTest() {
|
||||
if (this.baselineFromTest) {
|
||||
Harness.Baseline.runBaseline(
|
||||
this.getBaselineFileNameForContainingTestFile(this.baselineFromTest[0].ext),
|
||||
this.baselineFromTest.map(({ command, actual }) => `// === ${command} ===\n${actual}`).join("\n\n\n\n"),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
baselineTsserverLog() {
|
||||
if (this.logger) {
|
||||
Harness.Baseline.runBaseline(
|
||||
`tsserver/fourslashServer/${ts.getBaseFileName(this.originalInputFileName).replace(".ts", ".js")}`,
|
||||
this.logger.logs!.join("\n"),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private getFileContent(fileName: string): string {
|
||||
return ts.Debug.checkDefined(this.tryGetFileContent(fileName));
|
||||
}
|
||||
@@ -827,6 +869,71 @@ export class TestState {
|
||||
return baseline;
|
||||
}
|
||||
|
||||
public baselineGoToDefinition(
|
||||
markerOrRange: MarkerOrNameOrRange[] | undefined,
|
||||
rangeText: string[] | undefined,
|
||||
) {
|
||||
this.baselineEachMarkerOrRange("goToDefinition", markerOrRange, rangeText, markerOrRange =>
|
||||
this.baselineGoToDefs(
|
||||
"/*GOTO DEF*/",
|
||||
markerOrRange,
|
||||
() => this.getGoToDefinitionAndBoundSpan(),
|
||||
));
|
||||
}
|
||||
|
||||
public baselineGetDefinitionAtPosition(
|
||||
markerOrRange: MarkerOrNameOrRange[] | undefined,
|
||||
rangeText: string[] | undefined,
|
||||
) {
|
||||
this.baselineEachMarkerOrRange("getDefinitionAtPosition", markerOrRange, rangeText, markerOrRange =>
|
||||
this.baselineGoToDefs(
|
||||
"/*GOTO DEF POS*/",
|
||||
markerOrRange,
|
||||
() => this.getGoToDefinition(),
|
||||
));
|
||||
}
|
||||
|
||||
public baselineGoToSourceDefinition(
|
||||
markerOrRange: MarkerOrNameOrRange[] | undefined,
|
||||
rangeText: string[] | undefined,
|
||||
) {
|
||||
if (this.testType !== FourSlashTestType.Server) {
|
||||
this.raiseError("goToSourceDefinition may only be used in fourslash/server tests.");
|
||||
}
|
||||
this.baselineEachMarkerOrRange("goToSourceDefinition", markerOrRange, rangeText, markerOrRange =>
|
||||
this.baselineGoToDefs(
|
||||
"/*GOTO SOURCE DEF*/",
|
||||
markerOrRange,
|
||||
() =>
|
||||
(this.languageService as ts.server.SessionClient)
|
||||
.getSourceDefinitionAndBoundSpan(this.activeFile.fileName, this.currentCaretPosition),
|
||||
));
|
||||
}
|
||||
|
||||
public baselineGoToType(
|
||||
markerOrRange: MarkerOrNameOrRange[] | undefined,
|
||||
rangeText: string[] | undefined,
|
||||
) {
|
||||
this.baselineEachMarkerOrRange("goToType", markerOrRange, rangeText, markerOrRange =>
|
||||
this.baselineGoToDefs(
|
||||
"/*GOTO TYPE*/",
|
||||
markerOrRange,
|
||||
() => this.languageService.getTypeDefinitionAtPosition(this.activeFile.fileName, this.currentCaretPosition),
|
||||
));
|
||||
}
|
||||
|
||||
public baselineGoToImplementation(
|
||||
markerOrRange: MarkerOrNameOrRange[] | undefined,
|
||||
rangeText: string[] | undefined,
|
||||
) {
|
||||
this.baselineEachMarkerOrRange("goToImplementation", markerOrRange, rangeText, markerOrRange =>
|
||||
this.baselineGoToDefs(
|
||||
"/*GOTO IMPL*/",
|
||||
markerOrRange,
|
||||
() => this.languageService.getImplementationAtPosition(this.activeFile.fileName, this.currentCaretPosition),
|
||||
));
|
||||
}
|
||||
|
||||
public verifyGetEmitOutputForCurrentFile(expected: string): void {
|
||||
const emit = this.languageService.getEmitOutput(this.activeFile.fileName);
|
||||
if (emit.outputFiles.length !== 1) {
|
||||
@@ -855,7 +962,6 @@ export class TestState {
|
||||
return a.position - b.position;
|
||||
};
|
||||
|
||||
const baselineFile = this.getBaselineFileNameForContainingTestFile();
|
||||
const fileName = this.activeFile.fileName;
|
||||
const hints = this.languageService.provideInlayHints(fileName, span, preferences);
|
||||
const annotations = ts.map(hints.sort(sortHints), hint => {
|
||||
@@ -880,7 +986,7 @@ export class TestState {
|
||||
annotations.push("=== No inlay hints ===");
|
||||
}
|
||||
|
||||
Harness.Baseline.runBaseline(baselineFile, annotations.join("\n\n"));
|
||||
this.baseline("Inlay Hints", annotations.join("\n\n"));
|
||||
}
|
||||
|
||||
public verifyCompletions(options: FourSlashInterface.VerifyCompletionsOptions) {
|
||||
@@ -1220,184 +1326,106 @@ export class TestState {
|
||||
}
|
||||
}
|
||||
|
||||
public verifyBaselineCommands(...commands: FourSlashInterface.BaselineCommand[]) {
|
||||
let baselineContent = "";
|
||||
const baselineEachMarkerOrRange = (
|
||||
command: FourSlashInterface.BaselineCommandWithMarkerOrRange,
|
||||
worker: (markerORange: MarkerOrNameOrRange) => string,
|
||||
) => {
|
||||
let done = false;
|
||||
if (command.markerOrRange !== undefined) {
|
||||
done = baselineArrayOrSingle(command, command.markerOrRange, worker);
|
||||
}
|
||||
if (command.rangeText !== undefined) {
|
||||
toArray(command.rangeText).forEach(text => done = baselineArrayOrSingle(command, this.rangesByText().get(text)!, worker) || done);
|
||||
}
|
||||
if (!done) {
|
||||
baselineArrayOrSingle(command, this.getRanges(), worker);
|
||||
}
|
||||
};
|
||||
commands.forEach(command => {
|
||||
switch (command.type) {
|
||||
case "findAllReferences":
|
||||
return baselineEachMarkerOrRange(
|
||||
command,
|
||||
markerOrRange => this.baselineFindAllReferencesWorker(markerOrRange),
|
||||
);
|
||||
case "getFileReferences":
|
||||
return baselineArrayOrSingle(
|
||||
command,
|
||||
command.fileName,
|
||||
fileName => this.baselineGetFileReferences(fileName),
|
||||
);
|
||||
case "findRenameLocations":
|
||||
return baselineEachMarkerOrRange(
|
||||
command,
|
||||
markerOrRange => this.baselineRenameWorker(markerOrRange, command.options),
|
||||
);
|
||||
case "goToDefinition":
|
||||
return baselineEachMarkerOrRange(
|
||||
command,
|
||||
markerOrRange =>
|
||||
this.baselineGoToDefs(
|
||||
"/*GOTO DEF*/",
|
||||
markerOrRange,
|
||||
() => this.getGoToDefinitionAndBoundSpan(),
|
||||
),
|
||||
);
|
||||
case "getDefinitionAtPosition":
|
||||
return baselineEachMarkerOrRange(
|
||||
command,
|
||||
markerOrRange =>
|
||||
this.baselineGoToDefs(
|
||||
"/*GOTO DEF POS*/",
|
||||
markerOrRange,
|
||||
() => this.getGoToDefinition(),
|
||||
),
|
||||
);
|
||||
case "goToSourceDefinition":
|
||||
if (this.testType !== FourSlashTestType.Server) {
|
||||
this.raiseError("goToSourceDefinition may only be used in fourslash/server tests.");
|
||||
private baselineEachMarkerOrRangeArrayOrSingle(
|
||||
command: string,
|
||||
markerOrRange: ArrayOrSingle<MarkerOrNameOrRange> | undefined,
|
||||
rangeText: ArrayOrSingle<string> | undefined,
|
||||
worker: (markerORange: MarkerOrNameOrRange) => string,
|
||||
) {
|
||||
return this.baselineEachMarkerOrRange(
|
||||
command,
|
||||
markerOrRange !== undefined ? toArray(markerOrRange) : undefined,
|
||||
rangeText !== undefined ? toArray(rangeText) : undefined,
|
||||
worker,
|
||||
);
|
||||
}
|
||||
|
||||
private baselineEachMarkerOrRange(
|
||||
command: string,
|
||||
markerOrRange: readonly MarkerOrNameOrRange[] | undefined,
|
||||
rangeText: readonly string[] | undefined,
|
||||
worker: (markerORange: MarkerOrNameOrRange) => string,
|
||||
) {
|
||||
let done = false;
|
||||
if (markerOrRange !== undefined) {
|
||||
done = this.baselineArray(command, markerOrRange, worker);
|
||||
}
|
||||
if (rangeText !== undefined) {
|
||||
toArray(rangeText).forEach(text => done = this.baselineArray(command, this.rangesByText().get(text)!, worker) || done);
|
||||
}
|
||||
if (!done) this.baselineArray(command, this.getRanges(), worker);
|
||||
}
|
||||
|
||||
private baselineArray<T>(
|
||||
command: string,
|
||||
array: readonly T[],
|
||||
worker: (single: T) => string,
|
||||
) {
|
||||
array.forEach(single => this.baseline(command, worker(single), ".baseline.jsonc"));
|
||||
return !!array.length;
|
||||
}
|
||||
|
||||
public baselineFindAllReferences(
|
||||
markerOrRange: MarkerOrNameOrRange[] | undefined,
|
||||
rangeText: string[] | undefined,
|
||||
) {
|
||||
this.baselineEachMarkerOrRange("findAllReferences", markerOrRange, rangeText, markerOrRange => {
|
||||
this.goToMarkerOrNameOrRange(markerOrRange);
|
||||
const references = this.findReferencesAtCaret();
|
||||
const defIdMap = new Map<ts.ReferencedSymbolDefinitionInfo | ts.ReferencedSymbolEntry, number>();
|
||||
const markerInfo = { markerOrRange, markerName: "/*FIND ALL REFS*/" };
|
||||
let baseline = this.getBaselineForDocumentSpansWithFileContents(
|
||||
ts.flatMap(references, (r, def) => {
|
||||
if (references!.length > 1) {
|
||||
defIdMap.set(r.definition, def);
|
||||
r.references.forEach(r => defIdMap.set(r, def));
|
||||
}
|
||||
return baselineEachMarkerOrRange(
|
||||
command,
|
||||
markerOrRange =>
|
||||
this.baselineGoToDefs(
|
||||
"/*GOTO SOURCE DEF*/",
|
||||
markerOrRange,
|
||||
() =>
|
||||
(this.languageService as ts.server.SessionClient)
|
||||
.getSourceDefinitionAndBoundSpan(this.activeFile.fileName, this.currentCaretPosition),
|
||||
),
|
||||
);
|
||||
case "goToType":
|
||||
return baselineEachMarkerOrRange(
|
||||
command,
|
||||
markerOrRange =>
|
||||
this.baselineGoToDefs(
|
||||
"/*GOTO TYPE*/",
|
||||
markerOrRange,
|
||||
() => this.languageService.getTypeDefinitionAtPosition(this.activeFile.fileName, this.currentCaretPosition),
|
||||
),
|
||||
);
|
||||
case "goToImplementation":
|
||||
return baselineEachMarkerOrRange(
|
||||
command,
|
||||
markerOrRange =>
|
||||
this.baselineGoToDefs(
|
||||
"/*GOTO IMPL*/",
|
||||
markerOrRange,
|
||||
() => this.languageService.getImplementationAtPosition(this.activeFile.fileName, this.currentCaretPosition),
|
||||
),
|
||||
);
|
||||
case "documentHighlights":
|
||||
return baselineEachMarkerOrRange(
|
||||
command,
|
||||
markerOrRange => this.baselineGetDocumentHighlights(markerOrRange, command.options),
|
||||
);
|
||||
case "customWork":
|
||||
return addToBaseline(command, readableJsoncBaseline(command.work() || ""));
|
||||
default:
|
||||
ts.Debug.assertNever(command);
|
||||
}
|
||||
});
|
||||
Harness.Baseline.runBaseline(this.getBaselineFileNameForContainingTestFile(".baseline.jsonc"), baselineContent);
|
||||
|
||||
function baselineArrayOrSingle<T>(
|
||||
command: FourSlashInterface.BaselineCommand,
|
||||
arrayOrSingle: ArrayOrSingle<T>,
|
||||
worker: (single: T) => string,
|
||||
) {
|
||||
if (ts.isArray(arrayOrSingle)) {
|
||||
arrayOrSingle.forEach(single => addToBaseline(command, worker(single)));
|
||||
return !!arrayOrSingle.length;
|
||||
}
|
||||
else {
|
||||
addToBaseline(command, worker(arrayOrSingle));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function addToBaseline(command: FourSlashInterface.BaselineCommand, text: string) {
|
||||
if (baselineContent) baselineContent += "\n\n\n\n";
|
||||
baselineContent += `// === ${command.type} ===\n` + text;
|
||||
}
|
||||
}
|
||||
|
||||
private baselineFindAllReferencesWorker(markerOrRange: MarkerOrNameOrRange) {
|
||||
this.goToMarkerOrNameOrRange(markerOrRange);
|
||||
const references = this.findReferencesAtCaret();
|
||||
const defIdMap = new Map<ts.ReferencedSymbolDefinitionInfo | ts.ReferencedSymbolEntry, number>();
|
||||
const markerInfo = { markerOrRange, markerName: "/*FIND ALL REFS*/" };
|
||||
let baseline = this.getBaselineForDocumentSpansWithFileContents(
|
||||
ts.flatMap(references, (r, def) => {
|
||||
if (references!.length > 1) {
|
||||
defIdMap.set(r.definition, def);
|
||||
r.references.forEach(r => defIdMap.set(r, def));
|
||||
}
|
||||
return r.references;
|
||||
}),
|
||||
{
|
||||
markerInfo,
|
||||
documentSpanId: defIdMap.size ? ref => `defId: ${defIdMap.get(ref)}` : undefined,
|
||||
},
|
||||
);
|
||||
if (references?.length) {
|
||||
baseline += "\n\n";
|
||||
baseline += indentJsonBaseline(
|
||||
"// === Definitions ===\n" +
|
||||
this.getBaselineForDocumentSpansWithFileContents(
|
||||
references.map(r => r.definition),
|
||||
{
|
||||
markerInfo,
|
||||
documentSpanId: defIdMap.size ? def => `defId: ${defIdMap.get(def)}` : undefined,
|
||||
skipDocumentSpanDetails: true,
|
||||
skipDocumentContainingOnlyMarker: true,
|
||||
},
|
||||
) +
|
||||
"\n\n// === Details ===\n" +
|
||||
JSON.stringify(
|
||||
references.map(r => ({
|
||||
defId: defIdMap.get(r.definition),
|
||||
...r.definition,
|
||||
fileName: undefined,
|
||||
textSpan: undefined,
|
||||
contextSpan: undefined,
|
||||
})),
|
||||
undefined,
|
||||
" ",
|
||||
),
|
||||
return r.references;
|
||||
}),
|
||||
{
|
||||
markerInfo,
|
||||
documentSpanId: defIdMap.size ? ref => `defId: ${defIdMap.get(ref)}` : undefined,
|
||||
},
|
||||
);
|
||||
}
|
||||
return baseline;
|
||||
if (references?.length) {
|
||||
baseline += "\n\n";
|
||||
baseline += indentJsonBaseline(
|
||||
"// === Definitions ===\n" +
|
||||
this.getBaselineForDocumentSpansWithFileContents(
|
||||
references.map(r => r.definition),
|
||||
{
|
||||
markerInfo,
|
||||
documentSpanId: defIdMap.size ? def => `defId: ${defIdMap.get(def)}` : undefined,
|
||||
skipDocumentSpanDetails: true,
|
||||
skipDocumentContainingOnlyMarker: true,
|
||||
},
|
||||
) +
|
||||
"\n\n// === Details ===\n" +
|
||||
JSON.stringify(
|
||||
references.map(r => ({
|
||||
defId: defIdMap.get(r.definition),
|
||||
...r.definition,
|
||||
fileName: undefined,
|
||||
textSpan: undefined,
|
||||
contextSpan: undefined,
|
||||
})),
|
||||
undefined,
|
||||
" ",
|
||||
),
|
||||
);
|
||||
}
|
||||
return baseline;
|
||||
});
|
||||
}
|
||||
|
||||
private baselineGetFileReferences(fileName: string) {
|
||||
const references = this.languageService.getFileReferences(fileName);
|
||||
return `// fileName: ${fileName}\n\n` + this.getBaselineForDocumentSpansWithFileContents(
|
||||
references,
|
||||
{ markerInfo: undefined },
|
||||
);
|
||||
public baselineGetFileReferences(fileNames: string[]) {
|
||||
this.baselineArray("getFileReferences", fileNames, fileName => {
|
||||
const references = this.languageService.getFileReferences(fileName);
|
||||
return `// fileName: ${fileName}\n\n` + this.getBaselineForDocumentSpansWithFileContents(
|
||||
references,
|
||||
{ markerInfo: undefined },
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
private getBaselineForDocumentSpansWithFileContents<T extends ts.DocumentSpan>(
|
||||
@@ -1869,47 +1897,53 @@ export class TestState {
|
||||
}
|
||||
}
|
||||
|
||||
private baselineRenameWorker(markerOrRange: MarkerOrNameOrRange, options?: FourSlashInterface.RenameOptions) {
|
||||
const { fileName, position } = ts.isString(markerOrRange) ?
|
||||
this.getMarkerByName(markerOrRange) :
|
||||
isMarker(markerOrRange) ?
|
||||
markerOrRange :
|
||||
{ fileName: markerOrRange.fileName, position: markerOrRange.pos };
|
||||
const {
|
||||
findInStrings = false,
|
||||
findInComments = false,
|
||||
providePrefixAndSuffixTextForRename = true,
|
||||
quotePreference = "double",
|
||||
} = options || {};
|
||||
const locations = this.languageService.findRenameLocations(
|
||||
fileName,
|
||||
position,
|
||||
findInStrings,
|
||||
findInComments,
|
||||
{ providePrefixAndSuffixTextForRename, quotePreference },
|
||||
);
|
||||
public baselineRename(
|
||||
markerOrRange: ArrayOrSingle<MarkerOrNameOrRange> | undefined,
|
||||
rangeText: ArrayOrSingle<string> | undefined,
|
||||
options: FourSlashInterface.RenameOptions | undefined,
|
||||
) {
|
||||
this.baselineEachMarkerOrRangeArrayOrSingle("findRenameLocations", markerOrRange, rangeText, markerOrRange => {
|
||||
const { fileName, position } = ts.isString(markerOrRange) ?
|
||||
this.getMarkerByName(markerOrRange) :
|
||||
isMarker(markerOrRange) ?
|
||||
markerOrRange :
|
||||
{ fileName: markerOrRange.fileName, position: markerOrRange.pos };
|
||||
const {
|
||||
findInStrings = false,
|
||||
findInComments = false,
|
||||
providePrefixAndSuffixTextForRename = true,
|
||||
quotePreference = "double",
|
||||
} = options || {};
|
||||
const locations = this.languageService.findRenameLocations(
|
||||
fileName,
|
||||
position,
|
||||
findInStrings,
|
||||
findInComments,
|
||||
{ providePrefixAndSuffixTextForRename, quotePreference },
|
||||
);
|
||||
|
||||
if (!locations) {
|
||||
this.raiseError(`baselineRename failed. Could not rename at the provided position.`);
|
||||
}
|
||||
if (!locations) {
|
||||
this.raiseError(`baselineRename failed. Could not rename at the provided position.`);
|
||||
}
|
||||
|
||||
const renameOptions = options ?
|
||||
(options.findInStrings !== undefined ? `// @findInStrings: ${findInStrings}\n` : "") +
|
||||
(options.findInComments !== undefined ? `// @findInComments: ${findInComments}\n` : "") +
|
||||
(options.providePrefixAndSuffixTextForRename !== undefined ? `// @providePrefixAndSuffixTextForRename: ${providePrefixAndSuffixTextForRename}\n` : "") +
|
||||
(options.quotePreference !== undefined ? `// @quotePreference: ${quotePreference}\n` : "") :
|
||||
"";
|
||||
const renameOptions = options ?
|
||||
(options.findInStrings !== undefined ? `// @findInStrings: ${findInStrings}\n` : "") +
|
||||
(options.findInComments !== undefined ? `// @findInComments: ${findInComments}\n` : "") +
|
||||
(options.providePrefixAndSuffixTextForRename !== undefined ? `// @providePrefixAndSuffixTextForRename: ${providePrefixAndSuffixTextForRename}\n` : "") +
|
||||
(options.quotePreference !== undefined ? `// @quotePreference: ${quotePreference}\n` : "") :
|
||||
"";
|
||||
|
||||
return renameOptions + (renameOptions ? "\n" : "") + this.getBaselineForDocumentSpansWithFileContents(
|
||||
locations,
|
||||
{
|
||||
markerInfo: { markerOrRange, markerName: "/*RENAME*/" },
|
||||
endMarker: "RENAME|]",
|
||||
startMarkerPrefix: span => span.prefixText ? `/*START PREFIX*/${span.prefixText}` : "",
|
||||
endMarkerSuffix: span => span.suffixText ? `${span.suffixText}/*END SUFFIX*/` : "",
|
||||
ignoredDocumentSpanProperties: ["prefixText", "suffixText"],
|
||||
},
|
||||
);
|
||||
return renameOptions + (renameOptions ? "\n" : "") + this.getBaselineForDocumentSpansWithFileContents(
|
||||
locations,
|
||||
{
|
||||
markerInfo: { markerOrRange, markerName: "/*RENAME*/" },
|
||||
endMarker: "RENAME|]",
|
||||
startMarkerPrefix: span => span.prefixText ? `/*START PREFIX*/${span.prefixText}` : "",
|
||||
endMarkerSuffix: span => span.suffixText ? `${span.suffixText}/*END SUFFIX*/` : "",
|
||||
ignoredDocumentSpanProperties: ["prefixText", "suffixText"],
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
public verifyQuickInfoExists(negative: boolean) {
|
||||
@@ -2228,8 +2262,7 @@ export class TestState {
|
||||
}
|
||||
|
||||
public baselineCurrentFileBreakpointLocations() {
|
||||
const baselineFile = this.getBaselineFileNameForInternalFourslashFile().replace("breakpointValidation", "bpSpan");
|
||||
Harness.Baseline.runBaseline(baselineFile, this.baselineCurrentFileLocations(pos => this.getBreakpointStatementLocation(pos)!));
|
||||
this.baseline("breakpoints", this.baselineCurrentFileLocations(pos => this.getBreakpointStatementLocation(pos)!));
|
||||
}
|
||||
|
||||
private getEmitFiles(): readonly FourSlashFile[] {
|
||||
@@ -2293,7 +2326,7 @@ export class TestState {
|
||||
resultString += Harness.IO.newLine();
|
||||
}
|
||||
|
||||
Harness.Baseline.runBaseline(ts.Debug.checkDefined(this.testData.globalOptions[MetadataOptionNames.baselineFile]), resultString);
|
||||
this.baseline("EmitOutput", resultString);
|
||||
}
|
||||
|
||||
private flattenChainedMessage(diag: ts.DiagnosticMessageChain, indent = " ") {
|
||||
@@ -2310,7 +2343,7 @@ export class TestState {
|
||||
public baselineSyntacticDiagnostics() {
|
||||
const files = this.getCompilerTestFiles();
|
||||
const result = this.getSyntacticDiagnosticBaselineText(files);
|
||||
Harness.Baseline.runBaseline(this.getBaselineFileNameForContainingTestFile(), result);
|
||||
this.baseline("Syntax Diagnostics", result);
|
||||
}
|
||||
|
||||
private getCompilerTestFiles() {
|
||||
@@ -2326,7 +2359,7 @@ export class TestState {
|
||||
+ Harness.IO.newLine()
|
||||
+ Harness.IO.newLine()
|
||||
+ this.getSemanticDiagnosticBaselineText(files);
|
||||
Harness.Baseline.runBaseline(this.getBaselineFileNameForContainingTestFile(), result);
|
||||
this.baseline("Syntax and Semantic Diagnostics", result);
|
||||
}
|
||||
|
||||
private getSyntacticDiagnosticBaselineText(files: Harness.Compiler.TestFile[]) {
|
||||
@@ -2346,7 +2379,6 @@ export class TestState {
|
||||
}
|
||||
|
||||
public baselineQuickInfo() {
|
||||
const baselineFile = this.getBaselineFileNameForContainingTestFile();
|
||||
const result = ts.arrayFrom(this.testData.markerPositions.entries(), ([name, marker]) => ({
|
||||
marker: { ...marker, name },
|
||||
item: this.languageService.getQuickInfoAtPosition(marker.fileName, marker.position),
|
||||
@@ -2361,11 +2393,10 @@ export class TestState {
|
||||
...(tags?.length ? tags.map(p => `@${p.name} ${p.text?.map(dp => dp.text).join("") ?? ""}`).join("\n").split("\n") : []),
|
||||
],
|
||||
);
|
||||
Harness.Baseline.runBaseline(baselineFile, annotations + "\n\n" + stringify(result));
|
||||
this.baseline("QuickInfo", annotations + "\n\n" + stringify(result));
|
||||
}
|
||||
|
||||
public baselineSignatureHelp() {
|
||||
const baselineFile = this.getBaselineFileNameForContainingTestFile();
|
||||
const result = ts.arrayFrom(this.testData.markerPositions.entries(), ([name, marker]) => ({
|
||||
marker: { ...marker, name },
|
||||
item: this.languageService.getSignatureHelpItems(marker.fileName, marker.position, /*options*/ undefined),
|
||||
@@ -2396,11 +2427,10 @@ export class TestState {
|
||||
return tooltip;
|
||||
},
|
||||
);
|
||||
Harness.Baseline.runBaseline(baselineFile, annotations + "\n\n" + stringify(result));
|
||||
this.baseline("SignatureHelp", annotations + "\n\n" + stringify(result));
|
||||
}
|
||||
|
||||
public baselineCompletions(preferences?: ts.UserPreferences) {
|
||||
const baselineFile = this.getBaselineFileNameForContainingTestFile();
|
||||
const result = ts.arrayFrom(this.testData.markerPositions.entries(), ([name, marker]) => {
|
||||
this.goToMarker(marker);
|
||||
const completions = this.getCompletionListAtCaret(preferences);
|
||||
@@ -2443,8 +2473,8 @@ export class TestState {
|
||||
}
|
||||
}
|
||||
}
|
||||
Harness.Baseline.runBaseline(
|
||||
baselineFile,
|
||||
this.baseline(
|
||||
"Completions",
|
||||
annotations + "\n\n" + stringify(result, (key, value) => {
|
||||
return key === "exportMapKey"
|
||||
? value.replace(/ \d+ /g, " * ")
|
||||
@@ -2503,7 +2533,6 @@ export class TestState {
|
||||
|
||||
public baselineSmartSelection() {
|
||||
const n = "\n";
|
||||
const baselineFile = this.getBaselineFileNameForContainingTestFile();
|
||||
const markers = this.getMarkers();
|
||||
const fileContent = this.activeFile.content;
|
||||
const text = markers.map(marker => {
|
||||
@@ -2531,7 +2560,7 @@ export class TestState {
|
||||
return baselineContent.join(fileContent.includes("\n") ? n + n : n);
|
||||
}).join(n.repeat(2) + "=".repeat(80) + n.repeat(2));
|
||||
|
||||
Harness.Baseline.runBaseline(baselineFile, text);
|
||||
this.baseline("Smart Selection", text);
|
||||
}
|
||||
|
||||
public printBreakpointLocation(pos: number) {
|
||||
@@ -2589,11 +2618,6 @@ export class TestState {
|
||||
Harness.IO.log(stringify(help.items[help.selectedItemIndex]));
|
||||
}
|
||||
|
||||
private getBaselineFileNameForInternalFourslashFile(ext = ".baseline") {
|
||||
return this.testData.globalOptions[MetadataOptionNames.baselineFile] ||
|
||||
ts.getBaseFileName(this.activeFile.fileName).replace(ts.Extension.Ts, ext);
|
||||
}
|
||||
|
||||
private getBaselineFileNameForContainingTestFile(ext = ".baseline") {
|
||||
return this.testData.globalOptions[MetadataOptionNames.baselineFile] ||
|
||||
ts.getBaseFileName(this.originalInputFileName).replace(ts.Extension.Ts, ext);
|
||||
@@ -3006,8 +3030,8 @@ export class TestState {
|
||||
}
|
||||
|
||||
public baselineCurrentFileNameOrDottedNameSpans() {
|
||||
Harness.Baseline.runBaseline(
|
||||
this.testData.globalOptions[MetadataOptionNames.baselineFile],
|
||||
this.baseline(
|
||||
"NameOrDottedNameSpans",
|
||||
this.baselineCurrentFileLocations(pos => this.getNameOrDottedNameSpan(pos)!),
|
||||
);
|
||||
}
|
||||
@@ -3323,12 +3347,16 @@ export class TestState {
|
||||
this.verifyRangeIs(expectedText, includeWhiteSpace);
|
||||
}
|
||||
|
||||
public verifyCodeFixAll({ fixId, fixAllDescription, newFileContent, commands: expectedCommands }: FourSlashInterface.VerifyCodeFixAllOptions): void {
|
||||
const fixWithId = ts.find(this.getCodeFixes(this.activeFile.fileName), a => a.fixId === fixId);
|
||||
public verifyCodeFixAll({ fixId, fixAllDescription, newFileContent, commands: expectedCommands, preferences }: FourSlashInterface.VerifyCodeFixAllOptions): void {
|
||||
if (this.testType === FourSlashTestType.Server && preferences) {
|
||||
this.configure(preferences);
|
||||
}
|
||||
|
||||
const fixWithId = ts.find(this.getCodeFixes(this.activeFile.fileName, /*errorCode*/ undefined, preferences), a => a.fixId === fixId);
|
||||
ts.Debug.assert(fixWithId !== undefined, "No available code fix has the expected id. Fix All is not available if there is only one potentially fixable diagnostic present.", () => `Expected '${fixId}'. Available actions:\n${ts.mapDefined(this.getCodeFixes(this.activeFile.fileName), a => `${a.fixName} (${a.fixId || "no fix id"})`).join("\n")}`);
|
||||
ts.Debug.assertEqual(fixWithId.fixAllDescription, fixAllDescription);
|
||||
|
||||
const { changes, commands } = this.languageService.getCombinedCodeFix({ type: "file", fileName: this.activeFile.fileName }, fixId, this.formatCodeSettings, ts.emptyOptions);
|
||||
const { changes, commands } = this.languageService.getCombinedCodeFix({ type: "file", fileName: this.activeFile.fileName }, fixId, this.formatCodeSettings, preferences || ts.emptyOptions);
|
||||
assert.deepEqual<readonly {}[] | undefined>(commands, expectedCommands);
|
||||
this.verifyNewContent({ newFileContent }, changes);
|
||||
}
|
||||
@@ -3583,7 +3611,6 @@ export class TestState {
|
||||
|
||||
public baselineAutoImports(markerName: string, fullNamesForCodeFix?: string[], preferences?: ts.UserPreferences) {
|
||||
const marker = this.getMarkerByName(markerName);
|
||||
const baselineFile = this.getBaselineFileNameForContainingTestFile(`.baseline.md`);
|
||||
const completionPreferences = {
|
||||
includeCompletionsForModuleExports: true,
|
||||
includeCompletionsWithInsertText: true,
|
||||
@@ -3651,7 +3678,7 @@ export class TestState {
|
||||
}
|
||||
}
|
||||
|
||||
Harness.Baseline.runBaseline(baselineFile, baselineText);
|
||||
this.baseline("Auto Imports", baselineText, `.baseline.md`);
|
||||
}
|
||||
|
||||
public verifyJsxClosingTag(map: { [markerName: string]: ts.JsxClosingTagInfo | undefined; }): void {
|
||||
@@ -3671,7 +3698,6 @@ export class TestState {
|
||||
}
|
||||
|
||||
public baselineLinkedEditing(): void {
|
||||
const baselineFile = this.getBaselineFileNameForContainingTestFile(".linkedEditing.txt");
|
||||
const files = this.testData.files;
|
||||
|
||||
let baselineContent = "";
|
||||
@@ -3682,7 +3708,7 @@ export class TestState {
|
||||
offset = result.offset;
|
||||
}
|
||||
|
||||
Harness.Baseline.runBaseline(baselineFile, baselineContent);
|
||||
this.baseline("Linked Editing", baselineContent, ".linkedEditing.txt");
|
||||
|
||||
function getLinkedEditingBaselineWorker(activeFile: FourSlashFile, offset: number, languageService: ts.LanguageService) {
|
||||
const fileName = activeFile.fileName;
|
||||
@@ -3858,19 +3884,30 @@ export class TestState {
|
||||
return this.languageService.getDocumentHighlights(this.activeFile.fileName, this.currentCaretPosition, filesToSearch);
|
||||
}
|
||||
|
||||
private baselineGetDocumentHighlights(markerOrRange: MarkerOrNameOrRange, options: FourSlashInterface.VerifyDocumentHighlightsOptions | undefined) {
|
||||
this.goToMarkerOrNameOrRange(markerOrRange);
|
||||
const highlights = this.getDocumentHighlightsAtCurrentPosition(ts.map(options?.filesToSearch, ts.normalizePath) || [this.activeFile.fileName]);
|
||||
public baselineDocumentHighlights(
|
||||
markerOrRange: ArrayOrSingle<MarkerOrNameOrRange> | undefined,
|
||||
rangeText: ArrayOrSingle<string> | undefined,
|
||||
options: FourSlashInterface.VerifyDocumentHighlightsOptions | undefined,
|
||||
) {
|
||||
this.baselineEachMarkerOrRangeArrayOrSingle(
|
||||
"documentHighlights",
|
||||
markerOrRange,
|
||||
rangeText,
|
||||
markerOrRange => {
|
||||
this.goToMarkerOrNameOrRange(markerOrRange);
|
||||
const highlights = this.getDocumentHighlightsAtCurrentPosition(ts.map(options?.filesToSearch, ts.normalizePath) || [this.activeFile.fileName]);
|
||||
|
||||
// Write input files
|
||||
const filesToSearch = options ? "// filesToSearch:\n" +
|
||||
options.filesToSearch.map(f => "// " + f).join("\n") + "\n\n" :
|
||||
"";
|
||||
const baselineContent = this.getBaselineForGroupedDocumentSpansWithFileContents(
|
||||
highlights?.map(h => h.highlightSpans.map(s => s.fileName ? s as ts.DocumentSpan : { ...s, fileName: h.fileName })) || ts.emptyArray,
|
||||
{ markerInfo: { markerOrRange, markerName: "/*HIGHLIGHTS*/" } },
|
||||
// Write input files
|
||||
const filesToSearch = options ? "// filesToSearch:\n" +
|
||||
options.filesToSearch.map(f => "// " + f).join("\n") + "\n\n" :
|
||||
"";
|
||||
const baselineContent = this.getBaselineForGroupedDocumentSpansWithFileContents(
|
||||
highlights?.map(h => h.highlightSpans.map(s => s.fileName ? s as ts.DocumentSpan : { ...s, fileName: h.fileName })) || ts.emptyArray,
|
||||
{ markerInfo: { markerOrRange, markerName: "/*HIGHLIGHTS*/" } },
|
||||
);
|
||||
return filesToSearch + baselineContent;
|
||||
},
|
||||
);
|
||||
return filesToSearch + baselineContent;
|
||||
}
|
||||
|
||||
public verifyCodeFixAvailable(negative: boolean, expected: FourSlashInterface.VerifyCodeFixAvailableOptions[] | string | undefined): void {
|
||||
@@ -4275,10 +4312,9 @@ export class TestState {
|
||||
}
|
||||
|
||||
public baselineCallHierarchy() {
|
||||
const baselineFile = this.getBaselineFileNameForContainingTestFile(".callHierarchy.txt");
|
||||
const callHierarchyItem = this.languageService.prepareCallHierarchy(this.activeFile.fileName, this.currentCaretPosition);
|
||||
const text = callHierarchyItem ? ts.mapOneOrMany(callHierarchyItem, item => this.formatCallHierarchy(item), result => result.join("")) : "none";
|
||||
Harness.Baseline.runBaseline(baselineFile, text);
|
||||
this.baseline("Call Hierarchy", text, ".callHierarchy.txt");
|
||||
}
|
||||
|
||||
private getLineContent(index: number) {
|
||||
@@ -4503,12 +4539,16 @@ function renameKeys<T>(obj: { readonly [key: string]: T; }, renameKey: (key: str
|
||||
return res;
|
||||
}
|
||||
|
||||
export function runFourSlashTest(basePath: string, testType: FourSlashTestType, fileName: string) {
|
||||
const content = Harness.IO.readFile(fileName)!;
|
||||
runFourSlashTestContent(basePath, testType, content, fileName);
|
||||
export interface FourSlashServerLogBaseliner {
|
||||
baseline?: () => void;
|
||||
}
|
||||
|
||||
export function runFourSlashTestContent(basePath: string, testType: FourSlashTestType, content: string, fileName: string): void {
|
||||
export function runFourSlashTest(basePath: string, testType: FourSlashTestType, fileName: string, serverLogBaseliner?: FourSlashServerLogBaseliner) {
|
||||
const content = Harness.IO.readFile(fileName)!;
|
||||
runFourSlashTestContent(basePath, testType, content, fileName, serverLogBaseliner);
|
||||
}
|
||||
|
||||
export function runFourSlashTestContent(basePath: string, testType: FourSlashTestType, content: string, fileName: string, serverLogBaseliner?: FourSlashServerLogBaseliner): void {
|
||||
// Give file paths an absolute path for the virtual file system
|
||||
const absoluteBasePath = ts.combinePaths(Harness.virtualFileSystemRoot, basePath);
|
||||
const absoluteFileName = ts.combinePaths(Harness.virtualFileSystemRoot, fileName);
|
||||
@@ -4516,12 +4556,14 @@ export function runFourSlashTestContent(basePath: string, testType: FourSlashTes
|
||||
// Parse out the files and their metadata
|
||||
const testData = parseTestData(absoluteBasePath, content, absoluteFileName);
|
||||
const state = new TestState(absoluteFileName, absoluteBasePath, testType, testData);
|
||||
if (serverLogBaseliner) serverLogBaseliner.baseline = () => state.baselineTsserverLog();
|
||||
const actualFileName = Harness.IO.resolvePath(fileName) || absoluteFileName;
|
||||
const output = ts.transpileModule(content, { reportDiagnostics: true, fileName: actualFileName, compilerOptions: { target: ts.ScriptTarget.ES2015, inlineSourceMap: true, inlineSources: true } });
|
||||
if (output.diagnostics!.length > 0) {
|
||||
throw new Error(`Syntax error in ${absoluteBasePath}: ${output.diagnostics![0].messageText}`);
|
||||
}
|
||||
runCode(output.outputText, state, actualFileName);
|
||||
state.baselineTest();
|
||||
}
|
||||
|
||||
function runCode(code: string, state: TestState, fileName: string): void {
|
||||
|
||||
@@ -333,68 +333,64 @@ export class Verify extends VerifyNegatable {
|
||||
this.state.verifyTypeAtLocation(range, expected);
|
||||
}
|
||||
|
||||
public baselineCommands(...commands: BaselineCommand[]) {
|
||||
this.state.verifyBaselineCommands(...commands);
|
||||
}
|
||||
|
||||
public baselineFindAllReferences(...markerOrRange: FourSlash.MarkerOrNameOrRange[]) {
|
||||
this.state.verifyBaselineCommands({ type: "findAllReferences", markerOrRange });
|
||||
this.state.baselineFindAllReferences(markerOrRange, /*rangeText*/ undefined);
|
||||
}
|
||||
|
||||
public baselineFindAllReferencesAtRangesWithText(...rangeText: string[]) {
|
||||
this.state.verifyBaselineCommands({ type: "findAllReferences", rangeText });
|
||||
this.state.baselineFindAllReferences(/*markerOrRange*/ undefined, rangeText);
|
||||
}
|
||||
|
||||
public baselineGetFileReferences(...fileName: string[]) {
|
||||
this.state.verifyBaselineCommands({ type: "getFileReferences", fileName });
|
||||
this.state.baselineGetFileReferences(fileName);
|
||||
}
|
||||
|
||||
public baselineGoToDefinition(...markerOrRange: FourSlash.MarkerOrNameOrRange[]) {
|
||||
this.state.verifyBaselineCommands({ type: "goToDefinition", markerOrRange });
|
||||
this.state.baselineGoToDefinition(markerOrRange, /*rangeText*/ undefined);
|
||||
}
|
||||
|
||||
public baselineGoToDefinitionAtRangesWithText(...rangeText: string[]) {
|
||||
this.state.verifyBaselineCommands({ type: "goToDefinition", rangeText });
|
||||
this.state.baselineGoToDefinition(/*markerOrRange*/ undefined, rangeText);
|
||||
}
|
||||
|
||||
public baselineGetDefinitionAtPosition(...markerOrRange: FourSlash.MarkerOrNameOrRange[]) {
|
||||
this.state.verifyBaselineCommands({ type: "getDefinitionAtPosition", markerOrRange });
|
||||
this.state.baselineGetDefinitionAtPosition(markerOrRange, /*rangeText*/ undefined);
|
||||
}
|
||||
|
||||
public baselineGetDefinitionAtRangesWithText(...rangeText: string[]) {
|
||||
this.state.verifyBaselineCommands({ type: "getDefinitionAtPosition", rangeText });
|
||||
this.state.baselineGetDefinitionAtPosition(/*markerOrRange*/ undefined, rangeText);
|
||||
}
|
||||
|
||||
public baselineGoToSourceDefinition(...markerOrRange: FourSlash.MarkerOrNameOrRange[]) {
|
||||
this.state.verifyBaselineCommands({ type: "goToSourceDefinition", markerOrRange });
|
||||
this.state.baselineGoToSourceDefinition(markerOrRange, /*rangeText*/ undefined);
|
||||
}
|
||||
|
||||
public baselineGoToSourceDefinitionAtRangesWithText(...rangeText: string[]) {
|
||||
this.state.verifyBaselineCommands({ type: "goToSourceDefinition", rangeText });
|
||||
this.state.baselineGoToSourceDefinition(/*markerOrRange*/ undefined, rangeText);
|
||||
}
|
||||
|
||||
public baselineGoToType(...markerOrRange: FourSlash.MarkerOrNameOrRange[]) {
|
||||
this.state.verifyBaselineCommands({ type: "goToType", markerOrRange });
|
||||
this.state.baselineGoToType(markerOrRange, /*rangeText*/ undefined);
|
||||
}
|
||||
|
||||
public baselineGoToTypeAtRangesWithText(...rangeText: string[]) {
|
||||
this.state.verifyBaselineCommands({ type: "goToType", rangeText });
|
||||
this.state.baselineGoToType(/*markerOrRange*/ undefined, rangeText);
|
||||
}
|
||||
|
||||
public baselineGoToImplementation(...markerOrRange: FourSlash.MarkerOrNameOrRange[]) {
|
||||
this.state.verifyBaselineCommands({ type: "goToImplementation", markerOrRange });
|
||||
this.state.baselineGoToImplementation(markerOrRange, /*rangeText*/ undefined);
|
||||
}
|
||||
|
||||
public baselineGoToImplementationAtRangesWithText(...rangeText: string[]) {
|
||||
this.state.verifyBaselineCommands({ type: "goToImplementation", rangeText });
|
||||
this.state.baselineGoToImplementation(/*markerOrRange*/ undefined, rangeText);
|
||||
}
|
||||
|
||||
public baselineDocumentHighlights(markerOrRange?: ArrayOrSingle<FourSlash.MarkerOrNameOrRange>, options?: VerifyDocumentHighlightsOptions) {
|
||||
this.state.verifyBaselineCommands({ type: "documentHighlights", markerOrRange, options });
|
||||
this.state.baselineDocumentHighlights(markerOrRange, /*rangeText*/ undefined, options);
|
||||
}
|
||||
|
||||
public baselineDocumentHighlightsAtRangesWithText(rangeText?: ArrayOrSingle<string>, options?: VerifyDocumentHighlightsOptions) {
|
||||
this.state.verifyBaselineCommands({ type: "documentHighlights", rangeText, options });
|
||||
this.state.baselineDocumentHighlights(/*markerOrRange*/ undefined, rangeText, options);
|
||||
}
|
||||
|
||||
public noErrors() {
|
||||
@@ -574,11 +570,11 @@ export class Verify extends VerifyNegatable {
|
||||
}
|
||||
|
||||
public baselineRename(markerOrRange?: ArrayOrSingle<FourSlash.MarkerOrNameOrRange>, options?: RenameOptions) {
|
||||
this.state.verifyBaselineCommands({ type: "findRenameLocations", markerOrRange, options });
|
||||
this.state.baselineRename(markerOrRange, /*rangeText*/ undefined, options);
|
||||
}
|
||||
|
||||
public baselineRenameAtRangesWithText(rangeText?: ArrayOrSingle<string>, options?: RenameOptions) {
|
||||
this.state.verifyBaselineCommands({ type: "findRenameLocations", rangeText, options });
|
||||
this.state.baselineRename(/*markerOrRange*/ undefined, rangeText, options);
|
||||
}
|
||||
|
||||
public verifyQuickInfoDisplayParts(kind: string, kindModifiers: string, textSpan: FourSlash.TextSpan, displayParts: ts.SymbolDisplayPart[], documentation: ts.SymbolDisplayPart[], tags: ts.JSDocTagInfo[]) {
|
||||
@@ -1873,6 +1869,7 @@ export interface VerifyCodeFixAllOptions {
|
||||
fixAllDescription: string;
|
||||
newFileContent: NewFileContent;
|
||||
commands: readonly {}[];
|
||||
preferences?: ts.UserPreferences;
|
||||
}
|
||||
|
||||
export interface VerifyRefactorOptions {
|
||||
@@ -1931,25 +1928,3 @@ export interface RenameOptions {
|
||||
readonly providePrefixAndSuffixTextForRename?: boolean;
|
||||
readonly quotePreference?: "auto" | "double" | "single";
|
||||
}
|
||||
export type BaselineCommandWithMarkerOrRange = {
|
||||
type: "findAllReferences" | "goToDefinition" | "getDefinitionAtPosition" | "goToSourceDefinition" | "goToType" | "goToImplementation";
|
||||
markerOrRange?: ArrayOrSingle<FourSlash.MarkerOrNameOrRange>;
|
||||
rangeText?: ArrayOrSingle<string>;
|
||||
} | {
|
||||
type: "findRenameLocations";
|
||||
markerOrRange?: ArrayOrSingle<FourSlash.MarkerOrNameOrRange>;
|
||||
rangeText?: ArrayOrSingle<string>;
|
||||
options?: RenameOptions;
|
||||
} | {
|
||||
type: "documentHighlights";
|
||||
markerOrRange?: ArrayOrSingle<FourSlash.MarkerOrNameOrRange>;
|
||||
rangeText?: ArrayOrSingle<string>;
|
||||
options?: VerifyDocumentHighlightsOptions;
|
||||
};
|
||||
export type BaselineCommand = BaselineCommandWithMarkerOrRange | {
|
||||
type: "getFileReferences";
|
||||
fileName: ArrayOrSingle<string>;
|
||||
} | {
|
||||
type: "customWork";
|
||||
work: () => string | undefined;
|
||||
};
|
||||
|
||||
@@ -262,7 +262,7 @@ export namespace Compiler {
|
||||
export const es2015DefaultLibFileName = "lib.es2015.d.ts";
|
||||
|
||||
// Cache of lib files from "built/local"
|
||||
let libFileNameSourceFileMap: Map<string, ts.SourceFile> | undefined;
|
||||
export let libFileNameSourceFileMap: Map<string, ts.SourceFile> | undefined;
|
||||
|
||||
export function getDefaultLibrarySourceFile(fileName = defaultLibFileName): ts.SourceFile | undefined {
|
||||
if (!isDefaultLibraryFile(fileName)) {
|
||||
|
||||
@@ -14,6 +14,14 @@ import * as vpath from "./_namespaces/vpath";
|
||||
import {
|
||||
incrementalVerifier,
|
||||
} from "./incrementalUtils";
|
||||
import {
|
||||
createLoggerWithInMemoryLogs,
|
||||
HarnessLSCouldNotResolveModule,
|
||||
Logger,
|
||||
} from "./tsserverLogger";
|
||||
import {
|
||||
createWatchUtils,
|
||||
} from "./watchUtils";
|
||||
|
||||
export function makeDefaultProxy(info: ts.server.PluginCreateInfo): ts.LanguageService {
|
||||
const proxy = Object.create(/*o*/ null); // eslint-disable-line no-null/no-null
|
||||
@@ -122,6 +130,7 @@ export interface LanguageServiceAdapter {
|
||||
getLanguageService(): ts.LanguageService;
|
||||
getClassifier(): ts.Classifier;
|
||||
getPreProcessedFileInfo(fileName: string, fileContents: string): ts.PreProcessedFileInfo;
|
||||
getLogger(): Logger | undefined;
|
||||
}
|
||||
|
||||
export abstract class LanguageServiceAdapterHost {
|
||||
@@ -326,6 +335,7 @@ class NativeLanguageServiceHost extends LanguageServiceAdapterHost implements ts
|
||||
|
||||
export class NativeLanguageServiceAdapter implements LanguageServiceAdapter {
|
||||
private host: NativeLanguageServiceHost;
|
||||
getLogger = ts.returnUndefined;
|
||||
constructor(cancellationToken?: ts.HostCancellationToken, options?: ts.CompilerOptions) {
|
||||
this.host = new NativeLanguageServiceHost(cancellationToken, options);
|
||||
}
|
||||
@@ -370,10 +380,18 @@ class SessionClientHost extends NativeLanguageServiceHost implements ts.server.S
|
||||
}
|
||||
}
|
||||
|
||||
class SessionServerHost implements ts.server.ServerHost, ts.server.Logger {
|
||||
interface ServerHostFileWatcher {
|
||||
cb: ts.FileWatcherCallback;
|
||||
pollingInterval: ts.PollingInterval;
|
||||
}
|
||||
interface ServerHostDirectoryWatcher {
|
||||
cb: ts.DirectoryWatcherCallback;
|
||||
}
|
||||
class SessionServerHost implements ts.server.ServerHost {
|
||||
args: string[] = [];
|
||||
newLine: string;
|
||||
useCaseSensitiveFileNames = false;
|
||||
watchUtils = createWatchUtils<ServerHostFileWatcher, ServerHostDirectoryWatcher>("watchedFiles", "watchedDirectories");
|
||||
|
||||
constructor(private host: NativeLanguageServiceHost) {
|
||||
this.newLine = this.host.getNewLine();
|
||||
@@ -440,61 +458,28 @@ class SessionServerHost implements ts.server.ServerHost, ts.server.Logger {
|
||||
return this.host.readDirectory(path, extensions, exclude, include, depth);
|
||||
}
|
||||
|
||||
watchFile(): ts.FileWatcher {
|
||||
return { close: ts.noop };
|
||||
watchFile(file: string, cb: ts.FileWatcherCallback, pollingInterval: ts.PollingInterval) {
|
||||
return this.watchUtils.pollingWatch(file, { cb, pollingInterval });
|
||||
}
|
||||
|
||||
watchDirectory(): ts.FileWatcher {
|
||||
return { close: ts.noop };
|
||||
watchDirectory(dir: string, cb: ts.DirectoryWatcherCallback, recursive: boolean): ts.FileWatcher {
|
||||
return this.watchUtils.fsWatch(dir, recursive, { cb });
|
||||
}
|
||||
|
||||
close = ts.noop;
|
||||
|
||||
info(message: string): void {
|
||||
this.host.log(message);
|
||||
setTimeout(_callback: (...args: any[]) => void, _ms: number, ..._args: any[]): any {
|
||||
// Currently none of the tests use this and we would want something thats deterministic like unittests where we tell which callbacks to invoke
|
||||
}
|
||||
|
||||
msg(message: string): void {
|
||||
this.host.log(message);
|
||||
clearTimeout(_timeoutId: any): void {
|
||||
// Currently none of the tests use this and we would want something thats deterministic like unittests where we tell which callbacks to invoke
|
||||
}
|
||||
|
||||
loggingEnabled() {
|
||||
return true;
|
||||
setImmediate(_callback: (...args: any[]) => void, ..._args: any[]): any {
|
||||
// Currently none of the tests use this and we would want something thats deterministic like unittests where we tell which callbacks to invoke
|
||||
}
|
||||
|
||||
getLogFileName(): string | undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
hasLevel() {
|
||||
return false;
|
||||
}
|
||||
|
||||
startGroup() {
|
||||
throw ts.notImplemented();
|
||||
}
|
||||
endGroup() {
|
||||
throw ts.notImplemented();
|
||||
}
|
||||
|
||||
perftrc(message: string): void {
|
||||
return this.host.log(message);
|
||||
}
|
||||
|
||||
setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): any {
|
||||
return setTimeout(callback, ms, ...args);
|
||||
}
|
||||
|
||||
clearTimeout(timeoutId: any): void {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
|
||||
setImmediate(callback: (...args: any[]) => void, _ms: number, ...args: any[]): any {
|
||||
return setImmediate(callback, args);
|
||||
}
|
||||
|
||||
clearImmediate(timeoutId: any): void {
|
||||
clearImmediate(timeoutId);
|
||||
clearImmediate(_timeoutId: any): void {
|
||||
// Currently none of the tests use this and we would want something thats deterministic like unittests where we tell which callbacks to invoke
|
||||
}
|
||||
|
||||
createHash(s: string) {
|
||||
@@ -597,22 +582,36 @@ class SessionServerHost implements ts.server.ServerHost, ts.server.Logger {
|
||||
default:
|
||||
return {
|
||||
module: undefined,
|
||||
error: new Error("Could not resolve module"),
|
||||
error: new Error(HarnessLSCouldNotResolveModule),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class FourslashSession extends ts.server.Session {
|
||||
constructor(opts: ts.server.SessionOptions, readonly baselineHost: (when: string) => void) {
|
||||
super(opts);
|
||||
}
|
||||
getText(fileName: string) {
|
||||
return ts.getSnapshotText(this.projectService.getDefaultProjectForFile(ts.server.toNormalizedPath(fileName), /*ensureProject*/ true)!.getScriptSnapshot(fileName)!);
|
||||
}
|
||||
|
||||
protected override toStringMessage(message: string): string {
|
||||
return JSON.stringify(JSON.parse(message), undefined, 2);
|
||||
}
|
||||
|
||||
public override onMessage(message: string): void {
|
||||
this.baselineHost("Before Request");
|
||||
super.onMessage(message);
|
||||
this.baselineHost("After Request");
|
||||
}
|
||||
}
|
||||
|
||||
export class ServerLanguageServiceAdapter implements LanguageServiceAdapter {
|
||||
private host: SessionClientHost;
|
||||
private client: ts.server.SessionClient;
|
||||
private server: FourslashSession;
|
||||
private logger: Logger;
|
||||
constructor(cancellationToken?: ts.HostCancellationToken, options?: ts.CompilerOptions) {
|
||||
// This is the main host that tests use to direct tests
|
||||
const clientHost = new SessionClientHost(cancellationToken, options);
|
||||
@@ -621,6 +620,7 @@ export class ServerLanguageServiceAdapter implements LanguageServiceAdapter {
|
||||
// This host is just a proxy for the clientHost, it uses the client
|
||||
// host to answer server queries about files on disk
|
||||
const serverHost = new SessionServerHost(clientHost);
|
||||
this.logger = createLoggerWithInMemoryLogs(serverHost, /*sanitizeLibs*/ true);
|
||||
const opts: ts.server.SessionOptions = {
|
||||
host: serverHost,
|
||||
cancellationToken: ts.server.nullCancellationToken,
|
||||
@@ -629,11 +629,17 @@ export class ServerLanguageServiceAdapter implements LanguageServiceAdapter {
|
||||
typingsInstaller: { ...ts.server.nullTypingsInstaller, globalTypingsCacheLocation: "/Library/Caches/typescript" },
|
||||
byteLength: Buffer.byteLength,
|
||||
hrtime: process.hrtime,
|
||||
logger: serverHost,
|
||||
logger: this.logger,
|
||||
canUseEvents: true,
|
||||
incrementalVerifier,
|
||||
};
|
||||
this.server = new FourslashSession(opts);
|
||||
this.server = new FourslashSession(opts, when => {
|
||||
const baseline = serverHost.watchUtils.serializeWatches();
|
||||
if (baseline.length) {
|
||||
this.logger.log(when);
|
||||
baseline.forEach(s => this.logger.log(s));
|
||||
}
|
||||
});
|
||||
|
||||
// Fake the connection between the client and the server
|
||||
serverHost.writeMessage = client.onMessage.bind(client);
|
||||
@@ -647,6 +653,9 @@ export class ServerLanguageServiceAdapter implements LanguageServiceAdapter {
|
||||
this.client = client;
|
||||
this.host = clientHost;
|
||||
}
|
||||
getLogger() {
|
||||
return this.logger;
|
||||
}
|
||||
getHost() {
|
||||
return this.host;
|
||||
}
|
||||
|
||||
+100
-30
@@ -264,7 +264,7 @@ export function verifyResolutionCache(
|
||||
resolutionToExpected.get(resolution)!.refCount === resolution.refCount,
|
||||
`${projectName}:: Expected Resolution ref count ${resolutionToExpected.get(resolution)!.refCount} but got ${resolution.refCount}`,
|
||||
);
|
||||
verifySet(resolutionToExpected.get(resolution)!.files, resolution.files, `Resolution files`);
|
||||
verifySet(resolutionToExpected.get(resolution)!.files, resolution.files, `${projectName}:: Resolution files`);
|
||||
});
|
||||
verifyMapOfResolutionSet(expected.resolvedFileToResolution, actual.resolvedFileToResolution, `resolvedFileToResolution`);
|
||||
verifyResolutionSet(expected.resolutionsWithFailedLookups, actual.resolutionsWithFailedLookups, `resolutionsWithFailedLookups`);
|
||||
@@ -273,6 +273,7 @@ export function verifyResolutionCache(
|
||||
verifyFileWatchesOfAffectingLocations(expected.fileWatchesOfAffectingLocations, actual.fileWatchesOfAffectingLocations);
|
||||
|
||||
// Stop watching resolutions to verify everything gets closed.
|
||||
expected.startCachingPerDirectoryResolution();
|
||||
actual.resolvedModuleNames.forEach((_resolutions, path) => expected.removeResolutionsOfFile(path));
|
||||
actual.resolvedTypeReferenceDirectives.forEach((_resolutions, path) => expected.removeResolutionsOfFile(path));
|
||||
expected.finishCachingPerDirectoryResolution(/*newProgram*/ undefined, actualProgram);
|
||||
@@ -339,35 +340,6 @@ export function verifyResolutionCache(
|
||||
return expectedResolution;
|
||||
}
|
||||
|
||||
function verifyMap<Expected, Actual>(
|
||||
expected: Map<string, Expected> | undefined,
|
||||
actual: Map<string, Actual> | undefined,
|
||||
verifyValue: (expected: Expected | undefined, actual: Actual | undefined, key: string) => void,
|
||||
caption: string,
|
||||
) {
|
||||
expected?.forEach((expected, path) => verifyValue(expected, actual?.get(path), `${caption}:: ${path}`));
|
||||
actual?.forEach((actual, path) => verifyValue(expected?.get(path), actual, `${caption}:: ${path}`));
|
||||
}
|
||||
|
||||
function verifySet(
|
||||
expected: Set<string> | undefined,
|
||||
actual: Set<string> | undefined,
|
||||
caption: string,
|
||||
) {
|
||||
expected?.forEach(expected =>
|
||||
ts.Debug.assert(
|
||||
actual?.has(expected),
|
||||
`${projectName}:: ${caption}:: Expected should be present in actual`,
|
||||
)
|
||||
);
|
||||
actual?.forEach(actual =>
|
||||
ts.Debug.assert(
|
||||
expected?.has(actual),
|
||||
`${projectName}:: ${caption}:: Actual should be present in expected`,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function verifyMapOfResolutionSet(
|
||||
expected: Map<ts.Path, Set<ts.ResolutionWithFailedLookupLocations>> | undefined,
|
||||
actual: Map<ts.Path, Set<ts.ResolutionWithFailedLookupLocations>> | undefined,
|
||||
@@ -421,6 +393,35 @@ export function verifyResolutionCache(
|
||||
}
|
||||
}
|
||||
|
||||
function verifyMap<Key extends string, Expected, Actual>(
|
||||
expected: Map<Key, Expected> | undefined,
|
||||
actual: Map<Key, Actual> | undefined,
|
||||
verifyValue: (expected: Expected | undefined, actual: Actual | undefined, key: string) => void,
|
||||
caption: string,
|
||||
) {
|
||||
expected?.forEach((expected, path) => verifyValue(expected, actual?.get(path), `${caption}:: ${path}`));
|
||||
actual?.forEach((actual, path) => verifyValue(expected?.get(path), actual, `${caption}:: ${path}`));
|
||||
}
|
||||
|
||||
function verifySet(
|
||||
expected: Set<string> | undefined,
|
||||
actual: Set<string> | undefined,
|
||||
caption: string,
|
||||
) {
|
||||
expected?.forEach(expected =>
|
||||
ts.Debug.assert(
|
||||
actual?.has(expected),
|
||||
`${caption}:: Expected should be present in actual`,
|
||||
)
|
||||
);
|
||||
actual?.forEach(actual =>
|
||||
ts.Debug.assert(
|
||||
expected?.has(actual),
|
||||
`${caption}:: Actual should be present in expected`,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function verifyProgram(service: ts.server.ProjectService, project: ts.server.Project) {
|
||||
if (service.serverMode === ts.LanguageServiceMode.Syntactic) return;
|
||||
const options = project.getCompilerOptions();
|
||||
@@ -510,6 +511,74 @@ function verifyProgram(service: ts.server.ProjectService, project: ts.server.Pro
|
||||
verifyResolutionCache(project.resolutionCache, project.getCurrentProgram()!, resolutionHostCacheHost, project.projectName);
|
||||
}
|
||||
|
||||
interface ResolveSingleModuleNameWithoutWatchingData {
|
||||
resolutionToData: Map<ts.ResolutionWithFailedLookupLocations, Pick<ts.ResolvedModuleWithFailedLookupLocations, "failedLookupLocations" | "affectingLocations" | "resolutionDiagnostics">>;
|
||||
packageJsonMap: Map<ts.Path, ts.PackageJsonInfo | boolean> | undefined;
|
||||
}
|
||||
|
||||
function beforeResolveSingleModuleNameWithoutWatching(
|
||||
moduleResolutionCache: ts.ModuleResolutionCache,
|
||||
): ResolveSingleModuleNameWithoutWatchingData {
|
||||
const resolutionToData: ResolveSingleModuleNameWithoutWatchingData["resolutionToData"] = new Map();
|
||||
// Currently it doesnt matter if moduleResolutionCache itself changes or not so just verify resolutions:
|
||||
moduleResolutionCache.directoryToModuleNameMap.getOwnMap().forEach(cache => {
|
||||
cache.forEach(resolution => {
|
||||
if (resolutionToData.has(resolution)) return;
|
||||
resolutionToData.set(resolution, {
|
||||
failedLookupLocations: resolution.failedLookupLocations?.slice(),
|
||||
affectingLocations: resolution.affectingLocations?.slice(),
|
||||
resolutionDiagnostics: resolution.resolutionDiagnostics?.slice(),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// We also care about package json info cache
|
||||
const packageJsonMap = moduleResolutionCache.getPackageJsonInfoCache().getInternalMap();
|
||||
return {
|
||||
resolutionToData,
|
||||
packageJsonMap: packageJsonMap && new Map(packageJsonMap),
|
||||
};
|
||||
}
|
||||
|
||||
function afterResolveSingleModuleNameWithoutWatching(
|
||||
moduleResolutionCache: ts.ModuleResolutionCache,
|
||||
moduleName: string,
|
||||
containingFile: string,
|
||||
result: ts.ResolvedModuleWithFailedLookupLocations,
|
||||
data: ResolveSingleModuleNameWithoutWatchingData,
|
||||
) {
|
||||
const existing = data.resolutionToData.get(result);
|
||||
if (existing) {
|
||||
verifyArrayLength(existing.failedLookupLocations, result.failedLookupLocations, "failedLookupLocations");
|
||||
verifyArrayLength(existing.affectingLocations, result.affectingLocations, "affectingLocations");
|
||||
verifyArrayLength(existing.resolutionDiagnostics, result.resolutionDiagnostics, "resolutionDiagnostics");
|
||||
}
|
||||
|
||||
verifyMap(
|
||||
data.packageJsonMap,
|
||||
moduleResolutionCache.getPackageJsonInfoCache().getInternalMap(),
|
||||
(expected, actual, caption) => ts.Debug.assert(expected === actual, caption),
|
||||
`Expected packageJsonInfo to not change: ${moduleName} ${containingFile}`,
|
||||
);
|
||||
|
||||
function verifyArrayLength<T>(expected: T[] | undefined, actual: T[] | undefined, caption: string) {
|
||||
ts.Debug.assert(
|
||||
expected?.length === actual?.length,
|
||||
`Expected ${caption} to not change: ${moduleName} ${containingFile}`,
|
||||
() =>
|
||||
`Expected: ${JSON.stringify(expected, undefined, " ")}` +
|
||||
`Actual: ${JSON.stringify(actual, undefined, " ")}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function onProjectCreation(project: ts.server.Project) {
|
||||
if (project.projectKind !== ts.server.ProjectKind.Auxiliary) return;
|
||||
|
||||
(project as ts.ResolutionCacheHost).beforeResolveSingleModuleNameWithoutWatching = beforeResolveSingleModuleNameWithoutWatching;
|
||||
(project as ts.ResolutionCacheHost).afterResolveSingleModuleNameWithoutWatching = afterResolveSingleModuleNameWithoutWatching;
|
||||
}
|
||||
|
||||
export interface IncrementalVerifierCallbacks {
|
||||
beforeVerification?(): any;
|
||||
afterVerification?(dataFromBefore: any): void;
|
||||
@@ -518,6 +587,7 @@ export interface IncrementalVerifierCallbacks {
|
||||
export function incrementalVerifier(service: ts.server.ProjectService) {
|
||||
service.verifyDocumentRegistry = withIncrementalVerifierCallbacks(service, verifyDocumentRegistry);
|
||||
service.verifyProgram = withIncrementalVerifierCallbacks(service, verifyProgram);
|
||||
service.onProjectCreation = onProjectCreation;
|
||||
}
|
||||
|
||||
function withIncrementalVerifierCallbacks(
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
import * as ts from "./_namespaces/ts";
|
||||
import {
|
||||
Compiler,
|
||||
} from "./harnessIO";
|
||||
|
||||
export const HarnessLSCouldNotResolveModule = "HarnessLanguageService:: Could not resolve module";
|
||||
|
||||
export function replaceAll(source: string, searchValue: string, replaceValue: string): string {
|
||||
let result: string | undefined = (source as string & { replaceAll: typeof source.replace; }).replaceAll?.(searchValue, replaceValue);
|
||||
|
||||
if (result !== undefined) {
|
||||
return result;
|
||||
}
|
||||
|
||||
result = "";
|
||||
const searchLength = searchValue.length;
|
||||
while (true) {
|
||||
const index = source.indexOf(searchValue);
|
||||
if (index < 0) {
|
||||
break;
|
||||
}
|
||||
result += source.slice(0, index);
|
||||
result += replaceValue;
|
||||
source = source.slice(index + searchLength);
|
||||
}
|
||||
result += source;
|
||||
return result;
|
||||
}
|
||||
|
||||
export interface Logger extends ts.server.Logger {
|
||||
logs?: string[];
|
||||
log(s: string): void;
|
||||
host?: ts.server.ServerHost;
|
||||
}
|
||||
|
||||
export function nullLogger(): Logger {
|
||||
return {
|
||||
close: ts.noop,
|
||||
hasLevel: ts.returnFalse,
|
||||
loggingEnabled: ts.returnFalse,
|
||||
perftrc: ts.noop,
|
||||
info: ts.noop,
|
||||
msg: ts.noop,
|
||||
startGroup: ts.noop,
|
||||
endGroup: ts.noop,
|
||||
getLogFileName: ts.returnUndefined,
|
||||
log: ts.noop,
|
||||
isTestLogger: true,
|
||||
};
|
||||
}
|
||||
|
||||
export function createHasErrorMessageLogger(): Logger {
|
||||
return {
|
||||
...nullLogger(),
|
||||
msg: (s, type) => ts.Debug.fail(`Error: ${s}, type: ${type}`),
|
||||
};
|
||||
}
|
||||
function handleLoggerGroup(logger: Logger): Logger {
|
||||
let inGroup = false;
|
||||
let firstInGroup = false;
|
||||
logger.startGroup = () => {
|
||||
inGroup = true;
|
||||
firstInGroup = true;
|
||||
};
|
||||
logger.endGroup = () => inGroup = false;
|
||||
const originalInfo = logger.info;
|
||||
logger.info = s => msg(s, ts.server.Msg.Info, s => originalInfo.call(logger, s));
|
||||
logger.log = s => originalInfo.call(logger, s);
|
||||
return logger;
|
||||
|
||||
function msg(s: string, type = ts.server.Msg.Err, write: (s: string) => void) {
|
||||
s = `[${nowString(logger)}] ${s}`;
|
||||
if (!inGroup || firstInGroup) s = padStringRight(type + " seq", " ") + s;
|
||||
if (ts.Debug.isDebugging) console.log(s);
|
||||
write(s);
|
||||
}
|
||||
|
||||
function padStringRight(str: string, padding: string) {
|
||||
return (str + padding).slice(0, padding.length);
|
||||
}
|
||||
}
|
||||
|
||||
export function nowString(logger: Logger) {
|
||||
// E.g. "12:34:56.789"
|
||||
logger.host?.now?.(); // To increment the time but not print it to avoid the baseline updates
|
||||
return `hh:mm:ss:mss`;
|
||||
}
|
||||
|
||||
export function createLoggerWritingToConsole(host: ts.server.ServerHost) {
|
||||
return handleLoggerGroup({
|
||||
...nullLogger(),
|
||||
hasLevel: ts.returnTrue,
|
||||
loggingEnabled: ts.returnTrue,
|
||||
perftrc: s => console.log(s),
|
||||
info: s => console.log(s),
|
||||
msg: (s, type) => console.log(`${type}:: ${s}`),
|
||||
host,
|
||||
});
|
||||
}
|
||||
|
||||
export function sanitizeLog(s: string): string {
|
||||
s = s.replace(/Elapsed::?\s*\d+(?:\.\d+)?ms/g, "Elapsed:: *ms");
|
||||
s = s.replace(/"updateGraphDurationMs":\s*\d+(?:\.\d+)?/g, `"updateGraphDurationMs": *`);
|
||||
s = s.replace(/"createAutoImportProviderProgramDurationMs":\s*\d+(?:\.\d+)?/g, `"createAutoImportProviderProgramDurationMs": *`);
|
||||
s = replaceAll(s, ts.version, "FakeVersion");
|
||||
s = s.replace(/getCompletionData: Get current token: \d+(?:\.\d+)?/g, `getCompletionData: Get current token: *`);
|
||||
s = s.replace(/getCompletionData: Is inside comment: \d+(?:\.\d+)?/g, `getCompletionData: Is inside comment: *`);
|
||||
s = s.replace(/getCompletionData: Get previous token: \d+(?:\.\d+)?/g, `getCompletionData: Get previous token: *`);
|
||||
s = s.replace(/getCompletionsAtPosition: isCompletionListBlocker: \d+(?:\.\d+)?/g, `getCompletionsAtPosition: isCompletionListBlocker: *`);
|
||||
s = s.replace(/getCompletionData: Semantic work: \d+(?:\.\d+)?/g, `getCompletionData: Semantic work: *`);
|
||||
s = s.replace(/getCompletionsAtPosition: getCompletionEntriesFromSymbols: \d+(?:\.\d+)?/g, `getCompletionsAtPosition: getCompletionEntriesFromSymbols: *`);
|
||||
s = s.replace(/forEachExternalModuleToImportFrom autoImportProvider: \d+(?:\.\d+)?/g, `forEachExternalModuleToImportFrom autoImportProvider: *`);
|
||||
s = s.replace(/getExportInfoMap: done in \d+(?:\.\d+)?/g, `getExportInfoMap: done in *`);
|
||||
s = s.replace(/collectAutoImports: \d+(?:\.\d+)?/g, `collectAutoImports: *`);
|
||||
s = s.replace(/continuePreviousIncompleteResponse: \d+(?:\.\d+)?/g, `continuePreviousIncompleteResponse: *`);
|
||||
s = s.replace(/dependencies in \d+(?:\.\d+)?/g, `dependencies in *`);
|
||||
s = s.replace(/"exportMapKey":\s*"\d+ \d+ /g, match => match.replace(/ \d+ /, ` * `));
|
||||
s = s.replace(/getIndentationAtPosition: getCurrentSourceFile: \d+(?:\.\d+)?/, `getIndentationAtPosition: getCurrentSourceFile: *`);
|
||||
s = s.replace(/getIndentationAtPosition: computeIndentation\s*: \d+(?:\.\d+)?/, `getIndentationAtPosition: computeIndentation: *`);
|
||||
s = sanitizeHarnessLSException(s);
|
||||
return s;
|
||||
}
|
||||
|
||||
function sanitizeHarnessLSException(s: string) {
|
||||
const index = s.indexOf(HarnessLSCouldNotResolveModule);
|
||||
if (index > 0) s = s.substring(0, index) + HarnessLSCouldNotResolveModule;
|
||||
return s;
|
||||
}
|
||||
|
||||
export function sanitizeLibFileText(s: string): string {
|
||||
Compiler.libFileNameSourceFileMap?.forEach((lib, fileName) => {
|
||||
s = replaceAll(s, JSON.stringify(lib.text), `${fileName}-Text`);
|
||||
s = replaceAll(s, lib.text, `${fileName}-Text`);
|
||||
});
|
||||
return s;
|
||||
}
|
||||
|
||||
export function createLoggerWithInMemoryLogs(host: ts.server.ServerHost, sanitizeLibs?: true): Logger {
|
||||
const logger = createHasErrorMessageLogger();
|
||||
const logs: string[] = [];
|
||||
if (host) logs.push(`currentDirectory:: ${host.getCurrentDirectory()} useCaseSensitiveFileNames: ${host.useCaseSensitiveFileNames}`);
|
||||
return handleLoggerGroup({
|
||||
...logger,
|
||||
logs,
|
||||
hasLevel: ts.returnTrue,
|
||||
loggingEnabled: ts.returnTrue,
|
||||
info: s => logs.push((sanitizeLibs ? sanitizeLibFileText : ts.identity)(sanitizeLog(s))),
|
||||
host,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
import {
|
||||
arrayFrom,
|
||||
compareStringsCaseSensitive,
|
||||
contains,
|
||||
createMultiMap,
|
||||
Debug,
|
||||
FileWatcher,
|
||||
FileWatcherCallback,
|
||||
MultiMap,
|
||||
PollingInterval,
|
||||
} from "./_namespaces/ts";
|
||||
|
||||
export interface TestFileWatcher {
|
||||
cb: FileWatcherCallback;
|
||||
pollingInterval: PollingInterval;
|
||||
}
|
||||
|
||||
export interface TestFsWatcher<DirCallback> {
|
||||
cb: DirCallback;
|
||||
inode: number | undefined;
|
||||
}
|
||||
|
||||
export interface WatchUtils<PollingWatcherData, FsWatcherData, Path extends string = string> {
|
||||
pollingWatches: MultiMap<Path, PollingWatcherData>;
|
||||
fsWatches: MultiMap<Path, FsWatcherData>;
|
||||
fsWatchesRecursive: MultiMap<Path, FsWatcherData>;
|
||||
pollingWatch(path: Path, data: PollingWatcherData): FileWatcher;
|
||||
fsWatch(path: Path, recursive: boolean, data: FsWatcherData): FileWatcher;
|
||||
serializeWatches(baseline?: string[]): string[];
|
||||
getHasWatchChanges(): boolean;
|
||||
setHasWatchChanges(): void;
|
||||
}
|
||||
|
||||
export function createWatchUtils<PollingWatcherData, FsWatcherData, Path extends string = string>(
|
||||
pollingWatchesName: string,
|
||||
fsWatchesName: string,
|
||||
): WatchUtils<PollingWatcherData, FsWatcherData, Path> {
|
||||
const pollingWatches = createMultiMap<Path, PollingWatcherData>();
|
||||
const fsWatches = createMultiMap<Path, FsWatcherData>();
|
||||
const fsWatchesRecursive = createMultiMap<Path, FsWatcherData>();
|
||||
|
||||
let hasWatchChanges = false;
|
||||
|
||||
let serializedPollingWatches: Map<string, PollingWatcherData[]> | undefined;
|
||||
let serializedFsWatches: Map<string, FsWatcherData[]> | undefined;
|
||||
let serializedFsWatchesRecursive: Map<string, FsWatcherData[]> | undefined;
|
||||
|
||||
return {
|
||||
pollingWatches,
|
||||
fsWatches,
|
||||
fsWatchesRecursive,
|
||||
pollingWatch,
|
||||
fsWatch,
|
||||
serializeWatches,
|
||||
getHasWatchChanges: () => hasWatchChanges,
|
||||
setHasWatchChanges: () => hasWatchChanges = true,
|
||||
};
|
||||
|
||||
function createWatcher<T>(map: MultiMap<Path, T>, path: Path, callback: T): FileWatcher {
|
||||
hasWatchChanges = true;
|
||||
map.add(path, callback);
|
||||
let closed = false;
|
||||
return {
|
||||
close: () => {
|
||||
Debug.assert(!closed);
|
||||
map.remove(path, callback);
|
||||
hasWatchChanges = true;
|
||||
closed = true;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function pollingWatch(path: Path, data: PollingWatcherData) {
|
||||
return createWatcher(
|
||||
pollingWatches,
|
||||
path,
|
||||
data,
|
||||
);
|
||||
}
|
||||
|
||||
function fsWatch(path: Path, recursive: boolean, data: FsWatcherData) {
|
||||
return createWatcher(
|
||||
recursive ? fsWatchesRecursive : fsWatches,
|
||||
path,
|
||||
data,
|
||||
);
|
||||
}
|
||||
|
||||
function serializeWatches(baseline: string[] = []) {
|
||||
if (!hasWatchChanges) return baseline;
|
||||
serializedPollingWatches = serializeMultiMap(baseline, pollingWatchesName, pollingWatches, serializedPollingWatches);
|
||||
serializedFsWatches = serializeMultiMap(baseline, fsWatchesName, fsWatches, serializedFsWatches);
|
||||
serializedFsWatchesRecursive = serializeMultiMap(baseline, `${fsWatchesName}Recursive`, fsWatchesRecursive, serializedFsWatchesRecursive);
|
||||
hasWatchChanges = false;
|
||||
return baseline;
|
||||
}
|
||||
}
|
||||
|
||||
export function serializeMultiMap<T>(baseline: string[], caption: string, multiMap: MultiMap<string, T>, serialized: Map<string, T[]> | undefined) {
|
||||
let hasChange = diffMap(baseline, caption, multiMap, serialized, /*deleted*/ false);
|
||||
hasChange = diffMap(baseline, caption, serialized, multiMap, /*deleted*/ true) || hasChange;
|
||||
if (hasChange) {
|
||||
serialized = new Map();
|
||||
multiMap.forEach((value, key) => serialized!.set(key, new Array(...value)));
|
||||
}
|
||||
return serialized;
|
||||
}
|
||||
|
||||
function diffMap<T>(
|
||||
baseline: string[],
|
||||
caption: string,
|
||||
map: Map<string, T[]> | undefined,
|
||||
old: Map<string, T[]> | undefined,
|
||||
deleted: boolean,
|
||||
) {
|
||||
let captionAdded = false;
|
||||
let baselineChanged = false;
|
||||
let hasChange = false;
|
||||
if (map) {
|
||||
for (const key of arrayFrom(map.keys()).sort(compareStringsCaseSensitive)) {
|
||||
const existing = old?.get(key);
|
||||
let addedKey = false;
|
||||
const values = map.get(key)!;
|
||||
for (const value of values) {
|
||||
const hasExisting = contains(existing, value);
|
||||
if (deleted && hasExisting) continue;
|
||||
if (!hasExisting) hasChange = true;
|
||||
if (!addedKey) {
|
||||
addBaseline(`${key}:${deleted || existing ? "" : " *new*"}`);
|
||||
addedKey = true;
|
||||
}
|
||||
addBaseline(` ${JSON.stringify(value)}${deleted || hasExisting || !existing ? "" : " *new*"}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (baselineChanged) baseline.push("");
|
||||
return hasChange;
|
||||
|
||||
function addBaseline(s: string) {
|
||||
if (!captionAdded) {
|
||||
baseline.push(`${caption}${deleted ? " *deleted*" : ""}::`);
|
||||
captionAdded = true;
|
||||
}
|
||||
baseline.push(s);
|
||||
baselineChanged = true;
|
||||
}
|
||||
}
|
||||
+15
-12
@@ -30,6 +30,9 @@ import {
|
||||
Version,
|
||||
versionMajorMinor,
|
||||
} from "./_namespaces/ts";
|
||||
import {
|
||||
stringifyIndented,
|
||||
} from "./_namespaces/ts.server";
|
||||
|
||||
export interface TypingResolutionHost {
|
||||
directoryExists(path: string): boolean;
|
||||
@@ -174,7 +177,7 @@ export function discoverTypings(
|
||||
}
|
||||
|
||||
// A typing name to typing file path mapping
|
||||
const inferredTypings = new Map<string, string>();
|
||||
const inferredTypings = new Map<string, string | false>();
|
||||
|
||||
// Only infer typings for .js and .jsx files
|
||||
fileNames = mapDefined(fileNames, fileName => {
|
||||
@@ -211,24 +214,24 @@ export function discoverTypings(
|
||||
);
|
||||
addInferredTypings(module, "Inferred typings from unresolved imports");
|
||||
}
|
||||
// Add the cached typing locations for inferred typings that are already installed
|
||||
packageNameToTypingLocation.forEach((typing, name) => {
|
||||
const registryEntry = typesRegistry.get(name);
|
||||
if (inferredTypings.has(name) && inferredTypings.get(name) === undefined && registryEntry !== undefined && isTypingUpToDate(typing, registryEntry)) {
|
||||
inferredTypings.set(name, typing.typingLocation);
|
||||
}
|
||||
});
|
||||
|
||||
// Remove typings that the user has added to the exclude list
|
||||
for (const excludeTypingName of exclude) {
|
||||
const didDelete = inferredTypings.delete(excludeTypingName);
|
||||
if (didDelete && log) log(`Typing for ${excludeTypingName} is in exclude list, will be ignored.`);
|
||||
}
|
||||
|
||||
// Add the cached typing locations for inferred typings that are already installed
|
||||
packageNameToTypingLocation.forEach((typing, name) => {
|
||||
const registryEntry = typesRegistry.get(name);
|
||||
if (inferredTypings.get(name) === false && registryEntry !== undefined && isTypingUpToDate(typing, registryEntry)) {
|
||||
inferredTypings.set(name, typing.typingLocation);
|
||||
}
|
||||
});
|
||||
|
||||
const newTypingNames: string[] = [];
|
||||
const cachedTypingPaths: string[] = [];
|
||||
inferredTypings.forEach((inferred, typing) => {
|
||||
if (inferred !== undefined) {
|
||||
if (inferred) {
|
||||
cachedTypingPaths.push(inferred);
|
||||
}
|
||||
else {
|
||||
@@ -236,12 +239,12 @@ export function discoverTypings(
|
||||
}
|
||||
});
|
||||
const result = { cachedTypingPaths, newTypingNames, filesToWatch };
|
||||
if (log) log(`Result: ${JSON.stringify(result)}`);
|
||||
if (log) log(`Finished typings discovery:${stringifyIndented(result)}`);
|
||||
return result;
|
||||
|
||||
function addInferredTyping(typingName: string) {
|
||||
if (!inferredTypings.has(typingName)) {
|
||||
inferredTypings.set(typingName, undefined!); // TODO: GH#18217
|
||||
inferredTypings.set(typingName, false);
|
||||
}
|
||||
}
|
||||
function addInferredTypings(typingNames: readonly string[], message: string) {
|
||||
|
||||
@@ -65,3 +65,19 @@ export function nowString() {
|
||||
const d = new Date();
|
||||
return `${d.getHours().toString().padStart(2, "0")}:${d.getMinutes().toString().padStart(2, "0")}:${d.getSeconds().toString().padStart(2, "0")}.${d.getMilliseconds().toString().padStart(3, "0")}`;
|
||||
}
|
||||
|
||||
const indentStr = "\n ";
|
||||
|
||||
/** @internal */
|
||||
export function indent(str: string): string {
|
||||
return indentStr + str.replace(/\n/g, indentStr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Put stringified JSON on the next line, indented.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export function stringifyIndented(json: {}): string {
|
||||
return indent(JSON.stringify(json, undefined, 2));
|
||||
}
|
||||
|
||||
Vendored
+11
-11
@@ -191,7 +191,7 @@ interface Int8Array {
|
||||
* Copies and sorts the array.
|
||||
* @param compareFn Function used to determine the order of the elements. It is expected to return
|
||||
* a negative value if the first argument is less than the second argument, zero if they're equal, and a positive
|
||||
* value otherwise. If omitted, the elements are sorted in ascending, ASCII character order.
|
||||
* value otherwise. If omitted, the elements are sorted in ascending order.
|
||||
* ```ts
|
||||
* const myNums = Uint8Array.from([11, 2, 22, 1]);
|
||||
* myNums.toSorted((a, b) => a - b) // Uint8Array(4) [1, 2, 11, 22]
|
||||
@@ -255,7 +255,7 @@ interface Uint8Array {
|
||||
* Copies and sorts the array.
|
||||
* @param compareFn Function used to determine the order of the elements. It is expected to return
|
||||
* a negative value if the first argument is less than the second argument, zero if they're equal, and a positive
|
||||
* value otherwise. If omitted, the elements are sorted in ascending, ASCII character order.
|
||||
* value otherwise. If omitted, the elements are sorted in ascending order.
|
||||
* ```ts
|
||||
* const myNums = Uint8Array.from([11, 2, 22, 1]);
|
||||
* myNums.toSorted((a, b) => a - b) // Uint8Array(4) [1, 2, 11, 22]
|
||||
@@ -327,7 +327,7 @@ interface Uint8ClampedArray {
|
||||
* Copies and sorts the array.
|
||||
* @param compareFn Function used to determine the order of the elements. It is expected to return
|
||||
* a negative value if the first argument is less than the second argument, zero if they're equal, and a positive
|
||||
* value otherwise. If omitted, the elements are sorted in ascending, ASCII character order.
|
||||
* value otherwise. If omitted, the elements are sorted in ascending order.
|
||||
* ```ts
|
||||
* const myNums = Uint8ClampedArray.from([11, 2, 22, 1]);
|
||||
* myNums.toSorted((a, b) => a - b) // Uint8ClampedArray(4) [1, 2, 11, 22]
|
||||
@@ -391,7 +391,7 @@ interface Int16Array {
|
||||
* Copies and sorts the array.
|
||||
* @param compareFn Function used to determine the order of the elements. It is expected to return
|
||||
* a negative value if the first argument is less than the second argument, zero if they're equal, and a positive
|
||||
* value otherwise. If omitted, the elements are sorted in ascending, ASCII character order.
|
||||
* value otherwise. If omitted, the elements are sorted in ascending order.
|
||||
* ```ts
|
||||
* const myNums = Int16Array.from([11, 2, -22, 1]);
|
||||
* myNums.toSorted((a, b) => a - b) // Int16Array(4) [-22, 1, 2, 11]
|
||||
@@ -463,7 +463,7 @@ interface Uint16Array {
|
||||
* Copies and sorts the array.
|
||||
* @param compareFn Function used to determine the order of the elements. It is expected to return
|
||||
* a negative value if the first argument is less than the second argument, zero if they're equal, and a positive
|
||||
* value otherwise. If omitted, the elements are sorted in ascending, ASCII character order.
|
||||
* value otherwise. If omitted, the elements are sorted in ascending order.
|
||||
* ```ts
|
||||
* const myNums = Uint16Array.from([11, 2, 22, 1]);
|
||||
* myNums.toSorted((a, b) => a - b) // Uint16Array(4) [1, 2, 11, 22]
|
||||
@@ -527,7 +527,7 @@ interface Int32Array {
|
||||
* Copies and sorts the array.
|
||||
* @param compareFn Function used to determine the order of the elements. It is expected to return
|
||||
* a negative value if the first argument is less than the second argument, zero if they're equal, and a positive
|
||||
* value otherwise. If omitted, the elements are sorted in ascending, ASCII character order.
|
||||
* value otherwise. If omitted, the elements are sorted in ascending order.
|
||||
* ```ts
|
||||
* const myNums = Int32Array.from([11, 2, -22, 1]);
|
||||
* myNums.toSorted((a, b) => a - b) // Int32Array(4) [-22, 1, 2, 11]
|
||||
@@ -599,7 +599,7 @@ interface Uint32Array {
|
||||
* Copies and sorts the array.
|
||||
* @param compareFn Function used to determine the order of the elements. It is expected to return
|
||||
* a negative value if the first argument is less than the second argument, zero if they're equal, and a positive
|
||||
* value otherwise. If omitted, the elements are sorted in ascending, ASCII character order.
|
||||
* value otherwise. If omitted, the elements are sorted in ascending order.
|
||||
* ```ts
|
||||
* const myNums = Uint32Array.from([11, 2, 22, 1]);
|
||||
* myNums.toSorted((a, b) => a - b) // Uint32Array(4) [1, 2, 11, 22]
|
||||
@@ -671,7 +671,7 @@ interface Float32Array {
|
||||
* Copies and sorts the array.
|
||||
* @param compareFn Function used to determine the order of the elements. It is expected to return
|
||||
* a negative value if the first argument is less than the second argument, zero if they're equal, and a positive
|
||||
* value otherwise. If omitted, the elements are sorted in ascending, ASCII character order.
|
||||
* value otherwise. If omitted, the elements are sorted in ascending order.
|
||||
* ```ts
|
||||
* const myNums = Float32Array.from([11.25, 2, -22.5, 1]);
|
||||
* myNums.toSorted((a, b) => a - b) // Float32Array(4) [-22.5, 1, 2, 11.5]
|
||||
@@ -743,7 +743,7 @@ interface Float64Array {
|
||||
* Copies and sorts the array.
|
||||
* @param compareFn Function used to determine the order of the elements. It is expected to return
|
||||
* a negative value if the first argument is less than the second argument, zero if they're equal, and a positive
|
||||
* value otherwise. If omitted, the elements are sorted in ascending, ASCII character order.
|
||||
* value otherwise. If omitted, the elements are sorted in ascending order.
|
||||
* ```ts
|
||||
* const myNums = Float64Array.from([11.25, 2, -22.5, 1]);
|
||||
* myNums.toSorted((a, b) => a - b) // Float64Array(4) [-22.5, 1, 2, 11.5]
|
||||
@@ -815,7 +815,7 @@ interface BigInt64Array {
|
||||
* Copies and sorts the array.
|
||||
* @param compareFn Function used to determine the order of the elements. It is expected to return
|
||||
* a negative value if the first argument is less than the second argument, zero if they're equal, and a positive
|
||||
* value otherwise. If omitted, the elements are sorted in ascending, ASCII character order.
|
||||
* value otherwise. If omitted, the elements are sorted in ascending order.
|
||||
* ```ts
|
||||
* const myNums = BigInt64Array.from([11n, 2n, -22n, 1n]);
|
||||
* myNums.toSorted((a, b) => Number(a - b)) // BigInt64Array(4) [-22n, 1n, 2n, 11n]
|
||||
@@ -887,7 +887,7 @@ interface BigUint64Array {
|
||||
* Copies and sorts the array.
|
||||
* @param compareFn Function used to determine the order of the elements. It is expected to return
|
||||
* a negative value if the first argument is less than the second argument, zero if they're equal, and a positive
|
||||
* value otherwise. If omitted, the elements are sorted in ascending, ASCII character order.
|
||||
* value otherwise. If omitted, the elements are sorted in ascending order.
|
||||
* ```ts
|
||||
* const myNums = BigUint64Array.from([11n, 2n, 22n, 1n]);
|
||||
* myNums.toSorted((a, b) => Number(a - b)) // BigUint64Array(4) [1n, 2n, 11n, 22n]
|
||||
|
||||
@@ -14,7 +14,6 @@ import {
|
||||
CommandLineOption,
|
||||
CompilerOptions,
|
||||
CompletionInfo,
|
||||
ConfigFileProgramReloadLevel,
|
||||
contains,
|
||||
containsPath,
|
||||
convertCompilerOptionsForTelemetry,
|
||||
@@ -95,6 +94,7 @@ import {
|
||||
PerformanceEvent,
|
||||
PluginImport,
|
||||
PollingInterval,
|
||||
ProgramUpdateLevel,
|
||||
ProjectPackageJsonInfo,
|
||||
ProjectReference,
|
||||
ReadMapFile,
|
||||
@@ -140,6 +140,7 @@ import {
|
||||
ActionSet,
|
||||
asNormalizedPath,
|
||||
AutoImportProviderProject,
|
||||
AuxiliaryProject,
|
||||
BeginEnablePluginResult,
|
||||
BeginInstallTypes,
|
||||
ConfiguredProject,
|
||||
@@ -153,6 +154,7 @@ import {
|
||||
hasNoTypeScriptSource,
|
||||
InferredProject,
|
||||
InvalidateCachedTypings,
|
||||
isBackgroundProject,
|
||||
isConfiguredProject,
|
||||
isDynamicFileName,
|
||||
isInferredProject,
|
||||
@@ -874,7 +876,7 @@ export interface ParsedConfig {
|
||||
* true if watchedDirectories need to be updated as per parsedCommandLine's updated watched directories
|
||||
*/
|
||||
watchedDirectoriesStale?: boolean;
|
||||
reloadLevel?: ConfigFileProgramReloadLevel.Partial | ConfigFileProgramReloadLevel.Full;
|
||||
updateLevel?: ProgramUpdateLevel.RootNamesAndUpdate | ProgramUpdateLevel.Full;
|
||||
}
|
||||
|
||||
function createProjectNameFactoryWithCounter(nameFactory: (counter: number) => string) {
|
||||
@@ -1129,6 +1131,7 @@ export class ProjectService {
|
||||
|
||||
/** @internal */ verifyDocumentRegistry = noop;
|
||||
/** @internal */ verifyProgram: (project: Project) => void = noop;
|
||||
/** @internal */ onProjectCreation: (project: Project) => void = noop;
|
||||
|
||||
readonly jsDocParsingMode: JSDocParsingMode | undefined;
|
||||
|
||||
@@ -1325,15 +1328,14 @@ export class ProjectService {
|
||||
|
||||
private delayUpdateProjectGraph(project: Project) {
|
||||
project.markAsDirty();
|
||||
if (project.projectKind !== ProjectKind.AutoImportProvider && project.projectKind !== ProjectKind.Auxiliary) {
|
||||
const projectName = project.getProjectName();
|
||||
this.pendingProjectUpdates.set(projectName, project);
|
||||
this.throttledOperations.schedule(projectName, /*delay*/ 250, () => {
|
||||
if (this.pendingProjectUpdates.delete(projectName)) {
|
||||
updateProjectIfDirty(project);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (isBackgroundProject(project)) return;
|
||||
const projectName = project.getProjectName();
|
||||
this.pendingProjectUpdates.set(projectName, project);
|
||||
this.throttledOperations.schedule(projectName, /*delay*/ 250, () => {
|
||||
if (this.pendingProjectUpdates.delete(projectName)) {
|
||||
updateProjectIfDirty(project);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
@@ -1671,7 +1673,7 @@ export class ProjectService {
|
||||
) return;
|
||||
|
||||
// Reload is pending, do the reload
|
||||
if (config.reloadLevel !== ConfigFileProgramReloadLevel.Full) config.reloadLevel = ConfigFileProgramReloadLevel.Partial;
|
||||
if (config.updateLevel !== ProgramUpdateLevel.Full) config.updateLevel = ProgramUpdateLevel.RootNamesAndUpdate;
|
||||
config.projects.forEach((watchWildcardDirectories, projectCanonicalPath) => {
|
||||
if (!watchWildcardDirectories) return;
|
||||
const project = this.getConfiguredProjectByCanonicalConfigFilePath(projectCanonicalPath);
|
||||
@@ -1679,23 +1681,23 @@ export class ProjectService {
|
||||
|
||||
// Load root file names for configured project with the config file name
|
||||
// But only schedule update if project references this config file
|
||||
const reloadLevel = configuredProjectForConfig === project ? ConfigFileProgramReloadLevel.Partial : ConfigFileProgramReloadLevel.None;
|
||||
if (project.pendingReload !== undefined && project.pendingReload > reloadLevel) return;
|
||||
const updateLevel = configuredProjectForConfig === project ? ProgramUpdateLevel.RootNamesAndUpdate : ProgramUpdateLevel.Update;
|
||||
if (project.pendingUpdateLevel !== undefined && project.pendingUpdateLevel > updateLevel) return;
|
||||
|
||||
// don't trigger callback on open, existing files
|
||||
if (this.openFiles.has(fileOrDirectoryPath)) {
|
||||
const info = Debug.checkDefined(this.getScriptInfoForPath(fileOrDirectoryPath));
|
||||
if (info.isAttached(project)) {
|
||||
const loadLevelToSet = Math.max(reloadLevel, project.openFileWatchTriggered.get(fileOrDirectoryPath) || ConfigFileProgramReloadLevel.None) as ConfigFileProgramReloadLevel;
|
||||
const loadLevelToSet = Math.max(updateLevel, project.openFileWatchTriggered.get(fileOrDirectoryPath) || ProgramUpdateLevel.Update) as ProgramUpdateLevel;
|
||||
project.openFileWatchTriggered.set(fileOrDirectoryPath, loadLevelToSet);
|
||||
}
|
||||
else {
|
||||
project.pendingReload = reloadLevel;
|
||||
project.pendingUpdateLevel = updateLevel;
|
||||
this.delayUpdateProjectGraphAndEnsureProjectStructureForOpenFiles(project);
|
||||
}
|
||||
}
|
||||
else {
|
||||
project.pendingReload = reloadLevel;
|
||||
project.pendingUpdateLevel = updateLevel;
|
||||
this.delayUpdateProjectGraphAndEnsureProjectStructureForOpenFiles(project);
|
||||
}
|
||||
});
|
||||
@@ -1708,12 +1710,12 @@ export class ProjectService {
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
private delayUpdateProjectsFromParsedConfigOnConfigFileChange(canonicalConfigFilePath: NormalizedPath, reloadReason: string) {
|
||||
private delayUpdateProjectsFromParsedConfigOnConfigFileChange(canonicalConfigFilePath: NormalizedPath, loadReason: string) {
|
||||
const configFileExistenceInfo = this.configFileExistenceInfoCache.get(canonicalConfigFilePath);
|
||||
if (!configFileExistenceInfo?.config) return false;
|
||||
let scheduledAnyProjectUpdate = false;
|
||||
// Update projects watching cached config
|
||||
configFileExistenceInfo.config.reloadLevel = ConfigFileProgramReloadLevel.Full;
|
||||
configFileExistenceInfo.config.updateLevel = ProgramUpdateLevel.Full;
|
||||
|
||||
configFileExistenceInfo.config.projects.forEach((_watchWildcardDirectories, projectCanonicalPath) => {
|
||||
const project = this.getConfiguredProjectByCanonicalConfigFilePath(projectCanonicalPath);
|
||||
@@ -1723,8 +1725,8 @@ export class ProjectService {
|
||||
if (projectCanonicalPath === canonicalConfigFilePath) {
|
||||
// Skip refresh if project is not yet loaded
|
||||
if (project.isInitialLoadPending()) return;
|
||||
project.pendingReload = ConfigFileProgramReloadLevel.Full;
|
||||
project.pendingReloadReason = reloadReason;
|
||||
project.pendingUpdateLevel = ProgramUpdateLevel.Full;
|
||||
project.pendingUpdateReason = loadReason;
|
||||
this.delayUpdateProjectGraph(project);
|
||||
}
|
||||
else {
|
||||
@@ -1922,11 +1924,11 @@ export class ProjectService {
|
||||
|
||||
// If project had open file affecting
|
||||
// Reload the root Files from config if its not already scheduled
|
||||
const reloadLevel = p.openFileWatchTriggered.get(info.path);
|
||||
if (reloadLevel !== undefined) {
|
||||
const updateLevel = p.openFileWatchTriggered.get(info.path);
|
||||
if (updateLevel !== undefined) {
|
||||
p.openFileWatchTriggered.delete(info.path);
|
||||
if (p.pendingReload !== undefined && p.pendingReload < reloadLevel) {
|
||||
p.pendingReload = reloadLevel;
|
||||
if (p.pendingUpdateLevel !== undefined && p.pendingUpdateLevel < updateLevel) {
|
||||
p.pendingUpdateLevel = updateLevel;
|
||||
p.markFileAsDirty(info.path);
|
||||
}
|
||||
}
|
||||
@@ -2418,6 +2420,7 @@ export class ProjectService {
|
||||
private addFilesToNonInferredProject<T>(project: ConfiguredProject | ExternalProject, files: T[], propertyReader: FilePropertyReader<T>, typeAcquisition: TypeAcquisition): void {
|
||||
this.updateNonInferredProjectFiles(project, files, propertyReader);
|
||||
project.setTypeAcquisition(typeAcquisition);
|
||||
project.markAsDirty();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
@@ -2438,7 +2441,7 @@ export class ProjectService {
|
||||
configFileExistenceInfo.config = {
|
||||
cachedDirectoryStructureHost: createCachedDirectoryStructureHost(this.host, this.host.getCurrentDirectory(), this.host.useCaseSensitiveFileNames)!,
|
||||
projects: new Map(),
|
||||
reloadLevel: ConfigFileProgramReloadLevel.Full,
|
||||
updateLevel: ProgramUpdateLevel.Full,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2457,8 +2460,8 @@ export class ProjectService {
|
||||
/** @internal */
|
||||
private createConfiguredProjectWithDelayLoad(configFileName: NormalizedPath, reason: string) {
|
||||
const project = this.createConfiguredProject(configFileName);
|
||||
project.pendingReload = ConfigFileProgramReloadLevel.Full;
|
||||
project.pendingReloadReason = reason;
|
||||
project.pendingUpdateLevel = ProgramUpdateLevel.Full;
|
||||
project.pendingUpdateReason = reason;
|
||||
return project;
|
||||
}
|
||||
|
||||
@@ -2521,7 +2524,7 @@ export class ProjectService {
|
||||
this.watchWildcards(configFilename, configFileExistenceInfo, project);
|
||||
}
|
||||
project.enablePluginsWithOptions(compilerOptions);
|
||||
const filesToAdd = parsedCommandLine.fileNames.concat(project.getExternalFiles());
|
||||
const filesToAdd = parsedCommandLine.fileNames.concat(project.getExternalFiles(ProgramUpdateLevel.Full));
|
||||
this.updateRootAndOptionsOfNonInferredProject(project, filesToAdd, fileNamePropertyReader, compilerOptions, parsedCommandLine.typeAcquisition!, parsedCommandLine.compileOnSave, parsedCommandLine.watchOptions);
|
||||
tracing?.pop();
|
||||
}
|
||||
@@ -2529,8 +2532,8 @@ export class ProjectService {
|
||||
/** @internal */
|
||||
ensureParsedConfigUptoDate(configFilename: NormalizedPath, canonicalConfigFilePath: NormalizedPath, configFileExistenceInfo: ConfigFileExistenceInfo, forProject: ConfiguredProject): ConfigFileExistenceInfo {
|
||||
if (configFileExistenceInfo.config) {
|
||||
if (!configFileExistenceInfo.config.reloadLevel) return configFileExistenceInfo;
|
||||
if (configFileExistenceInfo.config.reloadLevel === ConfigFileProgramReloadLevel.Partial) {
|
||||
if (!configFileExistenceInfo.config.updateLevel) return configFileExistenceInfo;
|
||||
if (configFileExistenceInfo.config.updateLevel === ProgramUpdateLevel.RootNamesAndUpdate) {
|
||||
this.reloadFileNamesOfParsedConfig(configFilename, configFileExistenceInfo.config);
|
||||
return configFileExistenceInfo;
|
||||
}
|
||||
@@ -2580,7 +2583,7 @@ export class ProjectService {
|
||||
else {
|
||||
configFileExistenceInfo.config.parsedCommandLine = parsedCommandLine;
|
||||
configFileExistenceInfo.config.watchedDirectoriesStale = true;
|
||||
configFileExistenceInfo.config.reloadLevel = undefined;
|
||||
configFileExistenceInfo.config.updateLevel = undefined;
|
||||
}
|
||||
|
||||
// If watch options different than older options when setting for the first time, update the config file watcher
|
||||
@@ -2669,7 +2672,7 @@ export class ProjectService {
|
||||
configFileExistenceInfo.config.watchedDirectoriesStale = undefined;
|
||||
}
|
||||
|
||||
private updateNonInferredProjectFiles<T>(project: Project, files: T[], propertyReader: FilePropertyReader<T>) {
|
||||
private updateNonInferredProjectFiles<T>(project: Project, files: readonly T[], propertyReader: FilePropertyReader<T>) {
|
||||
const projectRootFilesMap = project.getRootFilesMap();
|
||||
const newRootScriptInfoMap = new Map<string, true>();
|
||||
|
||||
@@ -2736,10 +2739,6 @@ export class ProjectService {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Just to ensure that even if root files dont change, the changes to the non root file are picked up,
|
||||
// mark the project as dirty unconditionally
|
||||
project.markAsDirty();
|
||||
}
|
||||
|
||||
private updateRootAndOptionsOfNonInferredProject<T>(project: ExternalProject | ConfiguredProject, newUncheckedFiles: T[], propertyReader: FilePropertyReader<T>, newOptions: CompilerOptions, newTypeAcquisition: TypeAcquisition, compileOnSave: boolean | undefined, watchOptions: WatchOptions | undefined) {
|
||||
@@ -2761,14 +2760,15 @@ export class ProjectService {
|
||||
reloadFileNamesOfConfiguredProject(project: ConfiguredProject) {
|
||||
const fileNames = this.reloadFileNamesOfParsedConfig(project.getConfigFilePath(), this.configFileExistenceInfoCache.get(project.canonicalConfigFilePath)!.config!);
|
||||
project.updateErrorOnNoInputFiles(fileNames);
|
||||
this.updateNonInferredProjectFiles(project, fileNames.concat(project.getExternalFiles()), fileNamePropertyReader);
|
||||
this.updateNonInferredProjectFiles(project, fileNames.concat(project.getExternalFiles(ProgramUpdateLevel.RootNamesAndUpdate)), fileNamePropertyReader);
|
||||
project.markAsDirty();
|
||||
return project.updateGraph();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
private reloadFileNamesOfParsedConfig(configFileName: NormalizedPath, config: ParsedConfig) {
|
||||
if (config.reloadLevel === undefined) return config.parsedCommandLine!.fileNames;
|
||||
Debug.assert(config.reloadLevel === ConfigFileProgramReloadLevel.Partial);
|
||||
if (config.updateLevel === undefined) return config.parsedCommandLine!.fileNames;
|
||||
Debug.assert(config.updateLevel === ProgramUpdateLevel.RootNamesAndUpdate);
|
||||
const configFileSpecs = config.parsedCommandLine!.options.configFile!.configFileSpecs!;
|
||||
const fileNames = getFileNamesFromConfigSpecs(
|
||||
configFileSpecs,
|
||||
@@ -2782,7 +2782,7 @@ export class ProjectService {
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
setFileNamesOfAutoImportProviderProject(project: AutoImportProviderProject, fileNames: string[]) {
|
||||
setFileNamesOfAutpImportProviderOrAuxillaryProject(project: AutoImportProviderProject | AuxiliaryProject, fileNames: readonly string[]) {
|
||||
this.updateNonInferredProjectFiles(project, fileNames, fileNamePropertyReader);
|
||||
}
|
||||
|
||||
@@ -3398,7 +3398,7 @@ export class ProjectService {
|
||||
this.configuredProjects.forEach(project => {
|
||||
if (
|
||||
project.hasExternalProjectRef() &&
|
||||
project.pendingReload === ConfigFileProgramReloadLevel.Full &&
|
||||
project.pendingUpdateLevel === ProgramUpdateLevel.Full &&
|
||||
!this.pendingProjectUpdates.has(project.getProjectName())
|
||||
) {
|
||||
project.updateGraph();
|
||||
@@ -3468,7 +3468,7 @@ export class ProjectService {
|
||||
|
||||
// Ensure everything is reloaded for cached configs
|
||||
this.configFileExistenceInfoCache.forEach(info => {
|
||||
if (info.config) info.config.reloadLevel = ConfigFileProgramReloadLevel.Full;
|
||||
if (info.config) info.config.updateLevel = ProgramUpdateLevel.Full;
|
||||
});
|
||||
|
||||
// Reload Projects
|
||||
@@ -3517,8 +3517,8 @@ export class ProjectService {
|
||||
if (!updatedProjects.has(project.canonicalConfigFilePath)) {
|
||||
updatedProjects.set(project.canonicalConfigFilePath, true);
|
||||
if (delayReload) {
|
||||
project.pendingReload = ConfigFileProgramReloadLevel.Full;
|
||||
project.pendingReloadReason = reason;
|
||||
project.pendingUpdateLevel = ProgramUpdateLevel.Full;
|
||||
project.pendingUpdateReason = reason;
|
||||
if (clearSemanticCache) this.clearSemanticCache(project);
|
||||
this.delayUpdateProjectGraph(project);
|
||||
}
|
||||
|
||||
+33
-49
@@ -17,7 +17,6 @@ import {
|
||||
CompilerHost,
|
||||
CompilerOptions,
|
||||
concatenate,
|
||||
ConfigFileProgramReloadLevel,
|
||||
containsPath,
|
||||
createCacheableExportInfoMap,
|
||||
createLanguageService,
|
||||
@@ -98,6 +97,7 @@ import {
|
||||
PluginImport,
|
||||
PollingInterval,
|
||||
Program,
|
||||
ProgramUpdateLevel,
|
||||
ProjectPackageJsonInfo,
|
||||
ProjectReference,
|
||||
removeFileExtension,
|
||||
@@ -254,7 +254,7 @@ export interface PluginCreateInfo {
|
||||
|
||||
export interface PluginModule {
|
||||
create(createInfo: PluginCreateInfo): LanguageService;
|
||||
getExternalFiles?(proj: Project): string[];
|
||||
getExternalFiles?(proj: Project, updateLevel: ProgramUpdateLevel): string[];
|
||||
onConfigurationChanged?(config: any): void;
|
||||
}
|
||||
|
||||
@@ -579,9 +579,10 @@ export abstract class Project implements LanguageServiceHost, ModuleResolutionHo
|
||||
this.disableLanguageService(lastFileExceededProgramSize);
|
||||
}
|
||||
this.markAsDirty();
|
||||
if (projectKind !== ProjectKind.AutoImportProvider) {
|
||||
if (!isBackgroundProject(this)) {
|
||||
this.projectService.pendingEnsureProjectForOpenFiles = true;
|
||||
}
|
||||
this.projectService.onProjectCreation(this);
|
||||
}
|
||||
|
||||
isKnownTypesPackageName(name: string): boolean {
|
||||
@@ -727,6 +728,7 @@ export abstract class Project implements LanguageServiceHost, ModuleResolutionHo
|
||||
return this.resolutionCache.resolveModuleNameLiterals(moduleLiterals, containingFile, redirectedReference, options, containingSourceFile, reusedNames);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
getModuleResolutionCache(): ModuleResolutionCache | undefined {
|
||||
return this.resolutionCache.getModuleResolutionCache();
|
||||
}
|
||||
@@ -1027,11 +1029,11 @@ export abstract class Project implements LanguageServiceHost, ModuleResolutionHo
|
||||
return { ...newTypeAcquisition, include: this.removeExistingTypings(newTypeAcquisition.include) };
|
||||
}
|
||||
|
||||
getExternalFiles(): SortedReadonlyArray<string> {
|
||||
getExternalFiles(updateLevel?: ProgramUpdateLevel): SortedReadonlyArray<string> {
|
||||
return sort(flatMap(this.plugins, plugin => {
|
||||
if (typeof plugin.module.getExternalFiles !== "function") return;
|
||||
try {
|
||||
return plugin.module.getExternalFiles(this);
|
||||
return plugin.module.getExternalFiles(this, updateLevel || ProgramUpdateLevel.Update);
|
||||
}
|
||||
catch (e) {
|
||||
this.projectService.logger.info(`A plugin threw an exception in getExternalFiles: ${e}`);
|
||||
@@ -1892,10 +1894,10 @@ export abstract class Project implements LanguageServiceHost, ModuleResolutionHo
|
||||
}
|
||||
// compute and return the difference
|
||||
const lastReportedFileNames = this.lastReportedFileNames;
|
||||
const externalFiles = this.getExternalFiles().map((f): protocol.FileWithProjectReferenceRedirectInfo => ({
|
||||
const externalFiles = this.externalFiles?.map((f): protocol.FileWithProjectReferenceRedirectInfo => ({
|
||||
fileName: toNormalizedPath(f),
|
||||
isSourceOfProjectReferenceRedirect: false,
|
||||
}));
|
||||
})) || emptyArray;
|
||||
const currentFiles = arrayToMap(
|
||||
this.getFileNamesWithRedirectInfo(!!includeProjectReferenceRedirectInfo).concat(externalFiles),
|
||||
info => info.fileName,
|
||||
@@ -1945,10 +1947,10 @@ export abstract class Project implements LanguageServiceHost, ModuleResolutionHo
|
||||
else {
|
||||
// unknown version - return everything
|
||||
const projectFileNames = this.getFileNamesWithRedirectInfo(!!includeProjectReferenceRedirectInfo);
|
||||
const externalFiles = this.getExternalFiles().map((f): protocol.FileWithProjectReferenceRedirectInfo => ({
|
||||
const externalFiles = this.externalFiles?.map((f): protocol.FileWithProjectReferenceRedirectInfo => ({
|
||||
fileName: toNormalizedPath(f),
|
||||
isSourceOfProjectReferenceRedirect: false,
|
||||
}));
|
||||
})) || emptyArray;
|
||||
const allFiles = projectFileNames.concat(externalFiles);
|
||||
this.lastReportedFileNames = arrayToMap(
|
||||
allFiles,
|
||||
@@ -2185,35 +2187,15 @@ export abstract class Project implements LanguageServiceHost, ModuleResolutionHo
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
getNoDtsResolutionProject(rootFileNames: readonly string[]): Project {
|
||||
getNoDtsResolutionProject(rootFile: NormalizedPath): AuxiliaryProject {
|
||||
Debug.assert(this.projectService.serverMode === LanguageServiceMode.Semantic);
|
||||
if (!this.noDtsResolutionProject) {
|
||||
this.noDtsResolutionProject = new AuxiliaryProject(this.projectService, this.documentRegistry, this.getCompilerOptionsForNoDtsResolutionProject(), this.currentDirectory);
|
||||
}
|
||||
|
||||
enumerateInsertsAndDeletes<NormalizedPath, NormalizedPath>(
|
||||
rootFileNames.map(toNormalizedPath),
|
||||
this.noDtsResolutionProject.getRootFiles(),
|
||||
getStringComparer(!this.useCaseSensitiveFileNames()),
|
||||
pathToAdd => {
|
||||
const info = this.projectService.getOrCreateScriptInfoNotOpenedByClient(
|
||||
pathToAdd,
|
||||
this.currentDirectory,
|
||||
this.noDtsResolutionProject!.directoryStructureHost,
|
||||
);
|
||||
if (info) {
|
||||
this.noDtsResolutionProject!.addRoot(info, pathToAdd);
|
||||
}
|
||||
},
|
||||
pathToRemove => {
|
||||
// It may be preferable to remove roots only once project grows to a certain size?
|
||||
const info = this.noDtsResolutionProject!.getScriptInfo(pathToRemove);
|
||||
if (info) {
|
||||
this.noDtsResolutionProject!.removeRoot(info);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
if (this.noDtsResolutionProject.rootFile !== rootFile) {
|
||||
this.projectService.setFileNamesOfAutpImportProviderOrAuxillaryProject(this.noDtsResolutionProject, [rootFile]);
|
||||
this.noDtsResolutionProject.rootFile = rootFile;
|
||||
}
|
||||
return this.noDtsResolutionProject;
|
||||
}
|
||||
|
||||
@@ -2394,6 +2376,7 @@ export class InferredProject extends Project {
|
||||
|
||||
/** @internal */
|
||||
export class AuxiliaryProject extends Project {
|
||||
/** @internal */ rootFile: NormalizedPath | undefined;
|
||||
constructor(projectService: ProjectService, documentRegistry: DocumentRegistry, compilerOptions: CompilerOptions, currentDirectory: string) {
|
||||
super(projectService.newAuxiliaryProjectName(), ProjectKind.Auxiliary, projectService, documentRegistry, /*hasExplicitListOfFiles*/ false, /*lastFileExceededProgramSize*/ undefined, compilerOptions, /*compileOnSaveEnabled*/ false, /*watchOptions*/ undefined, projectService.host, currentDirectory);
|
||||
}
|
||||
@@ -2601,7 +2584,7 @@ export class AutoImportProviderProject extends Project {
|
||||
);
|
||||
}
|
||||
|
||||
this.projectService.setFileNamesOfAutoImportProviderProject(this, rootFileNames);
|
||||
this.projectService.setFileNamesOfAutpImportProviderOrAuxillaryProject(this, rootFileNames);
|
||||
this.rootFileNames = rootFileNames;
|
||||
const oldProgram = this.getCurrentProgram();
|
||||
const hasSameSetOfFiles = super.updateGraph();
|
||||
@@ -2657,10 +2640,6 @@ export class AutoImportProviderProject extends Project {
|
||||
return PackageJsonAutoImportPreference.Off;
|
||||
}
|
||||
|
||||
override getTypeAcquisition(): TypeAcquisition {
|
||||
return { enable: false };
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
override getSymlinkCache() {
|
||||
return this.hostProject.getSymlinkCache();
|
||||
@@ -2679,12 +2658,12 @@ export class AutoImportProviderProject extends Project {
|
||||
*/
|
||||
export class ConfiguredProject extends Project {
|
||||
/** @internal */
|
||||
pendingReload: ConfigFileProgramReloadLevel | undefined;
|
||||
pendingUpdateLevel: ProgramUpdateLevel | undefined;
|
||||
/** @internal */
|
||||
pendingReloadReason: string | undefined;
|
||||
pendingUpdateReason: string | undefined;
|
||||
|
||||
/** @internal */
|
||||
openFileWatchTriggered = new Map<string, ConfigFileProgramReloadLevel>();
|
||||
openFileWatchTriggered = new Map<string, ProgramUpdateLevel>();
|
||||
|
||||
/** @internal */
|
||||
canConfigFileJsonReportNoInputFiles = false;
|
||||
@@ -2775,18 +2754,18 @@ export class ConfiguredProject extends Project {
|
||||
override updateGraph(): boolean {
|
||||
const isInitialLoad = this.isInitialLoadPending();
|
||||
this.isInitialLoadPending = returnFalse;
|
||||
const reloadLevel = this.pendingReload;
|
||||
this.pendingReload = ConfigFileProgramReloadLevel.None;
|
||||
const updateLevel = this.pendingUpdateLevel;
|
||||
this.pendingUpdateLevel = ProgramUpdateLevel.Update;
|
||||
let result: boolean;
|
||||
switch (reloadLevel) {
|
||||
case ConfigFileProgramReloadLevel.Partial:
|
||||
switch (updateLevel) {
|
||||
case ProgramUpdateLevel.RootNamesAndUpdate:
|
||||
this.openFileWatchTriggered.clear();
|
||||
result = this.projectService.reloadFileNamesOfConfiguredProject(this);
|
||||
break;
|
||||
case ConfigFileProgramReloadLevel.Full:
|
||||
case ProgramUpdateLevel.Full:
|
||||
this.openFileWatchTriggered.clear();
|
||||
const reason = Debug.checkDefined(this.pendingReloadReason);
|
||||
this.pendingReloadReason = undefined;
|
||||
const reason = Debug.checkDefined(this.pendingUpdateReason);
|
||||
this.pendingUpdateReason = undefined;
|
||||
this.projectService.reloadConfiguredProject(this, reason, isInitialLoad, /*clearSemanticCache*/ false);
|
||||
result = true;
|
||||
break;
|
||||
@@ -3015,3 +2994,8 @@ export function isConfiguredProject(project: Project): project is ConfiguredProj
|
||||
export function isExternalProject(project: Project): project is ExternalProject {
|
||||
return project.projectKind === ProjectKind.External;
|
||||
}
|
||||
|
||||
/**@internal */
|
||||
export function isBackgroundProject(project: Project): project is AutoImportProviderProject | AuxiliaryProject {
|
||||
return project.projectKind === ProjectKind.AutoImportProvider || project.projectKind === ProjectKind.Auxiliary;
|
||||
}
|
||||
|
||||
@@ -40,13 +40,13 @@ import {
|
||||
Errors,
|
||||
ExternalProject,
|
||||
InferredProject,
|
||||
isBackgroundProject,
|
||||
isConfiguredProject,
|
||||
isExternalProject,
|
||||
isInferredProject,
|
||||
maxFileSize,
|
||||
NormalizedPath,
|
||||
Project,
|
||||
ProjectKind,
|
||||
ScriptVersionCache,
|
||||
ServerHost,
|
||||
} from "./_namespaces/ts.server";
|
||||
@@ -682,7 +682,7 @@ export class ScriptInfo {
|
||||
isContainedByBackgroundProject() {
|
||||
return some(
|
||||
this.containingProjects,
|
||||
p => p.projectKind === ProjectKind.AutoImportProvider || p.projectKind === ProjectKind.Auxiliary,
|
||||
isBackgroundProject,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -730,7 +730,7 @@ export class ScriptInfo {
|
||||
* reported as the default project for a ScriptInfo.
|
||||
*/
|
||||
function ensurePrimaryProjectKind(project: Project | undefined) {
|
||||
if (!project || project.projectKind === ProjectKind.AutoImportProvider || project.projectKind === ProjectKind.Auxiliary) {
|
||||
if (!project || isBackgroundProject(project)) {
|
||||
return Errors.ThrowNoProject();
|
||||
}
|
||||
return project;
|
||||
|
||||
+14
-15
@@ -139,6 +139,7 @@ import {
|
||||
WithMetadata,
|
||||
} from "./_namespaces/ts";
|
||||
import {
|
||||
AuxiliaryProject,
|
||||
CloseFileWatcherEvent,
|
||||
ConfigFileDiagEvent,
|
||||
ConfiguredProject,
|
||||
@@ -322,7 +323,7 @@ export function formatMessage<T extends protocol.Message>(msg: T, logger: Logger
|
||||
|
||||
const json = JSON.stringify(msg);
|
||||
if (verboseLogging) {
|
||||
logger.info(`${msg.type}:${indent(JSON.stringify(msg, undefined, " "))}`);
|
||||
logger.info(`${msg.type}:${stringifyIndented(msg)}`);
|
||||
}
|
||||
|
||||
const len = byteLength(json, "utf8");
|
||||
@@ -1542,7 +1543,7 @@ export class Session<TMessage = string> implements EventSender {
|
||||
if (needsJsResolution) {
|
||||
const definitionSet = createSet<DefinitionInfo>(d => d.textSpan.start, documentSpansEqual);
|
||||
definitions?.forEach(d => definitionSet.add(d));
|
||||
const noDtsProject = project.getNoDtsResolutionProject([file]);
|
||||
const noDtsProject = project.getNoDtsResolutionProject(file);
|
||||
const ls = noDtsProject.getLanguageService();
|
||||
const jsDefinitions = ls.getDefinitionAtPosition(file, position, /*searchOtherFilesOnly*/ true, /*stopAtAlias*/ false)
|
||||
?.filter(d => toNormalizedPath(d.fileName) !== file);
|
||||
@@ -1564,8 +1565,16 @@ export class Session<TMessage = string> implements EventSender {
|
||||
const ambientCandidates = definitions.filter(d => toNormalizedPath(d.fileName) !== file && d.isAmbient);
|
||||
for (const candidate of some(ambientCandidates) ? ambientCandidates : getAmbientCandidatesByClimbingAccessChain()) {
|
||||
const fileNameToSearch = findImplementationFileFromDtsFileName(candidate.fileName, file, noDtsProject);
|
||||
if (!fileNameToSearch || !ensureRoot(noDtsProject, fileNameToSearch)) {
|
||||
continue;
|
||||
if (!fileNameToSearch) continue;
|
||||
const info = this.projectService.getOrCreateScriptInfoNotOpenedByClient(
|
||||
fileNameToSearch,
|
||||
noDtsProject.currentDirectory,
|
||||
noDtsProject.directoryStructureHost,
|
||||
);
|
||||
if (!info) continue;
|
||||
if (!noDtsProject.containsScriptInfo(info)) {
|
||||
noDtsProject.addRoot(info);
|
||||
noDtsProject.updateGraph();
|
||||
}
|
||||
const noDtsProgram = ls.getProgram()!;
|
||||
const fileToSearch = Debug.checkDefined(noDtsProgram.getSourceFile(fileNameToSearch));
|
||||
@@ -1580,7 +1589,7 @@ export class Session<TMessage = string> implements EventSender {
|
||||
definitions = definitions.filter(d => !d.isAmbient && !d.failedAliasResolution);
|
||||
return this.mapDefinitionInfo(definitions, project);
|
||||
|
||||
function findImplementationFileFromDtsFileName(fileName: string, resolveFromFile: string, auxiliaryProject: Project) {
|
||||
function findImplementationFileFromDtsFileName(fileName: string, resolveFromFile: string, auxiliaryProject: AuxiliaryProject) {
|
||||
const nodeModulesPathParts = getNodeModulePathParts(fileName);
|
||||
if (nodeModulesPathParts && fileName.lastIndexOf(nodeModulesPathPart) === nodeModulesPathParts.topLevelNodeModulesIndex) {
|
||||
// Second check ensures the fileName only contains one `/node_modules/`. If there's more than one I give up.
|
||||
@@ -1671,16 +1680,6 @@ export class Session<TMessage = string> implements EventSender {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function ensureRoot(project: Project, fileName: string) {
|
||||
const info = project.getScriptInfo(fileName);
|
||||
if (!info) return false;
|
||||
if (!project.containsScriptInfo(info)) {
|
||||
project.addRoot(info);
|
||||
project.updateGraph();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private getEmitOutput(args: protocol.EmitOutputRequestArgs): EmitOutput | protocol.EmitOutput {
|
||||
|
||||
@@ -110,19 +110,3 @@ export function removeSorted<T>(array: SortedArray<T>, remove: T, compare: Compa
|
||||
array.splice(removeIndex, 1);
|
||||
}
|
||||
}
|
||||
|
||||
const indentStr = "\n ";
|
||||
|
||||
/** @internal */
|
||||
export function indent(str: string): string {
|
||||
return indentStr + str.replace(/\n/g, indentStr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Put stringified JSON on the next line, indented.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export function stringifyIndented(json: {}): string {
|
||||
return indentStr + JSON.stringify(json);
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ function makeChange(changeTracker: textChanges.ChangeTracker, sourceFile: Source
|
||||
}
|
||||
}
|
||||
fixedDeclarations?.add(getNodeId(insertionSite));
|
||||
const cloneWithModifier = factory.updateModifiers(
|
||||
const cloneWithModifier = factory.replaceModifiers(
|
||||
getSynthesizedDeepClone(insertionSite, /*includeTrivia*/ true),
|
||||
factory.createNodeArray(factory.createModifiersFromModifierFlags(getSyntacticModifierFlags(insertionSite) | ModifierFlags.Async)),
|
||||
);
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
getModeForUsageLocation,
|
||||
getTextOfNode,
|
||||
getTokenAtPosition,
|
||||
hasSyntacticModifier,
|
||||
hasOverrideModifier,
|
||||
ImportDeclaration,
|
||||
isBinaryExpression,
|
||||
isClassElement,
|
||||
@@ -27,7 +27,6 @@ import {
|
||||
isPropertyAccessExpression,
|
||||
isQualifiedName,
|
||||
isStringLiteralLike,
|
||||
ModifierFlags,
|
||||
Node,
|
||||
NodeFlags,
|
||||
ScriptTarget,
|
||||
@@ -131,7 +130,7 @@ function getInfo(sourceFile: SourceFile, pos: number, context: CodeFixContextBas
|
||||
const props = checker.getContextualTypeForArgumentAtIndex(tag, 0);
|
||||
suggestedSymbol = checker.getSuggestedSymbolForNonexistentJSXAttribute(node, props!);
|
||||
}
|
||||
else if (hasSyntacticModifier(parent, ModifierFlags.Override) && isClassElement(parent) && parent.name === node) {
|
||||
else if (hasOverrideModifier(parent) && isClassElement(parent) && parent.name === node) {
|
||||
const baseDeclaration = findAncestor(node, isClassLike);
|
||||
const baseTypeNode = baseDeclaration ? getEffectiveBaseTypeNode(baseDeclaration) : undefined;
|
||||
const baseType = baseTypeNode ? checker.getTypeAtLocation(baseTypeNode) : undefined;
|
||||
|
||||
@@ -389,6 +389,7 @@ function createImportAdderWorker(sourceFile: SourceFile, program: Program, useAu
|
||||
namedImports && arrayFrom(namedImports.entries(), ([name, addAsTypeOnly]) => ({ addAsTypeOnly, name })),
|
||||
namespaceLikeImport,
|
||||
compilerOptions,
|
||||
preferences,
|
||||
);
|
||||
newDeclarations = combine(newDeclarations, declarations);
|
||||
});
|
||||
@@ -877,7 +878,7 @@ function getNewImportFixes(
|
||||
const rejectNodeModulesRelativePaths = moduleResolutionUsesNodeModules(moduleResolution);
|
||||
const getModuleSpecifiers = fromCacheOnly
|
||||
? (moduleSymbol: Symbol) => ({ moduleSpecifiers: moduleSpecifiers.tryGetModuleSpecifiersFromCache(moduleSymbol, sourceFile, moduleSpecifierResolutionHost, preferences), computedWithoutCache: false })
|
||||
: (moduleSymbol: Symbol, checker: TypeChecker) => moduleSpecifiers.getModuleSpecifiersWithCacheInfo(moduleSymbol, checker, compilerOptions, sourceFile, moduleSpecifierResolutionHost, preferences);
|
||||
: (moduleSymbol: Symbol, checker: TypeChecker) => moduleSpecifiers.getModuleSpecifiersWithCacheInfo(moduleSymbol, checker, compilerOptions, sourceFile, moduleSpecifierResolutionHost, preferences, /*options*/ undefined, /*forAutoImport*/ true);
|
||||
|
||||
let computedWithoutCacheCount = 0;
|
||||
const fixes = flatMap(exportInfo, (exportInfo, i) => {
|
||||
@@ -1348,6 +1349,7 @@ function codeActionForFixWorker(
|
||||
namedImports,
|
||||
namespaceLikeImport,
|
||||
program.getCompilerOptions(),
|
||||
preferences,
|
||||
),
|
||||
/*blankLineBetween*/ true,
|
||||
preferences,
|
||||
@@ -1505,7 +1507,7 @@ function doAddExistingFix(
|
||||
const newSpecifiers = stableSort(
|
||||
namedImports.map(namedImport =>
|
||||
factory.createImportSpecifier(
|
||||
(!clause.isTypeOnly || promoteFromTypeOnly) && needsTypeOnly(namedImport),
|
||||
(!clause.isTypeOnly || promoteFromTypeOnly) && shouldUseTypeOnly(namedImport, preferences),
|
||||
/*propertyName*/ undefined,
|
||||
factory.createIdentifier(namedImport.name),
|
||||
)
|
||||
@@ -1604,6 +1606,10 @@ function needsTypeOnly({ addAsTypeOnly }: { addAsTypeOnly: AddAsTypeOnly; }): bo
|
||||
return addAsTypeOnly === AddAsTypeOnly.Required;
|
||||
}
|
||||
|
||||
function shouldUseTypeOnly(info: { addAsTypeOnly: AddAsTypeOnly; }, preferences: UserPreferences): boolean {
|
||||
return needsTypeOnly(info) || !!preferences.preferTypeOnlyAutoImports && info.addAsTypeOnly !== AddAsTypeOnly.NotAllowed;
|
||||
}
|
||||
|
||||
function getNewImports(
|
||||
moduleSpecifier: string,
|
||||
quotePreference: QuotePreference,
|
||||
@@ -1611,6 +1617,7 @@ function getNewImports(
|
||||
namedImports: readonly Import[] | undefined,
|
||||
namespaceLikeImport: Import & { importKind: ImportKind.CommonJS | ImportKind.Namespace; } | undefined,
|
||||
compilerOptions: CompilerOptions,
|
||||
preferences: UserPreferences,
|
||||
): AnyImportSyntax | readonly AnyImportSyntax[] {
|
||||
const quotedModuleSpecifier = makeStringLiteral(moduleSpecifier, quotePreference);
|
||||
let statements: AnyImportSyntax | readonly AnyImportSyntax[] | undefined;
|
||||
@@ -1618,18 +1625,18 @@ function getNewImports(
|
||||
// `verbatimModuleSyntax` should prefer top-level `import type` -
|
||||
// even though it's not an error, it would add unnecessary runtime emit.
|
||||
const topLevelTypeOnly = (!defaultImport || needsTypeOnly(defaultImport)) && every(namedImports, needsTypeOnly) ||
|
||||
compilerOptions.verbatimModuleSyntax &&
|
||||
(compilerOptions.verbatimModuleSyntax || preferences.preferTypeOnlyAutoImports) &&
|
||||
defaultImport?.addAsTypeOnly !== AddAsTypeOnly.NotAllowed &&
|
||||
!some(namedImports, i => i.addAsTypeOnly === AddAsTypeOnly.NotAllowed);
|
||||
statements = combine(
|
||||
statements,
|
||||
makeImport(
|
||||
defaultImport && factory.createIdentifier(defaultImport.name),
|
||||
namedImports?.map(({ addAsTypeOnly, name }) =>
|
||||
namedImports?.map(namedImport =>
|
||||
factory.createImportSpecifier(
|
||||
!topLevelTypeOnly && addAsTypeOnly === AddAsTypeOnly.Required,
|
||||
!topLevelTypeOnly && shouldUseTypeOnly(namedImport, preferences),
|
||||
/*propertyName*/ undefined,
|
||||
factory.createIdentifier(name),
|
||||
factory.createIdentifier(namedImport.name),
|
||||
)
|
||||
),
|
||||
moduleSpecifier,
|
||||
@@ -1643,14 +1650,14 @@ function getNewImports(
|
||||
const declaration = namespaceLikeImport.importKind === ImportKind.CommonJS
|
||||
? factory.createImportEqualsDeclaration(
|
||||
/*modifiers*/ undefined,
|
||||
needsTypeOnly(namespaceLikeImport),
|
||||
shouldUseTypeOnly(namespaceLikeImport, preferences),
|
||||
factory.createIdentifier(namespaceLikeImport.name),
|
||||
factory.createExternalModuleReference(quotedModuleSpecifier),
|
||||
)
|
||||
: factory.createImportDeclaration(
|
||||
/*modifiers*/ undefined,
|
||||
factory.createImportClause(
|
||||
needsTypeOnly(namespaceLikeImport),
|
||||
shouldUseTypeOnly(namespaceLikeImport, preferences),
|
||||
/*name*/ undefined,
|
||||
factory.createNamespaceImport(factory.createIdentifier(namespaceLikeImport.name)),
|
||||
),
|
||||
|
||||
@@ -1329,7 +1329,11 @@ function completionInfoFromData(
|
||||
|
||||
if (keywordFilters !== KeywordCompletionFilters.None) {
|
||||
for (const keywordEntry of getKeywordCompletions(keywordFilters, !insideJsDocTagTypeExpression && isSourceFileJS(sourceFile))) {
|
||||
if (isTypeOnlyLocation && isTypeKeyword(stringToToken(keywordEntry.name)!) || !uniqueNames.has(keywordEntry.name)) {
|
||||
if (
|
||||
isTypeOnlyLocation && isTypeKeyword(stringToToken(keywordEntry.name)!) ||
|
||||
!isTypeOnlyLocation && isContextualKeywordInAutoImportableExpressionSpace(keywordEntry.name) ||
|
||||
!uniqueNames.has(keywordEntry.name)
|
||||
) {
|
||||
uniqueNames.add(keywordEntry.name);
|
||||
insertSorted(entries, keywordEntry, compareCompletionEntries, /*allowDuplicates*/ true);
|
||||
}
|
||||
@@ -1984,7 +1988,7 @@ function getEntryForMemberCompletion(
|
||||
// and we need to make sure the modifiers are uniform for all nodes/signatures.
|
||||
modifiers = node.modifierFlagsCache | requiredModifiers;
|
||||
}
|
||||
node = factory.updateModifiers(node, modifiers);
|
||||
node = factory.replaceModifiers(node, modifiers);
|
||||
completionNodes.push(node);
|
||||
},
|
||||
body,
|
||||
@@ -2014,12 +2018,12 @@ function getEntryForMemberCompletion(
|
||||
modifiers &= ~ModifierFlags.Public;
|
||||
}
|
||||
modifiers |= allowedAndPresent;
|
||||
completionNodes = completionNodes.map(node => factory.updateModifiers(node, modifiers));
|
||||
completionNodes = completionNodes.map(node => factory.replaceModifiers(node, modifiers));
|
||||
// Add back the decorators that were already present.
|
||||
if (presentDecorators?.length) {
|
||||
const lastNode = completionNodes[completionNodes.length - 1];
|
||||
if (canHaveDecorators(lastNode)) {
|
||||
completionNodes[completionNodes.length - 1] = factory.updateModifierLike(lastNode, (presentDecorators as ModifierLike[]).concat(getModifiers(lastNode) || []));
|
||||
completionNodes[completionNodes.length - 1] = factory.replaceDecoratorsAndModifiers(lastNode, (presentDecorators as ModifierLike[]).concat(getModifiers(lastNode) || []));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2824,7 +2828,7 @@ export function getCompletionEntryDetails(
|
||||
return createSimpleDetails(completionNameForLiteral(sourceFile, preferences, literal), ScriptElementKind.string, typeof literal === "string" ? SymbolDisplayPartKind.stringLiteral : SymbolDisplayPartKind.numericLiteral);
|
||||
}
|
||||
case "cases": {
|
||||
const { entry, importAdder } = getExhaustiveCaseSnippets(
|
||||
const snippets = getExhaustiveCaseSnippets(
|
||||
contextToken!.parent as CaseBlock,
|
||||
sourceFile,
|
||||
preferences,
|
||||
@@ -2832,8 +2836,10 @@ export function getCompletionEntryDetails(
|
||||
host,
|
||||
program,
|
||||
/*formatContext*/ undefined,
|
||||
)!;
|
||||
if (importAdder.hasFixes()) {
|
||||
);
|
||||
|
||||
if (snippets?.importAdder.hasFixes()) {
|
||||
const { entry, importAdder } = snippets;
|
||||
const changes = textChanges.ChangeTracker.with(
|
||||
{ host, formatContext, preferences },
|
||||
importAdder.writeFixes,
|
||||
@@ -2850,8 +2856,9 @@ export function getCompletionEntryDetails(
|
||||
}],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
name: entry.name,
|
||||
name,
|
||||
kind: ScriptElementKind.unknown,
|
||||
kindModifiers: "",
|
||||
displayParts: [],
|
||||
@@ -5826,3 +5833,31 @@ function toUpperCharCode(charCode: number) {
|
||||
}
|
||||
return charCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* These are all the contextual keywords that would be valid to auto-import
|
||||
* in expression space and also a valid keyword in the same location, depending
|
||||
* on what gets typed afterwards. In these cases, we want to offer both the
|
||||
* auto-import and the keyword completion. For example,
|
||||
*
|
||||
* ```ts
|
||||
* type
|
||||
* ```
|
||||
*
|
||||
* may be the beginning of a type alias declaration (keyword completion), or
|
||||
* it may be the beginning of
|
||||
*
|
||||
* ```ts
|
||||
* import { type } from "os";
|
||||
* type() === "Darwin" ? doSomething() : doSomethingElse();
|
||||
* ```
|
||||
*/
|
||||
function isContextualKeywordInAutoImportableExpressionSpace(keyword: string) {
|
||||
return keyword === "abstract" ||
|
||||
keyword === "async" ||
|
||||
keyword === "await" ||
|
||||
keyword === "declare" ||
|
||||
keyword === "module" ||
|
||||
keyword === "namespace" ||
|
||||
keyword === "type";
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
ArrayTypeNode,
|
||||
ArrowFunction,
|
||||
CallExpression,
|
||||
CharacterCodes,
|
||||
ConditionalTypeNode,
|
||||
ConstructorTypeNode,
|
||||
createPrinterWithRemoveComments,
|
||||
@@ -12,6 +13,7 @@ import {
|
||||
EmitHint,
|
||||
EnumMember,
|
||||
equateStringsCaseInsensitive,
|
||||
escapeString,
|
||||
Expression,
|
||||
findChildOfKind,
|
||||
findIndex,
|
||||
@@ -26,6 +28,7 @@ import {
|
||||
getLanguageVariant,
|
||||
getLeadingCommentRanges,
|
||||
getNameOfDeclaration,
|
||||
getQuotePreference,
|
||||
hasContextSensitiveParameters,
|
||||
Identifier,
|
||||
idText,
|
||||
@@ -59,9 +62,11 @@ import {
|
||||
isPropertyAccessExpression,
|
||||
isPropertyDeclaration,
|
||||
isSpreadElement,
|
||||
isStringLiteral,
|
||||
isTypeNode,
|
||||
isVarConst,
|
||||
isVariableDeclaration,
|
||||
LiteralExpression,
|
||||
LiteralTypeNode,
|
||||
MappedTypeNode,
|
||||
MethodDeclaration,
|
||||
@@ -70,7 +75,6 @@ import {
|
||||
Node,
|
||||
NodeArray,
|
||||
NodeBuilderFlags,
|
||||
NumericLiteral,
|
||||
OptionalTypeNode,
|
||||
ParameterDeclaration,
|
||||
ParenthesizedTypeNode,
|
||||
@@ -78,11 +82,11 @@ import {
|
||||
PropertyDeclaration,
|
||||
PropertySignature,
|
||||
QualifiedName,
|
||||
QuotePreference,
|
||||
RestTypeNode,
|
||||
Signature,
|
||||
skipParentheses,
|
||||
some,
|
||||
StringLiteral,
|
||||
Symbol,
|
||||
SymbolFlags,
|
||||
SyntaxKind,
|
||||
@@ -125,6 +129,7 @@ export function provideInlayHints(context: InlayHintsContext): InlayHint[] {
|
||||
const { file, program, span, cancellationToken, preferences } = context;
|
||||
const sourceFileText = file.text;
|
||||
const compilerOptions = program.getCompilerOptions();
|
||||
const quotePreference = getQuotePreference(file, preferences);
|
||||
|
||||
const checker = program.getTypeChecker();
|
||||
const result: InlayHint[] = [];
|
||||
@@ -473,6 +478,11 @@ export function provideInlayHints(context: InlayHintsContext): InlayHint[] {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isLiteralExpression(node)) {
|
||||
parts.push({ text: getLiteralText(node) });
|
||||
return;
|
||||
}
|
||||
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.Identifier:
|
||||
const identifier = node as Identifier;
|
||||
@@ -485,12 +495,6 @@ export function provideInlayHints(context: InlayHintsContext): InlayHint[] {
|
||||
parts.push({ text: identifierText });
|
||||
}
|
||||
break;
|
||||
case SyntaxKind.NumericLiteral:
|
||||
parts.push({ text: (node as NumericLiteral).text });
|
||||
break;
|
||||
case SyntaxKind.StringLiteral:
|
||||
parts.push({ text: `"${(node as StringLiteral).text}"` });
|
||||
break;
|
||||
case SyntaxKind.QualifiedName:
|
||||
const qualifiedName = node as QualifiedName;
|
||||
visitForDisplayParts(qualifiedName.left);
|
||||
@@ -749,6 +753,13 @@ export function provideInlayHints(context: InlayHintsContext): InlayHint[] {
|
||||
visitForDisplayParts(node);
|
||||
});
|
||||
}
|
||||
|
||||
function getLiteralText(node: LiteralExpression) {
|
||||
if (isStringLiteral(node)) {
|
||||
return quotePreference === QuotePreference.Single ? `'${escapeString(node.text, CharacterCodes.singleQuote)}'` : `"${escapeString(node.text, CharacterCodes.doubleQuote)}"`;
|
||||
}
|
||||
return node.text;
|
||||
}
|
||||
}
|
||||
|
||||
function isUndefined(name: __String) {
|
||||
|
||||
@@ -43,6 +43,7 @@ import {
|
||||
isFunctionTypeNode,
|
||||
isIdentifier,
|
||||
isJSDoc,
|
||||
isJSDocOverloadTag,
|
||||
isJSDocParameterTag,
|
||||
isJSDocPropertyLikeTag,
|
||||
isJSDocTypeLiteral,
|
||||
@@ -230,6 +231,11 @@ function getCommentHavingNodes(declaration: Declaration): readonly (JSDoc | JSDo
|
||||
case SyntaxKind.JSDocCallbackTag:
|
||||
case SyntaxKind.JSDocTypedefTag:
|
||||
return [declaration as JSDocTypedefTag, (declaration as JSDocTypedefTag).parent];
|
||||
case SyntaxKind.JSDocSignature:
|
||||
if (isJSDocOverloadTag(declaration.parent)) {
|
||||
return [declaration.parent.parent];
|
||||
}
|
||||
// falls through
|
||||
default:
|
||||
return getJSDocCommentsAndTags(declaration);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import {
|
||||
ArrowFunction,
|
||||
AssertClause,
|
||||
Block,
|
||||
CallExpression,
|
||||
CancellationToken,
|
||||
@@ -302,11 +301,10 @@ function getOutliningSpanForNode(n: Node, sourceFile: SourceFile): OutliningSpan
|
||||
case SyntaxKind.NamedImports:
|
||||
case SyntaxKind.NamedExports:
|
||||
case SyntaxKind.ImportAttributes:
|
||||
case SyntaxKind.AssertClause:
|
||||
return spanForImportExportElements(n as NamedImports | NamedExports | AssertClause | ImportAttributes);
|
||||
return spanForImportExportElements(n as NamedImports | NamedExports | ImportAttributes);
|
||||
}
|
||||
|
||||
function spanForImportExportElements(node: NamedImports | NamedExports | AssertClause | ImportAttributes) {
|
||||
function spanForImportExportElements(node: NamedImports | NamedExports | ImportAttributes) {
|
||||
if (!node.elements.length) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
getTokenAtPosition,
|
||||
getUniqueName,
|
||||
ignoreSourceNewlines,
|
||||
isArray,
|
||||
isConditionalTypeNode,
|
||||
isFunctionLike,
|
||||
isIdentifier,
|
||||
@@ -44,6 +45,7 @@ import {
|
||||
isTypePredicateNode,
|
||||
isTypeQueryNode,
|
||||
isTypeReferenceNode,
|
||||
isUnionTypeNode,
|
||||
JSDocTag,
|
||||
JSDocTemplateTag,
|
||||
Node,
|
||||
@@ -59,6 +61,7 @@ import {
|
||||
SymbolFlags,
|
||||
textChanges,
|
||||
TextRange,
|
||||
toArray,
|
||||
TypeChecker,
|
||||
TypeElement,
|
||||
TypeNode,
|
||||
@@ -151,7 +154,7 @@ registerRefactor(refactorName, {
|
||||
|
||||
interface TypeAliasInfo {
|
||||
isJS: boolean;
|
||||
selection: TypeNode;
|
||||
selection: TypeNode | TypeNode[];
|
||||
enclosingNode: Node;
|
||||
typeParameters: readonly TypeParameterDeclaration[];
|
||||
typeElements?: readonly TypeElement[];
|
||||
@@ -159,7 +162,7 @@ interface TypeAliasInfo {
|
||||
|
||||
interface InterfaceInfo {
|
||||
isJS: boolean;
|
||||
selection: TypeNode;
|
||||
selection: TypeNode | TypeNode[];
|
||||
enclosingNode: Node;
|
||||
typeParameters: readonly TypeParameterDeclaration[];
|
||||
typeElements: readonly TypeElement[];
|
||||
@@ -173,16 +176,32 @@ function getRangeToExtract(context: RefactorContext, considerEmptySpans = true):
|
||||
const current = getTokenAtPosition(file, startPosition);
|
||||
const range = createTextRangeFromSpan(getRefactorContextSpan(context));
|
||||
const cursorRequest = range.pos === range.end && considerEmptySpans;
|
||||
const overlappingRange = nodeOverlapsWithStartEnd(current, file, range.pos, range.end);
|
||||
|
||||
const selection = findAncestor(current, node =>
|
||||
const firstType = findAncestor(current, node =>
|
||||
node.parent && isTypeNode(node) && !rangeContainsSkipTrivia(range, node.parent, file) &&
|
||||
(cursorRequest || nodeOverlapsWithStartEnd(current, file, range.pos, range.end)));
|
||||
if (!selection || !isTypeNode(selection)) return { error: getLocaleSpecificMessage(Diagnostics.Selection_is_not_a_valid_type_node) };
|
||||
(cursorRequest || overlappingRange));
|
||||
if (!firstType || !isTypeNode(firstType)) return { error: getLocaleSpecificMessage(Diagnostics.Selection_is_not_a_valid_type_node) };
|
||||
|
||||
const checker = context.program.getTypeChecker();
|
||||
const enclosingNode = getEnclosingNode(selection, isJS);
|
||||
const enclosingNode = getEnclosingNode(firstType, isJS);
|
||||
if (enclosingNode === undefined) return { error: getLocaleSpecificMessage(Diagnostics.No_type_could_be_extracted_from_this_type_node) };
|
||||
|
||||
const expandedFirstType = getExpandedSelectionNode(firstType, enclosingNode);
|
||||
if (!isTypeNode(expandedFirstType)) return { error: getLocaleSpecificMessage(Diagnostics.Selection_is_not_a_valid_type_node) };
|
||||
|
||||
const typeList: TypeNode[] = [];
|
||||
if ((isUnionTypeNode(expandedFirstType.parent) || isIntersectionTypeNode(expandedFirstType.parent)) && range.end > firstType.end) {
|
||||
// the only extraction cases in which multiple nodes may need to be selected to capture the entire type are union and intersection types
|
||||
addRange(
|
||||
typeList,
|
||||
expandedFirstType.parent.types.filter(type => {
|
||||
return nodeOverlapsWithStartEnd(type, file, range.pos, range.end);
|
||||
}),
|
||||
);
|
||||
}
|
||||
const selection = typeList.length > 1 ? typeList : expandedFirstType;
|
||||
|
||||
const typeParameters = collectTypeParameters(checker, selection, enclosingNode, file);
|
||||
if (!typeParameters) return { error: getLocaleSpecificMessage(Diagnostics.No_type_could_be_extracted_from_this_type_node) };
|
||||
|
||||
@@ -190,12 +209,21 @@ function getRangeToExtract(context: RefactorContext, considerEmptySpans = true):
|
||||
return { isJS, selection, enclosingNode, typeParameters, typeElements };
|
||||
}
|
||||
|
||||
function flattenTypeLiteralNodeReference(checker: TypeChecker, node: TypeNode | undefined): readonly TypeElement[] | undefined {
|
||||
if (!node) return undefined;
|
||||
if (isIntersectionTypeNode(node)) {
|
||||
function flattenTypeLiteralNodeReference(checker: TypeChecker, selection: TypeNode | TypeNode[] | undefined): readonly TypeElement[] | undefined {
|
||||
if (!selection) return undefined;
|
||||
if (isArray(selection)) {
|
||||
const result: TypeElement[] = [];
|
||||
for (const type of selection) {
|
||||
const flattenedTypeMembers = flattenTypeLiteralNodeReference(checker, type);
|
||||
if (!flattenedTypeMembers) return undefined;
|
||||
addRange(result, flattenedTypeMembers);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
if (isIntersectionTypeNode(selection)) {
|
||||
const result: TypeElement[] = [];
|
||||
const seen = new Map<string, true>();
|
||||
for (const type of node.types) {
|
||||
for (const type of selection.types) {
|
||||
const flattenedTypeMembers = flattenTypeLiteralNodeReference(checker, type);
|
||||
if (!flattenedTypeMembers || !flattenedTypeMembers.every(type => type.name && addToSeen(seen, getNameFromPropertyName(type.name) as string))) {
|
||||
return undefined;
|
||||
@@ -205,22 +233,27 @@ function flattenTypeLiteralNodeReference(checker: TypeChecker, node: TypeNode |
|
||||
}
|
||||
return result;
|
||||
}
|
||||
else if (isParenthesizedTypeNode(node)) {
|
||||
return flattenTypeLiteralNodeReference(checker, node.type);
|
||||
else if (isParenthesizedTypeNode(selection)) {
|
||||
return flattenTypeLiteralNodeReference(checker, selection.type);
|
||||
}
|
||||
else if (isTypeLiteralNode(node)) {
|
||||
return node.members;
|
||||
else if (isTypeLiteralNode(selection)) {
|
||||
return selection.members;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function rangeContainsSkipTrivia(r1: TextRange, node: Node, file: SourceFile): boolean {
|
||||
function rangeContainsSkipTrivia(r1: TextRange, node: TextRange, file: SourceFile): boolean {
|
||||
return rangeContainsStartEnd(r1, skipTrivia(file.text, node.pos), node.end);
|
||||
}
|
||||
|
||||
function collectTypeParameters(checker: TypeChecker, selection: TypeNode, enclosingNode: Node, file: SourceFile): TypeParameterDeclaration[] | undefined {
|
||||
function collectTypeParameters(checker: TypeChecker, selection: TypeNode | TypeNode[], enclosingNode: Node, file: SourceFile): TypeParameterDeclaration[] | undefined {
|
||||
const result: TypeParameterDeclaration[] = [];
|
||||
return visitor(selection) ? undefined : result;
|
||||
const selectionArray = toArray(selection);
|
||||
const selectionRange = { pos: selectionArray[0].pos, end: selectionArray[selectionArray.length - 1].end };
|
||||
for (const t of selectionArray) {
|
||||
if (visitor(t)) return undefined;
|
||||
}
|
||||
return result;
|
||||
|
||||
function visitor(node: Node): true | undefined {
|
||||
if (isTypeReferenceNode(node)) {
|
||||
@@ -231,11 +264,11 @@ function collectTypeParameters(checker: TypeChecker, selection: TypeNode, enclos
|
||||
if (isTypeParameterDeclaration(decl) && decl.getSourceFile() === file) {
|
||||
// skip extraction if the type node is in the range of the type parameter declaration.
|
||||
// function foo<T extends { a?: /**/T }>(): void;
|
||||
if (decl.name.escapedText === typeName.escapedText && rangeContainsSkipTrivia(decl, selection, file)) {
|
||||
if (decl.name.escapedText === typeName.escapedText && rangeContainsSkipTrivia(decl, selectionRange, file)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (rangeContainsSkipTrivia(enclosingNode, decl, file) && !rangeContainsSkipTrivia(selection, decl, file)) {
|
||||
if (rangeContainsSkipTrivia(enclosingNode, decl, file) && !rangeContainsSkipTrivia(selectionRange, decl, file)) {
|
||||
pushIfUnique(result, decl);
|
||||
break;
|
||||
}
|
||||
@@ -245,25 +278,25 @@ function collectTypeParameters(checker: TypeChecker, selection: TypeNode, enclos
|
||||
}
|
||||
else if (isInferTypeNode(node)) {
|
||||
const conditionalTypeNode = findAncestor(node, n => isConditionalTypeNode(n) && rangeContainsSkipTrivia(n.extendsType, node, file));
|
||||
if (!conditionalTypeNode || !rangeContainsSkipTrivia(selection, conditionalTypeNode, file)) {
|
||||
if (!conditionalTypeNode || !rangeContainsSkipTrivia(selectionRange, conditionalTypeNode, file)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if ((isTypePredicateNode(node) || isThisTypeNode(node))) {
|
||||
const functionLikeNode = findAncestor(node.parent, isFunctionLike);
|
||||
if (functionLikeNode && functionLikeNode.type && rangeContainsSkipTrivia(functionLikeNode.type, node, file) && !rangeContainsSkipTrivia(selection, functionLikeNode, file)) {
|
||||
if (functionLikeNode && functionLikeNode.type && rangeContainsSkipTrivia(functionLikeNode.type, node, file) && !rangeContainsSkipTrivia(selectionRange, functionLikeNode, file)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (isTypeQueryNode(node)) {
|
||||
if (isIdentifier(node.exprName)) {
|
||||
const symbol = checker.resolveName(node.exprName.text, node.exprName, SymbolFlags.Value, /*excludeGlobals*/ false);
|
||||
if (symbol?.valueDeclaration && rangeContainsSkipTrivia(enclosingNode, symbol.valueDeclaration, file) && !rangeContainsSkipTrivia(selection, symbol.valueDeclaration, file)) {
|
||||
if (symbol?.valueDeclaration && rangeContainsSkipTrivia(enclosingNode, symbol.valueDeclaration, file) && !rangeContainsSkipTrivia(selectionRange, symbol.valueDeclaration, file)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (isThisIdentifier(node.exprName.left) && !rangeContainsSkipTrivia(selection, node.parent, file)) {
|
||||
if (isThisIdentifier(node.exprName.left) && !rangeContainsSkipTrivia(selectionRange, node.parent, file)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -278,20 +311,20 @@ function collectTypeParameters(checker: TypeChecker, selection: TypeNode, enclos
|
||||
}
|
||||
|
||||
function doTypeAliasChange(changes: textChanges.ChangeTracker, file: SourceFile, name: string, info: TypeAliasInfo) {
|
||||
const { enclosingNode, selection, typeParameters } = info;
|
||||
|
||||
const newTypeNode = factory.createTypeAliasDeclaration(
|
||||
const { enclosingNode, typeParameters } = info;
|
||||
const { firstTypeNode, lastTypeNode, newTypeNode } = getNodesToEdit(info);
|
||||
const newTypeDeclaration = factory.createTypeAliasDeclaration(
|
||||
/*modifiers*/ undefined,
|
||||
name,
|
||||
typeParameters.map(id => factory.updateTypeParameterDeclaration(id, id.modifiers, id.name, id.constraint, /*defaultType*/ undefined)),
|
||||
selection,
|
||||
newTypeNode,
|
||||
);
|
||||
changes.insertNodeBefore(file, enclosingNode, ignoreSourceNewlines(newTypeNode), /*blankLineBetween*/ true);
|
||||
changes.replaceNode(file, selection, factory.createTypeReferenceNode(name, typeParameters.map(id => factory.createTypeReferenceNode(id.name, /*typeArguments*/ undefined))), { leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude, trailingTriviaOption: textChanges.TrailingTriviaOption.ExcludeWhitespace });
|
||||
changes.insertNodeBefore(file, enclosingNode, ignoreSourceNewlines(newTypeDeclaration), /*blankLineBetween*/ true);
|
||||
changes.replaceNodeRange(file, firstTypeNode, lastTypeNode, factory.createTypeReferenceNode(name, typeParameters.map(id => factory.createTypeReferenceNode(id.name, /*typeArguments*/ undefined))), { leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude, trailingTriviaOption: textChanges.TrailingTriviaOption.ExcludeWhitespace });
|
||||
}
|
||||
|
||||
function doInterfaceChange(changes: textChanges.ChangeTracker, file: SourceFile, name: string, info: InterfaceInfo) {
|
||||
const { enclosingNode, selection, typeParameters, typeElements } = info;
|
||||
const { enclosingNode, typeParameters, typeElements } = info;
|
||||
|
||||
const newTypeNode = factory.createInterfaceDeclaration(
|
||||
/*modifiers*/ undefined,
|
||||
@@ -302,17 +335,21 @@ function doInterfaceChange(changes: textChanges.ChangeTracker, file: SourceFile,
|
||||
);
|
||||
setTextRange(newTypeNode, typeElements[0]?.parent);
|
||||
changes.insertNodeBefore(file, enclosingNode, ignoreSourceNewlines(newTypeNode), /*blankLineBetween*/ true);
|
||||
changes.replaceNode(file, selection, factory.createTypeReferenceNode(name, typeParameters.map(id => factory.createTypeReferenceNode(id.name, /*typeArguments*/ undefined))), { leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude, trailingTriviaOption: textChanges.TrailingTriviaOption.ExcludeWhitespace });
|
||||
|
||||
const { firstTypeNode, lastTypeNode } = getNodesToEdit(info);
|
||||
changes.replaceNodeRange(file, firstTypeNode, lastTypeNode, factory.createTypeReferenceNode(name, typeParameters.map(id => factory.createTypeReferenceNode(id.name, /*typeArguments*/ undefined))), { leadingTriviaOption: textChanges.LeadingTriviaOption.Exclude, trailingTriviaOption: textChanges.TrailingTriviaOption.ExcludeWhitespace });
|
||||
}
|
||||
|
||||
function doTypedefChange(changes: textChanges.ChangeTracker, context: RefactorContext, file: SourceFile, name: string, info: ExtractInfo) {
|
||||
const { enclosingNode, selection, typeParameters } = info;
|
||||
|
||||
setEmitFlags(selection, EmitFlags.NoComments | EmitFlags.NoNestedComments);
|
||||
toArray(info.selection).forEach(typeNode => {
|
||||
setEmitFlags(typeNode, EmitFlags.NoComments | EmitFlags.NoNestedComments);
|
||||
});
|
||||
const { enclosingNode, typeParameters } = info;
|
||||
const { firstTypeNode, lastTypeNode, newTypeNode } = getNodesToEdit(info);
|
||||
|
||||
const node = factory.createJSDocTypedefTag(
|
||||
factory.createIdentifier("typedef"),
|
||||
factory.createJSDocTypeExpression(selection),
|
||||
factory.createJSDocTypeExpression(newTypeNode),
|
||||
factory.createIdentifier(name),
|
||||
);
|
||||
|
||||
@@ -339,9 +376,36 @@ function doTypedefChange(changes: textChanges.ChangeTracker, context: RefactorCo
|
||||
else {
|
||||
changes.insertNodeBefore(file, enclosingNode, jsDoc, /*blankLineBetween*/ true);
|
||||
}
|
||||
changes.replaceNode(file, selection, factory.createTypeReferenceNode(name, typeParameters.map(id => factory.createTypeReferenceNode(id.name, /*typeArguments*/ undefined))));
|
||||
changes.replaceNodeRange(file, firstTypeNode, lastTypeNode, factory.createTypeReferenceNode(name, typeParameters.map(id => factory.createTypeReferenceNode(id.name, /*typeArguments*/ undefined))));
|
||||
}
|
||||
|
||||
function getNodesToEdit(info: ExtractInfo) {
|
||||
if (isArray(info.selection)) {
|
||||
return {
|
||||
firstTypeNode: info.selection[0],
|
||||
lastTypeNode: info.selection[info.selection.length - 1],
|
||||
newTypeNode: isUnionTypeNode(info.selection[0].parent) ? factory.createUnionTypeNode(info.selection) : factory.createIntersectionTypeNode(info.selection),
|
||||
};
|
||||
}
|
||||
return {
|
||||
firstTypeNode: info.selection,
|
||||
lastTypeNode: info.selection,
|
||||
newTypeNode: info.selection,
|
||||
};
|
||||
}
|
||||
|
||||
function getEnclosingNode(node: Node, isJS: boolean) {
|
||||
return findAncestor(node, isStatement) || (isJS ? findAncestor(node, isJSDoc) : undefined);
|
||||
}
|
||||
|
||||
function getExpandedSelectionNode(firstType: Node, enclosingNode: Node) {
|
||||
// intended to capture the entire type in cases where the user selection is not exactly the entire type
|
||||
// currently only implemented for union and intersection types
|
||||
return findAncestor(firstType, node => {
|
||||
if (node === enclosingNode) return "quit";
|
||||
if (isUnionTypeNode(node.parent) || isIntersectionTypeNode(node.parent)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}) ?? firstType;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
FunctionDeclaration,
|
||||
FunctionExpression,
|
||||
getLocaleSpecificMessage,
|
||||
getTokenAtPosition,
|
||||
getTouchingPropertyName,
|
||||
isArrowFunction,
|
||||
isBlock,
|
||||
isInJSFile,
|
||||
@@ -104,7 +104,7 @@ function doChange(sourceFile: SourceFile, changes: textChanges.ChangeTracker, de
|
||||
function getInfo(context: RefactorContext): FunctionInfo | RefactorErrorInfo | undefined {
|
||||
if (isInJSFile(context.file) || !refactorKindBeginsWith(inferReturnTypeAction.kind, context.kind)) return;
|
||||
|
||||
const token = getTokenAtPosition(context.file, context.startPosition);
|
||||
const token = getTouchingPropertyName(context.file, context.startPosition);
|
||||
const declaration = findAncestor(token, n =>
|
||||
isBlock(n) || n.parent && isArrowFunction(n.parent) && (n.kind === SyntaxKind.EqualsGreaterThanToken || n.parent.body === n) ? "quit" :
|
||||
isConvertibleDeclaration(n)) as ConvertibleDeclaration | undefined;
|
||||
|
||||
@@ -68,6 +68,7 @@ import {
|
||||
Identifier,
|
||||
ImportDeclaration,
|
||||
ImportEqualsDeclaration,
|
||||
importFromModuleSpecifier,
|
||||
insertImports,
|
||||
InterfaceDeclaration,
|
||||
InternalSymbolName,
|
||||
@@ -84,6 +85,8 @@ import {
|
||||
isImportDeclaration,
|
||||
isImportEqualsDeclaration,
|
||||
isNamedExports,
|
||||
isNamedImports,
|
||||
isObjectBindingPattern,
|
||||
isObjectLiteralExpression,
|
||||
isOmittedExpression,
|
||||
isPrologueDirective,
|
||||
@@ -96,6 +99,7 @@ import {
|
||||
isStringLiteralLike,
|
||||
isValidTypeOnlyAliasUseSite,
|
||||
isVariableDeclaration,
|
||||
isVariableDeclarationInitializedToRequire,
|
||||
isVariableDeclarationList,
|
||||
isVariableStatement,
|
||||
LanguageServiceHost,
|
||||
@@ -194,16 +198,15 @@ function error(notApplicableReason: string) {
|
||||
|
||||
function doChange(context: RefactorContext, oldFile: SourceFile, targetFile: string, program: Program, toMove: ToMove, changes: textChanges.ChangeTracker, host: LanguageServiceHost, preferences: UserPreferences): void {
|
||||
const checker = program.getTypeChecker();
|
||||
const usage = getUsageInfo(oldFile, toMove.all, checker);
|
||||
// For a new file
|
||||
if (!host.fileExists(targetFile)) {
|
||||
changes.createNewFile(oldFile, targetFile, getNewStatementsAndRemoveFromOldFile(oldFile, targetFile, usage, changes, toMove, program, host, preferences));
|
||||
changes.createNewFile(oldFile, targetFile, getNewStatementsAndRemoveFromOldFile(oldFile, targetFile, getUsageInfo(oldFile, toMove.all, checker), changes, toMove, program, host, preferences));
|
||||
addNewFileToTsconfig(program, changes, oldFile.fileName, targetFile, hostGetCanonicalFileName(host));
|
||||
}
|
||||
else {
|
||||
const targetSourceFile = Debug.checkDefined(program.getSourceFile(targetFile));
|
||||
const importAdder = codefix.createImportAdder(targetSourceFile, context.program, context.preferences, context.host);
|
||||
getNewStatementsAndRemoveFromOldFile(oldFile, targetSourceFile, usage, changes, toMove, program, host, preferences, importAdder);
|
||||
getNewStatementsAndRemoveFromOldFile(oldFile, targetSourceFile, getUsageInfo(oldFile, toMove.all, checker, getExistingImports(targetSourceFile, checker)), changes, toMove, program, host, preferences, importAdder);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -993,7 +996,7 @@ function isPureImport(node: Node): boolean {
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function getUsageInfo(oldFile: SourceFile, toMove: readonly Statement[], checker: TypeChecker): UsageInfo {
|
||||
export function getUsageInfo(oldFile: SourceFile, toMove: readonly Statement[], checker: TypeChecker, existingTargetImports: ReadonlySet<Symbol> = new Set()): UsageInfo {
|
||||
const movedSymbols = new Set<Symbol>();
|
||||
const oldImportsNeededByTargetFile = new Map<Symbol, /*isValidTypeOnlyUseSite*/ boolean>();
|
||||
const targetFileImportsFromOldFile = new Set<Symbol>();
|
||||
@@ -1010,9 +1013,17 @@ export function getUsageInfo(oldFile: SourceFile, toMove: readonly Statement[],
|
||||
movedSymbols.add(Debug.checkDefined(isExpressionStatement(decl) ? checker.getSymbolAtLocation(decl.expression.left) : decl.symbol, "Need a symbol here"));
|
||||
});
|
||||
}
|
||||
|
||||
const unusedImportsFromOldFile = new Set<Symbol>();
|
||||
for (const statement of toMove) {
|
||||
forEachReference(statement, checker, (symbol, isValidTypeOnlyUseSite) => {
|
||||
if (!symbol.declarations) return;
|
||||
if (!symbol.declarations) {
|
||||
return;
|
||||
}
|
||||
if (existingTargetImports.has(skipAlias(symbol, checker))) {
|
||||
unusedImportsFromOldFile.add(symbol);
|
||||
return;
|
||||
}
|
||||
for (const decl of symbol.declarations) {
|
||||
if (isInImport(decl)) {
|
||||
const prevIsTypeOnly = oldImportsNeededByTargetFile.get(symbol);
|
||||
@@ -1024,7 +1035,10 @@ export function getUsageInfo(oldFile: SourceFile, toMove: readonly Statement[],
|
||||
}
|
||||
});
|
||||
}
|
||||
const unusedImportsFromOldFile = new Set(oldImportsNeededByTargetFile.keys());
|
||||
|
||||
for (const unusedImport of oldImportsNeededByTargetFile.keys()) {
|
||||
unusedImportsFromOldFile.add(unusedImport);
|
||||
}
|
||||
|
||||
const oldFileImportsFromTargetFile = new Set<Symbol>();
|
||||
for (const statement of oldFile.statements) {
|
||||
@@ -1228,3 +1242,30 @@ function getOverloadRangeToMove(sourceFile: SourceFile, statement: Statement) {
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function getExistingImports(sourceFile: SourceFile, checker: TypeChecker) {
|
||||
const imports = new Set<Symbol>();
|
||||
for (const moduleSpecifier of sourceFile.imports) {
|
||||
const declaration = importFromModuleSpecifier(moduleSpecifier);
|
||||
if (
|
||||
isImportDeclaration(declaration) && declaration.importClause &&
|
||||
declaration.importClause.namedBindings && isNamedImports(declaration.importClause.namedBindings)
|
||||
) {
|
||||
for (const e of declaration.importClause.namedBindings.elements) {
|
||||
const symbol = checker.getSymbolAtLocation(e.propertyName || e.name);
|
||||
if (symbol) {
|
||||
imports.add(skipAlias(symbol, checker));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isVariableDeclarationInitializedToRequire(declaration.parent) && isObjectBindingPattern(declaration.parent.name)) {
|
||||
for (const e of declaration.parent.name.elements) {
|
||||
const symbol = checker.getSymbolAtLocation(e.propertyName || e.name);
|
||||
if (symbol) {
|
||||
imports.add(skipAlias(symbol, checker));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return imports;
|
||||
}
|
||||
|
||||
@@ -96,7 +96,6 @@ import {
|
||||
LiteralTypeNode,
|
||||
mapDefined,
|
||||
MapLike,
|
||||
ModuleKind,
|
||||
moduleResolutionUsesNodeModules,
|
||||
ModuleSpecifierEnding,
|
||||
moduleSpecifiers,
|
||||
@@ -952,7 +951,7 @@ function getCompletionEntriesForNonRelativeModules(
|
||||
}
|
||||
const keys = getOwnKeys(exports);
|
||||
const fragmentSubpath = components.join("/") + (components.length && hasTrailingDirectorySeparator(fragment) ? "/" : "");
|
||||
const conditions = getConditions(compilerOptions, mode === ModuleKind.ESNext);
|
||||
const conditions = getConditions(compilerOptions, mode);
|
||||
addCompletionEntriesFromPathsOrExports(
|
||||
result,
|
||||
fragmentSubpath,
|
||||
|
||||
@@ -52,9 +52,18 @@ export class FourSlashRunner extends RunnerBase {
|
||||
if (testIndex >= 0) fn = fn.substr(testIndex);
|
||||
|
||||
if (justName !== "fourslash.ts") {
|
||||
it(this.testSuiteName + " test " + justName + " runs correctly", () => {
|
||||
FourSlash.runFourSlashTest(this.basePath, this.testType, fn);
|
||||
let serverLogBaseliner: FourSlash.FourSlashServerLogBaseliner = {};
|
||||
after(() => {
|
||||
serverLogBaseliner = undefined!;
|
||||
});
|
||||
it(this.testSuiteName + " test " + justName + " runs correctly", () => {
|
||||
FourSlash.runFourSlashTest(this.basePath, this.testType, fn, serverLogBaseliner);
|
||||
});
|
||||
if (this.testType === FourSlash.FourSlashTestType.Server) {
|
||||
it(this.testSuiteName + " test " + justName + " tsserver log", () => {
|
||||
serverLogBaseliner.baseline?.();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import * as Harness from "../../_namespaces/Harness";
|
||||
import * as ts from "../../_namespaces/ts";
|
||||
import {
|
||||
jsonToReadableText,
|
||||
} from "../helpers";
|
||||
|
||||
describe("unittests:: config:: commandLineParsing:: parseCommandLine", () => {
|
||||
function assertParseResult(subScenario: string, commandLine: string[], workerDiagnostic?: () => ts.ParseCommandLineWorkerDiagnostics) {
|
||||
@@ -8,9 +11,9 @@ describe("unittests:: config:: commandLineParsing:: parseCommandLine", () => {
|
||||
baseline.push(commandLine.join(" "));
|
||||
const parsed = ts.parseCommandLineWorker(workerDiagnostic?.() || ts.compilerOptionsDidYouMeanDiagnostics, commandLine);
|
||||
baseline.push("CompilerOptions::");
|
||||
baseline.push(JSON.stringify(parsed.options, /*replacer*/ undefined, " "));
|
||||
baseline.push(jsonToReadableText(parsed.options));
|
||||
baseline.push("WatchOptions::");
|
||||
baseline.push(JSON.stringify(parsed.watchOptions, /*replacer*/ undefined, " "));
|
||||
baseline.push(jsonToReadableText(parsed.watchOptions));
|
||||
baseline.push("FileNames::");
|
||||
baseline.push(parsed.fileNames.join());
|
||||
baseline.push("Errors::");
|
||||
@@ -200,9 +203,9 @@ describe("unittests:: config:: commandLineParsing:: parseBuildOptions", () => {
|
||||
baseline.push(commandLine.join(" "));
|
||||
const parsed = ts.parseBuildCommand(commandLine);
|
||||
baseline.push("buildOptions::");
|
||||
baseline.push(JSON.stringify(parsed.buildOptions, /*replacer*/ undefined, " "));
|
||||
baseline.push(jsonToReadableText(parsed.buildOptions));
|
||||
baseline.push("WatchOptions::");
|
||||
baseline.push(JSON.stringify(parsed.watchOptions, /*replacer*/ undefined, " "));
|
||||
baseline.push(jsonToReadableText(parsed.watchOptions));
|
||||
baseline.push("Projects::");
|
||||
baseline.push(parsed.projects.join());
|
||||
baseline.push("Errors::");
|
||||
|
||||
@@ -2,6 +2,9 @@ import * as fakes from "../../_namespaces/fakes";
|
||||
import * as Harness from "../../_namespaces/Harness";
|
||||
import * as ts from "../../_namespaces/ts";
|
||||
import * as vfs from "../../_namespaces/vfs";
|
||||
import {
|
||||
jsonToReadableText,
|
||||
} from "../helpers";
|
||||
import {
|
||||
baselineParseConfig,
|
||||
baselineParseConfigHost,
|
||||
@@ -12,122 +15,122 @@ function createFileSystem(ignoreCase: boolean, cwd: string, root: string) {
|
||||
cwd,
|
||||
files: {
|
||||
[root]: {
|
||||
"dev/node_modules/@foo/tsconfig/package.json": JSON.stringify({
|
||||
"dev/node_modules/@foo/tsconfig/package.json": jsonToReadableText({
|
||||
name: "@foo/tsconfig",
|
||||
version: "1.0.0",
|
||||
exports: {
|
||||
".": "./src/tsconfig.json",
|
||||
},
|
||||
}),
|
||||
"dev/node_modules/@foo/tsconfig/src/tsconfig.json": JSON.stringify({
|
||||
"dev/node_modules/@foo/tsconfig/src/tsconfig.json": jsonToReadableText({
|
||||
compilerOptions: {
|
||||
strict: true,
|
||||
},
|
||||
}),
|
||||
"dev/tsconfig.extendsFoo.json": JSON.stringify({
|
||||
"dev/tsconfig.extendsFoo.json": jsonToReadableText({
|
||||
extends: "@foo/tsconfig",
|
||||
files: [
|
||||
"main.ts",
|
||||
],
|
||||
}),
|
||||
"dev/node_modules/config-box/package.json": JSON.stringify({
|
||||
"dev/node_modules/config-box/package.json": jsonToReadableText({
|
||||
name: "config-box",
|
||||
version: "1.0.0",
|
||||
tsconfig: "./strict.json",
|
||||
}),
|
||||
"dev/node_modules/config-box/strict.json": JSON.stringify({
|
||||
"dev/node_modules/config-box/strict.json": jsonToReadableText({
|
||||
compilerOptions: {
|
||||
strict: true,
|
||||
},
|
||||
}),
|
||||
"dev/node_modules/config-box/unstrict.json": JSON.stringify({
|
||||
"dev/node_modules/config-box/unstrict.json": jsonToReadableText({
|
||||
compilerOptions: {
|
||||
strict: false,
|
||||
},
|
||||
}),
|
||||
"dev/tsconfig.extendsBox.json": JSON.stringify({
|
||||
"dev/tsconfig.extendsBox.json": jsonToReadableText({
|
||||
extends: "config-box",
|
||||
files: [
|
||||
"main.ts",
|
||||
],
|
||||
}),
|
||||
"dev/tsconfig.extendsStrict.json": JSON.stringify({
|
||||
"dev/tsconfig.extendsStrict.json": jsonToReadableText({
|
||||
extends: "config-box/strict",
|
||||
files: [
|
||||
"main.ts",
|
||||
],
|
||||
}),
|
||||
"dev/tsconfig.extendsUnStrict.json": JSON.stringify({
|
||||
"dev/tsconfig.extendsUnStrict.json": jsonToReadableText({
|
||||
extends: "config-box/unstrict",
|
||||
files: [
|
||||
"main.ts",
|
||||
],
|
||||
}),
|
||||
"dev/tsconfig.extendsStrictExtension.json": JSON.stringify({
|
||||
"dev/tsconfig.extendsStrictExtension.json": jsonToReadableText({
|
||||
extends: "config-box/strict.json",
|
||||
files: [
|
||||
"main.ts",
|
||||
],
|
||||
}),
|
||||
"dev/node_modules/config-box-implied/package.json": JSON.stringify({
|
||||
"dev/node_modules/config-box-implied/package.json": jsonToReadableText({
|
||||
name: "config-box-implied",
|
||||
version: "1.0.0",
|
||||
}),
|
||||
"dev/node_modules/config-box-implied/tsconfig.json": JSON.stringify({
|
||||
"dev/node_modules/config-box-implied/tsconfig.json": jsonToReadableText({
|
||||
compilerOptions: {
|
||||
strict: true,
|
||||
},
|
||||
}),
|
||||
"dev/node_modules/config-box-implied/unstrict/tsconfig.json": JSON.stringify({
|
||||
"dev/node_modules/config-box-implied/unstrict/tsconfig.json": jsonToReadableText({
|
||||
compilerOptions: {
|
||||
strict: false,
|
||||
},
|
||||
}),
|
||||
"dev/tsconfig.extendsBoxImplied.json": JSON.stringify({
|
||||
"dev/tsconfig.extendsBoxImplied.json": jsonToReadableText({
|
||||
extends: "config-box-implied",
|
||||
files: [
|
||||
"main.ts",
|
||||
],
|
||||
}),
|
||||
"dev/tsconfig.extendsBoxImpliedUnstrict.json": JSON.stringify({
|
||||
"dev/tsconfig.extendsBoxImpliedUnstrict.json": jsonToReadableText({
|
||||
extends: "config-box-implied/unstrict",
|
||||
files: [
|
||||
"main.ts",
|
||||
],
|
||||
}),
|
||||
"dev/tsconfig.extendsBoxImpliedUnstrictExtension.json": JSON.stringify({
|
||||
"dev/tsconfig.extendsBoxImpliedUnstrictExtension.json": jsonToReadableText({
|
||||
extends: "config-box-implied/unstrict/tsconfig",
|
||||
files: [
|
||||
"main.ts",
|
||||
],
|
||||
}),
|
||||
"dev/tsconfig.extendsBoxImpliedPath.json": JSON.stringify({
|
||||
"dev/tsconfig.extendsBoxImpliedPath.json": jsonToReadableText({
|
||||
extends: "config-box-implied/tsconfig.json",
|
||||
files: [
|
||||
"main.ts",
|
||||
],
|
||||
}),
|
||||
"dev/tsconfig.json": JSON.stringify({
|
||||
"dev/tsconfig.json": jsonToReadableText({
|
||||
extends: "./configs/base",
|
||||
files: [
|
||||
"main.ts",
|
||||
"supplemental.ts",
|
||||
],
|
||||
}),
|
||||
"dev/tsconfig.nostrictnull.json": JSON.stringify({
|
||||
"dev/tsconfig.nostrictnull.json": jsonToReadableText({
|
||||
extends: "./tsconfig",
|
||||
compilerOptions: {
|
||||
strictNullChecks: false,
|
||||
},
|
||||
}),
|
||||
"dev/configs/base.json": JSON.stringify({
|
||||
"dev/configs/base.json": jsonToReadableText({
|
||||
compilerOptions: {
|
||||
allowJs: true,
|
||||
noImplicitAny: true,
|
||||
strictNullChecks: true,
|
||||
},
|
||||
}),
|
||||
"dev/configs/tests.json": JSON.stringify({
|
||||
"dev/configs/tests.json": jsonToReadableText({
|
||||
compilerOptions: {
|
||||
preserveConstEnums: true,
|
||||
removeComments: false,
|
||||
@@ -141,55 +144,55 @@ function createFileSystem(ignoreCase: boolean, cwd: string, root: string) {
|
||||
"../tests/**/*.ts",
|
||||
],
|
||||
}),
|
||||
"dev/circular.json": JSON.stringify({
|
||||
"dev/circular.json": jsonToReadableText({
|
||||
extends: "./circular2",
|
||||
compilerOptions: {
|
||||
module: "amd",
|
||||
},
|
||||
}),
|
||||
"dev/circular2.json": JSON.stringify({
|
||||
"dev/circular2.json": jsonToReadableText({
|
||||
extends: "./circular",
|
||||
compilerOptions: {
|
||||
module: "commonjs",
|
||||
},
|
||||
}),
|
||||
"dev/missing.json": JSON.stringify({
|
||||
"dev/missing.json": jsonToReadableText({
|
||||
extends: "./missing2",
|
||||
compilerOptions: {
|
||||
types: [],
|
||||
},
|
||||
}),
|
||||
"dev/failure.json": JSON.stringify({
|
||||
"dev/failure.json": jsonToReadableText({
|
||||
extends: "./failure2.json",
|
||||
compilerOptions: {
|
||||
typeRoots: [],
|
||||
},
|
||||
}),
|
||||
"dev/failure2.json": JSON.stringify({
|
||||
"dev/failure2.json": jsonToReadableText({
|
||||
excludes: ["*.js"],
|
||||
}),
|
||||
"dev/configs/first.json": JSON.stringify({
|
||||
"dev/configs/first.json": jsonToReadableText({
|
||||
extends: "./base",
|
||||
compilerOptions: {
|
||||
module: "commonjs",
|
||||
},
|
||||
files: ["../main.ts"],
|
||||
}),
|
||||
"dev/configs/second.json": JSON.stringify({
|
||||
"dev/configs/second.json": jsonToReadableText({
|
||||
extends: "./base",
|
||||
compilerOptions: {
|
||||
module: "amd",
|
||||
},
|
||||
include: ["../supplemental.*"],
|
||||
}),
|
||||
"dev/configs/third.json": JSON.stringify({
|
||||
"dev/configs/third.json": jsonToReadableText({
|
||||
extends: "./second",
|
||||
compilerOptions: {
|
||||
module: null, // eslint-disable-line no-null/no-null
|
||||
},
|
||||
include: ["../supplemental.*"],
|
||||
}),
|
||||
"dev/configs/fourth.json": JSON.stringify({
|
||||
"dev/configs/fourth.json": jsonToReadableText({
|
||||
extends: "./third",
|
||||
compilerOptions: {
|
||||
module: "system",
|
||||
@@ -197,35 +200,35 @@ function createFileSystem(ignoreCase: boolean, cwd: string, root: string) {
|
||||
include: null, // eslint-disable-line no-null/no-null
|
||||
files: ["../main.ts"],
|
||||
}),
|
||||
"dev/configs/fifth.json": JSON.stringify({
|
||||
"dev/configs/fifth.json": jsonToReadableText({
|
||||
extends: "./fourth",
|
||||
include: ["../tests/utils.ts"],
|
||||
files: [],
|
||||
}),
|
||||
"dev/extends.json": JSON.stringify({ extends: 42 }),
|
||||
"dev/extends2.json": JSON.stringify({ extends: "configs/base" }),
|
||||
"dev/extends3.json": JSON.stringify({ extends: "" }),
|
||||
"dev/extends4.json": JSON.stringify({ extends: [""] }),
|
||||
"dev/extends.json": jsonToReadableText({ extends: 42 }),
|
||||
"dev/extends2.json": jsonToReadableText({ extends: "configs/base" }),
|
||||
"dev/extends3.json": jsonToReadableText({ extends: "" }),
|
||||
"dev/extends4.json": jsonToReadableText({ extends: [""] }),
|
||||
"dev/main.ts": "",
|
||||
"dev/supplemental.ts": "",
|
||||
"dev/tests/unit/spec.ts": "",
|
||||
"dev/tests/utils.ts": "",
|
||||
"dev/tests/scenarios/first.json": "",
|
||||
"dev/tests/baselines/first/output.ts": "",
|
||||
"dev/configs/extendsArrayFirst.json": JSON.stringify({
|
||||
"dev/configs/extendsArrayFirst.json": jsonToReadableText({
|
||||
compilerOptions: {
|
||||
allowJs: true,
|
||||
noImplicitAny: true,
|
||||
strictNullChecks: true,
|
||||
},
|
||||
}),
|
||||
"dev/configs/extendsArraySecond.json": JSON.stringify({
|
||||
"dev/configs/extendsArraySecond.json": jsonToReadableText({
|
||||
compilerOptions: {
|
||||
module: "amd",
|
||||
},
|
||||
include: ["../supplemental.*"],
|
||||
}),
|
||||
"dev/configs/extendsArrayThird.json": JSON.stringify({
|
||||
"dev/configs/extendsArrayThird.json": jsonToReadableText({
|
||||
compilerOptions: {
|
||||
module: null, // eslint-disable-line no-null/no-null
|
||||
noImplicitAny: false,
|
||||
@@ -233,7 +236,7 @@ function createFileSystem(ignoreCase: boolean, cwd: string, root: string) {
|
||||
extends: "./extendsArrayFirst",
|
||||
include: ["../supplemental.*"],
|
||||
}),
|
||||
"dev/configs/extendsArrayFourth.json": JSON.stringify({
|
||||
"dev/configs/extendsArrayFourth.json": jsonToReadableText({
|
||||
compilerOptions: {
|
||||
module: "system",
|
||||
strictNullChecks: false,
|
||||
@@ -241,17 +244,17 @@ function createFileSystem(ignoreCase: boolean, cwd: string, root: string) {
|
||||
include: null, // eslint-disable-line no-null/no-null
|
||||
files: ["../main.ts"],
|
||||
}),
|
||||
"dev/configs/extendsArrayFifth.json": JSON.stringify({
|
||||
"dev/configs/extendsArrayFifth.json": jsonToReadableText({
|
||||
extends: ["./extendsArrayFirst", "./extendsArraySecond", "./extendsArrayThird", "./extendsArrayFourth"],
|
||||
files: [],
|
||||
}),
|
||||
"dev/extendsArrayFails.json": JSON.stringify({
|
||||
"dev/extendsArrayFails.json": jsonToReadableText({
|
||||
extends: ["./missingFile"],
|
||||
compilerOptions: {
|
||||
types: [],
|
||||
},
|
||||
}),
|
||||
"dev/extendsArrayFails2.json": JSON.stringify({ extends: [42] }),
|
||||
"dev/extendsArrayFails2.json": jsonToReadableText({ extends: [42] }),
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -314,7 +317,7 @@ describe("unittests:: config:: configurationExtension", () => {
|
||||
configFileName: entry,
|
||||
baselineParsed: (baseline, parsed) => {
|
||||
baseline.push("CompilerOptions::");
|
||||
baseline.push(JSON.stringify(parsed.options, undefined, " "));
|
||||
baseline.push(jsonToReadableText(parsed.options));
|
||||
baseline.push("FileNames::");
|
||||
baseline.push(parsed.fileNames.join());
|
||||
},
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import * as fakes from "../../_namespaces/fakes";
|
||||
import * as vfs from "../../_namespaces/vfs";
|
||||
import {
|
||||
jsonToReadableText,
|
||||
} from "../helpers";
|
||||
import {
|
||||
baselineParseConfig,
|
||||
} from "./helpers";
|
||||
@@ -8,7 +11,7 @@ describe("unittests:: config:: convertCompilerOptionsFromJson", () => {
|
||||
function baselineCompilerOptions(subScenario: string, json: any, configFileName: string) {
|
||||
baselineCompilerOptionsJsonText(
|
||||
subScenario,
|
||||
JSON.stringify(json, undefined, " "),
|
||||
jsonToReadableText(json),
|
||||
configFileName,
|
||||
/*skipJson*/ false,
|
||||
);
|
||||
@@ -36,7 +39,7 @@ describe("unittests:: config:: convertCompilerOptionsFromJson", () => {
|
||||
jsonText,
|
||||
configFileName,
|
||||
basePath: "/apath",
|
||||
baselineParsed: (baseline, parsed) => baseline.push("CompilerOptions::", JSON.stringify(parsed.options, undefined, " ")),
|
||||
baselineParsed: (baseline, parsed) => baseline.push("CompilerOptions::", jsonToReadableText(parsed.options)),
|
||||
}],
|
||||
skipJson,
|
||||
});
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import * as fakes from "../../_namespaces/fakes";
|
||||
import * as vfs from "../../_namespaces/vfs";
|
||||
import {
|
||||
jsonToReadableText,
|
||||
} from "../helpers";
|
||||
import {
|
||||
baselineParseConfig,
|
||||
} from "./helpers";
|
||||
@@ -10,7 +13,7 @@ describe("unittests:: config:: convertTypeAcquisitionFromJson", () => {
|
||||
scenario: "convertTypeAcquisitionFromJson",
|
||||
subScenario,
|
||||
input: () => {
|
||||
const jsonText = JSON.stringify(json, undefined, " ");
|
||||
const jsonText = jsonToReadableText(json);
|
||||
return [{
|
||||
createHost: () =>
|
||||
new fakes.ParseConfigHost(
|
||||
@@ -29,7 +32,7 @@ describe("unittests:: config:: convertTypeAcquisitionFromJson", () => {
|
||||
jsonText,
|
||||
configFileName,
|
||||
basePath: "/apath",
|
||||
baselineParsed: (baseline, parsed) => baseline.push("TypeAcquisition::", JSON.stringify(parsed.typeAcquisition, undefined, " ")),
|
||||
baselineParsed: (baseline, parsed) => baseline.push("TypeAcquisition::", jsonToReadableText(parsed.typeAcquisition)),
|
||||
}];
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import * as fakes from "../../_namespaces/fakes";
|
||||
import * as ts from "../../_namespaces/ts";
|
||||
import * as vfs from "../../_namespaces/vfs";
|
||||
import {
|
||||
jsonToReadableText,
|
||||
} from "../helpers";
|
||||
import {
|
||||
baselineParseConfig,
|
||||
} from "./helpers";
|
||||
@@ -162,7 +165,7 @@ const caseInsensitiveHostWithSameFileNamesWithDifferentExtensions = new fakes.Pa
|
||||
);
|
||||
|
||||
function baselineMatches(subScenario: string, json: any, host: fakes.ParseConfigHost, basePath: string) {
|
||||
const jsonText = JSON.stringify(json, undefined, " ");
|
||||
const jsonText = jsonToReadableText(json);
|
||||
baselineParseConfig({
|
||||
scenario: "matchFiles",
|
||||
subScenario,
|
||||
@@ -176,15 +179,11 @@ function baselineMatches(subScenario: string, json: any, host: fakes.ParseConfig
|
||||
if (parsed.wildcardDirectories) ts.getOwnKeys(parsed.wildcardDirectories).forEach(dir => wildcardDirectories![dir] = `WatchDirectoryFlags.${(ts as any).WatchDirectoryFlags[parsed.wildcardDirectories![dir]]}`);
|
||||
baseline.push(
|
||||
"Result",
|
||||
JSON.stringify(
|
||||
{
|
||||
...parsed,
|
||||
errors: undefined,
|
||||
wildcardDirectories,
|
||||
},
|
||||
undefined,
|
||||
" ",
|
||||
),
|
||||
jsonToReadableText({
|
||||
...parsed,
|
||||
errors: undefined,
|
||||
wildcardDirectories,
|
||||
}),
|
||||
);
|
||||
},
|
||||
}],
|
||||
|
||||
@@ -2,6 +2,9 @@ import * as fakes from "../../_namespaces/fakes";
|
||||
import * as Harness from "../../_namespaces/Harness";
|
||||
import * as ts from "../../_namespaces/ts";
|
||||
import * as vfs from "../../_namespaces/vfs";
|
||||
import {
|
||||
jsonToReadableText,
|
||||
} from "../helpers";
|
||||
import {
|
||||
baselineParseConfig,
|
||||
} from "./helpers";
|
||||
@@ -20,7 +23,7 @@ describe("unittests:: config:: tsconfigParsing:: parseConfigFileTextToJson", ()
|
||||
for (const jsonText of jsonTexts()) {
|
||||
baseline.push("Input::", jsonText);
|
||||
const parsed = ts.parseConfigFileTextToJson("/apath/tsconfig.json", jsonText);
|
||||
baseline.push("Config::", JSON.stringify(parsed.config, /*replacer*/ undefined, " "));
|
||||
baseline.push("Config::", jsonToReadableText(parsed.config));
|
||||
baseline.push("Errors::");
|
||||
baseline.push(formatErrors(parsed.error ? [parsed.error] : ts.emptyArray));
|
||||
baseline.push("");
|
||||
@@ -232,7 +235,7 @@ describe("unittests:: config:: tsconfigParsing:: parseConfigFileTextToJson", ()
|
||||
baseline.push("Initial::", content);
|
||||
const result = ts.parseJsonText("config.json", content);
|
||||
const configJsonObject = ts.convertToObject(result, result.parseDiagnostics);
|
||||
baseline.push("Result::", JSON.stringify(configJsonObject, undefined, " "));
|
||||
baseline.push("Result::", jsonToReadableText(configJsonObject));
|
||||
baseline.push("Errors::", formatErrors(result.parseDiagnostics));
|
||||
Harness.Baseline.runBaseline(`config/tsconfigParsing/parse and re-emit tsconfig.json file with diagnostics.js`, baseline.join("\n"));
|
||||
});
|
||||
@@ -320,7 +323,7 @@ describe("unittests:: config:: tsconfigParsing:: parseConfigFileTextToJson", ()
|
||||
}], /*skipJson*/ true);
|
||||
|
||||
baselinedParsed("generates errors when files is not string", () => [{
|
||||
jsonText: JSON.stringify({
|
||||
jsonText: jsonToReadableText({
|
||||
files: [{
|
||||
compilerOptions: {
|
||||
experimentalDecorators: true,
|
||||
@@ -334,7 +337,7 @@ describe("unittests:: config:: tsconfigParsing:: parseConfigFileTextToJson", ()
|
||||
}]);
|
||||
|
||||
baselinedParsed("generates errors when include is not string", () => [{
|
||||
jsonText: JSON.stringify({
|
||||
jsonText: jsonToReadableText({
|
||||
include: [
|
||||
["./**/*.ts"],
|
||||
],
|
||||
@@ -345,7 +348,7 @@ describe("unittests:: config:: tsconfigParsing:: parseConfigFileTextToJson", ()
|
||||
}]);
|
||||
|
||||
baselinedParsed("generates errors when commandline option is in tsconfig", () => [{
|
||||
jsonText: JSON.stringify({
|
||||
jsonText: jsonToReadableText({
|
||||
compilerOptions: {
|
||||
help: true,
|
||||
},
|
||||
@@ -382,7 +385,7 @@ describe("unittests:: config:: tsconfigParsing:: parseConfigFileTextToJson", ()
|
||||
|
||||
baselineWildcards("parses wildcard directories even when parent directories have dots", () => [{
|
||||
configFileName: "/foo.bar/tsconfig.json",
|
||||
jsonText: JSON.stringify({
|
||||
jsonText: jsonToReadableText({
|
||||
include: ["src"],
|
||||
}),
|
||||
basePath: "/foo.bar",
|
||||
@@ -390,7 +393,7 @@ describe("unittests:: config:: tsconfigParsing:: parseConfigFileTextToJson", ()
|
||||
|
||||
baselineWildcards("correctly parses wild card directories from implicit glob when two keys differ only in directory seperator", () => [{
|
||||
configFileName: "/foo.bar/tsconfig.json",
|
||||
jsonText: JSON.stringify({
|
||||
jsonText: jsonToReadableText({
|
||||
include: ["./", "./**/*.json"],
|
||||
}),
|
||||
basePath: "/foo",
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import * as fakes from "../../_namespaces/fakes";
|
||||
import * as ts from "../../_namespaces/ts";
|
||||
import * as vfs from "../../_namespaces/vfs";
|
||||
import {
|
||||
jsonToReadableText,
|
||||
} from "../helpers";
|
||||
import {
|
||||
baselineParseConfig,
|
||||
} from "./helpers";
|
||||
@@ -18,7 +21,7 @@ describe("unittests:: config:: tsconfigParsingWatchOptions:: parseConfigFileText
|
||||
subScenario,
|
||||
input: () =>
|
||||
scenario().map(({ json, additionalFiles, existingWatchOptions }) => {
|
||||
const jsonText = JSON.stringify(json, undefined, " ");
|
||||
const jsonText = jsonToReadableText(json);
|
||||
return {
|
||||
createHost: () =>
|
||||
new fakes.ParseConfigHost(
|
||||
@@ -39,7 +42,7 @@ describe("unittests:: config:: tsconfigParsingWatchOptions:: parseConfigFileText
|
||||
existingWatchOptions,
|
||||
baselineParsed: (baseline, parsed) => {
|
||||
baseline.push(`Result: WatchOptions::`);
|
||||
baseline.push(JSON.stringify(parsed.watchOptions, undefined, " "));
|
||||
baseline.push(jsonToReadableText(parsed.watchOptions));
|
||||
},
|
||||
};
|
||||
}),
|
||||
@@ -77,7 +80,7 @@ describe("unittests:: config:: tsconfigParsingWatchOptions:: parseConfigFileText
|
||||
},
|
||||
},
|
||||
additionalFiles: {
|
||||
"/base.json": JSON.stringify({
|
||||
"/base.json": jsonToReadableText({
|
||||
watchOptions: {
|
||||
watchFile: "UseFsEventsOnParentDirectory",
|
||||
watchDirectory: "FixedPollingInterval",
|
||||
@@ -90,7 +93,7 @@ describe("unittests:: config:: tsconfigParsingWatchOptions:: parseConfigFileText
|
||||
extends: "./base.json",
|
||||
},
|
||||
additionalFiles: {
|
||||
"/base.json": JSON.stringify({
|
||||
"/base.json": jsonToReadableText({
|
||||
watchOptions: {
|
||||
watchFile: "UseFsEventsOnParentDirectory",
|
||||
watchDirectory: "FixedPollingInterval",
|
||||
|
||||
@@ -62,4 +62,25 @@ describe("unittests:: evaluation:: asyncGeneratorEvaluation", () => {
|
||||
{ done: true, value: undefined },
|
||||
]);
|
||||
});
|
||||
it("pass promise to gen.return()", async () => {
|
||||
const result = evaluator.evaluateTypeScript(
|
||||
`
|
||||
export const output = [];
|
||||
async function* fn() {
|
||||
yield* [1]
|
||||
return 3
|
||||
}
|
||||
export async function main() {
|
||||
const it = fn();
|
||||
output.push(await it.next());
|
||||
output.push(await it.return(Promise.resolve(2)));
|
||||
}`,
|
||||
{ target: ts.ScriptTarget.ES2017 },
|
||||
);
|
||||
await result.main();
|
||||
assert.deepEqual(result.output, [
|
||||
{ done: false, value: 1 },
|
||||
{ done: true, value: 2 },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -167,3 +167,7 @@ export function updateProgramText(files: readonly NamedSourceText[], fileName: s
|
||||
const file = ts.find(files, f => f.name === fileName)!;
|
||||
file.text = file.text.updateProgram(newProgramText);
|
||||
}
|
||||
|
||||
export function jsonToReadableText(json: any) {
|
||||
return JSON.stringify(json, undefined, 2);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import * as fakes from "../../_namespaces/fakes";
|
||||
import * as Harness from "../../_namespaces/Harness";
|
||||
import * as ts from "../../_namespaces/ts";
|
||||
import {
|
||||
jsonToReadableText,
|
||||
} from "../helpers";
|
||||
import {
|
||||
TscCompileSystem,
|
||||
} from "./tsc";
|
||||
@@ -54,8 +57,8 @@ export function baselinePrograms(baseline: string[], programs: readonly CommandL
|
||||
function baselineProgram(baseline: string[], [program, builderProgram]: CommandLineProgram, oldProgram: CommandLineProgram | undefined, baselineDependencies: boolean | undefined) {
|
||||
if (program !== oldProgram?.[0]) {
|
||||
const options = program.getCompilerOptions();
|
||||
baseline.push(`Program root files: ${JSON.stringify(program.getRootFileNames())}`);
|
||||
baseline.push(`Program options: ${JSON.stringify(options)}`);
|
||||
baseline.push(`Program root files: ${jsonToReadableText(program.getRootFileNames())}`);
|
||||
baseline.push(`Program options: ${jsonToReadableText(options)}`);
|
||||
baseline.push(`Program structureReused: ${(ts as any).StructureIsReused[program.structureIsReused]}`);
|
||||
baseline.push("Program files::");
|
||||
for (const file of program.getSourceFiles()) {
|
||||
@@ -275,7 +278,7 @@ function generateBuildInfoProgramBaseline(sys: ts.System, buildInfoPath: string,
|
||||
size: ts.getBuildInfoText({ ...buildInfo, version }).length,
|
||||
};
|
||||
// For now its just JSON.stringify
|
||||
sys.writeFile(`${buildInfoPath}.readable.baseline.txt`, JSON.stringify(result, /*replacer*/ undefined, 2));
|
||||
sys.writeFile(`${buildInfoPath}.readable.baseline.txt`, jsonToReadableText(result));
|
||||
|
||||
function toFileName(fileId: ts.ProgramBuildInfoFileId) {
|
||||
return buildInfo.program!.fileNames[fileId - 1];
|
||||
|
||||
@@ -25,3 +25,7 @@ interface Symbol {
|
||||
export interface FsContents {
|
||||
[path: string]: string;
|
||||
}
|
||||
|
||||
export function libPath(forLib: string) {
|
||||
return `${ts.getDirectoryPath(libFile.path)}/lib.${forLib}.d.ts`;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
import {
|
||||
dedent,
|
||||
} from "../../_namespaces/Utils";
|
||||
import {
|
||||
jsonToReadableText,
|
||||
} from "../helpers";
|
||||
import {
|
||||
FsContents,
|
||||
libContent,
|
||||
} from "./contents";
|
||||
import {
|
||||
loadProjectFromFiles,
|
||||
} from "./vfs";
|
||||
import {
|
||||
createWatchedSystem,
|
||||
libFile,
|
||||
} from "./virtualFileSystemWithWatch";
|
||||
|
||||
export function getFsContentsForDemoProjectReferencesCoreConfig(additional?: object) {
|
||||
return jsonToReadableText({
|
||||
extends: "../tsconfig-base.json",
|
||||
compilerOptions: {
|
||||
outDir: "../lib/core",
|
||||
rootDir: ".",
|
||||
},
|
||||
...additional,
|
||||
});
|
||||
}
|
||||
export function getFsContentsForDemoProjectReferences(): FsContents {
|
||||
return {
|
||||
"/user/username/projects/demo/animals/animal.ts": dedent`
|
||||
export type Size = "small" | "medium" | "large";
|
||||
export default interface Animal {
|
||||
size: Size;
|
||||
}
|
||||
`,
|
||||
"/user/username/projects/demo/animals/dog.ts": dedent`
|
||||
import Animal from '.';
|
||||
import { makeRandomName } from '../core/utilities';
|
||||
|
||||
export interface Dog extends Animal {
|
||||
woof(): void;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export function createDog(): Dog {
|
||||
return ({
|
||||
size: "medium",
|
||||
woof: function(this: Dog) {
|
||||
console.log(\`\${ this.name } says "Woof"!\`);
|
||||
},
|
||||
name: makeRandomName()
|
||||
});
|
||||
}
|
||||
`,
|
||||
"/user/username/projects/demo/animals/index.ts": dedent`
|
||||
import Animal from './animal';
|
||||
|
||||
export default Animal;
|
||||
import { createDog, Dog } from './dog';
|
||||
export { createDog, Dog };
|
||||
`,
|
||||
"/user/username/projects/demo/animals/tsconfig.json": jsonToReadableText({
|
||||
extends: "../tsconfig-base.json",
|
||||
compilerOptions: {
|
||||
outDir: "../lib/animals",
|
||||
rootDir: ".",
|
||||
},
|
||||
references: [
|
||||
{ path: "../core" },
|
||||
],
|
||||
}),
|
||||
"/user/username/projects/demo/core/utilities.ts": dedent`
|
||||
|
||||
export function makeRandomName() {
|
||||
return "Bob!?! ";
|
||||
}
|
||||
|
||||
export function lastElementOf<T>(arr: T[]): T | undefined {
|
||||
if (arr.length === 0) return undefined;
|
||||
return arr[arr.length - 1];
|
||||
}
|
||||
`,
|
||||
"/user/username/projects/demo/core/tsconfig.json": getFsContentsForDemoProjectReferencesCoreConfig(),
|
||||
"/user/username/projects/demo/zoo/zoo.ts": dedent`
|
||||
import { Dog, createDog } from '../animals/index';
|
||||
|
||||
export function createZoo(): Array<Dog> {
|
||||
return [
|
||||
createDog()
|
||||
];
|
||||
}
|
||||
`,
|
||||
"/user/username/projects/demo/zoo/tsconfig.json": jsonToReadableText({
|
||||
extends: "../tsconfig-base.json",
|
||||
compilerOptions: {
|
||||
outDir: "../lib/zoo",
|
||||
rootDir: ".",
|
||||
},
|
||||
references: [
|
||||
{
|
||||
path: "../animals",
|
||||
},
|
||||
],
|
||||
}),
|
||||
"/user/username/projects/demo/tsconfig-base.json": jsonToReadableText({
|
||||
compilerOptions: {
|
||||
declaration: true,
|
||||
target: "es5",
|
||||
module: "commonjs",
|
||||
strict: true,
|
||||
noUnusedLocals: true,
|
||||
noUnusedParameters: true,
|
||||
noImplicitReturns: true,
|
||||
noFallthroughCasesInSwitch: true,
|
||||
composite: true,
|
||||
},
|
||||
}),
|
||||
"/user/username/projects/demo/tsconfig.json": jsonToReadableText({
|
||||
files: [],
|
||||
references: [
|
||||
{
|
||||
path: "./core",
|
||||
},
|
||||
{
|
||||
path: "./animals",
|
||||
},
|
||||
{
|
||||
path: "./zoo",
|
||||
},
|
||||
],
|
||||
}),
|
||||
[libFile.path]: libContent,
|
||||
};
|
||||
}
|
||||
|
||||
export function getFsForDemoProjectReferences() {
|
||||
return loadProjectFromFiles(
|
||||
getFsContentsForDemoProjectReferences(),
|
||||
{
|
||||
cwd: "/user/username/projects/demo",
|
||||
executingFilePath: libFile.path,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export function getSysForDemoProjectReferences() {
|
||||
return createWatchedSystem(
|
||||
getFsContentsForDemoProjectReferences(),
|
||||
{
|
||||
currentDirectory: "/user/username/projects/demo",
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
import {
|
||||
dedent,
|
||||
} from "../../_namespaces/Utils";
|
||||
import {
|
||||
jsonToReadableText,
|
||||
} from "../helpers";
|
||||
import {
|
||||
createServerHost,
|
||||
createWatchedSystem,
|
||||
@@ -10,20 +13,20 @@ import {
|
||||
|
||||
export function getSymlinkedExtendsSys(forTsserver?: true): TestServerHost {
|
||||
return (!forTsserver ? createWatchedSystem : createServerHost)({
|
||||
"/users/user/projects/myconfigs/node_modules/@something/tsconfig-node/tsconfig.json": JSON.stringify({
|
||||
"/users/user/projects/myconfigs/node_modules/@something/tsconfig-node/tsconfig.json": jsonToReadableText({
|
||||
extends: "@something/tsconfig-base/tsconfig.json",
|
||||
compilerOptions: {
|
||||
removeComments: true,
|
||||
},
|
||||
}),
|
||||
"/users/user/projects/myconfigs/node_modules/@something/tsconfig-base/tsconfig.json": JSON.stringify({
|
||||
"/users/user/projects/myconfigs/node_modules/@something/tsconfig-base/tsconfig.json": jsonToReadableText({
|
||||
compilerOptions: { composite: true },
|
||||
}),
|
||||
"/users/user/projects/myproject/src/index.ts": dedent`
|
||||
// some comment
|
||||
export const x = 10;
|
||||
`,
|
||||
"/users/user/projects/myproject/src/tsconfig.json": JSON.stringify({
|
||||
"/users/user/projects/myproject/src/tsconfig.json": jsonToReadableText({
|
||||
extends: "@something/tsconfig-node/tsconfig.json",
|
||||
}),
|
||||
"/users/user/projects/myproject/node_modules/@something/tsconfig-node": {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import {
|
||||
dedent,
|
||||
} from "../../_namespaces/Utils";
|
||||
import {
|
||||
jsonToReadableText,
|
||||
} from "../helpers";
|
||||
import {
|
||||
FsContents,
|
||||
libContent,
|
||||
@@ -24,23 +27,23 @@ function getFsContentsForLibResolution(libRedirection?: boolean): FsContents {
|
||||
/// <reference lib="scripthost"/>
|
||||
/// <reference lib="es5"/>
|
||||
`,
|
||||
"/home/src/projects/project1/tsconfig.json": JSON.stringify({
|
||||
"/home/src/projects/project1/tsconfig.json": jsonToReadableText({
|
||||
compilerOptions: { composite: true, typeRoots: ["./typeroot1"], lib: ["es5", "dom"], traceResolution: true },
|
||||
}),
|
||||
"/home/src/projects/project1/typeroot1/sometype/index.d.ts": `export type TheNum = "type1";`,
|
||||
"/home/src/projects/project2/utils.d.ts": `export const y = 10;`,
|
||||
"/home/src/projects/project2/index.ts": `export const y = 10`,
|
||||
"/home/src/projects/project2/tsconfig.json": JSON.stringify({
|
||||
"/home/src/projects/project2/tsconfig.json": jsonToReadableText({
|
||||
compilerOptions: { composite: true, lib: ["es5", "dom"], traceResolution: true },
|
||||
}),
|
||||
"/home/src/projects/project3/utils.d.ts": `export const y = 10;`,
|
||||
"/home/src/projects/project3/index.ts": `export const z = 10`,
|
||||
"/home/src/projects/project3/tsconfig.json": JSON.stringify({
|
||||
"/home/src/projects/project3/tsconfig.json": jsonToReadableText({
|
||||
compilerOptions: { composite: true, lib: ["es5", "dom"], traceResolution: true },
|
||||
}),
|
||||
"/home/src/projects/project4/utils.d.ts": `export const y = 10;`,
|
||||
"/home/src/projects/project4/index.ts": `export const z = 10`,
|
||||
"/home/src/projects/project4/tsconfig.json": JSON.stringify({
|
||||
"/home/src/projects/project4/tsconfig.json": jsonToReadableText({
|
||||
compilerOptions: { composite: true, lib: ["esnext", "dom", "webworker"], traceResolution: true },
|
||||
}),
|
||||
"/home/src/lib/lib.es5.d.ts": libContent,
|
||||
@@ -79,7 +82,7 @@ export function getSysForLibResolution(libRedirection?: true) {
|
||||
);
|
||||
}
|
||||
|
||||
export function getServerHosForLibResolution(libRedirection?: true) {
|
||||
export function getServerHostForLibResolution(libRedirection?: true) {
|
||||
return createServerHost(
|
||||
getFsContentsForLibResolution(libRedirection),
|
||||
{
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import {
|
||||
dedent,
|
||||
} from "../../_namespaces/Utils";
|
||||
import {
|
||||
jsonToReadableText,
|
||||
} from "../helpers";
|
||||
import {
|
||||
FsContents,
|
||||
libContent,
|
||||
} from "./contents";
|
||||
import {
|
||||
loadProjectFromFiles,
|
||||
} from "./vfs";
|
||||
import {
|
||||
createWatchedSystem,
|
||||
libFile,
|
||||
} from "./virtualFileSystemWithWatch";
|
||||
|
||||
export function getFsContentsForNoEmitOnError(): FsContents {
|
||||
return {
|
||||
"/user/username/projects/noEmitOnError/tsconfig.json": jsonToReadableText({
|
||||
compilerOptions: {
|
||||
outDir: "./dev-build",
|
||||
noEmitOnError: true,
|
||||
},
|
||||
}),
|
||||
"/user/username/projects/noEmitOnError/shared/types/db.ts": dedent`
|
||||
export interface A {
|
||||
name: string;
|
||||
}
|
||||
`,
|
||||
"/user/username/projects/noEmitOnError/src/main.ts": dedent`
|
||||
import { A } from "../shared/types/db";
|
||||
const a = {
|
||||
lastName: 'sdsd'
|
||||
;
|
||||
`,
|
||||
"/user/username/projects/noEmitOnError/src/other.ts": dedent`
|
||||
console.log("hi");
|
||||
export { }
|
||||
`,
|
||||
[libFile.path]: libContent,
|
||||
};
|
||||
}
|
||||
|
||||
export function getFsForNoEmitOnError() {
|
||||
return loadProjectFromFiles(
|
||||
getFsContentsForNoEmitOnError(),
|
||||
{
|
||||
cwd: "/user/username/projects/noEmitOnError",
|
||||
executingFilePath: libFile.path,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export function getSysForNoEmitOnError() {
|
||||
return createWatchedSystem(
|
||||
getFsContentsForNoEmitOnError(),
|
||||
{
|
||||
currentDirectory: "/user/username/projects/noEmitOnError",
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
import {
|
||||
dedent,
|
||||
} from "../../_namespaces/Utils";
|
||||
import {
|
||||
jsonToReadableText,
|
||||
} from "../helpers";
|
||||
import {
|
||||
FsContents,
|
||||
} from "./contents";
|
||||
@@ -9,41 +12,33 @@ import {
|
||||
} from "./virtualFileSystemWithWatch";
|
||||
|
||||
export function getFsConentsForNode10ResultAtTypesPackageJson(packageName: string, addTypesCondition: boolean) {
|
||||
return JSON.stringify(
|
||||
{
|
||||
name: `@types/${packageName}`,
|
||||
version: "1.0.0",
|
||||
types: "index.d.ts",
|
||||
exports: {
|
||||
".": {
|
||||
...(addTypesCondition ? { types: "./index.d.ts" } : {}),
|
||||
require: "./index.d.ts",
|
||||
},
|
||||
return jsonToReadableText({
|
||||
name: `@types/${packageName}`,
|
||||
version: "1.0.0",
|
||||
types: "index.d.ts",
|
||||
exports: {
|
||||
".": {
|
||||
...(addTypesCondition ? { types: "./index.d.ts" } : {}),
|
||||
require: "./index.d.ts",
|
||||
},
|
||||
},
|
||||
undefined,
|
||||
" ",
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function getFsContentsForNode10ResultPackageJson(packageName: string, addTypes: boolean, addTypesCondition: boolean) {
|
||||
return JSON.stringify(
|
||||
{
|
||||
name: packageName,
|
||||
version: "1.0.0",
|
||||
main: "index.js",
|
||||
...(addTypes ? { types: "index.d.ts" } : {}),
|
||||
exports: {
|
||||
".": {
|
||||
...(addTypesCondition ? { types: "./index.d.ts" } : {}),
|
||||
import: "./index.mjs",
|
||||
require: "./index.js",
|
||||
},
|
||||
return jsonToReadableText({
|
||||
name: packageName,
|
||||
version: "1.0.0",
|
||||
main: "index.js",
|
||||
...(addTypes ? { types: "index.d.ts" } : {}),
|
||||
exports: {
|
||||
".": {
|
||||
...(addTypesCondition ? { types: "./index.d.ts" } : {}),
|
||||
import: "./index.mjs",
|
||||
require: "./index.js",
|
||||
},
|
||||
},
|
||||
undefined,
|
||||
" ",
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function getFsContentsForNode10ResultDts(packageName: string) {
|
||||
@@ -84,7 +79,7 @@ export function getFsContentsForNode10Result(): FsContents {
|
||||
import { foo2 } from "foo2";
|
||||
import { bar2 } from "bar2";
|
||||
`,
|
||||
"/home/src/projects/project/tsconfig.json": JSON.stringify({
|
||||
"/home/src/projects/project/tsconfig.json": jsonToReadableText({
|
||||
compilerOptions: {
|
||||
moduleResolution: "node16",
|
||||
traceResolution: true,
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
import {
|
||||
dedent,
|
||||
} from "../../_namespaces/Utils";
|
||||
import {
|
||||
jsonToReadableText,
|
||||
} from "../helpers";
|
||||
import {
|
||||
FsContents,
|
||||
} from "./contents";
|
||||
import {
|
||||
loadProjectFromFiles,
|
||||
} from "./vfs";
|
||||
import {
|
||||
createServerHost,
|
||||
createWatchedSystem,
|
||||
libFile,
|
||||
} from "./virtualFileSystemWithWatch";
|
||||
|
||||
export function getFsContentsForSampleProjectReferencesLogicConfig() {
|
||||
return jsonToReadableText({
|
||||
compilerOptions: {
|
||||
composite: true,
|
||||
declaration: true,
|
||||
sourceMap: true,
|
||||
forceConsistentCasingInFileNames: true,
|
||||
skipDefaultLibCheck: true,
|
||||
},
|
||||
references: [
|
||||
{ path: "../core" },
|
||||
],
|
||||
});
|
||||
}
|
||||
export function getFsContentsForSampleProjectReferences(): FsContents {
|
||||
return {
|
||||
[libFile.path]: libFile.content,
|
||||
"/user/username/projects/sample1/core/tsconfig.json": jsonToReadableText({
|
||||
compilerOptions: {
|
||||
composite: true,
|
||||
declaration: true,
|
||||
declarationMap: true,
|
||||
skipDefaultLibCheck: true,
|
||||
},
|
||||
}),
|
||||
"/user/username/projects/sample1/core/index.ts": dedent`
|
||||
export const someString: string = "HELLO WORLD";
|
||||
export function leftPad(s: string, n: number) { return s + n; }
|
||||
export function multiply(a: number, b: number) { return a * b; }
|
||||
`,
|
||||
"/user/username/projects/sample1/core/some_decl.d.ts": `declare const dts: any;`,
|
||||
"/user/username/projects/sample1/core/anotherModule.ts": `export const World = "hello";`,
|
||||
"/user/username/projects/sample1/logic/tsconfig.json": getFsContentsForSampleProjectReferencesLogicConfig(),
|
||||
"/user/username/projects/sample1/logic/index.ts": dedent`
|
||||
import * as c from '../core/index';
|
||||
export function getSecondsInDay() {
|
||||
return c.multiply(10, 15);
|
||||
}
|
||||
import * as mod from '../core/anotherModule';
|
||||
export const m = mod;
|
||||
`,
|
||||
"/user/username/projects/sample1/tests/tsconfig.json": jsonToReadableText({
|
||||
references: [
|
||||
{ path: "../core" },
|
||||
{ path: "../logic" },
|
||||
],
|
||||
files: ["index.ts"],
|
||||
compilerOptions: {
|
||||
composite: true,
|
||||
declaration: true,
|
||||
forceConsistentCasingInFileNames: true,
|
||||
skipDefaultLibCheck: true,
|
||||
},
|
||||
}),
|
||||
"/user/username/projects/sample1/tests/index.ts": dedent`
|
||||
import * as c from '../core/index';
|
||||
import * as logic from '../logic/index';
|
||||
|
||||
c.leftPad("", 10);
|
||||
logic.getSecondsInDay();
|
||||
|
||||
import * as mod from '../core/anotherModule';
|
||||
export const m = mod;
|
||||
`,
|
||||
};
|
||||
}
|
||||
|
||||
export function getFsForSampleProjectReferences() {
|
||||
return loadProjectFromFiles(
|
||||
getFsContentsForSampleProjectReferences(),
|
||||
{
|
||||
cwd: "/user/username/projects/sample1",
|
||||
executingFilePath: libFile.path,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export function getSysForSampleProjectReferences() {
|
||||
return createWatchedSystem(
|
||||
getFsContentsForSampleProjectReferences(),
|
||||
{
|
||||
currentDirectory: "/user/username/projects/sample1",
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export function getServerHostForSampleProjectReferences() {
|
||||
return createServerHost(
|
||||
getFsContentsForSampleProjectReferences(),
|
||||
{
|
||||
currentDirectory: "/user/username/projects/sample1",
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
import * as fakes from "../../_namespaces/fakes";
|
||||
import * as ts from "../../_namespaces/ts";
|
||||
import {
|
||||
jsonToReadableText,
|
||||
} from "../helpers";
|
||||
import {
|
||||
commandLineCallbacks,
|
||||
} from "./baseline";
|
||||
@@ -9,11 +12,7 @@ import {
|
||||
} from "./tsc";
|
||||
import {
|
||||
changeToHostTrackingWrittenFiles,
|
||||
createWatchedSystem,
|
||||
FileOrFolderOrSymLink,
|
||||
FileOrFolderOrSymLinkMap,
|
||||
TestServerHost,
|
||||
TestServerHostCreationParameters,
|
||||
} from "./virtualFileSystemWithWatch";
|
||||
|
||||
export function createSolutionBuilderHostForBaseline(
|
||||
@@ -37,7 +36,7 @@ export function createSolutionBuilder(system: TestServerHost, rootNames: readonl
|
||||
export function ensureErrorFreeBuild(host: TestServerHost, rootNames: readonly string[]) {
|
||||
// ts build should succeed
|
||||
solutionBuildWithBaseline(host, rootNames);
|
||||
assert.equal(host.getOutput().length, 0, JSON.stringify(host.getOutput(), /*replacer*/ undefined, " "));
|
||||
assert.equal(host.getOutput().length, 0, jsonToReadableText(host.getOutput()));
|
||||
}
|
||||
|
||||
export function solutionBuildWithBaseline(sys: TestServerHost, solutionRoots: readonly string[], originalRead?: TestServerHost["readFile"]) {
|
||||
@@ -59,7 +58,3 @@ export function solutionBuildWithBaseline(sys: TestServerHost, solutionRoots: re
|
||||
sys.writtenFiles = undefined;
|
||||
return sys;
|
||||
}
|
||||
|
||||
export function createSystemWithSolutionBuild(solutionRoots: readonly string[], files: FileOrFolderOrSymLinkMap | readonly FileOrFolderOrSymLink[], params?: TestServerHostCreationParameters) {
|
||||
return solutionBuildWithBaseline(createWatchedSystem(files, params), solutionRoots);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import {
|
||||
dedent,
|
||||
} from "../../_namespaces/Utils";
|
||||
import {
|
||||
jsonToReadableText,
|
||||
} from "../helpers";
|
||||
import {
|
||||
FsContents,
|
||||
libContent,
|
||||
} from "./contents";
|
||||
import {
|
||||
libFile,
|
||||
} from "./virtualFileSystemWithWatch";
|
||||
|
||||
export function getFsContentsForTransitiveReferencesRefsAdts() {
|
||||
return dedent`
|
||||
export class X {}
|
||||
export class A {}
|
||||
`;
|
||||
}
|
||||
|
||||
export function getFsContentsForTransitiveReferencesBConfig() {
|
||||
return jsonToReadableText({
|
||||
compilerOptions: {
|
||||
composite: true,
|
||||
baseUrl: "./",
|
||||
paths: {
|
||||
"@ref/*": ["./*"],
|
||||
},
|
||||
},
|
||||
files: ["b.ts"],
|
||||
references: [{ path: "tsconfig.a.json" }],
|
||||
});
|
||||
}
|
||||
|
||||
export function getFsContentsForTransitiveReferencesAConfig() {
|
||||
return jsonToReadableText({
|
||||
compilerOptions: { composite: true },
|
||||
files: ["a.ts"],
|
||||
});
|
||||
}
|
||||
|
||||
export function getFsContentsForTransitiveReferences(): FsContents {
|
||||
return {
|
||||
"/user/username/projects/transitiveReferences/refs/a.d.ts": getFsContentsForTransitiveReferencesRefsAdts(),
|
||||
"/user/username/projects/transitiveReferences/a.ts": dedent`
|
||||
export class A {}
|
||||
`,
|
||||
"/user/username/projects/transitiveReferences/b.ts": dedent`
|
||||
import {A} from '@ref/a';
|
||||
export const b = new A();
|
||||
`,
|
||||
"/user/username/projects/transitiveReferences/c.ts": dedent`
|
||||
import {b} from './b';
|
||||
import {X} from "@ref/a";
|
||||
b;
|
||||
X;
|
||||
`,
|
||||
"/user/username/projects/transitiveReferences/tsconfig.a.json": getFsContentsForTransitiveReferencesAConfig(),
|
||||
"/user/username/projects/transitiveReferences/tsconfig.b.json": getFsContentsForTransitiveReferencesBConfig(),
|
||||
"/user/username/projects/transitiveReferences/tsconfig.c.json": jsonToReadableText({
|
||||
files: ["c.ts"],
|
||||
compilerOptions: {
|
||||
baseUrl: "./",
|
||||
paths: {
|
||||
"@ref/*": ["./refs/*"],
|
||||
},
|
||||
},
|
||||
references: [{ path: "tsconfig.b.json" }],
|
||||
}),
|
||||
[libFile.path]: libContent,
|
||||
};
|
||||
}
|
||||
@@ -2,6 +2,9 @@ import * as fakes from "../../_namespaces/fakes";
|
||||
import * as Harness from "../../_namespaces/Harness";
|
||||
import * as ts from "../../_namespaces/ts";
|
||||
import * as vfs from "../../_namespaces/vfs";
|
||||
import {
|
||||
jsonToReadableText,
|
||||
} from "../helpers";
|
||||
import {
|
||||
baselinePrograms,
|
||||
CommandLineCallbacks,
|
||||
@@ -136,13 +139,15 @@ export function testTscCompile(input: TestTscCompile) {
|
||||
makeSystemReadyForBaseline(sys);
|
||||
actualReadFileMap = {};
|
||||
const originalReadFile = sys.readFile;
|
||||
sys.readFile = path => {
|
||||
// Dont record libs
|
||||
if (path.startsWith("/src/")) {
|
||||
actualReadFileMap![path] = (ts.getProperty(actualReadFileMap!, path) || 0) + 1;
|
||||
}
|
||||
return originalReadFile.call(sys, path);
|
||||
};
|
||||
if (input.baselineReadFileCalls) {
|
||||
sys.readFile = path => {
|
||||
// Dont record libs
|
||||
if (!path.startsWith(ts.getDirectoryPath(sys.getExecutingFilePath()))) {
|
||||
actualReadFileMap![path] = (ts.getProperty(actualReadFileMap!, path) || 0) + 1;
|
||||
}
|
||||
return originalReadFile.call(sys, path);
|
||||
};
|
||||
}
|
||||
|
||||
const result = commandLineCallbacks(sys, originalReadFile);
|
||||
ts.executeCommandLine(
|
||||
@@ -164,7 +169,7 @@ export function testTscCompile(input: TestTscCompile) {
|
||||
sys.write(baseline.join("\n"));
|
||||
}
|
||||
if (baselineReadFileCalls) {
|
||||
sys.write(`readFiles:: ${JSON.stringify(actualReadFileMap, /*replacer*/ undefined, " ")} `);
|
||||
sys.write(`readFiles:: ${jsonToReadableText(actualReadFileMap)} `);
|
||||
}
|
||||
if (baselineSourceMap) generateSourceMapBaselineFiles(sys);
|
||||
actualReadFileMap = undefined;
|
||||
@@ -302,9 +307,9 @@ function verifyTscEditDiscrepancies({
|
||||
if (!incrementalFileInfo || !cleanFileInfo || incrementalFileInfo.signature !== cleanFileInfo.signature && (!dtsForKey || incrementalFileInfo.signature !== dtsForKey.signature)) {
|
||||
return [
|
||||
`Incremental signature is neither dts signature nor file version for File:: ${key}`,
|
||||
`Incremental:: ${JSON.stringify(incrementalFileInfo, /*replacer*/ undefined, 2)}`,
|
||||
`Clean:: ${JSON.stringify(cleanFileInfo, /*replacer*/ undefined, 2)}`,
|
||||
`Dts Signature:: $${JSON.stringify(dtsForKey?.signature)}`,
|
||||
`Incremental:: ${jsonToReadableText(incrementalFileInfo)}`,
|
||||
`Clean:: ${jsonToReadableText(cleanFileInfo)}`,
|
||||
`Dts Signature:: $${jsonToReadableText(dtsForKey?.signature)}`,
|
||||
];
|
||||
}
|
||||
},
|
||||
@@ -324,9 +329,9 @@ function verifyTscEditDiscrepancies({
|
||||
) {
|
||||
return [
|
||||
`Incremental Reference set is neither from dts nor files reference map for File:: ${key}::`,
|
||||
`Incremental:: ${JSON.stringify(incrementalReferenceSet, /*replacer*/ undefined, 2)}`,
|
||||
`Clean:: ${JSON.stringify(cleanReferenceSet, /*replacer*/ undefined, 2)}`,
|
||||
`DtsExportsMap:: ${JSON.stringify(dtsForKey?.exportedModules, /*replacer*/ undefined, 2)}`,
|
||||
`Incremental:: ${jsonToReadableText(incrementalReferenceSet)}`,
|
||||
`Clean:: ${jsonToReadableText(cleanReferenceSet)}`,
|
||||
`DtsExportsMap:: ${jsonToReadableText(dtsForKey?.exportedModules)}`,
|
||||
];
|
||||
}
|
||||
},
|
||||
@@ -405,9 +410,9 @@ function verifyTscEditDiscrepancies({
|
||||
addBaseline(
|
||||
message,
|
||||
"CleanBuild:",
|
||||
ts.isString(expected) ? expected : JSON.stringify(expected),
|
||||
ts.isString(expected) ? expected : jsonToReadableText(expected),
|
||||
"IncrementalBuild:",
|
||||
ts.isString(actual) ? actual : JSON.stringify(actual),
|
||||
ts.isString(actual) ? actual : jsonToReadableText(actual),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -437,25 +442,21 @@ function getBuildInfoForIncrementalCorrectnessCheck(text: string | undefined): {
|
||||
}
|
||||
}
|
||||
return {
|
||||
buildInfo: JSON.stringify(
|
||||
{
|
||||
...readableBuildInfo,
|
||||
program: readableBuildInfo.program && {
|
||||
...readableBuildInfo.program,
|
||||
fileNames: undefined,
|
||||
fileNamesList: undefined,
|
||||
fileInfos: sanitizedFileInfos,
|
||||
// Ignore noEmit since that shouldnt be reason to emit the tsbuild info and presence of it in the buildinfo file does not matter
|
||||
options: { ...readableBuildInfo.program.options, noEmit: undefined },
|
||||
exportedModulesMap: undefined,
|
||||
affectedFilesPendingEmit: undefined,
|
||||
latestChangedDtsFile: readableBuildInfo.program.latestChangedDtsFile ? "FakeFileName" : undefined,
|
||||
},
|
||||
size: undefined, // Size doesnt need to be equal
|
||||
buildInfo: jsonToReadableText({
|
||||
...readableBuildInfo,
|
||||
program: readableBuildInfo.program && {
|
||||
...readableBuildInfo.program,
|
||||
fileNames: undefined,
|
||||
fileNamesList: undefined,
|
||||
fileInfos: sanitizedFileInfos,
|
||||
// Ignore noEmit since that shouldnt be reason to emit the tsbuild info and presence of it in the buildinfo file does not matter
|
||||
options: { ...readableBuildInfo.program.options, noEmit: undefined },
|
||||
exportedModulesMap: undefined,
|
||||
affectedFilesPendingEmit: undefined,
|
||||
latestChangedDtsFile: readableBuildInfo.program.latestChangedDtsFile ? "FakeFileName" : undefined,
|
||||
},
|
||||
/*replacer*/ undefined,
|
||||
2,
|
||||
),
|
||||
size: undefined, // Size doesnt need to be equal
|
||||
}),
|
||||
readableBuildInfo,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
import {
|
||||
incrementalVerifier,
|
||||
} from "../../../harness/incrementalUtils";
|
||||
import {
|
||||
createHasErrorMessageLogger,
|
||||
createLoggerWithInMemoryLogs,
|
||||
Logger,
|
||||
} from "../../../harness/tsserverLogger";
|
||||
import * as Harness from "../../_namespaces/Harness";
|
||||
import * as ts from "../../_namespaces/ts";
|
||||
import {
|
||||
ActionWatchTypingLocations,
|
||||
} from "../../_namespaces/ts.server";
|
||||
import {
|
||||
ensureErrorFreeBuild,
|
||||
} from "./solutionBuilder";
|
||||
import {
|
||||
customTypesMap,
|
||||
TestTypingsInstaller,
|
||||
TestTypingsInstallerOptions,
|
||||
} from "./typingsInstaller";
|
||||
import {
|
||||
changeToHostTrackingWrittenFiles,
|
||||
createServerHost,
|
||||
@@ -19,164 +26,6 @@ import {
|
||||
TestServerHostTrackingWrittenFiles,
|
||||
} from "./virtualFileSystemWithWatch";
|
||||
|
||||
export const customTypesMap = {
|
||||
path: "/typesMap.json" as ts.Path,
|
||||
content: `{
|
||||
"typesMap": {
|
||||
"jquery": {
|
||||
"match": "jquery(-(\\\\.?\\\\d+)+)?(\\\\.intellisense)?(\\\\.min)?\\\\.js$",
|
||||
"types": ["jquery"]
|
||||
},
|
||||
"quack": {
|
||||
"match": "/duckquack-(\\\\d+)\\\\.min\\\\.js",
|
||||
"types": ["duck-types"]
|
||||
}
|
||||
},
|
||||
"simpleMap": {
|
||||
"Bacon": "baconjs",
|
||||
"bliss": "blissfuljs",
|
||||
"commander": "commander",
|
||||
"cordova": "cordova",
|
||||
"react": "react",
|
||||
"lodash": "lodash"
|
||||
}
|
||||
}`,
|
||||
};
|
||||
|
||||
function replaceAll(source: string, searchValue: string, replaceValue: string): string {
|
||||
let result: string | undefined = (source as string & { replaceAll: typeof source.replace; }).replaceAll?.(searchValue, replaceValue);
|
||||
|
||||
if (result !== undefined) {
|
||||
return result;
|
||||
}
|
||||
|
||||
result = "";
|
||||
const searchLength = searchValue.length;
|
||||
while (true) {
|
||||
const index = source.indexOf(searchValue);
|
||||
if (index < 0) {
|
||||
break;
|
||||
}
|
||||
result += source.slice(0, index);
|
||||
result += replaceValue;
|
||||
source = source.slice(index + searchLength);
|
||||
}
|
||||
result += source;
|
||||
return result;
|
||||
}
|
||||
|
||||
export interface PostExecAction {
|
||||
readonly success: boolean;
|
||||
requestId: number;
|
||||
readonly packageNames: readonly string[];
|
||||
readonly callback: ts.server.typingsInstaller.RequestCompletedAction;
|
||||
}
|
||||
|
||||
export interface Logger extends ts.server.Logger {
|
||||
logs?: string[];
|
||||
log(s: string): void;
|
||||
host?: TestServerHost;
|
||||
}
|
||||
|
||||
export function nullLogger(): Logger {
|
||||
return {
|
||||
close: ts.noop,
|
||||
hasLevel: ts.returnFalse,
|
||||
loggingEnabled: ts.returnFalse,
|
||||
perftrc: ts.noop,
|
||||
info: ts.noop,
|
||||
msg: ts.noop,
|
||||
startGroup: ts.noop,
|
||||
endGroup: ts.noop,
|
||||
getLogFileName: ts.returnUndefined,
|
||||
log: ts.noop,
|
||||
isTestLogger: true,
|
||||
};
|
||||
}
|
||||
|
||||
export function createHasErrorMessageLogger(): Logger {
|
||||
return {
|
||||
...nullLogger(),
|
||||
msg: (s, type) => ts.Debug.fail(`Error: ${s}, type: ${type}`),
|
||||
};
|
||||
}
|
||||
|
||||
function handleLoggerGroup(logger: Logger, host: TestServerHost | undefined): Logger {
|
||||
let inGroup = false;
|
||||
let firstInGroup = false;
|
||||
logger.startGroup = () => {
|
||||
inGroup = true;
|
||||
firstInGroup = true;
|
||||
};
|
||||
logger.endGroup = () => inGroup = false;
|
||||
logger.host = host;
|
||||
const originalInfo = logger.info;
|
||||
logger.info = s => msg(s, ts.server.Msg.Info, s => originalInfo.call(logger, s));
|
||||
logger.log = s => originalInfo.call(logger, s);
|
||||
return logger;
|
||||
|
||||
function msg(s: string, type = ts.server.Msg.Err, write: (s: string) => void) {
|
||||
s = `[${nowString(logger.host!)}] ${s}`;
|
||||
if (!inGroup || firstInGroup) s = padStringRight(type + " seq", " ") + s;
|
||||
if (ts.Debug.isDebugging) console.log(s);
|
||||
write(s);
|
||||
}
|
||||
|
||||
function padStringRight(str: string, padding: string) {
|
||||
return (str + padding).slice(0, padding.length);
|
||||
}
|
||||
}
|
||||
|
||||
function nowString(host: TestServerHost) {
|
||||
// E.g. "12:34:56.789"
|
||||
host.now(); // To increment the time but not print it to avoid the baseline updates
|
||||
return `hh:mm:ss:mss`;
|
||||
}
|
||||
|
||||
export function createLoggerWritingToConsole(host: TestServerHost): Logger {
|
||||
return handleLoggerGroup({
|
||||
...nullLogger(),
|
||||
hasLevel: ts.returnTrue,
|
||||
loggingEnabled: ts.returnTrue,
|
||||
perftrc: s => console.log(s),
|
||||
info: s => console.log(s),
|
||||
msg: (s, type) => console.log(`${type}:: ${s}`),
|
||||
}, host);
|
||||
}
|
||||
|
||||
function sanitizeLog(s: string): string {
|
||||
s = s.replace(/Elapsed::?\s*\d+(?:\.\d+)?ms/g, "Elapsed:: *ms");
|
||||
s = s.replace(/"updateGraphDurationMs":\s*\d+(?:\.\d+)?/g, `"updateGraphDurationMs": *`);
|
||||
s = s.replace(/"createAutoImportProviderProgramDurationMs":\s*\d+(?:\.\d+)?/g, `"createAutoImportProviderProgramDurationMs": *`);
|
||||
s = replaceAll(s, ts.version, "FakeVersion");
|
||||
s = s.replace(/getCompletionData: Get current token: \d+(?:\.\d+)?/g, `getCompletionData: Get current token: *`);
|
||||
s = s.replace(/getCompletionData: Is inside comment: \d+(?:\.\d+)?/g, `getCompletionData: Is inside comment: *`);
|
||||
s = s.replace(/getCompletionData: Get previous token: \d+(?:\.\d+)?/g, `getCompletionData: Get previous token: *`);
|
||||
s = s.replace(/getCompletionsAtPosition: isCompletionListBlocker: \d+(?:\.\d+)?/g, `getCompletionsAtPosition: isCompletionListBlocker: *`);
|
||||
s = s.replace(/getCompletionData: Semantic work: \d+(?:\.\d+)?/g, `getCompletionData: Semantic work: *`);
|
||||
s = s.replace(/getCompletionsAtPosition: getCompletionEntriesFromSymbols: \d+(?:\.\d+)?/g, `getCompletionsAtPosition: getCompletionEntriesFromSymbols: *`);
|
||||
s = s.replace(/forEachExternalModuleToImportFrom autoImportProvider: \d+(?:\.\d+)?/g, `forEachExternalModuleToImportFrom autoImportProvider: *`);
|
||||
s = s.replace(/getExportInfoMap: done in \d+(?:\.\d+)?/g, `getExportInfoMap: done in *`);
|
||||
s = s.replace(/collectAutoImports: \d+(?:\.\d+)?/g, `collectAutoImports: *`);
|
||||
s = s.replace(/continuePreviousIncompleteResponse: \d+(?:\.\d+)?/g, `continuePreviousIncompleteResponse: *`);
|
||||
s = s.replace(/dependencies in \d+(?:\.\d+)?/g, `dependencies in *`);
|
||||
s = s.replace(/"exportMapKey":\s*"\d+ \d+ /g, match => match.replace(/ \d+ /, ` * `));
|
||||
return s;
|
||||
}
|
||||
|
||||
export function createLoggerWithInMemoryLogs(host: TestServerHost): Logger {
|
||||
const logger = createHasErrorMessageLogger();
|
||||
const logs: string[] = [];
|
||||
if (host) logs.push(`currentDirectory:: ${host.getCurrentDirectory()} useCaseSensitiveFileNames: ${host.useCaseSensitiveFileNames}`);
|
||||
return handleLoggerGroup({
|
||||
...logger,
|
||||
logs,
|
||||
hasLevel: ts.returnTrue,
|
||||
loggingEnabled: ts.returnTrue,
|
||||
info: s => logs.push(sanitizeLog(s)),
|
||||
}, host);
|
||||
}
|
||||
|
||||
export function baselineTsserverLogs(scenario: string, subScenario: string, sessionOrService: { logger: Logger; }) {
|
||||
ts.Debug.assert(sessionOrService.logger.logs?.length); // Ensure caller used in memory logger
|
||||
Harness.Baseline.runBaseline(`tsserver/${scenario}/${subScenario.split(" ").join("-")}.js`, sessionOrService.logger.logs.join("\r\n"));
|
||||
@@ -189,231 +38,6 @@ export function appendAllScriptInfos(session: TestSession) {
|
||||
session.logger.log("");
|
||||
}
|
||||
|
||||
function loggerToTypingsInstallerLog(logger: Logger): ts.server.typingsInstaller.Log | undefined {
|
||||
return logger?.loggingEnabled() ? {
|
||||
isEnabled: ts.returnTrue,
|
||||
writeLine: s => {
|
||||
// This is a VERY VERY NAIVE sanitization strategy.
|
||||
// If a substring containing the exact TypeScript version is found,
|
||||
// even if it's unrelated to TypeScript itself, then it will be replaced,
|
||||
// leaving us with two options:
|
||||
//
|
||||
// 1. Deal with flip-flopping baselines.
|
||||
// 2. Change the TypeScript version until no matching substring is found.
|
||||
//
|
||||
const initialLog = sanitizeLog(s);
|
||||
const pseudoSanitizedLog = replaceAll(initialLog, `@ts${ts.versionMajorMinor}`, `@tsFakeMajor.Minor`);
|
||||
return logger.log(`TI:: [${nowString(logger.host!)}] ${pseudoSanitizedLog}`);
|
||||
},
|
||||
} : undefined;
|
||||
}
|
||||
|
||||
interface TypesRegistryFile {
|
||||
entries: ts.MapLike<ts.MapLike<string>>;
|
||||
}
|
||||
|
||||
function loadTypesRegistryFile(typesRegistryFilePath: string, host: TestServerHost, log: ts.server.typingsInstaller.Log): Map<string, ts.MapLike<string>> {
|
||||
if (!host.fileExists(typesRegistryFilePath)) {
|
||||
if (log.isEnabled()) {
|
||||
log.writeLine(`Types registry file '${typesRegistryFilePath}' does not exist`);
|
||||
}
|
||||
return new Map<string, ts.MapLike<string>>();
|
||||
}
|
||||
try {
|
||||
const content = JSON.parse(host.readFile(typesRegistryFilePath)!) as TypesRegistryFile;
|
||||
return new Map(Object.entries(content.entries));
|
||||
}
|
||||
catch (e) {
|
||||
if (log.isEnabled()) {
|
||||
log.writeLine(`Error when loading types registry file '${typesRegistryFilePath}': ${(e as Error).message}, ${(e as Error).stack}`);
|
||||
}
|
||||
return new Map<string, ts.MapLike<string>>();
|
||||
}
|
||||
}
|
||||
|
||||
const typesRegistryPackageName = "types-registry";
|
||||
function getTypesRegistryFileLocation(globalTypingsCacheLocation: string): string {
|
||||
return ts.combinePaths(ts.normalizeSlashes(globalTypingsCacheLocation), `node_modules/${typesRegistryPackageName}/index.json`);
|
||||
}
|
||||
|
||||
export class TestTypingsInstallerWorker extends ts.server.typingsInstaller.TypingsInstaller {
|
||||
readonly typesRegistry: Map<string, ts.MapLike<string>>;
|
||||
protected projectService!: ts.server.ProjectService;
|
||||
constructor(
|
||||
readonly globalTypingsCacheLocation: string,
|
||||
throttleLimit: number,
|
||||
installTypingHost: TestServerHost,
|
||||
logger: Logger,
|
||||
typesRegistry?: string | readonly string[],
|
||||
) {
|
||||
const log = loggerToTypingsInstallerLog(logger);
|
||||
if (log?.isEnabled()) {
|
||||
patchHostTimeouts(
|
||||
changeToHostTrackingWrittenFiles(installTypingHost),
|
||||
logger,
|
||||
);
|
||||
(installTypingHost as TestSessionAndServiceHost).baselineHost("TI:: Creating typing installer");
|
||||
}
|
||||
super(
|
||||
installTypingHost,
|
||||
globalTypingsCacheLocation,
|
||||
"/safeList.json" as ts.Path,
|
||||
customTypesMap.path,
|
||||
throttleLimit,
|
||||
log,
|
||||
);
|
||||
|
||||
this.ensurePackageDirectoryExists(globalTypingsCacheLocation);
|
||||
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`Updating ${typesRegistryPackageName} npm package...`);
|
||||
this.log.writeLine(`npm install --ignore-scripts ${typesRegistryPackageName}@${this.latestDistTag}`);
|
||||
}
|
||||
installTypingHost.ensureFileOrFolder({
|
||||
path: getTypesRegistryFileLocation(globalTypingsCacheLocation),
|
||||
content: JSON.stringify(
|
||||
createTypesRegistryFileContent(
|
||||
typesRegistry ?
|
||||
ts.isString(typesRegistry) ?
|
||||
[typesRegistry] :
|
||||
typesRegistry :
|
||||
ts.emptyArray,
|
||||
),
|
||||
undefined,
|
||||
" ",
|
||||
),
|
||||
});
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`TI:: Updated ${typesRegistryPackageName} npm package`);
|
||||
}
|
||||
this.typesRegistry = loadTypesRegistryFile(getTypesRegistryFileLocation(globalTypingsCacheLocation), installTypingHost, this.log);
|
||||
if (this.log.isEnabled()) {
|
||||
(installTypingHost as TestSessionAndServiceHost).baselineHost("TI:: typing installer creation complete");
|
||||
}
|
||||
}
|
||||
|
||||
protected postExecActions: PostExecAction[] = [];
|
||||
|
||||
executePendingCommands() {
|
||||
const actionsToRun = this.postExecActions;
|
||||
this.postExecActions = [];
|
||||
for (const action of actionsToRun) {
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`#${action.requestId} with arguments'${JSON.stringify(action.packageNames)}':: ${action.success}`);
|
||||
}
|
||||
action.callback(action.success);
|
||||
}
|
||||
}
|
||||
|
||||
attach(projectService: ts.server.ProjectService) {
|
||||
this.projectService = projectService;
|
||||
}
|
||||
|
||||
getInstallTypingHost() {
|
||||
return this.installTypingHost;
|
||||
}
|
||||
|
||||
installWorker(requestId: number, packageNames: string[], _cwd: string, cb: ts.server.typingsInstaller.RequestCompletedAction): void {
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`#${requestId} with arguments'${JSON.stringify(packageNames)}'.`);
|
||||
}
|
||||
this.addPostExecAction("success", requestId, packageNames, cb);
|
||||
}
|
||||
|
||||
sendResponse(response: ts.server.SetTypings | ts.server.InvalidateCachedTypings | ts.server.WatchTypingLocations) {
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`Sending response:\n ${JSON.stringify(response)}`);
|
||||
}
|
||||
if (response.kind !== ActionWatchTypingLocations) this.projectService.updateTypingsForProject(response);
|
||||
else this.projectService.watchTypingLocations(response);
|
||||
}
|
||||
|
||||
enqueueInstallTypingsRequest(project: ts.server.Project, typeAcquisition: ts.TypeAcquisition, unresolvedImports: ts.SortedReadonlyArray<string>) {
|
||||
const request = ts.server.createInstallTypingsRequest(project, typeAcquisition, unresolvedImports, this.globalTypingsCacheLocation);
|
||||
this.install(request);
|
||||
}
|
||||
|
||||
addPostExecAction(stdout: string | string[], requestId: number, packageNames: string[], cb: ts.server.typingsInstaller.RequestCompletedAction) {
|
||||
const out = ts.isString(stdout) ? stdout : createNpmPackageJsonString(stdout);
|
||||
const action: PostExecAction = {
|
||||
success: !!out,
|
||||
requestId,
|
||||
packageNames,
|
||||
callback: cb,
|
||||
};
|
||||
this.postExecActions.push(action);
|
||||
}
|
||||
}
|
||||
|
||||
export class TestTypingsInstaller<T extends TestTypingsInstallerWorker = TestTypingsInstallerWorker> implements ts.server.ITypingsInstaller {
|
||||
protected projectService!: ts.server.ProjectService;
|
||||
public installer!: T;
|
||||
constructor(
|
||||
readonly globalTypingsCacheLocation: string,
|
||||
private throttleLimit: number,
|
||||
private installTypingHost: TestServerHost,
|
||||
private logger: Logger,
|
||||
private workerConstructor?: new (...args: ConstructorParameters<typeof TestTypingsInstallerWorker>) => T,
|
||||
private typesRegistry?: string | readonly string[],
|
||||
) {
|
||||
}
|
||||
|
||||
isKnownTypesPackageName = ts.notImplemented;
|
||||
installPackage = ts.notImplemented;
|
||||
|
||||
attach(projectService: ts.server.ProjectService) {
|
||||
this.projectService = projectService;
|
||||
}
|
||||
|
||||
onProjectClosed(p: ts.server.Project) {
|
||||
this.installer?.closeProject({ projectName: p.getProjectName(), kind: "closeProject" });
|
||||
}
|
||||
|
||||
enqueueInstallTypingsRequest(project: ts.server.Project, typeAcquisition: ts.TypeAcquisition, unresolvedImports: ts.SortedReadonlyArray<string>) {
|
||||
if (!this.installer) {
|
||||
if (this.workerConstructor) {
|
||||
this.installer ??= new this.workerConstructor(this.globalTypingsCacheLocation, this.throttleLimit, this.installTypingHost, this.logger, this.typesRegistry);
|
||||
}
|
||||
else {
|
||||
this.installer = new TestTypingsInstallerWorker(this.globalTypingsCacheLocation, this.throttleLimit, this.installTypingHost, this.logger, this.typesRegistry) as T;
|
||||
}
|
||||
this.installer.attach(this.projectService);
|
||||
}
|
||||
this.installer.enqueueInstallTypingsRequest(project, typeAcquisition, unresolvedImports);
|
||||
}
|
||||
}
|
||||
|
||||
function createNpmPackageJsonString(installedTypings: string[]): string {
|
||||
const dependencies: ts.MapLike<any> = {};
|
||||
for (const typing of installedTypings) {
|
||||
dependencies[typing] = "1.0.0";
|
||||
}
|
||||
return JSON.stringify({ dependencies });
|
||||
}
|
||||
|
||||
function createTypesRegistryFileContent(list: readonly string[]): TypesRegistryFile {
|
||||
const versionMap = {
|
||||
"latest": "1.3.0",
|
||||
"ts2.0": "1.0.0",
|
||||
"ts2.1": "1.0.0",
|
||||
"ts2.2": "1.2.0",
|
||||
"ts2.3": "1.3.0",
|
||||
"ts2.4": "1.3.0",
|
||||
"ts2.5": "1.3.0",
|
||||
"ts2.6": "1.3.0",
|
||||
"ts2.7": "1.3.0",
|
||||
};
|
||||
const entries: ts.MapLike<ts.MapLike<string>> = {};
|
||||
for (const l of list) {
|
||||
entries[l] = versionMap;
|
||||
}
|
||||
return { entries };
|
||||
}
|
||||
|
||||
export function createTypesRegistry(...list: string[]) {
|
||||
return new Map(Object.entries(createTypesRegistryFileContent(list).entries));
|
||||
}
|
||||
|
||||
export function toExternalFile(fileName: string): ts.server.protocol.ExternalFile {
|
||||
return { fileName };
|
||||
}
|
||||
@@ -427,7 +51,7 @@ export type TestSessionAndServiceHost = TestServerHostTrackingWrittenFiles & {
|
||||
baselineHost(title: string): void;
|
||||
logTimeoutQueueLength(): void;
|
||||
};
|
||||
function patchHostTimeouts(
|
||||
export function patchHostTimeouts(
|
||||
inputHost: TestServerHostTrackingWrittenFiles,
|
||||
logger: Logger,
|
||||
) {
|
||||
@@ -486,7 +110,7 @@ function patchHostTimeouts(
|
||||
}
|
||||
}
|
||||
|
||||
export interface TestSessionOptions extends ts.server.SessionOptions {
|
||||
export interface TestSessionOptions extends ts.server.SessionOptions, TestTypingsInstallerOptions {
|
||||
logger: Logger;
|
||||
allowNonBaseliningLogger?: boolean;
|
||||
disableAutomaticTypingAcquisition?: boolean;
|
||||
@@ -523,11 +147,11 @@ export class TestSession extends ts.server.Session {
|
||||
public override executeCommand(request: ts.server.protocol.Request) {
|
||||
if (this.logger.hasLevel(ts.server.LogLevel.verbose)) {
|
||||
this.testhost.baselineHost("Before request");
|
||||
this.logger.info(`request:${ts.server.indent(JSON.stringify(request, undefined, 2))}`);
|
||||
this.logger.info(`request:${ts.server.stringifyIndented(request)}`);
|
||||
}
|
||||
const response = super.executeCommand(request);
|
||||
if (this.logger.hasLevel(ts.server.LogLevel.verbose)) {
|
||||
this.logger.info(`response:${ts.server.indent(JSON.stringify(response.response === ts.getSupportedCodeFixes() ? { ...response, response: "ts.getSupportedCodeFixes()" } : response, undefined, 2))}`);
|
||||
this.logger.info(`response:${ts.server.stringifyIndented(response.response === ts.getSupportedCodeFixes() ? { ...response, response: "ts.getSupportedCodeFixes()" } : response)}`);
|
||||
this.testhost.baselineHost("After request");
|
||||
}
|
||||
return response;
|
||||
@@ -545,7 +169,11 @@ export class TestSession extends ts.server.Session {
|
||||
export function createSession(host: TestServerHost, opts: Partial<TestSessionOptions> = {}) {
|
||||
const logger = opts.logger || createHasErrorMessageLogger();
|
||||
if (!opts.disableAutomaticTypingAcquisition && opts.typingsInstaller === undefined) {
|
||||
opts.typingsInstaller = new TestTypingsInstaller(host.getHostSpecificPath("/a/data/"), /*throttleLimit*/ 5, host, logger);
|
||||
opts.typingsInstaller = new TestTypingsInstaller(
|
||||
host,
|
||||
logger,
|
||||
opts,
|
||||
);
|
||||
}
|
||||
|
||||
if (opts.eventHandler !== undefined) {
|
||||
|
||||
@@ -0,0 +1,334 @@
|
||||
import {
|
||||
Logger,
|
||||
nowString,
|
||||
replaceAll,
|
||||
sanitizeLog,
|
||||
} from "../../../harness/tsserverLogger";
|
||||
import * as ts from "../../_namespaces/ts";
|
||||
import {
|
||||
ActionWatchTypingLocations,
|
||||
stringifyIndented,
|
||||
} from "../../_namespaces/ts.server";
|
||||
import {
|
||||
jsonToReadableText,
|
||||
} from "../helpers";
|
||||
import {
|
||||
patchHostTimeouts,
|
||||
TestSessionAndServiceHost,
|
||||
} from "./tsserver";
|
||||
import {
|
||||
changeToHostTrackingWrittenFiles,
|
||||
File,
|
||||
TestServerHost,
|
||||
} from "./virtualFileSystemWithWatch";
|
||||
|
||||
export const customTypesMap = {
|
||||
path: "/typesMap.json" as ts.Path,
|
||||
content: `{
|
||||
"typesMap": {
|
||||
"jquery": {
|
||||
"match": "jquery(-(\\\\.?\\\\d+)+)?(\\\\.intellisense)?(\\\\.min)?\\\\.js$",
|
||||
"types": ["jquery"]
|
||||
},
|
||||
"quack": {
|
||||
"match": "/duckquack-(\\\\d+)\\\\.min\\\\.js",
|
||||
"types": ["duck-types"]
|
||||
}
|
||||
},
|
||||
"simpleMap": {
|
||||
"Bacon": "baconjs",
|
||||
"bliss": "blissfuljs",
|
||||
"commander": "commander",
|
||||
"cordova": "cordova",
|
||||
"react": "react",
|
||||
"lodash": "lodash"
|
||||
}
|
||||
}`,
|
||||
};
|
||||
|
||||
export interface PostExecAction {
|
||||
readonly success: boolean;
|
||||
requestId: number;
|
||||
readonly packageNames: readonly string[];
|
||||
readonly callback: ts.server.typingsInstaller.RequestCompletedAction;
|
||||
}
|
||||
export function loggerToTypingsInstallerLog(logger: Logger): ts.server.typingsInstaller.Log | undefined {
|
||||
return logger?.loggingEnabled() ? {
|
||||
isEnabled: ts.returnTrue,
|
||||
writeLine: s => {
|
||||
// This is a VERY VERY NAIVE sanitization strategy.
|
||||
// If a substring containing the exact TypeScript version is found,
|
||||
// even if it's unrelated to TypeScript itself, then it will be replaced,
|
||||
// leaving us with two options:
|
||||
//
|
||||
// 1. Deal with flip-flopping baselines.
|
||||
// 2. Change the TypeScript version until no matching substring is found.
|
||||
//
|
||||
const initialLog = sanitizeLog(s);
|
||||
const pseudoSanitizedLog = replaceAll(initialLog, `@ts${ts.versionMajorMinor}`, `@tsFakeMajor.Minor`);
|
||||
return logger.log(`TI:: [${nowString(logger)}] ${pseudoSanitizedLog}`);
|
||||
},
|
||||
} : undefined;
|
||||
}
|
||||
interface TypesRegistryFile {
|
||||
entries: ts.MapLike<ts.MapLike<string>>;
|
||||
}
|
||||
function loadTypesRegistryFile(typesRegistryFilePath: string, host: TestServerHost, log: ts.server.typingsInstaller.Log): Map<string, ts.MapLike<string>> {
|
||||
if (!host.fileExists(typesRegistryFilePath)) {
|
||||
if (log.isEnabled()) {
|
||||
log.writeLine(`Types registry file '${typesRegistryFilePath}' does not exist`);
|
||||
}
|
||||
return new Map<string, ts.MapLike<string>>();
|
||||
}
|
||||
try {
|
||||
const content = JSON.parse(host.readFile(typesRegistryFilePath)!) as TypesRegistryFile;
|
||||
return new Map(Object.entries(content.entries));
|
||||
}
|
||||
catch (e) {
|
||||
if (log.isEnabled()) {
|
||||
log.writeLine(`Error when loading types registry file '${typesRegistryFilePath}': ${(e as Error).message}, ${(e as Error).stack}`);
|
||||
}
|
||||
return new Map<string, ts.MapLike<string>>();
|
||||
}
|
||||
}
|
||||
const typesRegistryPackageName = "types-registry";
|
||||
function getTypesRegistryFileLocation(globalTypingsCacheLocation: string): string {
|
||||
return ts.combinePaths(ts.normalizeSlashes(globalTypingsCacheLocation), `node_modules/${typesRegistryPackageName}/index.json`);
|
||||
}
|
||||
|
||||
export interface FileWithPackageName extends File {
|
||||
package: string;
|
||||
}
|
||||
export type InstallActionThrowingError = string;
|
||||
export type InstallActionWithTypingFiles = [installedTypings: string[] | string, typingFiles: File[]];
|
||||
export type InstallActionWithFilteredTypings = [typingFiles: FileWithPackageName[]];
|
||||
export type InstallAction = InstallActionThrowingError | InstallActionWithTypingFiles | InstallActionWithFilteredTypings;
|
||||
export class TestTypingsInstallerWorker extends ts.server.typingsInstaller.TypingsInstaller {
|
||||
readonly typesRegistry: Map<string, ts.MapLike<string>>;
|
||||
protected projectService!: ts.server.ProjectService;
|
||||
constructor(
|
||||
readonly globalTypingsCacheLocation: string,
|
||||
throttleLimit: number,
|
||||
installTypingHost: TestServerHost,
|
||||
logger: Logger,
|
||||
typesRegistry: string | readonly string[] | undefined,
|
||||
private installAction: InstallAction | undefined,
|
||||
) {
|
||||
const log = loggerToTypingsInstallerLog(logger);
|
||||
if (log?.isEnabled()) {
|
||||
patchHostTimeouts(
|
||||
changeToHostTrackingWrittenFiles(installTypingHost),
|
||||
logger,
|
||||
);
|
||||
(installTypingHost as TestSessionAndServiceHost).baselineHost("TI:: Creating typing installer");
|
||||
}
|
||||
super(
|
||||
installTypingHost,
|
||||
globalTypingsCacheLocation,
|
||||
"/safeList.json" as ts.Path,
|
||||
customTypesMap.path,
|
||||
throttleLimit,
|
||||
log,
|
||||
);
|
||||
|
||||
this.ensurePackageDirectoryExists(globalTypingsCacheLocation);
|
||||
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`Updating ${typesRegistryPackageName} npm package...`);
|
||||
this.log.writeLine(`npm install --ignore-scripts ${typesRegistryPackageName}@${this.latestDistTag}`);
|
||||
}
|
||||
installTypingHost.ensureFileOrFolder({
|
||||
path: getTypesRegistryFileLocation(globalTypingsCacheLocation),
|
||||
content: jsonToReadableText(createTypesRegistryFileContent(
|
||||
typesRegistry ?
|
||||
ts.isString(typesRegistry) ?
|
||||
[typesRegistry] :
|
||||
typesRegistry :
|
||||
ts.emptyArray,
|
||||
)),
|
||||
});
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`TI:: Updated ${typesRegistryPackageName} npm package`);
|
||||
}
|
||||
this.typesRegistry = loadTypesRegistryFile(getTypesRegistryFileLocation(globalTypingsCacheLocation), installTypingHost, this.log);
|
||||
if (this.log.isEnabled()) {
|
||||
(installTypingHost as TestSessionAndServiceHost).baselineHost("TI:: typing installer creation complete");
|
||||
}
|
||||
}
|
||||
|
||||
protected postExecActions: PostExecAction[] = [];
|
||||
|
||||
executePendingCommands() {
|
||||
const actionsToRun = this.postExecActions;
|
||||
this.postExecActions = [];
|
||||
for (const action of actionsToRun) {
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`#${action.requestId} with arguments'${jsonToReadableText(action.packageNames)}':: ${action.success}`);
|
||||
}
|
||||
action.callback(action.success);
|
||||
}
|
||||
}
|
||||
|
||||
attach(projectService: ts.server.ProjectService) {
|
||||
this.projectService = projectService;
|
||||
}
|
||||
|
||||
getInstallTypingHost() {
|
||||
return this.installTypingHost;
|
||||
}
|
||||
|
||||
installWorker(requestId: number, packageNames: string[], _cwd: string, cb: ts.server.typingsInstaller.RequestCompletedAction): void {
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`#${requestId} with arguments'${jsonToReadableText(packageNames)}'.`);
|
||||
}
|
||||
if (!this.installAction) {
|
||||
this.addPostExecAction("success", requestId, packageNames, cb);
|
||||
}
|
||||
else if (ts.isString(this.installAction)) {
|
||||
assert(false, this.installAction);
|
||||
}
|
||||
else if (this.installAction.length === 2) {
|
||||
this.executeInstallWithTypingFiles(
|
||||
requestId,
|
||||
packageNames,
|
||||
this.installAction[0],
|
||||
this.installAction[1],
|
||||
cb,
|
||||
);
|
||||
}
|
||||
else {
|
||||
const typingFiles = this.installAction[0].filter(f => packageNames.includes(ts.server.typingsInstaller.typingsName(f.package)));
|
||||
this.executeInstallWithTypingFiles(
|
||||
requestId,
|
||||
packageNames,
|
||||
typingFiles.map(f => f.package),
|
||||
typingFiles,
|
||||
cb,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
executeInstallWithTypingFiles(
|
||||
requestId: number,
|
||||
packageNames: string[],
|
||||
installedTypings: string[] | string,
|
||||
typingFiles: File[],
|
||||
cb: ts.server.typingsInstaller.RequestCompletedAction,
|
||||
): void {
|
||||
this.addPostExecAction(installedTypings, requestId, packageNames, success => {
|
||||
const host = this.getInstallTypingHost() as TestSessionAndServiceHost;
|
||||
host.baselineHost("TI:: Before installWorker");
|
||||
for (const file of typingFiles) {
|
||||
host.ensureFileOrFolder(file);
|
||||
}
|
||||
host.baselineHost("TI:: After installWorker");
|
||||
cb(success);
|
||||
});
|
||||
}
|
||||
|
||||
sendResponse(response: ts.server.SetTypings | ts.server.InvalidateCachedTypings | ts.server.WatchTypingLocations) {
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`Sending response:${stringifyIndented(response)}`);
|
||||
}
|
||||
if (response.kind !== ActionWatchTypingLocations) this.projectService.updateTypingsForProject(response);
|
||||
else this.projectService.watchTypingLocations(response);
|
||||
}
|
||||
|
||||
enqueueInstallTypingsRequest(project: ts.server.Project, typeAcquisition: ts.TypeAcquisition, unresolvedImports: ts.SortedReadonlyArray<string>) {
|
||||
const request = ts.server.createInstallTypingsRequest(project, typeAcquisition, unresolvedImports, this.globalTypingsCacheLocation);
|
||||
this.install(request);
|
||||
}
|
||||
|
||||
addPostExecAction(stdout: string | string[], requestId: number, packageNames: string[], cb: ts.server.typingsInstaller.RequestCompletedAction) {
|
||||
const out = ts.isString(stdout) ? stdout : createNpmPackageJsonString(stdout);
|
||||
const action: PostExecAction = {
|
||||
success: !!out,
|
||||
requestId,
|
||||
packageNames,
|
||||
callback: cb,
|
||||
};
|
||||
this.postExecActions.push(action);
|
||||
}
|
||||
}
|
||||
|
||||
export interface TestTypingsInstallerOptions {
|
||||
globalTypingsCacheLocation?: string;
|
||||
throttleLimit?: number;
|
||||
installAction?: InstallAction;
|
||||
typesRegistry?: string | readonly string[];
|
||||
}
|
||||
|
||||
export class TestTypingsInstaller implements ts.server.ITypingsInstaller {
|
||||
protected projectService!: ts.server.ProjectService;
|
||||
public installer!: TestTypingsInstallerWorker;
|
||||
readonly globalTypingsCacheLocation: string;
|
||||
private readonly throttleLimit: number;
|
||||
private installAction?: InstallAction;
|
||||
private typesRegistry?: string | readonly string[];
|
||||
|
||||
constructor(
|
||||
private installTypingHost: TestServerHost,
|
||||
private logger: Logger,
|
||||
options?: TestTypingsInstallerOptions,
|
||||
) {
|
||||
this.globalTypingsCacheLocation = options?.globalTypingsCacheLocation || this.installTypingHost.getHostSpecificPath("/a/data");
|
||||
this.throttleLimit = options?.throttleLimit || 5;
|
||||
this.installAction = options?.installAction;
|
||||
this.typesRegistry = options?.typesRegistry;
|
||||
}
|
||||
|
||||
isKnownTypesPackageName = ts.notImplemented;
|
||||
installPackage = ts.notImplemented;
|
||||
|
||||
attach(projectService: ts.server.ProjectService) {
|
||||
this.projectService = projectService;
|
||||
}
|
||||
|
||||
onProjectClosed(p: ts.server.Project) {
|
||||
this.installer?.closeProject({ projectName: p.getProjectName(), kind: "closeProject" });
|
||||
}
|
||||
|
||||
enqueueInstallTypingsRequest(project: ts.server.Project, typeAcquisition: ts.TypeAcquisition, unresolvedImports: ts.SortedReadonlyArray<string>) {
|
||||
if (!this.installer) {
|
||||
this.installer = new TestTypingsInstallerWorker(
|
||||
this.globalTypingsCacheLocation,
|
||||
this.throttleLimit,
|
||||
this.installTypingHost,
|
||||
this.logger,
|
||||
this.typesRegistry,
|
||||
this.installAction,
|
||||
);
|
||||
this.installer.attach(this.projectService);
|
||||
}
|
||||
this.installer.enqueueInstallTypingsRequest(project, typeAcquisition, unresolvedImports);
|
||||
}
|
||||
}
|
||||
function createNpmPackageJsonString(installedTypings: string[]): string {
|
||||
const dependencies: ts.MapLike<any> = {};
|
||||
for (const typing of installedTypings) {
|
||||
dependencies[typing] = "1.0.0";
|
||||
}
|
||||
return jsonToReadableText({ dependencies });
|
||||
}
|
||||
function createTypesRegistryFileContent(list: readonly string[]): TypesRegistryFile {
|
||||
const versionMap = {
|
||||
"latest": "1.3.0",
|
||||
"ts2.0": "1.0.0",
|
||||
"ts2.1": "1.0.0",
|
||||
"ts2.2": "1.2.0",
|
||||
"ts2.3": "1.3.0",
|
||||
"ts2.4": "1.3.0",
|
||||
"ts2.5": "1.3.0",
|
||||
"ts2.6": "1.3.0",
|
||||
"ts2.7": "1.3.0",
|
||||
};
|
||||
const entries: ts.MapLike<ts.MapLike<string>> = {};
|
||||
for (const l of list) {
|
||||
entries[l] = versionMap;
|
||||
}
|
||||
return { entries };
|
||||
}
|
||||
|
||||
export function createTypesRegistry(...list: string[]) {
|
||||
return new Map(Object.entries(createTypesRegistryFileContent(list).entries));
|
||||
}
|
||||
@@ -1,9 +1,7 @@
|
||||
import * as Harness from "../../_namespaces/Harness";
|
||||
import {
|
||||
getDirectoryPath,
|
||||
} from "../../_namespaces/ts";
|
||||
import * as vfs from "../../_namespaces/vfs";
|
||||
import * as vpath from "../../_namespaces/vpath";
|
||||
import {
|
||||
libContent,
|
||||
} from "./contents";
|
||||
@@ -21,19 +19,6 @@ function valueOfFsOptions(options: FsOptionsOrLibContentsToAppend | undefined, k
|
||||
options?.[key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Load project from disk into /src folder
|
||||
*/
|
||||
export function loadProjectFromDisk(
|
||||
root: string,
|
||||
options?: FsOptionsOrLibContentsToAppend,
|
||||
): vfs.FileSystem {
|
||||
const resolver = vfs.createResolver(Harness.IO);
|
||||
return loadProjectFromFiles({
|
||||
["/src"]: new vfs.Mount(vpath.resolve(Harness.IO.getWorkspaceRoot(), root), resolver),
|
||||
}, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* All the files must be in /src
|
||||
*/
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
import * as Harness from "../../_namespaces/Harness";
|
||||
import {
|
||||
arrayFrom,
|
||||
createWatchUtils,
|
||||
} from "../../../harness/watchUtils";
|
||||
import {
|
||||
clear,
|
||||
clone,
|
||||
combinePaths,
|
||||
compareStringsCaseSensitive,
|
||||
contains,
|
||||
createGetCanonicalFileName,
|
||||
createMultiMap,
|
||||
createSystemWatchFunctions,
|
||||
Debug,
|
||||
directorySeparator,
|
||||
FileSystemEntryKind,
|
||||
FileWatcher,
|
||||
FileWatcherCallback,
|
||||
FileWatcherEventKind,
|
||||
filterMutate,
|
||||
@@ -285,9 +283,7 @@ export class TestServerHost implements server.ServerHost, FormatDiagnosticsHost,
|
||||
readonly immediateCallbacks = new Callbacks(this, "Immedidate");
|
||||
readonly screenClears: number[] = [];
|
||||
|
||||
readonly watchedFiles = createMultiMap<Path, TestFileWatcher>();
|
||||
readonly fsWatches = createMultiMap<Path, TestFsWatcher>();
|
||||
readonly fsWatchesRecursive = createMultiMap<Path, TestFsWatcher>();
|
||||
readonly watchUtils = createWatchUtils<TestFileWatcher, TestFsWatcher, Path>("PolledWatches", "FsWatches");
|
||||
runWithFallbackPolling: boolean;
|
||||
public readonly useCaseSensitiveFileNames: boolean;
|
||||
public readonly newLine: string;
|
||||
@@ -623,24 +619,8 @@ export class TestServerHost implements server.ServerHost, FormatDiagnosticsHost,
|
||||
this.removeFileOrFolder(currentEntry);
|
||||
}
|
||||
|
||||
hasWatchChanges?: boolean;
|
||||
private createWatcher<T>(map: MultiMap<Path, T>, path: Path, callback: T): FileWatcher {
|
||||
this.hasWatchChanges = true;
|
||||
map.add(path, callback);
|
||||
let closed = false;
|
||||
return {
|
||||
close: () => {
|
||||
Debug.assert(!closed);
|
||||
map.remove(path, callback);
|
||||
this.hasWatchChanges = true;
|
||||
closed = true;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private watchFileWorker(fileName: string, cb: FileWatcherCallback, pollingInterval: PollingInterval) {
|
||||
return this.createWatcher(
|
||||
this.watchedFiles,
|
||||
return this.watchUtils.pollingWatch(
|
||||
this.toFullPath(fileName),
|
||||
{ cb, pollingInterval },
|
||||
);
|
||||
@@ -655,9 +635,9 @@ export class TestServerHost implements server.ServerHost, FormatDiagnosticsHost,
|
||||
const path = this.toFullPath(fileOrDirectory);
|
||||
// Error if the path does not exist
|
||||
if (this.inodeWatching && !this.inodes?.has(path)) throw new Error();
|
||||
const result = this.createWatcher(
|
||||
recursive ? this.fsWatchesRecursive : this.fsWatches,
|
||||
const result = this.watchUtils.fsWatch(
|
||||
path,
|
||||
recursive,
|
||||
{
|
||||
cb,
|
||||
inode: this.inodes?.get(path),
|
||||
@@ -668,7 +648,7 @@ export class TestServerHost implements server.ServerHost, FormatDiagnosticsHost,
|
||||
}
|
||||
|
||||
invokeFileWatcher(fileFullPath: string, eventKind: FileWatcherEventKind, modifiedTime: Date | undefined) {
|
||||
invokeWatcherCallbacks(this.watchedFiles.get(this.toPath(fileFullPath)), ({ cb }) => cb(fileFullPath, eventKind, modifiedTime));
|
||||
invokeWatcherCallbacks(this.watchUtils.pollingWatches.get(this.toPath(fileFullPath)), ({ cb }) => cb(fileFullPath, eventKind, modifiedTime));
|
||||
}
|
||||
|
||||
private fsWatchCallback(map: MultiMap<Path, TestFsWatcher>, fullPath: string, eventName: "rename" | "change", modifiedTime: Date | undefined, entryFullPath: string | undefined, useTildeSuffix: boolean | undefined) {
|
||||
@@ -684,11 +664,11 @@ export class TestServerHost implements server.ServerHost, FormatDiagnosticsHost,
|
||||
}
|
||||
|
||||
invokeFsWatchesCallbacks(fullPath: string, eventName: "rename" | "change", modifiedTime?: Date, entryFullPath?: string, useTildeSuffix?: boolean) {
|
||||
this.fsWatchCallback(this.fsWatches, fullPath, eventName, modifiedTime, entryFullPath, useTildeSuffix);
|
||||
this.fsWatchCallback(this.watchUtils.fsWatches, fullPath, eventName, modifiedTime, entryFullPath, useTildeSuffix);
|
||||
}
|
||||
|
||||
invokeFsWatchesRecursiveCallbacks(fullPath: string, eventName: "rename" | "change", modifiedTime?: Date, entryFullPath?: string, useTildeSuffix?: boolean) {
|
||||
this.fsWatchCallback(this.fsWatchesRecursive, fullPath, eventName, modifiedTime, entryFullPath, useTildeSuffix);
|
||||
this.fsWatchCallback(this.watchUtils.fsWatchesRecursive, fullPath, eventName, modifiedTime, entryFullPath, useTildeSuffix);
|
||||
}
|
||||
|
||||
private getRelativePathToDirectory(directoryFullPath: string, fileFullPath: string) {
|
||||
@@ -1004,16 +984,8 @@ export class TestServerHost implements server.ServerHost, FormatDiagnosticsHost,
|
||||
baseline.push("");
|
||||
}
|
||||
|
||||
private serializedWatchedFiles: Map<string, TestFileWatcher[]> | undefined;
|
||||
private serializedFsWatches: Map<string, TestFsWatcher[]> | undefined;
|
||||
private serializedFsWatchesRecursive: Map<string, TestFsWatcher[]> | undefined;
|
||||
serializeWatches(baseline: string[] = []) {
|
||||
if (!this.hasWatchChanges) return baseline;
|
||||
this.serializedWatchedFiles = serializeMultiMap(baseline, "PolledWatches", this.watchedFiles, this.serializedWatchedFiles);
|
||||
this.serializedFsWatches = serializeMultiMap(baseline, "FsWatches", this.fsWatches, this.serializedFsWatches);
|
||||
this.serializedFsWatchesRecursive = serializeMultiMap(baseline, "FsWatchesRecursive", this.fsWatchesRecursive, this.serializedFsWatchesRecursive);
|
||||
this.hasWatchChanges = false;
|
||||
return baseline;
|
||||
serializeWatches(baseline?: string[]) {
|
||||
return this.watchUtils.serializeWatches(baseline);
|
||||
}
|
||||
|
||||
realpath(s: string): string {
|
||||
@@ -1115,56 +1087,6 @@ function diffFsEntry(baseline: string[], oldFsEntry: FSEntry | undefined, newFsE
|
||||
}
|
||||
}
|
||||
|
||||
export function serializeMultiMap<T>(baseline: string[], caption: string, multiMap: MultiMap<string, T>, serialized: Map<string, T[]> | undefined) {
|
||||
let hasChange = diffMap(baseline, caption, multiMap, serialized, /*deleted*/ false);
|
||||
hasChange = diffMap(baseline, caption, serialized, multiMap, /*deleted*/ true) || hasChange;
|
||||
if (hasChange) {
|
||||
serialized = new Map();
|
||||
multiMap.forEach((value, key) => serialized!.set(key, new Array(...value)));
|
||||
}
|
||||
return serialized;
|
||||
}
|
||||
|
||||
function diffMap<T>(
|
||||
baseline: string[],
|
||||
caption: string,
|
||||
map: Map<string, T[]> | undefined,
|
||||
old: Map<string, T[]> | undefined,
|
||||
deleted: boolean,
|
||||
) {
|
||||
let captionAdded = false;
|
||||
let baselineChanged = false;
|
||||
let hasChange = false;
|
||||
if (map) {
|
||||
for (const key of arrayFrom(map.keys()).sort(compareStringsCaseSensitive)) {
|
||||
const existing = old?.get(key);
|
||||
let addedKey = false;
|
||||
const values = map.get(key)!;
|
||||
for (const value of values) {
|
||||
const hasExisting = contains(existing, value);
|
||||
if (deleted && hasExisting) continue;
|
||||
if (!hasExisting) hasChange = true;
|
||||
if (!addedKey) {
|
||||
addBaseline(`${key}:${deleted || existing ? "" : " *new*"}`);
|
||||
addedKey = true;
|
||||
}
|
||||
addBaseline(` ${JSON.stringify(value)}${deleted || hasExisting || !existing ? "" : " *new*"}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (baselineChanged) baseline.push("");
|
||||
return hasChange;
|
||||
|
||||
function addBaseline(s: string) {
|
||||
if (!captionAdded) {
|
||||
baseline.push(`${caption}${deleted ? " *deleted*" : ""}::`);
|
||||
captionAdded = true;
|
||||
}
|
||||
baseline.push(s);
|
||||
baselineChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
function baselineOutputs(baseline: string[], output: readonly string[], start: number, end = output.length) {
|
||||
let baselinedOutput: string[] | undefined;
|
||||
for (let i = start; i < end; i++) {
|
||||
@@ -1187,14 +1109,3 @@ export function changeToHostTrackingWrittenFiles(inputHost: TestServerHost) {
|
||||
};
|
||||
return host;
|
||||
}
|
||||
|
||||
export function getTsBuildProjectFilePath(project: string, file: string) {
|
||||
return `/user/username/projects/${project}/${file}`;
|
||||
}
|
||||
|
||||
export function getTsBuildProjectFile(project: string, file: string): File {
|
||||
return {
|
||||
path: getTsBuildProjectFilePath(project, file),
|
||||
content: Harness.IO.readFile(`${Harness.IO.getWorkspaceRoot()}/tests/projects/${project}/${file}`)!,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import * as Harness from "../_namespaces/Harness";
|
||||
import * as ts from "../_namespaces/ts";
|
||||
import {
|
||||
jsonToReadableText,
|
||||
} from "./helpers";
|
||||
|
||||
interface File {
|
||||
name: string;
|
||||
@@ -84,7 +87,7 @@ describe("unittests:: moduleResolution:: Node module resolution - relative paths
|
||||
const moduleFile = { name: moduleFileNameNoExt + ext };
|
||||
baselines.push(`Resolving "${moduleName}" from ${containingFile.name} when module has extension: ${ext}${hasDirectoryExists ? "" : " with host that doesnt have directoryExists"}`);
|
||||
const resolution = ts.nodeModuleNameResolver(moduleName, containingFile.name, {}, createModuleResolutionHost(baselines, hasDirectoryExists, containingFile, moduleFile));
|
||||
baselines.push(`Resolution:: ${JSON.stringify(resolution, /*replacer*/ undefined, 2)}`);
|
||||
baselines.push(`Resolution:: ${jsonToReadableText(resolution)}`);
|
||||
baselines.push("");
|
||||
}
|
||||
}
|
||||
@@ -104,11 +107,11 @@ describe("unittests:: moduleResolution:: Node module resolution - relative paths
|
||||
|
||||
function test(hasDirectoryExists: boolean) {
|
||||
const containingFile = { name: containingFileName };
|
||||
const packageJson = { name: packageJsonFileName, content: JSON.stringify({ typings: fieldRef }) };
|
||||
const packageJson = { name: packageJsonFileName, content: jsonToReadableText({ typings: fieldRef }) };
|
||||
const moduleFile = { name: moduleFileName };
|
||||
baselines.push(`Resolving "${moduleName}" from ${containingFile.name} with typings: ${fieldRef}${hasDirectoryExists ? "" : " with host that doesnt have directoryExists"}`);
|
||||
const resolution = ts.nodeModuleNameResolver(moduleName, containingFile.name, {}, createModuleResolutionHost(baselines, hasDirectoryExists, containingFile, packageJson, moduleFile));
|
||||
baselines.push(`Resolution:: ${JSON.stringify(resolution, /*replacer*/ undefined, 2)}`);
|
||||
baselines.push(`Resolution:: ${jsonToReadableText(resolution)}`);
|
||||
baselines.push("");
|
||||
}
|
||||
}
|
||||
@@ -128,15 +131,15 @@ describe("unittests:: moduleResolution:: Node module resolution - relative paths
|
||||
test(/*hasDirectoryExists*/ false);
|
||||
function test(hasDirectoryExists: boolean) {
|
||||
const containingFile = { name: "/a/b.ts" };
|
||||
const packageJson = { name: "/node_modules/b/package.json", content: JSON.stringify({ typings }) };
|
||||
const packageJson = { name: "/node_modules/b/package.json", content: jsonToReadableText({ typings }) };
|
||||
const moduleFile = { name: "/a/b.d.ts" };
|
||||
|
||||
const indexPath = "/node_modules/b/index.d.ts";
|
||||
const indexFile = { name: indexPath };
|
||||
|
||||
baselines.push(`Resolving "b" from ${containingFile.name} with typings: ${JSON.stringify(typings)}${hasDirectoryExists ? "" : " with host that doesnt have directoryExists"}`);
|
||||
baselines.push(`Resolving "b" from ${containingFile.name} with typings: ${jsonToReadableText(typings)}${hasDirectoryExists ? "" : " with host that doesnt have directoryExists"}`);
|
||||
const resolution = ts.nodeModuleNameResolver("b", containingFile.name, {}, createModuleResolutionHost(baselines, hasDirectoryExists, containingFile, packageJson, moduleFile, indexFile));
|
||||
baselines.push(`Resolution:: ${JSON.stringify(resolution, /*replacer*/ undefined, 2)}`);
|
||||
baselines.push(`Resolution:: ${jsonToReadableText(resolution)}`);
|
||||
baselines.push("");
|
||||
}
|
||||
}
|
||||
@@ -149,11 +152,11 @@ describe("unittests:: moduleResolution:: Node module resolution - relative paths
|
||||
|
||||
function test(hasDirectoryExists: boolean) {
|
||||
const containingFile = { name: "/a/b/c.ts" };
|
||||
const packageJson = { name: "/a/b/foo/package.json", content: JSON.stringify({ main: "/c/d" }) };
|
||||
const packageJson = { name: "/a/b/foo/package.json", content: jsonToReadableText({ main: "/c/d" }) };
|
||||
const indexFile = { name: "/a/b/foo/index.d.ts" };
|
||||
baselines.push(`Resolving "./foo" from ${containingFile.name}${hasDirectoryExists ? "" : " with host that doesnt have directoryExists"}`);
|
||||
const resolution = ts.nodeModuleNameResolver("./foo", containingFile.name, {}, createModuleResolutionHost(baselines, hasDirectoryExists, containingFile, packageJson, indexFile));
|
||||
baselines.push(`Resolution:: ${JSON.stringify(resolution, /*replacer*/ undefined, 2)}`);
|
||||
baselines.push(`Resolution:: ${jsonToReadableText(resolution)}`);
|
||||
baselines.push("");
|
||||
}
|
||||
});
|
||||
@@ -265,7 +268,7 @@ describe("unittests:: moduleResolution:: Node module resolution - non-relative p
|
||||
const moduleFile = { name: "/a/b/node_modules/foo.ts" };
|
||||
baselines.push(`Resolving "foo" from ${containingFile.name}${hasDirectoryExists ? "" : " with host that doesnt have directoryExists"}`);
|
||||
const resolution = ts.nodeModuleNameResolver("foo", containingFile.name, {}, createModuleResolutionHost(baselines, hasDirectoryExists, containingFile, moduleFile));
|
||||
baselines.push(`Resolution:: ${JSON.stringify(resolution, /*replacer*/ undefined, 2)}`);
|
||||
baselines.push(`Resolution:: ${jsonToReadableText(resolution)}`);
|
||||
baselines.push("");
|
||||
}
|
||||
});
|
||||
@@ -281,7 +284,7 @@ describe("unittests:: moduleResolution:: Node module resolution - non-relative p
|
||||
const moduleFile = { name: "/a/b/node_modules/foo.d.ts" };
|
||||
baselines.push(`Resolving "foo" from ${containingFile.name}${hasDirectoryExists ? "" : " with host that doesnt have directoryExists"}`);
|
||||
const resolution = ts.nodeModuleNameResolver("foo", containingFile.name, {}, createModuleResolutionHost(baselines, hasDirectoryExists, containingFile, moduleFile));
|
||||
baselines.push(`Resolution:: ${JSON.stringify(resolution, /*replacer*/ undefined, 2)}`);
|
||||
baselines.push(`Resolution:: ${jsonToReadableText(resolution)}`);
|
||||
baselines.push("");
|
||||
}
|
||||
});
|
||||
@@ -297,7 +300,7 @@ describe("unittests:: moduleResolution:: Node module resolution - non-relative p
|
||||
const moduleFile: File = { name: "/a/node_modules/foo/index.d.ts" };
|
||||
baselines.push(`Resolving "foo" from ${containingFile.name}${hasDirectoryExists ? "" : " with host that doesnt have directoryExists"}`);
|
||||
const resolution = ts.nodeModuleNameResolver("foo", containingFile.name, {}, createModuleResolutionHost(baselines, hasDirectoryExists, containingFile, moduleFile));
|
||||
baselines.push(`Resolution:: ${JSON.stringify(resolution, /*replacer*/ undefined, 2)}`);
|
||||
baselines.push(`Resolution:: ${jsonToReadableText(resolution)}`);
|
||||
baselines.push("");
|
||||
}
|
||||
});
|
||||
@@ -314,11 +317,11 @@ describe("unittests:: moduleResolution:: Node module resolution - non-relative p
|
||||
baselines,
|
||||
/*hasDirectoryExists*/ true,
|
||||
{ name: realFileName, symlinks: [symlinkFileName] },
|
||||
{ name: "/app/node_modules/linked/package.json", content: '{"version": "0.0.0", "main": "./index"}' },
|
||||
{ name: "/app/node_modules/linked/package.json", content: jsonToReadableText({ version: "0.0.0", main: "./index" }) },
|
||||
);
|
||||
baselines.push(`Resolving "linked" from /app/app.ts when preserveSymlinks is ${preserveSymlinks}`);
|
||||
const resolution = ts.nodeModuleNameResolver("linked", "/app/app.ts", { preserveSymlinks }, host);
|
||||
baselines.push(`Resolution:: ${JSON.stringify(resolution, /*replacer*/ undefined, 2)}`);
|
||||
baselines.push(`Resolution:: ${jsonToReadableText(resolution)}`);
|
||||
baselines.push("");
|
||||
}
|
||||
});
|
||||
@@ -334,24 +337,24 @@ describe("unittests:: moduleResolution:: Node module resolution - non-relative p
|
||||
},
|
||||
{
|
||||
name: "/sub/node_modules/a/package.json",
|
||||
content: '{"version": "0.0.0", "main": "./index"}',
|
||||
content: jsonToReadableText({ version: "0.0.0", main: "./index" }),
|
||||
},
|
||||
);
|
||||
const compilerOptions: ts.CompilerOptions = { moduleResolution: ts.ModuleResolutionKind.Node10 };
|
||||
const cache = ts.createModuleResolutionCache("/", f => f);
|
||||
baselines.push(`Resolving "a" from /sub/dir/foo.ts`);
|
||||
let resolution = ts.resolveModuleName("a", "/sub/dir/foo.ts", compilerOptions, host, cache);
|
||||
baselines.push(`Resolution:: ${JSON.stringify(resolution, /*replacer*/ undefined, 2)}`);
|
||||
baselines.push(`Resolution:: ${jsonToReadableText(resolution)}`);
|
||||
baselines.push("");
|
||||
|
||||
baselines.push(`Resolving "a" from /sub/foo.ts`);
|
||||
resolution = ts.resolveModuleName("a", "/sub/foo.ts", compilerOptions, host, cache);
|
||||
baselines.push(`Resolution:: ${JSON.stringify(resolution, /*replacer*/ undefined, 2)}`);
|
||||
baselines.push(`Resolution:: ${jsonToReadableText(resolution)}`);
|
||||
baselines.push("");
|
||||
|
||||
baselines.push(`Resolving "a" from /foo.ts`);
|
||||
resolution = ts.resolveModuleName("a", "/foo.ts", compilerOptions, host, cache);
|
||||
baselines.push(`Resolution:: ${JSON.stringify(resolution, /*replacer*/ undefined, 2)}`);
|
||||
baselines.push(`Resolution:: ${jsonToReadableText(resolution)}`);
|
||||
baselines.push("");
|
||||
runBaseline("non relative uses originalPath for caching", baselines);
|
||||
});
|
||||
@@ -362,7 +365,7 @@ describe("unittests:: moduleResolution:: Node module resolution - non-relative p
|
||||
baselines,
|
||||
/*hasDirectoryExists*/ true,
|
||||
{ name: "/linked/index.d.ts", symlinks: ["/app/node_modules/linked/index.d.ts"] },
|
||||
{ name: "/app/node_modules/linked/package.json", content: '{"version": "0.0.0", "main": "./index"}' },
|
||||
{ name: "/app/node_modules/linked/package.json", content: jsonToReadableText({ version: "0.0.0", main: "./index" }) },
|
||||
);
|
||||
const cache = ts.createModuleResolutionCache("/", f => f);
|
||||
const compilerOptions: ts.CompilerOptions = { moduleResolution: ts.ModuleResolutionKind.Node10 };
|
||||
@@ -373,7 +376,7 @@ describe("unittests:: moduleResolution:: Node module resolution - non-relative p
|
||||
function baselineResolution(containingFile: string) {
|
||||
baselines.push(`Resolving "linked" from ${containingFile}`);
|
||||
const resolution = ts.resolveModuleName("linked", containingFile, compilerOptions, host, cache);
|
||||
baselines.push(`Resolution:: ${JSON.stringify(resolution, /*replacer*/ undefined, 2)}`);
|
||||
baselines.push(`Resolution:: ${jsonToReadableText(resolution)}`);
|
||||
baselines.push("");
|
||||
}
|
||||
});
|
||||
@@ -657,19 +660,19 @@ describe("unittests:: moduleResolution:: baseUrl augmented module resolution", (
|
||||
{
|
||||
baselines.push(`Resolving "folder2/file2" from ${file1.name}${hasDirectoryExists ? "" : " with host that doesnt have directoryExists"}`);
|
||||
const result = ts.resolveModuleName("folder2/file2", file1.name, options, host);
|
||||
baselines.push(`Resolution:: ${JSON.stringify(result, /*replacer*/ undefined, 2)}`);
|
||||
baselines.push(`Resolution:: ${jsonToReadableText(result)}`);
|
||||
baselines.push("");
|
||||
}
|
||||
{
|
||||
baselines.push(`Resolving "./file3" from ${file2.name}${hasDirectoryExists ? "" : " with host that doesnt have directoryExists"}`);
|
||||
const result = ts.resolveModuleName("./file3", file2.name, options, host);
|
||||
baselines.push(`Resolution:: ${JSON.stringify(result, /*replacer*/ undefined, 2)}`);
|
||||
baselines.push(`Resolution:: ${jsonToReadableText(result)}`);
|
||||
baselines.push("");
|
||||
}
|
||||
{
|
||||
baselines.push(`Resolving "/root/folder1/file1" from ${file2.name}${hasDirectoryExists ? "" : " with host that doesnt have directoryExists"}`);
|
||||
const result = ts.resolveModuleName("/root/folder1/file1", file2.name, options, host);
|
||||
baselines.push(`Resolution:: ${JSON.stringify(result, /*replacer*/ undefined, 2)}`);
|
||||
baselines.push(`Resolution:: ${jsonToReadableText(result)}`);
|
||||
baselines.push("");
|
||||
}
|
||||
}
|
||||
@@ -687,7 +690,7 @@ describe("unittests:: moduleResolution:: baseUrl augmented module resolution", (
|
||||
const main: File = { name: "/root/a/b/main.ts" };
|
||||
const m1: File = { name: "/root/m1.ts" }; // load file as module
|
||||
const m2: File = { name: "/root/m2/index.d.ts" }; // load folder as module
|
||||
const m3: File = { name: "/root/m3/package.json", content: JSON.stringify({ typings: "dist/typings.d.ts" }) };
|
||||
const m3: File = { name: "/root/m3/package.json", content: jsonToReadableText({ typings: "dist/typings.d.ts" }) };
|
||||
const m3Typings: File = { name: "/root/m3/dist/typings.d.ts" };
|
||||
const m4: File = { name: "/root/node_modules/m4.ts" }; // fallback to node
|
||||
|
||||
@@ -702,7 +705,7 @@ describe("unittests:: moduleResolution:: baseUrl augmented module resolution", (
|
||||
function check(name: string, caller: File) {
|
||||
baselines.push(`Resolving "${name}" from ${caller.name}${hasDirectoryExists ? "" : " with host that doesnt have directoryExists"}`);
|
||||
const result = ts.resolveModuleName(name, caller.name, options, host);
|
||||
baselines.push(`Resolution:: ${JSON.stringify(result, /*replacer*/ undefined, 2)}`);
|
||||
baselines.push(`Resolution:: ${jsonToReadableText(result)}`);
|
||||
baselines.push("");
|
||||
}
|
||||
}
|
||||
@@ -728,7 +731,7 @@ describe("unittests:: moduleResolution:: baseUrl augmented module resolution", (
|
||||
function check(name: string, caller: File) {
|
||||
baselines.push(`Resolving "${name}" from ${caller.name}${hasDirectoryExists ? "" : " with host that doesnt have directoryExists"}`);
|
||||
const result = ts.resolveModuleName(name, caller.name, options, host);
|
||||
baselines.push(`Resolution:: ${JSON.stringify(result, /*replacer*/ undefined, 2)}`);
|
||||
baselines.push(`Resolution:: ${jsonToReadableText(result)}`);
|
||||
baselines.push("");
|
||||
}
|
||||
}
|
||||
@@ -746,7 +749,7 @@ describe("unittests:: moduleResolution:: baseUrl augmented module resolution", (
|
||||
const file1: File = { name: "/root/folder1/file1.ts" };
|
||||
const file2: File = { name: "/root/generated/folder1/file2.ts" }; // load remapped file as module
|
||||
const file3: File = { name: "/root/generated/folder2/file3/index.d.ts" }; // load folder a module
|
||||
const file4Typings: File = { name: "/root/generated/folder2/file4/package.json", content: JSON.stringify({ typings: "dist/types.d.ts" }) };
|
||||
const file4Typings: File = { name: "/root/generated/folder2/file4/package.json", content: jsonToReadableText({ typings: "dist/types.d.ts" }) };
|
||||
const file4: File = { name: "/root/generated/folder2/file4/dist/types.d.ts" }; // load file pointed by typings
|
||||
const file5: File = { name: "/root/someanotherfolder/file5/index.d.ts" }; // load remapped module from folder
|
||||
const file6: File = { name: "/root/node_modules/file6.ts" }; // fallback to node
|
||||
@@ -780,7 +783,7 @@ describe("unittests:: moduleResolution:: baseUrl augmented module resolution", (
|
||||
function check(name: string) {
|
||||
baselines.push(`Resolving "${name}" from ${main.name}${hasDirectoryExists ? "" : " with host that doesnt have directoryExists"}`);
|
||||
const result = ts.resolveModuleName(name, main.name, options, host);
|
||||
baselines.push(`Resolution:: ${JSON.stringify(result, /*replacer*/ undefined, 2)}`);
|
||||
baselines.push(`Resolution:: ${jsonToReadableText(result)}`);
|
||||
baselines.push("");
|
||||
}
|
||||
}
|
||||
@@ -825,7 +828,7 @@ describe("unittests:: moduleResolution:: baseUrl augmented module resolution", (
|
||||
function check(name: string) {
|
||||
baselines.push(`Resolving "${name}" from ${main.name}${hasDirectoryExists ? "" : " with host that doesnt have directoryExists"}`);
|
||||
const result = ts.resolveModuleName(name, main.name, options, host);
|
||||
baselines.push(`Resolution:: ${JSON.stringify(result, /*replacer*/ undefined, 2)}`);
|
||||
baselines.push(`Resolution:: ${jsonToReadableText(result)}`);
|
||||
baselines.push("");
|
||||
}
|
||||
}
|
||||
@@ -857,7 +860,7 @@ describe("unittests:: moduleResolution:: baseUrl augmented module resolution", (
|
||||
function check(name: string, container: File) {
|
||||
baselines.push(`Resolving "${name}" from ${container.name}${hasDirectoryExists ? "" : " with host that doesnt have directoryExists"}`);
|
||||
const result = ts.resolveModuleName(name, container.name, options, host);
|
||||
baselines.push(`Resolution:: ${JSON.stringify(result, /*replacer*/ undefined, 2)}`);
|
||||
baselines.push(`Resolution:: ${jsonToReadableText(result)}`);
|
||||
baselines.push("");
|
||||
}
|
||||
}
|
||||
@@ -889,7 +892,7 @@ describe("unittests:: moduleResolution:: baseUrl augmented module resolution", (
|
||||
function check(name: string, container: File) {
|
||||
baselines.push(`Resolving "${name}" from ${container.name}${hasDirectoryExists ? "" : " with host that doesnt have directoryExists"}`);
|
||||
const result = ts.resolveModuleName(name, container.name, options, host);
|
||||
baselines.push(`Resolution:: ${JSON.stringify(result, /*replacer*/ undefined, 2)}`);
|
||||
baselines.push(`Resolution:: ${jsonToReadableText(result)}`);
|
||||
baselines.push("");
|
||||
}
|
||||
}
|
||||
@@ -903,7 +906,7 @@ describe("unittests:: moduleResolution:: baseUrl augmented module resolution", (
|
||||
|
||||
function test(hasDirectoryExists: boolean) {
|
||||
const app: File = { name: "/root/src/app.ts" };
|
||||
const libsPackage: File = { name: "/root/src/libs/guid/package.json", content: JSON.stringify({ typings: "dist/guid.d.ts" }) };
|
||||
const libsPackage: File = { name: "/root/src/libs/guid/package.json", content: jsonToReadableText({ typings: "dist/guid.d.ts" }) };
|
||||
const libsTypings: File = { name: "/root/src/libs/guid/dist/guid.d.ts" };
|
||||
const host = createModuleResolutionHost(baselines, hasDirectoryExists, app, libsPackage, libsTypings);
|
||||
const options: ts.CompilerOptions = {
|
||||
@@ -915,7 +918,7 @@ describe("unittests:: moduleResolution:: baseUrl augmented module resolution", (
|
||||
};
|
||||
baselines.push(`Resolving "libs/guid" from ${app.name}${hasDirectoryExists ? "" : " with host that doesnt have directoryExists"}`);
|
||||
const result = ts.resolveModuleName("libs/guid", app.name, options, host);
|
||||
baselines.push(`Resolution:: ${JSON.stringify(result, /*replacer*/ undefined, 2)}`);
|
||||
baselines.push(`Resolution:: ${jsonToReadableText(result)}`);
|
||||
baselines.push("");
|
||||
}
|
||||
});
|
||||
@@ -939,7 +942,7 @@ describe("unittests:: moduleResolution:: Type reference directive resolution: ",
|
||||
const host = createModuleResolutionHost(baselines, hasDirectoryExists, ...[initialFile, targetFile].concat(...otherFiles));
|
||||
baselines.push(`Resolving "${typeDirective}" from ${initialFile.name} typesRoots: ${typesRoot ? `[${typesRoot}]` : undefined}${hasDirectoryExists ? "" : " with host that doesnt have directoryExists"}`);
|
||||
const result = ts.resolveTypeReferenceDirective(typeDirective, initialFile.name, typesRoot ? { typeRoots: [typesRoot] } : {}, host);
|
||||
baselines.push(`Resolution:: ${JSON.stringify(result, /*replacer*/ undefined, 2)}`);
|
||||
baselines.push(`Resolution:: ${jsonToReadableText(result)}`);
|
||||
baselines.push("");
|
||||
}
|
||||
|
||||
@@ -957,7 +960,7 @@ describe("unittests:: moduleResolution:: Type reference directive resolution: ",
|
||||
{
|
||||
const f1 = { name: "/root/src/app.ts" };
|
||||
const f2 = { name: "/root/src/types/lib/typings/lib.d.ts" };
|
||||
const packageFile = { name: "/root/src/types/lib/package.json", content: JSON.stringify({ types: "typings/lib.d.ts" }) };
|
||||
const packageFile = { name: "/root/src/types/lib/package.json", content: jsonToReadableText({ types: "typings/lib.d.ts" }) };
|
||||
test(baselines, /*typesRoot*/ "/root/src/types", /* typeDirective */ "lib", f1, f2, packageFile);
|
||||
}
|
||||
{
|
||||
@@ -968,7 +971,7 @@ describe("unittests:: moduleResolution:: Type reference directive resolution: ",
|
||||
{
|
||||
const f1 = { name: "/root/src/app.ts" };
|
||||
const f2 = { name: "/root/src/node_modules/lib/typings/lib.d.ts" };
|
||||
const packageFile = { name: "/root/src/node_modules/lib/package.json", content: JSON.stringify({ types: "typings/lib.d.ts" }) };
|
||||
const packageFile = { name: "/root/src/node_modules/lib/package.json", content: jsonToReadableText({ types: "typings/lib.d.ts" }) };
|
||||
test(baselines, /*typesRoot*/ "/root/src/types", /* typeDirective */ "lib", f1, f2, packageFile);
|
||||
}
|
||||
{
|
||||
@@ -979,7 +982,7 @@ describe("unittests:: moduleResolution:: Type reference directive resolution: ",
|
||||
{
|
||||
const f1 = { name: "/root/src/app.ts" };
|
||||
const f2 = { name: "/root/src/node_modules/@types/lib/typings/lib.d.ts" };
|
||||
const packageFile = { name: "/root/src/node_modules/@types/lib/package.json", content: JSON.stringify({ types: "typings/lib.d.ts" }) };
|
||||
const packageFile = { name: "/root/src/node_modules/@types/lib/package.json", content: jsonToReadableText({ types: "typings/lib.d.ts" }) };
|
||||
test(baselines, /*typesRoot*/ "/root/src/types", /* typeDirective */ "lib", f1, f2, packageFile);
|
||||
}
|
||||
runBaseline("type reference from primary location", baselines);
|
||||
@@ -999,7 +1002,7 @@ describe("unittests:: moduleResolution:: Type reference directive resolution: ",
|
||||
{
|
||||
const f1 = { name: "/root/src/app.ts" };
|
||||
const f2 = { name: "/root/node_modules/lib/typings/lib.d.ts" };
|
||||
const packageFile = { name: "/root/node_modules/lib/package.json", content: JSON.stringify({ typings: "typings/lib.d.ts" }) };
|
||||
const packageFile = { name: "/root/node_modules/lib/package.json", content: jsonToReadableText({ typings: "typings/lib.d.ts" }) };
|
||||
test(baselines, /*typesRoot*/ "/root/src/types", /* typeDirective */ "lib", f1, f2, packageFile);
|
||||
}
|
||||
{
|
||||
@@ -1010,7 +1013,7 @@ describe("unittests:: moduleResolution:: Type reference directive resolution: ",
|
||||
{
|
||||
const f1 = { name: "/root/src/app.ts" };
|
||||
const f2 = { name: "/root/node_modules/@types/lib/typings/lib.d.ts" };
|
||||
const packageFile = { name: "/root/node_modules/@types/lib/package.json", content: JSON.stringify({ typings: "typings/lib.d.ts" }) };
|
||||
const packageFile = { name: "/root/node_modules/@types/lib/package.json", content: jsonToReadableText({ typings: "typings/lib.d.ts" }) };
|
||||
test(baselines, /*typesRoot*/ "/root/src/types", /* typeDirective */ "lib", f1, f2, packageFile);
|
||||
}
|
||||
runBaseline("type reference from secondary location", baselines);
|
||||
|
||||
@@ -3,6 +3,9 @@ import * as fakes from "../_namespaces/fakes";
|
||||
import * as Harness from "../_namespaces/Harness";
|
||||
import * as ts from "../_namespaces/ts";
|
||||
import * as vfs from "../_namespaces/vfs";
|
||||
import {
|
||||
jsonToReadableText,
|
||||
} from "./helpers";
|
||||
|
||||
function verifyMissingFilePaths(missingPaths: readonly ts.Path[], expected: readonly string[]) {
|
||||
assert.isDefined(missingPaths);
|
||||
@@ -145,7 +148,7 @@ describe("unittests:: Program.isSourceFileFromExternalLibrary", () => {
|
||||
// In this example '/node_modules/foo/index.d.ts' will redirect to '/node_modules/bar/node_modules/foo/index.d.ts'.
|
||||
const a = new documents.TextDocument("/a.ts", 'import * as bar from "bar"; import * as foo from "foo";');
|
||||
const bar = new documents.TextDocument("/node_modules/bar/index.d.ts", 'import * as foo from "foo";');
|
||||
const fooPackageJsonText = '{ "name": "foo", "version": "1.2.3" }';
|
||||
const fooPackageJsonText = jsonToReadableText({ name: "foo", version: "1.2.3" });
|
||||
const fooIndexText = "export const x: number;";
|
||||
const barFooPackage = new documents.TextDocument("/node_modules/bar/node_modules/foo/package.json", fooPackageJsonText);
|
||||
const barFooIndex = new documents.TextDocument("/node_modules/bar/node_modules/foo/index.d.ts", fooIndexText);
|
||||
@@ -177,7 +180,7 @@ describe("unittests:: Program.isSourceFileFromExternalLibrary", () => {
|
||||
describe("unittests:: Program.getNodeCount / Program.getIdentifierCount", () => {
|
||||
it("works on projects that have .json files", () => {
|
||||
const main = new documents.TextDocument("/main.ts", 'export { version } from "./package.json";');
|
||||
const pkg = new documents.TextDocument("/package.json", '{"version": "1.0.0"}');
|
||||
const pkg = new documents.TextDocument("/package.json", jsonToReadableText({ version: "1.0.0" }));
|
||||
|
||||
const fs = vfs.createFromFileSystem(Harness.IO, /*ignoreCase*/ false, { documents: [main, pkg], cwd: "/" });
|
||||
const program = ts.createProgram(["/main.ts"], { resolveJsonModule: true }, new fakes.CompilerHost(fs, { newLine: ts.NewLineKind.LineFeed }));
|
||||
|
||||
@@ -231,3 +231,34 @@ describe("unittests:: Public APIs:: getChild* methods on EndOfFileToken with JSD
|
||||
assert.notEqual(endOfFileToken.getChildAt(0), /*expected*/ undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe("unittests:: Public APIs:: get syntactic and effective modifiers", () => {
|
||||
it("caches and reports correct flags in TS file", () => {
|
||||
// https://github.com/microsoft/TypeScript/issues/42189
|
||||
const content = `
|
||||
class C {
|
||||
/** @private */
|
||||
prop = 1;
|
||||
}`;
|
||||
const sourceFile = ts.createSourceFile("/file.ts", content, ts.ScriptTarget.ESNext, /*setParentNodes*/ true);
|
||||
const classNode = sourceFile.statements[0] as ts.ClassDeclaration;
|
||||
const propNode = classNode.members[0] as ts.PropertyDeclaration;
|
||||
assert.equal(ts.ModifierFlags.None, ts.getSyntacticModifierFlags(propNode));
|
||||
assert.equal(ts.ModifierFlags.None, ts.getEffectiveModifierFlags(propNode));
|
||||
assert.equal(ts.ModifierFlags.None, ts.getSyntacticModifierFlags(propNode));
|
||||
});
|
||||
it("caches and reports correct flags in JS file", () => {
|
||||
// https://github.com/microsoft/TypeScript/issues/42189
|
||||
const content = `
|
||||
class C {
|
||||
/** @private */
|
||||
prop = 1;
|
||||
}`;
|
||||
const sourceFile = ts.createSourceFile("/file.js", content, ts.ScriptTarget.ESNext, /*setParentNodes*/ true);
|
||||
const classNode = sourceFile.statements[0] as ts.ClassDeclaration;
|
||||
const propNode = classNode.members[0] as ts.PropertyDeclaration;
|
||||
assert.equal(ts.ModifierFlags.None, ts.getSyntacticModifierFlags(propNode));
|
||||
assert.equal(ts.ModifierFlags.Private, ts.getEffectiveModifierFlags(propNode));
|
||||
assert.equal(ts.ModifierFlags.None, ts.getSyntacticModifierFlags(propNode));
|
||||
});
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user