Compare commits

...

8 Commits

Author SHA1 Message Date
Charlie Marsh
b35a804f9d Bump version to 0.0.172 2022-12-09 17:47:34 -05:00
Charlie Marsh
e594ed6528 Implement D301 (backslash checks) (#1169) 2022-12-09 17:44:18 -05:00
Charlie Marsh
197645d90d Always use raw docstrings for pydocstyle rules (#1167) 2022-12-09 17:31:04 -05:00
Charlie Marsh
26d3ff5a3a Add pyflakes test suite for annotations (#1166) 2022-12-09 16:28:07 -05:00
Charlie Marsh
0dacf61153 Implement F842 (UnusedAnnotation) (#1165) 2022-12-09 12:42:03 -05:00
Charlie Marsh
a6251360b7 Avoid RET false-positives for usages in f-strings (#1163) 2022-12-09 12:28:09 -05:00
Charlie Marsh
2965e2561d Clarify combination of combine-as-imports and force-wrap-aliases (#1162) 2022-12-09 12:20:15 -05:00
Charlie Marsh
a19050b8a4 Update README.md 2022-12-08 23:39:01 -05:00
26 changed files with 1417 additions and 146 deletions

View File

@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.171
rev: v0.0.172
hooks:
- id: ruff

8
Cargo.lock generated
View File

@@ -724,7 +724,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8-to-ruff"
version = "0.0.171-dev.0"
version = "0.0.172-dev.0"
dependencies = [
"anyhow",
"clap 4.0.29",
@@ -1821,7 +1821,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.171"
version = "0.0.172"
dependencies = [
"annotate-snippets 0.9.1",
"anyhow",
@@ -1874,7 +1874,7 @@ dependencies = [
[[package]]
name = "ruff_dev"
version = "0.0.171"
version = "0.0.172"
dependencies = [
"anyhow",
"clap 4.0.29",
@@ -1892,7 +1892,7 @@ dependencies = [
[[package]]
name = "ruff_macros"
version = "0.0.171"
version = "0.0.172"
dependencies = [
"proc-macro2",
"quote",

View File

@@ -6,7 +6,7 @@ members = [
[package]
name = "ruff"
version = "0.0.171"
version = "0.0.172"
edition = "2021"
rust-version = "1.65.0"
@@ -41,7 +41,7 @@ quick-junit = { version = "0.3.2" }
rayon = { version = "1.5.3" }
regex = { version = "1.6.0" }
ropey = { version = "1.5.0", features = ["cr_lines", "simd"], default-features = false }
ruff_macros = { version = "0.0.171", path = "ruff_macros" }
ruff_macros = { version = "0.0.172", path = "ruff_macros" }
rustc-hash = { version = "1.1.0" }
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "28f9f65ccc625f00835d84bbb5fba274dce5aa89" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "28f9f65ccc625f00835d84bbb5fba274dce5aa89" }

View File

@@ -147,7 +147,7 @@ Ruff also works with [pre-commit](https://pre-commit.com):
```yaml
repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.171
rev: v0.0.172
hooks:
- id: ruff
```
@@ -438,11 +438,13 @@ For more, see [Pyflakes](https://pypi.org/project/pyflakes/2.5.0/) on PyPI.
| F706 | ReturnOutsideFunction | `return` statement outside of a function/method | |
| F707 | DefaultExceptNotLast | An `except` block as not the last exception handler | |
| F722 | ForwardAnnotationSyntaxError | Syntax error in forward annotation: `...` | |
| F811 | RedefinedWhileUnused | Redefinition of unused `...` from line 1 | |
| F821 | UndefinedName | Undefined name `...` | |
| F822 | UndefinedExport | Undefined name `...` in `__all__` | |
| F823 | UndefinedLocal | Local variable `...` referenced before assignment | |
| F831 | DuplicateArgumentName | Duplicate argument name in function definition | |
| F841 | UnusedVariable | Local variable `...` is assigned to but never used | |
| F842 | UnusedAnnotation | Local variable `...` is annotated but never used | |
| F901 | RaiseNotImplemented | `raise NotImplemented` should be `raise NotImplementedError` | 🛠 |
### pycodestyle (E, W)
@@ -515,6 +517,7 @@ For more, see [pydocstyle](https://pypi.org/project/pydocstyle/6.1.1/) on PyPI.
| D214 | SectionNotOverIndented | Section is over-indented ("Returns") | 🛠 |
| D215 | SectionUnderlineNotOverIndented | Section underline is over-indented ("Returns") | 🛠 |
| D300 | UsesTripleQuotes | Use """triple double quotes""" | |
| D301 | UsesRPrefixForBackslashedContent | Use r""" if any backslashes in a docstring | |
| D400 | EndsInPeriod | First line should end with a period | 🛠 |
| D402 | NoSignature | First line should not be the function's signature | |
| D403 | FirstLineCapitalized | First word of the first line should be properly capitalized | |
@@ -1980,6 +1983,10 @@ from .utils import (
)
```
Note that this setting is only effective when combined with `combine-as-imports = true`.
When `combine-as-imports` isn't enabled, every aliased `import from` will be given its
own line, in which case, wrapping is not necessary.
**Default value**: `false`
**Type**: `bool`
@@ -1989,6 +1996,7 @@ from .utils import (
```toml
[tool.ruff.isort]
force-wrap-aliases = true
combine-as-imports = true
```
---

View File

@@ -771,7 +771,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8_to_ruff"
version = "0.0.171"
version = "0.0.172"
dependencies = [
"anyhow",
"clap",
@@ -1975,7 +1975,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.171"
version = "0.0.172"
dependencies = [
"anyhow",
"bincode",

View File

@@ -1,6 +1,6 @@
[package]
name = "flake8-to-ruff"
version = "0.0.171-dev.0"
version = "0.0.172-dev.0"
edition = "2021"
[lib]

View File

@@ -512,6 +512,7 @@ mod tests {
CheckCodePrefix::D214,
CheckCodePrefix::D215,
CheckCodePrefix::D300,
CheckCodePrefix::D301,
CheckCodePrefix::D400,
CheckCodePrefix::D403,
CheckCodePrefix::D404,

View File

@@ -162,6 +162,7 @@ impl DocstringConvention {
// CheckCodePrefix::D214,
// CheckCodePrefix::D215,
CheckCodePrefix::D300,
CheckCodePrefix::D301,
CheckCodePrefix::D400,
CheckCodePrefix::D402,
CheckCodePrefix::D403,
@@ -209,6 +210,7 @@ impl DocstringConvention {
CheckCodePrefix::D214,
CheckCodePrefix::D215,
CheckCodePrefix::D300,
CheckCodePrefix::D301,
CheckCodePrefix::D400,
// CheckCodePrefix::D402,
CheckCodePrefix::D403,
@@ -257,6 +259,7 @@ impl DocstringConvention {
CheckCodePrefix::D214,
// CheckCodePrefix::D215,
CheckCodePrefix::D300,
CheckCodePrefix::D301,
// CheckCodePrefix::D400,
CheckCodePrefix::D402,
CheckCodePrefix::D403,

View File

@@ -130,6 +130,12 @@ def x():
return val
def x():
a = 1
print(f"a={a}")
return a
# Test cases for using value for assignment then returning it
# See:https://github.com/afonasev/flake8-return/issues/47
def resolve_from_url(self, url: str) -> dict:

View File

@@ -0,0 +1,13 @@
def f():
name: str
age: int
class A:
name: str
age: int
class B:
name: str = "Bob"
age: int = 18

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_dev"
version = "0.0.171"
version = "0.0.172"
edition = "2021"
[dependencies]

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_macros"
version = "0.0.171"
version = "0.0.172"
edition = "2021"
[lib]

View File

@@ -2283,7 +2283,6 @@ where
self.in_subscript = true;
visitor::walk_expr(self, expr);
} else {
self.in_subscript = true;
match typing::match_annotated_subscript(
value,
&self.from_imports,
@@ -2303,8 +2302,7 @@ where
// Ex) Annotated[int, "Hello, world!"]
SubscriptKind::PEP593AnnotatedSubscript => {
// First argument is a type (including forward references); the
// rest are arbitrary Python
// objects.
// rest are arbitrary Python objects.
self.visit_expr(value);
if let ExprKind::Tuple { elts, ctx } = &slice.node {
if let Some(expr) = elts.first() {
@@ -2662,8 +2660,7 @@ impl<'a> Checker<'a> {
}
}
// TODO(charlie): Don't treat annotations as assignments if there is an existing
// value.
// Assume the rebound name is used as a global or within a loop.
let scope = self.current_scope();
let binding = match scope.values.get(&name) {
None => binding,
@@ -2673,10 +2670,14 @@ impl<'a> Checker<'a> {
},
};
self.bindings.push(binding);
// Don't treat annotations as assignments if there is an existing value
// in scope.
let scope = &mut self.scopes[*(self.scope_stack.last().expect("No current scope found"))];
scope.values.insert(name, index);
if !(matches!(binding.kind, BindingKind::Annotation) && scope.values.contains_key(name)) {
scope.values.insert(name, index);
}
self.bindings.push(binding);
}
fn handle_node_load(&mut self, expr: &Expr) {
@@ -2701,6 +2702,13 @@ impl<'a> Checker<'a> {
// Mark the binding as used.
self.bindings[*index].used = Some((scope_id, Range::from_located(expr)));
if matches!(self.bindings[*index].kind, BindingKind::Annotation)
&& !self.in_deferred_string_type_definition
&& !self.in_deferred_type_definition
{
continue;
}
// If the name of the sub-importation is the same as an alias of another
// importation and the alias is used, that sub-importation should be
// marked as used too.
@@ -3112,7 +3120,17 @@ impl<'a> Checker<'a> {
while let Some((index, (scopes, _parents))) = self.deferred_assignments.pop() {
if self.settings.enabled.contains(&CheckCode::F841) {
self.add_checks(
pyflakes::checks::unused_variables(
pyflakes::checks::unused_variable(
&self.scopes[index],
&self.bindings,
&self.settings.dummy_variable_rgx,
)
.into_iter(),
);
}
if self.settings.enabled.contains(&CheckCode::F842) {
self.add_checks(
pyflakes::checks::unused_annotation(
&self.scopes[index],
&self.bindings,
&self.settings.dummy_variable_rgx,
@@ -3413,6 +3431,9 @@ impl<'a> Checker<'a> {
if self.settings.enabled.contains(&CheckCode::D300) {
pydocstyle::plugins::triple_quotes(self, &definition);
}
if self.settings.enabled.contains(&CheckCode::D301) {
pydocstyle::plugins::backslashes(self, &definition);
}
if self.settings.enabled.contains(&CheckCode::D400) {
pydocstyle::plugins::ends_with_period(self, &definition);
}

View File

@@ -93,6 +93,7 @@ pub enum CheckCode {
F823,
F831,
F841,
F842,
F901,
// pylint
PLC0414,
@@ -243,6 +244,7 @@ pub enum CheckCode {
D214,
D215,
D300,
D301,
D400,
D402,
D403,
@@ -627,6 +629,7 @@ pub enum CheckKind {
TwoStarredExpressions,
UndefinedExport(String),
UndefinedLocal(String),
UnusedAnnotation(String),
UndefinedName(String),
UnusedImport(String, bool),
UnusedVariable(String),
@@ -796,6 +799,7 @@ pub enum CheckKind {
SectionUnderlineMatchesSectionLength(String),
SectionUnderlineNotOverIndented(String),
SkipDocstring,
UsesRPrefixForBackslashedContent,
UsesTripleQuotes,
// pep8-naming
InvalidClassName(String),
@@ -940,6 +944,7 @@ impl CheckCode {
CheckCode::F823 => CheckKind::UndefinedLocal("...".to_string()),
CheckCode::F831 => CheckKind::DuplicateArgumentName,
CheckCode::F841 => CheckKind::UnusedVariable("...".to_string()),
CheckCode::F842 => CheckKind::UnusedAnnotation("...".to_string()),
CheckCode::F901 => CheckKind::RaiseNotImplemented,
// pylint
CheckCode::PLC0414 => CheckKind::UselessImportAlias,
@@ -1112,6 +1117,7 @@ impl CheckCode {
CheckCode::D214 => CheckKind::SectionNotOverIndented("Returns".to_string()),
CheckCode::D215 => CheckKind::SectionUnderlineNotOverIndented("Returns".to_string()),
CheckCode::D300 => CheckKind::UsesTripleQuotes,
CheckCode::D301 => CheckKind::UsesRPrefixForBackslashedContent,
CheckCode::D400 => CheckKind::EndsInPeriod,
CheckCode::D402 => CheckKind::NoSignature,
CheckCode::D403 => CheckKind::FirstLineCapitalized,
@@ -1292,6 +1298,7 @@ impl CheckCode {
CheckCode::D214 => CheckCategory::Pydocstyle,
CheckCode::D215 => CheckCategory::Pydocstyle,
CheckCode::D300 => CheckCategory::Pydocstyle,
CheckCode::D301 => CheckCategory::Pydocstyle,
CheckCode::D400 => CheckCategory::Pydocstyle,
CheckCode::D402 => CheckCategory::Pydocstyle,
CheckCode::D403 => CheckCategory::Pydocstyle,
@@ -1368,6 +1375,7 @@ impl CheckCode {
CheckCode::F823 => CheckCategory::Pyflakes,
CheckCode::F831 => CheckCategory::Pyflakes,
CheckCode::F841 => CheckCategory::Pyflakes,
CheckCode::F842 => CheckCategory::Pyflakes,
CheckCode::F901 => CheckCategory::Pyflakes,
CheckCode::FBT001 => CheckCategory::Flake8BooleanTrap,
CheckCode::FBT002 => CheckCategory::Flake8BooleanTrap,
@@ -1516,6 +1524,7 @@ impl CheckKind {
CheckKind::UndefinedName(_) => &CheckCode::F821,
CheckKind::UnusedImport(..) => &CheckCode::F401,
CheckKind::UnusedVariable(_) => &CheckCode::F841,
CheckKind::UnusedAnnotation(_) => &CheckCode::F842,
CheckKind::YieldOutsideFunction(_) => &CheckCode::F704,
// pycodestyle warnings
CheckKind::NoNewLineAtEndOfFile => &CheckCode::W292,
@@ -1685,6 +1694,7 @@ impl CheckKind {
CheckKind::SectionUnderlineMatchesSectionLength(_) => &CheckCode::D409,
CheckKind::SectionUnderlineNotOverIndented(_) => &CheckCode::D215,
CheckKind::SkipDocstring => &CheckCode::D418,
CheckKind::UsesRPrefixForBackslashedContent => &CheckCode::D301,
CheckKind::UsesTripleQuotes => &CheckCode::D300,
// pep8-naming
CheckKind::InvalidClassName(_) => &CheckCode::N801,
@@ -1902,6 +1912,9 @@ impl CheckKind {
CheckKind::UndefinedName(name) => {
format!("Undefined name `{name}`")
}
CheckKind::UnusedAnnotation(name) => {
format!("Local variable `{name}` is annotated but never used")
}
CheckKind::UnusedImport(name, ignore_init) => {
if *ignore_init {
format!(
@@ -1931,8 +1944,8 @@ impl CheckKind {
let types = types.join(", ");
format!("Merge these isinstance calls: `isinstance({obj}, ({types}))`")
}
CheckKind::MisplacedComparisonConstant(comprison) => {
format!("Comparison should be {comprison}")
CheckKind::MisplacedComparisonConstant(comparison) => {
format!("Comparison should be {comparison}")
}
CheckKind::UnnecessaryDirectLambdaCall => "Lambda expression called directly. Execute \
the expression inline instead."
@@ -2340,6 +2353,9 @@ impl CheckKind {
CheckKind::FirstLineCapitalized => {
"First word of the first line should be properly capitalized".to_string()
}
CheckKind::UsesRPrefixForBackslashedContent => {
r#"Use r""" if any backslashes in a docstring"#.to_string()
}
CheckKind::UsesTripleQuotes => r#"Use """triple double quotes""""#.to_string(),
CheckKind::MultiLineSummaryFirstLine => {
"Multi-line docstring summary should start at the first line".to_string()

View File

@@ -139,6 +139,7 @@ pub enum CheckCodePrefix {
D3,
D30,
D300,
D301,
D4,
D40,
D400,
@@ -253,6 +254,7 @@ pub enum CheckCodePrefix {
F831,
F84,
F841,
F842,
F9,
F90,
F901,
@@ -761,6 +763,7 @@ impl CheckCodePrefix {
CheckCode::D214,
CheckCode::D215,
CheckCode::D300,
CheckCode::D301,
CheckCode::D400,
CheckCode::D402,
CheckCode::D403,
@@ -863,9 +866,10 @@ impl CheckCodePrefix {
CheckCodePrefix::D213 => vec![CheckCode::D213],
CheckCodePrefix::D214 => vec![CheckCode::D214],
CheckCodePrefix::D215 => vec![CheckCode::D215],
CheckCodePrefix::D3 => vec![CheckCode::D300],
CheckCodePrefix::D30 => vec![CheckCode::D300],
CheckCodePrefix::D3 => vec![CheckCode::D300, CheckCode::D301],
CheckCodePrefix::D30 => vec![CheckCode::D300, CheckCode::D301],
CheckCodePrefix::D300 => vec![CheckCode::D300],
CheckCodePrefix::D301 => vec![CheckCode::D301],
CheckCodePrefix::D4 => vec![
CheckCode::D400,
CheckCode::D402,
@@ -1034,6 +1038,7 @@ impl CheckCodePrefix {
CheckCode::F823,
CheckCode::F831,
CheckCode::F841,
CheckCode::F842,
CheckCode::F901,
],
CheckCodePrefix::F4 => vec![
@@ -1167,6 +1172,7 @@ impl CheckCodePrefix {
CheckCode::F823,
CheckCode::F831,
CheckCode::F841,
CheckCode::F842,
],
CheckCodePrefix::F81 => vec![CheckCode::F811],
CheckCodePrefix::F811 => vec![CheckCode::F811],
@@ -1176,8 +1182,9 @@ impl CheckCodePrefix {
CheckCodePrefix::F823 => vec![CheckCode::F823],
CheckCodePrefix::F83 => vec![CheckCode::F831],
CheckCodePrefix::F831 => vec![CheckCode::F831],
CheckCodePrefix::F84 => vec![CheckCode::F841],
CheckCodePrefix::F84 => vec![CheckCode::F841, CheckCode::F842],
CheckCodePrefix::F841 => vec![CheckCode::F841],
CheckCodePrefix::F842 => vec![CheckCode::F842],
CheckCodePrefix::F9 => vec![CheckCode::F901],
CheckCodePrefix::F90 => vec![CheckCode::F901],
CheckCodePrefix::F901 => vec![CheckCode::F901],
@@ -1935,6 +1942,7 @@ impl CheckCodePrefix {
CheckCodePrefix::D3 => SuffixLength::One,
CheckCodePrefix::D30 => SuffixLength::Two,
CheckCodePrefix::D300 => SuffixLength::Three,
CheckCodePrefix::D301 => SuffixLength::Three,
CheckCodePrefix::D4 => SuffixLength::One,
CheckCodePrefix::D40 => SuffixLength::Two,
CheckCodePrefix::D400 => SuffixLength::Three,
@@ -2049,6 +2057,7 @@ impl CheckCodePrefix {
CheckCodePrefix::F831 => SuffixLength::Three,
CheckCodePrefix::F84 => SuffixLength::Two,
CheckCodePrefix::F841 => SuffixLength::Three,
CheckCodePrefix::F842 => SuffixLength::Three,
CheckCodePrefix::F9 => SuffixLength::One,
CheckCodePrefix::F90 => SuffixLength::Two,
CheckCodePrefix::F901 => SuffixLength::Three,

View File

@@ -1,5 +1,9 @@
pub const TRIPLE_QUOTE_PREFIXES: &[&str] = &[
"ur\"\"\"", "ur'''", "u\"\"\"", "u'''", "r\"\"\"", "r'''", "\"\"\"", "'''",
"ur\"\"\"", "ur'''", "u\"\"\"", "u'''", "r\"\"\"", "r'''", "UR\"\"\"", "UR'''", "Ur\"\"\"",
"Ur'''", "U\"\"\"", "U'''", "uR\"\"\"", "uR'''", "R\"\"\"", "R'''", "\"\"\"", "'''",
];
pub const SINGLE_QUOTE_PREFIXES: &[&str] = &["ur\"", "ur'", "u\"", "u'", "r\"", "r'", "\"", "'"];
pub const SINGLE_QUOTE_PREFIXES: &[&str] = &[
"ur\"", "ur'", "u\"", "u'", "r\"", "r'", "ur\"", "ur'", "u\"", "u'", "r\"", "r'", "UR\"",
"UR'", "Ur\"", "Ur'", "U\"", "U'", "uR\"", "uR'", "R\"", "R'", "\"", "'",
];

View File

@@ -18,6 +18,8 @@ pub struct Stack<'a> {
#[derive(Default)]
pub struct ReturnVisitor<'a> {
pub stack: Stack<'a>,
// If we're in an f-string, the location of the defining expression.
in_f_string: Option<Location>,
}
impl<'a> ReturnVisitor<'a> {
@@ -34,7 +36,7 @@ impl<'a> ReturnVisitor<'a> {
.assigns
.entry(id)
.or_insert_with(Vec::new)
.push(expr.location);
.push(self.in_f_string.unwrap_or(expr.location));
return;
}
_ => {}
@@ -70,7 +72,7 @@ impl<'a> Visitor<'a> for ReturnVisitor<'a> {
.refs
.entry(id)
.or_insert_with(Vec::new)
.push(value.location);
.push(self.in_f_string.unwrap_or(value.location));
}
visitor::walk_expr(self, value);
@@ -111,7 +113,13 @@ impl<'a> Visitor<'a> for ReturnVisitor<'a> {
.refs
.entry(id)
.or_insert_with(Vec::new)
.push(expr.location);
.push(self.in_f_string.unwrap_or(expr.location));
}
ExprKind::JoinedStr { .. } => {
let prev_in_f_string = self.in_f_string;
self.in_f_string = Some(expr.location);
visitor::walk_expr(self, expr);
self.in_f_string = prev_in_f_string;
}
_ => visitor::walk_expr(self, expr),
}

View File

@@ -32,11 +32,16 @@ pub struct Options {
test_id as test_id
)
```
Note that this setting is only effective when combined with `combine-as-imports = true`.
When `combine-as-imports` isn't enabled, every aliased `import from` will be given its
own line, in which case, wrapping is not necessary.
"#,
default = r#"false"#,
value_type = "bool",
example = r#"
force-wrap-aliases = true
combine-as-imports = true
"#
)]
pub force_wrap_aliases: Option<bool>,

View File

@@ -4,13 +4,27 @@ use crate::ast::types::Range;
use crate::docstrings::constants;
use crate::SourceCodeLocator;
/// Strip the leading and trailing quotes from a docstring.
pub fn raw_contents(contents: &str) -> &str {
for pattern in constants::TRIPLE_QUOTE_PREFIXES {
if contents.starts_with(pattern) {
return &contents[pattern.len()..contents.len() - 3];
}
}
for pattern in constants::SINGLE_QUOTE_PREFIXES {
if contents.starts_with(pattern) {
return &contents[pattern.len()..contents.len() - 1];
}
}
unreachable!("Expected docstring to start with a valid triple- or single-quote prefix")
}
/// Return the leading quote string for a docstring (e.g., `"""`).
pub fn leading_quote<'a>(docstring: &Expr, locator: &'a SourceCodeLocator) -> Option<&'a str> {
if let Some(first_line) = locator
.slice_source_code_range(&Range::from_located(docstring))
.lines()
.next()
.map(str::to_lowercase)
{
for pattern in constants::TRIPLE_QUOTE_PREFIXES
.iter()

View File

@@ -37,6 +37,7 @@ mod tests {
#[test_case(CheckCode::D214, Path::new("sections.py"); "D214")]
#[test_case(CheckCode::D215, Path::new("sections.py"); "D215")]
#[test_case(CheckCode::D300, Path::new("D.py"); "D300")]
#[test_case(CheckCode::D301, Path::new("D.py"); "D301")]
#[test_case(CheckCode::D400, Path::new("D.py"); "D400_0")]
#[test_case(CheckCode::D400, Path::new("D400.py"); "D400_1")]
#[test_case(CheckCode::D402, Path::new("D.py"); "D402")]

View File

@@ -2,7 +2,7 @@ use itertools::Itertools;
use once_cell::sync::Lazy;
use regex::Regex;
use rustc_hash::FxHashSet;
use rustpython_ast::{Constant, ExprKind, Location, StmtKind};
use rustpython_ast::{Location, StmtKind};
use crate::ast::types::Range;
use crate::ast::whitespace::LinesWithTrailingNewline;
@@ -14,6 +14,7 @@ use crate::docstrings::constants;
use crate::docstrings::definition::{Definition, DefinitionKind};
use crate::docstrings::sections::{section_contexts, SectionContext};
use crate::docstrings::styles::SectionStyle;
use crate::pydocstyle::helpers;
use crate::pydocstyle::helpers::{leading_quote, logical_line};
use crate::visibility::{is_init, is_magic, is_overload, is_override, is_staticmethod, Visibility};
@@ -123,12 +124,11 @@ pub fn one_liner(checker: &mut Checker, definition: &Definition) {
let Some(docstring) = &definition.docstring else {
return;
};
let ExprKind::Constant {
value: Constant::Str(string),
..
} = &docstring.node else {
return;
};
let contents = checker
.locator
.slice_source_code_range(&Range::from_located(docstring));
let string = helpers::raw_contents(&contents);
let mut line_count = 0;
let mut non_empty_line_count = 0;
@@ -167,12 +167,6 @@ pub fn blank_before_after_function(checker: &mut Checker, definition: &Definitio
) = &definition.kind else {
return;
};
let ExprKind::Constant {
value: Constant::Str(_),
..
} = &docstring.node else {
return;
};
if checker.settings.enabled.contains(&CheckCode::D201) {
let (before, ..) = checker.locator.partition_source_code_at(
@@ -255,12 +249,6 @@ pub fn blank_before_after_class(checker: &mut Checker, definition: &Definition)
let (DefinitionKind::Class(parent) | DefinitionKind::NestedClass(parent)) = &definition.kind else {
return;
};
let ExprKind::Constant {
value: Constant::Str(_),
..
} = &docstring.node else {
return;
};
if checker.settings.enabled.contains(&CheckCode::D203)
|| checker.settings.enabled.contains(&CheckCode::D211)
@@ -356,12 +344,11 @@ pub fn blank_after_summary(checker: &mut Checker, definition: &Definition) {
let Some(docstring) = definition.docstring else {
return;
};
let ExprKind::Constant {
value: Constant::Str(string),
..
} = &docstring.node else {
return;
};
let contents = checker
.locator
.slice_source_code_range(&Range::from_located(docstring));
let string = helpers::raw_contents(&contents);
let mut lines_count = 1;
let mut blanks_count = 0;
@@ -410,12 +397,11 @@ pub fn indent(checker: &mut Checker, definition: &Definition) {
let Some(docstring) = definition.docstring else {
return;
};
let ExprKind::Constant {
value: Constant::Str(string),
..
} = &docstring.node else {
return;
};
let contents = checker
.locator
.slice_source_code_range(&Range::from_located(docstring));
let string = helpers::raw_contents(&contents);
// Split the docstring into lines.
let lines: Vec<&str> = LinesWithTrailingNewline::from(string).collect();
@@ -550,12 +536,11 @@ pub fn newline_after_last_paragraph(checker: &mut Checker, definition: &Definiti
let Some(docstring) = definition.docstring else {
return;
};
let ExprKind::Constant {
value: Constant::Str(string),
..
} = &docstring.node else {
return;
};
let contents = checker
.locator
.slice_source_code_range(&Range::from_located(docstring));
let string = helpers::raw_contents(&contents);
let mut line_count = 0;
for line in LinesWithTrailingNewline::from(string) {
@@ -563,10 +548,7 @@ pub fn newline_after_last_paragraph(checker: &mut Checker, definition: &Definiti
line_count += 1;
}
if line_count > 1 {
let content = checker
.locator
.slice_source_code_range(&Range::from_located(docstring));
if let Some(last_line) = content.lines().last().map(str::trim) {
if let Some(last_line) = contents.lines().last().map(str::trim) {
if last_line != "\"\"\"" && last_line != "'''" {
let mut check = Check::new(
CheckKind::NewLineAfterLastParagraph,
@@ -599,12 +581,11 @@ pub fn no_surrounding_whitespace(checker: &mut Checker, definition: &Definition)
let Some(docstring) = definition.docstring else {
return;
};
let ExprKind::Constant {
value: Constant::Str(string),
..
} = &docstring.node else {
return;
};
let contents = checker
.locator
.slice_source_code_range(&Range::from_located(docstring));
let string = helpers::raw_contents(&contents);
let mut lines = LinesWithTrailingNewline::from(string);
let Some(line) = lines.next() else {
@@ -650,26 +631,23 @@ pub fn multi_line_summary_start(checker: &mut Checker, definition: &Definition)
let Some(docstring) = definition.docstring else {
return;
};
let ExprKind::Constant {
value: Constant::Str(string),
..
} = &docstring.node else
{
return;
};
let contents = checker
.locator
.slice_source_code_range(&Range::from_located(docstring));
let string = helpers::raw_contents(&contents);
if LinesWithTrailingNewline::from(string).nth(1).is_none() {
return;
};
let Some(first_line) = checker
.locator
.slice_source_code_range(&Range::from_located(docstring))
let Some(first_line) = contents
.lines()
.next()
.map(str::to_lowercase) else
else
{
return;
};
if constants::TRIPLE_QUOTE_PREFIXES.contains(&first_line.as_str()) {
if constants::TRIPLE_QUOTE_PREFIXES.contains(&first_line) {
if checker.settings.enabled.contains(&CheckCode::D212) {
checker.add_check(Check::new(
CheckKind::MultiLineSummaryFirstLine,
@@ -691,15 +669,13 @@ pub fn triple_quotes(checker: &mut Checker, definition: &Definition) {
let Some(docstring) = definition.docstring else {
return;
};
let ExprKind::Constant {
value: Constant::Str(string),
..
} = &docstring.node else {
return;
};
let Some(first_line) = checker
let contents = checker
.locator
.slice_source_code_range(&Range::from_located(docstring))
.slice_source_code_range(&Range::from_located(docstring));
let string = helpers::raw_contents(&contents);
let Some(first_line) = contents
.lines()
.next()
.map(str::to_lowercase) else
@@ -725,17 +701,42 @@ pub fn triple_quotes(checker: &mut Checker, definition: &Definition) {
}
}
static BACKSLASH_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"\\[^\nuN]").unwrap());
/// D301
pub fn backslashes(checker: &mut Checker, definition: &Definition) {
let Some(docstring) = definition.docstring else {
return;
};
let contents = checker
.locator
.slice_source_code_range(&Range::from_located(docstring));
// Docstring is already raw.
if contents.starts_with('r') || contents.starts_with("ur") {
return;
}
if BACKSLASH_REGEX.is_match(&contents) {
checker.add_check(Check::new(
CheckKind::UsesRPrefixForBackslashedContent,
Range::from_located(docstring),
));
}
}
/// D400
pub fn ends_with_period(checker: &mut Checker, definition: &Definition) {
let Some(docstring) = definition.docstring else {
return;
};
let ExprKind::Constant {
value: Constant::Str(string),
..
} = &docstring.node else {
return;
};
let contents = checker
.locator
.slice_source_code_range(&Range::from_located(docstring));
let string = helpers::raw_contents(&contents);
if let Some(index) = logical_line(string) {
let line = string.lines().nth(index).unwrap();
let trimmed = line.trim_end();
@@ -777,12 +778,12 @@ pub fn no_signature(checker: &mut Checker, definition: &Definition) {
let StmtKind::FunctionDef { name, .. } = &parent.node else {
return;
};
let ExprKind::Constant {
value: Constant::Str(string),
..
} = &docstring.node else {
return;
};
let contents = checker
.locator
.slice_source_code_range(&Range::from_located(docstring));
let string = helpers::raw_contents(&contents);
let Some(first_line) = string.lines().next() else {
return;
};
@@ -804,12 +805,11 @@ pub fn capitalized(checker: &mut Checker, definition: &Definition) {
let Some(docstring) = definition.docstring else {
return
};
let ExprKind::Constant {
value: Constant::Str(string),
..
} = &docstring.node else {
return
};
let contents = checker
.locator
.slice_source_code_range(&Range::from_located(docstring));
let string = helpers::raw_contents(&contents);
let Some(first_word) = string.split(' ').next() else {
return
};
@@ -838,12 +838,11 @@ pub fn starts_with_this(checker: &mut Checker, definition: &Definition) {
let Some(docstring) = definition.docstring else {
return
};
let ExprKind::Constant {
value: Constant::Str(string),
..
} = &docstring.node else {
return
};
let contents = checker
.locator
.slice_source_code_range(&Range::from_located(docstring));
let string = helpers::raw_contents(&contents);
let trimmed = string.trim();
if trimmed.is_empty() {
@@ -871,12 +870,12 @@ pub fn ends_with_punctuation(checker: &mut Checker, definition: &Definition) {
let Some(docstring) = definition.docstring else {
return
};
let ExprKind::Constant {
value: Constant::Str(string),
..
} = &docstring.node else {
return
};
let contents = checker
.locator
.slice_source_code_range(&Range::from_located(docstring));
let string = helpers::raw_contents(&contents);
if let Some(index) = logical_line(string) {
let line = string.lines().nth(index).unwrap();
let trimmed = line.trim_end();
@@ -930,12 +929,12 @@ pub fn not_empty(checker: &mut Checker, definition: &Definition) -> bool {
let Some(docstring) = definition.docstring else {
return true;
};
let ExprKind::Constant {
value: Constant::Str(string),
..
} = &docstring.node else {
return true;
};
let contents = checker
.locator
.slice_source_code_range(&Range::from_located(docstring));
let string = helpers::raw_contents(&contents);
if !string.trim().is_empty() {
return true;
}
@@ -955,12 +954,11 @@ pub fn sections(checker: &mut Checker, definition: &Definition) {
let Some(docstring) = definition.docstring else {
return
};
let ExprKind::Constant {
value: Constant::Str(string),
..
} = &docstring.node else {
return;
};
let contents = checker
.locator
.slice_source_code_range(&Range::from_located(docstring));
let string = helpers::raw_contents(&contents);
let lines: Vec<&str> = LinesWithTrailingNewline::from(string).collect();
if lines.len() < 2 {
@@ -1495,10 +1493,15 @@ fn parameters_section(checker: &mut Checker, definition: &Definition, context: &
// Collect the list of arguments documented in the docstring.
let mut docstring_args: FxHashSet<&str> = FxHashSet::default();
let section_level_indent = whitespace::leading_space(context.line);
for i in 1..context.following_lines.len() {
let current_line = context.following_lines[i - 1];
// Join line continuations, then resplit by line.
let adjusted_following_lines = context.following_lines.join("\n").replace("\\\n", "");
let lines: Vec<&str> = LinesWithTrailingNewline::from(&adjusted_following_lines).collect();
for i in 1..lines.len() {
let current_line = lines[i - 1];
let current_leading_space = whitespace::leading_space(current_line);
let next_line = context.following_lines[i];
let next_line = lines[i];
if current_leading_space == section_level_indent
&& (whitespace::leading_space(next_line).len() > current_leading_space.len())
&& !next_line.trim().is_empty()

View File

@@ -0,0 +1,29 @@
---
source: src/pydocstyle/mod.rs
expression: checks
---
- kind: UsesRPrefixForBackslashedContent
location:
row: 328
column: 4
end_location:
row: 328
column: 16
fix: ~
- kind: UsesRPrefixForBackslashedContent
location:
row: 333
column: 4
end_location:
row: 333
column: 20
fix: ~
- kind: UsesRPrefixForBackslashedContent
location:
row: 338
column: 4
end_location:
row: 338
column: 21
fix: ~

View File

@@ -388,7 +388,7 @@ pub fn undefined_local(name: &str, scopes: &[&Scope], bindings: &[Binding]) -> O
}
/// F841
pub fn unused_variables(
pub fn unused_variable(
scope: &Scope,
bindings: &[Binding],
dummy_variable_rgx: &Regex,
@@ -421,6 +421,31 @@ pub fn unused_variables(
checks
}
/// F842
pub fn unused_annotation(
scope: &Scope,
bindings: &[Binding],
dummy_variable_rgx: &Regex,
) -> Vec<Check> {
let mut checks: Vec<Check> = vec![];
for (name, binding) in scope
.values
.iter()
.map(|(name, index)| (name, &bindings[*index]))
{
if binding.used.is_none()
&& matches!(binding.kind, BindingKind::Annotation)
&& !dummy_variable_rgx.is_match(name)
{
checks.push(Check::new(
CheckKind::UnusedAnnotation((*name).to_string()),
binding.range,
));
}
}
checks
}
/// F707
pub fn default_except_not_last(handlers: &[Excepthandler]) -> Option<Check> {
for (idx, handler) in handlers.iter().enumerate() {

1
src/pyflakes/foo.rs Normal file
View File

@@ -0,0 +1 @@

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,23 @@
---
source: src/pyflakes/mod.rs
expression: checks
---
- kind:
UnusedAnnotation: name
location:
row: 2
column: 4
end_location:
row: 2
column: 8
fix: ~
- kind:
UnusedAnnotation: age
location:
row: 3
column: 4
end_location:
row: 3
column: 7
fix: ~