github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/scripts/find-bad-doc-comments.py (about) 1 #!/usr/bin/python 2 3 # Copyright 2014 Canonical Ltd. 4 # Licensed under the AGPLv3, see LICENCE file for details. 5 6 """ 7 This quick-and-dirty tool locates Go doc comments in a source tree 8 that don't follow the convention of the first word of the comment 9 matching the function name. It highlights cases where doc comments 10 haven't been updated in step with function name changes or where doc 11 comments have been copied and pasted but not updated. 12 13 Tests are excluded by the check, unless --tests is given. 14 Unexported methods and functions are also excluded, unless --unexported 15 is given. 16 17 By default, all problems found are emitted but there is also an 18 interactive edit mode is available via --fix. 19 """ 20 21 import argparse 22 import fnmatch 23 import os 24 import re 25 import subprocess 26 from os import path 27 28 def find_go_files(root): 29 for directory, _, files in os.walk(root): 30 for filename in fnmatch.filter(files, '*.go'): 31 yield path.join(directory, filename) 32 33 DOC_COMMENT_PATT = '\n\n//.+\n(//.+\n)*func.+\n' 34 FIRST_WORD_PATT = '// *(\w+)' 35 FUNC_NAME_PATT = 'func(?: \([^)]+\))? (\S+)\(' 36 37 def extract_doc_comments(text): 38 for match in re.finditer(DOC_COMMENT_PATT, text, re.MULTILINE): 39 yield match.group(0).strip() 40 41 def find_bad_doc_comments(comments): 42 for comment in comments: 43 lines = comment.splitlines() 44 first_word_match = re.match(FIRST_WORD_PATT, lines[0]) 45 if first_word_match: 46 first_word = first_word_match.group(1) 47 func_name = re.match(FUNC_NAME_PATT, lines[-1]).group(1) 48 if first_word != func_name: 49 yield func_name, comment 50 51 def cmdline(): 52 parser = argparse.ArgumentParser(description=__doc__) 53 parser.add_argument('--fix', default=False, action='store_true', 54 help='Interactive fix-up mode') 55 parser.add_argument('--tests', default=False, action='store_true', 56 help='Include test methods in the check') 57 parser.add_argument('--unexported', default=False, action='store_true', 58 help='Include unexported methods in the check') 59 parser.add_argument('root', nargs='?', default=os.getcwd()) 60 return parser.parse_args() 61 62 def emit(filename, comment): 63 print 64 print '%s: ' % filename 65 print comment 66 67 def fix(filename, func_name, comment): 68 emit(filename, comment) 69 resp = raw_input('Fix? [Y/n] ').strip().lower() 70 if resp in ('', 'y'): 71 subprocess.check_call(['vim', '-c', '/func .*'+func_name+'(', filename]) 72 73 def main(): 74 args = cmdline() 75 76 count = 0 77 for filename in find_go_files(args.root): 78 with open(filename) as sourceFile: 79 source = sourceFile.read() 80 comments = extract_doc_comments(source) 81 for func_name, bad_comment in find_bad_doc_comments(comments): 82 if func_name.startswith('Test') and not args.tests: 83 # Skip tests unless told otherwise. 84 continue 85 if 'export_test.go' in filename and not args.tests: 86 # Skip export_test.go unless --tests is given. 87 continue 88 if func_name[0].islower() and not args.unexported: 89 # Skip unexported unless told otherwise. 90 continue 91 if args.fix: 92 fix(filename, func_name, bad_comment) 93 else: 94 emit(filename, bad_comment) 95 count += 1 96 97 print 98 print "Problems found:", count 99 100 if __name__ == '__main__': 101 main()