rustc_lint/
nonstandard_style.rs

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