-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathlint-files.rs
141 lines (131 loc) · 4.43 KB
/
lint-files.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
//! This example calculates the cognitive complexity for each function and
//! method in a Rust file.
use std::{
fs,
path::{Path, PathBuf},
};
use ansi_term::Color::{Blue, Red};
use anyhow::{Context, Result};
use complexity::Complexity;
use structopt::StructOpt;
use syn::{self, File, ImplItem, Item, Type, TypePath};
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
struct Function {
complexity: u32,
name: String,
}
fn parse_file(path: &Path) -> Result<File> {
let contents = fs::read_to_string(path)
.with_context(|| format!("failed to read file `{}`", path.display()))?;
let file = syn::parse_file(&contents)
.with_context(|| format!("failed to parse syntax of `{}`", path.display()))?;
Ok(file)
}
fn lint(path: &Path) -> Result<Vec<Function>> {
let file = parse_file(path)?;
let mut functions = Vec::new();
for item in &file.items {
match item {
// An impl block like `impl Struct { .. }` or `impl Display for Struct { .. }`
Item::Impl(item_impl) => {
for impl_item in &item_impl.items {
if let ImplItem::Method(method) = impl_item {
match &*item_impl.self_ty {
Type::Path(TypePath { qself: None, path }) => {
let name = format!(
"{}::{}",
path.segments.last().unwrap().ident,
method.sig.ident
);
let complexity = method.complexity();
functions.push(Function { name, complexity });
}
_ => {}
}
}
}
}
// A bare function like `fn function(arg: Arg) -> Result { .. }`
Item::Fn(item_fn) => {
let name = item_fn.sig.ident.to_string();
let complexity = item_fn.complexity();
functions.push(Function { name, complexity });
}
_ => {}
}
}
Ok(functions)
}
fn resolve_paths(paths: Vec<PathBuf>) -> Result<Vec<PathBuf>> {
let mut result = Vec::with_capacity(paths.len());
for path in paths {
if path.is_dir() {
result.extend(
walkdir::WalkDir::new(path)
.into_iter()
.filter_map(Result::ok)
.filter(|e| e.path().extension().map(|s| s == "rs").unwrap_or(false))
.map(walkdir::DirEntry::into_path),
);
} else {
result.push(path)
}
}
Ok(result)
}
fn run(paths: Vec<PathBuf>, max_complexity: &Option<u32>) -> Result<bool> {
let mut table = tabular::Table::new("{:<} {:>}");
let mut result = false;
for path in resolve_paths(paths)? {
let mut functions = lint(&path)?;
functions.sort();
for (index, function) in functions
.into_iter()
.rev()
.filter(|f| max_complexity.map(|m| f.complexity >= m).unwrap_or(true))
.enumerate()
{
if index == 0 {
table.add_heading(format!(
"\n{}: {}",
Blue.bold().paint("file"),
Blue.bold().paint(path.display().to_string()),
));
}
table.add_row(tabular::row!(function.name, function.complexity));
result = true;
}
}
print!("{}", table);
Ok(result)
}
#[derive(Debug, StructOpt)]
struct Opt {
/// One or more files to calculate cognitive complexity for.
#[structopt(name = "PATH")]
paths: Vec<PathBuf>,
/// Require functions/methods that have a complexity greater than or equal
/// to this.
#[structopt(short, long, name = "INT")]
max_complexity: Option<u32>,
}
fn main() -> Result<()> {
let Opt {
paths,
max_complexity,
} = Opt::from_args();
if !run(paths, &max_complexity)? {
if let Some(max_complexity) = max_complexity {
eprintln!(
"\n{}",
Red.paint(format!(
"The indicated methods and functions did not meet the required complexity of \
{}",
max_complexity
)),
);
std::process::exit(1);
}
}
Ok(())
}