Index: clang-tidy/validate_check.py =================================================================== --- /dev/null +++ clang-tidy/validate_check.py @@ -0,0 +1,281 @@ +#!/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 + +no_maximize = False + +# Adds a release notes entry. +def validate_file(single_file): + filename = os.path.normpath(single_file) + return validate_rst(filename) + +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_no_newline = line.rstrip('\r\n'); + line_num = line_num + 1 + current_line_length = len(line_no_newline) + + # Look for multiple 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 + ignore_last_line = True + continue + else: + previous_line_blank = False + last_non_empty_line=line_num + + # Look for trailing white space but not just a newline. + if current_line_length > 0 and len(line.rstrip()) != current_line_length: + print('warning: line %d trailing whitespace' % (line_num)) + + if skip_blank_line: + skip_blank_line = False + continue + + if (line.startswith(".. code-block::") or + line.startswith(".. code::") or + line.startswith(":orphan:") or + line.startswith("Examples:")): + 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 + + # Look for places where the markup does match the text above. + if (not in_code_block and (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(" :") or + line.startswith(" - ") or + line.startswith("``") or + line.startswith("===") or + line.startswith("---") or + line.startswith(" ") or + line.lstrip().startswith("* ") or + line.lstrip().startswith("- ") or + re.search(r"^\d+\. .*",line.lstrip())): + ignore_last_line = True + continue + + # URL can be longer than 80 characters just ignore those lines. + if (line.find("https:")!=-1 or + line.find("http:")!=-1 or + line.find(".html")!=-1): + ignore_last_line = True + continue + + # Look for double whitespaces often cause during editing. + # e.g.". The...." + if (current_line_length > 0 and line.lstrip().find(" ")!=-1): + if not in_code_block: + print('warning: line %d contains double spaces' % (line_num)) + + + # If we are in a code clock, and we've skipped the the first + # blank line and future blank line is the end of the code block. + if in_code_block: + if current_line_length == 0: + 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 not can_lines_be_joined(current_line_length,last_line,line, + line_num,ignore_last_line): + ignore_last_line = False + + # Remember this line so we can check the previous line next time + last_line = line + + # After processing the whole file determine if we have and blank + # lines at the edit of the file + 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)) + return 1 + + return 0 + +# Look to see if we cannot concatinate this line with the last one +# to make the previous line upto or closer to 80 characters. +def can_lines_be_joined(current_line_length,last_line,line, + line_num,ignore_last_line): + if not no_maximize: + return False + if line_num <= 1: + return False + + length_last_line = len(last_line) + + if (length_last_line == 0 and length_last_line >= 80): + return False + + if ignore_last_line: + return False + + if current_line_length == 0: + return False + + # The previous line is smaller than 80 see if we can't + # add the first work from the next line. + words = line.split() + + # If there are no words to split we are done + if (len(words) ==0 or len(words[0]) == 0): + return False; + + # ignore joining what looks like quoted code or tag + if (words[0].startswith("`") or words[0].startswith("<")): + return False; + + # Allow for the space that would be needed and if its + # still less than 80 then ask user to concatinate + new_line_length = len(words[0]) + len(last_line) + + if new_line_length < 80: + print("warning: line %d maximize 80 characters by joining:" + "'[%s]' and '[%s...]\n" + % (line_num-1, last_line.rstrip(), words[0])) + return True + + return False + +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( + '--no-maximize', + action='store_true', + help='do not check for maximizing the line to 80 characters') + parser.add_argument( + '--rst', + nargs = '?', + help='the rst file to be checked') + parser.add_argument( + 'module', + nargs = '?', + help='module directory under which to tidy check is found (e.g., misc)') + parser.add_argument( + 'check', + nargs= '?', + help='name of tidy check to check (e.g. foo-do-the-stuff)') + args = parser.parse_args() + + clang_tidy_path = os.path.dirname(sys.argv[0]) + no_maximize = args.no_maximize + + if args.check_all: + validate_release_notes(clang_tidy_path) + validate_all_checks(clang_tidy_path) + return 0 + + if args.rst: + return validate_file(args.rst) + + if not args.module or not args.check: + print('Both module and check must be specified.') + parser.print_usage() + return 1 + + 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 1 + + 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!') + return 0 + + +if __name__ == '__main__': + sys.exit(main())