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(..)
176            | ast::ItemKind::Enum(..)
177            | ast::ItemKind::Struct(..)
178            | ast::ItemKind::Union(..) => self.check_case(cx, "type", &it.ident),
179            ast::ItemKind::Trait(..) => self.check_case(cx, "trait", &it.ident),
180            ast::ItemKind::TraitAlias(..) => self.check_case(cx, "trait alias", &it.ident),
181
182            // N.B. This check is only for inherent associated types, so that we don't lint against
183            // trait impls where we should have warned for the trait definition already.
184            ast::ItemKind::Impl(box ast::Impl { of_trait: None, items, .. }) => {
185                for it in items {
186                    // FIXME: this doesn't respect `#[allow(..)]` on the item itself.
187                    if let ast::AssocItemKind::Type(..) = it.kind {
188                        self.check_case(cx, "associated type", &it.ident);
189                    }
190                }
191            }
192            _ => (),
193        }
194    }
195
196    fn check_trait_item(&mut self, cx: &EarlyContext<'_>, it: &ast::AssocItem) {
197        if let ast::AssocItemKind::Type(..) = it.kind {
198            self.check_case(cx, "associated type", &it.ident);
199        }
200    }
201
202    fn check_variant(&mut self, cx: &EarlyContext<'_>, v: &ast::Variant) {
203        self.check_case(cx, "variant", &v.ident);
204    }
205
206    fn check_generic_param(&mut self, cx: &EarlyContext<'_>, param: &ast::GenericParam) {
207        if let ast::GenericParamKind::Type { .. } = param.kind {
208            self.check_case(cx, "type parameter", &param.ident);
209        }
210    }
211}
212
213declare_lint! {
214    /// The `non_snake_case` lint detects variables, methods, functions,
215    /// lifetime parameters and modules that don't have snake case names.
216    ///
217    /// ### Example
218    ///
219    /// ```rust
220    /// let MY_VALUE = 5;
221    /// ```
222    ///
223    /// {{produces}}
224    ///
225    /// ### Explanation
226    ///
227    /// The preferred style for these identifiers is to use "snake case",
228    /// where all the characters are in lowercase, with words separated with a
229    /// single underscore, such as `my_value`.
230    pub NON_SNAKE_CASE,
231    Warn,
232    "variables, methods, functions, lifetime parameters and modules should have snake case names"
233}
234
235declare_lint_pass!(NonSnakeCase => [NON_SNAKE_CASE]);
236
237impl NonSnakeCase {
238    fn to_snake_case(mut name: &str) -> String {
239        let mut words = vec![];
240        // Preserve leading underscores
241        name = name.trim_start_matches(|c: char| {
242            if c == '_' {
243                words.push(String::new());
244                true
245            } else {
246                false
247            }
248        });
249        for s in name.split('_') {
250            let mut last_upper = false;
251            let mut buf = String::new();
252            if s.is_empty() {
253                continue;
254            }
255            for ch in s.chars() {
256                if !buf.is_empty() && buf != "'" && ch.is_uppercase() && !last_upper {
257                    words.push(buf);
258                    buf = String::new();
259                }
260                last_upper = ch.is_uppercase();
261                buf.extend(ch.to_lowercase());
262            }
263            words.push(buf);
264        }
265        words.join("_")
266    }
267
268    /// Checks if a given identifier is snake case, and reports a diagnostic if not.
269    fn check_snake_case(&self, cx: &LateContext<'_>, sort: &str, ident: &Ident) {
270        fn is_snake_case(ident: &str) -> bool {
271            if ident.is_empty() {
272                return true;
273            }
274            let ident = ident.trim_start_matches('\'');
275            let ident = ident.trim_matches('_');
276
277            if ident.contains("__") {
278                return false;
279            }
280
281            // This correctly handles letters in languages with and without
282            // cases, as well as numbers and underscores.
283            !ident.chars().any(char::is_uppercase)
284        }
285
286        let name = ident.name.as_str();
287
288        if !is_snake_case(name) {
289            let span = ident.span;
290            let sc = NonSnakeCase::to_snake_case(name);
291            // We cannot provide meaningful suggestions
292            // if the characters are in the category of "Uppercase Letter".
293            let sub = if name != sc {
294                // We have a valid span in almost all cases, but we don't have one when linting a
295                // crate name provided via the command line.
296                if !span.is_dummy() {
297                    let sc_ident = Ident::from_str_and_span(&sc, span);
298                    if sc_ident.is_reserved() {
299                        // We shouldn't suggest a reserved identifier to fix non-snake-case
300                        // identifiers. Instead, recommend renaming the identifier entirely or, if
301                        // permitted, escaping it to create a raw identifier.
302                        if sc_ident.name.can_be_raw() {
303                            NonSnakeCaseDiagSub::RenameOrConvertSuggestion {
304                                span,
305                                suggestion: sc_ident,
306                            }
307                        } else {
308                            NonSnakeCaseDiagSub::SuggestionAndNote { span }
309                        }
310                    } else {
311                        NonSnakeCaseDiagSub::ConvertSuggestion { span, suggestion: sc.clone() }
312                    }
313                } else {
314                    NonSnakeCaseDiagSub::Help
315                }
316            } else {
317                NonSnakeCaseDiagSub::Label { span }
318            };
319            cx.emit_span_lint(NON_SNAKE_CASE, span, NonSnakeCaseDiag { sort, name, sc, sub });
320        }
321    }
322}
323
324impl<'tcx> LateLintPass<'tcx> for NonSnakeCase {
325    fn check_mod(&mut self, cx: &LateContext<'_>, _: &'tcx hir::Mod<'tcx>, id: hir::HirId) {
326        if id != hir::CRATE_HIR_ID {
327            return;
328        }
329
330        // Issue #45127: don't enforce `snake_case` for binary crates as binaries are not intended
331        // to be distributed and depended on like libraries. The lint is not suppressed for cdylib
332        // or staticlib because it's not clear what the desired lint behavior for those are.
333        if cx.tcx.crate_types().iter().all(|&crate_type| crate_type == CrateType::Executable) {
334            return;
335        }
336
337        let crate_ident = if let Some(name) = &cx.tcx.sess.opts.crate_name {
338            Some(Ident::from_str(name))
339        } else {
340            ast::attr::find_by_name(cx.tcx.hir_attrs(hir::CRATE_HIR_ID), sym::crate_name).and_then(
341                |attr| {
342                    if let Attribute::Unparsed(n) = attr
343                        && let AttrItem { args: AttrArgs::Eq { eq_span: _, expr: lit }, .. } =
344                            n.as_ref()
345                        && let ast::LitKind::Str(name, ..) = lit.kind
346                    {
347                        // Discard the double quotes surrounding the literal.
348                        let sp = cx
349                            .sess()
350                            .source_map()
351                            .span_to_snippet(lit.span)
352                            .ok()
353                            .and_then(|snippet| {
354                                let left = snippet.find('"')?;
355                                let right = snippet.rfind('"').map(|pos| snippet.len() - pos)?;
356
357                                Some(
358                                    lit.span
359                                        .with_lo(lit.span.lo() + BytePos(left as u32 + 1))
360                                        .with_hi(lit.span.hi() - BytePos(right as u32)),
361                                )
362                            })
363                            .unwrap_or(lit.span);
364
365                        Some(Ident::new(name, sp))
366                    } else {
367                        None
368                    }
369                },
370            )
371        };
372
373        if let Some(ident) = &crate_ident {
374            self.check_snake_case(cx, "crate", ident);
375        }
376    }
377
378    fn check_generic_param(&mut self, cx: &LateContext<'_>, param: &hir::GenericParam<'_>) {
379        if let GenericParamKind::Lifetime { .. } = param.kind {
380            self.check_snake_case(cx, "lifetime", &param.name.ident());
381        }
382    }
383
384    fn check_fn(
385        &mut self,
386        cx: &LateContext<'_>,
387        fk: FnKind<'_>,
388        _: &hir::FnDecl<'_>,
389        _: &hir::Body<'_>,
390        _: Span,
391        id: LocalDefId,
392    ) {
393        match &fk {
394            FnKind::Method(ident, sig, ..) => match method_context(cx, id) {
395                MethodLateContext::PlainImpl => {
396                    if sig.header.abi != ExternAbi::Rust && cx.tcx.has_attr(id, sym::no_mangle) {
397                        return;
398                    }
399                    self.check_snake_case(cx, "method", ident);
400                }
401                MethodLateContext::TraitAutoImpl => {
402                    self.check_snake_case(cx, "trait method", ident);
403                }
404                _ => (),
405            },
406            FnKind::ItemFn(ident, _, header) => {
407                // Skip foreign-ABI #[no_mangle] functions (Issue #31924)
408                if header.abi != ExternAbi::Rust && cx.tcx.has_attr(id, sym::no_mangle) {
409                    return;
410                }
411                self.check_snake_case(cx, "function", ident);
412            }
413            FnKind::Closure => (),
414        }
415    }
416
417    fn check_item(&mut self, cx: &LateContext<'_>, it: &hir::Item<'_>) {
418        if let hir::ItemKind::Mod(ident, _) = it.kind {
419            self.check_snake_case(cx, "module", &ident);
420        }
421    }
422
423    fn check_trait_item(&mut self, cx: &LateContext<'_>, item: &hir::TraitItem<'_>) {
424        if let hir::TraitItemKind::Fn(_, hir::TraitFn::Required(pnames)) = item.kind {
425            self.check_snake_case(cx, "trait method", &item.ident);
426            for param_name in pnames {
427                if let Some(param_name) = param_name {
428                    self.check_snake_case(cx, "variable", param_name);
429                }
430            }
431        }
432    }
433
434    fn check_pat(&mut self, cx: &LateContext<'_>, p: &hir::Pat<'_>) {
435        if let PatKind::Binding(_, hid, ident, _) = p.kind {
436            if let hir::Node::PatField(field) = cx.tcx.parent_hir_node(hid) {
437                if !field.is_shorthand {
438                    // Only check if a new name has been introduced, to avoid warning
439                    // on both the struct definition and this pattern.
440                    self.check_snake_case(cx, "variable", &ident);
441                }
442                return;
443            }
444            self.check_snake_case(cx, "variable", &ident);
445        }
446    }
447
448    fn check_struct_def(&mut self, cx: &LateContext<'_>, s: &hir::VariantData<'_>) {
449        for sf in s.fields() {
450            self.check_snake_case(cx, "structure field", &sf.ident);
451        }
452    }
453}
454
455declare_lint! {
456    /// The `non_upper_case_globals` lint detects static items that don't have
457    /// uppercase identifiers.
458    ///
459    /// ### Example
460    ///
461    /// ```rust
462    /// static max_points: i32 = 5;
463    /// ```
464    ///
465    /// {{produces}}
466    ///
467    /// ### Explanation
468    ///
469    /// The preferred style is for static item names to use all uppercase
470    /// letters such as `MAX_POINTS`.
471    pub NON_UPPER_CASE_GLOBALS,
472    Warn,
473    "static constants should have uppercase identifiers"
474}
475
476declare_lint_pass!(NonUpperCaseGlobals => [NON_UPPER_CASE_GLOBALS]);
477
478impl NonUpperCaseGlobals {
479    fn check_upper_case(cx: &LateContext<'_>, sort: &str, ident: &Ident) {
480        let name = ident.name.as_str();
481        if name.chars().any(|c| c.is_lowercase()) {
482            let uc = NonSnakeCase::to_snake_case(name).to_uppercase();
483            // We cannot provide meaningful suggestions
484            // if the characters are in the category of "Lowercase Letter".
485            let sub = if *name != uc {
486                NonUpperCaseGlobalSub::Suggestion { span: ident.span, replace: uc }
487            } else {
488                NonUpperCaseGlobalSub::Label { span: ident.span }
489            };
490            cx.emit_span_lint(
491                NON_UPPER_CASE_GLOBALS,
492                ident.span,
493                NonUpperCaseGlobal { sort, name, sub },
494            );
495        }
496    }
497}
498
499impl<'tcx> LateLintPass<'tcx> for NonUpperCaseGlobals {
500    fn check_item(&mut self, cx: &LateContext<'_>, it: &hir::Item<'_>) {
501        let attrs = cx.tcx.hir_attrs(it.hir_id());
502        match it.kind {
503            hir::ItemKind::Static(ident, ..)
504                if !ast::attr::contains_name(attrs, sym::no_mangle) =>
505            {
506                NonUpperCaseGlobals::check_upper_case(cx, "static variable", &ident);
507            }
508            hir::ItemKind::Const(ident, ..) => {
509                NonUpperCaseGlobals::check_upper_case(cx, "constant", &ident);
510            }
511            _ => {}
512        }
513    }
514
515    fn check_trait_item(&mut self, cx: &LateContext<'_>, ti: &hir::TraitItem<'_>) {
516        if let hir::TraitItemKind::Const(..) = ti.kind {
517            NonUpperCaseGlobals::check_upper_case(cx, "associated constant", &ti.ident);
518        }
519    }
520
521    fn check_impl_item(&mut self, cx: &LateContext<'_>, ii: &hir::ImplItem<'_>) {
522        if let hir::ImplItemKind::Const(..) = ii.kind
523            && !assoc_item_in_trait_impl(cx, ii)
524        {
525            NonUpperCaseGlobals::check_upper_case(cx, "associated constant", &ii.ident);
526        }
527    }
528
529    fn check_pat(&mut self, cx: &LateContext<'_>, p: &hir::Pat<'_>) {
530        // Lint for constants that look like binding identifiers (#7526)
531        if let PatKind::Expr(hir::PatExpr {
532            kind: PatExprKind::Path(hir::QPath::Resolved(None, path)),
533            ..
534        }) = p.kind
535        {
536            if let Res::Def(DefKind::Const, _) = path.res {
537                if let [segment] = path.segments {
538                    NonUpperCaseGlobals::check_upper_case(
539                        cx,
540                        "constant in pattern",
541                        &segment.ident,
542                    );
543                }
544            }
545        }
546    }
547
548    fn check_generic_param(&mut self, cx: &LateContext<'_>, param: &hir::GenericParam<'_>) {
549        if let GenericParamKind::Const { .. } = param.kind {
550            NonUpperCaseGlobals::check_upper_case(cx, "const parameter", &param.name.ident());
551        }
552    }
553}
554
555#[cfg(test)]
556mod tests;