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.
7 # This file is part of GCC.
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
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
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/>. */
23 # The script requires python packages, which can be installed via pip3
25 # $ pip3 install unidiff termcolor
31 def import_pip3(*args
):
33 for (module
, names
) in args
:
35 lib
= __import__(module
)
37 missing
.append(module
)
39 if not isinstance(names
, list):
42 globals()[name
]=getattr(lib
, name
)
44 missing_and_sep
= ' and '.join(missing
)
45 missing_space_sep
= ' '.join(missing
)
46 print('%s %s missing (run: pip3 install %s)'
48 ("module is" if len(missing
) == 1 else "modules are"),
52 import_pip3(('termcolor', 'colored'),
53 ('unidiff', 'PatchSet'))
55 from itertools
import *
61 return colored(s
, 'red', attrs
= ['bold'])
64 def __init__(self
, filename
, lineno
, console_error
, error_message
,
66 self
.filename
= filename
68 self
.console_error
= console_error
69 self
.error_message
= error_message
72 def error_location(self
):
73 return '%s:%d:%d:' % (self
.filename
, self
.lineno
,
74 self
.column
if self
.column
!= -1 else -1)
76 class LineLengthCheck
:
79 self
.expanded_tab
= ' ' * ts
81 def check(self
, filename
, lineno
, line
):
82 line_expanded
= line
.replace('\t', self
.expanded_tab
)
83 if len(line_expanded
) > self
.limit
:
84 return CheckError(filename
, lineno
,
85 line_expanded
[:self
.limit
]
86 + error_string(line_expanded
[self
.limit
:]),
87 'lines should not exceed 80 characters', self
.limit
)
93 self
.expanded_tab
= ' ' * ts
95 def check(self
, filename
, lineno
, line
):
96 i
= line
.find(self
.expanded_tab
)
98 return CheckError(filename
, lineno
,
99 line
.replace(self
.expanded_tab
, error_string(ws_char
* ts
)),
100 'blocks of 8 spaces should be replaced with tabs', i
)
102 class SpacesAndTabsMixedCheck
:
104 self
.re
= re
.compile('\ \t')
106 def check(self
, filename
, lineno
, line
):
107 stripped
= line
.lstrip()
108 start
= line
[:len(line
) - len(stripped
)]
109 if self
.re
.search(line
):
110 return CheckError(filename
, lineno
,
111 error_string(start
.replace('\t', ws_char
* ts
)) + line
[len(start
):],
112 'a space should not precede a tab', 0)
114 class TrailingWhitespaceCheck
:
116 self
.re
= re
.compile('(\s+)$')
118 def check(self
, filename
, lineno
, line
):
119 assert(len(line
) == 0 or line
[-1] != '\n')
120 m
= self
.re
.search(line
)
122 return CheckError(filename
, lineno
,
123 line
[:m
.start(1)] + error_string(ws_char
* len(m
.group(1)))
125 'trailing whitespace', m
.start(1))
127 class SentenceSeparatorCheck
:
129 self
.re
= re
.compile('\w\.(\s|\s{3,})\w')
131 def check(self
, filename
, lineno
, line
):
132 m
= self
.re
.search(line
)
134 return CheckError(filename
, lineno
,
135 line
[:m
.start(1)] + error_string(ws_char
* len(m
.group(1)))
137 'dot, space, space, new sentence', m
.start(1))
139 class SentenceEndOfCommentCheck
:
141 self
.re
= re
.compile('\w\.(\s{0,1}|\s{3,})\*/')
143 def check(self
, filename
, lineno
, line
):
144 m
= self
.re
.search(line
)
146 return CheckError(filename
, lineno
,
147 line
[:m
.start(1)] + error_string(ws_char
* len(m
.group(1)))
149 'dot, space, space, end of comment', m
.start(1))
151 class SentenceDotEndCheck
:
153 self
.re
= re
.compile('\w(\s*\*/)')
155 def check(self
, filename
, lineno
, line
):
156 m
= self
.re
.search(line
)
158 return CheckError(filename
, lineno
,
159 line
[:m
.start(1)] + error_string(m
.group(1)) + line
[m
.end(1):],
160 'dot, space, space, end of comment', m
.start(1))
162 class FunctionParenthesisCheck
:
163 # TODO: filter out GTY stuff
165 self
.re
= re
.compile('\w(\s{2,})?(\()')
167 def check(self
, filename
, lineno
, line
):
168 if '#define' in line
:
171 m
= self
.re
.search(line
)
173 return CheckError(filename
, lineno
,
174 line
[:m
.start(2)] + error_string(m
.group(2)) + line
[m
.end(2):],
175 'there should be exactly one space between function name ' \
176 'and parenthesis', m
.start(2))
178 class SquareBracketCheck
:
180 self
.re
= re
.compile('\w\s+(\[)')
182 def check(self
, filename
, lineno
, line
):
183 m
= self
.re
.search(line
)
185 return CheckError(filename
, lineno
,
186 line
[:m
.start(1)] + error_string(m
.group(1)) + line
[m
.end(1):],
187 'there should be no space before a left square bracket',
190 class ClosingParenthesisCheck
:
192 self
.re
= re
.compile('\S\s+(\))')
194 def check(self
, filename
, lineno
, line
):
195 m
= self
.re
.search(line
)
197 return CheckError(filename
, lineno
,
198 line
[:m
.start(1)] + error_string(m
.group(1)) + line
[m
.end(1):],
199 'there should be no space before closing parenthesis',
202 class BracesOnSeparateLineCheck
:
203 # This will give false positives for C99 compound literals.
206 self
.re
= re
.compile('(\)|else)\s*({)')
208 def check(self
, filename
, lineno
, line
):
209 m
= self
.re
.search(line
)
211 return CheckError(filename
, lineno
,
212 line
[:m
.start(2)] + error_string(m
.group(2)) + line
[m
.end(2):],
213 'braces should be on a separate line', m
.start(2))
215 class TrailinigOperatorCheck
:
217 regex
= '^\s.*(([^a-zA-Z_]\*)|([-%<=&|^?])|([^*]/)|([^:][+]))$'
218 self
.re
= re
.compile(regex
)
220 def check(self
, filename
, lineno
, line
):
221 m
= self
.re
.search(line
)
223 return CheckError(filename
, lineno
,
224 line
[:m
.start(1)] + error_string(m
.group(1)) + line
[m
.end(1):],
225 'trailing operator', m
.start(1))
227 class LineLengthTest(unittest
.TestCase
):
229 self
.check
= LineLengthCheck()
231 def test_line_length_check_basic(self
):
232 r
= self
.check
.check('foo', 123, self
.check
.limit
* 'a' + ' = 123;')
233 self
.assertIsNotNone(r
)
234 self
.assertEqual('foo', r
.filename
)
235 self
.assertEqual(80, r
.column
)
236 self
.assertEqual(r
.console_error
,
237 self
.check
.limit
* 'a' + error_string(' = 123;'))
239 class TrailingWhitespaceTest(unittest
.TestCase
):
241 self
.check
= TrailingWhitespaceCheck()
243 def test_trailing_whitespace_check_basic(self
):
244 r
= self
.check
.check('foo', 123, 'a = 123;')
246 r
= self
.check
.check('foo', 123, 'a = 123; ')
247 self
.assertIsNotNone(r
)
248 r
= self
.check
.check('foo', 123, 'a = 123;\t')
249 self
.assertIsNotNone(r
)
251 class SpacesAndTabsMixedTest(unittest
.TestCase
):
253 self
.check
= SpacesAndTabsMixedCheck()
255 def test_trailing_whitespace_check_basic(self
):
256 r
= self
.check
.check('foo', 123, ' \ta = 123;')
257 self
.assertEqual('foo', r
.filename
)
258 self
.assertEqual(0, r
.column
)
259 self
.assertIsNotNone(r
.console_error
)
260 r
= self
.check
.check('foo', 123, ' \t a = 123;')
261 self
.assertIsNotNone(r
.console_error
)
262 r
= self
.check
.check('foo', 123, '\t a = 123;')
265 def check_GNU_style_file(file, file_encoding
, format
):
266 checks
= [LineLengthCheck(), SpacesCheck(), TrailingWhitespaceCheck(),
267 SentenceSeparatorCheck(), SentenceEndOfCommentCheck(),
268 SentenceDotEndCheck(), FunctionParenthesisCheck(),
269 SquareBracketCheck(), ClosingParenthesisCheck(),
270 BracesOnSeparateLineCheck(), TrailinigOperatorCheck(),
271 SpacesAndTabsMixedCheck()]
274 patch
= PatchSet(file, encoding
=file_encoding
)
276 for pfile
in patch
.added_files
+ patch
.modified_files
:
277 t
= pfile
.target_file
.lstrip('b/')
278 # Skip testsuite files
279 if 'testsuite' in t
or t
.endswith('.py'):
285 if line
.is_added
and line
.target_line_no
!= None:
287 line_chomp
= line
.value
.replace('\n', '')
288 e
= check
.check(t
, line
.target_line_no
, line_chomp
)
292 if format
== 'stdio':
293 fn
= lambda x
: x
.error_message
295 for (k
, errors
) in groupby(sorted(errors
, key
= fn
), fn
):
296 errors
= list(errors
)
297 print('=== ERROR type #%d: %s (%d error(s)) ==='
298 % (i
, k
, len(errors
)))
301 print(e
.error_location () + e
.console_error
)
304 exit(0 if len(errors
) == 0 else 1)
305 elif format
== 'quickfix':
307 with
open(f
, 'w+') as qf
:
309 qf
.write('%s%s\n' % (e
.error_location(), e
.error_message
))
313 print('%d error(s) written to %s file.' % (len(errors
), f
))
318 if __name__
== '__main__':