1 //===--- MisleadingIndentationCheck.cpp - clang-tidy-----------------------===//
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
7 //===----------------------------------------------------------------------===//
9 #include "MisleadingIndentationCheck.h"
10 #include "../utils/LexerUtils.h"
11 #include "clang/AST/ASTContext.h"
12 #include "clang/ASTMatchers/ASTMatchFinder.h"
14 using namespace clang::ast_matchers
;
16 namespace clang::tidy::readability
{
18 static const IfStmt
*getPrecedingIf(const SourceManager
&SM
,
19 ASTContext
*Context
, const IfStmt
*If
) {
20 auto Parents
= Context
->getParents(*If
);
21 if (Parents
.size() != 1)
23 if (const auto *PrecedingIf
= Parents
[0].get
<IfStmt
>()) {
24 SourceLocation PreviousElseLoc
= PrecedingIf
->getElseLoc();
25 if (SM
.getExpansionLineNumber(PreviousElseLoc
) ==
26 SM
.getExpansionLineNumber(If
->getIfLoc()))
32 void MisleadingIndentationCheck::danglingElseCheck(const SourceManager
&SM
,
35 SourceLocation IfLoc
= If
->getIfLoc();
36 SourceLocation ElseLoc
= If
->getElseLoc();
38 if (IfLoc
.isMacroID() || ElseLoc
.isMacroID())
41 if (SM
.getExpansionLineNumber(If
->getThen()->getEndLoc()) ==
42 SM
.getExpansionLineNumber(ElseLoc
))
45 // Find location of first 'if' in a 'if else if' chain.
46 for (const auto *PrecedingIf
= getPrecedingIf(SM
, Context
, If
); PrecedingIf
;
47 PrecedingIf
= getPrecedingIf(SM
, Context
, PrecedingIf
))
48 IfLoc
= PrecedingIf
->getIfLoc();
50 if (SM
.getExpansionColumnNumber(IfLoc
) !=
51 SM
.getExpansionColumnNumber(ElseLoc
))
52 diag(ElseLoc
, "different indentation for 'if' and corresponding 'else'");
55 static bool isAtStartOfLineIncludingEmptyMacro(SourceLocation NextLoc
,
56 const SourceManager
&SM
,
57 const LangOptions
&LangOpts
) {
58 const SourceLocation BeforeLoc
=
59 utils::lexer::getPreviousTokenAndStart(NextLoc
, SM
, LangOpts
).second
;
60 if (BeforeLoc
.isInvalid())
62 return SM
.getExpansionLineNumber(BeforeLoc
) !=
63 SM
.getExpansionLineNumber(NextLoc
);
66 void MisleadingIndentationCheck::missingBracesCheck(
67 const SourceManager
&SM
, const CompoundStmt
*CStmt
,
68 const LangOptions
&LangOpts
) {
69 const static StringRef StmtNames
[] = {"if", "for", "while"};
70 for (unsigned int I
= 0; I
< CStmt
->size() - 1; I
++) {
71 const Stmt
*CurrentStmt
= CStmt
->body_begin()[I
];
72 const Stmt
*Inner
= nullptr;
75 if (const auto *CurrentIf
= dyn_cast
<IfStmt
>(CurrentStmt
)) {
78 CurrentIf
->getElse() ? CurrentIf
->getElse() : CurrentIf
->getThen();
79 } else if (const auto *CurrentFor
= dyn_cast
<ForStmt
>(CurrentStmt
)) {
81 Inner
= CurrentFor
->getBody();
82 } else if (const auto *CurrentWhile
= dyn_cast
<WhileStmt
>(CurrentStmt
)) {
84 Inner
= CurrentWhile
->getBody();
89 if (isa
<CompoundStmt
>(Inner
))
92 SourceLocation InnerLoc
= Inner
->getBeginLoc();
93 SourceLocation OuterLoc
= CurrentStmt
->getBeginLoc();
95 if (InnerLoc
.isInvalid() || InnerLoc
.isMacroID() || OuterLoc
.isInvalid() ||
99 if (SM
.getExpansionLineNumber(InnerLoc
) ==
100 SM
.getExpansionLineNumber(OuterLoc
))
103 const Stmt
*NextStmt
= CStmt
->body_begin()[I
+ 1];
104 SourceLocation NextLoc
= NextStmt
->getBeginLoc();
106 if (NextLoc
.isInvalid() || NextLoc
.isMacroID())
108 if (!isAtStartOfLineIncludingEmptyMacro(NextLoc
, SM
, LangOpts
))
111 if (SM
.getExpansionColumnNumber(InnerLoc
) ==
112 SM
.getExpansionColumnNumber(NextLoc
)) {
113 diag(NextLoc
, "misleading indentation: statement is indented too deeply");
114 diag(OuterLoc
, "did you mean this line to be inside this '%0'",
116 << StmtNames
[StmtKind
];
121 void MisleadingIndentationCheck::registerMatchers(MatchFinder
*Finder
) {
123 ifStmt(unless(hasThen(nullStmt())), hasElse(stmt())).bind("if"), this);
125 compoundStmt(has(stmt(anyOf(ifStmt(), forStmt(), whileStmt()))))
130 void MisleadingIndentationCheck::check(const MatchFinder::MatchResult
&Result
) {
131 if (const auto *If
= Result
.Nodes
.getNodeAs
<IfStmt
>("if"))
132 danglingElseCheck(*Result
.SourceManager
, Result
.Context
, If
);
134 if (const auto *CStmt
= Result
.Nodes
.getNodeAs
<CompoundStmt
>("compound"))
135 missingBracesCheck(*Result
.SourceManager
, CStmt
,
136 Result
.Context
->getLangOpts());
139 } // namespace clang::tidy::readability