Skip to content

Commit 27b5dd8

Browse files
committed
Auto merge of #2857 - avborhanian:master, r=phansch
Adding lint test for excessive LOC. This is a WIP for #2377. Just wanted to pull in because I had a few questions: 1. Is it okay that I'm approaching this via counting by looking at each line in the snippet instead of looking at the AST tree? If there's another way to do it, I want to make sure I'm doing the correct way, but I wasn't sure since the output AST JSON doesn't seem to contain whitespace. 2. My function is definitely going to trigger the lint, so also wanted to see if there was something obvious I could do to reduce it. 3. Are the two tests fine, or is there something obvious I'm missing? 4. Obviously bigger question - am I approaching the line count correctly. Current strategy is count a line if it contains some code, so skip if it's just comments or empty.
2 parents 9f88641 + ac9472d commit 27b5dd8

26 files changed

+370
-13
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -981,6 +981,7 @@ All notable changes to this project will be documented in this file.
981981
[`temporary_assignment`]: https://rust-lang.github.io/rust-clippy/master/index.html#temporary_assignment
982982
[`temporary_cstring_as_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#temporary_cstring_as_ptr
983983
[`too_many_arguments`]: https://rust-lang.github.io/rust-clippy/master/index.html#too_many_arguments
984+
[`too_many_lines`]: https://rust-lang.github.io/rust-clippy/master/index.html#too_many_lines
984985
[`toplevel_ref_arg`]: https://rust-lang.github.io/rust-clippy/master/index.html#toplevel_ref_arg
985986
[`transmute_bytes_to_str`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_bytes_to_str
986987
[`transmute_int_to_bool`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_int_to_bool

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
A collection of lints to catch common mistakes and improve your [Rust](https://github.com/rust-lang/rust) code.
99

10-
[There are 294 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
10+
[There are 295 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
1111

1212
We have a bunch of lint categories to allow you to choose how much Clippy is supposed to ~~annoy~~ help you:
1313

clippy_lints/src/assign_ops.rs

+1
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ impl LintPass for AssignOps {
6464
}
6565

6666
impl<'a, 'tcx> LateLintPass<'a, 'tcx> for AssignOps {
67+
#[allow(clippy::too_many_lines)]
6768
fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx hir::Expr) {
6869
match &expr.node {
6970
hir::ExprKind::AssignOp(op, lhs, rhs) => {

clippy_lints/src/bit_mask.rs

+1
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ fn check_compare(cx: &LateContext<'_, '_>, bit_op: &Expr, cmp_op: BinOpKind, cmp
177177
}
178178
}
179179

180+
#[allow(clippy::too_many_lines)]
180181
fn check_bit_mask(
181182
cx: &LateContext<'_, '_>,
182183
bit_op: BinOpKind,

clippy_lints/src/eq_op.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ impl LintPass for EqOp {
5959
}
6060

6161
impl<'a, 'tcx> LateLintPass<'a, 'tcx> for EqOp {
62-
#[allow(clippy::similar_names)]
62+
#[allow(clippy::similar_names, clippy::too_many_lines)]
6363
fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, e: &'tcx Expr) {
6464
if let ExprKind::Binary(op, ref left, ref right) = e.node {
6565
if in_macro(e.span) {

clippy_lints/src/functions.rs

+96-5
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
use crate::utils::{iter_input_pats, span_lint, type_is_unsafe_function};
1+
use crate::utils::{iter_input_pats, snippet, span_lint, type_is_unsafe_function};
22
use matches::matches;
33
use rustc::hir;
44
use rustc::hir::def::Def;
55
use rustc::hir::intravisit;
6-
use rustc::lint::{LateContext, LateLintPass, LintArray, LintPass};
6+
use rustc::lint::{in_external_macro, LateContext, LateLintPass, LintArray, LintContext, LintPass};
77
use rustc::ty;
88
use rustc::{declare_tool_lint, lint_array};
99
use rustc_data_structures::fx::FxHashSet;
@@ -31,6 +31,29 @@ declare_clippy_lint! {
3131
"functions with too many arguments"
3232
}
3333

34+
/// **What it does:** Checks for functions with a large amount of lines.
35+
///
36+
/// **Why is this bad?** Functions with a lot of lines are harder to understand
37+
/// due to having to look at a larger amount of code to understand what the
38+
/// function is doing. Consider splitting the body of the function into
39+
/// multiple functions.
40+
///
41+
/// **Known problems:** None.
42+
///
43+
/// **Example:**
44+
/// ``` rust
45+
/// fn im_too_long() {
46+
/// println!("");
47+
/// // ... 100 more LoC
48+
/// println!("");
49+
/// }
50+
/// ```
51+
declare_clippy_lint! {
52+
pub TOO_MANY_LINES,
53+
pedantic,
54+
"functions with too many lines"
55+
}
56+
3457
/// **What it does:** Checks for public functions that dereferences raw pointer
3558
/// arguments but are not marked unsafe.
3659
///
@@ -62,17 +85,18 @@ declare_clippy_lint! {
6285
#[derive(Copy, Clone)]
6386
pub struct Functions {
6487
threshold: u64,
88+
max_lines: u64,
6589
}
6690

6791
impl Functions {
68-
pub fn new(threshold: u64) -> Self {
69-
Self { threshold }
92+
pub fn new(threshold: u64, max_lines: u64) -> Self {
93+
Self { threshold, max_lines }
7094
}
7195
}
7296

7397
impl LintPass for Functions {
7498
fn get_lints(&self) -> LintArray {
75-
lint_array!(TOO_MANY_ARGUMENTS, NOT_UNSAFE_PTR_ARG_DEREF)
99+
lint_array!(TOO_MANY_ARGUMENTS, TOO_MANY_LINES, NOT_UNSAFE_PTR_ARG_DEREF)
76100
}
77101

78102
fn name(&self) -> &'static str {
@@ -123,6 +147,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Functions {
123147
}
124148

125149
self.check_raw_ptr(cx, unsafety, decl, body, nodeid);
150+
self.check_line_number(cx, span);
126151
}
127152

128153
fn check_trait_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx hir::TraitItem) {
@@ -153,6 +178,72 @@ impl<'a, 'tcx> Functions {
153178
}
154179
}
155180

181+
fn check_line_number(self, cx: &LateContext<'_, '_>, span: Span) {
182+
if in_external_macro(cx.sess(), span) {
183+
return;
184+
}
185+
186+
let code_snippet = snippet(cx, span, "..");
187+
let mut line_count: u64 = 0;
188+
let mut in_comment = false;
189+
let mut code_in_line;
190+
191+
// Skip the surrounding function decl.
192+
let start_brace_idx = match code_snippet.find('{') {
193+
Some(i) => i + 1,
194+
None => 0,
195+
};
196+
let end_brace_idx = match code_snippet.find('}') {
197+
Some(i) => i,
198+
None => code_snippet.len(),
199+
};
200+
let function_lines = code_snippet[start_brace_idx..end_brace_idx].lines();
201+
202+
for mut line in function_lines {
203+
code_in_line = false;
204+
loop {
205+
line = line.trim_start();
206+
if line.is_empty() {
207+
break;
208+
}
209+
if in_comment {
210+
match line.find("*/") {
211+
Some(i) => {
212+
line = &line[i + 2..];
213+
in_comment = false;
214+
continue;
215+
},
216+
None => break,
217+
}
218+
} else {
219+
let multi_idx = match line.find("/*") {
220+
Some(i) => i,
221+
None => line.len(),
222+
};
223+
let single_idx = match line.find("//") {
224+
Some(i) => i,
225+
None => line.len(),
226+
};
227+
code_in_line |= multi_idx > 0 && single_idx > 0;
228+
// Implies multi_idx is below line.len()
229+
if multi_idx < single_idx {
230+
line = &line[multi_idx + 2..];
231+
in_comment = true;
232+
continue;
233+
}
234+
break;
235+
}
236+
}
237+
if code_in_line {
238+
line_count += 1;
239+
}
240+
}
241+
242+
if line_count > self.max_lines {
243+
span_lint(cx, TOO_MANY_LINES, span, "This function has a large number of lines.")
244+
}
245+
}
246+
156247
fn check_raw_ptr(
157248
self,
158249
cx: &LateContext<'a, 'tcx>,

clippy_lints/src/lib.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,7 @@ pub fn read_conf(reg: &rustc_plugin::Registry<'_>) -> Conf {
290290
}
291291
}
292292

293+
#[allow(clippy::too_many_lines)]
293294
#[rustfmt::skip]
294295
pub fn register_plugins(reg: &mut rustc_plugin::Registry<'_>, conf: &Conf) {
295296
let mut store = reg.sess.lint_store.borrow_mut();
@@ -427,7 +428,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry<'_>, conf: &Conf) {
427428
reg.register_late_lint_pass(box blacklisted_name::BlackListedName::new(
428429
conf.blacklisted_names.iter().cloned().collect()
429430
));
430-
reg.register_late_lint_pass(box functions::Functions::new(conf.too_many_arguments_threshold));
431+
reg.register_late_lint_pass(box functions::Functions::new(conf.too_many_arguments_threshold, conf.too_many_lines_threshold));
431432
reg.register_early_lint_pass(box doc::Doc::new(conf.doc_valid_idents.iter().cloned().collect()));
432433
reg.register_late_lint_pass(box neg_multiply::NegMultiply);
433434
reg.register_early_lint_pass(box unsafe_removed_from_name::UnsafeNameRemoval);
@@ -527,6 +528,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry<'_>, conf: &Conf) {
527528
enum_glob_use::ENUM_GLOB_USE,
528529
enum_variants::MODULE_NAME_REPETITIONS,
529530
enum_variants::PUB_ENUM_VARIANT_NAMES,
531+
functions::TOO_MANY_LINES,
530532
if_not_else::IF_NOT_ELSE,
531533
infinite_iter::MAYBE_INFINITE_ITER,
532534
items_after_statements::ITEMS_AFTER_STATEMENTS,

clippy_lints/src/loops.rs

+2
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,7 @@ impl LintPass for Pass {
472472
}
473473

474474
impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Pass {
475+
#[allow(clippy::too_many_lines)]
475476
fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr) {
476477
// we don't want to check expanded macros
477478
if in_macro(expr.span) {
@@ -1066,6 +1067,7 @@ fn detect_manual_memcpy<'a, 'tcx>(
10661067

10671068
/// Check for looping over a range and then indexing a sequence with it.
10681069
/// The iteratee must be a range literal.
1070+
#[allow(clippy::too_many_lines)]
10691071
fn check_for_loop_range<'a, 'tcx>(
10701072
cx: &LateContext<'a, 'tcx>,
10711073
pat: &'tcx Pat,

clippy_lints/src/methods/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -1005,6 +1005,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Pass {
10051005
}
10061006

10071007
/// Checks for the `OR_FUN_CALL` lint.
1008+
#[allow(clippy::too_many_lines)]
10081009
fn lint_or_fun_call(cx: &LateContext<'_, '_>, expr: &hir::Expr, method_span: Span, name: &str, args: &[hir::Expr]) {
10091010
/// Check for `unwrap_or(T::new())` or `unwrap_or(T::default())`.
10101011
fn check_unwrap_or_default(
@@ -1151,6 +1152,7 @@ fn lint_or_fun_call(cx: &LateContext<'_, '_>, expr: &hir::Expr, method_span: Spa
11511152
}
11521153

11531154
/// Checks for the `EXPECT_FUN_CALL` lint.
1155+
#[allow(clippy::too_many_lines)]
11541156
fn lint_expect_fun_call(cx: &LateContext<'_, '_>, expr: &hir::Expr, method_span: Span, name: &str, args: &[hir::Expr]) {
11551157
// Strip `&`, `as_ref()` and `as_str()` off `arg` until we're left with either a `String` or
11561158
// `&str`

clippy_lints/src/needless_pass_by_value.rs

+1
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ macro_rules! need {
7373
}
7474

7575
impl<'a, 'tcx> LateLintPass<'a, 'tcx> for NeedlessPassByValue {
76+
#[allow(clippy::too_many_lines)]
7677
fn check_fn(
7778
&mut self,
7879
cx: &LateContext<'a, 'tcx>,

clippy_lints/src/non_expressive_names.rs

+1
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ impl<'a, 'tcx, 'b> SimilarNamesNameVisitor<'a, 'tcx, 'b> {
158158
);
159159
}
160160
}
161+
#[allow(clippy::too_many_lines)]
161162
fn check_name(&mut self, span: Span, name: Name) {
162163
let interned_name = name.as_str();
163164
if interned_name.chars().any(char::is_uppercase) {

clippy_lints/src/ptr.rs

+1
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for PointerPass {
151151
}
152152
}
153153

154+
#[allow(clippy::too_many_lines)]
154155
fn check_fn(cx: &LateContext<'_, '_>, decl: &FnDecl, fn_id: NodeId, opt_body_id: Option<BodyId>) {
155156
let fn_def_id = cx.tcx.hir().local_def_id(fn_id);
156157
let sig = cx.tcx.fn_sig(fn_def_id);

clippy_lints/src/redundant_clone.rs

+1
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ impl LintPass for RedundantClone {
8080
}
8181

8282
impl<'a, 'tcx> LateLintPass<'a, 'tcx> for RedundantClone {
83+
#[allow(clippy::too_many_lines)]
8384
fn check_fn(
8485
&mut self,
8586
cx: &LateContext<'a, 'tcx>,

clippy_lints/src/transmute.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ impl LintPass for Transmute {
226226
}
227227

228228
impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Transmute {
229-
#[allow(clippy::similar_names)]
229+
#[allow(clippy::similar_names, clippy::too_many_lines)]
230230
fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, e: &'tcx Expr) {
231231
if let ExprKind::Call(ref path_expr, ref args) = e.node {
232232
if let ExprKind::Path(ref qpath) = path_expr.node {

clippy_lints/src/types.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ fn match_type_parameter(cx: &LateContext<'_, '_>, qpath: &QPath, path: &[&str])
240240
///
241241
/// The parameter `is_local` distinguishes the context of the type; types from
242242
/// local bindings should only be checked for the `BORROWED_BOX` lint.
243+
#[allow(clippy::too_many_lines)]
243244
fn check_ty(cx: &LateContext<'_, '_>, hir_ty: &hir::Ty, is_local: bool) {
244245
if in_macro(hir_ty.span) {
245246
return;
@@ -1966,7 +1967,7 @@ impl LintPass for ImplicitHasher {
19661967
}
19671968

19681969
impl<'a, 'tcx> LateLintPass<'a, 'tcx> for ImplicitHasher {
1969-
#[allow(clippy::cast_possible_truncation)]
1970+
#[allow(clippy::cast_possible_truncation, clippy::too_many_lines)]
19701971
fn check_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx Item) {
19711972
use syntax_pos::BytePos;
19721973

clippy_lints/src/utils/author.rs

+2
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ struct PrintVisitor {
194194
}
195195

196196
impl<'tcx> Visitor<'tcx> for PrintVisitor {
197+
#[allow(clippy::too_many_lines)]
197198
fn visit_expr(&mut self, expr: &Expr) {
198199
print!(" if let ExprKind::");
199200
let current = format!("{}.node", self.current);
@@ -506,6 +507,7 @@ impl<'tcx> Visitor<'tcx> for PrintVisitor {
506507
}
507508
}
508509

510+
#[allow(clippy::too_many_lines)]
509511
fn visit_pat(&mut self, pat: &Pat) {
510512
print!(" if let PatKind::");
511513
let current = format!("{}.node", self.current);

clippy_lints/src/utils/conf.rs

+2
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,8 @@ define_Conf! {
148148
(literal_representation_threshold, "literal_representation_threshold", 16384 => u64),
149149
/// Lint: TRIVIALLY_COPY_PASS_BY_REF. The maximum size (in bytes) to consider a `Copy` type for passing by value instead of by reference.
150150
(trivial_copy_size_limit, "trivial_copy_size_limit", None => Option<u64>),
151+
/// Lint: TOO_MANY_LINES. The maximum number of lines a function or method can have
152+
(too_many_lines_threshold, "too_many_lines_threshold", 100 => u64),
151153
}
152154

153155
impl Default for Conf {

clippy_lints/src/utils/hir_utils.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -389,7 +389,7 @@ impl<'a, 'tcx: 'a> SpanlessHash<'a, 'tcx> {
389389
.hash(&mut self.s);
390390
}
391391

392-
#[allow(clippy::many_single_char_names)]
392+
#[allow(clippy::many_single_char_names, clippy::too_many_lines)]
393393
pub fn hash_expr(&mut self, e: &Expr) {
394394
if let Some(e) = constant_simple(self.cx, self.tables, e) {
395395
return e.hash(&mut self.s);

src/driver.rs

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ fn show_version() {
2020
println!(env!("CARGO_PKG_VERSION"));
2121
}
2222

23+
#[allow(clippy::too_many_lines)]
2324
pub fn main() {
2425
rustc_driver::init_rustc_env_logger();
2526
exit(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
too-many-lines-threshold = 1
+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#![warn(clippy::too_many_lines)]
2+
3+
// This function should be considered one line.
4+
fn many_comments_but_one_line_of_code() {
5+
/* println!("This is good."); */
6+
// println!("This is good.");
7+
/* */ // println!("This is good.");
8+
/* */ // println!("This is good.");
9+
/* */ // println!("This is good.");
10+
/* */ // println!("This is good.");
11+
/* println!("This is good.");
12+
println!("This is good.");
13+
println!("This is good."); */
14+
println!("This is good.");
15+
}
16+
17+
// This should be considered two and a fail.
18+
fn too_many_lines() {
19+
println!("This is bad.");
20+
println!("This is bad.");
21+
}
22+
23+
// This should be considered one line.
24+
#[rustfmt::skip]
25+
fn comment_starts_after_code() {
26+
let _ = 5; /* closing comment. */ /*
27+
this line shouldn't be counted theoretically.
28+
*/
29+
}
30+
31+
// This should be considered one line.
32+
fn comment_after_code() {
33+
let _ = 5; /* this line should get counted once. */
34+
}
35+
36+
// This should fail since it is technically two lines.
37+
#[rustfmt::skip]
38+
fn comment_before_code() {
39+
let _ = "test";
40+
/* This comment extends to the front of
41+
teh code but this line should still count. */ let _ = 5;
42+
}
43+
44+
// This should be considered one line.
45+
fn main() {}

0 commit comments

Comments
 (0)