| /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying | |
| file LICENSE.rst or https://cmake.org/licensing for details. */ | |
| #include "StringConcatenationUseCmstrcatCheck.h" | |
| #include <cassert> | |
| #include <clang/ASTMatchers/ASTMatchFinder.h> | |
| #include <clang/Lex/Lexer.h> | |
| namespace clang { | |
| namespace tidy { | |
| namespace cmake { | |
| using namespace ast_matchers; | |
| StringConcatenationUseCmstrcatCheck::StringConcatenationUseCmstrcatCheck( | |
| StringRef Name, ClangTidyContext* Context) | |
| : ClangTidyCheck(Name, Context) | |
| { | |
| } | |
| void StringConcatenationUseCmstrcatCheck::registerMatchers(MatchFinder* Finder) | |
| { | |
| auto IsString = expr(hasType(qualType(hasUnqualifiedDesugaredType( | |
| recordType(hasDeclaration(classTemplateSpecializationDecl( | |
| hasName("::std::basic_string"), | |
| hasTemplateArgument( | |
| 0, templateArgument(refersToType(asString("char"))))))))))); | |
| auto IsChar = expr(hasType(asString("char"))); | |
| auto IsCharPtr = expr(hasType(pointerType(pointee(asString("const char"))))); | |
| auto IsStringConcat = | |
| cxxOperatorCallExpr(hasOperatorName("+"), | |
| anyOf(allOf(hasLHS(IsString), hasRHS(IsString)), | |
| allOf(hasLHS(IsString), hasRHS(IsChar)), | |
| allOf(hasLHS(IsString), hasRHS(IsCharPtr)), | |
| allOf(hasLHS(IsChar), hasRHS(IsString)), | |
| allOf(hasLHS(IsCharPtr), hasRHS(IsString)))); | |
| auto IsStringAppend = cxxOperatorCallExpr( | |
| hasOperatorName("+="), hasLHS(IsString), | |
| anyOf(hasRHS(IsString), hasRHS(IsChar), hasRHS(IsCharPtr))); | |
| auto IsStringConcatWithLHS = | |
| cxxOperatorCallExpr( | |
| IsStringConcat, | |
| optionally(hasLHS(materializeTemporaryExpr( | |
| has(cxxBindTemporaryExpr(has(IsStringConcat.bind("lhs")))))))) | |
| .bind("concat"); | |
| auto IsStringAppendWithRHS = | |
| cxxOperatorCallExpr( | |
| IsStringAppend, | |
| optionally(hasRHS(materializeTemporaryExpr(has(implicitCastExpr( | |
| has(cxxBindTemporaryExpr(has(IsStringConcat.bind("rhs")))))))))) | |
| .bind("append"); | |
| Finder->addMatcher(IsStringConcatWithLHS, this); | |
| Finder->addMatcher(IsStringAppendWithRHS, this); | |
| } | |
| void StringConcatenationUseCmstrcatCheck::check( | |
| MatchFinder::MatchResult const& Result) | |
| { | |
| CXXOperatorCallExpr const* AppendNode = | |
| Result.Nodes.getNodeAs<CXXOperatorCallExpr>("append"); | |
| CXXOperatorCallExpr const* ConcatNode = | |
| Result.Nodes.getNodeAs<CXXOperatorCallExpr>("concat"); | |
| if (AppendNode != nullptr) { | |
| if (AppendNode->getBeginLoc().isValid()) { | |
| assert(InProgressExprChains.find(AppendNode) == | |
| InProgressExprChains.end()); | |
| ExprChain TmpExprChain = | |
| std::make_pair(OperatorType::PlusEquals, | |
| std::vector<CXXOperatorCallExpr const*>{ AppendNode }); | |
| CXXOperatorCallExpr const* RHSNode = | |
| Result.Nodes.getNodeAs<CXXOperatorCallExpr>("rhs"); | |
| if (RHSNode != nullptr) { | |
| if (RHSNode->getBeginLoc().isValid()) { | |
| InProgressExprChains[RHSNode] = std::move(TmpExprChain); | |
| } | |
| } else { | |
| issueCorrection(TmpExprChain, Result); | |
| } | |
| } | |
| } | |
| if (ConcatNode != nullptr) { | |
| if (ConcatNode->getBeginLoc().isValid()) { | |
| ExprChain TmpExprChain; | |
| if (!(InProgressExprChains.find(ConcatNode) == | |
| InProgressExprChains.end())) { | |
| TmpExprChain = std::move(InProgressExprChains[ConcatNode]); | |
| InProgressExprChains.erase(ConcatNode); | |
| if (TmpExprChain.first == OperatorType::PlusEquals) { | |
| TmpExprChain.second.insert(TmpExprChain.second.begin() + 1, | |
| ConcatNode); | |
| } else { | |
| TmpExprChain.second.insert(TmpExprChain.second.begin(), ConcatNode); | |
| } | |
| } else { | |
| TmpExprChain = std::make_pair( | |
| OperatorType::Plus, | |
| std::vector<CXXOperatorCallExpr const*>{ ConcatNode }); | |
| } | |
| CXXOperatorCallExpr const* LHSNode = | |
| Result.Nodes.getNodeAs<CXXOperatorCallExpr>("lhs"); | |
| if (LHSNode != nullptr) { | |
| if (LHSNode->getBeginLoc().isValid()) { | |
| InProgressExprChains[LHSNode] = std::move(TmpExprChain); | |
| } | |
| } else { | |
| issueCorrection(TmpExprChain, Result); | |
| } | |
| } | |
| } | |
| } | |
| void StringConcatenationUseCmstrcatCheck::issueCorrection( | |
| ExprChain const& Chain, MatchFinder::MatchResult const& Result) | |
| { | |
| if (Chain.second.size() < 2) { | |
| return; | |
| } | |
| std::vector<FixItHint> FixIts; | |
| CXXOperatorCallExpr const* ExprNode; | |
| std::vector<clang::CXXOperatorCallExpr const*>::const_iterator It = | |
| Chain.second.begin(); | |
| if (Chain.first == OperatorType::PlusEquals) { | |
| ExprNode = *It; | |
| StringRef LHS = Lexer::getSourceText( | |
| CharSourceRange::getTokenRange(ExprNode->getArg(0)->getSourceRange()), | |
| Result.Context->getSourceManager(), Result.Context->getLangOpts()); | |
| FixIts.push_back(FixItHint::CreateReplacement( | |
| ExprNode->getExprLoc(), "= cmStrCat(" + LHS.str() + ",")); | |
| It++; | |
| } else { | |
| ExprNode = *It; | |
| FixIts.push_back( | |
| FixItHint::CreateInsertion(ExprNode->getBeginLoc(), "cmStrCat(")); | |
| } | |
| while (It != std::end(Chain.second)) { | |
| ExprNode = *It; | |
| FixIts.push_back( | |
| FixItHint::CreateReplacement(ExprNode->getOperatorLoc(), ",")); | |
| It++; | |
| } | |
| It--; | |
| ExprNode = *It; | |
| StringRef LastToken = Lexer::getSourceText( | |
| CharSourceRange::getTokenRange( | |
| ExprNode->getArg(1)->getSourceRange().getEnd()), | |
| Result.Context->getSourceManager(), Result.Context->getLangOpts()); | |
| FixIts.push_back(FixItHint::CreateInsertion( | |
| ExprNode->getEndLoc().getLocWithOffset(LastToken.str().size()), ")")); | |
| It = Chain.second.begin(); | |
| ExprNode = *It; | |
| if (Chain.first == OperatorType::PlusEquals) { | |
| this->diag(ExprNode->getOperatorLoc(), | |
| "use cmStrCat() instead of string append") | |
| << FixIts; | |
| } else { | |
| this->diag(ExprNode->getBeginLoc(), | |
| "use cmStrCat() instead of string concatenation") | |
| << FixIts; | |
| } | |
| } | |
| } | |
| } | |
| } |