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
14using namespace clang::ast_matchers;
15
16namespace clang::tidy::readability {
17
18static const IfStmt *getPrecedingIf(const SourceManager &SM,
19 ASTContext *Context, const IfStmt *If) {
20 auto Parents = Context->getParents(Node: *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(Loc: PreviousElseLoc) ==
26 SM.getExpansionLineNumber(Loc: If->getIfLoc()))
27 return PrecedingIf;
28 }
29 return nullptr;
30}
31
32void 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(Loc: If->getThen()->getEndLoc()) ==
42 SM.getExpansionLineNumber(Loc: 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, If: PrecedingIf))
48 IfLoc = PrecedingIf->getIfLoc();
49
50 if (SM.getExpansionColumnNumber(Loc: IfLoc) !=
51 SM.getExpansionColumnNumber(Loc: ElseLoc))
52 diag(Loc: ElseLoc, Description: "different indentation for 'if' and corresponding 'else'");
53}
54
55static bool isAtStartOfLineIncludingEmptyMacro(SourceLocation NextLoc,
56 const SourceManager &SM,
57 const LangOptions &LangOpts) {
58 const SourceLocation BeforeLoc =
59 utils::lexer::getPreviousTokenAndStart(Location: NextLoc, SM, LangOpts).second;
60 if (BeforeLoc.isInvalid())
61 return false;
62 return SM.getExpansionLineNumber(Loc: BeforeLoc) !=
63 SM.getExpansionLineNumber(Loc: NextLoc);
64}
65
66void 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>(Val: CurrentStmt)) {
76 StmtKind = 0;
77 Inner =
78 CurrentIf->getElse() ? CurrentIf->getElse() : CurrentIf->getThen();
79 } else if (const auto *CurrentFor = dyn_cast<ForStmt>(Val: CurrentStmt)) {
80 StmtKind = 1;
81 Inner = CurrentFor->getBody();
82 } else if (const auto *CurrentWhile = dyn_cast<WhileStmt>(Val: CurrentStmt)) {
83 StmtKind = 2;
84 Inner = CurrentWhile->getBody();
85 } else {
86 continue;
87 }
88
89 if (isa<CompoundStmt>(Val: 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(Loc: InnerLoc) ==
100 SM.getExpansionLineNumber(Loc: 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(Loc: InnerLoc) ==
112 SM.getExpansionColumnNumber(Loc: NextLoc)) {
113 diag(Loc: NextLoc, Description: "misleading indentation: statement is indented too deeply");
114 diag(Loc: OuterLoc, Description: "did you mean this line to be inside this '%0'",
115 Level: DiagnosticIDs::Note)
116 << StmtNames[StmtKind];
117 }
118 }
119}
120
121void MisleadingIndentationCheck::registerMatchers(MatchFinder *Finder) {
122 Finder->addMatcher(
123 NodeMatch: ifStmt(unless(hasThen(InnerMatcher: nullStmt())), hasElse(InnerMatcher: stmt())).bind(ID: "if"), Action: this);
124 Finder->addMatcher(
125 NodeMatch: compoundStmt(has(stmt(anyOf(ifStmt(), forStmt(), whileStmt()))))
126 .bind(ID: "compound"),
127 Action: this);
128}
129
130void MisleadingIndentationCheck::check(const MatchFinder::MatchResult &Result) {
131 if (const auto *If = Result.Nodes.getNodeAs<IfStmt>(ID: "if"))
132 danglingElseCheck(SM: *Result.SourceManager, Context: Result.Context, If);
133
134 if (const auto *CStmt = Result.Nodes.getNodeAs<CompoundStmt>(ID: "compound"))
135 missingBracesCheck(SM: *Result.SourceManager, CStmt,
136 LangOpts: Result.Context->getLangOpts());
137}
138
139} // namespace clang::tidy::readability
140

source code of clang-tools-extra/clang-tidy/readability/MisleadingIndentationCheck.cpp