1use rustc_abi::ExternAbi;
2use rustc_attr_parsing::AttributeParser;
3use rustc_errors::Applicability;
4use rustc_hir::attrs::{AttributeKind, ReprAttr};
5use rustc_hir::def::{DefKind, Res};
6use rustc_hir::def_id::DefId;
7use rustc_hir::intravisit::{FnKind, Visitor};
8use rustc_hir::{Attribute, GenericParamKind, PatExprKind, PatKind, find_attr};
9use rustc_middle::hir::nested_filter::All;
10use rustc_middle::ty::AssocContainer;
11use rustc_session::config::CrateType;
12use rustc_session::{declare_lint, declare_lint_pass};
13use rustc_span::def_id::LocalDefId;
14use rustc_span::{BytePos, Ident, Span, sym};
15use {rustc_ast as ast, rustc_hir as hir};
16
17use crate::lints::{
18 NonCamelCaseType, NonCamelCaseTypeSub, NonSnakeCaseDiag, NonSnakeCaseDiagSub,
19 NonUpperCaseGlobal, NonUpperCaseGlobalSub, NonUpperCaseGlobalSubTool,
20};
21use crate::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext};
22
23declare_lint! {
24 pub NON_CAMEL_CASE_TYPES,
43 Warn,
44 "types, variants, traits and type parameters should have camel case names"
45}
46
47declare_lint_pass!(NonCamelCaseTypes => [NON_CAMEL_CASE_TYPES]);
48
49fn char_has_case(c: char) -> bool {
53 let mut l = c.to_lowercase();
54 let mut u = c.to_uppercase();
55 while let Some(l) = l.next() {
56 match u.next() {
57 Some(u) if l != u => return true,
58 _ => {}
59 }
60 }
61 u.next().is_some()
62}
63
64fn is_camel_case(name: &str) -> bool {
65 let name = name.trim_matches('_');
66 if name.is_empty() {
67 return true;
68 }
69
70 !name.chars().next().unwrap().is_lowercase()
73 && !name.contains("__")
74 && !name.chars().collect::<Vec<_>>().array_windows().any(|&[fst, snd]| {
75 char_has_case(fst) && snd == '_' || char_has_case(snd) && fst == '_'
77 })
78}
79
80fn to_camel_case(s: &str) -> String {
81 s.trim_matches('_')
82 .split('_')
83 .filter(|component| !component.is_empty())
84 .map(|component| {
85 let mut camel_cased_component = String::new();
86
87 let mut new_word = true;
88 let mut prev_is_lower_case = true;
89
90 for c in component.chars() {
91 if prev_is_lower_case && c.is_uppercase() {
94 new_word = true;
95 }
96
97 if new_word {
98 camel_cased_component.extend(c.to_uppercase());
99 } else {
100 camel_cased_component.extend(c.to_lowercase());
101 }
102
103 prev_is_lower_case = c.is_lowercase();
104 new_word = false;
105 }
106
107 camel_cased_component
108 })
109 .fold((String::new(), None), |(acc, prev): (String, Option<String>), next| {
110 let join = if let Some(prev) = prev {
113 let l = prev.chars().last().unwrap();
114 let f = next.chars().next().unwrap();
115 !char_has_case(l) && !char_has_case(f)
116 } else {
117 false
118 };
119 (acc + if join { "_" } else { "" } + &next, Some(next))
120 })
121 .0
122}
123
124impl NonCamelCaseTypes {
125 fn check_case(&self, cx: &EarlyContext<'_>, sort: &str, ident: &Ident) {
126 let name = ident.name.as_str();
127
128 if !is_camel_case(name) {
129 let cc = to_camel_case(name);
130 let sub = if *name != cc {
131 NonCamelCaseTypeSub::Suggestion { span: ident.span, replace: cc }
132 } else {
133 NonCamelCaseTypeSub::Label { span: ident.span }
134 };
135 cx.emit_span_lint(
136 NON_CAMEL_CASE_TYPES,
137 ident.span,
138 NonCamelCaseType { sort, name, sub },
139 );
140 }
141 }
142}
143
144impl EarlyLintPass for NonCamelCaseTypes {
145 fn check_item(&mut self, cx: &EarlyContext<'_>, it: &ast::Item) {
146 let has_repr_c = matches!(
147 AttributeParser::parse_limited(cx.sess(), &it.attrs, sym::repr, it.span, it.id, None),
148 Some(Attribute::Parsed(AttributeKind::Repr { reprs, ..})) if reprs.iter().any(|(r, _)| r == &ReprAttr::ReprC)
149 );
150
151 if has_repr_c {
152 return;
153 }
154
155 match &it.kind {
156 ast::ItemKind::TyAlias(box ast::TyAlias { ident, .. })
157 | ast::ItemKind::Enum(ident, ..)
158 | ast::ItemKind::Struct(ident, ..)
159 | ast::ItemKind::Union(ident, ..) => self.check_case(cx, "type", ident),
160 ast::ItemKind::Trait(box ast::Trait { ident, .. }) => {
161 self.check_case(cx, "trait", ident)
162 }
163 ast::ItemKind::TraitAlias(ident, _, _) => self.check_case(cx, "trait alias", ident),
164
165 ast::ItemKind::Impl(ast::Impl { of_trait: None, items, .. }) => {
168 for it in items {
169 if let ast::AssocItemKind::Type(alias) = &it.kind {
171 self.check_case(cx, "associated type", &alias.ident);
172 }
173 }
174 }
175 _ => (),
176 }
177 }
178
179 fn check_trait_item(&mut self, cx: &EarlyContext<'_>, it: &ast::AssocItem) {
180 if let ast::AssocItemKind::Type(alias) = &it.kind {
181 self.check_case(cx, "associated type", &alias.ident);
182 }
183 }
184
185 fn check_variant(&mut self, cx: &EarlyContext<'_>, v: &ast::Variant) {
186 self.check_case(cx, "variant", &v.ident);
187 }
188
189 fn check_generic_param(&mut self, cx: &EarlyContext<'_>, param: &ast::GenericParam) {
190 if let ast::GenericParamKind::Type { .. } = param.kind {
191 self.check_case(cx, "type parameter", ¶m.ident);
192 }
193 }
194}
195
196declare_lint! {
197 pub NON_SNAKE_CASE,
214 Warn,
215 "variables, methods, functions, lifetime parameters and modules should have snake case names"
216}
217
218declare_lint_pass!(NonSnakeCase => [NON_SNAKE_CASE]);
219
220impl NonSnakeCase {
221 fn to_snake_case(mut name: &str) -> String {
222 let mut words = vec![];
223 name = name.trim_start_matches(|c: char| {
225 if c == '_' {
226 words.push(String::new());
227 true
228 } else {
229 false
230 }
231 });
232 for s in name.split('_') {
233 let mut last_upper = false;
234 let mut buf = String::new();
235 if s.is_empty() {
236 continue;
237 }
238 for ch in s.chars() {
239 if !buf.is_empty() && buf != "'" && ch.is_uppercase() && !last_upper {
240 words.push(buf);
241 buf = String::new();
242 }
243 last_upper = ch.is_uppercase();
244 buf.extend(ch.to_lowercase());
245 }
246 words.push(buf);
247 }
248 words.join("_")
249 }
250
251 fn check_snake_case(&self, cx: &LateContext<'_>, sort: &str, ident: &Ident) {
253 fn is_snake_case(ident: &str) -> bool {
254 if ident.is_empty() {
255 return true;
256 }
257 let ident = ident.trim_start_matches('\'');
258 let ident = ident.trim_matches('_');
259
260 if ident.contains("__") {
261 return false;
262 }
263
264 !ident.chars().any(char::is_uppercase)
267 }
268
269 let name = ident.name.as_str();
270
271 if !is_snake_case(name) {
272 let span = ident.span;
273 let sc = NonSnakeCase::to_snake_case(name);
274 let sub = if name != sc {
277 if !span.is_dummy() {
280 let sc_ident = Ident::from_str_and_span(&sc, span);
281 if sc_ident.is_reserved() {
282 if sc_ident.name.can_be_raw() {
286 NonSnakeCaseDiagSub::RenameOrConvertSuggestion {
287 span,
288 suggestion: sc_ident,
289 }
290 } else {
291 NonSnakeCaseDiagSub::SuggestionAndNote { span }
292 }
293 } else {
294 NonSnakeCaseDiagSub::ConvertSuggestion { span, suggestion: sc.clone() }
295 }
296 } else {
297 NonSnakeCaseDiagSub::Help
298 }
299 } else {
300 NonSnakeCaseDiagSub::Label { span }
301 };
302 cx.emit_span_lint(NON_SNAKE_CASE, span, NonSnakeCaseDiag { sort, name, sc, sub });
303 }
304 }
305}
306
307impl<'tcx> LateLintPass<'tcx> for NonSnakeCase {
308 fn check_mod(&mut self, cx: &LateContext<'_>, _: &'tcx hir::Mod<'tcx>, id: hir::HirId) {
309 if id != hir::CRATE_HIR_ID {
310 return;
311 }
312
313 if cx.tcx.crate_types().iter().all(|&crate_type| crate_type == CrateType::Executable) {
317 return;
318 }
319
320 let crate_ident = if let Some(name) = &cx.tcx.sess.opts.crate_name {
321 Some(Ident::from_str(name))
322 } else {
323 find_attr!(cx.tcx.hir_attrs(hir::CRATE_HIR_ID), AttributeKind::CrateName{name, name_span,..} => (name, name_span)).map(
324 |(&name, &span)| {
325 let sp = cx
327 .sess()
328 .source_map()
329 .span_to_snippet(span)
330 .ok()
331 .and_then(|snippet| {
332 let left = snippet.find('"')?;
333 let right = snippet.rfind('"').map(|pos| snippet.len() - pos)?;
334
335 Some(
336 span
337 .with_lo(span.lo() + BytePos(left as u32 + 1))
338 .with_hi(span.hi() - BytePos(right as u32)),
339 )
340 })
341 .unwrap_or(span);
342
343 Ident::new(name, sp)
344 },
345 )
346 };
347
348 if let Some(ident) = &crate_ident {
349 self.check_snake_case(cx, "crate", ident);
350 }
351 }
352
353 fn check_generic_param(&mut self, cx: &LateContext<'_>, param: &hir::GenericParam<'_>) {
354 if let GenericParamKind::Lifetime { .. } = param.kind {
355 self.check_snake_case(cx, "lifetime", ¶m.name.ident());
356 }
357 }
358
359 fn check_fn(
360 &mut self,
361 cx: &LateContext<'_>,
362 fk: FnKind<'_>,
363 _: &hir::FnDecl<'_>,
364 _: &hir::Body<'_>,
365 _: Span,
366 id: LocalDefId,
367 ) {
368 match &fk {
369 FnKind::Method(ident, sig, ..) => match cx.tcx.associated_item(id).container {
370 AssocContainer::InherentImpl => {
371 if sig.header.abi != ExternAbi::Rust
372 && find_attr!(cx.tcx.get_all_attrs(id), AttributeKind::NoMangle(..))
373 {
374 return;
375 }
376 self.check_snake_case(cx, "method", ident);
377 }
378 AssocContainer::Trait => {
379 self.check_snake_case(cx, "trait method", ident);
380 }
381 AssocContainer::TraitImpl(_) => {}
382 },
383 FnKind::ItemFn(ident, _, header) => {
384 if header.abi != ExternAbi::Rust
386 && find_attr!(cx.tcx.get_all_attrs(id), AttributeKind::NoMangle(..))
387 {
388 return;
389 }
390 self.check_snake_case(cx, "function", ident);
391 }
392 FnKind::Closure => (),
393 }
394 }
395
396 fn check_item(&mut self, cx: &LateContext<'_>, it: &hir::Item<'_>) {
397 if let hir::ItemKind::Mod(ident, _) = it.kind {
398 self.check_snake_case(cx, "module", &ident);
399 }
400 }
401
402 fn check_ty(&mut self, cx: &LateContext<'_>, ty: &hir::Ty<'_, hir::AmbigArg>) {
403 if let hir::TyKind::FnPtr(hir::FnPtrTy { param_idents, .. }) = &ty.kind {
404 for param_ident in *param_idents {
405 if let Some(param_ident) = param_ident {
406 self.check_snake_case(cx, "variable", param_ident);
407 }
408 }
409 }
410 }
411
412 fn check_trait_item(&mut self, cx: &LateContext<'_>, item: &hir::TraitItem<'_>) {
413 if let hir::TraitItemKind::Fn(_, hir::TraitFn::Required(param_idents)) = item.kind {
414 self.check_snake_case(cx, "trait method", &item.ident);
415 for param_ident in param_idents {
416 if let Some(param_ident) = param_ident {
417 self.check_snake_case(cx, "variable", param_ident);
418 }
419 }
420 }
421 }
422
423 fn check_pat(&mut self, cx: &LateContext<'_>, p: &hir::Pat<'_>) {
424 if let PatKind::Binding(_, hid, ident, _) = p.kind {
425 if let hir::Node::PatField(field) = cx.tcx.parent_hir_node(hid) {
426 if !field.is_shorthand {
427 self.check_snake_case(cx, "variable", &ident);
430 }
431 return;
432 }
433 self.check_snake_case(cx, "variable", &ident);
434 }
435 }
436
437 fn check_struct_def(&mut self, cx: &LateContext<'_>, s: &hir::VariantData<'_>) {
438 for sf in s.fields() {
439 self.check_snake_case(cx, "structure field", &sf.ident);
440 }
441 }
442}
443
444declare_lint! {
445 pub NON_UPPER_CASE_GLOBALS,
461 Warn,
462 "static constants should have uppercase identifiers"
463}
464
465declare_lint_pass!(NonUpperCaseGlobals => [NON_UPPER_CASE_GLOBALS]);
466
467impl NonUpperCaseGlobals {
468 fn check_upper_case(cx: &LateContext<'_>, sort: &str, did: Option<LocalDefId>, ident: &Ident) {
469 let name = ident.name.as_str();
470 if name.chars().any(|c| c.is_lowercase()) {
471 let uc = NonSnakeCase::to_snake_case(name).to_uppercase();
472
473 let can_change_usages = if let Some(did) = did {
476 !cx.tcx.effective_visibilities(()).is_exported(did)
477 } else {
478 false
479 };
480
481 let sub = if *name != uc {
484 NonUpperCaseGlobalSub::Suggestion {
485 span: ident.span,
486 replace: uc.clone(),
487 applicability: if can_change_usages {
488 Applicability::MachineApplicable
489 } else {
490 Applicability::MaybeIncorrect
491 },
492 }
493 } else {
494 NonUpperCaseGlobalSub::Label { span: ident.span }
495 };
496
497 struct UsageCollector<'a, 'tcx> {
498 cx: &'tcx LateContext<'a>,
499 did: DefId,
500 collected: Vec<Span>,
501 }
502
503 impl<'v, 'tcx> Visitor<'v> for UsageCollector<'v, 'tcx> {
504 type NestedFilter = All;
505
506 fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
507 self.cx.tcx
508 }
509
510 fn visit_path(
511 &mut self,
512 path: &rustc_hir::Path<'v>,
513 _id: rustc_hir::HirId,
514 ) -> Self::Result {
515 if let Some(final_seg) = path.segments.last()
516 && final_seg.res.opt_def_id() == Some(self.did)
517 {
518 self.collected.push(final_seg.ident.span);
519 }
520 }
521 }
522
523 cx.emit_span_lint_lazy(NON_UPPER_CASE_GLOBALS, ident.span, || {
524 let usages = if can_change_usages
527 && *name != uc
528 && let Some(did) = did
529 {
530 let mut usage_collector =
531 UsageCollector { cx, did: did.to_def_id(), collected: Vec::new() };
532 cx.tcx.hir_walk_toplevel_module(&mut usage_collector);
533 usage_collector
534 .collected
535 .into_iter()
536 .map(|span| NonUpperCaseGlobalSubTool { span, replace: uc.clone() })
537 .collect()
538 } else {
539 vec![]
540 };
541
542 NonUpperCaseGlobal { sort, name, sub, usages }
543 });
544 }
545 }
546}
547
548impl<'tcx> LateLintPass<'tcx> for NonUpperCaseGlobals {
549 fn check_item(&mut self, cx: &LateContext<'_>, it: &hir::Item<'_>) {
550 let attrs = cx.tcx.hir_attrs(it.hir_id());
551 match it.kind {
552 hir::ItemKind::Static(_, ident, ..)
553 if !find_attr!(attrs, AttributeKind::NoMangle(..)) =>
554 {
555 NonUpperCaseGlobals::check_upper_case(
556 cx,
557 "static variable",
558 Some(it.owner_id.def_id),
559 &ident,
560 );
561 }
562 hir::ItemKind::Const(ident, ..) => {
563 NonUpperCaseGlobals::check_upper_case(
564 cx,
565 "constant",
566 Some(it.owner_id.def_id),
567 &ident,
568 );
569 }
570 _ => {}
571 }
572 }
573
574 fn check_trait_item(&mut self, cx: &LateContext<'_>, ti: &hir::TraitItem<'_>) {
575 if let hir::TraitItemKind::Const(..) = ti.kind {
576 NonUpperCaseGlobals::check_upper_case(cx, "associated constant", None, &ti.ident);
577 }
578 }
579
580 fn check_impl_item(&mut self, cx: &LateContext<'_>, ii: &hir::ImplItem<'_>) {
581 if let hir::ImplItemKind::Const(..) = ii.kind
582 && let hir::ImplItemImplKind::Inherent { .. } = ii.impl_kind
583 {
584 NonUpperCaseGlobals::check_upper_case(cx, "associated constant", None, &ii.ident);
585 }
586 }
587
588 fn check_pat(&mut self, cx: &LateContext<'_>, p: &hir::Pat<'_>) {
589 if let PatKind::Expr(hir::PatExpr {
591 kind: PatExprKind::Path(hir::QPath::Resolved(None, path)),
592 ..
593 }) = p.kind
594 {
595 if let Res::Def(DefKind::Const, _) = path.res
596 && let [segment] = path.segments
597 {
598 NonUpperCaseGlobals::check_upper_case(
599 cx,
600 "constant in pattern",
601 None,
602 &segment.ident,
603 );
604 }
605 }
606 }
607
608 fn check_generic_param(&mut self, cx: &LateContext<'_>, param: &hir::GenericParam<'_>) {
609 if let GenericParamKind::Const { .. } = param.kind {
610 NonUpperCaseGlobals::check_upper_case(
611 cx,
612 "const parameter",
613 Some(param.def_id),
614 ¶m.name.ident(),
615 );
616 }
617 }
618}
619
620#[cfg(test)]
621mod tests;