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 m
= self
.re
.search(line
)
109 return CheckError(filename
, lineno
,
110 line
[:m
.start(1)] + error_string(ws_char
* len(m
.group(1)))
112 'trailing whitespace', m
.start(1))
114 class SentenceSeparatorCheck
:
116 self
.re
= re
.compile('\w\.(\s|\s{3,})\w')
118 def check(self
, filename
, lineno
, line
):
119 m
= self
.re
.search(line
)
121 return CheckError(filename
, lineno
,
122 line
[:m
.start(1)] + error_string(ws_char
* len(m
.group(1)))
124 'dot, space, space, new sentence', m
.start(1))
126 class SentenceEndOfCommentCheck
:
128 self
.re
= re
.compile('\w\.(\s{0,1}|\s{3,})\*/')
130 def check(self
, filename
, lineno
, line
):
131 m
= self
.re
.search(line
)
133 return CheckError(filename
, lineno
,
134 line
[:m
.start(1)] + error_string(ws_char
* len(m
.group(1)))
136 'dot, space, space, end of comment', m
.start(1))
138 class SentenceDotEndCheck
:
140 self
.re
= re
.compile('\w(\s*\*/)')
142 def check(self
, filename
, lineno
, line
):
143 m
= self
.re
.search(line
)
145 return CheckError(filename
, lineno
,
146 line
[:m
.start(1)] + error_string(m
.group(1)) + line
[m
.end(1):],
147 'dot, space, space, end of comment', m
.start(1))
149 class FunctionParenthesisCheck
:
150 # TODO: filter out GTY stuff
152 self
.re
= re
.compile('\w(\s{2,})?(\()')
154 def check(self
, filename
, lineno
, line
):
155 if '#define' in line
:
158 m
= self
.re
.search(line
)
160 return CheckError(filename
, lineno
,
161 line
[:m
.start(2)] + error_string(m
.group(2)) + line
[m
.end(2):],
162 'there should be exactly one space between function name ' \
163 'and parenthesis', m
.start(2))
165 class SquareBracketCheck
:
167 self
.re
= re
.compile('\w\s+(\[)')
169 def check(self
, filename
, lineno
, line
):
170 m
= self
.re
.search(line
)
172 return CheckError(filename
, lineno
,
173 line
[:m
.start(1)] + error_string(m
.group(1)) + line
[m
.end(1):],
174 'there should be no space before a left square bracket',
177 class ClosingParenthesisCheck
:
179 self
.re
= re
.compile('\S\s+(\))')
181 def check(self
, filename
, lineno
, line
):
182 m
= self
.re
.search(line
)
184 return CheckError(filename
, lineno
,
185 line
[:m
.start(1)] + error_string(m
.group(1)) + line
[m
.end(1):],
186 'there should be no space before closing parenthesis',
189 class BracesOnSeparateLineCheck
:
190 # This will give false positives for C99 compound literals.
193 self
.re
= re
.compile('(\)|else)\s*({)')
195 def check(self
, filename
, lineno
, line
):
196 m
= self
.re
.search(line
)
198 return CheckError(filename
, lineno
,
199 line
[:m
.start(2)] + error_string(m
.group(2)) + line
[m
.end(2):],
200 'braces should be on a separate line', m
.start(2))
202 class TrailinigOperatorCheck
:
204 regex
= '^\s.*(([^a-zA-Z_]\*)|([-%<=&|^?])|([^*]/)|([^:][+]))$'
205 self
.re
= re
.compile(regex
)
207 def check(self
, filename
, lineno
, line
):
208 m
= self
.re
.search(line
)
210 return CheckError(filename
, lineno
,
211 line
[:m
.start(1)] + error_string(m
.group(1)) + line
[m
.end(1):],
212 'trailing operator', m
.start(1))
214 class LineLengthTest(unittest
.TestCase
):
216 self
.check
= LineLengthCheck()
218 def test_line_length_check_basic(self
):
219 r
= self
.check
.check('foo', 123, self
.check
.limit
* 'a' + ' = 123;')
220 self
.assertIsNotNone(r
)
221 self
.assertEqual('foo', r
.filename
)
222 self
.assertEqual(80, r
.column
)
223 self
.assertEqual(r
.console_error
,
224 self
.check
.limit
* 'a' + error_string(' = 123;'))
226 def check_GNU_style_file(file, format
):
227 checks
= [LineLengthCheck(), SpacesCheck(), TrailingWhitespaceCheck(),
228 SentenceSeparatorCheck(), SentenceEndOfCommentCheck(),
229 SentenceDotEndCheck(), FunctionParenthesisCheck(),
230 SquareBracketCheck(), ClosingParenthesisCheck(),
231 BracesOnSeparateLineCheck(), TrailinigOperatorCheck()]
234 with
open(file, 'rb') as diff_file
:
235 patch
= PatchSet(diff_file
, encoding
= 'utf-8')
237 for pfile
in patch
.added_files
+ patch
.modified_files
:
238 t
= pfile
.target_file
.lstrip('b/')
239 # Skip testsuite files
246 if line
.is_added
and line
.target_line_no
!= None:
248 e
= check
.check(t
, line
.target_line_no
, line
.value
)
252 if format
== 'stdio':
253 fn
= lambda x
: x
.error_message
255 for (k
, errors
) in groupby(sorted(errors
, key
= fn
), fn
):
256 errors
= list(errors
)
257 print('=== ERROR type #%d: %s (%d error(s)) ==='
258 % (i
, k
, len(errors
)))
261 print(e
.error_location () + e
.console_error
)
264 exit(0 if len(errors
) == 0 else 1)
265 elif format
== 'quickfix':
267 with
open(f
, 'w+') as qf
:
269 qf
.write('%s%s\n' % (e
.error_location(), e
.error_message
))
273 print('%d error(s) written to %s file.' % (len(errors
), f
))
278 if __name__
== '__main__':