Compare commits
1 Commits
0.12.7
...
david/enum
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e935bc5578 |
30
CHANGELOG.md
30
CHANGELOG.md
@@ -1,35 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## 0.12.7
|
||||
|
||||
This is a follow-up release to 0.12.6. Because of an issue in the package metadata, 0.12.6 failed to publish fully to PyPI and has been yanked. Similarly, there is no GitHub release or Git tag for 0.12.6. The contents of the 0.12.7 release are identical to 0.12.6, except for the updated metadata.
|
||||
|
||||
## 0.12.6
|
||||
|
||||
### Preview features
|
||||
|
||||
- \[`flake8-commas`\] Add support for trailing comma checks in type parameter lists (`COM812`, `COM819`) ([#19390](https://github.com/astral-sh/ruff/pull/19390))
|
||||
- \[`pylint`\] Implement auto-fix for `missing-maxsplit-arg` (`PLC0207`) ([#19387](https://github.com/astral-sh/ruff/pull/19387))
|
||||
- \[`ruff`\] Offer fixes for `RUF039` in more cases ([#19065](https://github.com/astral-sh/ruff/pull/19065))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Support `.pyi` files in ruff analyze graph ([#19611](https://github.com/astral-sh/ruff/pull/19611))
|
||||
- \[`flake8-pyi`\] Preserve inline comment in ellipsis removal (`PYI013`) ([#19399](https://github.com/astral-sh/ruff/pull/19399))
|
||||
- \[`perflint`\] Ignore rule if target is `global` or `nonlocal` (`PERF401`) ([#19539](https://github.com/astral-sh/ruff/pull/19539))
|
||||
- \[`pyupgrade`\] Fix `UP030` to avoid modifying double curly braces in format strings ([#19378](https://github.com/astral-sh/ruff/pull/19378))
|
||||
- \[`refurb`\] Ignore decorated functions for `FURB118` ([#19339](https://github.com/astral-sh/ruff/pull/19339))
|
||||
- \[`refurb`\] Mark `int` and `bool` cases for `Decimal.from_float` as safe fixes (`FURB164`) ([#19468](https://github.com/astral-sh/ruff/pull/19468))
|
||||
- \[`ruff`\] Fix `RUF033` for named default expressions ([#19115](https://github.com/astral-sh/ruff/pull/19115))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- \[`flake8-blind-except`\] Change `BLE001` to permit `logging.critical(..., exc_info=True)` ([#19520](https://github.com/astral-sh/ruff/pull/19520))
|
||||
|
||||
### Performance
|
||||
|
||||
- Add support for specifying minimum dots in detected string imports ([#19538](https://github.com/astral-sh/ruff/pull/19538))
|
||||
|
||||
## 0.12.5
|
||||
|
||||
### Preview features
|
||||
|
||||
6
Cargo.lock
generated
6
Cargo.lock
generated
@@ -2744,7 +2744,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.12.7"
|
||||
version = "0.12.5"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argfile",
|
||||
@@ -2997,7 +2997,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_linter"
|
||||
version = "0.12.7"
|
||||
version = "0.12.5"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"anyhow",
|
||||
@@ -3329,7 +3329,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_wasm"
|
||||
version = "0.12.7"
|
||||
version = "0.12.5"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"console_log",
|
||||
|
||||
@@ -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.7/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/0.12.7/install.ps1 | iex"
|
||||
curl -LsSf https://astral.sh/ruff/0.12.5/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/0.12.5/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.7
|
||||
rev: v0.12.5
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff-check
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.12.7"
|
||||
version = "0.12.5"
|
||||
publish = true
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_linter"
|
||||
version = "0.12.7"
|
||||
version = "0.12.5"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -39,11 +39,6 @@ class NonEmptyWithInit:
|
||||
pass
|
||||
|
||||
|
||||
class NonEmptyChildWithInlineComment:
|
||||
value: int
|
||||
... # preserve me
|
||||
|
||||
|
||||
class EmptyClass:
|
||||
...
|
||||
|
||||
|
||||
@@ -38,10 +38,6 @@ class NonEmptyWithInit:
|
||||
def __init__():
|
||||
pass
|
||||
|
||||
class NonEmptyChildWithInlineComment:
|
||||
value: int
|
||||
... # preserve me
|
||||
|
||||
# Not violations
|
||||
|
||||
class EmptyClass: ...
|
||||
|
||||
@@ -59,7 +59,3 @@ kwargs = {x: x for x in range(10)}
|
||||
"{1}_{0}".format(1, 2, *args)
|
||||
|
||||
"{1}_{0}".format(1, 2)
|
||||
|
||||
r"\d{{1,2}} {0}".format(42)
|
||||
|
||||
"{{{0}}}".format(123)
|
||||
|
||||
@@ -590,16 +590,6 @@ impl<'a> Checker<'a> {
|
||||
member,
|
||||
})
|
||||
}
|
||||
|
||||
/// Return the [`LintContext`] for the current analysis.
|
||||
///
|
||||
/// Note that you should always prefer calling methods like `settings`, `report_diagnostic`, or
|
||||
/// `is_rule_enabled` directly on [`Checker`] when possible. This method exists only for the
|
||||
/// rare cases where rules or helper functions need to be accessed by both a `Checker` and a
|
||||
/// `LintContext` in different analysis phases.
|
||||
pub(crate) const fn context(&self) -> &'a LintContext<'a> {
|
||||
self.context
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct TypingImporter<'a, 'b> {
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::whitespace::trailing_comment_start_offset;
|
||||
use ruff_python_ast::{Stmt, StmtExpr};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::fix;
|
||||
use crate::{Edit, Fix, FixAvailability, Violation};
|
||||
use crate::{Fix, FixAvailability, Violation};
|
||||
|
||||
/// ## What it does
|
||||
/// Removes ellipses (`...`) in otherwise non-empty class bodies.
|
||||
@@ -51,21 +50,15 @@ pub(crate) fn ellipsis_in_non_empty_class_body(checker: &Checker, body: &[Stmt])
|
||||
}
|
||||
|
||||
for stmt in body {
|
||||
let Stmt::Expr(StmtExpr { value, .. }) = stmt else {
|
||||
let Stmt::Expr(StmtExpr { value, .. }) = &stmt else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if value.is_ellipsis_literal_expr() {
|
||||
let mut diagnostic =
|
||||
checker.report_diagnostic(EllipsisInNonEmptyClassBody, stmt.range());
|
||||
|
||||
// Try to preserve trailing comment if it exists
|
||||
let edit = if let Some(index) = trailing_comment_start_offset(stmt, checker.source()) {
|
||||
Edit::range_deletion(stmt.range().add_end(index))
|
||||
} else {
|
||||
fix::edits::delete_stmt(stmt, Some(stmt), checker.locator(), checker.indexer())
|
||||
};
|
||||
|
||||
let edit =
|
||||
fix::edits::delete_stmt(stmt, Some(stmt), checker.locator(), checker.indexer());
|
||||
diagnostic.set_fix(Fix::safe_edit(edit).isolate(Checker::isolation(
|
||||
checker.semantic().current_statement_id(),
|
||||
)));
|
||||
|
||||
@@ -145,22 +145,3 @@ PYI013.py:36:5: PYI013 [*] Non-empty class body must not contain `...`
|
||||
37 36 |
|
||||
38 37 | def __init__():
|
||||
39 38 | pass
|
||||
|
||||
PYI013.py:44:5: PYI013 [*] Non-empty class body must not contain `...`
|
||||
|
|
||||
42 | class NonEmptyChildWithInlineComment:
|
||||
43 | value: int
|
||||
44 | ... # preserve me
|
||||
| ^^^ PYI013
|
||||
|
|
||||
= help: Remove unnecessary `...`
|
||||
|
||||
ℹ Safe fix
|
||||
41 41 |
|
||||
42 42 | class NonEmptyChildWithInlineComment:
|
||||
43 43 | value: int
|
||||
44 |- ... # preserve me
|
||||
44 |+ # preserve me
|
||||
45 45 |
|
||||
46 46 |
|
||||
47 47 | class EmptyClass:
|
||||
|
||||
@@ -17,10 +17,9 @@ PYI013.pyi:5:5: PYI013 [*] Non-empty class body must not contain `...`
|
||||
3 3 | class OneAttributeClass:
|
||||
4 4 | value: int
|
||||
5 |- ... # Error
|
||||
5 |+ # Error
|
||||
6 6 |
|
||||
7 7 | class OneAttributeClass2:
|
||||
8 8 | ... # Error
|
||||
6 5 |
|
||||
7 6 | class OneAttributeClass2:
|
||||
8 7 | ... # Error
|
||||
|
||||
PYI013.pyi:8:5: PYI013 [*] Non-empty class body must not contain `...`
|
||||
|
|
||||
@@ -36,10 +35,9 @@ PYI013.pyi:8:5: PYI013 [*] Non-empty class body must not contain `...`
|
||||
6 6 |
|
||||
7 7 | class OneAttributeClass2:
|
||||
8 |- ... # Error
|
||||
8 |+ # Error
|
||||
9 9 | value: int
|
||||
10 10 |
|
||||
11 11 | class MyClass:
|
||||
9 8 | value: int
|
||||
10 9 |
|
||||
11 10 | class MyClass:
|
||||
|
||||
PYI013.pyi:12:5: PYI013 [*] Non-empty class body must not contain `...`
|
||||
|
|
||||
@@ -93,10 +91,9 @@ PYI013.pyi:17:5: PYI013 [*] Non-empty class body must not contain `...`
|
||||
15 15 | class TwoEllipsesClass:
|
||||
16 16 | ...
|
||||
17 |- ... # Error
|
||||
17 |+ # Error
|
||||
18 18 |
|
||||
19 19 | class DocstringClass:
|
||||
20 20 | """
|
||||
18 17 |
|
||||
19 18 | class DocstringClass:
|
||||
20 19 | """
|
||||
|
||||
PYI013.pyi:24:5: PYI013 [*] Non-empty class body must not contain `...`
|
||||
|
|
||||
@@ -114,10 +111,9 @@ PYI013.pyi:24:5: PYI013 [*] Non-empty class body must not contain `...`
|
||||
22 22 | """
|
||||
23 23 |
|
||||
24 |- ... # Error
|
||||
24 |+ # Error
|
||||
25 25 |
|
||||
26 26 | class NonEmptyChild(Exception):
|
||||
27 27 | value: int
|
||||
25 24 |
|
||||
26 25 | class NonEmptyChild(Exception):
|
||||
27 26 | value: int
|
||||
|
||||
PYI013.pyi:28:5: PYI013 [*] Non-empty class body must not contain `...`
|
||||
|
|
||||
@@ -135,10 +131,9 @@ PYI013.pyi:28:5: PYI013 [*] Non-empty class body must not contain `...`
|
||||
26 26 | class NonEmptyChild(Exception):
|
||||
27 27 | value: int
|
||||
28 |- ... # Error
|
||||
28 |+ # Error
|
||||
29 29 |
|
||||
30 30 | class NonEmptyChild2(Exception):
|
||||
31 31 | ... # Error
|
||||
29 28 |
|
||||
30 29 | class NonEmptyChild2(Exception):
|
||||
31 30 | ... # Error
|
||||
|
||||
PYI013.pyi:31:5: PYI013 [*] Non-empty class body must not contain `...`
|
||||
|
|
||||
@@ -154,10 +149,9 @@ PYI013.pyi:31:5: PYI013 [*] Non-empty class body must not contain `...`
|
||||
29 29 |
|
||||
30 30 | class NonEmptyChild2(Exception):
|
||||
31 |- ... # Error
|
||||
31 |+ # Error
|
||||
32 32 | value: int
|
||||
33 33 |
|
||||
34 34 | class NonEmptyWithInit:
|
||||
32 31 | value: int
|
||||
33 32 |
|
||||
34 33 | class NonEmptyWithInit:
|
||||
|
||||
PYI013.pyi:36:5: PYI013 [*] Non-empty class body must not contain `...`
|
||||
|
|
||||
@@ -175,28 +169,6 @@ PYI013.pyi:36:5: PYI013 [*] Non-empty class body must not contain `...`
|
||||
34 34 | class NonEmptyWithInit:
|
||||
35 35 | value: int
|
||||
36 |- ... # Error
|
||||
36 |+ # Error
|
||||
37 37 |
|
||||
38 38 | def __init__():
|
||||
39 39 | pass
|
||||
|
||||
PYI013.pyi:43:5: PYI013 [*] Non-empty class body must not contain `...`
|
||||
|
|
||||
41 | class NonEmptyChildWithInlineComment:
|
||||
42 | value: int
|
||||
43 | ... # preserve me
|
||||
| ^^^ PYI013
|
||||
44 |
|
||||
45 | # Not violations
|
||||
|
|
||||
= help: Remove unnecessary `...`
|
||||
|
||||
ℹ Safe fix
|
||||
40 40 |
|
||||
41 41 | class NonEmptyChildWithInlineComment:
|
||||
42 42 | value: int
|
||||
43 |- ... # preserve me
|
||||
43 |+ # preserve me
|
||||
44 44 |
|
||||
45 45 | # Not violations
|
||||
46 46 |
|
||||
37 36 |
|
||||
38 37 | def __init__():
|
||||
39 38 | pass
|
||||
|
||||
@@ -124,20 +124,10 @@ fn is_sequential(indices: &[usize]) -> bool {
|
||||
indices.iter().enumerate().all(|(idx, value)| idx == *value)
|
||||
}
|
||||
|
||||
static FORMAT_SPECIFIER: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(
|
||||
r"(?x)
|
||||
(?P<prefix>
|
||||
^|[^{]|(?:\{{2})+ # preceded by nothing, a non-brace, or an even number of braces
|
||||
)
|
||||
\{ # opening curly brace
|
||||
(?P<int>\d+) # followed by any integer
|
||||
(?P<fmt>.*?) # followed by any text
|
||||
} # followed by a closing brace
|
||||
",
|
||||
)
|
||||
.unwrap()
|
||||
});
|
||||
// An opening curly brace, followed by any integer, followed by any text,
|
||||
// followed by a closing brace.
|
||||
static FORMAT_SPECIFIER: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"\{(?P<int>\d+)(?P<fmt>.*?)}").unwrap());
|
||||
|
||||
/// Remove the explicit positional indices from a format string.
|
||||
fn remove_specifiers<'a>(value: &mut Expression<'a>, arena: &'a typed_arena::Arena<String>) {
|
||||
@@ -145,7 +135,7 @@ fn remove_specifiers<'a>(value: &mut Expression<'a>, arena: &'a typed_arena::Are
|
||||
Expression::SimpleString(expr) => {
|
||||
expr.value = arena.alloc(
|
||||
FORMAT_SPECIFIER
|
||||
.replace_all(expr.value, "$prefix{$fmt}")
|
||||
.replace_all(expr.value, "{$fmt}")
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
@@ -156,7 +146,7 @@ fn remove_specifiers<'a>(value: &mut Expression<'a>, arena: &'a typed_arena::Are
|
||||
libcst_native::String::Simple(string) => {
|
||||
string.value = arena.alloc(
|
||||
FORMAT_SPECIFIER
|
||||
.replace_all(string.value, "$prefix{$fmt}")
|
||||
.replace_all(string.value, "{$fmt}")
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -481,7 +481,6 @@ UP030_0.py:59:1: UP030 [*] Use implicit references for positional format fields
|
||||
59 |+"{}_{}".format(2, 1, )
|
||||
60 60 |
|
||||
61 61 | "{1}_{0}".format(1, 2)
|
||||
62 62 |
|
||||
|
||||
UP030_0.py:61:1: UP030 [*] Use implicit references for positional format fields
|
||||
|
|
||||
@@ -489,8 +488,6 @@ UP030_0.py:61:1: UP030 [*] Use implicit references for positional format fields
|
||||
60 |
|
||||
61 | "{1}_{0}".format(1, 2)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ UP030
|
||||
62 |
|
||||
63 | r"\d{{1,2}} {0}".format(42)
|
||||
|
|
||||
= help: Remove explicit positional indices
|
||||
|
||||
@@ -500,42 +497,3 @@ UP030_0.py:61:1: UP030 [*] Use implicit references for positional format fields
|
||||
60 60 |
|
||||
61 |-"{1}_{0}".format(1, 2)
|
||||
61 |+"{}_{}".format(2, 1)
|
||||
62 62 |
|
||||
63 63 | r"\d{{1,2}} {0}".format(42)
|
||||
64 64 |
|
||||
|
||||
UP030_0.py:63:1: UP030 [*] Use implicit references for positional format fields
|
||||
|
|
||||
61 | "{1}_{0}".format(1, 2)
|
||||
62 |
|
||||
63 | r"\d{{1,2}} {0}".format(42)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP030
|
||||
64 |
|
||||
65 | "{{{0}}}".format(123)
|
||||
|
|
||||
= help: Remove explicit positional indices
|
||||
|
||||
ℹ Unsafe fix
|
||||
60 60 |
|
||||
61 61 | "{1}_{0}".format(1, 2)
|
||||
62 62 |
|
||||
63 |-r"\d{{1,2}} {0}".format(42)
|
||||
63 |+r"\d{{1,2}} {}".format(42)
|
||||
64 64 |
|
||||
65 65 | "{{{0}}}".format(123)
|
||||
|
||||
UP030_0.py:65:1: UP030 [*] Use implicit references for positional format fields
|
||||
|
|
||||
63 | r"\d{{1,2}} {0}".format(42)
|
||||
64 |
|
||||
65 | "{{{0}}}".format(123)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ UP030
|
||||
|
|
||||
= help: Remove explicit positional indices
|
||||
|
||||
ℹ Unsafe fix
|
||||
62 62 |
|
||||
63 63 | r"\d{{1,2}} {0}".format(42)
|
||||
64 64 |
|
||||
65 |-"{{{0}}}".format(123)
|
||||
65 |+"{{{}}}".format(123)
|
||||
|
||||
@@ -12,6 +12,7 @@ use crate::checkers::ast::{Checker, LintContext};
|
||||
use crate::preview::is_unicode_to_unicode_confusables_enabled;
|
||||
use crate::rules::ruff::rules::Context;
|
||||
use crate::rules::ruff::rules::confusables::confusable;
|
||||
use crate::settings::LinterSettings;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for ambiguous Unicode characters in strings.
|
||||
@@ -179,7 +180,9 @@ pub(crate) fn ambiguous_unicode_character_comment(
|
||||
range: TextRange,
|
||||
) {
|
||||
let text = locator.slice(range);
|
||||
ambiguous_unicode_character(text, range, Context::Comment, context);
|
||||
for candidate in ambiguous_unicode_character(text, range, context.settings()) {
|
||||
candidate.into_diagnostic(Context::Comment, context);
|
||||
}
|
||||
}
|
||||
|
||||
/// RUF001, RUF002
|
||||
@@ -200,19 +203,22 @@ pub(crate) fn ambiguous_unicode_character_string(checker: &Checker, string_like:
|
||||
match part {
|
||||
ast::StringLikePart::String(string_literal) => {
|
||||
let text = checker.locator().slice(string_literal);
|
||||
ambiguous_unicode_character(
|
||||
text,
|
||||
string_literal.range(),
|
||||
context,
|
||||
checker.context(),
|
||||
);
|
||||
for candidate in
|
||||
ambiguous_unicode_character(text, string_literal.range(), checker.settings())
|
||||
{
|
||||
candidate.report_diagnostic(checker, context);
|
||||
}
|
||||
}
|
||||
ast::StringLikePart::Bytes(_) => {}
|
||||
ast::StringLikePart::FString(FString { elements, .. })
|
||||
| ast::StringLikePart::TString(TString { elements, .. }) => {
|
||||
for literal in elements.literals() {
|
||||
let text = checker.locator().slice(literal);
|
||||
ambiguous_unicode_character(text, literal.range(), context, checker.context());
|
||||
for candidate in
|
||||
ambiguous_unicode_character(text, literal.range(), checker.settings())
|
||||
{
|
||||
candidate.report_diagnostic(checker, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -222,12 +228,13 @@ pub(crate) fn ambiguous_unicode_character_string(checker: &Checker, string_like:
|
||||
fn ambiguous_unicode_character(
|
||||
text: &str,
|
||||
range: TextRange,
|
||||
context: Context,
|
||||
lint_context: &LintContext,
|
||||
) {
|
||||
settings: &LinterSettings,
|
||||
) -> Vec<Candidate> {
|
||||
let mut candidates = Vec::new();
|
||||
|
||||
// Most of the time, we don't need to check for ambiguous unicode characters at all.
|
||||
if text.is_ascii() {
|
||||
return;
|
||||
return candidates;
|
||||
}
|
||||
|
||||
// Iterate over the "words" in the text.
|
||||
@@ -239,7 +246,7 @@ fn ambiguous_unicode_character(
|
||||
if !word_candidates.is_empty() {
|
||||
if word_flags.is_candidate_word() {
|
||||
for candidate in word_candidates.drain(..) {
|
||||
candidate.into_diagnostic(context, lint_context);
|
||||
candidates.push(candidate);
|
||||
}
|
||||
}
|
||||
word_candidates.clear();
|
||||
@@ -250,23 +257,21 @@ fn ambiguous_unicode_character(
|
||||
// case, it's always included as a diagnostic.
|
||||
if !current_char.is_ascii() {
|
||||
if let Some(representant) = confusable(current_char as u32).filter(|representant| {
|
||||
is_unicode_to_unicode_confusables_enabled(lint_context.settings())
|
||||
|| representant.is_ascii()
|
||||
is_unicode_to_unicode_confusables_enabled(settings) || representant.is_ascii()
|
||||
}) {
|
||||
let candidate = Candidate::new(
|
||||
TextSize::try_from(relative_offset).unwrap() + range.start(),
|
||||
current_char,
|
||||
representant,
|
||||
);
|
||||
candidate.into_diagnostic(context, lint_context);
|
||||
candidates.push(candidate);
|
||||
}
|
||||
}
|
||||
} else if current_char.is_ascii() {
|
||||
// The current word contains at least one ASCII character.
|
||||
word_flags |= WordFlags::ASCII;
|
||||
} else if let Some(representant) = confusable(current_char as u32).filter(|representant| {
|
||||
is_unicode_to_unicode_confusables_enabled(lint_context.settings())
|
||||
|| representant.is_ascii()
|
||||
is_unicode_to_unicode_confusables_enabled(settings) || representant.is_ascii()
|
||||
}) {
|
||||
// The current word contains an ambiguous unicode character.
|
||||
word_candidates.push(Candidate::new(
|
||||
@@ -284,11 +289,13 @@ fn ambiguous_unicode_character(
|
||||
if !word_candidates.is_empty() {
|
||||
if word_flags.is_candidate_word() {
|
||||
for candidate in word_candidates.drain(..) {
|
||||
candidate.into_diagnostic(context, lint_context);
|
||||
candidates.push(candidate);
|
||||
}
|
||||
}
|
||||
word_candidates.clear();
|
||||
}
|
||||
|
||||
candidates
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
@@ -366,6 +373,39 @@ impl Candidate {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn report_diagnostic(self, checker: &Checker, context: Context) {
|
||||
if !checker
|
||||
.settings()
|
||||
.allowed_confusables
|
||||
.contains(&self.confusable)
|
||||
{
|
||||
let char_range = TextRange::at(self.offset, self.confusable.text_len());
|
||||
match context {
|
||||
Context::String => checker.report_diagnostic_if_enabled(
|
||||
AmbiguousUnicodeCharacterString {
|
||||
confusable: self.confusable,
|
||||
representant: self.representant,
|
||||
},
|
||||
char_range,
|
||||
),
|
||||
Context::Docstring => checker.report_diagnostic_if_enabled(
|
||||
AmbiguousUnicodeCharacterDocstring {
|
||||
confusable: self.confusable,
|
||||
representant: self.representant,
|
||||
},
|
||||
char_range,
|
||||
),
|
||||
Context::Comment => checker.report_diagnostic_if_enabled(
|
||||
AmbiguousUnicodeCharacterComment {
|
||||
confusable: self.confusable,
|
||||
representant: self.representant,
|
||||
},
|
||||
char_range,
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct NamedUnicode(char);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_wasm"
|
||||
version = "0.12.7"
|
||||
version = "0.12.5"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -66,9 +66,9 @@ enum CompletionTargetTokens<'t> {
|
||||
/// A token was found under the cursor, but it didn't
|
||||
/// match any of our anticipated token patterns.
|
||||
Generic { token: &'t Token },
|
||||
/// No token was found. We generally treat this like
|
||||
/// `Generic` (i.e., offer scope based completions).
|
||||
Unknown,
|
||||
/// No token was found, but we have the offset of the
|
||||
/// cursor.
|
||||
Unknown { offset: TextSize },
|
||||
}
|
||||
|
||||
impl<'t> CompletionTargetTokens<'t> {
|
||||
@@ -78,7 +78,7 @@ impl<'t> CompletionTargetTokens<'t> {
|
||||
static OBJECT_DOT_NON_EMPTY: [TokenKind; 2] = [TokenKind::Dot, TokenKind::Name];
|
||||
|
||||
let offset = match parsed.tokens().at_offset(offset) {
|
||||
TokenAt::None => return Some(CompletionTargetTokens::Unknown),
|
||||
TokenAt::None => return Some(CompletionTargetTokens::Unknown { offset }),
|
||||
TokenAt::Single(tok) => tok.end(),
|
||||
TokenAt::Between(_, tok) => tok.start(),
|
||||
};
|
||||
@@ -122,7 +122,7 @@ impl<'t> CompletionTargetTokens<'t> {
|
||||
return None;
|
||||
} else {
|
||||
let Some(last) = before.last() else {
|
||||
return Some(CompletionTargetTokens::Unknown);
|
||||
return Some(CompletionTargetTokens::Unknown { offset });
|
||||
};
|
||||
CompletionTargetTokens::Generic { token: last }
|
||||
},
|
||||
@@ -171,7 +171,7 @@ impl<'t> CompletionTargetTokens<'t> {
|
||||
node: covering_node.node(),
|
||||
})
|
||||
}
|
||||
CompletionTargetTokens::Unknown => {
|
||||
CompletionTargetTokens::Unknown { offset } => {
|
||||
let range = TextRange::empty(offset);
|
||||
let covering_node = covering_node(parsed.syntax().into(), range);
|
||||
Some(CompletionTargetAst::Scoped {
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
# `replace`
|
||||
|
||||
The `replace` function and the `replace` protocol were added in Python 3.13:
|
||||
<https://docs.python.org/3/whatsnew/3.13.html#copy>
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.13"
|
||||
```
|
||||
|
||||
## Basic
|
||||
|
||||
```py
|
||||
from copy import replace
|
||||
from datetime import time
|
||||
|
||||
t = time(12, 0, 0)
|
||||
t = replace(t, minute=30)
|
||||
|
||||
reveal_type(t) # revealed: time
|
||||
```
|
||||
|
||||
## The `__replace__` protocol
|
||||
|
||||
### Dataclasses
|
||||
|
||||
Dataclasses support the `__replace__` protocol:
|
||||
|
||||
```py
|
||||
from dataclasses import dataclass
|
||||
from copy import replace
|
||||
|
||||
@dataclass
|
||||
class Point:
|
||||
x: int
|
||||
y: int
|
||||
|
||||
reveal_type(Point.__replace__) # revealed: (self: Point, *, x: int = int, y: int = int) -> Point
|
||||
```
|
||||
|
||||
The `__replace__` method can either be called directly or through the `replace` function:
|
||||
|
||||
```py
|
||||
a = Point(1, 2)
|
||||
|
||||
b = a.__replace__(x=3, y=4)
|
||||
reveal_type(b) # revealed: Point
|
||||
|
||||
b = replace(a, x=3, y=4)
|
||||
reveal_type(b) # revealed: Point
|
||||
```
|
||||
|
||||
A call to `replace` does not require all keyword arguments:
|
||||
|
||||
```py
|
||||
c = a.__replace__(y=4)
|
||||
reveal_type(c) # revealed: Point
|
||||
|
||||
d = replace(a, y=4)
|
||||
reveal_type(d) # revealed: Point
|
||||
```
|
||||
|
||||
Invalid calls to `__replace__` or `replace` will raise an error:
|
||||
|
||||
```py
|
||||
e = a.__replace__(x="wrong") # error: [invalid-argument-type]
|
||||
|
||||
# TODO: this should ideally also be emit an error
|
||||
e = replace(a, x="wrong")
|
||||
```
|
||||
@@ -559,6 +559,22 @@ class Answer(Enum):
|
||||
reveal_type(enum_members(Answer))
|
||||
```
|
||||
|
||||
## Subclasses of `enum.Flag`
|
||||
|
||||
```py
|
||||
from enum import Flag, auto
|
||||
|
||||
class KeyModifier(Flag):
|
||||
SHIFT = auto()
|
||||
CTRL = auto()
|
||||
ALT = auto()
|
||||
|
||||
reveal_type(KeyModifier.SHIFT) # revealed: Literal[KeyModifier.SHIFT]
|
||||
|
||||
# TODO: this should be `KeyModifier`
|
||||
reveal_type(KeyModifier.SHIFT | KeyModifier.CTRL) # revealed: Literal[KeyModifier.CTRL]
|
||||
```
|
||||
|
||||
## Custom enum types
|
||||
|
||||
Enum classes can also be defined using a subclass of `enum.Enum` or any class that uses
|
||||
|
||||
@@ -1,141 +0,0 @@
|
||||
// This is a Dot representation of a flow diagram meant to describe Python's
|
||||
// import resolution rules. This particular diagram starts with one particular
|
||||
// search path and one particular module name. (Typical import resolution
|
||||
// implementation will try multiple search paths.)
|
||||
//
|
||||
// This diagram also assumes that stubs are allowed. The ty implementation
|
||||
// of import resolution makes this a configurable parameter, but it should
|
||||
// be straight-forward to adapt this flow diagram to one where no stubs
|
||||
// are allowed. (i.e., Remove `.pyi` checks and remove the `package-stubs`
|
||||
// handling.)
|
||||
//
|
||||
// This flow diagram exists to act as a sort of specification. At the time
|
||||
// of writing (2025-07-29), it was written to capture the implementation of
|
||||
// resolving a *particular* module name. We wanted to add another code path for
|
||||
// *listing* available module names. Since code reuse is somewhat difficult
|
||||
// between these two access patterns, I wrote this flow diagram as a way of 1)
|
||||
// learning how module resolution works and 2) to provide a "source of truth"
|
||||
// that we can compare implementations to.
|
||||
//
|
||||
// To convert this file into an actual image, you'll need the `dot` program
|
||||
// (which is typically part of a `graphviz` package in a Linux distro):
|
||||
//
|
||||
// dot -Tsvg import-resolution-diagram.dot > import-resolution-diagram.svg
|
||||
//
|
||||
// And then view it in a web browser (or some other svg viewer):
|
||||
//
|
||||
// firefox ./import-resolution-diagram.svg
|
||||
//
|
||||
// [Dot]: https://graphviz.org/doc/info/lang.html
|
||||
|
||||
digraph python_import_resolution {
|
||||
labelloc="t";
|
||||
label=<
|
||||
<b>Python import resolution flow diagram for a single module name in a single "search path"</b>
|
||||
<br/>(assumes that the module name is valid and that stubs are allowed)
|
||||
>;
|
||||
|
||||
// These are the final affirmative states we can end up in. A
|
||||
// module is a regular `foo.py` file module. A package is a
|
||||
// directory containing an `__init__.py`. A namespace package is a
|
||||
// directory that does *not* contain an `__init__.py`.
|
||||
module [label="Single-file Module",peripheries=2];
|
||||
package [label="Package",peripheries=2];
|
||||
namespace_package [label="Namespace Package",peripheries=2];
|
||||
not_found [label="Module Not Found",peripheries=2];
|
||||
|
||||
// The final states are wrapped in a subgraph with invisible edges
|
||||
// to convince GraphViz to give a more human digestible rendering.
|
||||
// Without this, the nodes are scattered every which way and the
|
||||
// flow diagram is pretty hard to follow. This encourages (but does
|
||||
// not guarantee) GraphViz to put these nodes "close" together, and
|
||||
// this generally gets us something grokable.
|
||||
subgraph final {
|
||||
rank = same;
|
||||
module -> package -> namespace_package -> not_found [style=invis];
|
||||
}
|
||||
|
||||
START [label=<<b>START</b>>];
|
||||
START -> non_shadowable;
|
||||
|
||||
non_shadowable [label=<
|
||||
Is the search path not the standard library and<br/>
|
||||
the module name is `types` or some other built-in?
|
||||
>];
|
||||
non_shadowable -> not_found [label="Yes"];
|
||||
non_shadowable -> stub_package_check [label="No"];
|
||||
|
||||
stub_package_check [label=<
|
||||
Is the search path in the standard library?
|
||||
>];
|
||||
stub_package_check -> stub_package_set [label="No"];
|
||||
stub_package_check -> determine_parent_kind [label="Yes"];
|
||||
|
||||
stub_package_set [label=<
|
||||
Set `module_name` to `{top-package}-stubs.{rest}`
|
||||
>];
|
||||
stub_package_set -> determine_parent_kind;
|
||||
|
||||
determine_parent_kind [label=<
|
||||
Does every parent package of `module_name`<br/>
|
||||
correspond to a directory that contains an<br/>
|
||||
`__init__.py` or an `__init__.pyi`?
|
||||
>];
|
||||
determine_parent_kind -> maybe_package [label="Yes"];
|
||||
determine_parent_kind -> namespace_parent1 [label="No"];
|
||||
|
||||
namespace_parent1 [label=<
|
||||
Is the direct parent package<br/>
|
||||
a directory that contains<br/>
|
||||
an `__init__.py` or `__init__.pyi`?
|
||||
>];
|
||||
namespace_parent1 -> bail [label="Yes"];
|
||||
namespace_parent1 -> namespace_parent2 [label="No"];
|
||||
|
||||
namespace_parent2 [label=<
|
||||
Does the direct parent package<br/>
|
||||
have a sibling file with the same<br/>
|
||||
basename and a `py` or `pyi` extension?<br/>
|
||||
>];
|
||||
namespace_parent2 -> bail [label="Yes"];
|
||||
namespace_parent2 -> namespace_parent3 [label="No"];
|
||||
|
||||
namespace_parent3 [label=<
|
||||
Is every parent above the direct<br/>
|
||||
parent package a normal package or<br/>
|
||||
otherwise satisfy the previous two<br/>
|
||||
namespace package requirements?
|
||||
>];
|
||||
namespace_parent3 -> bail [label="No"];
|
||||
namespace_parent3 -> maybe_package [label="Yes"];
|
||||
|
||||
maybe_package [label=<
|
||||
After replacing `.` with `/` in module name,<br/>
|
||||
does `{path}/__init__.py` or `{path}/__init__.pyi` exist?
|
||||
>];
|
||||
maybe_package -> package [label="Yes"];
|
||||
maybe_package -> maybe_module [label="No"];
|
||||
|
||||
maybe_module [label=<
|
||||
Does `{path}.py` or `{path}.pyi` exist?
|
||||
>];
|
||||
maybe_module -> module [label="Yes"];
|
||||
maybe_module -> maybe_namespace [label="No"];
|
||||
|
||||
maybe_namespace [label=<
|
||||
Is `{path}` a directory?
|
||||
>];
|
||||
maybe_namespace -> namespace_package [label="Yes"];
|
||||
maybe_namespace -> bail [label="No"];
|
||||
|
||||
bail [label=<
|
||||
Is `module_name` set to a stub package candidate?
|
||||
>];
|
||||
bail -> not_found [label="No"];
|
||||
bail -> retry [label="Yes"];
|
||||
|
||||
retry [label=<
|
||||
Reset `module_name` to original
|
||||
>];
|
||||
retry -> determine_parent_kind;
|
||||
}
|
||||
@@ -1,296 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Generated by graphviz version 13.0.1 (0)
|
||||
-->
|
||||
<!-- Title: python_import_resolution Pages: 1 -->
|
||||
<svg width="1463pt" height="1417pt"
|
||||
viewBox="0.00 0.00 1463.00 1417.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 1413.5)">
|
||||
<title>python_import_resolution</title>
|
||||
<polygon fill="white" stroke="none" points="-4,4 -4,-1413.5 1458.95,-1413.5 1458.95,4 -4,4"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="337.85" y="-1393.2" font-family="Times,serif" font-size="14.00">        </text>
|
||||
<text xml:space="preserve" text-anchor="start" x="373.85" y="-1393.2" font-family="Times,serif" font-weight="bold" font-size="14.00">Python import resolution flow diagram for a single module name in a single "search path"</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="1081.1" y="-1393.2" font-family="Times,serif" font-size="14.00">        </text>
|
||||
<text xml:space="preserve" text-anchor="start" x="476.23" y="-1379.2" font-family="Times,serif" font-size="14.00">(assumes that the module name is valid and that stubs are allowed)    </text>
|
||||
<!-- module -->
|
||||
<g id="node1" class="node">
|
||||
<title>module</title>
|
||||
<ellipse fill="none" stroke="black" cx="105.71" cy="-22" rx="101.71" ry="18"/>
|
||||
<ellipse fill="none" stroke="black" cx="105.71" cy="-22" rx="105.71" ry="22"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="105.71" y="-17.32" font-family="Times,serif" font-size="14.00">Single-file Module</text>
|
||||
</g>
|
||||
<!-- package -->
|
||||
<g id="node2" class="node">
|
||||
<title>package</title>
|
||||
<ellipse fill="none" stroke="black" cx="302.71" cy="-22" rx="52.26" ry="18"/>
|
||||
<ellipse fill="none" stroke="black" cx="302.71" cy="-22" rx="56.26" ry="22"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="302.71" y="-17.32" font-family="Times,serif" font-size="14.00">Package</text>
|
||||
</g>
|
||||
<!-- module->package -->
|
||||
<!-- namespace_package -->
|
||||
<g id="node3" class="node">
|
||||
<title>namespace_package</title>
|
||||
<ellipse fill="none" stroke="black" cx="511.71" cy="-22" rx="113.29" ry="18"/>
|
||||
<ellipse fill="none" stroke="black" cx="511.71" cy="-22" rx="117.29" ry="22"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="511.71" y="-17.32" font-family="Times,serif" font-size="14.00">Namespace Package</text>
|
||||
</g>
|
||||
<!-- package->namespace_package -->
|
||||
<!-- not_found -->
|
||||
<g id="node4" class="node">
|
||||
<title>not_found</title>
|
||||
<ellipse fill="none" stroke="black" cx="1200.71" cy="-22" rx="104.35" ry="18"/>
|
||||
<ellipse fill="none" stroke="black" cx="1200.71" cy="-22" rx="108.35" ry="22"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="1200.71" y="-17.32" font-family="Times,serif" font-size="14.00">Module Not Found</text>
|
||||
</g>
|
||||
<!-- namespace_package->not_found -->
|
||||
<!-- START -->
|
||||
<g id="node5" class="node">
|
||||
<title>START</title>
|
||||
<ellipse fill="none" stroke="black" cx="1147.71" cy="-1355.5" rx="47.53" ry="18"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="1121.84" y="-1351.82" font-family="Times,serif" font-weight="bold" font-size="14.00">START</text>
|
||||
</g>
|
||||
<!-- non_shadowable -->
|
||||
<g id="node6" class="node">
|
||||
<title>non_shadowable</title>
|
||||
<ellipse fill="none" stroke="black" cx="1147.71" cy="-1270.44" rx="307.24" ry="30.05"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="961.71" y="-1274.39" font-family="Times,serif" font-size="14.00">        Is the search path not the standard library and</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="938.46" y="-1257.14" font-family="Times,serif" font-size="14.00">        the module name is `types` or some other built-in?    </text>
|
||||
</g>
|
||||
<!-- START->non_shadowable -->
|
||||
<g id="edge4" class="edge">
|
||||
<title>START->non_shadowable</title>
|
||||
<path fill="none" stroke="black" d="M1147.71,-1337.29C1147.71,-1329.95 1147.71,-1321.06 1147.71,-1312.21"/>
|
||||
<polygon fill="black" stroke="black" points="1151.21,-1312.25 1147.71,-1302.25 1144.21,-1312.25 1151.21,-1312.25"/>
|
||||
</g>
|
||||
<!-- non_shadowable->not_found -->
|
||||
<g id="edge5" class="edge">
|
||||
<title>non_shadowable->not_found</title>
|
||||
<path fill="none" stroke="black" d="M1192.48,-1240.23C1213.37,-1222.8 1233.71,-1198.54 1233.71,-1170.14 1233.71,-1170.14 1233.71,-1170.14 1233.71,-114.25 1233.71,-93.46 1226.05,-71.52 1218.11,-54.36"/>
|
||||
<polygon fill="black" stroke="black" points="1221.41,-53.14 1213.86,-45.7 1215.12,-56.22 1221.41,-53.14"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="1245.71" y="-609.2" font-family="Times,serif" font-size="14.00">Yes</text>
|
||||
</g>
|
||||
<!-- stub_package_check -->
|
||||
<g id="node7" class="node">
|
||||
<title>stub_package_check</title>
|
||||
<ellipse fill="none" stroke="black" cx="892.71" cy="-1169.14" rx="261.65" ry="18"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="714.21" y="-1164.47" font-family="Times,serif" font-size="14.00">        Is the search path in the standard library?    </text>
|
||||
</g>
|
||||
<!-- non_shadowable->stub_package_check -->
|
||||
<g id="edge6" class="edge">
|
||||
<title>non_shadowable->stub_package_check</title>
|
||||
<path fill="none" stroke="black" d="M1074.3,-1240.85C1033.86,-1225.11 984.5,-1205.89 947.46,-1191.46"/>
|
||||
<polygon fill="black" stroke="black" points="948.87,-1188.25 938.28,-1187.89 946.33,-1194.78 948.87,-1188.25"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="1030.34" y="-1209.09" font-family="Times,serif" font-size="14.00">No</text>
|
||||
</g>
|
||||
<!-- stub_package_set -->
|
||||
<g id="node8" class="node">
|
||||
<title>stub_package_set</title>
|
||||
<ellipse fill="none" stroke="black" cx="892.71" cy="-1079.89" rx="312.68" ry="18"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="677.84" y="-1075.22" font-family="Times,serif" font-size="14.00">        Set `module_name` to `{top-package}-stubs.{rest}`    </text>
|
||||
</g>
|
||||
<!-- stub_package_check->stub_package_set -->
|
||||
<g id="edge7" class="edge">
|
||||
<title>stub_package_check->stub_package_set</title>
|
||||
<path fill="none" stroke="black" d="M892.71,-1150.9C892.71,-1139.07 892.71,-1123.1 892.71,-1109.39"/>
|
||||
<polygon fill="black" stroke="black" points="896.21,-1109.61 892.71,-1099.61 889.21,-1109.61 896.21,-1109.61"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="902.84" y="-1119.84" font-family="Times,serif" font-size="14.00">No</text>
|
||||
</g>
|
||||
<!-- determine_parent_kind -->
|
||||
<g id="node9" class="node">
|
||||
<title>determine_parent_kind</title>
|
||||
<ellipse fill="none" stroke="black" cx="833.71" cy="-982.64" rx="269.05" ry="42.25"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="651.46" y="-995.22" font-family="Times,serif" font-size="14.00">        Does every parent package of `module_name`</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="664.96" y="-977.97" font-family="Times,serif" font-size="14.00">        correspond to a directory that contains an</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="691.59" y="-960.72" font-family="Times,serif" font-size="14.00">        `__init__.py` or an `__init__.pyi`?    </text>
|
||||
</g>
|
||||
<!-- stub_package_check->determine_parent_kind -->
|
||||
<g id="edge8" class="edge">
|
||||
<title>stub_package_check->determine_parent_kind</title>
|
||||
<path fill="none" stroke="black" d="M729.85,-1154.72C652.04,-1144.58 570.67,-1127.2 546.71,-1097.89 511.98,-1055.39 550.89,-1028.61 610.12,-1011.78"/>
|
||||
<polygon fill="black" stroke="black" points="610.79,-1015.22 619.54,-1009.24 608.97,-1008.46 610.79,-1015.22"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="558.71" y="-1075.22" font-family="Times,serif" font-size="14.00">Yes</text>
|
||||
</g>
|
||||
<!-- stub_package_set->determine_parent_kind -->
|
||||
<g id="edge9" class="edge">
|
||||
<title>stub_package_set->determine_parent_kind</title>
|
||||
<path fill="none" stroke="black" d="M881.89,-1061.42C877.15,-1053.77 871.32,-1044.35 865.37,-1034.74"/>
|
||||
<polygon fill="black" stroke="black" points="868.45,-1033.07 860.21,-1026.42 862.5,-1036.76 868.45,-1033.07"/>
|
||||
</g>
|
||||
<!-- maybe_package -->
|
||||
<g id="node10" class="node">
|
||||
<title>maybe_package</title>
|
||||
<ellipse fill="none" stroke="black" cx="427.71" cy="-395.05" rx="328.45" ry="30.05"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="254.09" y="-399" font-family="Times,serif" font-size="14.00">        After replacing `.` with `/` in module name,</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="203.46" y="-381.75" font-family="Times,serif" font-size="14.00">        does `{path}/__init__.py` or `{path}/__init__.pyi` exist?    </text>
|
||||
</g>
|
||||
<!-- determine_parent_kind->maybe_package -->
|
||||
<g id="edge10" class="edge">
|
||||
<title>determine_parent_kind->maybe_package</title>
|
||||
<path fill="none" stroke="black" d="M620.67,-956.53C500.71,-936.22 374.71,-901.62 374.71,-845.89 374.71,-845.89 374.71,-845.89 374.71,-531.8 374.71,-497.63 389.47,-461.73 403.41,-435.43"/>
|
||||
<polygon fill="black" stroke="black" points="406.39,-437.27 408.15,-426.82 400.26,-433.89 406.39,-437.27"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="386.71" y="-690.27" font-family="Times,serif" font-size="14.00">Yes</text>
|
||||
</g>
|
||||
<!-- namespace_parent1 -->
|
||||
<g id="node11" class="node">
|
||||
<title>namespace_parent1</title>
|
||||
<ellipse fill="none" stroke="black" cx="833.71" cy="-844.89" rx="212.31" ry="42.25"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="714.84" y="-857.47" font-family="Times,serif" font-size="14.00">        Is the direct parent package</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="727.59" y="-840.22" font-family="Times,serif" font-size="14.00">        a directory that contains</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="691.59" y="-822.97" font-family="Times,serif" font-size="14.00">        an `__init__.py` or `__init__.pyi`?    </text>
|
||||
</g>
|
||||
<!-- determine_parent_kind->namespace_parent1 -->
|
||||
<g id="edge11" class="edge">
|
||||
<title>determine_parent_kind->namespace_parent1</title>
|
||||
<path fill="none" stroke="black" d="M833.71,-940.17C833.71,-927.14 833.71,-912.56 833.71,-898.84"/>
|
||||
<polygon fill="black" stroke="black" points="837.21,-899.09 833.71,-889.09 830.21,-899.09 837.21,-899.09"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="843.84" y="-909.09" font-family="Times,serif" font-size="14.00">No</text>
|
||||
</g>
|
||||
<!-- maybe_package->package -->
|
||||
<g id="edge18" class="edge">
|
||||
<title>maybe_package->package</title>
|
||||
<path fill="none" stroke="black" d="M301.68,-366.89C269.45,-352.16 243.71,-329.51 243.71,-294.75 243.71,-294.75 243.71,-294.75 243.71,-114.25 243.71,-90.15 258.4,-67.38 273.12,-50.62"/>
|
||||
<polygon fill="black" stroke="black" points="275.47,-53.24 279.71,-43.53 270.34,-48.47 275.47,-53.24"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="255.71" y="-199.82" font-family="Times,serif" font-size="14.00">Yes</text>
|
||||
</g>
|
||||
<!-- maybe_module -->
|
||||
<g id="node15" class="node">
|
||||
<title>maybe_module</title>
|
||||
<ellipse fill="none" stroke="black" cx="521.71" cy="-293.75" rx="249.55" ry="18"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="351.84" y="-289.07" font-family="Times,serif" font-size="14.00">        Does `{path}.py` or `{path}.pyi` exist?    </text>
|
||||
</g>
|
||||
<!-- maybe_package->maybe_module -->
|
||||
<g id="edge19" class="edge">
|
||||
<title>maybe_package->maybe_module</title>
|
||||
<path fill="none" stroke="black" d="M455.41,-364.8C468.86,-350.58 484.87,-333.67 497.79,-320.03"/>
|
||||
<polygon fill="black" stroke="black" points="500.01,-322.77 504.34,-313.1 494.92,-317.96 500.01,-322.77"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="496.02" y="-333.7" font-family="Times,serif" font-size="14.00">No</text>
|
||||
</g>
|
||||
<!-- bail -->
|
||||
<g id="node12" class="node">
|
||||
<title>bail</title>
|
||||
<ellipse fill="none" stroke="black" cx="860.71" cy="-115.25" rx="306.9" ry="18"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="649.96" y="-110.58" font-family="Times,serif" font-size="14.00">        Is `module_name` set to a stub package candidate?    </text>
|
||||
</g>
|
||||
<!-- namespace_parent1->bail -->
|
||||
<g id="edge12" class="edge">
|
||||
<title>namespace_parent1->bail</title>
|
||||
<path fill="none" stroke="black" d="M878.81,-803.16C904.14,-775.71 930.71,-737.1 930.71,-695.95 930.71,-695.95 930.71,-695.95 930.71,-203.5 930.71,-178.3 912.73,-156.23 894.99,-140.59"/>
|
||||
<polygon fill="black" stroke="black" points="897.48,-138.11 887.55,-134.41 893.01,-143.49 897.48,-138.11"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="942.71" y="-390.38" font-family="Times,serif" font-size="14.00">Yes</text>
|
||||
</g>
|
||||
<!-- namespace_parent2 -->
|
||||
<g id="node13" class="node">
|
||||
<title>namespace_parent2</title>
|
||||
<ellipse fill="none" stroke="black" cx="659.71" cy="-694.95" rx="242.54" ry="54.45"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="529.59" y="-716.15" font-family="Times,serif" font-size="14.00">        Does the direct parent package</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="526.21" y="-698.9" font-family="Times,serif" font-size="14.00">        have a sibling file with the same</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="496.21" y="-681.65" font-family="Times,serif" font-size="14.00">        basename and a `py` or `pyi` extension?</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="650.71" y="-664.4" font-family="Times,serif" font-size="14.00">    </text>
|
||||
</g>
|
||||
<!-- namespace_parent1->namespace_parent2 -->
|
||||
<g id="edge13" class="edge">
|
||||
<title>namespace_parent1->namespace_parent2</title>
|
||||
<path fill="none" stroke="black" d="M786.17,-803.47C768.72,-788.63 748.58,-771.5 729.58,-755.35"/>
|
||||
<polygon fill="black" stroke="black" points="731.98,-752.79 722.09,-748.98 727.44,-758.13 731.98,-752.79"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="772.42" y="-771.34" font-family="Times,serif" font-size="14.00">No</text>
|
||||
</g>
|
||||
<!-- bail->not_found -->
|
||||
<g id="edge24" class="edge">
|
||||
<title>bail->not_found</title>
|
||||
<path fill="none" stroke="black" d="M924.27,-97.19C981.44,-81.85 1065.5,-59.29 1125.91,-43.08"/>
|
||||
<polygon fill="black" stroke="black" points="1126.68,-46.49 1135.44,-40.52 1124.87,-39.73 1126.68,-46.49"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="1061.2" y="-65.95" font-family="Times,serif" font-size="14.00">No</text>
|
||||
</g>
|
||||
<!-- retry -->
|
||||
<g id="node17" class="node">
|
||||
<title>retry</title>
|
||||
<ellipse fill="none" stroke="black" cx="860.71" cy="-22" rx="213.78" ry="18"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="716.34" y="-17.32" font-family="Times,serif" font-size="14.00">        Reset `module_name` to original    </text>
|
||||
</g>
|
||||
<!-- bail->retry -->
|
||||
<g id="edge25" class="edge">
|
||||
<title>bail->retry</title>
|
||||
<path fill="none" stroke="black" d="M860.71,-97.09C860.71,-84.3 860.71,-66.53 860.71,-51.61"/>
|
||||
<polygon fill="black" stroke="black" points="864.21,-51.91 860.71,-41.91 857.21,-51.91 864.21,-51.91"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="872.71" y="-65.95" font-family="Times,serif" font-size="14.00">Yes</text>
|
||||
</g>
|
||||
<!-- namespace_parent2->bail -->
|
||||
<g id="edge14" class="edge">
|
||||
<title>namespace_parent2->bail</title>
|
||||
<path fill="none" stroke="black" d="M794.49,-649.24C842.41,-624.2 884.71,-587.02 884.71,-533.8 884.71,-533.8 884.71,-533.8 884.71,-203.5 884.71,-183.2 878.6,-161.17 872.55,-144.33"/>
|
||||
<polygon fill="black" stroke="black" points="875.82,-143.08 868.98,-134.99 869.28,-145.58 875.82,-143.08"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="896.71" y="-333.7" font-family="Times,serif" font-size="14.00">Yes</text>
|
||||
</g>
|
||||
<!-- namespace_parent3 -->
|
||||
<g id="node14" class="node">
|
||||
<title>namespace_parent3</title>
|
||||
<ellipse fill="none" stroke="black" cx="629.71" cy="-532.8" rx="227.16" ry="54.45"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="496.96" y="-554" font-family="Times,serif" font-size="14.00">        Is every parent above the direct</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="481.59" y="-536.75" font-family="Times,serif" font-size="14.00">        parent package a normal package or</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="488.34" y="-519.5" font-family="Times,serif" font-size="14.00">        otherwise satisfy the previous two</text>
|
||||
<text xml:space="preserve" text-anchor="start" x="477.09" y="-502.25" font-family="Times,serif" font-size="14.00">        namespace package requirements?    </text>
|
||||
</g>
|
||||
<!-- namespace_parent2->namespace_parent3 -->
|
||||
<g id="edge15" class="edge">
|
||||
<title>namespace_parent2->namespace_parent3</title>
|
||||
<path fill="none" stroke="black" d="M649.64,-640.17C647.13,-626.77 644.41,-612.25 641.81,-598.38"/>
|
||||
<polygon fill="black" stroke="black" points="645.32,-598.1 640.04,-588.92 638.44,-599.39 645.32,-598.1"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="655.74" y="-609.2" font-family="Times,serif" font-size="14.00">No</text>
|
||||
</g>
|
||||
<!-- namespace_parent3->maybe_package -->
|
||||
<g id="edge17" class="edge">
|
||||
<title>namespace_parent3->maybe_package</title>
|
||||
<path fill="none" stroke="black" d="M554.21,-481.06C529.7,-464.59 503.04,-446.67 480.54,-431.56"/>
|
||||
<polygon fill="black" stroke="black" points="482.69,-428.78 472.43,-426.1 478.78,-434.59 482.69,-428.78"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="534.66" y="-447.05" font-family="Times,serif" font-size="14.00">Yes</text>
|
||||
</g>
|
||||
<!-- namespace_parent3->bail -->
|
||||
<g id="edge16" class="edge">
|
||||
<title>namespace_parent3->bail</title>
|
||||
<path fill="none" stroke="black" d="M754.07,-486.86C789.03,-465.83 817.71,-436.31 817.71,-396.05 817.71,-396.05 817.71,-396.05 817.71,-203.5 817.71,-181.34 829.02,-158.97 840.03,-142.43"/>
|
||||
<polygon fill="black" stroke="black" points="842.62,-144.83 845.52,-134.64 836.9,-140.8 842.62,-144.83"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="827.84" y="-289.07" font-family="Times,serif" font-size="14.00">No</text>
|
||||
</g>
|
||||
<!-- maybe_module->module -->
|
||||
<g id="edge20" class="edge">
|
||||
<title>maybe_module->module</title>
|
||||
<path fill="none" stroke="black" d="M465.44,-275.74C429.3,-263.58 382.11,-245.35 343.71,-222.5 338.06,-219.13 202.5,-104.78 138.54,-50.75"/>
|
||||
<polygon fill="black" stroke="black" points="141.07,-48.3 131.17,-44.52 136.55,-53.65 141.07,-48.3"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="289.08" y="-155.2" font-family="Times,serif" font-size="14.00">Yes</text>
|
||||
</g>
|
||||
<!-- maybe_namespace -->
|
||||
<g id="node16" class="node">
|
||||
<title>maybe_namespace</title>
|
||||
<ellipse fill="none" stroke="black" cx="521.71" cy="-204.5" rx="169.06" ry="18"/>
|
||||
<text xml:space="preserve" text-anchor="start" x="409.21" y="-199.82" font-family="Times,serif" font-size="14.00">        Is `{path}` a directory?    </text>
|
||||
</g>
|
||||
<!-- maybe_module->maybe_namespace -->
|
||||
<g id="edge21" class="edge">
|
||||
<title>maybe_module->maybe_namespace</title>
|
||||
<path fill="none" stroke="black" d="M521.71,-275.51C521.71,-263.68 521.71,-247.7 521.71,-234"/>
|
||||
<polygon fill="black" stroke="black" points="525.21,-234.22 521.71,-224.22 518.21,-234.22 525.21,-234.22"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="531.84" y="-244.45" font-family="Times,serif" font-size="14.00">No</text>
|
||||
</g>
|
||||
<!-- maybe_namespace->namespace_package -->
|
||||
<g id="edge22" class="edge">
|
||||
<title>maybe_namespace->namespace_package</title>
|
||||
<path fill="none" stroke="black" d="M520.76,-186.19C519.09,-156.13 515.64,-93.8 513.51,-55.47"/>
|
||||
<polygon fill="black" stroke="black" points="517.03,-55.71 512.98,-45.91 510.04,-56.09 517.03,-55.71"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="529.71" y="-110.58" font-family="Times,serif" font-size="14.00">Yes</text>
|
||||
</g>
|
||||
<!-- maybe_namespace->bail -->
|
||||
<g id="edge23" class="edge">
|
||||
<title>maybe_namespace->bail</title>
|
||||
<path fill="none" stroke="black" d="M584.29,-187.4C640.48,-172.93 723.17,-151.65 783.47,-136.13"/>
|
||||
<polygon fill="black" stroke="black" points="784.17,-139.56 792.99,-133.68 782.43,-132.78 784.17,-139.56"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="731.7" y="-155.2" font-family="Times,serif" font-size="14.00">No</text>
|
||||
</g>
|
||||
<!-- retry->determine_parent_kind -->
|
||||
<g id="edge26" class="edge">
|
||||
<title>retry->determine_parent_kind</title>
|
||||
<path fill="none" stroke="black" d="M1001.81,-35.84C1093.37,-48.22 1195.71,-71.6 1195.71,-114.25 1195.71,-845.89 1195.71,-845.89 1195.71,-845.89 1195.71,-889.15 1103.96,-923.26 1012.84,-946.47"/>
|
||||
<polygon fill="black" stroke="black" points="1012.05,-943.05 1003.2,-948.87 1013.75,-949.84 1012.05,-943.05"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 21 KiB |
@@ -1,13 +1,3 @@
|
||||
/*!
|
||||
This module principally provides two routines for resolving a particular module
|
||||
name to a `Module`: [`resolve_module`] and [`resolve_real_module`]. You'll
|
||||
usually want the former, unless you're certain you want to forbid stubs, in
|
||||
which case, use the latter.
|
||||
|
||||
For implementors, see `import-resolution-diagram.svg` for a flow diagram that
|
||||
specifies ty's implementation of Python's import resolution algorithm.
|
||||
*/
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::fmt;
|
||||
use std::iter::FusedIterator;
|
||||
@@ -62,7 +52,7 @@ impl ModuleResolveMode {
|
||||
/// This query should not be called directly. Instead, use [`resolve_module`]. It only exists
|
||||
/// because Salsa requires the module name to be an ingredient.
|
||||
#[salsa::tracked(heap_size=get_size2::GetSize::get_heap_size)]
|
||||
fn resolve_module_query<'db>(
|
||||
pub(crate) fn resolve_module_query<'db>(
|
||||
db: &'db dyn Db,
|
||||
module_name: ModuleNameIngredient<'db>,
|
||||
) -> Option<Module<'db>> {
|
||||
@@ -159,9 +149,8 @@ pub(crate) fn search_paths(db: &dyn Db) -> SearchPathIterator {
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct SearchPaths {
|
||||
/// Search paths that have been statically determined purely from reading
|
||||
/// ty's configuration settings. These shouldn't ever change unless the
|
||||
/// config settings themselves change.
|
||||
/// Search paths that have been statically determined purely from reading ty's configuration settings.
|
||||
/// These shouldn't ever change unless the config settings themselves change.
|
||||
static_paths: Vec<SearchPath>,
|
||||
|
||||
/// site-packages paths are not included in the above field:
|
||||
@@ -204,19 +193,19 @@ impl SearchPaths {
|
||||
|
||||
for path in extra_paths {
|
||||
let path = canonicalize(path, system);
|
||||
tracing::debug!("Adding extra search-path `{path}`");
|
||||
tracing::debug!("Adding extra search-path '{path}'");
|
||||
|
||||
static_paths.push(SearchPath::extra(system, path)?);
|
||||
}
|
||||
|
||||
for src_root in src_roots {
|
||||
tracing::debug!("Adding first-party search path `{src_root}`");
|
||||
tracing::debug!("Adding first-party search path '{src_root}'");
|
||||
static_paths.push(SearchPath::first_party(system, src_root.to_path_buf())?);
|
||||
}
|
||||
|
||||
let (typeshed_versions, stdlib_path) = if let Some(typeshed) = typeshed {
|
||||
let typeshed = canonicalize(typeshed, system);
|
||||
tracing::debug!("Adding custom-stdlib search path `{typeshed}`");
|
||||
tracing::debug!("Adding custom-stdlib search path '{typeshed}'");
|
||||
|
||||
let versions_path = typeshed.join("stdlib/VERSIONS");
|
||||
|
||||
@@ -245,22 +234,17 @@ impl SearchPaths {
|
||||
let mut site_packages: Vec<_> = Vec::with_capacity(site_packages_paths.len());
|
||||
|
||||
for path in site_packages_paths {
|
||||
tracing::debug!("Adding site-packages search path `{path}`");
|
||||
tracing::debug!("Adding site-packages search path '{path}'");
|
||||
site_packages.push(SearchPath::site_packages(system, path.clone())?);
|
||||
}
|
||||
|
||||
// TODO vendor typeshed's third-party stubs as well as the stdlib and
|
||||
// fallback to them as a final step?
|
||||
//
|
||||
// See: <https://github.com/astral-sh/ruff/pull/19620#discussion_r2240609135>
|
||||
// TODO vendor typeshed's third-party stubs as well as the stdlib and fallback to them as a final step
|
||||
|
||||
// Filter out module resolution paths that point to the same directory
|
||||
// on disk (the same invariant maintained by [`sys.path` at runtime]).
|
||||
// (Paths may, however, *overlap* -- e.g. you could have both `src/`
|
||||
// and `src/foo` as module resolution paths simultaneously.)
|
||||
// Filter out module resolution paths that point to the same directory on disk (the same invariant maintained by [`sys.path` at runtime]).
|
||||
// (Paths may, however, *overlap* -- e.g. you could have both `src/` and `src/foo`
|
||||
// as module resolution paths simultaneously.)
|
||||
//
|
||||
// This code doesn't use an `IndexSet` because the key is the system
|
||||
// path and not the search root.
|
||||
// This code doesn't use an `IndexSet` because the key is the system path and not the search root.
|
||||
//
|
||||
// [`sys.path` at runtime]: https://docs.python.org/3/library/site.html#module-site
|
||||
let mut seen_paths = FxHashSet::with_capacity_and_hasher(static_paths.len(), FxBuildHasher);
|
||||
@@ -537,7 +521,7 @@ impl<'db> Iterator for PthFileIterator<'db> {
|
||||
let contents = match system.read_to_string(&path) {
|
||||
Ok(contents) => contents,
|
||||
Err(error) => {
|
||||
tracing::warn!("Failed to read .pth file `{path}`: {error}");
|
||||
tracing::warn!("Failed to read .pth file '{path}': {error}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
@@ -598,8 +582,7 @@ fn resolve_name(db: &dyn Db, name: &ModuleName, mode: ModuleResolveMode) -> Opti
|
||||
Ok((package_kind, ResolvedName::FileModule(module))) => {
|
||||
if package_kind.is_root() && module.kind.is_module() {
|
||||
tracing::trace!(
|
||||
"Search path `{search_path}` contains a module \
|
||||
named `{stub_name}` but a standalone module isn't a valid stub."
|
||||
"Search path '{search_path} contains a module named `{stub_name}` but a standalone module isn't a valid stub."
|
||||
);
|
||||
} else {
|
||||
return Some(ResolvedName::FileModule(module));
|
||||
@@ -610,12 +593,12 @@ fn resolve_name(db: &dyn Db, name: &ModuleName, mode: ModuleResolveMode) -> Opti
|
||||
}
|
||||
Err(PackageKind::Root) => {
|
||||
tracing::trace!(
|
||||
"Search path `{search_path}` contains no stub package named `{stub_name}`."
|
||||
"Search path '{search_path}' contains no stub package named `{stub_name}`."
|
||||
);
|
||||
}
|
||||
Err(PackageKind::Regular) => {
|
||||
tracing::trace!(
|
||||
"Stub-package in `{search_path}` doesn't contain module: `{name}`"
|
||||
"Stub-package in `{search_path} doesn't contain module: `{name}`"
|
||||
);
|
||||
// stub exists, but the module doesn't.
|
||||
// TODO: Support partial packages.
|
||||
@@ -623,8 +606,7 @@ fn resolve_name(db: &dyn Db, name: &ModuleName, mode: ModuleResolveMode) -> Opti
|
||||
}
|
||||
Err(PackageKind::Namespace) => {
|
||||
tracing::trace!(
|
||||
"Stub-package in `{search_path}` doesn't contain module: \
|
||||
`{name}` but it is a namespace package, keep going."
|
||||
"Stub-package in `{search_path} doesn't contain module: `{name}` but it is a namespace package, keep going."
|
||||
);
|
||||
// stub exists, but the module doesn't. But this is a namespace package,
|
||||
// keep searching the next search path for a stub package with the same name.
|
||||
@@ -643,19 +625,18 @@ fn resolve_name(db: &dyn Db, name: &ModuleName, mode: ModuleResolveMode) -> Opti
|
||||
Err(kind) => match kind {
|
||||
PackageKind::Root => {
|
||||
tracing::trace!(
|
||||
"Search path `{search_path}` contains no package named `{name}`."
|
||||
"Search path '{search_path}' contains no package named `{name}`."
|
||||
);
|
||||
}
|
||||
PackageKind::Regular => {
|
||||
// For regular packages, don't search the next search path. All files of that
|
||||
// package must be in the same location
|
||||
tracing::trace!("Package in `{search_path}` doesn't contain module: `{name}`");
|
||||
tracing::trace!("Package in `{search_path} doesn't contain module: `{name}`");
|
||||
return None;
|
||||
}
|
||||
PackageKind::Namespace => {
|
||||
tracing::trace!(
|
||||
"Package in `{search_path}` doesn't contain module: \
|
||||
`{name}` but it is a namespace package, keep going."
|
||||
"Package in `{search_path} doesn't contain module: `{name}` but it is a namespace package, keep going."
|
||||
);
|
||||
}
|
||||
},
|
||||
@@ -676,9 +657,7 @@ enum ResolvedName {
|
||||
|
||||
/// The module name resolved to a namespace package.
|
||||
///
|
||||
/// For example, `from opentelemetry import trace, metrics` where
|
||||
/// `opentelemetry` is a namespace package (and `trace` and `metrics` are
|
||||
/// sub packages).
|
||||
/// For example, `from opentelemetry import trace, metrics` where `opentelemetry` is a namespace package (and `trace` and `metrics` are sub packages).
|
||||
NamespacePackage,
|
||||
}
|
||||
|
||||
@@ -689,20 +668,6 @@ struct ResolvedFileModule {
|
||||
file: File,
|
||||
}
|
||||
|
||||
/// Attempts to resolve a module name in a particular search path.
|
||||
///
|
||||
/// `search_path` should be the directory to start looking for the module.
|
||||
///
|
||||
/// `name` should be a complete non-empty module name, e.g, `foo` or
|
||||
/// `foo.bar.baz`.
|
||||
///
|
||||
/// Upon success, this returns the kind of the parent package (root, regular
|
||||
/// package or namespace package) along with the resolved details of the
|
||||
/// module: its kind (single-file module or package), the search path in
|
||||
/// which it was found (guaranteed to be equal to the one given) and the
|
||||
/// corresponding `File`.
|
||||
///
|
||||
/// Upon error, the kind of the parent package is returned.
|
||||
fn resolve_name_in_search_path(
|
||||
context: &ResolverContext,
|
||||
name: &RelaxedModuleName,
|
||||
@@ -744,23 +709,19 @@ fn resolve_name_in_search_path(
|
||||
));
|
||||
}
|
||||
|
||||
// Last resort, check if a folder with the given name exists. If so,
|
||||
// then this is a namespace package. We need to skip this check for
|
||||
// typeshed because the `resolve_file_module` can also return `None` if the
|
||||
// `__init__.py` exists but isn't available for the current Python version.
|
||||
// Let's assume that the `xml` module is only available on Python 3.11+ and
|
||||
// we're resolving for Python 3.10:
|
||||
// Last resort, check if a folder with the given name exists.
|
||||
// If so, then this is a namespace package.
|
||||
// We need to skip this check for typeshed because the `resolve_file_module` can also return `None`
|
||||
// if the `__init__.py` exists but isn't available for the current Python version.
|
||||
// Let's assume that the `xml` module is only available on Python 3.11+ and we're resolving for Python 3.10:
|
||||
// * `resolve_file_module("xml/__init__.pyi")` returns `None` even though the file exists but the
|
||||
// module isn't available for the current Python version.
|
||||
// * The check here would now return `true` because the `xml` directory exists, resulting
|
||||
// in a false positive for a namespace package.
|
||||
//
|
||||
// * `resolve_file_module("xml/__init__.pyi")` returns `None` even though
|
||||
// the file exists but the module isn't available for the current Python
|
||||
// version.
|
||||
// * The check here would now return `true` because the `xml` directory
|
||||
// exists, resulting in a false positive for a namespace package.
|
||||
//
|
||||
// Since typeshed doesn't use any namespace packages today (May 2025),
|
||||
// simply skip this check which also helps performance. If typeshed
|
||||
// ever uses namespace packages, ensure that this check also takes the
|
||||
// `VERSIONS` file into consideration.
|
||||
// Since typeshed doesn't use any namespace packages today (May 2025), simply skip this
|
||||
// check which also helps performance. If typeshed ever uses namespace packages, ensure that
|
||||
// this check also takes the `VERSIONS` file into consideration.
|
||||
if !search_path.is_standard_library() && package_path.is_directory(context) {
|
||||
if let Some(path) = package_path.to_system_path() {
|
||||
let system = context.db.system();
|
||||
@@ -812,20 +773,6 @@ fn resolve_file_module(module: &ModulePath, resolver_state: &ResolverContext) ->
|
||||
Some(file)
|
||||
}
|
||||
|
||||
/// Attempt to resolve the parent package of a module.
|
||||
///
|
||||
/// `module_search_path` should be the directory to start looking for the
|
||||
/// parent package.
|
||||
///
|
||||
/// `components` should be the full module name of the parent package. This
|
||||
/// specifically should not include the basename of the module. So e.g.,
|
||||
/// for `foo.bar.baz`, `components` should be `[foo, bar]`. It follows that
|
||||
/// `components` may be empty (in which case, the parent package is the root).
|
||||
///
|
||||
/// Upon success, the path to the package and its "kind" (root, regular or
|
||||
/// namespace) is returned. Upon error, the kind of the package is still
|
||||
/// returned based on how many components were found and whether `__init__.py`
|
||||
/// is present.
|
||||
fn resolve_package<'a, 'db, I>(
|
||||
module_search_path: &SearchPath,
|
||||
components: I,
|
||||
@@ -844,7 +791,7 @@ where
|
||||
// `true` if resolving a sub-package. For example, `true` when resolving `bar` of `foo.bar`.
|
||||
let mut in_sub_package = false;
|
||||
|
||||
// For `foo.bar.baz`, test that `foo` and `bar` both contain a `__init__.py`.
|
||||
// For `foo.bar.baz`, test that `foo` and `baz` both contain a `__init__.py`.
|
||||
for folder in components {
|
||||
package_path.push(folder);
|
||||
|
||||
@@ -856,8 +803,7 @@ where
|
||||
// Pure modules hide namespace packages with the same name
|
||||
&& resolve_file_module(&package_path, resolver_state).is_none()
|
||||
{
|
||||
// A directory without an `__init__.py(i)` is a namespace package,
|
||||
// continue with the next folder.
|
||||
// A directory without an `__init__.py(i)` is a namespace package, continue with the next folder.
|
||||
in_namespace_package = true;
|
||||
} else if in_namespace_package {
|
||||
// Package not found but it is part of a namespace package.
|
||||
@@ -903,11 +849,9 @@ enum PackageKind {
|
||||
/// For example, `bar` in `foo.bar` when the `foo` directory contains an `__init__.py`.
|
||||
Regular,
|
||||
|
||||
/// A sub-package in a namespace package. A namespace package is a package
|
||||
/// without an `__init__.py`.
|
||||
/// A sub-package in a namespace package. A namespace package is a package without an `__init__.py`.
|
||||
///
|
||||
/// For example, `bar` in `foo.bar` if the `foo` directory contains no
|
||||
/// `__init__.py`.
|
||||
/// For example, `bar` in `foo.bar` if the `foo` directory contains no `__init__.py`.
|
||||
Namespace,
|
||||
}
|
||||
|
||||
@@ -1544,8 +1488,8 @@ mod tests {
|
||||
db.memory_file_system().remove_file(&bar_path).unwrap();
|
||||
bar.sync(&mut db);
|
||||
|
||||
// Re-query the foo module. The foo module should still be cached
|
||||
// because `bar.py` isn't relevant for resolving `foo`.
|
||||
// Re-query the foo module. The foo module should still be cached because `bar.py` isn't relevant
|
||||
// for resolving `foo`.
|
||||
|
||||
let foo_module2 = resolve_module(&db, &foo_module_name);
|
||||
let foo_pieces2 = foo_module2.map(|foo_module2| {
|
||||
@@ -2079,9 +2023,8 @@ not_a_directory
|
||||
db.write_file(src.join("main.py"), "print('Hy')")
|
||||
.context("Failed to write `main.py`")?;
|
||||
|
||||
// The symlink triggers the slow-path in the `OsSystem`'s
|
||||
// `exists_path_case_sensitive` code because canonicalizing the path
|
||||
// for `a/__init__.py` results in `a-package/__init__.py`
|
||||
// The symlink triggers the slow-path in the `OsSystem`'s `exists_path_case_sensitive`
|
||||
// code because canonicalizing the path for `a/__init__.py` results in `a-package/__init__.py`
|
||||
std::os::unix::fs::symlink(a_package_target.as_std_path(), a_src.as_std_path())
|
||||
.context("Failed to symlink `src/a` to `a-package`")?;
|
||||
|
||||
@@ -2100,8 +2043,7 @@ not_a_directory
|
||||
let a_module_name = ModuleName::new_static("A").unwrap();
|
||||
assert_eq!(resolve_module(&db, &a_module_name), None);
|
||||
|
||||
// Now lookup the same module using the lowercase `a` and it should
|
||||
// resolve to the file in the system site-packages
|
||||
// Now lookup the same module using the lowercase `a` and it should resolve to the file in the system site-packages
|
||||
let a_module_name = ModuleName::new_static("a").unwrap();
|
||||
let a_module = resolve_module(&db, &a_module_name).expect("a.py to resolve");
|
||||
assert!(
|
||||
|
||||
@@ -1596,10 +1596,7 @@ impl<'db> ClassLiteral<'db> {
|
||||
|
||||
let field_policy = CodeGeneratorKind::from_class(db, self)?;
|
||||
|
||||
let instance_ty =
|
||||
Type::instance(db, self.apply_optional_specialization(db, specialization));
|
||||
|
||||
let signature_from_fields = |mut parameters: Vec<_>, return_ty: Option<Type<'db>>| {
|
||||
let signature_from_fields = |mut parameters: Vec<_>| {
|
||||
let mut kw_only_field_seen = false;
|
||||
for (
|
||||
field_name,
|
||||
@@ -1672,26 +1669,21 @@ impl<'db> ClassLiteral<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
let mut parameter = if kw_only_field_seen || name == "__replace__" {
|
||||
let mut parameter = if kw_only_field_seen {
|
||||
Parameter::keyword_only(field_name)
|
||||
} else {
|
||||
Parameter::positional_or_keyword(field_name)
|
||||
}
|
||||
.with_annotated_type(field_ty);
|
||||
|
||||
if name == "__replace__" {
|
||||
// When replacing, we know there is a default value for the field
|
||||
// (the value that is currently assigned to the field)
|
||||
// assume this to be the declared type of the field
|
||||
parameter = parameter.with_default_type(field_ty);
|
||||
} else if let Some(default_ty) = default_ty {
|
||||
if let Some(default_ty) = default_ty {
|
||||
parameter = parameter.with_default_type(default_ty);
|
||||
}
|
||||
|
||||
parameters.push(parameter);
|
||||
}
|
||||
|
||||
let mut signature = Signature::new(Parameters::new(parameters), return_ty);
|
||||
let mut signature = Signature::new(Parameters::new(parameters), Some(Type::none(db)));
|
||||
signature.inherited_generic_context = self.generic_context(db);
|
||||
Some(CallableType::function_like(db, signature))
|
||||
};
|
||||
@@ -1709,13 +1701,16 @@ impl<'db> ClassLiteral<'db> {
|
||||
|
||||
let self_parameter = Parameter::positional_or_keyword(Name::new_static("self"))
|
||||
// TODO: could be `Self`.
|
||||
.with_annotated_type(instance_ty);
|
||||
signature_from_fields(vec![self_parameter], Some(Type::none(db)))
|
||||
.with_annotated_type(Type::instance(
|
||||
db,
|
||||
self.apply_optional_specialization(db, specialization),
|
||||
));
|
||||
signature_from_fields(vec![self_parameter])
|
||||
}
|
||||
(CodeGeneratorKind::NamedTuple, "__new__") => {
|
||||
let cls_parameter = Parameter::positional_or_keyword(Name::new_static("cls"))
|
||||
.with_annotated_type(KnownClass::Type.to_instance(db));
|
||||
signature_from_fields(vec![cls_parameter], Some(Type::none(db)))
|
||||
signature_from_fields(vec![cls_parameter])
|
||||
}
|
||||
(CodeGeneratorKind::DataclassLike, "__lt__" | "__le__" | "__gt__" | "__ge__") => {
|
||||
if !has_dataclass_param(DataclassParams::ORDER) {
|
||||
@@ -1726,10 +1721,16 @@ impl<'db> ClassLiteral<'db> {
|
||||
Parameters::new([
|
||||
Parameter::positional_or_keyword(Name::new_static("self"))
|
||||
// TODO: could be `Self`.
|
||||
.with_annotated_type(instance_ty),
|
||||
.with_annotated_type(Type::instance(
|
||||
db,
|
||||
self.apply_optional_specialization(db, specialization),
|
||||
)),
|
||||
Parameter::positional_or_keyword(Name::new_static("other"))
|
||||
// TODO: could be `Self`.
|
||||
.with_annotated_type(instance_ty),
|
||||
.with_annotated_type(Type::instance(
|
||||
db,
|
||||
self.apply_optional_specialization(db, specialization),
|
||||
)),
|
||||
]),
|
||||
Some(KnownClass::Bool.to_instance(db)),
|
||||
);
|
||||
@@ -1744,20 +1745,15 @@ impl<'db> ClassLiteral<'db> {
|
||||
.place
|
||||
.ignore_possibly_unbound()
|
||||
}
|
||||
(CodeGeneratorKind::DataclassLike, "__replace__")
|
||||
if Program::get(db).python_version(db) >= PythonVersion::PY313 =>
|
||||
{
|
||||
let self_parameter = Parameter::positional_or_keyword(Name::new_static("self"))
|
||||
.with_annotated_type(instance_ty);
|
||||
|
||||
signature_from_fields(vec![self_parameter], Some(instance_ty))
|
||||
}
|
||||
(CodeGeneratorKind::DataclassLike, "__setattr__") => {
|
||||
if has_dataclass_param(DataclassParams::FROZEN) {
|
||||
let signature = Signature::new(
|
||||
Parameters::new([
|
||||
Parameter::positional_or_keyword(Name::new_static("self"))
|
||||
.with_annotated_type(instance_ty),
|
||||
.with_annotated_type(Type::instance(
|
||||
db,
|
||||
self.apply_optional_specialization(db, specialization),
|
||||
)),
|
||||
Parameter::positional_or_keyword(Name::new_static("name")),
|
||||
Parameter::positional_or_keyword(Name::new_static("value")),
|
||||
]),
|
||||
|
||||
@@ -10905,8 +10905,8 @@ fn contains_string_literal(expr: &ast::Expr) -> bool {
|
||||
|
||||
/// Map based on a `Vec`. It doesn't enforce
|
||||
/// uniqueness on insertion. Instead, it relies on the caller
|
||||
/// that elements are unique. For example, the way we visit definitions
|
||||
/// in the `TypeInference` builder already implicitly guarantees that each definition
|
||||
/// that elements are uniuqe. For example, the way we visit definitions
|
||||
/// in the `TypeInference` builder make already implicitly guarantees that each definition
|
||||
/// is only visited once.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
struct VecMap<K, V>(Vec<(K, V)>);
|
||||
|
||||
@@ -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.7-alpine
|
||||
name: ghcr.io/astral-sh/ruff:0.12.5-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.7
|
||||
rev: v0.12.5
|
||||
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.7
|
||||
rev: v0.12.5
|
||||
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.7
|
||||
rev: v0.12.5
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff
|
||||
|
||||
@@ -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.7
|
||||
rev: v0.12.5
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff
|
||||
|
||||
@@ -4,12 +4,13 @@ build-backend = "maturin"
|
||||
|
||||
[project]
|
||||
name = "ruff"
|
||||
version = "0.12.7"
|
||||
version = "0.12.5"
|
||||
description = "An extremely fast Python linter and code formatter, written in Rust."
|
||||
authors = [{ name = "Astral Software Inc.", email = "hey@astral.sh" }]
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.7"
|
||||
license = { file = "LICENSE" }
|
||||
license = "MIT"
|
||||
license-files = ["LICENSE"]
|
||||
keywords = [
|
||||
"automation",
|
||||
"flake8",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "scripts"
|
||||
version = "0.12.7"
|
||||
version = "0.12.5"
|
||||
description = ""
|
||||
authors = ["Charles Marsh <charlie.r.marsh@gmail.com>"]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user