use gazebo::variants::VariantName;
use thiserror::Error;
use crate::{
analysis::types::{LintT, LintWarning},
codemap::CodeMap,
syntax::{
ast::{Argument, AstExpr, Expr},
AstModule,
},
};
#[derive(Error, Debug, VariantName)]
pub(crate) enum Performance {
#[error("Dict copy `{0}` is more efficient as `{1}`")]
DictWithoutStarStar(String, String),
}
impl LintWarning for Performance {
fn is_serious(&self) -> bool {
true
}
}
fn match_dict_copy(codemap: &CodeMap, x: &AstExpr, res: &mut Vec<LintT<Performance>>) {
match &**x {
Expr::Call(fun, args) if args.len() == 1 => match (&***fun, &*args[0]) {
(Expr::Identifier(f, _), Argument::KwArgs(arg)) if f.node == "dict" => {
res.push(LintT::new(
codemap,
x.span,
Performance::DictWithoutStarStar(x.to_string(), format!("dict({})", arg.node)),
))
}
_ => {}
},
_ => {}
}
}
fn dict_copy(module: &AstModule, res: &mut Vec<LintT<Performance>>) {
fn check(codemap: &CodeMap, x: &AstExpr, res: &mut Vec<LintT<Performance>>) {
match_dict_copy(codemap, x, res);
x.visit_expr(|x| check(codemap, x, res));
}
module
.statement
.visit_expr(|x| check(&module.codemap, x, res));
}
pub(crate) fn performance(module: &AstModule) -> Vec<LintT<Performance>> {
let mut res = Vec::new();
dict_copy(module, &mut res);
res
}
#[cfg(test)]
mod tests {
use gazebo::prelude::*;
use super::*;
use crate::syntax::Dialect;
fn module(x: &str) -> AstModule {
AstModule::parse("bad.bzl", x.to_owned(), &Dialect::Extended).unwrap()
}
#[test]
fn test_lint_performance() {
let mut res = Vec::new();
dict_copy(
&module(
r#"
def foo(extra, **kwargs):
x = dict(**kwargs)
y = dict(extra)
return (x,y)
"#,
),
&mut res,
);
assert_eq!(
res.map(|x| x.to_string()),
&["bad.bzl:3:9-23: Dict copy `dict(**kwargs)` is more efficient as `dict(kwargs)`"]
);
}
}