Mercurial > hg > CbC > CbC_llvm
view clang-tools-extra/clang-tidy/readability/IdentifierNamingCheck.cpp @ 266:00f31e85ec16 default tip
Added tag current for changeset 31d058e83c98
author | Shinji KONO <kono@ie.u-ryukyu.ac.jp> |
---|---|
date | Sat, 14 Oct 2023 10:13:55 +0900 |
parents | 1f2b6ac9f198 |
children |
line wrap: on
line source
//===--- IdentifierNamingCheck.cpp - clang-tidy ---------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "IdentifierNamingCheck.h" #include "../GlobList.h" #include "clang/AST/CXXInheritance.h" #include "clang/Lex/PPCallbacks.h" #include "clang/Lex/Preprocessor.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/DenseMapInfo.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Debug.h" #include "llvm/Support/Error.h" #include "llvm/Support/FormatVariadic.h" #include "llvm/Support/Path.h" #include "llvm/Support/Regex.h" #include "llvm/Support/YAMLParser.h" #include <optional> #define DEBUG_TYPE "clang-tidy" // FixItHint using namespace clang::ast_matchers; namespace clang::tidy { llvm::ArrayRef< std::pair<readability::IdentifierNamingCheck::CaseType, StringRef>> OptionEnumMapping< readability::IdentifierNamingCheck::CaseType>::getEnumMapping() { static constexpr std::pair<readability::IdentifierNamingCheck::CaseType, StringRef> Mapping[] = { {readability::IdentifierNamingCheck::CT_AnyCase, "aNy_CasE"}, {readability::IdentifierNamingCheck::CT_LowerCase, "lower_case"}, {readability::IdentifierNamingCheck::CT_UpperCase, "UPPER_CASE"}, {readability::IdentifierNamingCheck::CT_CamelBack, "camelBack"}, {readability::IdentifierNamingCheck::CT_CamelCase, "CamelCase"}, {readability::IdentifierNamingCheck::CT_CamelSnakeCase, "Camel_Snake_Case"}, {readability::IdentifierNamingCheck::CT_CamelSnakeBack, "camel_Snake_Back"}}; return llvm::ArrayRef(Mapping); } template <> struct OptionEnumMapping< readability::IdentifierNamingCheck::HungarianPrefixType> { using HungarianPrefixType = readability::IdentifierNamingCheck::HungarianPrefixType; static llvm::ArrayRef<std::pair<HungarianPrefixType, StringRef>> getEnumMapping() { static constexpr std::pair<HungarianPrefixType, StringRef> Mapping[] = { {HungarianPrefixType::HPT_Off, "Off"}, {HungarianPrefixType::HPT_On, "On"}, {HungarianPrefixType::HPT_LowerCase, "LowerCase"}, {HungarianPrefixType::HPT_CamelCase, "CamelCase"}}; return llvm::ArrayRef(Mapping); } }; namespace readability { // clang-format off #define NAMING_KEYS(m) \ m(Namespace) \ m(InlineNamespace) \ m(EnumConstant) \ m(ScopedEnumConstant) \ m(ConstexprVariable) \ m(ConstantMember) \ m(PrivateMember) \ m(ProtectedMember) \ m(PublicMember) \ m(Member) \ m(ClassConstant) \ m(ClassMember) \ m(GlobalConstant) \ m(GlobalConstantPointer) \ m(GlobalPointer) \ m(GlobalVariable) \ m(LocalConstant) \ m(LocalConstantPointer) \ m(LocalPointer) \ m(LocalVariable) \ m(StaticConstant) \ m(StaticVariable) \ m(Constant) \ m(Variable) \ m(ConstantParameter) \ m(ParameterPack) \ m(Parameter) \ m(PointerParameter) \ m(ConstantPointerParameter) \ m(AbstractClass) \ m(Struct) \ m(Class) \ m(Union) \ m(Enum) \ m(GlobalFunction) \ m(ConstexprFunction) \ m(Function) \ m(ConstexprMethod) \ m(VirtualMethod) \ m(ClassMethod) \ m(PrivateMethod) \ m(ProtectedMethod) \ m(PublicMethod) \ m(Method) \ m(Typedef) \ m(TypeTemplateParameter) \ m(ValueTemplateParameter) \ m(TemplateTemplateParameter) \ m(TemplateParameter) \ m(TypeAlias) \ m(MacroDefinition) \ m(ObjcIvar) \ enum StyleKind : int { #define ENUMERATE(v) SK_ ## v, NAMING_KEYS(ENUMERATE) #undef ENUMERATE SK_Count, SK_Invalid }; static StringRef const StyleNames[] = { #define STRINGIZE(v) #v, NAMING_KEYS(STRINGIZE) #undef STRINGIZE }; #define HUNGARIAN_NOTATION_PRIMITIVE_TYPES(m) \ m(int8_t) \ m(int16_t) \ m(int32_t) \ m(int64_t) \ m(uint8_t) \ m(uint16_t) \ m(uint32_t) \ m(uint64_t) \ m(char8_t) \ m(char16_t) \ m(char32_t) \ m(float) \ m(double) \ m(char) \ m(bool) \ m(_Bool) \ m(int) \ m(size_t) \ m(wchar_t) \ m(short-int) \ m(short) \ m(signed-int) \ m(signed-short) \ m(signed-short-int) \ m(signed-long-long-int) \ m(signed-long-long) \ m(signed-long-int) \ m(signed-long) \ m(signed) \ m(unsigned-long-long-int) \ m(unsigned-long-long) \ m(unsigned-long-int) \ m(unsigned-long) \ m(unsigned-short-int) \ m(unsigned-short) \ m(unsigned-int) \ m(unsigned-char) \ m(unsigned) \ m(long-long-int) \ m(long-double) \ m(long-long) \ m(long-int) \ m(long) \ m(ptrdiff_t) \ m(void) \ static StringRef const HungarainNotationPrimitiveTypes[] = { #define STRINGIZE(v) #v, HUNGARIAN_NOTATION_PRIMITIVE_TYPES(STRINGIZE) #undef STRINGIZE }; #define HUNGARIAN_NOTATION_USER_DEFINED_TYPES(m) \ m(BOOL) \ m(BOOLEAN) \ m(BYTE) \ m(CHAR) \ m(UCHAR) \ m(SHORT) \ m(USHORT) \ m(WORD) \ m(DWORD) \ m(DWORD32) \ m(DWORD64) \ m(LONG) \ m(ULONG) \ m(ULONG32) \ m(ULONG64) \ m(ULONGLONG) \ m(HANDLE) \ m(INT) \ m(INT8) \ m(INT16) \ m(INT32) \ m(INT64) \ m(UINT) \ m(UINT8) \ m(UINT16) \ m(UINT32) \ m(UINT64) \ m(PVOID) \ static StringRef const HungarainNotationUserDefinedTypes[] = { #define STRINGIZE(v) #v, HUNGARIAN_NOTATION_USER_DEFINED_TYPES(STRINGIZE) #undef STRINGIZE }; #undef NAMING_KEYS // clang-format on IdentifierNamingCheck::NamingStyle::NamingStyle( std::optional<IdentifierNamingCheck::CaseType> Case, StringRef Prefix, StringRef Suffix, StringRef IgnoredRegexpStr, HungarianPrefixType HPType) : Case(Case), Prefix(Prefix), Suffix(Suffix), IgnoredRegexpStr(IgnoredRegexpStr), HPType(HPType) { if (!IgnoredRegexpStr.empty()) { IgnoredRegexp = llvm::Regex(llvm::SmallString<128>({"^", IgnoredRegexpStr, "$"})); if (!IgnoredRegexp.isValid()) llvm::errs() << "Invalid IgnoredRegexp regular expression: " << IgnoredRegexpStr; } } IdentifierNamingCheck::FileStyle IdentifierNamingCheck::getFileStyleFromOptions( const ClangTidyCheck::OptionsView &Options) const { IdentifierNamingCheck::HungarianNotationOption HNOption; HungarianNotation.loadDefaultConfig(HNOption); HungarianNotation.loadFileConfig(Options, HNOption); SmallVector<std::optional<IdentifierNamingCheck::NamingStyle>, 0> Styles; Styles.resize(SK_Count); SmallString<64> StyleString; for (unsigned I = 0; I < SK_Count; ++I) { size_t StyleSize = StyleNames[I].size(); StyleString.assign({StyleNames[I], "HungarianPrefix"}); auto HPTOpt = Options.get<IdentifierNamingCheck::HungarianPrefixType>(StyleString); if (HPTOpt && !HungarianNotation.checkOptionValid(I)) configurationDiag("invalid identifier naming option '%0'") << StyleString; memcpy(&StyleString[StyleSize], "IgnoredRegexp", 13); StyleString.truncate(StyleSize + 13); std::optional<StringRef> IgnoredRegexpStr = Options.get(StyleString); memcpy(&StyleString[StyleSize], "Prefix", 6); StyleString.truncate(StyleSize + 6); std::optional<StringRef> Prefix(Options.get(StyleString)); // Fast replacement of [Pre]fix -> [Suf]fix. memcpy(&StyleString[StyleSize], "Suf", 3); std::optional<StringRef> Postfix(Options.get(StyleString)); memcpy(&StyleString[StyleSize], "Case", 4); StyleString.pop_back_n(2); std::optional<CaseType> CaseOptional = Options.get<IdentifierNamingCheck::CaseType>(StyleString); if (CaseOptional || Prefix || Postfix || IgnoredRegexpStr || HPTOpt) Styles[I].emplace(std::move(CaseOptional), Prefix.value_or(""), Postfix.value_or(""), IgnoredRegexpStr.value_or(""), HPTOpt.value_or(IdentifierNamingCheck::HPT_Off)); } bool IgnoreMainLike = Options.get("IgnoreMainLikeFunctions", false); return {std::move(Styles), std::move(HNOption), IgnoreMainLike}; } std::string IdentifierNamingCheck::HungarianNotation::getDeclTypeName( const NamedDecl *ND) const { const auto *VD = dyn_cast<ValueDecl>(ND); if (!VD) return {}; if (isa<FunctionDecl, EnumConstantDecl>(ND)) return {}; // Get type text of variable declarations. auto &SM = VD->getASTContext().getSourceManager(); const char *Begin = SM.getCharacterData(VD->getBeginLoc()); const char *End = SM.getCharacterData(VD->getEndLoc()); intptr_t StrLen = End - Begin; // FIXME: Sometimes the value that returns from ValDecl->getEndLoc() // is wrong(out of location of Decl). This causes `StrLen` will be assigned // an unexpected large value. Current workaround to find the terminated // character instead of the `getEndLoc()` function. const char *EOL = strchr(Begin, '\n'); if (!EOL) EOL = Begin + strlen(Begin); const char *PosList[] = {strchr(Begin, '='), strchr(Begin, ';'), strchr(Begin, ','), strchr(Begin, ')'), EOL}; for (const auto &Pos : PosList) { if (Pos > Begin) EOL = std::min(EOL, Pos); } StrLen = EOL - Begin; std::string TypeName; if (StrLen > 0) { std::string Type(Begin, StrLen); static constexpr StringRef Keywords[] = { // Constexpr specifiers "constexpr", "constinit", "consteval", // Qualifier "const", "volatile", "restrict", "mutable", // Storage class specifiers "register", "static", "extern", "thread_local", // Other keywords "virtual"}; // Remove keywords for (StringRef Kw : Keywords) { for (size_t Pos = 0; (Pos = Type.find(Kw.data(), Pos)) != std::string::npos;) { Type.replace(Pos, Kw.size(), ""); } } TypeName = Type.erase(0, Type.find_first_not_of(' ')); // Remove template parameters const size_t Pos = Type.find('<'); if (Pos != std::string::npos) { TypeName = Type.erase(Pos, Type.size() - Pos); } // Replace spaces with single space. for (size_t Pos = 0; (Pos = Type.find(" ", Pos)) != std::string::npos; Pos += strlen(" ")) { Type.replace(Pos, strlen(" "), " "); } // Replace " &" with "&". for (size_t Pos = 0; (Pos = Type.find(" &", Pos)) != std::string::npos; Pos += strlen("&")) { Type.replace(Pos, strlen(" &"), "&"); } // Replace " *" with "* ". for (size_t Pos = 0; (Pos = Type.find(" *", Pos)) != std::string::npos; Pos += strlen("*")) { Type.replace(Pos, strlen(" *"), "* "); } // Remove redundant tailing. static constexpr StringRef TailsOfMultiWordType[] = { " int", " char", " double", " long", " short"}; bool RedundantRemoved = false; for (auto Kw : TailsOfMultiWordType) { size_t Pos = Type.rfind(Kw.data()); if (Pos != std::string::npos) { const size_t PtrCount = getAsteriskCount(Type, ND); Type = Type.substr(0, Pos + Kw.size() + PtrCount); RedundantRemoved = true; break; } } TypeName = Type.erase(0, Type.find_first_not_of(' ')); if (!RedundantRemoved) { std::size_t FoundSpace = Type.find(' '); if (FoundSpace != std::string::npos) Type = Type.substr(0, FoundSpace); } TypeName = Type.erase(0, Type.find_first_not_of(' ')); QualType QT = VD->getType(); if (!QT.isNull() && QT->isArrayType()) TypeName.append("[]"); } return TypeName; } IdentifierNamingCheck::IdentifierNamingCheck(StringRef Name, ClangTidyContext *Context) : RenamerClangTidyCheck(Name, Context), Context(Context), GetConfigPerFile(Options.get("GetConfigPerFile", true)), IgnoreFailedSplit(Options.get("IgnoreFailedSplit", false)) { auto IterAndInserted = NamingStylesCache.try_emplace( llvm::sys::path::parent_path(Context->getCurrentFile()), getFileStyleFromOptions(Options)); assert(IterAndInserted.second && "Couldn't insert Style"); // Holding a reference to the data in the vector is safe as it should never // move. MainFileStyle = &IterAndInserted.first->getValue(); } IdentifierNamingCheck::~IdentifierNamingCheck() = default; bool IdentifierNamingCheck::HungarianNotation::checkOptionValid( int StyleKindIndex) const { if ((StyleKindIndex >= SK_EnumConstant) && (StyleKindIndex <= SK_ConstantParameter)) return true; if ((StyleKindIndex >= SK_Parameter) && (StyleKindIndex <= SK_Enum)) return true; return false; } bool IdentifierNamingCheck::HungarianNotation::isOptionEnabled( StringRef OptionKey, const llvm::StringMap<std::string> &StrMap) const { if (OptionKey.empty()) return false; auto Iter = StrMap.find(OptionKey); if (Iter == StrMap.end()) return false; return *llvm::yaml::parseBool(Iter->getValue()); } void IdentifierNamingCheck::HungarianNotation::loadFileConfig( const ClangTidyCheck::OptionsView &Options, IdentifierNamingCheck::HungarianNotationOption &HNOption) const { static constexpr StringRef HNOpts[] = {"TreatStructAsClass"}; static constexpr StringRef HNDerivedTypes[] = {"Array", "Pointer", "FunctionPointer"}; StringRef Section = "HungarianNotation."; SmallString<128> Buffer = {Section, "General."}; size_t DefSize = Buffer.size(); for (const auto &Opt : HNOpts) { Buffer.truncate(DefSize); Buffer.append(Opt); StringRef Val = Options.get(Buffer, ""); if (!Val.empty()) HNOption.General[Opt] = Val.str(); } Buffer = {Section, "DerivedType."}; DefSize = Buffer.size(); for (const auto &Type : HNDerivedTypes) { Buffer.truncate(DefSize); Buffer.append(Type); StringRef Val = Options.get(Buffer, ""); if (!Val.empty()) HNOption.DerivedType[Type] = Val.str(); } static constexpr std::pair<StringRef, StringRef> HNCStrings[] = { {"CharPointer", "char*"}, {"CharArray", "char[]"}, {"WideCharPointer", "wchar_t*"}, {"WideCharArray", "wchar_t[]"}}; Buffer = {Section, "CString."}; DefSize = Buffer.size(); for (const auto &CStr : HNCStrings) { Buffer.truncate(DefSize); Buffer.append(CStr.first); StringRef Val = Options.get(Buffer, ""); if (!Val.empty()) HNOption.CString[CStr.second] = Val.str(); } Buffer = {Section, "PrimitiveType."}; DefSize = Buffer.size(); for (const auto &PrimType : HungarainNotationPrimitiveTypes) { Buffer.truncate(DefSize); Buffer.append(PrimType); StringRef Val = Options.get(Buffer, ""); if (!Val.empty()) { std::string Type = PrimType.str(); std::replace(Type.begin(), Type.end(), '-', ' '); HNOption.PrimitiveType[Type] = Val.str(); } } Buffer = {Section, "UserDefinedType."}; DefSize = Buffer.size(); for (const auto &Type : HungarainNotationUserDefinedTypes) { Buffer.truncate(DefSize); Buffer.append(Type); StringRef Val = Options.get(Buffer, ""); if (!Val.empty()) HNOption.UserDefinedType[Type] = Val.str(); } } std::string IdentifierNamingCheck::HungarianNotation::getPrefix( const Decl *D, const IdentifierNamingCheck::HungarianNotationOption &HNOption) const { if (!D) return {}; const auto *ND = dyn_cast<NamedDecl>(D); if (!ND) return {}; std::string Prefix; if (const auto *ECD = dyn_cast<EnumConstantDecl>(ND)) { Prefix = getEnumPrefix(ECD); } else if (const auto *CRD = dyn_cast<CXXRecordDecl>(ND)) { Prefix = getClassPrefix(CRD, HNOption); } else if (isa<VarDecl, FieldDecl, RecordDecl>(ND)) { std::string TypeName = getDeclTypeName(ND); if (!TypeName.empty()) Prefix = getDataTypePrefix(TypeName, ND, HNOption); } return Prefix; } bool IdentifierNamingCheck::HungarianNotation::removeDuplicatedPrefix( SmallVector<StringRef, 8> &Words, const IdentifierNamingCheck::HungarianNotationOption &HNOption) const { if (Words.size() <= 1) return true; std::string CorrectName = Words[0].str(); std::vector<llvm::StringMap<std::string>> MapList = { HNOption.CString, HNOption.DerivedType, HNOption.PrimitiveType, HNOption.UserDefinedType}; for (const auto &Map : MapList) { for (const auto &Str : Map) { if (Str.getValue() == CorrectName) { Words.erase(Words.begin(), Words.begin() + 1); return true; } } } return false; } std::string IdentifierNamingCheck::HungarianNotation::getDataTypePrefix( StringRef TypeName, const NamedDecl *ND, const IdentifierNamingCheck::HungarianNotationOption &HNOption) const { if (!ND || TypeName.empty()) return TypeName.str(); std::string ModifiedTypeName(TypeName); // Derived types std::string PrefixStr; if (const auto *TD = dyn_cast<ValueDecl>(ND)) { QualType QT = TD->getType(); if (QT->isFunctionPointerType()) { PrefixStr = HNOption.DerivedType.lookup("FunctionPointer"); } else if (QT->isPointerType()) { for (const auto &CStr : HNOption.CString) { std::string Key = CStr.getKey().str(); if (ModifiedTypeName.find(Key) == 0) { PrefixStr = CStr.getValue(); ModifiedTypeName = ModifiedTypeName.substr( Key.size(), ModifiedTypeName.size() - Key.size()); break; } } } else if (QT->isArrayType()) { for (const auto &CStr : HNOption.CString) { std::string Key = CStr.getKey().str(); if (ModifiedTypeName.find(Key) == 0) { PrefixStr = CStr.getValue(); break; } } if (PrefixStr.empty()) PrefixStr = HNOption.DerivedType.lookup("Array"); } else if (QT->isReferenceType()) { size_t Pos = ModifiedTypeName.find_last_of('&'); if (Pos != std::string::npos) ModifiedTypeName = ModifiedTypeName.substr(0, Pos); } } // Pointers size_t PtrCount = getAsteriskCount(ModifiedTypeName); if (PtrCount > 0) { ModifiedTypeName = [&](std::string Str, StringRef From, StringRef To) { size_t StartPos = 0; while ((StartPos = Str.find(From.data(), StartPos)) != std::string::npos) { Str.replace(StartPos, From.size(), To.data()); StartPos += To.size(); } return Str; }(ModifiedTypeName, "*", ""); } // Primitive types if (PrefixStr.empty()) { for (const auto &Type : HNOption.PrimitiveType) { if (ModifiedTypeName == Type.getKey()) { PrefixStr = Type.getValue(); break; } } } // User-Defined types if (PrefixStr.empty()) { for (const auto &Type : HNOption.UserDefinedType) { if (ModifiedTypeName == Type.getKey()) { PrefixStr = Type.getValue(); break; } } } for (size_t Idx = 0; Idx < PtrCount; Idx++) PrefixStr.insert(0, HNOption.DerivedType.lookup("Pointer")); return PrefixStr; } std::string IdentifierNamingCheck::HungarianNotation::getClassPrefix( const CXXRecordDecl *CRD, const IdentifierNamingCheck::HungarianNotationOption &HNOption) const { if (CRD->isUnion()) return {}; if (CRD->isStruct() && !isOptionEnabled("TreatStructAsClass", HNOption.General)) return {}; return CRD->isAbstract() ? "I" : "C"; } std::string IdentifierNamingCheck::HungarianNotation::getEnumPrefix( const EnumConstantDecl *ECD) const { const EnumDecl *ED = cast<EnumDecl>(ECD->getDeclContext()); std::string Name = ED->getName().str(); if (std::string::npos != Name.find("enum")) { Name = Name.substr(strlen("enum"), Name.length() - strlen("enum")); Name = Name.erase(0, Name.find_first_not_of(' ')); } static llvm::Regex Splitter( "([a-z0-9A-Z]*)(_+)|([A-Z]?[a-z0-9]+)([A-Z]|$)|([A-Z]+)([A-Z]|$)"); StringRef EnumName(Name); SmallVector<StringRef, 8> Substrs; EnumName.split(Substrs, "_", -1, false); SmallVector<StringRef, 8> Words; SmallVector<StringRef, 8> Groups; for (auto Substr : Substrs) { while (!Substr.empty()) { Groups.clear(); if (!Splitter.match(Substr, &Groups)) break; if (Groups[2].size() > 0) { Words.push_back(Groups[1]); Substr = Substr.substr(Groups[0].size()); } else if (Groups[3].size() > 0) { Words.push_back(Groups[3]); Substr = Substr.substr(Groups[0].size() - Groups[4].size()); } else if (Groups[5].size() > 0) { Words.push_back(Groups[5]); Substr = Substr.substr(Groups[0].size() - Groups[6].size()); } } } std::string Initial; for (StringRef Word : Words) Initial += tolower(Word[0]); return Initial; } size_t IdentifierNamingCheck::HungarianNotation::getAsteriskCount( const std::string &TypeName) const { size_t Pos = TypeName.find('*'); size_t Count = 0; for (; Pos < TypeName.length(); Pos++, Count++) { if ('*' != TypeName[Pos]) break; } return Count; } size_t IdentifierNamingCheck::HungarianNotation::getAsteriskCount( const std::string &TypeName, const NamedDecl *ND) const { size_t PtrCount = 0; if (const auto *TD = dyn_cast<ValueDecl>(ND)) { QualType QT = TD->getType(); if (QT->isPointerType()) PtrCount = getAsteriskCount(TypeName); } return PtrCount; } void IdentifierNamingCheck::HungarianNotation::loadDefaultConfig( IdentifierNamingCheck::HungarianNotationOption &HNOption) const { // Options static constexpr std::pair<StringRef, StringRef> General[] = { {"TreatStructAsClass", "false"}}; for (const auto &G : General) HNOption.General.try_emplace(G.first, G.second); // Derived types static constexpr std::pair<StringRef, StringRef> DerivedTypes[] = { {"Array", "a"}, {"Pointer", "p"}, {"FunctionPointer", "fn"}}; for (const auto &DT : DerivedTypes) HNOption.DerivedType.try_emplace(DT.first, DT.second); // C strings static constexpr std::pair<StringRef, StringRef> CStrings[] = { {"char*", "sz"}, {"char[]", "sz"}, {"wchar_t*", "wsz"}, {"wchar_t[]", "wsz"}}; for (const auto &CStr : CStrings) HNOption.CString.try_emplace(CStr.first, CStr.second); // clang-format off static constexpr std::pair<StringRef, StringRef> PrimitiveTypes[] = { {"int8_t", "i8" }, {"int16_t", "i16" }, {"int32_t", "i32" }, {"int64_t", "i64" }, {"uint8_t", "u8" }, {"uint16_t", "u16" }, {"uint32_t", "u32" }, {"uint64_t", "u64" }, {"char8_t", "c8" }, {"char16_t", "c16" }, {"char32_t", "c32" }, {"float", "f" }, {"double", "d" }, {"char", "c" }, {"bool", "b" }, {"_Bool", "b" }, {"int", "i" }, {"size_t", "n" }, {"wchar_t", "wc" }, {"short int", "si" }, {"short", "s" }, {"signed int", "si" }, {"signed short", "ss" }, {"signed short int", "ssi" }, {"signed long long int", "slli"}, {"signed long long", "sll" }, {"signed long int", "sli" }, {"signed long", "sl" }, {"signed", "s" }, {"unsigned long long int", "ulli"}, {"unsigned long long", "ull" }, {"unsigned long int", "uli" }, {"unsigned long", "ul" }, {"unsigned short int", "usi" }, {"unsigned short", "us" }, {"unsigned int", "ui" }, {"unsigned char", "uc" }, {"unsigned", "u" }, {"long long int", "lli" }, {"long double", "ld" }, {"long long", "ll" }, {"long int", "li" }, {"long", "l" }, {"ptrdiff_t", "p" }, {"void", "" }}; // clang-format on for (const auto &PT : PrimitiveTypes) HNOption.PrimitiveType.try_emplace(PT.first, PT.second); // clang-format off static constexpr std::pair<StringRef, StringRef> UserDefinedTypes[] = { // Windows data types {"BOOL", "b" }, {"BOOLEAN", "b" }, {"BYTE", "by" }, {"CHAR", "c" }, {"UCHAR", "uc" }, {"SHORT", "s" }, {"USHORT", "us" }, {"WORD", "w" }, {"DWORD", "dw" }, {"DWORD32", "dw32"}, {"DWORD64", "dw64"}, {"LONG", "l" }, {"ULONG", "ul" }, {"ULONG32", "ul32"}, {"ULONG64", "ul64"}, {"ULONGLONG", "ull" }, {"HANDLE", "h" }, {"INT", "i" }, {"INT8", "i8" }, {"INT16", "i16" }, {"INT32", "i32" }, {"INT64", "i64" }, {"UINT", "ui" }, {"UINT8", "u8" }, {"UINT16", "u16" }, {"UINT32", "u32" }, {"UINT64", "u64" }, {"PVOID", "p" } }; // clang-format on for (const auto &UDT : UserDefinedTypes) HNOption.UserDefinedType.try_emplace(UDT.first, UDT.second); } void IdentifierNamingCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { RenamerClangTidyCheck::storeOptions(Opts); SmallString<64> StyleString; ArrayRef<std::optional<NamingStyle>> Styles = MainFileStyle->getStyles(); for (size_t I = 0; I < SK_Count; ++I) { if (!Styles[I]) continue; size_t StyleSize = StyleNames[I].size(); StyleString.assign({StyleNames[I], "HungarianPrefix"}); Options.store(Opts, StyleString, Styles[I]->HPType); memcpy(&StyleString[StyleSize], "IgnoredRegexp", 13); StyleString.truncate(StyleSize + 13); Options.store(Opts, StyleString, Styles[I]->IgnoredRegexpStr); memcpy(&StyleString[StyleSize], "Prefix", 6); StyleString.truncate(StyleSize + 6); Options.store(Opts, StyleString, Styles[I]->Prefix); // Fast replacement of [Pre]fix -> [Suf]fix. memcpy(&StyleString[StyleSize], "Suf", 3); Options.store(Opts, StyleString, Styles[I]->Suffix); if (Styles[I]->Case) { memcpy(&StyleString[StyleSize], "Case", 4); StyleString.pop_back_n(2); Options.store(Opts, StyleString, *Styles[I]->Case); } } Options.store(Opts, "GetConfigPerFile", GetConfigPerFile); Options.store(Opts, "IgnoreFailedSplit", IgnoreFailedSplit); Options.store(Opts, "IgnoreMainLikeFunctions", MainFileStyle->isIgnoringMainLikeFunction()); } bool IdentifierNamingCheck::matchesStyle( StringRef Type, StringRef Name, const IdentifierNamingCheck::NamingStyle &Style, const IdentifierNamingCheck::HungarianNotationOption &HNOption, const NamedDecl *Decl) const { static llvm::Regex Matchers[] = { llvm::Regex("^.*$"), llvm::Regex("^[a-z][a-z0-9_]*$"), llvm::Regex("^[a-z][a-zA-Z0-9]*$"), llvm::Regex("^[A-Z][A-Z0-9_]*$"), llvm::Regex("^[A-Z][a-zA-Z0-9]*$"), llvm::Regex("^[A-Z]([a-z0-9]*(_[A-Z])?)*"), llvm::Regex("^[a-z]([a-z0-9]*(_[A-Z])?)*"), }; if (!Name.consume_front(Style.Prefix)) return false; if (!Name.consume_back(Style.Suffix)) return false; if (IdentifierNamingCheck::HungarianPrefixType::HPT_Off != Style.HPType) { std::string HNPrefix = HungarianNotation.getPrefix(Decl, HNOption); if (!Name.consume_front(HNPrefix)) return false; } // Ensure the name doesn't have any extra underscores beyond those specified // in the prefix and suffix. if (Name.startswith("_") || Name.endswith("_")) return false; if (Style.Case && !Matchers[static_cast<size_t>(*Style.Case)].match(Name)) return false; return true; } std::string IdentifierNamingCheck::fixupWithCase( StringRef Type, StringRef Name, const Decl *D, const IdentifierNamingCheck::NamingStyle &Style, const IdentifierNamingCheck::HungarianNotationOption &HNOption, IdentifierNamingCheck::CaseType Case) const { static llvm::Regex Splitter( "([a-z0-9A-Z]*)(_+)|([A-Z]?[a-z0-9]+)([A-Z]|$)|([A-Z]+)([A-Z]|$)"); SmallVector<StringRef, 8> Substrs; Name.split(Substrs, "_", -1, false); SmallVector<StringRef, 8> Words; SmallVector<StringRef, 8> Groups; for (auto Substr : Substrs) { while (!Substr.empty()) { Groups.clear(); if (!Splitter.match(Substr, &Groups)) break; if (Groups[2].size() > 0) { Words.push_back(Groups[1]); Substr = Substr.substr(Groups[0].size()); } else if (Groups[3].size() > 0) { Words.push_back(Groups[3]); Substr = Substr.substr(Groups[0].size() - Groups[4].size()); } else if (Groups[5].size() > 0) { Words.push_back(Groups[5]); Substr = Substr.substr(Groups[0].size() - Groups[6].size()); } } } if (Words.empty()) return Name.str(); if (IdentifierNamingCheck::HungarianPrefixType::HPT_Off != Style.HPType) { HungarianNotation.removeDuplicatedPrefix(Words, HNOption); } SmallString<128> Fixup; switch (Case) { case IdentifierNamingCheck::CT_AnyCase: return Name.str(); break; case IdentifierNamingCheck::CT_LowerCase: for (auto const &Word : Words) { if (&Word != &Words.front()) Fixup += "_"; Fixup += Word.lower(); } break; case IdentifierNamingCheck::CT_UpperCase: for (auto const &Word : Words) { if (&Word != &Words.front()) Fixup += "_"; Fixup += Word.upper(); } break; case IdentifierNamingCheck::CT_CamelCase: for (auto const &Word : Words) { Fixup += toupper(Word.front()); Fixup += Word.substr(1).lower(); } break; case IdentifierNamingCheck::CT_CamelBack: for (auto const &Word : Words) { if (&Word == &Words.front()) { Fixup += Word.lower(); } else { Fixup += toupper(Word.front()); Fixup += Word.substr(1).lower(); } } break; case IdentifierNamingCheck::CT_CamelSnakeCase: for (auto const &Word : Words) { if (&Word != &Words.front()) Fixup += "_"; Fixup += toupper(Word.front()); Fixup += Word.substr(1).lower(); } break; case IdentifierNamingCheck::CT_CamelSnakeBack: for (auto const &Word : Words) { if (&Word != &Words.front()) { Fixup += "_"; Fixup += toupper(Word.front()); } else { Fixup += tolower(Word.front()); } Fixup += Word.substr(1).lower(); } break; } return Fixup.str().str(); } bool IdentifierNamingCheck::isParamInMainLikeFunction( const ParmVarDecl &ParmDecl, bool IncludeMainLike) const { const auto *FDecl = dyn_cast_or_null<FunctionDecl>(ParmDecl.getParentFunctionOrMethod()); if (!FDecl) return false; if (FDecl->isMain()) return true; if (!IncludeMainLike) return false; if (FDecl->getAccess() != AS_public && FDecl->getAccess() != AS_none) return false; // If the function doesn't have a name that's an identifier, can occur if the // function is an operator overload, bail out early. if (!FDecl->getDeclName().isIdentifier()) return false; enum MainType { None, Main, WMain }; auto IsCharPtrPtr = [](QualType QType) -> MainType { if (QType.isNull()) return None; if (QType = QType->getPointeeType(), QType.isNull()) return None; if (QType = QType->getPointeeType(), QType.isNull()) return None; if (QType->isCharType()) return Main; if (QType->isWideCharType()) return WMain; return None; }; auto IsIntType = [](QualType QType) { if (QType.isNull()) return false; if (const auto *Builtin = dyn_cast<BuiltinType>(QType->getUnqualifiedDesugaredType())) { return Builtin->getKind() == BuiltinType::Int; } return false; }; if (!IsIntType(FDecl->getReturnType())) return false; if (FDecl->getNumParams() < 2 || FDecl->getNumParams() > 3) return false; if (!IsIntType(FDecl->parameters()[0]->getType())) return false; MainType Type = IsCharPtrPtr(FDecl->parameters()[1]->getType()); if (Type == None) return false; if (FDecl->getNumParams() == 3 && IsCharPtrPtr(FDecl->parameters()[2]->getType()) != Type) return false; if (Type == Main) { static llvm::Regex Matcher( "(^[Mm]ain([_A-Z]|$))|([a-z0-9_]Main([_A-Z]|$))|(_main(_|$))"); assert(Matcher.isValid() && "Invalid Matcher for main like functions."); return Matcher.match(FDecl->getName()); } static llvm::Regex Matcher("(^((W[Mm])|(wm))ain([_A-Z]|$))|([a-z0-9_]W[Mm]" "ain([_A-Z]|$))|(_wmain(_|$))"); assert(Matcher.isValid() && "Invalid Matcher for wmain like functions."); return Matcher.match(FDecl->getName()); } std::string IdentifierNamingCheck::fixupWithStyle( StringRef Type, StringRef Name, const IdentifierNamingCheck::NamingStyle &Style, const IdentifierNamingCheck::HungarianNotationOption &HNOption, const Decl *D) const { Name.consume_front(Style.Prefix); Name.consume_back(Style.Suffix); std::string Fixed = fixupWithCase( Type, Name, D, Style, HNOption, Style.Case.value_or(IdentifierNamingCheck::CaseType::CT_AnyCase)); std::string HungarianPrefix; using HungarianPrefixType = IdentifierNamingCheck::HungarianPrefixType; if (HungarianPrefixType::HPT_Off != Style.HPType) { HungarianPrefix = HungarianNotation.getPrefix(D, HNOption); if (!HungarianPrefix.empty()) { if (Style.HPType == HungarianPrefixType::HPT_LowerCase) HungarianPrefix += "_"; if (Style.HPType == HungarianPrefixType::HPT_CamelCase) Fixed[0] = toupper(Fixed[0]); } } StringRef Mid = StringRef(Fixed).trim("_"); if (Mid.empty()) Mid = "_"; return (Style.Prefix + HungarianPrefix + Mid + Style.Suffix).str(); } StyleKind IdentifierNamingCheck::findStyleKind( const NamedDecl *D, ArrayRef<std::optional<IdentifierNamingCheck::NamingStyle>> NamingStyles, bool IgnoreMainLikeFunctions) const { assert(D && D->getIdentifier() && !D->getName().empty() && !D->isImplicit() && "Decl must be an explicit identifier with a name."); if (isa<ObjCIvarDecl>(D) && NamingStyles[SK_ObjcIvar]) return SK_ObjcIvar; if (isa<TypedefDecl>(D) && NamingStyles[SK_Typedef]) return SK_Typedef; if (isa<TypeAliasDecl>(D) && NamingStyles[SK_TypeAlias]) return SK_TypeAlias; if (const auto *Decl = dyn_cast<NamespaceDecl>(D)) { if (Decl->isAnonymousNamespace()) return SK_Invalid; if (Decl->isInline() && NamingStyles[SK_InlineNamespace]) return SK_InlineNamespace; if (NamingStyles[SK_Namespace]) return SK_Namespace; } if (isa<EnumDecl>(D) && NamingStyles[SK_Enum]) return SK_Enum; if (const auto *EnumConst = dyn_cast<EnumConstantDecl>(D)) { if (cast<EnumDecl>(EnumConst->getDeclContext())->isScoped() && NamingStyles[SK_ScopedEnumConstant]) return SK_ScopedEnumConstant; if (NamingStyles[SK_EnumConstant]) return SK_EnumConstant; if (NamingStyles[SK_Constant]) return SK_Constant; return SK_Invalid; } if (const auto *Decl = dyn_cast<CXXRecordDecl>(D)) { if (Decl->isAnonymousStructOrUnion()) return SK_Invalid; if (const auto *Definition = Decl->getDefinition()) { if (Definition->isAbstract() && NamingStyles[SK_AbstractClass]) return SK_AbstractClass; if (Definition->isStruct() && NamingStyles[SK_Struct]) return SK_Struct; if (Definition->isStruct() && NamingStyles[SK_Class]) return SK_Class; if (Definition->isClass() && NamingStyles[SK_Class]) return SK_Class; if (Definition->isClass() && NamingStyles[SK_Struct]) return SK_Struct; if (Definition->isUnion() && NamingStyles[SK_Union]) return SK_Union; if (Definition->isEnum() && NamingStyles[SK_Enum]) return SK_Enum; } return SK_Invalid; } if (const auto *Decl = dyn_cast<FieldDecl>(D)) { QualType Type = Decl->getType(); if (!Type.isNull() && Type.isConstQualified()) { if (NamingStyles[SK_ConstantMember]) return SK_ConstantMember; if (NamingStyles[SK_Constant]) return SK_Constant; } if (Decl->getAccess() == AS_private && NamingStyles[SK_PrivateMember]) return SK_PrivateMember; if (Decl->getAccess() == AS_protected && NamingStyles[SK_ProtectedMember]) return SK_ProtectedMember; if (Decl->getAccess() == AS_public && NamingStyles[SK_PublicMember]) return SK_PublicMember; if (NamingStyles[SK_Member]) return SK_Member; return SK_Invalid; } if (const auto *Decl = dyn_cast<ParmVarDecl>(D)) { if (isParamInMainLikeFunction(*Decl, IgnoreMainLikeFunctions)) return SK_Invalid; QualType Type = Decl->getType(); if (Decl->isConstexpr() && NamingStyles[SK_ConstexprVariable]) return SK_ConstexprVariable; if (!Type.isNull() && Type.isConstQualified()) { if (Type.getTypePtr()->isAnyPointerType() && NamingStyles[SK_ConstantPointerParameter]) return SK_ConstantPointerParameter; if (NamingStyles[SK_ConstantParameter]) return SK_ConstantParameter; if (NamingStyles[SK_Constant]) return SK_Constant; } if (Decl->isParameterPack() && NamingStyles[SK_ParameterPack]) return SK_ParameterPack; if (!Type.isNull() && Type.getTypePtr()->isAnyPointerType() && NamingStyles[SK_PointerParameter]) return SK_PointerParameter; if (NamingStyles[SK_Parameter]) return SK_Parameter; return SK_Invalid; } if (const auto *Decl = dyn_cast<VarDecl>(D)) { QualType Type = Decl->getType(); if (Decl->isConstexpr() && NamingStyles[SK_ConstexprVariable]) return SK_ConstexprVariable; if (!Type.isNull() && Type.isConstQualified()) { if (Decl->isStaticDataMember() && NamingStyles[SK_ClassConstant]) return SK_ClassConstant; if (Decl->isFileVarDecl() && Type.getTypePtr()->isAnyPointerType() && NamingStyles[SK_GlobalConstantPointer]) return SK_GlobalConstantPointer; if (Decl->isFileVarDecl() && NamingStyles[SK_GlobalConstant]) return SK_GlobalConstant; if (Decl->isStaticLocal() && NamingStyles[SK_StaticConstant]) return SK_StaticConstant; if (Decl->isLocalVarDecl() && Type.getTypePtr()->isAnyPointerType() && NamingStyles[SK_LocalConstantPointer]) return SK_LocalConstantPointer; if (Decl->isLocalVarDecl() && NamingStyles[SK_LocalConstant]) return SK_LocalConstant; if (Decl->isFunctionOrMethodVarDecl() && NamingStyles[SK_LocalConstant]) return SK_LocalConstant; if (NamingStyles[SK_Constant]) return SK_Constant; } if (Decl->isStaticDataMember() && NamingStyles[SK_ClassMember]) return SK_ClassMember; if (Decl->isFileVarDecl() && Type.getTypePtr()->isAnyPointerType() && NamingStyles[SK_GlobalPointer]) return SK_GlobalPointer; if (Decl->isFileVarDecl() && NamingStyles[SK_GlobalVariable]) return SK_GlobalVariable; if (Decl->isStaticLocal() && NamingStyles[SK_StaticVariable]) return SK_StaticVariable; if (Decl->isLocalVarDecl() && Type.getTypePtr()->isAnyPointerType() && NamingStyles[SK_LocalPointer]) return SK_LocalPointer; if (Decl->isLocalVarDecl() && NamingStyles[SK_LocalVariable]) return SK_LocalVariable; if (Decl->isFunctionOrMethodVarDecl() && NamingStyles[SK_LocalVariable]) return SK_LocalVariable; if (NamingStyles[SK_Variable]) return SK_Variable; return SK_Invalid; } if (const auto *Decl = dyn_cast<CXXMethodDecl>(D)) { if (Decl->isMain() || !Decl->isUserProvided() || Decl->size_overridden_methods() > 0 || Decl->hasAttr<OverrideAttr>()) return SK_Invalid; // If this method has the same name as any base method, this is likely // necessary even if it's not an override. e.g. CRTP. for (const CXXBaseSpecifier &Base : Decl->getParent()->bases()) if (const auto *RD = Base.getType()->getAsCXXRecordDecl()) if (RD->hasMemberName(Decl->getDeclName())) return SK_Invalid; if (Decl->isConstexpr() && NamingStyles[SK_ConstexprMethod]) return SK_ConstexprMethod; if (Decl->isConstexpr() && NamingStyles[SK_ConstexprFunction]) return SK_ConstexprFunction; if (Decl->isStatic() && NamingStyles[SK_ClassMethod]) return SK_ClassMethod; if (Decl->isVirtual() && NamingStyles[SK_VirtualMethod]) return SK_VirtualMethod; if (Decl->getAccess() == AS_private && NamingStyles[SK_PrivateMethod]) return SK_PrivateMethod; if (Decl->getAccess() == AS_protected && NamingStyles[SK_ProtectedMethod]) return SK_ProtectedMethod; if (Decl->getAccess() == AS_public && NamingStyles[SK_PublicMethod]) return SK_PublicMethod; if (NamingStyles[SK_Method]) return SK_Method; if (NamingStyles[SK_Function]) return SK_Function; return SK_Invalid; } if (const auto *Decl = dyn_cast<FunctionDecl>(D)) { if (Decl->isMain()) return SK_Invalid; if (Decl->isConstexpr() && NamingStyles[SK_ConstexprFunction]) return SK_ConstexprFunction; if (Decl->isGlobal() && NamingStyles[SK_GlobalFunction]) return SK_GlobalFunction; if (NamingStyles[SK_Function]) return SK_Function; } if (isa<TemplateTypeParmDecl>(D)) { if (NamingStyles[SK_TypeTemplateParameter]) return SK_TypeTemplateParameter; if (NamingStyles[SK_TemplateParameter]) return SK_TemplateParameter; return SK_Invalid; } if (isa<NonTypeTemplateParmDecl>(D)) { if (NamingStyles[SK_ValueTemplateParameter]) return SK_ValueTemplateParameter; if (NamingStyles[SK_TemplateParameter]) return SK_TemplateParameter; return SK_Invalid; } if (isa<TemplateTemplateParmDecl>(D)) { if (NamingStyles[SK_TemplateTemplateParameter]) return SK_TemplateTemplateParameter; if (NamingStyles[SK_TemplateParameter]) return SK_TemplateParameter; return SK_Invalid; } return SK_Invalid; } std::optional<RenamerClangTidyCheck::FailureInfo> IdentifierNamingCheck::getFailureInfo( StringRef Type, StringRef Name, const NamedDecl *ND, SourceLocation Location, ArrayRef<std::optional<IdentifierNamingCheck::NamingStyle>> NamingStyles, const IdentifierNamingCheck::HungarianNotationOption &HNOption, StyleKind SK, const SourceManager &SM, bool IgnoreFailedSplit) const { if (SK == SK_Invalid || !NamingStyles[SK]) return std::nullopt; const IdentifierNamingCheck::NamingStyle &Style = *NamingStyles[SK]; if (Style.IgnoredRegexp.isValid() && Style.IgnoredRegexp.match(Name)) return std::nullopt; if (matchesStyle(Type, Name, Style, HNOption, ND)) return std::nullopt; std::string KindName = fixupWithCase(Type, StyleNames[SK], ND, Style, HNOption, IdentifierNamingCheck::CT_LowerCase); std::replace(KindName.begin(), KindName.end(), '_', ' '); std::string Fixup = fixupWithStyle(Type, Name, Style, HNOption, ND); if (StringRef(Fixup).equals(Name)) { if (!IgnoreFailedSplit) { LLVM_DEBUG(Location.print(llvm::dbgs(), SM); llvm::dbgs() << llvm::formatv(": unable to split words for {0} '{1}'\n", KindName, Name)); } return std::nullopt; } return RenamerClangTidyCheck::FailureInfo{std::move(KindName), std::move(Fixup)}; } std::optional<RenamerClangTidyCheck::FailureInfo> IdentifierNamingCheck::getDeclFailureInfo(const NamedDecl *Decl, const SourceManager &SM) const { SourceLocation Loc = Decl->getLocation(); const FileStyle &FileStyle = getStyleForFile(SM.getFilename(Loc)); if (!FileStyle.isActive()) return std::nullopt; return getFailureInfo(HungarianNotation.getDeclTypeName(Decl), Decl->getName(), Decl, Loc, FileStyle.getStyles(), FileStyle.getHNOption(), findStyleKind(Decl, FileStyle.getStyles(), FileStyle.isIgnoringMainLikeFunction()), SM, IgnoreFailedSplit); } std::optional<RenamerClangTidyCheck::FailureInfo> IdentifierNamingCheck::getMacroFailureInfo(const Token &MacroNameTok, const SourceManager &SM) const { SourceLocation Loc = MacroNameTok.getLocation(); const FileStyle &Style = getStyleForFile(SM.getFilename(Loc)); if (!Style.isActive()) return std::nullopt; return getFailureInfo("", MacroNameTok.getIdentifierInfo()->getName(), nullptr, Loc, Style.getStyles(), Style.getHNOption(), SK_MacroDefinition, SM, IgnoreFailedSplit); } RenamerClangTidyCheck::DiagInfo IdentifierNamingCheck::getDiagInfo(const NamingCheckId &ID, const NamingCheckFailure &Failure) const { return DiagInfo{"invalid case style for %0 '%1'", [&](DiagnosticBuilder &Diag) { Diag << Failure.Info.KindName << ID.second; }}; } const IdentifierNamingCheck::FileStyle & IdentifierNamingCheck::getStyleForFile(StringRef FileName) const { if (!GetConfigPerFile) return *MainFileStyle; StringRef Parent = llvm::sys::path::parent_path(FileName); auto Iter = NamingStylesCache.find(Parent); if (Iter != NamingStylesCache.end()) return Iter->getValue(); llvm::StringRef CheckName = getID(); ClangTidyOptions Options = Context->getOptionsForFile(FileName); if (Options.Checks && GlobList(*Options.Checks).contains(CheckName)) { auto It = NamingStylesCache.try_emplace( Parent, getFileStyleFromOptions({CheckName, Options.CheckOptions, Context})); assert(It.second); return It.first->getValue(); } // Default construction gives an empty style. auto It = NamingStylesCache.try_emplace(Parent); assert(It.second); return It.first->getValue(); } } // namespace readability } // namespace clang::tidy