summaryrefslogtreecommitdiff
path: root/checkers/design_analysis.py
diff options
context:
space:
mode:
Diffstat (limited to 'checkers/design_analysis.py')
-rw-r--r--checkers/design_analysis.py360
1 files changed, 0 insertions, 360 deletions
diff --git a/checkers/design_analysis.py b/checkers/design_analysis.py
deleted file mode 100644
index cfd2d80..0000000
--- a/checkers/design_analysis.py
+++ /dev/null
@@ -1,360 +0,0 @@
-# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE).
-# http://www.logilab.fr/ -- mailto:contact@logilab.fr
-#
-# This program is free software; you can redistribute it and/or modify it under
-# the terms of the GNU General Public License as published by the Free Software
-# Foundation; either version 2 of the License, or (at your option) any later
-# version.
-#
-# This program is distributed in the hope that it will be useful, but WITHOUT
-# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along with
-# this program; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-"""check for signs of poor design"""
-
-from astroid import Function, If, InferenceError
-
-from pylint.interfaces import IAstroidChecker
-from pylint.checkers import BaseChecker
-from pylint.checkers.utils import check_messages
-
-import re
-
-# regexp for ignored argument name
-IGNORED_ARGUMENT_NAMES = re.compile('_.*')
-
-
-def class_is_abstract(klass):
- """return true if the given class node should be considered as an abstract
- class
- """
- for attr in klass.values():
- if isinstance(attr, Function):
- if attr.is_abstract(pass_is_abstract=False):
- return True
- return False
-
-
-MSGS = {
- 'R0901': ('Too many ancestors (%s/%s)',
- 'too-many-ancestors',
- 'Used when class has too many parent classes, try to reduce \
- this to get a simpler (and so easier to use) class.'),
- 'R0902': ('Too many instance attributes (%s/%s)',
- 'too-many-instance-attributes',
- 'Used when class has too many instance attributes, try to reduce \
- this to get a simpler (and so easier to use) class.'),
- 'R0903': ('Too few public methods (%s/%s)',
- 'too-few-public-methods',
- 'Used when class has too few public methods, so be sure it\'s \
- really worth it.'),
- 'R0904': ('Too many public methods (%s/%s)',
- 'too-many-public-methods',
- 'Used when class has too many public methods, try to reduce \
- this to get a simpler (and so easier to use) class.'),
-
- 'R0911': ('Too many return statements (%s/%s)',
- 'too-many-return-statements',
- 'Used when a function or method has too many return statement, \
- making it hard to follow.'),
- 'R0912': ('Too many branches (%s/%s)',
- 'too-many-branches',
- 'Used when a function or method has too many branches, \
- making it hard to follow.'),
- 'R0913': ('Too many arguments (%s/%s)',
- 'too-many-arguments',
- 'Used when a function or method takes too many arguments.'),
- 'R0914': ('Too many local variables (%s/%s)',
- 'too-many-locals',
- 'Used when a function or method has too many local variables.'),
- 'R0915': ('Too many statements (%s/%s)',
- 'too-many-statements',
- 'Used when a function or method has too many statements. You \
- should then split it in smaller functions / methods.'),
-
- 'R0921': ('Abstract class not referenced',
- 'abstract-class-not-used',
- 'Used when an abstract class is not used as ancestor anywhere.'),
- 'R0922': ('Abstract class is only referenced %s times',
- 'abstract-class-little-used',
- 'Used when an abstract class is used less than X times as \
- ancestor.'),
- 'R0923': ('Interface not implemented',
- 'interface-not-implemented',
- 'Used when an interface class is not implemented anywhere.'),
- }
-
-
-class MisdesignChecker(BaseChecker):
- """checks for sign of poor/misdesign:
- * number of methods, attributes, local variables...
- * size, complexity of functions, methods
- """
-
- __implements__ = (IAstroidChecker,)
-
- # configuration section name
- name = 'design'
- # messages
- msgs = MSGS
- priority = -2
- # configuration options
- options = (('max-args',
- {'default' : 5, 'type' : 'int', 'metavar' : '<int>',
- 'help': 'Maximum number of arguments for function / method'}
- ),
- ('ignored-argument-names',
- {'default' : IGNORED_ARGUMENT_NAMES,
- 'type' :'regexp', 'metavar' : '<regexp>',
- 'help' : 'Argument names that match this expression will be '
- 'ignored. Default to name with leading underscore'}
- ),
- ('max-locals',
- {'default' : 15, 'type' : 'int', 'metavar' : '<int>',
- 'help': 'Maximum number of locals for function / method body'}
- ),
- ('max-returns',
- {'default' : 6, 'type' : 'int', 'metavar' : '<int>',
- 'help': 'Maximum number of return / yield for function / '
- 'method body'}
- ),
- ('max-branches',
- {'default' : 12, 'type' : 'int', 'metavar' : '<int>',
- 'help': 'Maximum number of branch for function / method body'}
- ),
- ('max-statements',
- {'default' : 50, 'type' : 'int', 'metavar' : '<int>',
- 'help': 'Maximum number of statements in function / method '
- 'body'}
- ),
- ('max-parents',
- {'default' : 7,
- 'type' : 'int',
- 'metavar' : '<num>',
- 'help' : 'Maximum number of parents for a class (see R0901).'}
- ),
- ('max-attributes',
- {'default' : 7,
- 'type' : 'int',
- 'metavar' : '<num>',
- 'help' : 'Maximum number of attributes for a class \
-(see R0902).'}
- ),
- ('min-public-methods',
- {'default' : 2,
- 'type' : 'int',
- 'metavar' : '<num>',
- 'help' : 'Minimum number of public methods for a class \
-(see R0903).'}
- ),
- ('max-public-methods',
- {'default' : 20,
- 'type' : 'int',
- 'metavar' : '<num>',
- 'help' : 'Maximum number of public methods for a class \
-(see R0904).'}
- ),
- )
-
- def __init__(self, linter=None):
- BaseChecker.__init__(self, linter)
- self.stats = None
- self._returns = None
- self._branches = None
- self._used_abstracts = None
- self._used_ifaces = None
- self._abstracts = None
- self._ifaces = None
- self._stmts = 0
-
- def open(self):
- """initialize visit variables"""
- self.stats = self.linter.add_stats()
- self._returns = []
- self._branches = []
- self._used_abstracts = {}
- self._used_ifaces = {}
- self._abstracts = []
- self._ifaces = []
-
- # Check 'R0921', 'R0922', 'R0923'
- def close(self):
- """check that abstract/interface classes are used"""
- for abstract in self._abstracts:
- if not abstract in self._used_abstracts:
- self.add_message('R0921', node=abstract)
- elif self._used_abstracts[abstract] < 2:
- self.add_message('R0922', node=abstract,
- args=self._used_abstracts[abstract])
- for iface in self._ifaces:
- if not iface in self._used_ifaces:
- self.add_message('R0923', node=iface)
-
- @check_messages('R0901', 'R0902', 'R0903', 'R0904', 'R0921', 'R0922', 'R0923')
- def visit_class(self, node):
- """check size of inheritance hierarchy and number of instance attributes
- """
- self._inc_branch()
- # Is the total inheritance hierarchy is 7 or less?
- nb_parents = len(list(node.ancestors()))
- if nb_parents > self.config.max_parents:
- self.add_message('R0901', node=node,
- args=(nb_parents, self.config.max_parents))
- # Does the class contain less than 20 attributes for
- # non-GUI classes (40 for GUI)?
- # FIXME detect gui classes
- if len(node.instance_attrs) > self.config.max_attributes:
- self.add_message('R0902', node=node,
- args=(len(node.instance_attrs),
- self.config.max_attributes))
- # update abstract / interface classes structures
- if class_is_abstract(node):
- self._abstracts.append(node)
- elif node.type == 'interface' and node.name != 'Interface':
- self._ifaces.append(node)
- for parent in node.ancestors(False):
- if parent.name == 'Interface':
- continue
- self._used_ifaces[parent] = 1
- try:
- for iface in node.interfaces():
- self._used_ifaces[iface] = 1
- except InferenceError:
- # XXX log ?
- pass
- for parent in node.ancestors():
- try:
- self._used_abstracts[parent] += 1
- except KeyError:
- self._used_abstracts[parent] = 1
-
- @check_messages('R0901', 'R0902', 'R0903', 'R0904', 'R0921', 'R0922', 'R0923')
- def leave_class(self, node):
- """check number of public methods"""
- nb_public_methods = 0
- special_methods = set()
- for method in node.methods():
- if not method.name.startswith('_'):
- nb_public_methods += 1
- if method.name.startswith("__"):
- special_methods.add(method.name)
- # Does the class contain less than 20 public methods ?
- if nb_public_methods > self.config.max_public_methods:
- self.add_message('R0904', node=node,
- args=(nb_public_methods,
- self.config.max_public_methods))
- # stop here for exception, metaclass and interface classes
- if node.type != 'class':
- return
- # Does the class contain more than 5 public methods ?
- if nb_public_methods < self.config.min_public_methods:
- self.add_message('R0903', node=node,
- args=(nb_public_methods,
- self.config.min_public_methods))
-
- @check_messages('R0911', 'R0912', 'R0913', 'R0914', 'R0915')
- def visit_function(self, node):
- """check function name, docstring, arguments, redefinition,
- variable names, max locals
- """
- self._inc_branch()
- # init branch and returns counters
- self._returns.append(0)
- self._branches.append(0)
- # check number of arguments
- args = node.args.args
- if args is not None:
- ignored_args_num = len(
- [arg for arg in args
- if self.config.ignored_argument_names.match(arg.name)])
- argnum = len(args) - ignored_args_num
- if argnum > self.config.max_args:
- self.add_message('R0913', node=node,
- args=(len(args), self.config.max_args))
- else:
- ignored_args_num = 0
- # check number of local variables
- locnum = len(node.locals) - ignored_args_num
- if locnum > self.config.max_locals:
- self.add_message('R0914', node=node,
- args=(locnum, self.config.max_locals))
- # init statements counter
- self._stmts = 1
-
- @check_messages('R0911', 'R0912', 'R0913', 'R0914', 'R0915')
- def leave_function(self, node):
- """most of the work is done here on close:
- checks for max returns, branch, return in __init__
- """
- returns = self._returns.pop()
- if returns > self.config.max_returns:
- self.add_message('R0911', node=node,
- args=(returns, self.config.max_returns))
- branches = self._branches.pop()
- if branches > self.config.max_branches:
- self.add_message('R0912', node=node,
- args=(branches, self.config.max_branches))
- # check number of statements
- if self._stmts > self.config.max_statements:
- self.add_message('R0915', node=node,
- args=(self._stmts, self.config.max_statements))
-
- def visit_return(self, _):
- """count number of returns"""
- if not self._returns:
- return # return outside function, reported by the base checker
- self._returns[-1] += 1
-
- def visit_default(self, node):
- """default visit method -> increments the statements counter if
- necessary
- """
- if node.is_statement:
- self._stmts += 1
-
- def visit_tryexcept(self, node):
- """increments the branches counter"""
- branches = len(node.handlers)
- if node.orelse:
- branches += 1
- self._inc_branch(branches)
- self._stmts += branches
-
- def visit_tryfinally(self, _):
- """increments the branches counter"""
- self._inc_branch(2)
- self._stmts += 2
-
- def visit_if(self, node):
- """increments the branches counter"""
- branches = 1
- # don't double count If nodes coming from some 'elif'
- if node.orelse and (len(node.orelse) > 1 or
- not isinstance(node.orelse[0], If)):
- branches += 1
- self._inc_branch(branches)
- self._stmts += branches
-
- def visit_while(self, node):
- """increments the branches counter"""
- branches = 1
- if node.orelse:
- branches += 1
- self._inc_branch(branches)
-
- visit_for = visit_while
-
- def _inc_branch(self, branchesnum=1):
- """increments the branches counter"""
- branches = self._branches
- for i in xrange(len(branches)):
- branches[i] += branchesnum
-
- # FIXME: make a nice report...
-
-def register(linter):
- """required method to auto register this checker """
- linter.register_checker(MisdesignChecker(linter))