clang-tools 24.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 unless(isImplicit()))
89 .bind("initlist"),
90 this);
91}
92
93void TrailingCommaCheck::check(const MatchFinder::MatchResult &Result) {
94 if (const auto *Enum = Result.Nodes.getNodeAs<EnumDecl>("enum"))
95 checkEnumDecl(Enum, Result);
96 else if (const auto *InitList =
97 Result.Nodes.getNodeAs<InitListExpr>("initlist"))
98 checkInitListExpr(InitList, Result);
99 else
100 llvm_unreachable("No matches found");
101}
102
103void TrailingCommaCheck::checkEnumDecl(const EnumDecl *Enum,
104 const MatchFinder::MatchResult &Result) {
105 const bool IsSingleLine = isSingleLine(
106 {Enum->getBeginLoc(), Enum->getEndLoc()}, *Result.SourceManager);
107 const CommaPolicyKind Policy =
108 IsSingleLine ? SingleLineCommaPolicy : MultiLineCommaPolicy;
109
110 if (Policy == CommaPolicyKind::Ignore)
111 return;
112
113 const std::optional<Token> LastTok =
114 Lexer::findPreviousToken(Enum->getBraceRange().getEnd(),
115 *Result.SourceManager, getLangOpts(), false);
116 if (!LastTok)
117 return;
118
119 emitDiag(LastTok->getLocation(), LastTok, DiagKind::Enum, Result, Policy);
120}
121
122void TrailingCommaCheck::checkInitListExpr(
123 const InitListExpr *InitList, const MatchFinder::MatchResult &Result) {
124 // We need to use non-empty syntactic form for correct source locations.
125 if (const InitListExpr *SynInitInitList = InitList->getSyntacticForm()) {
126 if (SynInitInitList->getNumInits() == 0)
127 return;
128 InitList = SynInitInitList;
129 }
130
131 const bool IsSingleLine = isSingleLine(
132 {InitList->getBeginLoc(), InitList->getEndLoc()}, *Result.SourceManager);
133 const CommaPolicyKind Policy =
134 IsSingleLine ? SingleLineCommaPolicy : MultiLineCommaPolicy;
135
136 if (Policy == CommaPolicyKind::Ignore)
137 return;
138
139 const Expr *LastInit = InitList->inits().back();
140 assert(LastInit);
141
142 // Skip pack expansions - they already have special syntax with '...'
143 if (isa<PackExpansionExpr>(LastInit))
144 return;
145
146 const std::optional<Token> NextTok =
148 LastInit->getEndLoc(), *Result.SourceManager, getLangOpts());
149
150 // If the next token is neither a comma nor closing brace, there might be
151 // a macro (e.g., #define COMMA ,) that we can't safely analyze.
152 if (NextTok && !NextTok->isOneOf(tok::comma, tok::r_brace))
153 return;
154
155 emitDiag(LastInit->getEndLoc(), NextTok, DiagKind::InitList, Result, Policy);
156}
157
158void TrailingCommaCheck::emitDiag(
159 SourceLocation LastLoc, std::optional<Token> Token, DiagKind Kind,
160 const ast_matchers::MatchFinder::MatchResult &Result,
161 CommaPolicyKind Policy) {
162 if (LastLoc.isInvalid() || !Token)
163 return;
164
165 const bool HasTrailingComma = Token->is(tok::comma);
166 if (Policy == CommaPolicyKind::Append && !HasTrailingComma) {
167 const SourceLocation InsertLoc = Lexer::getLocForEndOfToken(
168 LastLoc, 0, *Result.SourceManager, getLangOpts());
169 diag(InsertLoc, "%select{initializer list|enum}0 should have "
170 "a trailing comma")
171 << Kind << FixItHint::CreateInsertion(InsertLoc, ",");
172 } else if (Policy == CommaPolicyKind::Remove && HasTrailingComma) {
173 const SourceLocation CommaLoc = Token->getLocation();
174 if (CommaLoc.isInvalid())
175 return;
176 diag(CommaLoc, "%select{initializer list|enum}0 should not have "
177 "a trailing comma")
178 << Kind << FixItHint::CreateRemoval(CommaLoc);
179 }
180}
181
182} // 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:106
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...