reference, declarationdefinition
definition → references, declarations, derived classes, virtual overrides
reference to multiple definitions → definitions
unreferenced
    1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
//===--- MisleadingIndentationCheck.cpp - clang-tidy-----------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#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<IfStmt>()) {
    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<IfStmt>(CurrentStmt)) {
      StmtKind = 0;
      Inner =
          CurrentIf->getElse() ? CurrentIf->getElse() : CurrentIf->getThen();
    } else if (const auto *CurrentFor = dyn_cast<ForStmt>(CurrentStmt)) {
      StmtKind = 1;
      Inner = CurrentFor->getBody();
    } else if (const auto *CurrentWhile = dyn_cast<WhileStmt>(CurrentStmt)) {
      StmtKind = 2;
      Inner = CurrentWhile->getBody();
    } else {
      continue;
    }

    if (isa<CompoundStmt>(Inner))
      continue;

    SourceLocation InnerLoc = Inner->getBeginLoc();
    SourceLocation OuterLoc = CurrentStmt->getBeginLoc();

    if (InnerLoc.isInvalid() || InnerLoc.isMacroID() || OuterLoc.isInvalid() ||
        OuterLoc.isMacroID())
      continue;

    if (SM.getExpansionLineNumber(InnerLoc) ==
        SM.getExpansionLineNumber(OuterLoc))
      continue;

    const Stmt *NextStmt = CStmt->body_begin()[i + 1];
    SourceLocation NextLoc = NextStmt->getBeginLoc();

    if (NextLoc.isInvalid() || 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<IfStmt>("if"))
    danglingElseCheck(*Result.SourceManager, Result.Context, If);

  if (const auto *CStmt = Result.Nodes.getNodeAs<CompoundStmt>("compound"))
    missingBracesCheck(*Result.SourceManager, CStmt);
}

} // namespace readability
} // namespace tidy
} // namespace clang