xref: /llvm-project/clang-tools-extra/clang-tidy/readability/MisleadingIndentationCheck.cpp (revision 72390c5c56c5f6a2c2d55231d96f03471eee8cb4)
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 "../utils/LexerUtils.h"
11 #include "clang/AST/ASTContext.h"
12 #include "clang/ASTMatchers/ASTMatchFinder.h"
13 
14 using namespace clang::ast_matchers;
15 
16 namespace clang::tidy::readability {
17 
getPrecedingIf(const SourceManager & SM,ASTContext * Context,const IfStmt * If)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)
22     return nullptr;
23   if (const auto *PrecedingIf = Parents[0].get<IfStmt>()) {
24     SourceLocation PreviousElseLoc = PrecedingIf->getElseLoc();
25     if (SM.getExpansionLineNumber(PreviousElseLoc) ==
26         SM.getExpansionLineNumber(If->getIfLoc()))
27       return PrecedingIf;
28   }
29   return nullptr;
30 }
31 
danglingElseCheck(const SourceManager & SM,ASTContext * Context,const IfStmt * If)32 void MisleadingIndentationCheck::danglingElseCheck(const SourceManager &SM,
33                                                    ASTContext *Context,
34                                                    const IfStmt *If) {
35   SourceLocation IfLoc = If->getIfLoc();
36   SourceLocation ElseLoc = If->getElseLoc();
37 
38   if (IfLoc.isMacroID() || ElseLoc.isMacroID())
39     return;
40 
41   if (SM.getExpansionLineNumber(If->getThen()->getEndLoc()) ==
42       SM.getExpansionLineNumber(ElseLoc))
43     return;
44 
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();
49 
50   if (SM.getExpansionColumnNumber(IfLoc) !=
51       SM.getExpansionColumnNumber(ElseLoc))
52     diag(ElseLoc, "different indentation for 'if' and corresponding 'else'");
53 }
54 
isAtStartOfLineIncludingEmptyMacro(SourceLocation NextLoc,const SourceManager & SM,const LangOptions & LangOpts)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())
61     return false;
62   return SM.getExpansionLineNumber(BeforeLoc) !=
63          SM.getExpansionLineNumber(NextLoc);
64 }
65 
missingBracesCheck(const SourceManager & SM,const CompoundStmt * CStmt,const LangOptions & LangOpts)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;
73     int StmtKind = 0;
74 
75     if (const auto *CurrentIf = dyn_cast<IfStmt>(CurrentStmt)) {
76       StmtKind = 0;
77       Inner =
78           CurrentIf->getElse() ? CurrentIf->getElse() : CurrentIf->getThen();
79     } else if (const auto *CurrentFor = dyn_cast<ForStmt>(CurrentStmt)) {
80       StmtKind = 1;
81       Inner = CurrentFor->getBody();
82     } else if (const auto *CurrentWhile = dyn_cast<WhileStmt>(CurrentStmt)) {
83       StmtKind = 2;
84       Inner = CurrentWhile->getBody();
85     } else {
86       continue;
87     }
88 
89     if (isa<CompoundStmt>(Inner))
90       continue;
91 
92     SourceLocation InnerLoc = Inner->getBeginLoc();
93     SourceLocation OuterLoc = CurrentStmt->getBeginLoc();
94 
95     if (InnerLoc.isInvalid() || InnerLoc.isMacroID() || OuterLoc.isInvalid() ||
96         OuterLoc.isMacroID())
97       continue;
98 
99     if (SM.getExpansionLineNumber(InnerLoc) ==
100         SM.getExpansionLineNumber(OuterLoc))
101       continue;
102 
103     const Stmt *NextStmt = CStmt->body_begin()[I + 1];
104     SourceLocation NextLoc = NextStmt->getBeginLoc();
105 
106     if (NextLoc.isInvalid() || NextLoc.isMacroID())
107       continue;
108     if (!isAtStartOfLineIncludingEmptyMacro(NextLoc, SM, LangOpts))
109       continue;
110 
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'",
115            DiagnosticIDs::Note)
116           << StmtNames[StmtKind];
117     }
118   }
119 }
120 
registerMatchers(MatchFinder * Finder)121 void MisleadingIndentationCheck::registerMatchers(MatchFinder *Finder) {
122   Finder->addMatcher(
123       ifStmt(unless(hasThen(nullStmt())), hasElse(stmt())).bind("if"), this);
124   Finder->addMatcher(
125       compoundStmt(has(stmt(anyOf(ifStmt(), forStmt(), whileStmt()))))
126           .bind("compound"),
127       this);
128 }
129 
check(const MatchFinder::MatchResult & Result)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);
133 
134   if (const auto *CStmt = Result.Nodes.getNodeAs<CompoundStmt>("compound"))
135     missingBracesCheck(*Result.SourceManager, CStmt,
136                        Result.Context->getLangOpts());
137 }
138 
139 } // namespace clang::tidy::readability
140