Compare commits
33 Commits
micha/perf
...
cjm/oxidd
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d91bb060f1 | ||
|
|
bf0918d72f | ||
|
|
6870d2c53e | ||
|
|
54561192dd | ||
|
|
91c9168dd7 | ||
|
|
80577a49f8 | ||
|
|
f463fa7b7c | ||
|
|
c3b6139f39 | ||
|
|
c9fdb1f5e3 | ||
|
|
463046ae07 | ||
|
|
dcb99cc817 | ||
|
|
7c2e7cf25e | ||
|
|
867a8f9497 | ||
|
|
e22718f25f | ||
|
|
dcdc6e7c64 | ||
|
|
4ddf9228f6 | ||
|
|
6d72be2683 | ||
|
|
712c886749 | ||
|
|
50739f91dc | ||
|
|
6a5eff6017 | ||
|
|
a623d8f7c4 | ||
|
|
aa429b413f | ||
|
|
425c248232 | ||
|
|
bcd944347d | ||
|
|
86eff81c6a | ||
|
|
24ace68560 | ||
|
|
b664505d7b | ||
|
|
aa575da1e7 | ||
|
|
921eb2acb3 | ||
|
|
8665d2dc95 | ||
|
|
1cc27c995c | ||
|
|
a93bc2af6b | ||
|
|
d848182340 |
9
.github/actionlint.yaml
vendored
Normal file
9
.github/actionlint.yaml
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
# Configuration for the actionlint tool, which we run via pre-commit
|
||||
# to verify the correctness of the syntax in our GitHub Actions workflows.
|
||||
|
||||
self-hosted-runner:
|
||||
# Various runners we use that aren't recognized out-of-the-box by actionlint:
|
||||
labels:
|
||||
- depot-ubuntu-latest-8
|
||||
- depot-ubuntu-22.04-16
|
||||
- windows-latest-xlarge
|
||||
8
.github/workflows/build-binaries.yml
vendored
8
.github/workflows/build-binaries.yml
vendored
@@ -53,7 +53,7 @@ jobs:
|
||||
args: --out dist
|
||||
- name: "Test sdist"
|
||||
run: |
|
||||
pip install dist/${PACKAGE_NAME}-*.tar.gz --force-reinstall
|
||||
pip install dist/"${PACKAGE_NAME}"-*.tar.gz --force-reinstall
|
||||
"${MODULE_NAME}" --help
|
||||
python -m "${MODULE_NAME}" --help
|
||||
- name: "Upload sdist"
|
||||
@@ -125,7 +125,7 @@ jobs:
|
||||
args: --release --locked --out dist
|
||||
- name: "Test wheel - aarch64"
|
||||
run: |
|
||||
pip install dist/${PACKAGE_NAME}-*.whl --force-reinstall
|
||||
pip install dist/"${PACKAGE_NAME}"-*.whl --force-reinstall
|
||||
ruff --help
|
||||
python -m ruff --help
|
||||
- name: "Upload wheels"
|
||||
@@ -186,7 +186,7 @@ jobs:
|
||||
if: ${{ !startsWith(matrix.platform.target, 'aarch64') }}
|
||||
shell: bash
|
||||
run: |
|
||||
python -m pip install dist/${PACKAGE_NAME}-*.whl --force-reinstall
|
||||
python -m pip install dist/"${PACKAGE_NAME}"-*.whl --force-reinstall
|
||||
"${MODULE_NAME}" --help
|
||||
python -m "${MODULE_NAME}" --help
|
||||
- name: "Upload wheels"
|
||||
@@ -236,7 +236,7 @@ jobs:
|
||||
- name: "Test wheel"
|
||||
if: ${{ startsWith(matrix.target, 'x86_64') }}
|
||||
run: |
|
||||
pip install dist/${PACKAGE_NAME}-*.whl --force-reinstall
|
||||
pip install dist/"${PACKAGE_NAME}"-*.whl --force-reinstall
|
||||
"${MODULE_NAME}" --help
|
||||
python -m "${MODULE_NAME}" --help
|
||||
- name: "Upload wheels"
|
||||
|
||||
3
.github/workflows/build-docker.yml
vendored
3
.github/workflows/build-docker.yml
vendored
@@ -142,6 +142,7 @@ jobs:
|
||||
# The printf will expand the base image with the `<RUFF_BASE_IMG>@sha256:<sha256> ...` for each sha256 in the directory
|
||||
# The final command becomes `docker buildx imagetools create -t tag1 -t tag2 ... <RUFF_BASE_IMG>@sha256:<sha256_1> <RUFF_BASE_IMG>@sha256:<sha256_2> ...`
|
||||
run: |
|
||||
# shellcheck disable=SC2046
|
||||
docker buildx imagetools create \
|
||||
$(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
|
||||
$(printf "${RUFF_BASE_IMG}@sha256:%s " *)
|
||||
@@ -286,6 +287,8 @@ jobs:
|
||||
# The final command becomes `docker buildx imagetools create -t tag1 -t tag2 ... <RUFF_BASE_IMG>@sha256:<sha256_1> <RUFF_BASE_IMG>@sha256:<sha256_2> ...`
|
||||
run: |
|
||||
readarray -t lines <<< "$DOCKER_METADATA_OUTPUT_ANNOTATIONS"; annotations=(); for line in "${lines[@]}"; do annotations+=(--annotation "$line"); done
|
||||
|
||||
# shellcheck disable=SC2046
|
||||
docker buildx imagetools create \
|
||||
"${annotations[@]}" \
|
||||
$(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
|
||||
|
||||
67
.github/workflows/ci.yaml
vendored
67
.github/workflows/ci.yaml
vendored
@@ -290,7 +290,9 @@ jobs:
|
||||
file: "Cargo.toml"
|
||||
field: "workspace.package.rust-version"
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup default ${{ steps.msrv.outputs.value }}
|
||||
env:
|
||||
MSRV: ${{ steps.msrv.outputs.value }}
|
||||
run: rustup default "${MSRV}"
|
||||
- name: "Install mold"
|
||||
uses: rui314/setup-mold@v1
|
||||
- name: "Install cargo nextest"
|
||||
@@ -306,7 +308,8 @@ jobs:
|
||||
shell: bash
|
||||
env:
|
||||
NEXTEST_PROFILE: "ci"
|
||||
run: cargo +${{ steps.msrv.outputs.value }} insta test --all-features --unreferenced reject --test-runner nextest
|
||||
MSRV: ${{ steps.msrv.outputs.value }}
|
||||
run: cargo "+${MSRV}" insta test --all-features --unreferenced reject --test-runner nextest
|
||||
|
||||
cargo-fuzz-build:
|
||||
name: "cargo fuzz build"
|
||||
@@ -354,16 +357,18 @@ jobs:
|
||||
name: ruff
|
||||
path: ruff-to-test
|
||||
- name: Fuzz
|
||||
env:
|
||||
DOWNLOAD_PATH: ${{ steps.download-cached-binary.outputs.download-path }}
|
||||
run: |
|
||||
# Make executable, since artifact download doesn't preserve this
|
||||
chmod +x ${{ steps.download-cached-binary.outputs.download-path }}/ruff
|
||||
chmod +x "${DOWNLOAD_PATH}/ruff"
|
||||
|
||||
(
|
||||
uvx \
|
||||
--python=${{ env.PYTHON_VERSION }} \
|
||||
--python="${PYTHON_VERSION}" \
|
||||
--from=./python/py-fuzzer \
|
||||
fuzz \
|
||||
--test-executable=${{ steps.download-cached-binary.outputs.download-path }}/ruff \
|
||||
--test-executable="${DOWNLOAD_PATH}/ruff" \
|
||||
--bin=ruff \
|
||||
0-500
|
||||
)
|
||||
@@ -429,64 +434,72 @@ jobs:
|
||||
|
||||
- name: Run `ruff check` stable ecosystem check
|
||||
if: ${{ needs.determine_changes.outputs.linter == 'true' }}
|
||||
env:
|
||||
DOWNLOAD_PATH: ${{ steps.ruff-target.outputs.download-path }}
|
||||
run: |
|
||||
# Make executable, since artifact download doesn't preserve this
|
||||
chmod +x ./ruff ${{ steps.ruff-target.outputs.download-path }}/ruff
|
||||
chmod +x ./ruff "${DOWNLOAD_PATH}/ruff"
|
||||
|
||||
# Set pipefail to avoid hiding errors with tee
|
||||
set -eo pipefail
|
||||
|
||||
ruff-ecosystem check ./ruff ${{ steps.ruff-target.outputs.download-path }}/ruff --cache ./checkouts --output-format markdown | tee ecosystem-result-check-stable
|
||||
ruff-ecosystem check ./ruff "${DOWNLOAD_PATH}/ruff" --cache ./checkouts --output-format markdown | tee ecosystem-result-check-stable
|
||||
|
||||
cat ecosystem-result-check-stable > $GITHUB_STEP_SUMMARY
|
||||
cat ecosystem-result-check-stable > "$GITHUB_STEP_SUMMARY"
|
||||
echo "### Linter (stable)" > ecosystem-result
|
||||
cat ecosystem-result-check-stable >> ecosystem-result
|
||||
echo "" >> ecosystem-result
|
||||
|
||||
- name: Run `ruff check` preview ecosystem check
|
||||
if: ${{ needs.determine_changes.outputs.linter == 'true' }}
|
||||
env:
|
||||
DOWNLOAD_PATH: ${{ steps.ruff-target.outputs.download-path }}
|
||||
run: |
|
||||
# Make executable, since artifact download doesn't preserve this
|
||||
chmod +x ./ruff ${{ steps.ruff-target.outputs.download-path }}/ruff
|
||||
chmod +x ./ruff "${DOWNLOAD_PATH}/ruff"
|
||||
|
||||
# Set pipefail to avoid hiding errors with tee
|
||||
set -eo pipefail
|
||||
|
||||
ruff-ecosystem check ./ruff ${{ steps.ruff-target.outputs.download-path }}/ruff --cache ./checkouts --output-format markdown --force-preview | tee ecosystem-result-check-preview
|
||||
ruff-ecosystem check ./ruff "${DOWNLOAD_PATH}/ruff" --cache ./checkouts --output-format markdown --force-preview | tee ecosystem-result-check-preview
|
||||
|
||||
cat ecosystem-result-check-preview > $GITHUB_STEP_SUMMARY
|
||||
cat ecosystem-result-check-preview > "$GITHUB_STEP_SUMMARY"
|
||||
echo "### Linter (preview)" >> ecosystem-result
|
||||
cat ecosystem-result-check-preview >> ecosystem-result
|
||||
echo "" >> ecosystem-result
|
||||
|
||||
- name: Run `ruff format` stable ecosystem check
|
||||
if: ${{ needs.determine_changes.outputs.formatter == 'true' }}
|
||||
env:
|
||||
DOWNLOAD_PATH: ${{ steps.ruff-target.outputs.download-path }}
|
||||
run: |
|
||||
# Make executable, since artifact download doesn't preserve this
|
||||
chmod +x ./ruff ${{ steps.ruff-target.outputs.download-path }}/ruff
|
||||
chmod +x ./ruff "${DOWNLOAD_PATH}/ruff"
|
||||
|
||||
# Set pipefail to avoid hiding errors with tee
|
||||
set -eo pipefail
|
||||
|
||||
ruff-ecosystem format ./ruff ${{ steps.ruff-target.outputs.download-path }}/ruff --cache ./checkouts --output-format markdown | tee ecosystem-result-format-stable
|
||||
ruff-ecosystem format ./ruff "${DOWNLOAD_PATH}/ruff" --cache ./checkouts --output-format markdown | tee ecosystem-result-format-stable
|
||||
|
||||
cat ecosystem-result-format-stable > $GITHUB_STEP_SUMMARY
|
||||
cat ecosystem-result-format-stable > "$GITHUB_STEP_SUMMARY"
|
||||
echo "### Formatter (stable)" >> ecosystem-result
|
||||
cat ecosystem-result-format-stable >> ecosystem-result
|
||||
echo "" >> ecosystem-result
|
||||
|
||||
- name: Run `ruff format` preview ecosystem check
|
||||
if: ${{ needs.determine_changes.outputs.formatter == 'true' }}
|
||||
env:
|
||||
DOWNLOAD_PATH: ${{ steps.ruff-target.outputs.download-path }}
|
||||
run: |
|
||||
# Make executable, since artifact download doesn't preserve this
|
||||
chmod +x ./ruff ${{ steps.ruff-target.outputs.download-path }}/ruff
|
||||
chmod +x ./ruff "${DOWNLOAD_PATH}/ruff"
|
||||
|
||||
# Set pipefail to avoid hiding errors with tee
|
||||
set -eo pipefail
|
||||
|
||||
ruff-ecosystem format ./ruff ${{ steps.ruff-target.outputs.download-path }}/ruff --cache ./checkouts --output-format markdown --force-preview | tee ecosystem-result-format-preview
|
||||
ruff-ecosystem format ./ruff "${DOWNLOAD_PATH}/ruff" --cache ./checkouts --output-format markdown --force-preview | tee ecosystem-result-format-preview
|
||||
|
||||
cat ecosystem-result-format-preview > $GITHUB_STEP_SUMMARY
|
||||
cat ecosystem-result-format-preview > "$GITHUB_STEP_SUMMARY"
|
||||
echo "### Formatter (preview)" >> ecosystem-result
|
||||
cat ecosystem-result-format-preview >> ecosystem-result
|
||||
echo "" >> ecosystem-result
|
||||
@@ -541,7 +554,7 @@ jobs:
|
||||
args: --out dist
|
||||
- name: "Test wheel"
|
||||
run: |
|
||||
pip install --force-reinstall --find-links dist ${{ env.PACKAGE_NAME }}
|
||||
pip install --force-reinstall --find-links dist "${PACKAGE_NAME}"
|
||||
ruff --help
|
||||
python -m ruff --help
|
||||
- name: "Remove wheels from cache"
|
||||
@@ -570,13 +583,13 @@ jobs:
|
||||
key: pre-commit-${{ hashFiles('.pre-commit-config.yaml') }}
|
||||
- name: "Run pre-commit"
|
||||
run: |
|
||||
echo '```console' > $GITHUB_STEP_SUMMARY
|
||||
echo '```console' > "$GITHUB_STEP_SUMMARY"
|
||||
# Enable color output for pre-commit and remove it for the summary
|
||||
SKIP=cargo-fmt,clippy,dev-generate-all pre-commit run --all-files --show-diff-on-failure --color=always | \
|
||||
tee >(sed -E 's/\x1B\[([0-9]{1,2}(;[0-9]{1,2})*)?[mGK]//g' >> $GITHUB_STEP_SUMMARY) >&1
|
||||
exit_code=${PIPESTATUS[0]}
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
exit $exit_code
|
||||
tee >(sed -E 's/\x1B\[([0-9]{1,2}(;[0-9]{1,2})*)?[mGK]//g' >> "$GITHUB_STEP_SUMMARY") >&1
|
||||
exit_code="${PIPESTATUS[0]}"
|
||||
echo '```' >> "$GITHUB_STEP_SUMMARY"
|
||||
exit "$exit_code"
|
||||
|
||||
docs:
|
||||
name: "mkdocs"
|
||||
@@ -637,7 +650,7 @@ jobs:
|
||||
- name: "Run checks"
|
||||
run: scripts/formatter_ecosystem_checks.sh
|
||||
- name: "Github step summary"
|
||||
run: cat target/formatter-ecosystem/stats.txt > $GITHUB_STEP_SUMMARY
|
||||
run: cat target/formatter-ecosystem/stats.txt > "$GITHUB_STEP_SUMMARY"
|
||||
- name: "Remove checkouts from cache"
|
||||
run: rm -r target/formatter-ecosystem
|
||||
|
||||
@@ -676,11 +689,13 @@ jobs:
|
||||
just install
|
||||
|
||||
- name: Run ruff-lsp tests
|
||||
env:
|
||||
DOWNLOAD_PATH: ${{ steps.ruff-target.outputs.download-path }}
|
||||
run: |
|
||||
# Setup development binary
|
||||
pip uninstall --yes ruff
|
||||
chmod +x ${{ steps.ruff-target.outputs.download-path }}/ruff
|
||||
export PATH=${{ steps.ruff-target.outputs.download-path }}:$PATH
|
||||
chmod +x "${DOWNLOAD_PATH}/ruff"
|
||||
export PATH="${DOWNLOAD_PATH}:${PATH}"
|
||||
ruff version
|
||||
|
||||
just test
|
||||
|
||||
1
.github/workflows/daily_fuzz.yaml
vendored
1
.github/workflows/daily_fuzz.yaml
vendored
@@ -46,6 +46,7 @@ jobs:
|
||||
run: cargo build --locked
|
||||
- name: Fuzz
|
||||
run: |
|
||||
# shellcheck disable=SC2046
|
||||
(
|
||||
uvx \
|
||||
--python=3.12 \
|
||||
|
||||
13
.github/workflows/pr-comment.yaml
vendored
13
.github/workflows/pr-comment.yaml
vendored
@@ -10,12 +10,11 @@ on:
|
||||
description: The ecosystem workflow that triggers the workflow run
|
||||
required: true
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
comment:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: dawidd6/action-download-artifact@v7
|
||||
name: Download pull request number
|
||||
@@ -30,7 +29,7 @@ jobs:
|
||||
run: |
|
||||
if [[ -f pr-number ]]
|
||||
then
|
||||
echo "pr-number=$(<pr-number)" >> $GITHUB_OUTPUT
|
||||
echo "pr-number=$(<pr-number)" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- uses: dawidd6/action-download-artifact@v7
|
||||
@@ -66,9 +65,9 @@ jobs:
|
||||
cat pr/ecosystem/ecosystem-result >> comment.txt
|
||||
echo "" >> comment.txt
|
||||
|
||||
echo 'comment<<EOF' >> $GITHUB_OUTPUT
|
||||
cat comment.txt >> $GITHUB_OUTPUT
|
||||
echo 'EOF' >> $GITHUB_OUTPUT
|
||||
echo 'comment<<EOF' >> "$GITHUB_OUTPUT"
|
||||
cat comment.txt >> "$GITHUB_OUTPUT"
|
||||
echo 'EOF' >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Find existing comment
|
||||
uses: peter-evans/find-comment@v3
|
||||
|
||||
20
.github/workflows/publish-docs.yml
vendored
20
.github/workflows/publish-docs.yml
vendored
@@ -44,8 +44,8 @@ jobs:
|
||||
# Use version as display name for now
|
||||
display_name="$version"
|
||||
|
||||
echo "version=$version" >> $GITHUB_ENV
|
||||
echo "display_name=$display_name" >> $GITHUB_ENV
|
||||
echo "version=$version" >> "$GITHUB_ENV"
|
||||
echo "display_name=$display_name" >> "$GITHUB_ENV"
|
||||
|
||||
- name: "Set branch name"
|
||||
run: |
|
||||
@@ -55,8 +55,8 @@ jobs:
|
||||
# characters disallowed in git branch names with hyphens
|
||||
branch_display_name="$(echo "${display_name}" | tr -c '[:alnum:]._' '-' | tr -s '-')"
|
||||
|
||||
echo "branch_name=update-docs-$branch_display_name-$timestamp" >> $GITHUB_ENV
|
||||
echo "timestamp=$timestamp" >> $GITHUB_ENV
|
||||
echo "branch_name=update-docs-$branch_display_name-$timestamp" >> "$GITHUB_ENV"
|
||||
echo "timestamp=$timestamp" >> "$GITHUB_ENV"
|
||||
|
||||
- name: "Add SSH key"
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
||||
@@ -112,7 +112,7 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.ASTRAL_DOCS_PAT }}
|
||||
run: |
|
||||
# set the PR title
|
||||
pull_request_title="Update ruff documentation for "${display_name}""
|
||||
pull_request_title="Update ruff documentation for ${display_name}"
|
||||
|
||||
# Delete any existing pull requests that are open for this version
|
||||
# by checking against pull_request_title because the new PR will
|
||||
@@ -124,10 +124,12 @@ jobs:
|
||||
git push origin "${branch_name}"
|
||||
|
||||
# create the PR
|
||||
gh pr create --base main --head "${branch_name}" \
|
||||
--title "$pull_request_title" \
|
||||
--body "Automated documentation update for "${display_name}"" \
|
||||
--label "documentation"
|
||||
gh pr create \
|
||||
--base=main \
|
||||
--head="${branch_name}" \
|
||||
--title="${pull_request_title}" \
|
||||
--body="Automated documentation update for ${display_name}" \
|
||||
--label="documentation"
|
||||
|
||||
- name: "Merge Pull Request"
|
||||
if: ${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }}
|
||||
|
||||
2
.github/workflows/sync_typeshed.yaml
vendored
2
.github/workflows/sync_typeshed.yaml
vendored
@@ -59,7 +59,7 @@ jobs:
|
||||
run: |
|
||||
cd ruff
|
||||
git push --force origin typeshedbot/sync-typeshed
|
||||
gh pr list --repo $GITHUB_REPOSITORY --head typeshedbot/sync-typeshed --json id --jq length | grep 1 && exit 0 # exit if there is existing pr
|
||||
gh pr list --repo "$GITHUB_REPOSITORY" --head typeshedbot/sync-typeshed --json id --jq length | grep 1 && exit 0 # exit if there is existing pr
|
||||
gh pr create --title "Sync vendored typeshed stubs" --body "Close and reopen this PR to trigger CI" --label "internal"
|
||||
|
||||
create-issue-on-failure:
|
||||
|
||||
6
.github/zizmor.yml
vendored
Normal file
6
.github/zizmor.yml
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
# Configuration for the zizmor static analysis tool, run via pre-commit in CI
|
||||
# https://woodruffw.github.io/zizmor/configuration/
|
||||
rules:
|
||||
dangerous-triggers:
|
||||
ignore:
|
||||
- pr-comment.yaml
|
||||
@@ -21,3 +21,11 @@ MD014: false
|
||||
MD024:
|
||||
# Allow when nested under different parents e.g. CHANGELOG.md
|
||||
siblings_only: true
|
||||
|
||||
# MD046/code-block-style
|
||||
#
|
||||
# Ignore this because it conflicts with the code block style used in content
|
||||
# tabs of mkdocs-material which is to add a blank line after the content title.
|
||||
#
|
||||
# Ref: https://github.com/astral-sh/ruff/pull/15011#issuecomment-2544790854
|
||||
MD046: false
|
||||
|
||||
@@ -26,9 +26,8 @@ repos:
|
||||
hooks:
|
||||
- id: mdformat
|
||||
additional_dependencies:
|
||||
- mdformat-mkdocs
|
||||
- mdformat-admon
|
||||
- mdformat-footnote
|
||||
- mdformat-mkdocs==4.0.0
|
||||
- mdformat-footnote==0.1.1
|
||||
exclude: |
|
||||
(?x)^(
|
||||
docs/formatter/black\.md
|
||||
@@ -59,7 +58,7 @@ repos:
|
||||
- black==24.10.0
|
||||
|
||||
- repo: https://github.com/crate-ci/typos
|
||||
rev: v1.28.2
|
||||
rev: v1.28.3
|
||||
hooks:
|
||||
- id: typos
|
||||
|
||||
@@ -73,7 +72,7 @@ repos:
|
||||
pass_filenames: false # This makes it a lot faster
|
||||
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.8.2
|
||||
rev: v0.8.3
|
||||
hooks:
|
||||
- id: ruff-format
|
||||
- id: ruff
|
||||
@@ -88,8 +87,10 @@ repos:
|
||||
- id: prettier
|
||||
types: [yaml]
|
||||
|
||||
# zizmor detects security vulnerabilities in GitHub Actions workflows.
|
||||
# Additional configuration for the tool is found in `.github/zizmor.yml`
|
||||
- repo: https://github.com/woodruffw/zizmor-pre-commit
|
||||
rev: v0.8.0
|
||||
rev: v0.9.2
|
||||
hooks:
|
||||
- id: zizmor
|
||||
# `release.yml` is autogenerated by `dist`; security issues need to be fixed there
|
||||
@@ -101,5 +102,23 @@ repos:
|
||||
hooks:
|
||||
- id: check-github-workflows
|
||||
|
||||
# `actionlint` hook, for verifying correct syntax in GitHub Actions workflows.
|
||||
# Some additional configuration for `actionlint` can be found in `.github/actionlint.yaml`.
|
||||
- repo: https://github.com/rhysd/actionlint
|
||||
rev: v1.7.4
|
||||
hooks:
|
||||
- id: actionlint
|
||||
# `release.yml` is autogenerated by `dist`; issues need to be fixed there
|
||||
# (https://opensource.axo.dev/cargo-dist/)
|
||||
exclude: .github/workflows/release.yml
|
||||
args:
|
||||
- "-ignore=SC2129" # ignorable stylistic lint from shellcheck
|
||||
- "-ignore=SC2016" # another shellcheck lint: seems to have false positives?
|
||||
additional_dependencies:
|
||||
# actionlint has a shellcheck integration which extracts shell scripts in `run:` steps from GitHub Actions
|
||||
# and checks these with shellcheck. This is arguably its most useful feature,
|
||||
# but the integration only works if shellcheck is installed
|
||||
- "github.com/wasilibs/go-shellcheck/cmd/shellcheck@v0.10.0"
|
||||
|
||||
ci:
|
||||
skip: [cargo-fmt, dev-generate-all]
|
||||
|
||||
345
Cargo.lock
generated
345
Cargo.lock
generated
@@ -30,6 +30,12 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "allocator-api2"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||
|
||||
[[package]]
|
||||
name = "android-tzdata"
|
||||
version = "0.1.1"
|
||||
@@ -203,6 +209,18 @@ version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
|
||||
|
||||
[[package]]
|
||||
name = "bitvec"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c"
|
||||
dependencies = [
|
||||
"funty",
|
||||
"radium",
|
||||
"tap",
|
||||
"wyz",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.4"
|
||||
@@ -220,9 +238,9 @@ checksum = "7f839cdf7e2d3198ac6ca003fd8ebc61715755f41c1cad15ff13df67531e00ed"
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "1.11.0"
|
||||
version = "1.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a68f1f47cdf0ec8ee4b941b2eee2a80cb796db73118c0dd09ac63fbe405be22"
|
||||
checksum = "786a307d683a5bf92e6fd5fd69a7eb613751668d1d8d67d802846dfe367c62c8"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"regex-automata 0.4.8",
|
||||
@@ -314,9 +332,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.38"
|
||||
version = "0.4.39"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
|
||||
checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
@@ -465,12 +483,12 @@ checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
|
||||
|
||||
[[package]]
|
||||
name = "colored"
|
||||
version = "2.1.0"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8"
|
||||
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -835,6 +853,15 @@ version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
|
||||
|
||||
[[package]]
|
||||
name = "document-features"
|
||||
version = "0.2.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb6969eaabd2421f8a2775cfd2471a2b634372b4a25d41e3bd647b79912850a0"
|
||||
dependencies = [
|
||||
"litrs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "drop_bomb"
|
||||
version = "0.1.5"
|
||||
@@ -923,9 +950,9 @@ checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6"
|
||||
|
||||
[[package]]
|
||||
name = "fern"
|
||||
version = "0.7.0"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69ff9c9d5fb3e6da8ac2f77ab76fe7e8087d512ce095200f8f29ac5b656cf6dc"
|
||||
checksum = "4316185f709b23713e41e3195f90edef7fb00c3ed4adc79769cf09cc762a3b29"
|
||||
dependencies = [
|
||||
"log",
|
||||
]
|
||||
@@ -952,6 +979,15 @@ dependencies = [
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flume"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095"
|
||||
dependencies = [
|
||||
"spin",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
@@ -985,6 +1021,12 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "funty"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.7"
|
||||
@@ -1102,6 +1144,17 @@ dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hugealloc"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "542733215a252c27c674e5d875a910d7de509cb74123d0bdbaa050f871ec84c2"
|
||||
dependencies = [
|
||||
"allocator-api2",
|
||||
"libc",
|
||||
"sptr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "humantime"
|
||||
version = "2.1.0"
|
||||
@@ -1444,6 +1497,12 @@ dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_sorted"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "357376465c37db3372ef6a00585d336ed3d0f11d4345eef77ebcb05865392b21"
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.10.5"
|
||||
@@ -1521,9 +1580,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.167"
|
||||
version = "0.2.168"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc"
|
||||
checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d"
|
||||
|
||||
[[package]]
|
||||
name = "libcst"
|
||||
@@ -1571,6 +1630,12 @@ dependencies = [
|
||||
"redox_syscall 0.5.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linear-hashtbl"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e252bb7e9b36739fb2482438638e862241d111c76fae3ebee272454477c63a4f"
|
||||
|
||||
[[package]]
|
||||
name = "linked-hash-map"
|
||||
version = "0.5.6"
|
||||
@@ -1589,6 +1654,12 @@ version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704"
|
||||
|
||||
[[package]]
|
||||
name = "litrs"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.11"
|
||||
@@ -1703,6 +1774,12 @@ dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nanorand"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3"
|
||||
|
||||
[[package]]
|
||||
name = "natord"
|
||||
version = "1.0.9"
|
||||
@@ -1856,6 +1933,153 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||
|
||||
[[package]]
|
||||
name = "oxidd"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22f7c2dbbc26b8d2e1ae861401a4bfa1d2e229e39da77ff8f0d64f2b4793e6da"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"document-features",
|
||||
"oxidd-cache",
|
||||
"oxidd-core",
|
||||
"oxidd-derive",
|
||||
"oxidd-dump",
|
||||
"oxidd-manager-index",
|
||||
"oxidd-reorder",
|
||||
"oxidd-rules-bdd",
|
||||
"oxidd-rules-mtbdd",
|
||||
"oxidd-rules-tdd",
|
||||
"oxidd-rules-zbdd",
|
||||
"rustc-hash 1.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "oxidd-cache"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "efcc7923864f2a2525e76e26a3645b65a82e29ac3d3118d6906bad0117eb704e"
|
||||
dependencies = [
|
||||
"allocator-api2",
|
||||
"document-features",
|
||||
"hugealloc",
|
||||
"oxidd-core",
|
||||
"parking_lot",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "oxidd-core"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b47e2c527b7aba7a7c789c7e5300df5a338002b33284c823fd08e1c661d176d6"
|
||||
dependencies = [
|
||||
"nanorand",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "oxidd-derive"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ccd72606db214bdf9b3880a6dedb50990fafdac23a05b65f982b65ac22862ac"
|
||||
dependencies = [
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "oxidd-dump"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84ef9fed6a604b8199c988efce9231de372b058cf9501d1b60fa53ed0ea1d43a"
|
||||
dependencies = [
|
||||
"bitvec",
|
||||
"document-features",
|
||||
"is_sorted",
|
||||
"memchr",
|
||||
"oxidd-core",
|
||||
"rustc-hash 1.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "oxidd-manager-index"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd41278c0cd6f0418f78e8ed5a3a062fa31449a929b3a0d03d4c1056af620cdc"
|
||||
dependencies = [
|
||||
"bitvec",
|
||||
"crossbeam-utils",
|
||||
"linear-hashtbl",
|
||||
"oxidd-core",
|
||||
"parking_lot",
|
||||
"rayon",
|
||||
"rustc-hash 1.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "oxidd-reorder"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "222b3b3c71f9c4a5f875c01fda840e25d40d4fa86a5f0636938c17f7adf6c068"
|
||||
dependencies = [
|
||||
"flume",
|
||||
"is_sorted",
|
||||
"oxidd-core",
|
||||
"rayon",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "oxidd-rules-bdd"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8ab7d000b0fb53ddcc282296929f51b2a5215591f6c6aa134a9fca9f133f61b"
|
||||
dependencies = [
|
||||
"bitvec",
|
||||
"document-features",
|
||||
"oxidd-core",
|
||||
"oxidd-derive",
|
||||
"oxidd-dump",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "oxidd-rules-mtbdd"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9bd51a1a297d98cefd4843cf065aba5cb04b2e78b0623857f0f8babe9f3d421"
|
||||
dependencies = [
|
||||
"document-features",
|
||||
"oxidd-core",
|
||||
"oxidd-derive",
|
||||
"oxidd-dump",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "oxidd-rules-tdd"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "753f378e1d76f1f23afd1a06bbe4f23025ac95d19fb3c2b3ff052c0d3fcd9af3"
|
||||
dependencies = [
|
||||
"document-features",
|
||||
"oxidd-core",
|
||||
"oxidd-derive",
|
||||
"oxidd-dump",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "oxidd-rules-zbdd"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "821482c23511e87b1a9bd7f0454971f4a73b01a7ed16611179d79c5872690dce"
|
||||
dependencies = [
|
||||
"bitvec",
|
||||
"document-features",
|
||||
"oxidd-core",
|
||||
"oxidd-derive",
|
||||
"oxidd-dump",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.3"
|
||||
@@ -2127,6 +2351,29 @@ dependencies = [
|
||||
"yansi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
|
||||
dependencies = [
|
||||
"proc-macro-error-attr",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error-attr"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.92"
|
||||
@@ -2161,7 +2408,7 @@ dependencies = [
|
||||
"newtype-uuid",
|
||||
"quick-xml",
|
||||
"strip-ansi-escapes",
|
||||
"thiserror 2.0.6",
|
||||
"thiserror 2.0.7",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
@@ -2203,6 +2450,12 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "radium"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
@@ -2265,6 +2518,9 @@ dependencies = [
|
||||
"crossbeam",
|
||||
"ctrlc",
|
||||
"filetime",
|
||||
"oxidd",
|
||||
"oxidd-core",
|
||||
"oxidd-dump",
|
||||
"rayon",
|
||||
"red_knot_python_semantic",
|
||||
"red_knot_server",
|
||||
@@ -2314,7 +2570,7 @@ dependencies = [
|
||||
"static_assertions",
|
||||
"tempfile",
|
||||
"test-case",
|
||||
"thiserror 2.0.6",
|
||||
"thiserror 2.0.7",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
@@ -2411,7 +2667,7 @@ dependencies = [
|
||||
"rustc-hash 2.1.0",
|
||||
"salsa",
|
||||
"serde",
|
||||
"thiserror 2.0.6",
|
||||
"thiserror 2.0.7",
|
||||
"toml",
|
||||
"tracing",
|
||||
]
|
||||
@@ -2564,7 +2820,7 @@ dependencies = [
|
||||
"strum",
|
||||
"tempfile",
|
||||
"test-case",
|
||||
"thiserror 2.0.6",
|
||||
"thiserror 2.0.7",
|
||||
"tikv-jemallocator",
|
||||
"toml",
|
||||
"tracing",
|
||||
@@ -2634,7 +2890,7 @@ dependencies = [
|
||||
"salsa",
|
||||
"serde",
|
||||
"tempfile",
|
||||
"thiserror 2.0.6",
|
||||
"thiserror 2.0.7",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"tracing-tree",
|
||||
@@ -2786,7 +3042,7 @@ dependencies = [
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"test-case",
|
||||
"thiserror 2.0.6",
|
||||
"thiserror 2.0.7",
|
||||
"toml",
|
||||
"typed-arena",
|
||||
"unicode-normalization",
|
||||
@@ -2820,7 +3076,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"serde_with",
|
||||
"test-case",
|
||||
"thiserror 2.0.6",
|
||||
"thiserror 2.0.7",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
@@ -2892,7 +3148,7 @@ dependencies = [
|
||||
"similar",
|
||||
"smallvec",
|
||||
"static_assertions",
|
||||
"thiserror 2.0.6",
|
||||
"thiserror 2.0.7",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
@@ -3025,7 +3281,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"shellexpand",
|
||||
"thiserror 2.0.6",
|
||||
"thiserror 2.0.7",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
@@ -3193,7 +3449,7 @@ checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1"
|
||||
[[package]]
|
||||
name = "salsa"
|
||||
version = "0.18.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=254c749b02cde2fd29852a7463a33e800b771758#254c749b02cde2fd29852a7463a33e800b771758"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=3c7f1694c9efba751dbeeacfbc93b227586e316a#3c7f1694c9efba751dbeeacfbc93b227586e316a"
|
||||
dependencies = [
|
||||
"append-only-vec",
|
||||
"arc-swap",
|
||||
@@ -3203,6 +3459,7 @@ dependencies = [
|
||||
"indexmap",
|
||||
"lazy_static",
|
||||
"parking_lot",
|
||||
"rayon",
|
||||
"rustc-hash 2.1.0",
|
||||
"salsa-macro-rules",
|
||||
"salsa-macros",
|
||||
@@ -3213,12 +3470,12 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "salsa-macro-rules"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=254c749b02cde2fd29852a7463a33e800b771758#254c749b02cde2fd29852a7463a33e800b771758"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=3c7f1694c9efba751dbeeacfbc93b227586e316a#3c7f1694c9efba751dbeeacfbc93b227586e316a"
|
||||
|
||||
[[package]]
|
||||
name = "salsa-macros"
|
||||
version = "0.18.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=254c749b02cde2fd29852a7463a33e800b771758#254c749b02cde2fd29852a7463a33e800b771758"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=3c7f1694c9efba751dbeeacfbc93b227586e316a#3c7f1694c9efba751dbeeacfbc93b227586e316a"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
@@ -3280,9 +3537,9 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.215"
|
||||
version = "1.0.216"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f"
|
||||
checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
@@ -3300,9 +3557,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.215"
|
||||
version = "1.0.216"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0"
|
||||
checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3442,6 +3699,15 @@ name = "spin"
|
||||
version = "0.9.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sptr"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b9b39299b249ad65f3b7e96443bad61c02ca5cd3589f46cb6d610a0fd6c0d6a"
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
@@ -3537,6 +3803,12 @@ dependencies = [
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tap"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.14.0"
|
||||
@@ -3623,11 +3895,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.6"
|
||||
version = "2.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fec2a1820ebd077e2b90c4df007bebf344cd394098a13c563957d0afc83ea47"
|
||||
checksum = "93605438cbd668185516ab499d589afb7ee1859ea3d5fc8f6b0755e1c7443767"
|
||||
dependencies = [
|
||||
"thiserror-impl 2.0.6",
|
||||
"thiserror-impl 2.0.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3643,9 +3915,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "2.0.6"
|
||||
version = "2.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d65750cab40f4ff1929fb1ba509e9914eb756131cef4210da8d5d700d26f6312"
|
||||
checksum = "e1d8749b4531af2117677a5fcd12b1348a3fe2b81e36e61ffeac5c4aa3273e36"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -4473,6 +4745,15 @@ version = "0.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
|
||||
|
||||
[[package]]
|
||||
name = "wyz"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed"
|
||||
dependencies = [
|
||||
"tap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yansi"
|
||||
version = "1.0.1"
|
||||
|
||||
@@ -105,6 +105,7 @@ mimalloc = { version = "0.1.39" }
|
||||
natord = { version = "1.0.9" }
|
||||
notify = { version = "7.0.0" }
|
||||
ordermap = { version = "0.5.0" }
|
||||
oxidd = { version = "0.9.0", features = ["tdd"] }
|
||||
path-absolutize = { version = "3.1.1" }
|
||||
path-slash = { version = "0.2.1" }
|
||||
pathdiff = { version = "0.2.1" }
|
||||
@@ -118,7 +119,8 @@ rand = { version = "0.8.5" }
|
||||
rayon = { version = "1.10.0" }
|
||||
regex = { version = "1.10.2" }
|
||||
rustc-hash = { version = "2.0.0" }
|
||||
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "254c749b02cde2fd29852a7463a33e800b771758" }
|
||||
# When updating salsa, make sure to also update the revision in `fuzz/Cargo.toml`
|
||||
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "3c7f1694c9efba751dbeeacfbc93b227586e316a" }
|
||||
schemars = { version = "0.8.16" }
|
||||
seahash = { version = "4.1.0" }
|
||||
serde = { version = "1.0.197", features = ["derive"] }
|
||||
|
||||
@@ -24,6 +24,9 @@ colored = { workspace = true }
|
||||
countme = { workspace = true, features = ["enable"] }
|
||||
crossbeam = { workspace = true }
|
||||
ctrlc = { version = "3.4.4" }
|
||||
oxidd = { workspace = true }
|
||||
oxidd-core = { version = "0.9.0" }
|
||||
oxidd-dump = { version = "0.4.0" }
|
||||
rayon = { workspace = true }
|
||||
salsa = { workspace = true }
|
||||
tracing = { workspace = true, features = ["release_max_level_debug"] }
|
||||
|
||||
@@ -105,8 +105,44 @@ pub enum Command {
|
||||
Server,
|
||||
}
|
||||
|
||||
use oxidd::bdd::BDDFunction;
|
||||
use oxidd::ManagerRef;
|
||||
use oxidd::{BooleanFunction, BooleanFunctionQuant};
|
||||
use oxidd_core::Manager;
|
||||
use oxidd_dump::dot::dump_all;
|
||||
|
||||
#[allow(clippy::print_stdout, clippy::unnecessary_wraps, clippy::print_stderr)]
|
||||
pub fn main() -> ExitStatus {
|
||||
let mgr = oxidd::bdd::new_manager(24, 24, 1);
|
||||
let (x, y, z) = mgr.with_manager_exclusive(|mgr| {
|
||||
(
|
||||
BDDFunction::new_var(mgr).unwrap(),
|
||||
BDDFunction::new_var(mgr).unwrap(),
|
||||
BDDFunction::new_var(mgr).unwrap(),
|
||||
)
|
||||
});
|
||||
mgr.with_manager_shared(|manager| {
|
||||
let inner_func = x
|
||||
.or(&y.and(&x.not().unwrap()).unwrap())
|
||||
.unwrap()
|
||||
.or(&y.not().unwrap().and(&x.not().unwrap()).unwrap())
|
||||
.unwrap();
|
||||
let func = z.and(&inner_func).unwrap();
|
||||
|
||||
let func = func.restrict(&z).unwrap();
|
||||
|
||||
manager.gc();
|
||||
|
||||
let file = std::fs::File::create("bdd.dot").expect("could not create `bdd.dot`");
|
||||
dump_all(
|
||||
file,
|
||||
manager,
|
||||
[(&x, "x"), (&y, "y"), (&z, "z")],
|
||||
[(&func, "z ^ (x ∨ (y ∧ ~x) ∨ (~y ∧ ~x))")],
|
||||
)
|
||||
.expect("dot export failed");
|
||||
});
|
||||
panic!("FOO");
|
||||
run().unwrap_or_else(|error| {
|
||||
use std::io::Write;
|
||||
|
||||
@@ -279,7 +315,7 @@ impl MainLoop {
|
||||
while let Ok(message) = self.receiver.recv() {
|
||||
match message {
|
||||
MainLoopMessage::CheckWorkspace => {
|
||||
let db = db.snapshot();
|
||||
let db = db.clone();
|
||||
let sender = self.sender.clone();
|
||||
|
||||
// Spawn a new task that checks the workspace. This needs to be done in a separate thread
|
||||
|
||||
@@ -3,43 +3,59 @@
|
||||
The `typing` module has various aliases to other stdlib classes. These are a legacy feature, but
|
||||
still need to be supported by a type checker.
|
||||
|
||||
## Currently unsupported
|
||||
## Correspondence
|
||||
|
||||
Support for most of these symbols is currently a TODO:
|
||||
All of the following symbols can be mapped one-to-one with the actual type:
|
||||
|
||||
```py
|
||||
import typing
|
||||
|
||||
def f(
|
||||
a: typing.List,
|
||||
b: typing.List[int],
|
||||
c: typing.Dict,
|
||||
d: typing.Dict[int, str],
|
||||
e: typing.DefaultDict,
|
||||
f: typing.DefaultDict[str, int],
|
||||
g: typing.Set,
|
||||
h: typing.Set[int],
|
||||
i: typing.FrozenSet,
|
||||
j: typing.FrozenSet[str],
|
||||
k: typing.OrderedDict,
|
||||
l: typing.OrderedDict[int, str],
|
||||
m: typing.Counter,
|
||||
n: typing.Counter[int],
|
||||
list_bare: typing.List,
|
||||
list_parametrized: typing.List[int],
|
||||
dict_bare: typing.Dict,
|
||||
dict_parametrized: typing.Dict[int, str],
|
||||
set_bare: typing.Set,
|
||||
set_parametrized: typing.Set[int],
|
||||
frozen_set_bare: typing.FrozenSet,
|
||||
frozen_set_parametrized: typing.FrozenSet[str],
|
||||
chain_map_bare: typing.ChainMap,
|
||||
chain_map_parametrized: typing.ChainMap[int],
|
||||
counter_bare: typing.Counter,
|
||||
counter_parametrized: typing.Counter[int],
|
||||
default_dict_bare: typing.DefaultDict,
|
||||
default_dict_parametrized: typing.DefaultDict[str, int],
|
||||
deque_bare: typing.Deque,
|
||||
deque_parametrized: typing.Deque[str],
|
||||
ordered_dict_bare: typing.OrderedDict,
|
||||
ordered_dict_parametrized: typing.OrderedDict[int, str],
|
||||
):
|
||||
reveal_type(a) # revealed: @Todo(Unsupported or invalid type in a type expression)
|
||||
reveal_type(b) # revealed: @Todo(typing.List alias)
|
||||
reveal_type(c) # revealed: @Todo(Unsupported or invalid type in a type expression)
|
||||
reveal_type(d) # revealed: @Todo(typing.Dict alias)
|
||||
reveal_type(e) # revealed: @Todo(Unsupported or invalid type in a type expression)
|
||||
reveal_type(f) # revealed: @Todo(typing.DefaultDict[] alias)
|
||||
reveal_type(g) # revealed: @Todo(Unsupported or invalid type in a type expression)
|
||||
reveal_type(h) # revealed: @Todo(typing.Set alias)
|
||||
reveal_type(i) # revealed: @Todo(Unsupported or invalid type in a type expression)
|
||||
reveal_type(j) # revealed: @Todo(typing.FrozenSet alias)
|
||||
reveal_type(k) # revealed: @Todo(Unsupported or invalid type in a type expression)
|
||||
reveal_type(l) # revealed: @Todo(typing.OrderedDict alias)
|
||||
reveal_type(m) # revealed: @Todo(Unsupported or invalid type in a type expression)
|
||||
reveal_type(n) # revealed: @Todo(typing.Counter[] alias)
|
||||
reveal_type(list_bare) # revealed: list
|
||||
reveal_type(list_parametrized) # revealed: list
|
||||
|
||||
reveal_type(dict_bare) # revealed: dict
|
||||
reveal_type(dict_parametrized) # revealed: dict
|
||||
|
||||
reveal_type(set_bare) # revealed: set
|
||||
reveal_type(set_parametrized) # revealed: set
|
||||
|
||||
reveal_type(frozen_set_bare) # revealed: frozenset
|
||||
reveal_type(frozen_set_parametrized) # revealed: frozenset
|
||||
|
||||
reveal_type(chain_map_bare) # revealed: ChainMap
|
||||
reveal_type(chain_map_parametrized) # revealed: ChainMap
|
||||
|
||||
reveal_type(counter_bare) # revealed: Counter
|
||||
reveal_type(counter_parametrized) # revealed: Counter
|
||||
|
||||
reveal_type(default_dict_bare) # revealed: defaultdict
|
||||
reveal_type(default_dict_parametrized) # revealed: defaultdict
|
||||
|
||||
reveal_type(deque_bare) # revealed: deque
|
||||
reveal_type(deque_parametrized) # revealed: deque
|
||||
|
||||
reveal_type(ordered_dict_bare) # revealed: OrderedDict
|
||||
reveal_type(ordered_dict_parametrized) # revealed: OrderedDict
|
||||
```
|
||||
|
||||
## Inheritance
|
||||
@@ -49,35 +65,63 @@ The aliases can be inherited from. Some of these are still partially or wholly T
|
||||
```py
|
||||
import typing
|
||||
|
||||
class A(typing.Dict): ...
|
||||
####################
|
||||
### Built-ins
|
||||
|
||||
class ListSubclass(typing.List): ...
|
||||
|
||||
# TODO: should have `Generic`, should not have `Unknown`
|
||||
reveal_type(A.__mro__) # revealed: tuple[Literal[A], Literal[dict], Unknown, Literal[object]]
|
||||
# revealed: tuple[Literal[ListSubclass], Literal[list], Unknown, Literal[object]]
|
||||
reveal_type(ListSubclass.__mro__)
|
||||
|
||||
class B(typing.List): ...
|
||||
class DictSubclass(typing.Dict): ...
|
||||
|
||||
# TODO: should have `Generic`, should not have `Unknown`
|
||||
reveal_type(B.__mro__) # revealed: tuple[Literal[B], Literal[list], Unknown, Literal[object]]
|
||||
# revealed: tuple[Literal[DictSubclass], Literal[dict], Unknown, Literal[object]]
|
||||
reveal_type(DictSubclass.__mro__)
|
||||
|
||||
class C(typing.Set): ...
|
||||
class SetSubclass(typing.Set): ...
|
||||
|
||||
# TODO: should have `Generic`, should not have `Unknown`
|
||||
reveal_type(C.__mro__) # revealed: tuple[Literal[C], Literal[set], Unknown, Literal[object]]
|
||||
# revealed: tuple[Literal[SetSubclass], Literal[set], Unknown, Literal[object]]
|
||||
reveal_type(SetSubclass.__mro__)
|
||||
|
||||
class D(typing.FrozenSet): ...
|
||||
class FrozenSetSubclass(typing.FrozenSet): ...
|
||||
|
||||
# TODO: should have `Generic`, should not have `Unknown`
|
||||
reveal_type(D.__mro__) # revealed: tuple[Literal[D], Literal[frozenset], Unknown, Literal[object]]
|
||||
# revealed: tuple[Literal[FrozenSetSubclass], Literal[frozenset], Unknown, Literal[object]]
|
||||
reveal_type(FrozenSetSubclass.__mro__)
|
||||
|
||||
class E(typing.DefaultDict): ...
|
||||
####################
|
||||
### `collections`
|
||||
|
||||
reveal_type(E.__mro__) # revealed: tuple[Literal[E], @Todo(Support for more typing aliases as base classes), Literal[object]]
|
||||
class ChainMapSubclass(typing.ChainMap): ...
|
||||
|
||||
class F(typing.OrderedDict): ...
|
||||
# TODO: Should be (ChainMapSubclass, ChainMap, MutableMapping, Mapping, Collection, Sized, Iterable, Container, Generic, object)
|
||||
# revealed: tuple[Literal[ChainMapSubclass], Literal[ChainMap], Unknown, Literal[object]]
|
||||
reveal_type(ChainMapSubclass.__mro__)
|
||||
|
||||
reveal_type(F.__mro__) # revealed: tuple[Literal[F], @Todo(Support for more typing aliases as base classes), Literal[object]]
|
||||
class CounterSubclass(typing.Counter): ...
|
||||
|
||||
class G(typing.Counter): ...
|
||||
# TODO: Should be (CounterSubclass, Counter, dict, MutableMapping, Mapping, Collection, Sized, Iterable, Container, Generic, object)
|
||||
# revealed: tuple[Literal[CounterSubclass], Literal[Counter], Unknown, Literal[object]]
|
||||
reveal_type(CounterSubclass.__mro__)
|
||||
|
||||
reveal_type(G.__mro__) # revealed: tuple[Literal[G], @Todo(Support for more typing aliases as base classes), Literal[object]]
|
||||
class DefaultDictSubclass(typing.DefaultDict): ...
|
||||
|
||||
# TODO: Should be (DefaultDictSubclass, defaultdict, dict, MutableMapping, Mapping, Collection, Sized, Iterable, Container, Generic, object)
|
||||
# revealed: tuple[Literal[DefaultDictSubclass], Literal[defaultdict], Unknown, Literal[object]]
|
||||
reveal_type(DefaultDictSubclass.__mro__)
|
||||
|
||||
class DequeSubclass(typing.Deque): ...
|
||||
|
||||
# TODO: Should be (DequeSubclass, deque, MutableSequence, Sequence, Reversible, Collection, Sized, Iterable, Container, Generic, object)
|
||||
# revealed: tuple[Literal[DequeSubclass], Literal[deque], Unknown, Literal[object]]
|
||||
reveal_type(DequeSubclass.__mro__)
|
||||
|
||||
class OrderedDictSubclass(typing.OrderedDict): ...
|
||||
|
||||
# TODO: Should be (OrderedDictSubclass, OrderedDict, dict, MutableMapping, Mapping, Collection, Sized, Iterable, Container, Generic, object)
|
||||
# revealed: tuple[Literal[OrderedDictSubclass], Literal[OrderedDict], Unknown, Literal[object]]
|
||||
reveal_type(OrderedDictSubclass.__mro__)
|
||||
```
|
||||
|
||||
@@ -51,7 +51,7 @@ class D(TypeIs): ... # error: [invalid-base]
|
||||
class E(Concatenate): ... # error: [invalid-base]
|
||||
class F(Callable): ...
|
||||
|
||||
reveal_type(F.__mro__) # revealed: tuple[Literal[F], @Todo(Support for more typing aliases as base classes), Literal[object]]
|
||||
reveal_type(F.__mro__) # revealed: tuple[Literal[F], @Todo(Support for Callable as a base class), Literal[object]]
|
||||
```
|
||||
|
||||
## Subscriptability
|
||||
|
||||
@@ -67,6 +67,6 @@ def _(flag: bool):
|
||||
def __call__(self) -> int: ...
|
||||
|
||||
a = NonCallable()
|
||||
# error: "Object of type `Literal[1] | Literal[__call__]` is not callable (due to union element `Literal[1]`)"
|
||||
reveal_type(a()) # revealed: Unknown | int
|
||||
# error: "Object of type `Literal[__call__] | Literal[1]` is not callable (due to union element `Literal[1]`)"
|
||||
reveal_type(a()) # revealed: int | Unknown
|
||||
```
|
||||
|
||||
@@ -19,14 +19,17 @@ def _(flag: bool):
|
||||
x = 1 # error: [conflicting-declarations] "Conflicting declared types for `x`: str, int"
|
||||
```
|
||||
|
||||
## Partial declarations
|
||||
## Incompatible declarations for 2 (out of 3) types
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
if flag:
|
||||
def _(flag1: bool, flag2: bool):
|
||||
if flag1:
|
||||
x: str
|
||||
elif flag2:
|
||||
x: int
|
||||
|
||||
x = 1 # error: [conflicting-declarations] "Conflicting declared types for `x`: Unknown, int"
|
||||
# Here, the declared type for `x` is `int | str | Unknown`.
|
||||
x = 1 # error: [conflicting-declarations] "Conflicting declared types for `x`: str, int"
|
||||
```
|
||||
|
||||
## Incompatible declarations with bad assignment
|
||||
@@ -42,3 +45,31 @@ def _(flag: bool):
|
||||
# error: [invalid-assignment]
|
||||
x = b"foo"
|
||||
```
|
||||
|
||||
## No errors
|
||||
|
||||
Currently, we avoid raising the conflicting-declarations for the following cases:
|
||||
|
||||
### Partial declarations
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
if flag:
|
||||
x: int
|
||||
|
||||
x = 1
|
||||
```
|
||||
|
||||
### Partial declarations in try-except
|
||||
|
||||
Refer to <https://github.com/astral-sh/ruff/issues/13966>
|
||||
|
||||
```py
|
||||
def _():
|
||||
try:
|
||||
x: int = 1
|
||||
except:
|
||||
x = 2
|
||||
|
||||
x = 3
|
||||
```
|
||||
|
||||
@@ -25,3 +25,82 @@ reveal_type(D) # revealed: Literal[C]
|
||||
```py path=b.py
|
||||
class C: ...
|
||||
```
|
||||
|
||||
## Nested
|
||||
|
||||
```py
|
||||
import a.b
|
||||
|
||||
reveal_type(a.b.C) # revealed: Literal[C]
|
||||
```
|
||||
|
||||
```py path=a/__init__.py
|
||||
```
|
||||
|
||||
```py path=a/b.py
|
||||
class C: ...
|
||||
```
|
||||
|
||||
## Deeply nested
|
||||
|
||||
```py
|
||||
import a.b.c
|
||||
|
||||
reveal_type(a.b.c.C) # revealed: Literal[C]
|
||||
```
|
||||
|
||||
```py path=a/__init__.py
|
||||
```
|
||||
|
||||
```py path=a/b/__init__.py
|
||||
```
|
||||
|
||||
```py path=a/b/c.py
|
||||
class C: ...
|
||||
```
|
||||
|
||||
## Nested with rename
|
||||
|
||||
```py
|
||||
import a.b as b
|
||||
|
||||
reveal_type(b.C) # revealed: Literal[C]
|
||||
```
|
||||
|
||||
```py path=a/__init__.py
|
||||
```
|
||||
|
||||
```py path=a/b.py
|
||||
class C: ...
|
||||
```
|
||||
|
||||
## Deeply nested with rename
|
||||
|
||||
```py
|
||||
import a.b.c as c
|
||||
|
||||
reveal_type(c.C) # revealed: Literal[C]
|
||||
```
|
||||
|
||||
```py path=a/__init__.py
|
||||
```
|
||||
|
||||
```py path=a/b/__init__.py
|
||||
```
|
||||
|
||||
```py path=a/b/c.py
|
||||
class C: ...
|
||||
```
|
||||
|
||||
## Unresolvable submodule imports
|
||||
|
||||
```py
|
||||
# Topmost component resolvable, submodule not resolvable:
|
||||
import a.foo # error: [unresolved-import] "Cannot resolve import `a.foo`"
|
||||
|
||||
# Topmost component unresolvable:
|
||||
import b.foo # error: [unresolved-import] "Cannot resolve import `b.foo`"
|
||||
```
|
||||
|
||||
```py path=a/__init__.py
|
||||
```
|
||||
|
||||
@@ -7,3 +7,25 @@ from import bar # error: [invalid-syntax]
|
||||
|
||||
reveal_type(bar) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Invalid nested module import
|
||||
|
||||
TODO: This is correctly flagged as an error, but we could clean up the diagnostics that we report.
|
||||
|
||||
```py
|
||||
# TODO: No second diagnostic
|
||||
# error: [invalid-syntax] "Expected ',', found '.'"
|
||||
# error: [unresolved-import] "Module `a` has no member `c`"
|
||||
from a import b.c
|
||||
|
||||
# TODO: Should these be inferred as Unknown?
|
||||
reveal_type(b) # revealed: <module 'a.b'>
|
||||
reveal_type(b.c) # revealed: Literal[1]
|
||||
```
|
||||
|
||||
```py path=a/__init__.py
|
||||
```
|
||||
|
||||
```py path=a/b.py
|
||||
c = 1
|
||||
```
|
||||
|
||||
@@ -121,23 +121,44 @@ X = 42
|
||||
```
|
||||
|
||||
```py path=package/bar.py
|
||||
# TODO: support submodule imports
|
||||
from . import foo # error: [unresolved-import]
|
||||
from . import foo
|
||||
|
||||
y = foo.X
|
||||
|
||||
# TODO: should be `Literal[42]`
|
||||
reveal_type(y) # revealed: Unknown
|
||||
reveal_type(foo.X) # revealed: Literal[42]
|
||||
```
|
||||
|
||||
## Non-existent + bare to module
|
||||
|
||||
This test verifies that we emit an error when we try to import a symbol that is neither a submodule
|
||||
nor an attribute of `package`.
|
||||
|
||||
```py path=package/__init__.py
|
||||
```
|
||||
|
||||
```py path=package/bar.py
|
||||
# TODO: support submodule imports
|
||||
from . import foo # error: [unresolved-import]
|
||||
|
||||
reveal_type(foo) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Import submodule from self
|
||||
|
||||
We don't currently consider `from...import` statements when building up the `imported_modules` set
|
||||
in the semantic index. When accessing an attribute of a module, we only consider it a potential
|
||||
submodule when that submodule name appears in the `imported_modules` set. That means that submodules
|
||||
that are imported via `from...import` are not visible to our type inference if you also access that
|
||||
submodule via the attribute on its parent package.
|
||||
|
||||
```py path=package/__init__.py
|
||||
```
|
||||
|
||||
```py path=package/foo.py
|
||||
X = 42
|
||||
```
|
||||
|
||||
```py path=package/bar.py
|
||||
from . import foo
|
||||
import package
|
||||
|
||||
# error: [unresolved-attribute] "Type `<module 'package'>` has no attribute `foo`"
|
||||
reveal_type(package.foo.X) # revealed: Unknown
|
||||
```
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
# Tracking imported modules
|
||||
|
||||
These tests depend on how we track which modules have been imported. There are currently two
|
||||
characteristics of our module tracking that can lead to inaccuracies:
|
||||
|
||||
- Imports are tracked on a per-file basis. At runtime, importing a submodule in one file makes that
|
||||
submodule globally available via any reference to the containing package. We will flag an error
|
||||
if a file tries to access a submodule without there being an import of that submodule _in that
|
||||
same file_.
|
||||
|
||||
This is a purposeful decision, and not one we plan to change. If a module wants to re-export some
|
||||
other module that it imports, there are ways to do that (tested below) that are blessed by the
|
||||
typing spec and that are visible to our file-scoped import tracking.
|
||||
|
||||
- Imports are tracked flow-insensitively: submodule accesses are allowed and resolved if that
|
||||
submodule is imported _anywhere in the file_. This handles the common case where all imports are
|
||||
grouped at the top of the file, and is easiest to implement. We might revisit this decision and
|
||||
track submodule imports flow-sensitively, in which case we will have to update the assertions in
|
||||
some of these tests.
|
||||
|
||||
## Import submodule later in file
|
||||
|
||||
This test highlights our flow-insensitive analysis, since we access the `a.b` submodule before it
|
||||
has been imported.
|
||||
|
||||
```py
|
||||
import a
|
||||
|
||||
# Would be an error with flow-sensitive tracking
|
||||
reveal_type(a.b.C) # revealed: Literal[C]
|
||||
|
||||
import a.b
|
||||
```
|
||||
|
||||
```py path=a/__init__.py
|
||||
```
|
||||
|
||||
```py path=a/b.py
|
||||
class C: ...
|
||||
```
|
||||
|
||||
## Rename a re-export
|
||||
|
||||
This test highlights how import tracking is local to each file, but specifically to the file where a
|
||||
containing module is first referenced. This allows the main module to see that `q.a` contains a
|
||||
submodule `b`, even though `a.b` is never imported in the main module.
|
||||
|
||||
```py
|
||||
from q import a, b
|
||||
|
||||
reveal_type(b) # revealed: <module 'a.b'>
|
||||
reveal_type(b.C) # revealed: Literal[C]
|
||||
|
||||
reveal_type(a.b) # revealed: <module 'a.b'>
|
||||
reveal_type(a.b.C) # revealed: Literal[C]
|
||||
```
|
||||
|
||||
```py path=a/__init__.py
|
||||
```
|
||||
|
||||
```py path=a/b.py
|
||||
class C: ...
|
||||
```
|
||||
|
||||
```py path=q.py
|
||||
import a as a
|
||||
import a.b as b
|
||||
```
|
||||
|
||||
## Attribute overrides submodule
|
||||
|
||||
Technically, either a submodule or a non-module attribute could shadow the other, depending on the
|
||||
ordering of when the submodule is loaded relative to the parent module's `__init__.py` file being
|
||||
evaluated. We have chosen to always have the submodule take priority. (This matches pyright's
|
||||
current behavior, and opposite of mypy's current behavior.)
|
||||
|
||||
```py
|
||||
import sub.b
|
||||
import attr.b
|
||||
|
||||
# In the Python interpreter, `attr.b` is Literal[1]
|
||||
reveal_type(sub.b) # revealed: <module 'sub.b'>
|
||||
reveal_type(attr.b) # revealed: <module 'attr.b'>
|
||||
```
|
||||
|
||||
```py path=sub/__init__.py
|
||||
b = 1
|
||||
```
|
||||
|
||||
```py path=sub/b.py
|
||||
```
|
||||
|
||||
```py path=attr/__init__.py
|
||||
from . import b as _
|
||||
|
||||
b = 1
|
||||
```
|
||||
|
||||
```py path=attr/b.py
|
||||
```
|
||||
@@ -0,0 +1,221 @@
|
||||
# Narrowing For Truthiness Checks (`if x` or `if not x`)
|
||||
|
||||
## Value Literals
|
||||
|
||||
```py
|
||||
def foo() -> Literal[0, -1, True, False, "", "foo", b"", b"bar", None] | tuple[()]:
|
||||
return 0
|
||||
|
||||
x = foo()
|
||||
|
||||
if x:
|
||||
reveal_type(x) # revealed: Literal[-1] | Literal[True] | Literal["foo"] | Literal[b"bar"]
|
||||
else:
|
||||
reveal_type(x) # revealed: Literal[0] | Literal[False] | Literal[""] | Literal[b""] | None | tuple[()]
|
||||
|
||||
if not x:
|
||||
reveal_type(x) # revealed: Literal[0] | Literal[False] | Literal[""] | Literal[b""] | None | tuple[()]
|
||||
else:
|
||||
reveal_type(x) # revealed: Literal[-1] | Literal[True] | Literal["foo"] | Literal[b"bar"]
|
||||
|
||||
if x and not x:
|
||||
reveal_type(x) # revealed: Never
|
||||
else:
|
||||
reveal_type(x) # revealed: Literal[-1, 0] | bool | Literal["", "foo"] | Literal[b"", b"bar"] | None | tuple[()]
|
||||
|
||||
if not (x and not x):
|
||||
reveal_type(x) # revealed: Literal[-1, 0] | bool | Literal["", "foo"] | Literal[b"", b"bar"] | None | tuple[()]
|
||||
else:
|
||||
reveal_type(x) # revealed: Never
|
||||
|
||||
if x or not x:
|
||||
reveal_type(x) # revealed: Literal[-1, 0] | bool | Literal["foo", ""] | Literal[b"bar", b""] | None | tuple[()]
|
||||
else:
|
||||
reveal_type(x) # revealed: Never
|
||||
|
||||
if not (x or not x):
|
||||
reveal_type(x) # revealed: Never
|
||||
else:
|
||||
reveal_type(x) # revealed: Literal[-1, 0] | bool | Literal["foo", ""] | Literal[b"bar", b""] | None | tuple[()]
|
||||
|
||||
if (isinstance(x, int) or isinstance(x, str)) and x:
|
||||
reveal_type(x) # revealed: Literal[-1] | Literal[True] | Literal["foo"]
|
||||
else:
|
||||
reveal_type(x) # revealed: Literal[b"", b"bar"] | None | tuple[()] | Literal[0] | Literal[False] | Literal[""]
|
||||
```
|
||||
|
||||
## Function Literals
|
||||
|
||||
Basically functions are always truthy.
|
||||
|
||||
```py
|
||||
def flag() -> bool:
|
||||
return True
|
||||
|
||||
def foo(hello: int) -> bytes:
|
||||
return b""
|
||||
|
||||
def bar(world: str, *args, **kwargs) -> float:
|
||||
return 0.0
|
||||
|
||||
x = foo if flag() else bar
|
||||
|
||||
if x:
|
||||
reveal_type(x) # revealed: Literal[foo, bar]
|
||||
else:
|
||||
reveal_type(x) # revealed: Never
|
||||
```
|
||||
|
||||
## Mutable Truthiness
|
||||
|
||||
### Truthiness of Instances
|
||||
|
||||
The boolean value of an instance is not always consistent. For example, `__bool__` can be customized
|
||||
to return random values, or in the case of a `list()`, the result depends on the number of elements
|
||||
in the list. Therefore, these types should not be narrowed by `if x` or `if not x`.
|
||||
|
||||
```py
|
||||
class A: ...
|
||||
class B: ...
|
||||
|
||||
def f(x: A | B):
|
||||
if x:
|
||||
reveal_type(x) # revealed: A & ~AlwaysFalsy | B & ~AlwaysFalsy
|
||||
else:
|
||||
reveal_type(x) # revealed: A & ~AlwaysTruthy | B & ~AlwaysTruthy
|
||||
|
||||
if x and not x:
|
||||
reveal_type(x) # revealed: A & ~AlwaysFalsy & ~AlwaysTruthy | B & ~AlwaysFalsy & ~AlwaysTruthy
|
||||
else:
|
||||
reveal_type(x) # revealed: A & ~AlwaysTruthy | B & ~AlwaysTruthy | A & ~AlwaysFalsy | B & ~AlwaysFalsy
|
||||
|
||||
if x or not x:
|
||||
reveal_type(x) # revealed: A & ~AlwaysFalsy | B & ~AlwaysFalsy | A & ~AlwaysTruthy | B & ~AlwaysTruthy
|
||||
else:
|
||||
reveal_type(x) # revealed: A & ~AlwaysTruthy & ~AlwaysFalsy | B & ~AlwaysTruthy & ~AlwaysFalsy
|
||||
```
|
||||
|
||||
### Truthiness of Types
|
||||
|
||||
Also, types may not be Truthy. This is because `__bool__` can be customized via a metaclass.
|
||||
Although this is a very rare case, we may consider metaclass checks in the future to handle this
|
||||
more accurately.
|
||||
|
||||
```py
|
||||
def flag() -> bool:
|
||||
return True
|
||||
|
||||
x = int if flag() else str
|
||||
reveal_type(x) # revealed: Literal[int, str]
|
||||
|
||||
if x:
|
||||
reveal_type(x) # revealed: Literal[int] & ~AlwaysFalsy | Literal[str] & ~AlwaysFalsy
|
||||
else:
|
||||
reveal_type(x) # revealed: Literal[int] & ~AlwaysTruthy | Literal[str] & ~AlwaysTruthy
|
||||
```
|
||||
|
||||
## Determined Truthiness
|
||||
|
||||
Some custom classes can have a boolean value that is consistently determined as either `True` or
|
||||
`False`, regardless of the instance's state. This is achieved by defining a `__bool__` method that
|
||||
always returns a fixed value.
|
||||
|
||||
These types can always be fully narrowed in boolean contexts, as shown below:
|
||||
|
||||
```py
|
||||
class T:
|
||||
def __bool__(self) -> Literal[True]:
|
||||
return True
|
||||
|
||||
class F:
|
||||
def __bool__(self) -> Literal[False]:
|
||||
return False
|
||||
|
||||
t = T()
|
||||
|
||||
if t:
|
||||
reveal_type(t) # revealed: T
|
||||
else:
|
||||
reveal_type(t) # revealed: Never
|
||||
|
||||
f = F()
|
||||
|
||||
if f:
|
||||
reveal_type(f) # revealed: Never
|
||||
else:
|
||||
reveal_type(f) # revealed: F
|
||||
```
|
||||
|
||||
## Narrowing Complex Intersection and Union
|
||||
|
||||
```py
|
||||
class A: ...
|
||||
class B: ...
|
||||
|
||||
def flag() -> bool:
|
||||
return True
|
||||
|
||||
def instance() -> A | B:
|
||||
return A()
|
||||
|
||||
def literals() -> Literal[0, 42, "", "hello"]:
|
||||
return 42
|
||||
|
||||
x = instance()
|
||||
y = literals()
|
||||
|
||||
if isinstance(x, str) and not isinstance(x, B):
|
||||
reveal_type(x) # revealed: A & str & ~B
|
||||
reveal_type(y) # revealed: Literal[0, 42] | Literal["", "hello"]
|
||||
|
||||
z = x if flag() else y
|
||||
|
||||
reveal_type(z) # revealed: A & str & ~B | Literal[0, 42] | Literal["", "hello"]
|
||||
|
||||
if z:
|
||||
reveal_type(z) # revealed: A & str & ~B & ~AlwaysFalsy | Literal[42] | Literal["hello"]
|
||||
else:
|
||||
reveal_type(z) # revealed: A & str & ~B & ~AlwaysTruthy | Literal[0] | Literal[""]
|
||||
```
|
||||
|
||||
## Narrowing Multiple Variables
|
||||
|
||||
```py
|
||||
def f(x: Literal[0, 1], y: Literal["", "hello"]):
|
||||
if x and y and not x and not y:
|
||||
reveal_type(x) # revealed: Never
|
||||
reveal_type(y) # revealed: Never
|
||||
else:
|
||||
# ~(x or not x) and ~(y or not y)
|
||||
reveal_type(x) # revealed: Literal[0, 1]
|
||||
reveal_type(y) # revealed: Literal["", "hello"]
|
||||
|
||||
if (x or not x) and (y and not y):
|
||||
reveal_type(x) # revealed: Literal[0, 1]
|
||||
reveal_type(y) # revealed: Never
|
||||
else:
|
||||
# ~(x or not x) or ~(y and not y)
|
||||
reveal_type(x) # revealed: Literal[0, 1]
|
||||
reveal_type(y) # revealed: Literal["", "hello"]
|
||||
```
|
||||
|
||||
## ControlFlow Merging
|
||||
|
||||
After merging control flows, when we take the union of all constraints applied in each branch, we
|
||||
should return to the original state.
|
||||
|
||||
```py
|
||||
class A: ...
|
||||
|
||||
x = A()
|
||||
|
||||
if x and not x:
|
||||
y = x
|
||||
reveal_type(y) # revealed: A & ~AlwaysFalsy & ~AlwaysTruthy
|
||||
else:
|
||||
y = x
|
||||
reveal_type(y) # revealed: A & ~AlwaysTruthy | A & ~AlwaysFalsy
|
||||
|
||||
# TODO: It should be A. We should improve UnionBuilder or IntersectionBuilder. (issue #15023)
|
||||
reveal_type(y) # revealed: A & ~AlwaysTruthy | A & ~AlwaysFalsy
|
||||
```
|
||||
@@ -61,10 +61,8 @@ class B: ...
|
||||
```py path=a/test.py
|
||||
import a.b
|
||||
|
||||
# TODO: no diagnostic
|
||||
# error: [unresolved-attribute]
|
||||
def f(c: type[a.b.C]):
|
||||
reveal_type(c) # revealed: @Todo(unsupported type[X] special form)
|
||||
reveal_type(c) # revealed: type[C]
|
||||
```
|
||||
|
||||
```py path=a/__init__.py
|
||||
|
||||
@@ -27,6 +27,7 @@ pub(crate) mod tests {
|
||||
use ruff_db::{Db as SourceDb, Upcast};
|
||||
|
||||
#[salsa::db]
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct TestDb {
|
||||
storage: salsa::Storage<Self>,
|
||||
files: Files,
|
||||
|
||||
@@ -186,6 +186,26 @@ impl ModuleName {
|
||||
self.0.push('.');
|
||||
self.0.push_str(other);
|
||||
}
|
||||
|
||||
/// Returns an iterator of this module name and all of its parent modules.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use red_knot_python_semantic::ModuleName;
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// ModuleName::new_static("foo.bar.baz").unwrap().ancestors().collect::<Vec<_>>(),
|
||||
/// vec![
|
||||
/// ModuleName::new_static("foo.bar.baz").unwrap(),
|
||||
/// ModuleName::new_static("foo.bar").unwrap(),
|
||||
/// ModuleName::new_static("foo").unwrap(),
|
||||
/// ],
|
||||
/// );
|
||||
/// ```
|
||||
pub fn ancestors(&self) -> impl Iterator<Item = Self> {
|
||||
std::iter::successors(Some(self.clone()), Self::parent)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for ModuleName {
|
||||
|
||||
@@ -7,7 +7,7 @@ use super::path::SearchPath;
|
||||
use crate::module_name::ModuleName;
|
||||
|
||||
/// Representation of a Python module.
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
#[derive(Clone, PartialEq, Eq, Hash)]
|
||||
pub struct Module {
|
||||
inner: Arc<ModuleInner>,
|
||||
}
|
||||
@@ -61,7 +61,7 @@ impl std::fmt::Debug for Module {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq)]
|
||||
#[derive(PartialEq, Eq, Hash)]
|
||||
struct ModuleInner {
|
||||
name: ModuleName,
|
||||
kind: ModuleKind,
|
||||
|
||||
@@ -73,6 +73,15 @@ enum SystemOrVendoredPathRef<'a> {
|
||||
Vendored(&'a VendoredPath),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for SystemOrVendoredPathRef<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
SystemOrVendoredPathRef::System(system) => system.fmt(f),
|
||||
SystemOrVendoredPathRef::Vendored(vendored) => vendored.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolves the module for the file with the given id.
|
||||
///
|
||||
/// Returns `None` if the file is not a module locatable via any of the known search paths.
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
use std::iter::FusedIterator;
|
||||
use std::sync::Arc;
|
||||
|
||||
use rustc_hash::{FxBuildHasher, FxHashMap};
|
||||
use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet};
|
||||
use salsa::plumbing::AsId;
|
||||
|
||||
use ruff_db::files::File;
|
||||
use ruff_db::parsed::parsed_module;
|
||||
use ruff_index::{IndexSlice, IndexVec};
|
||||
|
||||
use crate::module_name::ModuleName;
|
||||
use crate::semantic_index::ast_ids::node_key::ExpressionNodeKey;
|
||||
use crate::semantic_index::ast_ids::AstIds;
|
||||
use crate::semantic_index::builder::SemanticIndexBuilder;
|
||||
@@ -60,6 +61,22 @@ pub(crate) fn symbol_table<'db>(db: &'db dyn Db, scope: ScopeId<'db>) -> Arc<Sym
|
||||
index.symbol_table(scope.file_scope_id(db))
|
||||
}
|
||||
|
||||
/// Returns the set of modules that are imported anywhere in `file`.
|
||||
///
|
||||
/// This set only considers `import` statements, not `from...import` statements, because:
|
||||
///
|
||||
/// - In `from foo import bar`, we cannot determine whether `foo.bar` is a submodule (and is
|
||||
/// therefore imported) without looking outside the content of this file. (We could turn this
|
||||
/// into a _potentially_ imported modules set, but that would change how it's used in our type
|
||||
/// inference logic.)
|
||||
///
|
||||
/// - We cannot resolve relative imports (which aren't allowed in `import` statements) without
|
||||
/// knowing the name of the current module, and whether it's a package.
|
||||
#[salsa::tracked]
|
||||
pub(crate) fn imported_modules<'db>(db: &'db dyn Db, file: File) -> Arc<FxHashSet<ModuleName>> {
|
||||
semantic_index(db, file).imported_modules.clone()
|
||||
}
|
||||
|
||||
/// Returns the use-def map for a specific `scope`.
|
||||
///
|
||||
/// Using [`use_def_map`] over [`semantic_index`] has the advantage that
|
||||
@@ -116,6 +133,9 @@ pub(crate) struct SemanticIndex<'db> {
|
||||
/// changing a file invalidates all dependents.
|
||||
ast_ids: IndexVec<FileScopeId, AstIds>,
|
||||
|
||||
/// The set of modules that are imported anywhere within this file.
|
||||
imported_modules: Arc<FxHashSet<ModuleName>>,
|
||||
|
||||
/// Flags about the global scope (code usage impacting inference)
|
||||
has_future_annotations: bool,
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use except_handlers::TryNodeContextStackManager;
|
||||
use rustc_hash::FxHashMap;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
|
||||
use ruff_db::files::File;
|
||||
use ruff_db::parsed::ParsedModule;
|
||||
@@ -12,6 +12,7 @@ use ruff_python_ast::visitor::{walk_expr, walk_pattern, walk_stmt, Visitor};
|
||||
use ruff_python_ast::{BoolOp, Expr};
|
||||
|
||||
use crate::ast_node_ref::AstNodeRef;
|
||||
use crate::module_name::ModuleName;
|
||||
use crate::semantic_index::ast_ids::node_key::ExpressionNodeKey;
|
||||
use crate::semantic_index::ast_ids::AstIdsBuilder;
|
||||
use crate::semantic_index::definition::{
|
||||
@@ -79,6 +80,7 @@ pub(super) struct SemanticIndexBuilder<'db> {
|
||||
scopes_by_expression: FxHashMap<ExpressionNodeKey, FileScopeId>,
|
||||
definitions_by_node: FxHashMap<DefinitionNodeKey, Definition<'db>>,
|
||||
expressions_by_node: FxHashMap<ExpressionNodeKey, Expression<'db>>,
|
||||
imported_modules: FxHashSet<ModuleName>,
|
||||
}
|
||||
|
||||
impl<'db> SemanticIndexBuilder<'db> {
|
||||
@@ -105,6 +107,8 @@ impl<'db> SemanticIndexBuilder<'db> {
|
||||
scopes_by_node: FxHashMap::default(),
|
||||
definitions_by_node: FxHashMap::default(),
|
||||
expressions_by_node: FxHashMap::default(),
|
||||
|
||||
imported_modules: FxHashSet::default(),
|
||||
};
|
||||
|
||||
builder.push_scope_with_parent(NodeWithScopeRef::Module, None);
|
||||
@@ -558,6 +562,7 @@ impl<'db> SemanticIndexBuilder<'db> {
|
||||
scopes_by_expression: self.scopes_by_expression,
|
||||
scopes_by_node: self.scopes_by_node,
|
||||
use_def_maps,
|
||||
imported_modules: Arc::new(self.imported_modules),
|
||||
has_future_annotations: self.has_future_annotations,
|
||||
}
|
||||
}
|
||||
@@ -661,6 +666,12 @@ where
|
||||
}
|
||||
ast::Stmt::Import(node) => {
|
||||
for alias in &node.names {
|
||||
// Mark the imported module, and all of its parents, as being imported in this
|
||||
// file.
|
||||
if let Some(module_name) = ModuleName::new(&alias.name) {
|
||||
self.imported_modules.extend(module_name.ancestors());
|
||||
}
|
||||
|
||||
let symbol_name = if let Some(asname) = &alias.asname {
|
||||
asname.id.clone()
|
||||
} else {
|
||||
|
||||
@@ -17,6 +17,7 @@ pub(crate) enum CoreStdlibModule {
|
||||
Sys,
|
||||
#[allow(dead_code)]
|
||||
Abc, // currently only used in tests
|
||||
Collections,
|
||||
}
|
||||
|
||||
impl CoreStdlibModule {
|
||||
@@ -29,6 +30,7 @@ impl CoreStdlibModule {
|
||||
Self::TypingExtensions => "typing_extensions",
|
||||
Self::Sys => "sys",
|
||||
Self::Abc => "abc",
|
||||
Self::Collections => "collections",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,13 +14,14 @@ pub(crate) use self::infer::{
|
||||
infer_deferred_types, infer_definition_types, infer_expression_types, infer_scope_types,
|
||||
};
|
||||
pub(crate) use self::signatures::Signature;
|
||||
use crate::module_resolver::file_to_module;
|
||||
use crate::module_name::ModuleName;
|
||||
use crate::module_resolver::{file_to_module, resolve_module};
|
||||
use crate::semantic_index::ast_ids::HasScopedExpressionId;
|
||||
use crate::semantic_index::definition::Definition;
|
||||
use crate::semantic_index::symbol::{self as symbol, ScopeId, ScopedSymbolId};
|
||||
use crate::semantic_index::{
|
||||
global_scope, semantic_index, symbol_table, use_def_map, BindingWithConstraints,
|
||||
BindingWithConstraintsIterator, DeclarationsIterator,
|
||||
global_scope, imported_modules, semantic_index, symbol_table, use_def_map,
|
||||
BindingWithConstraints, BindingWithConstraintsIterator, DeclarationsIterator,
|
||||
};
|
||||
use crate::stdlib::{
|
||||
builtins_symbol, core_module_symbol, typing_extensions_symbol, CoreStdlibModule,
|
||||
@@ -291,8 +292,8 @@ type DeclaredTypeResult<'db> = Result<Type<'db>, (Type<'db>, Box<[Type<'db>]>)>;
|
||||
/// `Ok(declared_type)`. If there are conflicting declarations, returns
|
||||
/// `Err((union_of_declared_types, conflicting_declared_types))`.
|
||||
///
|
||||
/// If undeclared is a possibility, `undeclared_ty` type will be part of the return type (and may
|
||||
/// conflict with other declarations.)
|
||||
/// If undeclared is a possibility, `undeclared_ty` type will be part of the return type but it
|
||||
/// will not be considered to be conflicting with any other types.
|
||||
///
|
||||
/// # Panics
|
||||
/// Will panic if there are no declarations and no `undeclared_ty` is provided. This is a logic
|
||||
@@ -303,27 +304,31 @@ fn declarations_ty<'db>(
|
||||
declarations: DeclarationsIterator<'_, 'db>,
|
||||
undeclared_ty: Option<Type<'db>>,
|
||||
) -> DeclaredTypeResult<'db> {
|
||||
let decl_types = declarations.map(|declaration| declaration_ty(db, declaration));
|
||||
let mut declaration_types = declarations.map(|declaration| declaration_ty(db, declaration));
|
||||
|
||||
let mut all_types = undeclared_ty.into_iter().chain(decl_types);
|
||||
|
||||
let first = all_types.next().expect(
|
||||
"declarations_ty must not be called with zero declarations and no may-be-undeclared",
|
||||
);
|
||||
let Some(first) = declaration_types.next() else {
|
||||
if let Some(undeclared_ty) = undeclared_ty {
|
||||
// Short-circuit to return the undeclared type if there are no declarations.
|
||||
return Ok(undeclared_ty);
|
||||
}
|
||||
panic!("declarations_ty must not be called with zero declarations and no undeclared_ty");
|
||||
};
|
||||
|
||||
let mut conflicting: Vec<Type<'db>> = vec![];
|
||||
let declared_ty = if let Some(second) = all_types.next() {
|
||||
let mut builder = UnionBuilder::new(db).add(first);
|
||||
for other in [second].into_iter().chain(all_types) {
|
||||
if !first.is_equivalent_to(db, other) {
|
||||
conflicting.push(other);
|
||||
}
|
||||
builder = builder.add(other);
|
||||
let mut builder = UnionBuilder::new(db).add(first);
|
||||
for other in declaration_types {
|
||||
if !first.is_equivalent_to(db, other) {
|
||||
conflicting.push(other);
|
||||
}
|
||||
builder.build()
|
||||
} else {
|
||||
first
|
||||
};
|
||||
builder = builder.add(other);
|
||||
}
|
||||
// Avoid considering the undeclared type for the conflicting declaration diagnostics. It
|
||||
// should still be part of the declared type.
|
||||
if let Some(undeclared_ty) = undeclared_ty {
|
||||
builder = builder.add(undeclared_ty);
|
||||
}
|
||||
let declared_ty = builder.build();
|
||||
|
||||
if conflicting.is_empty() {
|
||||
Ok(declared_ty)
|
||||
} else {
|
||||
@@ -413,7 +418,7 @@ pub enum Type<'db> {
|
||||
/// A specific function object
|
||||
FunctionLiteral(FunctionType<'db>),
|
||||
/// A specific module object
|
||||
ModuleLiteral(File),
|
||||
ModuleLiteral(ModuleLiteralType<'db>),
|
||||
/// A specific class object
|
||||
ClassLiteral(ClassLiteralType<'db>),
|
||||
// The set of all class objects that are subclasses of the given class (C), spelled `type[C]`.
|
||||
@@ -426,6 +431,11 @@ pub enum Type<'db> {
|
||||
Union(UnionType<'db>),
|
||||
/// The set of objects in all of the types in the intersection
|
||||
Intersection(IntersectionType<'db>),
|
||||
/// Represents objects whose `__bool__` method is deterministic:
|
||||
/// - `AlwaysTruthy`: `__bool__` always returns `True`
|
||||
/// - `AlwaysFalsy`: `__bool__` always returns `False`
|
||||
AlwaysTruthy,
|
||||
AlwaysFalsy,
|
||||
/// An integer literal
|
||||
IntLiteral(i64),
|
||||
/// A boolean literal, either `True` or `False`.
|
||||
@@ -446,6 +456,10 @@ pub enum Type<'db> {
|
||||
}
|
||||
|
||||
impl<'db> Type<'db> {
|
||||
pub const fn is_unknown(&self) -> bool {
|
||||
matches!(self, Type::Unknown)
|
||||
}
|
||||
|
||||
pub const fn is_never(&self) -> bool {
|
||||
matches!(self, Type::Never)
|
||||
}
|
||||
@@ -475,15 +489,19 @@ impl<'db> Type<'db> {
|
||||
matches!(self, Type::ClassLiteral(..))
|
||||
}
|
||||
|
||||
pub const fn into_module_literal(self) -> Option<File> {
|
||||
pub fn module_literal(db: &'db dyn Db, importing_file: File, submodule: Module) -> Self {
|
||||
Self::ModuleLiteral(ModuleLiteralType::new(db, importing_file, submodule))
|
||||
}
|
||||
|
||||
pub const fn into_module_literal(self) -> Option<ModuleLiteralType<'db>> {
|
||||
match self {
|
||||
Type::ModuleLiteral(file) => Some(file),
|
||||
Type::ModuleLiteral(module) => Some(module),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn expect_module_literal(self) -> File {
|
||||
pub fn expect_module_literal(self) -> ModuleLiteralType<'db> {
|
||||
self.into_module_literal()
|
||||
.expect("Expected a Type::ModuleLiteral variant")
|
||||
}
|
||||
@@ -704,6 +722,15 @@ impl<'db> Type<'db> {
|
||||
.all(|&neg_ty| self.is_disjoint_from(db, neg_ty))
|
||||
}
|
||||
|
||||
// Note that the definition of `Type::AlwaysFalsy` depends on the return value of `__bool__`.
|
||||
// If `__bool__` always returns True or False, it can be treated as a subtype of `AlwaysTruthy` or `AlwaysFalsy`, respectively.
|
||||
(left, Type::AlwaysFalsy) => matches!(left.bool(db), Truthiness::AlwaysFalse),
|
||||
(left, Type::AlwaysTruthy) => matches!(left.bool(db), Truthiness::AlwaysTrue),
|
||||
// Currently, the only supertype of `AlwaysFalsy` and `AlwaysTruthy` is the universal set (object instance).
|
||||
(Type::AlwaysFalsy | Type::AlwaysTruthy, _) => {
|
||||
target.is_equivalent_to(db, KnownClass::Object.to_instance(db))
|
||||
}
|
||||
|
||||
// All `StringLiteral` types are a subtype of `LiteralString`.
|
||||
(Type::StringLiteral(_), Type::LiteralString) => true,
|
||||
|
||||
@@ -1092,6 +1119,16 @@ impl<'db> Type<'db> {
|
||||
false
|
||||
}
|
||||
|
||||
(Type::AlwaysTruthy, ty) | (ty, Type::AlwaysTruthy) => {
|
||||
// `Truthiness::Ambiguous` may include `AlwaysTrue` as a subset, so it's not guaranteed to be disjoint.
|
||||
// Thus, they are only disjoint if `ty.bool() == AlwaysFalse`.
|
||||
matches!(ty.bool(db), Truthiness::AlwaysFalse)
|
||||
}
|
||||
(Type::AlwaysFalsy, ty) | (ty, Type::AlwaysFalsy) => {
|
||||
// Similarly, they are only disjoint if `ty.bool() == AlwaysTrue`.
|
||||
matches!(ty.bool(db), Truthiness::AlwaysTrue)
|
||||
}
|
||||
|
||||
(Type::KnownInstance(left), right) => {
|
||||
left.instance_fallback(db).is_disjoint_from(db, right)
|
||||
}
|
||||
@@ -1225,7 +1262,9 @@ impl<'db> Type<'db> {
|
||||
| Type::LiteralString
|
||||
| Type::BytesLiteral(_)
|
||||
| Type::SliceLiteral(_)
|
||||
| Type::KnownInstance(_) => true,
|
||||
| Type::KnownInstance(_)
|
||||
| Type::AlwaysFalsy
|
||||
| Type::AlwaysTruthy => true,
|
||||
Type::SubclassOf(SubclassOfType { base }) => matches!(base, ClassBase::Class(_)),
|
||||
Type::ClassLiteral(_) | Type::Instance(_) => {
|
||||
// TODO: Ideally, we would iterate over the MRO of the class, check if all
|
||||
@@ -1327,6 +1366,7 @@ impl<'db> Type<'db> {
|
||||
//
|
||||
false
|
||||
}
|
||||
Type::AlwaysTruthy | Type::AlwaysFalsy => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1380,6 +1420,11 @@ impl<'db> Type<'db> {
|
||||
| KnownClass::ModuleType
|
||||
| KnownClass::FunctionType
|
||||
| KnownClass::SpecialForm
|
||||
| KnownClass::ChainMap
|
||||
| KnownClass::Counter
|
||||
| KnownClass::DefaultDict
|
||||
| KnownClass::Deque
|
||||
| KnownClass::OrderedDict
|
||||
| KnownClass::StdlibAlias
|
||||
| KnownClass::TypeVar,
|
||||
) => false,
|
||||
@@ -1392,7 +1437,9 @@ impl<'db> Type<'db> {
|
||||
| Type::Todo(_)
|
||||
| Type::Union(..)
|
||||
| Type::Intersection(..)
|
||||
| Type::LiteralString => false,
|
||||
| Type::LiteralString
|
||||
| Type::AlwaysTruthy
|
||||
| Type::AlwaysFalsy => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1418,7 +1465,7 @@ impl<'db> Type<'db> {
|
||||
// TODO: attribute lookup on function type
|
||||
todo_type!().into()
|
||||
}
|
||||
Type::ModuleLiteral(file) => {
|
||||
Type::ModuleLiteral(module_ref) => {
|
||||
// `__dict__` is a very special member that is never overridden by module globals;
|
||||
// we should always look it up directly as an attribute on `types.ModuleType`,
|
||||
// never in the global scope of the module.
|
||||
@@ -1428,7 +1475,30 @@ impl<'db> Type<'db> {
|
||||
.member(db, "__dict__");
|
||||
}
|
||||
|
||||
let global_lookup = symbol(db, global_scope(db, *file), name);
|
||||
// If the file that originally imported the module has also imported a submodule
|
||||
// named [name], then the result is (usually) that submodule, even if the module
|
||||
// also defines a (non-module) symbol with that name.
|
||||
//
|
||||
// Note that technically, either the submodule or the non-module symbol could take
|
||||
// priority, depending on the ordering of when the submodule is loaded relative to
|
||||
// the parent module's `__init__.py` file being evaluated. That said, we have
|
||||
// chosen to always have the submodule take priority. (This matches pyright's
|
||||
// current behavior, and opposite of mypy's current behavior.)
|
||||
if let Some(submodule_name) = ModuleName::new(name) {
|
||||
let importing_file = module_ref.importing_file(db);
|
||||
let imported_submodules = imported_modules(db, importing_file);
|
||||
let mut full_submodule_name = module_ref.module(db).name().clone();
|
||||
full_submodule_name.extend(&submodule_name);
|
||||
if imported_submodules.contains(&full_submodule_name) {
|
||||
if let Some(submodule) = resolve_module(db, &full_submodule_name) {
|
||||
let submodule_ty = Type::module_literal(db, importing_file, submodule);
|
||||
return Symbol::Type(submodule_ty, Boundness::Bound);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let global_lookup =
|
||||
symbol(db, global_scope(db, module_ref.module(db).file()), name);
|
||||
|
||||
// If it's unbound, check if it's present as an instance on `types.ModuleType`
|
||||
// or `builtins.object`.
|
||||
@@ -1537,6 +1607,10 @@ impl<'db> Type<'db> {
|
||||
// TODO: implement tuple methods
|
||||
todo_type!().into()
|
||||
}
|
||||
Type::AlwaysTruthy | Type::AlwaysFalsy => {
|
||||
// TODO return `Callable[[], Literal[True/False]]` for `__bool__` access
|
||||
KnownClass::Object.to_instance(db).member(db, name)
|
||||
}
|
||||
&todo @ Type::Todo(_) => todo.into(),
|
||||
}
|
||||
}
|
||||
@@ -1559,6 +1633,8 @@ impl<'db> Type<'db> {
|
||||
// TODO: see above
|
||||
Truthiness::Ambiguous
|
||||
}
|
||||
Type::AlwaysTruthy => Truthiness::AlwaysTrue,
|
||||
Type::AlwaysFalsy => Truthiness::AlwaysFalse,
|
||||
instance_ty @ Type::Instance(InstanceType { class }) => {
|
||||
if class.is_known(db, KnownClass::NoneType) {
|
||||
Truthiness::AlwaysFalse
|
||||
@@ -1871,7 +1947,9 @@ impl<'db> Type<'db> {
|
||||
| Type::StringLiteral(_)
|
||||
| Type::SliceLiteral(_)
|
||||
| Type::Tuple(_)
|
||||
| Type::LiteralString => Type::Unknown,
|
||||
| Type::LiteralString
|
||||
| Type::AlwaysTruthy
|
||||
| Type::AlwaysFalsy => Type::Unknown,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1892,6 +1970,28 @@ impl<'db> Type<'db> {
|
||||
// We treat `typing.Type` exactly the same as `builtins.type`:
|
||||
Type::KnownInstance(KnownInstanceType::Type) => Ok(KnownClass::Type.to_instance(db)),
|
||||
Type::KnownInstance(KnownInstanceType::Tuple) => Ok(KnownClass::Tuple.to_instance(db)),
|
||||
|
||||
// Legacy `typing` aliases
|
||||
Type::KnownInstance(KnownInstanceType::List) => Ok(KnownClass::List.to_instance(db)),
|
||||
Type::KnownInstance(KnownInstanceType::Dict) => Ok(KnownClass::Dict.to_instance(db)),
|
||||
Type::KnownInstance(KnownInstanceType::Set) => Ok(KnownClass::Set.to_instance(db)),
|
||||
Type::KnownInstance(KnownInstanceType::FrozenSet) => {
|
||||
Ok(KnownClass::FrozenSet.to_instance(db))
|
||||
}
|
||||
Type::KnownInstance(KnownInstanceType::ChainMap) => {
|
||||
Ok(KnownClass::ChainMap.to_instance(db))
|
||||
}
|
||||
Type::KnownInstance(KnownInstanceType::Counter) => {
|
||||
Ok(KnownClass::Counter.to_instance(db))
|
||||
}
|
||||
Type::KnownInstance(KnownInstanceType::DefaultDict) => {
|
||||
Ok(KnownClass::DefaultDict.to_instance(db))
|
||||
}
|
||||
Type::KnownInstance(KnownInstanceType::Deque) => Ok(KnownClass::Deque.to_instance(db)),
|
||||
Type::KnownInstance(KnownInstanceType::OrderedDict) => {
|
||||
Ok(KnownClass::OrderedDict.to_instance(db))
|
||||
}
|
||||
|
||||
Type::Union(union) => {
|
||||
let mut builder = UnionBuilder::new(db);
|
||||
let mut invalid_expressions = smallvec::SmallVec::default();
|
||||
@@ -2011,6 +2111,7 @@ impl<'db> Type<'db> {
|
||||
ClassBase::try_from_ty(db, todo_type!("Intersection meta-type"))
|
||||
.expect("Type::Todo should be a valid ClassBase"),
|
||||
),
|
||||
Type::AlwaysTruthy | Type::AlwaysFalsy => KnownClass::Type.to_instance(db),
|
||||
Type::Todo(todo) => Type::subclass_of_base(ClassBase::Todo(*todo)),
|
||||
}
|
||||
}
|
||||
@@ -2153,6 +2254,12 @@ pub enum KnownClass {
|
||||
TypeVar,
|
||||
TypeAliasType,
|
||||
NoDefaultType,
|
||||
// Collections
|
||||
ChainMap,
|
||||
Counter,
|
||||
DefaultDict,
|
||||
Deque,
|
||||
OrderedDict,
|
||||
// sys
|
||||
VersionInfo,
|
||||
}
|
||||
@@ -2183,6 +2290,11 @@ impl<'db> KnownClass {
|
||||
Self::TypeVar => "TypeVar",
|
||||
Self::TypeAliasType => "TypeAliasType",
|
||||
Self::NoDefaultType => "_NoDefaultType",
|
||||
Self::ChainMap => "ChainMap",
|
||||
Self::Counter => "Counter",
|
||||
Self::DefaultDict => "defaultdict",
|
||||
Self::Deque => "deque",
|
||||
Self::OrderedDict => "OrderedDict",
|
||||
// For example, `typing.List` is defined as `List = _Alias()` in typeshed
|
||||
Self::StdlibAlias => "_Alias",
|
||||
// This is the name the type of `sys.version_info` has in typeshed,
|
||||
@@ -2246,6 +2358,11 @@ impl<'db> KnownClass {
|
||||
CoreStdlibModule::TypingExtensions
|
||||
}
|
||||
}
|
||||
Self::ChainMap
|
||||
| Self::Counter
|
||||
| Self::DefaultDict
|
||||
| Self::Deque
|
||||
| Self::OrderedDict => CoreStdlibModule::Collections,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2273,6 +2390,11 @@ impl<'db> KnownClass {
|
||||
| Self::ModuleType
|
||||
| Self::FunctionType
|
||||
| Self::SpecialForm
|
||||
| Self::ChainMap
|
||||
| Self::Counter
|
||||
| Self::DefaultDict
|
||||
| Self::Deque
|
||||
| Self::OrderedDict
|
||||
| Self::StdlibAlias
|
||||
| Self::BaseException
|
||||
| Self::BaseExceptionGroup
|
||||
@@ -2305,6 +2427,11 @@ impl<'db> KnownClass {
|
||||
"ModuleType" => Self::ModuleType,
|
||||
"FunctionType" => Self::FunctionType,
|
||||
"TypeAliasType" => Self::TypeAliasType,
|
||||
"ChainMap" => Self::ChainMap,
|
||||
"Counter" => Self::Counter,
|
||||
"defaultdict" => Self::DefaultDict,
|
||||
"deque" => Self::Deque,
|
||||
"OrderedDict" => Self::OrderedDict,
|
||||
"_Alias" => Self::StdlibAlias,
|
||||
"_SpecialForm" => Self::SpecialForm,
|
||||
"_NoDefaultType" => Self::NoDefaultType,
|
||||
@@ -2336,6 +2463,11 @@ impl<'db> KnownClass {
|
||||
| Self::Dict
|
||||
| Self::Slice
|
||||
| Self::GenericAlias
|
||||
| Self::ChainMap
|
||||
| Self::Counter
|
||||
| Self::DefaultDict
|
||||
| Self::Deque
|
||||
| Self::OrderedDict
|
||||
| Self::StdlibAlias // no equivalent class exists in typing_extensions, nor ever will
|
||||
| Self::ModuleType
|
||||
| Self::VersionInfo
|
||||
@@ -2371,6 +2503,24 @@ pub enum KnownInstanceType<'db> {
|
||||
Any,
|
||||
/// The symbol `typing.Tuple` (which can also be found as `typing_extensions.Tuple`)
|
||||
Tuple,
|
||||
/// The symbol `typing.List` (which can also be found as `typing_extensions.List`)
|
||||
List,
|
||||
/// The symbol `typing.Dict` (which can also be found as `typing_extensions.Dict`)
|
||||
Dict,
|
||||
/// The symbol `typing.Set` (which can also be found as `typing_extensions.Set`)
|
||||
Set,
|
||||
/// The symbol `typing.FrozenSet` (which can also be found as `typing_extensions.FrozenSet`)
|
||||
FrozenSet,
|
||||
/// The symbol `typing.ChainMap` (which can also be found as `typing_extensions.ChainMap`)
|
||||
ChainMap,
|
||||
/// The symbol `typing.Counter` (which can also be found as `typing_extensions.Counter`)
|
||||
Counter,
|
||||
/// The symbol `typing.DefaultDict` (which can also be found as `typing_extensions.DefaultDict`)
|
||||
DefaultDict,
|
||||
/// The symbol `typing.Deque` (which can also be found as `typing_extensions.Deque`)
|
||||
Deque,
|
||||
/// The symbol `typing.OrderedDict` (which can also be found as `typing_extensions.OrderedDict`)
|
||||
OrderedDict,
|
||||
/// The symbol `typing.Type` (which can also be found as `typing_extensions.Type`)
|
||||
Type,
|
||||
/// A single instance of `typing.TypeVar`
|
||||
@@ -2391,15 +2541,6 @@ pub enum KnownInstanceType<'db> {
|
||||
TypeAlias,
|
||||
TypeGuard,
|
||||
TypeIs,
|
||||
List,
|
||||
Dict,
|
||||
DefaultDict,
|
||||
Set,
|
||||
FrozenSet,
|
||||
Counter,
|
||||
Deque,
|
||||
ChainMap,
|
||||
OrderedDict,
|
||||
ReadOnly,
|
||||
// TODO: fill this enum out with more special forms, etc.
|
||||
}
|
||||
@@ -2860,6 +3001,18 @@ impl KnownFunction {
|
||||
}
|
||||
}
|
||||
|
||||
#[salsa::interned]
|
||||
pub struct ModuleLiteralType<'db> {
|
||||
/// The file in which this module was imported.
|
||||
///
|
||||
/// We need this in order to know which submodules should be attached to it as attributes
|
||||
/// (because the submodules were also imported in this file).
|
||||
pub importing_file: File,
|
||||
|
||||
/// The imported module.
|
||||
pub module: Module,
|
||||
}
|
||||
|
||||
/// Representation of a runtime class object.
|
||||
///
|
||||
/// Does not in itself represent a type,
|
||||
@@ -3443,6 +3596,8 @@ pub(crate) mod tests {
|
||||
SubclassOfAbcClass(&'static str),
|
||||
StdlibModule(CoreStdlibModule),
|
||||
SliceLiteral(i32, i32, i32),
|
||||
AlwaysTruthy,
|
||||
AlwaysFalsy,
|
||||
}
|
||||
|
||||
impl Ty {
|
||||
@@ -3501,7 +3656,8 @@ pub(crate) mod tests {
|
||||
.class,
|
||||
),
|
||||
Ty::StdlibModule(module) => {
|
||||
Type::ModuleLiteral(resolve_module(db, &module.name()).unwrap().file())
|
||||
let module = resolve_module(db, &module.name()).unwrap();
|
||||
Type::module_literal(db, module.file(), module)
|
||||
}
|
||||
Ty::SliceLiteral(start, stop, step) => Type::SliceLiteral(SliceLiteralType::new(
|
||||
db,
|
||||
@@ -3509,6 +3665,8 @@ pub(crate) mod tests {
|
||||
Some(stop),
|
||||
Some(step),
|
||||
)),
|
||||
Ty::AlwaysTruthy => Type::AlwaysTruthy,
|
||||
Ty::AlwaysFalsy => Type::AlwaysFalsy,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3647,6 +3805,12 @@ pub(crate) mod tests {
|
||||
)]
|
||||
#[test_case(Ty::SliceLiteral(1, 2, 3), Ty::BuiltinInstance("slice"))]
|
||||
#[test_case(Ty::SubclassOfBuiltinClass("str"), Ty::Intersection{pos: vec![], neg: vec![Ty::None]})]
|
||||
#[test_case(Ty::IntLiteral(1), Ty::AlwaysTruthy)]
|
||||
#[test_case(Ty::IntLiteral(0), Ty::AlwaysFalsy)]
|
||||
#[test_case(Ty::AlwaysTruthy, Ty::BuiltinInstance("object"))]
|
||||
#[test_case(Ty::AlwaysFalsy, Ty::BuiltinInstance("object"))]
|
||||
#[test_case(Ty::Never, Ty::AlwaysTruthy)]
|
||||
#[test_case(Ty::Never, Ty::AlwaysFalsy)]
|
||||
fn is_subtype_of(from: Ty, to: Ty) {
|
||||
let db = setup_db();
|
||||
assert!(from.into_type(&db).is_subtype_of(&db, to.into_type(&db)));
|
||||
@@ -3681,6 +3845,10 @@ pub(crate) mod tests {
|
||||
#[test_case(Ty::BuiltinClassLiteral("str"), Ty::SubclassOfAny)]
|
||||
#[test_case(Ty::AbcInstance("ABCMeta"), Ty::SubclassOfBuiltinClass("type"))]
|
||||
#[test_case(Ty::SubclassOfBuiltinClass("str"), Ty::BuiltinClassLiteral("str"))]
|
||||
#[test_case(Ty::IntLiteral(1), Ty::AlwaysFalsy)]
|
||||
#[test_case(Ty::IntLiteral(0), Ty::AlwaysTruthy)]
|
||||
#[test_case(Ty::BuiltinInstance("str"), Ty::AlwaysTruthy)]
|
||||
#[test_case(Ty::BuiltinInstance("str"), Ty::AlwaysFalsy)]
|
||||
fn is_not_subtype_of(from: Ty, to: Ty) {
|
||||
let db = setup_db();
|
||||
assert!(!from.into_type(&db).is_subtype_of(&db, to.into_type(&db)));
|
||||
@@ -3815,6 +3983,7 @@ pub(crate) mod tests {
|
||||
#[test_case(Ty::Tuple(vec![]), Ty::BuiltinClassLiteral("object"))]
|
||||
#[test_case(Ty::SubclassOfBuiltinClass("object"), Ty::None)]
|
||||
#[test_case(Ty::SubclassOfBuiltinClass("str"), Ty::LiteralString)]
|
||||
#[test_case(Ty::AlwaysFalsy, Ty::AlwaysTruthy)]
|
||||
fn is_disjoint_from(a: Ty, b: Ty) {
|
||||
let db = setup_db();
|
||||
let a = a.into_type(&db);
|
||||
@@ -3845,6 +4014,8 @@ pub(crate) mod tests {
|
||||
#[test_case(Ty::BuiltinClassLiteral("str"), Ty::BuiltinInstance("type"))]
|
||||
#[test_case(Ty::BuiltinClassLiteral("str"), Ty::SubclassOfAny)]
|
||||
#[test_case(Ty::AbcClassLiteral("ABC"), Ty::AbcInstance("ABCMeta"))]
|
||||
#[test_case(Ty::BuiltinInstance("str"), Ty::AlwaysTruthy)]
|
||||
#[test_case(Ty::BuiltinInstance("str"), Ty::AlwaysFalsy)]
|
||||
fn is_not_disjoint_from(a: Ty, b: Ty) {
|
||||
let db = setup_db();
|
||||
let a = a.into_type(&db);
|
||||
|
||||
@@ -30,6 +30,8 @@ use crate::types::{InstanceType, IntersectionType, KnownClass, Type, UnionType};
|
||||
use crate::{Db, FxOrderSet};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use super::Truthiness;
|
||||
|
||||
pub(crate) struct UnionBuilder<'db> {
|
||||
elements: Vec<Type<'db>>,
|
||||
db: &'db dyn Db,
|
||||
@@ -243,15 +245,22 @@ impl<'db> InnerIntersectionBuilder<'db> {
|
||||
}
|
||||
} else {
|
||||
// ~Literal[True] & bool = Literal[False]
|
||||
// ~AlwaysTruthy & bool = Literal[False]
|
||||
if let Type::Instance(InstanceType { class }) = new_positive {
|
||||
if class.is_known(db, KnownClass::Bool) {
|
||||
if let Some(&Type::BooleanLiteral(value)) = self
|
||||
if let Some(new_type) = self
|
||||
.negative
|
||||
.iter()
|
||||
.find(|element| element.is_boolean_literal())
|
||||
.find(|element| {
|
||||
element.is_boolean_literal()
|
||||
| matches!(element, Type::AlwaysFalsy | Type::AlwaysTruthy)
|
||||
})
|
||||
.map(|element| {
|
||||
Type::BooleanLiteral(element.bool(db) != Truthiness::AlwaysTrue)
|
||||
})
|
||||
{
|
||||
*self = Self::default();
|
||||
self.positive.insert(Type::BooleanLiteral(!value));
|
||||
self.positive.insert(new_type);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -318,15 +327,15 @@ impl<'db> InnerIntersectionBuilder<'db> {
|
||||
// simplify the representation.
|
||||
self.add_positive(db, ty);
|
||||
}
|
||||
// ~Literal[True] & bool = Literal[False]
|
||||
Type::BooleanLiteral(bool)
|
||||
if self
|
||||
.positive
|
||||
.iter()
|
||||
.any(|pos| *pos == KnownClass::Bool.to_instance(db)) =>
|
||||
// bool & ~Literal[True] = Literal[False]
|
||||
// bool & ~AlwaysTruthy = Literal[False]
|
||||
Type::BooleanLiteral(_) | Type::AlwaysFalsy | Type::AlwaysTruthy
|
||||
if self.positive.contains(&KnownClass::Bool.to_instance(db)) =>
|
||||
{
|
||||
*self = Self::default();
|
||||
self.positive.insert(Type::BooleanLiteral(!bool));
|
||||
self.positive.insert(Type::BooleanLiteral(
|
||||
new_negative.bool(db) != Truthiness::AlwaysTrue,
|
||||
));
|
||||
}
|
||||
_ => {
|
||||
let mut to_remove = SmallVec::<[usize; 1]>::new();
|
||||
@@ -380,7 +389,7 @@ mod tests {
|
||||
use super::{IntersectionBuilder, IntersectionType, Type, UnionType};
|
||||
|
||||
use crate::db::tests::{setup_db, TestDb};
|
||||
use crate::types::{global_symbol, todo_type, KnownClass, UnionBuilder};
|
||||
use crate::types::{global_symbol, todo_type, KnownClass, Truthiness, UnionBuilder};
|
||||
|
||||
use ruff_db::files::system_path_to_file;
|
||||
use ruff_db::system::DbWithTestSystem;
|
||||
@@ -997,42 +1006,43 @@ mod tests {
|
||||
assert_eq!(ty, expected);
|
||||
}
|
||||
|
||||
#[test_case(true)]
|
||||
#[test_case(false)]
|
||||
fn build_intersection_simplify_split_bool(bool_value: bool) {
|
||||
#[test_case(Type::BooleanLiteral(true))]
|
||||
#[test_case(Type::BooleanLiteral(false))]
|
||||
#[test_case(Type::AlwaysTruthy)]
|
||||
#[test_case(Type::AlwaysFalsy)]
|
||||
fn build_intersection_simplify_split_bool(t_splitter: Type) {
|
||||
let db = setup_db();
|
||||
|
||||
let t_bool = KnownClass::Bool.to_instance(&db);
|
||||
let t_boolean_literal = Type::BooleanLiteral(bool_value);
|
||||
let bool_value = t_splitter.bool(&db) == Truthiness::AlwaysTrue;
|
||||
|
||||
// We add t_object in various orders (in first or second position) in
|
||||
// the tests below to ensure that the boolean simplification eliminates
|
||||
// everything from the intersection, not just `bool`.
|
||||
let t_object = KnownClass::Object.to_instance(&db);
|
||||
let t_bool = KnownClass::Bool.to_instance(&db);
|
||||
|
||||
let ty = IntersectionBuilder::new(&db)
|
||||
.add_positive(t_object)
|
||||
.add_positive(t_bool)
|
||||
.add_negative(t_boolean_literal)
|
||||
.add_negative(t_splitter)
|
||||
.build();
|
||||
assert_eq!(ty, Type::BooleanLiteral(!bool_value));
|
||||
|
||||
let ty = IntersectionBuilder::new(&db)
|
||||
.add_positive(t_bool)
|
||||
.add_positive(t_object)
|
||||
.add_negative(t_boolean_literal)
|
||||
.add_negative(t_splitter)
|
||||
.build();
|
||||
assert_eq!(ty, Type::BooleanLiteral(!bool_value));
|
||||
|
||||
let ty = IntersectionBuilder::new(&db)
|
||||
.add_positive(t_object)
|
||||
.add_negative(t_boolean_literal)
|
||||
.add_negative(t_splitter)
|
||||
.add_positive(t_bool)
|
||||
.build();
|
||||
assert_eq!(ty, Type::BooleanLiteral(!bool_value));
|
||||
|
||||
let ty = IntersectionBuilder::new(&db)
|
||||
.add_negative(t_boolean_literal)
|
||||
.add_negative(t_splitter)
|
||||
.add_positive(t_object)
|
||||
.add_positive(t_bool)
|
||||
.build();
|
||||
|
||||
@@ -70,7 +70,9 @@ impl<'db> ClassBase<'db> {
|
||||
| Type::Tuple(_)
|
||||
| Type::SliceLiteral(_)
|
||||
| Type::ModuleLiteral(_)
|
||||
| Type::SubclassOf(_) => None,
|
||||
| Type::SubclassOf(_)
|
||||
| Type::AlwaysFalsy
|
||||
| Type::AlwaysTruthy => None,
|
||||
Type::KnownInstance(known_instance) => match known_instance {
|
||||
KnownInstanceType::TypeVar(_)
|
||||
| KnownInstanceType::TypeAliasType(_)
|
||||
@@ -112,15 +114,24 @@ impl<'db> ClassBase<'db> {
|
||||
KnownInstanceType::FrozenSet => {
|
||||
Self::try_from_ty(db, KnownClass::FrozenSet.to_class_literal(db))
|
||||
}
|
||||
KnownInstanceType::Callable
|
||||
| KnownInstanceType::ChainMap
|
||||
| KnownInstanceType::Counter
|
||||
| KnownInstanceType::DefaultDict
|
||||
| KnownInstanceType::Deque
|
||||
| KnownInstanceType::OrderedDict => Self::try_from_ty(
|
||||
db,
|
||||
todo_type!("Support for more typing aliases as base classes"),
|
||||
),
|
||||
KnownInstanceType::ChainMap => {
|
||||
Self::try_from_ty(db, KnownClass::ChainMap.to_class_literal(db))
|
||||
}
|
||||
KnownInstanceType::Counter => {
|
||||
Self::try_from_ty(db, KnownClass::Counter.to_class_literal(db))
|
||||
}
|
||||
KnownInstanceType::DefaultDict => {
|
||||
Self::try_from_ty(db, KnownClass::DefaultDict.to_class_literal(db))
|
||||
}
|
||||
KnownInstanceType::Deque => {
|
||||
Self::try_from_ty(db, KnownClass::Deque.to_class_literal(db))
|
||||
}
|
||||
KnownInstanceType::OrderedDict => {
|
||||
Self::try_from_ty(db, KnownClass::OrderedDict.to_class_literal(db))
|
||||
}
|
||||
KnownInstanceType::Callable => {
|
||||
Self::try_from_ty(db, todo_type!("Support for Callable as a base class"))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,8 +79,8 @@ impl Display for DisplayRepresentation<'_> {
|
||||
// `[Type::Todo]`'s display should be explicit that is not a valid display of
|
||||
// any other type
|
||||
Type::Todo(todo) => write!(f, "@Todo{todo}"),
|
||||
Type::ModuleLiteral(file) => {
|
||||
write!(f, "<module '{:?}'>", file.path(self.db))
|
||||
Type::ModuleLiteral(module) => {
|
||||
write!(f, "<module '{}'>", module.module(self.db).name())
|
||||
}
|
||||
// TODO functions and classes should display using a fully qualified name
|
||||
Type::ClassLiteral(ClassLiteralType { class }) => f.write_str(class.name(self.db)),
|
||||
@@ -140,6 +140,8 @@ impl Display for DisplayRepresentation<'_> {
|
||||
}
|
||||
f.write_str("]")
|
||||
}
|
||||
Type::AlwaysTruthy => f.write_str("AlwaysTruthy"),
|
||||
Type::AlwaysFalsy => f.write_str("AlwaysFalsy"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,7 +124,6 @@ pub(crate) fn infer_definition_types<'db>(
|
||||
let file = definition.file(db);
|
||||
let _span = tracing::trace_span!(
|
||||
"infer_definition_types",
|
||||
definition = ?definition.as_id(),
|
||||
range = ?definition.kind(db).range(),
|
||||
file = %file.path(db)
|
||||
)
|
||||
@@ -866,6 +865,14 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
self.types.bindings.insert(definition, inferred_ty);
|
||||
}
|
||||
|
||||
fn add_unknown_declaration_with_binding(
|
||||
&mut self,
|
||||
node: AnyNodeRef,
|
||||
definition: Definition<'db>,
|
||||
) {
|
||||
self.add_declaration_with_binding(node, definition, Type::Unknown, Type::Unknown);
|
||||
}
|
||||
|
||||
fn infer_module(&mut self, module: &ast::ModModule) {
|
||||
self.infer_body(&module.body);
|
||||
}
|
||||
@@ -2116,22 +2123,45 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
let ast::Alias {
|
||||
range: _,
|
||||
name,
|
||||
asname: _,
|
||||
asname,
|
||||
} = alias;
|
||||
|
||||
let module_ty = if let Some(module_name) = ModuleName::new(name) {
|
||||
if let Some(module) = self.module_ty_from_name(&module_name) {
|
||||
module
|
||||
} else {
|
||||
self.diagnostics.add_unresolved_module(alias, 0, Some(name));
|
||||
Type::Unknown
|
||||
}
|
||||
} else {
|
||||
// The name of the module being imported
|
||||
let Some(full_module_name) = ModuleName::new(name) else {
|
||||
tracing::debug!("Failed to resolve import due to invalid syntax");
|
||||
Type::Unknown
|
||||
self.add_unknown_declaration_with_binding(alias.into(), definition);
|
||||
return;
|
||||
};
|
||||
|
||||
self.add_declaration_with_binding(alias.into(), definition, module_ty, module_ty);
|
||||
// Resolve the module being imported.
|
||||
let Some(full_module_ty) = self.module_ty_from_name(&full_module_name) else {
|
||||
self.diagnostics.add_unresolved_module(alias, 0, Some(name));
|
||||
self.add_unknown_declaration_with_binding(alias.into(), definition);
|
||||
return;
|
||||
};
|
||||
|
||||
let binding_ty = if asname.is_some() {
|
||||
// If we are renaming the imported module via an `as` clause, then we bind the resolved
|
||||
// module's type to that name, even if that module is nested.
|
||||
full_module_ty
|
||||
} else if full_module_name.contains('.') {
|
||||
// If there's no `as` clause and the imported module is nested, we're not going to bind
|
||||
// the resolved module itself into the current scope; we're going to bind the top-most
|
||||
// parent package of that module.
|
||||
let topmost_parent_name =
|
||||
ModuleName::new(full_module_name.components().next().unwrap()).unwrap();
|
||||
let Some(topmost_parent_ty) = self.module_ty_from_name(&topmost_parent_name) else {
|
||||
self.add_unknown_declaration_with_binding(alias.into(), definition);
|
||||
return;
|
||||
};
|
||||
topmost_parent_ty
|
||||
} else {
|
||||
// If there's no `as` clause and the imported module isn't nested, then the imported
|
||||
// module _is_ what we bind into the current scope.
|
||||
full_module_ty
|
||||
};
|
||||
|
||||
self.add_declaration_with_binding(alias.into(), definition, binding_ty, binding_ty);
|
||||
}
|
||||
|
||||
fn infer_import_from_statement(&mut self, import: &ast::StmtImportFrom) {
|
||||
@@ -2211,12 +2241,6 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
// TODO:
|
||||
// - Absolute `*` imports (`from collections import *`)
|
||||
// - Relative `*` imports (`from ...foo import *`)
|
||||
// - Submodule imports (`from collections import abc`,
|
||||
// where `abc` is a submodule of the `collections` package)
|
||||
//
|
||||
// For the last item, see the currently skipped tests
|
||||
// `follow_relative_import_bare_to_module()` and
|
||||
// `follow_nonexistent_import_bare_to_module()`.
|
||||
let ast::StmtImportFrom { module, level, .. } = import_from;
|
||||
let module = module.as_deref();
|
||||
|
||||
@@ -2239,46 +2263,13 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
.ok_or(ModuleNameResolutionError::InvalidSyntax)
|
||||
};
|
||||
|
||||
let ty = match module_name {
|
||||
Ok(module_name) => {
|
||||
if let Some(module_ty) = self.module_ty_from_name(&module_name) {
|
||||
let ast::Alias {
|
||||
range: _,
|
||||
name,
|
||||
asname: _,
|
||||
} = alias;
|
||||
|
||||
match module_ty.member(self.db, &ast::name::Name::new(&name.id)) {
|
||||
Symbol::Type(ty, boundness) => {
|
||||
if boundness == Boundness::PossiblyUnbound {
|
||||
self.diagnostics.add_lint(
|
||||
&POSSIBLY_UNBOUND_IMPORT,
|
||||
AnyNodeRef::Alias(alias),
|
||||
format_args!("Member `{name}` of module `{module_name}` is possibly unbound", ),
|
||||
);
|
||||
}
|
||||
|
||||
ty
|
||||
}
|
||||
Symbol::Unbound => {
|
||||
self.diagnostics.add_lint(
|
||||
&UNRESOLVED_IMPORT,
|
||||
AnyNodeRef::Alias(alias),
|
||||
format_args!("Module `{module_name}` has no member `{name}`",),
|
||||
);
|
||||
Type::Unknown
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.diagnostics
|
||||
.add_unresolved_module(import_from, *level, module);
|
||||
Type::Unknown
|
||||
}
|
||||
}
|
||||
let module_name = match module_name {
|
||||
Ok(module_name) => module_name,
|
||||
Err(ModuleNameResolutionError::InvalidSyntax) => {
|
||||
tracing::debug!("Failed to resolve import due to invalid syntax");
|
||||
// Invalid syntax diagnostics are emitted elsewhere.
|
||||
Type::Unknown
|
||||
self.add_unknown_declaration_with_binding(alias.into(), definition);
|
||||
return;
|
||||
}
|
||||
Err(ModuleNameResolutionError::TooManyDots) => {
|
||||
tracing::debug!(
|
||||
@@ -2287,7 +2278,8 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
);
|
||||
self.diagnostics
|
||||
.add_unresolved_module(import_from, *level, module);
|
||||
Type::Unknown
|
||||
self.add_unknown_declaration_with_binding(alias.into(), definition);
|
||||
return;
|
||||
}
|
||||
Err(ModuleNameResolutionError::UnknownCurrentModule) => {
|
||||
tracing::debug!(
|
||||
@@ -2297,10 +2289,72 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
);
|
||||
self.diagnostics
|
||||
.add_unresolved_module(import_from, *level, module);
|
||||
Type::Unknown
|
||||
self.add_unknown_declaration_with_binding(alias.into(), definition);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let Some(module_ty) = self.module_ty_from_name(&module_name) else {
|
||||
self.diagnostics
|
||||
.add_unresolved_module(import_from, *level, module);
|
||||
self.add_unknown_declaration_with_binding(alias.into(), definition);
|
||||
return;
|
||||
};
|
||||
|
||||
let ast::Alias {
|
||||
range: _,
|
||||
name,
|
||||
asname: _,
|
||||
} = alias;
|
||||
|
||||
// Check if the symbol being imported is a submodule. This won't get handled by the
|
||||
// `Type::member` call below because it relies on the semantic index's `imported_modules`
|
||||
// set. The semantic index does not include information about `from...import` statements
|
||||
// because there are two things it cannot determine while only inspecting the content of
|
||||
// the current file:
|
||||
//
|
||||
// - whether the imported symbol is an attribute or submodule
|
||||
// - whether the containing file is in a module or a package (needed to correctly resolve
|
||||
// relative imports)
|
||||
//
|
||||
// The first would be solvable by making it a _potentially_ imported modules set. The
|
||||
// second is not.
|
||||
//
|
||||
// Regardless, for now, we sidestep all of that by repeating the submodule-or-attribute
|
||||
// check here when inferring types for a `from...import` statement.
|
||||
if let Some(submodule_name) = ModuleName::new(name) {
|
||||
let mut full_submodule_name = module_name.clone();
|
||||
full_submodule_name.extend(&submodule_name);
|
||||
if let Some(submodule_ty) = self.module_ty_from_name(&full_submodule_name) {
|
||||
self.add_declaration_with_binding(
|
||||
alias.into(),
|
||||
definition,
|
||||
submodule_ty,
|
||||
submodule_ty,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise load the requested attribute from the module.
|
||||
let Symbol::Type(ty, boundness) = module_ty.member(self.db, name) else {
|
||||
self.diagnostics.add_lint(
|
||||
&UNRESOLVED_IMPORT,
|
||||
AnyNodeRef::Alias(alias),
|
||||
format_args!("Module `{module_name}` has no member `{name}`",),
|
||||
);
|
||||
self.add_unknown_declaration_with_binding(alias.into(), definition);
|
||||
return;
|
||||
};
|
||||
|
||||
if boundness == Boundness::PossiblyUnbound {
|
||||
self.diagnostics.add_lint(
|
||||
&POSSIBLY_UNBOUND_IMPORT,
|
||||
AnyNodeRef::Alias(alias),
|
||||
format_args!("Member `{name}` of module `{module_name}` is possibly unbound",),
|
||||
);
|
||||
}
|
||||
|
||||
self.add_declaration_with_binding(alias.into(), definition, ty, ty);
|
||||
}
|
||||
|
||||
@@ -2316,7 +2370,8 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
}
|
||||
|
||||
fn module_ty_from_name(&self, module_name: &ModuleName) -> Option<Type<'db>> {
|
||||
resolve_module(self.db, module_name).map(|module| Type::ModuleLiteral(module.file()))
|
||||
resolve_module(self.db, module_name)
|
||||
.map(|module| Type::module_literal(self.db, self.file, module))
|
||||
}
|
||||
|
||||
fn infer_decorator(&mut self, decorator: &ast::Decorator) -> Type<'db> {
|
||||
@@ -4904,42 +4959,45 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
self.infer_type_expression(arguments_slice);
|
||||
todo_type!("Callable types")
|
||||
}
|
||||
|
||||
// TODO: Generics
|
||||
KnownInstanceType::ChainMap => {
|
||||
self.infer_type_expression(arguments_slice);
|
||||
todo_type!("typing.ChainMap alias")
|
||||
KnownClass::ChainMap.to_instance(self.db)
|
||||
}
|
||||
KnownInstanceType::OrderedDict => {
|
||||
self.infer_type_expression(arguments_slice);
|
||||
todo_type!("typing.OrderedDict alias")
|
||||
KnownClass::OrderedDict.to_instance(self.db)
|
||||
}
|
||||
KnownInstanceType::Dict => {
|
||||
self.infer_type_expression(arguments_slice);
|
||||
todo_type!("typing.Dict alias")
|
||||
KnownClass::Dict.to_instance(self.db)
|
||||
}
|
||||
KnownInstanceType::List => {
|
||||
self.infer_type_expression(arguments_slice);
|
||||
todo_type!("typing.List alias")
|
||||
KnownClass::List.to_instance(self.db)
|
||||
}
|
||||
KnownInstanceType::DefaultDict => {
|
||||
self.infer_type_expression(arguments_slice);
|
||||
todo_type!("typing.DefaultDict[] alias")
|
||||
KnownClass::DefaultDict.to_instance(self.db)
|
||||
}
|
||||
KnownInstanceType::Counter => {
|
||||
self.infer_type_expression(arguments_slice);
|
||||
todo_type!("typing.Counter[] alias")
|
||||
KnownClass::Counter.to_instance(self.db)
|
||||
}
|
||||
KnownInstanceType::Set => {
|
||||
self.infer_type_expression(arguments_slice);
|
||||
todo_type!("typing.Set alias")
|
||||
KnownClass::Set.to_instance(self.db)
|
||||
}
|
||||
KnownInstanceType::FrozenSet => {
|
||||
self.infer_type_expression(arguments_slice);
|
||||
todo_type!("typing.FrozenSet alias")
|
||||
KnownClass::FrozenSet.to_instance(self.db)
|
||||
}
|
||||
KnownInstanceType::Deque => {
|
||||
self.infer_type_expression(arguments_slice);
|
||||
todo_type!("typing.Deque alias")
|
||||
KnownClass::Deque.to_instance(self.db)
|
||||
}
|
||||
|
||||
KnownInstanceType::ReadOnly => {
|
||||
self.infer_type_expression(arguments_slice);
|
||||
todo_type!("Required[] type qualifier")
|
||||
|
||||
@@ -196,6 +196,7 @@ impl<'db> NarrowingConstraintsBuilder<'db> {
|
||||
is_positive: bool,
|
||||
) -> Option<NarrowingConstraints<'db>> {
|
||||
match expression_node {
|
||||
ast::Expr::Name(name) => Some(self.evaluate_expr_name(name, is_positive)),
|
||||
ast::Expr::Compare(expr_compare) => {
|
||||
self.evaluate_expr_compare(expr_compare, expression, is_positive)
|
||||
}
|
||||
@@ -254,6 +255,31 @@ impl<'db> NarrowingConstraintsBuilder<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
fn evaluate_expr_name(
|
||||
&mut self,
|
||||
expr_name: &ast::ExprName,
|
||||
is_positive: bool,
|
||||
) -> NarrowingConstraints<'db> {
|
||||
let ast::ExprName { id, .. } = expr_name;
|
||||
|
||||
let symbol = self
|
||||
.symbols()
|
||||
.symbol_id_by_name(id)
|
||||
.expect("Should always have a symbol for every Name node");
|
||||
let mut constraints = NarrowingConstraints::default();
|
||||
|
||||
constraints.insert(
|
||||
symbol,
|
||||
if is_positive {
|
||||
Type::AlwaysFalsy.negate(self.db)
|
||||
} else {
|
||||
Type::AlwaysTruthy.negate(self.db)
|
||||
},
|
||||
);
|
||||
|
||||
constraints
|
||||
}
|
||||
|
||||
fn evaluate_expr_compare(
|
||||
&mut self,
|
||||
expr_compare: &ast::ExprCompare,
|
||||
|
||||
@@ -75,6 +75,8 @@ fn arbitrary_core_type(g: &mut Gen) -> Ty {
|
||||
Ty::AbcClassLiteral("ABCMeta"),
|
||||
Ty::SubclassOfAbcClass("ABC"),
|
||||
Ty::SubclassOfAbcClass("ABCMeta"),
|
||||
Ty::AlwaysTruthy,
|
||||
Ty::AlwaysFalsy,
|
||||
])
|
||||
.unwrap()
|
||||
.clone()
|
||||
|
||||
@@ -91,11 +91,11 @@ fn background_request_task<'a, R: traits::BackgroundDocumentRequestHandler>(
|
||||
let db = match path {
|
||||
AnySystemPath::System(path) => {
|
||||
match session.workspace_db_for_path(path.as_std_path()) {
|
||||
Some(db) => db.snapshot(),
|
||||
None => session.default_workspace_db().snapshot(),
|
||||
Some(db) => db.clone(),
|
||||
None => session.default_workspace_db().clone(),
|
||||
}
|
||||
}
|
||||
AnySystemPath::SystemVirtual(_) => session.default_workspace_db().snapshot(),
|
||||
AnySystemPath::SystemVirtual(_) => session.default_workspace_db().clone(),
|
||||
};
|
||||
|
||||
let Some(snapshot) = session.take_snapshot(url) else {
|
||||
|
||||
@@ -9,6 +9,7 @@ use ruff_db::vendored::VendoredFileSystem;
|
||||
use ruff_db::{Db as SourceDb, Upcast};
|
||||
|
||||
#[salsa::db]
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct Db {
|
||||
workspace_root: SystemPathBuf,
|
||||
storage: salsa::Storage<Self>,
|
||||
|
||||
@@ -21,6 +21,7 @@ pub trait Db: SemanticDb + Upcast<dyn SemanticDb> {
|
||||
}
|
||||
|
||||
#[salsa::db]
|
||||
#[derive(Clone)]
|
||||
pub struct RootDatabase {
|
||||
workspace: Option<Workspace>,
|
||||
storage: salsa::Storage<RootDatabase>,
|
||||
@@ -80,17 +81,6 @@ impl RootDatabase {
|
||||
{
|
||||
Cancelled::catch(|| f(self))
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn snapshot(&self) -> Self {
|
||||
Self {
|
||||
workspace: self.workspace,
|
||||
storage: self.storage.clone(),
|
||||
files: self.files.snapshot(),
|
||||
system: Arc::clone(&self.system),
|
||||
rule_selection: Arc::clone(&self.rule_selection),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Upcast<dyn SemanticDb> for RootDatabase {
|
||||
@@ -184,6 +174,7 @@ pub(crate) mod tests {
|
||||
use crate::DEFAULT_LINT_REGISTRY;
|
||||
|
||||
#[salsa::db]
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct TestDb {
|
||||
storage: salsa::Storage<Self>,
|
||||
events: Arc<std::sync::Mutex<Vec<Event>>>,
|
||||
|
||||
@@ -195,13 +195,13 @@ impl Workspace {
|
||||
let result = Arc::new(std::sync::Mutex::new(Vec::new()));
|
||||
let inner_result = Arc::clone(&result);
|
||||
|
||||
let db = db.snapshot();
|
||||
let db = db.clone();
|
||||
let workspace_span = workspace_span.clone();
|
||||
|
||||
rayon::scope(move |scope| {
|
||||
for file in &files {
|
||||
let result = inner_result.clone();
|
||||
let db = db.snapshot();
|
||||
let db = db.clone();
|
||||
let workspace_span = workspace_span.clone();
|
||||
|
||||
scope.spawn(move |_| {
|
||||
|
||||
@@ -81,7 +81,7 @@ pub(crate) fn analyze_graph(
|
||||
// Collect and resolve the imports for each file.
|
||||
let result = Arc::new(Mutex::new(Vec::new()));
|
||||
let inner_result = Arc::clone(&result);
|
||||
let db = db.snapshot();
|
||||
let db = db.clone();
|
||||
|
||||
rayon::scope(move |scope| {
|
||||
for resolved_file in paths {
|
||||
@@ -137,7 +137,7 @@ pub(crate) fn analyze_graph(
|
||||
continue;
|
||||
};
|
||||
|
||||
let db = db.snapshot();
|
||||
let db = db.clone();
|
||||
let glob_resolver = glob_resolver.clone();
|
||||
let root = root.clone();
|
||||
let result = inner_result.clone();
|
||||
|
||||
@@ -31,15 +31,10 @@ static EXPECTED_DIAGNOSTICS: &[&str] = &[
|
||||
"warning[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:98:12 Name `char` used when possibly not defined",
|
||||
"warning[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:101:12 Name `char` used when possibly not defined",
|
||||
"warning[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:104:14 Name `char` used when possibly not defined",
|
||||
"error[lint:conflicting-declarations] /src/tomllib/_parser.py:108:17 Conflicting declared types for `second_char`: Unknown, str | None",
|
||||
"warning[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:115:14 Name `char` used when possibly not defined",
|
||||
"warning[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:126:12 Name `char` used when possibly not defined",
|
||||
"error[lint:conflicting-declarations] /src/tomllib/_parser.py:267:9 Conflicting declared types for `char`: Unknown, str | None",
|
||||
"warning[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:348:20 Name `nest` used when possibly not defined",
|
||||
"warning[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:353:5 Name `nest` used when possibly not defined",
|
||||
"error[lint:conflicting-declarations] /src/tomllib/_parser.py:364:9 Conflicting declared types for `char`: Unknown, str | None",
|
||||
"error[lint:conflicting-declarations] /src/tomllib/_parser.py:381:13 Conflicting declared types for `char`: Unknown, str | None",
|
||||
"error[lint:conflicting-declarations] /src/tomllib/_parser.py:395:9 Conflicting declared types for `char`: Unknown, str | None",
|
||||
"warning[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:453:24 Name `nest` used when possibly not defined",
|
||||
"warning[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:455:9 Name `nest` used when possibly not defined",
|
||||
"warning[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:482:16 Name `char` used when possibly not defined",
|
||||
@@ -47,7 +42,6 @@ static EXPECTED_DIAGNOSTICS: &[&str] = &[
|
||||
"warning[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:573:12 Name `char` used when possibly not defined",
|
||||
"warning[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:579:12 Name `char` used when possibly not defined",
|
||||
"warning[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:580:63 Name `char` used when possibly not defined",
|
||||
"error[lint:conflicting-declarations] /src/tomllib/_parser.py:590:9 Conflicting declared types for `char`: Unknown, str | None",
|
||||
"warning[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:629:38 Name `datetime_obj` used when possibly not defined",
|
||||
];
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ pub fn vendored_path_to_file(
|
||||
}
|
||||
|
||||
/// Lookup table that maps [file paths](`FilePath`) to salsa interned [`File`] instances.
|
||||
#[derive(Default)]
|
||||
#[derive(Default, Clone)]
|
||||
pub struct Files {
|
||||
inner: Arc<FilesInner>,
|
||||
}
|
||||
@@ -253,13 +253,6 @@ impl Files {
|
||||
root.set_revision(db).to(FileRevision::now());
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn snapshot(&self) -> Self {
|
||||
Self {
|
||||
inner: Arc::clone(&self.inner),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Files {
|
||||
|
||||
@@ -48,13 +48,13 @@ mod tests {
|
||||
///
|
||||
/// Uses an in memory filesystem and it stubs out the vendored files by default.
|
||||
#[salsa::db]
|
||||
#[derive(Default)]
|
||||
#[derive(Default, Clone)]
|
||||
pub(crate) struct TestDb {
|
||||
storage: salsa::Storage<Self>,
|
||||
files: Files,
|
||||
system: TestSystem,
|
||||
vendored: VendoredFileSystem,
|
||||
events: std::sync::Arc<std::sync::Mutex<Vec<salsa::Event>>>,
|
||||
events: Arc<std::sync::Mutex<Vec<salsa::Event>>>,
|
||||
}
|
||||
|
||||
impl TestDb {
|
||||
|
||||
@@ -175,19 +175,26 @@ pub trait DbWithTestSystem: Db + Sized {
|
||||
///
|
||||
/// # Panics
|
||||
/// If the system isn't using the memory file system.
|
||||
fn write_file(
|
||||
&mut self,
|
||||
path: impl AsRef<SystemPath>,
|
||||
content: impl ToString,
|
||||
) -> crate::system::Result<()> {
|
||||
fn write_file(&mut self, path: impl AsRef<SystemPath>, content: impl ToString) -> Result<()> {
|
||||
let path = path.as_ref();
|
||||
let result = self
|
||||
.test_system()
|
||||
.memory_file_system()
|
||||
.write_file(path, content);
|
||||
|
||||
let memory_fs = self.test_system().memory_file_system();
|
||||
|
||||
let sync_ancestors = path
|
||||
.parent()
|
||||
.is_some_and(|parent| !memory_fs.exists(parent));
|
||||
let result = memory_fs.write_file(path, content);
|
||||
|
||||
if result.is_ok() {
|
||||
File::sync_path(self, path);
|
||||
|
||||
// Sync the ancestor paths if the path's parent
|
||||
// directory didn't exist before.
|
||||
if sync_ancestors {
|
||||
for ancestor in path.ancestors() {
|
||||
File::sync_path(self, ancestor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
|
||||
@@ -16,7 +16,7 @@ static EMPTY_VENDORED: std::sync::LazyLock<VendoredFileSystem> = std::sync::Lazy
|
||||
});
|
||||
|
||||
#[salsa::db]
|
||||
#[derive(Default)]
|
||||
#[derive(Default, Clone)]
|
||||
pub struct ModuleDb {
|
||||
storage: salsa::Storage<Self>,
|
||||
files: Files,
|
||||
@@ -55,17 +55,6 @@ impl ModuleDb {
|
||||
|
||||
Ok(db)
|
||||
}
|
||||
|
||||
/// Create a snapshot of the current database.
|
||||
#[must_use]
|
||||
pub fn snapshot(&self) -> Self {
|
||||
Self {
|
||||
storage: self.storage.clone(),
|
||||
system: self.system.clone(),
|
||||
files: self.files.snapshot(),
|
||||
rule_selection: Arc::clone(&self.rule_selection),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Upcast<dyn SourceDb> for ModuleDb {
|
||||
|
||||
@@ -1,36 +1,80 @@
|
||||
from airflow import PY36, PY37, PY38, PY39, PY310, PY311, PY312
|
||||
from airflow.triggers.external_task import TaskStateTrigger
|
||||
from airflow.api_connexion.security import requires_access
|
||||
from airflow import (
|
||||
PY36,
|
||||
PY37,
|
||||
PY38,
|
||||
PY39,
|
||||
PY310,
|
||||
PY311,
|
||||
PY312,
|
||||
Dataset as DatasetFromRoot,
|
||||
)
|
||||
from airflow.api_connexion.security import requires_access, requires_access_dataset
|
||||
from airflow.auth.managers.base_auth_manager import is_authorized_dataset
|
||||
from airflow.auth.managers.models.resource_details import DatasetDetails
|
||||
from airflow.configuration import (
|
||||
as_dict,
|
||||
get,
|
||||
getboolean,
|
||||
getfloat,
|
||||
getint,
|
||||
has_option,
|
||||
remove_option,
|
||||
as_dict,
|
||||
set,
|
||||
)
|
||||
from airflow.contrib.aws_athena_hook import AWSAthenaHook
|
||||
from airflow.metrics.validators import AllowListValidator
|
||||
from airflow.metrics.validators import BlockListValidator
|
||||
from airflow.operators.subdag import SubDagOperator
|
||||
from airflow.sensors.external_task import ExternalTaskSensorLink
|
||||
from airflow.datasets import (
|
||||
Dataset,
|
||||
DatasetAlias,
|
||||
DatasetAliasEvent,
|
||||
DatasetAll,
|
||||
DatasetAny,
|
||||
expand_alias_to_datasets,
|
||||
)
|
||||
from airflow.datasets.metadata import Metadata
|
||||
from airflow.datasets.manager import (
|
||||
DatasetManager,
|
||||
dataset_manager,
|
||||
resolve_dataset_manager,
|
||||
)
|
||||
from airflow.lineage.hook import DatasetLineageInfo
|
||||
from airflow.listeners.spec.dataset import on_dataset_changed, on_dataset_created
|
||||
from airflow.metrics.validators import AllowListValidator, BlockListValidator
|
||||
from airflow.operators import dummy_operator
|
||||
from airflow.operators.bash_operator import BashOperator
|
||||
from airflow.operators.branch_operator import BaseBranchOperator
|
||||
from airflow.operators.dummy import EmptyOperator, DummyOperator
|
||||
from airflow.operators import dummy_operator
|
||||
from airflow.operators.dummy import DummyOperator, EmptyOperator
|
||||
from airflow.operators.email_operator import EmailOperator
|
||||
from airflow.operators.subdag import SubDagOperator
|
||||
from airflow.providers.amazon.auth_manager.avp.entities import AvpEntities
|
||||
from airflow.providers.amazon.aws.datasets import s3
|
||||
from airflow.providers.common.io.datasets import file as common_io_file
|
||||
from airflow.providers.fab.auth_manager import fab_auth_manager
|
||||
from airflow.providers.google.datasets import bigquery, gcs
|
||||
from airflow.providers.mysql.datasets import mysql
|
||||
from airflow.providers.openlineage.utils.utils import (
|
||||
DatasetInfo,
|
||||
translate_airflow_dataset,
|
||||
)
|
||||
from airflow.providers.postgres.datasets import postgres
|
||||
from airflow.providers.trino.datasets import trino
|
||||
from airflow.secrets.local_filesystem import get_connection, load_connections
|
||||
from airflow.security.permissions import RESOURCE_DATASET
|
||||
from airflow.sensors.base_sensor_operator import BaseSensorOperator
|
||||
from airflow.sensors.date_time_sensor import DateTimeSensor
|
||||
from airflow.sensors.external_task import (
|
||||
ExternalTaskSensorLink as ExternalTaskSensorLinkFromExternalTask,
|
||||
)
|
||||
from airflow.sensors.external_task_sensor import (
|
||||
ExternalTaskMarker,
|
||||
ExternalTaskSensor,
|
||||
ExternalTaskSensorLink,
|
||||
ExternalTaskSensorLink as ExternalTaskSensorLinkFromExternalTaskSensor,
|
||||
)
|
||||
from airflow.sensors.time_delta_sensor import TimeDeltaSensor
|
||||
from airflow.secrets.local_filesystem import get_connection, load_connections
|
||||
from airflow.timetables.datasets import DatasetOrTimeSchedule
|
||||
from airflow.timetables.simple import DatasetTriggeredTimetable
|
||||
from airflow.triggers.external_task import TaskStateTrigger
|
||||
from airflow.utils import dates
|
||||
from airflow.utils.dag_cycle_tester import test_cycle
|
||||
from airflow.utils.dates import (
|
||||
date_range,
|
||||
datetime_to_nano,
|
||||
@@ -44,70 +88,168 @@ from airflow.utils.decorators import apply_defaults
|
||||
from airflow.utils.file import TemporaryDirectory, mkdirs
|
||||
from airflow.utils.helpers import chain, cross_downstream
|
||||
from airflow.utils.state import SHUTDOWN, terminating_states
|
||||
from airflow.utils.dag_cycle_tester import test_cycle
|
||||
from airflow.utils.trigger_rule import TriggerRule
|
||||
from airflow.www.auth import has_access
|
||||
from airflow.www.auth import has_access, has_access_dataset
|
||||
from airflow.www.utils import get_sensitive_variables_fields, should_hide_value_for_key
|
||||
|
||||
# airflow root
|
||||
PY36, PY37, PY38, PY39, PY310, PY311, PY312
|
||||
DatasetFromRoot
|
||||
|
||||
# airflow.api_connexion.security
|
||||
requires_access, requires_access_dataset
|
||||
|
||||
# airflow.auth.managers
|
||||
is_authorized_dataset
|
||||
DatasetDetails
|
||||
|
||||
# airflow.configuration
|
||||
get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set
|
||||
|
||||
|
||||
# airflow.contrib.*
|
||||
AWSAthenaHook
|
||||
TaskStateTrigger
|
||||
|
||||
requires_access
|
||||
# airflow.datasets
|
||||
Dataset
|
||||
DatasetAlias
|
||||
DatasetAliasEvent
|
||||
DatasetAll
|
||||
DatasetAny
|
||||
expand_alias_to_datasets
|
||||
Metadata
|
||||
|
||||
AllowListValidator
|
||||
BlockListValidator
|
||||
# airflow.datasets.manager
|
||||
DatasetManager, dataset_manager, resolve_dataset_manager
|
||||
|
||||
# airflow.lineage.hook
|
||||
DatasetLineageInfo
|
||||
|
||||
# airflow.listeners.spec.dataset
|
||||
on_dataset_changed, on_dataset_created
|
||||
|
||||
# airflow.metrics.validators
|
||||
AllowListValidator, BlockListValidator
|
||||
|
||||
# airflow.operators.dummy_operator
|
||||
dummy_operator.EmptyOperator
|
||||
dummy_operator.DummyOperator
|
||||
|
||||
# airflow.operators.bash_operator
|
||||
BashOperator
|
||||
|
||||
# airflow.operators.branch_operator
|
||||
BaseBranchOperator
|
||||
|
||||
# airflow.operators.dummy
|
||||
EmptyOperator, DummyOperator
|
||||
|
||||
# airflow.operators.email_operator
|
||||
EmailOperator
|
||||
|
||||
# airflow.operators.subdag.*
|
||||
SubDagOperator
|
||||
|
||||
# airflow.providers.amazon
|
||||
AvpEntities.DATASET
|
||||
s3.create_dataset
|
||||
s3.convert_dataset_to_openlineage
|
||||
s3.sanitize_uri
|
||||
|
||||
# airflow.providers.common.io
|
||||
common_io_file.convert_dataset_to_openlineage
|
||||
common_io_file.create_dataset
|
||||
common_io_file.sanitize_uri
|
||||
|
||||
# airflow.providers.fab
|
||||
fab_auth_manager.is_authorized_dataset
|
||||
|
||||
# airflow.providers.google
|
||||
bigquery.sanitize_uri
|
||||
|
||||
gcs.create_dataset
|
||||
gcs.sanitize_uri
|
||||
gcs.convert_dataset_to_openlineage
|
||||
|
||||
# airflow.providers.mysql
|
||||
mysql.sanitize_uri
|
||||
|
||||
# airflow.providers.openlineage
|
||||
DatasetInfo, translate_airflow_dataset
|
||||
|
||||
# airflow.providers.postgres
|
||||
postgres.sanitize_uri
|
||||
|
||||
# airflow.providers.trino
|
||||
trino.sanitize_uri
|
||||
|
||||
# airflow.secrets
|
||||
get_connection, load_connections
|
||||
|
||||
# airflow.security.permissions
|
||||
RESOURCE_DATASET
|
||||
|
||||
# airflow.sensors.base_sensor_operator
|
||||
BaseSensorOperator
|
||||
|
||||
# airflow.sensors.date_time_sensor
|
||||
DateTimeSensor
|
||||
|
||||
# airflow.sensors.external_task
|
||||
ExternalTaskSensorLinkFromExternalTask
|
||||
|
||||
# airflow.sensors.external_task_sensor
|
||||
ExternalTaskMarker
|
||||
ExternalTaskSensor
|
||||
ExternalTaskSensorLinkFromExternalTaskSensor
|
||||
|
||||
# airflow.sensors.time_delta_sensor
|
||||
TimeDeltaSensor
|
||||
|
||||
# airflow.timetables
|
||||
DatasetOrTimeSchedule
|
||||
DatasetTriggeredTimetable
|
||||
|
||||
# airflow.triggers.external_task
|
||||
TaskStateTrigger
|
||||
|
||||
# airflow.utils.date
|
||||
dates.date_range
|
||||
dates.days_ago
|
||||
|
||||
date_range
|
||||
days_ago
|
||||
infer_time_unit
|
||||
parse_execution_date
|
||||
round_time
|
||||
scale_time_units
|
||||
infer_time_unit
|
||||
|
||||
|
||||
# This one was not deprecated.
|
||||
datetime_to_nano
|
||||
dates.datetime_to_nano
|
||||
|
||||
get, getboolean, getfloat, getint, has_option, remove_option, as_dict, set
|
||||
|
||||
get_connection, load_connections
|
||||
|
||||
|
||||
ExternalTaskSensorLink
|
||||
BashOperator
|
||||
BaseBranchOperator
|
||||
EmptyOperator, DummyOperator
|
||||
dummy_operator.EmptyOperator
|
||||
dummy_operator.DummyOperator
|
||||
EmailOperator
|
||||
BaseSensorOperator
|
||||
DateTimeSensor
|
||||
(ExternalTaskMarker, ExternalTaskSensor, ExternalTaskSensorLink)
|
||||
TimeDeltaSensor
|
||||
# airflow.utils.dag_cycle_tester
|
||||
test_cycle
|
||||
|
||||
# airflow.utils.decorators
|
||||
apply_defaults
|
||||
|
||||
TemporaryDirectory
|
||||
mkdirs
|
||||
# airflow.utils.file
|
||||
TemporaryDirectory, mkdirs
|
||||
|
||||
chain
|
||||
cross_downstream
|
||||
# airflow.utils.helpers
|
||||
chain, cross_downstream
|
||||
|
||||
SHUTDOWN
|
||||
terminating_states
|
||||
# airflow.utils.state
|
||||
SHUTDOWN, terminating_states
|
||||
|
||||
# airflow.utils.trigger_rule
|
||||
TriggerRule.DUMMY
|
||||
TriggerRule.NONE_FAILED_OR_SKIPPED
|
||||
|
||||
test_cycle
|
||||
|
||||
# airflow.www.auth
|
||||
has_access
|
||||
has_access_dataset
|
||||
|
||||
# airflow.www.utils
|
||||
get_sensitive_variables_fields, should_hide_value_for_key
|
||||
|
||||
@@ -244,3 +244,10 @@ def f():
|
||||
for a in values:
|
||||
result.append(a + 1) # PERF401
|
||||
result = []
|
||||
|
||||
def f():
|
||||
values = [1, 2, 3]
|
||||
result = []
|
||||
for i in values:
|
||||
result.append(i + 1) # Ok
|
||||
del i
|
||||
|
||||
@@ -31,3 +31,13 @@ def single_word():
|
||||
|
||||
def single_word_no_dot():
|
||||
"""singleword"""
|
||||
|
||||
def first_word_lots_of_whitespace():
|
||||
"""
|
||||
|
||||
|
||||
|
||||
here is the start of my docstring!
|
||||
|
||||
What do you think?
|
||||
"""
|
||||
|
||||
@@ -50,6 +50,50 @@ class T:
|
||||
obj = T()
|
||||
obj.a = obj.a + 1
|
||||
|
||||
|
||||
a = a+-1
|
||||
|
||||
# Regression tests for https://github.com/astral-sh/ruff/issues/11672
|
||||
test = 0x5
|
||||
test = test + 0xBA
|
||||
|
||||
test2 = b""
|
||||
test2 = test2 + b"\000"
|
||||
|
||||
test3 = ""
|
||||
test3 = test3 + ( a := R""
|
||||
f"oo" )
|
||||
|
||||
test4 = []
|
||||
test4 = test4 + ( e
|
||||
for e in
|
||||
range(10)
|
||||
)
|
||||
|
||||
test5 = test5 + (
|
||||
4
|
||||
*
|
||||
10
|
||||
)
|
||||
|
||||
test6 = test6 + \
|
||||
(
|
||||
4
|
||||
*
|
||||
10
|
||||
)
|
||||
|
||||
test7 = \
|
||||
100 \
|
||||
+ test7
|
||||
|
||||
test8 = \
|
||||
886 \
|
||||
+ \
|
||||
\
|
||||
test8
|
||||
|
||||
|
||||
# OK
|
||||
a_list[0] = a_list[:] * 3
|
||||
index = a_number = a_number + 1
|
||||
|
||||
@@ -46,7 +46,7 @@ pub(crate) fn definitions(checker: &mut Checker) {
|
||||
Rule::EndsInPeriod,
|
||||
Rule::EndsInPunctuation,
|
||||
Rule::EscapeSequenceInDocstring,
|
||||
Rule::FirstLineCapitalized,
|
||||
Rule::FirstWordUncapitalized,
|
||||
Rule::FitsOnOneLine,
|
||||
Rule::IndentWithSpaces,
|
||||
Rule::MultiLineSummaryFirstLine,
|
||||
@@ -277,7 +277,7 @@ pub(crate) fn definitions(checker: &mut Checker) {
|
||||
if checker.enabled(Rule::NoSignature) {
|
||||
pydocstyle::rules::no_signature(checker, &docstring);
|
||||
}
|
||||
if checker.enabled(Rule::FirstLineCapitalized) {
|
||||
if checker.enabled(Rule::FirstWordUncapitalized) {
|
||||
pydocstyle::rules::capitalized(checker, &docstring);
|
||||
}
|
||||
if checker.enabled(Rule::DocstringStartsWithThis) {
|
||||
|
||||
@@ -566,7 +566,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Pydocstyle, "400") => (RuleGroup::Stable, rules::pydocstyle::rules::EndsInPeriod),
|
||||
(Pydocstyle, "401") => (RuleGroup::Stable, rules::pydocstyle::rules::NonImperativeMood),
|
||||
(Pydocstyle, "402") => (RuleGroup::Stable, rules::pydocstyle::rules::NoSignature),
|
||||
(Pydocstyle, "403") => (RuleGroup::Stable, rules::pydocstyle::rules::FirstLineCapitalized),
|
||||
(Pydocstyle, "403") => (RuleGroup::Stable, rules::pydocstyle::rules::FirstWordUncapitalized),
|
||||
(Pydocstyle, "404") => (RuleGroup::Stable, rules::pydocstyle::rules::DocstringStartsWithThis),
|
||||
(Pydocstyle, "405") => (RuleGroup::Stable, rules::pydocstyle::rules::CapitalizeSectionName),
|
||||
(Pydocstyle, "406") => (RuleGroup::Stable, rules::pydocstyle::rules::NewLineAfterSectionName),
|
||||
|
||||
@@ -160,15 +160,23 @@ fn removed_name(checker: &mut Checker, expr: &Expr, ranged: impl Ranged) {
|
||||
.semantic()
|
||||
.resolve_qualified_name(expr)
|
||||
.and_then(|qualname| match qualname.segments() {
|
||||
["airflow", "triggers", "external_task", "TaskStateTrigger"] => {
|
||||
Some((qualname.to_string(), Replacement::None))
|
||||
}
|
||||
["airflow", "api_connexion", "security", "requires_access"] => Some((
|
||||
qualname.to_string(),
|
||||
Replacement::Name(
|
||||
"airflow.api_connexion.security.requires_access_*".to_string(),
|
||||
),
|
||||
)),
|
||||
["airflow", "api_connexion", "security", "requires_access_dataset"] => Some((
|
||||
qualname.to_string(),
|
||||
Replacement::Name("airflow.api_connexion.security.requires_access_asset".to_string()),
|
||||
)),
|
||||
["airflow", "triggers", "external_task", "TaskStateTrigger"] => {
|
||||
Some((qualname.to_string(), Replacement::None))
|
||||
}
|
||||
["airflow", "security", "permissions", "RESOURCE_DATASET"] => Some((
|
||||
qualname.to_string(),
|
||||
Replacement::Name("airflow.security.permissions.RESOURCE_ASSET".to_string()),
|
||||
)),
|
||||
// airflow.PY\d{1,2}
|
||||
["airflow", "PY36"] => Some((
|
||||
qualname.to_string(),
|
||||
@@ -231,8 +239,22 @@ fn removed_name(checker: &mut Checker, expr: &Expr, ranged: impl Ranged) {
|
||||
qualname.to_string(),
|
||||
Replacement::Name("airflow.configuration.conf.set".to_string()),
|
||||
)),
|
||||
// airflow.auth.managers
|
||||
["airflow", "auth", "managers", "models", "resource_details", "DatasetDetails"] => Some((
|
||||
qualname.to_string(),
|
||||
Replacement::Name("airflow.auth.managers.models.resource_details.AssetDetails".to_string()),
|
||||
)),
|
||||
["airflow", "auth", "managers", "base_auth_manager", "is_authorized_dataset"] => Some((
|
||||
qualname.to_string(),
|
||||
Replacement::Name("airflow.auth.managers.base_auth_manager.is_authorized_asset".to_string()),
|
||||
)),
|
||||
// airflow.contrib.*
|
||||
["airflow", "contrib", ..] => Some((qualname.to_string(), Replacement::None)),
|
||||
["airflow", "contrib", ..] => Some((qualname.to_string(),
|
||||
Replacement::Message(
|
||||
"The whole `airflow.contrib` module has been removed."
|
||||
.to_string(),
|
||||
),
|
||||
)),
|
||||
// airflow.metrics.validators
|
||||
["airflow", "metrics", "validators", "AllowListValidator"] => Some((
|
||||
qualname.to_string(),
|
||||
@@ -246,13 +268,86 @@ fn removed_name(checker: &mut Checker, expr: &Expr, ranged: impl Ranged) {
|
||||
"airflow.metrics.validators.PatternBlockListValidator".to_string(),
|
||||
),
|
||||
)),
|
||||
// airflow.operators
|
||||
["airflow", "operators", "subdag", ..] => {
|
||||
// airflow.datasets
|
||||
["airflow", "Dataset"] => Some((
|
||||
qualname.to_string(),
|
||||
Replacement::Name("airflow.sdk.definitions.asset.Asset".to_string()),
|
||||
)),
|
||||
["airflow", "datasets", "DatasetAliasEvent"] => {
|
||||
Some((qualname.to_string(), Replacement::None))
|
||||
}
|
||||
["airflow.sensors.external_task.ExternalTaskSensorLink"] => Some((
|
||||
["airflow", "datasets", "Dataset"] => Some((
|
||||
qualname.to_string(),
|
||||
Replacement::Name("airflow.sensors.external_task.ExternalDagLin".to_string()),
|
||||
Replacement::Name("airflow.sdk.definitions.asset.Asset".to_string()),
|
||||
)),
|
||||
["airflow", "datasets", "DatasetAlias"] => Some((
|
||||
qualname.to_string(),
|
||||
Replacement::Name("airflow.sdk.definitions.asset.AssetAlias".to_string()),
|
||||
)),
|
||||
["airflow", "datasets", "DatasetAll"] => Some((
|
||||
qualname.to_string(),
|
||||
Replacement::Name("airflow.sdk.definitions.asset.AssetAll".to_string()),
|
||||
)),
|
||||
["airflow", "datasets", "DatasetAny"] => Some((
|
||||
qualname.to_string(),
|
||||
Replacement::Name("airflow.sdk.definitions.asset.AssetAny".to_string()),
|
||||
)),
|
||||
["airflow", "datasets", "expand_alias_to_datasets"] => Some((
|
||||
qualname.to_string(),
|
||||
Replacement::Name("airflow.sdk.definitions.asset.expand_alias_to_assets".to_string()),
|
||||
)),
|
||||
["airflow", "datasets", "metadata", "Metadata"] => Some((
|
||||
qualname.to_string(),
|
||||
Replacement::Name("airflow.sdk.definitions.asset.metadata.Metadata".to_string()),
|
||||
)),
|
||||
// airflow.datasets.manager
|
||||
["airflow", "datasets", "manager", "dataset_manager"] => Some((
|
||||
qualname.to_string(),
|
||||
Replacement::Name("airflow.assets.manager".to_string()),
|
||||
)),
|
||||
["airflow", "datasets", "manager", "resolve_dataset_manager"] => Some((
|
||||
qualname.to_string(),
|
||||
Replacement::Name("airflow.assets.resolve_asset_manager".to_string()),
|
||||
)),
|
||||
["airflow", "datasets.manager", "DatasetManager"] => Some((
|
||||
qualname.to_string(),
|
||||
Replacement::Name("airflow.assets.AssetManager".to_string()),
|
||||
)),
|
||||
// airflow.listeners.spec
|
||||
["airflow", "listeners", "spec", "dataset", "on_dataset_created"] => Some((
|
||||
qualname.to_string(),
|
||||
Replacement::Name("airflow.listeners.spec.asset.on_asset_created".to_string()),
|
||||
)),
|
||||
["airflow", "listeners", "spec", "dataset", "on_dataset_changed"] => Some((
|
||||
qualname.to_string(),
|
||||
Replacement::Name("airflow.listeners.spec.asset.on_asset_changed".to_string()),
|
||||
)),
|
||||
// airflow.timetables
|
||||
["airflow", "timetables", "datasets", "DatasetOrTimeSchedule"] => Some((
|
||||
qualname.to_string(),
|
||||
Replacement::Name("airflow.timetables.assets.AssetOrTimeSchedule".to_string()),
|
||||
)),
|
||||
["airflow", "timetables", "simple", "DatasetTriggeredTimetable"] => Some((
|
||||
qualname.to_string(),
|
||||
Replacement::Name("airflow.timetables.simple.AssetTriggeredTimetable".to_string()),
|
||||
)),
|
||||
// airflow.lineage.hook
|
||||
["airflow", "lineage", "hook", "DatasetLineageInfo"] => Some((
|
||||
qualname.to_string(),
|
||||
Replacement::Name("airflow.lineage.hook.AssetLineageInfo".to_string()),
|
||||
)),
|
||||
// airflow.operators
|
||||
["airflow", "operators", "subdag", ..] => {
|
||||
Some((
|
||||
qualname.to_string(),
|
||||
Replacement::Message(
|
||||
"The whole `airflow.subdag` module has been removed.".to_string(),
|
||||
),
|
||||
))
|
||||
},
|
||||
["airflow", "sensors", "external_task", "ExternalTaskSensorLink"] => Some((
|
||||
qualname.to_string(),
|
||||
Replacement::Name("airflow.sensors.external_task.ExternalDagLink".to_string()),
|
||||
)),
|
||||
["airflow", "operators", "bash_operator", "BashOperator"] => Some((
|
||||
qualname.to_string(),
|
||||
@@ -305,7 +400,7 @@ fn removed_name(checker: &mut Checker, expr: &Expr, ranged: impl Ranged) {
|
||||
["airflow", "sensors", "external_task_sensor", "ExternalTaskSensorLink"] => Some((
|
||||
qualname.to_string(),
|
||||
Replacement::Name(
|
||||
"airflow.sensors.external_task.ExternalTaskSensorLink".to_string(),
|
||||
"airflow.sensors.external_task.ExternalDagLink".to_string(),
|
||||
),
|
||||
)),
|
||||
["airflow", "sensors", "time_delta_sensor", "TimeDeltaSensor"] => Some((
|
||||
@@ -354,7 +449,6 @@ fn removed_name(checker: &mut Checker, expr: &Expr, ranged: impl Ranged) {
|
||||
qualname.to_string(),
|
||||
Replacement::Name("pendulum.today('UTC').add(days=-N, ...)".to_string()),
|
||||
)),
|
||||
|
||||
// airflow.utils.helpers
|
||||
["airflow", "utils", "helpers", "chain"] => Some((
|
||||
qualname.to_string(),
|
||||
@@ -394,6 +488,10 @@ fn removed_name(checker: &mut Checker, expr: &Expr, ranged: impl Ranged) {
|
||||
qualname.to_string(),
|
||||
Replacement::Name("airflow.www.auth.has_access_*".to_string()),
|
||||
)),
|
||||
["airflow", "www", "auth", "has_access_dataset"] => Some((
|
||||
qualname.to_string(),
|
||||
Replacement::Name("airflow.www.auth.has_access_dataset.has_access_asset".to_string()),
|
||||
)),
|
||||
["airflow", "www", "utils", "get_sensitive_variables_fields"] => Some((
|
||||
qualname.to_string(),
|
||||
Replacement::Name(
|
||||
@@ -407,6 +505,82 @@ fn removed_name(checker: &mut Checker, expr: &Expr, ranged: impl Ranged) {
|
||||
"airflow.utils.log.secrets_masker.should_hide_value_for_key".to_string(),
|
||||
),
|
||||
)),
|
||||
// airflow.providers.amazon
|
||||
["airflow", "providers", "amazon", "aws", "datasets", "s3", "create_dataset"] => Some((
|
||||
qualname.to_string(),
|
||||
Replacement::Name("airflow.providers.amazon.aws.assets.s3.create_asset".to_string()),
|
||||
)),
|
||||
["airflow", "providers", "amazon", "aws", "datasets", "s3", "convert_dataset_to_openlineage"] => Some((
|
||||
qualname.to_string(),
|
||||
Replacement::Name("airflow.providers.amazon.aws.assets.s3.convert_asset_to_openlineage".to_string()),
|
||||
)),
|
||||
["airflow", "providers", "amazon", "aws", "datasets", "s3", "sanitize_uri"] => Some((
|
||||
qualname.to_string(),
|
||||
Replacement::Name("airflow.providers.amazon.aws.assets.s3.sanitize_uri".to_string()),
|
||||
)),
|
||||
["airflow", "providers", "amazon", "auth_manager", "avp", "entities", "AvpEntities", "DATASET"] => Some((
|
||||
qualname.to_string(),
|
||||
Replacement::Name("airflow.providers.amazon.auth_manager.avp.entities.AvpEntities.ASSET".to_string()),
|
||||
)),
|
||||
// airflow.providers.common.io
|
||||
["airflow", "providers", "common", "io", "datasets", "file", "create_dataset"] => Some((
|
||||
qualname.to_string(),
|
||||
Replacement::Name("airflow.providers.common.io.assets.file.create_asset".to_string()),
|
||||
)),
|
||||
["airflow", "providers", "common", "io", "datasets", "file", "convert_dataset_to_openlineage"] => Some((
|
||||
qualname.to_string(),
|
||||
Replacement::Name("airflow.providers.common.io.assets.file.convert_asset_to_openlineage".to_string()),
|
||||
)),
|
||||
["airflow", "providers", "common", "io", "datasets", "file", "sanitize_uri"] => Some((
|
||||
qualname.to_string(),
|
||||
Replacement::Name("airflow.providers.common.io.assets.file.sanitize_uri".to_string()),
|
||||
)),
|
||||
// airflow.providers.fab
|
||||
["airflow", "providers", "fab", "auth_manager", "fab_auth_manager", "is_authorized_dataset"] => Some((
|
||||
qualname.to_string(),
|
||||
Replacement::Name("airflow.providers.fab.auth_manager.fab_auth_manager.is_authorized_asset".to_string()),
|
||||
)),
|
||||
// airflow.providers.google
|
||||
["airflow", "providers", "google", "datasets", "bigquery", "create_dataset"] => Some((
|
||||
qualname.to_string(),
|
||||
Replacement::Name("airflow.providers.google.assets.bigquery.create_asset".to_string()),
|
||||
)),
|
||||
["airflow", "providers", "google", "datasets", "gcs", "create_dataset"] => Some((
|
||||
qualname.to_string(),
|
||||
Replacement::Name("airflow.providers.google.assets.gcs.create_asset".to_string()),
|
||||
)),
|
||||
["airflow", "providers", "google", "datasets", "gcs", "convert_dataset_to_openlineage"] => Some((
|
||||
qualname.to_string(),
|
||||
Replacement::Name("airflow.providers.google.assets.gcs.convert_asset_to_openlineage".to_string()),
|
||||
)),
|
||||
["airflow", "providers", "google", "datasets", "gcs", "sanitize_uri"] => Some((
|
||||
qualname.to_string(),
|
||||
Replacement::Name("airflow.providers.google.assets.gcs.sanitize_uri".to_string()),
|
||||
)),
|
||||
// airflow.providers.mysql
|
||||
["airflow", "providers", "mysql", "datasets", "mysql", "sanitize_uri"] => Some((
|
||||
qualname.to_string(),
|
||||
Replacement::Name("airflow.providers.mysql.assets.mysql.sanitize_uri".to_string()),
|
||||
)),
|
||||
// airflow.providers.postgres
|
||||
["airflow", "providers", "postgres", "datasets", "postgres", "sanitize_uri"] => Some((
|
||||
qualname.to_string(),
|
||||
Replacement::Name("airflow.providers.postgres.assets.postgres.sanitize_uri".to_string()),
|
||||
)),
|
||||
// airflow.providers.openlineage
|
||||
["airflow", "providers", "openlineage", "utils", "utils", "DatasetInfo"] => Some((
|
||||
qualname.to_string(),
|
||||
Replacement::Name("airflow.providers.openlineage.utils.utils.AssetInfo".to_string()),
|
||||
)),
|
||||
["airflow", "providers", "openlineage", "utils", "utils", "translate_airflow_dataset"] => Some((
|
||||
qualname.to_string(),
|
||||
Replacement::Name("airflow.providers.openlineage.utils.utils.translate_airflow_asset".to_string()),
|
||||
)),
|
||||
// airflow.providers.trino
|
||||
["airflow", "providers", "trino", "datasets", "trino", "sanitize_uri"] => Some((
|
||||
qualname.to_string(),
|
||||
Replacement::Name("airflow.providers.trino.assets.trino.sanitize_uri".to_string()),
|
||||
)),
|
||||
_ => None,
|
||||
});
|
||||
if let Some((deprecated, replacement)) = result {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -237,31 +237,12 @@ pub(crate) fn manual_list_comprehension(checker: &mut Checker, for_stmt: &ast::S
|
||||
// filtered = [x for x in y]
|
||||
// print(x)
|
||||
// ```
|
||||
let last_target_binding = checker
|
||||
let target_binding = checker
|
||||
.semantic()
|
||||
.lookup_symbol(for_stmt_target_id)
|
||||
.expect("for loop target must exist");
|
||||
|
||||
let target_binding = {
|
||||
let mut bindings = [last_target_binding].into_iter().chain(
|
||||
checker
|
||||
.semantic()
|
||||
.shadowed_bindings(checker.semantic().scope_id, last_target_binding)
|
||||
.filter_map(|shadowed| shadowed.same_scope().then_some(shadowed.shadowed_id())),
|
||||
);
|
||||
|
||||
bindings
|
||||
.find_map(|binding_id| {
|
||||
let binding = checker.semantic().binding(binding_id);
|
||||
binding
|
||||
.statement(checker.semantic())
|
||||
.and_then(ast::Stmt::as_for_stmt)
|
||||
.is_some_and(|stmt| stmt.range == for_stmt.range)
|
||||
.then_some(binding)
|
||||
})
|
||||
.expect("for target binding must exist")
|
||||
};
|
||||
|
||||
.bindings
|
||||
.iter()
|
||||
.find(|binding| for_stmt.target.range() == binding.range)
|
||||
.unwrap();
|
||||
// If any references to the loop target variable are after the loop,
|
||||
// then converting it into a comprehension would cause a NameError
|
||||
if target_binding
|
||||
|
||||
@@ -484,3 +484,5 @@ PERF401.py:245:13: PERF401 [*] Use `list.extend` to create a transformed list
|
||||
245 |- result.append(a + 1) # PERF401
|
||||
244 |+ result.extend(a + 1 for a in values) # PERF401
|
||||
246 245 | result = []
|
||||
247 246 |
|
||||
248 247 | def f():
|
||||
|
||||
@@ -32,8 +32,8 @@ mod tests {
|
||||
#[test_case(Rule::EndsInPeriod, Path::new("D400_415.py"))]
|
||||
#[test_case(Rule::EndsInPunctuation, Path::new("D.py"))]
|
||||
#[test_case(Rule::EndsInPunctuation, Path::new("D400_415.py"))]
|
||||
#[test_case(Rule::FirstLineCapitalized, Path::new("D.py"))]
|
||||
#[test_case(Rule::FirstLineCapitalized, Path::new("D403.py"))]
|
||||
#[test_case(Rule::FirstWordUncapitalized, Path::new("D.py"))]
|
||||
#[test_case(Rule::FirstWordUncapitalized, Path::new("D403.py"))]
|
||||
#[test_case(Rule::FitsOnOneLine, Path::new("D.py"))]
|
||||
#[test_case(Rule::IndentWithSpaces, Path::new("D.py"))]
|
||||
#[test_case(Rule::UndocumentedMagicMethod, Path::new("D.py"))]
|
||||
|
||||
@@ -10,8 +10,8 @@ use crate::docstrings::Docstring;
|
||||
/// Checks for docstrings that do not start with a capital letter.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// The first character in a docstring should be capitalized for, grammatical
|
||||
/// correctness and consistency.
|
||||
/// The first non-whitespace character in a docstring should be
|
||||
/// capitalized for grammatical correctness and consistency.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
@@ -30,16 +30,16 @@ use crate::docstrings::Docstring;
|
||||
/// - [NumPy Style Guide](https://numpydoc.readthedocs.io/en/latest/format.html)
|
||||
/// - [Google Python Style Guide - Docstrings](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct FirstLineCapitalized {
|
||||
pub(crate) struct FirstWordUncapitalized {
|
||||
first_word: String,
|
||||
capitalized_word: String,
|
||||
}
|
||||
|
||||
impl AlwaysFixableViolation for FirstLineCapitalized {
|
||||
impl AlwaysFixableViolation for FirstWordUncapitalized {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!(
|
||||
"First word of the first line should be capitalized: `{}` -> `{}`",
|
||||
"First word of the docstring should be capitalized: `{}` -> `{}`",
|
||||
self.first_word, self.capitalized_word
|
||||
)
|
||||
}
|
||||
@@ -59,7 +59,8 @@ pub(crate) fn capitalized(checker: &mut Checker, docstring: &Docstring) {
|
||||
}
|
||||
|
||||
let body = docstring.body();
|
||||
let first_word = body.split_once(' ').map_or_else(
|
||||
let trim_start_body = body.trim_start();
|
||||
let first_word = trim_start_body.split_once(' ').map_or_else(
|
||||
|| {
|
||||
// If the docstring is a single word, trim the punctuation marks because
|
||||
// it makes the ASCII test below fail.
|
||||
@@ -91,8 +92,10 @@ pub(crate) fn capitalized(checker: &mut Checker, docstring: &Docstring) {
|
||||
|
||||
let capitalized_word = uppercase_first_char.to_string() + first_word_chars.as_str();
|
||||
|
||||
let leading_whitespace_len = body.text_len() - trim_start_body.text_len();
|
||||
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
FirstLineCapitalized {
|
||||
FirstWordUncapitalized {
|
||||
first_word: first_word.to_string(),
|
||||
capitalized_word: capitalized_word.to_string(),
|
||||
},
|
||||
@@ -101,7 +104,7 @@ pub(crate) fn capitalized(checker: &mut Checker, docstring: &Docstring) {
|
||||
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
|
||||
capitalized_word,
|
||||
TextRange::at(body.start(), first_word.text_len()),
|
||||
TextRange::at(body.start() + leading_whitespace_len, first_word.text_len()),
|
||||
)));
|
||||
|
||||
checker.diagnostics.push(diagnostic);
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
|
||||
snapshot_kind: text
|
||||
---
|
||||
D403.py:2:5: D403 [*] First word of the first line should be capitalized: `this` -> `This`
|
||||
D403.py:2:5: D403 [*] First word of the docstring should be capitalized: `this` -> `This`
|
||||
|
|
||||
1 | def bad_function():
|
||||
2 | """this docstring is not capitalized"""
|
||||
@@ -20,7 +19,7 @@ D403.py:2:5: D403 [*] First word of the first line should be capitalized: `this`
|
||||
4 4 | def good_function():
|
||||
5 5 | """This docstring is capitalized."""
|
||||
|
||||
D403.py:30:5: D403 [*] First word of the first line should be capitalized: `singleword` -> `Singleword`
|
||||
D403.py:30:5: D403 [*] First word of the docstring should be capitalized: `singleword` -> `Singleword`
|
||||
|
|
||||
29 | def single_word():
|
||||
30 | """singleword."""
|
||||
@@ -40,11 +39,13 @@ D403.py:30:5: D403 [*] First word of the first line should be capitalized: `sing
|
||||
32 32 | def single_word_no_dot():
|
||||
33 33 | """singleword"""
|
||||
|
||||
D403.py:33:5: D403 [*] First word of the first line should be capitalized: `singleword` -> `Singleword`
|
||||
D403.py:33:5: D403 [*] First word of the docstring should be capitalized: `singleword` -> `Singleword`
|
||||
|
|
||||
32 | def single_word_no_dot():
|
||||
33 | """singleword"""
|
||||
| ^^^^^^^^^^^^^^^^ D403
|
||||
34 |
|
||||
35 | def first_word_lots_of_whitespace():
|
||||
|
|
||||
= help: Capitalize `singleword` to `Singleword`
|
||||
|
||||
@@ -54,3 +55,32 @@ D403.py:33:5: D403 [*] First word of the first line should be capitalized: `sing
|
||||
32 32 | def single_word_no_dot():
|
||||
33 |- """singleword"""
|
||||
33 |+ """Singleword"""
|
||||
34 34 |
|
||||
35 35 | def first_word_lots_of_whitespace():
|
||||
36 36 | """
|
||||
|
||||
D403.py:36:5: D403 [*] First word of the docstring should be capitalized: `here` -> `Here`
|
||||
|
|
||||
35 | def first_word_lots_of_whitespace():
|
||||
36 | """
|
||||
| _____^
|
||||
37 | |
|
||||
38 | |
|
||||
39 | |
|
||||
40 | | here is the start of my docstring!
|
||||
41 | |
|
||||
42 | | What do you think?
|
||||
43 | | """
|
||||
| |_______^ D403
|
||||
|
|
||||
= help: Capitalize `here` to `Here`
|
||||
|
||||
ℹ Safe fix
|
||||
37 37 |
|
||||
38 38 |
|
||||
39 39 |
|
||||
40 |- here is the start of my docstring!
|
||||
40 |+ Here is the start of my docstring!
|
||||
41 41 |
|
||||
42 42 | What do you think?
|
||||
43 43 | """
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use ast::{Expr, StmtAugAssign};
|
||||
use ast::Expr;
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::comparable::ComparableExpr;
|
||||
use ruff_python_ast::Operator;
|
||||
use ruff_python_codegen::Generator;
|
||||
use ruff_python_ast::parenthesize::parenthesized_range;
|
||||
use ruff_python_ast::{AstNode, ExprBinOp, ExpressionRef, Operator};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -103,11 +103,12 @@ pub(crate) fn non_augmented_assignment(checker: &mut Checker, assign: &ast::Stmt
|
||||
if ComparableExpr::from(target) == ComparableExpr::from(&value.left) {
|
||||
let mut diagnostic = Diagnostic::new(NonAugmentedAssignment { operator }, assign.range());
|
||||
diagnostic.set_fix(Fix::unsafe_edit(augmented_assignment(
|
||||
checker.generator(),
|
||||
checker,
|
||||
target,
|
||||
value.op,
|
||||
operator,
|
||||
&value.right,
|
||||
assign.range(),
|
||||
value,
|
||||
assign.range,
|
||||
)));
|
||||
checker.diagnostics.push(diagnostic);
|
||||
return;
|
||||
@@ -121,11 +122,12 @@ pub(crate) fn non_augmented_assignment(checker: &mut Checker, assign: &ast::Stmt
|
||||
{
|
||||
let mut diagnostic = Diagnostic::new(NonAugmentedAssignment { operator }, assign.range());
|
||||
diagnostic.set_fix(Fix::unsafe_edit(augmented_assignment(
|
||||
checker.generator(),
|
||||
checker,
|
||||
target,
|
||||
value.op,
|
||||
operator,
|
||||
&value.left,
|
||||
assign.range(),
|
||||
value,
|
||||
assign.range,
|
||||
)));
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
@@ -135,21 +137,30 @@ pub(crate) fn non_augmented_assignment(checker: &mut Checker, assign: &ast::Stmt
|
||||
///
|
||||
/// For example, given `x = x + 1`, the fix would be `x += 1`.
|
||||
fn augmented_assignment(
|
||||
generator: Generator,
|
||||
checker: &Checker,
|
||||
target: &Expr,
|
||||
operator: Operator,
|
||||
operator: AugmentedOperator,
|
||||
right_operand: &Expr,
|
||||
original_expr: &ExprBinOp,
|
||||
range: TextRange,
|
||||
) -> Edit {
|
||||
Edit::range_replacement(
|
||||
generator.stmt(&ast::Stmt::AugAssign(StmtAugAssign {
|
||||
range: TextRange::default(),
|
||||
target: Box::new(target.clone()),
|
||||
op: operator,
|
||||
value: Box::new(right_operand.clone()),
|
||||
})),
|
||||
range,
|
||||
)
|
||||
let locator = checker.locator();
|
||||
|
||||
let right_operand_ref = ExpressionRef::from(right_operand);
|
||||
let parent = original_expr.as_any_node_ref();
|
||||
let comment_ranges = checker.comment_ranges();
|
||||
let source = checker.source();
|
||||
|
||||
let right_operand_range =
|
||||
parenthesized_range(right_operand_ref, parent, comment_ranges, source)
|
||||
.unwrap_or(right_operand.range());
|
||||
let right_operand_expr = locator.slice(right_operand_range);
|
||||
|
||||
let target_expr = locator.slice(target);
|
||||
|
||||
let new_content = format!("{target_expr} {operator} {right_operand_expr}");
|
||||
|
||||
Edit::range_replacement(new_content, range)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
|
||||
@@ -269,7 +269,7 @@ non_augmented_assignment.py:29:1: PLR6104 [*] Use `&=` to perform an augmented a
|
||||
27 27 | to_cube = to_cube**to_cube
|
||||
28 28 | timeDiffSeconds = timeDiffSeconds % 60
|
||||
29 |-flags = flags & 0x1
|
||||
29 |+flags &= 1
|
||||
29 |+flags &= 0x1
|
||||
30 30 | flags = flags | 0x1
|
||||
31 31 | flags = flags ^ 0x1
|
||||
32 32 | flags = flags << 1
|
||||
@@ -290,7 +290,7 @@ non_augmented_assignment.py:30:1: PLR6104 [*] Use `|=` to perform an augmented a
|
||||
28 28 | timeDiffSeconds = timeDiffSeconds % 60
|
||||
29 29 | flags = flags & 0x1
|
||||
30 |-flags = flags | 0x1
|
||||
30 |+flags |= 1
|
||||
30 |+flags |= 0x1
|
||||
31 31 | flags = flags ^ 0x1
|
||||
32 32 | flags = flags << 1
|
||||
33 33 | flags = flags >> 1
|
||||
@@ -311,7 +311,7 @@ non_augmented_assignment.py:31:1: PLR6104 [*] Use `^=` to perform an augmented a
|
||||
29 29 | flags = flags & 0x1
|
||||
30 30 | flags = flags | 0x1
|
||||
31 |-flags = flags ^ 0x1
|
||||
31 |+flags ^= 1
|
||||
31 |+flags ^= 0x1
|
||||
32 32 | flags = flags << 1
|
||||
33 33 | flags = flags >> 1
|
||||
34 34 | mat1 = mat1 @ mat2
|
||||
@@ -495,7 +495,7 @@ non_augmented_assignment.py:42:1: PLR6104 [*] Use `*=` to perform an augmented a
|
||||
40 40 | a_list[:] = a_list[:] * 3
|
||||
41 41 |
|
||||
42 |-index = index * (index + 10)
|
||||
42 |+index *= index + 10
|
||||
42 |+index *= (index + 10)
|
||||
43 43 |
|
||||
44 44 |
|
||||
45 45 | class T:
|
||||
@@ -524,8 +524,6 @@ non_augmented_assignment.py:51:1: PLR6104 [*] Use `+=` to perform an augmented a
|
||||
50 | obj = T()
|
||||
51 | obj.a = obj.a + 1
|
||||
| ^^^^^^^^^^^^^^^^^ PLR6104
|
||||
52 |
|
||||
53 | # OK
|
||||
|
|
||||
= help: Replace with augmented assignment
|
||||
|
||||
@@ -536,5 +534,213 @@ non_augmented_assignment.py:51:1: PLR6104 [*] Use `+=` to perform an augmented a
|
||||
51 |-obj.a = obj.a + 1
|
||||
51 |+obj.a += 1
|
||||
52 52 |
|
||||
53 53 | # OK
|
||||
54 54 | a_list[0] = a_list[:] * 3
|
||||
53 53 |
|
||||
54 54 | a = a+-1
|
||||
|
||||
non_augmented_assignment.py:54:1: PLR6104 [*] Use `+=` to perform an augmented assignment directly
|
||||
|
|
||||
54 | a = a+-1
|
||||
| ^^^^^^^^ PLR6104
|
||||
55 |
|
||||
56 | # Regression tests for https://github.com/astral-sh/ruff/issues/11672
|
||||
|
|
||||
= help: Replace with augmented assignment
|
||||
|
||||
ℹ Unsafe fix
|
||||
51 51 | obj.a = obj.a + 1
|
||||
52 52 |
|
||||
53 53 |
|
||||
54 |-a = a+-1
|
||||
54 |+a += -1
|
||||
55 55 |
|
||||
56 56 | # Regression tests for https://github.com/astral-sh/ruff/issues/11672
|
||||
57 57 | test = 0x5
|
||||
|
||||
non_augmented_assignment.py:58:1: PLR6104 [*] Use `+=` to perform an augmented assignment directly
|
||||
|
|
||||
56 | # Regression tests for https://github.com/astral-sh/ruff/issues/11672
|
||||
57 | test = 0x5
|
||||
58 | test = test + 0xBA
|
||||
| ^^^^^^^^^^^^^^^^^^ PLR6104
|
||||
59 |
|
||||
60 | test2 = b""
|
||||
|
|
||||
= help: Replace with augmented assignment
|
||||
|
||||
ℹ Unsafe fix
|
||||
55 55 |
|
||||
56 56 | # Regression tests for https://github.com/astral-sh/ruff/issues/11672
|
||||
57 57 | test = 0x5
|
||||
58 |-test = test + 0xBA
|
||||
58 |+test += 0xBA
|
||||
59 59 |
|
||||
60 60 | test2 = b""
|
||||
61 61 | test2 = test2 + b"\000"
|
||||
|
||||
non_augmented_assignment.py:61:1: PLR6104 [*] Use `+=` to perform an augmented assignment directly
|
||||
|
|
||||
60 | test2 = b""
|
||||
61 | test2 = test2 + b"\000"
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ PLR6104
|
||||
62 |
|
||||
63 | test3 = ""
|
||||
|
|
||||
= help: Replace with augmented assignment
|
||||
|
||||
ℹ Unsafe fix
|
||||
58 58 | test = test + 0xBA
|
||||
59 59 |
|
||||
60 60 | test2 = b""
|
||||
61 |-test2 = test2 + b"\000"
|
||||
61 |+test2 += b"\000"
|
||||
62 62 |
|
||||
63 63 | test3 = ""
|
||||
64 64 | test3 = test3 + ( a := R""
|
||||
|
||||
non_augmented_assignment.py:64:1: PLR6104 [*] Use `+=` to perform an augmented assignment directly
|
||||
|
|
||||
63 | test3 = ""
|
||||
64 | / test3 = test3 + ( a := R""
|
||||
65 | | f"oo" )
|
||||
| |__________________________________^ PLR6104
|
||||
66 |
|
||||
67 | test4 = []
|
||||
|
|
||||
= help: Replace with augmented assignment
|
||||
|
||||
ℹ Unsafe fix
|
||||
61 61 | test2 = test2 + b"\000"
|
||||
62 62 |
|
||||
63 63 | test3 = ""
|
||||
64 |-test3 = test3 + ( a := R""
|
||||
64 |+test3 += ( a := R""
|
||||
65 65 | f"oo" )
|
||||
66 66 |
|
||||
67 67 | test4 = []
|
||||
|
||||
non_augmented_assignment.py:68:1: PLR6104 [*] Use `+=` to perform an augmented assignment directly
|
||||
|
|
||||
67 | test4 = []
|
||||
68 | / test4 = test4 + ( e
|
||||
69 | | for e in
|
||||
70 | | range(10)
|
||||
71 | | )
|
||||
| |___________________^ PLR6104
|
||||
72 |
|
||||
73 | test5 = test5 + (
|
||||
|
|
||||
= help: Replace with augmented assignment
|
||||
|
||||
ℹ Unsafe fix
|
||||
65 65 | f"oo" )
|
||||
66 66 |
|
||||
67 67 | test4 = []
|
||||
68 |-test4 = test4 + ( e
|
||||
68 |+test4 += ( e
|
||||
69 69 | for e in
|
||||
70 70 | range(10)
|
||||
71 71 | )
|
||||
|
||||
non_augmented_assignment.py:73:1: PLR6104 [*] Use `+=` to perform an augmented assignment directly
|
||||
|
|
||||
71 | )
|
||||
72 |
|
||||
73 | / test5 = test5 + (
|
||||
74 | | 4
|
||||
75 | | *
|
||||
76 | | 10
|
||||
77 | | )
|
||||
| |_^ PLR6104
|
||||
78 |
|
||||
79 | test6 = test6 + \
|
||||
|
|
||||
= help: Replace with augmented assignment
|
||||
|
||||
ℹ Unsafe fix
|
||||
70 70 | range(10)
|
||||
71 71 | )
|
||||
72 72 |
|
||||
73 |-test5 = test5 + (
|
||||
73 |+test5 += (
|
||||
74 74 | 4
|
||||
75 75 | *
|
||||
76 76 | 10
|
||||
|
||||
non_augmented_assignment.py:79:1: PLR6104 [*] Use `+=` to perform an augmented assignment directly
|
||||
|
|
||||
77 | )
|
||||
78 |
|
||||
79 | / test6 = test6 + \
|
||||
80 | | (
|
||||
81 | | 4
|
||||
82 | | *
|
||||
83 | | 10
|
||||
84 | | )
|
||||
| |_________^ PLR6104
|
||||
85 |
|
||||
86 | test7 = \
|
||||
|
|
||||
= help: Replace with augmented assignment
|
||||
|
||||
ℹ Unsafe fix
|
||||
76 76 | 10
|
||||
77 77 | )
|
||||
78 78 |
|
||||
79 |-test6 = test6 + \
|
||||
80 |- (
|
||||
79 |+test6 += (
|
||||
81 80 | 4
|
||||
82 81 | *
|
||||
83 82 | 10
|
||||
|
||||
non_augmented_assignment.py:86:1: PLR6104 [*] Use `+=` to perform an augmented assignment directly
|
||||
|
|
||||
84 | )
|
||||
85 |
|
||||
86 | / test7 = \
|
||||
87 | | 100 \
|
||||
88 | | + test7
|
||||
| |___________^ PLR6104
|
||||
89 |
|
||||
90 | test8 = \
|
||||
|
|
||||
= help: Replace with augmented assignment
|
||||
|
||||
ℹ Unsafe fix
|
||||
83 83 | 10
|
||||
84 84 | )
|
||||
85 85 |
|
||||
86 |-test7 = \
|
||||
87 |- 100 \
|
||||
88 |- + test7
|
||||
86 |+test7 += 100
|
||||
89 87 |
|
||||
90 88 | test8 = \
|
||||
91 89 | 886 \
|
||||
|
||||
non_augmented_assignment.py:90:1: PLR6104 [*] Use `+=` to perform an augmented assignment directly
|
||||
|
|
||||
88 | + test7
|
||||
89 |
|
||||
90 | / test8 = \
|
||||
91 | | 886 \
|
||||
92 | | + \
|
||||
93 | | \
|
||||
94 | | test8
|
||||
| |_________^ PLR6104
|
||||
|
|
||||
= help: Replace with augmented assignment
|
||||
|
||||
ℹ Unsafe fix
|
||||
87 87 | 100 \
|
||||
88 88 | + test7
|
||||
89 89 |
|
||||
90 |-test8 = \
|
||||
91 |- 886 \
|
||||
92 |- + \
|
||||
93 |- \
|
||||
94 |- test8
|
||||
90 |+test8 += 886
|
||||
95 91 |
|
||||
96 92 |
|
||||
97 93 | # OK
|
||||
|
||||
@@ -249,13 +249,13 @@ pub enum SimpleTokenKind {
|
||||
/// `;`
|
||||
Semi,
|
||||
|
||||
/// '/'
|
||||
/// `/`
|
||||
Slash,
|
||||
|
||||
/// '*'
|
||||
/// `*`
|
||||
Star,
|
||||
|
||||
/// `.`.
|
||||
/// `.`
|
||||
Dot,
|
||||
|
||||
/// `+`
|
||||
|
||||
@@ -37,18 +37,12 @@ impl ResolvedClientCapabilities {
|
||||
.and_then(|workspace_edit| workspace_edit.document_changes)
|
||||
.unwrap_or_default();
|
||||
|
||||
let workspace_refresh = true;
|
||||
|
||||
// TODO(jane): Once the bug involving workspace.diagnostic(s) deserialization has been fixed,
|
||||
// uncomment this.
|
||||
/*
|
||||
let workspace_refresh = client_capabilities
|
||||
.workspace
|
||||
.as_ref()
|
||||
.and_then(|workspace| workspace.diagnostic.as_ref())
|
||||
.and_then(|workspace| workspace.diagnostics.as_ref())
|
||||
.and_then(|diagnostic| diagnostic.refresh_support)
|
||||
.unwrap_or_default();
|
||||
*/
|
||||
|
||||
let pull_diagnostics = client_capabilities
|
||||
.text_document
|
||||
|
||||
@@ -95,6 +95,7 @@ Similar to Ruff's CLI, the Ruff Language Server fully supports Jupyter Notebook
|
||||
capabilities available to Python files.
|
||||
|
||||
!!! note
|
||||
|
||||
Unlike [`ruff-lsp`](https://github.com/astral-sh/ruff-lsp) and similar to the Ruff's CLI, the
|
||||
native language server requires user to explicitly include the Jupyter Notebook files in the set
|
||||
of files to lint and format. Refer to the [Jupyter Notebook discovery](https://docs.astral.sh/ruff/configuration/#jupyter-notebook-discovery)
|
||||
|
||||
@@ -22,6 +22,7 @@ The Ruff Language Server was available first in Ruff [v0.4.5](https://astral.sh/
|
||||
in beta and stabilized in Ruff [v0.5.3](https://github.com/astral-sh/ruff/releases/tag/0.5.3).
|
||||
|
||||
!!! note
|
||||
|
||||
This is the documentation for Ruff's built-in language server written in Rust (`ruff server`).
|
||||
If you are looking for the documentation for the `ruff-lsp` language server, please refer to the
|
||||
[README](https://github.com/astral-sh/ruff-lsp) of the `ruff-lsp` repository.
|
||||
|
||||
@@ -4,6 +4,7 @@ While `ruff server` supports the same feature set as [`ruff-lsp`](https://github
|
||||
`ruff server` may require changes to your Ruff or language server configuration.
|
||||
|
||||
!!! note
|
||||
|
||||
The [VS Code extension](https://github.com/astral-sh/ruff-vscode) settings include documentation to indicate which
|
||||
settings are supported by `ruff server`. As such, this migration guide is primarily targeted at editors that lack
|
||||
explicit documentation for `ruff server` settings, such as Helix or Neovim.
|
||||
@@ -20,6 +21,7 @@ Several `ruff-lsp` settings are not supported by `ruff server`. These are, as fo
|
||||
- `path`
|
||||
|
||||
!!! note
|
||||
|
||||
Some of these settings, like `interpreter` and `path`, are still accepted by the VS Code
|
||||
extension. `path`, in particular, can be used to specify a dedicated binary to use when
|
||||
initializing `ruff server`. But the language server itself will no longer accept such settings.
|
||||
|
||||
@@ -23,6 +23,7 @@ behavior of the Ruff CLI.
|
||||
**Example usage**:
|
||||
|
||||
=== "VS Code"
|
||||
|
||||
```json
|
||||
{
|
||||
"ruff.configuration": "~/path/to/ruff.toml"
|
||||
@@ -30,6 +31,7 @@ behavior of the Ruff CLI.
|
||||
```
|
||||
|
||||
=== "Neovim"
|
||||
|
||||
```lua
|
||||
require('lspconfig').ruff.setup {
|
||||
init_options = {
|
||||
@@ -41,6 +43,7 @@ behavior of the Ruff CLI.
|
||||
```
|
||||
|
||||
=== "Zed"
|
||||
|
||||
```json
|
||||
{
|
||||
"lsp": {
|
||||
@@ -72,6 +75,7 @@ configuration is prioritized over `ruff.toml` and `pyproject.toml` files.
|
||||
**Example usage**:
|
||||
|
||||
=== "VS Code"
|
||||
|
||||
```json
|
||||
{
|
||||
"ruff.configurationPreference": "filesystemFirst"
|
||||
@@ -79,6 +83,7 @@ configuration is prioritized over `ruff.toml` and `pyproject.toml` files.
|
||||
```
|
||||
|
||||
=== "Neovim"
|
||||
|
||||
```lua
|
||||
require('lspconfig').ruff.setup {
|
||||
init_options = {
|
||||
@@ -90,6 +95,7 @@ configuration is prioritized over `ruff.toml` and `pyproject.toml` files.
|
||||
```
|
||||
|
||||
=== "Zed"
|
||||
|
||||
```json
|
||||
{
|
||||
"lsp": {
|
||||
@@ -116,6 +122,7 @@ documentation](https://docs.astral.sh/ruff/settings/#exclude) for more details.
|
||||
**Example usage**:
|
||||
|
||||
=== "VS Code"
|
||||
|
||||
```json
|
||||
{
|
||||
"ruff.exclude": ["**/tests/**"]
|
||||
@@ -123,6 +130,7 @@ documentation](https://docs.astral.sh/ruff/settings/#exclude) for more details.
|
||||
```
|
||||
|
||||
=== "Neovim"
|
||||
|
||||
```lua
|
||||
require('lspconfig').ruff.setup {
|
||||
init_options = {
|
||||
@@ -134,6 +142,7 @@ documentation](https://docs.astral.sh/ruff/settings/#exclude) for more details.
|
||||
```
|
||||
|
||||
=== "Zed"
|
||||
|
||||
```json
|
||||
{
|
||||
"lsp": {
|
||||
@@ -159,6 +168,7 @@ The line length to use for the linter and formatter.
|
||||
**Example usage**:
|
||||
|
||||
=== "VS Code"
|
||||
|
||||
```json
|
||||
{
|
||||
"ruff.lineLength": 100
|
||||
@@ -166,6 +176,7 @@ The line length to use for the linter and formatter.
|
||||
```
|
||||
|
||||
=== "Neovim"
|
||||
|
||||
```lua
|
||||
require('lspconfig').ruff.setup {
|
||||
init_options = {
|
||||
@@ -177,6 +188,7 @@ The line length to use for the linter and formatter.
|
||||
```
|
||||
|
||||
=== "Zed"
|
||||
|
||||
```json
|
||||
{
|
||||
"lsp": {
|
||||
@@ -202,6 +214,7 @@ Whether to register the server as capable of handling `source.fixAll` code actio
|
||||
**Example usage**:
|
||||
|
||||
=== "VS Code"
|
||||
|
||||
```json
|
||||
{
|
||||
"ruff.fixAll": false
|
||||
@@ -209,6 +222,7 @@ Whether to register the server as capable of handling `source.fixAll` code actio
|
||||
```
|
||||
|
||||
=== "Neovim"
|
||||
|
||||
```lua
|
||||
require('lspconfig').ruff.setup {
|
||||
init_options = {
|
||||
@@ -220,6 +234,7 @@ Whether to register the server as capable of handling `source.fixAll` code actio
|
||||
```
|
||||
|
||||
=== "Zed"
|
||||
|
||||
```json
|
||||
{
|
||||
"lsp": {
|
||||
@@ -245,6 +260,7 @@ Whether to register the server as capable of handling `source.organizeImports` c
|
||||
**Example usage**:
|
||||
|
||||
=== "VS Code"
|
||||
|
||||
```json
|
||||
{
|
||||
"ruff.organizeImports": false
|
||||
@@ -252,6 +268,7 @@ Whether to register the server as capable of handling `source.organizeImports` c
|
||||
```
|
||||
|
||||
=== "Neovim"
|
||||
|
||||
```lua
|
||||
require('lspconfig').ruff.setup {
|
||||
init_options = {
|
||||
@@ -263,6 +280,7 @@ Whether to register the server as capable of handling `source.organizeImports` c
|
||||
```
|
||||
|
||||
=== "Zed"
|
||||
|
||||
```json
|
||||
{
|
||||
"lsp": {
|
||||
@@ -290,6 +308,7 @@ Whether to show syntax error diagnostics.
|
||||
**Example usage**:
|
||||
|
||||
=== "VS Code"
|
||||
|
||||
```json
|
||||
{
|
||||
"ruff.showSyntaxErrors": false
|
||||
@@ -297,6 +316,7 @@ Whether to show syntax error diagnostics.
|
||||
```
|
||||
|
||||
=== "Neovim"
|
||||
|
||||
```lua
|
||||
require('lspconfig').ruff.setup {
|
||||
init_options = {
|
||||
@@ -308,6 +328,7 @@ Whether to show syntax error diagnostics.
|
||||
```
|
||||
|
||||
=== "Zed"
|
||||
|
||||
```json
|
||||
{
|
||||
"lsp": {
|
||||
@@ -333,6 +354,7 @@ The log level to use for the server.
|
||||
**Example usage**:
|
||||
|
||||
=== "VS Code"
|
||||
|
||||
```json
|
||||
{
|
||||
"ruff.logLevel": "debug"
|
||||
@@ -340,6 +362,7 @@ The log level to use for the server.
|
||||
```
|
||||
|
||||
=== "Neovim"
|
||||
|
||||
```lua
|
||||
require('lspconfig').ruff.setup {
|
||||
init_options = {
|
||||
@@ -351,6 +374,7 @@ The log level to use for the server.
|
||||
```
|
||||
|
||||
=== "Zed"
|
||||
|
||||
```json
|
||||
{
|
||||
"lsp": {
|
||||
@@ -378,6 +402,7 @@ If not set, logs will be written to stderr.
|
||||
**Example usage**:
|
||||
|
||||
=== "VS Code"
|
||||
|
||||
```json
|
||||
{
|
||||
"ruff.logFile": "~/path/to/ruff.log"
|
||||
@@ -385,6 +410,7 @@ If not set, logs will be written to stderr.
|
||||
```
|
||||
|
||||
=== "Neovim"
|
||||
|
||||
```lua
|
||||
require('lspconfig').ruff.setup {
|
||||
init_options = {
|
||||
@@ -396,6 +422,7 @@ If not set, logs will be written to stderr.
|
||||
```
|
||||
|
||||
=== "Zed"
|
||||
|
||||
```json
|
||||
{
|
||||
"lsp": {
|
||||
@@ -425,6 +452,7 @@ Whether to display Quick Fix actions to disable rules via `noqa` suppression com
|
||||
**Example usage**:
|
||||
|
||||
=== "VS Code"
|
||||
|
||||
```json
|
||||
{
|
||||
"ruff.codeAction.disableRuleComment.enable": false
|
||||
@@ -432,6 +460,7 @@ Whether to display Quick Fix actions to disable rules via `noqa` suppression com
|
||||
```
|
||||
|
||||
=== "Neovim"
|
||||
|
||||
```lua
|
||||
require('lspconfig').ruff.setup {
|
||||
init_options = {
|
||||
@@ -447,6 +476,7 @@ Whether to display Quick Fix actions to disable rules via `noqa` suppression com
|
||||
```
|
||||
|
||||
=== "Zed"
|
||||
|
||||
```json
|
||||
{
|
||||
"lsp": {
|
||||
@@ -476,6 +506,7 @@ Whether to display Quick Fix actions to autofix violations.
|
||||
**Example usage**:
|
||||
|
||||
=== "VS Code"
|
||||
|
||||
```json
|
||||
{
|
||||
"ruff.codeAction.fixViolation.enable": false
|
||||
@@ -483,6 +514,7 @@ Whether to display Quick Fix actions to autofix violations.
|
||||
```
|
||||
|
||||
=== "Neovim"
|
||||
|
||||
```lua
|
||||
require('lspconfig').ruff.setup {
|
||||
init_options = {
|
||||
@@ -498,6 +530,7 @@ Whether to display Quick Fix actions to autofix violations.
|
||||
```
|
||||
|
||||
=== "Zed"
|
||||
|
||||
```json
|
||||
{
|
||||
"lsp": {
|
||||
@@ -531,6 +564,7 @@ Whether to enable linting. Set to `false` to use Ruff exclusively as a formatter
|
||||
**Example usage**:
|
||||
|
||||
=== "VS Code"
|
||||
|
||||
```json
|
||||
{
|
||||
"ruff.lint.enable": false
|
||||
@@ -538,6 +572,7 @@ Whether to enable linting. Set to `false` to use Ruff exclusively as a formatter
|
||||
```
|
||||
|
||||
=== "Neovim"
|
||||
|
||||
```lua
|
||||
require('lspconfig').ruff.setup {
|
||||
init_options = {
|
||||
@@ -551,6 +586,7 @@ Whether to enable linting. Set to `false` to use Ruff exclusively as a formatter
|
||||
```
|
||||
|
||||
=== "Zed"
|
||||
|
||||
```json
|
||||
{
|
||||
"lsp": {
|
||||
@@ -580,6 +616,7 @@ Whether to enable Ruff's preview mode when linting.
|
||||
**Example usage**:
|
||||
|
||||
=== "VS Code"
|
||||
|
||||
```json
|
||||
{
|
||||
"ruff.lint.preview": true
|
||||
@@ -587,6 +624,7 @@ Whether to enable Ruff's preview mode when linting.
|
||||
```
|
||||
|
||||
=== "Neovim"
|
||||
|
||||
```lua
|
||||
require('lspconfig').ruff.setup {
|
||||
init_options = {
|
||||
@@ -600,6 +638,7 @@ Whether to enable Ruff's preview mode when linting.
|
||||
```
|
||||
|
||||
=== "Zed"
|
||||
|
||||
```json
|
||||
{
|
||||
"lsp": {
|
||||
@@ -627,6 +666,7 @@ Rules to enable by default. See [the documentation](https://docs.astral.sh/ruff/
|
||||
**Example usage**:
|
||||
|
||||
=== "VS Code"
|
||||
|
||||
```json
|
||||
{
|
||||
"ruff.lint.select": ["E", "F"]
|
||||
@@ -634,6 +674,7 @@ Rules to enable by default. See [the documentation](https://docs.astral.sh/ruff/
|
||||
```
|
||||
|
||||
=== "Neovim"
|
||||
|
||||
```lua
|
||||
require('lspconfig').ruff.setup {
|
||||
init_options = {
|
||||
@@ -647,6 +688,7 @@ Rules to enable by default. See [the documentation](https://docs.astral.sh/ruff/
|
||||
```
|
||||
|
||||
=== "Zed"
|
||||
|
||||
```json
|
||||
{
|
||||
"lsp": {
|
||||
@@ -674,6 +716,7 @@ Rules to enable in addition to those in [`lint.select`](#select).
|
||||
**Example usage**:
|
||||
|
||||
=== "VS Code"
|
||||
|
||||
```json
|
||||
{
|
||||
"ruff.lint.extendSelect": ["W"]
|
||||
@@ -681,6 +724,7 @@ Rules to enable in addition to those in [`lint.select`](#select).
|
||||
```
|
||||
|
||||
=== "Neovim"
|
||||
|
||||
```lua
|
||||
require('lspconfig').ruff.setup {
|
||||
init_options = {
|
||||
@@ -694,6 +738,7 @@ Rules to enable in addition to those in [`lint.select`](#select).
|
||||
```
|
||||
|
||||
=== "Zed"
|
||||
|
||||
```json
|
||||
{
|
||||
"lsp": {
|
||||
@@ -721,6 +766,7 @@ Rules to disable by default. See [the documentation](https://docs.astral.sh/ruff
|
||||
**Example usage**:
|
||||
|
||||
=== "VS Code"
|
||||
|
||||
```json
|
||||
{
|
||||
"ruff.lint.ignore": ["E4", "E7"]
|
||||
@@ -728,6 +774,7 @@ Rules to disable by default. See [the documentation](https://docs.astral.sh/ruff
|
||||
```
|
||||
|
||||
=== "Neovim"
|
||||
|
||||
```lua
|
||||
require('lspconfig').ruff.setup {
|
||||
init_options = {
|
||||
@@ -741,6 +788,7 @@ Rules to disable by default. See [the documentation](https://docs.astral.sh/ruff
|
||||
```
|
||||
|
||||
=== "Zed"
|
||||
|
||||
```json
|
||||
{
|
||||
"lsp": {
|
||||
@@ -768,6 +816,7 @@ Rules to disable in addition to those in [`lint.ignore`](#ignore).
|
||||
**Example usage**:
|
||||
|
||||
=== "VS Code"
|
||||
|
||||
```json
|
||||
{
|
||||
"ruff.lint.extendIgnore": ["W1"]
|
||||
@@ -775,6 +824,7 @@ Rules to disable in addition to those in [`lint.ignore`](#ignore).
|
||||
```
|
||||
|
||||
=== "Neovim"
|
||||
|
||||
```lua
|
||||
require('lspconfig').ruff.setup {
|
||||
init_options = {
|
||||
@@ -788,6 +838,7 @@ Rules to disable in addition to those in [`lint.ignore`](#ignore).
|
||||
```
|
||||
|
||||
=== "Zed"
|
||||
|
||||
```json
|
||||
{
|
||||
"lsp": {
|
||||
@@ -819,6 +870,7 @@ Whether to enable Ruff's preview mode when formatting.
|
||||
**Example usage**:
|
||||
|
||||
=== "VS Code"
|
||||
|
||||
```json
|
||||
{
|
||||
"ruff.format.preview": true
|
||||
@@ -826,6 +878,7 @@ Whether to enable Ruff's preview mode when formatting.
|
||||
```
|
||||
|
||||
=== "Neovim"
|
||||
|
||||
```lua
|
||||
require('lspconfig').ruff.setup {
|
||||
init_options = {
|
||||
@@ -839,6 +892,7 @@ Whether to enable Ruff's preview mode when formatting.
|
||||
```
|
||||
|
||||
=== "Zed"
|
||||
|
||||
```json
|
||||
{
|
||||
"lsp": {
|
||||
|
||||
@@ -8,11 +8,13 @@ regardless of editor, there are several settings which have changed or are no lo
|
||||
the [migration guide](./migration.md) for more.
|
||||
|
||||
!!! note
|
||||
|
||||
The setup instructions provided below are on a best-effort basis. If you encounter any issues
|
||||
while setting up the Ruff in an editor, please [open an issue](https://github.com/astral-sh/ruff/issues/new)
|
||||
for assistance and help in improving this documentation.
|
||||
|
||||
!!! tip
|
||||
|
||||
Regardless of the editor, it is recommended to disable the older language server
|
||||
([`ruff-lsp`](https://github.com/astral-sh/ruff-lsp)) to prevent any conflicts.
|
||||
|
||||
@@ -45,6 +47,7 @@ require('lspconfig').ruff.setup({
|
||||
```
|
||||
|
||||
!!! note
|
||||
|
||||
If the installed version of `nvim-lspconfig` includes the changes from
|
||||
[neovim/nvim-lspconfig@`70d1c2c`](https://github.com/neovim/nvim-lspconfig/commit/70d1c2c31a88af4b36019dc1551be16bffb8f9db),
|
||||
you will need to use Ruff version `0.5.3` or later.
|
||||
@@ -242,6 +245,7 @@ language-servers = ["ruff", "pylsp"]
|
||||
```
|
||||
|
||||
!!! note
|
||||
|
||||
Support for multiple language servers for a language is only available in Helix version
|
||||
[`23.10`](https://github.com/helix-editor/helix/blob/master/CHANGELOG.md#2310-2023-10-24) and later.
|
||||
|
||||
@@ -287,6 +291,7 @@ logLevel = "debug"
|
||||
You can also divert Ruff's logs to a separate file with the [`logFile`](./settings.md#logfile) setting.
|
||||
|
||||
!!! note
|
||||
|
||||
Setting `RUFF_TRACE=verbose` does not enable Helix's verbose mode by itself. You'll need to run
|
||||
Helix with `-v` for verbose logging.
|
||||
|
||||
@@ -313,6 +318,7 @@ See [LSP Client documentation](https://docs.kde.org/stable5/en/kate/kate/kate-ap
|
||||
on how to configure the server from there.
|
||||
|
||||
!!! important
|
||||
|
||||
Kate's LSP Client plugin does not support multiple servers for the same language. As a
|
||||
workaround, you can use the [`python-lsp-server`](https://github.com/python-lsp/python-lsp-server)
|
||||
along with the [`python-lsp-ruff`](https://github.com/python-lsp/python-lsp-ruff) plugin to
|
||||
@@ -431,6 +437,7 @@ under the [`lsp.ruff.initialization_options.settings`](https://zed.dev/docs/conf
|
||||
```
|
||||
|
||||
!!! note
|
||||
|
||||
Support for multiple formatters for a given language is only available in Zed version
|
||||
`0.146.0` and later.
|
||||
|
||||
@@ -438,6 +445,7 @@ You can configure Ruff to format Python code on-save by registering the Ruff for
|
||||
and enabling the [`format_on_save`](https://zed.dev/docs/configuring-zed#format-on-save) setting:
|
||||
|
||||
=== "Zed 0.146.0+"
|
||||
|
||||
```json
|
||||
{
|
||||
"languages": {
|
||||
@@ -460,6 +468,7 @@ You can configure Ruff to fix lint violations and/or organize imports on-save by
|
||||
`source.fixAll.ruff` and `source.organizeImports.ruff` code actions respectively:
|
||||
|
||||
=== "Zed 0.146.0+"
|
||||
|
||||
```json
|
||||
{
|
||||
"languages": {
|
||||
@@ -485,12 +494,14 @@ Taken together, you can configure Ruff to format, fix, and organize imports on-s
|
||||
following `settings.json`:
|
||||
|
||||
!!! note
|
||||
|
||||
For this configuration, it is important to use the correct order of the code action and
|
||||
formatter language server settings. The code actions should be defined before the formatter to
|
||||
ensure that the formatter takes care of any remaining style issues after the code actions have
|
||||
been applied.
|
||||
|
||||
=== "Zed 0.146.0+"
|
||||
|
||||
```json
|
||||
{
|
||||
"languages": {
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
PyYAML==6.0.2
|
||||
ruff==0.8.2
|
||||
ruff==0.8.3
|
||||
mkdocs==1.6.1
|
||||
mkdocs-material @ git+ssh://git@github.com/astral-sh/mkdocs-material-insiders.git@39da7a5e761410349e9a1b8abf593b0cdd5453ff
|
||||
mkdocs-redirects==1.2.2
|
||||
mdformat==0.7.19
|
||||
mdformat-mkdocs==3.1.1
|
||||
mdformat-admon==2.0.6
|
||||
mdformat-mkdocs==4.0.0
|
||||
# Using a commit from pygments main branch so we get
|
||||
# https://github.com/pygments/pygments/pull/2773 before it's been released
|
||||
pygments @ git+https://github.com/pygments/pygments.git@67b460fdde6d9a00342b5102b37b3a8399f0e8ef
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
PyYAML==6.0.2
|
||||
ruff==0.8.2
|
||||
ruff==0.8.3
|
||||
mkdocs==1.6.1
|
||||
mkdocs-material==9.5.38
|
||||
mkdocs-redirects==1.2.2
|
||||
mdformat==0.7.19
|
||||
mdformat-mkdocs==3.1.1
|
||||
mdformat-admon==2.0.6
|
||||
mdformat-mkdocs==4.0.0
|
||||
# Using a commit from pygments main branch so we get
|
||||
# https://github.com/pygments/pygments/pull/2773 before it's been released
|
||||
pygments @ git+https://github.com/pygments/pygments.git@67b460fdde6d9a00342b5102b37b3a8399f0e8ef
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
name = "ruff-fuzz"
|
||||
version = "0.0.0"
|
||||
authors = [
|
||||
"Charlie Marsh <charlie.r.marsh@gmail.com>",
|
||||
"Addison Crump <research@addisoncrump.info>",
|
||||
"Charlie Marsh <charlie.r.marsh@gmail.com>",
|
||||
"Addison Crump <research@addisoncrump.info>",
|
||||
]
|
||||
publish = false
|
||||
edition = "2021"
|
||||
@@ -25,11 +25,11 @@ ruff_python_ast = { path = "../crates/ruff_python_ast" }
|
||||
ruff_python_codegen = { path = "../crates/ruff_python_codegen" }
|
||||
ruff_python_parser = { path = "../crates/ruff_python_parser" }
|
||||
ruff_source_file = { path = "../crates/ruff_source_file" }
|
||||
ruff_python_formatter = { path = "../crates/ruff_python_formatter"}
|
||||
ruff_python_formatter = { path = "../crates/ruff_python_formatter" }
|
||||
ruff_text_size = { path = "../crates/ruff_text_size" }
|
||||
|
||||
libfuzzer-sys = { git = "https://github.com/rust-fuzz/libfuzzer", default-features = false }
|
||||
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "254c749b02cde2fd29852a7463a33e800b771758" }
|
||||
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "3c7f1694c9efba751dbeeacfbc93b227586e316a" }
|
||||
similar = { version = "2.5.0" }
|
||||
tracing = { version = "0.1.40" }
|
||||
|
||||
|
||||
@@ -22,12 +22,13 @@ use ruff_python_parser::{parse_unchecked, Mode};
|
||||
///
|
||||
/// Uses an in memory filesystem and it stubs out the vendored files by default.
|
||||
#[salsa::db]
|
||||
#[derive(Clone)]
|
||||
struct TestDb {
|
||||
storage: salsa::Storage<Self>,
|
||||
files: Files,
|
||||
system: TestSystem,
|
||||
vendored: VendoredFileSystem,
|
||||
events: std::sync::Arc<std::sync::Mutex<Vec<salsa::Event>>>,
|
||||
events: std::sync::Arc<Mutex<Vec<salsa::Event>>>,
|
||||
rule_selection: std::sync::Arc<RuleSelection>,
|
||||
}
|
||||
|
||||
|
||||
16
playground/api/package-lock.json
generated
16
playground/api/package-lock.json
generated
@@ -16,7 +16,7 @@
|
||||
"@cloudflare/workers-types": "^4.20230801.0",
|
||||
"miniflare": "^3.20230801.1",
|
||||
"typescript": "^5.1.6",
|
||||
"wrangler": "3.93.0"
|
||||
"wrangler": "3.95.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@cloudflare/kv-asset-handler": {
|
||||
@@ -118,9 +118,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@cloudflare/workers-shared": {
|
||||
"version": "0.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@cloudflare/workers-shared/-/workers-shared-0.10.0.tgz",
|
||||
"integrity": "sha512-j3EwZBc9ctavmFVOQT1gqztRO/Plx4ZR0LMEEOif+5YoCcuD1P7/NEjlODPMc5a1w+8+7A/H+Ci8Ihd55+x0Zw==",
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@cloudflare/workers-shared/-/workers-shared-0.11.0.tgz",
|
||||
"integrity": "sha512-A+lQ8xp7992qSeMmuQ0ssL6CPmm+ZmAv6Ddikan0n1jjpMAic+97l7xtVIsswSn9iLMFPYQ9uNN/8Fl0AgARIQ==",
|
||||
"dev": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"dependencies": {
|
||||
@@ -1453,14 +1453,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/wrangler": {
|
||||
"version": "3.93.0",
|
||||
"resolved": "https://registry.npmjs.org/wrangler/-/wrangler-3.93.0.tgz",
|
||||
"integrity": "sha512-+wfxjOrtm6YgDS+NdJkB6aiBIS3ED97mNRQmfrEShRJW4pVo4sWY6oQ1FsGT+j4tGHplrTbWCE6U5yTgjNW/lw==",
|
||||
"version": "3.95.0",
|
||||
"resolved": "https://registry.npmjs.org/wrangler/-/wrangler-3.95.0.tgz",
|
||||
"integrity": "sha512-3w5852i3FNyDz421K2Qk4v5L8jjwegO5O8E1+VAQmjnm82HFNxpIRUBq0bmM7CTLvOPI/Jjcmj/eAWjQBL7QYg==",
|
||||
"dev": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"dependencies": {
|
||||
"@cloudflare/kv-asset-handler": "0.3.4",
|
||||
"@cloudflare/workers-shared": "0.10.0",
|
||||
"@cloudflare/workers-shared": "0.11.0",
|
||||
"@esbuild-plugins/node-globals-polyfill": "^0.2.3",
|
||||
"@esbuild-plugins/node-modules-polyfill": "^0.2.2",
|
||||
"blake3-wasm": "^2.1.5",
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"@cloudflare/workers-types": "^4.20230801.0",
|
||||
"miniflare": "^3.20230801.1",
|
||||
"typescript": "^5.1.6",
|
||||
"wrangler": "3.93.0"
|
||||
"wrangler": "3.95.0"
|
||||
},
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
150
playground/package-lock.json
generated
150
playground/package-lock.json
generated
@@ -1219,25 +1219,27 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-dom": {
|
||||
"version": "19.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.0.1.tgz",
|
||||
"integrity": "sha512-hljHij7MpWPKF6u5vojuyfV0YA4YURsQG7KT6SzV0Zs2BXAtgdTxG6A229Ub/xiWV4w/7JL8fi6aAyjshH4meA==",
|
||||
"version": "19.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.0.2.tgz",
|
||||
"integrity": "sha512-c1s+7TKFaDRRxr1TxccIX2u7sfCnc3RxkVyBIUA2lCpyqCF+QoAwQ/CBg7bsMdVwP120HEH143VQezKtef5nCg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/react": "*"
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "8.17.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.17.0.tgz",
|
||||
"integrity": "sha512-HU1KAdW3Tt8zQkdvNoIijfWDMvdSweFYm4hWh+KwhPstv+sCmWb89hCIP8msFm9N1R/ooh9honpSuvqKWlYy3w==",
|
||||
"version": "8.18.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.18.0.tgz",
|
||||
"integrity": "sha512-NR2yS7qUqCL7AIxdJUQf2MKKNDVNaig/dEB0GBLU7D+ZdHgK1NoH/3wsgO3OnPVipn51tG3MAwaODEGil70WEw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/regexpp": "^4.10.0",
|
||||
"@typescript-eslint/scope-manager": "8.17.0",
|
||||
"@typescript-eslint/type-utils": "8.17.0",
|
||||
"@typescript-eslint/utils": "8.17.0",
|
||||
"@typescript-eslint/visitor-keys": "8.17.0",
|
||||
"@typescript-eslint/scope-manager": "8.18.0",
|
||||
"@typescript-eslint/type-utils": "8.18.0",
|
||||
"@typescript-eslint/utils": "8.18.0",
|
||||
"@typescript-eslint/visitor-keys": "8.18.0",
|
||||
"graphemer": "^1.4.0",
|
||||
"ignore": "^5.3.1",
|
||||
"natural-compare": "^1.4.0",
|
||||
@@ -1252,24 +1254,21 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0",
|
||||
"eslint": "^8.57.0 || ^9.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
"eslint": "^8.57.0 || ^9.0.0",
|
||||
"typescript": ">=4.8.4 <5.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser": {
|
||||
"version": "8.17.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.17.0.tgz",
|
||||
"integrity": "sha512-Drp39TXuUlD49F7ilHHCG7TTg8IkA+hxCuULdmzWYICxGXvDXmDmWEjJYZQYgf6l/TFfYNE167m7isnc3xlIEg==",
|
||||
"version": "8.18.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.18.0.tgz",
|
||||
"integrity": "sha512-hgUZ3kTEpVzKaK3uNibExUYm6SKKOmTU2BOxBSvOYwtJEPdVQ70kZJpPjstlnhCHcuc2WGfSbpKlb/69ttyN5Q==",
|
||||
"dev": true,
|
||||
"license": "MITClause",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.17.0",
|
||||
"@typescript-eslint/types": "8.17.0",
|
||||
"@typescript-eslint/typescript-estree": "8.17.0",
|
||||
"@typescript-eslint/visitor-keys": "8.17.0",
|
||||
"@typescript-eslint/scope-manager": "8.18.0",
|
||||
"@typescript-eslint/types": "8.18.0",
|
||||
"@typescript-eslint/typescript-estree": "8.18.0",
|
||||
"@typescript-eslint/visitor-keys": "8.18.0",
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
"engines": {
|
||||
@@ -1280,22 +1279,19 @@
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "^8.57.0 || ^9.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
"eslint": "^8.57.0 || ^9.0.0",
|
||||
"typescript": ">=4.8.4 <5.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "8.17.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.17.0.tgz",
|
||||
"integrity": "sha512-/ewp4XjvnxaREtqsZjF4Mfn078RD/9GmiEAtTeLQ7yFdKnqwTOgRMSvFz4et9U5RiJQ15WTGXPLj89zGusvxBg==",
|
||||
"version": "8.18.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.18.0.tgz",
|
||||
"integrity": "sha512-PNGcHop0jkK2WVYGotk/hxj+UFLhXtGPiGtiaWgVBVP1jhMoMCHlTyJA+hEj4rszoSdLTK3fN4oOatrL0Cp+Xw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.17.0",
|
||||
"@typescript-eslint/visitor-keys": "8.17.0"
|
||||
"@typescript-eslint/types": "8.18.0",
|
||||
"@typescript-eslint/visitor-keys": "8.18.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -1306,13 +1302,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils": {
|
||||
"version": "8.17.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.17.0.tgz",
|
||||
"integrity": "sha512-q38llWJYPd63rRnJ6wY/ZQqIzPrBCkPdpIsaCfkR3Q4t3p6sb422zougfad4TFW9+ElIFLVDzWGiGAfbb/v2qw==",
|
||||
"version": "8.18.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.18.0.tgz",
|
||||
"integrity": "sha512-er224jRepVAVLnMF2Q7MZJCq5CsdH2oqjP4dT7K6ij09Kyd+R21r7UVJrF0buMVdZS5QRhDzpvzAxHxabQadow==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/typescript-estree": "8.17.0",
|
||||
"@typescript-eslint/utils": "8.17.0",
|
||||
"@typescript-eslint/typescript-estree": "8.18.0",
|
||||
"@typescript-eslint/utils": "8.18.0",
|
||||
"debug": "^4.3.4",
|
||||
"ts-api-utils": "^1.3.0"
|
||||
},
|
||||
@@ -1324,19 +1321,16 @@
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "^8.57.0 || ^9.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
"eslint": "^8.57.0 || ^9.0.0",
|
||||
"typescript": ">=4.8.4 <5.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/types": {
|
||||
"version": "8.17.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.17.0.tgz",
|
||||
"integrity": "sha512-gY2TVzeve3z6crqh2Ic7Cr+CAv6pfb0Egee7J5UAVWCpVvDI/F71wNfolIim4FE6hT15EbpZFVUj9j5i38jYXA==",
|
||||
"version": "8.18.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.18.0.tgz",
|
||||
"integrity": "sha512-FNYxgyTCAnFwTrzpBGq+zrnoTO4x0c1CKYY5MuUTzpScqmY5fmsh2o3+57lqdI3NZucBDCzDgdEbIaNfAjAHQA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
@@ -1346,13 +1340,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "8.17.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.17.0.tgz",
|
||||
"integrity": "sha512-JqkOopc1nRKZpX+opvKqnM3XUlM7LpFMD0lYxTqOTKQfCWAmxw45e3qlOCsEqEB2yuacujivudOFpCnqkBDNMw==",
|
||||
"version": "8.18.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.18.0.tgz",
|
||||
"integrity": "sha512-rqQgFRu6yPkauz+ms3nQpohwejS8bvgbPyIDq13cgEDbkXt4LH4OkDMT0/fN1RUtzG8e8AKJyDBoocuQh8qNeg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.17.0",
|
||||
"@typescript-eslint/visitor-keys": "8.17.0",
|
||||
"@typescript-eslint/types": "8.18.0",
|
||||
"@typescript-eslint/visitor-keys": "8.18.0",
|
||||
"debug": "^4.3.4",
|
||||
"fast-glob": "^3.3.2",
|
||||
"is-glob": "^4.0.3",
|
||||
@@ -1367,22 +1362,21 @@
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
"peerDependencies": {
|
||||
"typescript": ">=4.8.4 <5.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/utils": {
|
||||
"version": "8.17.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.17.0.tgz",
|
||||
"integrity": "sha512-bQC8BnEkxqG8HBGKwG9wXlZqg37RKSMY7v/X8VEWD8JG2JuTHuNK0VFvMPMUKQcbk6B+tf05k+4AShAEtCtJ/w==",
|
||||
"version": "8.18.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.18.0.tgz",
|
||||
"integrity": "sha512-p6GLdY383i7h5b0Qrfbix3Vc3+J2k6QWw6UMUeY5JGfm3C5LbZ4QIZzJNoNOfgyRe0uuYKjvVOsO/jD4SJO+xg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.4.0",
|
||||
"@typescript-eslint/scope-manager": "8.17.0",
|
||||
"@typescript-eslint/types": "8.17.0",
|
||||
"@typescript-eslint/typescript-estree": "8.17.0"
|
||||
"@typescript-eslint/scope-manager": "8.18.0",
|
||||
"@typescript-eslint/types": "8.18.0",
|
||||
"@typescript-eslint/typescript-estree": "8.18.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -1392,21 +1386,18 @@
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "^8.57.0 || ^9.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
"eslint": "^8.57.0 || ^9.0.0",
|
||||
"typescript": ">=4.8.4 <5.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "8.17.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.17.0.tgz",
|
||||
"integrity": "sha512-1Hm7THLpO6ww5QU6H/Qp+AusUUl+z/CAm3cNZZ0jQvon9yicgO7Rwd+/WWRpMKLYV6p2UvdbR27c86rzCPpreg==",
|
||||
"version": "8.18.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.18.0.tgz",
|
||||
"integrity": "sha512-pCh/qEA8Lb1wVIqNvBke8UaRjJ6wrAWkJO5yyIbs8Yx6TNGYyfNjOo61tLv+WwLvoLPp4BQ8B7AHKijl8NGUfw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.17.0",
|
||||
"@typescript-eslint/types": "8.18.0",
|
||||
"eslint-visitor-keys": "^4.2.0"
|
||||
},
|
||||
"engines": {
|
||||
@@ -1422,6 +1413,7 @@
|
||||
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
|
||||
"integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
@@ -3936,9 +3928,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/monaco-editor": {
|
||||
"version": "0.52.0",
|
||||
"resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.52.0.tgz",
|
||||
"integrity": "sha512-OeWhNpABLCeTqubfqLMXGsqf6OmPU6pHM85kF3dhy6kq5hnhuVS1p3VrEW/XhWHc71P2tHyS5JFySD8mgs1crw=="
|
||||
"version": "0.52.2",
|
||||
"resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.52.2.tgz",
|
||||
"integrity": "sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
@@ -4753,6 +4746,7 @@
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
|
||||
"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import mdformat
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import argparse
|
||||
|
||||
from markdown_it import MarkdownIt
|
||||
from mdformat.renderer import RenderContext, RenderTreeNode
|
||||
|
||||
|
||||
class NoEscapeTextPlugin:
|
||||
r"""Overrides the default text formatting behavior of mdformat.
|
||||
|
||||
By default mdformat will escape any markdown special character found in a
|
||||
text block, e.g., <. Some of these characters are found in our
|
||||
documentation, and when escaped (i.e. \<) will be rendered incorrectly by
|
||||
mkdocs, i.e., the backslash will appear in the render. Because our only
|
||||
purpose in using mdformat is to manage the line-breaks, it makes sense to
|
||||
override its text formatting behavior.
|
||||
"""
|
||||
|
||||
def __init__(self: NoEscapeTextPlugin) -> None:
|
||||
self.POSTPROCESSORS = {"text": NoEscapeTextPlugin.text}
|
||||
self.RENDERERS = {}
|
||||
|
||||
@staticmethod
|
||||
def add_cli_options(parser: argparse.ArgumentParser) -> None:
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def update_mdit(mdit: MarkdownIt) -> None:
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def text(_text: str, node: RenderTreeNode, _context: RenderContext) -> str:
|
||||
return node.content
|
||||
|
||||
|
||||
def add_no_escape_text_plugin() -> None:
|
||||
"""Add NoEscapeTextPlugin to the list of mdformat extensions."""
|
||||
mdformat.plugins.PARSER_EXTENSIONS["no-escape-text"] = NoEscapeTextPlugin()
|
||||
@@ -14,8 +14,6 @@ from typing import NamedTuple
|
||||
import mdformat
|
||||
import yaml
|
||||
|
||||
from _mdformat_utils import add_no_escape_text_plugin
|
||||
|
||||
|
||||
class Section(NamedTuple):
|
||||
"""A section to include in the MkDocs documentation."""
|
||||
@@ -229,12 +227,11 @@ def main() -> None:
|
||||
|
||||
f.write(clean_file_content(file_content, title))
|
||||
|
||||
add_no_escape_text_plugin()
|
||||
for rule_doc in Path("docs/rules").glob("*.md"):
|
||||
# Format rules docs. This has to be completed before adding the meta description
|
||||
# otherwise the meta description will be formatted in a way that mkdocs does not
|
||||
# support.
|
||||
mdformat.file(rule_doc, extensions=["mkdocs", "admon", "no-escape-text"])
|
||||
mdformat.file(rule_doc, extensions=["mkdocs"])
|
||||
|
||||
generate_rule_metadata(rule_doc)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user