mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-19 01:51:30 +00:00
Delay computation of Definition
visibility (#4339)
This commit is contained in:
parent
ffcf0618c7
commit
72e0ffc1ac
33 changed files with 1064 additions and 833 deletions
|
@ -1,20 +1,16 @@
|
||||||
use ruff_text_size::TextRange;
|
use ruff_text_size::TextRange;
|
||||||
use rustpython_parser::ast::Expr;
|
use rustpython_parser::ast::Expr;
|
||||||
|
|
||||||
use ruff_python_semantic::analyze::visibility::{Visibility, VisibleScope};
|
|
||||||
use ruff_python_semantic::context::Snapshot;
|
use ruff_python_semantic::context::Snapshot;
|
||||||
|
|
||||||
use crate::docstrings::definition::Definition;
|
|
||||||
|
|
||||||
/// A collection of AST nodes that are deferred for later analysis.
|
/// A collection of AST nodes that are deferred for later analysis.
|
||||||
/// Used to, e.g., store functions, whose bodies shouldn't be analyzed until all
|
/// Used to, e.g., store functions, whose bodies shouldn't be analyzed until all
|
||||||
/// module-level definitions have been analyzed.
|
/// module-level definitions have been analyzed.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Deferred<'a> {
|
pub struct Deferred<'a> {
|
||||||
pub definitions: Vec<(Definition<'a>, Visibility, Snapshot)>,
|
|
||||||
pub string_type_definitions: Vec<(TextRange, &'a str, Snapshot)>,
|
pub string_type_definitions: Vec<(TextRange, &'a str, Snapshot)>,
|
||||||
pub type_definitions: Vec<(&'a Expr, Snapshot)>,
|
pub type_definitions: Vec<(&'a Expr, Snapshot)>,
|
||||||
pub functions: Vec<(Snapshot, VisibleScope)>,
|
pub functions: Vec<Snapshot>,
|
||||||
pub lambdas: Vec<(&'a Expr, Snapshot)>,
|
pub lambdas: Vec<(&'a Expr, Snapshot)>,
|
||||||
pub for_loops: Vec<Snapshot>,
|
pub for_loops: Vec<Snapshot>,
|
||||||
pub assignments: Vec<Snapshot>,
|
pub assignments: Vec<Snapshot>,
|
||||||
|
|
|
@ -22,20 +22,21 @@ use ruff_python_ast::{cast, helpers, str, visitor};
|
||||||
use ruff_python_semantic::analyze;
|
use ruff_python_semantic::analyze;
|
||||||
use ruff_python_semantic::analyze::branch_detection;
|
use ruff_python_semantic::analyze::branch_detection;
|
||||||
use ruff_python_semantic::analyze::typing::{Callable, SubscriptKind};
|
use ruff_python_semantic::analyze::typing::{Callable, SubscriptKind};
|
||||||
|
use ruff_python_semantic::analyze::visibility::ModuleSource;
|
||||||
use ruff_python_semantic::binding::{
|
use ruff_python_semantic::binding::{
|
||||||
Binding, BindingId, BindingKind, Exceptions, ExecutionContext, Export, FromImportation,
|
Binding, BindingId, BindingKind, Exceptions, ExecutionContext, Export, FromImportation,
|
||||||
Importation, StarImportation, SubmoduleImportation,
|
Importation, StarImportation, SubmoduleImportation,
|
||||||
};
|
};
|
||||||
use ruff_python_semantic::context::Context;
|
use ruff_python_semantic::context::Context;
|
||||||
|
use ruff_python_semantic::definition::{Module, ModuleKind};
|
||||||
use ruff_python_semantic::node::NodeId;
|
use ruff_python_semantic::node::NodeId;
|
||||||
use ruff_python_semantic::scope::{ClassDef, FunctionDef, Lambda, Scope, ScopeId, ScopeKind};
|
use ruff_python_semantic::scope::{ClassDef, FunctionDef, Lambda, Scope, ScopeId, ScopeKind};
|
||||||
use ruff_python_stdlib::builtins::{BUILTINS, MAGIC_GLOBALS};
|
use ruff_python_stdlib::builtins::{BUILTINS, MAGIC_GLOBALS};
|
||||||
use ruff_python_stdlib::path::is_python_stub_file;
|
use ruff_python_stdlib::path::is_python_stub_file;
|
||||||
|
|
||||||
use crate::checkers::ast::deferred::Deferred;
|
use crate::checkers::ast::deferred::Deferred;
|
||||||
use crate::docstrings::definition::{
|
use crate::docstrings::extraction::ExtractionTarget;
|
||||||
transition_scope, Definition, DefinitionKind, Docstring, Documentable,
|
use crate::docstrings::Docstring;
|
||||||
};
|
|
||||||
use crate::fs::relativize_path;
|
use crate::fs::relativize_path;
|
||||||
use crate::importer::Importer;
|
use crate::importer::Importer;
|
||||||
use crate::noqa::NoqaMapping;
|
use crate::noqa::NoqaMapping;
|
||||||
|
@ -59,7 +60,7 @@ mod deferred;
|
||||||
pub struct Checker<'a> {
|
pub struct Checker<'a> {
|
||||||
// Settings, static metadata, etc.
|
// Settings, static metadata, etc.
|
||||||
path: &'a Path,
|
path: &'a Path,
|
||||||
module_path: Option<Vec<String>>,
|
module_path: Option<&'a [String]>,
|
||||||
package: Option<&'a Path>,
|
package: Option<&'a Path>,
|
||||||
is_stub: bool,
|
is_stub: bool,
|
||||||
noqa: flags::Noqa,
|
noqa: flags::Noqa,
|
||||||
|
@ -86,7 +87,7 @@ impl<'a> Checker<'a> {
|
||||||
noqa: flags::Noqa,
|
noqa: flags::Noqa,
|
||||||
path: &'a Path,
|
path: &'a Path,
|
||||||
package: Option<&'a Path>,
|
package: Option<&'a Path>,
|
||||||
module_path: Option<Vec<String>>,
|
module: Module<'a>,
|
||||||
locator: &'a Locator,
|
locator: &'a Locator,
|
||||||
stylist: &'a Stylist,
|
stylist: &'a Stylist,
|
||||||
indexer: &'a Indexer,
|
indexer: &'a Indexer,
|
||||||
|
@ -98,13 +99,13 @@ impl<'a> Checker<'a> {
|
||||||
noqa,
|
noqa,
|
||||||
path,
|
path,
|
||||||
package,
|
package,
|
||||||
module_path: module_path.clone(),
|
module_path: module.path(),
|
||||||
is_stub: is_python_stub_file(path),
|
is_stub: is_python_stub_file(path),
|
||||||
locator,
|
locator,
|
||||||
stylist,
|
stylist,
|
||||||
indexer,
|
indexer,
|
||||||
importer,
|
importer,
|
||||||
ctx: Context::new(&settings.typing_modules, path, module_path),
|
ctx: Context::new(&settings.typing_modules, path, module),
|
||||||
deferred: Deferred::default(),
|
deferred: Deferred::default(),
|
||||||
diagnostics: Vec::default(),
|
diagnostics: Vec::default(),
|
||||||
deletions: FxHashSet::default(),
|
deletions: FxHashSet::default(),
|
||||||
|
@ -996,7 +997,7 @@ where
|
||||||
}
|
}
|
||||||
if self.settings.rules.enabled(Rule::ImportSelf) {
|
if self.settings.rules.enabled(Rule::ImportSelf) {
|
||||||
if let Some(diagnostic) =
|
if let Some(diagnostic) =
|
||||||
pylint::rules::import_self(alias, self.module_path.as_deref())
|
pylint::rules::import_self(alias, self.module_path)
|
||||||
{
|
{
|
||||||
self.diagnostics.push(diagnostic);
|
self.diagnostics.push(diagnostic);
|
||||||
}
|
}
|
||||||
|
@ -1166,11 +1167,9 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.settings.rules.enabled(Rule::BannedApi) {
|
if self.settings.rules.enabled(Rule::BannedApi) {
|
||||||
if let Some(module) = helpers::resolve_imported_module_path(
|
if let Some(module) =
|
||||||
level,
|
helpers::resolve_imported_module_path(level, module, self.module_path)
|
||||||
module,
|
{
|
||||||
self.module_path.as_deref(),
|
|
||||||
) {
|
|
||||||
flake8_tidy_imports::banned_api::name_or_parent_is_banned(
|
flake8_tidy_imports::banned_api::name_or_parent_is_banned(
|
||||||
self, &module, stmt,
|
self, &module, stmt,
|
||||||
);
|
);
|
||||||
|
@ -1315,7 +1314,7 @@ where
|
||||||
stmt,
|
stmt,
|
||||||
level,
|
level,
|
||||||
module,
|
module,
|
||||||
self.module_path.as_deref(),
|
self.module_path,
|
||||||
&self.settings.flake8_tidy_imports.ban_relative_imports,
|
&self.settings.flake8_tidy_imports.ban_relative_imports,
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
@ -1460,12 +1459,9 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.settings.rules.enabled(Rule::ImportSelf) {
|
if self.settings.rules.enabled(Rule::ImportSelf) {
|
||||||
if let Some(diagnostic) = pylint::rules::import_from_self(
|
if let Some(diagnostic) =
|
||||||
level,
|
pylint::rules::import_from_self(level, module, names, self.module_path)
|
||||||
module,
|
{
|
||||||
names,
|
|
||||||
self.module_path.as_deref(),
|
|
||||||
) {
|
|
||||||
self.diagnostics.push(diagnostic);
|
self.diagnostics.push(diagnostic);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1944,7 +1940,6 @@ where
|
||||||
|
|
||||||
// Recurse.
|
// Recurse.
|
||||||
let prev_in_exception_handler = self.ctx.in_exception_handler;
|
let prev_in_exception_handler = self.ctx.in_exception_handler;
|
||||||
let prev_visible_scope = self.ctx.visible_scope;
|
|
||||||
match &stmt.node {
|
match &stmt.node {
|
||||||
StmtKind::FunctionDef(ast::StmtFunctionDef {
|
StmtKind::FunctionDef(ast::StmtFunctionDef {
|
||||||
body,
|
body,
|
||||||
|
@ -1963,20 +1958,9 @@ where
|
||||||
if self.settings.rules.enabled(Rule::FStringDocstring) {
|
if self.settings.rules.enabled(Rule::FStringDocstring) {
|
||||||
flake8_bugbear::rules::f_string_docstring(self, body);
|
flake8_bugbear::rules::f_string_docstring(self, body);
|
||||||
}
|
}
|
||||||
let definition = docstrings::extraction::extract(
|
|
||||||
self.ctx.visible_scope,
|
|
||||||
stmt,
|
|
||||||
body,
|
|
||||||
Documentable::Function,
|
|
||||||
);
|
|
||||||
if self.settings.rules.enabled(Rule::YieldInForLoop) {
|
if self.settings.rules.enabled(Rule::YieldInForLoop) {
|
||||||
pyupgrade::rules::yield_in_for_loop(self, stmt);
|
pyupgrade::rules::yield_in_for_loop(self, stmt);
|
||||||
}
|
}
|
||||||
let scope = transition_scope(self.ctx.visible_scope, stmt, Documentable::Function);
|
|
||||||
self.deferred
|
|
||||||
.definitions
|
|
||||||
.push((definition, scope.visibility, self.ctx.snapshot()));
|
|
||||||
self.ctx.visible_scope = scope;
|
|
||||||
|
|
||||||
// If any global bindings don't already exist in the global scope, add it.
|
// If any global bindings don't already exist in the global scope, add it.
|
||||||
let globals = helpers::extract_globals(body);
|
let globals = helpers::extract_globals(body);
|
||||||
|
@ -2001,6 +1985,14 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let definition = docstrings::extraction::extract_definition(
|
||||||
|
ExtractionTarget::Function,
|
||||||
|
stmt,
|
||||||
|
self.ctx.definition_id,
|
||||||
|
&self.ctx.definitions,
|
||||||
|
);
|
||||||
|
self.ctx.push_definition(definition);
|
||||||
|
|
||||||
self.ctx.push_scope(ScopeKind::Function(FunctionDef {
|
self.ctx.push_scope(ScopeKind::Function(FunctionDef {
|
||||||
name,
|
name,
|
||||||
body,
|
body,
|
||||||
|
@ -2010,9 +2002,7 @@ where
|
||||||
globals,
|
globals,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
self.deferred
|
self.deferred.functions.push(self.ctx.snapshot());
|
||||||
.functions
|
|
||||||
.push((self.ctx.snapshot(), self.ctx.visible_scope));
|
|
||||||
}
|
}
|
||||||
StmtKind::ClassDef(ast::StmtClassDef {
|
StmtKind::ClassDef(ast::StmtClassDef {
|
||||||
body,
|
body,
|
||||||
|
@ -2024,17 +2014,6 @@ where
|
||||||
if self.settings.rules.enabled(Rule::FStringDocstring) {
|
if self.settings.rules.enabled(Rule::FStringDocstring) {
|
||||||
flake8_bugbear::rules::f_string_docstring(self, body);
|
flake8_bugbear::rules::f_string_docstring(self, body);
|
||||||
}
|
}
|
||||||
let definition = docstrings::extraction::extract(
|
|
||||||
self.ctx.visible_scope,
|
|
||||||
stmt,
|
|
||||||
body,
|
|
||||||
Documentable::Class,
|
|
||||||
);
|
|
||||||
let scope = transition_scope(self.ctx.visible_scope, stmt, Documentable::Class);
|
|
||||||
self.deferred
|
|
||||||
.definitions
|
|
||||||
.push((definition, scope.visibility, self.ctx.snapshot()));
|
|
||||||
self.ctx.visible_scope = scope;
|
|
||||||
|
|
||||||
// If any global bindings don't already exist in the global scope, add it.
|
// If any global bindings don't already exist in the global scope, add it.
|
||||||
let globals = helpers::extract_globals(body);
|
let globals = helpers::extract_globals(body);
|
||||||
|
@ -2059,6 +2038,14 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let definition = docstrings::extraction::extract_definition(
|
||||||
|
ExtractionTarget::Class,
|
||||||
|
stmt,
|
||||||
|
self.ctx.definition_id,
|
||||||
|
&self.ctx.definitions,
|
||||||
|
);
|
||||||
|
self.ctx.push_definition(definition);
|
||||||
|
|
||||||
self.ctx.push_scope(ScopeKind::Class(ClassDef {
|
self.ctx.push_scope(ScopeKind::Class(ClassDef {
|
||||||
name,
|
name,
|
||||||
bases,
|
bases,
|
||||||
|
@ -2195,15 +2182,16 @@ where
|
||||||
}
|
}
|
||||||
_ => visitor::walk_stmt(self, stmt),
|
_ => visitor::walk_stmt(self, stmt),
|
||||||
};
|
};
|
||||||
self.ctx.visible_scope = prev_visible_scope;
|
|
||||||
|
|
||||||
// Post-visit.
|
// Post-visit.
|
||||||
match &stmt.node {
|
match &stmt.node {
|
||||||
StmtKind::FunctionDef(_) | StmtKind::AsyncFunctionDef(_) => {
|
StmtKind::FunctionDef(_) | StmtKind::AsyncFunctionDef(_) => {
|
||||||
self.ctx.pop_scope();
|
self.ctx.pop_scope();
|
||||||
|
self.ctx.pop_definition();
|
||||||
}
|
}
|
||||||
StmtKind::ClassDef(ast::StmtClassDef { name, .. }) => {
|
StmtKind::ClassDef(ast::StmtClassDef { name, .. }) => {
|
||||||
self.ctx.pop_scope();
|
self.ctx.pop_scope();
|
||||||
|
self.ctx.pop_definition();
|
||||||
self.add_binding(
|
self.add_binding(
|
||||||
name,
|
name,
|
||||||
Binding {
|
Binding {
|
||||||
|
@ -4751,23 +4739,11 @@ impl<'a> Checker<'a> {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visit_docstring(&mut self, python_ast: &'a Suite) -> bool {
|
fn visit_module(&mut self, python_ast: &'a Suite) -> bool {
|
||||||
if self.settings.rules.enabled(Rule::FStringDocstring) {
|
if self.settings.rules.enabled(Rule::FStringDocstring) {
|
||||||
flake8_bugbear::rules::f_string_docstring(self, python_ast);
|
flake8_bugbear::rules::f_string_docstring(self, python_ast);
|
||||||
}
|
}
|
||||||
let docstring = docstrings::extraction::docstring_from(python_ast);
|
let docstring = docstrings::extraction::docstring_from(python_ast);
|
||||||
self.deferred.definitions.push((
|
|
||||||
Definition {
|
|
||||||
kind: if self.path.ends_with("__init__.py") {
|
|
||||||
DefinitionKind::Package
|
|
||||||
} else {
|
|
||||||
DefinitionKind::Module
|
|
||||||
},
|
|
||||||
docstring,
|
|
||||||
},
|
|
||||||
self.ctx.visible_scope.visibility,
|
|
||||||
self.ctx.snapshot(),
|
|
||||||
));
|
|
||||||
docstring.is_some()
|
docstring.is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4794,8 +4770,6 @@ impl<'a> Checker<'a> {
|
||||||
let expr = allocator.alloc(expr);
|
let expr = allocator.alloc(expr);
|
||||||
|
|
||||||
self.ctx.restore(snapshot);
|
self.ctx.restore(snapshot);
|
||||||
self.ctx.in_type_definition = true;
|
|
||||||
self.ctx.in_deferred_string_type_definition = Some(kind);
|
|
||||||
|
|
||||||
if self.ctx.in_annotation && self.ctx.annotations_future_enabled {
|
if self.ctx.in_annotation && self.ctx.annotations_future_enabled {
|
||||||
if self.settings.rules.enabled(Rule::QuotedAnnotation) {
|
if self.settings.rules.enabled(Rule::QuotedAnnotation) {
|
||||||
|
@ -4807,6 +4781,9 @@ impl<'a> Checker<'a> {
|
||||||
flake8_pyi::rules::quoted_annotation_in_stub(self, value, range);
|
flake8_pyi::rules::quoted_annotation_in_stub(self, value, range);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.ctx.in_type_definition = true;
|
||||||
|
self.ctx.in_deferred_string_type_definition = Some(kind);
|
||||||
self.visit_expr(expr);
|
self.visit_expr(expr);
|
||||||
|
|
||||||
self.ctx.in_deferred_string_type_definition = None;
|
self.ctx.in_deferred_string_type_definition = None;
|
||||||
|
@ -4832,9 +4809,8 @@ impl<'a> Checker<'a> {
|
||||||
fn check_deferred_functions(&mut self) {
|
fn check_deferred_functions(&mut self) {
|
||||||
while !self.deferred.functions.is_empty() {
|
while !self.deferred.functions.is_empty() {
|
||||||
let deferred_functions = std::mem::take(&mut self.deferred.functions);
|
let deferred_functions = std::mem::take(&mut self.deferred.functions);
|
||||||
for (snapshot, visibility) in deferred_functions {
|
for snapshot in deferred_functions {
|
||||||
self.ctx.restore(snapshot);
|
self.ctx.restore(snapshot);
|
||||||
self.ctx.visible_scope = visibility;
|
|
||||||
|
|
||||||
match &self.ctx.stmt().node {
|
match &self.ctx.stmt().node {
|
||||||
StmtKind::FunctionDef(ast::StmtFunctionDef { body, args, .. })
|
StmtKind::FunctionDef(ast::StmtFunctionDef { body, args, .. })
|
||||||
|
@ -5343,6 +5319,11 @@ impl<'a> Checker<'a> {
|
||||||
self.diagnostics.extend(diagnostics);
|
self.diagnostics.extend(diagnostics);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Visit all the [`Definition`] nodes in the AST.
|
||||||
|
///
|
||||||
|
/// This phase is expected to run after the AST has been traversed in its entirety; as such,
|
||||||
|
/// it is expected that all [`Definition`] nodes have been visited by the time, and that this
|
||||||
|
/// method will not recurse into any other nodes.
|
||||||
fn check_definitions(&mut self) {
|
fn check_definitions(&mut self) {
|
||||||
let enforce_annotations = self.settings.rules.any_enabled(&[
|
let enforce_annotations = self.settings.rules.any_enabled(&[
|
||||||
Rule::MissingTypeFunctionArgument,
|
Rule::MissingTypeFunctionArgument,
|
||||||
|
@ -5357,6 +5338,8 @@ impl<'a> Checker<'a> {
|
||||||
Rule::MissingReturnTypeClassMethod,
|
Rule::MissingReturnTypeClassMethod,
|
||||||
Rule::AnyType,
|
Rule::AnyType,
|
||||||
]);
|
]);
|
||||||
|
let enforce_stubs =
|
||||||
|
self.is_stub && self.settings.rules.any_enabled(&[Rule::DocstringInStub]);
|
||||||
let enforce_docstrings = self.settings.rules.any_enabled(&[
|
let enforce_docstrings = self.settings.rules.any_enabled(&[
|
||||||
Rule::UndocumentedPublicModule,
|
Rule::UndocumentedPublicModule,
|
||||||
Rule::UndocumentedPublicClass,
|
Rule::UndocumentedPublicClass,
|
||||||
|
@ -5406,185 +5389,187 @@ impl<'a> Checker<'a> {
|
||||||
Rule::EmptyDocstring,
|
Rule::EmptyDocstring,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
if !enforce_annotations && !enforce_docstrings && !enforce_stubs {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let mut overloaded_name: Option<String> = None;
|
let mut overloaded_name: Option<String> = None;
|
||||||
while !self.deferred.definitions.is_empty() {
|
|
||||||
let definitions = std::mem::take(&mut self.deferred.definitions);
|
|
||||||
for (definition, visibility, snapshot) in definitions {
|
|
||||||
self.ctx.restore(snapshot);
|
|
||||||
|
|
||||||
// flake8-annotations
|
let definitions = std::mem::take(&mut self.ctx.definitions);
|
||||||
if enforce_annotations {
|
for (definition, visibility) in definitions.iter() {
|
||||||
// TODO(charlie): This should be even stricter, in that an overload
|
let docstring = docstrings::extraction::extract_docstring(definition);
|
||||||
// implementation should come immediately after the overloaded
|
|
||||||
// interfaces, without any AST nodes in between. Right now, we
|
// flake8-annotations
|
||||||
// only error when traversing definition boundaries (functions,
|
if enforce_annotations {
|
||||||
// classes, etc.).
|
// TODO(charlie): This should be even stricter, in that an overload
|
||||||
if !overloaded_name.map_or(false, |overloaded_name| {
|
// implementation should come immediately after the overloaded
|
||||||
flake8_annotations::helpers::is_overload_impl(
|
// interfaces, without any AST nodes in between. Right now, we
|
||||||
self,
|
// only error when traversing definition boundaries (functions,
|
||||||
&definition,
|
// classes, etc.).
|
||||||
&overloaded_name,
|
if !overloaded_name.map_or(false, |overloaded_name| {
|
||||||
)
|
flake8_annotations::helpers::is_overload_impl(
|
||||||
}) {
|
self,
|
||||||
self.diagnostics
|
definition,
|
||||||
.extend(flake8_annotations::rules::definition(
|
&overloaded_name,
|
||||||
self,
|
)
|
||||||
&definition,
|
}) {
|
||||||
visibility,
|
self.diagnostics
|
||||||
));
|
.extend(flake8_annotations::rules::definition(
|
||||||
}
|
self, definition, visibility,
|
||||||
overloaded_name =
|
));
|
||||||
flake8_annotations::helpers::overloaded_name(self, &definition);
|
|
||||||
}
|
}
|
||||||
|
overloaded_name = flake8_annotations::helpers::overloaded_name(self, definition);
|
||||||
|
}
|
||||||
|
|
||||||
|
// flake8-pyi
|
||||||
|
if enforce_stubs {
|
||||||
if self.is_stub {
|
if self.is_stub {
|
||||||
if self.settings.rules.enabled(Rule::DocstringInStub) {
|
if self.settings.rules.enabled(Rule::DocstringInStub) {
|
||||||
flake8_pyi::rules::docstring_in_stubs(self, definition.docstring);
|
flake8_pyi::rules::docstring_in_stubs(self, docstring);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// pydocstyle
|
// pydocstyle
|
||||||
if enforce_docstrings {
|
if enforce_docstrings {
|
||||||
if pydocstyle::helpers::should_ignore_definition(
|
if pydocstyle::helpers::should_ignore_definition(
|
||||||
|
self,
|
||||||
|
definition,
|
||||||
|
&self.settings.pydocstyle.ignore_decorators,
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract a `Docstring` from a `Definition`.
|
||||||
|
let Some(expr) = docstring else {
|
||||||
|
pydocstyle::rules::not_missing(self, definition, visibility);
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let contents = self.locator.slice(expr.range());
|
||||||
|
|
||||||
|
let indentation = self.locator.slice(TextRange::new(
|
||||||
|
self.locator.line_start(expr.start()),
|
||||||
|
expr.start(),
|
||||||
|
));
|
||||||
|
|
||||||
|
if pydocstyle::helpers::should_ignore_docstring(contents) {
|
||||||
|
#[allow(deprecated)]
|
||||||
|
let location = self.locator.compute_source_location(expr.start());
|
||||||
|
warn_user!(
|
||||||
|
"Docstring at {}:{}:{} contains implicit string concatenation; ignoring...",
|
||||||
|
relativize_path(self.path),
|
||||||
|
location.row,
|
||||||
|
location.column
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SAFETY: Safe for docstrings that pass `should_ignore_docstring`.
|
||||||
|
let body_range = str::raw_contents_range(contents).unwrap();
|
||||||
|
let docstring = Docstring {
|
||||||
|
definition,
|
||||||
|
expr,
|
||||||
|
contents,
|
||||||
|
body_range,
|
||||||
|
indentation,
|
||||||
|
};
|
||||||
|
|
||||||
|
if !pydocstyle::rules::not_empty(self, &docstring) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.settings.rules.enabled(Rule::FitsOnOneLine) {
|
||||||
|
pydocstyle::rules::one_liner(self, &docstring);
|
||||||
|
}
|
||||||
|
if self.settings.rules.any_enabled(&[
|
||||||
|
Rule::NoBlankLineBeforeFunction,
|
||||||
|
Rule::NoBlankLineAfterFunction,
|
||||||
|
]) {
|
||||||
|
pydocstyle::rules::blank_before_after_function(self, &docstring);
|
||||||
|
}
|
||||||
|
if self.settings.rules.any_enabled(&[
|
||||||
|
Rule::OneBlankLineBeforeClass,
|
||||||
|
Rule::OneBlankLineAfterClass,
|
||||||
|
Rule::BlankLineBeforeClass,
|
||||||
|
]) {
|
||||||
|
pydocstyle::rules::blank_before_after_class(self, &docstring);
|
||||||
|
}
|
||||||
|
if self.settings.rules.enabled(Rule::BlankLineAfterSummary) {
|
||||||
|
pydocstyle::rules::blank_after_summary(self, &docstring);
|
||||||
|
}
|
||||||
|
if self.settings.rules.any_enabled(&[
|
||||||
|
Rule::IndentWithSpaces,
|
||||||
|
Rule::UnderIndentation,
|
||||||
|
Rule::OverIndentation,
|
||||||
|
]) {
|
||||||
|
pydocstyle::rules::indent(self, &docstring);
|
||||||
|
}
|
||||||
|
if self.settings.rules.enabled(Rule::NewLineAfterLastParagraph) {
|
||||||
|
pydocstyle::rules::newline_after_last_paragraph(self, &docstring);
|
||||||
|
}
|
||||||
|
if self.settings.rules.enabled(Rule::SurroundingWhitespace) {
|
||||||
|
pydocstyle::rules::no_surrounding_whitespace(self, &docstring);
|
||||||
|
}
|
||||||
|
if self.settings.rules.any_enabled(&[
|
||||||
|
Rule::MultiLineSummaryFirstLine,
|
||||||
|
Rule::MultiLineSummarySecondLine,
|
||||||
|
]) {
|
||||||
|
pydocstyle::rules::multi_line_summary_start(self, &docstring);
|
||||||
|
}
|
||||||
|
if self.settings.rules.enabled(Rule::TripleSingleQuotes) {
|
||||||
|
pydocstyle::rules::triple_quotes(self, &docstring);
|
||||||
|
}
|
||||||
|
if self.settings.rules.enabled(Rule::EscapeSequenceInDocstring) {
|
||||||
|
pydocstyle::rules::backslashes(self, &docstring);
|
||||||
|
}
|
||||||
|
if self.settings.rules.enabled(Rule::EndsInPeriod) {
|
||||||
|
pydocstyle::rules::ends_with_period(self, &docstring);
|
||||||
|
}
|
||||||
|
if self.settings.rules.enabled(Rule::NonImperativeMood) {
|
||||||
|
pydocstyle::rules::non_imperative_mood(
|
||||||
self,
|
self,
|
||||||
&definition,
|
&docstring,
|
||||||
&self.settings.pydocstyle.ignore_decorators,
|
&self.settings.pydocstyle.property_decorators,
|
||||||
) {
|
);
|
||||||
continue;
|
}
|
||||||
}
|
if self.settings.rules.enabled(Rule::NoSignature) {
|
||||||
|
pydocstyle::rules::no_signature(self, &docstring);
|
||||||
if definition.docstring.is_none() {
|
}
|
||||||
pydocstyle::rules::not_missing(self, &definition, visibility);
|
if self.settings.rules.enabled(Rule::FirstLineCapitalized) {
|
||||||
continue;
|
pydocstyle::rules::capitalized(self, &docstring);
|
||||||
}
|
}
|
||||||
|
if self.settings.rules.enabled(Rule::DocstringStartsWithThis) {
|
||||||
// Extract a `Docstring` from a `Definition`.
|
pydocstyle::rules::starts_with_this(self, &docstring);
|
||||||
let expr = definition.docstring.unwrap();
|
}
|
||||||
let contents = self.locator.slice(expr.range());
|
if self.settings.rules.enabled(Rule::EndsInPunctuation) {
|
||||||
|
pydocstyle::rules::ends_with_punctuation(self, &docstring);
|
||||||
let indentation = self.locator.slice(TextRange::new(
|
}
|
||||||
self.locator.line_start(expr.start()),
|
if self.settings.rules.enabled(Rule::OverloadWithDocstring) {
|
||||||
expr.start(),
|
pydocstyle::rules::if_needed(self, &docstring);
|
||||||
));
|
}
|
||||||
|
if self.settings.rules.any_enabled(&[
|
||||||
if pydocstyle::helpers::should_ignore_docstring(contents) {
|
Rule::MultiLineSummaryFirstLine,
|
||||||
#[allow(deprecated)]
|
Rule::SectionNotOverIndented,
|
||||||
let location = self.locator.compute_source_location(expr.start());
|
Rule::SectionUnderlineNotOverIndented,
|
||||||
warn_user!(
|
Rule::CapitalizeSectionName,
|
||||||
"Docstring at {}:{}:{} contains implicit string concatenation; ignoring...",
|
Rule::NewLineAfterSectionName,
|
||||||
relativize_path(self.path),
|
Rule::DashedUnderlineAfterSection,
|
||||||
location.row,
|
Rule::SectionUnderlineAfterName,
|
||||||
location.column
|
Rule::SectionUnderlineMatchesSectionLength,
|
||||||
);
|
Rule::NoBlankLineAfterSection,
|
||||||
continue;
|
Rule::NoBlankLineBeforeSection,
|
||||||
}
|
Rule::BlankLinesBetweenHeaderAndContent,
|
||||||
|
Rule::BlankLineAfterLastSection,
|
||||||
// SAFETY: Safe for docstrings that pass `should_ignore_docstring`.
|
Rule::EmptyDocstringSection,
|
||||||
let body_range = str::raw_contents_range(contents).unwrap();
|
Rule::SectionNameEndsInColon,
|
||||||
let docstring = Docstring {
|
Rule::UndocumentedParam,
|
||||||
kind: definition.kind,
|
]) {
|
||||||
expr,
|
pydocstyle::rules::sections(
|
||||||
contents,
|
self,
|
||||||
indentation,
|
&docstring,
|
||||||
body_range,
|
self.settings.pydocstyle.convention.as_ref(),
|
||||||
};
|
);
|
||||||
|
|
||||||
if !pydocstyle::rules::not_empty(self, &docstring) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.settings.rules.enabled(Rule::FitsOnOneLine) {
|
|
||||||
pydocstyle::rules::one_liner(self, &docstring);
|
|
||||||
}
|
|
||||||
if self.settings.rules.any_enabled(&[
|
|
||||||
Rule::NoBlankLineBeforeFunction,
|
|
||||||
Rule::NoBlankLineAfterFunction,
|
|
||||||
]) {
|
|
||||||
pydocstyle::rules::blank_before_after_function(self, &docstring);
|
|
||||||
}
|
|
||||||
if self.settings.rules.any_enabled(&[
|
|
||||||
Rule::OneBlankLineBeforeClass,
|
|
||||||
Rule::OneBlankLineAfterClass,
|
|
||||||
Rule::BlankLineBeforeClass,
|
|
||||||
]) {
|
|
||||||
pydocstyle::rules::blank_before_after_class(self, &docstring);
|
|
||||||
}
|
|
||||||
if self.settings.rules.enabled(Rule::BlankLineAfterSummary) {
|
|
||||||
pydocstyle::rules::blank_after_summary(self, &docstring);
|
|
||||||
}
|
|
||||||
if self.settings.rules.any_enabled(&[
|
|
||||||
Rule::IndentWithSpaces,
|
|
||||||
Rule::UnderIndentation,
|
|
||||||
Rule::OverIndentation,
|
|
||||||
]) {
|
|
||||||
pydocstyle::rules::indent(self, &docstring);
|
|
||||||
}
|
|
||||||
if self.settings.rules.enabled(Rule::NewLineAfterLastParagraph) {
|
|
||||||
pydocstyle::rules::newline_after_last_paragraph(self, &docstring);
|
|
||||||
}
|
|
||||||
if self.settings.rules.enabled(Rule::SurroundingWhitespace) {
|
|
||||||
pydocstyle::rules::no_surrounding_whitespace(self, &docstring);
|
|
||||||
}
|
|
||||||
if self.settings.rules.any_enabled(&[
|
|
||||||
Rule::MultiLineSummaryFirstLine,
|
|
||||||
Rule::MultiLineSummarySecondLine,
|
|
||||||
]) {
|
|
||||||
pydocstyle::rules::multi_line_summary_start(self, &docstring);
|
|
||||||
}
|
|
||||||
if self.settings.rules.enabled(Rule::TripleSingleQuotes) {
|
|
||||||
pydocstyle::rules::triple_quotes(self, &docstring);
|
|
||||||
}
|
|
||||||
if self.settings.rules.enabled(Rule::EscapeSequenceInDocstring) {
|
|
||||||
pydocstyle::rules::backslashes(self, &docstring);
|
|
||||||
}
|
|
||||||
if self.settings.rules.enabled(Rule::EndsInPeriod) {
|
|
||||||
pydocstyle::rules::ends_with_period(self, &docstring);
|
|
||||||
}
|
|
||||||
if self.settings.rules.enabled(Rule::NonImperativeMood) {
|
|
||||||
pydocstyle::rules::non_imperative_mood(
|
|
||||||
self,
|
|
||||||
&docstring,
|
|
||||||
&self.settings.pydocstyle.property_decorators,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if self.settings.rules.enabled(Rule::NoSignature) {
|
|
||||||
pydocstyle::rules::no_signature(self, &docstring);
|
|
||||||
}
|
|
||||||
if self.settings.rules.enabled(Rule::FirstLineCapitalized) {
|
|
||||||
pydocstyle::rules::capitalized(self, &docstring);
|
|
||||||
}
|
|
||||||
if self.settings.rules.enabled(Rule::DocstringStartsWithThis) {
|
|
||||||
pydocstyle::rules::starts_with_this(self, &docstring);
|
|
||||||
}
|
|
||||||
if self.settings.rules.enabled(Rule::EndsInPunctuation) {
|
|
||||||
pydocstyle::rules::ends_with_punctuation(self, &docstring);
|
|
||||||
}
|
|
||||||
if self.settings.rules.enabled(Rule::OverloadWithDocstring) {
|
|
||||||
pydocstyle::rules::if_needed(self, &docstring);
|
|
||||||
}
|
|
||||||
if self.settings.rules.any_enabled(&[
|
|
||||||
Rule::MultiLineSummaryFirstLine,
|
|
||||||
Rule::SectionNotOverIndented,
|
|
||||||
Rule::SectionUnderlineNotOverIndented,
|
|
||||||
Rule::CapitalizeSectionName,
|
|
||||||
Rule::NewLineAfterSectionName,
|
|
||||||
Rule::DashedUnderlineAfterSection,
|
|
||||||
Rule::SectionUnderlineAfterName,
|
|
||||||
Rule::SectionUnderlineMatchesSectionLength,
|
|
||||||
Rule::NoBlankLineAfterSection,
|
|
||||||
Rule::NoBlankLineBeforeSection,
|
|
||||||
Rule::BlankLinesBetweenHeaderAndContent,
|
|
||||||
Rule::BlankLineAfterLastSection,
|
|
||||||
Rule::EmptyDocstringSection,
|
|
||||||
Rule::SectionNameEndsInColon,
|
|
||||||
Rule::UndocumentedParam,
|
|
||||||
]) {
|
|
||||||
pydocstyle::rules::sections(
|
|
||||||
self,
|
|
||||||
&docstring,
|
|
||||||
self.settings.pydocstyle.convention.as_ref(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5647,13 +5632,28 @@ pub fn check_ast(
|
||||||
path: &Path,
|
path: &Path,
|
||||||
package: Option<&Path>,
|
package: Option<&Path>,
|
||||||
) -> Vec<Diagnostic> {
|
) -> Vec<Diagnostic> {
|
||||||
|
let module_path = package.and_then(|package| to_module_path(package, path));
|
||||||
|
let module = Module {
|
||||||
|
kind: if path.ends_with("__init__.py") {
|
||||||
|
ModuleKind::Package
|
||||||
|
} else {
|
||||||
|
ModuleKind::Module
|
||||||
|
},
|
||||||
|
source: if let Some(module_path) = module_path.as_ref() {
|
||||||
|
ModuleSource::Path(module_path)
|
||||||
|
} else {
|
||||||
|
ModuleSource::File(path)
|
||||||
|
},
|
||||||
|
python_ast,
|
||||||
|
};
|
||||||
|
|
||||||
let mut checker = Checker::new(
|
let mut checker = Checker::new(
|
||||||
settings,
|
settings,
|
||||||
noqa_line_for,
|
noqa_line_for,
|
||||||
noqa,
|
noqa,
|
||||||
path,
|
path,
|
||||||
package,
|
package,
|
||||||
package.and_then(|package| to_module_path(package, path)),
|
module,
|
||||||
locator,
|
locator,
|
||||||
stylist,
|
stylist,
|
||||||
indexer,
|
indexer,
|
||||||
|
@ -5662,11 +5662,12 @@ pub fn check_ast(
|
||||||
checker.bind_builtins();
|
checker.bind_builtins();
|
||||||
|
|
||||||
// Check for module docstring.
|
// Check for module docstring.
|
||||||
let python_ast = if checker.visit_docstring(python_ast) {
|
let python_ast = if checker.visit_module(python_ast) {
|
||||||
&python_ast[1..]
|
&python_ast[1..]
|
||||||
} else {
|
} else {
|
||||||
python_ast
|
python_ast
|
||||||
};
|
};
|
||||||
|
|
||||||
// Iterate over the AST.
|
// Iterate over the AST.
|
||||||
checker.visit_body(python_ast);
|
checker.visit_body(python_ast);
|
||||||
|
|
||||||
|
|
|
@ -1,140 +0,0 @@
|
||||||
use ruff_text_size::{TextRange, TextSize};
|
|
||||||
use rustpython_parser::ast::{Expr, Stmt};
|
|
||||||
use std::fmt::{Debug, Formatter};
|
|
||||||
use std::ops::Deref;
|
|
||||||
|
|
||||||
use ruff_python_semantic::analyze::visibility::{
|
|
||||||
class_visibility, function_visibility, method_visibility, Modifier, Visibility, VisibleScope,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum DefinitionKind<'a> {
|
|
||||||
Module,
|
|
||||||
Package,
|
|
||||||
Class(&'a Stmt),
|
|
||||||
NestedClass(&'a Stmt),
|
|
||||||
Function(&'a Stmt),
|
|
||||||
NestedFunction(&'a Stmt),
|
|
||||||
Method(&'a Stmt),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Definition<'a> {
|
|
||||||
pub kind: DefinitionKind<'a>,
|
|
||||||
pub docstring: Option<&'a Expr>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Docstring<'a> {
|
|
||||||
pub kind: DefinitionKind<'a>,
|
|
||||||
pub expr: &'a Expr,
|
|
||||||
/// The content of the docstring, including the leading and trailing quotes.
|
|
||||||
pub contents: &'a str,
|
|
||||||
|
|
||||||
/// The range of the docstring body (without the quotes). The range is relative to [`Self::contents`].
|
|
||||||
pub body_range: TextRange,
|
|
||||||
pub indentation: &'a str,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Docstring<'a> {
|
|
||||||
pub fn body(&self) -> DocstringBody {
|
|
||||||
DocstringBody { docstring: self }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn start(&self) -> TextSize {
|
|
||||||
self.expr.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn end(&self) -> TextSize {
|
|
||||||
self.expr.end()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn range(&self) -> TextRange {
|
|
||||||
self.expr.range()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn leading_quote(&self) -> &'a str {
|
|
||||||
&self.contents[TextRange::up_to(self.body_range.start())]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
|
||||||
pub struct DocstringBody<'a> {
|
|
||||||
docstring: &'a Docstring<'a>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> DocstringBody<'a> {
|
|
||||||
#[inline]
|
|
||||||
pub fn start(self) -> TextSize {
|
|
||||||
self.range().start()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn end(self) -> TextSize {
|
|
||||||
self.range().end()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn range(self) -> TextRange {
|
|
||||||
self.docstring.body_range + self.docstring.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_str(self) -> &'a str {
|
|
||||||
&self.docstring.contents[self.docstring.body_range]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for DocstringBody<'_> {
|
|
||||||
type Target = str;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
self.as_str()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Debug for DocstringBody<'_> {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
||||||
f.debug_struct("DocstringBody")
|
|
||||||
.field("text", &self.as_str())
|
|
||||||
.field("range", &self.range())
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
|
||||||
pub enum Documentable {
|
|
||||||
Class,
|
|
||||||
Function,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn transition_scope(scope: VisibleScope, stmt: &Stmt, kind: Documentable) -> VisibleScope {
|
|
||||||
match kind {
|
|
||||||
Documentable::Function => VisibleScope {
|
|
||||||
modifier: Modifier::Function,
|
|
||||||
visibility: match scope {
|
|
||||||
VisibleScope {
|
|
||||||
modifier: Modifier::Module,
|
|
||||||
visibility: Visibility::Public,
|
|
||||||
} => function_visibility(stmt),
|
|
||||||
VisibleScope {
|
|
||||||
modifier: Modifier::Class,
|
|
||||||
visibility: Visibility::Public,
|
|
||||||
} => method_visibility(stmt),
|
|
||||||
_ => Visibility::Private,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Documentable::Class => VisibleScope {
|
|
||||||
modifier: Modifier::Class,
|
|
||||||
visibility: match scope {
|
|
||||||
VisibleScope {
|
|
||||||
modifier: Modifier::Module,
|
|
||||||
visibility: Visibility::Public,
|
|
||||||
} => class_visibility(stmt),
|
|
||||||
VisibleScope {
|
|
||||||
modifier: Modifier::Class,
|
|
||||||
visibility: Visibility::Public,
|
|
||||||
} => class_visibility(stmt),
|
|
||||||
_ => Visibility::Private,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,12 +2,10 @@
|
||||||
|
|
||||||
use rustpython_parser::ast::{self, Constant, Expr, ExprKind, Stmt, StmtKind};
|
use rustpython_parser::ast::{self, Constant, Expr, ExprKind, Stmt, StmtKind};
|
||||||
|
|
||||||
use ruff_python_semantic::analyze::visibility;
|
use ruff_python_semantic::definition::{Definition, DefinitionId, Definitions, Member, MemberKind};
|
||||||
|
|
||||||
use crate::docstrings::definition::{Definition, DefinitionKind, Documentable};
|
|
||||||
|
|
||||||
/// Extract a docstring from a function or class body.
|
/// Extract a docstring from a function or class body.
|
||||||
pub fn docstring_from(suite: &[Stmt]) -> Option<&Expr> {
|
pub(crate) fn docstring_from(suite: &[Stmt]) -> Option<&Expr> {
|
||||||
let stmt = suite.first()?;
|
let stmt = suite.first()?;
|
||||||
// Require the docstring to be a standalone expression.
|
// Require the docstring to be a standalone expression.
|
||||||
let StmtKind::Expr(ast::StmtExpr { value }) = &stmt.node else {
|
let StmtKind::Expr(ast::StmtExpr { value }) = &stmt.node else {
|
||||||
|
@ -26,59 +24,68 @@ pub fn docstring_from(suite: &[Stmt]) -> Option<&Expr> {
|
||||||
Some(value)
|
Some(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Extract a docstring from a `Definition`.
|
||||||
|
pub(crate) fn extract_docstring<'a>(definition: &'a Definition<'a>) -> Option<&'a Expr> {
|
||||||
|
match definition {
|
||||||
|
Definition::Module(module) => docstring_from(module.python_ast),
|
||||||
|
Definition::Member(member) => {
|
||||||
|
if let StmtKind::ClassDef(ast::StmtClassDef { body, .. })
|
||||||
|
| StmtKind::FunctionDef(ast::StmtFunctionDef { body, .. })
|
||||||
|
| StmtKind::AsyncFunctionDef(ast::StmtAsyncFunctionDef { body, .. }) =
|
||||||
|
&member.stmt.node
|
||||||
|
{
|
||||||
|
docstring_from(body)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub(crate) enum ExtractionTarget {
|
||||||
|
Class,
|
||||||
|
Function,
|
||||||
|
}
|
||||||
|
|
||||||
/// Extract a `Definition` from the AST node defined by a `Stmt`.
|
/// Extract a `Definition` from the AST node defined by a `Stmt`.
|
||||||
pub fn extract<'a>(
|
pub(crate) fn extract_definition<'a>(
|
||||||
scope: visibility::VisibleScope,
|
target: ExtractionTarget,
|
||||||
stmt: &'a Stmt,
|
stmt: &'a Stmt,
|
||||||
body: &'a [Stmt],
|
parent: DefinitionId,
|
||||||
kind: Documentable,
|
definitions: &Definitions<'a>,
|
||||||
) -> Definition<'a> {
|
) -> Member<'a> {
|
||||||
let expr = docstring_from(body);
|
match target {
|
||||||
match kind {
|
ExtractionTarget::Function => match &definitions[parent] {
|
||||||
Documentable::Function => match scope {
|
Definition::Module(..) => Member {
|
||||||
visibility::VisibleScope {
|
parent,
|
||||||
modifier: visibility::Modifier::Module,
|
kind: MemberKind::Function,
|
||||||
..
|
stmt,
|
||||||
} => Definition {
|
|
||||||
kind: DefinitionKind::Function(stmt),
|
|
||||||
docstring: expr,
|
|
||||||
},
|
},
|
||||||
visibility::VisibleScope {
|
Definition::Member(Member {
|
||||||
modifier: visibility::Modifier::Class,
|
kind: MemberKind::Class | MemberKind::NestedClass,
|
||||||
..
|
..
|
||||||
} => Definition {
|
}) => Member {
|
||||||
kind: DefinitionKind::Method(stmt),
|
parent,
|
||||||
docstring: expr,
|
kind: MemberKind::Method,
|
||||||
|
stmt,
|
||||||
},
|
},
|
||||||
visibility::VisibleScope {
|
Definition::Member(..) => Member {
|
||||||
modifier: visibility::Modifier::Function,
|
parent,
|
||||||
..
|
kind: MemberKind::NestedFunction,
|
||||||
} => Definition {
|
stmt,
|
||||||
kind: DefinitionKind::NestedFunction(stmt),
|
|
||||||
docstring: expr,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Documentable::Class => match scope {
|
ExtractionTarget::Class => match &definitions[parent] {
|
||||||
visibility::VisibleScope {
|
Definition::Module(..) => Member {
|
||||||
modifier: visibility::Modifier::Module,
|
parent,
|
||||||
..
|
kind: MemberKind::Class,
|
||||||
} => Definition {
|
stmt,
|
||||||
kind: DefinitionKind::Class(stmt),
|
|
||||||
docstring: expr,
|
|
||||||
},
|
},
|
||||||
visibility::VisibleScope {
|
Definition::Member(..) => Member {
|
||||||
modifier: visibility::Modifier::Class,
|
parent,
|
||||||
..
|
kind: MemberKind::NestedClass,
|
||||||
} => Definition {
|
stmt,
|
||||||
kind: DefinitionKind::NestedClass(stmt),
|
|
||||||
docstring: expr,
|
|
||||||
},
|
|
||||||
visibility::VisibleScope {
|
|
||||||
modifier: visibility::Modifier::Function,
|
|
||||||
..
|
|
||||||
} => Definition {
|
|
||||||
kind: DefinitionKind::NestedClass(stmt),
|
|
||||||
docstring: expr,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,89 @@
|
||||||
pub mod definition;
|
use std::fmt::{Debug, Formatter};
|
||||||
|
use std::ops::Deref;
|
||||||
|
|
||||||
|
use ruff_text_size::{TextRange, TextSize};
|
||||||
|
use rustpython_parser::ast::Expr;
|
||||||
|
|
||||||
|
use ruff_python_semantic::definition::Definition;
|
||||||
|
|
||||||
pub mod extraction;
|
pub mod extraction;
|
||||||
pub mod google;
|
pub mod google;
|
||||||
pub mod numpy;
|
pub mod numpy;
|
||||||
pub mod sections;
|
pub mod sections;
|
||||||
pub mod styles;
|
pub mod styles;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Docstring<'a> {
|
||||||
|
pub definition: &'a Definition<'a>,
|
||||||
|
pub expr: &'a Expr,
|
||||||
|
/// The content of the docstring, including the leading and trailing quotes.
|
||||||
|
pub contents: &'a str,
|
||||||
|
|
||||||
|
/// The range of the docstring body (without the quotes). The range is relative to [`Self::contents`].
|
||||||
|
pub body_range: TextRange,
|
||||||
|
pub indentation: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Docstring<'a> {
|
||||||
|
pub fn body(&self) -> DocstringBody {
|
||||||
|
DocstringBody { docstring: self }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn start(&self) -> TextSize {
|
||||||
|
self.expr.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn end(&self) -> TextSize {
|
||||||
|
self.expr.end()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn range(&self) -> TextRange {
|
||||||
|
self.expr.range()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn leading_quote(&self) -> &'a str {
|
||||||
|
&self.contents[TextRange::up_to(self.body_range.start())]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub struct DocstringBody<'a> {
|
||||||
|
docstring: &'a Docstring<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> DocstringBody<'a> {
|
||||||
|
#[inline]
|
||||||
|
pub fn start(self) -> TextSize {
|
||||||
|
self.range().start()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn end(self) -> TextSize {
|
||||||
|
self.range().end()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn range(self) -> TextRange {
|
||||||
|
self.docstring.body_range + self.docstring.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_str(self) -> &'a str {
|
||||||
|
&self.docstring.contents[self.docstring.body_range]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for DocstringBody<'_> {
|
||||||
|
type Target = str;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
self.as_str()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for DocstringBody<'_> {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("DocstringBody")
|
||||||
|
.field("text", &self.as_str())
|
||||||
|
.field("range", &self.range())
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -4,8 +4,8 @@ use std::fmt::{Debug, Formatter};
|
||||||
use std::iter::FusedIterator;
|
use std::iter::FusedIterator;
|
||||||
use strum_macros::EnumIter;
|
use strum_macros::EnumIter;
|
||||||
|
|
||||||
use crate::docstrings::definition::{Docstring, DocstringBody};
|
|
||||||
use crate::docstrings::styles::SectionStyle;
|
use crate::docstrings::styles::SectionStyle;
|
||||||
|
use crate::docstrings::{Docstring, DocstringBody};
|
||||||
use ruff_python_ast::whitespace;
|
use ruff_python_ast::whitespace;
|
||||||
|
|
||||||
#[derive(EnumIter, PartialEq, Eq, Debug, Clone, Copy)]
|
#[derive(EnumIter, PartialEq, Eq, Debug, Clone, Copy)]
|
||||||
|
|
|
@ -2,9 +2,9 @@ use rustpython_parser::ast::{self, Arguments, Expr, Stmt, StmtKind};
|
||||||
|
|
||||||
use ruff_python_ast::cast;
|
use ruff_python_ast::cast;
|
||||||
use ruff_python_semantic::analyze::visibility;
|
use ruff_python_semantic::analyze::visibility;
|
||||||
|
use ruff_python_semantic::definition::{Definition, Member, MemberKind};
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
use crate::docstrings::definition::{Definition, DefinitionKind};
|
|
||||||
|
|
||||||
pub(super) fn match_function_def(stmt: &Stmt) -> (&str, &Arguments, Option<&Expr>, &Vec<Stmt>) {
|
pub(super) fn match_function_def(stmt: &Stmt) -> (&str, &Arguments, Option<&Expr>, &Vec<Stmt>) {
|
||||||
match &stmt.node {
|
match &stmt.node {
|
||||||
|
@ -28,9 +28,11 @@ pub(super) fn match_function_def(stmt: &Stmt) -> (&str, &Arguments, Option<&Expr
|
||||||
|
|
||||||
/// Return the name of the function, if it's overloaded.
|
/// Return the name of the function, if it's overloaded.
|
||||||
pub fn overloaded_name(checker: &Checker, definition: &Definition) -> Option<String> {
|
pub fn overloaded_name(checker: &Checker, definition: &Definition) -> Option<String> {
|
||||||
if let DefinitionKind::Function(stmt)
|
if let Definition::Member(Member {
|
||||||
| DefinitionKind::NestedFunction(stmt)
|
kind: MemberKind::Function | MemberKind::NestedFunction | MemberKind::Method,
|
||||||
| DefinitionKind::Method(stmt) = definition.kind
|
stmt,
|
||||||
|
..
|
||||||
|
}) = definition
|
||||||
{
|
{
|
||||||
if visibility::is_overload(&checker.ctx, cast::decorator_list(stmt)) {
|
if visibility::is_overload(&checker.ctx, cast::decorator_list(stmt)) {
|
||||||
let (name, ..) = match_function_def(stmt);
|
let (name, ..) = match_function_def(stmt);
|
||||||
|
@ -46,9 +48,11 @@ pub fn overloaded_name(checker: &Checker, definition: &Definition) -> Option<Str
|
||||||
/// Return `true` if the definition is the implementation for an overloaded
|
/// Return `true` if the definition is the implementation for an overloaded
|
||||||
/// function.
|
/// function.
|
||||||
pub fn is_overload_impl(checker: &Checker, definition: &Definition, overloaded_name: &str) -> bool {
|
pub fn is_overload_impl(checker: &Checker, definition: &Definition, overloaded_name: &str) -> bool {
|
||||||
if let DefinitionKind::Function(stmt)
|
if let Definition::Member(Member {
|
||||||
| DefinitionKind::NestedFunction(stmt)
|
kind: MemberKind::Function | MemberKind::NestedFunction | MemberKind::Method,
|
||||||
| DefinitionKind::Method(stmt) = definition.kind
|
stmt,
|
||||||
|
..
|
||||||
|
}) = definition
|
||||||
{
|
{
|
||||||
if visibility::is_overload(&checker.ctx, cast::decorator_list(stmt)) {
|
if visibility::is_overload(&checker.ctx, cast::decorator_list(stmt)) {
|
||||||
false
|
false
|
||||||
|
|
|
@ -7,10 +7,10 @@ use ruff_python_ast::statement_visitor::StatementVisitor;
|
||||||
use ruff_python_ast::{cast, helpers};
|
use ruff_python_ast::{cast, helpers};
|
||||||
use ruff_python_semantic::analyze::visibility;
|
use ruff_python_semantic::analyze::visibility;
|
||||||
use ruff_python_semantic::analyze::visibility::Visibility;
|
use ruff_python_semantic::analyze::visibility::Visibility;
|
||||||
|
use ruff_python_semantic::definition::{Definition, Member, MemberKind};
|
||||||
use ruff_python_stdlib::typing::SIMPLE_MAGIC_RETURN_TYPES;
|
use ruff_python_stdlib::typing::SIMPLE_MAGIC_RETURN_TYPES;
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
use crate::docstrings::definition::{Definition, DefinitionKind};
|
|
||||||
use crate::registry::{AsRule, Rule};
|
use crate::registry::{AsRule, Rule};
|
||||||
|
|
||||||
use super::fixes;
|
use super::fixes;
|
||||||
|
@ -454,287 +454,285 @@ pub fn definition(
|
||||||
// TODO(charlie): Consider using the AST directly here rather than `Definition`.
|
// TODO(charlie): Consider using the AST directly here rather than `Definition`.
|
||||||
// We could adhere more closely to `flake8-annotations` by defining public
|
// We could adhere more closely to `flake8-annotations` by defining public
|
||||||
// vs. secret vs. protected.
|
// vs. secret vs. protected.
|
||||||
if let DefinitionKind::Function(stmt)
|
let Definition::Member(Member {
|
||||||
| DefinitionKind::NestedFunction(stmt)
|
kind,
|
||||||
| DefinitionKind::Method(stmt) = &definition.kind
|
stmt,
|
||||||
|
..
|
||||||
|
}) = definition else {
|
||||||
|
return vec![];
|
||||||
|
};
|
||||||
|
|
||||||
|
let is_method = match kind {
|
||||||
|
MemberKind::Method => true,
|
||||||
|
MemberKind::Function | MemberKind::NestedFunction => false,
|
||||||
|
_ => return vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
let (name, args, returns, body) = match_function_def(stmt);
|
||||||
|
// 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_typed_return = false; // Return value has been typed?
|
||||||
|
let mut has_typed_self_or_cls = false; // Has a typed `self` or `cls` argument?
|
||||||
|
|
||||||
|
// Temporary storage for diagnostics; we emit them at the end
|
||||||
|
// unless configured to suppress ANN* for declarations that are fully untyped.
|
||||||
|
let mut diagnostics = Vec::new();
|
||||||
|
|
||||||
|
// ANN001, ANN401
|
||||||
|
for arg in args
|
||||||
|
.posonlyargs
|
||||||
|
.iter()
|
||||||
|
.chain(args.args.iter())
|
||||||
|
.chain(args.kwonlyargs.iter())
|
||||||
|
.skip(
|
||||||
|
// If this is a non-static method, skip `cls` or `self`.
|
||||||
|
usize::from(
|
||||||
|
is_method && !visibility::is_staticmethod(&checker.ctx, cast::decorator_list(stmt)),
|
||||||
|
),
|
||||||
|
)
|
||||||
{
|
{
|
||||||
let is_method = matches!(definition.kind, DefinitionKind::Method(_));
|
// ANN401 for dynamically typed arguments
|
||||||
let (name, args, returns, body) = match_function_def(stmt);
|
if let Some(annotation) = &arg.node.annotation {
|
||||||
// Keep track of whether we've seen any typed arguments or return values.
|
has_any_typed_arg = true;
|
||||||
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_self_or_cls = false; // Has a typed `self` or `cls` argument?
|
|
||||||
|
|
||||||
// Temporary storage for diagnostics; we emit them at the end
|
|
||||||
// unless configured to suppress ANN* for declarations that are fully untyped.
|
|
||||||
let mut diagnostics = Vec::new();
|
|
||||||
|
|
||||||
// ANN001, ANN401
|
|
||||||
for arg in args
|
|
||||||
.posonlyargs
|
|
||||||
.iter()
|
|
||||||
.chain(args.args.iter())
|
|
||||||
.chain(args.kwonlyargs.iter())
|
|
||||||
.skip(
|
|
||||||
// If this is a non-static method, skip `cls` or `self`.
|
|
||||||
usize::from(
|
|
||||||
is_method
|
|
||||||
&& !visibility::is_staticmethod(&checker.ctx, cast::decorator_list(stmt)),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
{
|
|
||||||
// ANN401 for dynamically typed arguments
|
|
||||||
if let Some(annotation) = &arg.node.annotation {
|
|
||||||
has_any_typed_arg = true;
|
|
||||||
if checker.settings.rules.enabled(Rule::AnyType) {
|
|
||||||
check_dynamically_typed(
|
|
||||||
checker,
|
|
||||||
annotation,
|
|
||||||
|| arg.node.arg.to_string(),
|
|
||||||
&mut diagnostics,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if !(checker.settings.flake8_annotations.suppress_dummy_args
|
|
||||||
&& checker.settings.dummy_variable_rgx.is_match(&arg.node.arg))
|
|
||||||
{
|
|
||||||
if checker
|
|
||||||
.settings
|
|
||||||
.rules
|
|
||||||
.enabled(Rule::MissingTypeFunctionArgument)
|
|
||||||
{
|
|
||||||
diagnostics.push(Diagnostic::new(
|
|
||||||
MissingTypeFunctionArgument {
|
|
||||||
name: arg.node.arg.to_string(),
|
|
||||||
},
|
|
||||||
arg.range(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ANN002, ANN401
|
|
||||||
if let Some(arg) = &args.vararg {
|
|
||||||
if let Some(expr) = &arg.node.annotation {
|
|
||||||
has_any_typed_arg = true;
|
|
||||||
if !checker.settings.flake8_annotations.allow_star_arg_any {
|
|
||||||
if checker.settings.rules.enabled(Rule::AnyType) {
|
|
||||||
let name = &arg.node.arg;
|
|
||||||
check_dynamically_typed(
|
|
||||||
checker,
|
|
||||||
expr,
|
|
||||||
|| format!("*{name}"),
|
|
||||||
&mut diagnostics,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if !(checker.settings.flake8_annotations.suppress_dummy_args
|
|
||||||
&& checker.settings.dummy_variable_rgx.is_match(&arg.node.arg))
|
|
||||||
{
|
|
||||||
if checker.settings.rules.enabled(Rule::MissingTypeArgs) {
|
|
||||||
diagnostics.push(Diagnostic::new(
|
|
||||||
MissingTypeArgs {
|
|
||||||
name: arg.node.arg.to_string(),
|
|
||||||
},
|
|
||||||
arg.range(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ANN003, ANN401
|
|
||||||
if let Some(arg) = &args.kwarg {
|
|
||||||
if let Some(expr) = &arg.node.annotation {
|
|
||||||
has_any_typed_arg = true;
|
|
||||||
if !checker.settings.flake8_annotations.allow_star_arg_any {
|
|
||||||
if checker.settings.rules.enabled(Rule::AnyType) {
|
|
||||||
let name = &arg.node.arg;
|
|
||||||
check_dynamically_typed(
|
|
||||||
checker,
|
|
||||||
expr,
|
|
||||||
|| format!("**{name}"),
|
|
||||||
&mut diagnostics,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if !(checker.settings.flake8_annotations.suppress_dummy_args
|
|
||||||
&& checker.settings.dummy_variable_rgx.is_match(&arg.node.arg))
|
|
||||||
{
|
|
||||||
if checker.settings.rules.enabled(Rule::MissingTypeKwargs) {
|
|
||||||
diagnostics.push(Diagnostic::new(
|
|
||||||
MissingTypeKwargs {
|
|
||||||
name: arg.node.arg.to_string(),
|
|
||||||
},
|
|
||||||
arg.range(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ANN101, ANN102
|
|
||||||
if is_method && !visibility::is_staticmethod(&checker.ctx, cast::decorator_list(stmt)) {
|
|
||||||
if let Some(arg) = args.posonlyargs.first().or_else(|| args.args.first()) {
|
|
||||||
if arg.node.annotation.is_none() {
|
|
||||||
if visibility::is_classmethod(&checker.ctx, cast::decorator_list(stmt)) {
|
|
||||||
if checker.settings.rules.enabled(Rule::MissingTypeCls) {
|
|
||||||
diagnostics.push(Diagnostic::new(
|
|
||||||
MissingTypeCls {
|
|
||||||
name: arg.node.arg.to_string(),
|
|
||||||
},
|
|
||||||
arg.range(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if checker.settings.rules.enabled(Rule::MissingTypeSelf) {
|
|
||||||
diagnostics.push(Diagnostic::new(
|
|
||||||
MissingTypeSelf {
|
|
||||||
name: arg.node.arg.to_string(),
|
|
||||||
},
|
|
||||||
arg.range(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
has_typed_self_or_cls = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ANN201, ANN202, ANN401
|
|
||||||
if let Some(expr) = &returns {
|
|
||||||
has_typed_return = true;
|
|
||||||
if checker.settings.rules.enabled(Rule::AnyType) {
|
if checker.settings.rules.enabled(Rule::AnyType) {
|
||||||
check_dynamically_typed(checker, expr, || name.to_string(), &mut diagnostics);
|
check_dynamically_typed(
|
||||||
|
checker,
|
||||||
|
annotation,
|
||||||
|
|| arg.node.arg.to_string(),
|
||||||
|
&mut diagnostics,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else if !(
|
} else {
|
||||||
// Allow omission of return annotation if the function only returns `None`
|
if !(checker.settings.flake8_annotations.suppress_dummy_args
|
||||||
// (explicitly or implicitly).
|
&& checker.settings.dummy_variable_rgx.is_match(&arg.node.arg))
|
||||||
checker.settings.flake8_annotations.suppress_none_returning && is_none_returning(body)
|
|
||||||
) {
|
|
||||||
if is_method && visibility::is_classmethod(&checker.ctx, cast::decorator_list(stmt)) {
|
|
||||||
if checker
|
|
||||||
.settings
|
|
||||||
.rules
|
|
||||||
.enabled(Rule::MissingReturnTypeClassMethod)
|
|
||||||
{
|
|
||||||
diagnostics.push(Diagnostic::new(
|
|
||||||
MissingReturnTypeClassMethod {
|
|
||||||
name: name.to_string(),
|
|
||||||
},
|
|
||||||
helpers::identifier_range(stmt, checker.locator),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
} else if is_method
|
|
||||||
&& visibility::is_staticmethod(&checker.ctx, cast::decorator_list(stmt))
|
|
||||||
{
|
{
|
||||||
if checker
|
if checker
|
||||||
.settings
|
.settings
|
||||||
.rules
|
.rules
|
||||||
.enabled(Rule::MissingReturnTypeStaticMethod)
|
.enabled(Rule::MissingTypeFunctionArgument)
|
||||||
{
|
{
|
||||||
diagnostics.push(Diagnostic::new(
|
diagnostics.push(Diagnostic::new(
|
||||||
MissingReturnTypeStaticMethod {
|
MissingTypeFunctionArgument {
|
||||||
name: name.to_string(),
|
name: arg.node.arg.to_string(),
|
||||||
},
|
},
|
||||||
helpers::identifier_range(stmt, checker.locator),
|
arg.range(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
} else if is_method && visibility::is_init(name) {
|
}
|
||||||
// Allow omission of return annotation in `__init__` functions, as long as at
|
}
|
||||||
// least one argument is typed.
|
}
|
||||||
if checker
|
|
||||||
.settings
|
// ANN002, ANN401
|
||||||
.rules
|
if let Some(arg) = &args.vararg {
|
||||||
.enabled(Rule::MissingReturnTypeSpecialMethod)
|
if let Some(expr) = &arg.node.annotation {
|
||||||
{
|
has_any_typed_arg = true;
|
||||||
if !(checker.settings.flake8_annotations.mypy_init_return && has_any_typed_arg)
|
if !checker.settings.flake8_annotations.allow_star_arg_any {
|
||||||
{
|
if checker.settings.rules.enabled(Rule::AnyType) {
|
||||||
let mut diagnostic = Diagnostic::new(
|
let name = &arg.node.arg;
|
||||||
MissingReturnTypeSpecialMethod {
|
check_dynamically_typed(checker, expr, || format!("*{name}"), &mut diagnostics);
|
||||||
name: name.to_string(),
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if !(checker.settings.flake8_annotations.suppress_dummy_args
|
||||||
|
&& checker.settings.dummy_variable_rgx.is_match(&arg.node.arg))
|
||||||
|
{
|
||||||
|
if checker.settings.rules.enabled(Rule::MissingTypeArgs) {
|
||||||
|
diagnostics.push(Diagnostic::new(
|
||||||
|
MissingTypeArgs {
|
||||||
|
name: arg.node.arg.to_string(),
|
||||||
|
},
|
||||||
|
arg.range(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ANN003, ANN401
|
||||||
|
if let Some(arg) = &args.kwarg {
|
||||||
|
if let Some(expr) = &arg.node.annotation {
|
||||||
|
has_any_typed_arg = true;
|
||||||
|
if !checker.settings.flake8_annotations.allow_star_arg_any {
|
||||||
|
if checker.settings.rules.enabled(Rule::AnyType) {
|
||||||
|
let name = &arg.node.arg;
|
||||||
|
check_dynamically_typed(
|
||||||
|
checker,
|
||||||
|
expr,
|
||||||
|
|| format!("**{name}"),
|
||||||
|
&mut diagnostics,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if !(checker.settings.flake8_annotations.suppress_dummy_args
|
||||||
|
&& checker.settings.dummy_variable_rgx.is_match(&arg.node.arg))
|
||||||
|
{
|
||||||
|
if checker.settings.rules.enabled(Rule::MissingTypeKwargs) {
|
||||||
|
diagnostics.push(Diagnostic::new(
|
||||||
|
MissingTypeKwargs {
|
||||||
|
name: arg.node.arg.to_string(),
|
||||||
|
},
|
||||||
|
arg.range(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ANN101, ANN102
|
||||||
|
if is_method && !visibility::is_staticmethod(&checker.ctx, cast::decorator_list(stmt)) {
|
||||||
|
if let Some(arg) = args.posonlyargs.first().or_else(|| args.args.first()) {
|
||||||
|
if arg.node.annotation.is_none() {
|
||||||
|
if visibility::is_classmethod(&checker.ctx, cast::decorator_list(stmt)) {
|
||||||
|
if checker.settings.rules.enabled(Rule::MissingTypeCls) {
|
||||||
|
diagnostics.push(Diagnostic::new(
|
||||||
|
MissingTypeCls {
|
||||||
|
name: arg.node.arg.to_string(),
|
||||||
},
|
},
|
||||||
helpers::identifier_range(stmt, checker.locator),
|
arg.range(),
|
||||||
);
|
));
|
||||||
if checker.patch(diagnostic.kind.rule()) {
|
}
|
||||||
#[allow(deprecated)]
|
} else {
|
||||||
diagnostic.try_set_fix_from_edit(|| {
|
if checker.settings.rules.enabled(Rule::MissingTypeSelf) {
|
||||||
fixes::add_return_annotation(checker.locator, stmt, "None")
|
diagnostics.push(Diagnostic::new(
|
||||||
});
|
MissingTypeSelf {
|
||||||
}
|
name: arg.node.arg.to_string(),
|
||||||
diagnostics.push(diagnostic);
|
},
|
||||||
|
arg.range(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if is_method && visibility::is_magic(name) {
|
} else {
|
||||||
if checker
|
has_typed_self_or_cls = true;
|
||||||
.settings
|
}
|
||||||
.rules
|
}
|
||||||
.enabled(Rule::MissingReturnTypeSpecialMethod)
|
}
|
||||||
{
|
|
||||||
|
// ANN201, ANN202, ANN401
|
||||||
|
if let Some(expr) = &returns {
|
||||||
|
has_typed_return = true;
|
||||||
|
if checker.settings.rules.enabled(Rule::AnyType) {
|
||||||
|
check_dynamically_typed(checker, expr, || name.to_string(), &mut diagnostics);
|
||||||
|
}
|
||||||
|
} else if !(
|
||||||
|
// Allow omission of return annotation if the function only returns `None`
|
||||||
|
// (explicitly or implicitly).
|
||||||
|
checker.settings.flake8_annotations.suppress_none_returning && is_none_returning(body)
|
||||||
|
) {
|
||||||
|
if is_method && visibility::is_classmethod(&checker.ctx, cast::decorator_list(stmt)) {
|
||||||
|
if checker
|
||||||
|
.settings
|
||||||
|
.rules
|
||||||
|
.enabled(Rule::MissingReturnTypeClassMethod)
|
||||||
|
{
|
||||||
|
diagnostics.push(Diagnostic::new(
|
||||||
|
MissingReturnTypeClassMethod {
|
||||||
|
name: name.to_string(),
|
||||||
|
},
|
||||||
|
helpers::identifier_range(stmt, checker.locator),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} else if is_method && visibility::is_staticmethod(&checker.ctx, cast::decorator_list(stmt))
|
||||||
|
{
|
||||||
|
if checker
|
||||||
|
.settings
|
||||||
|
.rules
|
||||||
|
.enabled(Rule::MissingReturnTypeStaticMethod)
|
||||||
|
{
|
||||||
|
diagnostics.push(Diagnostic::new(
|
||||||
|
MissingReturnTypeStaticMethod {
|
||||||
|
name: name.to_string(),
|
||||||
|
},
|
||||||
|
helpers::identifier_range(stmt, checker.locator),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} else if is_method && visibility::is_init(name) {
|
||||||
|
// Allow omission of return annotation in `__init__` functions, as long as at
|
||||||
|
// least one argument is typed.
|
||||||
|
if checker
|
||||||
|
.settings
|
||||||
|
.rules
|
||||||
|
.enabled(Rule::MissingReturnTypeSpecialMethod)
|
||||||
|
{
|
||||||
|
if !(checker.settings.flake8_annotations.mypy_init_return && has_any_typed_arg) {
|
||||||
let mut diagnostic = Diagnostic::new(
|
let mut diagnostic = Diagnostic::new(
|
||||||
MissingReturnTypeSpecialMethod {
|
MissingReturnTypeSpecialMethod {
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
},
|
},
|
||||||
helpers::identifier_range(stmt, checker.locator),
|
helpers::identifier_range(stmt, checker.locator),
|
||||||
);
|
);
|
||||||
let return_type = SIMPLE_MAGIC_RETURN_TYPES.get(name);
|
if checker.patch(diagnostic.kind.rule()) {
|
||||||
if let Some(return_type) = return_type {
|
#[allow(deprecated)]
|
||||||
if checker.patch(diagnostic.kind.rule()) {
|
diagnostic.try_set_fix_from_edit(|| {
|
||||||
#[allow(deprecated)]
|
fixes::add_return_annotation(checker.locator, stmt, "None")
|
||||||
diagnostic.try_set_fix_from_edit(|| {
|
});
|
||||||
fixes::add_return_annotation(checker.locator, stmt, return_type)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
diagnostics.push(diagnostic);
|
diagnostics.push(diagnostic);
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
match visibility {
|
} else if is_method && visibility::is_magic(name) {
|
||||||
Visibility::Public => {
|
if checker
|
||||||
if checker
|
.settings
|
||||||
.settings
|
.rules
|
||||||
.rules
|
.enabled(Rule::MissingReturnTypeSpecialMethod)
|
||||||
.enabled(Rule::MissingReturnTypeUndocumentedPublicFunction)
|
{
|
||||||
{
|
let mut diagnostic = Diagnostic::new(
|
||||||
diagnostics.push(Diagnostic::new(
|
MissingReturnTypeSpecialMethod {
|
||||||
MissingReturnTypeUndocumentedPublicFunction {
|
name: name.to_string(),
|
||||||
name: name.to_string(),
|
},
|
||||||
},
|
helpers::identifier_range(stmt, checker.locator),
|
||||||
helpers::identifier_range(stmt, checker.locator),
|
);
|
||||||
));
|
let return_type = SIMPLE_MAGIC_RETURN_TYPES.get(name);
|
||||||
}
|
if let Some(return_type) = return_type {
|
||||||
|
if checker.patch(diagnostic.kind.rule()) {
|
||||||
|
#[allow(deprecated)]
|
||||||
|
diagnostic.try_set_fix_from_edit(|| {
|
||||||
|
fixes::add_return_annotation(checker.locator, stmt, return_type)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
Visibility::Private => {
|
}
|
||||||
if checker
|
diagnostics.push(diagnostic);
|
||||||
.settings
|
}
|
||||||
.rules
|
} else {
|
||||||
.enabled(Rule::MissingReturnTypePrivateFunction)
|
match visibility {
|
||||||
{
|
Visibility::Public => {
|
||||||
diagnostics.push(Diagnostic::new(
|
if checker
|
||||||
MissingReturnTypePrivateFunction {
|
.settings
|
||||||
name: name.to_string(),
|
.rules
|
||||||
},
|
.enabled(Rule::MissingReturnTypeUndocumentedPublicFunction)
|
||||||
helpers::identifier_range(stmt, checker.locator),
|
{
|
||||||
));
|
diagnostics.push(Diagnostic::new(
|
||||||
}
|
MissingReturnTypeUndocumentedPublicFunction {
|
||||||
|
name: name.to_string(),
|
||||||
|
},
|
||||||
|
helpers::identifier_range(stmt, checker.locator),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Visibility::Private => {
|
||||||
|
if checker
|
||||||
|
.settings
|
||||||
|
.rules
|
||||||
|
.enabled(Rule::MissingReturnTypePrivateFunction)
|
||||||
|
{
|
||||||
|
diagnostics.push(Diagnostic::new(
|
||||||
|
MissingReturnTypePrivateFunction {
|
||||||
|
name: name.to_string(),
|
||||||
|
},
|
||||||
|
helpers::identifier_range(stmt, checker.locator),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If settings say so, don't report any of the
|
}
|
||||||
// diagnostics gathered here if there were no type annotations at all.
|
// If settings say so, don't report any of the
|
||||||
if checker.settings.flake8_annotations.ignore_fully_untyped
|
// diagnostics gathered here if there were no type annotations at all.
|
||||||
&& !(has_any_typed_arg || has_typed_self_or_cls || has_typed_return)
|
if checker.settings.flake8_annotations.ignore_fully_untyped
|
||||||
{
|
&& !(has_any_typed_arg || has_typed_self_or_cls || has_typed_return)
|
||||||
vec![]
|
{
|
||||||
} else {
|
|
||||||
diagnostics
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
vec![]
|
vec![]
|
||||||
|
} else {
|
||||||
|
diagnostics
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
use ruff_python_ast::call_path::from_qualified_name;
|
|
||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
|
|
||||||
|
use ruff_python_ast::call_path::from_qualified_name;
|
||||||
use ruff_python_ast::cast;
|
use ruff_python_ast::cast;
|
||||||
use ruff_python_ast::helpers::map_callable;
|
use ruff_python_ast::helpers::map_callable;
|
||||||
use ruff_python_ast::newlines::StrExt;
|
use ruff_python_ast::newlines::StrExt;
|
||||||
use ruff_python_ast::str::is_implicit_concatenation;
|
use ruff_python_ast::str::is_implicit_concatenation;
|
||||||
|
use ruff_python_semantic::definition::{Definition, Member, MemberKind};
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
use crate::docstrings::definition::{Definition, DefinitionKind};
|
|
||||||
|
|
||||||
/// Return the index of the first logical line in a string.
|
/// Return the index of the first logical line in a string.
|
||||||
pub(crate) fn logical_line(content: &str) -> Option<usize> {
|
pub(crate) fn logical_line(content: &str) -> Option<usize> {
|
||||||
|
@ -45,11 +45,13 @@ pub(crate) fn should_ignore_definition(
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let DefinitionKind::Function(parent)
|
if let Definition::Member(Member {
|
||||||
| DefinitionKind::NestedFunction(parent)
|
kind: MemberKind::Function | MemberKind::NestedFunction | MemberKind::Method,
|
||||||
| DefinitionKind::Method(parent) = definition.kind
|
stmt,
|
||||||
|
..
|
||||||
|
}) = definition
|
||||||
{
|
{
|
||||||
for decorator in cast::decorator_list(parent) {
|
for decorator in cast::decorator_list(stmt) {
|
||||||
if let Some(call_path) = checker.ctx.resolve_call_path(map_callable(decorator)) {
|
if let Some(call_path) = checker.ctx.resolve_call_path(map_callable(decorator)) {
|
||||||
if ignore_decorators
|
if ignore_decorators
|
||||||
.iter()
|
.iter()
|
||||||
|
|
|
@ -5,7 +5,7 @@ use ruff_diagnostics::{Diagnostic, Violation};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
use crate::docstrings::definition::Docstring;
|
use crate::docstrings::Docstring;
|
||||||
|
|
||||||
#[violation]
|
#[violation]
|
||||||
pub struct EscapeSequenceInDocstring;
|
pub struct EscapeSequenceInDocstring;
|
||||||
|
|
|
@ -3,7 +3,7 @@ use ruff_macros::{derive_message_formats, violation};
|
||||||
use ruff_python_ast::newlines::{StrExt, UniversalNewlineIterator};
|
use ruff_python_ast::newlines::{StrExt, UniversalNewlineIterator};
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
use crate::docstrings::definition::Docstring;
|
use crate::docstrings::Docstring;
|
||||||
use crate::registry::AsRule;
|
use crate::registry::AsRule;
|
||||||
|
|
||||||
#[violation]
|
#[violation]
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
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::newlines::{StrExt, UniversalNewlineIterator};
|
use ruff_python_ast::newlines::{StrExt, UniversalNewlineIterator};
|
||||||
|
use ruff_python_semantic::definition::{Definition, Member, MemberKind};
|
||||||
use ruff_text_size::{TextLen, TextRange};
|
use ruff_text_size::{TextLen, TextRange};
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
use crate::docstrings::definition::{DefinitionKind, Docstring};
|
use crate::docstrings::Docstring;
|
||||||
use crate::registry::{AsRule, Rule};
|
use crate::registry::{AsRule, Rule};
|
||||||
|
|
||||||
#[violation]
|
#[violation]
|
||||||
|
@ -57,7 +58,11 @@ impl AlwaysAutofixableViolation for BlankLineBeforeClass {
|
||||||
|
|
||||||
/// D203, D204, D211
|
/// D203, D204, D211
|
||||||
pub fn blank_before_after_class(checker: &mut Checker, docstring: &Docstring) {
|
pub fn blank_before_after_class(checker: &mut Checker, docstring: &Docstring) {
|
||||||
let (DefinitionKind::Class(parent) | DefinitionKind::NestedClass(parent)) = &docstring.kind else {
|
let Definition::Member(Member {
|
||||||
|
kind: MemberKind::Class | MemberKind::NestedClass ,
|
||||||
|
stmt,
|
||||||
|
..
|
||||||
|
}) = docstring.definition else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -69,10 +74,10 @@ pub fn blank_before_after_class(checker: &mut Checker, docstring: &Docstring) {
|
||||||
{
|
{
|
||||||
let before = checker
|
let before = checker
|
||||||
.locator
|
.locator
|
||||||
.slice(TextRange::new(parent.start(), docstring.start()));
|
.slice(TextRange::new(stmt.start(), docstring.start()));
|
||||||
|
|
||||||
let mut blank_lines_before = 0usize;
|
let mut blank_lines_before = 0usize;
|
||||||
let mut lines = UniversalNewlineIterator::with_offset(before, parent.start()).rev();
|
let mut lines = UniversalNewlineIterator::with_offset(before, stmt.start()).rev();
|
||||||
let mut blank_lines_start = lines.next().map(|line| line.start()).unwrap_or_default();
|
let mut blank_lines_start = lines.next().map(|line| line.start()).unwrap_or_default();
|
||||||
|
|
||||||
for line in lines {
|
for line in lines {
|
||||||
|
@ -132,7 +137,7 @@ pub fn blank_before_after_class(checker: &mut Checker, docstring: &Docstring) {
|
||||||
if checker.settings.rules.enabled(Rule::OneBlankLineAfterClass) {
|
if checker.settings.rules.enabled(Rule::OneBlankLineAfterClass) {
|
||||||
let after = checker
|
let after = checker
|
||||||
.locator
|
.locator
|
||||||
.slice(TextRange::new(docstring.end(), parent.end()));
|
.slice(TextRange::new(docstring.end(), stmt.end()));
|
||||||
|
|
||||||
let all_blank_after = after
|
let all_blank_after = after
|
||||||
.universal_newlines()
|
.universal_newlines()
|
||||||
|
|
|
@ -5,9 +5,10 @@ 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_ast::newlines::{StrExt, UniversalNewlineIterator};
|
use ruff_python_ast::newlines::{StrExt, UniversalNewlineIterator};
|
||||||
|
use ruff_python_semantic::definition::{Definition, Member, MemberKind};
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
use crate::docstrings::definition::{DefinitionKind, Docstring};
|
use crate::docstrings::Docstring;
|
||||||
use crate::registry::{AsRule, Rule};
|
use crate::registry::{AsRule, Rule};
|
||||||
|
|
||||||
#[violation]
|
#[violation]
|
||||||
|
@ -49,11 +50,11 @@ static INNER_FUNCTION_OR_CLASS_REGEX: Lazy<Regex> =
|
||||||
|
|
||||||
/// D201, D202
|
/// D201, D202
|
||||||
pub fn blank_before_after_function(checker: &mut Checker, docstring: &Docstring) {
|
pub fn blank_before_after_function(checker: &mut Checker, docstring: &Docstring) {
|
||||||
let (
|
let Definition::Member(Member {
|
||||||
DefinitionKind::Function(parent)
|
kind: MemberKind::Function | MemberKind::NestedFunction | MemberKind::Method,
|
||||||
| DefinitionKind::NestedFunction(parent)
|
stmt,
|
||||||
| DefinitionKind::Method(parent)
|
..
|
||||||
) = &docstring.kind else {
|
}) = docstring.definition else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -64,9 +65,9 @@ pub fn blank_before_after_function(checker: &mut Checker, docstring: &Docstring)
|
||||||
{
|
{
|
||||||
let before = checker
|
let before = checker
|
||||||
.locator
|
.locator
|
||||||
.slice(TextRange::new(parent.start(), docstring.start()));
|
.slice(TextRange::new(stmt.start(), docstring.start()));
|
||||||
|
|
||||||
let mut lines = UniversalNewlineIterator::with_offset(before, parent.start()).rev();
|
let mut lines = UniversalNewlineIterator::with_offset(before, stmt.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();
|
||||||
|
|
||||||
|
@ -105,7 +106,7 @@ pub fn blank_before_after_function(checker: &mut Checker, docstring: &Docstring)
|
||||||
{
|
{
|
||||||
let after = checker
|
let after = checker
|
||||||
.locator
|
.locator
|
||||||
.slice(TextRange::new(docstring.end(), parent.end()));
|
.slice(TextRange::new(docstring.end(), stmt.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
|
let all_blank_after = after
|
||||||
|
|
|
@ -2,9 +2,10 @@ 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::{Definition, Member, MemberKind};
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
use crate::docstrings::definition::{DefinitionKind, Docstring};
|
use crate::docstrings::Docstring;
|
||||||
use crate::registry::AsRule;
|
use crate::registry::AsRule;
|
||||||
|
|
||||||
#[violation]
|
#[violation]
|
||||||
|
@ -33,8 +34,11 @@ impl AlwaysAutofixableViolation for FirstLineCapitalized {
|
||||||
/// D403
|
/// D403
|
||||||
pub fn capitalized(checker: &mut Checker, docstring: &Docstring) {
|
pub fn capitalized(checker: &mut Checker, docstring: &Docstring) {
|
||||||
if !matches!(
|
if !matches!(
|
||||||
docstring.kind,
|
docstring.definition,
|
||||||
DefinitionKind::Function(_) | DefinitionKind::NestedFunction(_) | DefinitionKind::Method(_)
|
Definition::Member(Member {
|
||||||
|
kind: MemberKind::Function | MemberKind::NestedFunction | MemberKind::Method,
|
||||||
|
..
|
||||||
|
})
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,8 +6,8 @@ use ruff_macros::{derive_message_formats, violation};
|
||||||
use ruff_python_ast::newlines::{StrExt, UniversalNewlineIterator};
|
use ruff_python_ast::newlines::{StrExt, UniversalNewlineIterator};
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
use crate::docstrings::definition::Docstring;
|
|
||||||
use crate::docstrings::sections::SectionKind;
|
use crate::docstrings::sections::SectionKind;
|
||||||
|
use crate::docstrings::Docstring;
|
||||||
use crate::registry::AsRule;
|
use crate::registry::AsRule;
|
||||||
use crate::rules::pydocstyle::helpers::logical_line;
|
use crate::rules::pydocstyle::helpers::logical_line;
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,8 @@ use ruff_macros::{derive_message_formats, violation};
|
||||||
use ruff_python_ast::newlines::{StrExt, UniversalNewlineIterator};
|
use ruff_python_ast::newlines::{StrExt, UniversalNewlineIterator};
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
use crate::docstrings::definition::Docstring;
|
|
||||||
use crate::docstrings::sections::SectionKind;
|
use crate::docstrings::sections::SectionKind;
|
||||||
|
use crate::docstrings::Docstring;
|
||||||
use crate::registry::AsRule;
|
use crate::registry::AsRule;
|
||||||
use crate::rules::pydocstyle::helpers::logical_line;
|
use crate::rules::pydocstyle::helpers::logical_line;
|
||||||
|
|
||||||
|
|
|
@ -3,9 +3,10 @@ use ruff_macros::{derive_message_formats, violation};
|
||||||
use ruff_python_ast::cast;
|
use ruff_python_ast::cast;
|
||||||
use ruff_python_ast::helpers::identifier_range;
|
use ruff_python_ast::helpers::identifier_range;
|
||||||
use ruff_python_semantic::analyze::visibility::is_overload;
|
use ruff_python_semantic::analyze::visibility::is_overload;
|
||||||
|
use ruff_python_semantic::definition::{Definition, Member, MemberKind};
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
use crate::docstrings::definition::{DefinitionKind, Docstring};
|
use crate::docstrings::Docstring;
|
||||||
|
|
||||||
#[violation]
|
#[violation]
|
||||||
pub struct OverloadWithDocstring;
|
pub struct OverloadWithDocstring;
|
||||||
|
@ -19,12 +20,12 @@ impl Violation for OverloadWithDocstring {
|
||||||
|
|
||||||
/// D418
|
/// D418
|
||||||
pub fn if_needed(checker: &mut Checker, docstring: &Docstring) {
|
pub fn if_needed(checker: &mut Checker, docstring: &Docstring) {
|
||||||
let (
|
let Definition::Member(Member {
|
||||||
DefinitionKind::Function(stmt)
|
kind: MemberKind::Function | MemberKind::NestedFunction | MemberKind::Method,
|
||||||
| DefinitionKind::NestedFunction(stmt)
|
stmt,
|
||||||
| DefinitionKind::Method(stmt)
|
..
|
||||||
) = docstring.kind else {
|
}) = docstring.definition else {
|
||||||
return
|
return;
|
||||||
};
|
};
|
||||||
if !is_overload(&checker.ctx, cast::decorator_list(stmt)) {
|
if !is_overload(&checker.ctx, cast::decorator_list(stmt)) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -6,7 +6,7 @@ use ruff_python_ast::whitespace;
|
||||||
use ruff_text_size::{TextLen, TextRange};
|
use ruff_text_size::{TextLen, TextRange};
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
use crate::docstrings::definition::Docstring;
|
use crate::docstrings::Docstring;
|
||||||
use crate::registry::{AsRule, Rule};
|
use crate::registry::{AsRule, Rule};
|
||||||
|
|
||||||
#[violation]
|
#[violation]
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
|
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::newlines::{NewlineWithTrailingNewline, UniversalNewlineIterator};
|
use ruff_python_ast::newlines::{NewlineWithTrailingNewline, UniversalNewlineIterator};
|
||||||
use ruff_python_ast::str::{is_triple_quote, leading_quote};
|
use ruff_python_ast::str::{is_triple_quote, leading_quote};
|
||||||
use ruff_text_size::{TextRange, TextSize};
|
use ruff_python_semantic::definition::{Definition, Member};
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
use crate::docstrings::definition::{DefinitionKind, Docstring};
|
use crate::docstrings::Docstring;
|
||||||
use crate::registry::{AsRule, Rule};
|
use crate::registry::{AsRule, Rule};
|
||||||
|
|
||||||
#[violation]
|
#[violation]
|
||||||
|
@ -91,22 +93,17 @@ pub fn multi_line_summary_start(checker: &mut Checker, docstring: &Docstring) {
|
||||||
if !indentation.chars().all(char::is_whitespace) {
|
if !indentation.chars().all(char::is_whitespace) {
|
||||||
fixable = false;
|
fixable = false;
|
||||||
|
|
||||||
// If the docstring isn't on its own line, look at the parent indentation, and
|
// If the docstring isn't on its own line, look at the statement indentation,
|
||||||
// add the default indentation to get the "right" level.
|
// and add the default indentation to get the "right" level.
|
||||||
if let DefinitionKind::Class(parent)
|
if let Definition::Member(Member { stmt, .. }) = &docstring.definition {
|
||||||
| DefinitionKind::NestedClass(parent)
|
let stmt_line_start = checker.locator.line_start(stmt.start());
|
||||||
| DefinitionKind::Function(parent)
|
let stmt_indentation = checker
|
||||||
| DefinitionKind::NestedFunction(parent)
|
|
||||||
| DefinitionKind::Method(parent) = &docstring.kind
|
|
||||||
{
|
|
||||||
let parent_line_start = checker.locator.line_start(parent.start());
|
|
||||||
let parent_indentation = checker
|
|
||||||
.locator
|
.locator
|
||||||
.slice(TextRange::new(parent_line_start, parent.start()));
|
.slice(TextRange::new(stmt_line_start, stmt.start()));
|
||||||
|
|
||||||
if parent_indentation.chars().all(char::is_whitespace) {
|
if stmt_indentation.chars().all(char::is_whitespace) {
|
||||||
indentation.clear();
|
indentation.clear();
|
||||||
indentation.push_str(parent_indentation);
|
indentation.push_str(stmt_indentation);
|
||||||
indentation.push_str(checker.stylist.indentation());
|
indentation.push_str(checker.stylist.indentation());
|
||||||
fixable = true;
|
fixable = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ use ruff_python_ast::whitespace;
|
||||||
use ruff_text_size::{TextLen, TextSize};
|
use ruff_text_size::{TextLen, TextSize};
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
use crate::docstrings::definition::Docstring;
|
use crate::docstrings::Docstring;
|
||||||
use crate::registry::AsRule;
|
use crate::registry::AsRule;
|
||||||
|
|
||||||
#[violation]
|
#[violation]
|
||||||
|
|
|
@ -3,9 +3,10 @@ use rustpython_parser::ast::{self, StmtKind};
|
||||||
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::newlines::StrExt;
|
use ruff_python_ast::newlines::StrExt;
|
||||||
|
use ruff_python_semantic::definition::{Definition, Member, MemberKind};
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
use crate::docstrings::definition::{DefinitionKind, Docstring};
|
use crate::docstrings::Docstring;
|
||||||
|
|
||||||
#[violation]
|
#[violation]
|
||||||
pub struct NoSignature;
|
pub struct NoSignature;
|
||||||
|
@ -19,14 +20,14 @@ impl Violation for NoSignature {
|
||||||
|
|
||||||
/// D402
|
/// D402
|
||||||
pub fn no_signature(checker: &mut Checker, docstring: &Docstring) {
|
pub fn no_signature(checker: &mut Checker, docstring: &Docstring) {
|
||||||
let (
|
let Definition::Member(Member {
|
||||||
DefinitionKind::Function(parent)
|
kind: MemberKind::Function | MemberKind::NestedFunction | MemberKind::Method,
|
||||||
| DefinitionKind::NestedFunction(parent)
|
stmt,
|
||||||
| DefinitionKind::Method(parent)
|
..
|
||||||
) = docstring.kind else {
|
}) = docstring.definition else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let StmtKind::FunctionDef(ast::StmtFunctionDef { name, .. }) = &parent.node else {
|
let StmtKind::FunctionDef(ast::StmtFunctionDef { name, .. }) = &stmt.node else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ use ruff_python_ast::newlines::NewlineWithTrailingNewline;
|
||||||
use ruff_text_size::{TextLen, TextRange};
|
use ruff_text_size::{TextLen, TextRange};
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
use crate::docstrings::definition::Docstring;
|
use crate::docstrings::Docstring;
|
||||||
use crate::registry::AsRule;
|
use crate::registry::AsRule;
|
||||||
use crate::rules::pydocstyle::helpers::ends_with_backslash;
|
use crate::rules::pydocstyle::helpers::ends_with_backslash;
|
||||||
|
|
||||||
|
|
|
@ -9,9 +9,10 @@ use ruff_python_ast::call_path::{from_qualified_name, CallPath};
|
||||||
use ruff_python_ast::cast;
|
use ruff_python_ast::cast;
|
||||||
use ruff_python_ast::newlines::StrExt;
|
use ruff_python_ast::newlines::StrExt;
|
||||||
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::{Definition, Member, MemberKind};
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
use crate::docstrings::definition::{DefinitionKind, Docstring};
|
use crate::docstrings::Docstring;
|
||||||
use crate::rules::pydocstyle::helpers::normalize_word;
|
use crate::rules::pydocstyle::helpers::normalize_word;
|
||||||
|
|
||||||
static MOOD: Lazy<Mood> = Lazy::new(Mood::new);
|
static MOOD: Lazy<Mood> = Lazy::new(Mood::new);
|
||||||
|
@ -22,23 +23,26 @@ pub fn non_imperative_mood(
|
||||||
docstring: &Docstring,
|
docstring: &Docstring,
|
||||||
property_decorators: &BTreeSet<String>,
|
property_decorators: &BTreeSet<String>,
|
||||||
) {
|
) {
|
||||||
let (
|
let Definition::Member(Member { kind, stmt, .. }) = &docstring.definition else {
|
||||||
DefinitionKind::Function(parent)
|
|
||||||
| DefinitionKind::NestedFunction(parent)
|
|
||||||
| DefinitionKind::Method(parent)
|
|
||||||
) = &docstring.kind 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(parent))
|
if is_test(cast::name(stmt))
|
||||||
|| is_property(
|
|| is_property(
|
||||||
&checker.ctx,
|
&checker.ctx,
|
||||||
cast::decorator_list(parent),
|
cast::decorator_list(stmt),
|
||||||
&property_decorators,
|
&property_decorators,
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
|
|
@ -2,7 +2,7 @@ use ruff_diagnostics::{Diagnostic, Violation};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
use crate::docstrings::definition::Docstring;
|
use crate::docstrings::Docstring;
|
||||||
use crate::registry::Rule;
|
use crate::registry::Rule;
|
||||||
|
|
||||||
#[violation]
|
#[violation]
|
||||||
|
|
|
@ -5,10 +5,11 @@ use ruff_python_ast::helpers::identifier_range;
|
||||||
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::{Definition, Member, MemberKind, Module, ModuleKind};
|
||||||
use ruff_text_size::TextRange;
|
use ruff_text_size::TextRange;
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
use crate::docstrings::definition::{Definition, DefinitionKind};
|
|
||||||
use crate::registry::Rule;
|
use crate::registry::Rule;
|
||||||
|
|
||||||
#[violation]
|
#[violation]
|
||||||
|
@ -93,12 +94,15 @@ impl Violation for UndocumentedPublicInit {
|
||||||
|
|
||||||
/// D100, D101, D102, D103, D104, D105, D106, D107
|
/// D100, D101, D102, D103, D104, D105, D106, D107
|
||||||
pub fn not_missing(checker: &mut Checker, definition: &Definition, visibility: Visibility) -> bool {
|
pub fn not_missing(checker: &mut Checker, definition: &Definition, visibility: Visibility) -> bool {
|
||||||
if matches!(visibility, Visibility::Private) {
|
if visibility.is_private() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
match definition.kind {
|
match definition {
|
||||||
DefinitionKind::Module => {
|
Definition::Module(Module {
|
||||||
|
kind: ModuleKind::Module,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
if checker
|
if checker
|
||||||
.settings
|
.settings
|
||||||
.rules
|
.rules
|
||||||
|
@ -111,7 +115,10 @@ pub fn not_missing(checker: &mut Checker, definition: &Definition, visibility: V
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
DefinitionKind::Package => {
|
Definition::Module(Module {
|
||||||
|
kind: ModuleKind::Package,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
if checker
|
if checker
|
||||||
.settings
|
.settings
|
||||||
.rules
|
.rules
|
||||||
|
@ -124,7 +131,11 @@ pub fn not_missing(checker: &mut Checker, definition: &Definition, visibility: V
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
DefinitionKind::Class(stmt) => {
|
Definition::Member(Member {
|
||||||
|
kind: MemberKind::Class,
|
||||||
|
stmt,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
if checker
|
if checker
|
||||||
.settings
|
.settings
|
||||||
.rules
|
.rules
|
||||||
|
@ -137,7 +148,11 @@ pub fn not_missing(checker: &mut Checker, definition: &Definition, visibility: V
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
DefinitionKind::NestedClass(stmt) => {
|
Definition::Member(Member {
|
||||||
|
kind: MemberKind::NestedClass,
|
||||||
|
stmt,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
if checker
|
if checker
|
||||||
.settings
|
.settings
|
||||||
.rules
|
.rules
|
||||||
|
@ -150,7 +165,11 @@ pub fn not_missing(checker: &mut Checker, definition: &Definition, visibility: V
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
DefinitionKind::Function(stmt) | DefinitionKind::NestedFunction(stmt) => {
|
Definition::Member(Member {
|
||||||
|
kind: MemberKind::Function | MemberKind::NestedFunction,
|
||||||
|
stmt,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
if is_overload(&checker.ctx, cast::decorator_list(stmt)) {
|
if is_overload(&checker.ctx, cast::decorator_list(stmt)) {
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
|
@ -167,7 +186,11 @@ pub fn not_missing(checker: &mut Checker, definition: &Definition, visibility: V
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DefinitionKind::Method(stmt) => {
|
Definition::Member(Member {
|
||||||
|
kind: MemberKind::Method,
|
||||||
|
stmt,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
if is_overload(&checker.ctx, cast::decorator_list(stmt))
|
if is_overload(&checker.ctx, cast::decorator_list(stmt))
|
||||||
|| is_override(&checker.ctx, cast::decorator_list(stmt))
|
|| is_override(&checker.ctx, cast::decorator_list(stmt))
|
||||||
{
|
{
|
||||||
|
|
|
@ -4,7 +4,7 @@ use ruff_python_ast::newlines::NewlineWithTrailingNewline;
|
||||||
use ruff_python_ast::str::{leading_quote, trailing_quote};
|
use ruff_python_ast::str::{leading_quote, trailing_quote};
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
use crate::docstrings::definition::Docstring;
|
use crate::docstrings::Docstring;
|
||||||
use crate::registry::AsRule;
|
use crate::registry::AsRule;
|
||||||
|
|
||||||
#[violation]
|
#[violation]
|
||||||
|
|
|
@ -12,11 +12,12 @@ use ruff_python_ast::helpers::identifier_range;
|
||||||
use ruff_python_ast::newlines::NewlineWithTrailingNewline;
|
use ruff_python_ast::newlines::NewlineWithTrailingNewline;
|
||||||
use ruff_python_ast::{cast, whitespace};
|
use ruff_python_ast::{cast, whitespace};
|
||||||
use ruff_python_semantic::analyze::visibility::is_staticmethod;
|
use ruff_python_semantic::analyze::visibility::is_staticmethod;
|
||||||
|
use ruff_python_semantic::definition::{Definition, Member, MemberKind};
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
use crate::docstrings::definition::{DefinitionKind, Docstring};
|
|
||||||
use crate::docstrings::sections::{SectionContext, SectionContexts, SectionKind};
|
use crate::docstrings::sections::{SectionContext, SectionContexts, SectionKind};
|
||||||
use crate::docstrings::styles::SectionStyle;
|
use crate::docstrings::styles::SectionStyle;
|
||||||
|
use crate::docstrings::Docstring;
|
||||||
use crate::registry::{AsRule, Rule};
|
use crate::registry::{AsRule, Rule};
|
||||||
use crate::rules::pydocstyle::settings::Convention;
|
use crate::rules::pydocstyle::settings::Convention;
|
||||||
|
|
||||||
|
@ -725,13 +726,14 @@ 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 (
|
let Definition::Member(Member {
|
||||||
DefinitionKind::Function(parent)
|
kind: MemberKind::Function | MemberKind::NestedFunction | MemberKind::Method,
|
||||||
| DefinitionKind::NestedFunction(parent)
|
stmt,
|
||||||
| DefinitionKind::Method(parent)
|
..
|
||||||
) = docstring.kind else {
|
}) = docstring.definition else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let (
|
let (
|
||||||
StmtKind::FunctionDef(ast::StmtFunctionDef {
|
StmtKind::FunctionDef(ast::StmtFunctionDef {
|
||||||
args: arguments, ..
|
args: arguments, ..
|
||||||
|
@ -739,7 +741,7 @@ fn missing_args(checker: &mut Checker, docstring: &Docstring, docstrings_args: &
|
||||||
| StmtKind::AsyncFunctionDef(ast::StmtAsyncFunctionDef {
|
| StmtKind::AsyncFunctionDef(ast::StmtAsyncFunctionDef {
|
||||||
args: arguments, ..
|
args: arguments, ..
|
||||||
})
|
})
|
||||||
) = &parent.node else {
|
) = &stmt.node else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -753,8 +755,8 @@ fn missing_args(checker: &mut Checker, docstring: &Docstring, docstrings_args: &
|
||||||
.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(
|
||||||
matches!(docstring.kind, DefinitionKind::Method(_))
|
docstring.definition.is_method()
|
||||||
&& !is_staticmethod(&checker.ctx, cast::decorator_list(parent)),
|
&& !is_staticmethod(&checker.ctx, cast::decorator_list(stmt)),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
@ -791,7 +793,7 @@ fn missing_args(checker: &mut Checker, docstring: &Docstring, docstrings_args: &
|
||||||
let names = missing_arg_names.into_iter().sorted().collect();
|
let names = missing_arg_names.into_iter().sorted().collect();
|
||||||
checker.diagnostics.push(Diagnostic::new(
|
checker.diagnostics.push(Diagnostic::new(
|
||||||
UndocumentedParam { names },
|
UndocumentedParam { names },
|
||||||
identifier_range(parent, checker.locator),
|
identifier_range(stmt, checker.locator),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ use ruff_diagnostics::{Diagnostic, Violation};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
use crate::docstrings::definition::Docstring;
|
use crate::docstrings::Docstring;
|
||||||
use crate::rules::pydocstyle::helpers::normalize_word;
|
use crate::rules::pydocstyle::helpers::normalize_word;
|
||||||
|
|
||||||
#[violation]
|
#[violation]
|
||||||
|
|
|
@ -2,7 +2,7 @@ use ruff_diagnostics::{Diagnostic, Violation};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
use crate::docstrings::definition::Docstring;
|
use crate::docstrings::Docstring;
|
||||||
|
|
||||||
#[violation]
|
#[violation]
|
||||||
pub struct TripleSingleQuotes;
|
pub struct TripleSingleQuotes;
|
||||||
|
|
|
@ -7,25 +7,12 @@ use ruff_python_ast::helpers::map_callable;
|
||||||
|
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy, is_macro::Is)]
|
||||||
pub enum Modifier {
|
|
||||||
Module,
|
|
||||||
Class,
|
|
||||||
Function,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
pub enum Visibility {
|
pub enum Visibility {
|
||||||
Public,
|
Public,
|
||||||
Private,
|
Private,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
pub struct VisibleScope {
|
|
||||||
pub modifier: Modifier,
|
|
||||||
pub visibility: Visibility,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns `true` if a function is a "static method".
|
/// Returns `true` if a function is a "static method".
|
||||||
pub fn is_staticmethod(ctx: &Context, decorator_list: &[Expr]) -> bool {
|
pub fn is_staticmethod(ctx: &Context, decorator_list: &[Expr]) -> bool {
|
||||||
decorator_list.iter().any(|expr| {
|
decorator_list.iter().any(|expr| {
|
||||||
|
@ -138,29 +125,45 @@ fn stem(path: &str) -> &str {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the `Visibility` of the Python file at `Path` based on its name.
|
/// A Python module can either be defined as a module path (i.e., the dot-separated path to the
|
||||||
pub fn module_visibility(module_path: Option<&[String]>, path: &Path) -> Visibility {
|
/// module) or, if the module can't be resolved, as a file path (i.e., the path to the file defining
|
||||||
if let Some(module_path) = module_path {
|
/// the module).
|
||||||
if module_path.iter().any(|m| is_private_module(m)) {
|
#[derive(Debug)]
|
||||||
return Visibility::Private;
|
pub enum ModuleSource<'a> {
|
||||||
}
|
/// A module path is a dot-separated path to the module.
|
||||||
} else {
|
Path(&'a [String]),
|
||||||
// When module_path is None, path is a script outside a package, so just
|
/// A file path is the path to the file defining the module, often a script outside of a
|
||||||
// check to see if the module name itself is private.
|
/// package.
|
||||||
// Ex) `_foo.py` (but not `__init__.py`)
|
File(&'a Path),
|
||||||
let mut components = path.iter().rev();
|
|
||||||
if let Some(filename) = components.next() {
|
|
||||||
let module_name = filename.to_string_lossy();
|
|
||||||
let module_name = stem(&module_name);
|
|
||||||
if is_private_module(module_name) {
|
|
||||||
return Visibility::Private;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Visibility::Public
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn function_visibility(stmt: &Stmt) -> Visibility {
|
impl ModuleSource<'_> {
|
||||||
|
/// Return the `Visibility` of the module.
|
||||||
|
pub(crate) fn to_visibility(&self) -> Visibility {
|
||||||
|
match self {
|
||||||
|
Self::Path(path) => {
|
||||||
|
if path.iter().any(|m| is_private_module(m)) {
|
||||||
|
return Visibility::Private;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self::File(path) => {
|
||||||
|
// Check to see if the filename itself indicates private visibility.
|
||||||
|
// Ex) `_foo.py` (but not `__init__.py`)
|
||||||
|
let mut components = path.iter().rev();
|
||||||
|
if let Some(filename) = components.next() {
|
||||||
|
let module_name = filename.to_string_lossy();
|
||||||
|
let module_name = stem(&module_name);
|
||||||
|
if is_private_module(module_name) {
|
||||||
|
return Visibility::Private;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Visibility::Public
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn function_visibility(stmt: &Stmt) -> Visibility {
|
||||||
match &stmt.node {
|
match &stmt.node {
|
||||||
StmtKind::FunctionDef(ast::StmtFunctionDef { name, .. })
|
StmtKind::FunctionDef(ast::StmtFunctionDef { name, .. })
|
||||||
| StmtKind::AsyncFunctionDef(ast::StmtAsyncFunctionDef { name, .. }) => {
|
| StmtKind::AsyncFunctionDef(ast::StmtAsyncFunctionDef { name, .. }) => {
|
||||||
|
@ -174,7 +177,7 @@ pub fn function_visibility(stmt: &Stmt) -> Visibility {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn method_visibility(stmt: &Stmt) -> Visibility {
|
pub(crate) fn method_visibility(stmt: &Stmt) -> Visibility {
|
||||||
match &stmt.node {
|
match &stmt.node {
|
||||||
StmtKind::FunctionDef(ast::StmtFunctionDef {
|
StmtKind::FunctionDef(ast::StmtFunctionDef {
|
||||||
name,
|
name,
|
||||||
|
@ -212,7 +215,7 @@ pub fn method_visibility(stmt: &Stmt) -> Visibility {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn class_visibility(stmt: &Stmt) -> Visibility {
|
pub(crate) fn class_visibility(stmt: &Stmt) -> Visibility {
|
||||||
match &stmt.node {
|
match &stmt.node {
|
||||||
StmtKind::ClassDef(ast::StmtClassDef { name, .. }) => {
|
StmtKind::ClassDef(ast::StmtClassDef { name, .. }) => {
|
||||||
if name.starts_with('_') {
|
if name.starts_with('_') {
|
||||||
|
|
|
@ -11,11 +11,11 @@ use ruff_python_ast::typing::AnnotationKind;
|
||||||
use ruff_python_stdlib::path::is_python_stub_file;
|
use ruff_python_stdlib::path::is_python_stub_file;
|
||||||
use ruff_python_stdlib::typing::TYPING_EXTENSIONS;
|
use ruff_python_stdlib::typing::TYPING_EXTENSIONS;
|
||||||
|
|
||||||
use crate::analyze::visibility::{module_visibility, Modifier, VisibleScope};
|
|
||||||
use crate::binding::{
|
use crate::binding::{
|
||||||
Binding, BindingId, BindingKind, Bindings, Exceptions, ExecutionContext, FromImportation,
|
Binding, BindingId, BindingKind, Bindings, Exceptions, ExecutionContext, FromImportation,
|
||||||
Importation, SubmoduleImportation,
|
Importation, SubmoduleImportation,
|
||||||
};
|
};
|
||||||
|
use crate::definition::{Definition, DefinitionId, Definitions, Member, Module};
|
||||||
use crate::node::{NodeId, Nodes};
|
use crate::node::{NodeId, Nodes};
|
||||||
use crate::scope::{Scope, ScopeId, ScopeKind, Scopes};
|
use crate::scope::{Scope, ScopeId, ScopeKind, Scopes};
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@ use crate::scope::{Scope, ScopeId, ScopeKind, Scopes};
|
||||||
pub struct Snapshot {
|
pub struct Snapshot {
|
||||||
scope_id: ScopeId,
|
scope_id: ScopeId,
|
||||||
stmt_id: Option<NodeId>,
|
stmt_id: Option<NodeId>,
|
||||||
|
definition_id: DefinitionId,
|
||||||
in_annotation: bool,
|
in_annotation: bool,
|
||||||
in_type_checking_block: bool,
|
in_type_checking_block: bool,
|
||||||
}
|
}
|
||||||
|
@ -31,7 +32,7 @@ pub struct Snapshot {
|
||||||
#[allow(clippy::struct_excessive_bools)]
|
#[allow(clippy::struct_excessive_bools)]
|
||||||
pub struct Context<'a> {
|
pub struct Context<'a> {
|
||||||
pub typing_modules: &'a [String],
|
pub typing_modules: &'a [String],
|
||||||
pub module_path: Option<Vec<String>>,
|
pub module_path: Option<&'a [String]>,
|
||||||
// Stack of all visited statements, along with the identifier of the current statement.
|
// Stack of all visited statements, along with the identifier of the current statement.
|
||||||
pub stmts: Nodes<'a>,
|
pub stmts: Nodes<'a>,
|
||||||
pub stmt_id: Option<NodeId>,
|
pub stmt_id: Option<NodeId>,
|
||||||
|
@ -41,6 +42,10 @@ pub struct Context<'a> {
|
||||||
pub scopes: Scopes<'a>,
|
pub scopes: Scopes<'a>,
|
||||||
pub scope_id: ScopeId,
|
pub scope_id: ScopeId,
|
||||||
pub dead_scopes: Vec<ScopeId>,
|
pub dead_scopes: Vec<ScopeId>,
|
||||||
|
// Stack of all definitions created in any scope, at any point in execution, along with the
|
||||||
|
// identifier of the current definition.
|
||||||
|
pub definitions: Definitions<'a>,
|
||||||
|
pub definition_id: DefinitionId,
|
||||||
// A stack of all bindings created in any scope, at any point in execution.
|
// A stack of all bindings created in any scope, at any point in execution.
|
||||||
pub bindings: Bindings<'a>,
|
pub bindings: Bindings<'a>,
|
||||||
// Map from binding index to indexes of bindings that shadow it in other scopes.
|
// Map from binding index to indexes of bindings that shadow it in other scopes.
|
||||||
|
@ -49,7 +54,6 @@ pub struct Context<'a> {
|
||||||
pub body: &'a [Stmt],
|
pub body: &'a [Stmt],
|
||||||
pub body_index: usize,
|
pub body_index: usize,
|
||||||
// Internal, derivative state.
|
// Internal, derivative state.
|
||||||
pub visible_scope: VisibleScope,
|
|
||||||
pub in_annotation: bool,
|
pub in_annotation: bool,
|
||||||
pub in_type_definition: bool,
|
pub in_type_definition: bool,
|
||||||
pub in_deferred_string_type_definition: Option<AnnotationKind>,
|
pub in_deferred_string_type_definition: Option<AnnotationKind>,
|
||||||
|
@ -67,29 +71,22 @@ pub struct Context<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Context<'a> {
|
impl<'a> Context<'a> {
|
||||||
pub fn new(
|
pub fn new(typing_modules: &'a [String], path: &'a Path, module: Module<'a>) -> Self {
|
||||||
typing_modules: &'a [String],
|
|
||||||
path: &'a Path,
|
|
||||||
module_path: Option<Vec<String>>,
|
|
||||||
) -> Self {
|
|
||||||
let visibility = module_visibility(module_path.as_deref(), path);
|
|
||||||
Self {
|
Self {
|
||||||
typing_modules,
|
typing_modules,
|
||||||
module_path,
|
module_path: module.path(),
|
||||||
stmts: Nodes::default(),
|
stmts: Nodes::default(),
|
||||||
stmt_id: None,
|
stmt_id: None,
|
||||||
|
exprs: Vec::default(),
|
||||||
scopes: Scopes::default(),
|
scopes: Scopes::default(),
|
||||||
scope_id: ScopeId::global(),
|
scope_id: ScopeId::global(),
|
||||||
dead_scopes: Vec::default(),
|
dead_scopes: Vec::default(),
|
||||||
|
definitions: Definitions::for_module(module),
|
||||||
|
definition_id: DefinitionId::module(),
|
||||||
bindings: Bindings::default(),
|
bindings: Bindings::default(),
|
||||||
shadowed_bindings: IntMap::default(),
|
shadowed_bindings: IntMap::default(),
|
||||||
exprs: Vec::default(),
|
|
||||||
body: &[],
|
body: &[],
|
||||||
body_index: 0,
|
body_index: 0,
|
||||||
visible_scope: VisibleScope {
|
|
||||||
modifier: Modifier::Module,
|
|
||||||
visibility,
|
|
||||||
},
|
|
||||||
in_annotation: false,
|
in_annotation: false,
|
||||||
in_type_definition: false,
|
in_type_definition: false,
|
||||||
in_deferred_string_type_definition: None,
|
in_deferred_string_type_definition: None,
|
||||||
|
@ -347,6 +344,19 @@ impl<'a> Context<'a> {
|
||||||
.expect("Attempted to pop without scope");
|
.expect("Attempted to pop without scope");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Push a [`Member`] onto the stack.
|
||||||
|
pub fn push_definition(&mut self, definition: Member<'a>) {
|
||||||
|
self.definition_id = self.definitions.push_member(definition);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pop the current [`Member`] off the stack.
|
||||||
|
pub fn pop_definition(&mut self) {
|
||||||
|
let Definition::Member(member) = &self.definitions[self.definition_id] else {
|
||||||
|
panic!("Attempted to pop without member definition");
|
||||||
|
};
|
||||||
|
self.definition_id = member.parent;
|
||||||
|
}
|
||||||
|
|
||||||
/// Return the current `Stmt`.
|
/// Return the current `Stmt`.
|
||||||
pub fn stmt(&self) -> &'a Stmt {
|
pub fn stmt(&self) -> &'a Stmt {
|
||||||
let node_id = self.stmt_id.expect("No current statement");
|
let node_id = self.stmt_id.expect("No current statement");
|
||||||
|
@ -450,6 +460,7 @@ impl<'a> Context<'a> {
|
||||||
Snapshot {
|
Snapshot {
|
||||||
scope_id: self.scope_id,
|
scope_id: self.scope_id,
|
||||||
stmt_id: self.stmt_id,
|
stmt_id: self.stmt_id,
|
||||||
|
definition_id: self.definition_id,
|
||||||
in_annotation: self.in_annotation,
|
in_annotation: self.in_annotation,
|
||||||
in_type_checking_block: self.in_type_checking_block,
|
in_type_checking_block: self.in_type_checking_block,
|
||||||
}
|
}
|
||||||
|
@ -460,11 +471,13 @@ impl<'a> Context<'a> {
|
||||||
let Snapshot {
|
let Snapshot {
|
||||||
scope_id,
|
scope_id,
|
||||||
stmt_id,
|
stmt_id,
|
||||||
|
definition_id,
|
||||||
in_annotation,
|
in_annotation,
|
||||||
in_type_checking_block,
|
in_type_checking_block,
|
||||||
} = snapshot;
|
} = snapshot;
|
||||||
self.scope_id = scope_id;
|
self.scope_id = scope_id;
|
||||||
self.stmt_id = stmt_id;
|
self.stmt_id = stmt_id;
|
||||||
|
self.definition_id = definition_id;
|
||||||
self.in_annotation = in_annotation;
|
self.in_annotation = in_annotation;
|
||||||
self.in_type_checking_block = in_type_checking_block;
|
self.in_type_checking_block = in_type_checking_block;
|
||||||
}
|
}
|
||||||
|
|
225
crates/ruff_python_semantic/src/definition.rs
Normal file
225
crates/ruff_python_semantic/src/definition.rs
Normal file
|
@ -0,0 +1,225 @@
|
||||||
|
//! Definitions within a Python program. In this context, a "definition" is any named entity that
|
||||||
|
//! can be documented, such as a module, class, or function.
|
||||||
|
|
||||||
|
use std::fmt::Debug;
|
||||||
|
use std::iter::FusedIterator;
|
||||||
|
use std::num::TryFromIntError;
|
||||||
|
use std::ops::{Deref, Index};
|
||||||
|
|
||||||
|
use rustpython_parser::ast::Stmt;
|
||||||
|
|
||||||
|
use crate::analyze::visibility::{
|
||||||
|
class_visibility, function_visibility, method_visibility, ModuleSource, Visibility,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Id uniquely identifying a definition in a program.
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
|
||||||
|
pub struct DefinitionId(u32);
|
||||||
|
|
||||||
|
impl DefinitionId {
|
||||||
|
/// Returns the ID for the module definition.
|
||||||
|
#[inline]
|
||||||
|
pub const fn module() -> Self {
|
||||||
|
DefinitionId(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<usize> for DefinitionId {
|
||||||
|
type Error = TryFromIntError;
|
||||||
|
|
||||||
|
fn try_from(value: usize) -> Result<Self, Self::Error> {
|
||||||
|
Ok(Self(u32::try_from(value)?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<DefinitionId> for usize {
|
||||||
|
fn from(value: DefinitionId) -> Self {
|
||||||
|
value.0 as usize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ModuleKind {
|
||||||
|
/// A Python file that represents a module within a package.
|
||||||
|
Module,
|
||||||
|
/// A Python file that represents the root of a package (i.e., an `__init__.py` file).
|
||||||
|
Package,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A Python module.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Module<'a> {
|
||||||
|
pub kind: ModuleKind,
|
||||||
|
pub source: ModuleSource<'a>,
|
||||||
|
pub python_ast: &'a [Stmt],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Module<'a> {
|
||||||
|
pub fn path(&self) -> Option<&'a [String]> {
|
||||||
|
if let ModuleSource::Path(path) = self.source {
|
||||||
|
Some(path)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
pub enum MemberKind {
|
||||||
|
/// A class definition within a program.
|
||||||
|
Class,
|
||||||
|
/// A nested class definition within a program.
|
||||||
|
NestedClass,
|
||||||
|
/// A function definition within a program.
|
||||||
|
Function,
|
||||||
|
/// A nested function definition within a program.
|
||||||
|
NestedFunction,
|
||||||
|
/// A method definition within a program.
|
||||||
|
Method,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A member of a Python module.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Member<'a> {
|
||||||
|
pub kind: MemberKind,
|
||||||
|
pub parent: DefinitionId,
|
||||||
|
pub stmt: &'a Stmt,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A definition within a Python program.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Definition<'a> {
|
||||||
|
Module(Module<'a>),
|
||||||
|
Member(Member<'a>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Definition<'_> {
|
||||||
|
/// Returns `true` if the [`Definition`] is a method definition.
|
||||||
|
pub const fn is_method(&self) -> bool {
|
||||||
|
matches!(
|
||||||
|
self,
|
||||||
|
Definition::Member(Member {
|
||||||
|
kind: MemberKind::Method,
|
||||||
|
..
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The definitions within a Python program indexed by [`DefinitionId`].
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct Definitions<'a>(Vec<Definition<'a>>);
|
||||||
|
|
||||||
|
impl<'a> Definitions<'a> {
|
||||||
|
pub fn for_module(definition: Module<'a>) -> Self {
|
||||||
|
Self(vec![Definition::Module(definition)])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pushes a new member definition and returns its unique id.
|
||||||
|
///
|
||||||
|
/// Members are assumed to be pushed in traversal order, such that parents are pushed before
|
||||||
|
/// their children.
|
||||||
|
pub fn push_member(&mut self, member: Member<'a>) -> DefinitionId {
|
||||||
|
let next_id = DefinitionId::try_from(self.0.len()).unwrap();
|
||||||
|
self.0.push(Definition::Member(member));
|
||||||
|
next_id
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterate over all definitions in a program, with their visibilities.
|
||||||
|
pub fn iter(&self) -> DefinitionsIter {
|
||||||
|
DefinitionsIter {
|
||||||
|
inner: self.0.iter(),
|
||||||
|
definitions: Vec::with_capacity(self.0.len()),
|
||||||
|
visibilities: Vec::with_capacity(self.0.len()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Index<DefinitionId> for Definitions<'a> {
|
||||||
|
type Output = Definition<'a>;
|
||||||
|
|
||||||
|
fn index(&self, index: DefinitionId) -> &Self::Output {
|
||||||
|
&self.0[usize::from(index)]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Deref for Definitions<'a> {
|
||||||
|
type Target = [Definition<'a>];
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DefinitionsIter<'a> {
|
||||||
|
inner: std::slice::Iter<'a, Definition<'a>>,
|
||||||
|
definitions: Vec<&'a Definition<'a>>,
|
||||||
|
visibilities: Vec<Visibility>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Iterator for DefinitionsIter<'a> {
|
||||||
|
type Item = (&'a Definition<'a>, Visibility);
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
let definition = self.inner.next()?;
|
||||||
|
|
||||||
|
// Determine the visibility of the next definition, taking into account its parent's
|
||||||
|
// visibility.
|
||||||
|
let visibility = {
|
||||||
|
match definition {
|
||||||
|
Definition::Module(module) => module.source.to_visibility(),
|
||||||
|
Definition::Member(member) => match member.kind {
|
||||||
|
MemberKind::Class => {
|
||||||
|
if self.visibilities[usize::from(member.parent)].is_private() {
|
||||||
|
Visibility::Private
|
||||||
|
} else {
|
||||||
|
class_visibility(member.stmt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MemberKind::NestedClass => {
|
||||||
|
if self.visibilities[usize::from(member.parent)].is_private()
|
||||||
|
|| matches!(
|
||||||
|
self.definitions[usize::from(member.parent)],
|
||||||
|
Definition::Member(Member {
|
||||||
|
kind: MemberKind::Function
|
||||||
|
| MemberKind::NestedFunction
|
||||||
|
| MemberKind::Method,
|
||||||
|
..
|
||||||
|
})
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Visibility::Private
|
||||||
|
} else {
|
||||||
|
class_visibility(member.stmt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MemberKind::Function => {
|
||||||
|
if self.visibilities[usize::from(member.parent)].is_private() {
|
||||||
|
Visibility::Private
|
||||||
|
} else {
|
||||||
|
function_visibility(member.stmt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MemberKind::NestedFunction => Visibility::Private,
|
||||||
|
MemberKind::Method => {
|
||||||
|
if self.visibilities[usize::from(member.parent)].is_private() {
|
||||||
|
Visibility::Private
|
||||||
|
} else {
|
||||||
|
method_visibility(member.stmt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
self.definitions.push(definition);
|
||||||
|
self.visibilities.push(visibility);
|
||||||
|
|
||||||
|
Some((definition, visibility))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||||
|
self.inner.size_hint()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FusedIterator for DefinitionsIter<'_> {}
|
||||||
|
impl ExactSizeIterator for DefinitionsIter<'_> {}
|
|
@ -1,5 +1,6 @@
|
||||||
pub mod analyze;
|
pub mod analyze;
|
||||||
pub mod binding;
|
pub mod binding;
|
||||||
pub mod context;
|
pub mod context;
|
||||||
|
pub mod definition;
|
||||||
pub mod node;
|
pub mod node;
|
||||||
pub mod scope;
|
pub mod scope;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue