1"""Use the Python pygments library to perform extra checks on C++ grammar.""" 2 3from pygments import token 4from pygments.lexers.compiled import CppLexer 5import os 6 7 8def check_header_file(fh_name, project_name, errors): 9 """Check a single C++ header file""" 10 _check_file(fh_name, project_name, True, errors) 11 12 13def check_cpp_file(fh_name, project_name, errors): 14 """Check a single C++ source file""" 15 _check_file(fh_name, project_name, False, errors) 16 17 18def _check_file(fh_name, project_name, header, errors): 19 fh, filename = fh_name 20 s = tokenize_file(fh) 21 check_tokens(s, filename, project_name, header, errors) 22 23 24def tokenize_file(fh): 25 """Use the Python pygments library to tokenize a C++ file""" 26 code = fh.read() 27 c = CppLexer() 28 scan = [] 29 for (index, tok, value) in c.get_tokens_unprocessed(code): 30 scan.append((tok, value)) 31 return scan 32 33 34def check_tokens(scan, filename, project_name, header, errors): 35 if filename.find("test_") == -1: 36 # we don't do it for python tests 37 check_comment_header(scan, filename, errors) 38 if header: 39 # Handle older versions of pygments which concatenate \n and # tokens 40 if len(scan) >= 3 and scan[2][0] == token.Comment.Preproc \ 41 and scan[2][1] == '\n#': 42 scan[2] = (token.Comment.Preproc, '#') 43 scan.insert(2, (token.Comment.Text, '\n')) 44 check_header_start_end(scan, filename, project_name, errors) 45 46 47def check_comment_header(scan, filename, errors): 48 if len(scan) < 1 or scan[0][0] not in (token.Comment, 49 token.Comment.Multiline): 50 errors.append('%s:1: First line should be a comment with a copyright ' 51 'notice and a description of the file' % filename) 52 53 54def have_header_guard(scan): 55 return len(scan) >= 11 \ 56 and scan[4][0] == token.Comment.Preproc \ 57 and scan[4][1].startswith('ifndef') \ 58 and scan[7][0] == token.Comment.Preproc \ 59 and scan[7][1].startswith('define') \ 60 and scan[-3][0] == token.Comment.Preproc \ 61 and scan[-3][1].startswith('endif') \ 62 and scan[-2][0] in (token.Comment, token.Comment.Multiline) 63 64 65def get_header_guard(filename, project_name): 66 """Get prefix and suffix for header guard""" 67 guard_prefix = project_name.replace(".", "").upper() 68 guard_suffix = os.path.split(filename)[1].replace(".", "_").upper() 69 return guard_prefix, guard_suffix 70 71 72def check_header_start_end(scan, filename, project_name, errors): 73 guard_prefix, guard_suffix = get_header_guard(filename, project_name) 74 header_guard = guard_prefix + '_' + guard_suffix 75 if len(scan) < 11: 76 bad = True 77 else: 78 bad = False 79 if not scan[4][0] == token.Comment.Preproc: 80 bad = True 81 if not scan[4][1].startswith('ifndef'): 82 errors.append('%s:%d: Header guard missing #ifndef.' 83 % (filename, 1)) 84 bad = True 85 if not scan[7][0] == token.Comment.Preproc: 86 bad = True 87 if not scan[7][1].startswith('define'): 88 errors.append('%s:%d: Header guard missing #define.' 89 % (filename, 1)) 90 bad = True 91 if not scan[-3][0] == token.Comment.Preproc \ 92 and not scan[-4][0] == token.Comment.Preproc: 93 bad = True 94 if not scan[-3][1].startswith('endif') \ 95 and not scan[-4][1].startswith('endif'): 96 errors.append('%s:%d: Header guard missing #endif.' 97 % (filename, 1)) 98 bad = True 99 if not scan[-2][0] in (token.Comment, token.Comment.Multiline) \ 100 and not scan[-3][0] in (token.Comment, token.Comment.Multiline): 101 errors.append('%s:%d: Header guard missing closing comment.' 102 % (filename, 1)) 103 bad = True 104 105 guard = scan[4][1][7:] 106 if not guard.startswith(guard_prefix): 107 errors.append('%s:%d: Header guard does not start with "%s".' 108 % (filename, 1, guard_prefix)) 109 bad = True 110 if not guard.replace("_", "").endswith(guard_suffix.replace("_", "")): 111 errors.append('%s:%d: Header guard does not end with "%s".' 112 % (filename, 1, guard_suffix)) 113 bad = True 114 if not scan[7][1] == 'define ' + guard: 115 errors.append('%s:%d: Header guard does not define "%s".' 116 % (filename, 1, guard)) 117 bad = True 118 if not scan[-2][1] == '/* %s */' % guard \ 119 and not scan[-3][1] == '/* %s */' % guard: 120 errors.append('%s:%d: Header guard close does not have a ' 121 'comment of "/* %s */".' % (filename, 1, guard)) 122 bad = True 123 if bad: 124 errors.append('%s:%d: Missing or incomplete header guard.' 125 % (filename, 1) + """ 126Header files should start with a comment, then a blank line, then the rest 127of the file wrapped with a header guard. This must start with %s 128and end with %s - in between can be placed extra qualifiers, e.g. for a 129namespace. For example, 130 131/** Copyright and file description */ 132 133#ifndef %s 134#define %s 135... 136#endif /* %s */ 137""" % (guard_prefix, guard_suffix, header_guard, header_guard, header_guard)) 138