//===--- FunctionSize.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 "FunctionSizeCheck.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"

using namespace clang::ast_matchers;

namespace clang {
namespace tidy {
namespace readability {

FunctionSizeCheck::FunctionSizeCheck(StringRef Name, ClangTidyContext *Context)
    : ClangTidyCheck(Name, Context),
      LineThreshold(Options.get("LineThreshold", -1U)),
      StatementThreshold(Options.get("StatementThreshold", 800U)),
      BranchThreshold(Options.get("BranchThreshold", -1U)) {}

void FunctionSizeCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
  Options.store(Opts, "LineThreshold", LineThreshold);
  Options.store(Opts, "StatementThreshold", StatementThreshold);
  Options.store(Opts, "BranchThreshold", BranchThreshold);
}

void FunctionSizeCheck::registerMatchers(MatchFinder *Finder) {
  Finder->addMatcher(
      functionDecl(
          unless(isInstantiated()),
          forEachDescendant(
              stmt(unless(compoundStmt()),
                   hasParent(stmt(anyOf(compoundStmt(), ifStmt(),
                                        anyOf(whileStmt(), doStmt(),
                                              cxxForRangeStmt(), forStmt())))))
                  .bind("stmt"))).bind("func"),
      this);
}

void FunctionSizeCheck::check(const MatchFinder::MatchResult &Result) {
  const auto *Func = Result.Nodes.getNodeAs<FunctionDecl>("func");

  FunctionInfo &FI = FunctionInfos[Func];

  // Count the lines including whitespace and comments. Really simple.
  if (!FI.Lines) {
    if (const Stmt *Body = Func->getBody()) {
      SourceManager *SM = Result.SourceManager;
      if (SM->isWrittenInSameFile(Body->getLocStart(), Body->getLocEnd())) {
        FI.Lines = SM->getSpellingLineNumber(Body->getLocEnd()) -
                   SM->getSpellingLineNumber(Body->getLocStart());
      }
    }
  }

  const auto *Statement = Result.Nodes.getNodeAs<Stmt>("stmt");
  ++FI.Statements;

  // TODO: switch cases, gotos
  if (isa<IfStmt>(Statement) || isa<WhileStmt>(Statement) ||
      isa<ForStmt>(Statement) || isa<SwitchStmt>(Statement) ||
      isa<DoStmt>(Statement) || isa<CXXForRangeStmt>(Statement))
    ++FI.Branches;
}

void FunctionSizeCheck::onEndOfTranslationUnit() {
  // If we're above the limit emit a warning.
  for (const auto &P : FunctionInfos) {
    const FunctionInfo &FI = P.second;
    if (FI.Lines > LineThreshold || FI.Statements > StatementThreshold ||
        FI.Branches > BranchThreshold) {
      diag(P.first->getLocation(),
           "function '%0' exceeds recommended size/complexity thresholds")
          << P.first->getNameAsString();
    }

    if (FI.Lines > LineThreshold) {
      diag(P.first->getLocation(),
           "%0 lines including whitespace and comments (threshold %1)",
           DiagnosticIDs::Note)
          << FI.Lines << LineThreshold;
    }

    if (FI.Statements > StatementThreshold) {
      diag(P.first->getLocation(), "%0 statements (threshold %1)",
           DiagnosticIDs::Note)
          << FI.Statements << StatementThreshold;
    }

    if (FI.Branches > BranchThreshold) {
      diag(P.first->getLocation(), "%0 branches (threshold %1)",
           DiagnosticIDs::Note)
          << FI.Branches << BranchThreshold;
    }
  }

  FunctionInfos.clear();
}

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