## Summary As discussed in Discord, and similar to oxc, we're going to refer to this as `.semantic()` everywhere. While I was auditing usages of `model: &SemanticModel`, I also changed as many function signatures as I could find to consistently take the model as the _last_ argument, rather than the first.
69 lines
2.2 KiB
Rust
69 lines
2.2 KiB
Rust
use rustpython_parser::ast::{self, Constant, Expr, Keyword};
|
|
|
|
use ruff_python_ast::call_path::collect_call_path;
|
|
use ruff_python_ast::helpers::find_keyword;
|
|
|
|
use crate::model::SemanticModel;
|
|
|
|
/// Return `true` if the given `Expr` is a potential logging call. Matches
|
|
/// `logging.error`, `logger.error`, `self.logger.error`, etc., but not
|
|
/// arbitrary `foo.error` calls.
|
|
///
|
|
/// It even matches direct `logging.error` calls even if the `logging` module
|
|
/// is aliased. Example:
|
|
/// ```python
|
|
/// import logging as bar
|
|
///
|
|
/// # This is detected to be a logger candidate
|
|
/// bar.error()
|
|
/// ```
|
|
pub fn is_logger_candidate(func: &Expr, semantic: &SemanticModel) -> bool {
|
|
if let Expr::Attribute(ast::ExprAttribute { value, .. }) = func {
|
|
let Some(call_path) = (if let Some(call_path) = semantic.resolve_call_path(value) {
|
|
if call_path.first().map_or(false, |module| *module == "logging") || call_path.as_slice() == ["flask", "current_app", "logger"] {
|
|
Some(call_path)
|
|
} else {
|
|
None
|
|
}
|
|
} else {
|
|
collect_call_path(value)
|
|
}) else {
|
|
return false;
|
|
};
|
|
if let Some(tail) = call_path.last() {
|
|
if tail.starts_with("log") || tail.ends_with("logger") || tail.ends_with("logging") {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
false
|
|
}
|
|
|
|
/// If the keywords to a logging call contain `exc_info=True` or `exc_info=sys.exc_info()`,
|
|
/// return the `Keyword` for `exc_info`.
|
|
pub fn exc_info<'a>(keywords: &'a [Keyword], semantic: &SemanticModel) -> Option<&'a Keyword> {
|
|
let exc_info = find_keyword(keywords, "exc_info")?;
|
|
|
|
// Ex) `logging.error("...", exc_info=True)`
|
|
if matches!(
|
|
exc_info.value,
|
|
Expr::Constant(ast::ExprConstant {
|
|
value: Constant::Bool(true),
|
|
..
|
|
})
|
|
) {
|
|
return Some(exc_info);
|
|
}
|
|
|
|
// Ex) `logging.error("...", exc_info=sys.exc_info())`
|
|
if let Expr::Call(ast::ExprCall { func, .. }) = &exc_info.value {
|
|
if semantic.resolve_call_path(func).map_or(false, |call_path| {
|
|
call_path.as_slice() == ["sys", "exc_info"]
|
|
}) {
|
|
return Some(exc_info);
|
|
}
|
|
}
|
|
|
|
None
|
|
}
|