1 //===--- UseUsingCheck.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 "UseUsingCheck.h"
10 #include "clang/AST/ASTContext.h"
11 #include "clang/Lex/Lexer.h"
12 
13 using namespace clang::ast_matchers;
14 
15 namespace clang {
16 namespace tidy {
17 namespace modernize {
18 
19 UseUsingCheck::UseUsingCheck(StringRef Name, ClangTidyContext *Context)
20     : ClangTidyCheck(Name, Context),
21       IgnoreMacros(Options.getLocalOrGlobal("IgnoreMacros", true)) {}
22 
23 void UseUsingCheck::registerMatchers(MatchFinder *Finder) {
24   if (!getLangOpts().CPlusPlus11)
25     return;
26   Finder->addMatcher(typedefDecl(unless(isInstantiated())).bind("typedef"),
27                      this);
28 }
29 
30 // Checks if 'typedef' keyword can be removed - we do it only if
31 // it is the only declaration in a declaration chain.
32 static bool CheckRemoval(SourceManager &SM, SourceLocation StartLoc,
33                          ASTContext &Context) {
34   assert(StartLoc.isFileID() && "StartLoc must not be in a macro");
35   std::pair<FileID, unsigned> LocInfo = SM.getDecomposedLoc(StartLoc);
36   StringRef File = SM.getBufferData(LocInfo.first);
37   const char *TokenBegin = File.data() + LocInfo.second;
38   Lexer DeclLexer(SM.getLocForStartOfFile(LocInfo.first), Context.getLangOpts(),
39                   File.begin(), TokenBegin, File.end());
40 
41   Token Tok;
42   int NestingLevel = 0; // Parens, braces, and square brackets
43   int AngleBracketLevel = 0;
44   bool FoundTypedef = false;
45 
46   while (!DeclLexer.LexFromRawLexer(Tok) && !Tok.is(tok::semi)) {
47     switch (Tok.getKind()) {
48     case tok::l_brace:
49       if (NestingLevel == 0 && AngleBracketLevel == 0) {
50         // At top level, this might be the `typedef struct {...} T;` case.
51         // Inside parens, square brackets, or angle brackets it's not.
52         return false;
53       }
54       ++NestingLevel;
55       break;
56     case tok::l_paren:
57     case tok::l_square:
58       ++NestingLevel;
59       break;
60     case tok::r_brace:
61     case tok::r_paren:
62     case tok::r_square:
63       --NestingLevel;
64       break;
65     case tok::less:
66       // If not nested in paren/brace/square bracket, treat as opening angle bracket.
67       if (NestingLevel == 0)
68         ++AngleBracketLevel;
69       break;
70     case tok::greater:
71       // Per C++ 17 Draft N4659, Section 17.2/3
72       //   https://timsong-cpp.github.io/cppwp/n4659/temp.names#3:
73       // "When parsing a template-argument-list, the first non-nested > is
74       // taken as the ending delimiter rather than a greater-than operator."
75       // If not nested in paren/brace/square bracket, treat as closing angle bracket.
76       if (NestingLevel == 0)
77         --AngleBracketLevel;
78       break;
79     case tok::comma:
80       if (NestingLevel == 0 && AngleBracketLevel == 0) {
81         // If there is a non-nested comma we have two or more declarations in this chain.
82         return false;
83       }
84       break;
85     case tok::raw_identifier:
86       if (Tok.getRawIdentifier() == "typedef") {
87         FoundTypedef = true;
88       }
89       break;
90     default:
91       break;
92     }
93   }
94 
95   // Sanity check against weird macro cases.
96   return FoundTypedef;
97 }
98 
99 void UseUsingCheck::check(const MatchFinder::MatchResult &Result) {
100   const auto *MatchedDecl = Result.Nodes.getNodeAs<TypedefDecl>("typedef");
101   if (MatchedDecl->getLocation().isInvalid())
102     return;
103 
104   auto &Context = *Result.Context;
105   auto &SM = *Result.SourceManager;
106 
107   SourceLocation StartLoc = MatchedDecl->getBeginLoc();
108 
109   if (StartLoc.isMacroID() && IgnoreMacros)
110     return;
111 
112   auto Diag = diag(StartLoc, "use 'using' instead of 'typedef'");
113 
114   // do not fix if there is macro or array
115   if (MatchedDecl->getUnderlyingType()->isArrayType() || StartLoc.isMacroID())
116     return;
117 
118   if (CheckRemoval(SM, StartLoc, Context)) {
119     auto printPolicy = PrintingPolicy(getLangOpts());
120     printPolicy.SuppressScope = true;
121     printPolicy.ConstantArraySizeAsWritten = true;
122     printPolicy.UseVoidForZeroParams = false;
123 
124     Diag << FixItHint::CreateReplacement(
125         MatchedDecl->getSourceRange(),
126         "using " + MatchedDecl->getNameAsString() + " = " +
127             MatchedDecl->getUnderlyingType().getAsString(printPolicy));
128   }
129 }
130 
131 } // namespace modernize
132 } // namespace tidy
133 } // namespace clang
134