diff --git a/clang-tools-extra/clang-tidy/readability/IdentifierNamingCheck.cpp b/clang-tools-extra/clang-tidy/readability/IdentifierNamingCheck.cpp --- a/clang-tools-extra/clang-tidy/readability/IdentifierNamingCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/IdentifierNamingCheck.cpp @@ -95,6 +95,15 @@ }; #undef NAMING_KEYS + +#define CASE_KEYS(m) \ + m("aNy_CasE", CT_AnyCase) \ + m("lower_case", CT_LowerCase) \ + m("UPPER_CASE", CT_UpperCase) \ + m("camelBack", CT_CamelBack) \ + m("CamelCase", CT_CamelCase) \ + m("Camel_Snake_Case", CT_CamelSnakeCase) \ + m("camel_Snake_Back", CT_CamelSnakeBack) // clang-format on namespace { @@ -131,19 +140,44 @@ IgnoreMainLikeFunctions(Options.get("IgnoreMainLikeFunctions", 0)) { auto const fromString = [](StringRef Str) { return llvm::StringSwitch>(Str) - .Case("aNy_CasE", CT_AnyCase) - .Case("lower_case", CT_LowerCase) - .Case("UPPER_CASE", CT_UpperCase) - .Case("camelBack", CT_CamelBack) - .Case("CamelCase", CT_CamelCase) - .Case("Camel_Snake_Case", CT_CamelSnakeCase) - .Case("camel_Snake_Back", CT_CamelSnakeBack) - .Default(llvm::None); +#define CASE(NAME, ENUM) .Case(NAME, ENUM) + CASE_KEYS(CASE) +#undef CASE + .Default(llvm::None); }; - for (auto const &Name : StyleNames) { - auto const caseOptional = - fromString(Options.get((Name + "Case").str(), "")); + auto CaseOptionName = (Name + "Case").str(); + auto CaseValue = Options.get(CaseOptionName, ""); + auto const caseOptional = fromString(CaseValue); + if (!caseOptional && !CaseValue.empty()) { + llvm::errs() << "warning: Invalid case style '" << CaseValue + << "' for readability-identifier-naming." << CaseOptionName; + constexpr StringRef AcceptibleNames[] = { +#define CASE(NAME, ENUM) NAME, + CASE_KEYS(CASE) +#undef CASE + }; + StringRef Closest = ""; + unsigned EditDistance = -1u; + for (StringRef CheckMatch : AcceptibleNames) { + if (CheckMatch.equals_lower( + CaseValue)) { // bad case just suggest the fix straight away + EditDistance = 0; + Closest = CheckMatch; + break; + } + unsigned Distance = + llvm::StringRef(CaseValue).edit_distance(CheckMatch); + if (Distance < EditDistance) { + EditDistance = Distance; + Closest = CheckMatch; + } + } + if (EditDistance < 3) + llvm::errs() << ": did you mean '" << Closest << "'\n"; + else + llvm::errs() << '\n'; + } auto prefix = Options.get((Name + "Prefix").str(), ""); auto postfix = Options.get((Name + "Suffix").str(), ""); diff --git a/clang-tools-extra/test/clang-tidy/checkers/readability-identifier-naming-case-validation.cpp b/clang-tools-extra/test/clang-tidy/checkers/readability-identifier-naming-case-validation.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/readability-identifier-naming-case-validation.cpp @@ -0,0 +1,16 @@ +// RUN: clang-tidy %s -checks=readability-identifier-naming \ +// RUN: -config="{CheckOptions: [\ +// RUN: {key: readability-identifier-naming.FunctionCase, value: camelback}, \ +// RUN: {key: readability-identifier-naming.VariableCase, value: camelBack}, \ +// RUN: {key: readability-identifier-naming.ClassCase, value: UUPER_CASE}, \ +// RUN: {key: readability-identifier-naming.StructCase, value: CAMEL}, \ +// RUN: {key: readability-identifier-naming.EnumCase, value: AnY_cASe}, \ +// RUN: ]}" 2>&1 | FileCheck %s --implicit-check-not warning + + +// CHECK-DAG: warning: Invalid case style 'camelback' for readability-identifier-naming.FunctionCase: did you mean 'camelBack'{{$}} +// CHECK-DAG: warning: Invalid case style 'UUPER_CASE' for readability-identifier-naming.ClassCase: did you mean 'UPPER_CASE'{{$}} +// Don't try to suggest an alternative for 'CAMEL' +// CHECK-DAG: warning: Invalid case style 'CAMEL' for readability-identifier-naming.StructCase{{$}} +// This fails on the EditDistance, but as it matches ignoring case suggest the correct value +// CHECK-DAG: warning: Invalid case style 'AnY_cASe' for readability-identifier-naming.EnumCase: did you mean 'aNy_CasE'{{$}}