use ruff_db::parsed::parsed_module;
use ruff_python_ast::{
self as ast,
name::Name,
visitor::{Visitor, walk_expr, walk_pattern, walk_stmt},
};
use rustc_hash::FxHashMap;
use ty_module_resolver::{ImportingFile, resolve_module_for_import_from};
use crate::{Db, ProgramFile};
#[salsa::tracked(
returns(deref),
cycle_initial=|_, _, _| Box::default(),
heap_size=ruff_memory_usage::heap_size)
]
pub(super) fn exported_names(db: &dyn Db, file: ProgramFile<'_>) -> Box<[Name]> {
let module = parsed_module(db, file.python_file(db)).load(db);
let mut finder = ExportFinder::new(db, file);
finder.visit_body(module.suite());
let mut exports = finder.resolve_exports();
exports.sort_unstable();
exports.into()
}
struct ExportFinder<'db> {
db: &'db dyn Db,
program_file: ProgramFile<'db>,
visiting_stub_file: bool,
exports: FxHashMap<&'db Name, PossibleExportKind>,
dunder_all: DunderAll,
}
impl<'db> ExportFinder<'db> {
fn new(db: &'db dyn Db, file: ProgramFile<'db>) -> Self {
Self {
db,
program_file: file,
visiting_stub_file: file.file(db).is_stub(db),
exports: FxHashMap::default(),
dunder_all: DunderAll::NotPresent,
}
}
fn possibly_add_export(&mut self, export: &'db Name, kind: PossibleExportKind) {
self.exports.insert(export, kind);
if export == "__all__" {
self.dunder_all = DunderAll::Present;
}
}
fn resolve_exports(self) -> Vec<Name> {
match self.dunder_all {
DunderAll::NotPresent => self
.exports
.into_iter()
.filter_map(|(name, kind)| {
if kind == PossibleExportKind::StubImportWithoutRedundantAlias {
return None;
}
if name.starts_with('_') {
return None;
}
Some(name.clone())
})
.collect(),
DunderAll::Present => self.exports.into_keys().cloned().collect(),
}
}
}
impl<'db> Visitor<'db> for ExportFinder<'db> {
fn visit_alias(&mut self, alias: &'db ast::Alias) {
let ast::Alias {
name,
asname,
range: _,
node_index: _,
} = alias;
let name = &name.id;
let asname = asname.as_ref().map(|asname| &asname.id);
let kind = if self.visiting_stub_file && asname.is_none_or(|asname| asname != name) {
PossibleExportKind::StubImportWithoutRedundantAlias
} else {
PossibleExportKind::Normal
};
self.possibly_add_export(asname.unwrap_or(name), kind);
}
fn visit_pattern(&mut self, pattern: &'db ast::Pattern) {
match pattern {
ast::Pattern::MatchAs(ast::PatternMatchAs {
pattern,
name,
range: _,
node_index: _,
}) => {
if let Some(pattern) = pattern {
self.visit_pattern(pattern);
}
if let Some(name) = name {
if name != "_" {
self.possibly_add_export(&name.id, PossibleExportKind::Normal);
}
}
}
ast::Pattern::MatchMapping(ast::PatternMatchMapping {
patterns,
rest,
keys: _,
range: _,
node_index: _,
}) => {
for pattern in patterns {
self.visit_pattern(pattern);
}
if let Some(rest) = rest {
self.possibly_add_export(&rest.id, PossibleExportKind::Normal);
}
}
ast::Pattern::MatchStar(ast::PatternMatchStar {
name,
range: _,
node_index: _,
}) => {
if let Some(name) = name {
self.possibly_add_export(&name.id, PossibleExportKind::Normal);
}
}
ast::Pattern::MatchSequence(_)
| ast::Pattern::MatchOr(_)
| ast::Pattern::MatchClass(_) => {
walk_pattern(self, pattern);
}
ast::Pattern::MatchSingleton(_) | ast::Pattern::MatchValue(_) => {}
}
}
fn visit_stmt(&mut self, stmt: &'db ast::Stmt) {
match stmt {
ast::Stmt::ClassDef(ast::StmtClassDef {
name,
decorator_list,
arguments,
type_params: _, body: _, range: _,
node_index: _,
}) => {
self.possibly_add_export(&name.id, PossibleExportKind::Normal);
for decorator in decorator_list {
self.visit_decorator(decorator);
}
if let Some(arguments) = arguments {
self.visit_arguments(arguments);
}
}
ast::Stmt::FunctionDef(ast::StmtFunctionDef {
name,
decorator_list,
parameters,
returns,
type_params: _, body: _, range: _,
node_index: _,
is_async: _,
}) => {
self.possibly_add_export(&name.id, PossibleExportKind::Normal);
for decorator in decorator_list {
self.visit_decorator(decorator);
}
self.visit_parameters(parameters);
if let Some(returns) = returns {
self.visit_expr(returns);
}
}
ast::Stmt::AnnAssign(ast::StmtAnnAssign {
target,
value,
annotation,
simple: _,
range: _,
node_index: _,
}) => {
if value.is_some() || self.visiting_stub_file {
self.visit_expr(target);
}
self.visit_expr(annotation);
if let Some(value) = value {
self.visit_expr(value);
}
}
ast::Stmt::TypeAlias(ast::StmtTypeAlias {
name,
type_params: _,
value: _,
range: _,
node_index: _,
}) => {
self.visit_expr(name);
}
ast::Stmt::ImportFrom(node) => {
let mut found_star = false;
for name in &node.names {
if &name.name.id == "*" {
if !found_star {
found_star = true;
let db = self.db;
let program_file = self.program_file;
let file = program_file.file(db);
let resolver_environment = program_file.resolver_environment(db);
for export in resolve_module_for_import_from(
db,
ImportingFile::File(file, resolver_environment),
node,
)
.iter()
.flat_map(|module| {
module
.file(db)
.map(|file| {
exported_names(
db,
ProgramFile::new(db, file, program_file.program(db)),
)
})
.unwrap_or_default()
}) {
self.possibly_add_export(export, PossibleExportKind::Normal);
}
}
} else {
self.visit_alias(name);
}
}
}
ast::Stmt::Import(_)
| ast::Stmt::AugAssign(_)
| ast::Stmt::While(_)
| ast::Stmt::If(_)
| ast::Stmt::With(_)
| ast::Stmt::Assert(_)
| ast::Stmt::Try(_)
| ast::Stmt::Expr(_)
| ast::Stmt::For(_)
| ast::Stmt::Assign(_)
| ast::Stmt::Match(_) => walk_stmt(self, stmt),
ast::Stmt::Global(_)
| ast::Stmt::Raise(_)
| ast::Stmt::Return(_)
| ast::Stmt::Break(_)
| ast::Stmt::Continue(_)
| ast::Stmt::IpyEscapeCommand(_)
| ast::Stmt::Delete(_)
| ast::Stmt::Nonlocal(_)
| ast::Stmt::Pass(_) => {}
}
}
fn visit_expr(&mut self, expr: &'db ast::Expr) {
match expr {
ast::Expr::Name(ast::ExprName {
id,
ctx,
range: _,
node_index: _,
}) => {
if ctx.is_store() {
self.possibly_add_export(id, PossibleExportKind::Normal);
}
}
ast::Expr::Lambda(_)
| ast::Expr::BooleanLiteral(_)
| ast::Expr::NoneLiteral(_)
| ast::Expr::NumberLiteral(_)
| ast::Expr::BytesLiteral(_)
| ast::Expr::EllipsisLiteral(_)
| ast::Expr::StringLiteral(_) => {}
ast::Expr::SetComp(_)
| ast::Expr::ListComp(_)
| ast::Expr::Generator(_)
| ast::Expr::DictComp(_) => {
let mut walrus_finder = WalrusFinder {
export_finder: self,
};
walk_expr(&mut walrus_finder, expr);
}
ast::Expr::BoolOp(_)
| ast::Expr::Named(_)
| ast::Expr::BinOp(_)
| ast::Expr::UnaryOp(_)
| ast::Expr::If(_)
| ast::Expr::Attribute(_)
| ast::Expr::Subscript(_)
| ast::Expr::Starred(_)
| ast::Expr::Call(_)
| ast::Expr::Compare(_)
| ast::Expr::Yield(_)
| ast::Expr::YieldFrom(_)
| ast::Expr::FString(_)
| ast::Expr::TString(_)
| ast::Expr::Tuple(_)
| ast::Expr::List(_)
| ast::Expr::Slice(_)
| ast::Expr::IpyEscapeCommand(_)
| ast::Expr::Dict(_)
| ast::Expr::Set(_)
| ast::Expr::Await(_) => walk_expr(self, expr),
}
}
}
struct WalrusFinder<'a, 'db> {
export_finder: &'a mut ExportFinder<'db>,
}
impl<'db> Visitor<'db> for WalrusFinder<'_, 'db> {
fn visit_expr(&mut self, expr: &'db ast::Expr) {
match expr {
ast::Expr::Lambda(_)
| ast::Expr::BooleanLiteral(_)
| ast::Expr::NoneLiteral(_)
| ast::Expr::NumberLiteral(_)
| ast::Expr::BytesLiteral(_)
| ast::Expr::EllipsisLiteral(_)
| ast::Expr::StringLiteral(_)
| ast::Expr::Name(_) => {}
ast::Expr::Named(ast::ExprNamed {
target,
value: _,
range: _,
node_index: _,
}) => {
if let ast::Expr::Name(ast::ExprName {
id,
ctx: ast::ExprContext::Store,
range: _,
node_index: _,
}) = &**target
{
self.export_finder
.possibly_add_export(id, PossibleExportKind::Normal);
}
}
ast::Expr::DictComp(_)
| ast::Expr::SetComp(_)
| ast::Expr::ListComp(_)
| ast::Expr::Generator(_)
| ast::Expr::BoolOp(_)
| ast::Expr::BinOp(_)
| ast::Expr::UnaryOp(_)
| ast::Expr::If(_)
| ast::Expr::Attribute(_)
| ast::Expr::Subscript(_)
| ast::Expr::Starred(_)
| ast::Expr::Call(_)
| ast::Expr::Compare(_)
| ast::Expr::Yield(_)
| ast::Expr::YieldFrom(_)
| ast::Expr::FString(_)
| ast::Expr::TString(_)
| ast::Expr::Tuple(_)
| ast::Expr::List(_)
| ast::Expr::Slice(_)
| ast::Expr::IpyEscapeCommand(_)
| ast::Expr::Dict(_)
| ast::Expr::Set(_)
| ast::Expr::Await(_) => walk_expr(self, expr),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum PossibleExportKind {
Normal,
StubImportWithoutRedundantAlias,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum DunderAll {
NotPresent,
Present,
}