mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-10 05:39:12 +00:00
Use dedicated AST nodes on MemberKind
(#6374)
## Summary This PR leverages the unified function definition node to add precise AST node types to `MemberKind`, which is used to power our docstring definition tracking (e.g., classes and functions, whether they're methods or functions or nested functions and so on, whether they have a docstring, etc.). It was painful to do this in the past because the function variants needed to support a union anyway, but storing precise nodes removes like a dozen panics. No behavior changes -- purely a refactor. ## Test Plan `cargo test`
This commit is contained in:
parent
daefa74e9a
commit
c439435615
23 changed files with 316 additions and 414 deletions
|
@ -1,5 +1,4 @@
|
||||||
use ruff_diagnostics::Diagnostic;
|
use ruff_diagnostics::Diagnostic;
|
||||||
use ruff_python_ast::cast;
|
|
||||||
use ruff_python_semantic::analyze::{branch_detection, visibility};
|
use ruff_python_semantic::analyze::{branch_detection, visibility};
|
||||||
use ruff_python_semantic::{Binding, BindingKind, ScopeKind};
|
use ruff_python_semantic::{Binding, BindingKind, ScopeKind};
|
||||||
|
|
||||||
|
@ -172,18 +171,25 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(source) = shadowed.source else {
|
let Some(statement_id) = shadowed.source else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
// If this is an overloaded function, abort.
|
// If this is an overloaded function, abort.
|
||||||
if shadowed.kind.is_function_definition()
|
if shadowed.kind.is_function_definition() {
|
||||||
&& visibility::is_overload(
|
if checker
|
||||||
cast::decorator_list(checker.semantic.statement(source)),
|
.semantic
|
||||||
&checker.semantic,
|
.statement(statement_id)
|
||||||
)
|
.as_function_def_stmt()
|
||||||
{
|
.is_some_and(|function| {
|
||||||
continue;
|
visibility::is_overload(
|
||||||
|
&function.decorator_list,
|
||||||
|
&checker.semantic,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Only enforce cross-scope shadowing for imports.
|
// Only enforce cross-scope shadowing for imports.
|
||||||
|
|
|
@ -455,14 +455,16 @@ where
|
||||||
|
|
||||||
// Step 2: Traversal
|
// Step 2: Traversal
|
||||||
match stmt {
|
match stmt {
|
||||||
Stmt::FunctionDef(ast::StmtFunctionDef {
|
Stmt::FunctionDef(
|
||||||
body,
|
function_def @ ast::StmtFunctionDef {
|
||||||
parameters,
|
body,
|
||||||
decorator_list,
|
parameters,
|
||||||
returns,
|
decorator_list,
|
||||||
type_params,
|
returns,
|
||||||
..
|
type_params,
|
||||||
}) => {
|
..
|
||||||
|
},
|
||||||
|
) => {
|
||||||
// Visit the decorators and arguments, but avoid the body, which will be
|
// Visit the decorators and arguments, but avoid the body, which will be
|
||||||
// deferred.
|
// deferred.
|
||||||
for decorator in decorator_list {
|
for decorator in decorator_list {
|
||||||
|
@ -523,8 +525,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
let definition = docstrings::extraction::extract_definition(
|
let definition = docstrings::extraction::extract_definition(
|
||||||
ExtractionTarget::Function,
|
ExtractionTarget::Function(function_def),
|
||||||
stmt,
|
|
||||||
self.semantic.definition_id,
|
self.semantic.definition_id,
|
||||||
&self.semantic.definitions,
|
&self.semantic.definitions,
|
||||||
);
|
);
|
||||||
|
@ -566,8 +567,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
let definition = docstrings::extraction::extract_definition(
|
let definition = docstrings::extraction::extract_definition(
|
||||||
ExtractionTarget::Class,
|
ExtractionTarget::Class(class_def),
|
||||||
stmt,
|
|
||||||
self.semantic.definition_id,
|
self.semantic.definition_id,
|
||||||
&self.semantic.definitions,
|
&self.semantic.definitions,
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
//! Extract docstrings from an AST.
|
//! Extract docstrings from an AST.
|
||||||
|
|
||||||
use ruff_python_ast::{self as ast, Constant, Expr, Stmt};
|
use ruff_python_ast::{self as ast, Constant, Expr, Stmt};
|
||||||
|
|
||||||
use ruff_python_semantic::{Definition, DefinitionId, Definitions, Member, MemberKind};
|
use ruff_python_semantic::{Definition, DefinitionId, Definitions, Member, MemberKind};
|
||||||
|
|
||||||
/// Extract a docstring from a function or class body.
|
/// Extract a docstring from a function or class body.
|
||||||
|
@ -28,62 +27,48 @@ pub(crate) fn docstring_from(suite: &[Stmt]) -> Option<&Expr> {
|
||||||
pub(crate) fn extract_docstring<'a>(definition: &'a Definition<'a>) -> Option<&'a Expr> {
|
pub(crate) fn extract_docstring<'a>(definition: &'a Definition<'a>) -> Option<&'a Expr> {
|
||||||
match definition {
|
match definition {
|
||||||
Definition::Module(module) => docstring_from(module.python_ast),
|
Definition::Module(module) => docstring_from(module.python_ast),
|
||||||
Definition::Member(member) => {
|
Definition::Member(member) => docstring_from(member.body()),
|
||||||
if let Stmt::ClassDef(ast::StmtClassDef { body, .. })
|
|
||||||
| Stmt::FunctionDef(ast::StmtFunctionDef { body, .. }) = &member.stmt
|
|
||||||
{
|
|
||||||
docstring_from(body)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub(crate) enum ExtractionTarget {
|
pub(crate) enum ExtractionTarget<'a> {
|
||||||
Class,
|
Class(&'a ast::StmtClassDef),
|
||||||
Function,
|
Function(&'a ast::StmtFunctionDef),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extract a `Definition` from the AST node defined by a `Stmt`.
|
/// Extract a `Definition` from the AST node defined by a `Stmt`.
|
||||||
pub(crate) fn extract_definition<'a>(
|
pub(crate) fn extract_definition<'a>(
|
||||||
target: ExtractionTarget,
|
target: ExtractionTarget<'a>,
|
||||||
stmt: &'a Stmt,
|
|
||||||
parent: DefinitionId,
|
parent: DefinitionId,
|
||||||
definitions: &Definitions<'a>,
|
definitions: &Definitions<'a>,
|
||||||
) -> Member<'a> {
|
) -> Member<'a> {
|
||||||
match target {
|
match target {
|
||||||
ExtractionTarget::Function => match &definitions[parent] {
|
ExtractionTarget::Function(function) => match &definitions[parent] {
|
||||||
Definition::Module(..) => Member {
|
Definition::Module(..) => Member {
|
||||||
parent,
|
parent,
|
||||||
kind: MemberKind::Function,
|
kind: MemberKind::Function(function),
|
||||||
stmt,
|
|
||||||
},
|
},
|
||||||
Definition::Member(Member {
|
Definition::Member(Member {
|
||||||
kind: MemberKind::Class | MemberKind::NestedClass,
|
kind: MemberKind::Class(_) | MemberKind::NestedClass(_),
|
||||||
..
|
..
|
||||||
}) => Member {
|
}) => Member {
|
||||||
parent,
|
parent,
|
||||||
kind: MemberKind::Method,
|
kind: MemberKind::Method(function),
|
||||||
stmt,
|
|
||||||
},
|
},
|
||||||
Definition::Member(..) => Member {
|
Definition::Member(_) => Member {
|
||||||
parent,
|
parent,
|
||||||
kind: MemberKind::NestedFunction,
|
kind: MemberKind::NestedFunction(function),
|
||||||
stmt,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ExtractionTarget::Class => match &definitions[parent] {
|
ExtractionTarget::Class(class) => match &definitions[parent] {
|
||||||
Definition::Module(..) => Member {
|
Definition::Module(_) => Member {
|
||||||
parent,
|
parent,
|
||||||
kind: MemberKind::Class,
|
kind: MemberKind::Class(class),
|
||||||
stmt,
|
|
||||||
},
|
},
|
||||||
Definition::Member(..) => Member {
|
Definition::Member(_) => Member {
|
||||||
parent,
|
parent,
|
||||||
kind: MemberKind::NestedClass,
|
kind: MemberKind::NestedClass(class),
|
||||||
stmt,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,25 +1,25 @@
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
use ruff_python_ast::{PySourceType, Ranged, Stmt};
|
use ruff_python_ast::{PySourceType, Ranged};
|
||||||
use ruff_python_parser::{lexer, AsMode, Tok};
|
use ruff_python_parser::{lexer, AsMode, Tok};
|
||||||
|
|
||||||
use ruff_diagnostics::Edit;
|
use ruff_diagnostics::Edit;
|
||||||
use ruff_source_file::Locator;
|
use ruff_source_file::Locator;
|
||||||
|
|
||||||
/// ANN204
|
/// ANN204
|
||||||
pub(crate) fn add_return_annotation(
|
pub(crate) fn add_return_annotation<T: Ranged>(
|
||||||
locator: &Locator,
|
statement: &T,
|
||||||
stmt: &Stmt,
|
|
||||||
annotation: &str,
|
annotation: &str,
|
||||||
source_type: PySourceType,
|
source_type: PySourceType,
|
||||||
|
locator: &Locator,
|
||||||
) -> Result<Edit> {
|
) -> Result<Edit> {
|
||||||
let contents = &locator.contents()[stmt.range()];
|
let contents = &locator.contents()[statement.range()];
|
||||||
|
|
||||||
// Find the colon (following the `def` keyword).
|
// Find the colon (following the `def` keyword).
|
||||||
let mut seen_lpar = false;
|
let mut seen_lpar = false;
|
||||||
let mut seen_rpar = false;
|
let mut seen_rpar = false;
|
||||||
let mut count = 0u32;
|
let mut count = 0u32;
|
||||||
for (tok, range) in
|
for (tok, range) in
|
||||||
lexer::lex_starts_at(contents, source_type.as_mode(), stmt.start()).flatten()
|
lexer::lex_starts_at(contents, source_type.as_mode(), statement.start()).flatten()
|
||||||
{
|
{
|
||||||
if seen_lpar && seen_rpar {
|
if seen_lpar && seen_rpar {
|
||||||
if matches!(tok, Tok::Colon) {
|
if matches!(tok, Tok::Colon) {
|
||||||
|
|
|
@ -1,45 +1,11 @@
|
||||||
use ruff_python_ast::{self as ast, Expr, Parameters, Stmt};
|
|
||||||
|
|
||||||
use ruff_python_ast::cast;
|
|
||||||
use ruff_python_semantic::analyze::visibility;
|
use ruff_python_semantic::analyze::visibility;
|
||||||
use ruff_python_semantic::{Definition, Member, MemberKind, SemanticModel};
|
use ruff_python_semantic::{Definition, SemanticModel};
|
||||||
|
|
||||||
pub(super) fn match_function_def(
|
|
||||||
stmt: &Stmt,
|
|
||||||
) -> (&str, &Parameters, Option<&Expr>, &[Stmt], &[ast::Decorator]) {
|
|
||||||
match stmt {
|
|
||||||
Stmt::FunctionDef(ast::StmtFunctionDef {
|
|
||||||
name,
|
|
||||||
parameters,
|
|
||||||
returns,
|
|
||||||
body,
|
|
||||||
decorator_list,
|
|
||||||
..
|
|
||||||
}) => (
|
|
||||||
name,
|
|
||||||
parameters,
|
|
||||||
returns.as_ref().map(AsRef::as_ref),
|
|
||||||
body,
|
|
||||||
decorator_list,
|
|
||||||
),
|
|
||||||
_ => panic!("Found non-FunctionDef in match_function_def"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return the name of the function, if it's overloaded.
|
/// Return the name of the function, if it's overloaded.
|
||||||
pub(crate) fn overloaded_name(definition: &Definition, semantic: &SemanticModel) -> Option<String> {
|
pub(crate) fn overloaded_name(definition: &Definition, semantic: &SemanticModel) -> Option<String> {
|
||||||
if let Definition::Member(Member {
|
let function = definition.as_function_def()?;
|
||||||
kind: MemberKind::Function | MemberKind::NestedFunction | MemberKind::Method,
|
if visibility::is_overload(&function.decorator_list, semantic) {
|
||||||
stmt,
|
Some(function.name.to_string())
|
||||||
..
|
|
||||||
}) = definition
|
|
||||||
{
|
|
||||||
if visibility::is_overload(cast::decorator_list(stmt), semantic) {
|
|
||||||
let (name, ..) = match_function_def(stmt);
|
|
||||||
Some(name.to_string())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -52,19 +18,12 @@ pub(crate) fn is_overload_impl(
|
||||||
overloaded_name: &str,
|
overloaded_name: &str,
|
||||||
semantic: &SemanticModel,
|
semantic: &SemanticModel,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
if let Definition::Member(Member {
|
let Some(function) = definition.as_function_def() else {
|
||||||
kind: MemberKind::Function | MemberKind::NestedFunction | MemberKind::Method,
|
return false;
|
||||||
stmt,
|
};
|
||||||
..
|
if visibility::is_overload(&function.decorator_list, semantic) {
|
||||||
}) = definition
|
|
||||||
{
|
|
||||||
if visibility::is_overload(cast::decorator_list(stmt), semantic) {
|
|
||||||
false
|
|
||||||
} else {
|
|
||||||
let (name, ..) = match_function_def(stmt);
|
|
||||||
name == overloaded_name
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
false
|
false
|
||||||
|
} else {
|
||||||
|
function.name.as_str() == overloaded_name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1,19 @@
|
||||||
use ruff_python_ast::{self as ast, Constant, Expr, ParameterWithDefault, Ranged, Stmt};
|
|
||||||
|
|
||||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Fix, Violation};
|
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Fix, Violation};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
use ruff_python_ast::cast;
|
|
||||||
use ruff_python_ast::helpers::ReturnStatementVisitor;
|
use ruff_python_ast::helpers::ReturnStatementVisitor;
|
||||||
use ruff_python_ast::identifier::Identifier;
|
use ruff_python_ast::identifier::Identifier;
|
||||||
use ruff_python_ast::statement_visitor::StatementVisitor;
|
use ruff_python_ast::statement_visitor::StatementVisitor;
|
||||||
|
use ruff_python_ast::{self as ast, Constant, Expr, ParameterWithDefault, Ranged, Stmt};
|
||||||
use ruff_python_parser::typing::parse_type_annotation;
|
use ruff_python_parser::typing::parse_type_annotation;
|
||||||
use ruff_python_semantic::analyze::visibility;
|
use ruff_python_semantic::analyze::visibility;
|
||||||
use ruff_python_semantic::{Definition, Member, MemberKind};
|
use ruff_python_semantic::Definition;
|
||||||
use ruff_python_stdlib::typing::simple_magic_return_type;
|
use ruff_python_stdlib::typing::simple_magic_return_type;
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
use crate::registry::{AsRule, Rule};
|
use crate::registry::{AsRule, Rule};
|
||||||
|
use crate::rules::flake8_annotations::fixes;
|
||||||
use crate::rules::ruff::typing::type_hint_resolves_to_any;
|
use crate::rules::ruff::typing::type_hint_resolves_to_any;
|
||||||
|
|
||||||
use super::super::fixes;
|
|
||||||
use super::super::helpers::match_function_def;
|
|
||||||
|
|
||||||
/// ## What it does
|
/// ## What it does
|
||||||
/// Checks that function arguments have type annotations.
|
/// Checks that function arguments have type annotations.
|
||||||
///
|
///
|
||||||
|
@ -498,20 +494,23 @@ pub(crate) fn definition(
|
||||||
definition: &Definition,
|
definition: &Definition,
|
||||||
visibility: visibility::Visibility,
|
visibility: visibility::Visibility,
|
||||||
) -> Vec<Diagnostic> {
|
) -> Vec<Diagnostic> {
|
||||||
// TODO(charlie): Consider using the AST directly here rather than `Definition`.
|
let Some(function) = definition.as_function_def() else {
|
||||||
// We could adhere more closely to `flake8-annotations` by defining public
|
|
||||||
// vs. secret vs. protected.
|
|
||||||
let Definition::Member(Member { kind, stmt, .. }) = definition else {
|
|
||||||
return vec![];
|
return vec![];
|
||||||
};
|
};
|
||||||
|
|
||||||
let is_method = match kind {
|
let ast::StmtFunctionDef {
|
||||||
MemberKind::Method => true,
|
range: _,
|
||||||
MemberKind::Function | MemberKind::NestedFunction => false,
|
is_async: _,
|
||||||
_ => return vec![],
|
decorator_list,
|
||||||
};
|
name,
|
||||||
|
type_params: _,
|
||||||
|
parameters,
|
||||||
|
returns,
|
||||||
|
body,
|
||||||
|
} = function;
|
||||||
|
|
||||||
|
let is_method = definition.is_method();
|
||||||
|
|
||||||
let (name, arguments, returns, body, decorator_list) = match_function_def(stmt);
|
|
||||||
// Keep track of whether we've seen any typed arguments or return values.
|
// Keep track of whether we've seen any typed arguments or return values.
|
||||||
let mut has_any_typed_arg = false; // Any argument has been typed?
|
let mut has_any_typed_arg = false; // Any argument has been typed?
|
||||||
let mut has_typed_return = false; // Return value has been typed?
|
let mut has_typed_return = false; // Return value has been typed?
|
||||||
|
@ -528,20 +527,19 @@ pub(crate) fn definition(
|
||||||
parameter,
|
parameter,
|
||||||
default: _,
|
default: _,
|
||||||
range: _,
|
range: _,
|
||||||
} in arguments
|
} in parameters
|
||||||
.posonlyargs
|
.posonlyargs
|
||||||
.iter()
|
.iter()
|
||||||
.chain(&arguments.args)
|
.chain(¶meters.args)
|
||||||
.chain(&arguments.kwonlyargs)
|
.chain(¶meters.kwonlyargs)
|
||||||
.skip(
|
.skip(
|
||||||
// If this is a non-static method, skip `cls` or `self`.
|
// If this is a non-static method, skip `cls` or `self`.
|
||||||
usize::from(
|
usize::from(
|
||||||
is_method
|
is_method && !visibility::is_staticmethod(decorator_list, checker.semantic()),
|
||||||
&& !visibility::is_staticmethod(cast::decorator_list(stmt), checker.semantic()),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
// ANN401 for dynamically typed arguments
|
// ANN401 for dynamically typed parameters
|
||||||
if let Some(annotation) = ¶meter.annotation {
|
if let Some(annotation) = ¶meter.annotation {
|
||||||
has_any_typed_arg = true;
|
has_any_typed_arg = true;
|
||||||
if checker.enabled(Rule::AnyType) && !is_overridden {
|
if checker.enabled(Rule::AnyType) && !is_overridden {
|
||||||
|
@ -572,7 +570,7 @@ pub(crate) fn definition(
|
||||||
}
|
}
|
||||||
|
|
||||||
// ANN002, ANN401
|
// ANN002, ANN401
|
||||||
if let Some(arg) = &arguments.vararg {
|
if let Some(arg) = ¶meters.vararg {
|
||||||
if let Some(expr) = &arg.annotation {
|
if let Some(expr) = &arg.annotation {
|
||||||
has_any_typed_arg = true;
|
has_any_typed_arg = true;
|
||||||
if !checker.settings.flake8_annotations.allow_star_arg_any {
|
if !checker.settings.flake8_annotations.allow_star_arg_any {
|
||||||
|
@ -598,7 +596,7 @@ pub(crate) fn definition(
|
||||||
}
|
}
|
||||||
|
|
||||||
// ANN003, ANN401
|
// ANN003, ANN401
|
||||||
if let Some(arg) = &arguments.kwarg {
|
if let Some(arg) = ¶meters.kwarg {
|
||||||
if let Some(expr) = &arg.annotation {
|
if let Some(expr) = &arg.annotation {
|
||||||
has_any_typed_arg = true;
|
has_any_typed_arg = true;
|
||||||
if !checker.settings.flake8_annotations.allow_star_arg_any {
|
if !checker.settings.flake8_annotations.allow_star_arg_any {
|
||||||
|
@ -629,18 +627,18 @@ pub(crate) fn definition(
|
||||||
}
|
}
|
||||||
|
|
||||||
// ANN101, ANN102
|
// ANN101, ANN102
|
||||||
if is_method && !visibility::is_staticmethod(cast::decorator_list(stmt), checker.semantic()) {
|
if is_method && !visibility::is_staticmethod(decorator_list, checker.semantic()) {
|
||||||
if let Some(ParameterWithDefault {
|
if let Some(ParameterWithDefault {
|
||||||
parameter,
|
parameter,
|
||||||
default: _,
|
default: _,
|
||||||
range: _,
|
range: _,
|
||||||
}) = arguments
|
}) = parameters
|
||||||
.posonlyargs
|
.posonlyargs
|
||||||
.first()
|
.first()
|
||||||
.or_else(|| arguments.args.first())
|
.or_else(|| parameters.args.first())
|
||||||
{
|
{
|
||||||
if parameter.annotation.is_none() {
|
if parameter.annotation.is_none() {
|
||||||
if visibility::is_classmethod(cast::decorator_list(stmt), checker.semantic()) {
|
if visibility::is_classmethod(decorator_list, checker.semantic()) {
|
||||||
if checker.enabled(Rule::MissingTypeCls) {
|
if checker.enabled(Rule::MissingTypeCls) {
|
||||||
diagnostics.push(Diagnostic::new(
|
diagnostics.push(Diagnostic::new(
|
||||||
MissingTypeCls {
|
MissingTypeCls {
|
||||||
|
@ -676,24 +674,22 @@ pub(crate) fn definition(
|
||||||
// (explicitly or implicitly).
|
// (explicitly or implicitly).
|
||||||
checker.settings.flake8_annotations.suppress_none_returning && is_none_returning(body)
|
checker.settings.flake8_annotations.suppress_none_returning && is_none_returning(body)
|
||||||
) {
|
) {
|
||||||
if is_method && visibility::is_classmethod(cast::decorator_list(stmt), checker.semantic()) {
|
if is_method && visibility::is_classmethod(decorator_list, checker.semantic()) {
|
||||||
if checker.enabled(Rule::MissingReturnTypeClassMethod) {
|
if checker.enabled(Rule::MissingReturnTypeClassMethod) {
|
||||||
diagnostics.push(Diagnostic::new(
|
diagnostics.push(Diagnostic::new(
|
||||||
MissingReturnTypeClassMethod {
|
MissingReturnTypeClassMethod {
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
},
|
},
|
||||||
stmt.identifier(),
|
function.identifier(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
} else if is_method
|
} else if is_method && visibility::is_staticmethod(decorator_list, checker.semantic()) {
|
||||||
&& visibility::is_staticmethod(cast::decorator_list(stmt), checker.semantic())
|
|
||||||
{
|
|
||||||
if checker.enabled(Rule::MissingReturnTypeStaticMethod) {
|
if checker.enabled(Rule::MissingReturnTypeStaticMethod) {
|
||||||
diagnostics.push(Diagnostic::new(
|
diagnostics.push(Diagnostic::new(
|
||||||
MissingReturnTypeStaticMethod {
|
MissingReturnTypeStaticMethod {
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
},
|
},
|
||||||
stmt.identifier(),
|
function.identifier(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
} else if is_method && visibility::is_init(name) {
|
} else if is_method && visibility::is_init(name) {
|
||||||
|
@ -705,15 +701,15 @@ pub(crate) fn definition(
|
||||||
MissingReturnTypeSpecialMethod {
|
MissingReturnTypeSpecialMethod {
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
},
|
},
|
||||||
stmt.identifier(),
|
function.identifier(),
|
||||||
);
|
);
|
||||||
if checker.patch(diagnostic.kind.rule()) {
|
if checker.patch(diagnostic.kind.rule()) {
|
||||||
diagnostic.try_set_fix(|| {
|
diagnostic.try_set_fix(|| {
|
||||||
fixes::add_return_annotation(
|
fixes::add_return_annotation(
|
||||||
checker.locator(),
|
function,
|
||||||
stmt,
|
|
||||||
"None",
|
"None",
|
||||||
checker.source_type,
|
checker.source_type,
|
||||||
|
checker.locator(),
|
||||||
)
|
)
|
||||||
.map(Fix::suggested)
|
.map(Fix::suggested)
|
||||||
});
|
});
|
||||||
|
@ -727,16 +723,16 @@ pub(crate) fn definition(
|
||||||
MissingReturnTypeSpecialMethod {
|
MissingReturnTypeSpecialMethod {
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
},
|
},
|
||||||
stmt.identifier(),
|
function.identifier(),
|
||||||
);
|
);
|
||||||
if checker.patch(diagnostic.kind.rule()) {
|
if checker.patch(diagnostic.kind.rule()) {
|
||||||
if let Some(return_type) = simple_magic_return_type(name) {
|
if let Some(return_type) = simple_magic_return_type(name) {
|
||||||
diagnostic.try_set_fix(|| {
|
diagnostic.try_set_fix(|| {
|
||||||
fixes::add_return_annotation(
|
fixes::add_return_annotation(
|
||||||
checker.locator(),
|
function,
|
||||||
stmt,
|
|
||||||
return_type,
|
return_type,
|
||||||
checker.source_type,
|
checker.source_type,
|
||||||
|
checker.locator(),
|
||||||
)
|
)
|
||||||
.map(Fix::suggested)
|
.map(Fix::suggested)
|
||||||
});
|
});
|
||||||
|
@ -752,7 +748,7 @@ pub(crate) fn definition(
|
||||||
MissingReturnTypeUndocumentedPublicFunction {
|
MissingReturnTypeUndocumentedPublicFunction {
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
},
|
},
|
||||||
stmt.identifier(),
|
function.identifier(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -762,7 +758,7 @@ pub(crate) fn definition(
|
||||||
MissingReturnTypePrivateFunction {
|
MissingReturnTypePrivateFunction {
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
},
|
},
|
||||||
stmt.identifier(),
|
function.identifier(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
use ruff_python_ast as ast;
|
use ruff_python_ast::Ranged;
|
||||||
use ruff_python_ast::{Ranged, Stmt};
|
|
||||||
|
|
||||||
use ruff_diagnostics::{Diagnostic, Violation};
|
use ruff_diagnostics::{Diagnostic, Violation};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
use ruff_python_ast::Expr;
|
use ruff_python_ast::helpers::map_subscript;
|
||||||
|
|
||||||
use ruff_python_semantic::{Definition, Member, MemberKind};
|
use ruff_python_semantic::{Definition, Member, MemberKind};
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
|
@ -67,39 +67,29 @@ impl Violation for IterMethodReturnIterable {
|
||||||
/// PYI045
|
/// PYI045
|
||||||
pub(crate) fn iter_method_return_iterable(checker: &mut Checker, definition: &Definition) {
|
pub(crate) fn iter_method_return_iterable(checker: &mut Checker, definition: &Definition) {
|
||||||
let Definition::Member(Member {
|
let Definition::Member(Member {
|
||||||
kind: MemberKind::Method,
|
kind: MemberKind::Method(function),
|
||||||
stmt,
|
|
||||||
..
|
..
|
||||||
}) = definition
|
}) = definition
|
||||||
else {
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let Stmt::FunctionDef(ast::StmtFunctionDef { name, returns, .. }) = stmt else {
|
let Some(returns) = function.returns.as_ref() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some(returns) = returns else {
|
let is_async = match function.name.as_str() {
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let annotation = if let Expr::Subscript(ast::ExprSubscript { value, .. }) = returns.as_ref() {
|
|
||||||
// Ex) `Iterable[T]`
|
|
||||||
value
|
|
||||||
} else {
|
|
||||||
// Ex) `Iterable`, `typing.Iterable`
|
|
||||||
returns
|
|
||||||
};
|
|
||||||
|
|
||||||
let is_async = match name.as_str() {
|
|
||||||
"__iter__" => false,
|
"__iter__" => false,
|
||||||
"__aiter__" => true,
|
"__aiter__" => true,
|
||||||
_ => return,
|
_ => return,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Support both `Iterable` and `Iterable[T]`.
|
||||||
|
let annotation = map_subscript(returns);
|
||||||
|
|
||||||
if checker
|
if checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(annotation)
|
.resolve_call_path(map_subscript(annotation))
|
||||||
.is_some_and(|call_path| {
|
.is_some_and(|call_path| {
|
||||||
if is_async {
|
if is_async {
|
||||||
matches!(
|
matches!(
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
|
|
||||||
use ruff_python_ast::call_path::from_qualified_name;
|
use ruff_python_ast::call_path::from_qualified_name;
|
||||||
use ruff_python_ast::cast;
|
|
||||||
use ruff_python_ast::helpers::map_callable;
|
use ruff_python_ast::helpers::map_callable;
|
||||||
use ruff_python_ast::str::is_implicit_concatenation;
|
use ruff_python_ast::str::is_implicit_concatenation;
|
||||||
use ruff_python_semantic::{Definition, Member, MemberKind, SemanticModel};
|
use ruff_python_semantic::{Definition, SemanticModel};
|
||||||
use ruff_source_file::UniversalNewlines;
|
use ruff_source_file::UniversalNewlines;
|
||||||
|
|
||||||
/// Return the index of the first logical line in a string.
|
/// Return the index of the first logical line in a string.
|
||||||
|
@ -48,25 +47,19 @@ pub(crate) fn should_ignore_definition(
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Definition::Member(Member {
|
let Some(function) = definition.as_function_def() else {
|
||||||
kind: MemberKind::Function | MemberKind::NestedFunction | MemberKind::Method,
|
return false;
|
||||||
stmt,
|
};
|
||||||
..
|
|
||||||
}) = definition
|
function.decorator_list.iter().any(|decorator| {
|
||||||
{
|
semantic
|
||||||
for decorator in cast::decorator_list(stmt) {
|
.resolve_call_path(map_callable(&decorator.expression))
|
||||||
if let Some(call_path) = semantic.resolve_call_path(map_callable(&decorator.expression))
|
.is_some_and(|call_path| {
|
||||||
{
|
ignore_decorators
|
||||||
if ignore_decorators
|
|
||||||
.iter()
|
.iter()
|
||||||
.any(|decorator| from_qualified_name(decorator) == call_path)
|
.any(|decorator| from_qualified_name(decorator) == call_path)
|
||||||
{
|
})
|
||||||
return true;
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if a docstring should be ignored.
|
/// Check if a docstring should be ignored.
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
use ruff_python_ast::Ranged;
|
|
||||||
use ruff_text_size::{TextLen, TextRange};
|
|
||||||
|
|
||||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
|
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
use ruff_python_semantic::{Definition, Member, MemberKind};
|
use ruff_python_ast::Ranged;
|
||||||
use ruff_python_trivia::PythonWhitespace;
|
use ruff_python_trivia::PythonWhitespace;
|
||||||
use ruff_source_file::{UniversalNewlineIterator, UniversalNewlines};
|
use ruff_source_file::{UniversalNewlineIterator, UniversalNewlines};
|
||||||
|
use ruff_text_size::{TextLen, TextRange};
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
use crate::docstrings::Docstring;
|
use crate::docstrings::Docstring;
|
||||||
|
@ -155,12 +153,7 @@ impl AlwaysAutofixableViolation for BlankLineBeforeClass {
|
||||||
|
|
||||||
/// D203, D204, D211
|
/// D203, D204, D211
|
||||||
pub(crate) fn blank_before_after_class(checker: &mut Checker, docstring: &Docstring) {
|
pub(crate) fn blank_before_after_class(checker: &mut Checker, docstring: &Docstring) {
|
||||||
let Definition::Member(Member {
|
let Some(class) = docstring.definition.as_class_def() else {
|
||||||
kind: MemberKind::Class | MemberKind::NestedClass,
|
|
||||||
stmt,
|
|
||||||
..
|
|
||||||
}) = docstring.definition
|
|
||||||
else {
|
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -168,7 +161,7 @@ pub(crate) fn blank_before_after_class(checker: &mut Checker, docstring: &Docstr
|
||||||
// ```python
|
// ```python
|
||||||
// class PhotoMetadata: """Metadata about a photo."""
|
// class PhotoMetadata: """Metadata about a photo."""
|
||||||
// ```
|
// ```
|
||||||
let between_range = TextRange::new(stmt.start(), docstring.start());
|
let between_range = TextRange::new(class.start(), docstring.start());
|
||||||
if !checker.locator().contains_line_break(between_range) {
|
if !checker.locator().contains_line_break(between_range) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -223,7 +216,7 @@ pub(crate) fn blank_before_after_class(checker: &mut Checker, docstring: &Docstr
|
||||||
}
|
}
|
||||||
|
|
||||||
if checker.enabled(Rule::OneBlankLineAfterClass) {
|
if checker.enabled(Rule::OneBlankLineAfterClass) {
|
||||||
let after_range = TextRange::new(docstring.end(), stmt.end());
|
let after_range = TextRange::new(docstring.end(), class.end());
|
||||||
|
|
||||||
let after = checker.locator().slice(after_range);
|
let after = checker.locator().slice(after_range);
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use ruff_python_ast::Ranged;
|
|
||||||
use ruff_text_size::{TextLen, TextRange};
|
|
||||||
|
|
||||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
|
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
use ruff_python_semantic::{Definition, Member, MemberKind};
|
use ruff_python_ast::Ranged;
|
||||||
use ruff_python_trivia::PythonWhitespace;
|
use ruff_python_trivia::PythonWhitespace;
|
||||||
use ruff_source_file::{UniversalNewlineIterator, UniversalNewlines};
|
use ruff_source_file::{UniversalNewlineIterator, UniversalNewlines};
|
||||||
|
use ruff_text_size::{TextLen, TextRange};
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
use crate::docstrings::Docstring;
|
use crate::docstrings::Docstring;
|
||||||
|
@ -104,21 +103,16 @@ static INNER_FUNCTION_OR_CLASS_REGEX: Lazy<Regex> =
|
||||||
|
|
||||||
/// D201, D202
|
/// D201, D202
|
||||||
pub(crate) fn blank_before_after_function(checker: &mut Checker, docstring: &Docstring) {
|
pub(crate) fn blank_before_after_function(checker: &mut Checker, docstring: &Docstring) {
|
||||||
let Definition::Member(Member {
|
let Some(function) = docstring.definition.as_function_def() else {
|
||||||
kind: MemberKind::Function | MemberKind::NestedFunction | MemberKind::Method,
|
|
||||||
stmt,
|
|
||||||
..
|
|
||||||
}) = docstring.definition
|
|
||||||
else {
|
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
if checker.enabled(Rule::NoBlankLineBeforeFunction) {
|
if checker.enabled(Rule::NoBlankLineBeforeFunction) {
|
||||||
let before = checker
|
let before = checker
|
||||||
.locator()
|
.locator()
|
||||||
.slice(TextRange::new(stmt.start(), docstring.start()));
|
.slice(TextRange::new(function.start(), docstring.start()));
|
||||||
|
|
||||||
let mut lines = UniversalNewlineIterator::with_offset(before, stmt.start()).rev();
|
let mut lines = UniversalNewlineIterator::with_offset(before, function.start()).rev();
|
||||||
let mut blank_lines_before = 0usize;
|
let mut blank_lines_before = 0usize;
|
||||||
let mut blank_lines_start = lines.next().map(|l| l.end()).unwrap_or_default();
|
let mut blank_lines_start = lines.next().map(|l| l.end()).unwrap_or_default();
|
||||||
|
|
||||||
|
@ -152,7 +146,7 @@ pub(crate) fn blank_before_after_function(checker: &mut Checker, docstring: &Doc
|
||||||
if checker.enabled(Rule::NoBlankLineAfterFunction) {
|
if checker.enabled(Rule::NoBlankLineAfterFunction) {
|
||||||
let after = checker
|
let after = checker
|
||||||
.locator()
|
.locator()
|
||||||
.slice(TextRange::new(docstring.end(), stmt.end()));
|
.slice(TextRange::new(docstring.end(), function.end()));
|
||||||
|
|
||||||
// If the docstring is only followed by blank and commented lines, abort.
|
// If the docstring is only followed by blank and commented lines, abort.
|
||||||
let all_blank_after = after.universal_newlines().skip(1).all(|line| {
|
let all_blank_after = after.universal_newlines().skip(1).all(|line| {
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
use ruff_python_ast::Ranged;
|
|
||||||
use ruff_text_size::{TextLen, TextRange};
|
|
||||||
|
|
||||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
|
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
use ruff_python_semantic::{Definition, Member, MemberKind};
|
use ruff_python_ast::Ranged;
|
||||||
|
use ruff_text_size::{TextLen, TextRange};
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
use crate::docstrings::Docstring;
|
use crate::docstrings::Docstring;
|
||||||
|
@ -57,13 +55,7 @@ impl AlwaysAutofixableViolation for FirstLineCapitalized {
|
||||||
|
|
||||||
/// D403
|
/// D403
|
||||||
pub(crate) fn capitalized(checker: &mut Checker, docstring: &Docstring) {
|
pub(crate) fn capitalized(checker: &mut Checker, docstring: &Docstring) {
|
||||||
if !matches!(
|
if docstring.definition.as_function_def().is_none() {
|
||||||
docstring.definition,
|
|
||||||
Definition::Member(Member {
|
|
||||||
kind: MemberKind::Function | MemberKind::NestedFunction | MemberKind::Method,
|
|
||||||
..
|
|
||||||
})
|
|
||||||
) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
use ruff_diagnostics::{Diagnostic, Violation};
|
use ruff_diagnostics::{Diagnostic, Violation};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
use ruff_python_ast::cast;
|
|
||||||
use ruff_python_ast::identifier::Identifier;
|
use ruff_python_ast::identifier::Identifier;
|
||||||
use ruff_python_semantic::analyze::visibility::is_overload;
|
use ruff_python_semantic::analyze::visibility::is_overload;
|
||||||
use ruff_python_semantic::{Definition, Member, MemberKind};
|
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
use crate::docstrings::Docstring;
|
use crate::docstrings::Docstring;
|
||||||
|
@ -80,18 +78,13 @@ impl Violation for OverloadWithDocstring {
|
||||||
|
|
||||||
/// D418
|
/// D418
|
||||||
pub(crate) fn if_needed(checker: &mut Checker, docstring: &Docstring) {
|
pub(crate) fn if_needed(checker: &mut Checker, docstring: &Docstring) {
|
||||||
let Definition::Member(Member {
|
let Some(function) = docstring.definition.as_function_def() else {
|
||||||
kind: MemberKind::Function | MemberKind::NestedFunction | MemberKind::Method,
|
|
||||||
stmt,
|
|
||||||
..
|
|
||||||
}) = docstring.definition
|
|
||||||
else {
|
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if !is_overload(cast::decorator_list(stmt), checker.semantic()) {
|
if is_overload(&function.decorator_list, checker.semantic()) {
|
||||||
return;
|
checker.diagnostics.push(Diagnostic::new(
|
||||||
|
OverloadWithDocstring,
|
||||||
|
function.identifier(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
checker
|
|
||||||
.diagnostics
|
|
||||||
.push(Diagnostic::new(OverloadWithDocstring, stmt.identifier()));
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ use ruff_text_size::{TextRange, TextSize};
|
||||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
|
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
use ruff_python_ast::str::{is_triple_quote, leading_quote};
|
use ruff_python_ast::str::{is_triple_quote, leading_quote};
|
||||||
use ruff_python_semantic::{Definition, Member};
|
use ruff_python_semantic::Definition;
|
||||||
use ruff_source_file::{NewlineWithTrailingNewline, UniversalNewlineIterator};
|
use ruff_source_file::{NewlineWithTrailingNewline, UniversalNewlineIterator};
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
|
@ -164,11 +164,11 @@ pub(crate) fn multi_line_summary_start(checker: &mut Checker, docstring: &Docstr
|
||||||
|
|
||||||
// If the docstring isn't on its own line, look at the statement indentation,
|
// If the docstring isn't on its own line, look at the statement indentation,
|
||||||
// and add the default indentation to get the "right" level.
|
// and add the default indentation to get the "right" level.
|
||||||
if let Definition::Member(Member { stmt, .. }) = &docstring.definition {
|
if let Definition::Member(member) = &docstring.definition {
|
||||||
let stmt_line_start = checker.locator().line_start(stmt.start());
|
let stmt_line_start = checker.locator().line_start(member.start());
|
||||||
let stmt_indentation = checker
|
let stmt_indentation = checker
|
||||||
.locator()
|
.locator()
|
||||||
.slice(TextRange::new(stmt_line_start, stmt.start()));
|
.slice(TextRange::new(stmt_line_start, member.start()));
|
||||||
|
|
||||||
if stmt_indentation.chars().all(char::is_whitespace) {
|
if stmt_indentation.chars().all(char::is_whitespace) {
|
||||||
indentation.clear();
|
indentation.clear();
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
use ruff_python_ast::{self as ast, Stmt};
|
|
||||||
|
|
||||||
use ruff_diagnostics::{Diagnostic, Violation};
|
use ruff_diagnostics::{Diagnostic, Violation};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
use ruff_python_semantic::{Definition, Member, MemberKind};
|
|
||||||
use ruff_source_file::UniversalNewlines;
|
use ruff_source_file::UniversalNewlines;
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
|
@ -54,15 +51,7 @@ impl Violation for NoSignature {
|
||||||
|
|
||||||
/// D402
|
/// D402
|
||||||
pub(crate) fn no_signature(checker: &mut Checker, docstring: &Docstring) {
|
pub(crate) fn no_signature(checker: &mut Checker, docstring: &Docstring) {
|
||||||
let Definition::Member(Member {
|
let Some(function) = docstring.definition.as_function_def() else {
|
||||||
kind: MemberKind::Function | MemberKind::NestedFunction | MemberKind::Method,
|
|
||||||
stmt,
|
|
||||||
..
|
|
||||||
}) = docstring.definition
|
|
||||||
else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let Stmt::FunctionDef(ast::StmtFunctionDef { name, .. }) = stmt else {
|
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -75,8 +64,8 @@ pub(crate) fn no_signature(checker: &mut Checker, docstring: &Docstring) {
|
||||||
// Search for occurrences of the function name followed by an open parenthesis (e.g., `foo(` for
|
// Search for occurrences of the function name followed by an open parenthesis (e.g., `foo(` for
|
||||||
// a function named `foo`).
|
// a function named `foo`).
|
||||||
if first_line
|
if first_line
|
||||||
.match_indices(name.as_str())
|
.match_indices(function.name.as_str())
|
||||||
.any(|(index, _)| first_line[index + name.len()..].starts_with('('))
|
.any(|(index, _)| first_line[index + function.name.len()..].starts_with('('))
|
||||||
{
|
{
|
||||||
checker
|
checker
|
||||||
.diagnostics
|
.diagnostics
|
||||||
|
|
|
@ -6,9 +6,7 @@ use once_cell::sync::Lazy;
|
||||||
use ruff_diagnostics::{Diagnostic, Violation};
|
use ruff_diagnostics::{Diagnostic, Violation};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
use ruff_python_ast::call_path::{from_qualified_name, CallPath};
|
use ruff_python_ast::call_path::{from_qualified_name, CallPath};
|
||||||
use ruff_python_ast::cast;
|
|
||||||
use ruff_python_semantic::analyze::visibility::{is_property, is_test};
|
use ruff_python_semantic::analyze::visibility::{is_property, is_test};
|
||||||
use ruff_python_semantic::{Definition, Member, MemberKind};
|
|
||||||
use ruff_source_file::UniversalNewlines;
|
use ruff_source_file::UniversalNewlines;
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
|
@ -69,25 +67,18 @@ pub(crate) fn non_imperative_mood(
|
||||||
docstring: &Docstring,
|
docstring: &Docstring,
|
||||||
property_decorators: &BTreeSet<String>,
|
property_decorators: &BTreeSet<String>,
|
||||||
) {
|
) {
|
||||||
let Definition::Member(Member { kind, stmt, .. }) = &docstring.definition else {
|
let Some(function) = docstring.definition.as_function_def() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
if !matches!(
|
|
||||||
kind,
|
|
||||||
MemberKind::Function | MemberKind::NestedFunction | MemberKind::Method,
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let property_decorators = property_decorators
|
let property_decorators = property_decorators
|
||||||
.iter()
|
.iter()
|
||||||
.map(|decorator| from_qualified_name(decorator))
|
.map(|decorator| from_qualified_name(decorator))
|
||||||
.collect::<Vec<CallPath>>();
|
.collect::<Vec<CallPath>>();
|
||||||
|
|
||||||
if is_test(cast::name(stmt))
|
if is_test(&function.name)
|
||||||
|| is_property(
|
|| is_property(
|
||||||
cast::decorator_list(stmt),
|
&function.decorator_list,
|
||||||
&property_decorators,
|
&property_decorators,
|
||||||
checker.semantic(),
|
checker.semantic(),
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
use ruff_text_size::TextRange;
|
|
||||||
|
|
||||||
use ruff_diagnostics::{Diagnostic, Violation};
|
use ruff_diagnostics::{Diagnostic, Violation};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
use ruff_python_ast::cast;
|
|
||||||
use ruff_python_ast::identifier::Identifier;
|
use ruff_python_ast::identifier::Identifier;
|
||||||
use ruff_python_semantic::analyze::visibility::{
|
use ruff_python_semantic::analyze::visibility::{
|
||||||
is_call, is_init, is_magic, is_new, is_overload, is_override, Visibility,
|
is_call, is_init, is_magic, is_new, is_overload, is_override, Visibility,
|
||||||
};
|
};
|
||||||
use ruff_python_semantic::{Definition, Member, MemberKind, Module, ModuleKind};
|
use ruff_python_semantic::{Definition, Member, MemberKind, Module, ModuleKind};
|
||||||
|
use ruff_text_size::TextRange;
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
use crate::registry::Rule;
|
use crate::registry::Rule;
|
||||||
|
@ -557,82 +555,82 @@ pub(crate) fn not_missing(
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
Definition::Member(Member {
|
Definition::Member(Member {
|
||||||
kind: MemberKind::Class,
|
kind: MemberKind::Class(class),
|
||||||
stmt,
|
|
||||||
..
|
..
|
||||||
}) => {
|
}) => {
|
||||||
if checker.enabled(Rule::UndocumentedPublicClass) {
|
if checker.enabled(Rule::UndocumentedPublicClass) {
|
||||||
checker
|
checker
|
||||||
.diagnostics
|
.diagnostics
|
||||||
.push(Diagnostic::new(UndocumentedPublicClass, stmt.identifier()));
|
.push(Diagnostic::new(UndocumentedPublicClass, class.identifier()));
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
Definition::Member(Member {
|
Definition::Member(Member {
|
||||||
kind: MemberKind::NestedClass,
|
kind: MemberKind::NestedClass(function),
|
||||||
stmt,
|
|
||||||
..
|
..
|
||||||
}) => {
|
}) => {
|
||||||
if checker.enabled(Rule::UndocumentedPublicNestedClass) {
|
if checker.enabled(Rule::UndocumentedPublicNestedClass) {
|
||||||
checker.diagnostics.push(Diagnostic::new(
|
checker.diagnostics.push(Diagnostic::new(
|
||||||
UndocumentedPublicNestedClass,
|
UndocumentedPublicNestedClass,
|
||||||
stmt.identifier(),
|
function.identifier(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
Definition::Member(Member {
|
Definition::Member(Member {
|
||||||
kind: MemberKind::Function | MemberKind::NestedFunction,
|
kind: MemberKind::Function(function) | MemberKind::NestedFunction(function),
|
||||||
stmt,
|
|
||||||
..
|
..
|
||||||
}) => {
|
}) => {
|
||||||
if is_overload(cast::decorator_list(stmt), checker.semantic()) {
|
if is_overload(&function.decorator_list, checker.semantic()) {
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
if checker.enabled(Rule::UndocumentedPublicFunction) {
|
if checker.enabled(Rule::UndocumentedPublicFunction) {
|
||||||
checker.diagnostics.push(Diagnostic::new(
|
checker.diagnostics.push(Diagnostic::new(
|
||||||
UndocumentedPublicFunction,
|
UndocumentedPublicFunction,
|
||||||
stmt.identifier(),
|
function.identifier(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Definition::Member(Member {
|
Definition::Member(Member {
|
||||||
kind: MemberKind::Method,
|
kind: MemberKind::Method(function),
|
||||||
stmt,
|
|
||||||
..
|
..
|
||||||
}) => {
|
}) => {
|
||||||
if is_overload(cast::decorator_list(stmt), checker.semantic())
|
if is_overload(&function.decorator_list, checker.semantic())
|
||||||
|| is_override(cast::decorator_list(stmt), checker.semantic())
|
|| is_override(&function.decorator_list, checker.semantic())
|
||||||
{
|
{
|
||||||
true
|
true
|
||||||
} else if is_init(cast::name(stmt)) {
|
} else if is_init(&function.name) {
|
||||||
if checker.enabled(Rule::UndocumentedPublicInit) {
|
if checker.enabled(Rule::UndocumentedPublicInit) {
|
||||||
checker
|
checker.diagnostics.push(Diagnostic::new(
|
||||||
.diagnostics
|
UndocumentedPublicInit,
|
||||||
.push(Diagnostic::new(UndocumentedPublicInit, stmt.identifier()));
|
function.identifier(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
} else if is_new(cast::name(stmt)) || is_call(cast::name(stmt)) {
|
} else if is_new(&function.name) || is_call(&function.name) {
|
||||||
if checker.enabled(Rule::UndocumentedPublicMethod) {
|
if checker.enabled(Rule::UndocumentedPublicMethod) {
|
||||||
checker
|
checker.diagnostics.push(Diagnostic::new(
|
||||||
.diagnostics
|
UndocumentedPublicMethod,
|
||||||
.push(Diagnostic::new(UndocumentedPublicMethod, stmt.identifier()));
|
function.identifier(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
} else if is_magic(cast::name(stmt)) {
|
} else if is_magic(&function.name) {
|
||||||
if checker.enabled(Rule::UndocumentedMagicMethod) {
|
if checker.enabled(Rule::UndocumentedMagicMethod) {
|
||||||
checker
|
checker.diagnostics.push(Diagnostic::new(
|
||||||
.diagnostics
|
UndocumentedMagicMethod,
|
||||||
.push(Diagnostic::new(UndocumentedMagicMethod, stmt.identifier()));
|
function.identifier(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
if checker.enabled(Rule::UndocumentedPublicMethod) {
|
if checker.enabled(Rule::UndocumentedPublicMethod) {
|
||||||
checker
|
checker.diagnostics.push(Diagnostic::new(
|
||||||
.diagnostics
|
UndocumentedPublicMethod,
|
||||||
.push(Diagnostic::new(UndocumentedPublicMethod, stmt.identifier()));
|
function.identifier(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,18 @@
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use ruff_python_ast::{self as ast, ParameterWithDefault, Stmt};
|
|
||||||
use ruff_text_size::{TextLen, TextRange, TextSize};
|
|
||||||
use rustc_hash::FxHashSet;
|
use rustc_hash::FxHashSet;
|
||||||
|
|
||||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Violation};
|
use ruff_diagnostics::{AlwaysAutofixableViolation, Violation};
|
||||||
use ruff_diagnostics::{Diagnostic, Edit, Fix};
|
use ruff_diagnostics::{Diagnostic, Edit, Fix};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
use ruff_python_ast::cast;
|
|
||||||
use ruff_python_ast::docstrings::{clean_space, leading_space};
|
use ruff_python_ast::docstrings::{clean_space, leading_space};
|
||||||
use ruff_python_ast::identifier::Identifier;
|
use ruff_python_ast::identifier::Identifier;
|
||||||
|
use ruff_python_ast::ParameterWithDefault;
|
||||||
use ruff_python_semantic::analyze::visibility::is_staticmethod;
|
use ruff_python_semantic::analyze::visibility::is_staticmethod;
|
||||||
use ruff_python_semantic::{Definition, Member, MemberKind};
|
|
||||||
use ruff_python_trivia::{textwrap::dedent, PythonWhitespace};
|
use ruff_python_trivia::{textwrap::dedent, PythonWhitespace};
|
||||||
use ruff_source_file::NewlineWithTrailingNewline;
|
use ruff_source_file::NewlineWithTrailingNewline;
|
||||||
|
use ruff_text_size::{TextLen, TextRange, TextSize};
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
use crate::docstrings::sections::{SectionContext, SectionContexts, SectionKind};
|
use crate::docstrings::sections::{SectionContext, SectionContexts, SectionKind};
|
||||||
|
@ -1717,16 +1715,7 @@ fn common_section(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn missing_args(checker: &mut Checker, docstring: &Docstring, docstrings_args: &FxHashSet<String>) {
|
fn missing_args(checker: &mut Checker, docstring: &Docstring, docstrings_args: &FxHashSet<String>) {
|
||||||
let Definition::Member(Member {
|
let Some(function) = docstring.definition.as_function_def() else {
|
||||||
kind: MemberKind::Function | MemberKind::NestedFunction | MemberKind::Method,
|
|
||||||
stmt,
|
|
||||||
..
|
|
||||||
}) = docstring.definition
|
|
||||||
else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let Stmt::FunctionDef(ast::StmtFunctionDef { parameters, .. }) = stmt else {
|
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1736,16 +1725,17 @@ fn missing_args(checker: &mut Checker, docstring: &Docstring, docstrings_args: &
|
||||||
parameter,
|
parameter,
|
||||||
default: _,
|
default: _,
|
||||||
range: _,
|
range: _,
|
||||||
} in parameters
|
} in function
|
||||||
|
.parameters
|
||||||
.posonlyargs
|
.posonlyargs
|
||||||
.iter()
|
.iter()
|
||||||
.chain(¶meters.args)
|
.chain(&function.parameters.args)
|
||||||
.chain(¶meters.kwonlyargs)
|
.chain(&function.parameters.kwonlyargs)
|
||||||
.skip(
|
.skip(
|
||||||
// If this is a non-static method, skip `cls` or `self`.
|
// If this is a non-static method, skip `cls` or `self`.
|
||||||
usize::from(
|
usize::from(
|
||||||
docstring.definition.is_method()
|
docstring.definition.is_method()
|
||||||
&& !is_staticmethod(cast::decorator_list(stmt), checker.semantic()),
|
&& !is_staticmethod(&function.decorator_list, checker.semantic()),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
@ -1757,7 +1747,7 @@ fn missing_args(checker: &mut Checker, docstring: &Docstring, docstrings_args: &
|
||||||
|
|
||||||
// Check specifically for `vararg` and `kwarg`, which can be prefixed with a
|
// Check specifically for `vararg` and `kwarg`, which can be prefixed with a
|
||||||
// single or double star, respectively.
|
// single or double star, respectively.
|
||||||
if let Some(arg) = ¶meters.vararg {
|
if let Some(arg) = function.parameters.vararg.as_ref() {
|
||||||
let arg_name = arg.name.as_str();
|
let arg_name = arg.name.as_str();
|
||||||
let starred_arg_name = format!("*{arg_name}");
|
let starred_arg_name = format!("*{arg_name}");
|
||||||
if !arg_name.starts_with('_')
|
if !arg_name.starts_with('_')
|
||||||
|
@ -1767,7 +1757,7 @@ fn missing_args(checker: &mut Checker, docstring: &Docstring, docstrings_args: &
|
||||||
missing_arg_names.insert(starred_arg_name);
|
missing_arg_names.insert(starred_arg_name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(arg) = ¶meters.kwarg {
|
if let Some(arg) = function.parameters.kwarg.as_ref() {
|
||||||
let arg_name = arg.name.as_str();
|
let arg_name = arg.name.as_str();
|
||||||
let starred_arg_name = format!("**{arg_name}");
|
let starred_arg_name = format!("**{arg_name}");
|
||||||
if !arg_name.starts_with('_')
|
if !arg_name.starts_with('_')
|
||||||
|
@ -1786,7 +1776,7 @@ fn missing_args(checker: &mut Checker, docstring: &Docstring, docstrings_args: &
|
||||||
definition: definition.to_string(),
|
definition: definition.to_string(),
|
||||||
names,
|
names,
|
||||||
},
|
},
|
||||||
stmt.identifier(),
|
function.identifier(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,7 +62,7 @@ pub(crate) fn yield_outside_function(checker: &mut Checker, expr: &Expr) {
|
||||||
Expr::Yield(_) => DeferralKeyword::Yield,
|
Expr::Yield(_) => DeferralKeyword::Yield,
|
||||||
Expr::YieldFrom(_) => DeferralKeyword::YieldFrom,
|
Expr::YieldFrom(_) => DeferralKeyword::YieldFrom,
|
||||||
Expr::Await(_) => DeferralKeyword::Await,
|
Expr::Await(_) => DeferralKeyword::Await,
|
||||||
_ => panic!("Expected Expr::Yield | Expr::YieldFrom | Expr::Await"),
|
_ => return,
|
||||||
};
|
};
|
||||||
checker.diagnostics.push(Diagnostic::new(
|
checker.diagnostics.push(Diagnostic::new(
|
||||||
YieldOutsideFunction { keyword },
|
YieldOutsideFunction { keyword },
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
use crate::{nodes, Decorator, Stmt};
|
|
||||||
|
|
||||||
pub fn name(stmt: &Stmt) -> &str {
|
|
||||||
let Stmt::FunctionDef(nodes::StmtFunctionDef { name, .. }) = stmt else {
|
|
||||||
panic!("Expected Stmt::FunctionDef")
|
|
||||||
};
|
|
||||||
name.as_str()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn decorator_list(stmt: &Stmt) -> &[Decorator] {
|
|
||||||
let Stmt::FunctionDef(nodes::StmtFunctionDef { decorator_list, .. }) = stmt else {
|
|
||||||
panic!("Expected Stmt::FunctionDef")
|
|
||||||
};
|
|
||||||
decorator_list
|
|
||||||
}
|
|
|
@ -20,6 +20,32 @@ pub trait Identifier {
|
||||||
fn identifier(&self) -> TextRange;
|
fn identifier(&self) -> TextRange;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Identifier for ast::StmtFunctionDef {
|
||||||
|
/// Return the [`TextRange`] of the identifier in the given function definition.
|
||||||
|
///
|
||||||
|
/// For example, return the range of `f` in:
|
||||||
|
/// ```python
|
||||||
|
/// def f():
|
||||||
|
/// ...
|
||||||
|
/// ```
|
||||||
|
fn identifier(&self) -> TextRange {
|
||||||
|
self.name.range()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Identifier for ast::StmtClassDef {
|
||||||
|
/// Return the [`TextRange`] of the identifier in the given class definition.
|
||||||
|
///
|
||||||
|
/// For example, return the range of `C` in:
|
||||||
|
/// ```python
|
||||||
|
/// class C():
|
||||||
|
/// ...
|
||||||
|
/// ```
|
||||||
|
fn identifier(&self) -> TextRange {
|
||||||
|
self.name.range()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Identifier for Stmt {
|
impl Identifier for Stmt {
|
||||||
/// Return the [`TextRange`] of the identifier in the given statement.
|
/// Return the [`TextRange`] of the identifier in the given statement.
|
||||||
///
|
///
|
||||||
|
@ -30,8 +56,8 @@ impl Identifier for Stmt {
|
||||||
/// ```
|
/// ```
|
||||||
fn identifier(&self) -> TextRange {
|
fn identifier(&self) -> TextRange {
|
||||||
match self {
|
match self {
|
||||||
Stmt::ClassDef(ast::StmtClassDef { name, .. })
|
Stmt::ClassDef(class) => class.identifier(),
|
||||||
| Stmt::FunctionDef(ast::StmtFunctionDef { name, .. }) => name.range(),
|
Stmt::FunctionDef(function) => function.identifier(),
|
||||||
_ => self.range(),
|
_ => self.range(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@ use std::path::Path;
|
||||||
|
|
||||||
pub mod all;
|
pub mod all;
|
||||||
pub mod call_path;
|
pub mod call_path;
|
||||||
pub mod cast;
|
|
||||||
pub mod comparable;
|
pub mod comparable;
|
||||||
pub mod docstrings;
|
pub mod docstrings;
|
||||||
pub mod hashable;
|
pub mod hashable;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use ruff_python_ast::{self as ast, Decorator, Stmt};
|
use ruff_python_ast::{self as ast, Decorator};
|
||||||
|
|
||||||
use ruff_python_ast::call_path::{collect_call_path, CallPath};
|
use ruff_python_ast::call_path::{collect_call_path, CallPath};
|
||||||
use ruff_python_ast::helpers::map_callable;
|
use ruff_python_ast::helpers::map_callable;
|
||||||
|
@ -176,57 +176,40 @@ impl ModuleSource<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn function_visibility(stmt: &Stmt) -> Visibility {
|
pub(crate) fn function_visibility(function: &ast::StmtFunctionDef) -> Visibility {
|
||||||
match stmt {
|
if function.name.starts_with('_') {
|
||||||
Stmt::FunctionDef(ast::StmtFunctionDef { name, .. }) => {
|
Visibility::Private
|
||||||
if name.starts_with('_') {
|
} else {
|
||||||
Visibility::Private
|
Visibility::Public
|
||||||
} else {
|
|
||||||
Visibility::Public
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => panic!("Found non-FunctionDef in function_visibility"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn method_visibility(stmt: &Stmt) -> Visibility {
|
pub(crate) fn method_visibility(function: &ast::StmtFunctionDef) -> Visibility {
|
||||||
let Stmt::FunctionDef(ast::StmtFunctionDef {
|
|
||||||
name,
|
|
||||||
decorator_list,
|
|
||||||
..
|
|
||||||
}) = stmt
|
|
||||||
else {
|
|
||||||
panic!("Found non-FunctionDef in method_visibility")
|
|
||||||
};
|
|
||||||
|
|
||||||
// Is this a setter or deleter?
|
// Is this a setter or deleter?
|
||||||
if decorator_list.iter().any(|decorator| {
|
if function.decorator_list.iter().any(|decorator| {
|
||||||
collect_call_path(&decorator.expression).is_some_and(|call_path| {
|
collect_call_path(&decorator.expression).is_some_and(|call_path| {
|
||||||
call_path.as_slice() == [name, "setter"] || call_path.as_slice() == [name, "deleter"]
|
call_path.as_slice() == [function.name.as_str(), "setter"]
|
||||||
|
|| call_path.as_slice() == [function.name.as_str(), "deleter"]
|
||||||
})
|
})
|
||||||
}) {
|
}) {
|
||||||
return Visibility::Private;
|
return Visibility::Private;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Is the method non-private?
|
// Is the method non-private?
|
||||||
if !name.starts_with('_') {
|
if !function.name.starts_with('_') {
|
||||||
return Visibility::Public;
|
return Visibility::Public;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Is this a magic method?
|
// Is this a magic method?
|
||||||
if name.starts_with("__") && name.ends_with("__") {
|
if function.name.starts_with("__") && function.name.ends_with("__") {
|
||||||
return Visibility::Public;
|
return Visibility::Public;
|
||||||
}
|
}
|
||||||
|
|
||||||
Visibility::Private
|
Visibility::Private
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn class_visibility(stmt: &Stmt) -> Visibility {
|
pub(crate) fn class_visibility(class: &ast::StmtClassDef) -> Visibility {
|
||||||
let Stmt::ClassDef(ast::StmtClassDef { name, .. }) = stmt else {
|
if class.name.starts_with('_') {
|
||||||
panic!("Found non-ClassDef in class_visibility")
|
|
||||||
};
|
|
||||||
|
|
||||||
if name.starts_with('_') {
|
|
||||||
Visibility::Private
|
Visibility::Private
|
||||||
} else {
|
} else {
|
||||||
Visibility::Public
|
Visibility::Public
|
||||||
|
|
|
@ -5,7 +5,8 @@ use std::fmt::Debug;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
|
||||||
use ruff_index::{newtype_index, IndexSlice, IndexVec};
|
use ruff_index::{newtype_index, IndexSlice, IndexVec};
|
||||||
use ruff_python_ast::{self as ast, Stmt};
|
use ruff_python_ast::{self as ast, Ranged, Stmt};
|
||||||
|
use ruff_text_size::TextRange;
|
||||||
|
|
||||||
use crate::analyze::visibility::{
|
use crate::analyze::visibility::{
|
||||||
class_visibility, function_visibility, method_visibility, ModuleSource, Visibility,
|
class_visibility, function_visibility, method_visibility, ModuleSource, Visibility,
|
||||||
|
@ -23,7 +24,7 @@ impl DefinitionId {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, is_macro::Is)]
|
||||||
pub enum ModuleKind {
|
pub enum ModuleKind {
|
||||||
/// A Python file that represents a module within a package.
|
/// A Python file that represents a module within a package.
|
||||||
Module,
|
Module,
|
||||||
|
@ -57,35 +58,60 @@ impl<'a> Module<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone, is_macro::Is)]
|
||||||
pub enum MemberKind {
|
pub enum MemberKind<'a> {
|
||||||
/// A class definition within a program.
|
/// A class definition within a program.
|
||||||
Class,
|
Class(&'a ast::StmtClassDef),
|
||||||
/// A nested class definition within a program.
|
/// A nested class definition within a program.
|
||||||
NestedClass,
|
NestedClass(&'a ast::StmtClassDef),
|
||||||
/// A function definition within a program.
|
/// A function definition within a program.
|
||||||
Function,
|
Function(&'a ast::StmtFunctionDef),
|
||||||
/// A nested function definition within a program.
|
/// A nested function definition within a program.
|
||||||
NestedFunction,
|
NestedFunction(&'a ast::StmtFunctionDef),
|
||||||
/// A method definition within a program.
|
/// A method definition within a program.
|
||||||
Method,
|
Method(&'a ast::StmtFunctionDef),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A member of a Python module.
|
/// A member of a Python module.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Member<'a> {
|
pub struct Member<'a> {
|
||||||
pub kind: MemberKind,
|
pub kind: MemberKind<'a>,
|
||||||
pub parent: DefinitionId,
|
pub parent: DefinitionId,
|
||||||
pub stmt: &'a Stmt,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Member<'a> {
|
impl<'a> Member<'a> {
|
||||||
/// Return the name of the member.
|
/// Return the name of the member.
|
||||||
pub fn name(&self) -> Option<&'a str> {
|
pub fn name(&self) -> &str {
|
||||||
match &self.stmt {
|
match self.kind {
|
||||||
Stmt::FunctionDef(ast::StmtFunctionDef { name, .. })
|
MemberKind::Class(class) => &class.name,
|
||||||
| Stmt::ClassDef(ast::StmtClassDef { name, .. }) => Some(name),
|
MemberKind::NestedClass(class) => &class.name,
|
||||||
_ => None,
|
MemberKind::Function(function) => &function.name,
|
||||||
|
MemberKind::NestedFunction(function) => &function.name,
|
||||||
|
MemberKind::Method(method) => &method.name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the body of the member.
|
||||||
|
pub fn body(&self) -> &[Stmt] {
|
||||||
|
match self.kind {
|
||||||
|
MemberKind::Class(class) => &class.body,
|
||||||
|
MemberKind::NestedClass(class) => &class.body,
|
||||||
|
MemberKind::Function(function) => &function.body,
|
||||||
|
MemberKind::NestedFunction(function) => &function.body,
|
||||||
|
MemberKind::Method(method) => &method.body,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ranged for Member<'_> {
|
||||||
|
/// Return the range of the member.
|
||||||
|
fn range(&self) -> TextRange {
|
||||||
|
match self.kind {
|
||||||
|
MemberKind::Class(class) => class.range(),
|
||||||
|
MemberKind::NestedClass(class) => class.range(),
|
||||||
|
MemberKind::Function(function) => function.range(),
|
||||||
|
MemberKind::NestedFunction(function) => function.range(),
|
||||||
|
MemberKind::Method(method) => method.range(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -103,16 +129,42 @@ impl Definition<'_> {
|
||||||
matches!(
|
matches!(
|
||||||
self,
|
self,
|
||||||
Definition::Member(Member {
|
Definition::Member(Member {
|
||||||
kind: MemberKind::Method,
|
kind: MemberKind::Method(_),
|
||||||
..
|
..
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the name of the definition.
|
||||||
pub fn name(&self) -> Option<&str> {
|
pub fn name(&self) -> Option<&str> {
|
||||||
match self {
|
match self {
|
||||||
Definition::Module(module) => module.name(),
|
Definition::Module(module) => module.name(),
|
||||||
Definition::Member(member) => member.name(),
|
Definition::Member(member) => Some(member.name()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the [`ast::StmtFunctionDef`] of the definition, if it's a function definition.
|
||||||
|
pub fn as_function_def(&self) -> Option<&ast::StmtFunctionDef> {
|
||||||
|
match self {
|
||||||
|
Definition::Member(Member {
|
||||||
|
kind:
|
||||||
|
MemberKind::Function(function)
|
||||||
|
| MemberKind::NestedFunction(function)
|
||||||
|
| MemberKind::Method(function),
|
||||||
|
..
|
||||||
|
}) => Some(function),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the [`ast::StmtClassDef`] of the definition, if it's a class definition.
|
||||||
|
pub fn as_class_def(&self) -> Option<&ast::StmtClassDef> {
|
||||||
|
match self {
|
||||||
|
Definition::Member(Member {
|
||||||
|
kind: MemberKind::Class(class) | MemberKind::NestedClass(class),
|
||||||
|
..
|
||||||
|
}) => Some(class),
|
||||||
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -146,55 +198,43 @@ impl<'a> Definitions<'a> {
|
||||||
match &definition {
|
match &definition {
|
||||||
Definition::Module(module) => module.source.to_visibility(),
|
Definition::Module(module) => module.source.to_visibility(),
|
||||||
Definition::Member(member) => match member.kind {
|
Definition::Member(member) => match member.kind {
|
||||||
MemberKind::Class => {
|
MemberKind::Class(class) => {
|
||||||
let parent = &definitions[member.parent];
|
let parent = &definitions[member.parent];
|
||||||
if parent.visibility.is_private()
|
if parent.visibility.is_private()
|
||||||
|| exports.is_some_and(|exports| {
|
|| exports.is_some_and(|exports| !exports.contains(&member.name()))
|
||||||
member.name().is_some_and(|name| !exports.contains(&name))
|
|
||||||
})
|
|
||||||
{
|
{
|
||||||
Visibility::Private
|
Visibility::Private
|
||||||
} else {
|
} else {
|
||||||
class_visibility(member.stmt)
|
class_visibility(class)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MemberKind::NestedClass => {
|
MemberKind::NestedClass(class) => {
|
||||||
let parent = &definitions[member.parent];
|
let parent = &definitions[member.parent];
|
||||||
if parent.visibility.is_private()
|
if parent.visibility.is_private()
|
||||||
|| matches!(
|
|| parent.definition.as_function_def().is_some()
|
||||||
parent.definition,
|
|
||||||
Definition::Member(Member {
|
|
||||||
kind: MemberKind::Function
|
|
||||||
| MemberKind::NestedFunction
|
|
||||||
| MemberKind::Method,
|
|
||||||
..
|
|
||||||
})
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
Visibility::Private
|
Visibility::Private
|
||||||
} else {
|
} else {
|
||||||
class_visibility(member.stmt)
|
class_visibility(class)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MemberKind::Function => {
|
MemberKind::Function(function) => {
|
||||||
let parent = &definitions[member.parent];
|
let parent = &definitions[member.parent];
|
||||||
if parent.visibility.is_private()
|
if parent.visibility.is_private()
|
||||||
|| exports.is_some_and(|exports| {
|
|| exports.is_some_and(|exports| !exports.contains(&member.name()))
|
||||||
member.name().is_some_and(|name| !exports.contains(&name))
|
|
||||||
})
|
|
||||||
{
|
{
|
||||||
Visibility::Private
|
Visibility::Private
|
||||||
} else {
|
} else {
|
||||||
function_visibility(member.stmt)
|
function_visibility(function)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MemberKind::NestedFunction => Visibility::Private,
|
MemberKind::NestedFunction(_) => Visibility::Private,
|
||||||
MemberKind::Method => {
|
MemberKind::Method(function) => {
|
||||||
let parent = &definitions[member.parent];
|
let parent = &definitions[member.parent];
|
||||||
if parent.visibility.is_private() {
|
if parent.visibility.is_private() {
|
||||||
Visibility::Private
|
Visibility::Private
|
||||||
} else {
|
} else {
|
||||||
method_visibility(member.stmt)
|
method_visibility(function)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue