diff --git a/crates/ruff_linter/resources/test/fixtures/pycodestyle/E402.py b/crates/ruff_linter/resources/test/fixtures/pycodestyle/E402_0.py similarity index 100% rename from crates/ruff_linter/resources/test/fixtures/pycodestyle/E402.py rename to crates/ruff_linter/resources/test/fixtures/pycodestyle/E402_0.py diff --git a/crates/ruff_linter/resources/test/fixtures/pycodestyle/E402_1.py b/crates/ruff_linter/resources/test/fixtures/pycodestyle/E402_1.py new file mode 100644 index 0000000000000..12c36ae127ba9 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/pycodestyle/E402_1.py @@ -0,0 +1,9 @@ +import a + +"""Some other docstring.""" + +import b + +"""Some other docstring.""" + +import c diff --git a/crates/ruff_linter/resources/test/fixtures/pyflakes/F404.py b/crates/ruff_linter/resources/test/fixtures/pyflakes/F404_0.py similarity index 100% rename from crates/ruff_linter/resources/test/fixtures/pyflakes/F404.py rename to crates/ruff_linter/resources/test/fixtures/pyflakes/F404_0.py diff --git a/crates/ruff_linter/resources/test/fixtures/pyflakes/F404_1.py b/crates/ruff_linter/resources/test/fixtures/pyflakes/F404_1.py new file mode 100644 index 0000000000000..97b4292da91fa --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/pyflakes/F404_1.py @@ -0,0 +1,5 @@ +"""Docstring""" + +"""Non-docstring""" + +from __future__ import absolute_import diff --git a/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP025.py b/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP025.py index d53c197a1f65a..899f827f80866 100644 --- a/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP025.py +++ b/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP025.py @@ -1,30 +1,28 @@ -# These should change -x = u"Hello" +u"Hello" -u'world' +x = u"Hello" # UP025 -print(u"Hello") +u'world' # UP025 -print(u'world') +print(u"Hello") # UP025 -import foo - -foo(u"Hello", U"world", a=u"Hello", b=u"world") +print(u'world') # UP025 -# These should stay quoted they way they are +import foo -x = u'hello' -x = u"""hello""" -x = u'''hello''' -x = u'Hello "World"' +foo(u"Hello", U"world", a=u"Hello", b=u"world") # UP025 -# These should not change -u = "Hello" +# Retain quotes when fixing. +x = u'hello' # UP025 +x = u"""hello""" # UP025 +x = u'''hello''' # UP025 +x = u'Hello "World"' # UP025 -u = u +u = "Hello" # OK +u = u # OK def hello(): - return"Hello" + return"Hello" # OK -f"foo"u"bar" -f"foo" u"bar" +f"foo"u"bar" # OK +f"foo" u"bar" # OK diff --git a/crates/ruff_linter/src/checkers/ast/mod.rs b/crates/ruff_linter/src/checkers/ast/mod.rs index fa47d9c77cc44..93396178d488d 100644 --- a/crates/ruff_linter/src/checkers/ast/mod.rs +++ b/crates/ruff_linter/src/checkers/ast/mod.rs @@ -287,7 +287,18 @@ where // Track whether we've seen docstrings, non-imports, etc. match stmt { + Stmt::Expr(ast::StmtExpr { value, .. }) + if !self + .semantic + .flags + .intersects(SemanticModelFlags::MODULE_DOCSTRING) + && value.is_string_literal_expr() => + { + self.semantic.flags |= SemanticModelFlags::MODULE_DOCSTRING; + } Stmt::ImportFrom(ast::StmtImportFrom { module, names, .. }) => { + self.semantic.flags |= SemanticModelFlags::MODULE_DOCSTRING; + // Allow __future__ imports until we see a non-__future__ import. if let Some("__future__") = module.as_deref() { if names @@ -301,9 +312,11 @@ where } } Stmt::Import(_) => { + self.semantic.flags |= SemanticModelFlags::MODULE_DOCSTRING; self.semantic.flags |= SemanticModelFlags::FUTURES_BOUNDARY; } _ => { + self.semantic.flags |= SemanticModelFlags::MODULE_DOCSTRING; self.semantic.flags |= SemanticModelFlags::FUTURES_BOUNDARY; if !(self.semantic.seen_import_boundary() || helpers::is_assignment_to_a_dunder(stmt) @@ -1435,11 +1448,8 @@ where impl<'a> Checker<'a> { /// Visit a [`Module`]. Returns `true` if the module contains a module-level docstring. - fn visit_module(&mut self, python_ast: &'a Suite) -> bool { + fn visit_module(&mut self, python_ast: &'a Suite) { analyze::module(python_ast, self); - - let docstring = docstrings::extraction::docstring_from(python_ast); - docstring.is_some() } /// Visit a list of [`Comprehension`] nodes, assumed to be the comprehensions that compose a @@ -2006,14 +2016,8 @@ pub(crate) fn check_ast( ); checker.bind_builtins(); - // Check for module docstring. - let python_ast = if checker.visit_module(python_ast) { - &python_ast[1..] - } else { - python_ast - }; - // Iterate over the AST. + checker.visit_module(python_ast); checker.visit_body(python_ast); // Visit any deferred syntax nodes. Take care to visit in order, such that we avoid adding diff --git a/crates/ruff_linter/src/rules/pycodestyle/mod.rs b/crates/ruff_linter/src/rules/pycodestyle/mod.rs index 008118ceeb48d..4c97f01c713e5 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/mod.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/mod.rs @@ -35,7 +35,8 @@ mod tests { #[test_case(Rule::LineTooLong, Path::new("E501_3.py"))] #[test_case(Rule::MixedSpacesAndTabs, Path::new("E101.py"))] #[test_case(Rule::ModuleImportNotAtTopOfFile, Path::new("E40.py"))] - #[test_case(Rule::ModuleImportNotAtTopOfFile, Path::new("E402.py"))] + #[test_case(Rule::ModuleImportNotAtTopOfFile, Path::new("E402_0.py"))] + #[test_case(Rule::ModuleImportNotAtTopOfFile, Path::new("E402_1.py"))] #[test_case(Rule::ModuleImportNotAtTopOfFile, Path::new("E402.ipynb"))] #[test_case(Rule::MultipleImportsOnOneLine, Path::new("E40.py"))] #[test_case(Rule::MultipleStatementsOnOneLineColon, Path::new("E70.py"))] @@ -65,7 +66,7 @@ mod tests { } #[test_case(Rule::IsLiteral, Path::new("constant_literals.py"))] - #[test_case(Rule::ModuleImportNotAtTopOfFile, Path::new("E402.py"))] + #[test_case(Rule::ModuleImportNotAtTopOfFile, Path::new("E402_0.py"))] #[test_case(Rule::TypeComparison, Path::new("E721.py"))] fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> { let snapshot = format!( diff --git a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E402_E402.py.snap b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E402_E402_0.py.snap similarity index 63% rename from crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E402_E402.py.snap rename to crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E402_E402_0.py.snap index 072290ae87cf6..29ab0d19c9a67 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E402_E402.py.snap +++ b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E402_E402_0.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/pycodestyle/mod.rs --- -E402.py:25:1: E402 Module level import not at top of file +E402_0.py:25:1: E402 Module level import not at top of file | 23 | sys.path.insert(0, "some/path") 24 | @@ -11,7 +11,7 @@ E402.py:25:1: E402 Module level import not at top of file 27 | import matplotlib | -E402.py:27:1: E402 Module level import not at top of file +E402_0.py:27:1: E402 Module level import not at top of file | 25 | import f 26 | @@ -21,7 +21,7 @@ E402.py:27:1: E402 Module level import not at top of file 29 | matplotlib.use("Agg") | -E402.py:31:1: E402 Module level import not at top of file +E402_0.py:31:1: E402 Module level import not at top of file | 29 | matplotlib.use("Agg") 30 | @@ -31,7 +31,7 @@ E402.py:31:1: E402 Module level import not at top of file 33 | __some__magic = 1 | -E402.py:35:1: E402 Module level import not at top of file +E402_0.py:35:1: E402 Module level import not at top of file | 33 | __some__magic = 1 34 | @@ -39,7 +39,7 @@ E402.py:35:1: E402 Module level import not at top of file | ^^^^^^^^ E402 | -E402.py:45:1: E402 Module level import not at top of file +E402_0.py:45:1: E402 Module level import not at top of file | 43 | import j 44 | @@ -47,7 +47,7 @@ E402.py:45:1: E402 Module level import not at top of file | ^^^^^^^^ E402 | -E402.py:45:11: E402 Module level import not at top of file +E402_0.py:45:11: E402 Module level import not at top of file | 43 | import j 44 | diff --git a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E402_E402_1.py.snap b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E402_E402_1.py.snap new file mode 100644 index 0000000000000..069d635ce46e1 --- /dev/null +++ b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E402_E402_1.py.snap @@ -0,0 +1,22 @@ +--- +source: crates/ruff_linter/src/rules/pycodestyle/mod.rs +--- +E402_1.py:5:1: E402 Module level import not at top of file + | +3 | """Some other docstring.""" +4 | +5 | import b + | ^^^^^^^^ E402 +6 | +7 | """Some other docstring.""" + | + +E402_1.py:9:1: E402 Module level import not at top of file + | +7 | """Some other docstring.""" +8 | +9 | import c + | ^^^^^^^^ E402 + | + + diff --git a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__preview__E402_E402.py.snap b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__preview__E402_E402_0.py.snap similarity index 62% rename from crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__preview__E402_E402.py.snap rename to crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__preview__E402_E402_0.py.snap index 7ec9e200b32e7..197644bcd690f 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__preview__E402_E402.py.snap +++ b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__preview__E402_E402_0.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/pycodestyle/mod.rs --- -E402.py:35:1: E402 Module level import not at top of file +E402_0.py:35:1: E402 Module level import not at top of file | 33 | __some__magic = 1 34 | @@ -9,7 +9,7 @@ E402.py:35:1: E402 Module level import not at top of file | ^^^^^^^^ E402 | -E402.py:45:1: E402 Module level import not at top of file +E402_0.py:45:1: E402 Module level import not at top of file | 43 | import j 44 | @@ -17,7 +17,7 @@ E402.py:45:1: E402 Module level import not at top of file | ^^^^^^^^ E402 | -E402.py:45:11: E402 Module level import not at top of file +E402_0.py:45:11: E402 Module level import not at top of file | 43 | import j 44 | diff --git a/crates/ruff_linter/src/rules/pyflakes/mod.rs b/crates/ruff_linter/src/rules/pyflakes/mod.rs index 721e7dceb7256..e4a313f54b40d 100644 --- a/crates/ruff_linter/src/rules/pyflakes/mod.rs +++ b/crates/ruff_linter/src/rules/pyflakes/mod.rs @@ -55,7 +55,8 @@ mod tests { #[test_case(Rule::UnusedImport, Path::new("F401_20.py"))] #[test_case(Rule::ImportShadowedByLoopVar, Path::new("F402.py"))] #[test_case(Rule::UndefinedLocalWithImportStar, Path::new("F403.py"))] - #[test_case(Rule::LateFutureImport, Path::new("F404.py"))] + #[test_case(Rule::LateFutureImport, Path::new("F404_0.py"))] + #[test_case(Rule::LateFutureImport, Path::new("F404_1.py"))] #[test_case(Rule::UndefinedLocalWithImportStarUsage, Path::new("F405.py"))] #[test_case(Rule::UndefinedLocalWithNestedImportStarUsage, Path::new("F406.py"))] #[test_case(Rule::FutureFeatureNotDefined, Path::new("F407.py"))] diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F404_F404.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F404_F404_0.py.snap similarity index 72% rename from crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F404_F404.py.snap rename to crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F404_F404_0.py.snap index fee99fc907f0d..6934119f5b95a 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F404_F404.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F404_F404_0.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/pyflakes/mod.rs --- -F404.py:6:1: F404 `from __future__` imports must occur at the beginning of the file +F404_0.py:6:1: F404 `from __future__` imports must occur at the beginning of the file | 4 | from collections import namedtuple 5 | diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F404_F404_1.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F404_F404_1.py.snap new file mode 100644 index 0000000000000..c9fbd78db83dd --- /dev/null +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F404_F404_1.py.snap @@ -0,0 +1,12 @@ +--- +source: crates/ruff_linter/src/rules/pyflakes/mod.rs +--- +F404_1.py:5:1: F404 `from __future__` imports must occur at the beginning of the file + | +3 | """Non-docstring""" +4 | +5 | from __future__ import absolute_import + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ F404 + | + + diff --git a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP025.py.snap b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP025.py.snap index 256e6222ec329..e5cd73cc97314 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP025.py.snap +++ b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP025.py.snap @@ -1,284 +1,302 @@ --- source: crates/ruff_linter/src/rules/pyupgrade/mod.rs --- -UP025.py:2:5: UP025 [*] Remove unicode literals from strings +UP025.py:1:1: UP025 [*] Remove unicode literals from strings | -1 | # These should change -2 | x = u"Hello" +1 | u"Hello" + | ^^^^^^^^ UP025 +2 | +3 | x = u"Hello" # UP025 + | + = help: Remove unicode prefix + +ℹ Safe fix +1 |-u"Hello" + 1 |+"Hello" +2 2 | +3 3 | x = u"Hello" # UP025 +4 4 | + +UP025.py:3:5: UP025 [*] Remove unicode literals from strings + | +1 | u"Hello" +2 | +3 | x = u"Hello" # UP025 | ^^^^^^^^ UP025 -3 | -4 | u'world' +4 | +5 | u'world' # UP025 | = help: Remove unicode prefix ℹ Safe fix -1 1 | # These should change -2 |-x = u"Hello" - 2 |+x = "Hello" -3 3 | -4 4 | u'world' -5 5 | +1 1 | u"Hello" +2 2 | +3 |-x = u"Hello" # UP025 + 3 |+x = "Hello" # UP025 +4 4 | +5 5 | u'world' # UP025 +6 6 | -UP025.py:4:1: UP025 [*] Remove unicode literals from strings +UP025.py:5:1: UP025 [*] Remove unicode literals from strings | -2 | x = u"Hello" -3 | -4 | u'world' +3 | x = u"Hello" # UP025 +4 | +5 | u'world' # UP025 | ^^^^^^^^ UP025 -5 | -6 | print(u"Hello") +6 | +7 | print(u"Hello") # UP025 | = help: Remove unicode prefix ℹ Safe fix -1 1 | # These should change -2 2 | x = u"Hello" -3 3 | -4 |-u'world' - 4 |+'world' -5 5 | -6 6 | print(u"Hello") -7 7 | +2 2 | +3 3 | x = u"Hello" # UP025 +4 4 | +5 |-u'world' # UP025 + 5 |+'world' # UP025 +6 6 | +7 7 | print(u"Hello") # UP025 +8 8 | -UP025.py:6:7: UP025 [*] Remove unicode literals from strings +UP025.py:7:7: UP025 [*] Remove unicode literals from strings | -4 | u'world' -5 | -6 | print(u"Hello") +5 | u'world' # UP025 +6 | +7 | print(u"Hello") # UP025 | ^^^^^^^^ UP025 -7 | -8 | print(u'world') +8 | +9 | print(u'world') # UP025 | = help: Remove unicode prefix ℹ Safe fix -3 3 | -4 4 | u'world' -5 5 | -6 |-print(u"Hello") - 6 |+print("Hello") -7 7 | -8 8 | print(u'world') -9 9 | +4 4 | +5 5 | u'world' # UP025 +6 6 | +7 |-print(u"Hello") # UP025 + 7 |+print("Hello") # UP025 +8 8 | +9 9 | print(u'world') # UP025 +10 10 | -UP025.py:8:7: UP025 [*] Remove unicode literals from strings +UP025.py:9:7: UP025 [*] Remove unicode literals from strings | - 6 | print(u"Hello") - 7 | - 8 | print(u'world') + 7 | print(u"Hello") # UP025 + 8 | + 9 | print(u'world') # UP025 | ^^^^^^^^ UP025 - 9 | -10 | import foo +10 | +11 | import foo | = help: Remove unicode prefix ℹ Safe fix -5 5 | -6 6 | print(u"Hello") -7 7 | -8 |-print(u'world') - 8 |+print('world') -9 9 | -10 10 | import foo -11 11 | +6 6 | +7 7 | print(u"Hello") # UP025 +8 8 | +9 |-print(u'world') # UP025 + 9 |+print('world') # UP025 +10 10 | +11 11 | import foo +12 12 | -UP025.py:12:5: UP025 [*] Remove unicode literals from strings +UP025.py:13:5: UP025 [*] Remove unicode literals from strings | -10 | import foo -11 | -12 | foo(u"Hello", U"world", a=u"Hello", b=u"world") +11 | import foo +12 | +13 | foo(u"Hello", U"world", a=u"Hello", b=u"world") # UP025 | ^^^^^^^^ UP025 -13 | -14 | # These should stay quoted they way they are +14 | +15 | # Retain quotes when fixing. | = help: Remove unicode prefix ℹ Safe fix -9 9 | -10 10 | import foo -11 11 | -12 |-foo(u"Hello", U"world", a=u"Hello", b=u"world") - 12 |+foo("Hello", U"world", a=u"Hello", b=u"world") -13 13 | -14 14 | # These should stay quoted they way they are -15 15 | +10 10 | +11 11 | import foo +12 12 | +13 |-foo(u"Hello", U"world", a=u"Hello", b=u"world") # UP025 + 13 |+foo("Hello", U"world", a=u"Hello", b=u"world") # UP025 +14 14 | +15 15 | # Retain quotes when fixing. +16 16 | x = u'hello' # UP025 -UP025.py:12:15: UP025 [*] Remove unicode literals from strings +UP025.py:13:15: UP025 [*] Remove unicode literals from strings | -10 | import foo -11 | -12 | foo(u"Hello", U"world", a=u"Hello", b=u"world") +11 | import foo +12 | +13 | foo(u"Hello", U"world", a=u"Hello", b=u"world") # UP025 | ^^^^^^^^ UP025 -13 | -14 | # These should stay quoted they way they are +14 | +15 | # Retain quotes when fixing. | = help: Remove unicode prefix ℹ Safe fix -9 9 | -10 10 | import foo -11 11 | -12 |-foo(u"Hello", U"world", a=u"Hello", b=u"world") - 12 |+foo(u"Hello", "world", a=u"Hello", b=u"world") -13 13 | -14 14 | # These should stay quoted they way they are -15 15 | +10 10 | +11 11 | import foo +12 12 | +13 |-foo(u"Hello", U"world", a=u"Hello", b=u"world") # UP025 + 13 |+foo(u"Hello", "world", a=u"Hello", b=u"world") # UP025 +14 14 | +15 15 | # Retain quotes when fixing. +16 16 | x = u'hello' # UP025 -UP025.py:12:27: UP025 [*] Remove unicode literals from strings +UP025.py:13:27: UP025 [*] Remove unicode literals from strings | -10 | import foo -11 | -12 | foo(u"Hello", U"world", a=u"Hello", b=u"world") +11 | import foo +12 | +13 | foo(u"Hello", U"world", a=u"Hello", b=u"world") # UP025 | ^^^^^^^^ UP025 -13 | -14 | # These should stay quoted they way they are +14 | +15 | # Retain quotes when fixing. | = help: Remove unicode prefix ℹ Safe fix -9 9 | -10 10 | import foo -11 11 | -12 |-foo(u"Hello", U"world", a=u"Hello", b=u"world") - 12 |+foo(u"Hello", U"world", a="Hello", b=u"world") -13 13 | -14 14 | # These should stay quoted they way they are -15 15 | +10 10 | +11 11 | import foo +12 12 | +13 |-foo(u"Hello", U"world", a=u"Hello", b=u"world") # UP025 + 13 |+foo(u"Hello", U"world", a="Hello", b=u"world") # UP025 +14 14 | +15 15 | # Retain quotes when fixing. +16 16 | x = u'hello' # UP025 -UP025.py:12:39: UP025 [*] Remove unicode literals from strings +UP025.py:13:39: UP025 [*] Remove unicode literals from strings | -10 | import foo -11 | -12 | foo(u"Hello", U"world", a=u"Hello", b=u"world") +11 | import foo +12 | +13 | foo(u"Hello", U"world", a=u"Hello", b=u"world") # UP025 | ^^^^^^^^ UP025 -13 | -14 | # These should stay quoted they way they are +14 | +15 | # Retain quotes when fixing. | = help: Remove unicode prefix ℹ Safe fix -9 9 | -10 10 | import foo -11 11 | -12 |-foo(u"Hello", U"world", a=u"Hello", b=u"world") - 12 |+foo(u"Hello", U"world", a=u"Hello", b="world") -13 13 | -14 14 | # These should stay quoted they way they are -15 15 | +10 10 | +11 11 | import foo +12 12 | +13 |-foo(u"Hello", U"world", a=u"Hello", b=u"world") # UP025 + 13 |+foo(u"Hello", U"world", a=u"Hello", b="world") # UP025 +14 14 | +15 15 | # Retain quotes when fixing. +16 16 | x = u'hello' # UP025 UP025.py:16:5: UP025 [*] Remove unicode literals from strings | -14 | # These should stay quoted they way they are -15 | -16 | x = u'hello' +15 | # Retain quotes when fixing. +16 | x = u'hello' # UP025 | ^^^^^^^^ UP025 -17 | x = u"""hello""" -18 | x = u'''hello''' +17 | x = u"""hello""" # UP025 +18 | x = u'''hello''' # UP025 | = help: Remove unicode prefix ℹ Safe fix -13 13 | -14 14 | # These should stay quoted they way they are -15 15 | -16 |-x = u'hello' - 16 |+x = 'hello' -17 17 | x = u"""hello""" -18 18 | x = u'''hello''' -19 19 | x = u'Hello "World"' +13 13 | foo(u"Hello", U"world", a=u"Hello", b=u"world") # UP025 +14 14 | +15 15 | # Retain quotes when fixing. +16 |-x = u'hello' # UP025 + 16 |+x = 'hello' # UP025 +17 17 | x = u"""hello""" # UP025 +18 18 | x = u'''hello''' # UP025 +19 19 | x = u'Hello "World"' # UP025 UP025.py:17:5: UP025 [*] Remove unicode literals from strings | -16 | x = u'hello' -17 | x = u"""hello""" +15 | # Retain quotes when fixing. +16 | x = u'hello' # UP025 +17 | x = u"""hello""" # UP025 | ^^^^^^^^^^^^ UP025 -18 | x = u'''hello''' -19 | x = u'Hello "World"' +18 | x = u'''hello''' # UP025 +19 | x = u'Hello "World"' # UP025 | = help: Remove unicode prefix ℹ Safe fix -14 14 | # These should stay quoted they way they are -15 15 | -16 16 | x = u'hello' -17 |-x = u"""hello""" - 17 |+x = """hello""" -18 18 | x = u'''hello''' -19 19 | x = u'Hello "World"' +14 14 | +15 15 | # Retain quotes when fixing. +16 16 | x = u'hello' # UP025 +17 |-x = u"""hello""" # UP025 + 17 |+x = """hello""" # UP025 +18 18 | x = u'''hello''' # UP025 +19 19 | x = u'Hello "World"' # UP025 20 20 | UP025.py:18:5: UP025 [*] Remove unicode literals from strings | -16 | x = u'hello' -17 | x = u"""hello""" -18 | x = u'''hello''' +16 | x = u'hello' # UP025 +17 | x = u"""hello""" # UP025 +18 | x = u'''hello''' # UP025 | ^^^^^^^^^^^^ UP025 -19 | x = u'Hello "World"' +19 | x = u'Hello "World"' # UP025 | = help: Remove unicode prefix ℹ Safe fix -15 15 | -16 16 | x = u'hello' -17 17 | x = u"""hello""" -18 |-x = u'''hello''' - 18 |+x = '''hello''' -19 19 | x = u'Hello "World"' +15 15 | # Retain quotes when fixing. +16 16 | x = u'hello' # UP025 +17 17 | x = u"""hello""" # UP025 +18 |-x = u'''hello''' # UP025 + 18 |+x = '''hello''' # UP025 +19 19 | x = u'Hello "World"' # UP025 20 20 | -21 21 | # These should not change +21 21 | u = "Hello" # OK UP025.py:19:5: UP025 [*] Remove unicode literals from strings | -17 | x = u"""hello""" -18 | x = u'''hello''' -19 | x = u'Hello "World"' +17 | x = u"""hello""" # UP025 +18 | x = u'''hello''' # UP025 +19 | x = u'Hello "World"' # UP025 | ^^^^^^^^^^^^^^^^ UP025 20 | -21 | # These should not change +21 | u = "Hello" # OK | = help: Remove unicode prefix ℹ Safe fix -16 16 | x = u'hello' -17 17 | x = u"""hello""" -18 18 | x = u'''hello''' -19 |-x = u'Hello "World"' - 19 |+x = 'Hello "World"' +16 16 | x = u'hello' # UP025 +17 17 | x = u"""hello""" # UP025 +18 18 | x = u'''hello''' # UP025 +19 |-x = u'Hello "World"' # UP025 + 19 |+x = 'Hello "World"' # UP025 20 20 | -21 21 | # These should not change -22 22 | u = "Hello" +21 21 | u = "Hello" # OK +22 22 | u = u # OK -UP025.py:29:7: UP025 [*] Remove unicode literals from strings +UP025.py:27:7: UP025 [*] Remove unicode literals from strings | -27 | return"Hello" -28 | -29 | f"foo"u"bar" +25 | return"Hello" # OK +26 | +27 | f"foo"u"bar" # OK | ^^^^^^ UP025 -30 | f"foo" u"bar" +28 | f"foo" u"bar" # OK | = help: Remove unicode prefix ℹ Safe fix -26 26 | def hello(): -27 27 | return"Hello" -28 28 | -29 |-f"foo"u"bar" - 29 |+f"foo""bar" -30 30 | f"foo" u"bar" +24 24 | def hello(): +25 25 | return"Hello" # OK +26 26 | +27 |-f"foo"u"bar" # OK + 27 |+f"foo""bar" # OK +28 28 | f"foo" u"bar" # OK -UP025.py:30:8: UP025 [*] Remove unicode literals from strings +UP025.py:28:8: UP025 [*] Remove unicode literals from strings | -29 | f"foo"u"bar" -30 | f"foo" u"bar" +27 | f"foo"u"bar" # OK +28 | f"foo" u"bar" # OK | ^^^^^^ UP025 | = help: Remove unicode prefix ℹ Safe fix -27 27 | return"Hello" -28 28 | -29 29 | f"foo"u"bar" -30 |-f"foo" u"bar" - 30 |+f"foo" "bar" +25 25 | return"Hello" # OK +26 26 | +27 27 | f"foo"u"bar" # OK +28 |-f"foo" u"bar" # OK + 28 |+f"foo" "bar" # OK diff --git a/crates/ruff_python_semantic/src/model.rs b/crates/ruff_python_semantic/src/model.rs index fd20a4714cda1..65dd5e6ae0bcd 100644 --- a/crates/ruff_python_semantic/src/model.rs +++ b/crates/ruff_python_semantic/src/model.rs @@ -1715,6 +1715,16 @@ bitflags! { /// ``` const FUTURE_ANNOTATIONS = 1 << 15; + /// The model has traversed past the module docstring. + /// + /// For example, the model could be visiting `x` in: + /// ```python + /// """Module docstring.""" + /// + /// x: int = 1 + /// ``` + const MODULE_DOCSTRING = 1 << 16; + /// The model is in a type parameter definition. /// /// For example, the model could be visiting `Record` in: @@ -1723,11 +1733,10 @@ bitflags! { /// /// Record = TypeVar("Record") /// - const TYPE_PARAM_DEFINITION = 1 << 16; + const TYPE_PARAM_DEFINITION = 1 << 17; /// The context is in any type annotation. - const ANNOTATION = Self::TYPING_ONLY_ANNOTATION.bits() | Self::RUNTIME_EVALUATED_ANNOTATION.bits() | Self::RUNTIME_REQUIRED_ANNOTATION.bits(); - + const ANNOTATION = Self::TYPING_ONLY_ANNOTATION.bits() | Self::RUNTIME_EVALUATED_ANNOTATION.bits() | Self::RUNTIME_REQUIRED_ANNOTATION.bits(); /// The context is in any string type definition. const STRING_TYPE_DEFINITION = Self::SIMPLE_STRING_TYPE_DEFINITION.bits()