1 //===--- MisleadingIndentationCheck.cpp - clang-tidy-----------------------===//
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 
9 #include "MisleadingIndentationCheck.h"
10 #include "clang/AST/ASTContext.h"
11 #include "clang/ASTMatchers/ASTMatchFinder.h"
12 
13 using namespace clang::ast_matchers;
14 
15 namespace clang {
16 namespace tidy {
17 namespace readability {
18 
getPrecedingIf(const SourceManager & SM,ASTContext * Context,const IfStmt * If)19 static const IfStmt *getPrecedingIf(const SourceManager &SM,
20                                     ASTContext *Context, const IfStmt *If) {
21   auto parents = Context->getParents(*If);
22   if (parents.size() != 1)
23     return nullptr;
24   if (const auto *PrecedingIf = parents[0].get<IfStmt>()) {
25     SourceLocation PreviousElseLoc = PrecedingIf->getElseLoc();
26     if (SM.getExpansionLineNumber(PreviousElseLoc) ==
27         SM.getExpansionLineNumber(If->getIfLoc()))
28       return PrecedingIf;
29   }
30   return nullptr;
31 }
32 
danglingElseCheck(const SourceManager & SM,ASTContext * Context,const IfStmt * If)33 void MisleadingIndentationCheck::danglingElseCheck(const SourceManager &SM,
34                                                    ASTContext *Context,
35                                                    const IfStmt *If) {
36   SourceLocation IfLoc = If->getIfLoc();
37   SourceLocation ElseLoc = If->getElseLoc();
38 
39   if (IfLoc.isMacroID() || ElseLoc.isMacroID())
40     return;
41 
42   if (SM.getExpansionLineNumber(If->getThen()->getEndLoc()) ==
43       SM.getExpansionLineNumber(ElseLoc))
44     return;
45 
46   // Find location of first 'if' in a 'if else if' chain.
47   for (auto PrecedingIf = getPrecedingIf(SM, Context, If); PrecedingIf;
48        PrecedingIf = getPrecedingIf(SM, Context, PrecedingIf))
49     IfLoc = PrecedingIf->getIfLoc();
50 
51   if (SM.getExpansionColumnNumber(IfLoc) !=
52       SM.getExpansionColumnNumber(ElseLoc))
53     diag(ElseLoc, "different indentation for 'if' and corresponding 'else'");
54 }
55 
missingBracesCheck(const SourceManager & SM,const CompoundStmt * CStmt)56 void MisleadingIndentationCheck::missingBracesCheck(const SourceManager &SM,
57                                                     const CompoundStmt *CStmt) {
58   const static StringRef StmtNames[] = {"if", "for", "while"};
59   for (unsigned int i = 0; i < CStmt->size() - 1; i++) {
60     const Stmt *CurrentStmt = CStmt->body_begin()[i];
61     const Stmt *Inner = nullptr;
62     int StmtKind = 0;
63 
64     if (const auto *CurrentIf = dyn_cast<IfStmt>(CurrentStmt)) {
65       StmtKind = 0;
66       Inner =
67           CurrentIf->getElse() ? CurrentIf->getElse() : CurrentIf->getThen();
68     } else if (const auto *CurrentFor = dyn_cast<ForStmt>(CurrentStmt)) {
69       StmtKind = 1;
70       Inner = CurrentFor->getBody();
71     } else if (const auto *CurrentWhile = dyn_cast<WhileStmt>(CurrentStmt)) {
72       StmtKind = 2;
73       Inner = CurrentWhile->getBody();
74     } else {
75       continue;
76     }
77 
78     if (isa<CompoundStmt>(Inner))
79       continue;
80 
81     SourceLocation InnerLoc = Inner->getBeginLoc();
82     SourceLocation OuterLoc = CurrentStmt->getBeginLoc();
83 
84     if (InnerLoc.isInvalid() || InnerLoc.isMacroID() || OuterLoc.isInvalid() ||
85         OuterLoc.isMacroID())
86       continue;
87 
88     if (SM.getExpansionLineNumber(InnerLoc) ==
89         SM.getExpansionLineNumber(OuterLoc))
90       continue;
91 
92     const Stmt *NextStmt = CStmt->body_begin()[i + 1];
93     SourceLocation NextLoc = NextStmt->getBeginLoc();
94 
95     if (NextLoc.isInvalid() || NextLoc.isMacroID())
96       continue;
97 
98     if (SM.getExpansionColumnNumber(InnerLoc) ==
99         SM.getExpansionColumnNumber(NextLoc)) {
100       diag(NextLoc, "misleading indentation: statement is indented too deeply");
101       diag(OuterLoc, "did you mean this line to be inside this '%0'",
102            DiagnosticIDs::Note)
103           << StmtNames[StmtKind];
104     }
105   }
106 }
107 
registerMatchers(MatchFinder * Finder)108 void MisleadingIndentationCheck::registerMatchers(MatchFinder *Finder) {
109   Finder->addMatcher(
110       ifStmt(allOf(hasElse(stmt()),
111                    unless(allOf(isConstexpr(), isInTemplateInstantiation()))))
112           .bind("if"),
113       this);
114   Finder->addMatcher(
115       compoundStmt(has(stmt(anyOf(ifStmt(), forStmt(), whileStmt()))))
116           .bind("compound"),
117       this);
118 }
119 
check(const MatchFinder::MatchResult & Result)120 void MisleadingIndentationCheck::check(const MatchFinder::MatchResult &Result) {
121   if (const auto *If = Result.Nodes.getNodeAs<IfStmt>("if"))
122     danglingElseCheck(*Result.SourceManager, Result.Context, If);
123 
124   if (const auto *CStmt = Result.Nodes.getNodeAs<CompoundStmt>("compound"))
125     missingBracesCheck(*Result.SourceManager, CStmt);
126 }
127 
128 } // namespace readability
129 } // namespace tidy
130 } // namespace clang
131