]>
Commit | Line | Data |
---|---|---|
b039eaaf SL |
1 | // Copyright 2015 The Rust Project Developers. See the COPYRIGHT |
2 | // file at the top-level directory of this distribution and at | |
3 | // http://rust-lang.org/COPYRIGHT. | |
4 | // | |
5 | // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | |
6 | // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license | |
7 | // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your | |
8 | // option. This file may not be copied, modified, or distributed | |
9 | // except according to those terms. | |
10 | ||
54a0048b SL |
11 | use rustc::hir::def::Def; |
12 | use rustc::ty; | |
b039eaaf SL |
13 | use lint::{LateContext, LintContext, LintArray}; |
14 | use lint::{LintPass, LateLintPass}; | |
15 | ||
16 | use syntax::ast; | |
17 | use syntax::attr::{self, AttrMetaMethods}; | |
3157f602 | 18 | use syntax_pos::Span; |
b039eaaf | 19 | |
54a0048b SL |
20 | use rustc::hir::{self, PatKind}; |
21 | use rustc::hir::intravisit::FnKind; | |
b039eaaf SL |
22 | |
23 | #[derive(PartialEq)] | |
24 | pub enum MethodLateContext { | |
25 | TraitDefaultImpl, | |
26 | TraitImpl, | |
27 | PlainImpl | |
28 | } | |
29 | ||
30 | pub fn method_context(cx: &LateContext, id: ast::NodeId, span: Span) -> MethodLateContext { | |
31 | let def_id = cx.tcx.map.local_def_id(id); | |
32 | match cx.tcx.impl_or_trait_items.borrow().get(&def_id) { | |
54a0048b | 33 | None => span_bug!(span, "missing method descriptor?!"), |
b039eaaf SL |
34 | Some(item) => match item.container() { |
35 | ty::TraitContainer(..) => MethodLateContext::TraitDefaultImpl, | |
36 | ty::ImplContainer(cid) => { | |
37 | match cx.tcx.impl_trait_ref(cid) { | |
38 | Some(_) => MethodLateContext::TraitImpl, | |
39 | None => MethodLateContext::PlainImpl | |
40 | } | |
41 | } | |
42 | } | |
43 | } | |
44 | } | |
45 | ||
46 | declare_lint! { | |
47 | pub NON_CAMEL_CASE_TYPES, | |
48 | Warn, | |
49 | "types, variants, traits and type parameters should have camel case names" | |
50 | } | |
51 | ||
52 | #[derive(Copy, Clone)] | |
53 | pub struct NonCamelCaseTypes; | |
54 | ||
55 | impl NonCamelCaseTypes { | |
56 | fn check_case(&self, cx: &LateContext, sort: &str, name: ast::Name, span: Span) { | |
57 | fn is_camel_case(name: ast::Name) -> bool { | |
58 | let name = name.as_str(); | |
59 | if name.is_empty() { | |
60 | return true; | |
61 | } | |
62 | let name = name.trim_matches('_'); | |
63 | ||
64 | // start with a non-lowercase letter rather than non-uppercase | |
65 | // ones (some scripts don't have a concept of upper/lowercase) | |
54a0048b SL |
66 | !name.is_empty() && |
67 | !name.chars().next().unwrap().is_lowercase() && | |
68 | !name.contains('_') | |
b039eaaf SL |
69 | } |
70 | ||
71 | fn to_camel_case(s: &str) -> String { | |
72 | s.split('_').flat_map(|word| word.chars().enumerate().map(|(i, c)| | |
73 | if i == 0 { | |
74 | c.to_uppercase().collect::<String>() | |
75 | } else { | |
76 | c.to_lowercase().collect() | |
77 | } | |
78 | )).collect::<Vec<_>>().concat() | |
79 | } | |
80 | ||
81 | let s = name.as_str(); | |
82 | ||
83 | if !is_camel_case(name) { | |
84 | let c = to_camel_case(&s); | |
85 | let m = if c.is_empty() { | |
86 | format!("{} `{}` should have a camel case name such as `CamelCase`", sort, s) | |
87 | } else { | |
88 | format!("{} `{}` should have a camel case name such as `{}`", sort, s, c) | |
89 | }; | |
90 | cx.span_lint(NON_CAMEL_CASE_TYPES, span, &m[..]); | |
91 | } | |
92 | } | |
93 | } | |
94 | ||
95 | impl LintPass for NonCamelCaseTypes { | |
96 | fn get_lints(&self) -> LintArray { | |
97 | lint_array!(NON_CAMEL_CASE_TYPES) | |
98 | } | |
99 | } | |
100 | ||
101 | impl LateLintPass for NonCamelCaseTypes { | |
102 | fn check_item(&mut self, cx: &LateContext, it: &hir::Item) { | |
103 | let extern_repr_count = it.attrs.iter().filter(|attr| { | |
104 | attr::find_repr_attrs(cx.tcx.sess.diagnostic(), attr).iter() | |
105 | .any(|r| r == &attr::ReprExtern) | |
106 | }).count(); | |
107 | let has_extern_repr = extern_repr_count > 0; | |
108 | ||
109 | if has_extern_repr { | |
110 | return; | |
111 | } | |
112 | ||
113 | match it.node { | |
114 | hir::ItemTy(..) | hir::ItemStruct(..) => { | |
115 | self.check_case(cx, "type", it.name, it.span) | |
116 | } | |
117 | hir::ItemTrait(..) => { | |
118 | self.check_case(cx, "trait", it.name, it.span) | |
119 | } | |
120 | hir::ItemEnum(ref enum_definition, _) => { | |
121 | if has_extern_repr { | |
122 | return; | |
123 | } | |
124 | self.check_case(cx, "type", it.name, it.span); | |
125 | for variant in &enum_definition.variants { | |
126 | self.check_case(cx, "variant", variant.node.name, variant.span); | |
127 | } | |
128 | } | |
129 | _ => () | |
130 | } | |
131 | } | |
132 | ||
133 | fn check_generics(&mut self, cx: &LateContext, it: &hir::Generics) { | |
134 | for gen in it.ty_params.iter() { | |
135 | self.check_case(cx, "type parameter", gen.name, gen.span); | |
136 | } | |
137 | } | |
138 | } | |
139 | ||
140 | declare_lint! { | |
141 | pub NON_SNAKE_CASE, | |
142 | Warn, | |
92a42be0 | 143 | "variables, methods, functions, lifetime parameters and modules should have snake case names" |
b039eaaf SL |
144 | } |
145 | ||
146 | #[derive(Copy, Clone)] | |
147 | pub struct NonSnakeCase; | |
148 | ||
149 | impl NonSnakeCase { | |
150 | fn to_snake_case(mut str: &str) -> String { | |
151 | let mut words = vec![]; | |
152 | // Preserve leading underscores | |
153 | str = str.trim_left_matches(|c: char| { | |
154 | if c == '_' { | |
155 | words.push(String::new()); | |
156 | true | |
157 | } else { | |
158 | false | |
159 | } | |
160 | }); | |
161 | for s in str.split('_') { | |
162 | let mut last_upper = false; | |
163 | let mut buf = String::new(); | |
164 | if s.is_empty() { | |
165 | continue; | |
166 | } | |
167 | for ch in s.chars() { | |
168 | if !buf.is_empty() && buf != "'" | |
169 | && ch.is_uppercase() | |
170 | && !last_upper { | |
171 | words.push(buf); | |
172 | buf = String::new(); | |
173 | } | |
174 | last_upper = ch.is_uppercase(); | |
175 | buf.extend(ch.to_lowercase()); | |
176 | } | |
177 | words.push(buf); | |
178 | } | |
179 | words.join("_") | |
180 | } | |
181 | ||
182 | fn check_snake_case(&self, cx: &LateContext, sort: &str, name: &str, span: Option<Span>) { | |
183 | fn is_snake_case(ident: &str) -> bool { | |
184 | if ident.is_empty() { | |
185 | return true; | |
186 | } | |
187 | let ident = ident.trim_left_matches('\''); | |
188 | let ident = ident.trim_matches('_'); | |
189 | ||
190 | let mut allow_underscore = true; | |
191 | ident.chars().all(|c| { | |
192 | allow_underscore = match c { | |
193 | '_' if !allow_underscore => return false, | |
194 | '_' => false, | |
195 | // It would be more obvious to use `c.is_lowercase()`, | |
196 | // but some characters do not have a lowercase form | |
197 | c if !c.is_uppercase() => true, | |
198 | _ => return false, | |
199 | }; | |
200 | true | |
201 | }) | |
202 | } | |
203 | ||
204 | if !is_snake_case(name) { | |
205 | let sc = NonSnakeCase::to_snake_case(name); | |
206 | let msg = if sc != name { | |
207 | format!("{} `{}` should have a snake case name such as `{}`", | |
208 | sort, name, sc) | |
209 | } else { | |
210 | format!("{} `{}` should have a snake case name", | |
211 | sort, name) | |
212 | }; | |
213 | match span { | |
214 | Some(span) => cx.span_lint(NON_SNAKE_CASE, span, &msg), | |
215 | None => cx.lint(NON_SNAKE_CASE, &msg), | |
216 | } | |
217 | } | |
218 | } | |
219 | } | |
220 | ||
221 | impl LintPass for NonSnakeCase { | |
222 | fn get_lints(&self) -> LintArray { | |
223 | lint_array!(NON_SNAKE_CASE) | |
224 | } | |
225 | } | |
226 | ||
227 | impl LateLintPass for NonSnakeCase { | |
228 | fn check_crate(&mut self, cx: &LateContext, cr: &hir::Crate) { | |
229 | let attr_crate_name = cr.attrs.iter().find(|at| at.check_name("crate_name")) | |
230 | .and_then(|at| at.value_str().map(|s| (at, s))); | |
231 | if let Some(ref name) = cx.tcx.sess.opts.crate_name { | |
232 | self.check_snake_case(cx, "crate", name, None); | |
233 | } else if let Some((attr, ref name)) = attr_crate_name { | |
234 | self.check_snake_case(cx, "crate", name, Some(attr.span)); | |
235 | } | |
236 | } | |
237 | ||
238 | fn check_fn(&mut self, cx: &LateContext, | |
239 | fk: FnKind, _: &hir::FnDecl, | |
240 | _: &hir::Block, span: Span, id: ast::NodeId) { | |
241 | match fk { | |
54a0048b | 242 | FnKind::Method(name, _, _, _) => match method_context(cx, id, span) { |
b039eaaf SL |
243 | MethodLateContext::PlainImpl => { |
244 | self.check_snake_case(cx, "method", &name.as_str(), Some(span)) | |
245 | }, | |
246 | MethodLateContext::TraitDefaultImpl => { | |
247 | self.check_snake_case(cx, "trait method", &name.as_str(), Some(span)) | |
248 | }, | |
249 | _ => (), | |
250 | }, | |
54a0048b | 251 | FnKind::ItemFn(name, _, _, _, _, _, _) => { |
b039eaaf SL |
252 | self.check_snake_case(cx, "function", &name.as_str(), Some(span)) |
253 | }, | |
54a0048b | 254 | FnKind::Closure(_) => (), |
b039eaaf SL |
255 | } |
256 | } | |
257 | ||
258 | fn check_item(&mut self, cx: &LateContext, it: &hir::Item) { | |
259 | if let hir::ItemMod(_) = it.node { | |
260 | self.check_snake_case(cx, "module", &it.name.as_str(), Some(it.span)); | |
261 | } | |
262 | } | |
263 | ||
264 | fn check_trait_item(&mut self, cx: &LateContext, trait_item: &hir::TraitItem) { | |
265 | if let hir::MethodTraitItem(_, None) = trait_item.node { | |
266 | self.check_snake_case(cx, "trait method", &trait_item.name.as_str(), | |
267 | Some(trait_item.span)); | |
268 | } | |
269 | } | |
270 | ||
271 | fn check_lifetime_def(&mut self, cx: &LateContext, t: &hir::LifetimeDef) { | |
272 | self.check_snake_case(cx, "lifetime", &t.lifetime.name.as_str(), | |
273 | Some(t.lifetime.span)); | |
274 | } | |
275 | ||
276 | fn check_pat(&mut self, cx: &LateContext, p: &hir::Pat) { | |
3157f602 XL |
277 | if let &PatKind::Binding(_, ref path1, _) = &p.node { |
278 | // Exclude parameter names from foreign functions (they have no `Def`) | |
279 | if cx.tcx.expect_def_or_none(p.id).is_some() { | |
a7813a04 | 280 | self.check_snake_case(cx, "variable", &path1.node.as_str(), Some(p.span)); |
b039eaaf SL |
281 | } |
282 | } | |
283 | } | |
284 | ||
285 | fn check_struct_def(&mut self, cx: &LateContext, s: &hir::VariantData, | |
286 | _: ast::Name, _: &hir::Generics, _: ast::NodeId) { | |
287 | for sf in s.fields() { | |
54a0048b | 288 | self.check_snake_case(cx, "structure field", &sf.name.as_str(), Some(sf.span)); |
b039eaaf SL |
289 | } |
290 | } | |
291 | } | |
292 | ||
293 | declare_lint! { | |
294 | pub NON_UPPER_CASE_GLOBALS, | |
295 | Warn, | |
296 | "static constants should have uppercase identifiers" | |
297 | } | |
298 | ||
299 | #[derive(Copy, Clone)] | |
300 | pub struct NonUpperCaseGlobals; | |
301 | ||
302 | impl NonUpperCaseGlobals { | |
303 | fn check_upper_case(cx: &LateContext, sort: &str, name: ast::Name, span: Span) { | |
304 | let s = name.as_str(); | |
305 | ||
306 | if s.chars().any(|c| c.is_lowercase()) { | |
307 | let uc = NonSnakeCase::to_snake_case(&s).to_uppercase(); | |
308 | if uc != &s[..] { | |
309 | cx.span_lint(NON_UPPER_CASE_GLOBALS, span, | |
310 | &format!("{} `{}` should have an upper case name such as `{}`", | |
311 | sort, s, uc)); | |
312 | } else { | |
313 | cx.span_lint(NON_UPPER_CASE_GLOBALS, span, | |
314 | &format!("{} `{}` should have an upper case name", | |
315 | sort, s)); | |
316 | } | |
317 | } | |
318 | } | |
319 | } | |
320 | ||
321 | impl LintPass for NonUpperCaseGlobals { | |
322 | fn get_lints(&self) -> LintArray { | |
323 | lint_array!(NON_UPPER_CASE_GLOBALS) | |
324 | } | |
325 | } | |
326 | ||
327 | impl LateLintPass for NonUpperCaseGlobals { | |
328 | fn check_item(&mut self, cx: &LateContext, it: &hir::Item) { | |
329 | match it.node { | |
330 | // only check static constants | |
331 | hir::ItemStatic(_, hir::MutImmutable, _) => { | |
332 | NonUpperCaseGlobals::check_upper_case(cx, "static constant", it.name, it.span); | |
333 | } | |
334 | hir::ItemConst(..) => { | |
335 | NonUpperCaseGlobals::check_upper_case(cx, "constant", it.name, it.span); | |
336 | } | |
337 | _ => {} | |
338 | } | |
339 | } | |
340 | ||
341 | fn check_trait_item(&mut self, cx: &LateContext, ti: &hir::TraitItem) { | |
342 | match ti.node { | |
343 | hir::ConstTraitItem(..) => { | |
344 | NonUpperCaseGlobals::check_upper_case(cx, "associated constant", | |
345 | ti.name, ti.span); | |
346 | } | |
347 | _ => {} | |
348 | } | |
349 | } | |
350 | ||
351 | fn check_impl_item(&mut self, cx: &LateContext, ii: &hir::ImplItem) { | |
352 | match ii.node { | |
92a42be0 | 353 | hir::ImplItemKind::Const(..) => { |
b039eaaf SL |
354 | NonUpperCaseGlobals::check_upper_case(cx, "associated constant", |
355 | ii.name, ii.span); | |
356 | } | |
357 | _ => {} | |
358 | } | |
359 | } | |
360 | ||
361 | fn check_pat(&mut self, cx: &LateContext, p: &hir::Pat) { | |
362 | // Lint for constants that look like binding identifiers (#7526) | |
5bcae85e | 363 | if let PatKind::Path(None, ref path) = p.node { |
3157f602 XL |
364 | if !path.global && path.segments.len() == 1 && path.segments[0].parameters.is_empty() { |
365 | if let Def::Const(..) = cx.tcx.expect_def(p.id) { | |
366 | NonUpperCaseGlobals::check_upper_case(cx, "constant in pattern", | |
367 | path.segments[0].name, path.span); | |
368 | } | |
b039eaaf | 369 | } |
b039eaaf SL |
370 | } |
371 | } | |
372 | } |