From 16f15ce391b2b463d37ff7f5e764c7f55c33cc5d Mon Sep 17 00:00:00 2001 From: Kevin Butler Date: Sun, 25 May 2014 23:23:41 +0100 Subject: [PATCH] rustc: Add lint for snake_case functions & methods. --- src/librustc/middle/lint.rs | 81 +++++++++++++++---- .../lint-non-snake-case-functions.rs | 51 ++++++++++++ 2 files changed, 118 insertions(+), 14 deletions(-) create mode 100644 src/test/compile-fail/lint-non-snake-case-functions.rs diff --git a/src/librustc/middle/lint.rs b/src/librustc/middle/lint.rs index eba0bc03bf7..8cceb16b34f 100644 --- a/src/librustc/middle/lint.rs +++ b/src/librustc/middle/lint.rs @@ -83,6 +83,7 @@ pub enum Lint { NonCamelCaseTypes, NonUppercaseStatics, NonUppercasePatternStatics, + NonSnakeCaseFunctions, UppercaseVariables, UnnecessaryParens, TypeLimits, @@ -220,6 +221,13 @@ static lint_table: &'static [(&'static str, LintSpec)] = &[ default: Warn }), + ("non_snake_case_functions", + LintSpec { + lint: NonSnakeCaseFunctions, + desc: "methods and functions should have snake case names", + default: Warn + }), + ("uppercase_variables", LintSpec { lint: UppercaseVariables, @@ -1342,6 +1350,30 @@ fn check_item_non_camel_case_types(cx: &Context, it: &ast::Item) { } } +fn check_snake_case(cx: &Context, sort: &str, ident: ast::Ident, span: Span) { + fn is_snake_case(ident: ast::Ident) -> bool { + let ident = token::get_ident(ident); + assert!(!ident.get().is_empty()); + let ident = ident.get().trim_chars('_'); + + let mut allow_underscore = true; + ident.chars().all(|c| { + allow_underscore = match c { + c if c.is_lowercase() || c.is_digit() => true, + '_' if allow_underscore => false, + _ => return false, + }; + true + }) + } + + if !is_snake_case(ident) { + cx.span_lint(NonSnakeCaseFunctions, span, + format!("{} `{}` should have a snake case identifier", + sort, token::get_ident(ident)).as_slice()); + } +} + fn check_item_non_uppercase_statics(cx: &Context, it: &ast::Item) { match it.node { // only check static constants @@ -1618,7 +1650,27 @@ fn check_missing_doc_item(cx: &Context, it: &ast::Item) { desc); } +#[deriving(Eq)] +enum MethodContext { + TraitDefaultImpl, + TraitImpl, + PlainImpl +} + fn check_missing_doc_method(cx: &Context, m: &ast::Method) { + // If the method is an impl for a trait, don't doc. + if method_context(cx, m) == TraitImpl { return; } + + // Otherwise, doc according to privacy. This will also check + // doc for default methods defined on traits. + check_missing_doc_attrs(cx, + Some(m.id), + m.attrs.as_slice(), + m.span, + "a method"); +} + +fn method_context(cx: &Context, m: &ast::Method) -> MethodContext { let did = ast::DefId { krate: ast::LOCAL_CRATE, node: m.id @@ -1628,25 +1680,16 @@ fn check_missing_doc_method(cx: &Context, m: &ast::Method) { None => cx.tcx.sess.span_bug(m.span, "missing method descriptor?!"), Some(md) => { match md.container { - // Always check default methods defined on traits. - ty::TraitContainer(..) => {} - // For methods defined on impls, it depends on whether - // it is an implementation for a trait or is a plain - // impl. + ty::TraitContainer(..) => TraitDefaultImpl, ty::ImplContainer(cid) => { match ty::impl_trait_ref(cx.tcx, cid) { - Some(..) => return, // impl for trait: don't doc - None => {} // plain impl: doc according to privacy + Some(..) => TraitImpl, + None => PlainImpl } } } } } - check_missing_doc_attrs(cx, - Some(m.id), - m.attrs.as_slice(), - m.span, - "a method"); } fn check_missing_doc_ty_method(cx: &Context, tm: &ast::TypeMethod) { @@ -1889,26 +1932,36 @@ impl<'a> Visitor<()> for Context<'a> { } match *fk { - visit::FkMethod(_, _, m) => { + visit::FkMethod(ident, _, m) => { self.with_lint_attrs(m.attrs.as_slice(), |cx| { check_missing_doc_method(cx, m); check_attrs_usage(cx, m.attrs.as_slice()); + match method_context(cx, m) { + PlainImpl => check_snake_case(cx, "method", ident, span), + TraitDefaultImpl => check_snake_case(cx, "trait method", ident, span), + _ => (), + } + cx.visit_ids(|v| { v.visit_fn(fk, decl, body, span, id, ()); }); recurse(cx); }) + }, + visit::FkItemFn(ident, _, _, _) => { + check_snake_case(self, "function", ident, span); + recurse(self); } _ => recurse(self), } } - fn visit_ty_method(&mut self, t: &ast::TypeMethod, _: ()) { self.with_lint_attrs(t.attrs.as_slice(), |cx| { check_missing_doc_ty_method(cx, t); check_attrs_usage(cx, t.attrs.as_slice()); + check_snake_case(cx, "trait method", t.ident, t.span); visit::walk_ty_method(cx, t, ()); }) diff --git a/src/test/compile-fail/lint-non-snake-case-functions.rs b/src/test/compile-fail/lint-non-snake-case-functions.rs new file mode 100644 index 00000000000..02ab85aff3b --- /dev/null +++ b/src/test/compile-fail/lint-non-snake-case-functions.rs @@ -0,0 +1,51 @@ +// Copyright 2014 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![deny(non_snake_case_functions)] +#![allow(dead_code)] + +struct Foo; + +impl Foo { + fn Foo_Method() {} + //~^ ERROR method `Foo_Method` should have a snake case identifier + + // Don't allow two underscores in a row + fn foo__method(&self) {} + //~^ ERROR method `foo__method` should have a snake case identifier + + pub fn xyZ(&mut self) {} + //~^ ERROR method `xyZ` should have a snake case identifier +} + +trait X { + fn ABC(); + //~^ ERROR trait method `ABC` should have a snake case identifier + + fn a_b_C(&self) {} + //~^ ERROR trait method `a_b_C` should have a snake case identifier + + fn something__else(&mut self); + //~^ ERROR trait method `something__else` should have a snake case identifier +} + +impl X for Foo { + // These errors should be caught at the trait definition not the impl + fn ABC() {} + fn something__else(&mut self) {} +} + +fn Cookie() {} +//~^ ERROR function `Cookie` should have a snake case identifier + +pub fn bi_S_Cuit() {} +//~^ ERROR function `bi_S_Cuit` should have a snake case identifier + +fn main() { }