diff options
-rw-r--r-- | ChangeLog | 5 | ||||
-rw-r--r-- | README | 119 | ||||
-rw-r--r-- | checkers/base.py | 10 | ||||
-rw-r--r-- | checkers/classes.py | 53 | ||||
-rw-r--r-- | test/input/func_base_stmt_without_effect.py | 3 | ||||
-rw-r--r-- | test/input/func_block_disable_msg.py | 48 | ||||
-rw-r--r-- | test/input/func_class_access_protected_members.py | 30 | ||||
-rw-r--r-- | test/input/func_format.py | 2 | ||||
-rw-r--r-- | test/input/func_w0233.py | 2 | ||||
-rw-r--r-- | test/messages/func_base_stmt_without_effect.txt | 1 | ||||
-rw-r--r-- | test/messages/func_class_access_protected_members.txt | 3 | ||||
-rw-r--r-- | utils.py | 7 |
12 files changed, 145 insertions, 138 deletions
@@ -1,6 +1,11 @@ ChangeLog for PyLint ==================== + -- + * new W0212 message for access to protected member from client code + * new W0105 message extracted from W0104 (statement seems to have no effect) + with string + 2006-04-19 -- 0.11.0 * fix crash caused by the exceptions checker in some case * fix some E1101 false positive with abstract method or classes defining @@ -4,129 +4,26 @@ README for PyLint Dependencies ------------ Pylint requires the logilab-astng (version >= 0.14), logilab-common -(version >= 0.13) and the optik (only for python < 2.3) packages. +(version >= 0.13) and the optik (only for python < 2.3) packages. +Pylint should be compatible with any python >= 2.2. * http://www.logilab.org/projects/astng * http://www.logilab.org/projects/common * http://optik.sourceforge.net/ -Distributions -------------- -The source tarball is available at ftp://ftp.logilab.fr/pub/pylint. - -You may apt-get a debian package by adding :: - - deb ftp://ftp.logilab.org/pub/debian unstable/ - -to your /etc/apt/sources.list files. Pylint is also available in the standard Debian distribution - -Contributed RPM packages for pylint and logilab-common are available at -ftp://ftp.nest.pld-linux.org/test . - -Pylint is also available in Gentoo, Fedora 4, Ubuntu, FreeBSD, Darwin. - - Install ------- From the source distribution, extract the tarball and run :: python setup.py install -For debian and rpm packages, use your usual tools according to your Linux -distribution. - -Note for Windows users: -On Windows, once you have installed pylint, the command line usage is -pylint.bat [options] module_or_package - -But this will only work if pylint.bat is either in the current -directory, or on your system path. (setup.py install install python.bat -to the Scripts subdirectory of your Python installation -- e.g. -C:\Python24\Scripts.) You can do any of the following to solve this: - -1. change to the appropriate directory before running pylint.bat - -2. add the Scripts directory to your path statement in your autoexec.bat - file (this file is found in the root directory of your boot-drive) - -3. create a 'redirect' batch file in a directory actually on your - systems path - -To effect (2), simply append the appropriate directory name to the PATH= -statement in autoexec.bat. Be sure to use the Windows directory -separator of ';' between entries. Then, once you have rebooted (this is -necessary so that the new path statement will take effect when -autoexec.bat is run), you will be able to invoke PyLint with -pylint.bat on the command line. - -(3) is the best solution. Once done, you can call pylint at the command -line without the .bat, just as do non-Windows users by typing: :: - - pylint [options] module_or_package - -To effect option (3), simply create a plain text file pylint.bat with -the single line: :: - - C:\PythonDirectory\Scripts\pylint.bat - -(where PythonDirectory is replaced by the actual Python installation -directory on your system -- e.g. C:\Python24\Scripts\pylint.bat). - - -IDE integration ---------------- - -Pylint is integrated in the following editors/IDEs: - - * emacs (of course) - - * eric3 - - * eclipse (using the pydev_ plugin, see also http://msdl.cs.mcgill.ca/MSDL/people/denis/meetings/pythonDev) - -To use pylint from within vim, see http://www.gonzo.kiev.ua/projects/pylint.vim - -_pydev: http://pydev.sourceforge.net - -Some projects using Pylint --------------------------- - - * The CheeseCake kwalitee reporting tool uses pylint to analyze the source code. - -The following projects use pylint to help develop better code: - - * http://browsershots.org - - * OSAF Chandler (http://www.osafoundation.org/) - - * CPS (http://www.nuxeo.org) - - * Xen (http://www.xensource.com/) - - * pyxmpp (http://pyxmpp.jabberstudio.org/) - - * eXe (http://exelearning.org/) - - * PrimaGIS (http://www.primagis.org) - - * python-cdd (http://projetos.ossystems.com.br/python-cdd/) - - * CDSWare (http://cdsware.cern.ch/) - - * ASE (http://dcwww.camp.dtu.dk/campos/ASE/intro.html) - - * RunJob (http://projects.fnal.gov/runjob/) - - * Slugathon (http://slugathon.python-hosting.com/) - - * mercurial - - * Topographica (http://topographica.org/Home/index.html) (at least they intend to do so) +You'll have to install dependancies in a similar way. For debian and +rpm packages, use your usual tools according to your Linux distribution. - * ERP5 (http://www.erp5.org/) +More information about installation and available distribution format +may be found in the user manual in the *doc* subdirectory. - * many more... Documentation ------------- @@ -145,12 +42,10 @@ http://www.logilab.org/mailinglists/python_projects/mailinglist_register_form Archives are available at http://lists.logilab.org/pipermail/python-projects/ -If you prefer speaking french instead of english, you can use the -generic forum-fr@logilab.org mailing list. Contributors ------------ -* Sylvain Thenault: main author / maintainer +* Sylvain Thénault: main author / maintainer * Alexandre Fayolle: TkInter gui, documentation, debian support * Brian van den Broek: windows installation documentation * Amaury Forgeot d'Arc: patch to check names imported from a module diff --git a/checkers/base.py b/checkers/base.py index 7d852ea..8b103af 100644 --- a/checkers/base.py +++ b/checkers/base.py @@ -113,6 +113,11 @@ MSGS = { 'W0104': ('Statement seems to have no effect', 'Used when a statement doesn\'t have (or at least seems to) \ any effect.'), + 'W0105': ('String statement has no effect', + 'Used when a string is used as a statement (which of course \ + has no effect). This is a particular case of W0104 with its \ + own message so you can easily disable it if you\'re using \ + those strings as documentation, instead of comments.'), 'W0122': ('Use of the exec statement', 'Used when you use the "exec" statement, to discourage its \ @@ -304,7 +309,10 @@ functions, methods def visit_discard(self, node): """check for statement without effect""" - if not isinstance(node.expr, astng.CallFunc): + if isinstance(node.expr, astng.Const) and \ + isinstance(node.expr.value, basestring): + self.add_message('W0105', node=node) + elif not isinstance(node.expr, astng.CallFunc): self.add_message('W0104', node=node) def visit_function(self, node): diff --git a/checkers/classes.py b/checkers/classes.py index 7c0e551..3a9e468 100644 --- a/checkers/classes.py +++ b/checkers/classes.py @@ -26,25 +26,11 @@ from pylint.checkers import BaseChecker from pylint.checkers.utils import overrides_a_method MSGS = { -## 'F0201': ('Unable to check method %r of interface %s', -## 'Used when PyLint has been unable to fetch a - ##method declared in \ -## an interface (either in the class or in the - ##interface) and so to\ -## check its implementation.'), 'F0202': ('Unable to check methods signature (%s / %s)', 'Used when PyLint has been unable to check methods signature \ compatibility for an unexpected raison. Please report this kind \ if you don\'t make sense of it.'), -## 'F0203': ('Unable to resolve %s', -## 'Used when PyLint has been unable to resolve a name.'), -## 'F0204': ('Name %s has not been resolved to a class as expected', -## 'Used when PyLint try to resolve an ancestor class name but \ -## gets something else than a Class node.'), -## 'E0201': ('Access to undefined member %r', -## 'Used when an instance member not defined in the instance, its\ -## class or its ancestors is accessed.'), 'E0202': ('An attribute inherited from %s hide this method', 'Used when a class defines a method which is hiden by an \ instance attribute from an ancestor class.'), @@ -55,12 +41,18 @@ MSGS = { 'Used when an instance attribute is defined outside the __init__\ method.'), + 'W0212': ('Access to a protected member %s of a client class', # E0214 + 'Used when a protected member (i.e. class member with a name \ + beginning with an underscore) is access outside the class or a \ + descendant of the class where it\'s defined.'), + 'E0211': ('Method has no argument', 'Used when a method which should have the bound instance as \ first argument has no argument defined.'), 'E0213': ('Method should have "self" as first argument', 'Used when a method has an attribute different the "self" as\ - first argument.'), + first argument. This is considered as an error since this is\ + a soooo common convention that you should\'nt break it!'), 'C0202': ('Class method should have "cls" as first argument', # E0212 'Used when a class method has an attribute different than "cls"\ as first argument, to easily differentiate them from regular \ @@ -68,6 +60,7 @@ MSGS = { 'C0203': ('Metaclass method should have "mcs" as first argument', # E0214 'Used when a metaclass method has an attribute different the \ "mcs" as first argument.'), + 'W0211': ('Static method with %r as first argument', 'Used when a static method has "self" or "cls" as first argument.' ), @@ -257,13 +250,35 @@ instance attributes.'} self.add_message('R0201', node=node) def visit_getattr(self, node): - """check if the name handle an access to a class member - if so, register it + """check if the getattr is an access to a class member + if so, register it. Also check for access to protected + class member from outside its class (but ignore __special__ + methods) """ + attrname = node.attrname if self._first_attrs and isinstance(node.expr, astng.Name) and \ node.expr.name == self._first_attrs[-1]: - self._accessed[-1].setdefault(node.attrname, []).append(node) - + self._accessed[-1].setdefault(attrname, []).append(node) + elif attrname[0] == '_' and not (attrname.startswith('__') and + attrname.endswith('__')): + # XXX move this in a reusable function + klass = node.frame() + while klass is not None and not isinstance(klass, astng.Class): + if klass.parent is None: + klass = None + else: + klass = klass.parent.frame() + # XXX infer to be more safe and less dirty ?? + # in classes, check we are not getting a parent method + # through the class object or through super + if klass is None or not ( + node.expr.as_string() in klass.basenames + or (isinstance(node.expr, astng.CallFunc) + and isinstance(node.expr.node, astng.Name) + and node.expr.node.name == 'super')): + self.add_message('W0212', node=node, args=attrname) + + def visit_name(self, node): """check if the name handle an access to a class member if so, register it diff --git a/test/input/func_base_stmt_without_effect.py b/test/input/func_base_stmt_without_effect.py index f282ff7..f9ff608 100644 --- a/test/input/func_base_stmt_without_effect.py +++ b/test/input/func_base_stmt_without_effect.py @@ -13,3 +13,6 @@ __revision__ <= 1 __revision__.lower() # ok [i for i in __revision__] # ko + + +"""inline doc string should use a separated message""" diff --git a/test/input/func_block_disable_msg.py b/test/input/func_block_disable_msg.py index f82fbd5..7de7acd 100644 --- a/test/input/func_block_disable_msg.py +++ b/test/input/func_block_disable_msg.py @@ -85,3 +85,51 @@ class Foo(object): # no error print self.bla print self.blop + + +class ClassLevelMessage(object): + """should'nt display to much attributes/not enough methods messages + """ + # pylint: disable-msg=R0902,R0903 + + def __init__(self): + self.attr1 = 1 + self.attr2 = 1 + self.attr3 = 1 + self.attr4 = 1 + self.attr5 = 1 + self.attr6 = 1 + self.attr7 = 1 + self.attr8 = 1 + self.attr9 = 1 + self.attr0 = 1 + + def too_complex_but_thats_ok(self, attr1, attr2): + """THIS Method has too much branchs but i don't care + """ + # pylint: disable-msg=R0912 + try: + attr3 = attr1+attr2 + except ValueError: + attr3 = None + except: + print 'duh', self + if attr1: + for i in attr1: + if attr2: + print i + else: + print 'duh' + elif attr2: + for i in attr2: + if attr2: + print i + else: + print 'duh' + else: + for i in range(15): + if attr3: + print i + else: + print 'doh' + diff --git a/test/input/func_class_access_protected_members.py b/test/input/func_class_access_protected_members.py new file mode 100644 index 0000000..f588953 --- /dev/null +++ b/test/input/func_class_access_protected_members.py @@ -0,0 +1,30 @@ +# pylint: disable-msg=R0903 +"""test external access to protected class members""" + +__revision__ = '' + +class MyClass: + """class docstring""" + _cls_protected = 5 + + def __init__(self, other): + """init""" + self._protected = 1 + self.public = other + + + def test(self): + """test""" + self._protected += self._cls_protected + print self.public._haha + + def clsmeth(cls): + """this is ok""" + print cls._cls_protected + clsmeth = classmethod(clsmeth) + +INST = MyClass() +print INST.public +print INST._protected +print INST._cls_protected + diff --git a/test/input/func_format.py b/test/input/func_format.py index da66d63..98a9f9a 100644 --- a/test/input/func_format.py +++ b/test/input/func_format.py @@ -1,4 +1,4 @@ -# pylint:disable-msg=C0103,W0104 +# pylint:disable-msg=C0103,W0104,W0105 """Check format """ __revision__ = '' diff --git a/test/input/func_w0233.py b/test/input/func_w0233.py index 763ccf0..1d32b95 100644 --- a/test/input/func_w0233.py +++ b/test/input/func_w0233.py @@ -1,4 +1,4 @@ -# pylint: disable-msg=R0903 +# pylint: disable-msg=R0903,W0212 """test for call to __init__ from a non ancestor class """ diff --git a/test/messages/func_base_stmt_without_effect.txt b/test/messages/func_base_stmt_without_effect.txt index b50fd82..e1cce0c 100644 --- a/test/messages/func_base_stmt_without_effect.txt +++ b/test/messages/func_base_stmt_without_effect.txt @@ -1,3 +1,4 @@ W: 9: Statement seems to have no effect W: 11: Statement seems to have no effect W: 15: Statement seems to have no effect +W: 18: String statement has no effect diff --git a/test/messages/func_class_access_protected_members.txt b/test/messages/func_class_access_protected_members.txt new file mode 100644 index 0000000..8c7bb02 --- /dev/null +++ b/test/messages/func_class_access_protected_members.txt @@ -0,0 +1,3 @@ +W: 19:MyClass.test: Access to a protected member _haha of a client class +W: 28: Access to a protected member _protected of a client class +W: 29: Access to a protected member _cls_protected of a client class @@ -136,13 +136,12 @@ class MessagesHandlerMixIn: msg_id = self.check_message_id(msg_id) if scope == 'module': assert line > 0 - if msg_id != 'I0011': - self.add_message('I0011', line=line, args=msg_id) - #self._module_msgs_state[msg_id] = False try: self._module_msgs_state[msg_id][line] = False except KeyError: self._module_msgs_state[msg_id] = {line: False} + if msg_id != 'I0011': + self.add_message('I0011', line=line, args=msg_id) else: msgs = self._msgs_state @@ -157,11 +156,11 @@ class MessagesHandlerMixIn: msg_id = self.check_message_id(msg_id) if scope == 'module': assert line > 0 - self.add_message('I0012', line=line, args=msg_id) try: self._module_msgs_state[msg_id][line] = True except KeyError: self._module_msgs_state[msg_id] = {line: True} + self.add_message('I0012', line=line, args=msg_id) else: msgs = self._msgs_state msgs[msg_id] = True |