use ruff_python_ast::{self as ast, Decorator}; 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(map_callable(&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(map_callable(&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(map_callable(&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( decorator_list: &[Decorator], extra_properties: &[QualifiedName], semantic: &SemanticModel, ) -> bool { decorator_list.iter().any(|decorator| { semantic .resolve_qualified_name(map_callable(&decorator.expression)) .is_some_and(|qualified_name| { matches!( qualified_name.segments(), ["" | "builtins", "property"] | ["functools", "cached_property"] ) || extra_properties .iter() .any(|extra_property| extra_property.segments() == qualified_name.segments()) }) }) } /// 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(map_callable(&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 } }