check_GNU_style_lib.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. #!/usr/bin/env python3
  2. #
  3. # Checks some of the GNU style formatting rules in a set of patches.
  4. # The script is a rewritten of the same bash script and should eventually
  5. # replace the former script.
  6. #
  7. # This file is part of GCC.
  8. #
  9. # GCC is free software; you can redistribute it and/or modify it under
  10. # the terms of the GNU General Public License as published by the Free
  11. # Software Foundation; either version 3, or (at your option) any later
  12. # version.
  13. #
  14. # GCC is distributed in the hope that it will be useful, but WITHOUT ANY
  15. # WARRANTY; without even the implied warranty of MERCHANTABILITY or
  16. # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
  17. # for more details.
  18. #
  19. # You should have received a copy of the GNU General Public License
  20. # along with GCC; see the file COPYING3. If not see
  21. # <http://www.gnu.org/licenses/>. */
  22. #
  23. # The script requires python packages, which can be installed via pip3
  24. # like this:
  25. # $ pip3 install unidiff termcolor
  26. import sys
  27. import re
  28. import unittest
  29. def import_pip3(*args):
  30. missing=[]
  31. for (module, names) in args:
  32. try:
  33. lib = __import__(module)
  34. except ImportError:
  35. missing.append(module)
  36. continue
  37. if not isinstance(names, list):
  38. names=[names]
  39. for name in names:
  40. globals()[name]=getattr(lib, name)
  41. if len(missing) > 0:
  42. missing_and_sep = ' and '.join(missing)
  43. missing_space_sep = ' '.join(missing)
  44. print('%s %s missing (run: pip3 install %s)'
  45. % (missing_and_sep,
  46. ("module is" if len(missing) == 1 else "modules are"),
  47. missing_space_sep))
  48. exit(3)
  49. import_pip3(('termcolor', 'colored'),
  50. ('unidiff', 'PatchSet'))
  51. from itertools import *
  52. ws_char = '█'
  53. ts = 8
  54. def error_string(s):
  55. return colored(s, 'red', attrs = ['bold'])
  56. class CheckError:
  57. def __init__(self, filename, lineno, console_error, error_message,
  58. column = -1):
  59. self.filename = filename
  60. self.lineno = lineno
  61. self.console_error = console_error
  62. self.error_message = error_message
  63. self.column = column
  64. def error_location(self):
  65. return '%s:%d:%d:' % (self.filename, self.lineno,
  66. self.column if self.column != -1 else -1)
  67. class LineLengthCheck:
  68. def __init__(self):
  69. self.limit = 80
  70. self.expanded_tab = ' ' * ts
  71. def check(self, filename, lineno, line):
  72. line_expanded = line.replace('\t', self.expanded_tab)
  73. if len(line_expanded) > self.limit:
  74. return CheckError(filename, lineno,
  75. line_expanded[:self.limit]
  76. + error_string(line_expanded[self.limit:]),
  77. 'lines should not exceed 80 characters', self.limit)
  78. return None
  79. class SpacesCheck:
  80. def __init__(self):
  81. self.expanded_tab = ' ' * ts
  82. def check(self, filename, lineno, line):
  83. i = line.find(self.expanded_tab)
  84. if i != -1:
  85. return CheckError(filename, lineno,
  86. line.replace(self.expanded_tab, error_string(ws_char * ts)),
  87. 'blocks of 8 spaces should be replaced with tabs', i)
  88. class SpacesAndTabsMixedCheck:
  89. def __init__(self):
  90. self.re = re.compile('\ \t')
  91. def check(self, filename, lineno, line):
  92. stripped = line.lstrip()
  93. start = line[:len(line) - len(stripped)]
  94. if self.re.search(line):
  95. return CheckError(filename, lineno,
  96. error_string(start.replace('\t', ws_char * ts)) + line[len(start):],
  97. 'a space should not precede a tab', 0)
  98. class TrailingWhitespaceCheck:
  99. def __init__(self):
  100. self.re = re.compile('(\s+)$')
  101. def check(self, filename, lineno, line):
  102. assert(len(line) == 0 or line[-1] != '\n')
  103. m = self.re.search(line)
  104. if m != None:
  105. return CheckError(filename, lineno,
  106. line[:m.start(1)] + error_string(ws_char * len(m.group(1)))
  107. + line[m.end(1):],
  108. 'trailing whitespace', m.start(1))
  109. class SentenceSeparatorCheck:
  110. def __init__(self):
  111. self.re = re.compile('\w\.(\s|\s{3,})\w')
  112. def check(self, filename, lineno, line):
  113. m = self.re.search(line)
  114. if m != None:
  115. return CheckError(filename, lineno,
  116. line[:m.start(1)] + error_string(ws_char * len(m.group(1)))
  117. + line[m.end(1):],
  118. 'dot, space, space, new sentence', m.start(1))
  119. class SentenceEndOfCommentCheck:
  120. def __init__(self):
  121. self.re = re.compile('\w\.(\s{0,1}|\s{3,})\*/')
  122. def check(self, filename, lineno, line):
  123. m = self.re.search(line)
  124. if m != None:
  125. return CheckError(filename, lineno,
  126. line[:m.start(1)] + error_string(ws_char * len(m.group(1)))
  127. + line[m.end(1):],
  128. 'dot, space, space, end of comment', m.start(1))
  129. class SentenceDotEndCheck:
  130. def __init__(self):
  131. self.re = re.compile('\w(\s*\*/)')
  132. def check(self, filename, lineno, line):
  133. m = self.re.search(line)
  134. if m != None:
  135. return CheckError(filename, lineno,
  136. line[:m.start(1)] + error_string(m.group(1)) + line[m.end(1):],
  137. 'dot, space, space, end of comment', m.start(1))
  138. class FunctionParenthesisCheck:
  139. # TODO: filter out GTY stuff
  140. def __init__(self):
  141. self.re = re.compile('\w(\s{2,})?(\()')
  142. def check(self, filename, lineno, line):
  143. if '#define' in line:
  144. return None
  145. m = self.re.search(line)
  146. if m != None:
  147. return CheckError(filename, lineno,
  148. line[:m.start(2)] + error_string(m.group(2)) + line[m.end(2):],
  149. 'there should be exactly one space between function name ' \
  150. 'and parenthesis', m.start(2))
  151. class SquareBracketCheck:
  152. def __init__(self):
  153. self.re = re.compile('\w\s+(\[)')
  154. def check(self, filename, lineno, line):
  155. m = self.re.search(line)
  156. if m != None:
  157. return CheckError(filename, lineno,
  158. line[:m.start(1)] + error_string(m.group(1)) + line[m.end(1):],
  159. 'there should be no space before a left square bracket',
  160. m.start(1))
  161. class ClosingParenthesisCheck:
  162. def __init__(self):
  163. self.re = re.compile('\S\s+(\))')
  164. def check(self, filename, lineno, line):
  165. m = self.re.search(line)
  166. if m != None:
  167. return CheckError(filename, lineno,
  168. line[:m.start(1)] + error_string(m.group(1)) + line[m.end(1):],
  169. 'there should be no space before closing parenthesis',
  170. m.start(1))
  171. class BracesOnSeparateLineCheck:
  172. # This will give false positives for C99 compound literals.
  173. def __init__(self):
  174. self.re = re.compile('(\)|else)\s*({)')
  175. def check(self, filename, lineno, line):
  176. m = self.re.search(line)
  177. if m != None:
  178. return CheckError(filename, lineno,
  179. line[:m.start(2)] + error_string(m.group(2)) + line[m.end(2):],
  180. 'braces should be on a separate line', m.start(2))
  181. class TrailinigOperatorCheck:
  182. def __init__(self):
  183. regex = '^\s.*(([^a-zA-Z_]\*)|([-%<=&|^?])|([^*]/)|([^:][+]))$'
  184. self.re = re.compile(regex)
  185. def check(self, filename, lineno, line):
  186. m = self.re.search(line)
  187. if m != None:
  188. return CheckError(filename, lineno,
  189. line[:m.start(1)] + error_string(m.group(1)) + line[m.end(1):],
  190. 'trailing operator', m.start(1))
  191. class LineLengthTest(unittest.TestCase):
  192. def setUp(self):
  193. self.check = LineLengthCheck()
  194. def test_line_length_check_basic(self):
  195. r = self.check.check('foo', 123, self.check.limit * 'a' + ' = 123;')
  196. self.assertIsNotNone(r)
  197. self.assertEqual('foo', r.filename)
  198. self.assertEqual(80, r.column)
  199. self.assertEqual(r.console_error,
  200. self.check.limit * 'a' + error_string(' = 123;'))
  201. class TrailingWhitespaceTest(unittest.TestCase):
  202. def setUp(self):
  203. self.check = TrailingWhitespaceCheck()
  204. def test_trailing_whitespace_check_basic(self):
  205. r = self.check.check('foo', 123, 'a = 123;')
  206. self.assertIsNone(r)
  207. r = self.check.check('foo', 123, 'a = 123; ')
  208. self.assertIsNotNone(r)
  209. r = self.check.check('foo', 123, 'a = 123;\t')
  210. self.assertIsNotNone(r)
  211. class SpacesAndTabsMixedTest(unittest.TestCase):
  212. def setUp(self):
  213. self.check = SpacesAndTabsMixedCheck()
  214. def test_trailing_whitespace_check_basic(self):
  215. r = self.check.check('foo', 123, ' \ta = 123;')
  216. self.assertEqual('foo', r.filename)
  217. self.assertEqual(0, r.column)
  218. self.assertIsNotNone(r.console_error)
  219. r = self.check.check('foo', 123, ' \t a = 123;')
  220. self.assertIsNotNone(r.console_error)
  221. r = self.check.check('foo', 123, '\t a = 123;')
  222. self.assertIsNone(r)
  223. def check_GNU_style_file(file, file_encoding, format):
  224. checks = [LineLengthCheck(), SpacesCheck(), TrailingWhitespaceCheck(),
  225. SentenceSeparatorCheck(), SentenceEndOfCommentCheck(),
  226. SentenceDotEndCheck(), FunctionParenthesisCheck(),
  227. SquareBracketCheck(), ClosingParenthesisCheck(),
  228. BracesOnSeparateLineCheck(), TrailinigOperatorCheck(),
  229. SpacesAndTabsMixedCheck()]
  230. errors = []
  231. patch = PatchSet(file, encoding=file_encoding)
  232. for pfile in patch.added_files + patch.modified_files:
  233. t = pfile.target_file.lstrip('b/')
  234. # Skip testsuite files
  235. if 'testsuite' in t or t.endswith('.py'):
  236. continue
  237. for hunk in pfile:
  238. delta = 0
  239. for line in hunk:
  240. if line.is_added and line.target_line_no != None:
  241. for check in checks:
  242. line_chomp = line.value.replace('\n', '')
  243. e = check.check(t, line.target_line_no, line_chomp)
  244. if e != None:
  245. errors.append(e)
  246. if format == 'stdio':
  247. fn = lambda x: x.error_message
  248. i = 1
  249. for (k, errors) in groupby(sorted(errors, key = fn), fn):
  250. errors = list(errors)
  251. print('=== ERROR type #%d: %s (%d error(s)) ==='
  252. % (i, k, len(errors)))
  253. i += 1
  254. for e in errors:
  255. print(e.error_location () + e.console_error)
  256. print()
  257. exit(0 if len(errors) == 0 else 1)
  258. elif format == 'quickfix':
  259. f = 'errors.err'
  260. with open(f, 'w+') as qf:
  261. for e in errors:
  262. qf.write('%s%s\n' % (e.error_location(), e.error_message))
  263. if len(errors) == 0:
  264. exit(0)
  265. else:
  266. print('%d error(s) written to %s file.' % (len(errors), f))
  267. exit(1)
  268. else:
  269. assert False
  270. if __name__ == '__main__':
  271. unittest.main()