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 TrailingWhitespaceCheck
:
104 self
.re
= re
.compile('(\s+)$')
106 def check(self
, filename
, lineno
, line
):
107 assert(len(line
) == 0 or line
[-1] != '\n')
108 m
= self
.re
.search(line
)
110 return CheckError(filename
, lineno
,
111 line
[:m
.start(1)] + error_string(ws_char
* len(m
.group(1)))
113 'trailing whitespace', m
.start(1))
115 class SentenceSeparatorCheck
:
117 self
.re
= re
.compile('\w\.(\s|\s{3,})\w')
119 def check(self
, filename
, lineno
, line
):
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 'dot, space, space, new sentence', m
.start(1))
127 class SentenceEndOfCommentCheck
:
129 self
.re
= re
.compile('\w\.(\s{0,1}|\s{3,})\*/')
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, end of comment', m
.start(1))
139 class SentenceDotEndCheck
:
141 self
.re
= re
.compile('\w(\s*\*/)')
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(m
.group(1)) + line
[m
.end(1):],
148 'dot, space, space, end of comment', m
.start(1))
150 class FunctionParenthesisCheck
:
151 # TODO: filter out GTY stuff
153 self
.re
= re
.compile('\w(\s{2,})?(\()')
155 def check(self
, filename
, lineno
, line
):
156 if '#define' in line
:
159 m
= self
.re
.search(line
)
161 return CheckError(filename
, lineno
,
162 line
[:m
.start(2)] + error_string(m
.group(2)) + line
[m
.end(2):],
163 'there should be exactly one space between function name ' \
164 'and parenthesis', m
.start(2))
166 class SquareBracketCheck
:
168 self
.re
= re
.compile('\w\s+(\[)')
170 def check(self
, filename
, lineno
, line
):
171 m
= self
.re
.search(line
)
173 return CheckError(filename
, lineno
,
174 line
[:m
.start(1)] + error_string(m
.group(1)) + line
[m
.end(1):],
175 'there should be no space before a left square bracket',
178 class ClosingParenthesisCheck
:
180 self
.re
= re
.compile('\S\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 closing parenthesis',
190 class BracesOnSeparateLineCheck
:
191 # This will give false positives for C99 compound literals.
194 self
.re
= re
.compile('(\)|else)\s*({)')
196 def check(self
, filename
, lineno
, line
):
197 m
= self
.re
.search(line
)
199 return CheckError(filename
, lineno
,
200 line
[:m
.start(2)] + error_string(m
.group(2)) + line
[m
.end(2):],
201 'braces should be on a separate line', m
.start(2))
203 class TrailinigOperatorCheck
:
205 regex
= '^\s.*(([^a-zA-Z_]\*)|([-%<=&|^?])|([^*]/)|([^:][+]))$'
206 self
.re
= re
.compile(regex
)
208 def check(self
, filename
, lineno
, line
):
209 m
= self
.re
.search(line
)
211 return CheckError(filename
, lineno
,
212 line
[:m
.start(1)] + error_string(m
.group(1)) + line
[m
.end(1):],
213 'trailing operator', m
.start(1))
215 class LineLengthTest(unittest
.TestCase
):
217 self
.check
= LineLengthCheck()
219 def test_line_length_check_basic(self
):
220 r
= self
.check
.check('foo', 123, self
.check
.limit
* 'a' + ' = 123;')
221 self
.assertIsNotNone(r
)
222 self
.assertEqual('foo', r
.filename
)
223 self
.assertEqual(80, r
.column
)
224 self
.assertEqual(r
.console_error
,
225 self
.check
.limit
* 'a' + error_string(' = 123;'))
227 class TrailingWhitespaceTest(unittest
.TestCase
):
229 self
.check
= TrailingWhitespaceCheck()
231 def test_trailing_whitespace_check_basic(self
):
232 r
= self
.check
.check('foo', 123, 'a = 123;')
234 r
= self
.check
.check('foo', 123, 'a = 123; ')
235 self
.assertIsNotNone(r
)
236 r
= self
.check
.check('foo', 123, 'a = 123;\t')
237 self
.assertIsNotNone(r
)
239 def check_GNU_style_file(file, file_encoding
, format
):
240 checks
= [LineLengthCheck(), SpacesCheck(), TrailingWhitespaceCheck(),
241 SentenceSeparatorCheck(), SentenceEndOfCommentCheck(),
242 SentenceDotEndCheck(), FunctionParenthesisCheck(),
243 SquareBracketCheck(), ClosingParenthesisCheck(),
244 BracesOnSeparateLineCheck(), TrailinigOperatorCheck()]
247 patch
= PatchSet(file, encoding
=file_encoding
)
249 for pfile
in patch
.added_files
+ patch
.modified_files
:
250 t
= pfile
.target_file
.lstrip('b/')
251 # Skip testsuite files
258 if line
.is_added
and line
.target_line_no
!= None:
260 line_chomp
= line
.value
.replace('\n', '')
261 e
= check
.check(t
, line
.target_line_no
, line_chomp
)
265 if format
== 'stdio':
266 fn
= lambda x
: x
.error_message
268 for (k
, errors
) in groupby(sorted(errors
, key
= fn
), fn
):
269 errors
= list(errors
)
270 print('=== ERROR type #%d: %s (%d error(s)) ==='
271 % (i
, k
, len(errors
)))
274 print(e
.error_location () + e
.console_error
)
277 exit(0 if len(errors
) == 0 else 1)
278 elif format
== 'quickfix':
280 with
open(f
, 'w+') as qf
:
282 qf
.write('%s%s\n' % (e
.error_location(), e
.error_message
))
286 print('%d error(s) written to %s file.' % (len(errors
), f
))
291 if __name__
== '__main__':