Compare commits

..

10 Commits

Author SHA1 Message Date
Zanie Blue
40b4aa28f9 Zizmor 2025-07-03 07:23:11 -05:00
Zanie Blue
ea4bf00c23 Revert "Only run the relevant test"
This reverts commit 82dc27f2680b8085280136848ffe2ee1d2952a4e.
2025-07-03 07:01:28 -05:00
Zanie Blue
7f4aa4b3fb Update for Depot 2025-07-03 07:01:28 -05:00
Zanie Blue
34c98361ae Do not set TMP 2025-07-03 07:01:28 -05:00
Zanie Blue
38bb96a6c2 Remove log variables 2025-07-03 07:01:28 -05:00
Zanie Blue
a014d55455 Remove fuzz corpus hack 2025-07-03 07:01:27 -05:00
Zanie Blue
306f6f17a9 Enable more logs 2025-07-03 07:01:27 -05:00
Zanie Blue
b233888f00 Only run the relevant test 2025-07-03 07:01:27 -05:00
Zanie Blue
540cbd9085 Add debug logs? 2025-07-03 07:01:27 -05:00
Zanie Blue
0112f7f0e4 Use a dev drive for testing on Windows 2025-07-03 07:01:27 -05:00
30 changed files with 206 additions and 331 deletions

View File

@@ -321,14 +321,30 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- name: Setup Dev Drive
run: ${{ github.workspace }}/.github/workflows/setup-dev-drive.ps1
# actions/checkout does not let us clone into anywhere outside `github.workspace`, so we have to copy the clone
- name: Copy Git Repo to Dev Drive
env:
RUFF_WORKSPACE: ${{ env.RUFF_WORKSPACE }}
run: |
Copy-Item -Path "${{ github.workspace }}" -Destination "${env:RUFF_WORKSPACE}" -Recurse
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
with:
workspaces: ${{ env.RUFF_WORKSPACE }}
- name: "Install Rust toolchain"
working-directory: ${{ env.RUFF_WORKSPACE }}
run: rustup show
- name: "Install cargo nextest"
uses: taiki-e/install-action@d12e869b89167df346dd0ff65da342d1fb1202fb # v2.53.2
with:
tool: cargo-nextest
- name: "Run tests"
working-directory: ${{ env.RUFF_WORKSPACE }}
shell: bash
env:
NEXTEST_PROFILE: "ci"

93
.github/workflows/setup-dev-drive.ps1 vendored Normal file
View File

@@ -0,0 +1,93 @@
# Configures a drive for testing in CI.
#
# When using standard GitHub Actions runners, a `D:` drive is present and has
# similar or better performance characteristics than a ReFS dev drive. Sometimes
# using a larger runner is still more performant (e.g., when running the test
# suite) and we need to create a dev drive. This script automatically configures
# the appropriate drive.
#
# When using GitHub Actions' "larger runners", the `D:` drive is not present and
# we create a DevDrive mount on `C:`. This is purported to be more performant
# than an ReFS drive, though we did not see a change when we switched over.
#
# When using Depot runners, the underling infrastructure is EC2, which does not
# support Hyper-V. The `New-VHD` commandlet only works with Hyper-V, but we can
# create a ReFS drive using `diskpart` and `format` directory. We cannot use a
# DevDrive, as that also requires Hyper-V. The Depot runners use `D:` already,
# so we must check if it's a Depot runner first, and we use `V:` as the target
# instead.
if ($env:DEPOT_RUNNER -eq "1") {
Write-Output "DEPOT_RUNNER detected, setting up custom dev drive..."
# Create VHD and configure drive using diskpart
$vhdPath = "C:\ruff_dev_drive.vhdx"
@"
create vdisk file="$vhdPath" maximum=20480 type=expandable
attach vdisk
create partition primary
active
assign letter=V
"@ | diskpart
# Format the drive as ReFS
format V: /fs:ReFS /q /y
$Drive = "V:"
Write-Output "Custom dev drive created at $Drive"
} elseif (Test-Path "D:\") {
# Note `Get-PSDrive` is not sufficient because the drive letter is assigned.
Write-Output "Using existing drive at D:"
$Drive = "D:"
} else {
# The size (20 GB) is chosen empirically to be large enough for our
# workflows; larger drives can take longer to set up.
$Volume = New-VHD -Path C:/ruff_dev_drive.vhdx -SizeBytes 20GB |
Mount-VHD -Passthru |
Initialize-Disk -Passthru |
New-Partition -AssignDriveLetter -UseMaximumSize |
Format-Volume -DevDrive -Confirm:$false -Force
$Drive = "$($Volume.DriveLetter):"
# Set the drive as trusted
# See https://learn.microsoft.com/en-us/windows/dev-drive/#how-do-i-designate-a-dev-drive-as-trusted
fsutil devdrv trust $Drive
# Disable antivirus filtering on dev drives
# See https://learn.microsoft.com/en-us/windows/dev-drive/#how-do-i-configure-additional-filters-on-dev-drive
fsutil devdrv enable /disallowAv
# Remount so the changes take effect
Dismount-VHD -Path C:/ruff_dev_drive.vhdx
Mount-VHD -Path C:/ruff_dev_drive.vhdx
# Show some debug information
Write-Output $Volume
fsutil devdrv query $Drive
Write-Output "Using Dev Drive at $Volume"
}
$Tmp = "$($Drive)\ruff-tmp"
# Create the directory ahead of time in an attempt to avoid race-conditions
New-Item $Tmp -ItemType Directory
# Move Cargo to the dev drive
New-Item -Path "$($Drive)/.cargo/bin" -ItemType Directory -Force
if (Test-Path "C:/Users/runneradmin/.cargo") {
Copy-Item -Path "C:/Users/runneradmin/.cargo/*" -Destination "$($Drive)/.cargo/" -Recurse -Force
}
Write-Output `
"DEV_DRIVE=$($Drive)" `
"TMP=$($Tmp)" `
"TEMP=$($Tmp)" `
"UV_INTERNAL__TEST_DIR=$($Tmp)" `
"RUSTUP_HOME=$($Drive)/.rustup" `
"CARGO_HOME=$($Drive)/.cargo" `
"RUFF_WORKSPACE=$($Drive)/ruff" `
"PATH=$($Drive)/.cargo/bin;$env:PATH" `
>> $env:GITHUB_ENV

View File

@@ -1,61 +1,5 @@
# Changelog
## 0.12.2
### Preview features
- \[`flake8-pyi`\] Expand `Optional[A]` to `A | None` (`PYI016`) ([#18572](https://github.com/astral-sh/ruff/pull/18572))
- \[`pyupgrade`\] Mark `UP008` fix safe if no comments are in range ([#18683](https://github.com/astral-sh/ruff/pull/18683))
### Bug fixes
- \[`flake8-comprehensions`\] Fix `C420` to prepend whitespace when needed ([#18616](https://github.com/astral-sh/ruff/pull/18616))
- \[`perflint`\] Fix `PERF403` panic on attribute or subscription loop variable ([#19042](https://github.com/astral-sh/ruff/pull/19042))
- \[`pydocstyle`\] Fix `D413` infinite loop for parenthesized docstring ([#18930](https://github.com/astral-sh/ruff/pull/18930))
- \[`pylint`\] Fix `PLW0108` autofix introducing a syntax error when the lambda's body contains an assignment expression ([#18678](https://github.com/astral-sh/ruff/pull/18678))
- \[`refurb`\] Fix false positive on empty tuples (`FURB168`) ([#19058](https://github.com/astral-sh/ruff/pull/19058))
- \[`ruff`\] Allow more `field` calls from `attrs` (`RUF009`) ([#19021](https://github.com/astral-sh/ruff/pull/19021))
- \[`ruff`\] Fix syntax error introduced for an empty string followed by a u-prefixed string (`UP025`) ([#18899](https://github.com/astral-sh/ruff/pull/18899))
### Rule changes
- \[`flake8-executable`\] Allow `uvx` in shebang line (`EXE003`) ([#18967](https://github.com/astral-sh/ruff/pull/18967))
- \[`pandas`\] Avoid flagging `PD002` if `pandas` is not imported ([#18963](https://github.com/astral-sh/ruff/pull/18963))
- \[`pyupgrade`\] Avoid PEP-604 unions with `typing.NamedTuple` (`UP007`, `UP045`) ([#18682](https://github.com/astral-sh/ruff/pull/18682))
### Documentation
- Document link between `import-outside-top-level (PLC0415)` and `lint.flake8-tidy-imports.banned-module-level-imports` ([#18733](https://github.com/astral-sh/ruff/pull/18733))
- Fix description of the `format.skip-magic-trailing-comma` example ([#19095](https://github.com/astral-sh/ruff/pull/19095))
- \[`airflow`\] Make `AIR302` example error out-of-the-box ([#18988](https://github.com/astral-sh/ruff/pull/18988))
- \[`airflow`\] Make `AIR312` example error out-of-the-box ([#18989](https://github.com/astral-sh/ruff/pull/18989))
- \[`flake8-annotations`\] Make `ANN401` example error out-of-the-box ([#18974](https://github.com/astral-sh/ruff/pull/18974))
- \[`flake8-async`\] Make `ASYNC100` example error out-of-the-box ([#18993](https://github.com/astral-sh/ruff/pull/18993))
- \[`flake8-async`\] Make `ASYNC105` example error out-of-the-box ([#19002](https://github.com/astral-sh/ruff/pull/19002))
- \[`flake8-async`\] Make `ASYNC110` example error out-of-the-box ([#18975](https://github.com/astral-sh/ruff/pull/18975))
- \[`flake8-async`\] Make `ASYNC210` example error out-of-the-box ([#18977](https://github.com/astral-sh/ruff/pull/18977))
- \[`flake8-async`\] Make `ASYNC220`, `ASYNC221`, and `ASYNC222` examples error out-of-the-box ([#18978](https://github.com/astral-sh/ruff/pull/18978))
- \[`flake8-async`\] Make `ASYNC251` example error out-of-the-box ([#18990](https://github.com/astral-sh/ruff/pull/18990))
- \[`flake8-bandit`\] Make `S201` example error out-of-the-box ([#19017](https://github.com/astral-sh/ruff/pull/19017))
- \[`flake8-bandit`\] Make `S604` and `S609` examples error out-of-the-box ([#19049](https://github.com/astral-sh/ruff/pull/19049))
- \[`flake8-bugbear`\] Make `B028` example error out-of-the-box ([#19054](https://github.com/astral-sh/ruff/pull/19054))
- \[`flake8-bugbear`\] Make `B911` example error out-of-the-box ([#19051](https://github.com/astral-sh/ruff/pull/19051))
- \[`flake8-datetimez`\] Make `DTZ011` example error out-of-the-box ([#19055](https://github.com/astral-sh/ruff/pull/19055))
- \[`flake8-datetimez`\] Make `DTZ901` example error out-of-the-box ([#19056](https://github.com/astral-sh/ruff/pull/19056))
- \[`flake8-pyi`\] Make `PYI032` example error out-of-the-box ([#19061](https://github.com/astral-sh/ruff/pull/19061))
- \[`flake8-pyi`\] Make example error out-of-the-box (`PYI014`, `PYI015`) ([#19097](https://github.com/astral-sh/ruff/pull/19097))
- \[`flake8-pyi`\] Make example error out-of-the-box (`PYI042`) ([#19101](https://github.com/astral-sh/ruff/pull/19101))
- \[`flake8-pyi`\] Make example error out-of-the-box (`PYI059`) ([#19080](https://github.com/astral-sh/ruff/pull/19080))
- \[`flake8-pyi`\] Make example error out-of-the-box (`PYI062`) ([#19079](https://github.com/astral-sh/ruff/pull/19079))
- \[`flake8-pytest-style`\] Make example error out-of-the-box (`PT023`) ([#19104](https://github.com/astral-sh/ruff/pull/19104))
- \[`flake8-pytest-style`\] Make example error out-of-the-box (`PT030`) ([#19105](https://github.com/astral-sh/ruff/pull/19105))
- \[`flake8-quotes`\] Make example error out-of-the-box (`Q003`) ([#19106](https://github.com/astral-sh/ruff/pull/19106))
- \[`flake8-simplify`\] Make example error out-of-the-box (`SIM110`) ([#19113](https://github.com/astral-sh/ruff/pull/19113))
- \[`flake8-simplify`\] Make example error out-of-the-box (`SIM113`) ([#19109](https://github.com/astral-sh/ruff/pull/19109))
- \[`flake8-simplify`\] Make example error out-of-the-box (`SIM401`) ([#19110](https://github.com/astral-sh/ruff/pull/19110))
- \[`pyflakes`\] Fix backslash in docs (`F621`) ([#19098](https://github.com/astral-sh/ruff/pull/19098))
- \[`pylint`\] Fix `PLC0415` example ([#18970](https://github.com/astral-sh/ruff/pull/18970))
## 0.12.1
### Preview features

6
Cargo.lock generated
View File

@@ -2724,7 +2724,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.12.2"
version = "0.12.1"
dependencies = [
"anyhow",
"argfile",
@@ -2970,7 +2970,7 @@ dependencies = [
[[package]]
name = "ruff_linter"
version = "0.12.2"
version = "0.12.1"
dependencies = [
"aho-corasick",
"anyhow",
@@ -3302,7 +3302,7 @@ dependencies = [
[[package]]
name = "ruff_wasm"
version = "0.12.2"
version = "0.12.1"
dependencies = [
"console_error_panic_hook",
"console_log",

View File

@@ -148,8 +148,8 @@ curl -LsSf https://astral.sh/ruff/install.sh | sh
powershell -c "irm https://astral.sh/ruff/install.ps1 | iex"
# For a specific version.
curl -LsSf https://astral.sh/ruff/0.12.2/install.sh | sh
powershell -c "irm https://astral.sh/ruff/0.12.2/install.ps1 | iex"
curl -LsSf https://astral.sh/ruff/0.12.1/install.sh | sh
powershell -c "irm https://astral.sh/ruff/0.12.1/install.ps1 | iex"
```
You can also install Ruff via [Homebrew](https://formulae.brew.sh/formula/ruff), [Conda](https://anaconda.org/conda-forge/ruff),
@@ -182,7 +182,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff
```yaml
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.12.2
rev: v0.12.1
hooks:
# Run the linter.
- id: ruff-check

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff"
version = "0.12.2"
version = "0.12.1"
publish = true
authors = { workspace = true }
edition = { workspace = true }

View File

@@ -242,20 +242,19 @@ fn large(bencher: Bencher, benchmark: &Benchmark) {
run_single_threaded(bencher, benchmark);
}
// Currently disabled because the benchmark is too noisy (± 10%) to give useful feedback.
// #[bench(args=[&*PYDANTIC], sample_size=3, sample_count=3)]
// fn multithreaded(bencher: Bencher, benchmark: &Benchmark) {
// let thread_pool = ThreadPoolBuilder::new().build().unwrap();
#[bench(args=[&*PYDANTIC], sample_size=3, sample_count=3)]
fn multithreaded(bencher: Bencher, benchmark: &Benchmark) {
let thread_pool = ThreadPoolBuilder::new().build().unwrap();
// bencher
// .with_inputs(|| benchmark.setup_iteration())
// .bench_local_values(|db| {
// thread_pool.install(|| {
// check_project(&db, benchmark.max_diagnostics);
// db
// })
// });
// }
bencher
.with_inputs(|| benchmark.setup_iteration())
.bench_local_values(|db| {
thread_pool.install(|| {
check_project(&db, benchmark.max_diagnostics);
db
})
});
}
fn main() {
ThreadPoolBuilder::new()

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_linter"
version = "0.12.2"
version = "0.12.1"
publish = false
authors = { workspace = true }
edition = { workspace = true }

View File

@@ -125,20 +125,3 @@ class J:
class K:
f: F = F()
g: G = G()
# Regression test for https://github.com/astral-sh/ruff/issues/19014
# These are all valid field calls and should not cause diagnostics.
@attr.define
class TestAttrField:
attr_field_factory: list[int] = attr.field(factory=list)
attr_field_default: list[int] = attr.field(default=attr.Factory(list))
attr_factory: list[int] = attr.Factory(list)
attr_ib: list[int] = attr.ib(factory=list)
attr_attr: list[int] = attr.attr(factory=list)
attr_attrib: list[int] = attr.attrib(factory=list)
@attr.attributes
class TestAttrAttributes:
x: list[int] = list() # RUF009

View File

@@ -31,7 +31,7 @@ use crate::rules::flake8_pytest_style::helpers::{Parentheses, get_mark_decorator
/// import pytest
///
///
/// @pytest.mark.foo()
/// @pytest.mark.foo
/// def test_something(): ...
/// ```
///
@@ -41,7 +41,7 @@ use crate::rules::flake8_pytest_style::helpers::{Parentheses, get_mark_decorator
/// import pytest
///
///
/// @pytest.mark.foo
/// @pytest.mark.foo()
/// def test_something(): ...
/// ```
///

View File

@@ -76,11 +76,11 @@ impl Violation for PytestWarnsWithMultipleStatements {
///
///
/// def test_foo():
/// with pytest.warns(Warning):
/// with pytest.warns(RuntimeWarning):
/// ...
///
/// # empty string is also an error
/// with pytest.warns(Warning, match=""):
/// with pytest.warns(RuntimeWarning, match=""):
/// ...
/// ```
///
@@ -90,7 +90,7 @@ impl Violation for PytestWarnsWithMultipleStatements {
///
///
/// def test_foo():
/// with pytest.warns(Warning, match="expected message"):
/// with pytest.warns(RuntimeWarning, match="expected message"):
/// ...
/// ```
///

View File

@@ -19,12 +19,12 @@ use crate::{AlwaysFixableViolation, Edit, Fix};
///
/// ## Example
/// ```python
/// foo = "bar\"s"
/// foo = 'bar\'s'
/// ```
///
/// Use instead:
/// ```python
/// foo = 'bar"s'
/// foo = "bar's"
/// ```
///
/// ## Formatter compatibility

View File

@@ -20,7 +20,6 @@ use crate::checkers::ast::Checker;
/// ## Example
/// ```python
/// fruits = ["apple", "banana", "cherry"]
/// i = 0
/// for fruit in fruits:
/// print(f"{i + 1}. {fruit}")
/// i += 1

View File

@@ -27,7 +27,6 @@ use crate::{Edit, Fix, FixAvailability, Violation};
///
/// ## Example
/// ```python
/// foo = {}
/// if "bar" in foo:
/// value = foo["bar"]
/// else:
@@ -36,7 +35,6 @@ use crate::{Edit, Fix, FixAvailability, Violation};
///
/// Use instead:
/// ```python
/// foo = {}
/// value = foo.get("bar", 0)
/// ```
///

View File

@@ -23,17 +23,15 @@ use crate::{Edit, Fix, FixAvailability, Violation};
///
/// ## Example
/// ```python
/// def foo():
/// for item in iterable:
/// if predicate(item):
/// return True
/// return False
/// for item in iterable:
/// if predicate(item):
/// return True
/// return False
/// ```
///
/// Use instead:
/// ```python
/// def foo():
/// return any(predicate(item) for item in iterable)
/// return any(predicate(item) for item in iterable)
/// ```
///
/// ## Fix safety

View File

@@ -44,13 +44,6 @@ use crate::{
/// print(platform.python_version())
/// ```
///
/// ## See also
/// This rule will ignore import statements configured in
/// [`lint.flake8-tidy-imports.banned-module-level-imports`][banned-module-level-imports]
/// if the rule [`banned-module-level-imports`][TID253] is enabled.
///
/// [banned-module-level-imports]: https://docs.astral.sh/ruff/settings/#lint_flake8-tidy-imports_banned-module-level-imports
/// [TID253]: https://docs.astral.sh/ruff/rules/banned-module-level-imports/
/// [PEP 8]: https://peps.python.org/pep-0008/#imports
#[derive(ViolationMetadata)]
pub(crate) struct ImportOutsideTopLevel;

View File

@@ -25,16 +25,14 @@ fn is_stdlib_dataclass_field(func: &Expr, semantic: &SemanticModel) -> bool {
.is_some_and(|qualified_name| matches!(qualified_name.segments(), ["dataclasses", "field"]))
}
/// Returns `true` if the given [`Expr`] is a call to an `attrs` field function.
/// Returns `true` if the given [`Expr`] is a call to `attr.ib()` or `attrs.field()`.
fn is_attrs_field(func: &Expr, semantic: &SemanticModel) -> bool {
semantic
.resolve_qualified_name(func)
.is_some_and(|qualified_name| {
matches!(
qualified_name.segments(),
["attrs", "field" | "Factory"]
// See https://github.com/python-attrs/attrs/blob/main/src/attr/__init__.py#L33
| ["attr", "ib" | "attr" | "attrib" | "field" | "Factory"]
["attrs", "field" | "Factory"] | ["attr", "ib"]
)
})
}
@@ -122,8 +120,7 @@ pub(super) fn dataclass_kind<'a>(
match qualified_name.segments() {
["attrs" | "attr", func @ ("define" | "frozen" | "mutable")]
// See https://github.com/python-attrs/attrs/blob/main/src/attr/__init__.py#L32
| ["attr", func @ ("s" | "attributes" | "attrs")] => {
| ["attr", func @ ("s" | "attrs")] => {
// `.define`, `.frozen` and `.mutable` all default `auto_attribs` to `None`,
// whereas `@attr.s` implicitly sets `auto_attribs=False`.
// https://www.attrs.org/en/stable/api.html#attrs.define

View File

@@ -98,11 +98,3 @@ RUF009_attrs.py:127:12: RUF009 Do not perform function call `G` in dataclass def
127 | g: G = G()
| ^^^ RUF009
|
RUF009_attrs.py:144:20: RUF009 Do not perform function call `list` in dataclass defaults
|
142 | @attr.attributes
143 | class TestAttrAttributes:
144 | x: list[int] = list() # RUF009
| ^^^^^^ RUF009
|

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_wasm"
version = "0.12.2"
version = "0.12.1"
publish = false
authors = { workspace = true }
edition = { workspace = true }

View File

@@ -1996,8 +1996,7 @@ pub struct Flake8TidyImportsOptions {
/// List of specific modules that may not be imported at module level, and should instead be
/// imported lazily (e.g., within a function definition, or an `if TYPE_CHECKING:`
/// block, or some other nested context). This also affects the rule `import-outside-top-level`
/// if `banned-module-level-imports` is enabled.
/// block, or some other nested context).
#[option(
default = r#"[]"#,
value_type = r#"list[str]"#,
@@ -3587,7 +3586,7 @@ pub struct FormatOptions {
/// Setting `skip-magic-trailing-comma = true` changes the formatting to:
///
/// ```python
/// # The arguments are collapsed to a single line because the trailing comma is ignored
/// # The arguments remain on separate lines because of the trailing comma after `b`
/// def test(a, b):
/// pass
/// ```

View File

@@ -919,6 +919,9 @@ fn directory_renamed() -> anyhow::Result<()> {
#[test]
fn directory_deleted() -> anyhow::Result<()> {
use ruff_db::testing::setup_logging;
let _logging = setup_logging();
let mut case = setup([
("bar.py", "import sub.a"),
("sub/__init__.py", ""),

View File

@@ -5,7 +5,7 @@ use ruff_db::parsed::{ParsedModuleRef, parsed_module};
use ruff_python_ast as ast;
use ruff_python_parser::{Token, TokenAt, TokenKind};
use ruff_text_size::{Ranged, TextRange, TextSize};
use ty_python_semantic::{Completion, NameKind, SemanticModel};
use ty_python_semantic::{Completion, SemanticModel};
use crate::Db;
use crate::find_node::covering_node;
@@ -325,7 +325,38 @@ fn import_from_tokens(tokens: &[Token]) -> Option<&Token> {
/// This has the effect of putting all dunder attributes after "normal"
/// attributes, and all single-underscore attributes after dunder attributes.
fn compare_suggestions(c1: &Completion, c2: &Completion) -> Ordering {
let (kind1, kind2) = (NameKind::classify(&c1.name), NameKind::classify(&c2.name));
/// A helper type for sorting completions based only on name.
///
/// This sorts "normal" names first, then dunder names and finally
/// single-underscore names. This matches the order of the variants defined for
/// this enum, which is in turn picked up by the derived trait implementation
/// for `Ord`.
#[derive(Clone, Copy, Eq, PartialEq, PartialOrd, Ord)]
enum Kind {
Normal,
Dunder,
Sunder,
}
impl Kind {
fn classify(c: &Completion) -> Kind {
// Dunder needs a prefix and suffix double underscore.
// When there's only a prefix double underscore, this
// results in explicit name mangling. We let that be
// classified as-if they were single underscore names.
//
// Ref: <https://docs.python.org/3/reference/lexical_analysis.html#reserved-classes-of-identifiers>
if c.name.starts_with("__") && c.name.ends_with("__") {
Kind::Dunder
} else if c.name.starts_with('_') {
Kind::Sunder
} else {
Kind::Normal
}
}
}
let (kind1, kind2) = (Kind::classify(c1), Kind::classify(c2));
kind1.cmp(&kind2).then_with(|| c1.name.cmp(&c2.name))
}
@@ -441,11 +472,6 @@ mod tests {
",
);
test.assert_completions_include("filter");
// Sunder items should be filtered out
test.assert_completions_do_not_include("_T");
// Dunder attributes should not be stripped
test.assert_completions_include("__annotations__");
// See `private_symbols_in_stub` for more comprehensive testing private of symbol filtering.
}
#[test]
@@ -510,112 +536,6 @@ re.<CURSOR>
test.assert_completions_include("findall");
}
#[test]
fn private_symbols_in_stub() {
let test = CursorTest::builder()
.source(
"package/__init__.pyi",
r#"\
from typing import TypeAlias, Literal, TypeVar, ParamSpec, TypeVarTuple, Protocol
public_name = 1
_private_name = 1
__mangled_name = 1
__dunder_name__ = 1
public_type_var = TypeVar("public_type_var")
_private_type_var = TypeVar("_private_type_var")
__mangled_type_var = TypeVar("__mangled_type_var")
public_param_spec = ParamSpec("public_param_spec")
_private_param_spec = ParamSpec("_private_param_spec")
public_type_var_tuple = TypeVarTuple("public_type_var_tuple")
_private_type_var_tuple = TypeVarTuple("_private_type_var_tuple")
public_explicit_type_alias: TypeAlias = Literal[1]
_private_explicit_type_alias: TypeAlias = Literal[1]
class PublicProtocol(Protocol):
def method(self) -> None: ...
class _PrivateProtocol(Protocol):
def method(self) -> None: ...
"#,
)
.source("main.py", "import package; package.<CURSOR>")
.build();
test.assert_completions_include("public_name");
test.assert_completions_include("_private_name");
test.assert_completions_include("__mangled_name");
test.assert_completions_include("__dunder_name__");
test.assert_completions_include("public_type_var");
test.assert_completions_do_not_include("_private_type_var");
test.assert_completions_do_not_include("__mangled_type_var");
test.assert_completions_include("public_param_spec");
test.assert_completions_do_not_include("_private_param_spec");
test.assert_completions_include("public_type_var_tuple");
test.assert_completions_do_not_include("_private_type_var_tuple");
test.assert_completions_include("public_explicit_type_alias");
test.assert_completions_include("_private_explicit_type_alias");
test.assert_completions_include("PublicProtocol");
test.assert_completions_do_not_include("_PrivateProtocol");
}
/// Unlike [`private_symbols_in_stub`], this test doesn't use a `.pyi` file so all of the names
/// are visible.
#[test]
fn private_symbols_in_module() {
let test = CursorTest::builder()
.source(
"package/__init__.py",
r#"\
from typing import TypeAlias, Literal, TypeVar, ParamSpec, TypeVarTuple, Protocol
public_name = 1
_private_name = 1
__mangled_name = 1
__dunder_name__ = 1
public_type_var = TypeVar("public_type_var")
_private_type_var = TypeVar("_private_type_var")
__mangled_type_var = TypeVar("__mangled_type_var")
public_param_spec = ParamSpec("public_param_spec")
_private_param_spec = ParamSpec("_private_param_spec")
public_type_var_tuple = TypeVarTuple("public_type_var_tuple")
_private_type_var_tuple = TypeVarTuple("_private_type_var_tuple")
public_explicit_type_alias: TypeAlias = Literal[1]
_private_explicit_type_alias: TypeAlias = Literal[1]
class PublicProtocol(Protocol):
def method(self) -> None: ...
class _PrivateProtocol(Protocol):
def method(self) -> None: ...
"#,
)
.source("main.py", "import package; package.<CURSOR>")
.build();
test.assert_completions_include("public_name");
test.assert_completions_include("_private_name");
test.assert_completions_include("__mangled_name");
test.assert_completions_include("__dunder_name__");
test.assert_completions_include("public_type_var");
test.assert_completions_include("_private_type_var");
test.assert_completions_include("__mangled_type_var");
test.assert_completions_include("public_param_spec");
test.assert_completions_include("_private_param_spec");
test.assert_completions_include("public_type_var_tuple");
test.assert_completions_include("_private_type_var_tuple");
test.assert_completions_include("public_explicit_type_alias");
test.assert_completions_include("_private_explicit_type_alias");
test.assert_completions_include("PublicProtocol");
test.assert_completions_include("_PrivateProtocol");
}
#[test]
fn one_function_prefix() {
let test = cursor_test(

View File

@@ -15,7 +15,7 @@ pub use program::{
PythonVersionWithSource, SearchPathSettings,
};
pub use python_platform::PythonPlatform;
pub use semantic_model::{Completion, HasType, NameKind, SemanticModel};
pub use semantic_model::{Completion, HasType, SemanticModel};
pub use site_packages::{PythonEnvironment, SitePackagesPaths, SysPrefixPathOrigin};
pub use util::diagnostics::add_inferred_python_version_hint_to_diagnostic;

View File

@@ -68,10 +68,12 @@ impl<'db> SemanticModel<'db> {
return vec![];
};
let ty = Type::module_literal(self.db, self.file, &module);
let builtin = module.is_known(KnownModule::Builtins);
crate::types::all_members(self.db, ty)
.into_iter()
.map(|name| Completion { name, builtin })
.map(|name| Completion {
name,
builtin: module.is_known(KnownModule::Builtins),
})
.collect()
}
@@ -128,39 +130,6 @@ impl<'db> SemanticModel<'db> {
}
}
/// A classification of symbol names.
///
/// The ordering here is used for sorting completions.
///
/// This sorts "normal" names first, then dunder names and finally
/// single-underscore names. This matches the order of the variants defined for
/// this enum, which is in turn picked up by the derived trait implementation
/// for `Ord`.
#[derive(Clone, Copy, Eq, PartialEq, PartialOrd, Ord)]
pub enum NameKind {
Normal,
Dunder,
Sunder,
}
impl NameKind {
pub fn classify(name: &Name) -> NameKind {
// Dunder needs a prefix and suffix double underscore.
// When there's only a prefix double underscore, this
// results in explicit name mangling. We let that be
// classified as-if they were single underscore names.
//
// Ref: <https://docs.python.org/3/reference/lexical_analysis.html#reserved-classes-of-identifiers>
if name.starts_with("__") && name.ends_with("__") {
NameKind::Dunder
} else if name.starts_with('_') {
NameKind::Sunder
} else {
NameKind::Normal
}
}
}
/// A suggestion for code completion.
#[derive(Clone, Debug)]
pub struct Completion {

View File

@@ -1,10 +1,10 @@
use crate::place::{Place, imported_symbol, place_from_bindings, place_from_declarations};
use crate::Db;
use crate::place::{imported_symbol, place_from_bindings, place_from_declarations};
use crate::semantic_index::place::ScopeId;
use crate::semantic_index::{
attribute_scopes, global_scope, imported_modules, place_table, semantic_index, use_def_map,
};
use crate::types::{ClassBase, ClassLiteral, KnownClass, KnownInstanceType, Type};
use crate::{Db, NameKind};
use crate::types::{ClassBase, ClassLiteral, KnownClass, Type};
use ruff_python_ast::name::Name;
use rustc_hash::FxHashSet;
@@ -144,41 +144,13 @@ impl AllMembers {
let Some(symbol_name) = place_table.place_expr(symbol_id).as_name() else {
continue;
};
let Place::Type(ty, _) = imported_symbol(db, file, symbol_name, None).place
else {
continue;
};
// Filter private symbols from stubs if they appear to be internal types
let is_stub_file = file.path(db).extension() == Some("pyi");
let is_private_symbol = match NameKind::classify(symbol_name) {
NameKind::Dunder | NameKind::Normal => false,
NameKind::Sunder => true,
};
if is_private_symbol && is_stub_file {
match ty {
Type::NominalInstance(instance)
if matches!(
instance.class.known(db),
Some(
KnownClass::TypeVar
| KnownClass::TypeVarTuple
| KnownClass::ParamSpec
)
) =>
{
continue;
}
Type::ClassLiteral(class) if class.is_protocol(db) => continue,
Type::KnownInstance(
KnownInstanceType::TypeVar(_) | KnownInstanceType::TypeAliasType(_),
) => continue,
_ => {}
}
if !imported_symbol(db, file, symbol_name, None)
.place
.is_unbound()
{
self.members
.insert(place_table.place_expr(symbol_id).expect_name().clone());
}
self.members
.insert(place_table.place_expr(symbol_id).expect_name().clone());
}
let module_name = module.name();

View File

@@ -80,7 +80,7 @@ You can add the following configuration to `.gitlab-ci.yml` to run a `ruff forma
stage: build
interruptible: true
image:
name: ghcr.io/astral-sh/ruff:0.12.2-alpine
name: ghcr.io/astral-sh/ruff:0.12.1-alpine
before_script:
- cd $CI_PROJECT_DIR
- ruff --version
@@ -106,7 +106,7 @@ Ruff can be used as a [pre-commit](https://pre-commit.com) hook via [`ruff-pre-c
```yaml
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.12.2
rev: v0.12.1
hooks:
# Run the linter.
- id: ruff
@@ -119,7 +119,7 @@ To enable lint fixes, add the `--fix` argument to the lint hook:
```yaml
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.12.2
rev: v0.12.1
hooks:
# Run the linter.
- id: ruff
@@ -133,7 +133,7 @@ To avoid running on Jupyter Notebooks, remove `jupyter` from the list of allowed
```yaml
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.12.2
rev: v0.12.1
hooks:
# Run the linter.
- id: ruff

View File

@@ -369,7 +369,7 @@ This tutorial has focused on Ruff's command-line interface, but Ruff can also be
```yaml
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.12.2
rev: v0.12.1
hooks:
# Run the linter.
- id: ruff

View File

@@ -4,7 +4,7 @@ build-backend = "maturin"
[project]
name = "ruff"
version = "0.12.2"
version = "0.12.1"
description = "An extremely fast Python linter and code formatter, written in Rust."
authors = [{ name = "Astral Software Inc.", email = "hey@astral.sh" }]
readme = "README.md"

4
ruff.schema.json generated
View File

@@ -1438,7 +1438,7 @@
}
},
"banned-module-level-imports": {
"description": "List of specific modules that may not be imported at module level, and should instead be imported lazily (e.g., within a function definition, or an `if TYPE_CHECKING:` block, or some other nested context). This also affects the rule `import-outside-top-level` if `banned-module-level-imports` is enabled.",
"description": "List of specific modules that may not be imported at module level, and should instead be imported lazily (e.g., within a function definition, or an `if TYPE_CHECKING:` block, or some other nested context).",
"type": [
"array",
"null"
@@ -1588,7 +1588,7 @@
]
},
"skip-magic-trailing-comma": {
"description": "Ruff uses existing trailing commas as an indication that short lines should be left separate. If this option is set to `true`, the magic trailing comma is ignored.\n\nFor example, Ruff leaves the arguments separate even though collapsing the arguments to a single line doesn't exceed the line length if `skip-magic-trailing-comma = false`:\n\n```python # The arguments remain on separate lines because of the trailing comma after `b` def test( a, b, ): pass ```\n\nSetting `skip-magic-trailing-comma = true` changes the formatting to:\n\n```python # The arguments are collapsed to a single line because the trailing comma is ignored def test(a, b): pass ```",
"description": "Ruff uses existing trailing commas as an indication that short lines should be left separate. If this option is set to `true`, the magic trailing comma is ignored.\n\nFor example, Ruff leaves the arguments separate even though collapsing the arguments to a single line doesn't exceed the line length if `skip-magic-trailing-comma = false`:\n\n```python # The arguments remain on separate lines because of the trailing comma after `b` def test( a, b, ): pass ```\n\nSetting `skip-magic-trailing-comma = true` changes the formatting to:\n\n```python # The arguments remain on separate lines because of the trailing comma after `b` def test(a, b): pass ```",
"type": [
"boolean",
"null"

View File

@@ -1,6 +1,6 @@
[project]
name = "scripts"
version = "0.12.2"
version = "0.12.1"
description = ""
authors = ["Charles Marsh <charlie.r.marsh@gmail.com>"]