Compare commits
80 Commits
brent/docu
...
micha/ty-p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ad2e09430 | ||
|
|
dd3a985109 | ||
|
|
12f5ea51e3 | ||
|
|
f9afcc400c | ||
|
|
768c5a2285 | ||
|
|
139149f87b | ||
|
|
2de4464e92 | ||
|
|
ded4d4bbe9 | ||
|
|
eef403f6cf | ||
|
|
adef89eb7c | ||
|
|
e3498121b4 | ||
|
|
5e7fc9a4e1 | ||
|
|
3c5956e93d | ||
|
|
81f34fbc8e | ||
|
|
184f487c84 | ||
|
|
969c8a547e | ||
|
|
acdda78189 | ||
|
|
c28c1f534d | ||
|
|
b723917463 | ||
|
|
5decf94644 | ||
|
|
89a55dd09f | ||
|
|
8710a8c4ac | ||
|
|
e245c1d76e | ||
|
|
4c175fa0e1 | ||
|
|
ed64c4d943 | ||
|
|
f1e6c9c3a0 | ||
|
|
d9fe996e64 | ||
|
|
5ea30c4c53 | ||
|
|
ccc9132f73 | ||
|
|
65021fcee9 | ||
|
|
aa21b70a8b | ||
|
|
22ce0c8a51 | ||
|
|
d6a7c9b4ed | ||
|
|
4745d15fff | ||
|
|
06db474f20 | ||
|
|
664686bdbc | ||
|
|
4a937543b9 | ||
|
|
ec034fc359 | ||
|
|
29d7f22c1f | ||
|
|
8fc4349fd3 | ||
|
|
816b19c4a6 | ||
|
|
87406b43ea | ||
|
|
422e99ea70 | ||
|
|
ea6730f546 | ||
|
|
a46835c224 | ||
|
|
884e83591e | ||
|
|
6b3dd28e63 | ||
|
|
572f57aa3c | ||
|
|
ed423e0ae2 | ||
|
|
fee4e2d72a | ||
|
|
b6e84eca16 | ||
|
|
b4c2825afd | ||
|
|
ad41728204 | ||
|
|
3ec63b964c | ||
|
|
f9a0e1e3f6 | ||
|
|
ef4507be96 | ||
|
|
3398ab23a9 | ||
|
|
5b475b45aa | ||
|
|
2a959ef3f2 | ||
|
|
674d3902c6 | ||
|
|
1a18ada931 | ||
|
|
dde0d0af68 | ||
|
|
b342f60b40 | ||
|
|
2151c3d351 | ||
|
|
cdb7a9fb33 | ||
|
|
df1552b9a4 | ||
|
|
77de3df150 | ||
|
|
0f18a08a0a | ||
|
|
b63b3c13fb | ||
|
|
270d755621 | ||
|
|
9809405b05 | ||
|
|
28cdbb18c5 | ||
|
|
f9d1a282fb | ||
|
|
0bac023cd2 | ||
|
|
30efab8138 | ||
|
|
58d25129aa | ||
|
|
e177cc2a5a | ||
|
|
30ce679b9a | ||
|
|
76854fdb15 | ||
|
|
5a2d3cda3d |
67
.claude/settings.local.json
Normal file
67
.claude/settings.local.json
Normal file
@@ -0,0 +1,67 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(cargo build:*)",
|
||||
"Read(//home/micha/astral/discord.py/discord/**)",
|
||||
"Bash(source:*)",
|
||||
"Bash(./target/profiling/ty check:*)",
|
||||
"Bash(tee:*)",
|
||||
"Read(//home/micha/astral/discord.py/.venv/**)",
|
||||
"Read(//home/micha/astral/discord.py/**)",
|
||||
"Bash(perf record:*)",
|
||||
"Bash(perf report:*)",
|
||||
"Bash(time:*)",
|
||||
"Bash(../ruff/target/profiling/ty check discord/audit_logs.py -vv)",
|
||||
"Bash(sed:*)",
|
||||
"Read(//home/micha/git/TypeScript/**)",
|
||||
"Bash(cargo test:*)",
|
||||
"Bash(MDTEST_TEST_FILTER='union_types.md - Union types - Unions of tuples' cargo test -p ty_python_semantic --test mdtest -- mdtest__union_types)",
|
||||
"Bash(timeout 10 cargo test --package ty_python_semantic --lib types::tests::divergent_type)",
|
||||
"Bash(timeout 30 cargo test:*)",
|
||||
"Bash(git stash:*)",
|
||||
"Bash(timeout 60 time:*)",
|
||||
"Bash(for i in 1 2 3 4 5)",
|
||||
"Bash(do echo \"Run $i:\")",
|
||||
"Bash(done)",
|
||||
"Bash(for i in 1 2 3)",
|
||||
"Bash(cargo fuzz run:*)",
|
||||
"Bash(timeout 60 cargo fuzz run -s none ty_check_invalid_syntax -- -timeout=1)",
|
||||
"Bash(for:*)",
|
||||
"Bash(do echo \"=== Checking $crash ===\")",
|
||||
"Bash(uvx ty@latest check \"$crash\")",
|
||||
"Bash(do echo \"=== $crash ===\")",
|
||||
"Bash(while read crash)",
|
||||
"Bash(cargo fuzz cmin:*)",
|
||||
"Bash(cargo +nightly fuzz cmin:*)",
|
||||
"Bash(timeout 120 cargo fuzz run -s none ty_check_invalid_syntax -- -timeout=1)",
|
||||
"Bash(awk:*)",
|
||||
"Bash(while read file)",
|
||||
"Bash(cat:*)",
|
||||
"Bash(uvx ty@latest:*)",
|
||||
"Bash(do cp \"$crash\" /tmp/isolated_crash.py)",
|
||||
"Bash(echo \"=== $crash ===\")",
|
||||
"Bash(do echo \"=== test$i.py ===\")",
|
||||
"Bash(do echo \"=== Testing $crash ===\")",
|
||||
"Bash(tree:*)",
|
||||
"Bash(cut:*)",
|
||||
"Bash(grep:*)",
|
||||
"Bash(ls:*)",
|
||||
"Bash(xargs basename:*)",
|
||||
"Bash(wc:*)",
|
||||
"Bash(find:*)",
|
||||
"Bash({} ;)",
|
||||
"Bash(git checkout:*)",
|
||||
"Bash(do)",
|
||||
"Bash(if ! grep -q \"use crate::types\" \"$f\")",
|
||||
"Bash(! grep -q \"crate::types::\" \"$f\")",
|
||||
"Bash(then)",
|
||||
"Bash(else)",
|
||||
"Bash(fi)",
|
||||
"Bash(1)",
|
||||
"Bash(__NEW_LINE__ echo \"Done\")",
|
||||
"Bash(rm:*)"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
}
|
||||
}
|
||||
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -22,6 +22,7 @@ crates/ruff_linter/resources/test/fixtures/pyupgrade/UP018_CR.py text eol=cr
|
||||
crates/ruff_linter/resources/test/fixtures/pyupgrade/UP018_LF.py text eol=lf
|
||||
|
||||
crates/ruff_python_parser/resources/inline linguist-generated=true
|
||||
crates/ty_python_semantic/resources/mdtest/external/*.lock linguist-generated=true
|
||||
|
||||
ruff.schema.json -diff linguist-generated=true text=auto eol=lf
|
||||
ty.schema.json -diff linguist-generated=true text=auto eol=lf
|
||||
|
||||
2
.github/actionlint.yaml
vendored
2
.github/actionlint.yaml
vendored
@@ -4,10 +4,12 @@
|
||||
self-hosted-runner:
|
||||
# Various runners we use that aren't recognized out-of-the-box by actionlint:
|
||||
labels:
|
||||
- depot-ubuntu-24.04-4
|
||||
- depot-ubuntu-latest-8
|
||||
- depot-ubuntu-22.04-16
|
||||
- depot-ubuntu-22.04-32
|
||||
- depot-windows-2022-16
|
||||
- depot-ubuntu-22.04-arm-4
|
||||
- github-windows-2025-x86_64-8
|
||||
- github-windows-2025-x86_64-16
|
||||
- codspeed-macro
|
||||
|
||||
126
.github/workflows/ci.yaml
vendored
126
.github/workflows/ci.yaml
vendored
@@ -952,7 +952,7 @@ jobs:
|
||||
tool: cargo-codspeed
|
||||
|
||||
- name: "Build benchmarks"
|
||||
run: cargo codspeed build --features "codspeed,instrumented" --profile profiling --no-default-features -p ruff_benchmark --bench formatter --bench lexer --bench linter --bench parser
|
||||
run: cargo codspeed build --features "codspeed,ruff_instrumented" --profile profiling --no-default-features -p ruff_benchmark --bench formatter --bench lexer --bench linter --bench parser
|
||||
|
||||
- name: "Run benchmarks"
|
||||
uses: CodSpeedHQ/action@346a2d8a8d9d38909abd0bc3d23f773110f076ad # v4.4.1
|
||||
@@ -960,9 +960,9 @@ jobs:
|
||||
mode: simulation
|
||||
run: cargo codspeed run
|
||||
|
||||
benchmarks-instrumented-ty:
|
||||
name: "benchmarks instrumented (ty)"
|
||||
runs-on: ubuntu-24.04
|
||||
benchmarks-instrumented-ty-build:
|
||||
name: "benchmarks instrumented ty (build)"
|
||||
runs-on: depot-ubuntu-24.04-4
|
||||
needs: determine_changes
|
||||
if: |
|
||||
github.repository == 'astral-sh/ruff' &&
|
||||
@@ -971,9 +971,83 @@ jobs:
|
||||
needs.determine_changes.outputs.ty == 'true'
|
||||
)
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- name: "Checkout Branch"
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
|
||||
- name: "Install codspeed"
|
||||
uses: taiki-e/install-action@3575e532701a5fc614b0c842e4119af4cc5fd16d # v2.62.60
|
||||
with:
|
||||
tool: cargo-codspeed
|
||||
|
||||
- name: "Build benchmarks"
|
||||
run: cargo codspeed build -m instrumentation --features "codspeed,ty_instrumented" --profile profiling --no-default-features -p ruff_benchmark --bench ty
|
||||
|
||||
- name: "Upload benchmark binary"
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: benchmarks-instrumented-ty-binary
|
||||
path: target/codspeed/simulation/ruff_benchmark
|
||||
retention-days: 1
|
||||
|
||||
benchmarks-instrumented-ty-run:
|
||||
name: "benchmarks instrumented ty (${{ matrix.benchmark }})"
|
||||
runs-on: ubuntu-24.04
|
||||
needs: benchmarks-instrumented-ty-build
|
||||
timeout-minutes: 20
|
||||
permissions:
|
||||
contents: read # required for actions/checkout
|
||||
id-token: write # required for OIDC authentication with CodSpeed
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
benchmark:
|
||||
- "check_file|micro|anyio"
|
||||
- "attrs|hydra|datetype"
|
||||
steps:
|
||||
- name: "Checkout Branch"
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
|
||||
|
||||
- name: "Install codspeed"
|
||||
uses: taiki-e/install-action@3575e532701a5fc614b0c842e4119af4cc5fd16d # v2.62.60
|
||||
with:
|
||||
tool: cargo-codspeed
|
||||
|
||||
- name: "Download benchmark binary"
|
||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v4.3.0
|
||||
with:
|
||||
name: benchmarks-instrumented-ty-binary
|
||||
path: target/codspeed/simulation/ruff_benchmark
|
||||
|
||||
- name: "Restore binary permissions"
|
||||
run: chmod +x target/codspeed/simulation/ruff_benchmark/ty
|
||||
|
||||
- name: "Run benchmarks"
|
||||
uses: CodSpeedHQ/action@346a2d8a8d9d38909abd0bc3d23f773110f076ad # v4.4.1
|
||||
with:
|
||||
mode: simulation
|
||||
run: cargo codspeed run --bench ty "${{ matrix.benchmark }}"
|
||||
|
||||
benchmarks-walltime-build:
|
||||
name: "benchmarks walltime (build)"
|
||||
# We only run this job if `github.repository == 'astral-sh/ruff'`,
|
||||
# so hardcoding depot here is fine
|
||||
runs-on: depot-ubuntu-22.04-arm-4
|
||||
needs: determine_changes
|
||||
if: ${{ github.repository == 'astral-sh/ruff' && !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.ty == 'true' || github.ref == 'refs/heads/main') }}
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- name: "Checkout Branch"
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
@@ -994,49 +1068,51 @@ jobs:
|
||||
tool: cargo-codspeed
|
||||
|
||||
- name: "Build benchmarks"
|
||||
run: cargo codspeed build --features "codspeed,instrumented" --profile profiling --no-default-features -p ruff_benchmark --bench ty
|
||||
run: cargo codspeed build -m walltime --features "codspeed,ty_walltime" --profile profiling --no-default-features -p ruff_benchmark
|
||||
|
||||
- name: "Run benchmarks"
|
||||
uses: CodSpeedHQ/action@346a2d8a8d9d38909abd0bc3d23f773110f076ad # v4.4.1
|
||||
- name: "Upload benchmark binary"
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
mode: simulation
|
||||
run: cargo codspeed run
|
||||
name: benchmarks-walltime-binary
|
||||
path: target/codspeed/walltime/ruff_benchmark
|
||||
retention-days: 1
|
||||
|
||||
benchmarks-walltime:
|
||||
name: "benchmarks walltime (${{ matrix.benchmarks }})"
|
||||
benchmarks-walltime-run:
|
||||
name: "benchmarks walltime (${{ matrix.benchmark }})"
|
||||
runs-on: codspeed-macro
|
||||
needs: determine_changes
|
||||
if: ${{ github.repository == 'astral-sh/ruff' && !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.ty == 'true' || github.ref == 'refs/heads/main') }}
|
||||
needs: benchmarks-walltime-build
|
||||
timeout-minutes: 20
|
||||
permissions:
|
||||
contents: read # required for actions/checkout
|
||||
id-token: write # required for OIDC authentication with CodSpeed
|
||||
strategy:
|
||||
matrix:
|
||||
benchmarks:
|
||||
- "medium|multithreaded"
|
||||
- "small|large"
|
||||
benchmark:
|
||||
- colour_science
|
||||
- "pandas|tanjun|altair"
|
||||
- "static_frame|sympy"
|
||||
- "pydantic|multithreaded|freqtrade"
|
||||
steps:
|
||||
- name: "Checkout Branch"
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
|
||||
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
|
||||
- name: "Install codspeed"
|
||||
uses: taiki-e/install-action@3575e532701a5fc614b0c842e4119af4cc5fd16d # v2.62.60
|
||||
with:
|
||||
tool: cargo-codspeed
|
||||
|
||||
- name: "Build benchmarks"
|
||||
run: cargo codspeed build --features "codspeed,walltime" --profile profiling --no-default-features -p ruff_benchmark
|
||||
- name: "Download benchmark binary"
|
||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||
with:
|
||||
name: benchmarks-walltime-binary
|
||||
path: target/codspeed/walltime/ruff_benchmark
|
||||
|
||||
- name: "Restore binary permissions"
|
||||
run: chmod +x target/codspeed/walltime/ruff_benchmark/ty_walltime
|
||||
|
||||
- name: "Run benchmarks"
|
||||
uses: CodSpeedHQ/action@346a2d8a8d9d38909abd0bc3d23f773110f076ad # v4.4.1
|
||||
@@ -1047,4 +1123,4 @@ jobs:
|
||||
CODSPEED_PERF_ENABLED: false
|
||||
with:
|
||||
mode: walltime
|
||||
run: cargo codspeed run --bench ty_walltime "${{ matrix.benchmarks }}"
|
||||
run: cargo codspeed run --bench ty_walltime -m walltime "${{ matrix.benchmark }}"
|
||||
|
||||
2
.github/workflows/daily_fuzz.yaml
vendored
2
.github/workflows/daily_fuzz.yaml
vendored
@@ -62,7 +62,7 @@ jobs:
|
||||
name: Create an issue if the daily fuzz surfaced any bugs
|
||||
runs-on: ubuntu-latest
|
||||
needs: fuzz
|
||||
if: ${{ github.repository == 'astral-sh/ruff' && always() && github.event_name == 'schedule' && needs.fuzz.result == 'failure' }}
|
||||
if: ${{ github.repository == 'astral-sh/ruff' && always() && github.event_name == 'schedule' && needs.fuzz.result != 'success' }}
|
||||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
|
||||
5
.github/workflows/mypy_primer.yaml
vendored
5
.github/workflows/mypy_primer.yaml
vendored
@@ -6,6 +6,11 @@ on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "crates/ty*/**"
|
||||
- "!crates/ty_ide/**"
|
||||
- "!crates/ty_server/**"
|
||||
- "!crates/ty_test/**"
|
||||
- "!crates/ty_completion_eval/**"
|
||||
- "!crates/ty_wasm/**"
|
||||
- "crates/ruff_db"
|
||||
- "crates/ruff_python_ast"
|
||||
- "crates/ruff_python_parser"
|
||||
|
||||
41
.github/workflows/sync_typeshed.yaml
vendored
41
.github/workflows/sync_typeshed.yaml
vendored
@@ -16,8 +16,7 @@ name: Sync typeshed
|
||||
# 3. Once the Windows worker is done, a MacOS worker:
|
||||
# a. Checks out the branch created by the Linux worker
|
||||
# b. Syncs all docstrings available on MacOS that are not available on Linux or Windows
|
||||
# c. Attempts to update any snapshots that might have changed
|
||||
# (this sub-step is allowed to fail)
|
||||
# c. Formats the code again
|
||||
# d. Commits the changes and pushes them to the same upstream branch
|
||||
# e. Creates a PR against the `main` branch using the branch all three workers have pushed to
|
||||
# 4. If any of steps 1-3 failed, an issue is created in the `astral-sh/ruff` repository
|
||||
@@ -198,42 +197,6 @@ jobs:
|
||||
run: |
|
||||
rm "${VENDORED_TYPESHED}/pyproject.toml"
|
||||
git commit -am "Remove pyproject.toml file"
|
||||
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||
- name: "Install Rust toolchain"
|
||||
if: ${{ success() }}
|
||||
run: rustup show
|
||||
- name: "Install mold"
|
||||
if: ${{ success() }}
|
||||
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||
- name: "Install cargo nextest"
|
||||
if: ${{ success() }}
|
||||
uses: taiki-e/install-action@3575e532701a5fc614b0c842e4119af4cc5fd16d # v2.62.60
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
- name: "Install cargo insta"
|
||||
if: ${{ success() }}
|
||||
uses: taiki-e/install-action@3575e532701a5fc614b0c842e4119af4cc5fd16d # v2.62.60
|
||||
with:
|
||||
tool: cargo-insta
|
||||
- name: Update snapshots
|
||||
if: ${{ success() }}
|
||||
run: |
|
||||
cargo r \
|
||||
--profile=profiling \
|
||||
-p ty_completion_eval \
|
||||
-- all --tasks ./crates/ty_completion_eval/completion-evaluation-tasks.csv
|
||||
|
||||
# The `cargo insta` docs indicate that `--unreferenced=delete` might be a good option,
|
||||
# but from local testing it appears to just revert all changes made by `cargo insta test --accept`.
|
||||
#
|
||||
# If there were only snapshot-related failures, `cargo insta test --accept` will have exit code 0,
|
||||
# but if there were also other mdtest failures (for example), it will return a nonzero exit code.
|
||||
# We don't care about other tests failing here, we just want snapshots updated where possible,
|
||||
# so we use `|| true` here to ignore the exit code.
|
||||
cargo insta test --accept --color=always --all-features --test-runner=nextest || true
|
||||
- name: Commit snapshot changes
|
||||
if: ${{ success() }}
|
||||
run: git commit -am "Update snapshots" || echo "No snapshot changes to commit"
|
||||
- name: Push changes upstream and create a PR
|
||||
if: ${{ success() }}
|
||||
run: |
|
||||
@@ -245,7 +208,7 @@ jobs:
|
||||
name: Create an issue if the typeshed sync failed
|
||||
runs-on: ubuntu-latest
|
||||
needs: [sync, docstrings-windows, docstrings-macos-and-pr]
|
||||
if: ${{ github.repository == 'astral-sh/ruff' && always() && github.event_name == 'schedule' && (needs.sync.result == 'failure' || needs.docstrings-windows.result == 'failure' || needs.docstrings-macos-and-pr.result == 'failure') }}
|
||||
if: ${{ github.repository == 'astral-sh/ruff' && always() && github.event_name == 'schedule' && (needs.sync.result != 'success' || needs.docstrings-windows.result != 'success' || needs.docstrings-macos-and-pr.result != 'success') }}
|
||||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
|
||||
22
.github/workflows/ty-ecosystem-analyzer.yaml
vendored
22
.github/workflows/ty-ecosystem-analyzer.yaml
vendored
@@ -17,7 +17,6 @@ env:
|
||||
RUSTUP_MAX_RETRIES: 10
|
||||
RUST_BACKTRACE: 1
|
||||
REF_NAME: ${{ github.ref_name }}
|
||||
CF_API_TOKEN_EXISTS: ${{ secrets.CF_API_TOKEN != '' }}
|
||||
|
||||
jobs:
|
||||
ty-ecosystem-analyzer:
|
||||
@@ -112,22 +111,13 @@ jobs:
|
||||
|
||||
cat diff-statistics.md >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- name: "Deploy to Cloudflare Pages"
|
||||
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
|
||||
id: deploy
|
||||
uses: cloudflare/wrangler-action@da0e0dfe58b7a431659754fdf3f186c529afbe65 # v3.14.1
|
||||
# NOTE: astral-sh-bot uses this artifact to post comments on PRs.
|
||||
# Make sure to update the bot if you rename the artifact.
|
||||
- name: "Upload full report"
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
apiToken: ${{ secrets.CF_API_TOKEN }}
|
||||
accountId: ${{ secrets.CF_ACCOUNT_ID }}
|
||||
command: pages deploy dist --project-name=ty-ecosystem --branch ${{ github.head_ref }} --commit-hash ${GITHUB_SHA}
|
||||
|
||||
- name: "Append deployment URL"
|
||||
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
|
||||
env:
|
||||
DEPLOYMENT_URL: ${{ steps.deploy.outputs.pages-deployment-alias-url }}
|
||||
run: |
|
||||
echo >> comment.md
|
||||
echo "**[Full report with detailed diff]($DEPLOYMENT_URL/diff)** ([timing results]($DEPLOYMENT_URL/timing))" >> comment.md
|
||||
name: full-report
|
||||
path: dist/
|
||||
|
||||
# NOTE: astral-sh-bot uses this artifact to post comments on PRs.
|
||||
# Make sure to update the bot if you rename the artifact.
|
||||
|
||||
18
.github/workflows/ty-ecosystem-report.yaml
vendored
18
.github/workflows/ty-ecosystem-report.yaml
vendored
@@ -14,7 +14,6 @@ env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RUSTUP_MAX_RETRIES: 10
|
||||
RUST_BACKTRACE: 1
|
||||
CF_API_TOKEN_EXISTS: ${{ secrets.CF_API_TOKEN != '' }}
|
||||
|
||||
jobs:
|
||||
ty-ecosystem-report:
|
||||
@@ -31,12 +30,12 @@ jobs:
|
||||
- name: Install the latest version of uv
|
||||
uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
|
||||
with:
|
||||
enable-cache: true # zizmor: ignore[cache-poisoning] acceptable risk for CloudFlare pages artifact
|
||||
enable-cache: true
|
||||
|
||||
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||
with:
|
||||
workspaces: "ruff"
|
||||
lookup-only: false # zizmor: ignore[cache-poisoning] acceptable risk for CloudFlare pages artifact
|
||||
lookup-only: false
|
||||
|
||||
- name: Install Rust toolchain
|
||||
run: rustup show
|
||||
@@ -70,11 +69,10 @@ jobs:
|
||||
ecosystem-diagnostics.json \
|
||||
--output dist/index.html
|
||||
|
||||
- name: "Deploy to Cloudflare Pages"
|
||||
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
|
||||
id: deploy
|
||||
uses: cloudflare/wrangler-action@da0e0dfe58b7a431659754fdf3f186c529afbe65 # v3.14.1
|
||||
# NOTE: astral-sh-bot uses this artifact to publish the ecosystem report.
|
||||
# Make sure to update the bot if you rename the artifact.
|
||||
- name: "Upload ecosystem report"
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
apiToken: ${{ secrets.CF_API_TOKEN }}
|
||||
accountId: ${{ secrets.CF_ACCOUNT_ID }}
|
||||
command: pages deploy dist --project-name=ty-ecosystem --branch main --commit-hash ${GITHUB_SHA}
|
||||
name: full-report
|
||||
path: dist/
|
||||
|
||||
5
.github/workflows/typing_conformance.yaml
vendored
5
.github/workflows/typing_conformance.yaml
vendored
@@ -6,6 +6,11 @@ on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "crates/ty*/**"
|
||||
- "!crates/ty_ide/**"
|
||||
- "!crates/ty_server/**"
|
||||
- "!crates/ty_test/**"
|
||||
- "!crates/ty_completion_eval/**"
|
||||
- "!crates/ty_wasm/**"
|
||||
- "crates/ruff_db"
|
||||
- "crates/ruff_python_ast"
|
||||
- "crates/ruff_python_parser"
|
||||
|
||||
@@ -5,7 +5,7 @@ exclude: |
|
||||
.github/workflows/release.yml|
|
||||
crates/ty_vendored/vendor/.*|
|
||||
crates/ty_project/resources/.*|
|
||||
crates/ty_python_semantic/resources/corpus/.*|
|
||||
crates/ty_python_types/resources/corpus/.*|
|
||||
crates/ty/docs/(configuration|rules|cli|environment).md|
|
||||
crates/ruff_benchmark/resources/.*|
|
||||
crates/ruff_linter/resources/.*|
|
||||
|
||||
109
Cargo.lock
generated
109
Cargo.lock
generated
@@ -3083,6 +3083,7 @@ dependencies = [
|
||||
"ty",
|
||||
"ty_project",
|
||||
"ty_python_semantic",
|
||||
"ty_python_types",
|
||||
"ty_static",
|
||||
"url",
|
||||
]
|
||||
@@ -3129,7 +3130,9 @@ dependencies = [
|
||||
"salsa",
|
||||
"schemars",
|
||||
"serde",
|
||||
"ty_module_resolver",
|
||||
"ty_python_semantic",
|
||||
"ty_python_types",
|
||||
"zip",
|
||||
]
|
||||
|
||||
@@ -3619,8 +3622,8 @@ checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||
|
||||
[[package]]
|
||||
name = "salsa"
|
||||
version = "0.24.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=55e5e7d32fa3fc189276f35bb04c9438f9aedbd1#55e5e7d32fa3fc189276f35bb04c9438f9aedbd1"
|
||||
version = "0.25.2"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=ce80691fa0b87dc2fd2235a26544e63e5e43d8d3#ce80691fa0b87dc2fd2235a26544e63e5e43d8d3"
|
||||
dependencies = [
|
||||
"boxcar",
|
||||
"compact_str",
|
||||
@@ -3644,13 +3647,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "salsa-macro-rules"
|
||||
version = "0.24.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=55e5e7d32fa3fc189276f35bb04c9438f9aedbd1#55e5e7d32fa3fc189276f35bb04c9438f9aedbd1"
|
||||
version = "0.25.2"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=ce80691fa0b87dc2fd2235a26544e63e5e43d8d3#ce80691fa0b87dc2fd2235a26544e63e5e43d8d3"
|
||||
|
||||
[[package]]
|
||||
name = "salsa-macros"
|
||||
version = "0.24.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=55e5e7d32fa3fc189276f35bb04c9438f9aedbd1#55e5e7d32fa3fc189276f35bb04c9438f9aedbd1"
|
||||
version = "0.25.2"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=ce80691fa0b87dc2fd2235a26544e63e5e43d8d3#ce80691fa0b87dc2fd2235a26544e63e5e43d8d3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -4375,6 +4378,7 @@ dependencies = [
|
||||
"tracing-flame",
|
||||
"tracing-subscriber",
|
||||
"ty_combine",
|
||||
"ty_module_resolver",
|
||||
"ty_project",
|
||||
"ty_python_semantic",
|
||||
"ty_server",
|
||||
@@ -4389,7 +4393,6 @@ dependencies = [
|
||||
"ordermap",
|
||||
"ruff_db",
|
||||
"ruff_python_ast",
|
||||
"ty_python_semantic",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4407,8 +4410,8 @@ dependencies = [
|
||||
"tempfile",
|
||||
"toml",
|
||||
"ty_ide",
|
||||
"ty_module_resolver",
|
||||
"ty_project",
|
||||
"ty_python_semantic",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
@@ -4438,8 +4441,33 @@ dependencies = [
|
||||
"salsa",
|
||||
"smallvec",
|
||||
"tracing",
|
||||
"ty_module_resolver",
|
||||
"ty_project",
|
||||
"ty_python_semantic",
|
||||
"ty_python_types",
|
||||
"ty_vendored",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ty_module_resolver"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"camino",
|
||||
"compact_str",
|
||||
"get-size2",
|
||||
"insta",
|
||||
"ruff_db",
|
||||
"ruff_memory_usage",
|
||||
"ruff_python_ast",
|
||||
"ruff_python_stdlib",
|
||||
"rustc-hash",
|
||||
"salsa",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"tempfile",
|
||||
"thiserror 2.0.17",
|
||||
"tracing",
|
||||
"ty_vendored",
|
||||
]
|
||||
|
||||
@@ -4477,7 +4505,9 @@ dependencies = [
|
||||
"toml",
|
||||
"tracing",
|
||||
"ty_combine",
|
||||
"ty_module_resolver",
|
||||
"ty_python_semantic",
|
||||
"ty_python_types",
|
||||
"ty_static",
|
||||
"ty_vendored",
|
||||
]
|
||||
@@ -4491,19 +4521,12 @@ dependencies = [
|
||||
"bitvec",
|
||||
"camino",
|
||||
"colored 3.0.0",
|
||||
"compact_str",
|
||||
"datatest-stable",
|
||||
"drop_bomb",
|
||||
"get-size2",
|
||||
"glob",
|
||||
"hashbrown 0.16.1",
|
||||
"indexmap",
|
||||
"indoc",
|
||||
"insta",
|
||||
"itertools 0.14.0",
|
||||
"memchr",
|
||||
"ordermap",
|
||||
"pretty_assertions",
|
||||
"quickcheck",
|
||||
"quickcheck_macros",
|
||||
"ruff_annotate_snippets",
|
||||
@@ -4513,9 +4536,7 @@ dependencies = [
|
||||
"ruff_macros",
|
||||
"ruff_memory_usage",
|
||||
"ruff_python_ast",
|
||||
"ruff_python_literal",
|
||||
"ruff_python_parser",
|
||||
"ruff_python_stdlib",
|
||||
"ruff_python_trivia",
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
@@ -4529,10 +4550,57 @@ dependencies = [
|
||||
"strsim",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"tempfile",
|
||||
"test-case",
|
||||
"thiserror 2.0.17",
|
||||
"tracing",
|
||||
"ty_combine",
|
||||
"ty_module_resolver",
|
||||
"ty_static",
|
||||
"ty_vendored",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ty_python_types"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.10.0",
|
||||
"camino",
|
||||
"compact_str",
|
||||
"datatest-stable",
|
||||
"drop_bomb",
|
||||
"get-size2",
|
||||
"glob",
|
||||
"indexmap",
|
||||
"indoc",
|
||||
"insta",
|
||||
"itertools 0.14.0",
|
||||
"memchr",
|
||||
"ordermap",
|
||||
"pretty_assertions",
|
||||
"quickcheck",
|
||||
"quickcheck_macros",
|
||||
"ruff_db",
|
||||
"ruff_diagnostics",
|
||||
"ruff_macros",
|
||||
"ruff_memory_usage",
|
||||
"ruff_python_ast",
|
||||
"ruff_python_literal",
|
||||
"ruff_python_parser",
|
||||
"ruff_python_stdlib",
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
"rustc-hash",
|
||||
"salsa",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"smallvec",
|
||||
"static_assertions",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"test-case",
|
||||
"tracing",
|
||||
"ty_module_resolver",
|
||||
"ty_python_semantic",
|
||||
"ty_static",
|
||||
"ty_test",
|
||||
@@ -4572,6 +4640,7 @@ dependencies = [
|
||||
"tracing-subscriber",
|
||||
"ty_combine",
|
||||
"ty_ide",
|
||||
"ty_module_resolver",
|
||||
"ty_project",
|
||||
"ty_python_semantic",
|
||||
]
|
||||
@@ -4612,7 +4681,9 @@ dependencies = [
|
||||
"thiserror 2.0.17",
|
||||
"toml",
|
||||
"tracing",
|
||||
"ty_module_resolver",
|
||||
"ty_python_semantic",
|
||||
"ty_python_types",
|
||||
"ty_static",
|
||||
"ty_vendored",
|
||||
]
|
||||
|
||||
@@ -45,8 +45,10 @@ ty = { path = "crates/ty" }
|
||||
ty_combine = { path = "crates/ty_combine" }
|
||||
ty_completion_eval = { path = "crates/ty_completion_eval" }
|
||||
ty_ide = { path = "crates/ty_ide" }
|
||||
ty_module_resolver = { path = "crates/ty_module_resolver" }
|
||||
ty_project = { path = "crates/ty_project", default-features = false }
|
||||
ty_python_semantic = { path = "crates/ty_python_semantic" }
|
||||
ty_python_types = { path = "crates/ty_python_types" }
|
||||
ty_server = { path = "crates/ty_server" }
|
||||
ty_static = { path = "crates/ty_static" }
|
||||
ty_test = { path = "crates/ty_test" }
|
||||
@@ -146,7 +148,7 @@ regex-automata = { version = "0.4.9" }
|
||||
rustc-hash = { version = "2.0.0" }
|
||||
rustc-stable-hash = { version = "0.1.2" }
|
||||
# When updating salsa, make sure to also update the revision in `fuzz/Cargo.toml`
|
||||
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "55e5e7d32fa3fc189276f35bb04c9438f9aedbd1", default-features = false, features = [
|
||||
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "ce80691fa0b87dc2fd2235a26544e63e5e43d8d3", default-features = false, features = [
|
||||
"compact_str",
|
||||
"macros",
|
||||
"salsa_unstable",
|
||||
|
||||
@@ -14,6 +14,6 @@ info:
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
::error title=Ruff (unformatted),file=[TMP]/input.py,line=1,col=1,endLine=2,endColumn=1::input.py:1:1: unformatted: File would be reformatted
|
||||
::error title=Ruff (unformatted),file=[TMP]/input.py,line=1,endLine=2::input.py:1:1: unformatted: File would be reformatted
|
||||
|
||||
----- stderr -----
|
||||
|
||||
@@ -19,32 +19,32 @@ doctest = false
|
||||
[[bench]]
|
||||
name = "linter"
|
||||
harness = false
|
||||
required-features = ["instrumented"]
|
||||
required-features = ["ruff_instrumented"]
|
||||
|
||||
[[bench]]
|
||||
name = "lexer"
|
||||
harness = false
|
||||
required-features = ["instrumented"]
|
||||
required-features = ["ruff_instrumented"]
|
||||
|
||||
[[bench]]
|
||||
name = "parser"
|
||||
harness = false
|
||||
required-features = ["instrumented"]
|
||||
required-features = ["ruff_instrumented"]
|
||||
|
||||
[[bench]]
|
||||
name = "formatter"
|
||||
harness = false
|
||||
required-features = ["instrumented"]
|
||||
required-features = ["ruff_instrumented"]
|
||||
|
||||
[[bench]]
|
||||
name = "ty"
|
||||
harness = false
|
||||
required-features = ["instrumented"]
|
||||
required-features = ["ty_instrumented"]
|
||||
|
||||
[[bench]]
|
||||
name = "ty_walltime"
|
||||
harness = false
|
||||
required-features = ["walltime"]
|
||||
required-features = ["ty_walltime"]
|
||||
|
||||
[dependencies]
|
||||
ruff_db = { workspace = true, features = ["testing"] }
|
||||
@@ -67,25 +67,32 @@ tracing = { workspace = true }
|
||||
workspace = true
|
||||
|
||||
[features]
|
||||
default = ["instrumented", "walltime"]
|
||||
# Enables the benchmark that should only run with codspeed's instrumented runner
|
||||
instrumented = [
|
||||
default = ["ty_instrumented", "ty_walltime", "ruff_instrumented"]
|
||||
# Enables the ruff instrumented benchmarks
|
||||
ruff_instrumented = [
|
||||
"criterion",
|
||||
"ruff_linter",
|
||||
"ruff_python_formatter",
|
||||
"ruff_python_parser",
|
||||
"ruff_python_trivia",
|
||||
"mimalloc",
|
||||
"tikv-jemallocator",
|
||||
]
|
||||
# Enables the ty instrumented benchmarks
|
||||
ty_instrumented = [
|
||||
"criterion",
|
||||
"ty_project",
|
||||
"ruff_python_trivia",
|
||||
]
|
||||
codspeed = ["codspeed-criterion-compat"]
|
||||
# Enables benchmark that should only run with codspeed's walltime runner.
|
||||
walltime = ["ruff_db/os", "ty_project", "divan"]
|
||||
# Enables the ty_walltime benchmarks
|
||||
ty_walltime = ["ruff_db/os", "ty_project", "divan"]
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dev-dependencies]
|
||||
mimalloc = { workspace = true }
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
mimalloc = { workspace = true, optional = true }
|
||||
|
||||
[target.'cfg(all(not(target_os = "windows"), not(target_os = "openbsd"), any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "powerpc64", target_arch = "riscv64")))'.dev-dependencies]
|
||||
tikv-jemallocator = { workspace = true }
|
||||
[target.'cfg(all(not(target_os = "windows"), not(target_os = "openbsd"), any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "powerpc64", target_arch = "riscv64")))'.dependencies]
|
||||
tikv-jemallocator = { workspace = true, optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
rustc-hash = { workspace = true }
|
||||
|
||||
@@ -194,7 +194,7 @@ static SYMPY: Benchmark = Benchmark::new(
|
||||
max_dep_date: "2025-06-17",
|
||||
python_version: PythonVersion::PY312,
|
||||
},
|
||||
13100,
|
||||
13106,
|
||||
);
|
||||
|
||||
static TANJUN: Benchmark = Benchmark::new(
|
||||
@@ -235,30 +235,55 @@ fn run_single_threaded(bencher: Bencher, benchmark: &Benchmark) {
|
||||
});
|
||||
}
|
||||
|
||||
#[bench(args=[&ALTAIR, &FREQTRADE, &TANJUN], sample_size=2, sample_count=3)]
|
||||
fn small(bencher: Bencher, benchmark: &Benchmark) {
|
||||
run_single_threaded(bencher, benchmark);
|
||||
#[bench(sample_size = 2, sample_count = 3)]
|
||||
fn altair(bencher: Bencher) {
|
||||
run_single_threaded(bencher, &ALTAIR);
|
||||
}
|
||||
|
||||
#[bench(args=[&COLOUR_SCIENCE, &PANDAS, &STATIC_FRAME], sample_size=1, sample_count=3)]
|
||||
fn medium(bencher: Bencher, benchmark: &Benchmark) {
|
||||
run_single_threaded(bencher, benchmark);
|
||||
#[bench(sample_size = 2, sample_count = 3)]
|
||||
fn freqtrade(bencher: Bencher) {
|
||||
run_single_threaded(bencher, &FREQTRADE);
|
||||
}
|
||||
|
||||
#[bench(args=[&SYMPY, &PYDANTIC], sample_size=1, sample_count=2)]
|
||||
fn large(bencher: Bencher, benchmark: &Benchmark) {
|
||||
run_single_threaded(bencher, benchmark);
|
||||
#[bench(sample_size = 2, sample_count = 3)]
|
||||
fn tanjun(bencher: Bencher) {
|
||||
run_single_threaded(bencher, &TANJUN);
|
||||
}
|
||||
|
||||
#[bench(args=[&ALTAIR], sample_size=3, sample_count=8)]
|
||||
fn multithreaded(bencher: Bencher, benchmark: &Benchmark) {
|
||||
#[bench(sample_size = 2, sample_count = 3)]
|
||||
fn pydantic(bencher: Bencher) {
|
||||
run_single_threaded(bencher, &PYDANTIC);
|
||||
}
|
||||
|
||||
#[bench(sample_size = 1, sample_count = 3)]
|
||||
fn static_frame(bencher: Bencher) {
|
||||
run_single_threaded(bencher, &STATIC_FRAME);
|
||||
}
|
||||
|
||||
#[bench(sample_size = 1, sample_count = 2)]
|
||||
fn colour_science(bencher: Bencher) {
|
||||
run_single_threaded(bencher, &COLOUR_SCIENCE);
|
||||
}
|
||||
|
||||
#[bench(sample_size = 1, sample_count = 2)]
|
||||
fn pandas(bencher: Bencher) {
|
||||
run_single_threaded(bencher, &PANDAS);
|
||||
}
|
||||
|
||||
#[bench(sample_size = 1, sample_count = 2)]
|
||||
fn sympy(bencher: Bencher) {
|
||||
run_single_threaded(bencher, &SYMPY);
|
||||
}
|
||||
|
||||
#[bench(sample_size = 3, sample_count = 8)]
|
||||
fn multithreaded(bencher: Bencher) {
|
||||
let thread_pool = ThreadPoolBuilder::new().build().unwrap();
|
||||
|
||||
bencher
|
||||
.with_inputs(|| benchmark.setup_iteration())
|
||||
.with_inputs(|| ALTAIR.setup_iteration())
|
||||
.bench_local_values(|db| {
|
||||
thread_pool.install(|| {
|
||||
check_project(&db, benchmark.project.name, benchmark.max_diagnostics);
|
||||
check_project(&db, ALTAIR.project.name, ALTAIR.max_diagnostics);
|
||||
db
|
||||
})
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[cfg(feature = "instrumented")]
|
||||
#[cfg(any(feature = "ty_instrumented", feature = "ruff_instrumented"))]
|
||||
pub mod criterion;
|
||||
pub mod real_world_projects;
|
||||
|
||||
|
||||
51
crates/ruff_db/src/cancellation.rs
Normal file
51
crates/ruff_db/src/cancellation.rs
Normal file
@@ -0,0 +1,51 @@
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
|
||||
/// Signals a [`CancellationToken`] that it should be canceled.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CancellationTokenSource {
|
||||
cancelled: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl Default for CancellationTokenSource {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl CancellationTokenSource {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
cancelled: Arc::new(AtomicBool::new(false)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_cancellation_requested(&self) -> bool {
|
||||
self.cancelled.load(std::sync::atomic::Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Creates a new token that uses this source.
|
||||
pub fn token(&self) -> CancellationToken {
|
||||
CancellationToken {
|
||||
cancelled: self.cancelled.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Requests cancellation for operations using this token.
|
||||
pub fn cancel(&self) {
|
||||
self.cancelled
|
||||
.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
/// Token signals whether an operation should be canceled.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CancellationToken {
|
||||
cancelled: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl CancellationToken {
|
||||
pub fn is_cancelled(&self) -> bool {
|
||||
self.cancelled.load(std::sync::atomic::Ordering::Relaxed)
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::{fmt::Formatter, path::Path, sync::Arc};
|
||||
use std::{borrow::Cow, fmt::Formatter, path::Path, sync::Arc};
|
||||
|
||||
use ruff_diagnostics::{Applicability, Fix};
|
||||
use ruff_source_file::{LineColumn, SourceCode, SourceFile};
|
||||
@@ -11,6 +11,7 @@ pub use self::render::{
|
||||
ceil_char_boundary,
|
||||
github::{DisplayGithubDiagnostics, GithubRenderer},
|
||||
};
|
||||
use crate::cancellation::CancellationToken;
|
||||
use crate::{Db, files::File};
|
||||
|
||||
mod render;
|
||||
@@ -410,11 +411,6 @@ impl Diagnostic {
|
||||
self.id().is_invalid_syntax()
|
||||
}
|
||||
|
||||
/// Returns the message body to display to the user.
|
||||
pub fn body(&self) -> &str {
|
||||
self.primary_message()
|
||||
}
|
||||
|
||||
/// Returns the message of the first sub-diagnostic with a `Help` severity.
|
||||
///
|
||||
/// Note that this is used as the fix title/suggestion for some of Ruff's output formats, but in
|
||||
@@ -1312,6 +1308,8 @@ pub struct DisplayDiagnosticConfig {
|
||||
show_fix_diff: bool,
|
||||
/// The lowest applicability that should be shown when reporting diagnostics.
|
||||
fix_applicability: Applicability,
|
||||
|
||||
cancellation_token: Option<CancellationToken>,
|
||||
}
|
||||
|
||||
impl DisplayDiagnosticConfig {
|
||||
@@ -1385,6 +1383,20 @@ impl DisplayDiagnosticConfig {
|
||||
pub fn fix_applicability(&self) -> Applicability {
|
||||
self.fix_applicability
|
||||
}
|
||||
|
||||
pub fn with_cancellation_token(
|
||||
mut self,
|
||||
token: Option<CancellationToken>,
|
||||
) -> DisplayDiagnosticConfig {
|
||||
self.cancellation_token = token;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn is_canceled(&self) -> bool {
|
||||
self.cancellation_token
|
||||
.as_ref()
|
||||
.is_some_and(|token| token.is_cancelled())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for DisplayDiagnosticConfig {
|
||||
@@ -1398,6 +1410,7 @@ impl Default for DisplayDiagnosticConfig {
|
||||
show_fix_status: false,
|
||||
show_fix_diff: false,
|
||||
fix_applicability: Applicability::Safe,
|
||||
cancellation_token: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1474,6 +1487,15 @@ pub enum ConciseMessage<'a> {
|
||||
Custom(&'a str),
|
||||
}
|
||||
|
||||
impl<'a> ConciseMessage<'a> {
|
||||
pub fn to_str(&self) -> Cow<'a, str> {
|
||||
match self {
|
||||
ConciseMessage::MainDiagnostic(s) | ConciseMessage::Custom(s) => Cow::Borrowed(s),
|
||||
ConciseMessage::Both { .. } => Cow::Owned(self.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ConciseMessage<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match *self {
|
||||
@@ -1490,6 +1512,16 @@ impl std::fmt::Display for ConciseMessage<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl serde::Serialize for ConciseMessage<'_> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.collect_str(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// A diagnostic message string.
|
||||
///
|
||||
/// This is, for all intents and purposes, equivalent to a `Box<str>`.
|
||||
|
||||
@@ -52,7 +52,7 @@ impl AzureRenderer<'_> {
|
||||
f,
|
||||
"code={code};]{body}",
|
||||
code = diag.secondary_code_or_id(),
|
||||
body = diag.body(),
|
||||
body = diag.concise_message(),
|
||||
)?;
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,10 @@ impl<'a> ConciseRenderer<'a> {
|
||||
|
||||
let sep = fmt_styled(":", stylesheet.separator);
|
||||
for diag in diagnostics {
|
||||
if self.config.is_canceled() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if let Some(span) = diag.primary_span() {
|
||||
write!(
|
||||
f,
|
||||
|
||||
@@ -53,6 +53,10 @@ impl<'a> FullRenderer<'a> {
|
||||
.hyperlink(stylesheet.hyperlink);
|
||||
|
||||
for diag in diagnostics {
|
||||
if self.config.is_canceled() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let resolved = Resolved::new(self.resolver, diag, self.config);
|
||||
let renderable = resolved.to_renderable(self.config.context);
|
||||
for diag in renderable.diagnostics.iter() {
|
||||
|
||||
@@ -49,14 +49,26 @@ impl<'a> GithubRenderer<'a> {
|
||||
}
|
||||
.unwrap_or_default();
|
||||
|
||||
write!(
|
||||
f,
|
||||
",line={row},col={column},endLine={end_row},endColumn={end_column}::",
|
||||
row = start_location.line,
|
||||
column = start_location.column,
|
||||
end_row = end_location.line,
|
||||
end_column = end_location.column,
|
||||
)?;
|
||||
// GitHub Actions workflow commands have constraints on error annotations:
|
||||
// - `col` and `endColumn` cannot be set if `line` and `endLine` are different
|
||||
// See: https://github.com/astral-sh/ruff/issues/22074
|
||||
if start_location.line == end_location.line {
|
||||
write!(
|
||||
f,
|
||||
",line={row},col={column},endLine={end_row},endColumn={end_column}::",
|
||||
row = start_location.line,
|
||||
column = start_location.column,
|
||||
end_row = end_location.line,
|
||||
end_column = end_location.column,
|
||||
)?;
|
||||
} else {
|
||||
write!(
|
||||
f,
|
||||
",line={row},endLine={end_row}::",
|
||||
row = start_location.line,
|
||||
end_row = end_location.line,
|
||||
)?;
|
||||
}
|
||||
|
||||
write!(
|
||||
f,
|
||||
@@ -75,7 +87,7 @@ impl<'a> GithubRenderer<'a> {
|
||||
write!(f, "{id}:", id = diagnostic.id())?;
|
||||
}
|
||||
|
||||
writeln!(f, " {}", diagnostic.body())?;
|
||||
writeln!(f, " {}", diagnostic.concise_message())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -98,7 +98,7 @@ impl Serialize for SerializedMessages<'_> {
|
||||
}
|
||||
fingerprints.insert(message_fingerprint);
|
||||
|
||||
let description = diagnostic.body();
|
||||
let description = diagnostic.concise_message();
|
||||
let check_name = diagnostic.secondary_code_or_id();
|
||||
let severity = match diagnostic.severity() {
|
||||
Severity::Info => "info",
|
||||
|
||||
@@ -6,7 +6,7 @@ use ruff_notebook::NotebookIndex;
|
||||
use ruff_source_file::{LineColumn, OneIndexed};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::diagnostic::{Diagnostic, DiagnosticSource, DisplayDiagnosticConfig};
|
||||
use crate::diagnostic::{ConciseMessage, Diagnostic, DiagnosticSource, DisplayDiagnosticConfig};
|
||||
|
||||
use super::FileResolver;
|
||||
|
||||
@@ -101,7 +101,7 @@ pub(super) fn diagnostic_to_json<'a>(
|
||||
JsonDiagnostic {
|
||||
code: diagnostic.secondary_code_or_id(),
|
||||
url: diagnostic.documentation_url(),
|
||||
message: diagnostic.body(),
|
||||
message: diagnostic.concise_message(),
|
||||
fix,
|
||||
cell: notebook_cell_index,
|
||||
location: start_location.map(JsonLocation::from),
|
||||
@@ -113,7 +113,7 @@ pub(super) fn diagnostic_to_json<'a>(
|
||||
JsonDiagnostic {
|
||||
code: diagnostic.secondary_code_or_id(),
|
||||
url: diagnostic.documentation_url(),
|
||||
message: diagnostic.body(),
|
||||
message: diagnostic.concise_message(),
|
||||
fix,
|
||||
cell: notebook_cell_index,
|
||||
location: Some(start_location.unwrap_or_default().into()),
|
||||
@@ -226,7 +226,7 @@ pub(crate) struct JsonDiagnostic<'a> {
|
||||
filename: Option<&'a str>,
|
||||
fix: Option<JsonFix<'a>>,
|
||||
location: Option<JsonLocation>,
|
||||
message: &'a str,
|
||||
message: ConciseMessage<'a>,
|
||||
noqa_row: Option<OneIndexed>,
|
||||
url: Option<&'a str>,
|
||||
}
|
||||
|
||||
@@ -56,17 +56,17 @@ impl<'a> JunitRenderer<'a> {
|
||||
start_location: location,
|
||||
} = diagnostic;
|
||||
let mut status = TestCaseStatus::non_success(NonSuccessKind::Failure);
|
||||
status.set_message(diagnostic.body());
|
||||
status.set_message(diagnostic.concise_message().to_str());
|
||||
|
||||
if let Some(location) = location {
|
||||
status.set_description(format!(
|
||||
"line {row}, col {col}, {body}",
|
||||
row = location.line,
|
||||
col = location.column,
|
||||
body = diagnostic.body()
|
||||
body = diagnostic.concise_message()
|
||||
));
|
||||
} else {
|
||||
status.set_description(diagnostic.body());
|
||||
status.set_description(diagnostic.concise_message().to_str());
|
||||
}
|
||||
|
||||
let code = diagnostic
|
||||
|
||||
@@ -55,7 +55,7 @@ impl PylintRenderer<'_> {
|
||||
f,
|
||||
"{path}:{row}: [{code}] {body}",
|
||||
path = filename,
|
||||
body = diagnostic.body()
|
||||
body = diagnostic.concise_message()
|
||||
)?;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ use ruff_diagnostics::{Edit, Fix};
|
||||
use ruff_source_file::{LineColumn, SourceCode};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::diagnostic::Diagnostic;
|
||||
use crate::diagnostic::{ConciseMessage, Diagnostic};
|
||||
|
||||
use super::FileResolver;
|
||||
|
||||
@@ -76,7 +76,7 @@ fn diagnostic_to_rdjson<'a>(
|
||||
let edits = diagnostic.fix().map(Fix::edits).unwrap_or_default();
|
||||
|
||||
RdjsonDiagnostic {
|
||||
message: diagnostic.body(),
|
||||
message: diagnostic.concise_message(),
|
||||
location,
|
||||
code: RdjsonCode {
|
||||
value: diagnostic
|
||||
@@ -155,7 +155,7 @@ struct RdjsonDiagnostic<'a> {
|
||||
code: RdjsonCode<'a>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
location: Option<RdjsonLocation<'a>>,
|
||||
message: &'a str,
|
||||
message: ConciseMessage<'a>,
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
suggestions: Vec<RdjsonSuggestion<'a>>,
|
||||
}
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
source: crates/ruff_db/src/diagnostic/render/github.rs
|
||||
expression: env.render_diagnostics(&diagnostics)
|
||||
---
|
||||
::error title=ty (invalid-syntax),file=/syntax_errors.py,line=1,col=15,endLine=2,endColumn=1::syntax_errors.py:1:15: invalid-syntax: Expected one or more symbol names after import
|
||||
::error title=ty (invalid-syntax),file=/syntax_errors.py,line=3,col=12,endLine=4,endColumn=1::syntax_errors.py:3:12: invalid-syntax: Expected ')', found newline
|
||||
::error title=ty (invalid-syntax),file=/syntax_errors.py,line=1,endLine=2::syntax_errors.py:1:15: invalid-syntax: Expected one or more symbol names after import
|
||||
::error title=ty (invalid-syntax),file=/syntax_errors.py,line=3,endLine=4::syntax_errors.py:3:12: invalid-syntax: Expected ')', found newline
|
||||
|
||||
@@ -12,6 +12,7 @@ use std::hash::BuildHasherDefault;
|
||||
use std::num::NonZeroUsize;
|
||||
use ty_static::EnvVars;
|
||||
|
||||
pub mod cancellation;
|
||||
pub mod diagnostic;
|
||||
pub mod display;
|
||||
pub mod file_revision;
|
||||
|
||||
@@ -275,16 +275,16 @@ impl OsSystem {
|
||||
/// instead of at least one system call for each component between `path` and `prefix`.
|
||||
///
|
||||
/// However, using `canonicalize` to resolve the path's casing doesn't work in two cases:
|
||||
/// * if `path` is a symlink because `canonicalize` then returns the symlink's target and not the symlink's source path.
|
||||
/// * on Windows: If `path` is a mapped network drive because `canonicalize` then returns the UNC path
|
||||
/// (e.g. `Z:\` is mapped to `\\server\share` and `canonicalize` then returns `\\?\UNC\server\share`).
|
||||
/// * if `path` is a symlink, `canonicalize` returns the symlink's target and not the symlink's source path.
|
||||
/// * on Windows: If `path` is a mapped network drive, `canonicalize` returns the UNC path
|
||||
/// (e.g. `Z:\` is mapped to `\\server\share` and `canonicalize` returns `\\?\UNC\server\share`).
|
||||
///
|
||||
/// Symlinks and mapped network drives should be rare enough that this fast path is worth trying first,
|
||||
/// even if it comes at a cost for those rare use cases.
|
||||
fn path_exists_case_sensitive_fast(&self, path: &SystemPath) -> Option<bool> {
|
||||
// This is a more forgiving version of `dunce::simplified` that removes all `\\?\` prefixes on Windows.
|
||||
// We use this more forgiving version because we don't intend on using either path for anything other than comparison
|
||||
// and the prefix is only relevant when passing the path to other programs and its longer than 200 something
|
||||
// and the prefix is only relevant when passing the path to other programs and it's longer than 200 something
|
||||
// characters.
|
||||
fn simplify_ignore_verbatim(path: &SystemPath) -> &SystemPath {
|
||||
if cfg!(windows) {
|
||||
@@ -298,9 +298,7 @@ impl OsSystem {
|
||||
}
|
||||
}
|
||||
|
||||
let simplified = simplify_ignore_verbatim(path);
|
||||
|
||||
let Ok(canonicalized) = simplified.as_std_path().canonicalize() else {
|
||||
let Ok(canonicalized) = path.as_std_path().canonicalize() else {
|
||||
// The path doesn't exist or can't be accessed. The path doesn't exist.
|
||||
return Some(false);
|
||||
};
|
||||
@@ -309,12 +307,13 @@ impl OsSystem {
|
||||
// The original path is valid UTF8 but the canonicalized path isn't. This definitely suggests
|
||||
// that a symlink is involved. Fall back to the slow path.
|
||||
tracing::debug!(
|
||||
"Falling back to the slow case-sensitive path existence check because the canonicalized path of `{simplified}` is not valid UTF-8"
|
||||
"Falling back to the slow case-sensitive path existence check because the canonicalized path of `{path}` is not valid UTF-8"
|
||||
);
|
||||
return None;
|
||||
};
|
||||
|
||||
let simplified_canonicalized = simplify_ignore_verbatim(&canonicalized);
|
||||
let simplified = simplify_ignore_verbatim(path);
|
||||
|
||||
// Test if the paths differ by anything other than casing. If so, that suggests that
|
||||
// `path` pointed to a symlink (or some other none reversible path normalization happened).
|
||||
|
||||
@@ -14,6 +14,7 @@ license = { workspace = true }
|
||||
ty = { workspace = true }
|
||||
ty_project = { workspace = true, features = ["schemars"] }
|
||||
ty_python_semantic = { workspace = true }
|
||||
ty_python_types = { workspace = true }
|
||||
ty_static = { workspace = true }
|
||||
ruff = { workspace = true }
|
||||
ruff_formatter = { workspace = true }
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
//! Generate a Markdown-compatible listing of configuration options for `pyproject.toml`.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::{fmt::Write, path::PathBuf};
|
||||
|
||||
use anyhow::bail;
|
||||
use itertools::Itertools;
|
||||
use pretty_assertions::StrComparison;
|
||||
use std::{fmt::Write, path::PathBuf};
|
||||
|
||||
use ruff_options_metadata::{OptionField, OptionSet, OptionsMetadata, Visit};
|
||||
use ruff_python_trivia::textwrap;
|
||||
use ty_project::metadata::Options;
|
||||
|
||||
use crate::{
|
||||
@@ -165,62 +167,69 @@ fn emit_field(output: &mut String, name: &str, field: &OptionField, parents: &[S
|
||||
let _ = writeln!(output, "**Default value**: `{}`", field.default);
|
||||
output.push('\n');
|
||||
let _ = writeln!(output, "**Type**: `{}`", field.value_type);
|
||||
|
||||
output.push('\n');
|
||||
output.push_str("**Example usage**:\n\n");
|
||||
output.push_str(&format_example(
|
||||
"pyproject.toml",
|
||||
&format_header(
|
||||
field.scope,
|
||||
field.example,
|
||||
parents,
|
||||
ConfigurationFile::PyprojectToml,
|
||||
),
|
||||
field.example,
|
||||
));
|
||||
output.push('\n');
|
||||
}
|
||||
|
||||
fn format_example(title: &str, header: &str, content: &str) -> String {
|
||||
if header.is_empty() {
|
||||
format!("```toml title=\"{title}\"\n{content}\n```\n",)
|
||||
} else {
|
||||
format!("```toml title=\"{title}\"\n{header}\n{content}\n```\n",)
|
||||
for configuration_file in [ConfigurationFile::PyprojectToml, ConfigurationFile::TyToml] {
|
||||
let (header, example) =
|
||||
format_snippet(field.scope, field.example, parents, configuration_file);
|
||||
output.push_str(&format_tab(configuration_file.name(), &header, &example));
|
||||
|
||||
output.push('\n');
|
||||
}
|
||||
}
|
||||
|
||||
fn format_tab(tab_name: &str, header: &str, content: &str) -> String {
|
||||
let header = if header.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
format!("\n {header}")
|
||||
};
|
||||
format!(
|
||||
"=== \"{}\"\n\n ```toml{}\n{}\n ```\n",
|
||||
tab_name,
|
||||
header,
|
||||
textwrap::indent(content, " ")
|
||||
)
|
||||
}
|
||||
/// Format the TOML header for the example usage for a given option.
|
||||
///
|
||||
/// For example: `[tool.ruff.format]` or `[tool.ruff.lint.isort]`.
|
||||
fn format_header(
|
||||
/// For example: `[tool.ty.rules]`.
|
||||
fn format_snippet<'a>(
|
||||
scope: Option<&str>,
|
||||
example: &str,
|
||||
example: &'a str,
|
||||
parents: &[Set],
|
||||
configuration: ConfigurationFile,
|
||||
) -> String {
|
||||
let tool_parent = match configuration {
|
||||
ConfigurationFile::PyprojectToml => Some("tool.ty"),
|
||||
ConfigurationFile::TyToml => None,
|
||||
};
|
||||
) -> (String, Cow<'a, str>) {
|
||||
let mut example = Cow::Borrowed(example);
|
||||
|
||||
let header = tool_parent
|
||||
let header = configuration
|
||||
.parent_table()
|
||||
.into_iter()
|
||||
.chain(parents.iter().filter_map(|parent| parent.name()))
|
||||
.chain(scope)
|
||||
.join(".");
|
||||
|
||||
// Rewrite examples starting with `[tool.ty]` or `[[tool.ty]]` to their `ty.toml` equivalent.
|
||||
if matches!(configuration, ConfigurationFile::TyToml) {
|
||||
example = example.replace("[tool.ty.", "[").into();
|
||||
}
|
||||
|
||||
// Ex) `[[tool.ty.xx]]`
|
||||
if example.starts_with(&format!("[[{header}")) {
|
||||
return String::new();
|
||||
return (String::new(), example);
|
||||
}
|
||||
|
||||
// Ex) `[tool.ty.rules]`
|
||||
if example.starts_with(&format!("[{header}")) {
|
||||
return String::new();
|
||||
return (String::new(), example);
|
||||
}
|
||||
|
||||
if header.is_empty() {
|
||||
String::new()
|
||||
(String::new(), example)
|
||||
} else {
|
||||
format!("[{header}]")
|
||||
(format!("[{header}]"), example)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,10 +252,25 @@ impl Visit for CollectOptionsVisitor {
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
enum ConfigurationFile {
|
||||
PyprojectToml,
|
||||
#[expect(dead_code)]
|
||||
TyToml,
|
||||
}
|
||||
|
||||
impl ConfigurationFile {
|
||||
const fn name(self) -> &'static str {
|
||||
match self {
|
||||
Self::PyprojectToml => "pyproject.toml",
|
||||
Self::TyToml => "ty.toml",
|
||||
}
|
||||
}
|
||||
|
||||
const fn parent_table(self) -> Option<&'static str> {
|
||||
match self {
|
||||
Self::PyprojectToml => Some("tool.ty"),
|
||||
Self::TyToml => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use anyhow::Result;
|
||||
|
||||
@@ -52,7 +52,7 @@ pub(crate) fn main(args: &Args) -> Result<()> {
|
||||
}
|
||||
|
||||
fn generate_markdown() -> String {
|
||||
let registry = ty_python_semantic::default_lint_registry();
|
||||
let registry = ty_python_types::default_lint_registry();
|
||||
|
||||
let mut output = String::new();
|
||||
|
||||
@@ -63,12 +63,7 @@ fn generate_markdown() -> String {
|
||||
let _ = writeln!(&mut output, "# Rules\n");
|
||||
|
||||
let mut lints: Vec<_> = registry.lints().iter().collect();
|
||||
lints.sort_by(|a, b| {
|
||||
a.default_level()
|
||||
.cmp(&b.default_level())
|
||||
.reverse()
|
||||
.then_with(|| a.name().cmp(&b.name()))
|
||||
});
|
||||
lints.sort_by_key(|a| a.name());
|
||||
|
||||
for lint in lints {
|
||||
let _ = writeln!(&mut output, "## `{rule_name}`\n", rule_name = lint.name());
|
||||
@@ -119,7 +114,7 @@ fn generate_markdown() -> String {
|
||||
let _ = writeln!(
|
||||
&mut output,
|
||||
r#"<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of '{level}'."><code>{level}</code></a> ·
|
||||
Default level: <a href="../../rules#rule-levels" title="This lint has a default level of '{level}'."><code>{level}</code></a> ·
|
||||
{status_text} ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20{encoded_name}" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/{file}#L{line}" target="_blank">View source</a>
|
||||
|
||||
@@ -16,7 +16,9 @@ ruff_linter = { workspace = true }
|
||||
ruff_macros = { workspace = true }
|
||||
ruff_python_ast = { workspace = true }
|
||||
ruff_python_parser = { workspace = true }
|
||||
ty_module_resolver = { workspace = true }
|
||||
ty_python_semantic = { workspace = true }
|
||||
ty_python_types = { workspace = true }
|
||||
|
||||
anyhow = { workspace = true }
|
||||
clap = { workspace = true, optional = true }
|
||||
|
||||
@@ -3,7 +3,7 @@ use ruff_python_ast::visitor::source_order::{
|
||||
SourceOrderVisitor, walk_expr, walk_module, walk_stmt,
|
||||
};
|
||||
use ruff_python_ast::{self as ast, Expr, Mod, Stmt};
|
||||
use ty_python_semantic::ModuleName;
|
||||
use ty_module_resolver::ModuleName;
|
||||
|
||||
/// Collect all imports for a given Python file.
|
||||
#[derive(Default, Debug)]
|
||||
|
||||
@@ -7,11 +7,13 @@ use ruff_db::files::{File, Files};
|
||||
use ruff_db::system::{OsSystem, System, SystemPathBuf};
|
||||
use ruff_db::vendored::{VendoredFileSystem, VendoredFileSystemBuilder};
|
||||
use ruff_python_ast::PythonVersion;
|
||||
use ty_module_resolver::{SearchPathSettings, SearchPaths};
|
||||
use ty_python_semantic::lint::{LintRegistry, RuleSelection};
|
||||
use ty_python_semantic::{
|
||||
Db, Program, ProgramSettings, PythonEnvironment, PythonPlatform, PythonVersionSource,
|
||||
PythonVersionWithSource, SearchPathSettings, SysPrefixPathOrigin, default_lint_registry,
|
||||
AnalysisSettings, Db, Program, ProgramSettings, PythonEnvironment, PythonPlatform,
|
||||
PythonVersionSource, PythonVersionWithSource, SysPrefixPathOrigin,
|
||||
};
|
||||
use ty_python_types::default_lint_registry;
|
||||
|
||||
static EMPTY_VENDORED: std::sync::LazyLock<VendoredFileSystem> = std::sync::LazyLock::new(|| {
|
||||
let mut builder = VendoredFileSystemBuilder::new(CompressionMethod::Stored);
|
||||
@@ -26,6 +28,7 @@ pub struct ModuleDb {
|
||||
files: Files,
|
||||
system: OsSystem,
|
||||
rule_selection: Arc<RuleSelection>,
|
||||
analysis_settings: Arc<AnalysisSettings>,
|
||||
}
|
||||
|
||||
impl ModuleDb {
|
||||
@@ -85,6 +88,13 @@ impl SourceDb for ModuleDb {
|
||||
}
|
||||
}
|
||||
|
||||
#[salsa::db]
|
||||
impl ty_module_resolver::Db for ModuleDb {
|
||||
fn search_paths(&self) -> &SearchPaths {
|
||||
Program::get(self).search_paths(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[salsa::db]
|
||||
impl Db for ModuleDb {
|
||||
fn should_check_file(&self, file: File) -> bool {
|
||||
@@ -102,6 +112,10 @@ impl Db for ModuleDb {
|
||||
fn verbose(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn analysis_settings(&self) -> &AnalysisSettings {
|
||||
&self.analysis_settings
|
||||
}
|
||||
}
|
||||
|
||||
#[salsa::db]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use ruff_db::files::{File, FilePath, system_path_to_file};
|
||||
use ruff_db::system::SystemPath;
|
||||
use ty_python_semantic::{
|
||||
use ty_module_resolver::{
|
||||
ModuleName, resolve_module, resolve_module_confident, resolve_real_module,
|
||||
resolve_real_module_confident,
|
||||
};
|
||||
|
||||
@@ -197,7 +197,7 @@ impl Display for RuleCodeAndBody<'_> {
|
||||
f,
|
||||
"{fix}{body}",
|
||||
fix = format_args!("[{}] ", "*".cyan()),
|
||||
body = self.message.body(),
|
||||
body = self.message.concise_message(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -208,14 +208,14 @@ impl Display for RuleCodeAndBody<'_> {
|
||||
f,
|
||||
"{code} {body}",
|
||||
code = code.red().bold(),
|
||||
body = self.message.body(),
|
||||
body = self.message.concise_message(),
|
||||
)
|
||||
} else {
|
||||
write!(
|
||||
f,
|
||||
"{code}: {body}",
|
||||
code = self.message.id().as_str().red().bold(),
|
||||
body = self.message.body(),
|
||||
body = self.message.concise_message(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -334,7 +334,7 @@ impl<'a> SarifResult<'a> {
|
||||
rule_id: RuleCode::from(diagnostic),
|
||||
level: "error".to_string(),
|
||||
message: SarifMessage {
|
||||
text: diagnostic.body().to_string(),
|
||||
text: diagnostic.concise_message().to_string(),
|
||||
},
|
||||
fixes: Self::fix(diagnostic, &uri).into_iter().collect(),
|
||||
locations: vec![SarifLocation {
|
||||
|
||||
@@ -37,10 +37,11 @@ use crate::{Fix, FixAvailability, Violation};
|
||||
/// import logging
|
||||
///
|
||||
/// logging.basicConfig(level=logging.INFO)
|
||||
/// logger = logging.getLogger(__name__)
|
||||
///
|
||||
///
|
||||
/// def sum_less_than_four(a, b):
|
||||
/// logging.debug("Calling sum_less_than_four")
|
||||
/// logger.debug("Calling sum_less_than_four")
|
||||
/// return a + b < 4
|
||||
/// ```
|
||||
///
|
||||
|
||||
@@ -243,7 +243,7 @@ fn to_lsp_diagnostic(
|
||||
) -> (usize, lsp_types::Diagnostic) {
|
||||
let diagnostic_range = diagnostic.range().unwrap_or_default();
|
||||
let name = diagnostic.name();
|
||||
let body = diagnostic.body().to_string();
|
||||
let body = diagnostic.concise_message().to_string();
|
||||
let fix = diagnostic.fix();
|
||||
let suggestion = diagnostic.first_help_text();
|
||||
let code = diagnostic.secondary_code();
|
||||
|
||||
@@ -241,7 +241,7 @@ impl Workspace {
|
||||
let range = msg.range().unwrap_or_default();
|
||||
ExpandedMessage {
|
||||
code: msg.secondary_code_or_id().to_string(),
|
||||
message: msg.body().to_string(),
|
||||
message: msg.concise_message().to_string(),
|
||||
start_location: source_code
|
||||
.source_location(range.start(), self.position_encoding)
|
||||
.into(),
|
||||
|
||||
@@ -42,6 +42,7 @@ wild = { workspace = true }
|
||||
[dev-dependencies]
|
||||
ruff_db = { workspace = true, features = ["testing"] }
|
||||
ruff_python_trivia = { workspace = true }
|
||||
ty_module_resolver = { workspace = true }
|
||||
|
||||
dunce = { workspace = true }
|
||||
insta = { workspace = true, features = ["filters"] }
|
||||
|
||||
1
crates/ty/docs/cli.md
generated
1
crates/ty/docs/cli.md
generated
@@ -56,6 +56,7 @@ over all configuration files.</p>
|
||||
</dd><dt id="ty-check--exit-zero"><a href="#ty-check--exit-zero"><code>--exit-zero</code></a></dt><dd><p>Always use exit code 0, even when there are error-level diagnostics</p>
|
||||
</dd><dt id="ty-check--extra-search-path"><a href="#ty-check--extra-search-path"><code>--extra-search-path</code></a> <i>path</i></dt><dd><p>Additional path to use as a module-resolution source (can be passed multiple times).</p>
|
||||
<p>This is an advanced option that should usually only be used for first-party or third-party modules that are not installed into your Python environment in a conventional way. Use <code>--python</code> to point ty to your Python environment if it is in an unusual location.</p>
|
||||
</dd><dt id="ty-check--force-exclude"><a href="#ty-check--force-exclude"><code>--force-exclude</code></a></dt><dd><p>Enforce exclusions, even for paths passed to ty directly on the command-line. Use <code>--no-force-exclude</code> to disable</p>
|
||||
</dd><dt id="ty-check--help"><a href="#ty-check--help"><code>--help</code></a>, <code>-h</code></dt><dd><p>Print help (see a summary with '-h')</p>
|
||||
</dd><dt id="ty-check--ignore"><a href="#ty-check--ignore"><code>--ignore</code></a> <i>rule</i></dt><dd><p>Disables the rule. Can be specified multiple times.</p>
|
||||
</dd><dt id="ty-check--no-progress"><a href="#ty-check--no-progress"><code>--no-progress</code></a></dt><dd><p>Hide all progress outputs.</p>
|
||||
|
||||
377
crates/ty/docs/configuration.md
generated
377
crates/ty/docs/configuration.md
generated
@@ -20,11 +20,59 @@ Valid severities are:
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml title="pyproject.toml"
|
||||
[tool.ty.rules]
|
||||
possibly-unresolved-reference = "warn"
|
||||
division-by-zero = "ignore"
|
||||
```
|
||||
=== "pyproject.toml"
|
||||
|
||||
```toml
|
||||
[tool.ty.rules]
|
||||
possibly-unresolved-reference = "warn"
|
||||
division-by-zero = "ignore"
|
||||
```
|
||||
|
||||
=== "ty.toml"
|
||||
|
||||
```toml
|
||||
[rules]
|
||||
possibly-unresolved-reference = "warn"
|
||||
division-by-zero = "ignore"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `analysis`
|
||||
|
||||
### `respect-type-ignore-comments`
|
||||
|
||||
Whether ty should respect `type: ignore` comments.
|
||||
|
||||
When set to `false`, `type: ignore` comments are treated like any other normal
|
||||
comment and can't be used to suppress ty errors (you have to use `ty: ignore` instead).
|
||||
|
||||
Setting this option can be useful when using ty alongside other type checkers or when
|
||||
you prefer using `ty: ignore` over `type: ignore`.
|
||||
|
||||
Defaults to `true`.
|
||||
|
||||
**Default value**: `true`
|
||||
|
||||
**Type**: `bool`
|
||||
|
||||
**Example usage**:
|
||||
|
||||
=== "pyproject.toml"
|
||||
|
||||
```toml
|
||||
[tool.ty.analysis]
|
||||
# Disable support for `type: ignore` comments
|
||||
respect-type-ignore-comments = false
|
||||
```
|
||||
|
||||
=== "ty.toml"
|
||||
|
||||
```toml
|
||||
[analysis]
|
||||
# Disable support for `type: ignore` comments
|
||||
respect-type-ignore-comments = false
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -47,10 +95,19 @@ configuration setting.
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml title="pyproject.toml"
|
||||
[tool.ty.environment]
|
||||
extra-paths = ["./shared/my-search-path"]
|
||||
```
|
||||
=== "pyproject.toml"
|
||||
|
||||
```toml
|
||||
[tool.ty.environment]
|
||||
extra-paths = ["./shared/my-search-path"]
|
||||
```
|
||||
|
||||
=== "ty.toml"
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
extra-paths = ["./shared/my-search-path"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -78,10 +135,19 @@ This option can be used to point to virtual or system Python environments.
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml title="pyproject.toml"
|
||||
[tool.ty.environment]
|
||||
python = "./custom-venv-location/.venv"
|
||||
```
|
||||
=== "pyproject.toml"
|
||||
|
||||
```toml
|
||||
[tool.ty.environment]
|
||||
python = "./custom-venv-location/.venv"
|
||||
```
|
||||
|
||||
=== "ty.toml"
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python = "./custom-venv-location/.venv"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -105,11 +171,21 @@ If no platform is specified, ty will use the current platform:
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml title="pyproject.toml"
|
||||
[tool.ty.environment]
|
||||
# Tailor type stubs and conditionalized type definitions to windows.
|
||||
python-platform = "win32"
|
||||
```
|
||||
=== "pyproject.toml"
|
||||
|
||||
```toml
|
||||
[tool.ty.environment]
|
||||
# Tailor type stubs and conditionalized type definitions to windows.
|
||||
python-platform = "win32"
|
||||
```
|
||||
|
||||
=== "ty.toml"
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
# Tailor type stubs and conditionalized type definitions to windows.
|
||||
python-platform = "win32"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -139,10 +215,19 @@ to reflect the differing contents of the standard library across Python versions
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml title="pyproject.toml"
|
||||
[tool.ty.environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
=== "pyproject.toml"
|
||||
|
||||
```toml
|
||||
[tool.ty.environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
=== "ty.toml"
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -167,11 +252,21 @@ it will also be included in the first party search path.
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml title="pyproject.toml"
|
||||
[tool.ty.environment]
|
||||
# Multiple directories (priority order)
|
||||
root = ["./src", "./lib", "./vendor"]
|
||||
```
|
||||
=== "pyproject.toml"
|
||||
|
||||
```toml
|
||||
[tool.ty.environment]
|
||||
# Multiple directories (priority order)
|
||||
root = ["./src", "./lib", "./vendor"]
|
||||
```
|
||||
|
||||
=== "ty.toml"
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
# Multiple directories (priority order)
|
||||
root = ["./src", "./lib", "./vendor"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -187,10 +282,19 @@ bundled as a zip file in the binary
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml title="pyproject.toml"
|
||||
[tool.ty.environment]
|
||||
typeshed = "/path/to/custom/typeshed"
|
||||
```
|
||||
=== "pyproject.toml"
|
||||
|
||||
```toml
|
||||
[tool.ty.environment]
|
||||
typeshed = "/path/to/custom/typeshed"
|
||||
```
|
||||
|
||||
=== "ty.toml"
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
typeshed = "/path/to/custom/typeshed"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -240,15 +344,29 @@ If not specified, defaults to `[]` (excludes no files).
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml title="pyproject.toml"
|
||||
[[tool.ty.overrides]]
|
||||
exclude = [
|
||||
"generated",
|
||||
"*.proto",
|
||||
"tests/fixtures/**",
|
||||
"!tests/fixtures/important.py" # Include this one file
|
||||
]
|
||||
```
|
||||
=== "pyproject.toml"
|
||||
|
||||
```toml
|
||||
[[tool.ty.overrides]]
|
||||
exclude = [
|
||||
"generated",
|
||||
"*.proto",
|
||||
"tests/fixtures/**",
|
||||
"!tests/fixtures/important.py" # Include this one file
|
||||
]
|
||||
```
|
||||
|
||||
=== "ty.toml"
|
||||
|
||||
```toml
|
||||
[[overrides]]
|
||||
exclude = [
|
||||
"generated",
|
||||
"*.proto",
|
||||
"tests/fixtures/**",
|
||||
"!tests/fixtures/important.py" # Include this one file
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -268,13 +386,25 @@ If not specified, defaults to `["**"]` (matches all files).
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml title="pyproject.toml"
|
||||
[[tool.ty.overrides]]
|
||||
include = [
|
||||
"src",
|
||||
"tests",
|
||||
]
|
||||
```
|
||||
=== "pyproject.toml"
|
||||
|
||||
```toml
|
||||
[[tool.ty.overrides]]
|
||||
include = [
|
||||
"src",
|
||||
"tests",
|
||||
]
|
||||
```
|
||||
|
||||
=== "ty.toml"
|
||||
|
||||
```toml
|
||||
[[overrides]]
|
||||
include = [
|
||||
"src",
|
||||
"tests",
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -292,13 +422,25 @@ severity levels or disable them entirely.
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml title="pyproject.toml"
|
||||
[[tool.ty.overrides]]
|
||||
include = ["src"]
|
||||
=== "pyproject.toml"
|
||||
|
||||
[tool.ty.overrides.rules]
|
||||
possibly-unresolved-reference = "ignore"
|
||||
```
|
||||
```toml
|
||||
[[tool.ty.overrides]]
|
||||
include = ["src"]
|
||||
|
||||
[tool.ty.overrides.rules]
|
||||
possibly-unresolved-reference = "ignore"
|
||||
```
|
||||
|
||||
=== "ty.toml"
|
||||
|
||||
```toml
|
||||
[[overrides]]
|
||||
include = ["src"]
|
||||
|
||||
[overrides.rules]
|
||||
possibly-unresolved-reference = "ignore"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -358,15 +500,29 @@ to re-include `dist` use `exclude = ["!dist"]`
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml title="pyproject.toml"
|
||||
[tool.ty.src]
|
||||
exclude = [
|
||||
"generated",
|
||||
"*.proto",
|
||||
"tests/fixtures/**",
|
||||
"!tests/fixtures/important.py" # Include this one file
|
||||
]
|
||||
```
|
||||
=== "pyproject.toml"
|
||||
|
||||
```toml
|
||||
[tool.ty.src]
|
||||
exclude = [
|
||||
"generated",
|
||||
"*.proto",
|
||||
"tests/fixtures/**",
|
||||
"!tests/fixtures/important.py" # Include this one file
|
||||
]
|
||||
```
|
||||
|
||||
=== "ty.toml"
|
||||
|
||||
```toml
|
||||
[src]
|
||||
exclude = [
|
||||
"generated",
|
||||
"*.proto",
|
||||
"tests/fixtures/**",
|
||||
"!tests/fixtures/important.py" # Include this one file
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -399,13 +555,25 @@ matches `<project_root>/src` and not `<project_root>/test/src`).
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml title="pyproject.toml"
|
||||
[tool.ty.src]
|
||||
include = [
|
||||
"src",
|
||||
"tests",
|
||||
]
|
||||
```
|
||||
=== "pyproject.toml"
|
||||
|
||||
```toml
|
||||
[tool.ty.src]
|
||||
include = [
|
||||
"src",
|
||||
"tests",
|
||||
]
|
||||
```
|
||||
|
||||
=== "ty.toml"
|
||||
|
||||
```toml
|
||||
[src]
|
||||
include = [
|
||||
"src",
|
||||
"tests",
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -421,10 +589,19 @@ Enabled by default.
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml title="pyproject.toml"
|
||||
[tool.ty.src]
|
||||
respect-ignore-files = false
|
||||
```
|
||||
=== "pyproject.toml"
|
||||
|
||||
```toml
|
||||
[tool.ty.src]
|
||||
respect-ignore-files = false
|
||||
```
|
||||
|
||||
=== "ty.toml"
|
||||
|
||||
```toml
|
||||
[src]
|
||||
respect-ignore-files = false
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -450,10 +627,19 @@ it will also be included in the first party search path.
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml title="pyproject.toml"
|
||||
[tool.ty.src]
|
||||
root = "./app"
|
||||
```
|
||||
=== "pyproject.toml"
|
||||
|
||||
```toml
|
||||
[tool.ty.src]
|
||||
root = "./app"
|
||||
```
|
||||
|
||||
=== "ty.toml"
|
||||
|
||||
```toml
|
||||
[src]
|
||||
root = "./app"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -471,11 +657,21 @@ Defaults to `false`.
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml title="pyproject.toml"
|
||||
[tool.ty.terminal]
|
||||
# Error if ty emits any warning-level diagnostics.
|
||||
error-on-warning = true
|
||||
```
|
||||
=== "pyproject.toml"
|
||||
|
||||
```toml
|
||||
[tool.ty.terminal]
|
||||
# Error if ty emits any warning-level diagnostics.
|
||||
error-on-warning = true
|
||||
```
|
||||
|
||||
=== "ty.toml"
|
||||
|
||||
```toml
|
||||
[terminal]
|
||||
# Error if ty emits any warning-level diagnostics.
|
||||
error-on-warning = true
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -491,10 +687,19 @@ Defaults to `full`.
|
||||
|
||||
**Example usage**:
|
||||
|
||||
```toml title="pyproject.toml"
|
||||
[tool.ty.terminal]
|
||||
output-format = "concise"
|
||||
```
|
||||
=== "pyproject.toml"
|
||||
|
||||
```toml
|
||||
[tool.ty.terminal]
|
||||
output-format = "concise"
|
||||
```
|
||||
|
||||
=== "ty.toml"
|
||||
|
||||
```toml
|
||||
[terminal]
|
||||
output-format = "concise"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
|
||||
1327
crates/ty/docs/rules.md
generated
1327
crates/ty/docs/rules.md
generated
File diff suppressed because it is too large
Load Diff
@@ -154,6 +154,17 @@ pub(crate) struct CheckCommand {
|
||||
#[clap(long, overrides_with("respect_ignore_files"), hide = true)]
|
||||
no_respect_ignore_files: bool,
|
||||
|
||||
/// Enforce exclusions, even for paths passed to ty directly on the command-line.
|
||||
/// Use `--no-force-exclude` to disable.
|
||||
#[arg(
|
||||
long,
|
||||
overrides_with("no_force_exclude"),
|
||||
help_heading = "File selection"
|
||||
)]
|
||||
force_exclude: bool,
|
||||
#[clap(long, overrides_with("force_exclude"), hide = true)]
|
||||
no_force_exclude: bool,
|
||||
|
||||
/// Glob patterns for files to exclude from type checking.
|
||||
///
|
||||
/// Uses gitignore-style syntax to exclude files and directories from type checking.
|
||||
@@ -178,6 +189,10 @@ pub(crate) struct CheckCommand {
|
||||
}
|
||||
|
||||
impl CheckCommand {
|
||||
pub(crate) fn force_exclude(&self) -> bool {
|
||||
resolve_bool_arg(self.force_exclude, self.no_force_exclude).unwrap_or_default()
|
||||
}
|
||||
|
||||
pub(crate) fn into_options(self) -> Options {
|
||||
let rules = if self.rules.is_empty() {
|
||||
None
|
||||
@@ -435,3 +450,12 @@ impl ConfigsArg {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_bool_arg(yes: bool, no: bool) -> Option<bool> {
|
||||
match (yes, no) {
|
||||
(true, false) => Some(true),
|
||||
(false, true) => Some(false),
|
||||
(false, false) => None,
|
||||
(..) => unreachable!("Clap should make this impossible"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,9 +10,9 @@ use ty_static::EnvVars;
|
||||
|
||||
use std::fmt::Write;
|
||||
use std::process::{ExitCode, Termination};
|
||||
use std::sync::Mutex;
|
||||
|
||||
use anyhow::Result;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use crate::args::{CheckCommand, Command, TerminalColor};
|
||||
use crate::logging::{VerbosityLevel, setup_tracing};
|
||||
@@ -22,6 +22,7 @@ use clap::{CommandFactory, Parser};
|
||||
use colored::Colorize;
|
||||
use crossbeam::channel as crossbeam_channel;
|
||||
use rayon::ThreadPoolBuilder;
|
||||
use ruff_db::cancellation::{CancellationToken, CancellationTokenSource};
|
||||
use ruff_db::diagnostic::{
|
||||
Diagnostic, DiagnosticId, DisplayDiagnosticConfig, DisplayDiagnostics, Severity,
|
||||
};
|
||||
@@ -118,9 +119,12 @@ fn run_check(args: CheckCommand) -> anyhow::Result<ExitStatus> {
|
||||
.config_file
|
||||
.as_ref()
|
||||
.map(|path| SystemPath::absolute(path, &cwd));
|
||||
let force_exclude = args.force_exclude();
|
||||
|
||||
let mut project_metadata = match &config_file {
|
||||
Some(config_file) => ProjectMetadata::from_config_file(config_file.clone(), &system)?,
|
||||
Some(config_file) => {
|
||||
ProjectMetadata::from_config_file(config_file.clone(), &project_path, &system)?
|
||||
}
|
||||
None => ProjectMetadata::discover(&project_path, &system)?,
|
||||
};
|
||||
|
||||
@@ -130,11 +134,13 @@ fn run_check(args: CheckCommand) -> anyhow::Result<ExitStatus> {
|
||||
project_metadata.apply_overrides(&project_options_overrides);
|
||||
|
||||
let mut db = ProjectDatabase::new(project_metadata, system)?;
|
||||
let project = db.project();
|
||||
|
||||
project.set_verbose(&mut db, verbosity >= VerbosityLevel::Verbose);
|
||||
project.set_force_exclude(&mut db, force_exclude);
|
||||
|
||||
db.project()
|
||||
.set_verbose(&mut db, verbosity >= VerbosityLevel::Verbose);
|
||||
if !check_paths.is_empty() {
|
||||
db.project().set_included_paths(&mut db, check_paths);
|
||||
project.set_included_paths(&mut db, check_paths);
|
||||
}
|
||||
|
||||
let (main_loop, main_loop_cancellation_token) =
|
||||
@@ -222,6 +228,11 @@ struct MainLoop {
|
||||
printer: Printer,
|
||||
|
||||
project_options_overrides: ProjectOptionsOverrides,
|
||||
|
||||
/// Cancellation token that gets set by Ctrl+C.
|
||||
/// Used for long-running operations on the main thread. Operations on background threads
|
||||
/// use Salsa's cancellation mechanism.
|
||||
cancellation_token: CancellationToken,
|
||||
}
|
||||
|
||||
impl MainLoop {
|
||||
@@ -231,6 +242,9 @@ impl MainLoop {
|
||||
) -> (Self, MainLoopCancellationToken) {
|
||||
let (sender, receiver) = crossbeam_channel::bounded(10);
|
||||
|
||||
let cancellation_token_source = CancellationTokenSource::new();
|
||||
let cancellation_token = cancellation_token_source.token();
|
||||
|
||||
(
|
||||
Self {
|
||||
sender: sender.clone(),
|
||||
@@ -238,8 +252,12 @@ impl MainLoop {
|
||||
watcher: None,
|
||||
project_options_overrides,
|
||||
printer,
|
||||
cancellation_token,
|
||||
},
|
||||
MainLoopCancellationToken {
|
||||
sender,
|
||||
source: cancellation_token_source,
|
||||
},
|
||||
MainLoopCancellationToken { sender },
|
||||
)
|
||||
}
|
||||
|
||||
@@ -273,9 +291,6 @@ impl MainLoop {
|
||||
let mut revision = 0u64;
|
||||
|
||||
while let Ok(message) = self.receiver.recv() {
|
||||
if self.watcher.is_some() {
|
||||
Printer::clear_screen()?;
|
||||
}
|
||||
match message {
|
||||
MainLoopMessage::CheckWorkspace => {
|
||||
let db = db.clone();
|
||||
@@ -314,6 +329,7 @@ impl MainLoop {
|
||||
let display_config = DisplayDiagnosticConfig::default()
|
||||
.format(terminal_settings.output_format.into())
|
||||
.color(colored::control::SHOULD_COLORIZE.should_colorize())
|
||||
.with_cancellation_token(Some(self.cancellation_token.clone()))
|
||||
.show_fix_diff(true);
|
||||
|
||||
if check_revision == revision {
|
||||
@@ -357,19 +373,21 @@ impl MainLoop {
|
||||
)?;
|
||||
}
|
||||
|
||||
if is_human_readable {
|
||||
writeln!(
|
||||
self.printer.stream_for_failure_summary(),
|
||||
"Found {} diagnostic{}",
|
||||
diagnostics_count,
|
||||
if diagnostics_count > 1 { "s" } else { "" }
|
||||
)?;
|
||||
}
|
||||
if !self.cancellation_token.is_cancelled() {
|
||||
if is_human_readable {
|
||||
writeln!(
|
||||
self.printer.stream_for_failure_summary(),
|
||||
"Found {} diagnostic{}",
|
||||
diagnostics_count,
|
||||
if diagnostics_count > 1 { "s" } else { "" }
|
||||
)?;
|
||||
}
|
||||
|
||||
if exit_status.is_internal_error() {
|
||||
tracing::warn!(
|
||||
"A fatal error occurred while checking some files. Not all project files were analyzed. See the diagnostics list above for details."
|
||||
);
|
||||
if exit_status.is_internal_error() {
|
||||
tracing::warn!(
|
||||
"A fatal error occurred while checking some files. Not all project files were analyzed. See the diagnostics list above for details."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if self.watcher.is_none() {
|
||||
@@ -384,12 +402,15 @@ impl MainLoop {
|
||||
}
|
||||
|
||||
MainLoopMessage::ApplyChanges(changes) => {
|
||||
Printer::clear_screen()?;
|
||||
|
||||
revision += 1;
|
||||
// Automatically cancels any pending queries and waits for them to complete.
|
||||
db.apply_changes(changes, Some(&self.project_options_overrides));
|
||||
if let Some(watcher) = self.watcher.as_mut() {
|
||||
watcher.update(db);
|
||||
}
|
||||
|
||||
self.sender.send(MainLoopMessage::CheckWorkspace).unwrap();
|
||||
}
|
||||
MainLoopMessage::Exit => {
|
||||
@@ -493,10 +514,12 @@ impl ty_project::ProgressReporter for IndicatifReporter {
|
||||
#[derive(Debug)]
|
||||
struct MainLoopCancellationToken {
|
||||
sender: crossbeam_channel::Sender<MainLoopMessage>,
|
||||
source: CancellationTokenSource,
|
||||
}
|
||||
|
||||
impl MainLoopCancellationToken {
|
||||
fn stop(self) {
|
||||
self.source.cancel();
|
||||
self.sender.send(MainLoopMessage::Exit).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
43
crates/ty/tests/cli/analysis_options.rs
Normal file
43
crates/ty/tests/cli/analysis_options.rs
Normal file
@@ -0,0 +1,43 @@
|
||||
use insta_cmd::assert_cmd_snapshot;
|
||||
|
||||
use crate::CliTest;
|
||||
|
||||
/// ty ignores `type: ignore` comments when setting `respect-type-ignore-comments=false`
|
||||
#[test]
|
||||
fn respect_type_ignore_comments_is_turned_off() -> anyhow::Result<()> {
|
||||
let case = CliTest::with_file(
|
||||
"test.py",
|
||||
r#"
|
||||
y = a + 5 # type: ignore
|
||||
"#,
|
||||
)?;
|
||||
|
||||
// Assert that there's an `unresolved-reference` diagnostic (error).
|
||||
assert_cmd_snapshot!(case.command(), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
All checks passed!
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
|
||||
assert_cmd_snapshot!(case.command().arg("--config").arg("analysis.respect-type-ignore-comments=false"), @r"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
error[unresolved-reference]: Name `a` used when not defined
|
||||
--> test.py:2:5
|
||||
|
|
||||
2 | y = a + 5 # type: ignore
|
||||
| ^
|
||||
|
|
||||
info: rule `unresolved-reference` is enabled by default
|
||||
|
||||
Found 1 diagnostic
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -133,7 +133,7 @@ fn cli_config_args_invalid_option() -> anyhow::Result<()> {
|
||||
|
|
||||
1 | bad-option=true
|
||||
| ^^^^^^^^^^
|
||||
unknown field `bad-option`, expected one of `environment`, `src`, `rules`, `terminal`, `overrides`
|
||||
unknown field `bad-option`, expected one of `environment`, `src`, `rules`, `terminal`, `analysis`, `overrides`
|
||||
|
||||
|
||||
Usage: ty <COMMAND>
|
||||
|
||||
@@ -589,6 +589,128 @@ fn explicit_path_overrides_exclude() -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Test behavior when explicitly checking a path that matches an exclude pattern and `--force-exclude` is provided
|
||||
#[test]
|
||||
fn explicit_path_overrides_exclude_force_exclude() -> anyhow::Result<()> {
|
||||
let case = CliTest::with_files([
|
||||
(
|
||||
"src/main.py",
|
||||
r#"
|
||||
print(undefined_var) # error: unresolved-reference
|
||||
"#,
|
||||
),
|
||||
(
|
||||
"tests/generated.py",
|
||||
r#"
|
||||
print(dist_undefined_var) # error: unresolved-reference
|
||||
"#,
|
||||
),
|
||||
(
|
||||
"dist/other.py",
|
||||
r#"
|
||||
print(other_undefined_var) # error: unresolved-reference
|
||||
"#,
|
||||
),
|
||||
(
|
||||
"ty.toml",
|
||||
r#"
|
||||
[src]
|
||||
exclude = ["tests/generated.py"]
|
||||
"#,
|
||||
),
|
||||
])?;
|
||||
|
||||
// Explicitly checking a file in an excluded directory should still check that file
|
||||
assert_cmd_snapshot!(case.command().arg("tests/generated.py").arg("src/main.py"), @r"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
error[unresolved-reference]: Name `undefined_var` used when not defined
|
||||
--> src/main.py:2:7
|
||||
|
|
||||
2 | print(undefined_var) # error: unresolved-reference
|
||||
| ^^^^^^^^^^^^^
|
||||
|
|
||||
info: rule `unresolved-reference` is enabled by default
|
||||
|
||||
error[unresolved-reference]: Name `dist_undefined_var` used when not defined
|
||||
--> tests/generated.py:2:7
|
||||
|
|
||||
2 | print(dist_undefined_var) # error: unresolved-reference
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
info: rule `unresolved-reference` is enabled by default
|
||||
|
||||
Found 2 diagnostics
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
|
||||
// Except when `--force-exclude` is set.
|
||||
assert_cmd_snapshot!(case.command().arg("tests/generated.py").arg("src/main.py").arg("--force-exclude"), @r"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
error[unresolved-reference]: Name `undefined_var` used when not defined
|
||||
--> src/main.py:2:7
|
||||
|
|
||||
2 | print(undefined_var) # error: unresolved-reference
|
||||
| ^^^^^^^^^^^^^
|
||||
|
|
||||
info: rule `unresolved-reference` is enabled by default
|
||||
|
||||
Found 1 diagnostic
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
|
||||
// Explicitly checking the entire excluded directory should check all files in it
|
||||
assert_cmd_snapshot!(case.command().arg("dist/").arg("src/main.py"), @r"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
error[unresolved-reference]: Name `other_undefined_var` used when not defined
|
||||
--> dist/other.py:2:7
|
||||
|
|
||||
2 | print(other_undefined_var) # error: unresolved-reference
|
||||
| ^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
info: rule `unresolved-reference` is enabled by default
|
||||
|
||||
error[unresolved-reference]: Name `undefined_var` used when not defined
|
||||
--> src/main.py:2:7
|
||||
|
|
||||
2 | print(undefined_var) # error: unresolved-reference
|
||||
| ^^^^^^^^^^^^^
|
||||
|
|
||||
info: rule `unresolved-reference` is enabled by default
|
||||
|
||||
Found 2 diagnostics
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
|
||||
// Except when using `--force-exclude`
|
||||
assert_cmd_snapshot!(case.command().arg("dist/").arg("src/main.py").arg("--force-exclude"), @r"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
error[unresolved-reference]: Name `undefined_var` used when not defined
|
||||
--> src/main.py:2:7
|
||||
|
|
||||
2 | print(undefined_var) # error: unresolved-reference
|
||||
| ^^^^^^^^^^^^^
|
||||
|
|
||||
info: rule `unresolved-reference` is enabled by default
|
||||
|
||||
Found 1 diagnostic
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cli_and_configuration_exclude() -> anyhow::Result<()> {
|
||||
let case = CliTest::with_files([
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
mod analysis_options;
|
||||
mod config_option;
|
||||
mod exit_code;
|
||||
mod file_selection;
|
||||
@@ -658,6 +659,8 @@ fn gitlab_diagnostics() -> anyhow::Result<()> {
|
||||
r#"
|
||||
print(x) # [unresolved-reference]
|
||||
print(4[1]) # [non-subscriptable]
|
||||
from typing_extensions import reveal_type
|
||||
reveal_type('str'.lower()) # [revealed-type]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
@@ -708,6 +711,25 @@ fn gitlab_diagnostics() -> anyhow::Result<()> {
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"check_name": "revealed-type",
|
||||
"description": "revealed-type: Revealed type: `LiteralString`",
|
||||
"severity": "info",
|
||||
"fingerprint": "[FINGERPRINT]",
|
||||
"location": {
|
||||
"path": "test.py",
|
||||
"positions": {
|
||||
"begin": {
|
||||
"line": 5,
|
||||
"column": 13
|
||||
},
|
||||
"end": {
|
||||
"line": 5,
|
||||
"column": 26
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
----- stderr -----
|
||||
@@ -723,6 +745,8 @@ fn github_diagnostics() -> anyhow::Result<()> {
|
||||
r#"
|
||||
print(x) # [unresolved-reference]
|
||||
print(4[1]) # [non-subscriptable]
|
||||
from typing_extensions import reveal_type
|
||||
reveal_type('str'.lower()) # [revealed-type]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
@@ -732,6 +756,7 @@ fn github_diagnostics() -> anyhow::Result<()> {
|
||||
----- stdout -----
|
||||
::warning title=ty (unresolved-reference),file=<temp_dir>/test.py,line=2,col=7,endLine=2,endColumn=8::test.py:2:7: unresolved-reference: Name `x` used when not defined
|
||||
::error title=ty (non-subscriptable),file=<temp_dir>/test.py,line=3,col=7,endLine=3,endColumn=11::test.py:3:7: non-subscriptable: Cannot subscript object of type `Literal[4]` with no `__getitem__` method
|
||||
::notice title=ty (revealed-type),file=<temp_dir>/test.py,line=5,col=13,endLine=5,endColumn=26::test.py:5:13: revealed-type: Revealed type: `LiteralString`
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
|
||||
@@ -10,12 +10,13 @@ use ruff_db::system::{
|
||||
OsSystem, System, SystemPath, SystemPathBuf, UserConfigDirectoryOverrideGuard, file_time_now,
|
||||
};
|
||||
use ruff_python_ast::PythonVersion;
|
||||
use ty_module_resolver::{Module, ModuleName, resolve_module_confident};
|
||||
use ty_project::metadata::options::{EnvironmentOptions, Options, ProjectOptionsOverrides};
|
||||
use ty_project::metadata::pyproject::{PyProject, Tool};
|
||||
use ty_project::metadata::value::{RangedValue, RelativePathBuf};
|
||||
use ty_project::watch::{ChangeEvent, ProjectWatcher, directory_watcher};
|
||||
use ty_project::{Db, ProjectDatabase, ProjectMetadata};
|
||||
use ty_python_semantic::{Module, ModuleName, PythonPlatform, resolve_module_confident};
|
||||
use ty_python_semantic::PythonPlatform;
|
||||
|
||||
struct TestCase {
|
||||
db: ProjectDatabase,
|
||||
@@ -1019,7 +1020,7 @@ fn search_path() -> anyhow::Result<()> {
|
||||
let site_packages = case.root_path().join("site_packages");
|
||||
|
||||
assert_eq!(
|
||||
resolve_module_confident(case.db(), &ModuleName::new("a").unwrap()),
|
||||
resolve_module_confident(case.db(), &ModuleName::new_static("a").unwrap()),
|
||||
None
|
||||
);
|
||||
|
||||
@@ -1192,7 +1193,7 @@ fn changed_versions_file() -> anyhow::Result<()> {
|
||||
|
||||
// Unset the custom typeshed directory.
|
||||
assert_eq!(
|
||||
resolve_module_confident(case.db(), &ModuleName::new("os").unwrap()),
|
||||
resolve_module_confident(case.db(), &ModuleName::new_static("os").unwrap()),
|
||||
None
|
||||
);
|
||||
|
||||
@@ -1207,7 +1208,7 @@ fn changed_versions_file() -> anyhow::Result<()> {
|
||||
|
||||
case.apply_changes(changes, None);
|
||||
|
||||
assert!(resolve_module_confident(case.db(), &ModuleName::new("os").unwrap()).is_some());
|
||||
assert!(resolve_module_confident(case.db(), &ModuleName::new_static("os").unwrap()).is_some());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1874,11 +1875,11 @@ fn rename_files_casing_only() -> anyhow::Result<()> {
|
||||
let mut case = setup([("lib.py", "class Foo: ...")])?;
|
||||
|
||||
assert!(
|
||||
resolve_module_confident(case.db(), &ModuleName::new("lib").unwrap()).is_some(),
|
||||
resolve_module_confident(case.db(), &ModuleName::new_static("lib").unwrap()).is_some(),
|
||||
"Expected `lib` module to exist."
|
||||
);
|
||||
assert_eq!(
|
||||
resolve_module_confident(case.db(), &ModuleName::new("Lib").unwrap()),
|
||||
resolve_module_confident(case.db(), &ModuleName::new_static("Lib").unwrap()),
|
||||
None,
|
||||
"Expected `Lib` module not to exist"
|
||||
);
|
||||
@@ -1911,13 +1912,13 @@ fn rename_files_casing_only() -> anyhow::Result<()> {
|
||||
|
||||
// Resolving `lib` should now fail but `Lib` should now succeed
|
||||
assert_eq!(
|
||||
resolve_module_confident(case.db(), &ModuleName::new("lib").unwrap()),
|
||||
resolve_module_confident(case.db(), &ModuleName::new_static("lib").unwrap()),
|
||||
None,
|
||||
"Expected `lib` module to no longer exist."
|
||||
);
|
||||
|
||||
assert!(
|
||||
resolve_module_confident(case.db(), &ModuleName::new("Lib").unwrap()).is_some(),
|
||||
resolve_module_confident(case.db(), &ModuleName::new_static("Lib").unwrap()).is_some(),
|
||||
"Expected `Lib` module to exist"
|
||||
);
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ license.workspace = true
|
||||
[dependencies]
|
||||
ruff_db = { workspace = true }
|
||||
ruff_python_ast = { workspace = true }
|
||||
ty_python_semantic = { workspace = true }
|
||||
|
||||
ordermap = { workspace = true }
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ use std::{collections::HashMap, hash::BuildHasher};
|
||||
use ordermap::OrderMap;
|
||||
use ruff_db::system::SystemPathBuf;
|
||||
use ruff_python_ast::PythonVersion;
|
||||
use ty_python_semantic::PythonPlatform;
|
||||
|
||||
/// Combine two values, preferring the values in `self`.
|
||||
///
|
||||
@@ -145,7 +144,6 @@ macro_rules! impl_noop_combine {
|
||||
}
|
||||
|
||||
impl_noop_combine!(SystemPathBuf);
|
||||
impl_noop_combine!(PythonPlatform);
|
||||
impl_noop_combine!(PythonVersion);
|
||||
|
||||
// std types
|
||||
|
||||
@@ -15,8 +15,8 @@ ruff_db = { workspace = true, features = ["os"] }
|
||||
ruff_text_size = { workspace = true }
|
||||
|
||||
ty_ide = { workspace = true }
|
||||
ty_module_resolver = { workspace = true }
|
||||
ty_project = { workspace = true }
|
||||
ty_python_semantic = { workspace = true }
|
||||
|
||||
anyhow = { workspace = true }
|
||||
bstr = { workspace = true }
|
||||
|
||||
@@ -28,4 +28,4 @@ scope-simple-long-identifier,main.py,0,1
|
||||
tstring-completions,main.py,0,1
|
||||
ty-extensions-lower-stdlib,main.py,0,9
|
||||
type-var-typing-over-ast,main.py,0,3
|
||||
type-var-typing-over-ast,main.py,1,251
|
||||
type-var-typing-over-ast,main.py,1,253
|
||||
|
||||
|
@@ -15,11 +15,11 @@ use regex::bytes::Regex;
|
||||
use ruff_db::files::system_path_to_file;
|
||||
use ruff_db::system::{OsSystem, SystemPath, SystemPathBuf};
|
||||
use ty_ide::Completion;
|
||||
use ty_module_resolver::ModuleName;
|
||||
use ty_project::metadata::Options;
|
||||
use ty_project::metadata::options::EnvironmentOptions;
|
||||
use ty_project::metadata::value::RelativePathBuf;
|
||||
use ty_project::{ProjectDatabase, ProjectMetadata};
|
||||
use ty_python_semantic::ModuleName;
|
||||
|
||||
#[derive(Debug, clap::Parser)]
|
||||
#[command(
|
||||
|
||||
@@ -22,7 +22,9 @@ ruff_python_importer = { workspace = true }
|
||||
ruff_python_trivia = { workspace = true }
|
||||
ruff_source_file = { workspace = true }
|
||||
ruff_text_size = { workspace = true }
|
||||
ty_module_resolver = { workspace = true }
|
||||
ty_python_semantic = { workspace = true }
|
||||
ty_python_types = { workspace = true }
|
||||
ty_project = { workspace = true, features = ["testing"] }
|
||||
ty_vendored = { workspace = true }
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use ruff_db::files::File;
|
||||
use ty_module_resolver::{Module, ModuleName, all_modules, resolve_real_shadowable_module};
|
||||
use ty_project::Db;
|
||||
use ty_python_semantic::{Module, ModuleName, all_modules, resolve_real_shadowable_module};
|
||||
|
||||
use crate::{
|
||||
SymbolKind,
|
||||
@@ -21,7 +21,7 @@ pub fn all_symbols<'db>(
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let typing_extensions = ModuleName::new("typing_extensions").unwrap();
|
||||
let typing_extensions = ModuleName::new_static("typing_extensions").unwrap();
|
||||
let is_typing_extensions_available = importing_from.is_stub(db)
|
||||
|| resolve_real_shadowable_module(db, importing_from, &typing_extensions).is_some();
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ use ruff_text_size::TextRange;
|
||||
use ty_project::Db;
|
||||
use ty_python_semantic::create_suppression_fix;
|
||||
use ty_python_semantic::lint::LintId;
|
||||
use ty_python_semantic::types::{UNDEFINED_REVEAL, UNRESOLVED_REFERENCE};
|
||||
use ty_python_types::types::{UNDEFINED_REVEAL, UNRESOLVED_REFERENCE};
|
||||
|
||||
/// A `QuickFix` Code Action
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -87,10 +87,8 @@ mod tests {
|
||||
use ruff_python_trivia::textwrap::dedent;
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
use ty_project::ProjectMetadata;
|
||||
use ty_python_semantic::{
|
||||
lint::LintMetadata,
|
||||
types::{UNDEFINED_REVEAL, UNRESOLVED_REFERENCE},
|
||||
};
|
||||
use ty_python_semantic::lint::LintMetadata;
|
||||
use ty_python_types::types::{UNDEFINED_REVEAL, UNRESOLVED_REFERENCE};
|
||||
|
||||
#[test]
|
||||
fn add_ignore() {
|
||||
|
||||
@@ -11,11 +11,9 @@ use ruff_python_ast::{self as ast, AnyNodeRef};
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
use rustc_hash::FxHashSet;
|
||||
use ty_python_semantic::types::UnionType;
|
||||
use ty_python_semantic::{
|
||||
Completion as SemanticCompletion, KnownModule, ModuleName, NameKind, SemanticModel,
|
||||
types::{CycleDetector, KnownClass, Type},
|
||||
};
|
||||
use ty_module_resolver::{KnownModule, ModuleName};
|
||||
use ty_python_types::types::{CycleDetector, KnownClass, Type, UnionType};
|
||||
use ty_python_types::{Completion as SemanticCompletion, NameKind, SemanticModel};
|
||||
|
||||
use crate::docstring::Docstring;
|
||||
use crate::goto::Definitions;
|
||||
@@ -2106,7 +2104,7 @@ mod tests {
|
||||
use insta::assert_snapshot;
|
||||
use ruff_python_ast::token::{TokenKind, Tokens};
|
||||
use ruff_python_parser::{Mode, ParseOptions};
|
||||
use ty_python_semantic::ModuleName;
|
||||
use ty_module_resolver::ModuleName;
|
||||
|
||||
use crate::completion::{Completion, completion};
|
||||
use crate::tests::{CursorTest, CursorTestBuilder};
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::references::{ReferencesMode, references};
|
||||
use crate::{Db, ReferenceTarget};
|
||||
use ruff_db::files::File;
|
||||
use ruff_text_size::TextSize;
|
||||
use ty_python_semantic::SemanticModel;
|
||||
use ty_python_types::SemanticModel;
|
||||
|
||||
/// Find all document highlights for a symbol at the given position.
|
||||
/// Document highlights are limited to the current file only.
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::references::{ReferencesMode, references};
|
||||
use crate::{Db, ReferenceTarget};
|
||||
use ruff_db::files::File;
|
||||
use ruff_text_size::TextSize;
|
||||
use ty_python_semantic::SemanticModel;
|
||||
use ty_python_types::SemanticModel;
|
||||
|
||||
/// Find all references to a symbol at the given position.
|
||||
/// Search for references across all files in the project.
|
||||
|
||||
@@ -12,12 +12,12 @@ use ruff_python_ast::token::{TokenKind, Tokens};
|
||||
use ruff_python_ast::{self as ast, AnyNodeRef};
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
|
||||
use ty_python_semantic::ResolvedDefinition;
|
||||
use ty_python_semantic::types::Type;
|
||||
use ty_python_semantic::types::ide_support::{
|
||||
use ty_python_types::ResolvedDefinition;
|
||||
use ty_python_types::types::Type;
|
||||
use ty_python_types::types::ide_support::{
|
||||
call_signature_details, call_type_simplified_by_overloads, definitions_for_keyword_argument,
|
||||
};
|
||||
use ty_python_semantic::{
|
||||
use ty_python_types::{
|
||||
HasDefinition, HasType, ImportAliasResolution, SemanticModel, definitions_for_imported_symbol,
|
||||
definitions_for_name,
|
||||
};
|
||||
@@ -228,15 +228,15 @@ impl<'db> Definitions<'db> {
|
||||
pub(crate) fn from_ty(db: &'db dyn crate::Db, ty: Type<'db>) -> Option<Self> {
|
||||
let ty_def = ty.definition(db)?;
|
||||
let resolved = match ty_def {
|
||||
ty_python_semantic::types::TypeDefinition::Module(module) => {
|
||||
ty_python_types::types::TypeDefinition::Module(module) => {
|
||||
ResolvedDefinition::Module(module.file(db)?)
|
||||
}
|
||||
ty_python_semantic::types::TypeDefinition::Class(definition)
|
||||
| ty_python_semantic::types::TypeDefinition::Function(definition)
|
||||
| ty_python_semantic::types::TypeDefinition::TypeVar(definition)
|
||||
| ty_python_semantic::types::TypeDefinition::TypeAlias(definition)
|
||||
| ty_python_semantic::types::TypeDefinition::SpecialForm(definition)
|
||||
| ty_python_semantic::types::TypeDefinition::NewType(definition) => {
|
||||
ty_python_types::types::TypeDefinition::Class(definition)
|
||||
| ty_python_types::types::TypeDefinition::Function(definition)
|
||||
| ty_python_types::types::TypeDefinition::TypeVar(definition)
|
||||
| ty_python_types::types::TypeDefinition::TypeAlias(definition)
|
||||
| ty_python_types::types::TypeDefinition::SpecialForm(definition)
|
||||
| ty_python_types::types::TypeDefinition::NewType(definition) => {
|
||||
ResolvedDefinition::Definition(definition)
|
||||
}
|
||||
};
|
||||
@@ -338,11 +338,11 @@ impl GotoTarget<'_> {
|
||||
}
|
||||
}
|
||||
GotoTarget::BinOp { expression, .. } => {
|
||||
let (_, ty) = ty_python_semantic::definitions_for_bin_op(model, expression)?;
|
||||
let (_, ty) = ty_python_types::definitions_for_bin_op(model, expression)?;
|
||||
Some(ty)
|
||||
}
|
||||
GotoTarget::UnaryOp { expression, .. } => {
|
||||
let (_, ty) = ty_python_semantic::definitions_for_unary_op(model, expression)?;
|
||||
let (_, ty) = ty_python_types::definitions_for_unary_op(model, expression)?;
|
||||
Some(ty)
|
||||
}
|
||||
// TODO: Support identifier targets
|
||||
@@ -526,15 +526,14 @@ impl GotoTarget<'_> {
|
||||
}
|
||||
|
||||
GotoTarget::BinOp { expression, .. } => {
|
||||
let (definitions, _) =
|
||||
ty_python_semantic::definitions_for_bin_op(model, expression)?;
|
||||
let (definitions, _) = ty_python_types::definitions_for_bin_op(model, expression)?;
|
||||
|
||||
Some(definitions)
|
||||
}
|
||||
|
||||
GotoTarget::UnaryOp { expression, .. } => {
|
||||
let (definitions, _) =
|
||||
ty_python_semantic::definitions_for_unary_op(model, expression)?;
|
||||
ty_python_types::definitions_for_unary_op(model, expression)?;
|
||||
|
||||
Some(definitions)
|
||||
}
|
||||
@@ -939,12 +938,12 @@ impl Ranged for GotoTarget<'_> {
|
||||
/// Converts a collection of `ResolvedDefinition` items into `NavigationTarget` items.
|
||||
fn convert_resolved_definitions_to_targets<'db>(
|
||||
db: &'db dyn ty_python_semantic::Db,
|
||||
definitions: Vec<ty_python_semantic::ResolvedDefinition<'db>>,
|
||||
definitions: Vec<ty_python_types::ResolvedDefinition<'db>>,
|
||||
) -> Vec<crate::NavigationTarget> {
|
||||
definitions
|
||||
.into_iter()
|
||||
.map(|resolved_definition| match resolved_definition {
|
||||
ty_python_semantic::ResolvedDefinition::Definition(definition) => {
|
||||
ty_python_types::ResolvedDefinition::Definition(definition) => {
|
||||
// Get the parsed module for range calculation
|
||||
let definition_file = definition.file(db);
|
||||
let module = ruff_db::parsed::parsed_module(db, definition_file).load(db);
|
||||
@@ -959,11 +958,11 @@ fn convert_resolved_definitions_to_targets<'db>(
|
||||
full_range: full_range.range(),
|
||||
}
|
||||
}
|
||||
ty_python_semantic::ResolvedDefinition::Module(file) => {
|
||||
ty_python_types::ResolvedDefinition::Module(file) => {
|
||||
// For modules, navigate to the start of the file
|
||||
crate::NavigationTarget::new(file, TextRange::default())
|
||||
}
|
||||
ty_python_semantic::ResolvedDefinition::FileWithRange(file_range) => {
|
||||
ty_python_types::ResolvedDefinition::FileWithRange(file_range) => {
|
||||
// For file ranges, navigate to the specific range within the file
|
||||
crate::NavigationTarget::from(file_range)
|
||||
}
|
||||
@@ -984,9 +983,9 @@ fn definitions_for_expression<'db>(
|
||||
expression.into(),
|
||||
alias_resolution,
|
||||
)),
|
||||
ast::ExprRef::Attribute(attribute) => Some(ty_python_semantic::definitions_for_attribute(
|
||||
model, attribute,
|
||||
)),
|
||||
ast::ExprRef::Attribute(attribute) => {
|
||||
Some(ty_python_types::definitions_for_attribute(model, attribute))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -1007,7 +1006,7 @@ fn definitions_for_callable<'db>(
|
||||
fn definitions_to_navigation_targets<'db>(
|
||||
db: &dyn ty_python_semantic::Db,
|
||||
stub_mapper: Option<&StubMapper<'db>>,
|
||||
mut definitions: Vec<ty_python_semantic::ResolvedDefinition<'db>>,
|
||||
mut definitions: Vec<ty_python_types::ResolvedDefinition<'db>>,
|
||||
) -> Option<crate::NavigationTargets> {
|
||||
if let Some(mapper) = stub_mapper {
|
||||
definitions = mapper.map_definitions(definitions);
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::{Db, NavigationTargets, RangedValue};
|
||||
use ruff_db::files::{File, FileRange};
|
||||
use ruff_db::parsed::parsed_module;
|
||||
use ruff_text_size::{Ranged, TextSize};
|
||||
use ty_python_semantic::{ImportAliasResolution, SemanticModel};
|
||||
use ty_python_types::{ImportAliasResolution, SemanticModel};
|
||||
|
||||
/// Navigate to the declaration of a symbol.
|
||||
///
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::{Db, NavigationTargets, RangedValue};
|
||||
use ruff_db::files::{File, FileRange};
|
||||
use ruff_db::parsed::parsed_module;
|
||||
use ruff_text_size::{Ranged, TextSize};
|
||||
use ty_python_semantic::{ImportAliasResolution, SemanticModel};
|
||||
use ty_python_types::{ImportAliasResolution, SemanticModel};
|
||||
|
||||
/// Navigate to the definition of a symbol.
|
||||
///
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::{Db, HasNavigationTargets, NavigationTargets, RangedValue};
|
||||
use ruff_db::files::{File, FileRange};
|
||||
use ruff_db::parsed::parsed_module;
|
||||
use ruff_text_size::{Ranged, TextSize};
|
||||
use ty_python_semantic::SemanticModel;
|
||||
use ty_python_types::SemanticModel;
|
||||
|
||||
pub fn goto_type_definition(
|
||||
db: &dyn Db,
|
||||
@@ -1339,13 +1339,13 @@ f(**kwargs<CURSOR>)
|
||||
| ^^^^^^ Clicking here
|
||||
|
|
||||
info: Found 1 type definition
|
||||
--> stdlib/builtins.pyi:2920:7
|
||||
--> stdlib/builtins.pyi:2947:7
|
||||
|
|
||||
2919 | @disjoint_base
|
||||
2920 | class dict(MutableMapping[_KT, _VT]):
|
||||
2946 | @disjoint_base
|
||||
2947 | class dict(MutableMapping[_KT, _VT]):
|
||||
| ----
|
||||
2921 | """dict() -> new empty dictionary
|
||||
2922 | dict(mapping) -> new dictionary initialized from a mapping object's
|
||||
2948 | """dict() -> new empty dictionary
|
||||
2949 | dict(mapping) -> new dictionary initialized from a mapping object's
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ use ruff_db::parsed::parsed_module;
|
||||
use ruff_text_size::{Ranged, TextSize};
|
||||
use std::fmt;
|
||||
use std::fmt::Formatter;
|
||||
use ty_python_semantic::types::{KnownInstanceType, Type, TypeVarVariance};
|
||||
use ty_python_semantic::{DisplaySettings, SemanticModel};
|
||||
use ty_python_types::types::{KnownInstanceType, Type, TypeVarVariance};
|
||||
use ty_python_types::{DisplaySettings, SemanticModel};
|
||||
|
||||
pub fn hover(db: &dyn Db, file: File, offset: TextSize) -> Option<RangedValue<Hover<'_>>> {
|
||||
let parsed = parsed_module(db, file).load(db);
|
||||
@@ -23,7 +23,7 @@ pub fn hover(db: &dyn Db, file: File, offset: TextSize) -> Option<RangedValue<Ho
|
||||
let docs = goto_target
|
||||
.get_definition_targets(
|
||||
&model,
|
||||
ty_python_semantic::ImportAliasResolution::ResolveAliases,
|
||||
ty_python_types::ImportAliasResolution::ResolveAliases,
|
||||
)
|
||||
.and_then(|definitions| definitions.docstring(db))
|
||||
.map(HoverContent::Docstring);
|
||||
@@ -2956,6 +2956,295 @@ def function():
|
||||
assert_snapshot!(test.hover(), @"Hover provided no content");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hover_func_with_concat_docstring() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
def a<CURSOR>b():
|
||||
"""wow cool docs""" """and docs"""
|
||||
return
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.hover(), @r#"
|
||||
def ab() -> Unknown
|
||||
---------------------------------------------
|
||||
wow cool docsand docs
|
||||
|
||||
---------------------------------------------
|
||||
```python
|
||||
def ab() -> Unknown
|
||||
```
|
||||
---
|
||||
wow cool docsand docs
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:2:5
|
||||
|
|
||||
2 | def ab():
|
||||
| ^-
|
||||
| ||
|
||||
| |Cursor offset
|
||||
| source
|
||||
3 | """wow cool docs""" """and docs"""
|
||||
4 | return
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hover_func_with_plus_docstring() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
def a<CURSOR>b():
|
||||
"""wow cool docs""" + """and docs"""
|
||||
return
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.hover(), @r#"
|
||||
def ab() -> Unknown
|
||||
---------------------------------------------
|
||||
```python
|
||||
def ab() -> Unknown
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:2:5
|
||||
|
|
||||
2 | def ab():
|
||||
| ^-
|
||||
| ||
|
||||
| |Cursor offset
|
||||
| source
|
||||
3 | """wow cool docs""" + """and docs"""
|
||||
4 | return
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hover_func_with_slash_docstring() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
def a<CURSOR>b():
|
||||
"""wow cool docs""" \
|
||||
"""and docs"""
|
||||
return
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.hover(), @r#"
|
||||
def ab() -> Unknown
|
||||
---------------------------------------------
|
||||
wow cool docsand docs
|
||||
|
||||
---------------------------------------------
|
||||
```python
|
||||
def ab() -> Unknown
|
||||
```
|
||||
---
|
||||
wow cool docsand docs
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:2:5
|
||||
|
|
||||
2 | def ab():
|
||||
| ^-
|
||||
| ||
|
||||
| |Cursor offset
|
||||
| source
|
||||
3 | """wow cool docs""" \
|
||||
4 | """and docs"""
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hover_func_with_sameline_commented_docstring() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
def a<CURSOR>b():
|
||||
"""wow cool docs""" # and a comment
|
||||
"""and docs""" # that shouldn't be included
|
||||
return
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.hover(), @r#"
|
||||
def ab() -> Unknown
|
||||
---------------------------------------------
|
||||
wow cool docs
|
||||
|
||||
---------------------------------------------
|
||||
```python
|
||||
def ab() -> Unknown
|
||||
```
|
||||
---
|
||||
wow cool docs
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:2:5
|
||||
|
|
||||
2 | def ab():
|
||||
| ^-
|
||||
| ||
|
||||
| |Cursor offset
|
||||
| source
|
||||
3 | """wow cool docs""" # and a comment
|
||||
4 | """and docs""" # that shouldn't be included
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hover_func_with_nextline_commented_docstring() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
def a<CURSOR>b():
|
||||
"""wow cool docs"""
|
||||
# and a comment that shouldn't be included
|
||||
"""and docs"""
|
||||
return
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.hover(), @r#"
|
||||
def ab() -> Unknown
|
||||
---------------------------------------------
|
||||
wow cool docs
|
||||
|
||||
---------------------------------------------
|
||||
```python
|
||||
def ab() -> Unknown
|
||||
```
|
||||
---
|
||||
wow cool docs
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:2:5
|
||||
|
|
||||
2 | def ab():
|
||||
| ^-
|
||||
| ||
|
||||
| |Cursor offset
|
||||
| source
|
||||
3 | """wow cool docs"""
|
||||
4 | # and a comment that shouldn't be included
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hover_func_with_parens_docstring() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
def a<CURSOR>b():
|
||||
(
|
||||
"""wow cool docs"""
|
||||
"""and docs"""
|
||||
)
|
||||
return
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.hover(), @r#"
|
||||
def ab() -> Unknown
|
||||
---------------------------------------------
|
||||
wow cool docsand docs
|
||||
|
||||
---------------------------------------------
|
||||
```python
|
||||
def ab() -> Unknown
|
||||
```
|
||||
---
|
||||
wow cool docsand docs
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:2:5
|
||||
|
|
||||
2 | def ab():
|
||||
| ^-
|
||||
| ||
|
||||
| |Cursor offset
|
||||
| source
|
||||
3 | (
|
||||
4 | """wow cool docs"""
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hover_func_with_nextline_commented_parens_docstring() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
def a<CURSOR>b():
|
||||
(
|
||||
"""wow cool docs"""
|
||||
# and a comment that shouldn't be included
|
||||
"""and docs"""
|
||||
)
|
||||
return
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.hover(), @r#"
|
||||
def ab() -> Unknown
|
||||
---------------------------------------------
|
||||
wow cool docsand docs
|
||||
|
||||
---------------------------------------------
|
||||
```python
|
||||
def ab() -> Unknown
|
||||
```
|
||||
---
|
||||
wow cool docsand docs
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:2:5
|
||||
|
|
||||
2 | def ab():
|
||||
| ^-
|
||||
| ||
|
||||
| |Cursor offset
|
||||
| source
|
||||
3 | (
|
||||
4 | """wow cool docs"""
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hover_attribute_docstring_spill() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
if True:
|
||||
a<CURSOR>b = 1
|
||||
"this shouldn't be a docstring but also it doesn't matter much"
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.hover(), @r#"
|
||||
Literal[1]
|
||||
---------------------------------------------
|
||||
```python
|
||||
Literal[1]
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:3:5
|
||||
|
|
||||
2 | if True:
|
||||
3 | ab = 1
|
||||
| ^-
|
||||
| ||
|
||||
| |Cursor offset
|
||||
| source
|
||||
4 | "this shouldn't be a docstring but also it doesn't matter much"
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hover_class_typevar_variance() {
|
||||
let test = cursor_test(
|
||||
|
||||
@@ -29,10 +29,11 @@ use ruff_python_ast::visitor::source_order::{SourceOrderVisitor, TraversalSignal
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_python_importer::Insertion;
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
use ty_module_resolver::ModuleName;
|
||||
use ty_project::Db;
|
||||
use ty_python_semantic::semantic_index::definition::DefinitionKind;
|
||||
use ty_python_semantic::types::Type;
|
||||
use ty_python_semantic::{MemberDefinition, ModuleName, SemanticModel};
|
||||
use ty_python_types::types::Type;
|
||||
use ty_python_types::{MemberDefinition, SemanticModel};
|
||||
|
||||
pub(crate) struct Importer<'a> {
|
||||
/// The ty Salsa database.
|
||||
@@ -880,11 +881,10 @@ mod tests {
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_python_trivia::textwrap::dedent;
|
||||
use ruff_text_size::TextSize;
|
||||
use ty_module_resolver::SearchPathSettings;
|
||||
use ty_project::ProjectMetadata;
|
||||
use ty_python_semantic::{
|
||||
Program, ProgramSettings, PythonPlatform, PythonVersionWithSource, SearchPathSettings,
|
||||
SemanticModel,
|
||||
};
|
||||
use ty_python_semantic::{Program, ProgramSettings, PythonPlatform, PythonVersionWithSource};
|
||||
use ty_python_types::SemanticModel;
|
||||
|
||||
use super::*;
|
||||
|
||||
|
||||
@@ -6,9 +6,9 @@ use ruff_db::parsed::parsed_module;
|
||||
use ruff_python_ast::visitor::source_order::{self, SourceOrderVisitor, TraversalSignal};
|
||||
use ruff_python_ast::{AnyNodeRef, ArgOrKeyword, Expr, ExprUnaryOp, Stmt, UnaryOp};
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
use ty_python_semantic::types::ide_support::inlay_hint_call_argument_details;
|
||||
use ty_python_semantic::types::{Type, TypeDetail};
|
||||
use ty_python_semantic::{HasType, SemanticModel};
|
||||
use ty_python_types::types::ide_support::inlay_hint_call_argument_details;
|
||||
use ty_python_types::types::{Type, TypeDetail};
|
||||
use ty_python_types::{HasType, SemanticModel};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct InlayHint {
|
||||
@@ -1241,12 +1241,12 @@ mod tests {
|
||||
|
||||
---------------------------------------------
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> stdlib/builtins.pyi:2695:7
|
||||
--> stdlib/builtins.pyi:2722:7
|
||||
|
|
||||
2694 | @disjoint_base
|
||||
2695 | class tuple(Sequence[_T_co]):
|
||||
2721 | @disjoint_base
|
||||
2722 | class tuple(Sequence[_T_co]):
|
||||
| ^^^^^
|
||||
2696 | """Built-in immutable sequence.
|
||||
2723 | """Built-in immutable sequence.
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:8:5
|
||||
@@ -1335,12 +1335,12 @@ mod tests {
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> stdlib/builtins.pyi:2695:7
|
||||
--> stdlib/builtins.pyi:2722:7
|
||||
|
|
||||
2694 | @disjoint_base
|
||||
2695 | class tuple(Sequence[_T_co]):
|
||||
2721 | @disjoint_base
|
||||
2722 | class tuple(Sequence[_T_co]):
|
||||
| ^^^^^
|
||||
2696 | """Built-in immutable sequence.
|
||||
2723 | """Built-in immutable sequence.
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:9:5
|
||||
@@ -1391,12 +1391,12 @@ mod tests {
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> stdlib/builtins.pyi:2695:7
|
||||
--> stdlib/builtins.pyi:2722:7
|
||||
|
|
||||
2694 | @disjoint_base
|
||||
2695 | class tuple(Sequence[_T_co]):
|
||||
2721 | @disjoint_base
|
||||
2722 | class tuple(Sequence[_T_co]):
|
||||
| ^^^^^
|
||||
2696 | """Built-in immutable sequence.
|
||||
2723 | """Built-in immutable sequence.
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:10:5
|
||||
@@ -2217,12 +2217,12 @@ mod tests {
|
||||
|
||||
---------------------------------------------
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> stdlib/builtins.pyi:2802:7
|
||||
--> stdlib/builtins.pyi:2829:7
|
||||
|
|
||||
2801 | @disjoint_base
|
||||
2802 | class list(MutableSequence[_T]):
|
||||
2828 | @disjoint_base
|
||||
2829 | class list(MutableSequence[_T]):
|
||||
| ^^^^
|
||||
2803 | """Built-in mutable sequence.
|
||||
2830 | """Built-in mutable sequence.
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:2:5
|
||||
@@ -2270,12 +2270,12 @@ mod tests {
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> stdlib/builtins.pyi:2802:7
|
||||
--> stdlib/builtins.pyi:2829:7
|
||||
|
|
||||
2801 | @disjoint_base
|
||||
2802 | class list(MutableSequence[_T]):
|
||||
2828 | @disjoint_base
|
||||
2829 | class list(MutableSequence[_T]):
|
||||
| ^^^^
|
||||
2803 | """Built-in mutable sequence.
|
||||
2830 | """Built-in mutable sequence.
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:3:5
|
||||
@@ -2325,12 +2325,12 @@ mod tests {
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> stdlib/builtins.pyi:2802:7
|
||||
--> stdlib/builtins.pyi:2829:7
|
||||
|
|
||||
2801 | @disjoint_base
|
||||
2802 | class list(MutableSequence[_T]):
|
||||
2828 | @disjoint_base
|
||||
2829 | class list(MutableSequence[_T]):
|
||||
| ^^^^
|
||||
2803 | """Built-in mutable sequence.
|
||||
2830 | """Built-in mutable sequence.
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:4:5
|
||||
@@ -2364,13 +2364,13 @@ mod tests {
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> stdlib/builtins.pyi:2591:7
|
||||
--> stdlib/builtins.pyi:2618:7
|
||||
|
|
||||
2590 | @final
|
||||
2591 | class bool(int):
|
||||
2617 | @final
|
||||
2618 | class bool(int):
|
||||
| ^^^^
|
||||
2592 | """Returns True when the argument is true, False otherwise.
|
||||
2593 | The builtins True and False are the only two instances of the class bool.
|
||||
2619 | """Returns True when the argument is true, False otherwise.
|
||||
2620 | The builtins True and False are the only two instances of the class bool.
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:4:20
|
||||
@@ -2384,12 +2384,12 @@ mod tests {
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> stdlib/builtins.pyi:2802:7
|
||||
--> stdlib/builtins.pyi:2829:7
|
||||
|
|
||||
2801 | @disjoint_base
|
||||
2802 | class list(MutableSequence[_T]):
|
||||
2828 | @disjoint_base
|
||||
2829 | class list(MutableSequence[_T]):
|
||||
| ^^^^
|
||||
2803 | """Built-in mutable sequence.
|
||||
2830 | """Built-in mutable sequence.
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:5:5
|
||||
@@ -2443,12 +2443,12 @@ mod tests {
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> stdlib/builtins.pyi:2802:7
|
||||
--> stdlib/builtins.pyi:2829:7
|
||||
|
|
||||
2801 | @disjoint_base
|
||||
2802 | class list(MutableSequence[_T]):
|
||||
2828 | @disjoint_base
|
||||
2829 | class list(MutableSequence[_T]):
|
||||
| ^^^^
|
||||
2803 | """Built-in mutable sequence.
|
||||
2830 | """Built-in mutable sequence.
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:6:5
|
||||
@@ -2502,12 +2502,12 @@ mod tests {
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> stdlib/builtins.pyi:2802:7
|
||||
--> stdlib/builtins.pyi:2829:7
|
||||
|
|
||||
2801 | @disjoint_base
|
||||
2802 | class list(MutableSequence[_T]):
|
||||
2828 | @disjoint_base
|
||||
2829 | class list(MutableSequence[_T]):
|
||||
| ^^^^
|
||||
2803 | """Built-in mutable sequence.
|
||||
2830 | """Built-in mutable sequence.
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:7:5
|
||||
@@ -2561,12 +2561,12 @@ mod tests {
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> stdlib/builtins.pyi:2802:7
|
||||
--> stdlib/builtins.pyi:2829:7
|
||||
|
|
||||
2801 | @disjoint_base
|
||||
2802 | class list(MutableSequence[_T]):
|
||||
2828 | @disjoint_base
|
||||
2829 | class list(MutableSequence[_T]):
|
||||
| ^^^^
|
||||
2803 | """Built-in mutable sequence.
|
||||
2830 | """Built-in mutable sequence.
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:8:5
|
||||
@@ -2620,12 +2620,12 @@ mod tests {
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> stdlib/builtins.pyi:2802:7
|
||||
--> stdlib/builtins.pyi:2829:7
|
||||
|
|
||||
2801 | @disjoint_base
|
||||
2802 | class list(MutableSequence[_T]):
|
||||
2828 | @disjoint_base
|
||||
2829 | class list(MutableSequence[_T]):
|
||||
| ^^^^
|
||||
2803 | """Built-in mutable sequence.
|
||||
2830 | """Built-in mutable sequence.
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:9:5
|
||||
@@ -2678,12 +2678,12 @@ mod tests {
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> stdlib/builtins.pyi:2802:7
|
||||
--> stdlib/builtins.pyi:2829:7
|
||||
|
|
||||
2801 | @disjoint_base
|
||||
2802 | class list(MutableSequence[_T]):
|
||||
2828 | @disjoint_base
|
||||
2829 | class list(MutableSequence[_T]):
|
||||
| ^^^^
|
||||
2803 | """Built-in mutable sequence.
|
||||
2830 | """Built-in mutable sequence.
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:10:5
|
||||
@@ -2737,12 +2737,12 @@ mod tests {
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> stdlib/builtins.pyi:2802:7
|
||||
--> stdlib/builtins.pyi:2829:7
|
||||
|
|
||||
2801 | @disjoint_base
|
||||
2802 | class list(MutableSequence[_T]):
|
||||
2828 | @disjoint_base
|
||||
2829 | class list(MutableSequence[_T]):
|
||||
| ^^^^
|
||||
2803 | """Built-in mutable sequence.
|
||||
2830 | """Built-in mutable sequence.
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:11:5
|
||||
@@ -2811,12 +2811,12 @@ mod tests {
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> stdlib/builtins.pyi:2802:7
|
||||
--> stdlib/builtins.pyi:2829:7
|
||||
|
|
||||
2801 | @disjoint_base
|
||||
2802 | class list(MutableSequence[_T]):
|
||||
2828 | @disjoint_base
|
||||
2829 | class list(MutableSequence[_T]):
|
||||
| ^^^^
|
||||
2803 | """Built-in mutable sequence.
|
||||
2830 | """Built-in mutable sequence.
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:12:5
|
||||
@@ -2925,12 +2925,12 @@ mod tests {
|
||||
|
||||
---------------------------------------------
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> stdlib/builtins.pyi:2695:7
|
||||
--> stdlib/builtins.pyi:2722:7
|
||||
|
|
||||
2694 | @disjoint_base
|
||||
2695 | class tuple(Sequence[_T_co]):
|
||||
2721 | @disjoint_base
|
||||
2722 | class tuple(Sequence[_T_co]):
|
||||
| ^^^^^
|
||||
2696 | """Built-in immutable sequence.
|
||||
2723 | """Built-in immutable sequence.
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:7:5
|
||||
@@ -3092,12 +3092,12 @@ mod tests {
|
||||
|
||||
---------------------------------------------
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> stdlib/builtins.pyi:2802:7
|
||||
--> stdlib/builtins.pyi:2829:7
|
||||
|
|
||||
2801 | @disjoint_base
|
||||
2802 | class list(MutableSequence[_T]):
|
||||
2828 | @disjoint_base
|
||||
2829 | class list(MutableSequence[_T]):
|
||||
| ^^^^
|
||||
2803 | """Built-in mutable sequence.
|
||||
2830 | """Built-in mutable sequence.
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:4:18
|
||||
@@ -3110,12 +3110,12 @@ mod tests {
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> stdlib/builtins.pyi:2695:7
|
||||
--> stdlib/builtins.pyi:2722:7
|
||||
|
|
||||
2694 | @disjoint_base
|
||||
2695 | class tuple(Sequence[_T_co]):
|
||||
2721 | @disjoint_base
|
||||
2722 | class tuple(Sequence[_T_co]):
|
||||
| ^^^^^
|
||||
2696 | """Built-in immutable sequence.
|
||||
2723 | """Built-in immutable sequence.
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:5:18
|
||||
@@ -3248,12 +3248,12 @@ mod tests {
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> stdlib/builtins.pyi:2695:7
|
||||
--> stdlib/builtins.pyi:2722:7
|
||||
|
|
||||
2694 | @disjoint_base
|
||||
2695 | class tuple(Sequence[_T_co]):
|
||||
2721 | @disjoint_base
|
||||
2722 | class tuple(Sequence[_T_co]):
|
||||
| ^^^^^
|
||||
2696 | """Built-in immutable sequence.
|
||||
2723 | """Built-in immutable sequence.
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:8:5
|
||||
@@ -4250,12 +4250,12 @@ mod tests {
|
||||
foo([x=]y[0])
|
||||
---------------------------------------------
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> stdlib/builtins.pyi:2802:7
|
||||
--> stdlib/builtins.pyi:2829:7
|
||||
|
|
||||
2801 | @disjoint_base
|
||||
2802 | class list(MutableSequence[_T]):
|
||||
2828 | @disjoint_base
|
||||
2829 | class list(MutableSequence[_T]):
|
||||
| ^^^^
|
||||
2803 | """Built-in mutable sequence.
|
||||
2830 | """Built-in mutable sequence.
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:3:5
|
||||
@@ -4303,12 +4303,12 @@ mod tests {
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> stdlib/builtins.pyi:2802:7
|
||||
--> stdlib/builtins.pyi:2829:7
|
||||
|
|
||||
2801 | @disjoint_base
|
||||
2802 | class list(MutableSequence[_T]):
|
||||
2828 | @disjoint_base
|
||||
2829 | class list(MutableSequence[_T]):
|
||||
| ^^^^
|
||||
2803 | """Built-in mutable sequence.
|
||||
2830 | """Built-in mutable sequence.
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:4:5
|
||||
@@ -5609,12 +5609,12 @@ mod tests {
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> stdlib/builtins.pyi:2802:7
|
||||
--> stdlib/builtins.pyi:2829:7
|
||||
|
|
||||
2801 | @disjoint_base
|
||||
2802 | class list(MutableSequence[_T]):
|
||||
2828 | @disjoint_base
|
||||
2829 | class list(MutableSequence[_T]):
|
||||
| ^^^^
|
||||
2803 | """Built-in mutable sequence.
|
||||
2830 | """Built-in mutable sequence.
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:3:14
|
||||
@@ -6046,13 +6046,13 @@ mod tests {
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> stdlib/builtins.pyi:2591:7
|
||||
--> stdlib/builtins.pyi:2618:7
|
||||
|
|
||||
2590 | @final
|
||||
2591 | class bool(int):
|
||||
2617 | @final
|
||||
2618 | class bool(int):
|
||||
| ^^^^
|
||||
2592 | """Returns True when the argument is true, False otherwise.
|
||||
2593 | The builtins True and False are the only two instances of the class bool.
|
||||
2619 | """Returns True when the argument is true, False otherwise.
|
||||
2620 | The builtins True and False are the only two instances of the class bool.
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:4:25
|
||||
@@ -6100,12 +6100,12 @@ mod tests {
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> stdlib/builtins.pyi:2802:7
|
||||
--> stdlib/builtins.pyi:2829:7
|
||||
|
|
||||
2801 | @disjoint_base
|
||||
2802 | class list(MutableSequence[_T]):
|
||||
2828 | @disjoint_base
|
||||
2829 | class list(MutableSequence[_T]):
|
||||
| ^^^^
|
||||
2803 | """Built-in mutable sequence.
|
||||
2830 | """Built-in mutable sequence.
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:4:49
|
||||
@@ -6614,13 +6614,6 @@ mod tests {
|
||||
3 | y[: type[T@f]] = x
|
||||
| ^^^
|
||||
|
|
||||
|
||||
---------------------------------------------
|
||||
info[inlay-hint-edit]: File after edits
|
||||
info: Source
|
||||
|
||||
def f[T](x: type[T]):
|
||||
y: type[T@f] = x
|
||||
"#);
|
||||
}
|
||||
|
||||
@@ -6739,14 +6732,14 @@ mod tests {
|
||||
A = TypeAliasType([name=]'A', [value=]str)
|
||||
---------------------------------------------
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> stdlib/typing.pyi:2032:26
|
||||
--> stdlib/typing.pyi:2037:26
|
||||
|
|
||||
2030 | """
|
||||
2031 |
|
||||
2032 | def __new__(cls, name: str, value: Any, *, type_params: tuple[_TypeParameter, ...] = ()) -> Self: ...
|
||||
2035 | """
|
||||
2036 |
|
||||
2037 | def __new__(cls, name: str, value: Any, *, type_params: tuple[_TypeParameter, ...] = ()) -> Self: ...
|
||||
| ^^^^
|
||||
2033 | @property
|
||||
2034 | def __value__(self) -> Any: ... # AnnotationForm
|
||||
2038 | @property
|
||||
2039 | def __value__(self) -> Any: ... # AnnotationForm
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:3:20
|
||||
@@ -6757,14 +6750,14 @@ mod tests {
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> stdlib/typing.pyi:2032:37
|
||||
--> stdlib/typing.pyi:2037:37
|
||||
|
|
||||
2030 | """
|
||||
2031 |
|
||||
2032 | def __new__(cls, name: str, value: Any, *, type_params: tuple[_TypeParameter, ...] = ()) -> Self: ...
|
||||
2035 | """
|
||||
2036 |
|
||||
2037 | def __new__(cls, name: str, value: Any, *, type_params: tuple[_TypeParameter, ...] = ()) -> Self: ...
|
||||
| ^^^^^
|
||||
2033 | @property
|
||||
2034 | def __value__(self) -> Any: ... # AnnotationForm
|
||||
2038 | @property
|
||||
2039 | def __value__(self) -> Any: ... # AnnotationForm
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:3:32
|
||||
|
||||
@@ -57,7 +57,7 @@ use ruff_text_size::{Ranged, TextRange};
|
||||
use rustc_hash::FxHashSet;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use ty_project::Db;
|
||||
use ty_python_semantic::types::{Type, TypeDefinition};
|
||||
use ty_python_types::types::{Type, TypeDefinition};
|
||||
|
||||
/// Information associated with a text range.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
|
||||
@@ -20,7 +20,7 @@ use ruff_python_ast::{
|
||||
visitor::source_order::{SourceOrderVisitor, TraversalSignal},
|
||||
};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
use ty_python_semantic::{ImportAliasResolution, SemanticModel};
|
||||
use ty_python_types::{ImportAliasResolution, SemanticModel};
|
||||
|
||||
/// Mode for references search behavior
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::references::{ReferencesMode, references};
|
||||
use crate::{Db, ReferenceTarget};
|
||||
use ruff_db::files::File;
|
||||
use ruff_text_size::{Ranged, TextSize};
|
||||
use ty_python_semantic::SemanticModel;
|
||||
use ty_python_types::SemanticModel;
|
||||
|
||||
/// Returns the range of the symbol if it can be renamed, None if not.
|
||||
pub fn can_rename(db: &dyn Db, file: File, offset: TextSize) -> Option<ruff_text_size::TextRange> {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -15,13 +15,13 @@ use ruff_python_ast::find_node::covering_node;
|
||||
use ruff_python_ast::token::TokenKind;
|
||||
use ruff_python_ast::{self as ast, AnyNodeRef};
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
use ty_python_semantic::ResolvedDefinition;
|
||||
use ty_python_semantic::SemanticModel;
|
||||
use ty_python_semantic::semantic_index::definition::Definition;
|
||||
use ty_python_semantic::types::ide_support::{
|
||||
use ty_python_types::ResolvedDefinition;
|
||||
use ty_python_types::SemanticModel;
|
||||
use ty_python_types::types::ide_support::{
|
||||
CallSignatureDetails, call_signature_details, find_active_signature_from_details,
|
||||
};
|
||||
use ty_python_semantic::types::{ParameterKind, Type};
|
||||
use ty_python_types::types::{ParameterKind, Type};
|
||||
|
||||
// TODO: We may want to add special-case handling for calls to constructors
|
||||
// so the class docstring is used in place of (or inaddition to) any docstring
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use itertools::Either;
|
||||
use ruff_db::system::SystemPathBuf;
|
||||
use ty_python_semantic::{ResolvedDefinition, map_stub_definition};
|
||||
use ty_python_types::{ResolvedDefinition, map_stub_definition};
|
||||
|
||||
use crate::cached_vendored_root;
|
||||
|
||||
|
||||
@@ -14,8 +14,8 @@ use ruff_python_ast::name::{Name, UnqualifiedName};
|
||||
use ruff_python_ast::visitor::source_order::{self, SourceOrderVisitor};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use ty_module_resolver::{ModuleName, resolve_module};
|
||||
use ty_project::Db;
|
||||
use ty_python_semantic::{ModuleName, resolve_module};
|
||||
|
||||
use crate::completion::CompletionKind;
|
||||
|
||||
|
||||
38
crates/ty_module_resolver/Cargo.toml
Normal file
38
crates/ty_module_resolver/Cargo.toml
Normal file
@@ -0,0 +1,38 @@
|
||||
[package]
|
||||
name = "ty_module_resolver"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
rust-version = { workspace = true }
|
||||
homepage = { workspace = true }
|
||||
documentation = { workspace = true }
|
||||
repository = { workspace = true }
|
||||
license = { workspace = true }
|
||||
|
||||
[dependencies]
|
||||
ruff_db = { workspace = true }
|
||||
ruff_memory_usage = { workspace = true }
|
||||
ruff_python_ast = { workspace = true, features = ["salsa"] }
|
||||
ruff_python_stdlib = { workspace = true }
|
||||
|
||||
camino = { workspace = true }
|
||||
compact_str = { workspace = true }
|
||||
get-size2 = { workspace = true }
|
||||
rustc-hash = { workspace = true }
|
||||
salsa = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
strum = { workspace = true }
|
||||
strum_macros = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
ruff_db = { workspace = true, features = ["testing", "os"] }
|
||||
ty_vendored = { workspace = true }
|
||||
|
||||
anyhow = { workspace = true }
|
||||
insta = { workspace = true, features = ["filters"] }
|
||||
tempfile = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
124
crates/ty_module_resolver/src/db.rs
Normal file
124
crates/ty_module_resolver/src/db.rs
Normal file
@@ -0,0 +1,124 @@
|
||||
use ruff_db::Db as SourceDb;
|
||||
|
||||
use crate::resolve::SearchPaths;
|
||||
|
||||
#[salsa::db]
|
||||
pub trait Db: SourceDb {
|
||||
/// Returns the search paths for module resolution.
|
||||
fn search_paths(&self) -> &SearchPaths;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use ruff_db::Db as SourceDb;
|
||||
use ruff_db::files::Files;
|
||||
use ruff_db::system::{DbWithTestSystem, TestSystem};
|
||||
use ruff_db::vendored::VendoredFileSystem;
|
||||
use ruff_python_ast::PythonVersion;
|
||||
|
||||
use super::Db;
|
||||
use crate::resolve::SearchPaths;
|
||||
|
||||
type Events = Arc<Mutex<Vec<salsa::Event>>>;
|
||||
|
||||
#[salsa::db]
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct TestDb {
|
||||
storage: salsa::Storage<Self>,
|
||||
files: Files,
|
||||
system: TestSystem,
|
||||
vendored: VendoredFileSystem,
|
||||
search_paths: Arc<SearchPaths>,
|
||||
python_version: PythonVersion,
|
||||
events: Events,
|
||||
}
|
||||
|
||||
impl TestDb {
|
||||
pub(crate) fn new() -> Self {
|
||||
let events = Events::default();
|
||||
Self {
|
||||
storage: salsa::Storage::new(Some(Box::new({
|
||||
let events = events.clone();
|
||||
move |event| {
|
||||
tracing::trace!("event: {event:?}");
|
||||
let mut events = events.lock().unwrap();
|
||||
events.push(event);
|
||||
}
|
||||
}))),
|
||||
system: TestSystem::default(),
|
||||
vendored: ty_vendored::file_system().clone(),
|
||||
files: Files::default(),
|
||||
search_paths: Arc::new(SearchPaths::empty(ty_vendored::file_system())),
|
||||
python_version: PythonVersion::default(),
|
||||
events,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn with_search_paths(mut self, search_paths: SearchPaths) -> Self {
|
||||
self.set_search_paths(search_paths);
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn with_python_version(mut self, python_version: PythonVersion) -> Self {
|
||||
self.python_version = python_version;
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn set_search_paths(&mut self, search_paths: SearchPaths) {
|
||||
search_paths.try_register_static_roots(self);
|
||||
self.search_paths = Arc::new(search_paths);
|
||||
}
|
||||
|
||||
/// Takes the salsa events.
|
||||
pub(crate) fn take_salsa_events(&mut self) -> Vec<salsa::Event> {
|
||||
let mut events = self.events.lock().unwrap();
|
||||
std::mem::take(&mut *events)
|
||||
}
|
||||
|
||||
/// Clears the salsa events.
|
||||
pub(crate) fn clear_salsa_events(&mut self) {
|
||||
self.take_salsa_events();
|
||||
}
|
||||
}
|
||||
|
||||
impl DbWithTestSystem for TestDb {
|
||||
fn test_system(&self) -> &TestSystem {
|
||||
&self.system
|
||||
}
|
||||
|
||||
fn test_system_mut(&mut self) -> &mut TestSystem {
|
||||
&mut self.system
|
||||
}
|
||||
}
|
||||
|
||||
#[salsa::db]
|
||||
impl SourceDb for TestDb {
|
||||
fn vendored(&self) -> &VendoredFileSystem {
|
||||
&self.vendored
|
||||
}
|
||||
|
||||
fn system(&self) -> &dyn ruff_db::system::System {
|
||||
&self.system
|
||||
}
|
||||
|
||||
fn files(&self) -> &Files {
|
||||
&self.files
|
||||
}
|
||||
|
||||
fn python_version(&self) -> PythonVersion {
|
||||
self.python_version
|
||||
}
|
||||
}
|
||||
|
||||
#[salsa::db]
|
||||
impl Db for TestDb {
|
||||
fn search_paths(&self) -> &SearchPaths {
|
||||
&self.search_paths
|
||||
}
|
||||
}
|
||||
|
||||
#[salsa::db]
|
||||
impl salsa::Database for TestDb {}
|
||||
}
|
||||
@@ -1,24 +1,31 @@
|
||||
use std::iter::FusedIterator;
|
||||
|
||||
pub use list::{all_modules, list_modules};
|
||||
pub use module::KnownModule;
|
||||
pub use module::Module;
|
||||
pub use path::{SearchPath, SearchPathValidationError};
|
||||
pub use resolver::SearchPaths;
|
||||
pub(crate) use resolver::file_to_module;
|
||||
pub use resolver::{
|
||||
resolve_module, resolve_module_confident, resolve_real_module, resolve_real_module_confident,
|
||||
resolve_real_shadowable_module,
|
||||
};
|
||||
use ruff_db::system::SystemPath;
|
||||
|
||||
use crate::Db;
|
||||
pub(crate) use resolver::{ModuleResolveMode, SearchPathIterator, search_paths};
|
||||
pub use db::Db;
|
||||
pub use module::KnownModule;
|
||||
pub use module::Module;
|
||||
pub use module_name::{ModuleName, ModuleNameResolutionError};
|
||||
pub use path::{SearchPath, SearchPathError};
|
||||
pub use resolve::{
|
||||
SearchPaths, file_to_module, resolve_module, resolve_module_confident, resolve_real_module,
|
||||
resolve_real_module_confident, resolve_real_shadowable_module,
|
||||
};
|
||||
pub use settings::{MisconfigurationMode, SearchPathSettings, SearchPathSettingsError};
|
||||
pub use typeshed::{
|
||||
PyVersionRange, TypeshedVersions, TypeshedVersionsParseError, vendored_typeshed_versions,
|
||||
};
|
||||
|
||||
pub use list::{all_modules, list_modules};
|
||||
pub use resolve::{ModuleResolveMode, SearchPathIterator, search_paths};
|
||||
|
||||
mod db;
|
||||
mod list;
|
||||
mod module;
|
||||
mod module_name;
|
||||
mod path;
|
||||
mod resolver;
|
||||
mod resolve;
|
||||
mod settings;
|
||||
mod typeshed;
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -3,12 +3,10 @@ use std::collections::btree_map::{BTreeMap, Entry};
|
||||
use ruff_python_ast::PythonVersion;
|
||||
|
||||
use crate::db::Db;
|
||||
use crate::module::{Module, ModuleKind};
|
||||
use crate::module_name::ModuleName;
|
||||
use crate::program::Program;
|
||||
|
||||
use super::module::{Module, ModuleKind};
|
||||
use super::path::{ModulePath, SearchPath, SystemOrVendoredPathRef};
|
||||
use super::resolver::{ModuleResolveMode, ResolverContext, resolve_file_module, search_paths};
|
||||
use crate::path::{ModulePath, SearchPath, SystemOrVendoredPathRef};
|
||||
use crate::resolve::{ModuleResolveMode, ResolverContext, resolve_file_module, search_paths};
|
||||
|
||||
/// List all available modules, including all sub-modules, sorted in lexicographic order.
|
||||
pub fn all_modules(db: &dyn Db) -> Vec<Module<'_>> {
|
||||
@@ -100,7 +98,6 @@ fn list_modules_in<'db>(
|
||||
/// in the same directory).
|
||||
struct Lister<'db> {
|
||||
db: &'db dyn Db,
|
||||
program: Program,
|
||||
search_path: &'db SearchPath,
|
||||
modules: BTreeMap<&'db ModuleName, Module<'db>>,
|
||||
}
|
||||
@@ -109,10 +106,8 @@ impl<'db> Lister<'db> {
|
||||
/// Create new state that can accumulate modules from a list
|
||||
/// of file paths.
|
||||
fn new(db: &'db dyn Db, search_path: &'db SearchPath) -> Lister<'db> {
|
||||
let program = Program::get(db);
|
||||
Lister {
|
||||
db,
|
||||
program,
|
||||
search_path,
|
||||
modules: BTreeMap::new(),
|
||||
}
|
||||
@@ -314,7 +309,7 @@ impl<'db> Lister<'db> {
|
||||
/// Returns the Python version we want to perform module resolution
|
||||
/// with.
|
||||
fn python_version(&self) -> PythonVersion {
|
||||
self.program.python_version(self.db)
|
||||
self.db.python_version()
|
||||
}
|
||||
|
||||
/// Constructs a resolver context for use with some APIs that require it.
|
||||
@@ -382,20 +377,17 @@ mod tests {
|
||||
use camino::{Utf8Component, Utf8Path};
|
||||
use ruff_db::Db as _;
|
||||
use ruff_db::files::{File, FilePath, FileRootKind};
|
||||
use ruff_db::system::{
|
||||
DbWithTestSystem, DbWithWritableSystem, OsSystem, SystemPath, SystemPathBuf,
|
||||
};
|
||||
use ruff_db::system::{DbWithTestSystem, DbWithWritableSystem, SystemPath, SystemPathBuf};
|
||||
use ruff_db::testing::assert_function_query_was_not_run;
|
||||
use ruff_python_ast::PythonVersion;
|
||||
|
||||
use crate::db::{Db, tests::TestDb};
|
||||
use crate::module_resolver::module::Module;
|
||||
use crate::module_resolver::resolver::{
|
||||
use crate::module::Module;
|
||||
use crate::resolve::{
|
||||
ModuleResolveMode, ModuleResolveModeIngredient, dynamic_resolution_paths,
|
||||
};
|
||||
use crate::module_resolver::testing::{FileSpec, MockedTypeshed, TestCase, TestCaseBuilder};
|
||||
use crate::program::{Program, ProgramSettings, SearchPathSettings};
|
||||
use crate::{PythonPlatform, PythonVersionSource, PythonVersionWithSource};
|
||||
use crate::settings::SearchPathSettings;
|
||||
use crate::testing::{FileSpec, MockedTypeshed, TestCase, TestCaseBuilder};
|
||||
|
||||
use super::list_modules;
|
||||
|
||||
@@ -940,7 +932,7 @@ mod tests {
|
||||
fn symlink() -> anyhow::Result<()> {
|
||||
use anyhow::Context;
|
||||
|
||||
let mut db = TestDb::new();
|
||||
let mut db = TestDb::new().with_python_version(PythonVersion::PY38);
|
||||
|
||||
let temp_dir = tempfile::TempDir::with_prefix("PREFIX-SENTINEL")?;
|
||||
let root = temp_dir
|
||||
@@ -948,7 +940,7 @@ mod tests {
|
||||
.canonicalize()
|
||||
.context("Failed to canonicalize temp dir")?;
|
||||
let root = SystemPath::from_std_path(&root).unwrap();
|
||||
db.use_system(OsSystem::new(root));
|
||||
db.use_system(ruff_db::system::OsSystem::new(root));
|
||||
|
||||
let src = root.join("src");
|
||||
let site_packages = root.join("site-packages");
|
||||
@@ -965,26 +957,21 @@ mod tests {
|
||||
std::fs::write(foo.as_std_path(), "")?;
|
||||
std::os::unix::fs::symlink(foo.as_std_path(), bar.as_std_path())?;
|
||||
|
||||
db.files().try_add_root(&db, &src, FileRootKind::Project);
|
||||
let settings = SearchPathSettings {
|
||||
src_roots: vec![src.clone()],
|
||||
custom_typeshed: Some(custom_typeshed),
|
||||
site_packages_paths: vec![site_packages],
|
||||
..SearchPathSettings::empty()
|
||||
};
|
||||
|
||||
Program::from_settings(
|
||||
&db,
|
||||
ProgramSettings {
|
||||
python_version: PythonVersionWithSource {
|
||||
version: PythonVersion::PY38,
|
||||
source: PythonVersionSource::default(),
|
||||
},
|
||||
python_platform: PythonPlatform::default(),
|
||||
search_paths: SearchPathSettings {
|
||||
custom_typeshed: Some(custom_typeshed),
|
||||
site_packages_paths: vec![site_packages],
|
||||
..SearchPathSettings::new(vec![src])
|
||||
}
|
||||
db.set_search_paths(
|
||||
settings
|
||||
.to_search_paths(db.system(), db.vendored())
|
||||
.expect("Valid search path settings"),
|
||||
},
|
||||
);
|
||||
|
||||
db.files().try_add_root(&db, &src, FileRootKind::Project);
|
||||
|
||||
// From the original test in the "resolve this module"
|
||||
// implementation, this test seems to symlink a Python module
|
||||
// and assert that they are treated as two distinct modules.
|
||||
@@ -1479,18 +1466,15 @@ not_a_directory
|
||||
db.files()
|
||||
.try_add_root(&db, SystemPath::new("/src"), FileRootKind::Project);
|
||||
|
||||
Program::from_settings(
|
||||
&db,
|
||||
ProgramSettings {
|
||||
python_version: PythonVersionWithSource::default(),
|
||||
python_platform: PythonPlatform::default(),
|
||||
search_paths: SearchPathSettings {
|
||||
site_packages_paths: vec![venv_site_packages],
|
||||
..SearchPathSettings::new(vec![src.to_path_buf()])
|
||||
}
|
||||
let settings = SearchPathSettings {
|
||||
site_packages_paths: vec![venv_site_packages],
|
||||
..SearchPathSettings::new(vec![src.to_path_buf()])
|
||||
};
|
||||
|
||||
db.set_search_paths(
|
||||
settings
|
||||
.to_search_paths(db.system(), db.vendored())
|
||||
.expect("Valid search path settings"),
|
||||
},
|
||||
);
|
||||
|
||||
insta::assert_debug_snapshot!(
|
||||
@@ -1533,18 +1517,15 @@ not_a_directory
|
||||
db.files()
|
||||
.try_add_root(&db, SystemPath::new("/src"), FileRootKind::Project);
|
||||
|
||||
Program::from_settings(
|
||||
&db,
|
||||
ProgramSettings {
|
||||
python_version: PythonVersionWithSource::default(),
|
||||
python_platform: PythonPlatform::default(),
|
||||
search_paths: SearchPathSettings {
|
||||
site_packages_paths: vec![venv_site_packages, system_site_packages],
|
||||
..SearchPathSettings::new(vec![SystemPathBuf::from("/src")])
|
||||
}
|
||||
let settings = SearchPathSettings {
|
||||
site_packages_paths: vec![venv_site_packages, system_site_packages],
|
||||
..SearchPathSettings::new(vec![SystemPathBuf::from("/src")])
|
||||
};
|
||||
|
||||
db.set_search_paths(
|
||||
settings
|
||||
.to_search_paths(db.system(), db.vendored())
|
||||
.expect("Valid search path settings"),
|
||||
},
|
||||
);
|
||||
|
||||
// The editable installs discovered from the `.pth` file in the
|
||||
@@ -1585,7 +1566,7 @@ not_a_directory
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn case_sensitive_resolution_with_symlinked_directory() -> anyhow::Result<()> {
|
||||
use anyhow::Context;
|
||||
use anyhow::Context as _;
|
||||
|
||||
let temp_dir = tempfile::TempDir::with_prefix("PREFIX-SENTINEL")?;
|
||||
let root = SystemPathBuf::from_path_buf(
|
||||
@@ -1602,7 +1583,7 @@ not_a_directory
|
||||
let a_package_target = root.join("a-package");
|
||||
let a_src = src.join("a");
|
||||
|
||||
db.use_system(OsSystem::new(&root));
|
||||
db.use_system(ruff_db::system::OsSystem::new(&root));
|
||||
|
||||
db.write_file(
|
||||
a_package_target.join("__init__.py"),
|
||||
@@ -1621,16 +1602,11 @@ not_a_directory
|
||||
|
||||
db.files().try_add_root(&db, &root, FileRootKind::Project);
|
||||
|
||||
Program::from_settings(
|
||||
&db,
|
||||
ProgramSettings {
|
||||
python_version: PythonVersionWithSource::default(),
|
||||
python_platform: PythonPlatform::default(),
|
||||
search_paths: SearchPathSettings::new(vec![src])
|
||||
.to_search_paths(db.system(), db.vendored())
|
||||
.expect("valid search path settings"),
|
||||
},
|
||||
);
|
||||
let settings = SearchPathSettings::new(vec![src]);
|
||||
let search_paths = settings
|
||||
.to_search_paths(db.system(), db.vendored())
|
||||
.expect("valid search path settings");
|
||||
db.set_search_paths(search_paths);
|
||||
|
||||
insta::with_settings!({
|
||||
// Temporary directory often have random chars in them, so
|
||||
@@ -1662,18 +1638,14 @@ not_a_directory
|
||||
db.files()
|
||||
.try_add_root(&db, &project_directory, FileRootKind::Project);
|
||||
|
||||
Program::from_settings(
|
||||
&db,
|
||||
ProgramSettings {
|
||||
python_version: PythonVersionWithSource::default(),
|
||||
python_platform: PythonPlatform::default(),
|
||||
search_paths: SearchPathSettings {
|
||||
site_packages_paths: vec![site_packages],
|
||||
..SearchPathSettings::new(vec![project_directory])
|
||||
}
|
||||
let settings = SearchPathSettings {
|
||||
site_packages_paths: vec![site_packages],
|
||||
..SearchPathSettings::new(vec![project_directory])
|
||||
};
|
||||
db.set_search_paths(
|
||||
settings
|
||||
.to_search_paths(db.system(), db.vendored())
|
||||
.unwrap(),
|
||||
},
|
||||
);
|
||||
|
||||
insta::assert_debug_snapshot!(
|
||||
@@ -1820,16 +1792,11 @@ not_a_directory
|
||||
db.files()
|
||||
.try_add_root(&db, SystemPath::new("/"), FileRootKind::Project);
|
||||
|
||||
Program::from_settings(
|
||||
&db,
|
||||
ProgramSettings {
|
||||
python_version: PythonVersionWithSource::default(),
|
||||
python_platform: PythonPlatform::default(),
|
||||
search_paths: SearchPathSettings::new(vec![project_directory])
|
||||
.to_search_paths(db.system(), db.vendored())
|
||||
.unwrap(),
|
||||
},
|
||||
);
|
||||
let settings = SearchPathSettings::new(vec![project_directory]);
|
||||
let search_paths = settings
|
||||
.to_search_paths(db.system(), db.vendored())
|
||||
.expect("Valid search path settings");
|
||||
db.set_search_paths(search_paths);
|
||||
|
||||
insta::assert_debug_snapshot!(
|
||||
list_snapshot_filter(&db, |m| m.name(&db).as_str() == "foo"),
|
||||
@@ -7,10 +7,9 @@ use ruff_db::vendored::VendoredPath;
|
||||
use salsa::Database;
|
||||
use salsa::plumbing::AsId;
|
||||
|
||||
use super::path::SearchPath;
|
||||
use crate::Db;
|
||||
use crate::module_name::ModuleName;
|
||||
use crate::module_resolver::path::SystemOrVendoredPathRef;
|
||||
use crate::path::{SearchPath, SystemOrVendoredPathRef};
|
||||
|
||||
/// Representation of a Python module.
|
||||
#[derive(Clone, Copy, Eq, Hash, PartialEq, salsa::Supertype, salsa::Update)]
|
||||
@@ -330,16 +329,14 @@ pub enum KnownModule {
|
||||
TyExtensions,
|
||||
#[strum(serialize = "importlib")]
|
||||
ImportLib,
|
||||
#[cfg(test)]
|
||||
#[strum(serialize = "unittest.mock")]
|
||||
UnittestMock,
|
||||
#[cfg(test)]
|
||||
Uuid,
|
||||
Warnings,
|
||||
}
|
||||
|
||||
impl KnownModule {
|
||||
pub(crate) const fn as_str(self) -> &'static str {
|
||||
pub const fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::Builtins => "builtins",
|
||||
Self::Enum => "enum",
|
||||
@@ -360,23 +357,18 @@ impl KnownModule {
|
||||
Self::TyExtensions => "ty_extensions",
|
||||
Self::ImportLib => "importlib",
|
||||
Self::Warnings => "warnings",
|
||||
#[cfg(test)]
|
||||
Self::UnittestMock => "unittest.mock",
|
||||
#[cfg(test)]
|
||||
Self::Uuid => "uuid",
|
||||
Self::Templatelib => "string.templatelib",
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn name(self) -> ModuleName {
|
||||
pub fn name(self) -> ModuleName {
|
||||
ModuleName::new_static(self.as_str())
|
||||
.unwrap_or_else(|| panic!("{self} should be a valid module name!"))
|
||||
}
|
||||
|
||||
pub(crate) fn try_from_search_path_and_name(
|
||||
search_path: &SearchPath,
|
||||
name: &ModuleName,
|
||||
) -> Option<Self> {
|
||||
fn try_from_search_path_and_name(search_path: &SearchPath, name: &ModuleName) -> Option<Self> {
|
||||
if search_path.is_standard_library() {
|
||||
Self::from_str(name.as_str()).ok()
|
||||
} else {
|
||||
@@ -384,23 +376,23 @@ impl KnownModule {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) const fn is_builtins(self) -> bool {
|
||||
pub const fn is_builtins(self) -> bool {
|
||||
matches!(self, Self::Builtins)
|
||||
}
|
||||
|
||||
pub(crate) const fn is_typing(self) -> bool {
|
||||
pub const fn is_typing(self) -> bool {
|
||||
matches!(self, Self::Typing)
|
||||
}
|
||||
|
||||
pub(crate) const fn is_ty_extensions(self) -> bool {
|
||||
pub const fn is_ty_extensions(self) -> bool {
|
||||
matches!(self, Self::TyExtensions)
|
||||
}
|
||||
|
||||
pub(crate) const fn is_inspect(self) -> bool {
|
||||
pub const fn is_inspect(self) -> bool {
|
||||
matches!(self, Self::Inspect)
|
||||
}
|
||||
|
||||
pub(crate) const fn is_importlib(self) -> bool {
|
||||
pub const fn is_importlib(self) -> bool {
|
||||
matches!(self, Self::ImportLib)
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,8 @@ use ruff_db::files::File;
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_stdlib::identifiers::is_identifier;
|
||||
|
||||
use crate::{db::Db, module_resolver::file_to_module};
|
||||
use crate::db::Db;
|
||||
use crate::resolve::file_to_module;
|
||||
|
||||
/// A module name, e.g. `foo.bar`.
|
||||
///
|
||||
@@ -47,7 +48,7 @@ impl ModuleName {
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```
|
||||
/// use ty_python_semantic::ModuleName;
|
||||
/// use ty_module_resolver::ModuleName;
|
||||
///
|
||||
/// assert_eq!(ModuleName::new_static("foo.bar").as_deref(), Some("foo.bar"));
|
||||
/// assert_eq!(ModuleName::new_static(""), None);
|
||||
@@ -73,7 +74,7 @@ impl ModuleName {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use ty_python_semantic::ModuleName;
|
||||
/// use ty_module_resolver::ModuleName;
|
||||
///
|
||||
/// assert_eq!(ModuleName::new_static("foo.bar.baz").unwrap().components().collect::<Vec<_>>(), vec!["foo", "bar", "baz"]);
|
||||
/// ```
|
||||
@@ -87,7 +88,7 @@ impl ModuleName {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use ty_python_semantic::ModuleName;
|
||||
/// use ty_module_resolver::ModuleName;
|
||||
///
|
||||
/// assert_eq!(ModuleName::new_static("foo.bar").unwrap().parent(), Some(ModuleName::new_static("foo").unwrap()));
|
||||
/// assert_eq!(ModuleName::new_static("foo.bar.baz").unwrap().parent(), Some(ModuleName::new_static("foo.bar").unwrap()));
|
||||
@@ -106,7 +107,7 @@ impl ModuleName {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use ty_python_semantic::ModuleName;
|
||||
/// use ty_module_resolver::ModuleName;
|
||||
///
|
||||
/// assert!(ModuleName::new_static("foo.bar").unwrap().starts_with(&ModuleName::new_static("foo").unwrap()));
|
||||
///
|
||||
@@ -142,7 +143,7 @@ impl ModuleName {
|
||||
/// module name:
|
||||
///
|
||||
/// ```
|
||||
/// use ty_python_semantic::ModuleName;
|
||||
/// use ty_module_resolver::ModuleName;
|
||||
///
|
||||
/// let this = ModuleName::new_static("importlib.resources").unwrap();
|
||||
/// let parent = ModuleName::new_static("importlib").unwrap();
|
||||
@@ -156,7 +157,7 @@ impl ModuleName {
|
||||
/// This shows some cases where it isn't a parent:
|
||||
///
|
||||
/// ```
|
||||
/// use ty_python_semantic::ModuleName;
|
||||
/// use ty_module_resolver::ModuleName;
|
||||
///
|
||||
/// let this = ModuleName::new_static("importliblib.resources").unwrap();
|
||||
/// let parent = ModuleName::new_static("importlib").unwrap();
|
||||
@@ -199,7 +200,7 @@ impl ModuleName {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use ty_python_semantic::ModuleName;
|
||||
/// use ty_module_resolver::ModuleName;
|
||||
///
|
||||
/// assert_eq!(&*ModuleName::from_components(["a"]).unwrap(), "a");
|
||||
/// assert_eq!(&*ModuleName::from_components(["a", "b"]).unwrap(), "a.b");
|
||||
@@ -240,7 +241,7 @@ impl ModuleName {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use ty_python_semantic::ModuleName;
|
||||
/// use ty_module_resolver::ModuleName;
|
||||
///
|
||||
/// let mut module_name = ModuleName::new_static("foo").unwrap();
|
||||
/// module_name.extend(&ModuleName::new_static("bar").unwrap());
|
||||
@@ -258,7 +259,7 @@ impl ModuleName {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use ty_python_semantic::ModuleName;
|
||||
/// use ty_module_resolver::ModuleName;
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// ModuleName::new_static("foo.bar.baz").unwrap().ancestors().collect::<Vec<_>>(),
|
||||
@@ -314,7 +315,7 @@ impl ModuleName {
|
||||
/// Computes the absolute module name for the package this file belongs to.
|
||||
///
|
||||
/// i.e. this resolves `.`
|
||||
pub(crate) fn package_for_file(
|
||||
pub fn package_for_file(
|
||||
db: &dyn Db,
|
||||
importing_file: File,
|
||||
) -> Result<Self, ModuleNameResolutionError> {
|
||||
@@ -8,11 +8,10 @@ use ruff_db::files::{File, FileError, FilePath, system_path_to_file, vendored_pa
|
||||
use ruff_db::system::{System, SystemPath, SystemPathBuf};
|
||||
use ruff_db::vendored::{VendoredPath, VendoredPathBuf};
|
||||
|
||||
use super::typeshed::{TypeshedVersionsParseError, TypeshedVersionsQueryResult, typeshed_versions};
|
||||
use crate::Db;
|
||||
use crate::module_name::ModuleName;
|
||||
use crate::module_resolver::resolver::{PyTyped, ResolverContext};
|
||||
use crate::site_packages::SitePackagesDiscoveryError;
|
||||
use crate::resolve::{PyTyped, ResolverContext};
|
||||
use crate::typeshed::{TypeshedVersionsQueryResult, typeshed_versions};
|
||||
|
||||
/// A path that points to a Python module.
|
||||
///
|
||||
@@ -397,13 +396,8 @@ fn query_stdlib_version(
|
||||
typeshed_versions(*db).query_module(&module_name, *python_version)
|
||||
}
|
||||
|
||||
/// Enumeration describing the various ways in which validation of a search path might fail.
|
||||
///
|
||||
/// If validation fails for a search path derived from the user settings,
|
||||
/// a message must be displayed to the user,
|
||||
/// as type checking cannot be done reliably in these circumstances.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum SearchPathValidationError {
|
||||
pub enum SearchPathError {
|
||||
/// The path provided by the user was not a directory
|
||||
#[error("{0} does not point to a directory")]
|
||||
NotADirectory(SystemPathBuf),
|
||||
@@ -413,41 +407,9 @@ pub enum SearchPathValidationError {
|
||||
/// (This is only relevant for stdlib search paths.)
|
||||
#[error("The directory at {0} has no `stdlib/` subdirectory")]
|
||||
NoStdlibSubdirectory(SystemPathBuf),
|
||||
|
||||
/// The typeshed path provided by the user is a directory,
|
||||
/// but `stdlib/VERSIONS` could not be read.
|
||||
/// (This is only relevant for stdlib search paths.)
|
||||
#[error("Failed to read the custom typeshed versions file '{path}'")]
|
||||
FailedToReadVersionsFile {
|
||||
path: SystemPathBuf,
|
||||
#[source]
|
||||
error: std::io::Error,
|
||||
},
|
||||
|
||||
/// The path provided by the user is a directory,
|
||||
/// and a `stdlib/VERSIONS` file exists, but it fails to parse.
|
||||
/// (This is only relevant for stdlib search paths.)
|
||||
#[error(transparent)]
|
||||
VersionsParseError(TypeshedVersionsParseError),
|
||||
|
||||
/// Failed to discover the site-packages for the configured virtual environment.
|
||||
#[error("Failed to discover the site-packages directory")]
|
||||
SitePackagesDiscovery(#[source] SitePackagesDiscoveryError),
|
||||
}
|
||||
|
||||
impl From<TypeshedVersionsParseError> for SearchPathValidationError {
|
||||
fn from(value: TypeshedVersionsParseError) -> Self {
|
||||
Self::VersionsParseError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SitePackagesDiscoveryError> for SearchPathValidationError {
|
||||
fn from(value: SitePackagesDiscoveryError) -> Self {
|
||||
Self::SitePackagesDiscovery(value)
|
||||
}
|
||||
}
|
||||
|
||||
type SearchPathResult<T> = Result<T, SearchPathValidationError>;
|
||||
type SearchPathResult<T> = Result<T, SearchPathError>;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, get_size2::GetSize)]
|
||||
enum SearchPathInner {
|
||||
@@ -495,7 +457,7 @@ impl SearchPath {
|
||||
if system.is_directory(&root) {
|
||||
Ok(root)
|
||||
} else {
|
||||
Err(SearchPathValidationError::NotADirectory(root))
|
||||
Err(SearchPathError::NotADirectory(root))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -519,17 +481,15 @@ impl SearchPath {
|
||||
typeshed: &SystemPath,
|
||||
) -> SearchPathResult<Self> {
|
||||
if !system.is_directory(typeshed) {
|
||||
return Err(SearchPathValidationError::NotADirectory(
|
||||
typeshed.to_path_buf(),
|
||||
));
|
||||
return Err(SearchPathError::NotADirectory(typeshed.to_path_buf()));
|
||||
}
|
||||
|
||||
let stdlib =
|
||||
Self::directory_path(system, typeshed.join("stdlib")).map_err(|err| match err {
|
||||
SearchPathValidationError::NotADirectory(_) => {
|
||||
SearchPathValidationError::NoStdlibSubdirectory(typeshed.to_path_buf())
|
||||
SearchPathError::NotADirectory(_) => {
|
||||
SearchPathError::NoStdlibSubdirectory(typeshed.to_path_buf())
|
||||
}
|
||||
err => err,
|
||||
SearchPathError::NoStdlibSubdirectory(_) => err,
|
||||
})?;
|
||||
|
||||
Ok(Self(Arc::new(SearchPathInner::StandardLibraryCustom(
|
||||
@@ -585,7 +545,7 @@ impl SearchPath {
|
||||
|
||||
/// Does this search path point to the standard library?
|
||||
#[must_use]
|
||||
pub(crate) fn is_standard_library(&self) -> bool {
|
||||
pub fn is_standard_library(&self) -> bool {
|
||||
matches!(
|
||||
&*self.0,
|
||||
SearchPathInner::StandardLibraryCustom(_)
|
||||
@@ -680,7 +640,7 @@ impl SearchPath {
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub(crate) fn as_system_path(&self) -> Option<&SystemPath> {
|
||||
pub fn as_system_path(&self) -> Option<&SystemPath> {
|
||||
self.as_path().as_system_path()
|
||||
}
|
||||
|
||||
@@ -709,7 +669,7 @@ impl SearchPath {
|
||||
/// Returns a string suitable for describing what kind of search path this is
|
||||
/// in user-facing diagnostics.
|
||||
#[must_use]
|
||||
pub(crate) fn describe_kind(&self) -> &'static str {
|
||||
pub fn describe_kind(&self) -> &'static str {
|
||||
match *self.0 {
|
||||
SearchPathInner::Extra(_) => {
|
||||
"extra search path specified on the CLI or in your config file"
|
||||
@@ -869,8 +829,8 @@ mod tests {
|
||||
use ruff_python_ast::PythonVersion;
|
||||
|
||||
use crate::db::tests::TestDb;
|
||||
use crate::module_resolver::resolver::ModuleResolveMode;
|
||||
use crate::module_resolver::testing::{FileSpec, MockedTypeshed, TestCase, TestCaseBuilder};
|
||||
use crate::resolve::ModuleResolveMode;
|
||||
use crate::testing::{FileSpec, MockedTypeshed, TestCase, TestCaseBuilder};
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -48,13 +48,11 @@ use ruff_python_ast::{
|
||||
};
|
||||
|
||||
use crate::db::Db;
|
||||
use crate::module::{Module, ModuleKind};
|
||||
use crate::module_name::ModuleName;
|
||||
use crate::module_resolver::typeshed::{TypeshedVersions, vendored_typeshed_versions};
|
||||
use crate::program::MisconfigurationMode;
|
||||
use crate::{Program, SearchPathSettings};
|
||||
|
||||
use super::module::{Module, ModuleKind};
|
||||
use super::path::{ModulePath, SearchPath, SearchPathValidationError, SystemOrVendoredPathRef};
|
||||
use crate::path::{ModulePath, SearchPath, SystemOrVendoredPathRef};
|
||||
use crate::typeshed::{TypeshedVersions, vendored_typeshed_versions};
|
||||
use crate::{MisconfigurationMode, SearchPathSettings, SearchPathSettingsError};
|
||||
|
||||
/// Resolves a module name to a module.
|
||||
pub fn resolve_module<'db>(
|
||||
@@ -137,7 +135,7 @@ pub fn resolve_real_shadowable_module<'db>(
|
||||
/// Which files should be visible when doing a module query
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, get_size2::GetSize)]
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
pub(crate) enum ModuleResolveMode {
|
||||
pub enum ModuleResolveMode {
|
||||
/// Stubs are allowed to appear.
|
||||
///
|
||||
/// This is the "normal" mode almost everything uses, as type checkers are in fact supposed
|
||||
@@ -326,7 +324,7 @@ pub(crate) fn path_to_module<'db>(db: &'db dyn Db, path: &FilePath) -> Option<Mo
|
||||
/// This intuition is particularly useful for understanding why it's correct that we pass
|
||||
/// the file itself as `importing_file` to various subroutines.
|
||||
#[salsa::tracked(heap_size=ruff_memory_usage::heap_size)]
|
||||
pub(crate) fn file_to_module(db: &dyn Db, file: File) -> Option<Module<'_>> {
|
||||
pub fn file_to_module(db: &dyn Db, file: File) -> Option<Module<'_>> {
|
||||
let _span = tracing::trace_span!("file_to_module", ?file).entered();
|
||||
|
||||
let path = SystemOrVendoredPathRef::try_from_file(db, file)?;
|
||||
@@ -392,8 +390,8 @@ fn file_to_module_impl<'db, 'a>(
|
||||
None
|
||||
}
|
||||
|
||||
pub(crate) fn search_paths(db: &dyn Db, resolve_mode: ModuleResolveMode) -> SearchPathIterator<'_> {
|
||||
Program::get(db).search_paths(db).iter(db, resolve_mode)
|
||||
pub fn search_paths(db: &dyn Db, resolve_mode: ModuleResolveMode) -> SearchPathIterator<'_> {
|
||||
db.search_paths().iter(db, resolve_mode)
|
||||
}
|
||||
|
||||
/// Get the search-paths for desperate resolution of absolute imports in this file.
|
||||
@@ -554,11 +552,11 @@ impl SearchPaths {
|
||||
/// This method also implements the typing spec's [module resolution order].
|
||||
///
|
||||
/// [module resolution order]: https://typing.python.org/en/latest/spec/distributing.html#import-resolution-ordering
|
||||
pub(crate) fn from_settings(
|
||||
pub fn from_settings(
|
||||
settings: &SearchPathSettings,
|
||||
system: &dyn System,
|
||||
vendored: &VendoredFileSystem,
|
||||
) -> Result<Self, SearchPathValidationError> {
|
||||
) -> Result<Self, SearchPathSettingsError> {
|
||||
fn canonicalize(path: &SystemPath, system: &dyn System) -> SystemPathBuf {
|
||||
system
|
||||
.canonicalize_path(path)
|
||||
@@ -586,7 +584,7 @@ impl SearchPaths {
|
||||
if *misconfiguration_mode == MisconfigurationMode::UseDefault {
|
||||
tracing::debug!("Skipping invalid extra search-path: {err}");
|
||||
} else {
|
||||
return Err(err);
|
||||
return Err(err.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -600,7 +598,7 @@ impl SearchPaths {
|
||||
if *misconfiguration_mode == MisconfigurationMode::UseDefault {
|
||||
tracing::debug!("Skipping invalid first-party search-path: {err}");
|
||||
} else {
|
||||
return Err(err);
|
||||
return Err(err.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -614,12 +612,10 @@ impl SearchPaths {
|
||||
|
||||
let results = system
|
||||
.read_to_string(&versions_path)
|
||||
.map_err(
|
||||
|error| SearchPathValidationError::FailedToReadVersionsFile {
|
||||
path: versions_path,
|
||||
error,
|
||||
},
|
||||
)
|
||||
.map_err(|error| SearchPathSettingsError::FailedToReadVersionsFile {
|
||||
path: versions_path,
|
||||
error,
|
||||
})
|
||||
.and_then(|versions_content| Ok(versions_content.parse()?))
|
||||
.and_then(|parsed| Ok((parsed, SearchPath::custom_stdlib(system, &typeshed)?)));
|
||||
|
||||
@@ -653,7 +649,7 @@ impl SearchPaths {
|
||||
tracing::debug!("Skipping invalid real-stdlib search-path: {err}");
|
||||
None
|
||||
} else {
|
||||
return Err(err);
|
||||
return Err(err.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -669,9 +665,9 @@ impl SearchPaths {
|
||||
Ok(path) => site_packages.push(path),
|
||||
Err(err) => {
|
||||
if settings.misconfiguration_mode == MisconfigurationMode::UseDefault {
|
||||
tracing::debug!("Skipping invalid real-stdlib search-path: {err}");
|
||||
tracing::debug!("Skipping invalid site-packages search-path: {err}");
|
||||
} else {
|
||||
return Err(err);
|
||||
return Err(err.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -709,13 +705,11 @@ impl SearchPaths {
|
||||
// preserve this behaviour to avoid getting into the weeds of corner cases.)
|
||||
let stdlib_path_is_shadowed = stdlib_path
|
||||
.as_system_path()
|
||||
.map(|path| seen_paths.contains(path))
|
||||
.unwrap_or(false);
|
||||
.is_some_and(|path| seen_paths.contains(path));
|
||||
let real_stdlib_path_is_shadowed = real_stdlib_path
|
||||
.as_ref()
|
||||
.and_then(SearchPath::as_system_path)
|
||||
.map(|path| seen_paths.contains(path))
|
||||
.unwrap_or(false);
|
||||
.is_some_and(|path| seen_paths.contains(path));
|
||||
|
||||
let stdlib_path = if stdlib_path_is_shadowed {
|
||||
None
|
||||
@@ -737,8 +731,21 @@ impl SearchPaths {
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns a new `SearchPaths` with no search paths configured.
|
||||
///
|
||||
/// This is primarily useful for testing.
|
||||
pub fn empty(vendored: &VendoredFileSystem) -> Self {
|
||||
Self {
|
||||
static_paths: vec![],
|
||||
stdlib_path: Some(SearchPath::vendored_stdlib()),
|
||||
real_stdlib_path: None,
|
||||
site_packages: vec![],
|
||||
typeshed_versions: vendored_typeshed_versions(vendored),
|
||||
}
|
||||
}
|
||||
|
||||
/// Registers the file roots for all non-dynamically discovered search paths that aren't first-party.
|
||||
pub(crate) fn try_register_static_roots(&self, db: &dyn Db) {
|
||||
pub fn try_register_static_roots(&self, db: &dyn Db) {
|
||||
let files = db.files();
|
||||
for path in self
|
||||
.static_paths
|
||||
@@ -779,13 +786,13 @@ impl SearchPaths {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn custom_stdlib(&self) -> Option<&SystemPath> {
|
||||
pub fn custom_stdlib(&self) -> Option<&SystemPath> {
|
||||
self.stdlib_path
|
||||
.as_ref()
|
||||
.and_then(SearchPath::as_system_path)
|
||||
}
|
||||
|
||||
pub(crate) fn typeshed_versions(&self) -> &TypeshedVersions {
|
||||
pub fn typeshed_versions(&self) -> &TypeshedVersions {
|
||||
&self.typeshed_versions
|
||||
}
|
||||
}
|
||||
@@ -811,7 +818,7 @@ pub(crate) fn dynamic_resolution_paths<'db>(
|
||||
site_packages,
|
||||
typeshed_versions: _,
|
||||
real_stdlib_path,
|
||||
} = Program::get(db).search_paths(db);
|
||||
} = db.search_paths();
|
||||
|
||||
let mut dynamic_paths = Vec::new();
|
||||
|
||||
@@ -932,7 +939,7 @@ pub(crate) fn dynamic_resolution_paths<'db>(
|
||||
/// are only calculated lazily.
|
||||
///
|
||||
/// [`sys.path` at runtime]: https://docs.python.org/3/library/site.html#module-site
|
||||
pub(crate) struct SearchPathIterator<'db> {
|
||||
pub struct SearchPathIterator<'db> {
|
||||
db: &'db dyn Db,
|
||||
static_paths: std::slice::Iter<'db, SearchPath>,
|
||||
stdlib_path: Option<&'db SearchPath>,
|
||||
@@ -1101,8 +1108,7 @@ fn resolve_name_impl<'a>(
|
||||
mode: ModuleResolveMode,
|
||||
search_paths: impl Iterator<Item = &'a SearchPath>,
|
||||
) -> Option<ResolvedName> {
|
||||
let program = Program::get(db);
|
||||
let python_version = program.python_version(db);
|
||||
let python_version = db.python_version();
|
||||
let resolver_state = ResolverContext::new(db, python_version, mode);
|
||||
let is_non_shadowable = mode.is_non_shadowable(python_version.minor, name.as_str());
|
||||
|
||||
@@ -1740,10 +1746,9 @@ mod tests {
|
||||
use ruff_python_ast::PythonVersion;
|
||||
|
||||
use crate::db::tests::TestDb;
|
||||
use crate::module::ModuleKind;
|
||||
use crate::module_name::ModuleName;
|
||||
use crate::module_resolver::module::ModuleKind;
|
||||
use crate::module_resolver::testing::{FileSpec, MockedTypeshed, TestCase, TestCaseBuilder};
|
||||
use crate::{ProgramSettings, PythonPlatform, PythonVersionWithSource};
|
||||
use crate::testing::{FileSpec, MockedTypeshed, TestCase, TestCaseBuilder};
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -2267,15 +2272,11 @@ mod tests {
|
||||
#[cfg(target_family = "unix")]
|
||||
fn symlink() -> anyhow::Result<()> {
|
||||
use anyhow::Context;
|
||||
|
||||
use crate::{
|
||||
PythonPlatform, PythonVersionSource, PythonVersionWithSource, program::Program,
|
||||
};
|
||||
use ruff_db::system::{OsSystem, SystemPath};
|
||||
|
||||
use crate::db::tests::TestDb;
|
||||
|
||||
let mut db = TestDb::new();
|
||||
let mut db = TestDb::new().with_python_version(PythonVersion::PY38);
|
||||
|
||||
let temp_dir = tempfile::tempdir()?;
|
||||
let root = temp_dir
|
||||
@@ -2300,22 +2301,15 @@ mod tests {
|
||||
std::fs::write(foo.as_std_path(), "")?;
|
||||
std::os::unix::fs::symlink(foo.as_std_path(), bar.as_std_path())?;
|
||||
|
||||
Program::from_settings(
|
||||
&db,
|
||||
ProgramSettings {
|
||||
python_version: PythonVersionWithSource {
|
||||
version: PythonVersion::PY38,
|
||||
source: PythonVersionSource::default(),
|
||||
},
|
||||
python_platform: PythonPlatform::default(),
|
||||
search_paths: SearchPathSettings {
|
||||
custom_typeshed: Some(custom_typeshed),
|
||||
site_packages_paths: vec![site_packages],
|
||||
..SearchPathSettings::new(vec![src.clone()])
|
||||
}
|
||||
.to_search_paths(db.system(), db.vendored())
|
||||
.expect("Valid search path settings"),
|
||||
},
|
||||
db.set_search_paths(
|
||||
SearchPathSettings {
|
||||
src_roots: vec![src.clone()],
|
||||
custom_typeshed: Some(custom_typeshed),
|
||||
site_packages_paths: vec![site_packages],
|
||||
..SearchPathSettings::empty()
|
||||
}
|
||||
.to_search_paths(db.system(), db.vendored())
|
||||
.expect("Valid search path settings"),
|
||||
);
|
||||
|
||||
let foo_module =
|
||||
@@ -2848,18 +2842,13 @@ not_a_directory
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
Program::from_settings(
|
||||
&db,
|
||||
ProgramSettings {
|
||||
python_version: PythonVersionWithSource::default(),
|
||||
python_platform: PythonPlatform::default(),
|
||||
search_paths: SearchPathSettings {
|
||||
site_packages_paths: vec![venv_site_packages, system_site_packages],
|
||||
..SearchPathSettings::new(vec![SystemPathBuf::from("/src")])
|
||||
}
|
||||
.to_search_paths(db.system(), db.vendored())
|
||||
.expect("Valid search path settings"),
|
||||
},
|
||||
db.set_search_paths(
|
||||
SearchPathSettings {
|
||||
site_packages_paths: vec![venv_site_packages, system_site_packages],
|
||||
..SearchPathSettings::new(vec![SystemPathBuf::from("/src")])
|
||||
}
|
||||
.to_search_paths(db.system(), db.vendored())
|
||||
.expect("Valid search path settings"),
|
||||
);
|
||||
|
||||
// The editable installs discovered from the `.pth` file in the first `site-packages` directory
|
||||
@@ -2924,15 +2913,10 @@ not_a_directory
|
||||
std::os::unix::fs::symlink(a_package_target.as_std_path(), a_src.as_std_path())
|
||||
.context("Failed to symlink `src/a` to `a-package`")?;
|
||||
|
||||
Program::from_settings(
|
||||
&db,
|
||||
ProgramSettings {
|
||||
python_version: PythonVersionWithSource::default(),
|
||||
python_platform: PythonPlatform::default(),
|
||||
search_paths: SearchPathSettings::new(vec![src])
|
||||
.to_search_paths(db.system(), db.vendored())
|
||||
.expect("valid search path settings"),
|
||||
},
|
||||
db.set_search_paths(
|
||||
SearchPathSettings::new(vec![src])
|
||||
.to_search_paths(db.system(), db.vendored())
|
||||
.expect("Valid search path settings"),
|
||||
);
|
||||
|
||||
// Now try to resolve the module `A` (note the capital `A` instead of `a`).
|
||||
@@ -2964,19 +2948,14 @@ not_a_directory
|
||||
let mut db = TestDb::new();
|
||||
db.write_file(&installed_foo_module, "").unwrap();
|
||||
|
||||
Program::from_settings(
|
||||
&db,
|
||||
ProgramSettings {
|
||||
python_version: PythonVersionWithSource::default(),
|
||||
python_platform: PythonPlatform::default(),
|
||||
search_paths: SearchPathSettings {
|
||||
site_packages_paths: vec![site_packages.clone()],
|
||||
..SearchPathSettings::new(vec![project_directory])
|
||||
}
|
||||
.to_search_paths(db.system(), db.vendored())
|
||||
.unwrap(),
|
||||
},
|
||||
);
|
||||
let search_paths = SearchPathSettings {
|
||||
src_roots: vec![project_directory],
|
||||
site_packages_paths: vec![site_packages.clone()],
|
||||
..SearchPathSettings::empty()
|
||||
}
|
||||
.to_search_paths(db.system(), db.vendored())
|
||||
.expect("Valid search path settings");
|
||||
db.set_search_paths(search_paths);
|
||||
|
||||
let foo_module_file = File::new(&db, FilePath::System(installed_foo_module));
|
||||
let module = file_to_module(&db, foo_module_file).unwrap();
|
||||
105
crates/ty_module_resolver/src/settings.rs
Normal file
105
crates/ty_module_resolver/src/settings.rs
Normal file
@@ -0,0 +1,105 @@
|
||||
//! Search path configuration settings.
|
||||
|
||||
use ruff_db::system::{System, SystemPathBuf};
|
||||
use ruff_db::vendored::VendoredFileSystem;
|
||||
|
||||
use crate::path::SearchPathError;
|
||||
use crate::resolve::SearchPaths;
|
||||
use crate::typeshed::TypeshedVersionsParseError;
|
||||
|
||||
/// How to handle apparent misconfiguration
|
||||
#[derive(PartialEq, Eq, Debug, Copy, Clone, Default, get_size2::GetSize)]
|
||||
pub enum MisconfigurationMode {
|
||||
/// Settings Failure Is Not An Error.
|
||||
///
|
||||
/// This is used by the default database, which we are incentivized to make infallible,
|
||||
/// while still trying to "do our best" to set things up properly where we can.
|
||||
UseDefault,
|
||||
/// Settings Failure Is An Error.
|
||||
#[default]
|
||||
Fail,
|
||||
}
|
||||
|
||||
/// Configures the search paths for module resolution.
|
||||
#[derive(Eq, PartialEq, Debug, Clone)]
|
||||
pub struct SearchPathSettings {
|
||||
/// List of user-provided paths that should take first priority in the module resolution.
|
||||
/// Examples in other type checkers are mypy's MYPYPATH environment variable,
|
||||
/// or pyright's stubPath configuration setting.
|
||||
pub extra_paths: Vec<SystemPathBuf>,
|
||||
|
||||
/// The root of the project, used for finding first-party modules.
|
||||
pub src_roots: Vec<SystemPathBuf>,
|
||||
|
||||
/// Optional path to a "custom typeshed" directory on disk for us to use for standard-library types.
|
||||
/// If this is not provided, we will fallback to our vendored typeshed stubs for the stdlib,
|
||||
/// bundled as a zip file in the binary
|
||||
pub custom_typeshed: Option<SystemPathBuf>,
|
||||
|
||||
/// List of site packages paths to use.
|
||||
pub site_packages_paths: Vec<SystemPathBuf>,
|
||||
|
||||
/// Option path to the real stdlib on the system, and not some instance of typeshed.
|
||||
///
|
||||
/// We should ideally only ever use this for things like goto-definition,
|
||||
/// where typeshed isn't the right answer.
|
||||
pub real_stdlib_path: Option<SystemPathBuf>,
|
||||
|
||||
/// How to handle apparent misconfiguration
|
||||
pub misconfiguration_mode: MisconfigurationMode,
|
||||
}
|
||||
|
||||
impl SearchPathSettings {
|
||||
pub fn new(src_roots: Vec<SystemPathBuf>) -> Self {
|
||||
Self {
|
||||
src_roots,
|
||||
..SearchPathSettings::empty()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn empty() -> Self {
|
||||
SearchPathSettings {
|
||||
src_roots: vec![],
|
||||
extra_paths: vec![],
|
||||
custom_typeshed: None,
|
||||
site_packages_paths: vec![],
|
||||
real_stdlib_path: None,
|
||||
misconfiguration_mode: MisconfigurationMode::Fail,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_search_paths(
|
||||
&self,
|
||||
system: &dyn System,
|
||||
vendored: &VendoredFileSystem,
|
||||
) -> Result<SearchPaths, SearchPathSettingsError> {
|
||||
SearchPaths::from_settings(self, system, vendored)
|
||||
}
|
||||
}
|
||||
|
||||
/// Enumeration describing the various ways in which validation of the search paths options might fail.
|
||||
///
|
||||
/// If validation fails for a search path derived from the user settings,
|
||||
/// a message must be displayed to the user,
|
||||
/// as type checking cannot be done reliably in these circumstances.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum SearchPathSettingsError {
|
||||
#[error(transparent)]
|
||||
InvalidSearchPath(#[from] SearchPathError),
|
||||
|
||||
/// The typeshed path provided by the user is a directory,
|
||||
/// but `stdlib/VERSIONS` could not be read.
|
||||
/// (This is only relevant for stdlib search paths.)
|
||||
#[error("Failed to read the custom typeshed versions file '{path}'")]
|
||||
FailedToReadVersionsFile {
|
||||
path: SystemPathBuf,
|
||||
#[source]
|
||||
error: std::io::Error,
|
||||
},
|
||||
|
||||
/// The path provided by the user is a directory,
|
||||
/// and a `stdlib/VERSIONS` file exists, but it fails to parse.
|
||||
/// (This is only relevant for stdlib search paths.)
|
||||
#[error(transparent)]
|
||||
VersionsParseError(#[from] TypeshedVersionsParseError),
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
use ruff_db::Db;
|
||||
use ruff_db::Db as _;
|
||||
use ruff_db::files::FileRootKind;
|
||||
use ruff_db::system::{
|
||||
DbWithTestSystem as _, DbWithWritableSystem as _, SystemPath, SystemPathBuf,
|
||||
@@ -7,8 +7,7 @@ use ruff_db::vendored::VendoredPathBuf;
|
||||
use ruff_python_ast::PythonVersion;
|
||||
|
||||
use crate::db::tests::TestDb;
|
||||
use crate::program::{Program, SearchPathSettings};
|
||||
use crate::{ProgramSettings, PythonPlatform, PythonVersionSource, PythonVersionWithSource};
|
||||
use crate::settings::SearchPathSettings;
|
||||
|
||||
/// A test case for the module resolver.
|
||||
///
|
||||
@@ -20,7 +19,7 @@ pub(crate) struct TestCase<T> {
|
||||
pub(crate) stdlib: T,
|
||||
// Most test cases only ever need a single `site-packages` directory,
|
||||
// so this is a single directory instead of a `Vec` of directories,
|
||||
// like it is in `ruff_db::Program`.
|
||||
// like it is in `SearchPaths`.
|
||||
pub(crate) site_packages: SystemPathBuf,
|
||||
pub(crate) python_version: PythonVersion,
|
||||
}
|
||||
@@ -105,7 +104,6 @@ pub(crate) struct UnspecifiedTypeshed;
|
||||
pub(crate) struct TestCaseBuilder<T> {
|
||||
typeshed_option: T,
|
||||
python_version: PythonVersion,
|
||||
python_platform: PythonPlatform,
|
||||
first_party_files: Vec<FileSpec>,
|
||||
site_packages_files: Vec<FileSpec>,
|
||||
// Additional file roots (beyond site_packages, src and stdlib)
|
||||
@@ -166,7 +164,6 @@ impl TestCaseBuilder<UnspecifiedTypeshed> {
|
||||
Self {
|
||||
typeshed_option: UnspecifiedTypeshed,
|
||||
python_version: PythonVersion::default(),
|
||||
python_platform: PythonPlatform::default(),
|
||||
first_party_files: vec![],
|
||||
site_packages_files: vec![],
|
||||
roots: vec![],
|
||||
@@ -178,7 +175,6 @@ impl TestCaseBuilder<UnspecifiedTypeshed> {
|
||||
let TestCaseBuilder {
|
||||
typeshed_option: _,
|
||||
python_version,
|
||||
python_platform,
|
||||
first_party_files,
|
||||
site_packages_files,
|
||||
roots,
|
||||
@@ -186,7 +182,6 @@ impl TestCaseBuilder<UnspecifiedTypeshed> {
|
||||
TestCaseBuilder {
|
||||
typeshed_option: VendoredTypeshed,
|
||||
python_version,
|
||||
python_platform,
|
||||
first_party_files,
|
||||
site_packages_files,
|
||||
roots,
|
||||
@@ -201,7 +196,6 @@ impl TestCaseBuilder<UnspecifiedTypeshed> {
|
||||
let TestCaseBuilder {
|
||||
typeshed_option: _,
|
||||
python_version,
|
||||
python_platform,
|
||||
first_party_files,
|
||||
site_packages_files,
|
||||
roots,
|
||||
@@ -210,7 +204,6 @@ impl TestCaseBuilder<UnspecifiedTypeshed> {
|
||||
TestCaseBuilder {
|
||||
typeshed_option: typeshed,
|
||||
python_version,
|
||||
python_platform,
|
||||
first_party_files,
|
||||
site_packages_files,
|
||||
roots,
|
||||
@@ -241,18 +234,29 @@ impl TestCaseBuilder<MockedTypeshed> {
|
||||
let TestCaseBuilder {
|
||||
typeshed_option,
|
||||
python_version,
|
||||
python_platform,
|
||||
first_party_files,
|
||||
site_packages_files,
|
||||
roots,
|
||||
} = self;
|
||||
|
||||
let mut db = TestDb::new();
|
||||
let mut db = TestDb::new().with_python_version(python_version);
|
||||
|
||||
let site_packages =
|
||||
Self::write_mock_directory(&mut db, "/site-packages", site_packages_files);
|
||||
let src = Self::write_mock_directory(&mut db, "/src", first_party_files);
|
||||
let typeshed = Self::build_typeshed_mock(&mut db, &typeshed_option);
|
||||
let stdlib = typeshed.join("stdlib");
|
||||
|
||||
let search_paths = SearchPathSettings {
|
||||
src_roots: vec![src.clone()],
|
||||
custom_typeshed: Some(typeshed),
|
||||
site_packages_paths: vec![site_packages.clone()],
|
||||
..SearchPathSettings::empty()
|
||||
}
|
||||
.to_search_paths(db.system(), db.vendored())
|
||||
.expect("Valid search path settings");
|
||||
|
||||
db = db.with_search_paths(search_paths);
|
||||
|
||||
// This root is needed for correct Salsa tracking.
|
||||
// Namely, a `SearchPath` is treated as an input, and
|
||||
@@ -262,36 +266,19 @@ impl TestCaseBuilder<MockedTypeshed> {
|
||||
// here, they won't get added.
|
||||
//
|
||||
// Roots for other search paths are added as part of
|
||||
// search path initialization in `Program::from_settings`,
|
||||
// search path initialization in `SearchPaths::from_settings`,
|
||||
// and any remaining are added below.
|
||||
db.files()
|
||||
.try_add_root(&db, SystemPath::new("/src"), FileRootKind::Project);
|
||||
|
||||
Program::from_settings(
|
||||
&db,
|
||||
ProgramSettings {
|
||||
python_version: PythonVersionWithSource {
|
||||
version: python_version,
|
||||
source: PythonVersionSource::default(),
|
||||
},
|
||||
python_platform,
|
||||
search_paths: SearchPathSettings {
|
||||
custom_typeshed: Some(typeshed.clone()),
|
||||
site_packages_paths: vec![site_packages.clone()],
|
||||
..SearchPathSettings::new(vec![src.clone()])
|
||||
}
|
||||
.to_search_paths(db.system(), db.vendored())
|
||||
.expect("valid search path settings"),
|
||||
},
|
||||
);
|
||||
|
||||
let stdlib = typeshed.join("stdlib");
|
||||
db.files()
|
||||
.try_add_root(&db, &stdlib, FileRootKind::LibrarySearchPath);
|
||||
|
||||
for root in &roots {
|
||||
db.files()
|
||||
.try_add_root(&db, root, FileRootKind::LibrarySearchPath);
|
||||
}
|
||||
|
||||
TestCase {
|
||||
db,
|
||||
src,
|
||||
@@ -324,42 +311,34 @@ impl TestCaseBuilder<VendoredTypeshed> {
|
||||
let TestCaseBuilder {
|
||||
typeshed_option: VendoredTypeshed,
|
||||
python_version,
|
||||
python_platform,
|
||||
first_party_files,
|
||||
site_packages_files,
|
||||
roots,
|
||||
} = self;
|
||||
|
||||
let mut db = TestDb::new();
|
||||
let mut db = TestDb::new().with_python_version(python_version);
|
||||
|
||||
let site_packages =
|
||||
Self::write_mock_directory(&mut db, "/site-packages", site_packages_files);
|
||||
let src = Self::write_mock_directory(&mut db, "/src", first_party_files);
|
||||
|
||||
let search_paths = SearchPathSettings {
|
||||
src_roots: vec![src.clone()],
|
||||
site_packages_paths: vec![site_packages.clone()],
|
||||
..SearchPathSettings::empty()
|
||||
}
|
||||
.to_search_paths(db.system(), db.vendored())
|
||||
.expect("Valid search path settings");
|
||||
|
||||
db = db.with_search_paths(search_paths);
|
||||
|
||||
db.files()
|
||||
.try_add_root(&db, SystemPath::new("/src"), FileRootKind::Project);
|
||||
|
||||
Program::from_settings(
|
||||
&db,
|
||||
ProgramSettings {
|
||||
python_version: PythonVersionWithSource {
|
||||
version: python_version,
|
||||
source: PythonVersionSource::default(),
|
||||
},
|
||||
python_platform,
|
||||
search_paths: SearchPathSettings {
|
||||
site_packages_paths: vec![site_packages.clone()],
|
||||
..SearchPathSettings::new(vec![src.clone()])
|
||||
}
|
||||
.to_search_paths(db.system(), db.vendored())
|
||||
.expect("valid search path settings"),
|
||||
},
|
||||
);
|
||||
|
||||
for root in &roots {
|
||||
db.files()
|
||||
.try_add_root(&db, root, FileRootKind::LibrarySearchPath);
|
||||
}
|
||||
|
||||
TestCase {
|
||||
db,
|
||||
src,
|
||||
@@ -8,13 +8,10 @@ use ruff_db::vendored::VendoredFileSystem;
|
||||
use ruff_python_ast::{PythonVersion, PythonVersionDeserializationError};
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use crate::Program;
|
||||
use crate::db::Db;
|
||||
use crate::module_name::ModuleName;
|
||||
|
||||
pub(in crate::module_resolver) fn vendored_typeshed_versions(
|
||||
vendored: &VendoredFileSystem,
|
||||
) -> TypeshedVersions {
|
||||
pub fn vendored_typeshed_versions(vendored: &VendoredFileSystem) -> TypeshedVersions {
|
||||
TypeshedVersions::from_str(
|
||||
&vendored
|
||||
.read_to_string("stdlib/VERSIONS")
|
||||
@@ -24,7 +21,7 @@ pub(in crate::module_resolver) fn vendored_typeshed_versions(
|
||||
}
|
||||
|
||||
pub(crate) fn typeshed_versions(db: &dyn Db) -> &TypeshedVersions {
|
||||
Program::get(db).search_paths(db).typeshed_versions()
|
||||
db.search_paths().typeshed_versions()
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
@@ -61,7 +58,7 @@ impl std::error::Error for TypeshedVersionsParseError {
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, thiserror::Error)]
|
||||
pub(crate) enum TypeshedVersionsParseErrorKind {
|
||||
pub enum TypeshedVersionsParseErrorKind {
|
||||
#[error("File has too many lines ({0}); maximum allowed is {max_allowed}", max_allowed = NonZeroU16::MAX)]
|
||||
TooManyLines(NonZeroUsize),
|
||||
#[error("Expected every non-comment line to have exactly one colon")]
|
||||
@@ -75,16 +72,16 @@ pub(crate) enum TypeshedVersionsParseErrorKind {
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, get_size2::GetSize)]
|
||||
pub(crate) struct TypeshedVersions(FxHashMap<ModuleName, PyVersionRange>);
|
||||
pub struct TypeshedVersions(FxHashMap<ModuleName, PyVersionRange>);
|
||||
|
||||
impl TypeshedVersions {
|
||||
#[must_use]
|
||||
pub(crate) fn exact(&self, module_name: &ModuleName) -> Option<&PyVersionRange> {
|
||||
pub fn exact(&self, module_name: &ModuleName) -> Option<&PyVersionRange> {
|
||||
self.0.get(module_name)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub(in crate::module_resolver) fn query_module(
|
||||
pub(crate) fn query_module(
|
||||
&self,
|
||||
module: &ModuleName,
|
||||
python_version: PythonVersion,
|
||||
@@ -231,14 +228,14 @@ impl fmt::Display for TypeshedVersions {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash, get_size2::GetSize)]
|
||||
pub(crate) enum PyVersionRange {
|
||||
pub enum PyVersionRange {
|
||||
AvailableFrom(RangeFrom<PythonVersion>),
|
||||
AvailableWithin(RangeInclusive<PythonVersion>),
|
||||
}
|
||||
|
||||
impl PyVersionRange {
|
||||
#[must_use]
|
||||
pub(crate) fn contains(&self, version: PythonVersion) -> bool {
|
||||
pub fn contains(&self, version: PythonVersion) -> bool {
|
||||
match self {
|
||||
Self::AvailableFrom(inner) => inner.contains(&version),
|
||||
Self::AvailableWithin(inner) => inner.contains(&version),
|
||||
@@ -246,7 +243,7 @@ impl PyVersionRange {
|
||||
}
|
||||
|
||||
/// Display the version range in a way that is suitable for rendering in user-facing diagnostics.
|
||||
pub(crate) fn diagnostic_display(&self) -> impl std::fmt::Display {
|
||||
pub fn diagnostic_display(&self) -> impl std::fmt::Display {
|
||||
struct DiagnosticDisplay<'a>(&'a PyVersionRange);
|
||||
|
||||
impl fmt::Display for DiagnosticDisplay<'_> {
|
||||
@@ -21,7 +21,9 @@ ruff_python_ast = { workspace = true, features = ["serde"] }
|
||||
ruff_python_formatter = { workspace = true, optional = true }
|
||||
ruff_text_size = { workspace = true }
|
||||
ty_combine = { workspace = true }
|
||||
ty_module_resolver = { workspace = true }
|
||||
ty_python_semantic = { workspace = true, features = ["serde"] }
|
||||
ty_python_types = { workspace = true }
|
||||
ty_static = { workspace = true }
|
||||
ty_vendored = { workspace = true }
|
||||
|
||||
|
||||
@@ -14,8 +14,9 @@ use ruff_db::files::{File, Files};
|
||||
use ruff_db::system::System;
|
||||
use ruff_db::vendored::VendoredFileSystem;
|
||||
use salsa::{Database, Event, Setter};
|
||||
use ty_module_resolver::SearchPaths;
|
||||
use ty_python_semantic::lint::{LintRegistry, RuleSelection};
|
||||
use ty_python_semantic::{Db as SemanticDb, Program};
|
||||
use ty_python_semantic::{AnalysisSettings, Db as SemanticDb, Program};
|
||||
|
||||
mod changes;
|
||||
|
||||
@@ -446,6 +447,13 @@ impl SalsaMemoryDump {
|
||||
}
|
||||
}
|
||||
|
||||
#[salsa::db]
|
||||
impl ty_module_resolver::Db for ProjectDatabase {
|
||||
fn search_paths(&self) -> &SearchPaths {
|
||||
Program::get(self).search_paths(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[salsa::db]
|
||||
impl SemanticDb for ProjectDatabase {
|
||||
fn should_check_file(&self, file: File) -> bool {
|
||||
@@ -459,7 +467,11 @@ impl SemanticDb for ProjectDatabase {
|
||||
}
|
||||
|
||||
fn lint_registry(&self) -> &LintRegistry {
|
||||
ty_python_semantic::default_lint_registry()
|
||||
ty_python_types::default_lint_registry()
|
||||
}
|
||||
|
||||
fn analysis_settings(&self) -> &AnalysisSettings {
|
||||
self.project().settings(self).analysis()
|
||||
}
|
||||
|
||||
fn verbose(&self) -> bool {
|
||||
@@ -523,9 +535,10 @@ pub(crate) mod tests {
|
||||
use ruff_db::files::{FileRootKind, Files};
|
||||
use ruff_db::system::{DbWithTestSystem, System, TestSystem};
|
||||
use ruff_db::vendored::VendoredFileSystem;
|
||||
use ty_module_resolver::SearchPathSettings;
|
||||
use ty_python_semantic::lint::{LintRegistry, RuleSelection};
|
||||
use ty_python_semantic::{
|
||||
Program, ProgramSettings, PythonPlatform, PythonVersionWithSource, SearchPathSettings,
|
||||
AnalysisSettings, Program, ProgramSettings, PythonPlatform, PythonVersionWithSource,
|
||||
};
|
||||
|
||||
use crate::db::Db;
|
||||
@@ -627,6 +640,13 @@ pub(crate) mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[salsa::db]
|
||||
impl ty_module_resolver::Db for TestDb {
|
||||
fn search_paths(&self) -> &ty_module_resolver::SearchPaths {
|
||||
Program::get(self).search_paths(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[salsa::db]
|
||||
impl ty_python_semantic::Db for TestDb {
|
||||
fn should_check_file(&self, file: ruff_db::files::File) -> bool {
|
||||
@@ -638,7 +658,11 @@ pub(crate) mod tests {
|
||||
}
|
||||
|
||||
fn lint_registry(&self) -> &LintRegistry {
|
||||
ty_python_semantic::default_lint_registry()
|
||||
ty_python_types::default_lint_registry()
|
||||
}
|
||||
|
||||
fn analysis_settings(&self) -> &AnalysisSettings {
|
||||
self.project().settings(self).analysis()
|
||||
}
|
||||
|
||||
fn verbose(&self) -> bool {
|
||||
|
||||
@@ -245,7 +245,9 @@ impl ProjectDatabase {
|
||||
|
||||
if result.project_changed {
|
||||
let new_project_metadata = match config_file_override {
|
||||
Some(config_file) => ProjectMetadata::from_config_file(config_file, self.system()),
|
||||
Some(config_file) => {
|
||||
ProjectMetadata::from_config_file(config_file, &project_root, self.system())
|
||||
}
|
||||
None => ProjectMetadata::discover(&project_root, self.system()),
|
||||
};
|
||||
match new_project_metadata {
|
||||
|
||||
@@ -29,7 +29,7 @@ use std::sync::Arc;
|
||||
use thiserror::Error;
|
||||
use ty_python_semantic::add_inferred_python_version_hint_to_diagnostic;
|
||||
use ty_python_semantic::lint::RuleSelection;
|
||||
use ty_python_semantic::types::check_types;
|
||||
use ty_python_types::types::check_types;
|
||||
|
||||
mod db;
|
||||
mod files;
|
||||
@@ -115,6 +115,10 @@ pub struct Project {
|
||||
|
||||
#[default]
|
||||
verbose_flag: bool,
|
||||
|
||||
/// Whether to enforce exclusion rules even to files explicitly passed to ty on the command line.
|
||||
#[default]
|
||||
force_exclude_flag: bool,
|
||||
}
|
||||
|
||||
/// A progress reporter.
|
||||
@@ -380,6 +384,16 @@ impl Project {
|
||||
self.verbose_flag(db)
|
||||
}
|
||||
|
||||
pub fn set_force_exclude(self, db: &mut dyn Db, force: bool) {
|
||||
if self.force_exclude_flag(db) != force {
|
||||
self.set_force_exclude_flag(db).to(force);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn force_exclude(self, db: &dyn Db) -> bool {
|
||||
self.force_exclude_flag(db)
|
||||
}
|
||||
|
||||
/// Returns the paths that should be checked.
|
||||
///
|
||||
/// The default is to check the entire project in which case this method returns
|
||||
@@ -755,7 +769,7 @@ mod tests {
|
||||
use ruff_db::system::{DbWithTestSystem, DbWithWritableSystem as _, SystemPath, SystemPathBuf};
|
||||
use ruff_db::testing::assert_function_query_was_not_run;
|
||||
use ruff_python_ast::name::Name;
|
||||
use ty_python_semantic::types::check_types;
|
||||
use ty_python_types::types::check_types;
|
||||
|
||||
#[test]
|
||||
fn check_file_skips_type_checking_when_file_cant_be_read() -> ruff_db::system::Result<()> {
|
||||
|
||||
@@ -56,6 +56,7 @@ impl ProjectMetadata {
|
||||
|
||||
pub fn from_config_file(
|
||||
path: SystemPathBuf,
|
||||
root: &SystemPath,
|
||||
system: &dyn System,
|
||||
) -> Result<Self, ProjectMetadataError> {
|
||||
tracing::debug!("Using overridden configuration file at '{path}'");
|
||||
@@ -70,8 +71,8 @@ impl ProjectMetadata {
|
||||
let options = config_file.into_options();
|
||||
|
||||
Ok(Self {
|
||||
name: Name::new(system.current_directory().file_name().unwrap_or("root")),
|
||||
root: system.current_directory().to_path_buf(),
|
||||
name: Name::new(root.file_name().unwrap_or("root")),
|
||||
root: root.to_path_buf(),
|
||||
options,
|
||||
extra_configuration_paths: vec![path],
|
||||
misconfiguration_mode: MisconfigurationMode::Fail,
|
||||
|
||||
@@ -28,11 +28,12 @@ use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
use thiserror::Error;
|
||||
use ty_combine::Combine;
|
||||
use ty_module_resolver::{SearchPathSettings, SearchPathSettingsError, SearchPaths};
|
||||
use ty_python_semantic::lint::{Level, LintSource, RuleSelection};
|
||||
use ty_python_semantic::{
|
||||
MisconfigurationMode, ProgramSettings, PythonEnvironment, PythonPlatform,
|
||||
PythonVersionFileSource, PythonVersionSource, PythonVersionWithSource, SearchPathSettings,
|
||||
SearchPathValidationError, SearchPaths, SitePackagesPaths, SysPrefixPathOrigin,
|
||||
AnalysisSettings, MisconfigurationMode, ProgramSettings, PythonEnvironment, PythonPlatform,
|
||||
PythonVersionFileSource, PythonVersionSource, PythonVersionWithSource, SitePackagesPaths,
|
||||
SysPrefixPathOrigin,
|
||||
};
|
||||
use ty_static::EnvVars;
|
||||
|
||||
@@ -86,6 +87,10 @@ pub struct Options {
|
||||
#[option_group]
|
||||
pub terminal: Option<TerminalOptions>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[option_group]
|
||||
pub analysis: Option<AnalysisOptions>,
|
||||
|
||||
/// Override configurations for specific file patterns.
|
||||
///
|
||||
/// Each override specifies include/exclude patterns and rule configurations
|
||||
@@ -258,7 +263,7 @@ impl Options {
|
||||
system: &dyn System,
|
||||
vendored: &VendoredFileSystem,
|
||||
misconfiguration_mode: MisconfigurationMode,
|
||||
) -> Result<SearchPaths, SearchPathValidationError> {
|
||||
) -> Result<SearchPaths, SearchPathSettingsError> {
|
||||
let environment = self.environment.or_default();
|
||||
let src = self.src.or_default();
|
||||
|
||||
@@ -434,6 +439,8 @@ impl Options {
|
||||
color: colored::control::SHOULD_COLORIZE.should_colorize(),
|
||||
})?;
|
||||
|
||||
let analysis = self.analysis.or_default().to_settings();
|
||||
|
||||
let overrides = self
|
||||
.to_overrides_settings(db, project_root, &mut diagnostics)
|
||||
.map_err(|err| ToSettingsError {
|
||||
@@ -446,6 +453,7 @@ impl Options {
|
||||
rules: Arc::new(rules),
|
||||
terminal,
|
||||
src,
|
||||
analysis,
|
||||
overrides,
|
||||
};
|
||||
|
||||
@@ -875,10 +883,7 @@ impl Rules {
|
||||
let lint_source = match source {
|
||||
ValueSource::File(_) => LintSource::File,
|
||||
ValueSource::Cli => LintSource::Cli,
|
||||
|
||||
ValueSource::Editor => {
|
||||
unreachable!("Can't configure rules from the user's editor")
|
||||
}
|
||||
ValueSource::Editor => LintSource::Editor,
|
||||
};
|
||||
if let Ok(severity) = Severity::try_from(**level) {
|
||||
selection.enable(lint, severity, lint_source);
|
||||
@@ -1014,7 +1019,12 @@ fn build_include_filter(
|
||||
SubDiagnosticSeverity::Info,
|
||||
"The pattern was specified on the CLI",
|
||||
)),
|
||||
ValueSource::Editor => unreachable!("Can't configure includes from the user's editor"),
|
||||
ValueSource::Editor => {
|
||||
diagnostic.sub(SubDiagnostic::new(
|
||||
SubDiagnosticSeverity::Info,
|
||||
"The pattern was specified in the editor settings.",
|
||||
))
|
||||
}
|
||||
}
|
||||
})?;
|
||||
}
|
||||
@@ -1097,9 +1107,10 @@ fn build_exclude_filter(
|
||||
SubDiagnosticSeverity::Info,
|
||||
"The pattern was specified on the CLI",
|
||||
)),
|
||||
ValueSource::Editor => unreachable!(
|
||||
"Can't configure excludes from the user's editor"
|
||||
)
|
||||
ValueSource::Editor => diagnostic.sub(SubDiagnostic::new(
|
||||
SubDiagnosticSeverity::Info,
|
||||
"The pattern was specified in the editor settings",
|
||||
))
|
||||
}
|
||||
})?;
|
||||
}
|
||||
@@ -1250,6 +1261,55 @@ pub struct TerminalOptions {
|
||||
pub error_on_warning: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Debug,
|
||||
Default,
|
||||
Clone,
|
||||
Eq,
|
||||
PartialEq,
|
||||
Combine,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
OptionsMetadata,
|
||||
get_size2::GetSize,
|
||||
)]
|
||||
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct AnalysisOptions {
|
||||
/// Whether ty should respect `type: ignore` comments.
|
||||
///
|
||||
/// When set to `false`, `type: ignore` comments are treated like any other normal
|
||||
/// comment and can't be used to suppress ty errors (you have to use `ty: ignore` instead).
|
||||
///
|
||||
/// Setting this option can be useful when using ty alongside other type checkers or when
|
||||
/// you prefer using `ty: ignore` over `type: ignore`.
|
||||
///
|
||||
/// Defaults to `true`.
|
||||
#[option(
|
||||
default = r#"true"#,
|
||||
value_type = "bool",
|
||||
example = r#"
|
||||
# Disable support for `type: ignore` comments
|
||||
respect-type-ignore-comments = false
|
||||
"#
|
||||
)]
|
||||
respect_type_ignore_comments: Option<bool>,
|
||||
}
|
||||
|
||||
impl AnalysisOptions {
|
||||
fn to_settings(&self) -> AnalysisSettings {
|
||||
let AnalysisSettings {
|
||||
respect_type_ignore_comments: respect_type_ignore_default,
|
||||
} = AnalysisSettings::default();
|
||||
|
||||
AnalysisSettings {
|
||||
respect_type_ignore_comments: self
|
||||
.respect_type_ignore_comments
|
||||
.unwrap_or(respect_type_ignore_default),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Configuration override that applies to specific files based on glob patterns.
|
||||
///
|
||||
/// An override allows you to apply different rule configurations to specific
|
||||
@@ -1606,7 +1666,7 @@ mod schema {
|
||||
fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
let registry = ty_python_semantic::default_lint_registry();
|
||||
let registry = ty_python_types::default_lint_registry();
|
||||
let level_schema = generator.subschema_for::<super::Level>();
|
||||
|
||||
let properties: Map<String, Value> = registry
|
||||
|
||||
@@ -2,6 +2,7 @@ use std::sync::Arc;
|
||||
|
||||
use ruff_db::files::File;
|
||||
use ty_combine::Combine;
|
||||
use ty_python_semantic::AnalysisSettings;
|
||||
use ty_python_semantic::lint::RuleSelection;
|
||||
|
||||
use crate::metadata::options::{InnerOverrideOptions, OutputFormat};
|
||||
@@ -25,6 +26,7 @@ pub struct Settings {
|
||||
pub(super) rules: Arc<RuleSelection>,
|
||||
pub(super) terminal: TerminalSettings,
|
||||
pub(super) src: SrcSettings,
|
||||
pub(super) analysis: AnalysisSettings,
|
||||
|
||||
/// Settings for configuration overrides that apply to specific file patterns.
|
||||
///
|
||||
@@ -54,6 +56,10 @@ impl Settings {
|
||||
pub fn overrides(&self) -> &[Override] {
|
||||
&self.overrides
|
||||
}
|
||||
|
||||
pub fn analysis(&self) -> &AnalysisSettings {
|
||||
&self.analysis
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, get_size2::GetSize)]
|
||||
|
||||
@@ -111,6 +111,8 @@ pub(crate) struct ProjectFilesWalker<'a> {
|
||||
walker: WalkDirectoryBuilder,
|
||||
|
||||
filter: ProjectFilesFilter<'a>,
|
||||
|
||||
force_exclude: bool,
|
||||
}
|
||||
|
||||
impl<'a> ProjectFilesWalker<'a> {
|
||||
@@ -158,7 +160,11 @@ impl<'a> ProjectFilesWalker<'a> {
|
||||
walker = walker.add(path);
|
||||
}
|
||||
|
||||
Some(Self { walker, filter })
|
||||
Some(Self {
|
||||
walker,
|
||||
filter,
|
||||
force_exclude: db.project().force_exclude(db),
|
||||
})
|
||||
}
|
||||
|
||||
/// Walks the project paths and collects the paths of all files that
|
||||
@@ -179,7 +185,7 @@ impl<'a> ProjectFilesWalker<'a> {
|
||||
// Skip excluded directories unless they were explicitly passed to the walker
|
||||
// (which is the case passed to `ty check <paths>`).
|
||||
if entry.file_type().is_directory() {
|
||||
if entry.depth() > 0 {
|
||||
if entry.depth() > 0 || self.force_exclude {
|
||||
let directory_included = filter
|
||||
.is_directory_included(entry.path(), GlobFilterCheckMode::TopDown);
|
||||
return match directory_included {
|
||||
@@ -218,7 +224,7 @@ impl<'a> ProjectFilesWalker<'a> {
|
||||
|
||||
// For all files, except the ones that were explicitly passed to the walker (CLI),
|
||||
// check if they're included in the project.
|
||||
if entry.depth() > 0 {
|
||||
if entry.depth() > 0 || self.force_exclude {
|
||||
match filter
|
||||
.is_file_included(entry.path(), GlobFilterCheckMode::TopDown)
|
||||
{
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user