aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Jasper <djasper@google.com>2014-12-09 10:02:51 +0000
committerDaniel Jasper <djasper@google.com>2014-12-09 10:02:51 +0000
commit8bd51ccea4c59d998691a71a14e868f622faeb7d (patch)
tree767103a50319115dd3f056a29750b381761cf223
parent4f73cc5a9f2f081371f380dea3d1562db91de1c0 (diff)
clang-tidy: Add a basic python script to generate checks.
There are still a vast range of improvements that can be done to this, but it seems like an ok initial version. Suggestions or patches are highly welcome. git-svn-id: https://llvm.org/svn/llvm-project/clang-tools-extra/trunk@223766 91177308-0d34-0410-b5e6-96231b3b80d8
-rwxr-xr-xclang-tidy/add_new_check.py214
1 files changed, 214 insertions, 0 deletions
diff --git a/clang-tidy/add_new_check.py b/clang-tidy/add_new_check.py
new file mode 100755
index 00000000..873a2bde
--- /dev/null
+++ b/clang-tidy/add_new_check.py
@@ -0,0 +1,214 @@
+#!/usr/bin/env python
+#
+#===- add_new_check.py - clang-tidy check generator ----------*- python -*--===#
+#
+# The LLVM Compiler Infrastructure
+#
+# This file is distributed under the University of Illinois Open Source
+# License. See LICENSE.TXT for details.
+#
+#===------------------------------------------------------------------------===#
+
+import os
+import re
+import sys
+
+
+# Adapts the module's CMakelist file. Returns 'True' if it could add a new entry
+# and 'False' if the entry already existed.
+def adapt_cmake(module_path, check_name_camel):
+ filename = os.path.join(module_path, 'CMakeLists.txt')
+ with open(filename, 'r') as f:
+ lines = f.read().split('\n')
+ # .split with separator returns one more element. Ignore it.
+ lines = lines[:-1]
+
+ cpp_file = check_name_camel + '.cpp'
+
+ # Figure out whether this check already exists.
+ for line in lines:
+ if line.strip() == cpp_file:
+ return False
+
+ with open(filename, 'w') as f:
+ cpp_found = False
+ file_added = False
+ for line in lines:
+ if not file_added and (line.endswith('.cpp') or cpp_found):
+ cpp_found = True
+ if line.strip() > cpp_file:
+ f.write(' ' + cpp_file + '\n')
+ file_added = True
+ f.write(line + '\n')
+
+ return True
+
+
+# Adds a header for the new check.
+def write_header(module_path, module, check_name, check_name_camel):
+ filename = os.path.join(module_path, check_name_camel) + '.h'
+ with open(filename, 'w') as f:
+ header_guard = ('LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_' + module.upper() +
+ '_' + check_name.upper().replace('-', '_') + '_H')
+ f.write('//===--- ')
+ f.write(os.path.basename(filename))
+ f.write(' - clang-tidy')
+ f.write('-' * max(0, 43 - len(os.path.basename(filename))))
+ f.write('*- C++ -*-===//')
+ f.write("""
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef %(header_guard)s
+#define %(header_guard)s
+
+#include "../ClangTidy.h"
+
+namespace clang {
+namespace tidy {
+
+class %(check_name)s : public ClangTidyCheck {
+public:
+ %(check_name)s(StringRef Name, ClangTidyContext *Context)
+ : ClangTidyCheck(Name, Context) {}
+ void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+ void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+};
+
+} // namespace tidy
+} // namespace clang
+
+#endif // %(header_guard)s
+
+""" % {'header_guard': header_guard,
+ 'check_name': check_name_camel})
+
+
+# Adds the implementation of the new check.
+def write_implementation(module_path, check_name_camel):
+ filename = os.path.join(module_path, check_name_camel) + '.cpp'
+ with open(filename, 'w') as f:
+ f.write('//===--- ')
+ f.write(os.path.basename(filename))
+ f.write(' - clang-tidy')
+ f.write('-' * max(0, 52 - len(os.path.basename(filename))))
+ f.write('-===//')
+ f.write("""
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "%(check_name)s.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang {
+namespace tidy {
+
+void %(check_name)s::registerMatchers(MatchFinder *Finder) {
+ // FIXME: Add matchers.
+ Finder->addMatcher(functionDecl().bind("x"), this);
+}
+
+void %(check_name)s::check(const MatchFinder::MatchResult &Result) {
+ // FIXME: Add callback implementation.
+ const FunctionDecl *MatchedDecl = Result.Nodes.getNodeAs<FunctionDecl>("x");
+ diag(MatchedDecl->getLocation(), "Dummy warning");
+}
+
+} // namespace tidy
+} // namespace clang
+
+""" % {'check_name': check_name_camel})
+
+
+# Modifies the module to include the new check.
+def adapt_module(module_path, module, check_name, check_name_camel):
+ filename = os.path.join(module_path, module.capitalize() + 'TidyModule.cpp')
+ with open(filename, 'r') as f:
+ lines = f.read().split('\n')
+ # .split with separator returns one more element. Ignore it.
+ lines = lines[:-1]
+
+ with open(filename, 'w') as f:
+ header_added = False
+ header_found = False
+ check_added = False
+ check_decl = (' CheckFactories.registerCheck<' + check_name_camel +
+ '>(\n "' + module + '-' + check_name + '");\n')
+
+ for line in lines:
+ if not header_added:
+ match = re.search('#include "(.*)"', line)
+ if match:
+ header_found = True
+ if match.group(1) > check_name_camel:
+ header_added = True
+ f.write('#include "' + check_name_camel + '.h"\n')
+ elif header_found:
+ header_added = True
+ f.write('#include "' + check_name_camel + '.h"\n')
+
+ if not check_added:
+ if line.strip() == '}':
+ check_added = True
+ f.write(check_decl)
+ else:
+ match = re.search('registerCheck<(.*)>', line)
+ if match and match.group(1) > check_name_camel:
+ check_added = True
+ f.write(check_decl)
+ f.write(line + '\n')
+
+
+# Adds a test for the check.
+def write_test(module_path, module, check_name):
+ filename = os.path.join(module_path, '../../test/clang-tidy',
+ module + '-' + check_name + '.cpp')
+ with open(filename, 'w') as f:
+ f.write('// RUN: $(dirname %s)/check_clang_tidy.sh %s ' + module + '-' +
+ check_name + ' %t\n')
+ f.write("""// REQUIRES: shell
+
+// FIXME: Add something that trigger the check here
+void f();
+// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: Dummy warning
+
+// FIXME: Add something that doesn't trigger the check here
+int i;
+// CHECK-MESSAGES-NOT: warning:""")
+
+def main():
+ if len(sys.argv) != 3:
+ print 'Usage: add_new_check.py <module> <check>, e.g.\n'
+ print 'add_new_check.py misc else-after-return\n'
+ return
+
+ module = sys.argv[1]
+ check_name = sys.argv[2]
+ check_name_camel = ''.join(map(lambda elem: elem.capitalize(),
+ check_name.split('-'))) + 'Check'
+ clang_tidy_path = os.path.dirname(sys.argv[0])
+ module_path = os.path.join(clang_tidy_path, module)
+
+ if not adapt_cmake(module_path, check_name_camel):
+ return
+ write_header(module_path, module, check_name, check_name_camel)
+ write_implementation(module_path, check_name_camel)
+ adapt_module(module_path, module, check_name, check_name_camel)
+ write_test(module_path, module, check_name)
+
+
+if __name__ == '__main__':
+ main()