| //===--- UppercaseLiteralSuffixCheck.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 "UppercaseLiteralSuffixCheck.h" |
| #include "../utils/ASTUtils.h" |
| #include "clang/AST/ASTContext.h" |
| #include "clang/ASTMatchers/ASTMatchFinder.h" |
| #include "clang/Lex/Lexer.h" |
| #include "llvm/ADT/Optional.h" |
| #include "llvm/ADT/SmallString.h" |
| #include <cctype> |
| |
| using namespace clang::ast_matchers; |
| |
| namespace clang { |
| namespace tidy { |
| namespace readability { |
| |
| namespace { |
| |
| struct IntegerLiteralCheck { |
| using type = clang::IntegerLiteral; |
| static constexpr llvm::StringLiteral Name = llvm::StringLiteral("integer"); |
| // What should be skipped before looking for the Suffixes? (Nothing here.) |
| static constexpr llvm::StringLiteral SkipFirst = llvm::StringLiteral(""); |
| // Suffix can only consist of 'u' and 'l' chars, and can be a complex number |
| // ('i', 'j'). In MS compatibility mode, suffixes like i32 are supported. |
| static constexpr llvm::StringLiteral Suffixes = |
| llvm::StringLiteral("uUlLiIjJ"); |
| }; |
| constexpr llvm::StringLiteral IntegerLiteralCheck::Name; |
| constexpr llvm::StringLiteral IntegerLiteralCheck::SkipFirst; |
| constexpr llvm::StringLiteral IntegerLiteralCheck::Suffixes; |
| |
| struct FloatingLiteralCheck { |
| using type = clang::FloatingLiteral; |
| static constexpr llvm::StringLiteral Name = |
| llvm::StringLiteral("floating point"); |
| // C++17 introduced hexadecimal floating-point literals, and 'f' is both a |
| // valid hexadecimal digit in a hex float literal and a valid floating-point |
| // literal suffix. |
| // So we can't just "skip to the chars that can be in the suffix". |
| // Since the exponent ('p'/'P') is mandatory for hexadecimal floating-point |
| // literals, we first skip everything before the exponent. |
| static constexpr llvm::StringLiteral SkipFirst = llvm::StringLiteral("pP"); |
| // Suffix can only consist of 'f', 'l', "f16", 'h', 'q' chars, |
| // and can be a complex number ('i', 'j'). |
| static constexpr llvm::StringLiteral Suffixes = |
| llvm::StringLiteral("fFlLhHqQiIjJ"); |
| }; |
| constexpr llvm::StringLiteral FloatingLiteralCheck::Name; |
| constexpr llvm::StringLiteral FloatingLiteralCheck::SkipFirst; |
| constexpr llvm::StringLiteral FloatingLiteralCheck::Suffixes; |
| |
| struct NewSuffix { |
| SourceRange LiteralLocation; |
| StringRef OldSuffix; |
| llvm::Optional<FixItHint> FixIt; |
| }; |
| |
| llvm::Optional<SourceLocation> getMacroAwareLocation(SourceLocation Loc, |
| const SourceManager &SM) { |
| // Do nothing if the provided location is invalid. |
| if (Loc.isInvalid()) |
| return llvm::None; |
| // Look where the location was *actually* written. |
| SourceLocation SpellingLoc = SM.getSpellingLoc(Loc); |
| if (SpellingLoc.isInvalid()) |
| return llvm::None; |
| return SpellingLoc; |
| } |
| |
| llvm::Optional<SourceRange> getMacroAwareSourceRange(SourceRange Loc, |
| const SourceManager &SM) { |
| llvm::Optional<SourceLocation> Begin = |
| getMacroAwareLocation(Loc.getBegin(), SM); |
| llvm::Optional<SourceLocation> End = getMacroAwareLocation(Loc.getEnd(), SM); |
| if (!Begin || !End) |
| return llvm::None; |
| return SourceRange(*Begin, *End); |
| } |
| |
| llvm::Optional<std::string> |
| getNewSuffix(llvm::StringRef OldSuffix, |
| const std::vector<std::string> &NewSuffixes) { |
| // If there is no config, just uppercase the entirety of the suffix. |
| if (NewSuffixes.empty()) |
| return OldSuffix.upper(); |
| // Else, find matching suffix, case-*insensitive*ly. |
| auto NewSuffix = llvm::find_if( |
| NewSuffixes, [OldSuffix](const std::string &PotentialNewSuffix) { |
| return OldSuffix.equals_insensitive(PotentialNewSuffix); |
| }); |
| // Have a match, return it. |
| if (NewSuffix != NewSuffixes.end()) |
| return *NewSuffix; |
| // Nope, I guess we have to keep it as-is. |
| return llvm::None; |
| } |
| |
| template <typename LiteralType> |
| llvm::Optional<NewSuffix> |
| shouldReplaceLiteralSuffix(const Expr &Literal, |
| const std::vector<std::string> &NewSuffixes, |
| const SourceManager &SM, const LangOptions &LO) { |
| NewSuffix ReplacementDsc; |
| |
| const auto &L = cast<typename LiteralType::type>(Literal); |
| |
| // The naive location of the literal. Is always valid. |
| ReplacementDsc.LiteralLocation = L.getSourceRange(); |
| |
| // Was this literal fully spelled or is it a product of macro expansion? |
| bool RangeCanBeFixed = |
| utils::rangeCanBeFixed(ReplacementDsc.LiteralLocation, &SM); |
| |
| // The literal may have macro expansion, we need the final expanded src range. |
| llvm::Optional<SourceRange> Range = |
| getMacroAwareSourceRange(ReplacementDsc.LiteralLocation, SM); |
| if (!Range) |
| return llvm::None; |
| |
| if (RangeCanBeFixed) |
| ReplacementDsc.LiteralLocation = *Range; |
| // Else keep the naive literal location! |
| |
| // Get the whole literal from the source buffer. |
| bool Invalid; |
| const StringRef LiteralSourceText = Lexer::getSourceText( |
| CharSourceRange::getTokenRange(*Range), SM, LO, &Invalid); |
| assert(!Invalid && "Failed to retrieve the source text."); |
| |
| // Make sure the first character is actually a digit, instead of |
| // something else, like a non-type template parameter. |
| if (!std::isdigit(static_cast<unsigned char>(LiteralSourceText.front()))) |
| return llvm::None; |
| |
| size_t Skip = 0; |
| |
| // Do we need to ignore something before actually looking for the suffix? |
| if (!LiteralType::SkipFirst.empty()) { |
| // E.g. we can't look for 'f' suffix in hexadecimal floating-point literals |
| // until after we skip to the exponent (which is mandatory there), |
| // because hex-digit-sequence may contain 'f'. |
| Skip = LiteralSourceText.find_first_of(LiteralType::SkipFirst); |
| // We could be in non-hexadecimal floating-point literal, with no exponent. |
| if (Skip == StringRef::npos) |
| Skip = 0; |
| } |
| |
| // Find the beginning of the suffix by looking for the first char that is |
| // one of these chars that can be in the suffix, potentially starting looking |
| // in the exponent, if we are skipping hex-digit-sequence. |
| Skip = LiteralSourceText.find_first_of(LiteralType::Suffixes, /*From=*/Skip); |
| |
| // We can't check whether the *Literal has any suffix or not without actually |
| // looking for the suffix. So it is totally possible that there is no suffix. |
| if (Skip == StringRef::npos) |
| return llvm::None; |
| |
| // Move the cursor in the source range to the beginning of the suffix. |
| Range->setBegin(Range->getBegin().getLocWithOffset(Skip)); |
| // And in our textual representation too. |
| ReplacementDsc.OldSuffix = LiteralSourceText.drop_front(Skip); |
| assert(!ReplacementDsc.OldSuffix.empty() && |
| "We still should have some chars left."); |
| |
| // And get the replacement suffix. |
| llvm::Optional<std::string> NewSuffix = |
| getNewSuffix(ReplacementDsc.OldSuffix, NewSuffixes); |
| if (!NewSuffix || ReplacementDsc.OldSuffix == *NewSuffix) |
| return llvm::None; // The suffix was already the way it should be. |
| |
| if (RangeCanBeFixed) |
| ReplacementDsc.FixIt = FixItHint::CreateReplacement(*Range, *NewSuffix); |
| |
| return ReplacementDsc; |
| } |
| |
| } // namespace |
| |
| UppercaseLiteralSuffixCheck::UppercaseLiteralSuffixCheck( |
| StringRef Name, ClangTidyContext *Context) |
| : ClangTidyCheck(Name, Context), |
| NewSuffixes( |
| utils::options::parseStringList(Options.get("NewSuffixes", ""))), |
| IgnoreMacros(Options.getLocalOrGlobal("IgnoreMacros", true)) {} |
| |
| void UppercaseLiteralSuffixCheck::storeOptions( |
| ClangTidyOptions::OptionMap &Opts) { |
| Options.store(Opts, "NewSuffixes", |
| utils::options::serializeStringList(NewSuffixes)); |
| Options.store(Opts, "IgnoreMacros", IgnoreMacros); |
| } |
| |
| void UppercaseLiteralSuffixCheck::registerMatchers(MatchFinder *Finder) { |
| // Sadly, we can't check whether the literal has suffix or not. |
| // E.g. i32 suffix still results in 'BuiltinType::Kind::Int'. |
| // And such an info is not stored in the *Literal itself. |
| Finder->addMatcher( |
| stmt(eachOf(integerLiteral().bind(IntegerLiteralCheck::Name), |
| floatLiteral().bind(FloatingLiteralCheck::Name)), |
| unless(anyOf(hasParent(userDefinedLiteral()), |
| hasAncestor(substNonTypeTemplateParmExpr())))), |
| this); |
| } |
| |
| template <typename LiteralType> |
| bool UppercaseLiteralSuffixCheck::checkBoundMatch( |
| const MatchFinder::MatchResult &Result) { |
| const auto *Literal = |
| Result.Nodes.getNodeAs<typename LiteralType::type>(LiteralType::Name); |
| if (!Literal) |
| return false; |
| |
| // We won't *always* want to diagnose. |
| // We might have a suffix that is already uppercase. |
| if (auto Details = shouldReplaceLiteralSuffix<LiteralType>( |
| *Literal, NewSuffixes, *Result.SourceManager, getLangOpts())) { |
| if (Details->LiteralLocation.getBegin().isMacroID() && IgnoreMacros) |
| return true; |
| auto Complaint = diag(Details->LiteralLocation.getBegin(), |
| "%0 literal has suffix '%1', which is not uppercase") |
| << LiteralType::Name << Details->OldSuffix; |
| if (Details->FixIt) // Similarly, a fix-it is not always possible. |
| Complaint << *(Details->FixIt); |
| } |
| |
| return true; |
| } |
| |
| void UppercaseLiteralSuffixCheck::check( |
| const MatchFinder::MatchResult &Result) { |
| if (checkBoundMatch<IntegerLiteralCheck>(Result)) |
| return; // If it *was* IntegerLiteral, don't check for FloatingLiteral. |
| checkBoundMatch<FloatingLiteralCheck>(Result); |
| } |
| |
| } // namespace readability |
| } // namespace tidy |
| } // namespace clang |