Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code

Side by Side Diff: flake8-abp/flake8_abp.py

Issue 29340727: Noissue - Added flake8 extension accounting for our coding style and some other stuff (Closed)
Patch Set: Accounf for elif Created April 22, 2016, 2:35 p.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | flake8-abp/setup.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 # This file is part of Adblock Plus <https://adblockplus.org/>,
2 # Copyright (C) 2006-2016 Eyeo GmbH
3 #
4 # Adblock Plus is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License version 3 as
6 # published by the Free Software Foundation.
7 #
8 # Adblock Plus is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
12 #
13 # You should have received a copy of the GNU General Public License
14 # along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>.
15
16 import ast
17 import re
18 import tokenize
19 import sys
20
21 __version__ = '0.1'
22
23 DEPRECATED_APIS = {
24 ('re', 'match'): 'A101 use re.search() instead re.match()',
25 ('codecs', 'open'): 'A102 use io.open() instead codecs.open()',
26 }
27
28 BAILOUT = (ast.Return, ast.Raise, ast.Continue, ast.Break)
29
30
31 def is_const(node):
32 if isinstance(node, (ast.Str, ast.Num)):
33 return True
34 if hasattr(ast, 'Bytes') and isinstance(node, ast.Bytes):
35 return True
36
37 if isinstance(node, (ast.Tuple, ast.List, ast.Set)):
38 return all(is_const(elt) for elt in node.elts)
39 if isinstance(node, ast.Name):
40 return node.id in {'None', 'True', 'False'}
41 if isinstance(node, ast.BinOp):
42 return is_const(node.left) and is_const(node.right)
43 if isinstance(node, ast.UnaryOp):
44 return is_const(node.operand)
45
46 return False
47
48
49 class TreeVisitor(ast.NodeVisitor):
50 def __init__(self):
51 self.errors = []
52 self.stack = []
53
54 def _visit_block(self, nodes, mandatory=False, docstring=False):
55 pass_node = None
56 bailed = False
57 dead_code = False
58
59 for node in nodes:
60 if isinstance(node, ast.Pass):
61 pass_node = node
62
63 if bailed and not dead_code:
64 dead_code = True
65 self.errors.append((node, 'A151 dead code after '
66 'return/raise/continue/break'))
67
68 if isinstance(node, BAILOUT):
69 bailed = True
70
71 if not isinstance(node, ast.Expr):
72 continue
73 if isinstance(node.value, (ast.Call, ast.Yield)):
74 continue
75 if docstring and node is nodes[0] and isinstance(node.value, ast.Str ):
76 continue
77
78 self.errors.append((node, 'A152 unused expression'))
79
80 if pass_node:
81 if len(nodes) > 1:
82 self.errors.append((pass_node, 'A153 redundant pass statement'))
83
84 if not mandatory and all(isinstance(n, ast.Pass) for n in nodes):
85 self.errors.append((pass_node, 'A154 empty block'))
86
87 def _visit_block_node(self, node, **kwargs):
88 self._visit_block(node.body, **kwargs)
89 if hasattr(node, 'orelse'):
90 self._visit_block(node.orelse)
91 if hasattr(node, 'finalbody'):
92 self._visit_block(node.finalbody)
93 self.generic_visit(node)
94
95 visit_Try = visit_TryExcept = visit_TryFinally = _visit_block_node
96
97 visit_ExceptHandler = visit_While = \
98 lambda self, node: self._visit_block_node(node, mandatory=True)
99
100 visit_Module = visit_ClassDef = \
101 lambda self, node: self._visit_block_node(node, mandatory=True,
102 docstring=True)
103
104 def visit_Attribute(self, node):
105 if isinstance(node.ctx, ast.Load) and isinstance(node.value, ast.Name):
106 error = DEPRECATED_APIS.get((node.value.id, node.attr))
107 if error:
108 self.errors.append((node, error))
109 self.generic_visit(node)
110
111 def visit_ImportFrom(self, node):
112 for alias in node.names:
113 error = DEPRECATED_APIS.get((node.module, alias.name))
114 if error:
115 self.errors.append((node, error))
116
117 def visit_BinOp(self, node):
118 if isinstance(node.op, ast.Mod) and isinstance(node.left, ast.Str):
119 self.errors.append((node, 'A111 use format() instead % operator '
120 'for string formatting'))
121
122 multi_addition = (isinstance(node.op, ast.Add) and
123 isinstance(node.left, ast.BinOp) and
124 isinstance(node.left.op, ast.Add))
125 if multi_addition and (isinstance(node.left.left, ast.Str) or
126 isinstance(node.left.right, ast.Str) or
127 isinstance(node.right, ast.Str)):
128 self.errors.append((node, 'A112 use format() instead + operator '
129 'when concatenating >2 strings'))
130
131 self.generic_visit(node)
132
133 def visit_comprehension(self, node):
134 if isinstance(node.iter, (ast.Tuple, ast.Set)):
135 self.errors.append((node.iter, 'A121 use lists for data '
136 'that have order'))
137 self.generic_visit(node)
138
139 def visit_For(self, node):
140 self._visit_block(node.body, mandatory=True)
141 self.visit_comprehension(node)
142
143 def visit_Compare(self, node):
144 left = node.left
145 single = len(node.ops) == 1
146
147 for op, right in zip(node.ops, node.comparators):
148 membership = isinstance(op, (ast.In, ast.NotIn))
149 symmetric = isinstance(op, (ast.Eq, ast.NotEq, ast.Is, ast.IsNot))
150
151 if membership and isinstance(right, (ast.Tuple, ast.List)):
152 self.errors.append((right, 'A122 use sets for distinct '
153 'unordered data'))
154
155 consts_first = single and not membership or symmetric
156 if consts_first and is_const(left) and not is_const(right):
157 self.errors.append((left, 'A123 yoda condition'))
158
159 left = right
160
161 self.generic_visit(node)
162
163 def visit_Call(self, node):
164 func = node.func
165 if isinstance(func, ast.Name) and len(node.args) > 0:
166 arg = node.args[0]
167
168 if isinstance(arg, ast.Lambda) and func.id in {'filter', 'map'}:
169 self.errors.append((node, 'A131 use a comprehension '
170 'instead calling {}() with '
171 'lambda function'.format(func.id)))
172
173 if isinstance(arg, (ast.ListComp, ast.GeneratorExp)):
174 redundant_comp = func.id in {'list', 'set'}
175 if func.id == 'dict':
176 redundant_comp = isinstance(arg.elt, (ast.Tuple, ast.List))
177
178 if redundant_comp:
179 self.errors.append((node, 'A132 use a {0} comprehension '
180 'instead calling the {0}() '
181 'constructor'.format(func.id)))
182
183 self.generic_visit(node)
184
185 def visit_FunctionDef(self, node):
186 self._visit_block(node.body, mandatory=True, docstring=True)
187
188 self.stack.append((set(), []))
189 self.generic_visit(node)
190 targets, globals = self.stack.pop()
191
192 for var in globals:
193 if any(name not in targets for name in var.names):
194 self.errors.append((var, 'A141 redundant global/nonlocal '
195 'declaration'))
196
197 def visit_Name(self, node):
198 if self.stack and isinstance(node.ctx, ast.Store):
199 self.stack[-1][0].add(node.id)
200
201 def visit_Global(self, node):
202 if self.stack:
203 self.stack[-1][1].append(node)
204 else:
205 self.errors.append((node, 'A141 global/nonlocal declaration '
206 'on top-level'))
207
208 visit_Nonlocal = visit_Global
209
210 def visit_If(self, node):
211 has_else = bool(node.orelse)
212
213 if has_else and any(isinstance(n, BAILOUT) for n in node.body):
214 self.errors.append((node, 'A159 redundant else statement after '
215 'return/raise/continue/break '
216 'in if-clause'))
217
218 self._visit_block(node.body, mandatory=has_else)
219 self._visit_block(node.orelse)
220 self.generic_visit(node)
221
222 def visit_Assign(self, node):
223 if isinstance(node.value, ast.BinOp) and len(node.targets) == 1:
224 target = node.targets[0]
225 left_is_target = (isinstance(target, ast.Name) and
226 isinstance(node.value.left, ast.Name) and
227 target.id == node.value.left.id)
228 if left_is_target:
229 self.errors.append((node, 'A161 use in-place assignment, '
230 'e.g. x += y instead x = x + y'))
231 self.generic_visit(node)
232
233
234 class ASTChecker(object):
235 name = 'abp'
236 version = __version__
237
238 def __init__(self, tree, filename):
239 self.tree = tree
240
241 def run(self):
242 visitor = TreeVisitor()
243 visitor.visit(self.tree)
244
245 for node, error in visitor.errors:
246 yield (node.lineno, node.col_offset, error, type(self))
247
248
249 def check_non_default_encoding(physical_line, line_number):
250 if line_number <= 2 and re.search(r'^\s*#.*coding[:=]', physical_line):
251 return (0, 'A201 non-default file encoding')
252
253 check_non_default_encoding.name = 'abp-non-default-encoding'
254 check_non_default_encoding.version = __version__
255
256
257 def check_quotes(logical_line, tokens, previous_logical, indent_level):
258 first_token = True
259
260 for kind, token, start, end, _ in tokens:
261 if kind == tokenize.INDENT:
262 continue
263
264 if kind == tokenize.STRING:
265 pos = start[1] - indent_level
266 match = re.search(r'^(u)?(b)?(r)?((""")?.*)$',
267 token, re.IGNORECASE | re.DOTALL)
268 (is_unicode, is_bytes, is_raw,
269 literal, has_doc_quotes) = match.groups()
270
271 if first_token and re.search(r'^(?:(?:def|class)\s|$)',
272 previous_logical):
273 if not has_doc_quotes:
274 yield (pos, 'A301 use triple double quotes for docstrings')
275 if is_unicode or is_bytes or is_raw:
276 yield (pos, "A302 don't use u, b or r for doc strings")
277 elif start[0] == end[0]:
278 if is_raw:
279 literal = re.sub(r'\\(?!{})'.format(literal[0]),
280 '\\\\\\\\', literal)
281
282 if sys.version_info[0] >= 3:
283 if is_bytes:
284 literal = 'b' + literal
285 else:
286 literal = re.sub(r'(?<!\\)\\x(?!a[0d])([a-f][0-9a-f])',
287 lambda m: chr(int(m.group(1), 16)),
288 literal)
289 elif is_unicode:
290 literal = 'u' + literal
291
292 if repr(eval(literal)) != literal:
293 yield (pos, "A311 string literal doesn't match repr()")
294
295 first_token = False
296
297 check_quotes.name = 'abp-quotes'
298 check_quotes.version = __version__
299
300
301 def check_redundant_parenthesis(logical_line, tokens, indent_level):
302 start_line = tokens[0][2][0]
303 level = 0
304 statement = None
305
306 for i, (kind, token, _, end, _) in enumerate(tokens):
307 if kind == tokenize.INDENT:
308 continue
309
310 if statement is None:
311 # logical line doesn't start with an if, elif or while statement
312 if kind != tokenize.NAME or token not in {'if', 'elif', 'while'}:
313 break
314
315 # expression doesn't start with parenthesis
316 next_token = tokens[i + 1]
317 if next_token[:2] != (tokenize.OP, '('):
318 break
319
320 # expression is empty tuple
321 if tokens[i + 2][:2] == (tokenize.OP, ')'):
322 break
323
324 statement = token
325 pos = next_token[2][1] - indent_level
326 continue
327
328 # expression ends on a different line, parenthesis are necessary
329 if end[0] > start_line:
330 break
331
332 if kind == tokenize.OP:
333 if token == ',':
334 # expression is non-empty tuple
335 if level == 1:
336 break
337 elif token == '(':
338 level += 1
339 elif token == ')':
340 level -= 1
341 if level == 0:
342 # outer parenthesis closed before end of expression
343 if tokens[i + 1][:2] != (tokenize.OP, ':'):
344 break
345
346 return [(pos, 'A401 redundant parenthesis for {} '
347 'statement'.format(statement))]
348
349 return []
350
351 check_redundant_parenthesis.name = 'abp-redundant-parenthesis'
352 check_redundant_parenthesis.version = __version__
OLDNEW
« no previous file with comments | « no previous file | flake8-abp/setup.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld