]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_lint/src/nonstandard_style.rs
Rollup merge of #107656 - jonhoo:bump-rust-installer, r=Mark-Simulacrum
[rust.git] / compiler / rustc_lint / src / nonstandard_style.rs
1 use crate::lints::{
2     NonCamelCaseType, NonCamelCaseTypeSub, NonSnakeCaseDiag, NonSnakeCaseDiagSub,
3     NonUpperCaseGlobal, NonUpperCaseGlobalSub,
4 };
5 use crate::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext};
6 use rustc_ast as ast;
7 use rustc_attr as attr;
8 use rustc_hir as hir;
9 use rustc_hir::def::{DefKind, Res};
10 use rustc_hir::intravisit::FnKind;
11 use rustc_hir::{GenericParamKind, PatKind};
12 use rustc_middle::ty;
13 use rustc_span::def_id::LocalDefId;
14 use rustc_span::symbol::{sym, Ident};
15 use rustc_span::{BytePos, Span};
16 use rustc_target::spec::abi::Abi;
17
18 #[derive(PartialEq)]
19 pub enum MethodLateContext {
20     TraitAutoImpl,
21     TraitImpl,
22     PlainImpl,
23 }
24
25 pub fn method_context(cx: &LateContext<'_>, id: LocalDefId) -> MethodLateContext {
26     let item = cx.tcx.associated_item(id);
27     match item.container {
28         ty::TraitContainer => MethodLateContext::TraitAutoImpl,
29         ty::ImplContainer => match cx.tcx.impl_trait_ref(item.container_id(cx.tcx)) {
30             Some(_) => MethodLateContext::TraitImpl,
31             None => MethodLateContext::PlainImpl,
32         },
33     }
34 }
35
36 declare_lint! {
37     /// The `non_camel_case_types` lint detects types, variants, traits and
38     /// type parameters that don't have camel case names.
39     ///
40     /// ### Example
41     ///
42     /// ```rust
43     /// struct my_struct;
44     /// ```
45     ///
46     /// {{produces}}
47     ///
48     /// ### Explanation
49     ///
50     /// The preferred style for these identifiers is to use "camel case", such
51     /// as `MyStruct`, where the first letter should not be lowercase, and
52     /// should not use underscores between letters. Underscores are allowed at
53     /// the beginning and end of the identifier, as well as between
54     /// non-letters (such as `X86_64`).
55     pub NON_CAMEL_CASE_TYPES,
56     Warn,
57     "types, variants, traits and type parameters should have camel case names"
58 }
59
60 declare_lint_pass!(NonCamelCaseTypes => [NON_CAMEL_CASE_TYPES]);
61
62 /// Some unicode characters *have* case, are considered upper case or lower case, but they *can't*
63 /// be upper cased or lower cased. For the purposes of the lint suggestion, we care about being able
64 /// to change the char's case.
65 fn char_has_case(c: char) -> bool {
66     let mut l = c.to_lowercase();
67     let mut u = c.to_uppercase();
68     while let Some(l) = l.next() {
69         match u.next() {
70             Some(u) if l != u => return true,
71             _ => {}
72         }
73     }
74     u.next().is_some()
75 }
76
77 fn is_camel_case(name: &str) -> bool {
78     let name = name.trim_matches('_');
79     if name.is_empty() {
80         return true;
81     }
82
83     // start with a non-lowercase letter rather than non-uppercase
84     // ones (some scripts don't have a concept of upper/lowercase)
85     !name.chars().next().unwrap().is_lowercase()
86         && !name.contains("__")
87         && !name.chars().collect::<Vec<_>>().array_windows().any(|&[fst, snd]| {
88             // contains a capitalisable character followed by, or preceded by, an underscore
89             char_has_case(fst) && snd == '_' || char_has_case(snd) && fst == '_'
90         })
91 }
92
93 fn to_camel_case(s: &str) -> String {
94     s.trim_matches('_')
95         .split('_')
96         .filter(|component| !component.is_empty())
97         .map(|component| {
98             let mut camel_cased_component = String::new();
99
100             let mut new_word = true;
101             let mut prev_is_lower_case = true;
102
103             for c in component.chars() {
104                 // Preserve the case if an uppercase letter follows a lowercase letter, so that
105                 // `camelCase` is converted to `CamelCase`.
106                 if prev_is_lower_case && c.is_uppercase() {
107                     new_word = true;
108                 }
109
110                 if new_word {
111                     camel_cased_component.extend(c.to_uppercase());
112                 } else {
113                     camel_cased_component.extend(c.to_lowercase());
114                 }
115
116                 prev_is_lower_case = c.is_lowercase();
117                 new_word = false;
118             }
119
120             camel_cased_component
121         })
122         .fold((String::new(), None), |(acc, prev): (String, Option<String>), next| {
123             // separate two components with an underscore if their boundary cannot
124             // be distinguished using an uppercase/lowercase case distinction
125             let join = if let Some(prev) = prev {
126                 let l = prev.chars().last().unwrap();
127                 let f = next.chars().next().unwrap();
128                 !char_has_case(l) && !char_has_case(f)
129             } else {
130                 false
131             };
132             (acc + if join { "_" } else { "" } + &next, Some(next))
133         })
134         .0
135 }
136
137 impl NonCamelCaseTypes {
138     fn check_case(&self, cx: &EarlyContext<'_>, sort: &str, ident: &Ident) {
139         let name = ident.name.as_str();
140
141         if !is_camel_case(name) {
142             let cc = to_camel_case(name);
143             let sub = if *name != cc {
144                 NonCamelCaseTypeSub::Suggestion { span: ident.span, replace: cc }
145             } else {
146                 NonCamelCaseTypeSub::Label { span: ident.span }
147             };
148             cx.emit_spanned_lint(
149                 NON_CAMEL_CASE_TYPES,
150                 ident.span,
151                 NonCamelCaseType { sort, name, sub },
152             );
153         }
154     }
155 }
156
157 impl EarlyLintPass for NonCamelCaseTypes {
158     fn check_item(&mut self, cx: &EarlyContext<'_>, it: &ast::Item) {
159         let has_repr_c = it
160             .attrs
161             .iter()
162             .any(|attr| attr::find_repr_attrs(cx.sess(), attr).contains(&attr::ReprC));
163
164         if has_repr_c {
165             return;
166         }
167
168         match &it.kind {
169             ast::ItemKind::TyAlias(..)
170             | ast::ItemKind::Enum(..)
171             | ast::ItemKind::Struct(..)
172             | ast::ItemKind::Union(..) => self.check_case(cx, "type", &it.ident),
173             ast::ItemKind::Trait(..) => self.check_case(cx, "trait", &it.ident),
174             ast::ItemKind::TraitAlias(..) => self.check_case(cx, "trait alias", &it.ident),
175
176             // N.B. This check is only for inherent associated types, so that we don't lint against
177             // trait impls where we should have warned for the trait definition already.
178             ast::ItemKind::Impl(box ast::Impl { of_trait: None, items, .. }) => {
179                 for it in items {
180                     if let ast::AssocItemKind::Type(..) = it.kind {
181                         self.check_case(cx, "associated type", &it.ident);
182                     }
183                 }
184             }
185             _ => (),
186         }
187     }
188
189     fn check_trait_item(&mut self, cx: &EarlyContext<'_>, it: &ast::AssocItem) {
190         if let ast::AssocItemKind::Type(..) = it.kind {
191             self.check_case(cx, "associated type", &it.ident);
192         }
193     }
194
195     fn check_variant(&mut self, cx: &EarlyContext<'_>, v: &ast::Variant) {
196         self.check_case(cx, "variant", &v.ident);
197     }
198
199     fn check_generic_param(&mut self, cx: &EarlyContext<'_>, param: &ast::GenericParam) {
200         if let ast::GenericParamKind::Type { .. } = param.kind {
201             self.check_case(cx, "type parameter", &param.ident);
202         }
203     }
204 }
205
206 declare_lint! {
207     /// The `non_snake_case` lint detects variables, methods, functions,
208     /// lifetime parameters and modules that don't have snake case names.
209     ///
210     /// ### Example
211     ///
212     /// ```rust
213     /// let MY_VALUE = 5;
214     /// ```
215     ///
216     /// {{produces}}
217     ///
218     /// ### Explanation
219     ///
220     /// The preferred style for these identifiers is to use "snake case",
221     /// where all the characters are in lowercase, with words separated with a
222     /// single underscore, such as `my_value`.
223     pub NON_SNAKE_CASE,
224     Warn,
225     "variables, methods, functions, lifetime parameters and modules should have snake case names"
226 }
227
228 declare_lint_pass!(NonSnakeCase => [NON_SNAKE_CASE]);
229
230 impl NonSnakeCase {
231     fn to_snake_case(mut str: &str) -> String {
232         let mut words = vec![];
233         // Preserve leading underscores
234         str = str.trim_start_matches(|c: char| {
235             if c == '_' {
236                 words.push(String::new());
237                 true
238             } else {
239                 false
240             }
241         });
242         for s in str.split('_') {
243             let mut last_upper = false;
244             let mut buf = String::new();
245             if s.is_empty() {
246                 continue;
247             }
248             for ch in s.chars() {
249                 if !buf.is_empty() && buf != "'" && ch.is_uppercase() && !last_upper {
250                     words.push(buf);
251                     buf = String::new();
252                 }
253                 last_upper = ch.is_uppercase();
254                 buf.extend(ch.to_lowercase());
255             }
256             words.push(buf);
257         }
258         words.join("_")
259     }
260
261     /// Checks if a given identifier is snake case, and reports a diagnostic if not.
262     fn check_snake_case(&self, cx: &LateContext<'_>, sort: &str, ident: &Ident) {
263         fn is_snake_case(ident: &str) -> bool {
264             if ident.is_empty() {
265                 return true;
266             }
267             let ident = ident.trim_start_matches('\'');
268             let ident = ident.trim_matches('_');
269
270             let mut allow_underscore = true;
271             ident.chars().all(|c| {
272                 allow_underscore = match c {
273                     '_' if !allow_underscore => return false,
274                     '_' => false,
275                     // It would be more obvious to use `c.is_lowercase()`,
276                     // but some characters do not have a lowercase form
277                     c if !c.is_uppercase() => true,
278                     _ => return false,
279                 };
280                 true
281             })
282         }
283
284         let name = ident.name.as_str();
285
286         if !is_snake_case(name) {
287             let span = ident.span;
288             let sc = NonSnakeCase::to_snake_case(name);
289             // We cannot provide meaningful suggestions
290             // if the characters are in the category of "Uppercase Letter".
291             let sub = if name != sc {
292                 // We have a valid span in almost all cases, but we don't have one when linting a crate
293                 // name provided via the command line.
294                 if !span.is_dummy() {
295                     let sc_ident = Ident::from_str_and_span(&sc, span);
296                     if sc_ident.is_reserved() {
297                         // We shouldn't suggest a reserved identifier to fix non-snake-case identifiers.
298                         // Instead, recommend renaming the identifier entirely or, if permitted,
299                         // escaping it to create a raw identifier.
300                         if sc_ident.name.can_be_raw() {
301                             NonSnakeCaseDiagSub::RenameOrConvertSuggestion {
302                                 span,
303                                 suggestion: sc_ident,
304                             }
305                         } else {
306                             NonSnakeCaseDiagSub::SuggestionAndNote { span }
307                         }
308                     } else {
309                         NonSnakeCaseDiagSub::ConvertSuggestion { span, suggestion: sc.clone() }
310                     }
311                 } else {
312                     NonSnakeCaseDiagSub::Help
313                 }
314             } else {
315                 NonSnakeCaseDiagSub::Label { span }
316             };
317             cx.emit_spanned_lint(NON_SNAKE_CASE, span, NonSnakeCaseDiag { sort, name, sc, sub });
318         }
319     }
320 }
321
322 impl<'tcx> LateLintPass<'tcx> for NonSnakeCase {
323     fn check_mod(&mut self, cx: &LateContext<'_>, _: &'tcx hir::Mod<'tcx>, id: hir::HirId) {
324         if id != hir::CRATE_HIR_ID {
325             return;
326         }
327
328         let crate_ident = if let Some(name) = &cx.tcx.sess.opts.crate_name {
329             Some(Ident::from_str(name))
330         } else {
331             cx.sess()
332                 .find_by_name(&cx.tcx.hir().attrs(hir::CRATE_HIR_ID), sym::crate_name)
333                 .and_then(|attr| attr.meta())
334                 .and_then(|meta| {
335                     meta.name_value_literal().and_then(|lit| {
336                         if let ast::LitKind::Str(name, ..) = lit.kind {
337                             // Discard the double quotes surrounding the literal.
338                             let sp = cx
339                                 .sess()
340                                 .source_map()
341                                 .span_to_snippet(lit.span)
342                                 .ok()
343                                 .and_then(|snippet| {
344                                     let left = snippet.find('"')?;
345                                     let right =
346                                         snippet.rfind('"').map(|pos| snippet.len() - pos)?;
347
348                                     Some(
349                                         lit.span
350                                             .with_lo(lit.span.lo() + BytePos(left as u32 + 1))
351                                             .with_hi(lit.span.hi() - BytePos(right as u32)),
352                                     )
353                                 })
354                                 .unwrap_or(lit.span);
355
356                             Some(Ident::new(name, sp))
357                         } else {
358                             None
359                         }
360                     })
361                 })
362         };
363
364         if let Some(ident) = &crate_ident {
365             self.check_snake_case(cx, "crate", ident);
366         }
367     }
368
369     fn check_generic_param(&mut self, cx: &LateContext<'_>, param: &hir::GenericParam<'_>) {
370         if let GenericParamKind::Lifetime { .. } = param.kind {
371             self.check_snake_case(cx, "lifetime", &param.name.ident());
372         }
373     }
374
375     fn check_fn(
376         &mut self,
377         cx: &LateContext<'_>,
378         fk: FnKind<'_>,
379         _: &hir::FnDecl<'_>,
380         _: &hir::Body<'_>,
381         _: Span,
382         id: LocalDefId,
383     ) {
384         match &fk {
385             FnKind::Method(ident, sig, ..) => match method_context(cx, id) {
386                 MethodLateContext::PlainImpl => {
387                     if sig.header.abi != Abi::Rust
388                         && cx.tcx.has_attr(id.to_def_id(), sym::no_mangle)
389                     {
390                         return;
391                     }
392                     self.check_snake_case(cx, "method", ident);
393                 }
394                 MethodLateContext::TraitAutoImpl => {
395                     self.check_snake_case(cx, "trait method", ident);
396                 }
397                 _ => (),
398             },
399             FnKind::ItemFn(ident, _, header) => {
400                 // Skip foreign-ABI #[no_mangle] functions (Issue #31924)
401                 if header.abi != Abi::Rust && cx.tcx.has_attr(id.to_def_id(), sym::no_mangle) {
402                     return;
403                 }
404                 self.check_snake_case(cx, "function", ident);
405             }
406             FnKind::Closure => (),
407         }
408     }
409
410     fn check_item(&mut self, cx: &LateContext<'_>, it: &hir::Item<'_>) {
411         if let hir::ItemKind::Mod(_) = it.kind {
412             self.check_snake_case(cx, "module", &it.ident);
413         }
414     }
415
416     fn check_trait_item(&mut self, cx: &LateContext<'_>, item: &hir::TraitItem<'_>) {
417         if let hir::TraitItemKind::Fn(_, hir::TraitFn::Required(pnames)) = item.kind {
418             self.check_snake_case(cx, "trait method", &item.ident);
419             for param_name in pnames {
420                 self.check_snake_case(cx, "variable", param_name);
421             }
422         }
423     }
424
425     fn check_pat(&mut self, cx: &LateContext<'_>, p: &hir::Pat<'_>) {
426         if let PatKind::Binding(_, hid, ident, _) = p.kind {
427             if let hir::Node::PatField(field) = cx.tcx.hir().get_parent(hid) {
428                 if !field.is_shorthand {
429                     // Only check if a new name has been introduced, to avoid warning
430                     // on both the struct definition and this pattern.
431                     self.check_snake_case(cx, "variable", &ident);
432                 }
433                 return;
434             }
435             self.check_snake_case(cx, "variable", &ident);
436         }
437     }
438
439     fn check_struct_def(&mut self, cx: &LateContext<'_>, s: &hir::VariantData<'_>) {
440         for sf in s.fields() {
441             self.check_snake_case(cx, "structure field", &sf.ident);
442         }
443     }
444 }
445
446 declare_lint! {
447     /// The `non_upper_case_globals` lint detects static items that don't have
448     /// uppercase identifiers.
449     ///
450     /// ### Example
451     ///
452     /// ```rust
453     /// static max_points: i32 = 5;
454     /// ```
455     ///
456     /// {{produces}}
457     ///
458     /// ### Explanation
459     ///
460     /// The preferred style is for static item names to use all uppercase
461     /// letters such as `MAX_POINTS`.
462     pub NON_UPPER_CASE_GLOBALS,
463     Warn,
464     "static constants should have uppercase identifiers"
465 }
466
467 declare_lint_pass!(NonUpperCaseGlobals => [NON_UPPER_CASE_GLOBALS]);
468
469 impl NonUpperCaseGlobals {
470     fn check_upper_case(cx: &LateContext<'_>, sort: &str, ident: &Ident) {
471         let name = ident.name.as_str();
472         if name.chars().any(|c| c.is_lowercase()) {
473             let uc = NonSnakeCase::to_snake_case(&name).to_uppercase();
474             // We cannot provide meaningful suggestions
475             // if the characters are in the category of "Lowercase Letter".
476             let sub = if *name != uc {
477                 NonUpperCaseGlobalSub::Suggestion { span: ident.span, replace: uc }
478             } else {
479                 NonUpperCaseGlobalSub::Label { span: ident.span }
480             };
481             cx.emit_spanned_lint(
482                 NON_UPPER_CASE_GLOBALS,
483                 ident.span,
484                 NonUpperCaseGlobal { sort, name, sub },
485             );
486         }
487     }
488 }
489
490 impl<'tcx> LateLintPass<'tcx> for NonUpperCaseGlobals {
491     fn check_item(&mut self, cx: &LateContext<'_>, it: &hir::Item<'_>) {
492         let attrs = cx.tcx.hir().attrs(it.hir_id());
493         match it.kind {
494             hir::ItemKind::Static(..) if !cx.sess().contains_name(attrs, sym::no_mangle) => {
495                 NonUpperCaseGlobals::check_upper_case(cx, "static variable", &it.ident);
496             }
497             hir::ItemKind::Const(..) => {
498                 NonUpperCaseGlobals::check_upper_case(cx, "constant", &it.ident);
499             }
500             _ => {}
501         }
502     }
503
504     fn check_trait_item(&mut self, cx: &LateContext<'_>, ti: &hir::TraitItem<'_>) {
505         if let hir::TraitItemKind::Const(..) = ti.kind {
506             NonUpperCaseGlobals::check_upper_case(cx, "associated constant", &ti.ident);
507         }
508     }
509
510     fn check_impl_item(&mut self, cx: &LateContext<'_>, ii: &hir::ImplItem<'_>) {
511         if let hir::ImplItemKind::Const(..) = ii.kind {
512             NonUpperCaseGlobals::check_upper_case(cx, "associated constant", &ii.ident);
513         }
514     }
515
516     fn check_pat(&mut self, cx: &LateContext<'_>, p: &hir::Pat<'_>) {
517         // Lint for constants that look like binding identifiers (#7526)
518         if let PatKind::Path(hir::QPath::Resolved(None, ref path)) = p.kind {
519             if let Res::Def(DefKind::Const, _) = path.res {
520                 if path.segments.len() == 1 {
521                     NonUpperCaseGlobals::check_upper_case(
522                         cx,
523                         "constant in pattern",
524                         &path.segments[0].ident,
525                     );
526                 }
527             }
528         }
529     }
530
531     fn check_generic_param(&mut self, cx: &LateContext<'_>, param: &hir::GenericParam<'_>) {
532         if let GenericParamKind::Const { .. } = param.kind {
533             NonUpperCaseGlobals::check_upper_case(cx, "const parameter", &param.name.ident());
534         }
535     }
536 }
537
538 #[cfg(test)]
539 mod tests;