Auto merge of #46907 - varkor:contrib-8, r=nagisa

Allow non-alphabetic underscores in camel case

Certain identifiers, such as `X86_64`, cannot currently be unambiguously represented in camel case (`X8664`, `X86_64`, `X8_664`, etc. are all transformed to the same identifier). This change relaxes the rules so that underscores are permitted between two non-alphabetic characters under `#[forbid(non_camel_case_types)]`. Fixes #34633 and fixes #41621.
This commit is contained in:
bors 2018-01-05 05:20:23 +00:00
commit 8a11b8cdd7
2 changed files with 34 additions and 4 deletions

View File

@ -53,6 +53,10 @@ pub struct NonCamelCaseTypes;
impl NonCamelCaseTypes { impl NonCamelCaseTypes {
fn check_case(&self, cx: &LateContext, sort: &str, name: ast::Name, span: Span) { fn check_case(&self, cx: &LateContext, sort: &str, name: ast::Name, span: Span) {
fn char_has_case(c: char) -> bool {
c.is_lowercase() || c.is_uppercase()
}
fn is_camel_case(name: ast::Name) -> bool { fn is_camel_case(name: ast::Name) -> bool {
let name = name.as_str(); let name = name.as_str();
if name.is_empty() { if name.is_empty() {
@ -62,20 +66,38 @@ impl NonCamelCaseTypes {
// start with a non-lowercase letter rather than non-uppercase // start with a non-lowercase letter rather than non-uppercase
// ones (some scripts don't have a concept of upper/lowercase) // ones (some scripts don't have a concept of upper/lowercase)
!name.is_empty() && !name.chars().next().unwrap().is_lowercase() && !name.contains('_') !name.is_empty() && !name.chars().next().unwrap().is_lowercase() &&
!name.contains("__") && !name.chars().collect::<Vec<_>>().windows(2).any(|pair| {
// contains a capitalisable character followed by, or preceded by, an underscore
char_has_case(pair[0]) && pair[1] == '_' ||
char_has_case(pair[1]) && pair[0] == '_'
})
} }
fn to_camel_case(s: &str) -> String { fn to_camel_case(s: &str) -> String {
s.split('_') s.trim_matches('_')
.flat_map(|word| { .split('_')
.map(|word| {
word.chars().enumerate().map(|(i, c)| if i == 0 { word.chars().enumerate().map(|(i, c)| if i == 0 {
c.to_uppercase().collect::<String>() c.to_uppercase().collect::<String>()
} else { } else {
c.to_lowercase().collect() c.to_lowercase().collect()
}) })
})
.collect::<Vec<_>>() .collect::<Vec<_>>()
.concat() .concat()
})
.filter(|x| !x.is_empty())
.collect::<Vec<_>>()
.iter().fold((String::new(), None), |(acc, prev): (String, Option<&String>), next| {
// separate two components with an underscore if their boundary cannot
// be distinguished using a uppercase/lowercase case distinction
let join = if let Some(prev) = prev {
let l = prev.chars().last().unwrap();
let f = next.chars().next().unwrap();
!char_has_case(l) && !char_has_case(f)
} else { false };
(acc + if join { "_" } else { "" } + next, Some(next))
}).0
} }
if !is_camel_case(name) { if !is_camel_case(name) {

View File

@ -45,4 +45,12 @@ struct foo7 {
type __ = isize; //~ ERROR type `__` should have a camel case name such as `CamelCase` type __ = isize; //~ ERROR type `__` should have a camel case name such as `CamelCase`
struct X86_64;
struct X86__64; //~ ERROR type `X86__64` should have a camel case name such as `X86_64`
struct Abc_123; //~ ERROR type `Abc_123` should have a camel case name such as `Abc123`
struct A1_b2_c3; //~ ERROR type `A1_b2_c3` should have a camel case name such as `A1B2C3`
fn main() { } fn main() { }