//===--- MisleadingIndentationCheck.cpp - clang-tidy-----------------------===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// #include "MisleadingIndentationCheck.h" #include "clang/AST/ASTContext.h" #include "clang/ASTMatchers/ASTMatchFinder.h" using namespace clang::ast_matchers; namespace clang { namespace tidy { namespace readability { static const IfStmt *getPrecedingIf(const SourceManager &SM, ASTContext *Context, const IfStmt *If) { auto parents = Context->getParents(*If); if (parents.size() != 1) return nullptr; if (const auto *PrecedingIf = parents[0].get()) { SourceLocation PreviousElseLoc = PrecedingIf->getElseLoc(); if (SM.getExpansionLineNumber(PreviousElseLoc) == SM.getExpansionLineNumber(If->getIfLoc())) return PrecedingIf; } return nullptr; } void MisleadingIndentationCheck::danglingElseCheck(const SourceManager &SM, ASTContext *Context, const IfStmt *If) { SourceLocation IfLoc = If->getIfLoc(); SourceLocation ElseLoc = If->getElseLoc(); if (IfLoc.isMacroID() || ElseLoc.isMacroID()) return; if (SM.getExpansionLineNumber(If->getThen()->getEndLoc()) == SM.getExpansionLineNumber(ElseLoc)) return; // Find location of first 'if' in a 'if else if' chain. for (auto PrecedingIf = getPrecedingIf(SM, Context, If); PrecedingIf; PrecedingIf = getPrecedingIf(SM, Context, PrecedingIf)) IfLoc = PrecedingIf->getIfLoc(); if (SM.getExpansionColumnNumber(IfLoc) != SM.getExpansionColumnNumber(ElseLoc)) diag(ElseLoc, "different indentation for 'if' and corresponding 'else'"); } void MisleadingIndentationCheck::missingBracesCheck(const SourceManager &SM, const CompoundStmt *CStmt) { const static StringRef StmtNames[] = {"if", "for", "while"}; for (unsigned int i = 0; i < CStmt->size() - 1; i++) { const Stmt *CurrentStmt = CStmt->body_begin()[i]; const Stmt *Inner = nullptr; int StmtKind = 0; if (const auto *CurrentIf = dyn_cast(CurrentStmt)) { StmtKind = 0; Inner = CurrentIf->getElse() ? CurrentIf->getElse() : CurrentIf->getThen(); } else if (const auto *CurrentFor = dyn_cast(CurrentStmt)) { StmtKind = 1; Inner = CurrentFor->getBody(); } else if (const auto *CurrentWhile = dyn_cast(CurrentStmt)) { StmtKind = 2; Inner = CurrentWhile->getBody(); } else { continue; } if (isa(Inner)) continue; SourceLocation InnerLoc = Inner->getBeginLoc(); SourceLocation OuterLoc = CurrentStmt->getBeginLoc(); if (SM.getExpansionLineNumber(InnerLoc) == SM.getExpansionLineNumber(OuterLoc)) continue; const Stmt *NextStmt = CStmt->body_begin()[i + 1]; SourceLocation NextLoc = NextStmt->getBeginLoc(); if (InnerLoc.isMacroID() || OuterLoc.isMacroID() || NextLoc.isMacroID()) continue; if (SM.getExpansionColumnNumber(InnerLoc) == SM.getExpansionColumnNumber(NextLoc)) { diag(NextLoc, "misleading indentation: statement is indented too deeply"); diag(OuterLoc, "did you mean this line to be inside this '%0'", DiagnosticIDs::Note) << StmtNames[StmtKind]; } } } void MisleadingIndentationCheck::registerMatchers(MatchFinder *Finder) { Finder->addMatcher(ifStmt(hasElse(stmt())).bind("if"), this); Finder->addMatcher( compoundStmt(has(stmt(anyOf(ifStmt(), forStmt(), whileStmt())))) .bind("compound"), this); } void MisleadingIndentationCheck::check(const MatchFinder::MatchResult &Result) { if (const auto *If = Result.Nodes.getNodeAs("if")) danglingElseCheck(*Result.SourceManager, Result.Context, If); if (const auto *CStmt = Result.Nodes.getNodeAs("compound")) missingBracesCheck(*Result.SourceManager, CStmt); } } // namespace readability } // namespace tidy } // namespace clang