Plugins/CheckerPlugins/CodeStyleChecker/NamingStyleChecker.py

branch
Py2 comp.
changeset 3057
10516539f238
parent 3056
9986ec0e559a
parent 3004
c4bf32c791d0
child 3058
0a02c433f52d
equal deleted inserted replaced
3056:9986ec0e559a 3057:10516539f238
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2013 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a checker for 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 NamingStyleChecker(object):
21 """
22 Class implementing a checker for 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("NamingStyleChecker",
35 "class names should use CapWords convention"),
36 "N802": QT_TRANSLATE_NOOP("NamingStyleChecker",
37 "function name should be lowercase"),
38 "N803": QT_TRANSLATE_NOOP("NamingStyleChecker",
39 "argument name should be lowercase"),
40 "N804": QT_TRANSLATE_NOOP("NamingStyleChecker",
41 "first argument of a class method should be named 'cls'"),
42 "N805": QT_TRANSLATE_NOOP("NamingStyleChecker",
43 "first argument of a method should be named 'self'"),
44 "N806": QT_TRANSLATE_NOOP("NamingStyleChecker",
45 "first argument of a static method should not be named"
46 " 'self' or 'cls"),
47 "N807": QT_TRANSLATE_NOOP("NamingStyleChecker",
48 "module names should be lowercase"),
49 "N808": QT_TRANSLATE_NOOP("NamingStyleChecker",
50 "package names should be lowercase"),
51 "N811": QT_TRANSLATE_NOOP("NamingStyleChecker",
52 "constant imported as non constant"),
53 "N812": QT_TRANSLATE_NOOP("NamingStyleChecker",
54 "lowercase imported as non lowercase"),
55 "N813": QT_TRANSLATE_NOOP("NamingStyleChecker",
56 "camelcase imported as lowercase"),
57 "N814": QT_TRANSLATE_NOOP("NamingStyleChecker",
58 "camelcase imported as constant"),
59 "N821": QT_TRANSLATE_NOOP("NamingStyleChecker",
60 "variable in function should be lowercase"),
61 "N831": QT_TRANSLATE_NOOP("NamingStyleChecker",
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 and self.__checkers:
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(
133 "NamingStyleChecker",
134 cls.Messages[code]).format(*args)
135 else:
136 return code + " " + QCoreApplication.translate(
137 "NamingStyleChecker",
138 "no message for this code defined")
139
140 def __visitTree(self, node):
141 """
142 Private method to scan the given AST tree.
143
144 @param node AST tree node to scan
145 @return tuple giving line number, offset within line, code and
146 checker function
147 """
148 for error in self.__visitNode(node):
149 yield error
150 self.__parents.append(node)
151 for child in ast.iter_child_nodes(node):
152 for error in self.__visitTree(child):
153 yield error
154 self.__parents.pop()
155
156 def __visitNode(self, node):
157 """
158 Private method to inspect the given AST node.
159
160 @param node AST tree node to inspect
161 @return tuple giving line number, offset within line, code and
162 checker function
163 """
164 if isinstance(node, ast.ClassDef):
165 self.__tagClassFunctions(node)
166 elif isinstance(node, ast.FunctionDef):
167 self.__findGlobalDefs(node)
168
169 checkerName = node.__class__.__name__.lower()
170 if checkerName in self.__checkers:
171 for checker in self.__checkers[checkerName]:
172 for error in checker(node, self.__parents):
173 yield error + (self.__checkers[checkerName],)
174
175 def __tagClassFunctions(self, classNode):
176 """
177 Private method to tag functions if they are methods, class methods or
178 static methods.
179
180 @param classNode AST tree node to tag
181 """
182 # try to find all 'old style decorators' like
183 # m = staticmethod(m)
184 lateDecoration = {}
185 for node in ast.iter_child_nodes(classNode):
186 if not (isinstance(node, ast.Assign) and
187 isinstance(node.value, ast.Call) and
188 isinstance(node.value.func, ast.Name)):
189 continue
190 funcName = node.value.func.id
191 if funcName in ("classmethod", "staticmethod"):
192 meth = (len(node.value.args) == 1 and node.value.args[0])
193 if isinstance(meth, ast.Name):
194 lateDecoration[meth.id] = funcName
195
196 # iterate over all functions and tag them
197 for node in ast.iter_child_nodes(classNode):
198 if not isinstance(node, ast.FunctionDef):
199 continue
200
201 node.function_type = 'method'
202 if node.name == "__new__":
203 node.function_type = "classmethod"
204
205 if node.name in lateDecoration:
206 node.function_type = lateDecoration[node.name]
207 elif node.decorator_list:
208 names = [d.id for d in node.decorator_list
209 if isinstance(d, ast.Name) and
210 d.id in ("classmethod", "staticmethod")]
211 if names:
212 node.function_type = names[0]
213
214 def __findGlobalDefs(self, functionNode):
215 """
216 Private method amend a node with global definitions information.
217
218 @param functionNode AST tree node to amend
219 """
220 globalNames = set()
221 nodesToCheck = collections.deque(ast.iter_child_nodes(functionNode))
222 while nodesToCheck:
223 node = nodesToCheck.pop()
224 if isinstance(node, ast.Global):
225 globalNames.update(node.names)
226
227 if not isinstance(node, (ast.FunctionDef, ast.ClassDef)):
228 nodesToCheck.extend(ast.iter_child_nodes(node))
229 functionNode.global_names = globalNames
230
231 def __getArgNames(self, node):
232 """
233 Private method to get the argument names of a function node.
234
235 @param node AST node to extract arguments names from
236 @return list of argument names (list of string)
237 """
238 posArgs = [arg.arg for arg in node.args.args]
239 kwOnly = [arg.arg for arg in node.args.kwonlyargs]
240 return posArgs + kwOnly
241
242 def __error(self, node, code):
243 """
244 Private method to build the error information.
245
246 @param node AST node to report an error for
247 @param code error code to report (string)
248 @return tuple giving line number, offset within line and error code
249 (integer, integer, string)
250 """
251 if isinstance(node, ast.Module):
252 lineno = 0
253 offset = 0
254 else:
255 lineno = node.lineno
256 offset = node.col_offset
257 if isinstance(node, ast.ClassDef):
258 lineno += len(node.decorator_list)
259 offset += 6
260 elif isinstance(node, ast.FunctionDef):
261 lineno += len(node.decorator_list)
262 offset += 4
263 return (lineno, offset, code)
264
265 def __isNameToBeAvoided(self, name):
266 """
267 Private method to check, if the given name should be avoided.
268
269 @param name name to be checked (string)
270 @return flag indicating to avoid it (boolen)
271 """
272 return name in ("l", "O", "I")
273
274 def __checkNameToBeAvoided(self, node, parents):
275 """
276 Private class to check the given node for a name to be avoided (N831).
277
278 @param node AST note to check
279 @param parents list of parent nodes
280 @return tuple giving line number, offset within line and error code
281 (integer, integer, string)
282 """
283 if isinstance(node, (ast.ClassDef, ast.FunctionDef)):
284 name = node.name
285 if self.__isNameToBeAvoided(name):
286 yield self.__error(node, "N831")
287 return
288
289 if isinstance(node, ast.FunctionDef):
290 argNames = self.__getArgNames(node)
291 for arg in argNames:
292 if self.__isNameToBeAvoided(arg):
293 yield self.__error(node, "N831")
294 return
295
296 if isinstance(node, ast.Assign):
297 for target in node.targets:
298 name = isinstance(target, ast.Name) and target.id
299 if not name:
300 return
301
302 if self.__isNameToBeAvoided(name):
303 yield self.__error(node, "N831")
304 return
305
306 def __checkClassName(self, node, parents):
307 """
308 Private class to check the given node for class name
309 conventions (N801).
310
311 Almost without exception, class names use the CapWords convention.
312 Classes for internal use have a leading underscore in addition.
313
314 @param node AST note to check
315 @param parents list of parent nodes
316 @return tuple giving line number, offset within line and error code
317 (integer, integer, string)
318 """
319 if not self.CamelcaseRegexp.match(node.name):
320 yield self.__error(node, "N801")
321
322 def __checkFuntionName(self, node, parents):
323 """
324 Private class to check the given node for function name
325 conventions (N802).
326
327 Function names should be lowercase, with words separated by underscores
328 as necessary to improve readability. Functions <b>not</b> being
329 methods '__' in front and back are not allowed. Mixed case is allowed
330 only in contexts where that's already the prevailing style
331 (e.g. threading.py), to retain backwards compatibility.
332
333 @param node AST note to check
334 @param parents list of parent nodes
335 @return tuple giving line number, offset within line and error code
336 (integer, integer, string)
337 """
338 functionType = getattr(node, "function_type", "function")
339 name = node.name
340 if (functionType == "function" and "__" in (name[:2], name[-2:])) or \
341 not self.LowercaseRegex.match(name):
342 yield self.__error(node, "N802")
343
344 def __checkFunctionArgumentNames(self, node, parents):
345 """
346 Private class to check the argument names of functions
347 (N803, N804, N805, N806).
348
349 The argument names of a function should be lowercase, with words
350 separated by underscores. A class method should have 'cls' as the
351 first argument. A method should have 'self' as the first argument.
352
353 @param node AST note to check
354 @param parents list of parent nodes
355 @return tuple giving line number, offset within line and error code
356 (integer, integer, string)
357 """
358 if node.args.kwarg is not None:
359 if not self.LowercaseRegex.match(node.args.kwarg):
360 yield self.__error(node, "N803")
361 return
362
363 if node.args.vararg is not None:
364 if not self.LowercaseRegex.match(node.args.vararg):
365 yield self.__error(node, "N803")
366 return
367
368 argNames = self.__getArgNames(node)
369 functionType = getattr(node, "function_type", "function")
370
371 if not argNames:
372 if functionType == "method":
373 yield self.__error(node, "N805")
374 elif functionType == "classmethod":
375 yield self.__error(node, "N804")
376 return
377
378 if functionType == "method":
379 if argNames[0] != "self":
380 yield self.__error(node, "N805")
381 elif functionType == "classmethod":
382 if argNames[0] != "cls":
383 yield self.__error(node, "N804")
384 elif functionType == "staticmethod":
385 if argNames[0] in ("cls", "self"):
386 yield self.__error(node, "N806")
387 for arg in argNames:
388 if not self.LowercaseRegex.match(arg):
389 yield self.__error(node, "N803")
390 return
391
392 def __checkVariablesInFunction(self, node, parents):
393 """
394 Private method to check local variables in functions (N821).
395
396 Local variables in functions should be lowercase.
397
398 @param node AST note to check
399 @param parents list of parent nodes
400 @return tuple giving line number, offset within line and error code
401 (integer, integer, string)
402 """
403 for parentFunc in reversed(parents):
404 if isinstance(parentFunc, ast.ClassDef):
405 return
406 if isinstance(parentFunc, ast.FunctionDef):
407 break
408 else:
409 return
410 for target in node.targets:
411 name = isinstance(target, ast.Name) and target.id
412 if not name or name in parentFunc.global_names:
413 return
414
415 if not self.LowercaseRegex.match(name) and name[:1] != '_':
416 yield self.__error(target, "N821")
417
418 def __checkModule(self, node, parents):
419 """
420 Private method to check module naming conventions (N807, N808).
421
422 Module and package names should be lowercase.
423
424 @param node AST note to check
425 @param parents list of parent nodes
426 @return tuple giving line number, offset within line and error code
427 (integer, integer, string)
428 """
429 if self.__filename:
430 moduleName = os.path.splitext(os.path.basename(self.__filename))[0]
431 if moduleName.lower() != moduleName:
432 yield self.__error(node, "N807")
433
434 if moduleName == "__init__":
435 # we got a package
436 packageName = \
437 os.path.split(os.path.dirname(self.__filename))[1]
438 if packageName.lower != packageName:
439 yield self.__error(node, "N808")
440
441 def __checkImportAs(self, node, parents):
442 """
443 Private method to check that imports don't change the
444 naming convention (N811, N812, N813, N814).
445
446 @param node AST note to check
447 @param parents list of parent nodes
448 @return tuple giving line number, offset within line and error code
449 (integer, integer, string)
450 """
451 for name in node.names:
452 if not name.asname:
453 continue
454
455 if self.UppercaseRegexp.match(name.name):
456 if not self.UppercaseRegexp.match(name.asname):
457 yield self.__error(node, "N811")
458 elif self.LowercaseRegex.match(name.name):
459 if not self.LowercaseRegex.match(name.asname):
460 yield self.__error(node, "N812")
461 elif self.LowercaseRegex.match(name.asname):
462 yield self.__error(node, "N813")
463 elif self.UppercaseRegexp.match(name.asname):
464 yield self.__error(node, "N814")

eric ide

mercurial