Compare commits
2 Commits
charlie/ru
...
alex/union
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c872fe4c08 | ||
|
|
f497167798 |
@@ -5,4 +5,4 @@ rustup component add clippy rustfmt
|
||||
cargo install cargo-insta
|
||||
cargo fetch
|
||||
|
||||
pip install maturin prek
|
||||
pip install maturin pre-commit
|
||||
|
||||
4
.github/actionlint.yaml
vendored
4
.github/actionlint.yaml
vendored
@@ -1,4 +1,4 @@
|
||||
# Configuration for the actionlint tool, which we run via prek
|
||||
# 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:
|
||||
@@ -17,4 +17,4 @@ self-hosted-runner:
|
||||
paths:
|
||||
".github/workflows/mypy_primer.yaml":
|
||||
ignore:
|
||||
- 'constant expression "false" in condition. remove the if: section'
|
||||
- 'condition "false" is always evaluated to false. remove the if: section'
|
||||
|
||||
4
.github/renovate.json5
vendored
4
.github/renovate.json5
vendored
@@ -76,9 +76,9 @@
|
||||
enabled: false,
|
||||
},
|
||||
{
|
||||
groupName: "prek dependencies",
|
||||
groupName: "pre-commit dependencies",
|
||||
matchManagers: ["pre-commit"],
|
||||
description: "Weekly update of prek dependencies",
|
||||
description: "Weekly update of pre-commit dependencies",
|
||||
},
|
||||
{
|
||||
groupName: "NPM Development dependencies",
|
||||
|
||||
70
.github/workflows/ci.yaml
vendored
70
.github/workflows/ci.yaml
vendored
@@ -281,15 +281,15 @@ jobs:
|
||||
- name: "Install mold"
|
||||
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||
- name: "Install cargo nextest"
|
||||
uses: taiki-e/install-action@0e76c5c569f13f7eb21e8e5b26fe710062b57b62 # v2.65.13
|
||||
uses: taiki-e/install-action@28a9d316db64b78a951f3f8587a5d08cc97ad8eb # v2.65.6
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
- name: "Install cargo insta"
|
||||
uses: taiki-e/install-action@0e76c5c569f13f7eb21e8e5b26fe710062b57b62 # v2.65.13
|
||||
uses: taiki-e/install-action@28a9d316db64b78a951f3f8587a5d08cc97ad8eb # v2.65.6
|
||||
with:
|
||||
tool: cargo-insta
|
||||
- name: "Install uv"
|
||||
uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
|
||||
uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
|
||||
with:
|
||||
enable-cache: "true"
|
||||
- name: ty mdtests (GitHub annotations)
|
||||
@@ -343,11 +343,11 @@ jobs:
|
||||
- name: "Install mold"
|
||||
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||
- name: "Install cargo nextest"
|
||||
uses: taiki-e/install-action@0e76c5c569f13f7eb21e8e5b26fe710062b57b62 # v2.65.13
|
||||
uses: taiki-e/install-action@28a9d316db64b78a951f3f8587a5d08cc97ad8eb # v2.65.6
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
- name: "Install uv"
|
||||
uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
|
||||
uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
|
||||
with:
|
||||
enable-cache: "true"
|
||||
- name: "Run tests"
|
||||
@@ -376,11 +376,11 @@ jobs:
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install cargo nextest"
|
||||
uses: taiki-e/install-action@0e76c5c569f13f7eb21e8e5b26fe710062b57b62 # v2.65.13
|
||||
uses: taiki-e/install-action@28a9d316db64b78a951f3f8587a5d08cc97ad8eb # v2.65.6
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
- name: "Install uv"
|
||||
uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
|
||||
uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
|
||||
with:
|
||||
enable-cache: "true"
|
||||
- name: "Run tests"
|
||||
@@ -486,7 +486,7 @@ jobs:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
|
||||
- uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
|
||||
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||
with:
|
||||
shared-key: ruff-linux-debug
|
||||
@@ -521,7 +521,7 @@ jobs:
|
||||
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
- uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
|
||||
- uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup component add rustfmt
|
||||
# Run all code generation scripts, and verify that the current output is
|
||||
@@ -561,7 +561,7 @@ jobs:
|
||||
ref: ${{ github.event.pull_request.base.ref }}
|
||||
persist-credentials: false
|
||||
|
||||
- uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
|
||||
- uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
activate-environment: true
|
||||
@@ -667,7 +667,7 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
|
||||
- uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
|
||||
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
@@ -726,7 +726,7 @@ jobs:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
|
||||
- uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
|
||||
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
@@ -769,32 +769,32 @@ jobs:
|
||||
- name: "Remove wheels from cache"
|
||||
run: rm -rf target/wheels
|
||||
|
||||
prek:
|
||||
name: "prek"
|
||||
pre-commit:
|
||||
name: "pre-commit"
|
||||
runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-16' || 'ubuntu-latest' }}
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
|
||||
- uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
|
||||
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||
with:
|
||||
node-version: 24
|
||||
- name: "Cache prek"
|
||||
- name: "Cache pre-commit"
|
||||
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
|
||||
with:
|
||||
path: ~/.cache/prek
|
||||
key: prek-${{ hashFiles('.pre-commit-config.yaml') }}
|
||||
- name: "Run prek"
|
||||
path: ~/.cache/pre-commit
|
||||
key: pre-commit-${{ hashFiles('.pre-commit-config.yaml') }}
|
||||
- name: "Run pre-commit"
|
||||
run: |
|
||||
echo '```console' > "$GITHUB_STEP_SUMMARY"
|
||||
# Enable color output for prek and remove it for the summary
|
||||
# Use --hook-stage=manual to enable slower hooks that are skipped by default
|
||||
SKIP=cargo-fmt uvx prek run --all-files --show-diff-on-failure --color always --hook-stage manual | \
|
||||
# Enable color output for pre-commit and remove it for the summary
|
||||
# Use --hook-stage=manual to enable slower pre-commit hooks that are skipped by default
|
||||
SKIP=cargo-fmt uvx --python="${PYTHON_VERSION}" pre-commit run --all-files --show-diff-on-failure --color=always --hook-stage=manual | \
|
||||
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"
|
||||
@@ -814,7 +814,7 @@ jobs:
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
|
||||
uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
|
||||
with:
|
||||
python-version: 3.13
|
||||
activate-environment: true
|
||||
@@ -966,13 +966,13 @@ jobs:
|
||||
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
- uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
|
||||
- uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
|
||||
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
|
||||
- name: "Install codspeed"
|
||||
uses: taiki-e/install-action@0e76c5c569f13f7eb21e8e5b26fe710062b57b62 # v2.65.13
|
||||
uses: taiki-e/install-action@28a9d316db64b78a951f3f8587a5d08cc97ad8eb # v2.65.6
|
||||
with:
|
||||
tool: cargo-codspeed
|
||||
|
||||
@@ -980,7 +980,7 @@ jobs:
|
||||
run: cargo codspeed build --features "codspeed,ruff_instrumented" --profile profiling --no-default-features -p ruff_benchmark --bench formatter --bench lexer --bench linter --bench parser
|
||||
|
||||
- name: "Run benchmarks"
|
||||
uses: CodSpeedHQ/action@dbda7111f8ac363564b0c51b992d4ce76bb89f2f # v4.5.2
|
||||
uses: CodSpeedHQ/action@972e3437949c89e1357ebd1a2dbc852fcbc57245 # v4.5.1
|
||||
with:
|
||||
mode: simulation
|
||||
run: cargo codspeed run
|
||||
@@ -1011,7 +1011,7 @@ jobs:
|
||||
run: rustup show
|
||||
|
||||
- name: "Install codspeed"
|
||||
uses: taiki-e/install-action@0e76c5c569f13f7eb21e8e5b26fe710062b57b62 # v2.65.13
|
||||
uses: taiki-e/install-action@28a9d316db64b78a951f3f8587a5d08cc97ad8eb # v2.65.6
|
||||
with:
|
||||
tool: cargo-codspeed
|
||||
|
||||
@@ -1044,10 +1044,10 @@ jobs:
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
|
||||
- uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
|
||||
|
||||
- name: "Install codspeed"
|
||||
uses: taiki-e/install-action@0e76c5c569f13f7eb21e8e5b26fe710062b57b62 # v2.65.13
|
||||
uses: taiki-e/install-action@28a9d316db64b78a951f3f8587a5d08cc97ad8eb # v2.65.6
|
||||
with:
|
||||
tool: cargo-codspeed
|
||||
|
||||
@@ -1061,7 +1061,7 @@ jobs:
|
||||
run: chmod +x target/codspeed/simulation/ruff_benchmark/ty
|
||||
|
||||
- name: "Run benchmarks"
|
||||
uses: CodSpeedHQ/action@dbda7111f8ac363564b0c51b992d4ce76bb89f2f # v4.5.2
|
||||
uses: CodSpeedHQ/action@972e3437949c89e1357ebd1a2dbc852fcbc57245 # v4.5.1
|
||||
with:
|
||||
mode: simulation
|
||||
run: cargo codspeed run --bench ty "${{ matrix.benchmark }}"
|
||||
@@ -1092,13 +1092,13 @@ jobs:
|
||||
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
- uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
|
||||
- uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
|
||||
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
|
||||
- name: "Install codspeed"
|
||||
uses: taiki-e/install-action@0e76c5c569f13f7eb21e8e5b26fe710062b57b62 # v2.65.13
|
||||
uses: taiki-e/install-action@28a9d316db64b78a951f3f8587a5d08cc97ad8eb # v2.65.6
|
||||
with:
|
||||
tool: cargo-codspeed
|
||||
|
||||
@@ -1133,10 +1133,10 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
|
||||
- uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
|
||||
|
||||
- name: "Install codspeed"
|
||||
uses: taiki-e/install-action@0e76c5c569f13f7eb21e8e5b26fe710062b57b62 # v2.65.13
|
||||
uses: taiki-e/install-action@28a9d316db64b78a951f3f8587a5d08cc97ad8eb # v2.65.6
|
||||
with:
|
||||
tool: cargo-codspeed
|
||||
|
||||
@@ -1150,7 +1150,7 @@ jobs:
|
||||
run: chmod +x target/codspeed/walltime/ruff_benchmark/ty_walltime
|
||||
|
||||
- name: "Run benchmarks"
|
||||
uses: CodSpeedHQ/action@dbda7111f8ac363564b0c51b992d4ce76bb89f2f # v4.5.2
|
||||
uses: CodSpeedHQ/action@972e3437949c89e1357ebd1a2dbc852fcbc57245 # v4.5.1
|
||||
env:
|
||||
# enabling walltime flamegraphs adds ~6 minutes to the CI time, and they don't
|
||||
# appear to provide much useful insight for our walltime benchmarks right now
|
||||
|
||||
2
.github/workflows/daily_fuzz.yaml
vendored
2
.github/workflows/daily_fuzz.yaml
vendored
@@ -34,7 +34,7 @@ jobs:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
|
||||
- uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install mold"
|
||||
|
||||
6
.github/workflows/mypy_primer.yaml
vendored
6
.github/workflows/mypy_primer.yaml
vendored
@@ -48,7 +48,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install the latest version of uv
|
||||
uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
|
||||
uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
|
||||
|
||||
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||
with:
|
||||
@@ -87,7 +87,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install the latest version of uv
|
||||
uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
|
||||
uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
|
||||
|
||||
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||
with:
|
||||
@@ -129,7 +129,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install the latest version of uv
|
||||
uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
|
||||
uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
|
||||
|
||||
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||
with:
|
||||
|
||||
2
.github/workflows/publish-pypi.yml
vendored
2
.github/workflows/publish-pypi.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
id-token: write
|
||||
steps:
|
||||
- name: "Install uv"
|
||||
uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
|
||||
uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
|
||||
- uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
||||
with:
|
||||
pattern: wheels-*
|
||||
|
||||
6
.github/workflows/sync_typeshed.yaml
vendored
6
.github/workflows/sync_typeshed.yaml
vendored
@@ -76,7 +76,7 @@ jobs:
|
||||
run: |
|
||||
git config --global user.name typeshedbot
|
||||
git config --global user.email '<>'
|
||||
- uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
|
||||
- uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
|
||||
- name: Sync typeshed stubs
|
||||
run: |
|
||||
rm -rf "ruff/${VENDORED_TYPESHED}"
|
||||
@@ -130,7 +130,7 @@ jobs:
|
||||
with:
|
||||
persist-credentials: true
|
||||
ref: ${{ env.UPSTREAM_BRANCH}}
|
||||
- uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
|
||||
- uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
|
||||
- name: Setup git
|
||||
run: |
|
||||
git config --global user.name typeshedbot
|
||||
@@ -169,7 +169,7 @@ jobs:
|
||||
with:
|
||||
persist-credentials: true
|
||||
ref: ${{ env.UPSTREAM_BRANCH}}
|
||||
- uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
|
||||
- uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
|
||||
- name: Setup git
|
||||
run: |
|
||||
git config --global user.name typeshedbot
|
||||
|
||||
2
.github/workflows/ty-ecosystem-analyzer.yaml
vendored
2
.github/workflows/ty-ecosystem-analyzer.yaml
vendored
@@ -38,7 +38,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install the latest version of uv
|
||||
uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
|
||||
uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
|
||||
with:
|
||||
enable-cache: true
|
||||
|
||||
|
||||
2
.github/workflows/ty-ecosystem-report.yaml
vendored
2
.github/workflows/ty-ecosystem-report.yaml
vendored
@@ -32,7 +32,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install the latest version of uv
|
||||
uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
|
||||
uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
|
||||
with:
|
||||
enable-cache: true
|
||||
|
||||
|
||||
@@ -21,62 +21,15 @@ exclude: |
|
||||
)$
|
||||
|
||||
repos:
|
||||
# Priority 0: Read-only hooks; hooks that modify disjoint file types.
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v6.0.0
|
||||
hooks:
|
||||
- id: check-merge-conflict
|
||||
priority: 0
|
||||
|
||||
- repo: https://github.com/abravalheri/validate-pyproject
|
||||
rev: v0.24.1
|
||||
hooks:
|
||||
- id: validate-pyproject
|
||||
priority: 0
|
||||
|
||||
- repo: https://github.com/crate-ci/typos
|
||||
rev: v1.41.0
|
||||
hooks:
|
||||
- id: typos
|
||||
priority: 0
|
||||
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: cargo-fmt
|
||||
name: cargo fmt
|
||||
entry: cargo fmt --
|
||||
language: system
|
||||
types: [rust]
|
||||
pass_filenames: false # This makes it a lot faster
|
||||
priority: 0
|
||||
|
||||
# Prettier
|
||||
- repo: https://github.com/rbubley/mirrors-prettier
|
||||
rev: v3.7.4
|
||||
hooks:
|
||||
- id: prettier
|
||||
types: [yaml]
|
||||
priority: 0
|
||||
|
||||
# zizmor detects security vulnerabilities in GitHub Actions workflows.
|
||||
# Additional configuration for the tool is found in `.github/zizmor.yml`
|
||||
- repo: https://github.com/zizmorcore/zizmor-pre-commit
|
||||
rev: v1.19.0
|
||||
hooks:
|
||||
- id: zizmor
|
||||
priority: 0
|
||||
|
||||
- repo: https://github.com/python-jsonschema/check-jsonschema
|
||||
rev: 0.36.0
|
||||
hooks:
|
||||
- id: check-github-workflows
|
||||
priority: 0
|
||||
|
||||
- repo: https://github.com/shellcheck-py/shellcheck-py
|
||||
rev: v0.11.0.1
|
||||
hooks:
|
||||
- id: shellcheck
|
||||
priority: 0
|
||||
|
||||
- repo: https://github.com/executablebooks/mdformat
|
||||
rev: 1.0.0
|
||||
@@ -91,20 +44,7 @@ repos:
|
||||
docs/formatter/black\.md
|
||||
| docs/\w+\.md
|
||||
)$
|
||||
priority: 0
|
||||
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.14.10
|
||||
hooks:
|
||||
- id: ruff-format
|
||||
priority: 0
|
||||
- id: ruff-check
|
||||
args: [--fix, --exit-non-zero-on-fix]
|
||||
types_or: [python, pyi]
|
||||
require_serial: true
|
||||
priority: 1
|
||||
|
||||
# Priority 1: Second-pass fixers (e.g., markdownlint-fix runs after mdformat).
|
||||
- repo: https://github.com/igorshubovych/markdownlint-cli
|
||||
rev: v0.47.0
|
||||
hooks:
|
||||
@@ -114,9 +54,7 @@ repos:
|
||||
docs/formatter/black\.md
|
||||
| docs/\w+\.md
|
||||
)$
|
||||
priority: 1
|
||||
|
||||
# Priority 2: blacken-docs runs after markdownlint-fix (both modify markdown).
|
||||
- repo: https://github.com/adamchainz/blacken-docs
|
||||
rev: 1.20.0
|
||||
hooks:
|
||||
@@ -130,18 +68,59 @@ repos:
|
||||
)$
|
||||
additional_dependencies:
|
||||
- black==25.12.0
|
||||
priority: 2
|
||||
|
||||
- repo: https://github.com/crate-ci/typos
|
||||
rev: v1.40.0
|
||||
hooks:
|
||||
- id: typos
|
||||
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: cargo-fmt
|
||||
name: cargo fmt
|
||||
entry: cargo fmt --
|
||||
language: system
|
||||
types: [rust]
|
||||
pass_filenames: false # This makes it a lot faster
|
||||
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.14.10
|
||||
hooks:
|
||||
- id: ruff-format
|
||||
- id: ruff-check
|
||||
args: [--fix, --exit-non-zero-on-fix]
|
||||
types_or: [python, pyi]
|
||||
require_serial: true
|
||||
|
||||
# Prettier
|
||||
- repo: https://github.com/rbubley/mirrors-prettier
|
||||
rev: v3.7.4
|
||||
hooks:
|
||||
- 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/zizmorcore/zizmor-pre-commit
|
||||
rev: v1.19.0
|
||||
hooks:
|
||||
- id: zizmor
|
||||
|
||||
- repo: https://github.com/python-jsonschema/check-jsonschema
|
||||
rev: 0.36.0
|
||||
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.10
|
||||
rev: v1.7.9
|
||||
hooks:
|
||||
- id: actionlint
|
||||
stages:
|
||||
# This hook is disabled by default, since it's quite slow.
|
||||
# To run all hooks *including* this hook, use `uvx prek run -a --hook-stage=manual`.
|
||||
# To run *just* this hook, use `uvx prek run -a actionlint --hook-stage=manual`.
|
||||
# To run all hooks *including* this hook, use `uvx pre-commit run -a --hook-stage=manual`.
|
||||
# To run *just* this hook, use `uvx pre-commit run -a actionlint --hook-stage=manual`.
|
||||
- manual
|
||||
args:
|
||||
- "-ignore=SC2129" # ignorable stylistic lint from shellcheck
|
||||
@@ -152,4 +131,8 @@ repos:
|
||||
# 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.11.1"
|
||||
priority: 0
|
||||
|
||||
- repo: https://github.com/shellcheck-py/shellcheck-py
|
||||
rev: v0.11.0.1
|
||||
hooks:
|
||||
- id: shellcheck
|
||||
|
||||
@@ -65,7 +65,6 @@ When working on ty, PR titles should start with `[ty]` and be tagged with the `t
|
||||
- All changes must be tested. If you're not testing your changes, you're not done.
|
||||
- Get your tests to pass. If you didn't run the tests, your code does not work.
|
||||
- Follow existing code style. Check neighboring files for patterns.
|
||||
- Always run `uvx prek run -a` at the end of a task.
|
||||
- Always run `uvx pre-commit run -a` at the end of a task.
|
||||
- Avoid writing significant amounts of new code. This is often a sign that we're missing an existing method or mechanism that could help solve the problem. Look for existing utilities first.
|
||||
- Avoid falling back to patterns that require `panic!`, `unreachable!`, or `.unwrap()`. Instead, try to encode those constraints in the type system.
|
||||
- Prefer let chains (`if let` combined with `&&`) over nested `if let` statements to reduce indentation and improve readability.
|
||||
|
||||
@@ -53,12 +53,12 @@ cargo install cargo-insta
|
||||
You'll need [uv](https://docs.astral.sh/uv/getting-started/installation/) (or `pipx` and `pip`) to
|
||||
run Python utility commands.
|
||||
|
||||
You can optionally install hooks to automatically run the validation checks
|
||||
You can optionally install pre-commit hooks to automatically run the validation checks
|
||||
when making a commit:
|
||||
|
||||
```shell
|
||||
uv tool install prek
|
||||
prek install
|
||||
uv tool install pre-commit
|
||||
pre-commit install
|
||||
```
|
||||
|
||||
We recommend [nextest](https://nexte.st/) to run Ruff's test suite (via `cargo nextest run`),
|
||||
@@ -85,7 +85,7 @@ and that it passes both the lint and test validation checks:
|
||||
```shell
|
||||
cargo clippy --workspace --all-targets --all-features -- -D warnings # Rust linting
|
||||
RUFF_UPDATE_SCHEMA=1 cargo test # Rust testing and updating ruff.schema.json
|
||||
uvx prek run -a # Rust and Python formatting, Markdown and Python linting, etc.
|
||||
uvx pre-commit run --all-files --show-diff-on-failure # Rust and Python formatting, Markdown and Python linting, etc.
|
||||
```
|
||||
|
||||
These checks will run on GitHub Actions when you open your pull request, but running them locally
|
||||
@@ -381,7 +381,7 @@ Commit each step of this process separately for easier review.
|
||||
|
||||
- Often labels will be missing from pull requests they will need to be manually organized into the proper section
|
||||
- Changes should be edited to be user-facing descriptions, avoiding internal details
|
||||
- Square brackets (eg, `[ruff]` project name) will be automatically escaped by `prek`
|
||||
- Square brackets (eg, `[ruff]` project name) will be automatically escaped by `pre-commit`
|
||||
|
||||
Additionally, for minor releases:
|
||||
|
||||
|
||||
55
Cargo.lock
generated
55
Cargo.lock
generated
@@ -466,9 +466,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.54"
|
||||
version = "4.5.53"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394"
|
||||
checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -476,9 +476,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.54"
|
||||
version = "4.5.53"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00"
|
||||
checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -1583,11 +1583,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "imperative"
|
||||
version = "1.0.7"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "35e1d0bd9c575c52e59aad8e122a11786e852a154678d0c86e9e243d55273970"
|
||||
checksum = "29a1f6526af721f9aec9ceed7ab8ebfca47f3399d08b80056c2acca3fcb694a9"
|
||||
dependencies = [
|
||||
"phf 0.13.1",
|
||||
"phf",
|
||||
"rust-stemmers",
|
||||
]
|
||||
|
||||
@@ -1648,9 +1648,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "insta"
|
||||
version = "1.46.0"
|
||||
version = "1.45.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b66886d14d18d420ab5052cbff544fc5d34d0b2cdd35eb5976aaa10a4a472e5"
|
||||
checksum = "983e3b24350c84ab8a65151f537d67afbbf7153bb9f1110e03e9fa9b07f67a5c"
|
||||
dependencies = [
|
||||
"console 0.15.11",
|
||||
"once_cell",
|
||||
@@ -1874,9 +1874,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.179"
|
||||
version = "0.2.178"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c5a2d376baa530d1238d133232d15e239abad80d05838b4b59354e5268af431f"
|
||||
checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091"
|
||||
|
||||
[[package]]
|
||||
name = "libcst"
|
||||
@@ -2488,17 +2488,7 @@ version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078"
|
||||
dependencies = [
|
||||
"phf_shared 0.11.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf"
|
||||
dependencies = [
|
||||
"phf_shared 0.13.1",
|
||||
"serde",
|
||||
"phf_shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2508,7 +2498,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a"
|
||||
dependencies = [
|
||||
"phf_generator",
|
||||
"phf_shared 0.11.3",
|
||||
"phf_shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2517,7 +2507,7 @@ version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
|
||||
dependencies = [
|
||||
"phf_shared 0.11.3",
|
||||
"phf_shared",
|
||||
"rand 0.8.5",
|
||||
]
|
||||
|
||||
@@ -2530,15 +2520,6 @@ dependencies = [
|
||||
"siphasher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_shared"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266"
|
||||
dependencies = [
|
||||
"siphasher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.16"
|
||||
@@ -4013,9 +3994,9 @@ checksum = "e396b6523b11ccb83120b115a0b7366de372751aa6edf19844dfb13a6af97e91"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.113"
|
||||
version = "2.0.111"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "678faa00651c9eb72dd2020cbdf275d92eccb2400d568e419efdd64838145cb4"
|
||||
checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -4079,7 +4060,7 @@ checksum = "d4ea810f0692f9f51b382fff5893887bb4580f5fa246fde546e0b13e7fcee662"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"nom",
|
||||
"phf 0.11.3",
|
||||
"phf",
|
||||
"phf_codegen",
|
||||
]
|
||||
|
||||
@@ -4827,7 +4808,7 @@ version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d1673eca9782c84de5f81b82e4109dcfb3611c8ba0d52930ec4a9478f547b2dd"
|
||||
dependencies = [
|
||||
"phf 0.11.3",
|
||||
"phf",
|
||||
"unicode_names2_generator",
|
||||
]
|
||||
|
||||
|
||||
@@ -1,133 +0,0 @@
|
||||
class Simple:
|
||||
x=1
|
||||
x=2 # fmt: skip
|
||||
x=3
|
||||
|
||||
class Semicolon:
|
||||
x=1
|
||||
x=2;x=3 # fmt: skip
|
||||
x=4
|
||||
|
||||
class TrailingSemicolon:
|
||||
x=1
|
||||
x=2;x=3 ; # fmt: skip
|
||||
x=4
|
||||
|
||||
class SemicolonNewLogicalLine:
|
||||
x=1;
|
||||
x=2;x=3 # fmt: skip
|
||||
x=4
|
||||
|
||||
class ManySemicolonOneLine:
|
||||
x=1
|
||||
x=2;x=3;x=4 # fmt: skip
|
||||
x=5
|
||||
|
||||
class CompoundInSuite:
|
||||
x=1
|
||||
def foo(): y=1 # fmt: skip
|
||||
x=2
|
||||
|
||||
class CompoundInSuiteNewline:
|
||||
x=1
|
||||
def foo():
|
||||
y=1 # fmt: skip
|
||||
x=2
|
||||
|
||||
class MultiLineSkip:
|
||||
x=1
|
||||
x = [
|
||||
'1',
|
||||
'2',
|
||||
] # fmt: skip
|
||||
|
||||
class MultiLineSemicolon:
|
||||
x=1
|
||||
x = [
|
||||
'1',
|
||||
'2',
|
||||
]; x=2 # fmt: skip
|
||||
|
||||
class LineContinuationSemicolonAfter:
|
||||
x=1
|
||||
x = ['a']\
|
||||
; y=1 # fmt: skip
|
||||
|
||||
class LineContinuationSemicolonBefore:
|
||||
x=1
|
||||
x = ['a']; \
|
||||
y=1 # fmt: skip
|
||||
|
||||
class LineContinuationSemicolonAndNewline:
|
||||
x=1
|
||||
x = ['a']; \
|
||||
|
||||
y=1 # fmt: skip
|
||||
|
||||
class LineContinuationSemicolonAndNewlineAndComment:
|
||||
x=1
|
||||
x = ['a']; \
|
||||
# 1
|
||||
y=1 # fmt: skip
|
||||
|
||||
class RepeatedLineContinuation:
|
||||
x=1
|
||||
x = ['a']; \
|
||||
\
|
||||
\
|
||||
y=1 # fmt: skip
|
||||
|
||||
class MultiLineSemicolonComments:
|
||||
x=1
|
||||
# 1
|
||||
x = [ # 2
|
||||
'1', # 3
|
||||
'2',
|
||||
# 4
|
||||
]; x=2 # 5 # fmt: skip # 6
|
||||
|
||||
class DocstringSkipped:
|
||||
'''This is a docstring''' # fmt: skip
|
||||
x=1
|
||||
|
||||
class MultilineDocstringSkipped:
|
||||
'''This is a docstring
|
||||
''' # fmt: skip
|
||||
x=1
|
||||
|
||||
class FirstStatementNewlines:
|
||||
|
||||
|
||||
|
||||
|
||||
x=1 # fmt: skip
|
||||
|
||||
class ChainingSemicolons:
|
||||
x=[
|
||||
'1',
|
||||
'2',
|
||||
'3',
|
||||
];x=1;x=[
|
||||
'1',
|
||||
'2',
|
||||
'3'
|
||||
];x=1;x=1 # fmt: skip
|
||||
|
||||
class LotsOfComments:
|
||||
# 1
|
||||
x=[ # 2
|
||||
'1', # 3
|
||||
'2',
|
||||
'3'
|
||||
] ;x=2;x=3 # 4 # fmt: skip # 5
|
||||
# 6
|
||||
|
||||
class MixingCompound:
|
||||
def foo(): bar(); import zoo # fmt: skip
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/17331
|
||||
def main() -> None:
|
||||
import ipdb; ipdb.set_trace() # noqa: E402,E702,I001 # fmt: skip
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/11430
|
||||
print(); print() # noqa # fmt: skip
|
||||
@@ -1,22 +0,0 @@
|
||||
# 0
|
||||
'''Module docstring
|
||||
multiple lines''' # 1 # fmt: skip # 2
|
||||
|
||||
import a;import b; from c import (
|
||||
x,y,z
|
||||
); import f # fmt: skip
|
||||
|
||||
x=1;x=2;x=3;x=4 # fmt: skip
|
||||
|
||||
# 1
|
||||
x=[ # 2
|
||||
'1', # 3
|
||||
'2',
|
||||
'3'
|
||||
];x=1;x=1 # 4 # fmt: skip # 5
|
||||
# 6
|
||||
|
||||
def foo(): x=[
|
||||
'1',
|
||||
'2',
|
||||
];x=1 # fmt: skip
|
||||
@@ -1,68 +0,0 @@
|
||||
class Simple:
|
||||
# Range comprises skip range
|
||||
x=1
|
||||
<RANGE_START>x=2 <RANGE_END># fmt: skip
|
||||
x=3
|
||||
|
||||
class Semicolon:
|
||||
# Range is part of skip range
|
||||
x=1
|
||||
x=2;<RANGE_START>x=3<RANGE_END> # fmt: skip
|
||||
x=4
|
||||
|
||||
class FormatFirst:
|
||||
x=1
|
||||
<RANGE_START>x=2<RANGE_END>;x=3 # fmt: skip
|
||||
x=4
|
||||
|
||||
class FormatMiddle:
|
||||
x=1
|
||||
x=2;<RANGE_START>x=3<RANGE_END>;x=4 # fmt: skip
|
||||
x=5
|
||||
|
||||
class SemicolonNewLogicalLine:
|
||||
# Range overlaps on right side
|
||||
<RANGE_START>x=1;
|
||||
x=2<RANGE_END>;x=3 # fmt: skip
|
||||
x=4
|
||||
|
||||
class SemicolonNewLogicalLine:
|
||||
# Range overlaps on left side
|
||||
x=1;
|
||||
x=2;<RANGE_START>x=3 # fmt: skip
|
||||
x=4<RANGE_END>
|
||||
|
||||
class ManySemicolonOneLine:
|
||||
x=1
|
||||
x=2;x=3;x=4 # fmt: skip
|
||||
x=5
|
||||
|
||||
class CompoundInSuite:
|
||||
x=1
|
||||
<RANGE_START>def foo(): y=1 <RANGE_END># fmt: skip
|
||||
x=2
|
||||
|
||||
class CompoundInSuiteNewline:
|
||||
x=1
|
||||
def foo():
|
||||
y=1 # fmt: skip
|
||||
x=2
|
||||
|
||||
class MultiLineSkip:
|
||||
# Range inside statement
|
||||
x=1
|
||||
x = <RANGE_START>[
|
||||
'1',
|
||||
'2',<RANGE_END>
|
||||
] # fmt: skip
|
||||
|
||||
|
||||
class LotsOfComments:
|
||||
# 1
|
||||
x=[ # 2
|
||||
'1', # 3<RANGE_START>
|
||||
'2',
|
||||
'3'
|
||||
] ;x=2;x=3 # 4<RANGE_END> # fmt: skip # 5
|
||||
# 6
|
||||
|
||||
@@ -24,6 +24,7 @@ pub use crate::options::{
|
||||
};
|
||||
use crate::range::is_logical_line;
|
||||
pub use crate::shared_traits::{AsFormat, FormattedIter, FormattedIterExt, IntoFormat};
|
||||
use crate::verbatim::suppressed_node;
|
||||
|
||||
pub(crate) mod builders;
|
||||
pub mod cli;
|
||||
@@ -60,39 +61,51 @@ where
|
||||
let node_ref = AnyNodeRef::from(node);
|
||||
let node_comments = comments.leading_dangling_trailing(node_ref);
|
||||
|
||||
leading_comments(node_comments.leading).fmt(f)?;
|
||||
if self.is_suppressed(node_comments.trailing, f.context()) {
|
||||
suppressed_node(node_ref).fmt(f)
|
||||
} else {
|
||||
leading_comments(node_comments.leading).fmt(f)?;
|
||||
|
||||
// Emit source map information for nodes that are valid "narrowing" targets
|
||||
// in range formatting. Never emit source map information if they're disabled
|
||||
// for performance reasons.
|
||||
let emit_source_position = (is_logical_line(node_ref) || node_ref.is_mod_module())
|
||||
&& f.options().source_map_generation().is_enabled();
|
||||
// Emit source map information for nodes that are valid "narrowing" targets
|
||||
// in range formatting. Never emit source map information if they're disabled
|
||||
// for performance reasons.
|
||||
let emit_source_position = (is_logical_line(node_ref) || node_ref.is_mod_module())
|
||||
&& f.options().source_map_generation().is_enabled();
|
||||
|
||||
emit_source_position
|
||||
.then_some(source_position(node.start()))
|
||||
.fmt(f)?;
|
||||
emit_source_position
|
||||
.then_some(source_position(node.start()))
|
||||
.fmt(f)?;
|
||||
|
||||
self.fmt_fields(node, f)?;
|
||||
self.fmt_fields(node, f)?;
|
||||
|
||||
debug_assert!(
|
||||
node_comments
|
||||
.dangling
|
||||
.iter()
|
||||
.all(SourceComment::is_formatted),
|
||||
"The node has dangling comments that need to be formatted manually. Add the special dangling comments handling to `fmt_fields`."
|
||||
);
|
||||
debug_assert!(
|
||||
node_comments
|
||||
.dangling
|
||||
.iter()
|
||||
.all(SourceComment::is_formatted),
|
||||
"The node has dangling comments that need to be formatted manually. Add the special dangling comments handling to `fmt_fields`."
|
||||
);
|
||||
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
emit_source_position.then_some(source_position(node.end())),
|
||||
trailing_comments(node_comments.trailing)
|
||||
]
|
||||
)
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
emit_source_position.then_some(source_position(node.end())),
|
||||
trailing_comments(node_comments.trailing)
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Formats the node's fields.
|
||||
fn fmt_fields(&self, item: &N, f: &mut PyFormatter) -> FormatResult<()>;
|
||||
|
||||
fn is_suppressed(
|
||||
&self,
|
||||
_trailing_comments: &[SourceComment],
|
||||
_context: &PyFormatContext,
|
||||
) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Debug, salsa::Update, PartialEq, Eq)]
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
use ruff_formatter::write;
|
||||
use ruff_python_ast::Decorator;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::comments::SourceComment;
|
||||
use crate::expression::maybe_parenthesize_expression;
|
||||
use crate::expression::parentheses::Parenthesize;
|
||||
use crate::verbatim::verbatim_text;
|
||||
use crate::{has_skip_comment, prelude::*};
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -12,27 +11,26 @@ pub struct FormatDecorator;
|
||||
|
||||
impl FormatNodeRule<Decorator> for FormatDecorator {
|
||||
fn fmt_fields(&self, item: &Decorator, f: &mut PyFormatter) -> FormatResult<()> {
|
||||
let comments = f.context().comments();
|
||||
let trailing = comments.trailing(item);
|
||||
let Decorator {
|
||||
expression,
|
||||
range: _,
|
||||
node_index: _,
|
||||
} = item;
|
||||
|
||||
if has_skip_comment(trailing, f.context().source()) {
|
||||
comments.mark_verbatim_node_comments_formatted(item.into());
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
token("@"),
|
||||
maybe_parenthesize_expression(expression, item, Parenthesize::Optional)
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
verbatim_text(item.range()).fmt(f)
|
||||
} else {
|
||||
let Decorator {
|
||||
expression,
|
||||
range: _,
|
||||
node_index: _,
|
||||
} = item;
|
||||
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
token("@"),
|
||||
maybe_parenthesize_expression(expression, item, Parenthesize::Optional)
|
||||
]
|
||||
)
|
||||
}
|
||||
fn is_suppressed(
|
||||
&self,
|
||||
trailing_comments: &[SourceComment],
|
||||
context: &PyFormatContext,
|
||||
) -> bool {
|
||||
has_skip_comment(trailing_comments, context.source())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
|
||||
use crate::comments::Comments;
|
||||
use crate::context::{IndentLevel, NodeLevel};
|
||||
use crate::prelude::*;
|
||||
use crate::statement::suite::{DocstringStmt, skip_range};
|
||||
use crate::statement::suite::DocstringStmt;
|
||||
use crate::verbatim::{ends_suppression, starts_suppression};
|
||||
use crate::{FormatModuleError, PyFormatOptions, format_module_source};
|
||||
|
||||
@@ -251,29 +251,7 @@ impl<'ast> SourceOrderVisitor<'ast> for FindEnclosingNode<'_, 'ast> {
|
||||
// We only visit statements that aren't suppressed that's why we don't need to track the suppression
|
||||
// state in a stack. Assert that this assumption is safe.
|
||||
debug_assert!(self.suppressed.is_no());
|
||||
|
||||
let mut iter = body.iter();
|
||||
|
||||
while let Some(stmt) = iter.next() {
|
||||
// If the range intersects a skip range then we need to
|
||||
// format the entire suite to properly handle the case
|
||||
// where a `fmt: skip` affects multiple statements.
|
||||
//
|
||||
// For example, in the case
|
||||
//
|
||||
// ```
|
||||
// <RANGE_START>x=1<RANGE_END>;x=2 # fmt: skip
|
||||
// ```
|
||||
//
|
||||
// the statement `x=1` does not "know" that it is
|
||||
// suppressed, but the suite does.
|
||||
if let Some(verbatim_range) = skip_range(stmt, iter.as_slice(), self.context)
|
||||
&& verbatim_range.intersect(self.range).is_some()
|
||||
{
|
||||
break;
|
||||
}
|
||||
self.visit_stmt(stmt);
|
||||
}
|
||||
walk_body(self, body);
|
||||
self.suppressed = Suppressed::No;
|
||||
}
|
||||
}
|
||||
@@ -583,7 +561,7 @@ impl NarrowRange<'_> {
|
||||
}
|
||||
|
||||
pub(crate) const fn is_logical_line(node: AnyNodeRef) -> bool {
|
||||
// Make sure to update [`FormatEnclosingNode`] when changing this.
|
||||
// Make sure to update [`FormatEnclosingLine`] when changing this.
|
||||
node.is_statement()
|
||||
|| node.is_decorator()
|
||||
|| node.is_except_handler()
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
use ruff_formatter::write;
|
||||
use ruff_python_ast::StmtAnnAssign;
|
||||
|
||||
use crate::comments::SourceComment;
|
||||
use crate::expression::is_splittable_expression;
|
||||
use crate::expression::parentheses::Parentheses;
|
||||
use crate::prelude::*;
|
||||
use crate::statement::stmt_assign::{
|
||||
AnyAssignmentOperator, AnyBeforeOperator, FormatStatementsLastExpression,
|
||||
};
|
||||
use crate::statement::trailing_semicolon;
|
||||
use crate::{has_skip_comment, prelude::*};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FormatStmtAnnAssign;
|
||||
@@ -83,4 +84,12 @@ impl FormatNodeRule<StmtAnnAssign> for FormatStmtAnnAssign {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_suppressed(
|
||||
&self,
|
||||
trailing_comments: &[SourceComment],
|
||||
context: &PyFormatContext,
|
||||
) -> bool {
|
||||
has_skip_comment(trailing_comments, context.source())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,10 @@ use ruff_formatter::prelude::{space, token};
|
||||
use ruff_formatter::write;
|
||||
use ruff_python_ast::StmtAssert;
|
||||
|
||||
use crate::comments::SourceComment;
|
||||
use crate::expression::maybe_parenthesize_expression;
|
||||
use crate::expression::parentheses::Parenthesize;
|
||||
use crate::prelude::*;
|
||||
use crate::{has_skip_comment, prelude::*};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FormatStmtAssert;
|
||||
@@ -40,4 +41,12 @@ impl FormatNodeRule<StmtAssert> for FormatStmtAssert {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_suppressed(
|
||||
&self,
|
||||
trailing_comments: &[SourceComment],
|
||||
context: &PyFormatContext,
|
||||
) -> bool {
|
||||
has_skip_comment(trailing_comments, context.source())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ use crate::expression::{
|
||||
maybe_parenthesize_expression,
|
||||
};
|
||||
use crate::other::interpolated_string::InterpolatedStringLayout;
|
||||
use crate::prelude::*;
|
||||
use crate::preview::is_parenthesize_lambda_bodies_enabled;
|
||||
use crate::statement::trailing_semicolon;
|
||||
use crate::string::StringLikeExtensions;
|
||||
@@ -27,6 +26,7 @@ use crate::string::implicit::{
|
||||
FormatImplicitConcatenatedStringExpanded, FormatImplicitConcatenatedStringFlat,
|
||||
ImplicitConcatenatedLayout,
|
||||
};
|
||||
use crate::{has_skip_comment, prelude::*};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FormatStmtAssign;
|
||||
@@ -104,6 +104,14 @@ impl FormatNodeRule<StmtAssign> for FormatStmtAssign {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_suppressed(
|
||||
&self,
|
||||
trailing_comments: &[SourceComment],
|
||||
context: &PyFormatContext,
|
||||
) -> bool {
|
||||
has_skip_comment(trailing_comments, context.source())
|
||||
}
|
||||
}
|
||||
|
||||
/// Formats a single target with the equal operator.
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
use ruff_formatter::write;
|
||||
use ruff_python_ast::StmtAugAssign;
|
||||
|
||||
use crate::comments::SourceComment;
|
||||
use crate::expression::parentheses::is_expression_parenthesized;
|
||||
use crate::prelude::*;
|
||||
use crate::statement::stmt_assign::{
|
||||
AnyAssignmentOperator, AnyBeforeOperator, FormatStatementsLastExpression,
|
||||
has_target_own_parentheses,
|
||||
};
|
||||
use crate::statement::trailing_semicolon;
|
||||
use crate::{AsFormat, FormatNodeRule};
|
||||
use crate::{has_skip_comment, prelude::*};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FormatStmtAugAssign;
|
||||
@@ -61,4 +62,12 @@ impl FormatNodeRule<StmtAugAssign> for FormatStmtAugAssign {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_suppressed(
|
||||
&self,
|
||||
trailing_comments: &[SourceComment],
|
||||
context: &PyFormatContext,
|
||||
) -> bool {
|
||||
has_skip_comment(trailing_comments, context.source())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use ruff_python_ast::StmtBreak;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::comments::SourceComment;
|
||||
use crate::{has_skip_comment, prelude::*};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FormatStmtBreak;
|
||||
@@ -9,4 +10,12 @@ impl FormatNodeRule<StmtBreak> for FormatStmtBreak {
|
||||
fn fmt_fields(&self, _item: &StmtBreak, f: &mut PyFormatter) -> FormatResult<()> {
|
||||
token("break").fmt(f)
|
||||
}
|
||||
|
||||
fn is_suppressed(
|
||||
&self,
|
||||
trailing_comments: &[SourceComment],
|
||||
context: &PyFormatContext,
|
||||
) -> bool {
|
||||
has_skip_comment(trailing_comments, context.source())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use ruff_python_ast::StmtContinue;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::comments::SourceComment;
|
||||
use crate::{has_skip_comment, prelude::*};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FormatStmtContinue;
|
||||
@@ -9,4 +10,12 @@ impl FormatNodeRule<StmtContinue> for FormatStmtContinue {
|
||||
fn fmt_fields(&self, _item: &StmtContinue, f: &mut PyFormatter) -> FormatResult<()> {
|
||||
token("continue").fmt(f)
|
||||
}
|
||||
|
||||
fn is_suppressed(
|
||||
&self,
|
||||
trailing_comments: &[SourceComment],
|
||||
context: &PyFormatContext,
|
||||
) -> bool {
|
||||
has_skip_comment(trailing_comments, context.source())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,10 @@ use ruff_python_ast::StmtDelete;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::builders::{PyFormatterExtensions, parenthesize_if_expands};
|
||||
use crate::comments::dangling_node_comments;
|
||||
use crate::comments::{SourceComment, dangling_node_comments};
|
||||
use crate::expression::maybe_parenthesize_expression;
|
||||
use crate::expression::parentheses::Parenthesize;
|
||||
use crate::prelude::*;
|
||||
use crate::{has_skip_comment, prelude::*};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FormatStmtDelete;
|
||||
@@ -57,4 +57,12 @@ impl FormatNodeRule<StmtDelete> for FormatStmtDelete {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_suppressed(
|
||||
&self,
|
||||
trailing_comments: &[SourceComment],
|
||||
context: &PyFormatContext,
|
||||
) -> bool {
|
||||
has_skip_comment(trailing_comments, context.source())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::{Expr, Operator, StmtExpr};
|
||||
|
||||
use crate::comments::SourceComment;
|
||||
|
||||
use crate::expression::maybe_parenthesize_expression;
|
||||
use crate::expression::parentheses::Parenthesize;
|
||||
use crate::prelude::*;
|
||||
use crate::statement::trailing_semicolon;
|
||||
use crate::{has_skip_comment, prelude::*};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FormatStmtExpr;
|
||||
@@ -28,6 +30,14 @@ impl FormatNodeRule<StmtExpr> for FormatStmtExpr {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_suppressed(
|
||||
&self,
|
||||
trailing_comments: &[SourceComment],
|
||||
context: &PyFormatContext,
|
||||
) -> bool {
|
||||
has_skip_comment(trailing_comments, context.source())
|
||||
}
|
||||
}
|
||||
|
||||
const fn is_arithmetic_like(expression: &Expr) -> bool {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use ruff_formatter::{format_args, write};
|
||||
use ruff_python_ast::StmtGlobal;
|
||||
|
||||
use crate::comments::SourceComment;
|
||||
use crate::has_skip_comment;
|
||||
use crate::prelude::*;
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -45,4 +47,12 @@ impl FormatNodeRule<StmtGlobal> for FormatStmtGlobal {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn is_suppressed(
|
||||
&self,
|
||||
trailing_comments: &[SourceComment],
|
||||
context: &PyFormatContext,
|
||||
) -> bool {
|
||||
has_skip_comment(trailing_comments, context.source())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use ruff_formatter::{format_args, write};
|
||||
use ruff_python_ast::StmtImport;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::comments::SourceComment;
|
||||
use crate::{has_skip_comment, prelude::*};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FormatStmtImport;
|
||||
@@ -20,4 +21,12 @@ impl FormatNodeRule<StmtImport> for FormatStmtImport {
|
||||
});
|
||||
write!(f, [token("import"), space(), names])
|
||||
}
|
||||
|
||||
fn is_suppressed(
|
||||
&self,
|
||||
trailing_comments: &[SourceComment],
|
||||
context: &PyFormatContext,
|
||||
) -> bool {
|
||||
has_skip_comment(trailing_comments, context.source())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,9 @@ use ruff_python_ast::StmtImportFrom;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::builders::{PyFormatterExtensions, TrailingComma, parenthesize_if_expands};
|
||||
use crate::comments::SourceComment;
|
||||
use crate::expression::parentheses::parenthesized;
|
||||
use crate::has_skip_comment;
|
||||
use crate::other::identifier::DotDelimitedIdentifier;
|
||||
use crate::prelude::*;
|
||||
|
||||
@@ -70,4 +72,12 @@ impl FormatNodeRule<StmtImportFrom> for FormatStmtImportFrom {
|
||||
.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
fn is_suppressed(
|
||||
&self,
|
||||
trailing_comments: &[SourceComment],
|
||||
context: &PyFormatContext,
|
||||
) -> bool {
|
||||
has_skip_comment(trailing_comments, context.source())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use ruff_python_ast::StmtIpyEscapeCommand;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::comments::SourceComment;
|
||||
use crate::{has_skip_comment, prelude::*};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FormatStmtIpyEscapeCommand;
|
||||
@@ -10,4 +11,12 @@ impl FormatNodeRule<StmtIpyEscapeCommand> for FormatStmtIpyEscapeCommand {
|
||||
fn fmt_fields(&self, item: &StmtIpyEscapeCommand, f: &mut PyFormatter) -> FormatResult<()> {
|
||||
source_text_slice(item.range()).fmt(f)
|
||||
}
|
||||
|
||||
fn is_suppressed(
|
||||
&self,
|
||||
trailing_comments: &[SourceComment],
|
||||
context: &PyFormatContext,
|
||||
) -> bool {
|
||||
has_skip_comment(trailing_comments, context.source())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use ruff_formatter::{format_args, write};
|
||||
use ruff_python_ast::StmtNonlocal;
|
||||
|
||||
use crate::comments::SourceComment;
|
||||
use crate::has_skip_comment;
|
||||
use crate::prelude::*;
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -45,4 +47,12 @@ impl FormatNodeRule<StmtNonlocal> for FormatStmtNonlocal {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn is_suppressed(
|
||||
&self,
|
||||
trailing_comments: &[SourceComment],
|
||||
context: &PyFormatContext,
|
||||
) -> bool {
|
||||
has_skip_comment(trailing_comments, context.source())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use ruff_python_ast::StmtPass;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::comments::SourceComment;
|
||||
use crate::{has_skip_comment, prelude::*};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FormatStmtPass;
|
||||
@@ -9,4 +10,12 @@ impl FormatNodeRule<StmtPass> for FormatStmtPass {
|
||||
fn fmt_fields(&self, _item: &StmtPass, f: &mut PyFormatter) -> FormatResult<()> {
|
||||
token("pass").fmt(f)
|
||||
}
|
||||
|
||||
fn is_suppressed(
|
||||
&self,
|
||||
trailing_comments: &[SourceComment],
|
||||
context: &PyFormatContext,
|
||||
) -> bool {
|
||||
has_skip_comment(trailing_comments, context.source())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use ruff_formatter::write;
|
||||
use ruff_python_ast::StmtRaise;
|
||||
|
||||
use crate::comments::SourceComment;
|
||||
use crate::expression::maybe_parenthesize_expression;
|
||||
use crate::expression::parentheses::Parenthesize;
|
||||
use crate::prelude::*;
|
||||
use crate::{has_skip_comment, prelude::*};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FormatStmtRaise;
|
||||
@@ -42,4 +43,12 @@ impl FormatNodeRule<StmtRaise> for FormatStmtRaise {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_suppressed(
|
||||
&self,
|
||||
trailing_comments: &[SourceComment],
|
||||
context: &PyFormatContext,
|
||||
) -> bool {
|
||||
has_skip_comment(trailing_comments, context.source())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use ruff_formatter::write;
|
||||
use ruff_python_ast::{Expr, StmtReturn};
|
||||
|
||||
use crate::comments::SourceComment;
|
||||
use crate::expression::expr_tuple::TupleParentheses;
|
||||
use crate::prelude::*;
|
||||
use crate::statement::stmt_assign::FormatStatementsLastExpression;
|
||||
use crate::{has_skip_comment, prelude::*};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FormatStmtReturn;
|
||||
@@ -42,4 +43,12 @@ impl FormatNodeRule<StmtReturn> for FormatStmtReturn {
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_suppressed(
|
||||
&self,
|
||||
trailing_comments: &[SourceComment],
|
||||
context: &PyFormatContext,
|
||||
) -> bool {
|
||||
has_skip_comment(trailing_comments, context.source())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use ruff_formatter::write;
|
||||
use ruff_python_ast::StmtTypeAlias;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::comments::SourceComment;
|
||||
use crate::statement::stmt_assign::{
|
||||
AnyAssignmentOperator, AnyBeforeOperator, FormatStatementsLastExpression,
|
||||
};
|
||||
use crate::{has_skip_comment, prelude::*};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FormatStmtTypeAlias;
|
||||
@@ -41,4 +42,12 @@ impl FormatNodeRule<StmtTypeAlias> for FormatStmtTypeAlias {
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
fn is_suppressed(
|
||||
&self,
|
||||
trailing_comments: &[SourceComment],
|
||||
context: &PyFormatContext,
|
||||
) -> bool {
|
||||
has_skip_comment(trailing_comments, context.source())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,15 +4,11 @@ use ruff_formatter::{
|
||||
use ruff_python_ast::helpers::is_compound_statement;
|
||||
use ruff_python_ast::{self as ast, Expr, PySourceType, Stmt, Suite};
|
||||
use ruff_python_ast::{AnyNodeRef, StmtExpr};
|
||||
use ruff_python_trivia::{
|
||||
SimpleTokenKind, SimpleTokenizer, lines_after, lines_after_ignoring_end_of_line_trivia,
|
||||
lines_before,
|
||||
};
|
||||
use ruff_python_trivia::{lines_after, lines_after_ignoring_end_of_line_trivia, lines_before};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use crate::comments::{
|
||||
Comments, LeadingDanglingTrailingComments, has_skip_comment, leading_comments,
|
||||
trailing_comments,
|
||||
Comments, LeadingDanglingTrailingComments, leading_comments, trailing_comments,
|
||||
};
|
||||
use crate::context::{NodeLevel, TopLevelStatementPosition, WithIndentLevel, WithNodeLevel};
|
||||
use crate::other::string_literal::StringLiteralKind;
|
||||
@@ -20,9 +16,9 @@ use crate::prelude::*;
|
||||
use crate::preview::{
|
||||
is_allow_newline_after_block_open_enabled, is_blank_line_before_decorated_class_in_stub_enabled,
|
||||
};
|
||||
use crate::statement::trailing_semicolon;
|
||||
use crate::statement::stmt_expr::FormatStmtExpr;
|
||||
use crate::verbatim::{
|
||||
write_skipped_statements, write_suppressed_statements_starting_with_leading_comment,
|
||||
suppressed_node, write_suppressed_statements_starting_with_leading_comment,
|
||||
write_suppressed_statements_starting_with_trailing_comment,
|
||||
};
|
||||
|
||||
@@ -156,21 +152,7 @@ impl FormatRule<Suite, PyFormatContext<'_>> for FormatSuite {
|
||||
|
||||
let first_comments = comments.leading_dangling_trailing(first);
|
||||
|
||||
let (mut preceding, mut empty_line_after_docstring) = if let Some(verbatim_range) =
|
||||
skip_range(first.statement(), iter.as_slice(), f.context())
|
||||
{
|
||||
let preceding =
|
||||
write_skipped_statements(first.statement(), &mut iter, verbatim_range, f)?;
|
||||
|
||||
// Insert a newline after a module level docstring, but treat
|
||||
// it as a docstring otherwise. See: https://github.com/psf/black/pull/3932.
|
||||
let empty_line_after_docstring =
|
||||
matches!(self.kind, SuiteKind::TopLevel | SuiteKind::Class)
|
||||
&& DocstringStmt::try_from_statement(preceding, self.kind, f.context())
|
||||
.is_some();
|
||||
|
||||
(preceding, empty_line_after_docstring)
|
||||
} else if first_comments
|
||||
let (mut preceding, mut empty_line_after_docstring) = if first_comments
|
||||
.leading
|
||||
.iter()
|
||||
.any(|comment| comment.is_suppression_off_comment(source))
|
||||
@@ -409,10 +391,7 @@ impl FormatRule<Suite, PyFormatContext<'_>> for FormatSuite {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(verbatim_range) = skip_range(following, iter.as_slice(), f.context()) {
|
||||
preceding = write_skipped_statements(following, &mut iter, verbatim_range, f)?;
|
||||
preceding_comments = comments.leading_dangling_trailing(preceding);
|
||||
} else if following_comments
|
||||
if following_comments
|
||||
.leading
|
||||
.iter()
|
||||
.any(|comment| comment.is_suppression_off_comment(source))
|
||||
@@ -861,57 +840,61 @@ impl Format<PyFormatContext<'_>> for DocstringStmt<'_> {
|
||||
let comments = f.context().comments().clone();
|
||||
let node_comments = comments.leading_dangling_trailing(self.docstring);
|
||||
|
||||
// SAFETY: Safe because `DocStringStmt` guarantees that it only ever wraps a `ExprStmt` containing a `ExprStringLiteral`.
|
||||
let string_literal = self
|
||||
.docstring
|
||||
.as_expr_stmt()
|
||||
.unwrap()
|
||||
.value
|
||||
.as_string_literal_expr()
|
||||
.unwrap();
|
||||
if FormatStmtExpr.is_suppressed(node_comments.trailing, f.context()) {
|
||||
suppressed_node(self.docstring).fmt(f)
|
||||
} else {
|
||||
// SAFETY: Safe because `DocStringStmt` guarantees that it only ever wraps a `ExprStmt` containing a `ExprStringLiteral`.
|
||||
let string_literal = self
|
||||
.docstring
|
||||
.as_expr_stmt()
|
||||
.unwrap()
|
||||
.value
|
||||
.as_string_literal_expr()
|
||||
.unwrap();
|
||||
|
||||
// We format the expression, but the statement carries the comments
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
leading_comments(node_comments.leading),
|
||||
f.options()
|
||||
.source_map_generation()
|
||||
.is_enabled()
|
||||
.then_some(source_position(self.docstring.start())),
|
||||
string_literal
|
||||
.format()
|
||||
.with_options(StringLiteralKind::Docstring),
|
||||
f.options()
|
||||
.source_map_generation()
|
||||
.is_enabled()
|
||||
.then_some(source_position(self.docstring.end())),
|
||||
]
|
||||
)?;
|
||||
// We format the expression, but the statement carries the comments
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
leading_comments(node_comments.leading),
|
||||
f.options()
|
||||
.source_map_generation()
|
||||
.is_enabled()
|
||||
.then_some(source_position(self.docstring.start())),
|
||||
string_literal
|
||||
.format()
|
||||
.with_options(StringLiteralKind::Docstring),
|
||||
f.options()
|
||||
.source_map_generation()
|
||||
.is_enabled()
|
||||
.then_some(source_position(self.docstring.end())),
|
||||
]
|
||||
)?;
|
||||
|
||||
if self.suite_kind == SuiteKind::Class {
|
||||
// Comments after class docstrings need a newline between the docstring and the
|
||||
// comment (https://github.com/astral-sh/ruff/issues/7948).
|
||||
// ```python
|
||||
// class ModuleBrowser:
|
||||
// """Browse module classes and functions in IDLE."""
|
||||
// # ^ Insert a newline above here
|
||||
//
|
||||
// def __init__(self, master, path, *, _htest=False, _utest=False):
|
||||
// pass
|
||||
// ```
|
||||
if let Some(own_line) = node_comments
|
||||
.trailing
|
||||
.iter()
|
||||
.find(|comment| comment.line_position().is_own_line())
|
||||
{
|
||||
if lines_before(own_line.start(), f.context().source()) < 2 {
|
||||
empty_line().fmt(f)?;
|
||||
if self.suite_kind == SuiteKind::Class {
|
||||
// Comments after class docstrings need a newline between the docstring and the
|
||||
// comment (https://github.com/astral-sh/ruff/issues/7948).
|
||||
// ```python
|
||||
// class ModuleBrowser:
|
||||
// """Browse module classes and functions in IDLE."""
|
||||
// # ^ Insert a newline above here
|
||||
//
|
||||
// def __init__(self, master, path, *, _htest=False, _utest=False):
|
||||
// pass
|
||||
// ```
|
||||
if let Some(own_line) = node_comments
|
||||
.trailing
|
||||
.iter()
|
||||
.find(|comment| comment.line_position().is_own_line())
|
||||
{
|
||||
if lines_before(own_line.start(), f.context().source()) < 2 {
|
||||
empty_line().fmt(f)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trailing_comments(node_comments.trailing).fmt(f)
|
||||
trailing_comments(node_comments.trailing).fmt(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -955,58 +938,6 @@ impl Format<PyFormatContext<'_>> for SuiteChildStatement<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn skip_range(
|
||||
first: &Stmt,
|
||||
statements: &[Stmt],
|
||||
context: &PyFormatContext,
|
||||
) -> Option<TextRange> {
|
||||
let start = first.start();
|
||||
let mut last_statement = first;
|
||||
|
||||
let comments = context.comments();
|
||||
let source = context.source();
|
||||
|
||||
for statement in statements {
|
||||
if new_logical_line_between_statements(
|
||||
source,
|
||||
TextRange::new(last_statement.end(), statement.start()),
|
||||
) {
|
||||
break;
|
||||
}
|
||||
last_statement = statement;
|
||||
}
|
||||
|
||||
if has_skip_comment(comments.trailing(last_statement), source) {
|
||||
Some(TextRange::new(
|
||||
start,
|
||||
trailing_semicolon(last_statement.into(), source)
|
||||
.map_or_else(|| last_statement.end(), ruff_text_size::TextRange::end),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn new_logical_line_between_statements(source: &str, between_statement_range: TextRange) -> bool {
|
||||
let mut tokenizer = SimpleTokenizer::new(source, between_statement_range).map(|tok| tok.kind());
|
||||
|
||||
while let Some(token) = tokenizer.next() {
|
||||
match token {
|
||||
SimpleTokenKind::Continuation => {
|
||||
tokenizer.next();
|
||||
}
|
||||
SimpleTokenKind::Newline => {
|
||||
return true;
|
||||
}
|
||||
// Since we are between statements, there are
|
||||
// no non-trivia tokens, so there is no need to check
|
||||
// for these and do an early return.
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use ruff_formatter::format;
|
||||
|
||||
@@ -2,7 +2,6 @@ use std::borrow::Cow;
|
||||
use std::iter::FusedIterator;
|
||||
use std::slice::Iter;
|
||||
|
||||
use itertools::PeekingNext;
|
||||
use ruff_formatter::{FormatError, write};
|
||||
use ruff_python_ast::AnyNodeRef;
|
||||
use ruff_python_ast::Stmt;
|
||||
@@ -452,40 +451,6 @@ fn write_suppressed_statements<'a>(
|
||||
}
|
||||
}
|
||||
|
||||
#[cold]
|
||||
pub(crate) fn write_skipped_statements<'a>(
|
||||
first_skipped: &'a Stmt,
|
||||
statements: &mut std::slice::Iter<'a, Stmt>,
|
||||
verbatim_range: TextRange,
|
||||
f: &mut PyFormatter,
|
||||
) -> FormatResult<&'a Stmt> {
|
||||
let comments = f.context().comments().clone();
|
||||
comments.mark_verbatim_node_comments_formatted(first_skipped.into());
|
||||
|
||||
let mut preceding = first_skipped;
|
||||
|
||||
while let Some(prec) = statements.peeking_next(|next| next.end() <= verbatim_range.end()) {
|
||||
comments.mark_verbatim_node_comments_formatted(prec.into());
|
||||
preceding = prec;
|
||||
}
|
||||
|
||||
let first_leading = comments.leading(first_skipped);
|
||||
let preceding_trailing = comments.trailing(preceding);
|
||||
|
||||
// Write the outer comments and format the node as verbatim
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
leading_comments(first_leading),
|
||||
source_position(verbatim_range.start()),
|
||||
verbatim_text(verbatim_range),
|
||||
source_position(verbatim_range.end()),
|
||||
trailing_comments(preceding_trailing)
|
||||
]
|
||||
)?;
|
||||
Ok(preceding)
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
enum InSuppression {
|
||||
No,
|
||||
@@ -928,6 +893,65 @@ impl Format<PyFormatContext<'_>> for VerbatimText {
|
||||
}
|
||||
}
|
||||
|
||||
/// Disables formatting for `node` and instead uses the same formatting as the node has in source.
|
||||
///
|
||||
/// The `node` gets indented as any formatted node to avoid syntax errors when the indentation string changes (e.g. from 2 spaces to 4).
|
||||
/// The `node`s leading and trailing comments are formatted as usual, except if they fall into the suppressed node's range.
|
||||
#[cold]
|
||||
pub(crate) fn suppressed_node<'a, N>(node: N) -> FormatSuppressedNode<'a>
|
||||
where
|
||||
N: Into<AnyNodeRef<'a>>,
|
||||
{
|
||||
FormatSuppressedNode { node: node.into() }
|
||||
}
|
||||
|
||||
pub(crate) struct FormatSuppressedNode<'a> {
|
||||
node: AnyNodeRef<'a>,
|
||||
}
|
||||
|
||||
impl Format<PyFormatContext<'_>> for FormatSuppressedNode<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<PyFormatContext<'_>>) -> FormatResult<()> {
|
||||
let comments = f.context().comments().clone();
|
||||
let node_comments = comments.leading_dangling_trailing(self.node);
|
||||
|
||||
// Mark all comments as formatted that fall into the node range
|
||||
for comment in node_comments.leading {
|
||||
if comment.start() > self.node.start() {
|
||||
comment.mark_formatted();
|
||||
}
|
||||
}
|
||||
|
||||
for comment in node_comments.trailing {
|
||||
if comment.start() < self.node.end() {
|
||||
comment.mark_formatted();
|
||||
}
|
||||
}
|
||||
|
||||
// Some statements may end with a semicolon. Preserve the semicolon
|
||||
let semicolon_range = self
|
||||
.node
|
||||
.is_statement()
|
||||
.then(|| trailing_semicolon(self.node, f.context().source()))
|
||||
.flatten();
|
||||
let verbatim_range = semicolon_range.map_or(self.node.range(), |semicolon| {
|
||||
TextRange::new(self.node.start(), semicolon.end())
|
||||
});
|
||||
comments.mark_verbatim_node_comments_formatted(self.node);
|
||||
|
||||
// Write the outer comments and format the node as verbatim
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
leading_comments(node_comments.leading),
|
||||
source_position(verbatim_range.start()),
|
||||
verbatim_text(verbatim_range),
|
||||
source_position(verbatim_range.end()),
|
||||
trailing_comments(node_comments.trailing)
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cold]
|
||||
pub(crate) fn write_suppressed_clause_header(
|
||||
header: ClauseHeader,
|
||||
|
||||
@@ -1,299 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_python_formatter/tests/fixtures.rs
|
||||
---
|
||||
## Input
|
||||
```python
|
||||
class Simple:
|
||||
x=1
|
||||
x=2 # fmt: skip
|
||||
x=3
|
||||
|
||||
class Semicolon:
|
||||
x=1
|
||||
x=2;x=3 # fmt: skip
|
||||
x=4
|
||||
|
||||
class TrailingSemicolon:
|
||||
x=1
|
||||
x=2;x=3 ; # fmt: skip
|
||||
x=4
|
||||
|
||||
class SemicolonNewLogicalLine:
|
||||
x=1;
|
||||
x=2;x=3 # fmt: skip
|
||||
x=4
|
||||
|
||||
class ManySemicolonOneLine:
|
||||
x=1
|
||||
x=2;x=3;x=4 # fmt: skip
|
||||
x=5
|
||||
|
||||
class CompoundInSuite:
|
||||
x=1
|
||||
def foo(): y=1 # fmt: skip
|
||||
x=2
|
||||
|
||||
class CompoundInSuiteNewline:
|
||||
x=1
|
||||
def foo():
|
||||
y=1 # fmt: skip
|
||||
x=2
|
||||
|
||||
class MultiLineSkip:
|
||||
x=1
|
||||
x = [
|
||||
'1',
|
||||
'2',
|
||||
] # fmt: skip
|
||||
|
||||
class MultiLineSemicolon:
|
||||
x=1
|
||||
x = [
|
||||
'1',
|
||||
'2',
|
||||
]; x=2 # fmt: skip
|
||||
|
||||
class LineContinuationSemicolonAfter:
|
||||
x=1
|
||||
x = ['a']\
|
||||
; y=1 # fmt: skip
|
||||
|
||||
class LineContinuationSemicolonBefore:
|
||||
x=1
|
||||
x = ['a']; \
|
||||
y=1 # fmt: skip
|
||||
|
||||
class LineContinuationSemicolonAndNewline:
|
||||
x=1
|
||||
x = ['a']; \
|
||||
|
||||
y=1 # fmt: skip
|
||||
|
||||
class LineContinuationSemicolonAndNewlineAndComment:
|
||||
x=1
|
||||
x = ['a']; \
|
||||
# 1
|
||||
y=1 # fmt: skip
|
||||
|
||||
class RepeatedLineContinuation:
|
||||
x=1
|
||||
x = ['a']; \
|
||||
\
|
||||
\
|
||||
y=1 # fmt: skip
|
||||
|
||||
class MultiLineSemicolonComments:
|
||||
x=1
|
||||
# 1
|
||||
x = [ # 2
|
||||
'1', # 3
|
||||
'2',
|
||||
# 4
|
||||
]; x=2 # 5 # fmt: skip # 6
|
||||
|
||||
class DocstringSkipped:
|
||||
'''This is a docstring''' # fmt: skip
|
||||
x=1
|
||||
|
||||
class MultilineDocstringSkipped:
|
||||
'''This is a docstring
|
||||
''' # fmt: skip
|
||||
x=1
|
||||
|
||||
class FirstStatementNewlines:
|
||||
|
||||
|
||||
|
||||
|
||||
x=1 # fmt: skip
|
||||
|
||||
class ChainingSemicolons:
|
||||
x=[
|
||||
'1',
|
||||
'2',
|
||||
'3',
|
||||
];x=1;x=[
|
||||
'1',
|
||||
'2',
|
||||
'3'
|
||||
];x=1;x=1 # fmt: skip
|
||||
|
||||
class LotsOfComments:
|
||||
# 1
|
||||
x=[ # 2
|
||||
'1', # 3
|
||||
'2',
|
||||
'3'
|
||||
] ;x=2;x=3 # 4 # fmt: skip # 5
|
||||
# 6
|
||||
|
||||
class MixingCompound:
|
||||
def foo(): bar(); import zoo # fmt: skip
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/17331
|
||||
def main() -> None:
|
||||
import ipdb; ipdb.set_trace() # noqa: E402,E702,I001 # fmt: skip
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/11430
|
||||
print(); print() # noqa # fmt: skip
|
||||
```
|
||||
|
||||
## Output
|
||||
```python
|
||||
class Simple:
|
||||
x = 1
|
||||
x=2 # fmt: skip
|
||||
x = 3
|
||||
|
||||
|
||||
class Semicolon:
|
||||
x = 1
|
||||
x=2;x=3 # fmt: skip
|
||||
x = 4
|
||||
|
||||
|
||||
class TrailingSemicolon:
|
||||
x = 1
|
||||
x=2;x=3 ; # fmt: skip
|
||||
x = 4
|
||||
|
||||
|
||||
class SemicolonNewLogicalLine:
|
||||
x = 1
|
||||
x=2;x=3 # fmt: skip
|
||||
x = 4
|
||||
|
||||
|
||||
class ManySemicolonOneLine:
|
||||
x = 1
|
||||
x=2;x=3;x=4 # fmt: skip
|
||||
x = 5
|
||||
|
||||
|
||||
class CompoundInSuite:
|
||||
x = 1
|
||||
|
||||
def foo(): y=1 # fmt: skip
|
||||
|
||||
x = 2
|
||||
|
||||
|
||||
class CompoundInSuiteNewline:
|
||||
x = 1
|
||||
|
||||
def foo():
|
||||
y=1 # fmt: skip
|
||||
|
||||
x = 2
|
||||
|
||||
|
||||
class MultiLineSkip:
|
||||
x = 1
|
||||
x = [
|
||||
'1',
|
||||
'2',
|
||||
] # fmt: skip
|
||||
|
||||
|
||||
class MultiLineSemicolon:
|
||||
x = 1
|
||||
x = [
|
||||
'1',
|
||||
'2',
|
||||
]; x=2 # fmt: skip
|
||||
|
||||
|
||||
class LineContinuationSemicolonAfter:
|
||||
x = 1
|
||||
x = ['a']\
|
||||
; y=1 # fmt: skip
|
||||
|
||||
|
||||
class LineContinuationSemicolonBefore:
|
||||
x = 1
|
||||
x = ['a']; \
|
||||
y=1 # fmt: skip
|
||||
|
||||
|
||||
class LineContinuationSemicolonAndNewline:
|
||||
x = 1
|
||||
x = ["a"]
|
||||
y=1 # fmt: skip
|
||||
|
||||
|
||||
class LineContinuationSemicolonAndNewlineAndComment:
|
||||
x = 1
|
||||
x = ["a"]
|
||||
# 1
|
||||
y=1 # fmt: skip
|
||||
|
||||
|
||||
class RepeatedLineContinuation:
|
||||
x = 1
|
||||
x = ['a']; \
|
||||
\
|
||||
\
|
||||
y=1 # fmt: skip
|
||||
|
||||
|
||||
class MultiLineSemicolonComments:
|
||||
x = 1
|
||||
# 1
|
||||
x = [ # 2
|
||||
'1', # 3
|
||||
'2',
|
||||
# 4
|
||||
]; x=2 # 5 # fmt: skip # 6
|
||||
|
||||
|
||||
class DocstringSkipped:
|
||||
'''This is a docstring''' # fmt: skip
|
||||
|
||||
x = 1
|
||||
|
||||
|
||||
class MultilineDocstringSkipped:
|
||||
'''This is a docstring
|
||||
''' # fmt: skip
|
||||
|
||||
x = 1
|
||||
|
||||
|
||||
class FirstStatementNewlines:
|
||||
x=1 # fmt: skip
|
||||
|
||||
|
||||
class ChainingSemicolons:
|
||||
x=[
|
||||
'1',
|
||||
'2',
|
||||
'3',
|
||||
];x=1;x=[
|
||||
'1',
|
||||
'2',
|
||||
'3'
|
||||
];x=1;x=1 # fmt: skip
|
||||
|
||||
|
||||
class LotsOfComments:
|
||||
# 1
|
||||
x=[ # 2
|
||||
'1', # 3
|
||||
'2',
|
||||
'3'
|
||||
] ;x=2;x=3 # 4 # fmt: skip # 5
|
||||
# 6
|
||||
|
||||
|
||||
class MixingCompound:
|
||||
def foo(): bar(); import zoo # fmt: skip
|
||||
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/17331
|
||||
def main() -> None:
|
||||
import ipdb; ipdb.set_trace() # noqa: E402,E702,I001 # fmt: skip
|
||||
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/11430
|
||||
print(); print() # noqa # fmt: skip
|
||||
```
|
||||
@@ -1,56 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_python_formatter/tests/fixtures.rs
|
||||
---
|
||||
## Input
|
||||
```python
|
||||
# 0
|
||||
'''Module docstring
|
||||
multiple lines''' # 1 # fmt: skip # 2
|
||||
|
||||
import a;import b; from c import (
|
||||
x,y,z
|
||||
); import f # fmt: skip
|
||||
|
||||
x=1;x=2;x=3;x=4 # fmt: skip
|
||||
|
||||
# 1
|
||||
x=[ # 2
|
||||
'1', # 3
|
||||
'2',
|
||||
'3'
|
||||
];x=1;x=1 # 4 # fmt: skip # 5
|
||||
# 6
|
||||
|
||||
def foo(): x=[
|
||||
'1',
|
||||
'2',
|
||||
];x=1 # fmt: skip
|
||||
```
|
||||
|
||||
## Output
|
||||
```python
|
||||
# 0
|
||||
'''Module docstring
|
||||
multiple lines''' # 1 # fmt: skip # 2
|
||||
|
||||
import a;import b; from c import (
|
||||
x,y,z
|
||||
); import f # fmt: skip
|
||||
|
||||
x=1;x=2;x=3;x=4 # fmt: skip
|
||||
|
||||
# 1
|
||||
x=[ # 2
|
||||
'1', # 3
|
||||
'2',
|
||||
'3'
|
||||
];x=1;x=1 # 4 # fmt: skip # 5
|
||||
# 6
|
||||
|
||||
|
||||
def foo():
|
||||
x=[
|
||||
'1',
|
||||
'2',
|
||||
];x=1 # fmt: skip
|
||||
```
|
||||
@@ -1,147 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_python_formatter/tests/fixtures.rs
|
||||
---
|
||||
## Input
|
||||
```python
|
||||
class Simple:
|
||||
# Range comprises skip range
|
||||
x=1
|
||||
<RANGE_START>x=2 <RANGE_END># fmt: skip
|
||||
x=3
|
||||
|
||||
class Semicolon:
|
||||
# Range is part of skip range
|
||||
x=1
|
||||
x=2;<RANGE_START>x=3<RANGE_END> # fmt: skip
|
||||
x=4
|
||||
|
||||
class FormatFirst:
|
||||
x=1
|
||||
<RANGE_START>x=2<RANGE_END>;x=3 # fmt: skip
|
||||
x=4
|
||||
|
||||
class FormatMiddle:
|
||||
x=1
|
||||
x=2;<RANGE_START>x=3<RANGE_END>;x=4 # fmt: skip
|
||||
x=5
|
||||
|
||||
class SemicolonNewLogicalLine:
|
||||
# Range overlaps on right side
|
||||
<RANGE_START>x=1;
|
||||
x=2<RANGE_END>;x=3 # fmt: skip
|
||||
x=4
|
||||
|
||||
class SemicolonNewLogicalLine:
|
||||
# Range overlaps on left side
|
||||
x=1;
|
||||
x=2;<RANGE_START>x=3 # fmt: skip
|
||||
x=4<RANGE_END>
|
||||
|
||||
class ManySemicolonOneLine:
|
||||
x=1
|
||||
x=2;x=3;x=4 # fmt: skip
|
||||
x=5
|
||||
|
||||
class CompoundInSuite:
|
||||
x=1
|
||||
<RANGE_START>def foo(): y=1 <RANGE_END># fmt: skip
|
||||
x=2
|
||||
|
||||
class CompoundInSuiteNewline:
|
||||
x=1
|
||||
def foo():
|
||||
y=1 # fmt: skip
|
||||
x=2
|
||||
|
||||
class MultiLineSkip:
|
||||
# Range inside statement
|
||||
x=1
|
||||
x = <RANGE_START>[
|
||||
'1',
|
||||
'2',<RANGE_END>
|
||||
] # fmt: skip
|
||||
|
||||
|
||||
class LotsOfComments:
|
||||
# 1
|
||||
x=[ # 2
|
||||
'1', # 3<RANGE_START>
|
||||
'2',
|
||||
'3'
|
||||
] ;x=2;x=3 # 4<RANGE_END> # fmt: skip # 5
|
||||
# 6
|
||||
|
||||
```
|
||||
|
||||
## Output
|
||||
```python
|
||||
class Simple:
|
||||
# Range comprises skip range
|
||||
x=1
|
||||
x=2 # fmt: skip
|
||||
x=3
|
||||
|
||||
class Semicolon:
|
||||
# Range is part of skip range
|
||||
x=1
|
||||
x=2;x=3 # fmt: skip
|
||||
x=4
|
||||
|
||||
class FormatFirst:
|
||||
x=1
|
||||
x=2;x=3 # fmt: skip
|
||||
x=4
|
||||
|
||||
class FormatMiddle:
|
||||
x=1
|
||||
x=2;x=3;x=4 # fmt: skip
|
||||
x=5
|
||||
|
||||
class SemicolonNewLogicalLine:
|
||||
# Range overlaps on right side
|
||||
x = 1
|
||||
x=2;x=3 # fmt: skip
|
||||
x=4
|
||||
|
||||
class SemicolonNewLogicalLine:
|
||||
# Range overlaps on left side
|
||||
x=1;
|
||||
x=2;x=3 # fmt: skip
|
||||
x = 4
|
||||
|
||||
class ManySemicolonOneLine:
|
||||
x=1
|
||||
x=2;x=3;x=4 # fmt: skip
|
||||
x=5
|
||||
|
||||
class CompoundInSuite:
|
||||
x=1
|
||||
def foo(): y=1 # fmt: skip
|
||||
|
||||
x=2
|
||||
|
||||
class CompoundInSuiteNewline:
|
||||
x=1
|
||||
def foo():
|
||||
y=1 # fmt: skip
|
||||
x=2
|
||||
|
||||
class MultiLineSkip:
|
||||
# Range inside statement
|
||||
x=1
|
||||
x = [
|
||||
'1',
|
||||
'2',
|
||||
] # fmt: skip
|
||||
|
||||
|
||||
class LotsOfComments:
|
||||
# 1
|
||||
x=[ # 2
|
||||
'1', # 3
|
||||
'2',
|
||||
'3'
|
||||
] ;x=2;x=3 # 4 # fmt: skip # 5
|
||||
# 6
|
||||
|
||||
```
|
||||
@@ -34,12 +34,12 @@ cargo install cargo-insta
|
||||
You'll need [uv](https://docs.astral.sh/uv/getting-started/installation/) (or `pipx` and `pip`) to
|
||||
run Python utility commands.
|
||||
|
||||
You can optionally install hooks to automatically run the validation checks
|
||||
You can optionally install pre-commit hooks to automatically run the validation checks
|
||||
when making a commit:
|
||||
|
||||
```shell
|
||||
uv tool install prek
|
||||
prek install
|
||||
uv tool install pre-commit
|
||||
pre-commit install
|
||||
```
|
||||
|
||||
We recommend [nextest](https://nexte.st/) to run ty's test suite (via `cargo nextest run`),
|
||||
@@ -66,7 +66,7 @@ and that it passes both the lint and test validation checks:
|
||||
```shell
|
||||
cargo clippy --workspace --all-targets --all-features -- -D warnings # Rust linting
|
||||
cargo test # Rust testing
|
||||
uvx prek run -a # Rust and Python formatting, Markdown and Python linting, etc.
|
||||
uvx pre-commit run --all-files --show-diff-on-failure # Rust and Python formatting, Markdown and Python linting, etc.
|
||||
```
|
||||
|
||||
These checks will run on GitHub Actions when you open your pull request, but running them locally
|
||||
|
||||
@@ -38,125 +38,6 @@ reveal_type(s1 > s2) # revealed: bool
|
||||
reveal_type(s1 >= s2) # revealed: bool
|
||||
```
|
||||
|
||||
## Signature derived from source ordering method
|
||||
|
||||
When the source ordering method accepts a broader type (like `object`) for its `other` parameter,
|
||||
the synthesized comparison methods should use the same signature. This allows comparisons with types
|
||||
other than the class itself:
|
||||
|
||||
```py
|
||||
from functools import total_ordering
|
||||
|
||||
@total_ordering
|
||||
class Comparable:
|
||||
def __init__(self, value: int):
|
||||
self.value = value
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
if isinstance(other, Comparable):
|
||||
return self.value == other.value
|
||||
if isinstance(other, int):
|
||||
return self.value == other
|
||||
return NotImplemented
|
||||
|
||||
def __lt__(self, other: object) -> bool:
|
||||
if isinstance(other, Comparable):
|
||||
return self.value < other.value
|
||||
if isinstance(other, int):
|
||||
return self.value < other
|
||||
return NotImplemented
|
||||
|
||||
a = Comparable(10)
|
||||
b = Comparable(20)
|
||||
|
||||
# Comparisons with the same type work.
|
||||
reveal_type(a <= b) # revealed: bool
|
||||
reveal_type(a >= b) # revealed: bool
|
||||
|
||||
# Comparisons with `int` also work because `__lt__` accepts `object`.
|
||||
reveal_type(a <= 15) # revealed: bool
|
||||
reveal_type(a >= 5) # revealed: bool
|
||||
```
|
||||
|
||||
## Multiple ordering methods with different signatures
|
||||
|
||||
When multiple ordering methods are defined with different signatures, the decorator selects a "root"
|
||||
method using the priority order: `__lt__` > `__le__` > `__gt__` > `__ge__`. Synthesized methods use
|
||||
the signature from the highest-priority method. Methods that are explicitly defined are not
|
||||
overridden.
|
||||
|
||||
```py
|
||||
from functools import total_ordering
|
||||
|
||||
@total_ordering
|
||||
class MultiSig:
|
||||
def __init__(self, value: int):
|
||||
self.value = value
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
return True
|
||||
# __lt__ accepts `object` (highest priority, used as root)
|
||||
def __lt__(self, other: object) -> bool:
|
||||
return True
|
||||
# __gt__ only accepts `MultiSig` (not overridden by decorator)
|
||||
def __gt__(self, other: "MultiSig") -> bool:
|
||||
return True
|
||||
|
||||
a = MultiSig(10)
|
||||
b = MultiSig(20)
|
||||
|
||||
# __le__ and __ge__ are synthesized with __lt__'s signature (accepts `object`)
|
||||
reveal_type(a <= b) # revealed: bool
|
||||
reveal_type(a <= 15) # revealed: bool
|
||||
reveal_type(a >= b) # revealed: bool
|
||||
reveal_type(a >= 15) # revealed: bool
|
||||
|
||||
# __gt__ keeps its original signature (only accepts MultiSig)
|
||||
reveal_type(a > b) # revealed: bool
|
||||
a > 15 # error: [unsupported-operator]
|
||||
```
|
||||
|
||||
## Overloaded ordering method
|
||||
|
||||
When the source ordering method is overloaded, the synthesized comparison methods should preserve
|
||||
all overloads:
|
||||
|
||||
```py
|
||||
from functools import total_ordering
|
||||
from typing import overload
|
||||
|
||||
@total_ordering
|
||||
class Flexible:
|
||||
def __init__(self, value: int):
|
||||
self.value = value
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
return True
|
||||
|
||||
@overload
|
||||
def __lt__(self, other: "Flexible") -> bool: ...
|
||||
@overload
|
||||
def __lt__(self, other: int) -> bool: ...
|
||||
def __lt__(self, other: "Flexible | int") -> bool:
|
||||
if isinstance(other, Flexible):
|
||||
return self.value < other.value
|
||||
return self.value < other
|
||||
|
||||
a = Flexible(10)
|
||||
b = Flexible(20)
|
||||
|
||||
# Synthesized __le__ preserves overloads from __lt__
|
||||
reveal_type(a <= b) # revealed: bool
|
||||
reveal_type(a <= 15) # revealed: bool
|
||||
|
||||
# Synthesized __ge__ also preserves overloads
|
||||
reveal_type(a >= b) # revealed: bool
|
||||
reveal_type(a >= 15) # revealed: bool
|
||||
|
||||
# But comparison with an unsupported type should still error
|
||||
a <= "string" # error: [unsupported-operator]
|
||||
```
|
||||
|
||||
## Using `__gt__` as the root comparison method
|
||||
|
||||
When a class defines `__eq__` and `__gt__`, the decorator synthesizes `__lt__`, `__le__`, and
|
||||
@@ -246,41 +127,6 @@ reveal_type(c1 > c2) # revealed: bool
|
||||
reveal_type(c1 >= c2) # revealed: bool
|
||||
```
|
||||
|
||||
## Method precedence with inheritance
|
||||
|
||||
The decorator always prefers `__lt__` > `__le__` > `__gt__` > `__ge__`, regardless of whether the
|
||||
method is defined locally or inherited. In this example, the inherited `__lt__` takes precedence
|
||||
over the locally-defined `__gt__`:
|
||||
|
||||
```py
|
||||
from functools import total_ordering
|
||||
from typing import Literal
|
||||
|
||||
class Base:
|
||||
def __lt__(self, other: "Base") -> Literal[True]:
|
||||
return True
|
||||
|
||||
@total_ordering
|
||||
class Child(Base):
|
||||
# __gt__ is defined locally, but __lt__ (inherited) takes precedence
|
||||
def __gt__(self, other: "Child") -> Literal[False]:
|
||||
return False
|
||||
|
||||
c1 = Child()
|
||||
c2 = Child()
|
||||
|
||||
# __lt__ is inherited from Base
|
||||
reveal_type(c1 < c2) # revealed: Literal[True]
|
||||
|
||||
# __gt__ is defined locally on Child
|
||||
reveal_type(c1 > c2) # revealed: Literal[False]
|
||||
|
||||
# __le__ and __ge__ are synthesized from __lt__ (the highest-priority method),
|
||||
# even though __gt__ is defined locally on the class itself
|
||||
reveal_type(c1 <= c2) # revealed: bool
|
||||
reveal_type(c1 >= c2) # revealed: bool
|
||||
```
|
||||
|
||||
## Explicitly-defined methods are not overridden
|
||||
|
||||
When a class explicitly defines multiple comparison methods, the decorator does not override them.
|
||||
@@ -399,79 +245,6 @@ n1 <= n2 # error: [unsupported-operator]
|
||||
n1 >= n2 # error: [unsupported-operator]
|
||||
```
|
||||
|
||||
## Non-bool return type
|
||||
|
||||
When the root ordering method returns a non-bool type (like `int`), the synthesized methods return a
|
||||
union of that type and `bool`. This is because `@total_ordering` generates methods like:
|
||||
|
||||
```python
|
||||
def __le__(self, other):
|
||||
return self < other or self == other
|
||||
```
|
||||
|
||||
If `__lt__` returns `int`, then the synthesized `__le__` could return either `int` (from
|
||||
`self < other`) or `bool` (from `self == other`). Since `bool` is a subtype of `int`, the union
|
||||
simplifies to `int`:
|
||||
|
||||
```py
|
||||
from functools import total_ordering
|
||||
|
||||
@total_ordering
|
||||
class IntReturn:
|
||||
def __init__(self, value: int):
|
||||
self.value = value
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
if not isinstance(other, IntReturn):
|
||||
return NotImplemented
|
||||
return self.value == other.value
|
||||
|
||||
def __lt__(self, other: "IntReturn") -> int:
|
||||
return self.value - other.value
|
||||
|
||||
a = IntReturn(10)
|
||||
b = IntReturn(20)
|
||||
|
||||
# User-defined __lt__ returns int.
|
||||
reveal_type(a < b) # revealed: int
|
||||
|
||||
# Synthesized methods return int (the union int | bool simplifies to int
|
||||
# because bool is a subtype of int in Python).
|
||||
reveal_type(a <= b) # revealed: int
|
||||
reveal_type(a > b) # revealed: int
|
||||
reveal_type(a >= b) # revealed: int
|
||||
```
|
||||
|
||||
When the root method returns a type that is not a supertype of `bool`, the union is preserved:
|
||||
|
||||
```py
|
||||
from functools import total_ordering
|
||||
|
||||
@total_ordering
|
||||
class StrReturn:
|
||||
def __init__(self, value: str):
|
||||
self.value = value
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
if not isinstance(other, StrReturn):
|
||||
return NotImplemented
|
||||
return self.value == other.value
|
||||
|
||||
def __lt__(self, other: "StrReturn") -> str:
|
||||
return self.value
|
||||
|
||||
a = StrReturn("a")
|
||||
b = StrReturn("b")
|
||||
|
||||
# User-defined __lt__ returns str.
|
||||
reveal_type(a < b) # revealed: str
|
||||
|
||||
# Synthesized methods return str | bool.
|
||||
reveal_type(a <= b) # revealed: str | bool
|
||||
reveal_type(a > b) # revealed: str | bool
|
||||
reveal_type(a >= b) # revealed: str | bool
|
||||
```
|
||||
|
||||
## Function call form
|
||||
|
||||
When `total_ordering` is called as a function (not as a decorator), the same validation is
|
||||
|
||||
@@ -171,27 +171,6 @@ static_assert(not is_equivalent_to(D[Any], C[Any]))
|
||||
static_assert(not is_equivalent_to(D[Any], C[Unknown]))
|
||||
```
|
||||
|
||||
## Bounded typevars in contravariant positions
|
||||
|
||||
When a bounded typevar appears in a contravariant position, the actual type doesn't need to satisfy
|
||||
the bound directly. The typevar can be solved to the intersection of the actual type and the bound
|
||||
(e.g., `Never` when disjoint).
|
||||
|
||||
```py
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T", contravariant=True)
|
||||
T_int = TypeVar("T_int", bound=int)
|
||||
|
||||
class Contra(Generic[T]): ...
|
||||
|
||||
def f(x: Contra[T_int]) -> T_int:
|
||||
raise NotImplementedError
|
||||
|
||||
def _(x: Contra[str]):
|
||||
reveal_type(f(x)) # revealed: Never
|
||||
```
|
||||
|
||||
## Invariance
|
||||
|
||||
With an invariant typevar, only equivalent specializations of the generic class are subtypes of or
|
||||
|
||||
@@ -172,23 +172,6 @@ static_assert(not is_equivalent_to(D[Any], C[Any]))
|
||||
static_assert(not is_equivalent_to(D[Any], C[Unknown]))
|
||||
```
|
||||
|
||||
## Bounded typevars in contravariant positions
|
||||
|
||||
When a bounded typevar appears in a contravariant position, the actual type doesn't need to satisfy
|
||||
the bound directly. The typevar can be solved to the intersection of the actual type and the bound
|
||||
(e.g., `Never` when disjoint).
|
||||
|
||||
```py
|
||||
class Contra[T]:
|
||||
def append(self, x: T): ...
|
||||
|
||||
def f[T: int](x: Contra[T]) -> T:
|
||||
raise NotImplementedError
|
||||
|
||||
def _(x: Contra[str]):
|
||||
reveal_type(f(x)) # revealed: Never
|
||||
```
|
||||
|
||||
## Invariance
|
||||
|
||||
With an invariant typevar, only equivalent specializations of the generic class are subtypes of or
|
||||
|
||||
@@ -556,27 +556,3 @@ def _(x: type[object], y: type[object], z: type[object]):
|
||||
if issubclass(z, Invariant):
|
||||
reveal_type(z) # revealed: type[Top[Invariant[Unknown]]]
|
||||
```
|
||||
|
||||
## Narrowing with TypedDict unions
|
||||
|
||||
Narrowing unions of `int` and multiple TypedDicts using `isinstance(x, dict)` should not panic
|
||||
during type ordering of normalized intersection types. Regression test for
|
||||
<https://github.com/astral-sh/ty/issues/2451>.
|
||||
|
||||
```py
|
||||
from typing import Any, TypedDict, cast
|
||||
|
||||
class A(TypedDict):
|
||||
x: str
|
||||
|
||||
class B(TypedDict):
|
||||
y: str
|
||||
|
||||
T = int | A | B
|
||||
|
||||
def test(a: Any, items: list[T]) -> None:
|
||||
combined = a or items
|
||||
v = combined[0]
|
||||
if isinstance(v, dict):
|
||||
cast(T, v) # no panic
|
||||
```
|
||||
|
||||
@@ -134,17 +134,12 @@ class IsEqualToEverything(type):
|
||||
class A(metaclass=IsEqualToEverything): ...
|
||||
class B(metaclass=IsEqualToEverything): ...
|
||||
|
||||
def _(x: A | B, y: object):
|
||||
def _(x: A | B):
|
||||
if type(x) == A:
|
||||
reveal_type(x) # revealed: A | B
|
||||
|
||||
if type(x) != A:
|
||||
reveal_type(x) # revealed: A | B
|
||||
|
||||
if type(y) == bool:
|
||||
reveal_type(y) # revealed: object
|
||||
else:
|
||||
reveal_type(y) # revealed: object
|
||||
```
|
||||
|
||||
## No narrowing for custom `type` callable
|
||||
|
||||
@@ -2479,37 +2479,6 @@ def f(arg1: type, arg2: type):
|
||||
reveal_type(arg2) # revealed: type & ~type[OnlyMethodMembers]
|
||||
```
|
||||
|
||||
Per PEP 544, `@runtime_checkable` propagates to subclasses. A protocol that inherits from a
|
||||
`@runtime_checkable` protocol is itself runtime-checkable, even without the decorator:
|
||||
|
||||
```py
|
||||
@runtime_checkable
|
||||
class RuntimeCheckableBase(Protocol):
|
||||
x: int
|
||||
|
||||
class RuntimeCheckableChild(RuntimeCheckableBase, Protocol):
|
||||
y: str
|
||||
|
||||
def g(arg: object):
|
||||
if isinstance(arg, RuntimeCheckableChild): # no error!
|
||||
reveal_type(arg) # revealed: RuntimeCheckableChild
|
||||
else:
|
||||
reveal_type(arg) # revealed: ~RuntimeCheckableChild
|
||||
```
|
||||
|
||||
This also applies to deeper inheritance hierarchies:
|
||||
|
||||
```py
|
||||
class RuntimeCheckableGrandchild(RuntimeCheckableChild, Protocol):
|
||||
z: float
|
||||
|
||||
def h(arg: object):
|
||||
if isinstance(arg, RuntimeCheckableGrandchild): # no error!
|
||||
reveal_type(arg) # revealed: RuntimeCheckableGrandchild
|
||||
else:
|
||||
reveal_type(arg) # revealed: ~RuntimeCheckableGrandchild
|
||||
```
|
||||
|
||||
## Truthiness of protocol instances
|
||||
|
||||
An instance of a protocol type generally has ambiguous truthiness:
|
||||
|
||||
@@ -53,26 +53,6 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/protocols.md
|
||||
38 | reveal_type(arg2) # revealed: type[OnlyMethodMembers]
|
||||
39 | else:
|
||||
40 | reveal_type(arg2) # revealed: type & ~type[OnlyMethodMembers]
|
||||
41 | @runtime_checkable
|
||||
42 | class RuntimeCheckableBase(Protocol):
|
||||
43 | x: int
|
||||
44 |
|
||||
45 | class RuntimeCheckableChild(RuntimeCheckableBase, Protocol):
|
||||
46 | y: str
|
||||
47 |
|
||||
48 | def g(arg: object):
|
||||
49 | if isinstance(arg, RuntimeCheckableChild): # no error!
|
||||
50 | reveal_type(arg) # revealed: RuntimeCheckableChild
|
||||
51 | else:
|
||||
52 | reveal_type(arg) # revealed: ~RuntimeCheckableChild
|
||||
53 | class RuntimeCheckableGrandchild(RuntimeCheckableChild, Protocol):
|
||||
54 | z: float
|
||||
55 |
|
||||
56 | def h(arg: object):
|
||||
57 | if isinstance(arg, RuntimeCheckableGrandchild): # no error!
|
||||
58 | reveal_type(arg) # revealed: RuntimeCheckableGrandchild
|
||||
59 | else:
|
||||
60 | reveal_type(arg) # revealed: ~RuntimeCheckableGrandchild
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
@@ -1833,7 +1833,7 @@ impl<'db> Type<'db> {
|
||||
///
|
||||
/// This method may have false negatives, but it should not have false positives. It should be
|
||||
/// a cheap shallow check, not an exhaustive recursive check.
|
||||
fn subtyping_is_always_reflexive(self) -> bool {
|
||||
const fn subtyping_is_always_reflexive(self) -> bool {
|
||||
match self {
|
||||
Type::Never
|
||||
| Type::FunctionLiteral(..)
|
||||
@@ -1854,6 +1854,7 @@ impl<'db> Type<'db> {
|
||||
| Type::AlwaysFalsy
|
||||
| Type::AlwaysTruthy
|
||||
| Type::PropertyInstance(_)
|
||||
| Type::TypeVar(_)
|
||||
// might inherit `Any`, but subtyping is still reflexive
|
||||
| Type::ClassLiteral(_)
|
||||
=> true,
|
||||
@@ -1865,7 +1866,6 @@ impl<'db> Type<'db> {
|
||||
| Type::Union(_)
|
||||
| Type::Intersection(_)
|
||||
| Type::Callable(_)
|
||||
| Type::TypeVar(_)
|
||||
| Type::BoundSuper(_)
|
||||
| Type::TypeIs(_)
|
||||
| Type::TypeGuard(_)
|
||||
@@ -11724,8 +11724,8 @@ pub(super) struct MetaclassCandidate<'db> {
|
||||
#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
|
||||
pub struct UnionType<'db> {
|
||||
/// The union type includes values in any of these types.
|
||||
#[returns(deref)]
|
||||
pub elements: Box<[Type<'db>]>,
|
||||
#[returns(ref)]
|
||||
pub elements: FxOrderSet<Type<'db>>,
|
||||
/// Whether the value pointed to by this type is recursively defined.
|
||||
/// If `Yes`, union literal widening is performed early.
|
||||
recursively_defined: RecursivelyDefined,
|
||||
|
||||
@@ -36,6 +36,8 @@
|
||||
//! shares exactly the same possible super-types, and none of them are subtypes of each other
|
||||
//! (unless exactly the same literal type), we can avoid many unnecessary redundancy checks.
|
||||
|
||||
use std::hash::BuildHasherDefault;
|
||||
|
||||
use crate::types::enums::{enum_member_literals, enum_metadata};
|
||||
use crate::types::type_ordering::union_or_intersection_elements_ordering;
|
||||
use crate::types::{
|
||||
@@ -706,7 +708,10 @@ impl<'db> UnionBuilder<'db> {
|
||||
}
|
||||
|
||||
pub(crate) fn try_build(self) -> Option<Type<'db>> {
|
||||
let mut types = vec![];
|
||||
let mut types: FxOrderSet<Type<'db>> = FxOrderSet::with_capacity_and_hasher(
|
||||
self.elements.len(),
|
||||
BuildHasherDefault::default(),
|
||||
);
|
||||
for element in self.elements {
|
||||
match element {
|
||||
UnionElement::IntLiterals(literals) => {
|
||||
@@ -721,7 +726,9 @@ impl<'db> UnionBuilder<'db> {
|
||||
UnionElement::EnumLiterals { literals, .. } => {
|
||||
types.extend(literals.into_iter().map(Type::EnumLiteral));
|
||||
}
|
||||
UnionElement::Type(ty) => types.push(ty),
|
||||
UnionElement::Type(ty) => {
|
||||
types.insert(ty);
|
||||
}
|
||||
}
|
||||
}
|
||||
if self.order_elements {
|
||||
@@ -730,11 +737,14 @@ impl<'db> UnionBuilder<'db> {
|
||||
match types.len() {
|
||||
0 => None,
|
||||
1 => Some(types[0]),
|
||||
_ => Some(Type::Union(UnionType::new(
|
||||
self.db,
|
||||
types.into_boxed_slice(),
|
||||
self.recursively_defined,
|
||||
))),
|
||||
_ => {
|
||||
types.shrink_to_fit();
|
||||
Some(Type::Union(UnionType::new(
|
||||
self.db,
|
||||
types,
|
||||
self.recursively_defined,
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -847,7 +857,7 @@ impl<'db> IntersectionBuilder<'db> {
|
||||
db,
|
||||
enum_member_literals(db, instance.class_literal(db), None)
|
||||
.expect("Calling `enum_member_literals` on an enum class")
|
||||
.collect::<Box<[_]>>(),
|
||||
.collect::<FxOrderSet<_>>(),
|
||||
RecursivelyDefined::No,
|
||||
)),
|
||||
seen_aliases,
|
||||
@@ -1412,6 +1422,7 @@ impl<'db> InnerIntersectionBuilder<'db> {
|
||||
mod tests {
|
||||
use super::{IntersectionBuilder, Type, UnionBuilder, UnionType};
|
||||
|
||||
use crate::FxOrderSet;
|
||||
use crate::db::tests::setup_db;
|
||||
use crate::place::known_module_symbol;
|
||||
use crate::types::enums::enum_member_literals;
|
||||
@@ -1445,7 +1456,7 @@ mod tests {
|
||||
let t1 = Type::IntLiteral(1);
|
||||
let union = UnionType::from_elements(&db, [t0, t1]).expect_union();
|
||||
|
||||
assert_eq!(union.elements(&db), &[t0, t1]);
|
||||
assert_eq!(union.elements(&db), &FxOrderSet::from_iter([t0, t1]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -4,10 +4,10 @@ use std::fmt::Display;
|
||||
use itertools::{Either, Itertools};
|
||||
use ruff_python_ast as ast;
|
||||
|
||||
use crate::Db;
|
||||
use crate::types::KnownClass;
|
||||
use crate::types::enums::{enum_member_literals, enum_metadata};
|
||||
use crate::types::tuple::{Tuple, TupleType};
|
||||
use crate::{Db, FxOrderSet};
|
||||
|
||||
use super::Type;
|
||||
|
||||
@@ -362,17 +362,17 @@ pub(crate) fn is_expandable_type<'db>(db: &'db dyn Db, ty: Type<'db>) -> bool {
|
||||
/// Expands a type into its possible subtypes, if applicable.
|
||||
///
|
||||
/// Returns [`None`] if the type cannot be expanded.
|
||||
fn expand_type<'db>(db: &'db dyn Db, ty: Type<'db>) -> Option<Vec<Type<'db>>> {
|
||||
fn expand_type<'db>(db: &'db dyn Db, ty: Type<'db>) -> Option<FxOrderSet<Type<'db>>> {
|
||||
// NOTE: Update `is_expandable_type` if this logic changes accordingly.
|
||||
match ty {
|
||||
Type::NominalInstance(instance) => {
|
||||
let class = instance.class(db);
|
||||
|
||||
if class.is_known(db, KnownClass::Bool) {
|
||||
return Some(vec![
|
||||
return Some(FxOrderSet::from_iter([
|
||||
Type::BooleanLiteral(true),
|
||||
Type::BooleanLiteral(false),
|
||||
]);
|
||||
]));
|
||||
}
|
||||
|
||||
// If the class is a fixed-length tuple subtype, we expand it to its elements.
|
||||
@@ -390,7 +390,7 @@ fn expand_type<'db>(db: &'db dyn Db, ty: Type<'db>) -> Option<Vec<Type<'db>>> {
|
||||
})
|
||||
.multi_cartesian_product()
|
||||
.map(|types| Type::tuple(TupleType::heterogeneous(db, types)))
|
||||
.collect::<Vec<_>>();
|
||||
.collect::<FxOrderSet<_>>();
|
||||
|
||||
if expanded.len() == 1 {
|
||||
// There are no elements in the tuple type that can be expanded.
|
||||
@@ -409,7 +409,7 @@ fn expand_type<'db>(db: &'db dyn Db, ty: Type<'db>) -> Option<Vec<Type<'db>>> {
|
||||
|
||||
None
|
||||
}
|
||||
Type::Union(union) => Some(union.elements(db).to_vec()),
|
||||
Type::Union(union) => Some(union.elements(db).clone()),
|
||||
// We don't handle `type[A | B]` here because it's already stored in the expanded form
|
||||
// i.e., `type[A] | type[B]` which is handled by the `Type::Union` case.
|
||||
_ => None,
|
||||
@@ -418,6 +418,7 @@ fn expand_type<'db>(db: &'db dyn Db, ty: Type<'db>) -> Option<Vec<Type<'db>>> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::FxOrderSet;
|
||||
use crate::db::tests::setup_db;
|
||||
use crate::types::tuple::TupleType;
|
||||
use crate::types::{KnownClass, Type, UnionType};
|
||||
@@ -427,12 +428,12 @@ mod tests {
|
||||
#[test]
|
||||
fn expand_union_type() {
|
||||
let db = setup_db();
|
||||
let types = [
|
||||
let types = FxOrderSet::from_iter([
|
||||
KnownClass::Int.to_instance(&db),
|
||||
KnownClass::Str.to_instance(&db),
|
||||
KnownClass::Bytes.to_instance(&db),
|
||||
];
|
||||
let union_type = UnionType::from_elements(&db, types);
|
||||
]);
|
||||
let union_type = UnionType::from_elements(&db, &types);
|
||||
let expanded = expand_type(&db, union_type).unwrap();
|
||||
assert_eq!(expanded.len(), types.len());
|
||||
assert_eq!(expanded, types);
|
||||
@@ -443,7 +444,8 @@ mod tests {
|
||||
let db = setup_db();
|
||||
let bool_instance = KnownClass::Bool.to_instance(&db);
|
||||
let expanded = expand_type(&db, bool_instance).unwrap();
|
||||
let expected_types = [Type::BooleanLiteral(true), Type::BooleanLiteral(false)];
|
||||
let expected_types =
|
||||
FxOrderSet::from_iter([Type::BooleanLiteral(true), Type::BooleanLiteral(false)]);
|
||||
assert_eq!(expanded.len(), expected_types.len());
|
||||
assert_eq!(expanded, expected_types);
|
||||
}
|
||||
@@ -477,14 +479,14 @@ mod tests {
|
||||
UnionType::from_elements(&db, [int_ty, str_ty, bytes_ty]),
|
||||
],
|
||||
);
|
||||
let expected_types = [
|
||||
let expected_types = FxOrderSet::from_iter([
|
||||
Type::heterogeneous_tuple(&db, [true_ty, int_ty]),
|
||||
Type::heterogeneous_tuple(&db, [true_ty, str_ty]),
|
||||
Type::heterogeneous_tuple(&db, [true_ty, bytes_ty]),
|
||||
Type::heterogeneous_tuple(&db, [false_ty, int_ty]),
|
||||
Type::heterogeneous_tuple(&db, [false_ty, str_ty]),
|
||||
Type::heterogeneous_tuple(&db, [false_ty, bytes_ty]),
|
||||
];
|
||||
]);
|
||||
let expanded = expand_type(&db, tuple_type2).unwrap();
|
||||
assert_eq!(expanded, expected_types);
|
||||
|
||||
@@ -498,12 +500,12 @@ mod tests {
|
||||
str_ty,
|
||||
],
|
||||
);
|
||||
let expected_types = [
|
||||
let expected_types = FxOrderSet::from_iter([
|
||||
Type::heterogeneous_tuple(&db, [true_ty, int_ty, str_ty, str_ty]),
|
||||
Type::heterogeneous_tuple(&db, [true_ty, int_ty, bytes_ty, str_ty]),
|
||||
Type::heterogeneous_tuple(&db, [false_ty, int_ty, str_ty, str_ty]),
|
||||
Type::heterogeneous_tuple(&db, [false_ty, int_ty, bytes_ty, str_ty]),
|
||||
];
|
||||
]);
|
||||
let expanded = expand_type(&db, tuple_type3).unwrap();
|
||||
assert_eq!(expanded, expected_types);
|
||||
|
||||
|
||||
@@ -2784,18 +2784,25 @@ impl<'a, 'db> ArgumentMatcher<'a, 'db> {
|
||||
// will allow us to error when `*args: P.args` is matched against, for example,
|
||||
// `n: int` and correctly type check when `*args: P.args` is matched against
|
||||
// `*args: P.args` (another ParamSpec).
|
||||
match union.elements(db) {
|
||||
[paramspec @ Type::TypeVar(typevar), other]
|
||||
| [other, paramspec @ Type::TypeVar(typevar)]
|
||||
if typevar.is_paramspec(db) && other.is_unknown() =>
|
||||
{
|
||||
VariadicArgumentType::ParamSpec(*paramspec)
|
||||
}
|
||||
_ => {
|
||||
// TODO: Same todo comment as in the non-paramspec case below
|
||||
VariadicArgumentType::Other(argument_type.iterate(db))
|
||||
let elements = union.elements(db);
|
||||
let mut paramspec = None;
|
||||
if elements.len() == 2 {
|
||||
match (elements[0], elements[1]) {
|
||||
(Type::TypeVar(typevar), Type::Dynamic(_))
|
||||
| (Type::Dynamic(_), Type::TypeVar(typevar))
|
||||
if typevar.is_paramspec(db) =>
|
||||
{
|
||||
paramspec = Some(Type::TypeVar(typevar));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
if let Some(paramspec) = paramspec {
|
||||
VariadicArgumentType::ParamSpec(paramspec)
|
||||
} else {
|
||||
// TODO: Same todo comment as in the non-paramspec case below
|
||||
VariadicArgumentType::Other(argument_type.iterate(db))
|
||||
}
|
||||
}
|
||||
Some(paramspec @ Type::TypeVar(typevar)) if typevar.is_paramspec(db) => {
|
||||
VariadicArgumentType::ParamSpec(paramspec)
|
||||
@@ -2915,15 +2922,20 @@ impl<'a, 'db> ArgumentMatcher<'a, 'db> {
|
||||
let value_type = match argument_type {
|
||||
Some(argument_type @ Type::Union(union)) => {
|
||||
// See the comment in `match_variadic` for why we special case this situation.
|
||||
match union.elements(db) {
|
||||
[paramspec @ Type::TypeVar(typevar), other]
|
||||
| [other, paramspec @ Type::TypeVar(typevar)]
|
||||
if typevar.is_paramspec(db) && other.is_unknown() =>
|
||||
{
|
||||
*paramspec
|
||||
let elements = union.elements(db);
|
||||
let mut paramspec = None;
|
||||
if elements.len() == 2 {
|
||||
match (elements[0], elements[1]) {
|
||||
(Type::TypeVar(typevar), Type::Dynamic(_))
|
||||
| (Type::Dynamic(_), Type::TypeVar(typevar))
|
||||
if typevar.is_paramspec(db) =>
|
||||
{
|
||||
paramspec = Some(Type::TypeVar(typevar));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
_ => dunder_getitem_return_type(argument_type),
|
||||
}
|
||||
paramspec.unwrap_or_else(|| dunder_getitem_return_type(argument_type))
|
||||
}
|
||||
Some(paramspec @ Type::TypeVar(typevar)) if typevar.is_paramspec(db) => paramspec,
|
||||
Some(argument_type) => dunder_getitem_return_type(argument_type),
|
||||
@@ -3572,15 +3584,20 @@ impl<'a, 'db> ArgumentTypeChecker<'a, 'db> {
|
||||
let value_type = match argument_type {
|
||||
Type::Union(union) => {
|
||||
// See the comment in `match_variadic` for why we special case this situation.
|
||||
match union.elements(self.db) {
|
||||
[paramspec @ Type::TypeVar(typevar), other]
|
||||
| [other, paramspec @ Type::TypeVar(typevar)]
|
||||
if typevar.is_paramspec(self.db) && other.is_unknown() =>
|
||||
{
|
||||
Some(*paramspec)
|
||||
let elements = union.elements(self.db);
|
||||
let mut paramspec = None;
|
||||
if elements.len() == 2 {
|
||||
match (elements[0], elements[1]) {
|
||||
(Type::TypeVar(typevar), Type::Dynamic(_))
|
||||
| (Type::Dynamic(_), Type::TypeVar(typevar))
|
||||
if typevar.is_paramspec(self.db) =>
|
||||
{
|
||||
paramspec = Some(Type::TypeVar(typevar));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
_ => value_type_fallback(argument_type),
|
||||
}
|
||||
paramspec.or_else(|| value_type_fallback(argument_type))
|
||||
}
|
||||
Type::TypeVar(typevar) if typevar.is_paramspec(self.db) => Some(argument_type),
|
||||
_ => value_type_fallback(argument_type),
|
||||
|
||||
@@ -1586,44 +1586,17 @@ impl<'db> ClassLiteral<'db> {
|
||||
}
|
||||
|
||||
/// Returns `true` if any class in this class's MRO (excluding `object`) defines an ordering
|
||||
/// method (`__lt__`, `__le__`, `__gt__`, `__ge__`). Used by `@total_ordering` validation.
|
||||
/// method (`__lt__`, `__le__`, `__gt__`, `__ge__`). Used by `@total_ordering` validation and
|
||||
/// for synthesizing comparison methods.
|
||||
pub(super) fn has_ordering_method_in_mro(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
specialization: Option<Specialization<'db>>,
|
||||
) -> bool {
|
||||
self.total_ordering_root_method(db, specialization)
|
||||
.is_some()
|
||||
}
|
||||
|
||||
/// Returns the type of the ordering method used by `@total_ordering`, if any.
|
||||
///
|
||||
/// Following `functools.total_ordering` precedence, we prefer `__lt__` > `__le__` > `__gt__` >
|
||||
/// `__ge__`, regardless of whether the method is defined locally or inherited.
|
||||
pub(super) fn total_ordering_root_method(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
specialization: Option<Specialization<'db>>,
|
||||
) -> Option<Type<'db>> {
|
||||
const ORDERING_METHODS: [&str; 4] = ["__lt__", "__le__", "__gt__", "__ge__"];
|
||||
|
||||
for name in ORDERING_METHODS {
|
||||
for base in self.iter_mro(db, specialization) {
|
||||
let Some(base_class) = base.into_class() else {
|
||||
continue;
|
||||
};
|
||||
let (base_literal, base_specialization) = base_class.class_literal(db);
|
||||
if base_literal.is_known(db, KnownClass::Object) {
|
||||
continue;
|
||||
}
|
||||
let member = class_member(db, base_literal.body_scope(db), name);
|
||||
if let Some(ty) = member.ignore_possibly_undefined() {
|
||||
return Some(ty.apply_optional_specialization(db, base_specialization));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
self.iter_mro(db, specialization)
|
||||
.filter_map(ClassBase::into_class)
|
||||
.filter(|class| !class.class_literal(db).0.is_known(db, KnownClass::Object))
|
||||
.any(|class| class.class_literal(db).0.has_own_ordering_method(db))
|
||||
}
|
||||
|
||||
pub(crate) fn generic_context(self, db: &'db dyn Db) -> Option<GenericContext<'db>> {
|
||||
@@ -2475,44 +2448,26 @@ impl<'db> ClassLiteral<'db> {
|
||||
// ordering method. The decorator requires at least one of __lt__,
|
||||
// __le__, __gt__, or __ge__ to be defined (either in this class or
|
||||
// inherited from a superclass, excluding `object`).
|
||||
//
|
||||
// Only synthesize methods that are not already defined in the MRO.
|
||||
if self.total_ordering(db)
|
||||
&& matches!(name, "__lt__" | "__le__" | "__gt__" | "__ge__")
|
||||
&& !self
|
||||
.iter_mro(db, specialization)
|
||||
.filter_map(ClassBase::into_class)
|
||||
.filter(|class| !class.class_literal(db).0.is_known(db, KnownClass::Object))
|
||||
.any(|class| {
|
||||
class_member(db, class.class_literal(db).0.body_scope(db), name)
|
||||
.ignore_possibly_undefined()
|
||||
.is_some()
|
||||
})
|
||||
&& self.has_ordering_method_in_mro(db, specialization)
|
||||
&& let Some(root_method_ty) = self.total_ordering_root_method(db, specialization)
|
||||
&& let Some(callables) = root_method_ty.try_upcast_to_callable(db)
|
||||
{
|
||||
let bool_ty = KnownClass::Bool.to_instance(db);
|
||||
let synthesized_callables = callables.map(|callable| {
|
||||
let signatures = CallableSignature::from_overloads(
|
||||
callable.signatures(db).iter().map(|signature| {
|
||||
// The generated methods return a union of the root method's return type
|
||||
// and `bool`. This is because `@total_ordering` synthesizes methods like:
|
||||
// def __gt__(self, other): return not (self == other or self < other)
|
||||
// If `__lt__` returns `int`, then `__gt__` could return `int | bool`.
|
||||
let return_ty =
|
||||
UnionType::from_elements(db, [signature.return_ty, bool_ty]);
|
||||
Signature::new_generic(
|
||||
signature.generic_context,
|
||||
signature.parameters().clone(),
|
||||
return_ty,
|
||||
)
|
||||
}),
|
||||
);
|
||||
CallableType::new(db, signatures, CallableTypeKind::FunctionLike)
|
||||
});
|
||||
if self.total_ordering(db) && matches!(name, "__lt__" | "__le__" | "__gt__" | "__ge__") {
|
||||
if self.has_ordering_method_in_mro(db, specialization) {
|
||||
let instance_ty =
|
||||
Type::instance(db, self.apply_optional_specialization(db, specialization));
|
||||
|
||||
return Some(synthesized_callables.into_type(db));
|
||||
let signature = Signature::new(
|
||||
Parameters::new(
|
||||
db,
|
||||
[
|
||||
Parameter::positional_or_keyword(Name::new_static("self"))
|
||||
.with_annotated_type(instance_ty),
|
||||
Parameter::positional_or_keyword(Name::new_static("other"))
|
||||
.with_annotated_type(instance_ty),
|
||||
],
|
||||
),
|
||||
KnownClass::Bool.to_instance(db),
|
||||
);
|
||||
|
||||
return Some(Type::function_like_callable(db, signature));
|
||||
}
|
||||
}
|
||||
|
||||
let field_policy = CodeGeneratorKind::from_class(db, self, specialization)?;
|
||||
|
||||
@@ -1878,21 +1878,6 @@ impl<'db> SpecializationBuilder<'db> {
|
||||
{
|
||||
match bound_typevar.typevar(self.db).bound_or_constraints(self.db) {
|
||||
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => {
|
||||
if polarity.is_contravariant() {
|
||||
// In a contravariant position, the formal type variable is a subtype of
|
||||
// the actual type (`T <: ty`). Since we also have the upper bound
|
||||
// constraint `T <: bound`, we just need to ensure that the intersection
|
||||
// of `ty` and `bound` is non-empty. Since `Never` is always a valid
|
||||
// intersection if the types are disjoint, we don't need to perform any
|
||||
// check here.
|
||||
self.add_type_mapping(
|
||||
bound_typevar,
|
||||
IntersectionType::from_elements(self.db, [bound, ty]),
|
||||
polarity,
|
||||
f,
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
if !ty
|
||||
.when_assignable_to(self.db, bound, self.inferable)
|
||||
.is_always_satisfied(self.db)
|
||||
@@ -1914,16 +1899,10 @@ impl<'db> SpecializationBuilder<'db> {
|
||||
}
|
||||
|
||||
for constraint in constraints.elements(self.db) {
|
||||
let is_satisfied = if polarity.is_contravariant() {
|
||||
constraint
|
||||
.when_assignable_to(self.db, ty, self.inferable)
|
||||
.is_always_satisfied(self.db)
|
||||
} else {
|
||||
ty.when_assignable_to(self.db, *constraint, self.inferable)
|
||||
.is_always_satisfied(self.db)
|
||||
};
|
||||
|
||||
if is_satisfied {
|
||||
if ty
|
||||
.when_assignable_to(self.db, *constraint, self.inferable)
|
||||
.is_always_satisfied(self.db)
|
||||
{
|
||||
self.add_type_mapping(bound_typevar, *constraint, polarity, f);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::FxIndexSet;
|
||||
use crate::FxOrderSet;
|
||||
use crate::place::builtins_module_scope;
|
||||
use crate::semantic_index::definition::Definition;
|
||||
use crate::semantic_index::definition::DefinitionKind;
|
||||
@@ -228,8 +229,8 @@ pub fn definitions_for_attribute<'db>(
|
||||
};
|
||||
|
||||
let tys = match lhs_ty {
|
||||
Type::Union(union) => union.elements(model.db()).to_vec(),
|
||||
_ => vec![lhs_ty],
|
||||
Type::Union(union) => union.elements(model.db()).clone(),
|
||||
_ => FxOrderSet::from_iter([lhs_ty]),
|
||||
};
|
||||
|
||||
// Expand intersections for each subtype into their components
|
||||
|
||||
@@ -7268,15 +7268,17 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
.any(|overload| overload.signature.generic_context.is_some());
|
||||
|
||||
// If the type context is a union, attempt to narrow to a specific element.
|
||||
let narrow_targets: &[_] = match call_expression_tcx.annotation {
|
||||
let narrow_targets = match call_expression_tcx.annotation {
|
||||
// TODO: We could theoretically attempt to narrow to every element of
|
||||
// the power set of this union. However, this leads to an exponential
|
||||
// explosion of inference attempts, and is rarely needed in practice.
|
||||
//
|
||||
// We only need to attempt narrowing on generic calls, otherwise the type
|
||||
// context has no effect.
|
||||
Some(Type::Union(union)) if has_generic_context => union.elements(db),
|
||||
_ => &[],
|
||||
Some(Type::Union(union)) if has_generic_context => {
|
||||
Either::Left(union.elements(db).iter().copied())
|
||||
}
|
||||
_ => Either::Right(std::iter::empty()),
|
||||
};
|
||||
|
||||
// We silence diagnostics until we successfully narrow to a specific type.
|
||||
@@ -7346,10 +7348,10 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
|
||||
// Prefer the declared type of generic classes.
|
||||
for narrowed_ty in narrow_targets
|
||||
.iter()
|
||||
.clone()
|
||||
.filter(|ty| ty.class_specialization(db).is_some())
|
||||
{
|
||||
if let Some(result) = try_narrow(*narrowed_ty) {
|
||||
if let Some(result) = try_narrow(narrowed_ty) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -7358,11 +7360,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
//
|
||||
// TODO: We could also attempt an inference without type context, but this
|
||||
// leads to similar performance issues.
|
||||
for narrowed_ty in narrow_targets
|
||||
.iter()
|
||||
.filter(|ty| ty.class_specialization(db).is_none())
|
||||
{
|
||||
if let Some(result) = try_narrow(*narrowed_ty) {
|
||||
for narrowed_ty in narrow_targets.filter(|ty| ty.class_specialization(db).is_none()) {
|
||||
if let Some(result) = try_narrow(narrowed_ty) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1220,10 +1220,10 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> {
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
let is_positive = match op {
|
||||
ast::CmpOp::Is => is_positive,
|
||||
ast::CmpOp::IsNot => !is_positive,
|
||||
_ => continue,
|
||||
let is_positive = if is_positive {
|
||||
op == &ast::CmpOp::Is
|
||||
} else {
|
||||
op == &ast::CmpOp::IsNot
|
||||
};
|
||||
|
||||
// `else`-branch narrowing for `if type(x) is Y` can only be done
|
||||
|
||||
@@ -76,17 +76,10 @@ impl<'db> ProtocolClass<'db> {
|
||||
}
|
||||
|
||||
pub(super) fn is_runtime_checkable(self, db: &'db dyn Db) -> bool {
|
||||
// Check if this class or any ancestor protocol is decorated with @runtime_checkable.
|
||||
// Per PEP 544, @runtime_checkable propagates to subclasses.
|
||||
self.0.iter_mro(db).any(|base| {
|
||||
base.into_class().is_some_and(|class| {
|
||||
class
|
||||
.class_literal(db)
|
||||
.0
|
||||
.known_function_decorators(db)
|
||||
.contains(&KnownFunction::RuntimeCheckable)
|
||||
})
|
||||
})
|
||||
self.class_literal(db)
|
||||
.0
|
||||
.known_function_decorators(db)
|
||||
.contains(&KnownFunction::RuntimeCheckable)
|
||||
}
|
||||
|
||||
/// Iterate through the body of the protocol class. Check that all definitions
|
||||
|
||||
@@ -467,23 +467,24 @@ impl<'db> Type<'db> {
|
||||
//
|
||||
// However, there is one exception to this general rule: for any given typevar `T`,
|
||||
// `T` will always be a subtype of any union containing `T`.
|
||||
(Type::TypeVar(bound_typevar), Type::Union(union))
|
||||
if !bound_typevar.is_inferable(db, inferable)
|
||||
(_, Type::Union(union))
|
||||
if (!relation.is_subtyping() || self.subtyping_is_always_reflexive())
|
||||
&& union.elements(db).contains(&self) =>
|
||||
{
|
||||
ConstraintSet::from(true)
|
||||
}
|
||||
|
||||
// A similar rule applies in reverse to intersection types.
|
||||
(Type::Intersection(intersection), Type::TypeVar(bound_typevar))
|
||||
if !bound_typevar.is_inferable(db, inferable)
|
||||
(Type::Intersection(intersection), _)
|
||||
if (!relation.is_subtyping() || target.subtyping_is_always_reflexive())
|
||||
&& intersection.positive(db).contains(&target) =>
|
||||
{
|
||||
ConstraintSet::from(true)
|
||||
}
|
||||
(Type::Intersection(intersection), Type::TypeVar(bound_typevar))
|
||||
if !bound_typevar.is_inferable(db, inferable)
|
||||
&& intersection.negative(db).contains(&target) =>
|
||||
(Type::Intersection(intersection), _)
|
||||
if (!relation.is_subtyping() || target.subtyping_is_always_reflexive())
|
||||
&& intersection.negative(db).contains(&target)
|
||||
&& !intersection.positive(db).iter().any(Type::is_dynamic) =>
|
||||
{
|
||||
ConstraintSet::from(false)
|
||||
}
|
||||
|
||||
@@ -1785,7 +1785,7 @@ impl<'db> Tuple<Type<'db>> {
|
||||
|
||||
// TODO: just grab this type from typeshed (it's a `sys._ReleaseLevel` type alias there)
|
||||
let release_level_ty = {
|
||||
let elements: Box<[Type<'db>]> = ["alpha", "beta", "candidate", "final"]
|
||||
let elements: FxOrderSet<_> = ["alpha", "beta", "candidate", "final"]
|
||||
.iter()
|
||||
.map(|level| Type::string_literal(db, level))
|
||||
.collect();
|
||||
|
||||
@@ -228,7 +228,9 @@ pub(super) fn union_or_intersection_elements_ordering<'db>(
|
||||
(Type::TypeAlias(_), _) => Ordering::Less,
|
||||
(_, Type::TypeAlias(_)) => Ordering::Greater,
|
||||
|
||||
(Type::TypedDict(left), Type::TypedDict(right)) => left.cmp(right),
|
||||
(Type::TypedDict(left), Type::TypedDict(right)) => {
|
||||
left.defining_class().cmp(&right.defining_class())
|
||||
}
|
||||
(Type::TypedDict(_), _) => Ordering::Less,
|
||||
(_, Type::TypedDict(_)) => Ordering::Greater,
|
||||
|
||||
|
||||
@@ -48,14 +48,7 @@ impl Default for TypedDictParams {
|
||||
|
||||
/// Type that represents the set of all inhabitants (`dict` instances) that conform to
|
||||
/// a given `TypedDict` schema.
|
||||
///
|
||||
/// # Ordering
|
||||
/// Ordering is derived from the variant order (`Class` < `Synthesized`) and the inner types.
|
||||
/// The Salsa IDs of inner types may change between runs or when the type was garbage collected
|
||||
/// and recreated.
|
||||
#[derive(
|
||||
Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, salsa::Update, Hash, get_size2::GetSize,
|
||||
)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, salsa::Update, Hash, get_size2::GetSize)]
|
||||
pub enum TypedDictType<'db> {
|
||||
/// A reference to the class (inheriting from `typing.TypedDict`) that specifies the
|
||||
/// schema of this `TypedDict`.
|
||||
@@ -885,11 +878,7 @@ pub(super) fn validate_typed_dict_dict_literal<'db>(
|
||||
}
|
||||
}
|
||||
|
||||
/// # Ordering
|
||||
/// Ordering is based on the type's salsa-assigned id and not on its values.
|
||||
/// The id may change between runs, or when the type was garbage collected and recreated.
|
||||
#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
|
||||
#[derive(PartialOrd, Ord)]
|
||||
pub struct SynthesizedTypedDictType<'db> {
|
||||
#[returns(ref)]
|
||||
pub(crate) items: TypedDictSchema<'db>,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use itertools::Either;
|
||||
use ruff_db::parsed::ParsedModuleRef;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
@@ -124,11 +125,13 @@ impl<'db, 'ast> Unpacker<'db, 'ast> {
|
||||
// See <https://github.com/astral-sh/ruff/pull/20377#issuecomment-3401380305>
|
||||
// for more discussion.
|
||||
let unpack_types = match value_ty {
|
||||
Type::Union(union_ty) => union_ty.elements(self.db()),
|
||||
_ => std::slice::from_ref(&value_ty),
|
||||
Type::Union(union_ty) => {
|
||||
Either::Left(union_ty.elements(self.db()).iter().copied())
|
||||
}
|
||||
_ => Either::Right(std::iter::once(value_ty)),
|
||||
};
|
||||
|
||||
for ty in unpack_types.iter().copied() {
|
||||
for ty in unpack_types {
|
||||
let tuple = ty.try_iterate(self.db()).unwrap_or_else(|err| {
|
||||
err.report_diagnostic(&self.context, ty, value_expr);
|
||||
Cow::Owned(TupleSpec::homogeneous(err.fallback_element_type(self.db())))
|
||||
|
||||
@@ -92,13 +92,6 @@ impl TypeVarVariance {
|
||||
TypeVarVariance::Covariant | TypeVarVariance::Bivariant
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) const fn is_contravariant(self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
TypeVarVariance::Contravariant | TypeVarVariance::Bivariant
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::iter::FromIterator<Self> for TypeVarVariance {
|
||||
|
||||
@@ -265,29 +265,26 @@ Instead, apply the `# fmt: off` comment to the entire statement:
|
||||
Like Black, Ruff will _also_ recognize [YAPF](https://github.com/google/yapf)'s `# yapf: disable` and `# yapf: enable` pragma
|
||||
comments, which are treated equivalently to `# fmt: off` and `# fmt: on`, respectively.
|
||||
|
||||
`# fmt: skip` comments suppress formatting for a case header, decorator,
|
||||
function definition, class definition, or the preceding statements
|
||||
on the same logical line. The formatter leaves the following unchanged:
|
||||
`# fmt: skip` comments suppress formatting for a preceding statement, case header, decorator,
|
||||
function definition, or class definition:
|
||||
|
||||
```python
|
||||
if True:
|
||||
pass
|
||||
elif False: # fmt: skip
|
||||
elif False: # fmt: skip
|
||||
pass
|
||||
|
||||
@Test
|
||||
@Test2(a,b) # fmt: skip
|
||||
@Test2 # fmt: skip
|
||||
def test(): ...
|
||||
|
||||
a = [1,2,3,4,5] # fmt: skip
|
||||
a = [1, 2, 3, 4, 5] # fmt: skip
|
||||
|
||||
def test(a,b,c,d,e,f) -> int: # fmt: skip
|
||||
def test(a, b, c, d, e, f) -> int: # fmt: skip
|
||||
pass
|
||||
|
||||
x=1;x=2;x=3 # fmt: skip
|
||||
```
|
||||
|
||||
Adding a `# fmt: skip` comment at the end of an expression will have no effect. In
|
||||
As such, adding an `# fmt: skip` comment at the end of an expression will have no effect. In
|
||||
the following example, the list entry `'1'` will be formatted, despite the `# fmt: skip`:
|
||||
|
||||
```python
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
PyYAML==6.0.3
|
||||
ruff==0.14.11
|
||||
ruff==0.14.10
|
||||
mkdocs==1.6.1
|
||||
mkdocs-material==9.7.1
|
||||
mkdocs-redirects==1.2.2
|
||||
|
||||
6
playground/api/package-lock.json
generated
6
playground/api/package-lock.json
generated
@@ -118,9 +118,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@cloudflare/workers-types": {
|
||||
"version": "4.20260103.0",
|
||||
"resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20260103.0.tgz",
|
||||
"integrity": "sha512-jANmoGpJcXARnwlkvrQOeWyjYD1quTfHcs+++Z544XRHOSfLc4XSlts7snIhbiIGgA5bo66zDhraF+9lKUr2hw==",
|
||||
"version": "4.20251229.0",
|
||||
"resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20251229.0.tgz",
|
||||
"integrity": "sha512-LgzxDZaT9bQhycQInf7S/fcZCQRTvWWQPE9xnEyedI+CXxWsXAD7hg84kvVyr+KUz+W9Oblzo75g6XZ3HdI5Yg==",
|
||||
"dev": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"peer": true
|
||||
|
||||
148
playground/package-lock.json
generated
148
playground/package-lock.json
generated
@@ -61,7 +61,6 @@
|
||||
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.27.1",
|
||||
"@babel/generator": "^7.28.5",
|
||||
@@ -1894,7 +1893,6 @@
|
||||
"integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"csstype": "^3.2.2"
|
||||
}
|
||||
@@ -1917,20 +1915,20 @@
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "8.51.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.51.0.tgz",
|
||||
"integrity": "sha512-XtssGWJvypyM2ytBnSnKtHYOGT+4ZwTnBVl36TA4nRO2f4PRNGz5/1OszHzcZCvcBMh+qb7I06uoCmLTRdR9og==",
|
||||
"version": "8.50.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.50.1.tgz",
|
||||
"integrity": "sha512-PKhLGDq3JAg0Jk/aK890knnqduuI/Qj+udH7wCf0217IGi4gt+acgCyPVe79qoT+qKUvHMDQkwJeKW9fwl8Cyw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/regexpp": "^4.10.0",
|
||||
"@typescript-eslint/scope-manager": "8.51.0",
|
||||
"@typescript-eslint/type-utils": "8.51.0",
|
||||
"@typescript-eslint/utils": "8.51.0",
|
||||
"@typescript-eslint/visitor-keys": "8.51.0",
|
||||
"@typescript-eslint/scope-manager": "8.50.1",
|
||||
"@typescript-eslint/type-utils": "8.50.1",
|
||||
"@typescript-eslint/utils": "8.50.1",
|
||||
"@typescript-eslint/visitor-keys": "8.50.1",
|
||||
"ignore": "^7.0.0",
|
||||
"natural-compare": "^1.4.0",
|
||||
"ts-api-utils": "^2.2.0"
|
||||
"ts-api-utils": "^2.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -1940,7 +1938,7 @@
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@typescript-eslint/parser": "^8.51.0",
|
||||
"@typescript-eslint/parser": "^8.50.1",
|
||||
"eslint": "^8.57.0 || ^9.0.0",
|
||||
"typescript": ">=4.8.4 <6.0.0"
|
||||
}
|
||||
@@ -1956,17 +1954,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser": {
|
||||
"version": "8.51.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.51.0.tgz",
|
||||
"integrity": "sha512-3xP4XzzDNQOIqBMWogftkwxhg5oMKApqY0BAflmLZiFYHqyhSOxv/cd/zPQLTcCXr4AkaKb25joocY0BD1WC6A==",
|
||||
"version": "8.50.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.50.1.tgz",
|
||||
"integrity": "sha512-hM5faZwg7aVNa819m/5r7D0h0c9yC4DUlWAOvHAtISdFTc8xB86VmX5Xqabrama3wIPJ/q9RbGS1worb6JfnMg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.51.0",
|
||||
"@typescript-eslint/types": "8.51.0",
|
||||
"@typescript-eslint/typescript-estree": "8.51.0",
|
||||
"@typescript-eslint/visitor-keys": "8.51.0",
|
||||
"@typescript-eslint/scope-manager": "8.50.1",
|
||||
"@typescript-eslint/types": "8.50.1",
|
||||
"@typescript-eslint/typescript-estree": "8.50.1",
|
||||
"@typescript-eslint/visitor-keys": "8.50.1",
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
"engines": {
|
||||
@@ -1982,14 +1979,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/project-service": {
|
||||
"version": "8.51.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.51.0.tgz",
|
||||
"integrity": "sha512-Luv/GafO07Z7HpiI7qeEW5NW8HUtZI/fo/kE0YbtQEFpJRUuR0ajcWfCE5bnMvL7QQFrmT/odMe8QZww8X2nfQ==",
|
||||
"version": "8.50.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.50.1.tgz",
|
||||
"integrity": "sha512-E1ur1MCVf+YiP89+o4Les/oBAVzmSbeRB0MQLfSlYtbWU17HPxZ6Bhs5iYmKZRALvEuBoXIZMOIRRc/P++Ortg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/tsconfig-utils": "^8.51.0",
|
||||
"@typescript-eslint/types": "^8.51.0",
|
||||
"@typescript-eslint/tsconfig-utils": "^8.50.1",
|
||||
"@typescript-eslint/types": "^8.50.1",
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
"engines": {
|
||||
@@ -2004,14 +2001,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "8.51.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.51.0.tgz",
|
||||
"integrity": "sha512-JhhJDVwsSx4hiOEQPeajGhCWgBMBwVkxC/Pet53EpBVs7zHHtayKefw1jtPaNRXpI9RA2uocdmpdfE7T+NrizA==",
|
||||
"version": "8.50.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.50.1.tgz",
|
||||
"integrity": "sha512-mfRx06Myt3T4vuoHaKi8ZWNTPdzKPNBhiblze5N50//TSHOAQQevl/aolqA/BcqqbJ88GUnLqjjcBc8EWdBcVw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.51.0",
|
||||
"@typescript-eslint/visitor-keys": "8.51.0"
|
||||
"@typescript-eslint/types": "8.50.1",
|
||||
"@typescript-eslint/visitor-keys": "8.50.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -2022,9 +2019,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/tsconfig-utils": {
|
||||
"version": "8.51.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.51.0.tgz",
|
||||
"integrity": "sha512-Qi5bSy/vuHeWyir2C8u/uqGMIlIDu8fuiYWv48ZGlZ/k+PRPHtaAu7erpc7p5bzw2WNNSniuxoMSO4Ar6V9OXw==",
|
||||
"version": "8.50.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.50.1.tgz",
|
||||
"integrity": "sha512-ooHmotT/lCWLXi55G4mvaUF60aJa012QzvLK0Y+Mp4WdSt17QhMhWOaBWeGTFVkb2gDgBe19Cxy1elPXylslDw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -2039,17 +2036,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils": {
|
||||
"version": "8.51.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.51.0.tgz",
|
||||
"integrity": "sha512-0XVtYzxnobc9K0VU7wRWg1yiUrw4oQzexCG2V2IDxxCxhqBMSMbjB+6o91A+Uc0GWtgjCa3Y8bi7hwI0Tu4n5Q==",
|
||||
"version": "8.50.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.50.1.tgz",
|
||||
"integrity": "sha512-7J3bf022QZE42tYMO6SL+6lTPKFk/WphhRPe9Tw/el+cEwzLz1Jjz2PX3GtGQVxooLDKeMVmMt7fWpYRdG5Etg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.51.0",
|
||||
"@typescript-eslint/typescript-estree": "8.51.0",
|
||||
"@typescript-eslint/utils": "8.51.0",
|
||||
"@typescript-eslint/types": "8.50.1",
|
||||
"@typescript-eslint/typescript-estree": "8.50.1",
|
||||
"@typescript-eslint/utils": "8.50.1",
|
||||
"debug": "^4.3.4",
|
||||
"ts-api-utils": "^2.2.0"
|
||||
"ts-api-utils": "^2.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -2064,9 +2061,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/types": {
|
||||
"version": "8.51.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.51.0.tgz",
|
||||
"integrity": "sha512-TizAvWYFM6sSscmEakjY3sPqGwxZRSywSsPEiuZF6d5GmGD9Gvlsv0f6N8FvAAA0CD06l3rIcWNbsN1e5F/9Ag==",
|
||||
"version": "8.50.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.50.1.tgz",
|
||||
"integrity": "sha512-v5lFIS2feTkNyMhd7AucE/9j/4V9v5iIbpVRncjk/K0sQ6Sb+Np9fgYS/63n6nwqahHQvbmujeBL7mp07Q9mlA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -2078,21 +2075,21 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "8.51.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.51.0.tgz",
|
||||
"integrity": "sha512-1qNjGqFRmlq0VW5iVlcyHBbCjPB7y6SxpBkrbhNWMy/65ZoncXCEPJxkRZL8McrseNH6lFhaxCIaX+vBuFnRng==",
|
||||
"version": "8.50.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.50.1.tgz",
|
||||
"integrity": "sha512-woHPdW+0gj53aM+cxchymJCrh0cyS7BTIdcDxWUNsclr9VDkOSbqC13juHzxOmQ22dDkMZEpZB+3X1WpUvzgVQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/project-service": "8.51.0",
|
||||
"@typescript-eslint/tsconfig-utils": "8.51.0",
|
||||
"@typescript-eslint/types": "8.51.0",
|
||||
"@typescript-eslint/visitor-keys": "8.51.0",
|
||||
"@typescript-eslint/project-service": "8.50.1",
|
||||
"@typescript-eslint/tsconfig-utils": "8.50.1",
|
||||
"@typescript-eslint/types": "8.50.1",
|
||||
"@typescript-eslint/visitor-keys": "8.50.1",
|
||||
"debug": "^4.3.4",
|
||||
"minimatch": "^9.0.4",
|
||||
"semver": "^7.6.0",
|
||||
"tinyglobby": "^0.2.15",
|
||||
"ts-api-utils": "^2.2.0"
|
||||
"ts-api-utils": "^2.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -2145,16 +2142,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/utils": {
|
||||
"version": "8.51.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.51.0.tgz",
|
||||
"integrity": "sha512-11rZYxSe0zabiKaCP2QAwRf/dnmgFgvTmeDTtZvUvXG3UuAdg/GU02NExmmIXzz3vLGgMdtrIosI84jITQOxUA==",
|
||||
"version": "8.50.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.50.1.tgz",
|
||||
"integrity": "sha512-lCLp8H1T9T7gPbEuJSnHwnSuO9mDf8mfK/Nion5mZmiEaQD9sWf9W4dfeFqRyqRjF06/kBuTmAqcs9sewM2NbQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.7.0",
|
||||
"@typescript-eslint/scope-manager": "8.51.0",
|
||||
"@typescript-eslint/types": "8.51.0",
|
||||
"@typescript-eslint/typescript-estree": "8.51.0"
|
||||
"@typescript-eslint/scope-manager": "8.50.1",
|
||||
"@typescript-eslint/types": "8.50.1",
|
||||
"@typescript-eslint/typescript-estree": "8.50.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -2169,13 +2166,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "8.51.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.51.0.tgz",
|
||||
"integrity": "sha512-mM/JRQOzhVN1ykejrvwnBRV3+7yTKK8tVANVN3o1O0t0v7o+jqdVu9crPy5Y9dov15TJk/FTIgoUGHrTOVL3Zg==",
|
||||
"version": "8.50.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.50.1.tgz",
|
||||
"integrity": "sha512-IrDKrw7pCRUR94zeuCSUWQ+w8JEf5ZX5jl/e6AHGSLi1/zIr0lgutfn/7JpfCey+urpgQEdrZVYzCaVVKiTwhQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.51.0",
|
||||
"@typescript-eslint/types": "8.50.1",
|
||||
"eslint-visitor-keys": "^4.2.1"
|
||||
},
|
||||
"engines": {
|
||||
@@ -2209,7 +2206,6 @@
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@@ -2566,7 +2562,6 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.9.0",
|
||||
"caniuse-lite": "^1.0.30001759",
|
||||
@@ -3220,7 +3215,6 @@
|
||||
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.8.0",
|
||||
"@eslint-community/regexpp": "^4.12.1",
|
||||
@@ -5093,7 +5087,6 @@
|
||||
"resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.55.1.tgz",
|
||||
"integrity": "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"dompurify": "3.2.7",
|
||||
"marked": "14.0.0"
|
||||
@@ -5538,7 +5531,6 @@
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz",
|
||||
"integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -5548,7 +5540,6 @@
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz",
|
||||
"integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"scheduler": "^0.26.0"
|
||||
},
|
||||
@@ -6218,7 +6209,6 @@
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -6240,9 +6230,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ts-api-utils": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz",
|
||||
"integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==",
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.3.0.tgz",
|
||||
"integrity": "sha512-6eg3Y9SF7SsAvGzRHQvvc1skDAhwI4YQ32ui1scxD1Ccr0G5qIIbUBT3pFTKX8kmWIQClHobtUdNuaBgwdfdWg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -6370,7 +6360,6 @@
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
@@ -6380,16 +6369,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/typescript-eslint": {
|
||||
"version": "8.51.0",
|
||||
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.51.0.tgz",
|
||||
"integrity": "sha512-jh8ZuM5oEh2PSdyQG9YAEM1TCGuWenLSuSUhf/irbVUNW9O5FhbFVONviN2TgMTBnUmyHv7E56rYnfLZK6TkiA==",
|
||||
"version": "8.50.1",
|
||||
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.50.1.tgz",
|
||||
"integrity": "sha512-ytTHO+SoYSbhAH9CrYnMhiLx8To6PSSvqnvXyPUgPETCvB6eBKmTI9w6XMPS3HsBRGkwTVBX+urA8dYQx6bHfQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "8.51.0",
|
||||
"@typescript-eslint/parser": "8.51.0",
|
||||
"@typescript-eslint/typescript-estree": "8.51.0",
|
||||
"@typescript-eslint/utils": "8.51.0"
|
||||
"@typescript-eslint/eslint-plugin": "8.50.1",
|
||||
"@typescript-eslint/parser": "8.50.1",
|
||||
"@typescript-eslint/typescript-estree": "8.50.1",
|
||||
"@typescript-eslint/utils": "8.50.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -6479,7 +6468,6 @@
|
||||
"integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.27.0",
|
||||
"fdir": "^6.5.0",
|
||||
@@ -6593,7 +6581,6 @@
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -6784,7 +6771,6 @@
|
||||
"integrity": "sha512-0wZ1IRqGGhMP76gLqz8EyfBXKk0J2qo2+H3fi4mcUP/KtTocoX08nmIAHl1Z2kJIZbZee8KOpBCSNPRgauucjw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user