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