Skip to content

Commit aa2d77c

Browse files
committed
Merge branch 'main' into dcreager/union-callables
* main: [red-knot] Use `try_call_dunder` for augmented assignment (#16717) [red-knot] Document current state of attribute assignment diagnostics (#16746) [red-knot] Case sensitive module resolver (#16521) [red-knot] Very minor simplification of the render tests (#16759) [syntax-errors] Unparenthesized assignment expressions in sets and indexes (#16404) ruff_db: add a new diagnostic renderer ruff_db: add `context` configuration red_knot: plumb through `DiagnosticFormat` to the CLI ruff_db: add concise diagnostic mode [syntax-errors] Star annotations before Python 3.11 (#16545) [syntax-errors] Star expression in index before Python 3.11 (#16544) Ruff 0.11.0 (#16723) [red-knot] Preliminary tests for typing.Final (#15917) [red-knot] fix: improve type inference for binary ops on tuples (#16725) [red-knot] Assignments to attributes (#16705) [`pygrep-hooks`]: Detect file-level suppressions comments without rul… (#16720) Fallback to requires-python in certain cases when target-version is not found (#16721)
2 parents 00891ae + ebcad6e commit aa2d77c

File tree

95 files changed

+9682
-386
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

95 files changed

+9682
-386
lines changed

BREAKING_CHANGES.md

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# Breaking Changes
22

3-
## 0.10.0
3+
## 0.11.0
4+
5+
This is a follow-up to release 0.10.0. Because of a mistake in the release process, the `requires-python` inference changes were not included in that release. Ruff 0.11.0 now includes this change as well as the stabilization of the preview behavior for `PGH004`.
46

57
- **Changes to how the Python version is inferred when a `target-version` is not specified** ([#16319](https://github.com/astral-sh/ruff/pull/16319))
68

@@ -23,6 +25,13 @@
2325
search for the closest `pyproject.toml` in the parent directories and use its
2426
`requires-python` setting.
2527

28+
## 0.10.0
29+
30+
- **Changes to how the Python version is inferred when a `target-version` is not specified** ([#16319](https://github.com/astral-sh/ruff/pull/16319))
31+
32+
Because of a mistake in the release process, the `requires-python` inference changes are not included in this release and instead shipped as part of 0.11.0.
33+
You can find a description of this change in the 0.11.0 section.
34+
2635
- **Updated `TYPE_CHECKING` behavior** ([#16669](https://github.com/astral-sh/ruff/pull/16669))
2736

2837
Previously, Ruff only recognized typechecking blocks that tested the `typing.TYPE_CHECKING` symbol. Now, Ruff recognizes any local variable named `TYPE_CHECKING`. This release also removes support for the legacy `if 0:` and `if False:` typechecking checks. Use a local `TYPE_CHECKING` variable instead.

CHANGELOG.md

+25-5
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
# Changelog
22

3-
## 0.10.0
3+
## 0.11.0
44

5-
Check out the [blog post](https://astral.sh/blog/ruff-v0.10.0) for a migration guide and overview of the changes!
5+
This is a follow-up to release 0.10.0. Because of a mistake in the release process, the `requires-python` inference changes were not included in that release. Ruff 0.11.0 now includes this change as well as the stabilization of the preview behavior for `PGH004`.
66

77
### Breaking changes
88

9-
See also, the "Remapped rules" section which may result in disabled rules.
10-
119
- **Changes to how the Python version is inferred when a `target-version` is not specified** ([#16319](https://github.com/astral-sh/ruff/pull/16319))
1210

1311
In previous versions of Ruff, you could specify your Python version with:
@@ -29,6 +27,29 @@ See also, the "Remapped rules" section which may result in disabled rules.
2927
search for the closest `pyproject.toml` in the parent directories and use its
3028
`requires-python` setting.
3129

30+
### Stabilization
31+
32+
The following behaviors have been stabilized:
33+
34+
- [`blanket-noqa`](https://docs.astral.sh/ruff/rules/blanket-noqa/) (`PGH004`): Also detect blanked file-level noqa comments (and not just line level comments).
35+
36+
### Preview features
37+
38+
- [syntax-errors] Tuple unpacking in `for` statement iterator clause before Python 3.9 ([#16558](https://github.com/astral-sh/ruff/pull/16558))
39+
40+
## 0.10.0
41+
42+
Check out the [blog post](https://astral.sh/blog/ruff-v0.10.0) for a migration guide and overview of the changes!
43+
44+
### Breaking changes
45+
46+
See also, the "Remapped rules" section which may result in disabled rules.
47+
48+
- **Changes to how the Python version is inferred when a `target-version` is not specified** ([#16319](https://github.com/astral-sh/ruff/pull/16319))
49+
50+
Because of a mistake in the release process, the `requires-python` inference changes are not included in this release and instead shipped as part of 0.11.0.
51+
You can find a description of this change in the 0.11.0 section.
52+
3253
- **Updated `TYPE_CHECKING` behavior** ([#16669](https://github.com/astral-sh/ruff/pull/16669))
3354

3455
Previously, Ruff only recognized typechecking blocks that tested the `typing.TYPE_CHECKING` symbol. Now, Ruff recognizes any local variable named `TYPE_CHECKING`. This release also removes support for the legacy `if 0:` and `if False:` typechecking checks. Use a local `TYPE_CHECKING` variable instead.
@@ -86,7 +107,6 @@ The following behaviors have been stabilized:
86107

87108
- [`bad-staticmethod-argument`](https://docs.astral.sh/ruff/rules/bad-staticmethod-argument/) (`PLW0211`) [`invalid-first-argument-name-for-class-method`](https://docs.astral.sh/ruff/rules/invalid-first-argument-name-for-class-method/) (`N804`): `__new__` methods are now no longer flagged by `invalid-first-argument-name-for-class-method` (`N804`) but instead by `bad-staticmethod-argument` (`PLW0211`)
88109
- [`bad-str-strip-call`](https://docs.astral.sh/ruff/rules/bad-str-strip-call/) (`PLE1310`): The rule now applies to objects which are known to have type `str` or `bytes`.
89-
- [`blanket-noqa`](https://docs.astral.sh/ruff/rules/blanket-noqa/) (`PGH004`): Also detect blanked file-level noqa comments (and not just line level comments).
90110
- [`custom-type-var-for-self`](https://docs.astral.sh/ruff/rules/custom-type-var-for-self/) (`PYI019`): More accurate detection of custom `TypeVars` replaceable by `Self`. The range of the diagnostic is now the full function header rather than just the return annotation.
91111
- [`invalid-argument-name`](https://docs.astral.sh/ruff/rules/invalid-argument-name/) (`N803`): Ignore argument names of functions decorated with `typing.override`
92112
- [`invalid-envvar-default`](https://docs.astral.sh/ruff/rules/invalid-envvar-default/) (`PLW1508`): Detect default value arguments to `os.environ.get` with invalid type.

Cargo.lock

+4-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ toml = { version = "0.8.11" }
154154
tracing = { version = "0.1.40" }
155155
tracing-flame = { version = "0.2.0" }
156156
tracing-indicatif = { version = "0.3.6" }
157+
tracing-log = { version = "0.2.0" }
157158
tracing-subscriber = { version = "0.3.18", default-features = false, features = [
158159
"env-filter",
159160
"fmt",

README.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -149,8 +149,8 @@ curl -LsSf https://astral.sh/ruff/install.sh | sh
149149
powershell -c "irm https://astral.sh/ruff/install.ps1 | iex"
150150

151151
# For a specific version.
152-
curl -LsSf https://astral.sh/ruff/0.10.0/install.sh | sh
153-
powershell -c "irm https://astral.sh/ruff/0.10.0/install.ps1 | iex"
152+
curl -LsSf https://astral.sh/ruff/0.11.0/install.sh | sh
153+
powershell -c "irm https://astral.sh/ruff/0.11.0/install.ps1 | iex"
154154
```
155155

156156
You can also install Ruff via [Homebrew](https://formulae.brew.sh/formula/ruff), [Conda](https://anaconda.org/conda-forge/ruff),
@@ -183,7 +183,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff
183183
```yaml
184184
- repo: https://github.com/astral-sh/ruff-pre-commit
185185
# Ruff version.
186-
rev: v0.10.0
186+
rev: v0.11.0
187187
hooks:
188188
# Run the linter.
189189
- id: ruff

crates/red_knot/src/args.rs

+36
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,10 @@ pub(crate) struct CheckCommand {
7575
#[clap(flatten)]
7676
pub(crate) rules: RulesArg,
7777

78+
/// The format to use for printing diagnostic messages.
79+
#[arg(long)]
80+
pub(crate) output_format: Option<OutputFormat>,
81+
7882
/// Use exit code 1 if there are any warning-level diagnostics.
7983
#[arg(long, conflicts_with = "exit_zero", default_missing_value = "true", num_args=0..1)]
8084
pub(crate) error_on_warning: Option<bool>,
@@ -117,6 +121,9 @@ impl CheckCommand {
117121
..EnvironmentOptions::default()
118122
}),
119123
terminal: Some(TerminalOptions {
124+
output_format: self
125+
.output_format
126+
.map(|output_format| RangedValue::cli(output_format.into())),
120127
error_on_warning: self.error_on_warning,
121128
}),
122129
rules,
@@ -211,3 +218,32 @@ impl clap::Args for RulesArg {
211218
Self::augment_args(cmd)
212219
}
213220
}
221+
222+
/// The diagnostic output format.
223+
#[derive(Copy, Clone, Hash, Debug, PartialEq, Eq, PartialOrd, Ord, Default, clap::ValueEnum)]
224+
pub enum OutputFormat {
225+
/// Print diagnostics verbosely, with context and helpful hints.
226+
///
227+
/// Diagnostic messages may include additional context and
228+
/// annotations on the input to help understand the message.
229+
#[default]
230+
#[value(name = "full")]
231+
Full,
232+
/// Print diagnostics concisely, one per line.
233+
///
234+
/// This will guarantee that each diagnostic is printed on
235+
/// a single line. Only the most important or primary aspects
236+
/// of the diagnostic are included. Contextual information is
237+
/// dropped.
238+
#[value(name = "concise")]
239+
Concise,
240+
}
241+
242+
impl From<OutputFormat> for ruff_db::diagnostic::DiagnosticFormat {
243+
fn from(format: OutputFormat) -> ruff_db::diagnostic::DiagnosticFormat {
244+
match format {
245+
OutputFormat::Full => Self::Full,
246+
OutputFormat::Concise => Self::Concise,
247+
}
248+
}
249+
}

crates/red_knot/src/main.rs

+3
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ fn run_check(args: CheckCommand) -> anyhow::Result<ExitStatus> {
8080
countme::enable(verbosity.is_trace());
8181
let _guard = setup_tracing(verbosity)?;
8282

83+
tracing::debug!("Version: {}", version::version());
84+
8385
// The base path to which all CLI arguments are relative to.
8486
let cwd = {
8587
let cwd = std::env::current_dir().context("Failed to get the current working directory")?;
@@ -256,6 +258,7 @@ impl MainLoop {
256258
revision: check_revision,
257259
} => {
258260
let display_config = DisplayDiagnosticConfig::default()
261+
.format(db.project().settings(db).terminal().output_format)
259262
.color(colored::control::SHOULD_COLORIZE.should_colorize());
260263

261264
let min_error_severity =

crates/red_knot/tests/cli.rs

+24
Original file line numberDiff line numberDiff line change
@@ -967,6 +967,30 @@ fn check_non_existing_path() -> anyhow::Result<()> {
967967
Ok(())
968968
}
969969

970+
#[test]
971+
fn concise_diagnostics() -> anyhow::Result<()> {
972+
let case = TestCase::with_file(
973+
"test.py",
974+
r#"
975+
print(x) # [unresolved-reference]
976+
print(4[1]) # [non-subscriptable]
977+
"#,
978+
)?;
979+
980+
assert_cmd_snapshot!(case.command().arg("--output-format=concise"), @r"
981+
success: false
982+
exit_code: 1
983+
----- stdout -----
984+
warning[lint:unresolved-reference] <temp_dir>/test.py:2:7: Name `x` used when not defined
985+
error[lint:non-subscriptable] <temp_dir>/test.py:3:7: Cannot subscript object of type `Literal[4]` with no `__getitem__` method
986+
Found 2 diagnostics
987+
988+
----- stderr -----
989+
");
990+
991+
Ok(())
992+
}
993+
970994
struct TestCase {
971995
_temp_dir: TempDir,
972996
_settings_scope: SettingsBindDropGuard,

crates/red_knot/tests/file_watching.rs

+80-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
#![allow(clippy::disallowed_names)]
2-
31
use std::collections::HashSet;
42
use std::io::Write;
53
use std::time::{Duration, Instant};
@@ -16,7 +14,7 @@ use ruff_db::source::source_text;
1614
use ruff_db::system::{
1715
OsSystem, System, SystemPath, SystemPathBuf, UserConfigDirectoryOverrideGuard,
1816
};
19-
use ruff_db::Upcast;
17+
use ruff_db::{Db as _, Upcast};
2018
use ruff_python_ast::PythonVersion;
2119

2220
struct TestCase {
@@ -1790,3 +1788,82 @@ fn changes_to_user_configuration() -> anyhow::Result<()> {
17901788

17911789
Ok(())
17921790
}
1791+
1792+
/// Tests that renaming a file from `lib.py` to `Lib.py` is correctly reflected.
1793+
///
1794+
/// This test currently fails on case-insensitive systems because `Files` is case-sensitive
1795+
/// but the `System::metadata` call isn't. This means that
1796+
/// Red Knot considers both `Lib.py` and `lib.py` to exist when only `lib.py` does
1797+
///
1798+
/// The incoming change events then are no-ops because they don't change either file's
1799+
/// status nor does it update their last modified time (renaming a file doesn't bump it's
1800+
/// last modified timestamp).
1801+
///
1802+
/// Fixing this requires to either make `Files` case-insensitive and store the
1803+
/// real-case path (if it differs) on `File` or make `Files` use a
1804+
/// case-sensitive `System::metadata` call. This does open the question if all
1805+
/// `System` calls should be case sensitive. This would be the most consistent
1806+
/// but might be hard to pull off.
1807+
///
1808+
/// What the right solution is also depends on if Red Knot itself should be case
1809+
/// sensitive or not. E.g. should `include="src"` be case sensitive on all systems
1810+
/// or only on case-sensitive systems?
1811+
///
1812+
/// Lastly, whatever solution we pick must also work well with VS Code which,
1813+
/// unfortunately ,doesn't propagate casing-only renames.
1814+
/// <https://github.com/rust-lang/rust-analyzer/issues/9581>
1815+
#[ignore]
1816+
#[test]
1817+
fn rename_files_casing_only() -> anyhow::Result<()> {
1818+
let mut case = setup([("lib.py", "class Foo: ...")])?;
1819+
1820+
assert!(
1821+
resolve_module(case.db(), &ModuleName::new("lib").unwrap()).is_some(),
1822+
"Expected `lib` module to exist."
1823+
);
1824+
assert_eq!(
1825+
resolve_module(case.db(), &ModuleName::new("Lib").unwrap()),
1826+
None,
1827+
"Expected `Lib` module not to exist"
1828+
);
1829+
1830+
// Now rename `lib.py` to `Lib.py`
1831+
if case.db().system().case_sensitivity().is_case_sensitive() {
1832+
std::fs::rename(
1833+
case.project_path("lib.py").as_std_path(),
1834+
case.project_path("Lib.py").as_std_path(),
1835+
)
1836+
.context("Failed to rename `lib.py` to `Lib.py`")?;
1837+
} else {
1838+
// On case-insensitive file systems, renaming a file to a different casing is a no-op.
1839+
// Rename to a different name first
1840+
std::fs::rename(
1841+
case.project_path("lib.py").as_std_path(),
1842+
case.project_path("temp.py").as_std_path(),
1843+
)
1844+
.context("Failed to rename `lib.py` to `temp.py`")?;
1845+
1846+
std::fs::rename(
1847+
case.project_path("temp.py").as_std_path(),
1848+
case.project_path("Lib.py").as_std_path(),
1849+
)
1850+
.context("Failed to rename `temp.py` to `Lib.py`")?;
1851+
}
1852+
1853+
let changes = case.stop_watch(event_for_file("Lib.py"));
1854+
case.apply_changes(changes);
1855+
1856+
// Resolving `lib` should now fail but `Lib` should now succeed
1857+
assert_eq!(
1858+
resolve_module(case.db(), &ModuleName::new("lib").unwrap()),
1859+
None,
1860+
"Expected `lib` module to no longer exist."
1861+
);
1862+
1863+
assert!(
1864+
resolve_module(case.db(), &ModuleName::new("Lib").unwrap()).is_some(),
1865+
"Expected `Lib` module to exist"
1866+
);
1867+
1868+
Ok(())
1869+
}

crates/red_knot_project/src/metadata/options.rs

+11-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use crate::metadata::value::{RangedValue, RelativePathBuf, ValueSource, ValueSou
22
use crate::Db;
33
use red_knot_python_semantic::lint::{GetLintError, Level, LintSource, RuleSelection};
44
use red_knot_python_semantic::{ProgramSettings, PythonPath, PythonPlatform, SearchPathSettings};
5-
use ruff_db::diagnostic::{DiagnosticId, OldDiagnosticTrait, Severity, Span};
5+
use ruff_db::diagnostic::{DiagnosticFormat, DiagnosticId, OldDiagnosticTrait, Severity, Span};
66
use ruff_db::files::system_path_to_file;
77
use ruff_db::system::{System, SystemPath};
88
use ruff_macros::Combine;
@@ -120,6 +120,11 @@ impl Options {
120120

121121
if let Some(terminal) = self.terminal.as_ref() {
122122
settings.set_terminal(TerminalSettings {
123+
output_format: terminal
124+
.output_format
125+
.as_deref()
126+
.copied()
127+
.unwrap_or_default(),
123128
error_on_warning: terminal.error_on_warning.unwrap_or_default(),
124129
});
125130
}
@@ -277,6 +282,11 @@ impl FromIterator<(RangedValue<String>, RangedValue<Level>)> for Rules {
277282
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
278283
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
279284
pub struct TerminalOptions {
285+
/// The format to use for printing diagnostic messages.
286+
///
287+
/// Defaults to `full`.
288+
#[serde(skip_serializing_if = "Option::is_none")]
289+
pub output_format: Option<RangedValue<DiagnosticFormat>>,
280290
/// Use exit code 1 if there are any warning-level diagnostics.
281291
///
282292
/// Defaults to `false`.

crates/red_knot_project/src/metadata/settings.rs

+2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::sync::Arc;
22

33
use red_knot_python_semantic::lint::RuleSelection;
4+
use ruff_db::diagnostic::DiagnosticFormat;
45

56
/// The resolved [`super::Options`] for the project.
67
///
@@ -49,5 +50,6 @@ impl Settings {
4950

5051
#[derive(Debug, Clone, PartialEq, Eq, Default)]
5152
pub struct TerminalSettings {
53+
pub output_format: DiagnosticFormat,
5254
pub error_on_warning: bool,
5355
}

0 commit comments

Comments
 (0)