Delay computation of Definition visibility (#4339)

This commit is contained in:
Charlie Marsh 2023-05-11 13:14:29 -04:00 committed by GitHub
parent ffcf0618c7
commit 72e0ffc1ac
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 1064 additions and 833 deletions

View file

@ -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>,

View file

@ -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);

View file

@ -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,
},
},
}
}

View file

@ -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,
}, },
}, },
} }

View file

@ -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()
}
}

View file

@ -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)]

View file

@ -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

View file

@ -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
} }
} }

View file

@ -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()

View file

@ -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;

View file

@ -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]

View file

@ -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()

View file

@ -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

View file

@ -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;
} }

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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]

View file

@ -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;
} }

View file

@ -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]

View file

@ -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;
}; };

View file

@ -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;

View file

@ -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,
) )
{ {

View file

@ -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]

View file

@ -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))
{ {

View file

@ -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]

View file

@ -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),
)); ));
} }
} }

View file

@ -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]

View file

@ -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;

View file

@ -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('_') {

View file

@ -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;
} }

View 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<'_> {}

View file

@ -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;