clang-tools 23.0.0git
TrailingCommaCheck.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
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
10#include "../utils/LexerUtils.h"
11#include "clang/AST/ASTContext.h"
12#include "clang/ASTMatchers/ASTMatchFinder.h"
13#include "clang/Lex/Lexer.h"
14
15using namespace clang::ast_matchers;
16
17namespace clang::tidy {
18
19template <>
20struct OptionEnumMapping<readability::TrailingCommaCheck::CommaPolicyKind> {
21 static llvm::ArrayRef<
22 std::pair<readability::TrailingCommaCheck::CommaPolicyKind, StringRef>>
24 static constexpr std::pair<readability::TrailingCommaCheck::CommaPolicyKind,
25 StringRef>
26 Mapping[] = {
28 "Append"},
30 "Remove"},
32 "Ignore"},
33 };
34 return {Mapping};
35 }
36};
37
38} // namespace clang::tidy
39
41
42static bool isSingleLine(SourceRange Range, const SourceManager &SM) {
43 return SM.getExpansionLineNumber(Range.getBegin()) ==
44 SM.getExpansionLineNumber(Range.getEnd());
45}
46
47namespace {
48
50 AST_POLYMORPHIC_SUPPORTED_TYPES(EnumDecl,
51 InitListExpr)) {
52 return Node.getBeginLoc().isMacroID() || Node.getEndLoc().isMacroID();
53}
54
55AST_MATCHER(EnumDecl, isEmptyEnum) { return Node.enumerators().empty(); }
56
57AST_MATCHER(InitListExpr, isEmptyInitList) { return Node.getNumInits() == 0; }
58
59} // namespace
60
62 ClangTidyContext *Context)
63 : ClangTidyCheck(Name, Context),
64 SingleLineCommaPolicy(
65 Options.get("SingleLineCommaPolicy", CommaPolicyKind::Remove)),
66 MultiLineCommaPolicy(
67 Options.get("MultiLineCommaPolicy", CommaPolicyKind::Append)) {
68 if (SingleLineCommaPolicy == CommaPolicyKind::Ignore &&
69 MultiLineCommaPolicy == CommaPolicyKind::Ignore)
70 configurationDiag("The check '%0' will not perform any analysis because "
71 "'SingleLineCommaPolicy' and 'MultiLineCommaPolicy' are "
72 "both set to 'Ignore'.")
73 << Name;
74}
75
77 Options.store(Opts, "SingleLineCommaPolicy", SingleLineCommaPolicy);
78 Options.store(Opts, "MultiLineCommaPolicy", MultiLineCommaPolicy);
79}
80
81void TrailingCommaCheck::registerMatchers(MatchFinder *Finder) {
82 Finder->addMatcher(
83 enumDecl(isDefinition(), unless(isEmptyEnum()), unless(isMacro()))
84 .bind("enum"),
85 this);
86
87 Finder->addMatcher(initListExpr(unless(isEmptyInitList()), unless(isMacro()))
88 .bind("initlist"),
89 this);
90}
91
92void TrailingCommaCheck::check(const MatchFinder::MatchResult &Result) {
93 if (const auto *Enum = Result.Nodes.getNodeAs<EnumDecl>("enum"))
94 checkEnumDecl(Enum, Result);
95 else if (const auto *InitList =
96 Result.Nodes.getNodeAs<InitListExpr>("initlist"))
97 checkInitListExpr(InitList, Result);
98 else
99 llvm_unreachable("No matches found");
100}
101
102void TrailingCommaCheck::checkEnumDecl(const EnumDecl *Enum,
103 const MatchFinder::MatchResult &Result) {
104 const bool IsSingleLine = isSingleLine(
105 {Enum->getBeginLoc(), Enum->getEndLoc()}, *Result.SourceManager);
106 const CommaPolicyKind Policy =
107 IsSingleLine ? SingleLineCommaPolicy : MultiLineCommaPolicy;
108
109 if (Policy == CommaPolicyKind::Ignore)
110 return;
111
112 const std::optional<Token> LastTok =
113 Lexer::findPreviousToken(Enum->getBraceRange().getEnd(),
114 *Result.SourceManager, getLangOpts(), false);
115 if (!LastTok)
116 return;
117
118 emitDiag(LastTok->getLocation(), LastTok, DiagKind::Enum, Result, Policy);
119}
120
121void TrailingCommaCheck::checkInitListExpr(
122 const InitListExpr *InitList, const MatchFinder::MatchResult &Result) {
123 // We need to use non-empty syntactic form for correct source locations.
124 if (const InitListExpr *SynInitInitList = InitList->getSyntacticForm();
125 SynInitInitList && SynInitInitList->getNumInits() > 0)
126 InitList = SynInitInitList;
127
128 const bool IsSingleLine = isSingleLine(
129 {InitList->getBeginLoc(), InitList->getEndLoc()}, *Result.SourceManager);
130 const CommaPolicyKind Policy =
131 IsSingleLine ? SingleLineCommaPolicy : MultiLineCommaPolicy;
132
133 if (Policy == CommaPolicyKind::Ignore)
134 return;
135
136 const Expr *LastInit = InitList->inits().back();
137 assert(LastInit);
138
139 // Skip pack expansions - they already have special syntax with '...'
140 if (isa<PackExpansionExpr>(LastInit))
141 return;
142
143 const std::optional<Token> NextTok =
145 LastInit->getEndLoc(), *Result.SourceManager, getLangOpts());
146
147 // If the next token is neither a comma nor closing brace, there might be
148 // a macro (e.g., #define COMMA ,) that we can't safely analyze.
149 if (NextTok && !NextTok->isOneOf(tok::comma, tok::r_brace))
150 return;
151
152 emitDiag(LastInit->getEndLoc(), NextTok, DiagKind::InitList, Result, Policy);
153}
154
155void TrailingCommaCheck::emitDiag(
156 SourceLocation LastLoc, std::optional<Token> Token, DiagKind Kind,
157 const ast_matchers::MatchFinder::MatchResult &Result,
158 CommaPolicyKind Policy) {
159 if (LastLoc.isInvalid() || !Token)
160 return;
161
162 const bool HasTrailingComma = Token->is(tok::comma);
163 if (Policy == CommaPolicyKind::Append && !HasTrailingComma) {
164 const SourceLocation InsertLoc = Lexer::getLocForEndOfToken(
165 LastLoc, 0, *Result.SourceManager, getLangOpts());
166 diag(InsertLoc, "%select{initializer list|enum}0 should have "
167 "a trailing comma")
168 << Kind << FixItHint::CreateInsertion(InsertLoc, ",");
169 } else if (Policy == CommaPolicyKind::Remove && HasTrailingComma) {
170 const SourceLocation CommaLoc = Token->getLocation();
171 if (CommaLoc.isInvalid())
172 return;
173 diag(CommaLoc, "%select{initializer list|enum}0 should not have "
174 "a trailing comma")
175 << Kind << FixItHint::CreateRemoval(CommaLoc);
176 }
177}
178
179} // namespace clang::tidy::readability
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
TrailingCommaCheck(StringRef Name, ClangTidyContext *Context)
void registerMatchers(ast_matchers::MatchFinder *Finder) override
AST_POLYMORPHIC_MATCHER(isInAbseilFile, AST_POLYMORPHIC_SUPPORTED_TYPES(Decl, Stmt, TypeLoc, NestedNameSpecifierLoc))
Matches AST nodes that were found within Abseil files.
static bool isSingleLine(SourceRange Range, const SourceManager &SM)
std::optional< Token > findNextTokenSkippingComments(SourceLocation Start, const SourceManager &SM, const LangOptions &LangOpts)
Definition LexerUtils.h:104
llvm::StringMap< ClangTidyValue > OptionMap
static llvm::ArrayRef< std::pair< readability::TrailingCommaCheck::CommaPolicyKind, StringRef > > getEnumMapping()
This class should be specialized by any enum type that needs to be converted to and from an llvm::Str...