xref: /llvm-project/clang-tools-extra/clang-tidy/readability/ConvertMemberFunctionsToStatic.cpp (revision b6f6be4b500ff64c23a5103ac3311cb74519542f)
1 //===--- ConvertMemberFunctionsToStatic.cpp - clang-tidy ------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #include "ConvertMemberFunctionsToStatic.h"
10 #include "../utils/Matchers.h"
11 #include "clang/AST/ASTContext.h"
12 #include "clang/AST/DeclCXX.h"
13 #include "clang/AST/RecursiveASTVisitor.h"
14 #include "clang/ASTMatchers/ASTMatchFinder.h"
15 #include "clang/Basic/SourceLocation.h"
16 #include "clang/Lex/Lexer.h"
17 
18 using namespace clang::ast_matchers;
19 
20 namespace clang::tidy::readability {
21 
22 namespace {
23 
24 AST_MATCHER(CXXMethodDecl, isOverloadedOperator) {
25   return Node.isOverloadedOperator();
26 }
27 
28 AST_MATCHER(CXXMethodDecl, usesThis) {
29   class FindUsageOfThis : public RecursiveASTVisitor<FindUsageOfThis> {
30   public:
31     bool Used = false;
32 
33     bool VisitCXXThisExpr(const CXXThisExpr *E) {
34       Used = true;
35       return false; // Stop traversal.
36     }
37 
38     // If we enter a class declaration, don't traverse into it as any usages of
39     // `this` will correspond to the nested class.
40     bool TraverseCXXRecordDecl(CXXRecordDecl *RD) { return true; }
41 
42   } UsageOfThis;
43 
44   // TraverseStmt does not modify its argument.
45   UsageOfThis.TraverseStmt(const_cast<Stmt *>(Node.getBody()));
46 
47   return UsageOfThis.Used;
48 }
49 
50 } // namespace
51 
52 void ConvertMemberFunctionsToStatic::registerMatchers(MatchFinder *Finder) {
53   Finder->addMatcher(
54       cxxMethodDecl(
55           isDefinition(), isUserProvided(),
56           unless(anyOf(
57               isExpansionInSystemHeader(), isVirtual(),
58               matchers::isStaticMethod(), matchers::hasTrivialBody(),
59               isOverloadedOperator(), cxxConstructorDecl(), cxxDestructorDecl(),
60               cxxConversionDecl(), matchers::isTemplate(),
61               matchers::isDependentContext(),
62               ofClass(anyOf(
63                   isLambda(),
64                   matchers::hasAnyDependentBases()) // Method might become
65                                                     // virtual depending on
66                                                     // template base class.
67                       ),
68               matchers::isInsideMacroDefinition(),
69               matchers::hasCanonicalDecl(matchers::isInsideMacroDefinition()),
70               usesThis())))
71           .bind("x"),
72       this);
73 }
74 
75 /// Obtain the original source code text from a SourceRange.
76 static StringRef getStringFromRange(SourceManager &SourceMgr,
77                                     const LangOptions &LangOpts,
78                                     SourceRange Range) {
79   if (SourceMgr.getFileID(Range.getBegin()) !=
80       SourceMgr.getFileID(Range.getEnd()))
81     return {};
82 
83   return Lexer::getSourceText(CharSourceRange(Range, true), SourceMgr,
84                               LangOpts);
85 }
86 
87 static SourceRange getLocationOfConst(const TypeSourceInfo *TSI,
88                                       SourceManager &SourceMgr,
89                                       const LangOptions &LangOpts) {
90   assert(TSI);
91   const auto FTL = TSI->getTypeLoc().IgnoreParens().getAs<FunctionTypeLoc>();
92   assert(FTL);
93 
94   SourceRange Range{FTL.getRParenLoc().getLocWithOffset(1),
95                     FTL.getLocalRangeEnd()};
96   // Inside Range, there might be other keywords and trailing return types.
97   // Find the exact position of "const".
98   StringRef Text = getStringFromRange(SourceMgr, LangOpts, Range);
99   size_t Offset = Text.find("const");
100   if (Offset == StringRef::npos)
101     return {};
102 
103   SourceLocation Start = Range.getBegin().getLocWithOffset(Offset);
104   return {Start, Start.getLocWithOffset(strlen("const") - 1)};
105 }
106 
107 void ConvertMemberFunctionsToStatic::check(
108     const MatchFinder::MatchResult &Result) {
109   const auto *Definition = Result.Nodes.getNodeAs<CXXMethodDecl>("x");
110 
111   // TODO: For out-of-line declarations, don't modify the source if the header
112   // is excluded by the -header-filter option.
113   DiagnosticBuilder Diag =
114       diag(Definition->getLocation(), "method %0 can be made static")
115       << Definition;
116 
117   // TODO: Would need to remove those in a fix-it.
118   if (Definition->getMethodQualifiers().hasVolatile() ||
119       Definition->getMethodQualifiers().hasRestrict() ||
120       Definition->getRefQualifier() != RQ_None)
121     return;
122 
123   const CXXMethodDecl *Declaration = Definition->getCanonicalDecl();
124 
125   if (Definition->isConst()) {
126     // Make sure that we either remove 'const' on both declaration and
127     // definition or emit no fix-it at all.
128     SourceRange DefConst = getLocationOfConst(Definition->getTypeSourceInfo(),
129                                               *Result.SourceManager,
130                                               Result.Context->getLangOpts());
131 
132     if (DefConst.isInvalid())
133       return;
134 
135     if (Declaration != Definition) {
136       SourceRange DeclConst = getLocationOfConst(
137           Declaration->getTypeSourceInfo(), *Result.SourceManager,
138           Result.Context->getLangOpts());
139 
140       if (DeclConst.isInvalid())
141         return;
142       Diag << FixItHint::CreateRemoval(DeclConst);
143     }
144 
145     // Remove existing 'const' from both declaration and definition.
146     Diag << FixItHint::CreateRemoval(DefConst);
147   }
148   Diag << FixItHint::CreateInsertion(Declaration->getBeginLoc(), "static ");
149 }
150 
151 } // namespace clang::tidy::readability
152