Merge remote-tracking branch 'origin/main' into release-5.3

This commit is contained in:
TypeScript Bot
2023-10-31 17:16:45 +00:00
2863 changed files with 236114 additions and 67628 deletions
+7 -3
View File
@@ -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"
]
}
+8 -10
View File
@@ -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.
+3 -7
View File
@@ -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
+4 -10
View File
@@ -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.
+1 -2
View File
@@ -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
+15
View File
@@ -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:
- '*'
+1
View File
@@ -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
View File
@@ -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
+4 -4
View File
@@ -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
+7 -7
View File
@@ -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
+2 -2
View File
@@ -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
+3 -5
View File
@@ -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 }}
+4 -4
View File
@@ -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
+5 -7
View File
@@ -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
+8 -8
View File
@@ -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
+1 -1
View File
@@ -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 }}
+6 -5
View File
@@ -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 }}
-36
View File
@@ -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
+3 -6
View File
@@ -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
-1
View File
@@ -2,7 +2,6 @@
# TypeScript
[![GitHub Actions CI](https://github.com/microsoft/TypeScript/workflows/CI/badge.svg)](https://github.com/microsoft/TypeScript/actions?query=workflow%3ACI)
[![Devops Build Status](https://dev.azure.com/typescript/TypeScript/_apis/build/status/Typescript/node10)](https://dev.azure.com/typescript/TypeScript/_build?definitionId=7)
[![npm version](https://badge.fury.io/js/typescript.svg)](https://www.npmjs.com/package/typescript)
[![Downloads](https://img.shields.io/npm/dm/typescript.svg)](https://www.npmjs.com/package/typescript)
[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/microsoft/TypeScript/badge)](https://api.securityscorecards.dev/projects/github.com/microsoft/TypeScript)
+11
View File
@@ -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).
+712 -559
View File
File diff suppressed because it is too large Load Diff
+3 -2
View File
@@ -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": {
+2 -2
View File
@@ -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();
}
+2 -2
View File
@@ -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
View File
@@ -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;
}
+2 -2
View File
@@ -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);
+13 -1
View File
@@ -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",
+1
View File
@@ -1182,6 +1182,7 @@ export const notImplementedResolver: EmitResolver = {
isBindingCapturedByNode: notImplemented,
getDeclarationStatementsForSourceFile: notImplemented,
isImportRequiredByAugmentation: notImplemented,
tryFindAmbientModule: notImplemented,
};
/**
+12 -11
View File
@@ -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 */
+28 -6
View File
@@ -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 {
+55 -28
View File
@@ -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,
);
}
+14 -3
View File
@@ -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;
+1 -2
View File
@@ -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?.(
+40 -5
View File
@@ -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) {
+27 -4
View File
@@ -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(
File diff suppressed because it is too large Load Diff
+3 -1
View File
@@ -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)));
}
+27 -27
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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();
+12 -6
View File
@@ -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
View File
@@ -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 {
+18 -43
View File
@@ -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;
};
+1 -1
View File
@@ -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)) {
+58 -49
View File
@@ -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
View File
@@ -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(
+150
View File
@@ -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,
});
}
+147
View File
@@ -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
View File
@@ -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) {
+16
View File
@@ -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));
}
+11 -11
View File
@@ -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]
+45 -45
View File
@@ -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
View File
@@ -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;
}
+3 -3
View File
@@ -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
View File
@@ -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 {
-16
View File
@@ -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);
}
+1 -1
View File
@@ -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)),
);
+2 -3
View File
@@ -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;
+15 -8
View File
@@ -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)),
),
+43 -8
View File
@@ -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";
}
+19 -8
View File
@@ -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) {
+6
View File
@@ -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);
}
+2 -4
View File
@@ -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;
}
+100 -36
View File
@@ -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;
+47 -6
View File
@@ -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;
}
+1 -2
View File
@@ -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,
+11 -2
View File
@@ -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)),
}];
},
});
+9 -10
View File
@@ -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 },
]);
});
});
+4
View File
@@ -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);
}
+6 -3
View File
@@ -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",
},
);
}
+6 -3
View File
@@ -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,
};
}
+35 -34
View File
@@ -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,
};
}
+19 -391
View File
@@ -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));
}
-15
View File
@@ -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}`)!,
};
}
+41 -38
View 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);
+5 -2
View File
@@ -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 }));
+31
View File
@@ -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