Skip to content

Commit f3397af

Browse files
authored
Merge pull request #994 from oli-obk/stuttter
Stuttter lint
2 parents 4e8c87a + e628e4d commit f3397af

25 files changed

+240
-161
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ All notable changes to this project will be documented in this file.
240240
[`string_add_assign`]: https://github.com/Manishearth/rust-clippy/wiki#string_add_assign
241241
[`string_lit_as_bytes`]: https://github.com/Manishearth/rust-clippy/wiki#string_lit_as_bytes
242242
[`string_to_string`]: https://github.com/Manishearth/rust-clippy/wiki#string_to_string
243+
[`stutter`]: https://github.com/Manishearth/rust-clippy/wiki#stutter
243244
[`suspicious_assignment_formatting`]: https://github.com/Manishearth/rust-clippy/wiki#suspicious_assignment_formatting
244245
[`suspicious_else_formatting`]: https://github.com/Manishearth/rust-clippy/wiki#suspicious_else_formatting
245246
[`temporary_assignment`]: https://github.com/Manishearth/rust-clippy/wiki#temporary_assignment

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ Table of contents:
1717

1818
## Lints
1919

20-
There are 153 lints included in this crate:
20+
There are 154 lints included in this crate:
2121

2222
name | default | meaning
2323
---------------------------------------------------------------------------------------------------------------------|---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -144,6 +144,7 @@ name
144144
[string_add](https://github.com/Manishearth/rust-clippy/wiki#string_add) | allow | using `x + ..` where x is a `String`; suggests using `push_str()` instead
145145
[string_add_assign](https://github.com/Manishearth/rust-clippy/wiki#string_add_assign) | allow | using `x = x + ..` where x is a `String`; suggests using `push_str()` instead
146146
[string_lit_as_bytes](https://github.com/Manishearth/rust-clippy/wiki#string_lit_as_bytes) | warn | calling `as_bytes` on a string literal; suggests using a byte string literal instead
147+
[stutter](https://github.com/Manishearth/rust-clippy/wiki#stutter) | allow | finds type names prefixed/postfixed with their containing module's name
147148
[suspicious_assignment_formatting](https://github.com/Manishearth/rust-clippy/wiki#suspicious_assignment_formatting) | warn | suspicious formatting of `*=`, `-=` or `!=`
148149
[suspicious_else_formatting](https://github.com/Manishearth/rust-clippy/wiki#suspicious_else_formatting) | warn | suspicious formatting of `else if`
149150
[temporary_assignment](https://github.com/Manishearth/rust-clippy/wiki#temporary_assignment) | warn | assignments to temporaries

clippy_lints/src/approx_const.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,15 @@ const KNOWN_CONSTS: &'static [(f64, &'static str, usize)] = &[(f64::E, "E", 4),
3737
(f64::SQRT_2, "SQRT_2", 5)];
3838

3939
#[derive(Copy,Clone)]
40-
pub struct ApproxConstant;
40+
pub struct Pass;
4141

42-
impl LintPass for ApproxConstant {
42+
impl LintPass for Pass {
4343
fn get_lints(&self) -> LintArray {
4444
lint_array!(APPROX_CONSTANT)
4545
}
4646
}
4747

48-
impl LateLintPass for ApproxConstant {
48+
impl LateLintPass for Pass {
4949
fn check_expr(&mut self, cx: &LateContext, e: &Expr) {
5050
if let ExprLit(ref lit) = e.node {
5151
check_lit(cx, lit, e);

clippy_lints/src/doc.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ pub fn check_attrs<'a>(cx: &EarlyContext, valid_idents: &[String], attrs: &'a [a
8080
}
8181

8282
#[allow(while_let_loop)] // #362
83-
pub fn check_doc(cx: &EarlyContext, valid_idents: &[String], docs: &[(&str, Span)]) -> Result<(), ()> {
83+
fn check_doc(cx: &EarlyContext, valid_idents: &[String], docs: &[(&str, Span)]) -> Result<(), ()> {
8484
// In markdown, `_` can be used to emphasize something, or, is a raw `_` depending on context.
8585
// There really is no markdown specification that would disambiguate this properly. This is
8686
// what GitHub and Rustdoc do:

clippy_lints/src/drop_ref.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,15 @@ declare_lint! {
2323
}
2424

2525
#[allow(missing_copy_implementations)]
26-
pub struct DropRefPass;
26+
pub struct Pass;
2727

28-
impl LintPass for DropRefPass {
28+
impl LintPass for Pass {
2929
fn get_lints(&self) -> LintArray {
3030
lint_array!(DROP_REF)
3131
}
3232
}
3333

34-
impl LateLintPass for DropRefPass {
34+
impl LateLintPass for Pass {
3535
fn check_expr(&mut self, cx: &LateContext, expr: &Expr) {
3636
if let ExprCall(ref path, ref args) = expr.node {
3737
if let ExprPath(None, _) = path.node {

clippy_lints/src/enum_clike.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,15 @@ declare_lint! {
1818
"finds C-like enums that are `repr(isize/usize)` and have values that don't fit into an `i32`"
1919
}
2020

21-
pub struct EnumClikeUnportableVariant;
21+
pub struct UnportableVariant;
2222

23-
impl LintPass for EnumClikeUnportableVariant {
23+
impl LintPass for UnportableVariant {
2424
fn get_lints(&self) -> LintArray {
2525
lint_array!(ENUM_CLIKE_UNPORTABLE_VARIANT)
2626
}
2727
}
2828

29-
impl LateLintPass for EnumClikeUnportableVariant {
29+
impl LateLintPass for UnportableVariant {
3030
#[allow(cast_possible_truncation, cast_sign_loss)]
3131
fn check_item(&mut self, cx: &LateContext, item: &Item) {
3232
if let ItemEnum(ref def, _) = item.node {

clippy_lints/src/enum_variants.rs

Lines changed: 123 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22
33
use rustc::lint::*;
44
use syntax::ast::*;
5+
use syntax::codemap::Span;
56
use syntax::parse::token::InternedString;
6-
use utils::span_help_and_lint;
7-
use utils::{camel_case_from, camel_case_until};
7+
use utils::{span_help_and_lint, span_lint};
8+
use utils::{camel_case_from, camel_case_until, in_macro};
89

910
/// **What it does:** Warns on enum variants that are prefixed or suffixed by the same characters
1011
///
@@ -18,87 +19,153 @@ declare_lint! {
1819
"finds enums where all variants share a prefix/postfix"
1920
}
2021

21-
pub struct EnumVariantNames;
22+
/// **What it does:** Warns on type names that are prefixed or suffixed by the containing module's name
23+
///
24+
/// **Why is this bad?** It requires the user to type the module name twice
25+
///
26+
/// **Known problems:** None
27+
///
28+
/// **Example:** mod cake { struct BlackForestCake; }
29+
declare_lint! {
30+
pub STUTTER, Allow,
31+
"finds type names prefixed/postfixed with their containing module's name"
32+
}
33+
34+
#[derive(Default)]
35+
pub struct EnumVariantNames {
36+
modules: Vec<String>,
37+
}
2238

2339
impl LintPass for EnumVariantNames {
2440
fn get_lints(&self) -> LintArray {
25-
lint_array!(ENUM_VARIANT_NAMES)
41+
lint_array!(ENUM_VARIANT_NAMES, STUTTER)
2642
}
2743
}
2844

2945
fn var2str(var: &Variant) -> InternedString {
3046
var.node.name.name.as_str()
3147
}
3248

33-
// FIXME: waiting for https://github.com/rust-lang/rust/pull/31700
34-
// fn partial_match(pre: &str, name: &str) -> usize {
35-
// // skip(1) to ensure that the prefix never takes the whole variant name
36-
// pre.chars().zip(name.chars().rev().skip(1).rev()).take_while(|&(l, r)| l == r).count()
37-
// }
38-
//
39-
// fn partial_rmatch(post: &str, name: &str) -> usize {
40-
// // skip(1) to ensure that the postfix never takes the whole variant name
41-
// post.chars().rev().zip(name.chars().skip(1).rev()).take_while(|&(l, r)| l == r).count()
42-
// }
43-
49+
/// Returns the number of chars that match from the start
4450
fn partial_match(pre: &str, name: &str) -> usize {
4551
let mut name_iter = name.chars();
4652
let _ = name_iter.next_back(); // make sure the name is never fully matched
4753
pre.chars().zip(name_iter).take_while(|&(l, r)| l == r).count()
4854
}
4955

56+
/// Returns the number of chars that match from the end
5057
fn partial_rmatch(post: &str, name: &str) -> usize {
5158
let mut name_iter = name.chars();
5259
let _ = name_iter.next(); // make sure the name is never fully matched
5360
post.chars().rev().zip(name_iter.rev()).take_while(|&(l, r)| l == r).count()
5461
}
5562

56-
impl EarlyLintPass for EnumVariantNames {
57-
// FIXME: #600
58-
#[allow(while_let_on_iterator)]
59-
fn check_item(&mut self, cx: &EarlyContext, item: &Item) {
60-
if let ItemKind::Enum(ref def, _) = item.node {
61-
if def.variants.len() < 2 {
62-
return;
63+
// FIXME: #600
64+
#[allow(while_let_on_iterator)]
65+
fn check_variant(cx: &EarlyContext, def: &EnumDef, item_name: &str, item_name_chars: usize, span: Span) {
66+
for var in &def.variants {
67+
let name = var2str(var);
68+
if partial_match(item_name, &name) == item_name_chars {
69+
span_lint(cx, ENUM_VARIANT_NAMES, var.span, "Variant name starts with the enum's name");
70+
}
71+
if partial_rmatch(item_name, &name) == item_name_chars {
72+
span_lint(cx, ENUM_VARIANT_NAMES, var.span, "Variant name ends with the enum's name");
73+
}
74+
}
75+
if def.variants.len() < 2 {
76+
return;
77+
}
78+
let first = var2str(&def.variants[0]);
79+
let mut pre = &first[..camel_case_until(&*first)];
80+
let mut post = &first[camel_case_from(&*first)..];
81+
for var in &def.variants {
82+
let name = var2str(var);
83+
84+
let pre_match = partial_match(pre, &name);
85+
pre = &pre[..pre_match];
86+
let pre_camel = camel_case_until(pre);
87+
pre = &pre[..pre_camel];
88+
while let Some((next, last)) = name[pre.len()..].chars().zip(pre.chars().rev()).next() {
89+
if next.is_lowercase() {
90+
let last = pre.len() - last.len_utf8();
91+
let last_camel = camel_case_until(&pre[..last]);
92+
pre = &pre[..last_camel];
93+
} else {
94+
break;
6395
}
64-
let first = var2str(&def.variants[0]);
65-
let mut pre = &first[..camel_case_until(&*first)];
66-
let mut post = &first[camel_case_from(&*first)..];
67-
for var in &def.variants {
68-
let name = var2str(var);
96+
}
6997

70-
let pre_match = partial_match(pre, &name);
71-
pre = &pre[..pre_match];
72-
let pre_camel = camel_case_until(pre);
73-
pre = &pre[..pre_camel];
74-
while let Some((next, last)) = name[pre.len()..].chars().zip(pre.chars().rev()).next() {
75-
if next.is_lowercase() {
76-
let last = pre.len() - last.len_utf8();
77-
let last_camel = camel_case_until(&pre[..last]);
78-
pre = &pre[..last_camel];
79-
} else {
80-
break;
98+
let post_match = partial_rmatch(post, &name);
99+
let post_end = post.len() - post_match;
100+
post = &post[post_end..];
101+
let post_camel = camel_case_from(post);
102+
post = &post[post_camel..];
103+
}
104+
let (what, value) = match (pre.is_empty(), post.is_empty()) {
105+
(true, true) => return,
106+
(false, _) => ("pre", pre),
107+
(true, false) => ("post", post),
108+
};
109+
span_help_and_lint(cx,
110+
ENUM_VARIANT_NAMES,
111+
span,
112+
&format!("All variants have the same {}fix: `{}`", what, value),
113+
&format!("remove the {}fixes and use full paths to \
114+
the variants instead of glob imports",
115+
what));
116+
}
117+
118+
fn to_camel_case(item_name: &str) -> String {
119+
let mut s = String::new();
120+
let mut up = true;
121+
for c in item_name.chars() {
122+
if c.is_uppercase() {
123+
// we only turn snake case text into CamelCase
124+
return item_name.to_string();
125+
}
126+
if c == '_' {
127+
up = true;
128+
continue;
129+
}
130+
if up {
131+
up = false;
132+
s.extend(c.to_uppercase());
133+
} else {
134+
s.push(c);
135+
}
136+
}
137+
s
138+
}
139+
140+
impl EarlyLintPass for EnumVariantNames {
141+
fn check_item_post(&mut self, _cx: &EarlyContext, _item: &Item) {
142+
let last = self.modules.pop();
143+
assert!(last.is_some());
144+
}
145+
146+
fn check_item(&mut self, cx: &EarlyContext, item: &Item) {
147+
let item_name = item.ident.name.as_str();
148+
let item_name_chars = item_name.chars().count();
149+
let item_camel = to_camel_case(&item_name);
150+
if item.vis == Visibility::Public && !in_macro(cx, item.span) {
151+
if let Some(mod_camel) = self.modules.last() {
152+
// constants don't have surrounding modules
153+
if !mod_camel.is_empty() {
154+
let matching = partial_match(mod_camel, &item_camel);
155+
let rmatching = partial_rmatch(mod_camel, &item_camel);
156+
let nchars = mod_camel.chars().count();
157+
if matching == nchars {
158+
span_lint(cx, STUTTER, item.span, &format!("Item name ({}) starts with its containing module's name ({})", item_camel, mod_camel));
159+
}
160+
if rmatching == nchars {
161+
span_lint(cx, STUTTER, item.span, &format!("Item name ({}) ends with its containing module's name ({})", item_camel, mod_camel));
81162
}
82163
}
83-
84-
let post_match = partial_rmatch(post, &name);
85-
let post_end = post.len() - post_match;
86-
post = &post[post_end..];
87-
let post_camel = camel_case_from(post);
88-
post = &post[post_camel..];
89164
}
90-
let (what, value) = match (pre.is_empty(), post.is_empty()) {
91-
(true, true) => return,
92-
(false, _) => ("pre", pre),
93-
(true, false) => ("post", post),
94-
};
95-
span_help_and_lint(cx,
96-
ENUM_VARIANT_NAMES,
97-
item.span,
98-
&format!("All variants have the same {}fix: `{}`", what, value),
99-
&format!("remove the {}fixes and use full paths to \
100-
the variants instead of glob imports",
101-
what));
102165
}
166+
if let ItemKind::Enum(ref def, _) = item.node {
167+
check_variant(cx, def, &item_name, item_name_chars, item.span);
168+
}
169+
self.modules.push(item_camel);
103170
}
104171
}

clippy_lints/src/escape.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use syntax::ast::NodeId;
1111
use syntax::codemap::Span;
1212
use utils::span_lint;
1313

14-
pub struct EscapePass;
14+
pub struct Pass;
1515

1616
/// **What it does:** This lint checks for usage of `Box<T>` where an unboxed `T` would work fine.
1717
///
@@ -44,13 +44,13 @@ struct EscapeDelegate<'a, 'tcx: 'a> {
4444
set: NodeSet,
4545
}
4646

47-
impl LintPass for EscapePass {
47+
impl LintPass for Pass {
4848
fn get_lints(&self) -> LintArray {
4949
lint_array!(BOXED_LOCAL)
5050
}
5151
}
5252

53-
impl LateLintPass for EscapePass {
53+
impl LateLintPass for Pass {
5454
fn check_fn(&mut self, cx: &LateContext, _: visit::FnKind, decl: &FnDecl, body: &Block, _: Span, id: NodeId) {
5555
let param_env = ty::ParameterEnvironment::for_item(cx.tcx, id);
5656

clippy_lints/src/format.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,15 @@ declare_lint! {
2323
}
2424

2525
#[derive(Copy, Clone, Debug)]
26-
pub struct FormatMacLint;
26+
pub struct Pass;
2727

28-
impl LintPass for FormatMacLint {
28+
impl LintPass for Pass {
2929
fn get_lints(&self) -> LintArray {
3030
lint_array![USELESS_FORMAT]
3131
}
3232
}
3333

34-
impl LateLintPass for FormatMacLint {
34+
impl LateLintPass for Pass {
3535
fn check_expr(&mut self, cx: &LateContext, expr: &Expr) {
3636
if let Some(span) = is_expn_of(cx, expr.span, "format") {
3737
match expr.node {

0 commit comments

Comments
 (0)