rust/src/librustc_lint/bad_style.rs
Marvin Löbel 2a8f358de7 Add syntax support for attributes on expressions and all syntax
nodes in statement position.

Extended #[cfg] folder to allow removal of statements, and
of expressions in optional positions like expression lists and trailing
block expressions.

Extended lint checker to recognize lint levels on expressions and
locals.
2015-11-26 21:46:12 +01:00

373 lines
12 KiB
Rust

// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use middle::def;
use middle::ty;
use lint::{LateContext, LintContext, LintArray};
use lint::{LintPass, LateLintPass};
use syntax::ast;
use syntax::attr::{self, AttrMetaMethods};
use syntax::codemap::Span;
use rustc_front::hir;
use rustc_front::intravisit::FnKind;
#[derive(PartialEq)]
pub enum MethodLateContext {
TraitDefaultImpl,
TraitImpl,
PlainImpl
}
pub fn method_context(cx: &LateContext, id: ast::NodeId, span: Span) -> MethodLateContext {
let def_id = cx.tcx.map.local_def_id(id);
match cx.tcx.impl_or_trait_items.borrow().get(&def_id) {
None => cx.sess().span_bug(span, "missing method descriptor?!"),
Some(item) => match item.container() {
ty::TraitContainer(..) => MethodLateContext::TraitDefaultImpl,
ty::ImplContainer(cid) => {
match cx.tcx.impl_trait_ref(cid) {
Some(_) => MethodLateContext::TraitImpl,
None => MethodLateContext::PlainImpl
}
}
}
}
}
declare_lint! {
pub NON_CAMEL_CASE_TYPES,
Warn,
"types, variants, traits and type parameters should have camel case names"
}
#[derive(Copy, Clone)]
pub struct NonCamelCaseTypes;
impl NonCamelCaseTypes {
fn check_case(&self, cx: &LateContext, sort: &str, name: ast::Name, span: Span) {
fn is_camel_case(name: ast::Name) -> bool {
let name = name.as_str();
if name.is_empty() {
return true;
}
let name = name.trim_matches('_');
// start with a non-lowercase letter rather than non-uppercase
// ones (some scripts don't have a concept of upper/lowercase)
!name.is_empty() && !name.char_at(0).is_lowercase() && !name.contains('_')
}
fn to_camel_case(s: &str) -> String {
s.split('_').flat_map(|word| word.chars().enumerate().map(|(i, c)|
if i == 0 {
c.to_uppercase().collect::<String>()
} else {
c.to_lowercase().collect()
}
)).collect::<Vec<_>>().concat()
}
let s = name.as_str();
if !is_camel_case(name) {
let c = to_camel_case(&s);
let m = if c.is_empty() {
format!("{} `{}` should have a camel case name such as `CamelCase`", sort, s)
} else {
format!("{} `{}` should have a camel case name such as `{}`", sort, s, c)
};
cx.span_lint(NON_CAMEL_CASE_TYPES, span, &m[..]);
}
}
}
impl LintPass for NonCamelCaseTypes {
fn get_lints(&self) -> LintArray {
lint_array!(NON_CAMEL_CASE_TYPES)
}
}
impl LateLintPass for NonCamelCaseTypes {
fn check_item(&mut self, cx: &LateContext, it: &hir::Item) {
let extern_repr_count = it.attrs.iter().filter(|attr| {
attr::find_repr_attrs(cx.tcx.sess.diagnostic(), attr).iter()
.any(|r| r == &attr::ReprExtern)
}).count();
let has_extern_repr = extern_repr_count > 0;
if has_extern_repr {
return;
}
match it.node {
hir::ItemTy(..) | hir::ItemStruct(..) => {
self.check_case(cx, "type", it.name, it.span)
}
hir::ItemTrait(..) => {
self.check_case(cx, "trait", it.name, it.span)
}
hir::ItemEnum(ref enum_definition, _) => {
if has_extern_repr {
return;
}
self.check_case(cx, "type", it.name, it.span);
for variant in &enum_definition.variants {
self.check_case(cx, "variant", variant.node.name, variant.span);
}
}
_ => ()
}
}
fn check_generics(&mut self, cx: &LateContext, it: &hir::Generics) {
for gen in it.ty_params.iter() {
self.check_case(cx, "type parameter", gen.name, gen.span);
}
}
}
declare_lint! {
pub NON_SNAKE_CASE,
Warn,
"variables, methods, functions, lifetime parameters and modules should have snake case names"
}
#[derive(Copy, Clone)]
pub struct NonSnakeCase;
impl NonSnakeCase {
fn to_snake_case(mut str: &str) -> String {
let mut words = vec![];
// Preserve leading underscores
str = str.trim_left_matches(|c: char| {
if c == '_' {
words.push(String::new());
true
} else {
false
}
});
for s in str.split('_') {
let mut last_upper = false;
let mut buf = String::new();
if s.is_empty() {
continue;
}
for ch in s.chars() {
if !buf.is_empty() && buf != "'"
&& ch.is_uppercase()
&& !last_upper {
words.push(buf);
buf = String::new();
}
last_upper = ch.is_uppercase();
buf.extend(ch.to_lowercase());
}
words.push(buf);
}
words.join("_")
}
fn check_snake_case(&self, cx: &LateContext, sort: &str, name: &str, span: Option<Span>) {
fn is_snake_case(ident: &str) -> bool {
if ident.is_empty() {
return true;
}
let ident = ident.trim_left_matches('\'');
let ident = ident.trim_matches('_');
let mut allow_underscore = true;
ident.chars().all(|c| {
allow_underscore = match c {
'_' if !allow_underscore => return false,
'_' => false,
// It would be more obvious to use `c.is_lowercase()`,
// but some characters do not have a lowercase form
c if !c.is_uppercase() => true,
_ => return false,
};
true
})
}
if !is_snake_case(name) {
let sc = NonSnakeCase::to_snake_case(name);
let msg = if sc != name {
format!("{} `{}` should have a snake case name such as `{}`",
sort, name, sc)
} else {
format!("{} `{}` should have a snake case name",
sort, name)
};
match span {
Some(span) => cx.span_lint(NON_SNAKE_CASE, span, &msg),
None => cx.lint(NON_SNAKE_CASE, &msg),
}
}
}
}
impl LintPass for NonSnakeCase {
fn get_lints(&self) -> LintArray {
lint_array!(NON_SNAKE_CASE)
}
}
impl LateLintPass for NonSnakeCase {
fn check_crate(&mut self, cx: &LateContext, cr: &hir::Crate) {
let attr_crate_name = cr.attrs.iter().find(|at| at.check_name("crate_name"))
.and_then(|at| at.value_str().map(|s| (at, s)));
if let Some(ref name) = cx.tcx.sess.opts.crate_name {
self.check_snake_case(cx, "crate", name, None);
} else if let Some((attr, ref name)) = attr_crate_name {
self.check_snake_case(cx, "crate", name, Some(attr.span));
}
}
fn check_fn(&mut self, cx: &LateContext,
fk: FnKind, _: &hir::FnDecl,
_: &hir::Block, span: Span, id: ast::NodeId) {
match fk {
FnKind::Method(name, _, _) => match method_context(cx, id, span) {
MethodLateContext::PlainImpl => {
self.check_snake_case(cx, "method", &name.as_str(), Some(span))
},
MethodLateContext::TraitDefaultImpl => {
self.check_snake_case(cx, "trait method", &name.as_str(), Some(span))
},
_ => (),
},
FnKind::ItemFn(name, _, _, _, _, _) => {
self.check_snake_case(cx, "function", &name.as_str(), Some(span))
},
_ => (),
}
}
fn check_item(&mut self, cx: &LateContext, it: &hir::Item) {
if let hir::ItemMod(_) = it.node {
self.check_snake_case(cx, "module", &it.name.as_str(), Some(it.span));
}
}
fn check_trait_item(&mut self, cx: &LateContext, trait_item: &hir::TraitItem) {
if let hir::MethodTraitItem(_, None) = trait_item.node {
self.check_snake_case(cx, "trait method", &trait_item.name.as_str(),
Some(trait_item.span));
}
}
fn check_lifetime_def(&mut self, cx: &LateContext, t: &hir::LifetimeDef) {
self.check_snake_case(cx, "lifetime", &t.lifetime.name.as_str(),
Some(t.lifetime.span));
}
fn check_pat(&mut self, cx: &LateContext, p: &hir::Pat) {
if let &hir::PatIdent(_, ref path1, _) = &p.node {
let def = cx.tcx.def_map.borrow().get(&p.id).map(|d| d.full_def());
if let Some(def::DefLocal(..)) = def {
self.check_snake_case(cx, "variable", &path1.node.name.as_str(), Some(p.span));
}
}
}
fn check_struct_def(&mut self, cx: &LateContext, s: &hir::VariantData,
_: ast::Name, _: &hir::Generics, _: ast::NodeId) {
for sf in s.fields() {
if let hir::StructField_ { kind: hir::NamedField(name, _), .. } = sf.node {
self.check_snake_case(cx, "structure field", &name.as_str(),
Some(sf.span));
}
}
}
}
declare_lint! {
pub NON_UPPER_CASE_GLOBALS,
Warn,
"static constants should have uppercase identifiers"
}
#[derive(Copy, Clone)]
pub struct NonUpperCaseGlobals;
impl NonUpperCaseGlobals {
fn check_upper_case(cx: &LateContext, sort: &str, name: ast::Name, span: Span) {
let s = name.as_str();
if s.chars().any(|c| c.is_lowercase()) {
let uc = NonSnakeCase::to_snake_case(&s).to_uppercase();
if uc != &s[..] {
cx.span_lint(NON_UPPER_CASE_GLOBALS, span,
&format!("{} `{}` should have an upper case name such as `{}`",
sort, s, uc));
} else {
cx.span_lint(NON_UPPER_CASE_GLOBALS, span,
&format!("{} `{}` should have an upper case name",
sort, s));
}
}
}
}
impl LintPass for NonUpperCaseGlobals {
fn get_lints(&self) -> LintArray {
lint_array!(NON_UPPER_CASE_GLOBALS)
}
}
impl LateLintPass for NonUpperCaseGlobals {
fn check_item(&mut self, cx: &LateContext, it: &hir::Item) {
match it.node {
// only check static constants
hir::ItemStatic(_, hir::MutImmutable, _) => {
NonUpperCaseGlobals::check_upper_case(cx, "static constant", it.name, it.span);
}
hir::ItemConst(..) => {
NonUpperCaseGlobals::check_upper_case(cx, "constant", it.name, it.span);
}
_ => {}
}
}
fn check_trait_item(&mut self, cx: &LateContext, ti: &hir::TraitItem) {
match ti.node {
hir::ConstTraitItem(..) => {
NonUpperCaseGlobals::check_upper_case(cx, "associated constant",
ti.name, ti.span);
}
_ => {}
}
}
fn check_impl_item(&mut self, cx: &LateContext, ii: &hir::ImplItem) {
match ii.node {
hir::ImplItemKind::Const(..) => {
NonUpperCaseGlobals::check_upper_case(cx, "associated constant",
ii.name, ii.span);
}
_ => {}
}
}
fn check_pat(&mut self, cx: &LateContext, p: &hir::Pat) {
// Lint for constants that look like binding identifiers (#7526)
match (&p.node, cx.tcx.def_map.borrow().get(&p.id).map(|d| d.full_def())) {
(&hir::PatIdent(_, ref path1, _), Some(def::DefConst(..))) => {
NonUpperCaseGlobals::check_upper_case(cx, "constant in pattern",
path1.node.name, p.span);
}
_ => {}
}
}
}