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 "clang/AST/ASTContext.h"
11 #include "clang/ASTMatchers/ASTMatchFinder.h"
13 using namespace clang::ast_matchers
;
15 namespace clang::tidy::readability
{
17 static const IfStmt
*getPrecedingIf(const SourceManager
&SM
,
18 ASTContext
*Context
, const IfStmt
*If
) {
19 auto Parents
= Context
->getParents(*If
);
20 if (Parents
.size() != 1)
22 if (const auto *PrecedingIf
= Parents
[0].get
<IfStmt
>()) {
23 SourceLocation PreviousElseLoc
= PrecedingIf
->getElseLoc();
24 if (SM
.getExpansionLineNumber(PreviousElseLoc
) ==
25 SM
.getExpansionLineNumber(If
->getIfLoc()))
31 void MisleadingIndentationCheck::danglingElseCheck(const SourceManager
&SM
,
34 SourceLocation IfLoc
= If
->getIfLoc();
35 SourceLocation ElseLoc
= If
->getElseLoc();
37 if (IfLoc
.isMacroID() || ElseLoc
.isMacroID())
40 if (SM
.getExpansionLineNumber(If
->getThen()->getEndLoc()) ==
41 SM
.getExpansionLineNumber(ElseLoc
))
44 // Find location of first 'if' in a 'if else if' chain.
45 for (const auto *PrecedingIf
= getPrecedingIf(SM
, Context
, If
); PrecedingIf
;
46 PrecedingIf
= getPrecedingIf(SM
, Context
, PrecedingIf
))
47 IfLoc
= PrecedingIf
->getIfLoc();
49 if (SM
.getExpansionColumnNumber(IfLoc
) !=
50 SM
.getExpansionColumnNumber(ElseLoc
))
51 diag(ElseLoc
, "different indentation for 'if' and corresponding 'else'");
54 void MisleadingIndentationCheck::missingBracesCheck(const SourceManager
&SM
,
55 const CompoundStmt
*CStmt
) {
56 const static StringRef StmtNames
[] = {"if", "for", "while"};
57 for (unsigned int I
= 0; I
< CStmt
->size() - 1; I
++) {
58 const Stmt
*CurrentStmt
= CStmt
->body_begin()[I
];
59 const Stmt
*Inner
= nullptr;
62 if (const auto *CurrentIf
= dyn_cast
<IfStmt
>(CurrentStmt
)) {
65 CurrentIf
->getElse() ? CurrentIf
->getElse() : CurrentIf
->getThen();
66 } else if (const auto *CurrentFor
= dyn_cast
<ForStmt
>(CurrentStmt
)) {
68 Inner
= CurrentFor
->getBody();
69 } else if (const auto *CurrentWhile
= dyn_cast
<WhileStmt
>(CurrentStmt
)) {
71 Inner
= CurrentWhile
->getBody();
76 if (isa
<CompoundStmt
>(Inner
))
79 SourceLocation InnerLoc
= Inner
->getBeginLoc();
80 SourceLocation OuterLoc
= CurrentStmt
->getBeginLoc();
82 if (InnerLoc
.isInvalid() || InnerLoc
.isMacroID() || OuterLoc
.isInvalid() ||
86 if (SM
.getExpansionLineNumber(InnerLoc
) ==
87 SM
.getExpansionLineNumber(OuterLoc
))
90 const Stmt
*NextStmt
= CStmt
->body_begin()[I
+ 1];
91 SourceLocation NextLoc
= NextStmt
->getBeginLoc();
93 if (NextLoc
.isInvalid() || NextLoc
.isMacroID())
96 if (SM
.getExpansionColumnNumber(InnerLoc
) ==
97 SM
.getExpansionColumnNumber(NextLoc
)) {
98 diag(NextLoc
, "misleading indentation: statement is indented too deeply");
99 diag(OuterLoc
, "did you mean this line to be inside this '%0'",
101 << StmtNames
[StmtKind
];
106 void MisleadingIndentationCheck::registerMatchers(MatchFinder
*Finder
) {
108 ifStmt(unless(hasThen(nullStmt())), hasElse(stmt())).bind("if"), this);
110 compoundStmt(has(stmt(anyOf(ifStmt(), forStmt(), whileStmt()))))
115 void MisleadingIndentationCheck::check(const MatchFinder::MatchResult
&Result
) {
116 if (const auto *If
= Result
.Nodes
.getNodeAs
<IfStmt
>("if"))
117 danglingElseCheck(*Result
.SourceManager
, Result
.Context
, If
);
119 if (const auto *CStmt
= Result
.Nodes
.getNodeAs
<CompoundStmt
>("compound"))
120 missingBracesCheck(*Result
.SourceManager
, CStmt
);
123 } // namespace clang::tidy::readability