Use upstreamed astmatcher.
emit messages as notes.
moar tests.
minor fixes.

http://reviews.llvm.org/D4986

Files:
  clang-tidy/misc/CMakeLists.txt
  clang-tidy/misc/FunctionLength.cpp
  clang-tidy/misc/FunctionLength.h
  clang-tidy/misc/MiscTidyModule.cpp
  unittests/clang-tidy/CMakeLists.txt
  unittests/clang-tidy/FunctionLengthCheckTest.cpp
Index: clang-tidy/misc/CMakeLists.txt
===================================================================
--- clang-tidy/misc/CMakeLists.txt
+++ clang-tidy/misc/CMakeLists.txt
@@ -3,6 +3,7 @@
 add_clang_library(clangTidyMiscModule
   ArgumentCommentCheck.cpp
   BoolPointerImplicitConversion.cpp
+  FunctionLength.cpp
   MiscTidyModule.cpp
   RedundantSmartptrGet.cpp
   SwappedArgumentsCheck.cpp
Index: clang-tidy/misc/FunctionLength.cpp
===================================================================
--- /dev/null
+++ clang-tidy/misc/FunctionLength.cpp
@@ -0,0 +1,92 @@
+//===--- FunctionLength.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 "FunctionLength.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang {
+namespace tidy {
+
+void FunctionLengthCheck::registerMatchers(MatchFinder *Finder) {
+  Finder->addMatcher(
+      functionDecl(
+          unless(isInstantiated()),
+          forEachDescendant(
+              stmt(unless(compoundStmt()),
+                   hasParent(stmt(anyOf(compoundStmt(), ifStmt(),
+                                        anyOf(whileStmt(), doStmt(),
+                                              forRangeStmt(), forStmt())))))
+                  .bind("stmt"))).bind("func"),
+      this);
+}
+
+void FunctionLengthCheck::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 FunctionLengthCheck::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 tidy
+} // namespace clang
Index: clang-tidy/misc/FunctionLength.h
===================================================================
--- /dev/null
+++ clang-tidy/misc/FunctionLength.h
@@ -0,0 +1,57 @@
+//===--- FunctionLength.h - clang-tidy --------------------------*- C++ -*-===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_FUNCTIONLENGTH_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_FUNCTIONLENGTH_H
+
+#include "../ClangTidy.h"
+
+namespace clang {
+namespace tidy {
+
+/// \brief Checks for large functions based on various metrics.
+class FunctionLengthCheck : public ClangTidyCheck {
+  struct FunctionInfo {
+    FunctionInfo() : Lines(0), Statements(0), Branches(0) {}
+    unsigned Lines;
+    unsigned Statements;
+    unsigned Branches;
+  };
+
+  const unsigned LineThreshold;
+  const unsigned StatementThreshold;
+  const unsigned BranchThreshold;
+
+  llvm::DenseMap<const FunctionDecl *, FunctionInfo> FunctionInfos;
+
+public:
+  /// \brief Creates a new function length check.
+  ///
+  /// \param LineThreshold      The maxmimum number of lines a function can
+  ///                           contain before a warning is emitted.
+  /// \param StatementThreshold The maximum number of statements a function can
+  ///                           contain before a warning is emitted.
+  /// \param BranchThreshold    The maximum number of branches a function can
+  ///                           contain before a warning is emitted.
+  FunctionLengthCheck(unsigned LineThreshold = -1U,
+                      unsigned StatementThreshold = 800,
+                      unsigned BranchThreshold = -1U)
+      : LineThreshold(LineThreshold), StatementThreshold(StatementThreshold),
+        BranchThreshold(BranchThreshold) {}
+
+  void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+  void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+  void onEndOfTranslationUnit() override;
+};
+
+} // namespace tidy
+} // namespace clang
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_FUNCTIONLENGTH_H
+
Index: clang-tidy/misc/MiscTidyModule.cpp
===================================================================
--- clang-tidy/misc/MiscTidyModule.cpp
+++ clang-tidy/misc/MiscTidyModule.cpp
@@ -12,6 +12,7 @@
 #include "../ClangTidyModuleRegistry.h"
 #include "ArgumentCommentCheck.h"
 #include "BoolPointerImplicitConversion.h"
+#include "FunctionLength.h"
 #include "RedundantSmartptrGet.h"
 #include "SwappedArgumentsCheck.h"
 #include "UndelegatedConstructor.h"
@@ -31,6 +32,9 @@
         "misc-bool-pointer-implicit-conversion",
         new ClangTidyCheckFactory<BoolPointerImplicitConversion>());
     CheckFactories.addCheckFactory(
+        "misc-function-length",
+        new ClangTidyCheckFactory<FunctionLengthCheck>());
+    CheckFactories.addCheckFactory(
         "misc-redundant-smartptr-get",
         new ClangTidyCheckFactory<RedundantSmartptrGet>());
     CheckFactories.addCheckFactory(
Index: unittests/clang-tidy/CMakeLists.txt
===================================================================
--- unittests/clang-tidy/CMakeLists.txt
+++ unittests/clang-tidy/CMakeLists.txt
@@ -9,8 +9,9 @@
 add_extra_unittest(ClangTidyTests
   ClangTidyDiagnosticConsumerTest.cpp
   ClangTidyOptionsTest.cpp
-  LLVMModuleTest.cpp
+  FunctionLengthCheckTest.cpp
   GoogleModuleTest.cpp
+  LLVMModuleTest.cpp
   MiscModuleTest.cpp)
 
 target_link_libraries(ClangTidyTests
Index: unittests/clang-tidy/FunctionLengthCheckTest.cpp
===================================================================
--- /dev/null
+++ unittests/clang-tidy/FunctionLengthCheckTest.cpp
@@ -0,0 +1,94 @@
+#include "ClangTidyTest.h"
+#include "misc/FunctionLength.h"
+#include "gtest/gtest.h"
+
+namespace clang {
+namespace tidy {
+namespace test {
+
+namespace {
+std::string getWarning(ClangTidyCheck &Check, StringRef Code) {
+  ClangTidyContext Context(
+      new DefaultOptionsProvider(ClangTidyGlobalOptions(), ClangTidyOptions()));
+  ClangTidyDiagnosticConsumer DiagConsumer(Context);
+  Check.setContext(&Context);
+  std::vector<std::string> Args(1, "-std=c++11");
+
+  ast_matchers::MatchFinder Finder;
+  Check.registerMatchers(&Finder);
+  std::unique_ptr<tooling::FrontendActionFactory> Factory(
+      tooling::newFrontendActionFactory(&Finder));
+  if (!tooling::runToolOnCodeWithArgs(Factory->create(), Code, Args))
+    return "tooling error";
+  DiagConsumer.finish();
+
+  if (Context.getErrors().size() == 0)
+    return "no errors";
+
+  if (Context.getErrors().size() != 1)
+    return "wrong number of errors";
+
+  // Munge error and notes into one long string.
+  std::string Out =
+      StringRef(Context.getErrors().back().Message.Message).rtrim(" []");
+  for (const auto &Note : Context.getErrors().back().Notes)
+    Out += "\n" + StringRef(Note.Message).rtrim(" []").str();
+
+  return Out;
+}
+} // namespace
+
+TEST(FunctionLengthCheckTest, BasicTest) {
+  FunctionLengthCheck C(0U, 0U, 0U);
+
+  const char *Code1 = "void foo() {\n}";
+  EXPECT_EQ("no errors", getWarning(C, Code1));
+
+  const char *Code2 = "void foo() {;}";
+  EXPECT_EQ("function 'foo' exceeds recommended size/complexity thresholds\n"
+            "1 statements (threshold 0)",
+            getWarning(C, Code2));
+
+  const char *Code3 = "void foo() {\n;\n\n}";
+  EXPECT_EQ("function 'foo' exceeds recommended size/complexity thresholds\n"
+            "3 lines including whitespace and comments (threshold 0)\n"
+            "1 statements (threshold 0)",
+            getWarning(C, Code3));
+
+  const char *Code4 = "void foo(int i) { if (i) {} else; {}\n}";
+  EXPECT_EQ("function 'foo' exceeds recommended size/complexity thresholds\n"
+            "1 lines including whitespace and comments (threshold 0)\n"
+            "3 statements (threshold 0)\n"
+            "1 branches (threshold 0)",
+            getWarning(C, Code4));
+
+  const char *Code5 = "void foo(int i) {for(;i;)while(i)\ndo;while(i);\n}";
+  EXPECT_EQ("function 'foo' exceeds recommended size/complexity thresholds\n"
+            "2 lines including whitespace and comments (threshold 0)\n"
+            "7 statements (threshold 0)\n"
+            "3 branches (threshold 0)",
+            getWarning(C, Code5));
+
+  const char *Code6 = "template <typename T> T foo(T i) {return i;\n}\n"
+                      "int x = foo(0);";
+  EXPECT_EQ("function 'foo' exceeds recommended size/complexity thresholds\n"
+            "1 lines including whitespace and comments (threshold 0)\n"
+            "1 statements (threshold 0)",
+            getWarning(C, Code6));
+
+  const char *Code7 = "void bar() { [](){;;;;;;;;;;;if(1){}}(); \n\n\n}";
+  EXPECT_EQ("function 'bar' exceeds recommended size/complexity thresholds\n"
+            "3 lines including whitespace and comments (threshold 0)\n"
+            "14 statements (threshold 0)\n"
+            "1 branches (threshold 0)",
+            getWarning(C, Code7));
+
+  const char *Code8 = "void bar() { class A { void x() {} }; }";
+  EXPECT_EQ("function 'bar' exceeds recommended size/complexity thresholds\n"
+            "1 statements (threshold 0)",
+            getWarning(C, Code8));
+}
+
+} // namespace test
+} // namespace tidy
+} // namespace clang
_______________________________________________
cfe-commits mailing list
[email protected]
http://lists.cs.uiuc.edu/mailman/listinfo/cfe-commits

Reply via email to