Skip to content

Commit b9f91fb

Browse files
committed
Merge branch 'main' into dcreager/terminal-visibility
* main: Add `check` command (#15692) [red-knot] Use itertools to clean up `SymbolState::merge` (#15702) [red-knot] Add `--ignore`, `--warn`, and `--error` CLI arguments (#15689) Use `uv init --lib` in tutorial (#15718) [red-knot] Use `Unknown | T_inferred` for undeclared public symbols (#15674) [`ruff`] Parenthesize fix when argument spans multiple lines for `unnecessary-round` (`RUF057`) (#15703) [red-knot] Rename `TestDbBuilder::typeshed` to `.custom_typeshed` (#15712) Honor banned top level imports by TID253 in PLC0415. (#15628) Apply `AIR302`-context check only in `@task` function (#15711) [`airflow`] Update `AIR302` to check for deprecated context keys (#15144) Remove test rules from JSON schema (#15627) Add two missing commits to changelog (#15701) Fix grep for version number in docker build (#15699) Bump version to 0.9.3 (#15698) Preserve raw string prefix and escapes (#15694) [`flake8-pytest-style`] Rewrite references to `.exception` (`PT027`) (#15680)
2 parents a434762 + 9353482 commit b9f91fb

File tree

75 files changed

+2392
-656
lines changed

Some content is hidden

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

75 files changed

+2392
-656
lines changed

.github/workflows/build-docker.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ jobs:
5151
env:
5252
TAG: ${{ inputs.plan != '' && fromJson(inputs.plan).announcement_tag || 'dry-run' }}
5353
run: |
54-
version=$(grep "version = " pyproject.toml | sed -e 's/version = "\(.*\)"/\1/g')
54+
version=$(grep -m 1 "^version = " pyproject.toml | sed -e 's/version = "\(.*\)"/\1/g')
5555
if [ "${TAG}" != "${version}" ]; then
5656
echo "The input tag does not match the version from pyproject.toml:" >&2
5757
echo "${TAG}" >&2

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ tracing-flamechart.svg
3030
tracing-flamegraph.svg
3131

3232
# insta
33-
.rs.pending-snap
33+
*.rs.pending-snap
3434

3535

3636
###

CHANGELOG.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,65 @@
11
# Changelog
22

3+
## 0.9.3
4+
5+
### Preview features
6+
7+
- \[`airflow`\] Argument `fail_stop` in DAG has been renamed as `fail_fast` (`AIR302`) ([#15633](https://github.com/astral-sh/ruff/pull/15633))
8+
- \[`airflow`\] Extend `AIR303` with more symbols ([#15611](https://github.com/astral-sh/ruff/pull/15611))
9+
- \[`flake8-bandit`\] Report all references to suspicious functions (`S3`) ([#15541](https://github.com/astral-sh/ruff/pull/15541))
10+
- \[`flake8-pytest-style`\] Do not emit diagnostics for empty `for` loops (`PT012`, `PT031`) ([#15542](https://github.com/astral-sh/ruff/pull/15542))
11+
- \[`flake8-simplify`\] Avoid double negations (`SIM103`) ([#15562](https://github.com/astral-sh/ruff/pull/15562))
12+
- \[`pyflakes`\] Fix infinite loop with unused local import in `__init__.py` (`F401`) ([#15517](https://github.com/astral-sh/ruff/pull/15517))
13+
- \[`pylint`\] Do not report methods with only one `EM101`-compatible `raise` (`PLR6301`) ([#15507](https://github.com/astral-sh/ruff/pull/15507))
14+
- \[`pylint`\] Implement `redefined-slots-in-subclass` (`W0244`) ([#9640](https://github.com/astral-sh/ruff/pull/9640))
15+
- \[`pyupgrade`\] Add rules to use PEP 695 generics in classes and functions (`UP046`, `UP047`) ([#15565](https://github.com/astral-sh/ruff/pull/15565), [#15659](https://github.com/astral-sh/ruff/pull/15659))
16+
- \[`refurb`\] Implement `for-loop-writes` (`FURB122`) ([#10630](https://github.com/astral-sh/ruff/pull/10630))
17+
- \[`ruff`\] Implement `needless-else` clause (`RUF047`) ([#15051](https://github.com/astral-sh/ruff/pull/15051))
18+
- \[`ruff`\] Implement `starmap-zip` (`RUF058`) ([#15483](https://github.com/astral-sh/ruff/pull/15483))
19+
20+
### Rule changes
21+
22+
- \[`flake8-bugbear`\] Do not raise error if keyword argument is present and target-python version is less or equals than 3.9 (`B903`) ([#15549](https://github.com/astral-sh/ruff/pull/15549))
23+
- \[`flake8-comprehensions`\] strip parentheses around generators in `unnecessary-generator-set` (`C401`) ([#15553](https://github.com/astral-sh/ruff/pull/15553))
24+
- \[`flake8-pytest-style`\] Rewrite references to `.exception` (`PT027`) ([#15680](https://github.com/astral-sh/ruff/pull/15680))
25+
- \[`flake8-simplify`\] Mark fixes as unsafe (`SIM201`, `SIM202`) ([#15626](https://github.com/astral-sh/ruff/pull/15626))
26+
- \[`flake8-type-checking`\] Fix some safe fixes being labeled unsafe (`TC006`,`TC008`) ([#15638](https://github.com/astral-sh/ruff/pull/15638))
27+
- \[`isort`\] Omit trailing whitespace in `unsorted-imports` (`I001`) ([#15518](https://github.com/astral-sh/ruff/pull/15518))
28+
- \[`pydoclint`\] Allow ignoring one line docstrings for `DOC` rules ([#13302](https://github.com/astral-sh/ruff/pull/13302))
29+
- \[`pyflakes`\] Apply redefinition fixes by source code order (`F811`) ([#15575](https://github.com/astral-sh/ruff/pull/15575))
30+
- \[`pyflakes`\] Avoid removing too many imports in `redefined-while-unused` (`F811`) ([#15585](https://github.com/astral-sh/ruff/pull/15585))
31+
- \[`pyflakes`\] Group redefinition fixes by source statement (`F811`) ([#15574](https://github.com/astral-sh/ruff/pull/15574))
32+
- \[`pylint`\] Include name of base class in message for `redefined-slots-in-subclass` (`W0244`) ([#15559](https://github.com/astral-sh/ruff/pull/15559))
33+
- \[`ruff`\] Update fix for `RUF055` to use `var == value` ([#15605](https://github.com/astral-sh/ruff/pull/15605))
34+
35+
### Formatter
36+
37+
- Fix bracket spacing for single-element tuples in f-string expressions ([#15537](https://github.com/astral-sh/ruff/pull/15537))
38+
- Fix unstable f-string formatting for expressions containing a trailing comma ([#15545](https://github.com/astral-sh/ruff/pull/15545))
39+
40+
### Performance
41+
42+
- Avoid quadratic membership check in import fixes ([#15576](https://github.com/astral-sh/ruff/pull/15576))
43+
44+
### Server
45+
46+
- Allow `unsafe-fixes` settings for code actions ([#15666](https://github.com/astral-sh/ruff/pull/15666))
47+
48+
### Bug fixes
49+
50+
- \[`flake8-bandit`\] Add missing single-line/dotall regex flag (`S608`) ([#15654](https://github.com/astral-sh/ruff/pull/15654))
51+
- \[`flake8-import-conventions`\] Fix infinite loop between `ICN001` and `I002` (`ICN001`) ([#15480](https://github.com/astral-sh/ruff/pull/15480))
52+
- \[`flake8-simplify`\] Do not emit diagnostics for expressions inside string type annotations (`SIM222`, `SIM223`) ([#15405](https://github.com/astral-sh/ruff/pull/15405))
53+
- \[`pyflakes`\] Treat arguments passed to the `default=` parameter of `TypeVar` as type expressions (`F821`) ([#15679](https://github.com/astral-sh/ruff/pull/15679))
54+
- \[`pyupgrade`\] Avoid syntax error when the iterable is a non-parenthesized tuple (`UP028`) ([#15543](https://github.com/astral-sh/ruff/pull/15543))
55+
- \[`ruff`\] Exempt `NewType` calls where the original type is immutable (`RUF009`) ([#15588](https://github.com/astral-sh/ruff/pull/15588))
56+
- Preserve raw string prefix and escapes in all codegen fixes ([#15694](https://github.com/astral-sh/ruff/pull/15694))
57+
58+
### Documentation
59+
60+
- Generate documentation redirects for lowercase rule codes ([#15564](https://github.com/astral-sh/ruff/pull/15564))
61+
- `TRY300`: Add some extra notes on not catching exceptions you didn't expect ([#15036](https://github.com/astral-sh/ruff/pull/15036))
62+
363
## 0.9.2
464

565
### Preview features

Cargo.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 3 additions & 3 deletions
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.9.2/install.sh | sh
153-
powershell -c "irm https://astral.sh/ruff/0.9.2/install.ps1 | iex"
152+
curl -LsSf https://astral.sh/ruff/0.9.3/install.sh | sh
153+
powershell -c "irm https://astral.sh/ruff/0.9.3/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.9.2
186+
rev: v0.9.3
187187
hooks:
188188
# Run the linter.
189189
- id: ruff

crates/red_knot/src/args.rs

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
use crate::logging::Verbosity;
2+
use crate::python_version::PythonVersion;
3+
use clap::{ArgAction, ArgMatches, Error, Parser};
4+
use red_knot_project::metadata::options::{EnvironmentOptions, Options};
5+
use red_knot_project::metadata::value::{RangedValue, RelativePathBuf};
6+
use red_knot_python_semantic::lint;
7+
use ruff_db::system::SystemPathBuf;
8+
9+
#[derive(Debug, Parser)]
10+
#[command(
11+
author,
12+
name = "red-knot",
13+
about = "An extremely fast Python type checker."
14+
)]
15+
#[command(version)]
16+
pub(crate) struct Args {
17+
#[command(subcommand)]
18+
pub(crate) command: Command,
19+
}
20+
21+
#[derive(Debug, clap::Subcommand)]
22+
pub(crate) enum Command {
23+
/// Check a project for type errors.
24+
Check(CheckCommand),
25+
26+
/// Start the language server
27+
Server,
28+
}
29+
30+
#[derive(Debug, Parser)]
31+
pub(crate) struct CheckCommand {
32+
/// Run the command within the given project directory.
33+
///
34+
/// All `pyproject.toml` files will be discovered by walking up the directory tree from the given project directory,
35+
/// as will the project's virtual environment (`.venv`) unless the `venv-path` option is set.
36+
///
37+
/// Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.
38+
#[arg(long, value_name = "PROJECT")]
39+
pub(crate) project: Option<SystemPathBuf>,
40+
41+
/// Path to the virtual environment the project uses.
42+
///
43+
/// If provided, red-knot will use the `site-packages` directory of this virtual environment
44+
/// to resolve type information for the project's third-party dependencies.
45+
#[arg(long, value_name = "PATH")]
46+
pub(crate) venv_path: Option<SystemPathBuf>,
47+
48+
/// Custom directory to use for stdlib typeshed stubs.
49+
#[arg(long, value_name = "PATH", alias = "custom-typeshed-dir")]
50+
pub(crate) typeshed: Option<SystemPathBuf>,
51+
52+
/// Additional path to use as a module-resolution source (can be passed multiple times).
53+
#[arg(long, value_name = "PATH")]
54+
pub(crate) extra_search_path: Option<Vec<SystemPathBuf>>,
55+
56+
/// Python version to assume when resolving types.
57+
#[arg(long, value_name = "VERSION", alias = "target-version")]
58+
pub(crate) python_version: Option<PythonVersion>,
59+
60+
#[clap(flatten)]
61+
pub(crate) verbosity: Verbosity,
62+
63+
#[clap(flatten)]
64+
pub(crate) rules: RulesArg,
65+
66+
/// Run in watch mode by re-running whenever files change.
67+
#[arg(long, short = 'W')]
68+
pub(crate) watch: bool,
69+
}
70+
71+
impl CheckCommand {
72+
pub(crate) fn into_options(self) -> Options {
73+
let rules = if self.rules.is_empty() {
74+
None
75+
} else {
76+
Some(
77+
self.rules
78+
.into_iter()
79+
.map(|(rule, level)| (RangedValue::cli(rule), RangedValue::cli(level)))
80+
.collect(),
81+
)
82+
};
83+
84+
Options {
85+
environment: Some(EnvironmentOptions {
86+
python_version: self
87+
.python_version
88+
.map(|version| RangedValue::cli(version.into())),
89+
venv_path: self.venv_path.map(RelativePathBuf::cli),
90+
typeshed: self.typeshed.map(RelativePathBuf::cli),
91+
extra_paths: self.extra_search_path.map(|extra_search_paths| {
92+
extra_search_paths
93+
.into_iter()
94+
.map(RelativePathBuf::cli)
95+
.collect()
96+
}),
97+
..EnvironmentOptions::default()
98+
}),
99+
rules,
100+
..Default::default()
101+
}
102+
}
103+
}
104+
105+
/// A list of rules to enable or disable with a given severity.
106+
///
107+
/// This type is used to parse the `--error`, `--warn`, and `--ignore` arguments
108+
/// while preserving the order in which they were specified (arguments last override previous severities).
109+
#[derive(Debug)]
110+
pub(crate) struct RulesArg(Vec<(String, lint::Level)>);
111+
112+
impl RulesArg {
113+
fn is_empty(&self) -> bool {
114+
self.0.is_empty()
115+
}
116+
117+
fn into_iter(self) -> impl Iterator<Item = (String, lint::Level)> {
118+
self.0.into_iter()
119+
}
120+
}
121+
122+
impl clap::FromArgMatches for RulesArg {
123+
fn from_arg_matches(matches: &ArgMatches) -> Result<Self, Error> {
124+
let mut rules = Vec::new();
125+
126+
for (level, arg_id) in [
127+
(lint::Level::Ignore, "ignore"),
128+
(lint::Level::Warn, "warn"),
129+
(lint::Level::Error, "error"),
130+
] {
131+
let indices = matches.indices_of(arg_id).into_iter().flatten();
132+
let levels = matches.get_many::<String>(arg_id).into_iter().flatten();
133+
rules.extend(
134+
indices
135+
.zip(levels)
136+
.map(|(index, rule)| (index, rule, level)),
137+
);
138+
}
139+
140+
// Sort by their index so that values specified later override earlier ones.
141+
rules.sort_by_key(|(index, _, _)| *index);
142+
143+
Ok(Self(
144+
rules
145+
.into_iter()
146+
.map(|(_, rule, level)| (rule.to_owned(), level))
147+
.collect(),
148+
))
149+
}
150+
151+
fn update_from_arg_matches(&mut self, matches: &ArgMatches) -> Result<(), Error> {
152+
self.0 = Self::from_arg_matches(matches)?.0;
153+
Ok(())
154+
}
155+
}
156+
157+
impl clap::Args for RulesArg {
158+
fn augment_args(cmd: clap::Command) -> clap::Command {
159+
const HELP_HEADING: &str = "Enabling / disabling rules";
160+
161+
cmd.arg(
162+
clap::Arg::new("error")
163+
.long("error")
164+
.action(ArgAction::Append)
165+
.help("Treat the given rule as having severity 'error'. Can be specified multiple times.")
166+
.value_name("RULE")
167+
.help_heading(HELP_HEADING),
168+
)
169+
.arg(
170+
clap::Arg::new("warn")
171+
.long("warn")
172+
.action(ArgAction::Append)
173+
.help("Treat the given rule as having severity 'warn'. Can be specified multiple times.")
174+
.value_name("RULE")
175+
.help_heading(HELP_HEADING),
176+
)
177+
.arg(
178+
clap::Arg::new("ignore")
179+
.long("ignore")
180+
.action(ArgAction::Append)
181+
.help("Disables the rule. Can be specified multiple times.")
182+
.value_name("RULE")
183+
.help_heading(HELP_HEADING),
184+
)
185+
}
186+
187+
fn augment_args_for_update(cmd: clap::Command) -> clap::Command {
188+
Self::augment_args(cmd)
189+
}
190+
}

0 commit comments

Comments
 (0)