Index: clang-tidy/validate_check.py =================================================================== --- /dev/null +++ clang-tidy/validate_check.py @@ -0,0 +1,192 @@ +#!/usr/bin/env python +# +#===- validate_check.py - validate clang-tidy files ----------*- python -*--===# +# +# The LLVM Compiler Infrastructure +# +# This file is distributed under the University of Illinois Open Source +# License. See LICENSE.TXT for details. +# +#===------------------------------------------------------------------------===# + +from __future__ import print_function +import argparse +import os +import re +import sys + +# Adds a release notes entry. +def validate_release_notes(clang_tidy_path): + filename = os.path.normpath(os.path.join(clang_tidy_path, + '../docs/ReleaseNotes.rst')) + validate_rst(filename) + +def validate_docs(module_path, module, check_name): + check_name_dashes = module + '-' + check_name + filename = os.path.normpath(os.path.join( + module_path, '../../docs/clang-tidy/checks/', check_name_dashes + '.rst')) + validate_rst(filename) + +def validate_rst(filename): + with open(filename, 'r') as f: + lines = f.readlines() + + print('Checking %s...' % filename) + + line_num = 0 + ignore_last_line = False + in_code_block = False + skip_blank_line = False + previous_line_blank = False + last_non_empty_line = 0 + last_line = "" + for line in lines: + line_num = line_num + 1 + current_line_length = len(line.rstrip('\r\n')) + + # look for adjacent unnecessary blank lines + if ((previous_line_blank == True) and (current_line_length == 0)): + print('warning: line %d multiple blank lines' % (line_num)) + previous_line_blank = False + + if (current_line_length == 0): + previous_line_blank = True + else: + previous_line_blank = False + last_non_empty_line=line_num + + # look for trailing white space but not just a \n + if (current_line_length > 0 and len(line.rstrip()) != current_line_length): + print('warning: line %d trailing whitespace' % (line_num)) + + if (current_line_length > 0 and line.lstrip().find(" ")!=-1): + print('warning: line %d contains double spaces' % (line_num)) + + if (skip_blank_line): + skip_blank_line = False + continue + + if (line.startswith(".. code-block::")): + in_code_block = True + skip_blank_line = True + ignore_last_line = True + continue + if (line.startswith(".. ")): + in_code_block = False + ignore_last_line = True + continue + if (line.startswith("---") or line.startswith("===") + or line.startswith("^^^^")): + if (line_num > 1 and len(line) != len(last_line)): + print('warning: line %d title and markup non matching lengths' % (line_num)) + ignore_last_line = True + continue + if (line.startswith(" :")): + ignore_last_line = True + continue + if (line.startswith(" - ")): + ignore_last_line = True + continue + if (line.startswith(" See http")): + ignore_last_line = True + continue + if (line == "\n"): + ignore_last_line = True + continue + if (in_code_block): + if (not line.startswith(" ")): + in_code_block = False + ignore_last_line = True + else: + continue + + # ignore very long clang check name + # ` to :doc:`readability-uppercase-literal-suffix + if (line.lstrip().startswith(" 80: + print('warning: line %d is in excess of 80 characters (%d) : %s...' % (line_num, len(line), line)) + elif (len(line)) > 0 and (not ignore_last_line and (len(last_line) > 0) and (len(last_line) < 80)): + words = line.split() + if (len(words) > 0 and len(words[0])>0 and not + words[0].startswith("`") and not words[0].startswith("<")): + # allow for the space that would be needed + if ((len(words[0]) + len(last_line)) < 80): + words_from_nextline = words[0]; + + print("warning: line %d maximize 80 characters by joining:'[%s]' and '[%s...]\n" % + (line_num-1, last_line.rstrip(), words_from_nextline)) + else: + ignore_last_line = False + + last_line = line + + if (line_num != last_non_empty_line): + print("warning: line %d %d empty lines at the end of the file\n" % (last_non_empty_line,line_num-last_non_empty_line)) + + +def validate_all_checks(clang_tidy_path): + docs_dir = os.path.join(clang_tidy_path, '../docs/clang-tidy/checks') + filename = os.path.normpath(os.path.join(docs_dir, 'list.rst')) + + with open(filename, 'r') as f: + lines = f.readlines() + + doc_files = list(filter(lambda s: s.endswith('.rst') and s != 'list.rst', + os.listdir(docs_dir))) + doc_files.sort() + + for doc_file in doc_files: + filename = os.path.join(docs_dir, doc_file) + validate_rst(filename) + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument( + '--check-all', + action='store_true', + help='check all the check rst files') + parser.add_argument( + 'module', + nargs='?', + help='module directory under which to place the tidy check (e.g., misc)') + parser.add_argument( + 'check', + nargs='?', + help='name of new tidy check to check (e.g. foo-do-the-stuff)') + args = parser.parse_args() + + clang_tidy_path = os.path.dirname(sys.argv[0]) + + if args.check_all: + validate_release_notes(clang_tidy_path) + validate_all_checks(clang_tidy_path) + return + + if not args.module or not args.check: + print('Module and check must be specified.') + parser.print_usage() + return + + module = args.module + check_name = args.check + + if check_name.startswith(module): + print('Check name "%s" must not start with the module "%s". Exiting.' % ( + check_name, module)) + return + + check_name_camel = ''.join(map(lambda elem: elem.capitalize(), + check_name.split('-'))) + 'Check' + module_path = os.path.join(clang_tidy_path, module) + + validate_release_notes(clang_tidy_path) + validate_docs(module_path, module, check_name) + print('You are ready to review!') + + +if __name__ == '__main__': + main()