use ruff_python_ast::{self as ast, Decorator, Expr}; use ruff_python_ast::helpers::map_callable; use ruff_python_ast::name::{QualifiedName, UnqualifiedName}; use crate::model::SemanticModel; use crate::{Module, ModuleSource}; #[derive(Debug, Clone, Copy, is_macro::Is)] pub enum Visibility { Public, Private, } /// Returns `true` if a function is a "static method". pub fn is_staticmethod(decorator_list: &[Decorator], semantic: &SemanticModel) -> bool { decorator_list .iter() .any(|decorator| semantic.match_builtin_expr(&decorator.expression, "staticmethod")) } /// Returns `true` if a function is a "class method". pub fn is_classmethod(decorator_list: &[Decorator], semantic: &SemanticModel) -> bool { decorator_list .iter() .any(|decorator| semantic.match_builtin_expr(&decorator.expression, "classmethod")) } /// Returns `true` if a function definition is an `@overload`. pub fn is_overload(decorator_list: &[Decorator], semantic: &SemanticModel) -> bool { decorator_list .iter() .any(|decorator| semantic.match_typing_expr(&decorator.expression, "overload")) } /// Returns `true` if a function definition is an `@override` (PEP 698). pub fn is_override(decorator_list: &[Decorator], semantic: &SemanticModel) -> bool { decorator_list .iter() .any(|decorator| semantic.match_typing_expr(&decorator.expression, "override")) } /// Returns `true` if a function definition is an abstract method based on its decorators. pub fn is_abstract(decorator_list: &[Decorator], semantic: &SemanticModel) -> bool { decorator_list.iter().any(|decorator| { semantic .resolve_qualified_name(&decorator.expression) .is_some_and(|qualified_name| { matches!( qualified_name.segments(), [ "abc", "abstractmethod" | "abstractclassmethod" | "abstractstaticmethod" | "abstractproperty" ] ) }) }) } /// Returns `true` if a function definition is a `@property`. /// `extra_properties` can be used to check additional non-standard /// `@property`-like decorators. pub fn is_property<'a, P, I>( decorator_list: &[Decorator], extra_properties: P, semantic: &SemanticModel, ) -> bool where P: IntoIterator, I: Iterator> + Clone, { let extra_properties = extra_properties.into_iter(); decorator_list.iter().any(|decorator| { semantic .resolve_qualified_name(map_callable(&decorator.expression)) .is_some_and(|qualified_name| { matches!( qualified_name.segments(), ["" | "builtins" | "enum", "property"] | ["functools", "cached_property"] | ["abc", "abstractproperty"] | ["types", "DynamicClassAttribute"] ) || extra_properties .clone() .any(|extra_property| extra_property == qualified_name) }) }) } /// Returns `true` if a function definition is an `attrs`-like validator based on its decorators. pub fn is_validator(decorator_list: &[Decorator], semantic: &SemanticModel) -> bool { decorator_list.iter().any(|decorator| { let Expr::Attribute(ast::ExprAttribute { value, attr, .. }) = &decorator.expression else { return false; }; if attr.as_str() != "validator" { return false; } let Expr::Name(value) = value.as_ref() else { return false; }; semantic .resolve_name(value) .is_some_and(|id| semantic.binding(id).kind.is_assignment()) }) } /// Returns `true` if a class is an `final`. pub fn is_final(decorator_list: &[Decorator], semantic: &SemanticModel) -> bool { decorator_list .iter() .any(|decorator| semantic.match_typing_expr(&decorator.expression, "final")) } /// Returns `true` if a function is a "magic method". pub fn is_magic(name: &str) -> bool { name.starts_with("__") && name.ends_with("__") } /// Returns `true` if a function is an `__init__`. pub fn is_init(name: &str) -> bool { name == "__init__" } /// Returns `true` if a function is a `__new__`. pub fn is_new(name: &str) -> bool { name == "__new__" } /// Returns `true` if a function is a `__call__`. pub fn is_call(name: &str) -> bool { name == "__call__" } /// Returns `true` if a function is a test one. pub fn is_test(name: &str) -> bool { name == "runTest" || name.starts_with("test") } /// Returns `true` if a module name indicates public visibility. fn is_public_module(module_name: &str) -> bool { !module_name.starts_with('_') || is_magic(module_name) } /// Returns `true` if a module name indicates private visibility. fn is_private_module(module_name: &str) -> bool { !is_public_module(module_name) } /// Return the stem of a module name (everything preceding the last dot). fn stem(path: &str) -> &str { if let Some(index) = path.rfind('.') { &path[..index] } else { path } } /// Infer the [`Visibility`] of a module from its path. pub(crate) fn module_visibility(module: &Module) -> Visibility { match &module.source { ModuleSource::Path(path) => { if path.iter().any(|m| is_private_module(m)) { return Visibility::Private; } } ModuleSource::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 } /// Infer the [`Visibility`] of a function from its name. pub(crate) fn function_visibility(function: &ast::StmtFunctionDef) -> Visibility { if function.name.starts_with('_') { Visibility::Private } else { Visibility::Public } } /// Infer the [`Visibility`] of a method from its name and decorators. pub fn method_visibility(function: &ast::StmtFunctionDef) -> Visibility { // Is this a setter or deleter? if function.decorator_list.iter().any(|decorator| { UnqualifiedName::from_expr(&decorator.expression).is_some_and(|name| { name.segments() == [function.name.as_str(), "setter"] || name.segments() == [function.name.as_str(), "deleter"] }) }) { return Visibility::Private; } // Is the method non-private? if !function.name.starts_with('_') { return Visibility::Public; } // Is this a magic method? if is_magic(&function.name) { return Visibility::Public; } Visibility::Private } /// Infer the [`Visibility`] of a class from its name. pub(crate) fn class_visibility(class: &ast::StmtClassDef) -> Visibility { if class.name.starts_with('_') { Visibility::Private } else { Visibility::Public } }