1111#include " clang/Lex/Lexer.h"
1212#include " clang/Lex/PPCallbacks.h"
1313#include " clang/Lex/Preprocessor.h"
14+ #include " llvm/ADT/STLForwardCompat.h"
1415
1516#include < array>
1617
1718namespace clang ::tidy::readability {
1819
1920namespace {
2021
21- class IfPreprocessorCallbacks final : public PPCallbacks {
22+ enum class DirectiveKind : bool { If, Elif };
23+
24+ struct Rewrite {
25+ SourceLocation DirectiveLoc;
26+ SourceRange ConditionRange;
27+ StringRef Macro;
28+ bool Negated;
29+ DirectiveKind Directive;
30+ };
31+
32+ struct StackEntry {
33+ SmallVector<Rewrite, 2 > Rewrites;
34+ bool ApplyingRewritesWouldBreakConsistency = false ;
35+ };
36+
37+ class UseConciseDirectivesPPCallbacks final : public PPCallbacks {
2238public:
23- IfPreprocessorCallbacks (ClangTidyCheck &Check, const Preprocessor &PP)
24- : Check(Check), PP(PP) {}
39+ UseConciseDirectivesPPCallbacks (UseConcisePreprocessorDirectivesCheck &Check,
40+ const Preprocessor &PP,
41+ bool PreserveConsistency)
42+ : Check(Check), PP(PP), PreserveConsistency(PreserveConsistency) {}
43+
44+ void Ifdef (SourceLocation, const Token &, const MacroDefinition &) override {
45+ Stack.emplace_back ();
46+ }
47+
48+ void Ifndef (SourceLocation, const Token &, const MacroDefinition &) override {
49+ Stack.emplace_back ();
50+ }
2551
2652 void If (SourceLocation Loc, SourceRange ConditionRange,
2753 ConditionValueKind) override {
28- impl (Loc, ConditionRange, {" ifdef" , " ifndef" });
54+ Stack.emplace_back ();
55+ handleCondition (Loc, ConditionRange, DirectiveKind::If);
2956 }
3057
3158 void Elif (SourceLocation Loc, SourceRange ConditionRange, ConditionValueKind,
3259 SourceLocation) override {
33- if (PP.getLangOpts ().C23 || PP.getLangOpts ().CPlusPlus23 )
34- impl (Loc, ConditionRange, {" elifdef" , " elifndef" });
60+ handleCondition (Loc, ConditionRange, DirectiveKind::Elif);
61+ }
62+
63+ void Endif (SourceLocation, SourceLocation) override {
64+ static constexpr std::array<std::array<StringRef, 2 >, 2 > Replacements = {{
65+ {" ifdef" , " ifndef" },
66+ {" elifdef" , " elifndef" },
67+ }};
68+
69+ const auto &[Rewrites, ApplyingRewritesWouldBreakConsistency] =
70+ Stack.back ();
71+ if (!(PreserveConsistency && ApplyingRewritesWouldBreakConsistency)) {
72+ for (const auto &[DirectiveLoc, ConditionRange, Macro, Negated,
73+ Directive] : Rewrites) {
74+ const StringRef Replacement =
75+ Replacements[llvm::to_underlying (Directive)][Negated];
76+ Check.diag (
77+ DirectiveLoc,
78+ " preprocessor condition can be written more concisely using '#%0'" )
79+ << Replacement
80+ << FixItHint::CreateReplacement (DirectiveLoc, Replacement)
81+ << FixItHint::CreateReplacement (ConditionRange, Macro);
82+ }
83+ }
84+
85+ Stack.pop_back ();
3586 }
3687
3788private:
38- void impl (SourceLocation DirectiveLoc, SourceRange ConditionRange,
39- const std::array<llvm::StringLiteral, 2 > &Replacements) {
89+ struct RewriteInfo {
90+ StringRef Macro;
91+ bool Negated;
92+ };
93+
94+ void handleCondition (SourceLocation DirectiveLoc, SourceRange ConditionRange,
95+ DirectiveKind Directive) {
96+ if (Directive != DirectiveKind::Elif || PP.getLangOpts ().C23 ||
97+ PP.getLangOpts ().CPlusPlus23 )
98+ if (const std::optional<RewriteInfo> Rewrite =
99+ tryRewrite (ConditionRange)) {
100+ Stack.back ().Rewrites .push_back ({DirectiveLoc, ConditionRange,
101+ Rewrite->Macro , Rewrite->Negated ,
102+ Directive});
103+ return ;
104+ }
105+
106+ if (!Stack.back ().ApplyingRewritesWouldBreakConsistency )
107+ Stack.back ().ApplyingRewritesWouldBreakConsistency =
108+ conditionContainsDefinedOperator (ConditionRange);
109+ }
110+
111+ std::optional<RewriteInfo> tryRewrite (SourceRange ConditionRange) {
40112 // Lexer requires its input range to be null-terminated.
41- SmallString< 128 > Condition =
113+ const StringRef SourceText =
42114 Lexer::getSourceText (CharSourceRange::getTokenRange (ConditionRange),
43115 PP.getSourceManager (), PP.getLangOpts ());
116+ SmallString<128 > Condition = SourceText;
44117 Condition.push_back (' \0 ' );
45- Lexer Lex (DirectiveLoc , PP.getLangOpts (), Condition.data (),
46- Condition.data (), Condition. data () + Condition.size () - 1 );
118+ Lexer Lex ({} , PP.getLangOpts (), Condition. data (), Condition.data (),
119+ Condition.data () + Condition.size () - 1 );
47120 Token Tok;
48- bool Inverted = false ; // The inverted form of #*def is #*ndef.
121+ bool Negated = false ;
49122 std::size_t ParensNestingDepth = 0 ;
50123 for (;;) {
51124 if (Lex.LexFromRawLexer (Tok))
52- return ;
125+ return {} ;
53126
54127 if (Tok.is (tok::TokenKind::exclaim) ||
55128 (PP.getLangOpts ().CPlusPlus &&
56129 Tok.is (tok::TokenKind::raw_identifier) &&
57130 Tok.getRawIdentifier () == " not" ))
58- Inverted = !Inverted ;
131+ Negated = !Negated ;
59132 else if (Tok.is (tok::TokenKind::l_paren))
60133 ++ParensNestingDepth;
61134 else
@@ -64,47 +137,80 @@ class IfPreprocessorCallbacks final : public PPCallbacks {
64137
65138 if (Tok.isNot (tok::TokenKind::raw_identifier) ||
66139 Tok.getRawIdentifier () != " defined" )
67- return ;
140+ return {} ;
68141
69142 bool NoMoreTokens = Lex.LexFromRawLexer (Tok);
70143 if (Tok.is (tok::TokenKind::l_paren)) {
71144 if (NoMoreTokens)
72- return ;
145+ return {} ;
73146 ++ParensNestingDepth;
74147 NoMoreTokens = Lex.LexFromRawLexer (Tok);
75148 }
76149
77150 if (Tok.isNot (tok::TokenKind::raw_identifier))
78- return ;
79- const StringRef Macro = Tok.getRawIdentifier ();
151+ return {};
152+
153+ // We need a stable StringRef into the ConditionRange, but because Lexer
154+ // forces us to work on a temporary null-terminated copy of the
155+ // ConditionRange, we need to do this ugly translation.
156+ const StringRef Macro = {
157+ SourceText.data () + (Tok.getRawIdentifier ().data () - Condition.data ()),
158+ Tok.getRawIdentifier ().size ()};
80159
81160 while (!NoMoreTokens) {
82161 NoMoreTokens = Lex.LexFromRawLexer (Tok);
83162 if (Tok.isNot (tok::TokenKind::r_paren))
84- return ;
163+ return {} ;
85164 --ParensNestingDepth;
86165 }
87166
88167 if (ParensNestingDepth != 0 )
89- return ;
90-
91- Check.diag (
92- DirectiveLoc,
93- " preprocessor condition can be written more concisely using '#%0'" )
94- << FixItHint::CreateReplacement (DirectiveLoc, Replacements[Inverted])
95- << FixItHint::CreateReplacement (ConditionRange, Macro)
96- << Replacements[Inverted];
168+ return {};
169+
170+ return {{Macro, Negated}};
97171 }
98172
99- ClangTidyCheck &Check;
173+ bool conditionContainsDefinedOperator (SourceRange ConditionRange) {
174+ // Lexer requires its input range to be null-terminated.
175+ SmallString<128 > Condition =
176+ Lexer::getSourceText (CharSourceRange::getTokenRange (ConditionRange),
177+ PP.getSourceManager (), PP.getLangOpts ());
178+ Condition.push_back (' \0 ' );
179+ Lexer Lex ({}, PP.getLangOpts (), Condition.data (), Condition.data (),
180+ Condition.data () + Condition.size () - 1 );
181+
182+ for (Token Tok;;) {
183+ const bool NoMoreTokens = Lex.LexFromRawLexer (Tok);
184+ if (Tok.is (tok::TokenKind::raw_identifier) &&
185+ Tok.getRawIdentifier () == " defined" )
186+ return true ;
187+ if (NoMoreTokens)
188+ return false ;
189+ }
190+ }
191+
192+ UseConcisePreprocessorDirectivesCheck &Check;
100193 const Preprocessor &PP;
194+ const bool PreserveConsistency;
195+ SmallVector<StackEntry, 4 > Stack;
101196};
102197
103198} // namespace
104199
200+ UseConcisePreprocessorDirectivesCheck::UseConcisePreprocessorDirectivesCheck (
201+ StringRef Name, ClangTidyContext *Context)
202+ : ClangTidyCheck(Name, Context),
203+ PreserveConsistency (Options.get(" PreserveConsistency" , false )) {}
204+
205+ void UseConcisePreprocessorDirectivesCheck::storeOptions (
206+ ClangTidyOptions::OptionMap &Opts) {
207+ Options.store (Opts, " PreserveConsistency" , PreserveConsistency);
208+ }
209+
105210void UseConcisePreprocessorDirectivesCheck::registerPPCallbacks (
106211 const SourceManager &, Preprocessor *PP, Preprocessor *) {
107- PP->addPPCallbacks (std::make_unique<IfPreprocessorCallbacks>(*this , *PP));
212+ PP->addPPCallbacks (std::make_unique<UseConciseDirectivesPPCallbacks>(
213+ *this , *PP, PreserveConsistency));
108214}
109215
110216} // namespace clang::tidy::readability
0 commit comments