Plugins/CheckerPlugins/Pep8/Pep8NamingChecker.py

branch
Py2 comp.
changeset 3056
9986ec0e559a
parent 2910
cdc56e9d9f12
equal deleted inserted replaced
2911:ce77f0b1ee67 3056:9986ec0e559a
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2013 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a checker for PEP-8 naming conventions.
8 """
9
10 from __future__ import unicode_literals # __IGNORE_WARNING__
11
12 import collections
13 import ast
14 import re
15 import os
16
17 from PyQt4.QtCore import QT_TRANSLATE_NOOP, QCoreApplication
18
19
20 class Pep8NamingChecker(object):
21 """
22 Class implementing a checker for PEP-8 naming conventions.
23 """
24 LowercaseRegex = re.compile(r"[_a-z][_a-z0-9]*$")
25 UppercaseRegexp = re.compile(r"[_A-Z][_A-Z0-9]*$")
26 CamelcaseRegexp = re.compile(r"_?[A-Z][a-zA-Z0-9]*$")
27 MixedcaseRegexp = re.compile(r"_?[a-z][a-zA-Z0-9]*$")
28
29 Codes = [
30 "N801", "N802", "N803", "N804", "N805", "N806", "N807", "N808",
31 "N811", "N812", "N813", "N814", "N821", "N831"
32 ]
33 Messages = {
34 "N801": QT_TRANSLATE_NOOP("Pep8NamingChecker",
35 "class names should use CapWords convention"),
36 "N802": QT_TRANSLATE_NOOP("Pep8NamingChecker",
37 "function name should be lowercase"),
38 "N803": QT_TRANSLATE_NOOP("Pep8NamingChecker",
39 "argument name should be lowercase"),
40 "N804": QT_TRANSLATE_NOOP("Pep8NamingChecker",
41 "first argument of a class method should be named 'cls'"),
42 "N805": QT_TRANSLATE_NOOP("Pep8NamingChecker",
43 "first argument of a method should be named 'self'"),
44 "N806": QT_TRANSLATE_NOOP("Pep8NamingChecker",
45 "first argument of a static method should not be named"
46 " 'self' or 'cls"),
47 "N807": QT_TRANSLATE_NOOP("Pep8NamingChecker",
48 "module names should be lowercase"),
49 "N808": QT_TRANSLATE_NOOP("Pep8NamingChecker",
50 "package names should be lowercase"),
51 "N811": QT_TRANSLATE_NOOP("Pep8NamingChecker",
52 "constant imported as non constant"),
53 "N812": QT_TRANSLATE_NOOP("Pep8NamingChecker",
54 "lowercase imported as non lowercase"),
55 "N813": QT_TRANSLATE_NOOP("Pep8NamingChecker",
56 "camelcase imported as lowercase"),
57 "N814": QT_TRANSLATE_NOOP("Pep8NamingChecker",
58 "camelcase imported as constant"),
59 "N821": QT_TRANSLATE_NOOP("Pep8NamingChecker",
60 "variable in function should be lowercase"),
61 "N831": QT_TRANSLATE_NOOP("Pep8NamingChecker",
62 "names 'l', 'O' and 'I' should be avoided"),
63 }
64
65 def __init__(self, tree, filename, options):
66 """
67 Constructor (according to 'extended' pep8.py API)
68
69 @param tree AST tree of the source file
70 @param filename name of the source file (string)
71 @param options options as parsed by pep8.StyleGuide
72 """
73 self.__parents = collections.deque()
74 self.__tree = tree
75 self.__filename = filename
76
77 self.__checkersWithCodes = {
78 "classdef": [
79 (self.__checkClassName, ("N801",)),
80 (self.__checkNameToBeAvoided, ("N831",)),
81 ],
82 "functiondef": [
83 (self.__checkFuntionName, ("N802",)),
84 (self.__checkFunctionArgumentNames,
85 ("N803", "N804", "N805", "N806")),
86 (self.__checkNameToBeAvoided, ("N831",)),
87 ],
88 "assign": [
89 (self.__checkVariablesInFunction, ("N821",)),
90 (self.__checkNameToBeAvoided, ("N831",)),
91 ],
92 "importfrom": [
93 (self.__checkImportAs, ("N811", "N812", "N813", "N814")),
94 ],
95 "module": [
96 (self.__checkModule, ("N807", "N808")),
97 ],
98 }
99
100 self.__checkers = {}
101 for key, checkers in self.__checkersWithCodes.items():
102 for checker, codes in checkers:
103 if any(not (code and options.ignore_code(code))
104 for code in codes):
105 if key not in self.__checkers:
106 self.__checkers[key] = []
107 self.__checkers[key].append(checker)
108
109 def run(self):
110 """
111 Public method run by the pep8.py checker.
112
113 @return tuple giving line number, offset within line, code and
114 checker function
115 """
116 if self.__tree:
117 return self.__visitTree(self.__tree)
118 else:
119 return ()
120
121 @classmethod
122 def getMessage(cls, code, *args):
123 """
124 Class method to get a translated and formatted message for a
125 given code.
126
127 @param code message code (string)
128 @param args arguments for a formatted message (list)
129 @return translated and formatted message (string)
130 """
131 if code in cls.Messages:
132 return code + " " + QCoreApplication.translate("Pep8NamingChecker",
133 cls.Messages[code]).format(*args)
134 else:
135 return code + " " + QCoreApplication.translate("Pep8NamingChecker",
136 "no message for this code defined")
137
138 def __visitTree(self, node):
139 """
140 Private method to scan the given AST tree.
141
142 @param node AST tree node to scan
143 @return tuple giving line number, offset within line, code and
144 checker function
145 """
146 for error in self.__visitNode(node):
147 yield error
148 self.__parents.append(node)
149 for child in ast.iter_child_nodes(node):
150 for error in self.__visitTree(child):
151 yield error
152 self.__parents.pop()
153
154 def __visitNode(self, node):
155 """
156 Private method to inspect the given AST node.
157
158 @param node AST tree node to inspect
159 @return tuple giving line number, offset within line, code and
160 checker function
161 """
162 if isinstance(node, ast.ClassDef):
163 self.__tagClassFunctions(node)
164 elif isinstance(node, ast.FunctionDef):
165 self.__findGlobalDefs(node)
166
167 checkerName = node.__class__.__name__.lower()
168 if checkerName in self.__checkers:
169 for checker in self.__checkers[checkerName]:
170 for error in checker(node, self.__parents):
171 yield error + (self.__checkers[checkerName],)
172
173 def __tagClassFunctions(self, classNode):
174 """
175 Private method to tag functions if they are methods, class methods or
176 static methods.
177
178 @param classNode AST tree node to tag
179 """
180 # try to find all 'old style decorators' like
181 # m = staticmethod(m)
182 lateDecoration = {}
183 for node in ast.iter_child_nodes(classNode):
184 if not (isinstance(node, ast.Assign) and
185 isinstance(node.value, ast.Call) and
186 isinstance(node.value.func, ast.Name)):
187 continue
188 funcName = node.value.func.id
189 if funcName in ("classmethod", "staticmethod"):
190 meth = (len(node.value.args) == 1 and node.value.args[0])
191 if isinstance(meth, ast.Name):
192 lateDecoration[meth.id] = funcName
193
194 # iterate over all functions and tag them
195 for node in ast.iter_child_nodes(classNode):
196 if not isinstance(node, ast.FunctionDef):
197 continue
198
199 node.function_type = 'method'
200 if node.name == "__new__":
201 node.function_type = "classmethod"
202
203 if node.name in lateDecoration:
204 node.function_type = lateDecoration[node.name]
205 elif node.decorator_list:
206 names = [d.id for d in node.decorator_list
207 if isinstance(d, ast.Name) and
208 d.id in ("classmethod", "staticmethod")]
209 if names:
210 node.function_type = names[0]
211
212 def __findGlobalDefs(self, functionNode):
213 """
214 Private method amend a node with global definitions information.
215
216 @param functionNode AST tree node to amend
217 """
218 globalNames = set()
219 nodesToCheck = collections.deque(ast.iter_child_nodes(functionNode))
220 while nodesToCheck:
221 node = nodesToCheck.pop()
222 if isinstance(node, ast.Global):
223 globalNames.update(node.names)
224
225 if not isinstance(node, (ast.FunctionDef, ast.ClassDef)):
226 nodesToCheck.extend(ast.iter_child_nodes(node))
227 functionNode.global_names = globalNames
228
229 def __getArgNames(self, node):
230 """
231 Private method to get the argument names of a function node.
232
233 @param node AST node to extract arguments names from
234 @return list of argument names (list of string)
235 """
236 posArgs = [arg.arg for arg in node.args.args]
237 kwOnly = [arg.arg for arg in node.args.kwonlyargs]
238 return posArgs + kwOnly
239
240 def __error(self, node, code):
241 """
242 Private method to build the error information
243
244 @param node AST node to report an error for
245 @param code error code to report (string)
246 @return tuple giving line number, offset within line and error code
247 (integer, integer, string)
248 """
249 if isinstance(node, ast.Module):
250 lineno = 0
251 offset = 0
252 else:
253 lineno = node.lineno
254 offset = node.col_offset
255 if isinstance(node, ast.ClassDef):
256 lineno += len(node.decorator_list)
257 offset += 6
258 elif isinstance(node, ast.FunctionDef):
259 lineno += len(node.decorator_list)
260 offset += 4
261 return (lineno, offset, code)
262
263 def __isNameToBeAvoided(self, name):
264 """
265 Private method to check, if the given name should be avoided.
266
267 @param name name to be checked (string)
268 @return flag indicating to avoid it (boolen)
269 """
270 return name in ("l", "O", "I")
271
272 def __checkNameToBeAvoided(self, node, parents):
273 """
274 Private class to check the given node for a name to be avoided (N831).
275
276 @param node AST note to check
277 @return tuple giving line number, offset within line and error code
278 (integer, integer, string)
279 """
280 if isinstance(node, (ast.ClassDef, ast.FunctionDef)):
281 name = node.name
282 if self.__isNameToBeAvoided(name):
283 yield self.__error(node, "N831")
284 return
285
286 if isinstance(node, ast.FunctionDef):
287 argNames = self.__getArgNames(node)
288 for arg in argNames:
289 if self.__isNameToBeAvoided(arg):
290 yield self.__error(node, "N831")
291 return
292
293 if isinstance(node, ast.Assign):
294 for target in node.targets:
295 name = isinstance(target, ast.Name) and target.id
296 if not name:
297 return
298
299 if self.__isNameToBeAvoided(name):
300 yield self.__error(node, "N831")
301 return
302
303 def __checkClassName(self, node, parents):
304 """
305 Private class to check the given node for class name
306 conventions (N801).
307
308 Almost without exception, class names use the CapWords convention.
309 Classes for internal use have a leading underscore in addition.
310
311 @param node AST note to check
312 @return tuple giving line number, offset within line and error code
313 (integer, integer, string)
314 """
315 if not self.CamelcaseRegexp.match(node.name):
316 yield self.__error(node, "N801")
317
318 def __checkFuntionName(self, node, parents):
319 """
320 Private class to check the given node for function name
321 conventions (N802).
322
323 Function names should be lowercase, with words separated by underscores
324 as necessary to improve readability. Functions <b>not</b> being
325 methods '__' in front and back are not allowed. Mixed case is allowed
326 only in contexts where that's already the prevailing style
327 (e.g. threading.py), to retain backwards compatibility.
328
329 @param node AST note to check
330 @return tuple giving line number, offset within line and error code
331 (integer, integer, string)
332 """
333 functionType = getattr(node, "function_type", "function")
334 name = node.name
335 if (functionType == "function" and "__" in (name[:2], name[-2:])) or \
336 not self.LowercaseRegex.match(name):
337 yield self.__error(node, "N802")
338
339 def __checkFunctionArgumentNames(self, node, parents):
340 """
341 Private class to check the argument names of functions
342 (N803, N804, N805, N806).
343
344 The argument names of a function should be lowercase, with words
345 separated by underscores. A class method should have 'cls' as the
346 first argument. A method should have 'self' as the first argument.
347
348 @param node AST note to check
349 @return tuple giving line number, offset within line and error code
350 (integer, integer, string)
351 """
352 if node.args.kwarg is not None:
353 if not self.LowercaseRegex.match(node.args.kwarg):
354 yield self.__error(node, "N803")
355 return
356
357 if node.args.vararg is not None:
358 if not self.LowercaseRegex.match(node.args.vararg):
359 yield self.__error(node, "N803")
360 return
361
362 argNames = self.__getArgNames(node)
363 functionType = getattr(node, "function_type", "function")
364
365 if not argNames:
366 if functionType == "method":
367 yield self.__error(node, "N805")
368 elif functionType == "classmethod":
369 yield self.__error(node, "N804")
370 return
371
372 if functionType == "method":
373 if argNames[0] != "self":
374 yield self.__error(node, "N805")
375 elif functionType == "classmethod":
376 if argNames[0] != "cls":
377 yield self.__error(node, "N804")
378 elif functionType == "staticmethod":
379 if argNames[0] in ("cls", "self"):
380 yield self.__error(node, "N806")
381 for arg in argNames:
382 if not self.LowercaseRegex.match(arg):
383 yield self.__error(node, "N803")
384 return
385
386 def __checkVariablesInFunction(self, node, parents):
387 """
388 Private method to check local variables in functions (N821).
389
390 Local variables in functions should be lowercase.
391
392 @param node AST note to check
393 @return tuple giving line number, offset within line and error code
394 (integer, integer, string)
395 """
396 for parentFunc in reversed(parents):
397 if isinstance(parentFunc, ast.ClassDef):
398 return
399 if isinstance(parentFunc, ast.FunctionDef):
400 break
401 else:
402 return
403 for target in node.targets:
404 name = isinstance(target, ast.Name) and target.id
405 if not name or name in parentFunc.global_names:
406 return
407
408 if not self.LowercaseRegex.match(name) and name[:1] != '_':
409 yield self.__error(target, "N821")
410
411 def __checkModule(self, node, parents):
412 """
413 Private method to check module naming conventions (N807, N808).
414
415 Module and package names should be lowercase.
416
417 @param node AST note to check
418 @return tuple giving line number, offset within line and error code
419 (integer, integer, string)
420 """
421 if self.__filename:
422 moduleName = os.path.splitext(os.path.basename(self.__filename))[0]
423 if moduleName.lower() != moduleName:
424 yield self.__error(node, "N807")
425
426 if moduleName == "__init__":
427 # we got a package
428 packageName = \
429 os.path.split(os.path.dirname(self.__filename))[1]
430 if packageName.lower != packageName:
431 yield self.__error(node, "N808")
432
433 def __checkImportAs(self, node, parents):
434 """
435 Private method to check that imports don't change the
436 naming convention (N811, N812, N813, N814).
437
438 @param node AST note to check
439 @return tuple giving line number, offset within line and error code
440 (integer, integer, string)
441 """
442 for name in node.names:
443 if not name.asname:
444 continue
445
446 if self.UppercaseRegexp.match(name.name):
447 if not self.UppercaseRegexp.match(name.asname):
448 yield self.__error(node, "N811")
449 elif self.LowercaseRegex.match(name.name):
450 if not self.LowercaseRegex.match(name.asname):
451 yield self.__error(node, "N812")
452 elif self.LowercaseRegex.match(name.asname):
453 yield self.__error(node, "N813")
454 elif self.UppercaseRegexp.match(name.asname):
455 yield self.__error(node, "N814")

eric ide

mercurial