diff options
author | root <devnull@localhost> | 2006-04-26 10:48:09 +0000 |
---|---|---|
committer | root <devnull@localhost> | 2006-04-26 10:48:09 +0000 |
commit | eea76f1da01a33dec2afc42119e001e4350aaea2 (patch) | |
tree | 3bb03a16daa8c780bf60c622dc288eb01cfca145 | |
download | pylint-eea76f1da01a33dec2afc42119e001e4350aaea2.tar.gz |
forget the past.
forget the past.
323 files changed, 14574 insertions, 0 deletions
diff --git a/.hgignore b/.hgignore new file mode 100644 index 0000000..2849616 --- /dev/null +++ b/.hgignore @@ -0,0 +1,4 @@ +(^|/)\.svn($|/) +(^|/)\.hg($|/) +(^|/)\.hgtags($|/) +^log$ diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..43305a9 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,518 @@ +ChangeLog for PyLint +==================== + +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 + __getattr__ + + * dirty fix to avoid "_socketobject" has not "connect" member. The actual + problem is that astng isn't able to understand the code used to create + socket.socket object with exec + + * added an option in the similarity checker to ignore docstrings, enabled + by default + + * included patch Benjamin Niemann to allow block level enabling/disabling + of messages + + +2006-03-06 -- 0.10.0 + * WARNING, this release include some configuration changes (see below), + so you may have to check and update your own configuration file(s) if + you use one + + * this release require the 0.15 version of astng or superior (it will save + you a lot of pylint crashes...) + + * W0705 has been reclassified to E0701, and is now detecting more + inheriting problem, and a false positive when empty except clause is + following an Exception catch has been fixed (close #10422) + + * E0212 and E0214 (metaclass/class method should have mcs/cls as first + argument have been reclassified to C0202 and C0203 since this not as + well established as "self" for instance method (E0213) + + * W0224 has been reclassified into F0220 (failed to resolve interfaces + implemented by a class) + + * a new typecheck checker, introducing the following checks: + - E1101, access to unexistant member (implements #10430), remove + the need of E0201 and so some options has been moved from the + classes checker to this one + - E1102, calling a non callable object + - E1111 and W1111 when an assigment is done on a function call but the + infered function returns None (implements #10431) + + * change in the base checker: + - checks module level and instance attribute names (new const-rgx + and attr-rgx configuration option) (implements #10209 and + #10440) + - list comprehension and generator expression variables have their + own regular expression (the inlinevar-rgx option) (implements + #9146) + - the C0101 check with its min-name-lentgh option has + been removed (this can be specified in the regxp after all...) + - W0103 and W0121 are now handled by the variables checker + (W0103 is now W0603 and W0604 has been splitted into different messages) + - W0131 and W0132 messages have been reclassified to C0111 and + C0112 respectivly + - new W0104 message on statement without effect + + * regexp support for dummy-variables (dummy-variables-rgx option + replace dummy-variables) (implements #10027) + + * better global statement handling, see W0602, W0603, W0604 messages + (implements #10344 and #10236) + + * --debug-mode option, disabling all checkers without error message + and filtering others to only display error + + * fixed some R0201 (method could be a function) false positive + + +2006-01-10 -- 0.9.0 + * a lot of updates to follow astng 0.14 API changes, so install + logilab-astng 0.14 or greater before using this version of pylint + + * checker number 10 ! newstyle will search for problems regarding old + style / new style classes usage problems (rely on astng 0.14 new + style detection feature) + + * new 'load-plugins' options to load additional pylint plugins (usable + from the command line or from a configuration file) (implements + #10031) + + * check if a "pylintrc" file exists in the current working directory + before using the one specified in the PYLINTRC environment variable + or the default ~/.pylintrc or /etc/pylintrc + + * fixed W0706 (Identifier used to raise an exception is assigned...) + false positive and reraising a catched exception instance + + * fixed E0611 (No name get in module blabla) false positive when accessing + to a class'__dict__ + + * fixed some E0203 ("access to member before its definition") false + positive + + * fixed E0214 ("metaclass method frist argument should be mcs) false + positive with staticmethod used on a metaclass + + * fixed packaging which was missing the test/regrtest_data directory + + * W0212 (method could be a function) has been reclassified in the + REFACTOR category as R0201, and is no more considerer when a method + overrides an abstract method from an ancestor class + + * include module name in W0401 (wildcard import), as suggested by + Amaury + + * when using the '--parseable', path are written relative to the + current working directory if in a sub-directory of it (#9789) + + * 'pylint --version' shows logilab-astng and logilab-common versions + + * fixed pylint.el to handle space in file names + + * misc lint style fixes + + + +2005-11-07 -- 0.8.1 + * fix "deprecated module" false positive when the code imports a + module whose name starts with a deprecated module's name (close + #10061) + + * fix "module has no name __dict__" false positive (close #10039) + + * fix "access to undefined variable __path__" false positive (close + #10065) + + * fix "explicit return in __init__" false positive when return is + actually in an inner function (close #10075) + + +2005-10-21 -- 0.8.0 + * check names imported from a module exists in the module (E0611), + patch contributed by Amaury Forgeot d'Arc + + * print a warning (W0212) for methods that could be a function + (implements #9100) + + * new --defining-attr-methods option on classes checker + + * new --acquired-members option on the classes checker, used when + --zope=yes to avoid false positive on acquired attributes (listed + using this new option) (close #8616) + + * generate one E0602 for each use of an undefined variable + (previously, only one for the first use but not for the following) + (implements #1000) + + * make profile option saveable + + * fix Windows .bat file, patch contributed by Amaury Forgeot d'Arc + + * fix one more false positive for E0601 (access before definition) + with for loop such as "for i in range(10): print i" (test + func_noerror_defined_and_used_on_same_line) + + * fix false positive for E0201 (undefined member) when accessing to + __name__ on a class object + + * fix astng checkers traversal order + + * fix bug in format checker when parsing a file from a platform + using different new line characters (close #9239) + + * fix encoding detection regexp + + * fix --rcfile handling (support for --rcfile=file, close #9590) + + + +2005-05-27 -- 0.7.0 + * WARNING: pylint is no longer a logilab subpackage. Users may have to + manually remove the old logilab/pylint directory. + + * introduce a new --additional-builtins option to handle user defined + builtins + + * --reports option has now -r as short alias, and -i for --include-ids + + * fix a bug in the variables checker which may causing some false + positives when variables are defined and used within the same + statement (test func_noerror_defined_and_used_on_same_line) + + * this time, real fix of the "disable-msg in the config file" problem, + test added to unittest_lint + + * fix bug with --list-messages and python -OO + + * fix possible false positive for W0201 + + + +2005-04-14 -- 0.6.4 + * allow to parse files without extension when a path is given on the + command line (test noext) + + * don't fail if we are unable to read an inline option (e.g. inside a + module), just produce an information message (test func_i0010) + + * new message E0103 for break or continue outside loop (close #8883, + test func_continue_not_in_loop) + + * fix bug in the variables checker, causing non detection of some + actual name error (close #8884, test + func_nameerror_on_string_substitution) + + * fix bug in the classes checker which was making pylint crash if + "object" is assigned in a class inheriting from it (test + func_noerror_object_as_class_attribute) + + * fix problem with the similar checker when related options are + defined in a configuration file + + * new --generate-man option to generate pylint's man page (require the + latest logilab.common (>= 0.9.3) + + * packaged (generated...) man page + + + +2005-02-24 -- 0.6.3 + * fix scope problem which may cause false positive and true negative + on E0602 + + * fix problem with some options such as disable-msg causing error when + they are coming from the configuration file + + + +2005-02-16 -- 0.6.2 + * fix false positive on E0201 ("access to undefined member") with + metaclasses + + * fix false positive on E0203 ("access to member before its + definition") when attributes are defined in a parent class + + * fix false positive on W0706 ("identifier used to raise an exception + assigned to...") + + * fix interpretation of "\t" as value for the indent-string + configuration variable + + * fix --rcfile so that --rcfile=pylintrc (only --rcfile pylintrc was + working in earlier release) + + * new raw checker example in the examples/ directory + + + +2005-02-04 -- 0.6.1 + * new --rcfile option to specify the configuration file without the + PYLINTRC environment variable + + * added an example module for a custom pylint checker (see the + example/ directory) + + * some fixes to handle fixes in common 0.9.1 (should however still working + with common 0.9.0, even if upgrade is recommended) + + + +2005-01-20 -- 0.6.0 + * refix pylint emacs mode + + * no more traceback when just typing "pylint" + + * fix a bug which may cause crashes on resolving parent classes + + * fix problems with the format checker: don't chock on files + containing multiple CR, avoid C0322, C0323, C0324 false positives + with triple quoted string with quote inside + + * correctly detect access to member defined latter in __init__ method + + * now depends on common 0.8.1 to fix problem with interface resolution + (close #8606) + + * new --list-msgs option describing available checkers and their + messages + + * added windows specific documentation to the README file, contributed + by Brian van den Broek + + * updated doc/features.txt (actually this file is now generated using + the --list-msgs option), more entries into the FAQ + + * improved tests coverage + + + +2004-10-19 -- 0.5.0 + * avoid to import analyzed modules ! + + * new Refactor and Convention message categories. Some Warnings have been + remaped into those new categories + + * added "similar", a tool to find copied and pasted lines of code, + both using a specific command line tool and integrated as a + pylint's checker + + * imports checker may report import dependancies as a dot graph + + * new checker regrouping most Refactor detection (with some new metrics) + + * more command line options storable in the configuration file + + * fix bug with total / undocumented number of methods + + + +2004-07-08 -- 0.4.2 + * fix pylint emacs mode + + * fix classes checkers to handler twisted interfaces + + + +2004-05-14 -- 0.4.1 + * fix the setup.py script to allow bdist_winst (well, the generated + installer has not been tested...) with the necessary + logilab/__init__.py file + + * fix file naming convention as suggested by Andreas Amoroso + + * fix stupid crash bug with bad method names + + + +2004-05-10 -- 0.4.0 + * fix file path with --parsable + + * --parsable option has been renamed to --parseable + + * added patch from Andreas Amoroso to output message to files instead + of standard output + + * added Run to the list of correct variable names + + * fix variable names regexp and checking of local classes names + + * some basic handling of metaclasses + + * no-docstring-rgx apply now on classes too + + * new option to specify a different regexp for methods than for + functions + + * do not display the evaluation report when no statements has been + analysed + + * fixed crash with a class nested in a method + + * fixed format checker to deals with triple quoted string and + lines with code and comment mixed + + * use logilab.common.ureports to layout reports + + + +2004-02-17 -- 0.3.3 + * added a parsable text output, used when the --parsable option is + provided + + * added an emacs mode using this output, availabe in the distrib's + elisp directory + + * fixed some typos in messages + + * change include-ids options to yn, and allow it to be in the + configuration file + + * do not chock on corrupted stats files + + * fixed bug in the format checker which may stop pylint execution + + * provide scripts for unix and windows to wrap the minimal pylint tk + gui + + + +2003-12-23 -- 0.3.2 + * html-escape messages in the HTML reporter (bug reported by Juergen + Hermann) + + * added "TODO" to the list of default note tags + + * added "rexec" to the list of default deprecated modules + + * fixed typos in some messages + + + +2003-12-05 -- 0.3.1 + * bug fix in format and classes checkers + + * remove print statement from imports checkers + + * provide a simple tk gui, essentially usefull for windows users + + + +2003-11-20 -- 0.3.0 + * new exceptions checker, checking for string exception and empty + except clauses. + + * imports checker checks for reimport of modules + + * classes checker checks for calls to ancestor's __init__ and abstract + method not overriden. It doesn't complain anymore for unused import in + __init__ files, and provides a new option ignore-interface-methods, + usefull when you're using zope Interface implementation in your project + + * base checker checks for black listed builtins call (controled by the + bad-functions option) and for use of * and ** + + * format checker checks for use of <> and "l" as long int marker + + * major internal API changes + + * use the rewrite of astng, based on compiler.ast + + * added unique id for messages, as suggested by Wolfgang Grafen + + * added unique id for reports + + * can take multiple modules or files as argument + + * new options command line options : --disable-msg, --enable-msg, + --help-msg, --include-ids, --reports, --disable-report, --cache-size + + * --version shows the version of the python interpreter + + * removed some options which are now replaced by [en|dis]able-msg, or + disable-report + + * read disable-msg and enable-msg options in source files (should be + in comments on the top of the file, in the form + "# pylint: disable-msg=W0402" + + * new message for modules importing themselves instead of the "cyclic + import" message + + * fix bug with relative and cyclic imports + + * fix bug in imports checker (cycle was not always detected) + + * still fixes in format checker : don't check comment and docstring, + check first line after an indent + + * black and white list now apply to all identifiers, not only + variables, so changed the configuration option from + (good|bad)-variable-names to (good|bad)-names + + * added string, rexec and Bastion to the default list of deprecated + modules + + * do not print redefinition warning for function/class/method defined + in mutually exclusive branchs + + + +2003-10-10 -- 0.2.1 + * added some documentation, fixed some typos + + * set environment variable PYLINT_IMPORT to 1 during pylint execution. + + * check that variables "imported" using the global statement exist + + * indentation problems are now warning instead of errors + + * fix checkers.initialize to try to load all files with a known python + extension (patch from wrobell) + + * fix a bunch of messages + + * fix sample configuration file + + * fix the bad-construction option + + * fix encoding checker + + * fix format checker + + + +2003-09-12 -- 0.2.0 + * new source encoding / FIXME checker (pep 263) + + * new --zope option which trigger Zope import. Usefull to check Zope + products code. + + * new --comment option which enable the evaluation note comment + (disabled by default). + + * a ton of bug fixes + + * easy functionnal test infrastructure + + + +2003-06-18 -- 0.1.2 + * bug fix release + + * remove dependency to pyreverse + + + +2003-06-01 -- 0.1.1 + * much more functionnalities ! + + + +2003-05-19 -- 0.1 + * initial release @@ -0,0 +1,3 @@ +python-logilab-common (>= 0.13.0) +python-logilab-astng (>= 0.15.0) +python-tk diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..feed5c6 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,18 @@ +include COPYING +include DEPENDS +include ChangeLog +include TODO +include bin/symilar +include bin/pylint +include bin/*.bat +include bin/pylint-gui +include examples/pylintrc* +include examples/*.py +include elisp/*.el +include elisp/startup +include man/pylint.1 +recursive-include doc *.txt *.html +recursive-include test/input *.py similar* noext +recursive-include test/messages *.txt *.txt2 +recursive-include test/regrtest_data *.py +include test/fulltest.sh @@ -0,0 +1,164 @@ +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. + +* 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) + + * ERP5 (http://www.erp5.org/) + + * many more... + +Documentation +------------- +Look in the doc/ subdirectory. + + +Comments, support, bug reports +------------------------------ +Use the python-projects@logilab.org mailing list. Since we do not have +publicly available bug tracker yet, bug reports should be emailed +there too. + +You can subscribe to this mailing list at +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 +* 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 + exists in the module +* Benjamin Niemann: patch to allow block level enabling/disabling of messages +* Wolfgang Grafen, Axel Muller, Fabio Zadrozny, Pierre Rouleau, + Maarten ter Huurne, Mirko Friedenhagen (among others): + bug reports, feedback, feature requests... +* All the Logilab's team: daily use, bug reports, feature requests +* Other people have contributed by their feedback, if I've forgotten + you, send me a note ! @@ -0,0 +1,46 @@ +PyLint's TODO list +------------------ + + +- un rapport avec les métriques vues dans TDD +- métrique manquantes +- tests + +* faire tourner sur wxpython... + +* test external dependancies + + +* avoir les options liés à un message id dans son aide + +* avoir les messages id géré par un checker dans --help + +* avoir la valeur courante des options dans --help + +* doc développeur + +* supporter des wildcards dans disable-msg ? + +* voir notes gvr sur main + +* récupérer les phrases d'évaluation dans un fichier texte + +* i18n avec gettext + +* commenter les regexp de format.py + +* gestion nested_scopes (modes py2.1, 2.2... ?) + +* checkers : + - vérifier arguments __new__ + - compléter format checker + voir http://www.python.org/peps/pep-0008.html + - vérifier classes sans __init__ mais avec plusieurs ancêtres ayant + un __init__ + - opérateur % avec des formats ne correspondant pas aux arguments + - mauvais nombre d'arguments passés à une méthode ou fonction + - utilisation constante dans condition + - gestion del statements + - vérification utilisation __getattribute__, __slots__ dans new + style class seulement + - vérification assignements quand __slots__ diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..abbb3e9 --- /dev/null +++ b/__init__.py @@ -0,0 +1,18 @@ +# 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., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +""" Copyright (c) 2002-2003 LOGILAB S.A. (Paris, FRANCE). +http://www.logilab.fr/ -- mailto:contact@logilab.fr +""" + +__revision__ = '$Id: __init__.py,v 1.5 2003-06-11 13:22:50 syt Exp $' + diff --git a/__pkginfo__.py b/__pkginfo__.py new file mode 100644 index 0000000..169e1bc --- /dev/null +++ b/__pkginfo__.py @@ -0,0 +1,74 @@ +# pylint: disable-msg=W0622,C0103 +# Copyright (c) 2003-2006 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., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +"""pylint packaging information""" + +__revision__ = '$Id: __pkginfo__.py,v 1.50 2006-04-19 09:17:40 syt Exp $' + + +modname = 'pylint' + +numversion = (0, 11, 0) +version = '.'.join([str(num) for num in numversion]) + +license = 'GPL' +copyright = '''Copyright (c) 2003-2006 Sylvain Thenault (thenault@gmail.com). +Copyright (c) 2003-2006 LOGILAB S.A. (Paris, FRANCE). +http://www.logilab.fr/ -- mailto:contact@logilab.fr''' + +short_desc = "python code static checker" +long_desc = """\ + Pylint is a Python source code analyzer which looks for programming + errors, helps enforcing a coding standard and sniffs for some code + smells (as defined in Martin Fowler's Refactoring book) + . + Pylint can be seen as another PyChecker since nearly all tests you + can do with PyChecker can also be done with Pylint. However, Pylint + offers some more features, like checking length of lines of code, + checking if variable names are well-formed according to your coding + standard, or checking if declared interfaces are truly implemented, + and much more. + . + Additionally, it is possible to write plugins to add your own checks.""" + +author = "Sylvain Thenault" +author_email = "sylvain.thenault@logilab.fr" + +web = "http://www.logilab.org/projects/%s" % modname +ftp = "ftp://ftp.logilab.org/pub/%s" % modname +mailinglist = "mailto://python-projects@logilab.org" + +from os.path import join +scripts = [join('bin', filename) + for filename in ('pylint', 'pylint-gui', "symilar")] + +include_dirs = [join('test', 'input'), join('test', 'messages'), + join('test', 'regrtest_data')] + +pyversions = ["2.2", "2.3", "2.4"] + +debian_uploader = 'Alexandre Fayolle <afayolle@debian.org>' + +classifiers = ['Development Status :: 4 - Beta', + 'Environment :: Console', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: GNU General Public License (GPL)', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Topic :: Software Development :: Debuggers', + 'Topic :: Software Development :: Quality Assurance', + 'Topic :: Software Development :: Testing', + ] diff --git a/announce.txt b/announce.txt new file mode 100644 index 0000000..4f49abe --- /dev/null +++ b/announce.txt @@ -0,0 +1,42 @@ +What's new ? +------------ +%CHANGELOG% + + +What is pylint ? +---------------- + +Pylint is a python tool that checks if a module satisfy a coding +standard. Pylint can be seen as another pychecker since nearly all +tests you can do with pychecker can also be done with Pylint. But +Pylint offers some more features, like checking line-code's length, +checking if variable names are well-formed according to your coding +standard, or checking if declared interfaces are truly implemented, +and much more (see http://www.logilab.org/projects/pylint/ for the +complete check list). The big advantage with Pylint is that it is +highly configurable, customizable, and you can easily write a small +plugin to add a personal feature. + +The usage it quite simple : + +$ pylint mypackage.mymodule + + +This command will output all the errors and warnings related to the +tested code (here : mypackage.mymodule), will dump a little summary at +the end, and will give a mark to the tested code. + +Pylint is free software distributed under the GNU Public Licence. + + +Home page +--------- +%WEB% + +Download +-------- +%FTP% + +Mailing list +------------ +%MAILINGLIST% diff --git a/bin/pylint b/bin/pylint new file mode 100755 index 0000000..e20e031 --- /dev/null +++ b/bin/pylint @@ -0,0 +1,4 @@ +#!/usr/bin/env python +import sys +from pylint import lint +lint.Run(sys.argv[1:]) diff --git a/bin/pylint-gui b/bin/pylint-gui new file mode 100755 index 0000000..847ef3a --- /dev/null +++ b/bin/pylint-gui @@ -0,0 +1,4 @@ +#!/usr/bin/env python +import sys +from pylint import gui +gui.Run(sys.argv[1:]) diff --git a/bin/pylint-gui.bat b/bin/pylint-gui.bat new file mode 100644 index 0000000..68d552e --- /dev/null +++ b/bin/pylint-gui.bat @@ -0,0 +1,20 @@ +@echo off
+rem = """-*-Python-*- script
+@echo off
+rem -------------------- DOS section --------------------
+rem You could set PYTHONPATH or TK environment variables here
+python -x %~f0 %*
+goto exit
+
+"""
+# -------------------- Python section --------------------
+import sys
+from pylint import gui
+gui.Run(sys.argv[1:])
+
+
+DosExitLabel = """
+:exit
+rem """
+
+
diff --git a/bin/pylint.bat b/bin/pylint.bat new file mode 100644 index 0000000..772735a --- /dev/null +++ b/bin/pylint.bat @@ -0,0 +1,19 @@ +@echo off
+rem = """-*-Python-*- script
+rem -------------------- DOS section --------------------
+rem You could set PYTHONPATH or TK environment variables here
+python -x %~f0 %*
+goto exit
+
+"""
+# -------------------- Python section --------------------
+import sys
+from pylint import lint
+lint.Run(sys.argv[1:])
+
+
+DosExitLabel = """
+:exit
+rem """
+
+
diff --git a/bin/symilar b/bin/symilar new file mode 100755 index 0000000..7ca139f --- /dev/null +++ b/bin/symilar @@ -0,0 +1,3 @@ +#!/usr/bin/env python +from pylint.checkers import similar +similar.run() diff --git a/bin/symilar.bat b/bin/symilar.bat new file mode 100644 index 0000000..5c9bd0e --- /dev/null +++ b/bin/symilar.bat @@ -0,0 +1,20 @@ +@echo off
+rem = """-*-Python-*- script
+@echo off
+rem -------------------- DOS section --------------------
+rem You could set PYTHONPATH or TK environment variables here
+python -x %~f0 %*
+goto exit
+
+"""
+# -------------------- Python section --------------------
+import sys
+from pylint.checkers import similar
+similar.run()
+
+
+DosExitLabel = """
+:exit
+rem """
+
+
diff --git a/checkers/__init__.py b/checkers/__init__.py new file mode 100644 index 0000000..fdf015a --- /dev/null +++ b/checkers/__init__.py @@ -0,0 +1,166 @@ +# Copyright (c) 2003-2005 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., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +"""utilities methods and classes for checkers + +Base id of standard checkers (used in msg and report ids): +01: base +02: classes +03: format +04: import +05: misc +06: variables +07: exceptions +08: similar +09: design_analysis +10: newstyle +""" + +__revision__ = "$Id: __init__.py,v 1.21 2005-11-21 23:08:11 syt Exp $" + +import tokenize +from os import listdir +from os.path import dirname, join, isdir, splitext + +from logilab.astng.utils import ASTWalker +from logilab.common.configuration import OptionsProviderMixIn + +from pylint.reporters import diff_string, EmptyReport + +def table_lines_from_stats(stats, old_stats, columns): + """get values listed in <columns> from <stats> and <old_stats>, + and return a formated list of values, designed to be given to a + ureport.Table object + """ + lines = [] + for m_type in columns: + new = stats[m_type] + format = str + if isinstance(new, float): + format = lambda num: '%.3f' % num + old = old_stats.get(m_type) + if old is not None: + diff_str = diff_string(old, new) + old = format(old) + else: + old, diff_str = 'NC', 'NC' + lines += (m_type.replace('_', ' '), format(new), old, diff_str) + return lines + + +class BaseChecker(OptionsProviderMixIn, ASTWalker): + """base class for checkers""" + + options = () + priority = -9 + may_be_disabled = True + name = None + + def __init__(self, linter=None): + """checker instances should have the linter as argument + + linter is an object implementing ILinter + """ + ASTWalker.__init__(self, self) + self.name = self.name.lower() + if self.may_be_disabled: + opt_name = 'enable-' + self.name + self.options = ( + (opt_name, + {'type' : 'yn', 'default' : 1, 'metavar': '<y_or_n>', + 'help' : "Enable / disable this checker"}) + ,) + self.options + OptionsProviderMixIn.__init__(self) + self.linter = linter + + def add_message(self, msg_id, line=None, node=None, args=None): + """add a message of a given type""" + self.linter.add_message(msg_id, line, node, args) + + def is_enabled(self): + """return true if the checker is enabled""" + opt = 'enable_' + self.name + return getattr(self.config, opt, 1) + + def enable(self, enable): + """enable / disable this checker if true / false is given + + it false values has no effect if the checker can't be disabled + """ + if self.may_be_disabled: + setattr(self.config, 'enable_' + self.name, enable) + + def package_dir(self): + """return the base directory for the analysed package""" + return dirname(self.linter.base_file) + + + # dummy methods implementing the IChecker interface + + def open(self): + """called before visiting project (i.e set of modules)""" + + def close(self): + """called after visiting project (i.e set of modules)""" + +class BaseRawChecker(BaseChecker): + """base class for raw checkers""" + + def process_module(self, stream): + """process a module + + the module's content is accessible via the stream object + + stream must implements the readline method + """ + self.process_tokens(tokenize.generate_tokens(stream.readline)) + + def process_tokens(self, tokens): + """should be overiden by subclasses""" + raise NotImplementedError() + + +PY_EXTS = ('.py', '.pyc', '.pyo', '.pyw', '.so', '.dll') + +def initialize(linter): + """initialize linter with checkers in this package """ + package_load(linter, __path__[0]) + +def package_load(linter, directory): + """load all module and package in the given directory, looking for a + 'register' function in each one, used to register pylint checkers + """ + globs = globals() + imported = {} + for filename in listdir(directory): + basename, extension = splitext(filename) + if not imported.has_key(basename) and ( + (extension in PY_EXTS and basename != '__init__') or ( + not extension and not basename == 'CVS' and + isdir(join(directory, basename)))): + try: + module = __import__(basename, globs, globs, None) + except ValueError: + # empty module name (usually emacs auto-save files) + continue + except ImportError: + import sys + print >> sys.stderr, "Problem importing module: %s" % filename + else: + if hasattr(module, 'register'): + module.register(linter) + imported[basename] = 1 + +__all__ = ('CheckerHandler', 'BaseChecker', 'initialize', 'package_load') diff --git a/checkers/base.py b/checkers/base.py new file mode 100644 index 0000000..7d852ea --- /dev/null +++ b/checkers/base.py @@ -0,0 +1,486 @@ +# 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., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +""" Copyright (c) 2003-2006 LOGILAB S.A. (Paris, FRANCE). + http://www.logilab.fr/ -- mailto:contact@logilab.fr + + basic checker for Python code +""" + +__revision__ = "$Id: base.py,v 1.65 2006-01-26 00:26:23 syt Exp $" + +from logilab import astng +from logilab.common.ureports import Table + +from pylint.interfaces import IASTNGChecker +from pylint.reporters import diff_string +from pylint.checkers import BaseChecker +from pylint.checkers.utils import are_exclusive + +import re + +# regex for class/function/variable/constant nane +CLASS_NAME_RGX = re.compile('[A-Z_][a-zA-Z0-9]+$') +MOD_NAME_RGX = re.compile('(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$') +CONST_NAME_RGX = re.compile('(([A-Z_][A-Z1-9_]*)|(__.*__))$') +COMP_VAR_RGX = re.compile('[A-Za-z_][A-Za-z0-9_]*$') +DEFAULT_NAME_RGX = re.compile('[a-z_][a-z0-9_]{2,30}$') +# do not require a doc string on system methods +NO_REQUIRED_DOC_RGX = re.compile('__.*__') + +del re + +def in_loop(node): + parent = node.parent + while parent is not None: + if isinstance(parent, (astng.For, astng.ListComp, astng.GenExpr)): + return True + parent = parent.parent + return False + +def in_nested_list(nested_list, obj): + """return true if the object is an element of <nested_list> or of a nested + list + """ + for elmt in nested_list: + if isinstance(elmt, (list, tuple)): + if in_nested_list(elmt, obj): + return True + elif elmt == obj: + return True + return False + +def report_by_type_stats(sect, stats, old_stats): + """make a report of + + * percentage of different types documented + * percentage of different types with a bad name + """ + # percentage of different types documented and/or with a bad name + nice_stats = {} + for node_type in ('module', 'class', 'method', 'function'): + nice_stats[node_type] = {} + total = stats[node_type] + if total == 0: + doc_percent = 0 + badname_percent = 0 + else: + documented = total - stats['undocumented_'+node_type] + doc_percent = float((documented)*100) / total + badname_percent = (float((stats['badname_'+node_type])*100) + / total) + nice_stats[node_type]['percent_documented'] = doc_percent + nice_stats[node_type]['percent_badname'] = badname_percent + lines = ('type', 'number', 'old number', 'difference', + '%documented', '%badname') + for node_type in ('module', 'class', 'method', 'function'): + new = stats[node_type] + old = old_stats.get(node_type, None) + if old is not None: + diff_str = diff_string(old, new) + else: + old, diff_str = 'NC', 'NC' + lines += (node_type, str(new), str(old), diff_str, + '%.2f' % nice_stats[node_type]['percent_documented'], + '%.2f' % nice_stats[node_type]['percent_badname']) + sect.append(Table(children=lines, cols=6, rheaders=1)) + + +MSGS = { + 'E0101': ('Explicit return in __init__', + 'Used when the special class method __ini__ has an explicit \ + return value.'), + 'E0102': ('%s already defined line %s', + 'Used when a function / class / method is redefined.'), + 'E0103': ('%r not properly in loop', + 'Used when break or continue keywords are used outside a loop.'), + + 'W0101': ('Unreachable code', + 'Used when there is some code behind a "return" or "raise" \ + statement, which will never be accessed.'), + 'W0102': ('Dangerous default value %s as argument', + 'Used when a mutable value as list or dictionary is detected in \ + a default value for an argument.'), + 'W0104': ('Statement seems to have no effect', + 'Used when a statement doesn\'t have (or at least seems to) \ + any effect.'), + + 'W0122': ('Use of the exec statement', + 'Used when you use the "exec" statement, to discourage its \ + usage. That doesn\'t mean you can not use it !'), + + 'W0141': ('Used builtin function %r', + 'Used when a black listed builtin function is used (see the \ + bad-function option). Usual black listed functions are the ones \ + like map, or filter , where Python offers now some cleaner \ + alternative like list comprehension.'), + 'W0142': ('Used * or ** magic', + 'Used when a function or method is called using `*args` or \ + `**kwargs` to dispatch arguments. This doesn\'t improve readility\ + and should be used with care.'), + + 'C0102': ('Black listed name "%s"', + 'Used when the name is listed in the black list (unauthorized \ + names).'), + 'C0103': ('Invalid name "%s" (should match %s)', + 'Used when the name doesn\'t match the regular expression \ + associated to its type (constant, variable, class...).'), + + 'C0111': ('Missing docstring', # W0131 + 'Used when a module, function, class or method has no docstring.\ + Some special methods like __init__ doesn\'t necessary require a \ + docstring.'), + 'C0112': ('Empty docstring', # W0132 + 'Used when a module, function, class or method has an empty \ + docstring (it would be to easy ;).'), + + 'C0121': ('Missing required attribute "%s"', # W0103 + 'Used when an attribute required for modules is missing.'), + + } + +class BasicChecker(BaseChecker): + """checks for : + * doc strings + * modules / classes / functions / methods / arguments / variables name + * number of arguments, local variables, branchs, returns and statements in +functions, methods + * required module attributes + * dangerous default values as arguments + * redefinition of function / method / class + * uses of the global statement + """ + + __implements__ = IASTNGChecker + + name = 'basic' + msgs = MSGS + priority = -1 + options = (('required-attributes', + {'default' : ('__revision__',), 'type' : 'csv', + 'metavar' : '<attributes>', + 'help' : 'Required attributes for module, separated by a ' + 'comma'} + ), + ('no-docstring-rgx', + {'default' : NO_REQUIRED_DOC_RGX, + 'type' : 'regexp', 'metavar' : '<regexp>', + 'help' : 'Regular expression which should only match ' + 'functions or classes name which do not require a ' + 'docstring'} + ), +## ('min-name-length', +## {'default' : 3, 'type' : 'int', 'metavar' : '<int>', +## 'help': 'Minimal length for module / class / function / ' +## 'method / argument / variable names'} +## ), + ('module-rgx', + {'default' : MOD_NAME_RGX, + 'type' :'regexp', 'metavar' : '<regexp>', + 'help' : 'Regular expression which should only match correct ' + 'module names'} + ), + ('const-rgx', + {'default' : CONST_NAME_RGX, + 'type' :'regexp', 'metavar' : '<regexp>', + 'help' : 'Regular expression which should only match correct ' + 'module level names'} + ), + ('class-rgx', + {'default' : CLASS_NAME_RGX, + 'type' :'regexp', 'metavar' : '<regexp>', + 'help' : 'Regular expression which should only match correct ' + 'class names'} + ), + ('function-rgx', + {'default' : DEFAULT_NAME_RGX, + 'type' :'regexp', 'metavar' : '<regexp>', + 'help' : 'Regular expression which should only match correct ' + 'function names'} + ), + ('method-rgx', + {'default' : DEFAULT_NAME_RGX, + 'type' :'regexp', 'metavar' : '<regexp>', + 'help' : 'Regular expression which should only match correct ' + 'method names'} + ), + ('attr-rgx', + {'default' : DEFAULT_NAME_RGX, + 'type' :'regexp', 'metavar' : '<regexp>', + 'help' : 'Regular expression which should only match correct ' + 'instance attribute names'} + ), + ('argument-rgx', + {'default' : DEFAULT_NAME_RGX, + 'type' :'regexp', 'metavar' : '<regexp>', + 'help' : 'Regular expression which should only match correct ' + 'argument names'}), + ('variable-rgx', + {'default' : DEFAULT_NAME_RGX, + 'type' :'regexp', 'metavar' : '<regexp>', + 'help' : 'Regular expression which should only match correct ' + 'variable names'} + ), + ('inlinevar-rgx', + {'default' : COMP_VAR_RGX, + 'type' :'regexp', 'metavar' : '<regexp>', + 'help' : 'Regular expression which should only match correct ' + 'list comprehension / generator expression variable \ + names'} + ), + ('good-names', + {'default' : ('i', 'j', 'k', 'ex', 'Run', '_'), + 'type' :'csv', 'metavar' : '<names>', + 'help' : 'Good variable names which should always be accepted,' + ' separated by a comma'} + ), + ('bad-names', + {'default' : ('foo', 'bar', 'baz', 'toto', 'tutu', 'tata'), + 'type' :'csv', 'metavar' : '<names>', + 'help' : 'Bad variable names which should always be refused, ' + 'separated by a comma'} + ), + + ('bad-functions', + {'default' : ('map', 'filter', 'apply', 'input'), + 'type' :'csv', 'metavar' : '<builtin function names>', + 'help' : 'List of builtins function names that should not be ' + 'used, separated by a comma'} + ), + ) + reports = ( ('R0101', 'Statistics by type', report_by_type_stats), ) + + def __init__(self, linter): + BaseChecker.__init__(self, linter) + self.stats = None + self._returns = None + + def open(self): + """initialize visit variables and statistics + """ + self._returns = [] + self.stats = self.linter.add_stats(module=0, function=0, + method=0, class_=0, + badname_module=0, + badname_class=0, badname_function=0, + badname_method=0, badname_attr=0, + badname_const=0, + badname_variable=0, + badname_inlinevar=0, + badname_argument=0, + undocumented_module=0, + undocumented_function=0, + undocumented_method=0, + undocumented_class=0) + + def visit_module(self, node): + """check module name, docstring and required arguments + """ + self.stats['module'] += 1 + self._check_name('module', node.name.split('.')[-1], node) + self._check_docstring('module', node) + self._check_required_attributes(node, self.config.required_attributes) + + def visit_class(self, node): + """check module name, docstring and redefinition + increment branch counter + """ + self.stats['class'] += 1 + self._check_name('class', node.name, node) + if self.config.no_docstring_rgx.match(node.name) is None: + self._check_docstring('class', node) + self._check_redefinition('class', node) + for attr, anodes in node.instance_attrs.items(): + self._check_name('attr', attr, anodes[0]) + + def visit_discard(self, node): + """check for statement without effect""" + if not isinstance(node.expr, astng.CallFunc): + self.add_message('W0104', node=node) + + def visit_function(self, node): + """check function name, docstring, arguments, redefinition, + variable names, max locals + """ + is_method = node.is_method() + self._returns.append(0) + f_type = is_method and 'method' or 'function' + self.stats[f_type] += 1 + # function name + self._check_name(f_type, node.name, node) + # docstring + if self.config.no_docstring_rgx.match(node.name) is None: + self._check_docstring(f_type, node) + # check default arguments'value + self._check_defaults(node) + # check arguments name + args = node.argnames + if args is not None: + self._recursive_check_names(args, node) + # check for redefinition + self._check_redefinition(is_method and 'method' or 'function', node) + + def leave_function(self, node): + """most of the work is done here on close: + checks for max returns, branch, return in __init__ + """ + nb_returns = self._returns.pop() + if node.is_method() and node.name == '__init__' and nb_returns: + self.add_message('E0101', node=node) + + def visit_assname(self, node): + """check module level assigned names""" + frame = node.frame() + ass_type = node.ass_type() + if isinstance(ass_type, (astng.ListCompFor, astng.GenExprFor)): + self._check_name('inlinevar', node.name, node) + elif isinstance(frame, astng.Module): + if isinstance(ass_type, astng.Assign) and not in_loop(ass_type): + self._check_name('const', node.name, node) + elif isinstance(frame, astng.Function): + # global introduced variable aren't in the function locals + if node.name in frame: + self._check_name('variable', node.name, node) + + def visit_return(self, node): + """check is the node has a right sibling (if so, that's some unreachable + code) + """ + self._returns[-1] += 1 + self._check_unreachable(node) + + def visit_yield(self, _): + """check is the node has a right sibling (if so, that's some unreachable + code) + """ + self._returns[-1] += 1 + + def visit_continue(self, node): + """check is the node has a right sibling (if so, that's some unreachable + code) + """ + self._check_unreachable(node) + self._check_in_loop(node, 'continue') + + def visit_break(self, node): + """check is the node has a right sibling (if so, that's some unreachable + code) + """ + self._check_unreachable(node) + self._check_in_loop(node, 'break') + + def visit_raise(self, node): + """check is the node has a right sibling (if so, that's some unreachable + code) + """ + self._check_unreachable(node) + + def visit_exec(self, node): + """just pring a warning on exec statements""" + self.add_message('W0122', node=node) + + def visit_callfunc(self, node): + """visit a CallFunc node -> check if this is not a blacklisted builtin + call and check for * or ** use + """ + if isinstance(node.node, astng.Name): + name = node.node.name + # ignore the name if it's not a builtin (ie not defined in the + # locals nor globals scope) + if not (node.frame().has_key(name) or + node.root().has_key(name)): + if name in self.config.bad_functions: + self.add_message('W0141', node=node, args=name) + if node.star_args or node.dstar_args: + self.add_message('W0142', node=node.node) + + + def _check_unreachable(self, node): + """check unreachable code""" + unreach_stmt = node.next_sibling() + if unreach_stmt is not None: + self.add_message('W0101', node=unreach_stmt) + + def _check_in_loop(self, node, node_name): + """check that a node is inside a for or while loop""" + _node = node.parent + while _node: + if isinstance(_node, (astng.For, astng.While)): + break + _node = _node.parent + else: + self.add_message('E0103', node=node, args=node_name) + + def _check_redefinition(self, redef_type, node): + """check for redefinition of a function / method / class name""" + defined_self = node.parent.frame()[node.name] + if defined_self is not node and not are_exclusive(node, defined_self): + self.add_message('E0102', node=node, + args=(redef_type, defined_self.lineno)) + + def _check_docstring(self, node_type, node): + """check the node has a non empty docstring""" + docstring = node.doc + if docstring is None: + self.stats['undocumented_'+node_type] += 1 + self.add_message('C0111', node=node) + elif not docstring.strip(): + self.stats['undocumented_'+node_type] += 1 + self.add_message('C0112', node=node) + + def _recursive_check_names(self, args, node): + """check names in a possibly recursive list <arg>""" + for arg in args: + if type(arg) is type(''): + self._check_name('argument', arg, node) + else: + self._recursive_check_names(arg, node) + + def _check_name(self, node_type, name, node): + """check for a name using the type's regexp""" + if name in self.config.good_names: + return + if name in self.config.bad_names: + self.stats['badname_' + node_type] += 1 + self.add_message('C0102', node=node, args=name) + return + regexp = getattr(self.config, node_type + '_rgx') + if regexp.match(name) is None: + self.add_message('C0103', node=node, args=(name, regexp.pattern)) + self.stats['badname_' + node_type] += 1 + + + def _check_defaults(self, node): + """check for dangerous default values as arguments""" + for default in node.defaults: + try: + value = default.infer().next() + except astng.InferenceError: + continue + if isinstance(value, (astng.Dict, astng.List)): + if value is default: + msg = default.as_string() + else: + msg = '%s (%s)' % (default.as_string(), value.as_string()) + self.add_message('W0102', node=node, args=(msg,)) + + def _check_required_attributes(self, node, attributes): + """check for required attributes""" + for attr in attributes: + if not node.has_key(attr): + self.add_message('C0121', node=node, args=attr) + + +def register(linter): + """required method to auto register this checker""" + linter.register_checker(BasicChecker(linter)) + diff --git a/checkers/classes.py b/checkers/classes.py new file mode 100644 index 0000000..7c0e551 --- /dev/null +++ b/checkers/classes.py @@ -0,0 +1,481 @@ +# Copyright (c) 2003-2006 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., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +"""classes checker for Python code +""" +from __future__ import generators + +__revision__ = "$Id: classes.py,v 1.77 2006-03-05 14:39:37 syt Exp $" + +from logilab import astng + +from pylint.interfaces import IASTNGChecker +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.'), + 'E0203': ('Access to member %r before its definition line %s', + 'Used when an instance member is accessed before it\'s actually\ + assigned.'), + 'W0201': ('Attribute %r defined outside __init__', + 'Used when an instance attribute is defined outside the __init__\ + method.'), + + '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.'), + '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 \ + instance methods.'), + '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.' + ), + 'R0201': ('Method could be a function', + 'Used when a method doesn\'t use its bound instance, and so could\ + be written as a function.' + ), + + 'E0221': ('Interface resolved to %s is not a class', + 'Used when a class claims to implement an interface which is not \ + a class.'), + 'E0222': ('Missing method %r from %s interface' , + 'Used when a method declared in an interface is missing from a \ + class implementing this interface'), + 'W0221': ('Arguments number differs from %s method', + 'Used when a method has a different number of arguments than in \ + the implemented interface or in an overriden method.'), + 'W0222': ('Signature differs from %s method', + 'Used when a method signature is different than in the \ + implemented interface or in an overriden method.'), + 'W0223': ('Method %r is abstract in class %r but is not overriden', + 'Used when an abstract method (ie raise NotImplementedError) is \ + not overriden in concrete class.' + ), + 'F0220': ('failed to resolve interfaces implemented by %s (%s)', # W0224 + 'Used when a PyLint as failed to find interfaces implemented by \ + a class'), + + + 'W0231': ('__init__ method from base class %r is not called', + 'Used when an ancestor class method has an __init__ method \ + which is not called by a derived class.'), + 'W0232': ('Class has no __init__ method', + 'Used when a class has no __init__ method, neither its parent \ + classes.'), + 'W0233': ('__init__ method from a non direct base class %r is called', + 'Used when an __init__ method is called on a class which is not \ + in the direct ancestors for the analysed class.'), + + } + + +class ClassChecker(BaseChecker): + """checks for : + * methods without self as first argument + * overriden methods signature + * access only to existant members via self + * attributes not defined in the __init__ method + * supported interfaces implementation + * unreachable code + """ + + __implements__ = (IASTNGChecker,) + + # configuration section name + name = 'classes' + # messages + msgs = MSGS + priority = -2 + # configuration options + options = (('ignore-iface-methods', + {'default' : (#zope interface + 'isImplementedBy', 'deferred', 'extends', 'names', + 'namesAndDescriptions', 'queryDescriptionFor', 'getBases', + 'getDescriptionFor', 'getDoc', 'getName', 'getTaggedValue', + 'getTaggedValueTags', 'isEqualOrExtendedBy', 'setTaggedValue', + 'isImplementedByInstancesOf', + # twisted + 'adaptWith', + # logilab.common interface + 'is_implemented_by'), + 'type' : 'csv', + 'metavar' : '<method names>', + 'help' : 'List of interface methods to ignore, \ +separated by a comma. This is used for instance to not check methods defines \ +in Zope\'s Interface base class.'} + ), + + ('defining-attr-methods', + {'default' : ('__init__', '__new__', 'setUp'), + 'type' : 'csv', + 'metavar' : '<method names>', + 'help' : 'List of method names used to declare (i.e. assign) \ +instance attributes.'} + ), + + ) + + def __init__(self, linter=None): + BaseChecker.__init__(self, linter) + self._accessed = [] + self._first_attrs = [] + self._meth_could_be_func = None + + def visit_class(self, node): + """init visit variable _accessed and check interfaces + """ + self._accessed.append({}) + self._check_bases_classes(node) + self._check_interfaces(node) + # if not an interface, exception, metaclass + if node.type == 'class': + try: + node.local_attr('__init__') + except astng.NotFoundError: + self.add_message('W0232', args=node, node=node) + + def leave_class(self, cnode): + """close a class node: + check that instance attributes are defined in __init__ and check + access to existant members + """ + # checks attributes are defined in an allowed method such as __init__ + defining_methods = self.config.defining_attr_methods + for attr, nodes in cnode.instance_attrs.items(): + node = nodes[0] # XXX + frame = node.frame() + if frame.name not in defining_methods: + # check attribute is defined in a parent's __init__ + for parent in cnode.instance_attr_ancestors(attr): + frame = parent.instance_attrs[attr][0].frame() # XXX + if frame.name in defining_methods: + # we're done :) + break + else: + # check attribute is defined as a class attribute + try: + cnode.local_attr(attr) + except astng.NotFoundError: + self.add_message('W0201', args=attr, node=node) + # check access to existant members on non metaclass classes + accessed = self._accessed.pop() + if cnode.type != 'metaclass': + self._check_accessed_members(cnode, accessed) + + def visit_function(self, node): + """check method arguments, overriding""" + # ignore actual functions + if not node.is_method(): + return + self._meth_could_be_func = True + # check first argument is self if this is actually a method + klass = node.parent.frame() + self._check_first_arg_for_type(node, klass.type == 'metaclass') + if node.name == '__init__': + self._check_init(node) + return + # check signature if the method overrload an herited method + for overriden in klass.local_attr_ancestors(node.name): + # get astng for the searched method + try: + meth_node = overriden[node.name] + except KeyError: + # we have found the method but it's not in the local + # dictionnary. + # This may happen with astng build from living objects + continue + if not isinstance(meth_node, astng.Function): + continue + self._check_signature(node, meth_node, 'overriden') + break + # check if the method overload an attribute + try: + overriden = klass.instance_attr(node.name)[0] # XXX + while not isinstance(overriden, astng.Class): + overriden = overriden.parent.frame() + self.add_message('E0202', args=overriden.name, node=node) + except astng.NotFoundError: + pass + + def leave_function(self, node): + """on method node, check if this method couldn't be a function + + ignore class, static and abstract methods, initializer, + methods overriden from a parent class and any + kind of method defined in an interface for this warning + """ + if node.is_method(): + if node.argnames is not None: + self._first_attrs.pop() + class_node = node.parent.frame() + if (self._meth_could_be_func and node.type == 'method' + and node.name != '__init__' + and not (node.is_abstract() or + overrides_a_method(class_node, node.name)) + and class_node.type != 'interface'): + 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 + """ + 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) + + def visit_name(self, node): + """check if the name handle an access to a class member + if so, register it + """ + if self._first_attrs and (node.name == self._first_attrs[-1] or + not self._first_attrs[-1]): + self._meth_could_be_func = False + + def _check_accessed_members(self, node, accessed): + """check that accessed members are defined""" + # XXX refactor, probably much simpler now that E0201 is in type checker + for attr, nodes in accessed.items(): +## # is it a builtin attribute ? +## if attr in ('__dict__', '__class__', '__doc__'): +## # FIXME: old class object doesn't have __class__ +## continue + # is it a class attribute ? + try: + node.local_attr(attr) + # yes, stop here + continue + except astng.NotFoundError: + pass + # is it an instance attribute of a parent class ? + try: + node.instance_attr_ancestors(attr).next() + # yes, stop here + continue + except StopIteration: + pass + # is it an instance attribute ? + try: + def_nodes = node.instance_attr(attr) # XXX + instance_attribute = True + except astng.NotFoundError: + instance_attribute = False + else: + if len(def_nodes) == 1: + def_node = def_nodes[0] + # check that if the node is accessed in the same method as + # it's defined, it's accessed after the initial assigment + frame = def_node.frame() + lno = def_node.source_line() + for _node in nodes: + if _node.frame() is frame and _node.lineno < lno: + self.add_message('E0203', node=_node, + args=(attr, lno)) + + def _check_first_arg_for_type(self, node, metaclass=0): + """check the name of first argument, expect: + + * 'self' for a regular method + * 'cls' for a class method + * 'mcs' for a metaclass + * not one of the above for a static method + """ + # don't care about functions with unknown argument (builtins) + if node.argnames is None: + return + self._first_attrs.append(node.argnames and node.argnames[0]) + # static method + if node.type == 'staticmethod': + if node.argnames and node.argnames[0] in ('self', 'cls', 'mcs'): + self.add_message('W0211', args=node.argnames[0], node=node) + self._first_attrs[-1] = None + # class / regular method with no args + elif not node.argnames: + self.add_message('E0211', node=node) + # metaclass method + elif metaclass: + if self._first_attrs[-1] != 'mcs': + self.add_message('C0203', node=node) + # class method + elif node.type == 'classmethod': + if self._first_attrs[-1] != 'cls': + self.add_message('C0202', node=node) + # regular method without self as argument + elif self._first_attrs[-1] != 'self': + self.add_message('E0213', node=node) + + def _check_bases_classes(self, node): + """check that the given class node implements abstract methods from + base classes + """ + for method in node.methods(): + owner = method.parent.frame() + if owner is node: + continue + # owner is not this class, it must be a parent class + # check that the ancestor's method is not abstract + if method.is_abstract(pass_is_abstract=False): + self.add_message('W0223', node=node, + args=(method.name, owner.name)) + + def _check_interfaces(self, node): + """check that the given class node really implements declared + interfaces + """ + e0221_hack = [False] + def iface_handler(obj): + """filter interface objects, it should be classes""" + if not isinstance(obj, astng.Class): + e0221_hack[0] = True + self.add_message('E0221', node=node, + args=(obj.as_string(),)) + return False + return True + ignore_iface_methods = self.config.ignore_iface_methods + try: + for iface in node.interfaces(handler_func=iface_handler): + for imethod in iface.methods(): + name = imethod.name + if name.startswith('_') or name in ignore_iface_methods: + # don't check method begining with an underscore, + # usually belonging to the interface implementation + continue + # get class method astng + try: + method = node_method(node, name) + except astng.NotFoundError: + self.add_message('E0222', args=(name, iface.name), + node=node) + continue + # ignore inherited methods + if method.parent.frame() is not node: + continue + # check signature + self._check_signature(method, imethod, + '%s interface' % iface.name) + except astng.InferenceError: + if e0221_hack[0]: + return + implements = astng.Instance(node).getattr('__implements__')[0] + assignment = implements.parent + assert isinstance(assignment, astng.Assign) + # assignment.expr can be a Name or a Tupe or whatever. + # Use as_string() for the message + # FIXME: in case of multiple interfaces, find which one could not + # be resolved + self.add_message('F0220', node=implements, + args=(node.name, assignment.expr.as_string())) + + def _check_init(self, node): + """check that the __init__ method call super or ancestors'__init__ + method + """ + klass_node = node.parent.frame() + to_call = _ancestors_to_call(klass_node) + for stmt in node.nodes_of_class(astng.CallFunc): + expr = stmt.node + if not isinstance(expr, astng.Getattr) \ + or expr.attrname != '__init__': + continue + # skip the test if using super + if isinstance(expr.expr, astng.CallFunc) and \ + isinstance(expr.expr.node, astng.Name) and \ + expr.expr.node.name == 'super': + return + try: + klass = expr.expr.infer().next() + try: + del to_call[klass] + except KeyError: + self.add_message('W0233', node=expr, args=klass.name) + except astng.InferenceError, ex: + continue + for klass in to_call.keys(): + if klass.name == 'object': + continue + self.add_message('W0231', args=klass.name, node=node) + + def _check_signature(self, method1, method2, class_type): + """check that the signature of the two given methods match + + class_type is in 'class', 'interface' + """ + if not (isinstance(method1, astng.Function) + and isinstance(method2, astng.Function)): + self.add_message('F0202', args=(method1, method2), node=method1) + return + # don't care about functions with unknown argument (builtins) + if method1.argnames is None or method2.argnames is None: + return + if len(method1.argnames) != len(method2.argnames): + self.add_message('W0221', args=class_type, node=method1) + elif len(method1.defaults) != len(method2.defaults): + self.add_message('W0222', args=class_type, node=method1) + + +def _ancestors_to_call(klass_node, method='__init__'): + """return a dictionary where keys are the list of base classes providing + the queried method, and so that should/may be called from the method node + """ + to_call = {} + for base_node in klass_node.ancestors(recurs=False): + try: + base_node.local_attr(method) + to_call[base_node] = 1 + except astng.NotFoundError: + continue + return to_call + + +def node_method(node, method_name): + """get astng for <method_name> on the given class node, ensuring it + is a Function node + """ + stmt = node.local_attr(method_name) + if not isinstance(stmt, astng.Function): + raise astng.NotFoundError(method_name) + return stmt + +def register(linter): + """required method to auto register this checker """ + linter.register_checker(ClassChecker(linter)) diff --git a/checkers/design_analysis.py b/checkers/design_analysis.py new file mode 100644 index 0000000..1d51e74 --- /dev/null +++ b/checkers/design_analysis.py @@ -0,0 +1,322 @@ +# Copyright (c) 2003-2005 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., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +"""check for signs of poor design + + + see http://intranet.logilab.fr/jpl/view?rql=Any%20X%20where%20X%20eid%201243 + FIXME: missing 13, 15, 16 +""" + +__revision__ = "$Id: design_analysis.py,v 1.11 2005-12-30 15:41:29 adim Exp $" + +from logilab.astng import Function, InferenceError + +from pylint.interfaces import IASTNGChecker +from pylint.checkers import BaseChecker + +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)', + 'Used when class has too many parent classes.'), + 'R0902': ('Too many instance attributes (%s/%s)', + 'Used when class has too many instance attributes.'), + 'R0903': ('Not enough public methods (%s/%s)', + 'Used when class has not enough public methods.'), + 'R0904': ('Too many public methods (%s/%s)', + 'Used when class has too many public methods.'), + + 'R0911': ('Too many return statements (%s/%s)', + 'Used when a function or method has too many return statement.'), + 'R0912': ('Too many branches (%s/%s)', + 'Used when a function or method has too many branches.'), + 'R0913': ('Too many arguments (%s/%s)', + 'Used when a function or method takes too many arguments.'), + 'R0914': ('Too many local variables (%s/%s)', + 'Used when a function or method has too many local variables.'), + 'R0915': ('Too many statements (%s/%s)', + 'Used when a function or method has too many statements. You \ + should then split it in smaller functions / methods.'), + + 'R0921': ('Abstract class not referenced', + 'Used when an abstract class is not used as ancestor anywhere.'), + 'R0922': ('Abstract class is only referenced %s times', + 'Used when an abstract class is used less than X times as \ + ancestor.'), + 'R0923': ('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__ = (IASTNGChecker,) + + # 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'} + ), + ('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-branchs', + {'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._branchs = 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._branchs = [] + self._used_abstracts = {} + self._used_ifaces = {} + self._abstracts = [] + self._ifaces = [] + + 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) + + 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 + + def leave_class(self, node): + """check number of public methods""" + nb_public_methods = 0 + for method in node.methods(): + if not method.name.startswith('_'): + nb_public_methods += 1 + # 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)) + + + 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._branchs.append(0) + # check number of arguments + args = node.argnames + if args is not None and len(args) > self.config.max_args: + self.add_message('R0913', node=node, + args=(len(args), self.config.max_args)) + # check number of local variables + locnum = len(node.locals) + if locnum > self.config.max_locals: + self.add_message('R0914', node=node, + args=(locnum, self.config.max_locals)) + # init statements counter + self._stmts = 1 + + 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)) + branchs = self._branchs.pop() + if branchs > self.config.max_branchs: + self.add_message('R0912', node=node, + args=(branchs, self.config.max_branchs)) + # 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/yields""" + self._returns[-1] += 1 + + def visit_yield(self, _): + """count number of returns/yields""" + 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 branchs counter""" + branchs = len(node.handlers) + if node.else_: + branchs += 1 + self._inc_branch(branchs) + self._stmts += branchs + + def visit_tryfinally(self, _): + """increments the branchs counter""" + self._inc_branch(2) + self._stmts += 2 + + def visit_if(self, node): + """increments the branchs counter""" + branchs = len(node.tests) + if node.else_: + branchs += 1 + self._inc_branch(branchs) + self._stmts += branchs + + def visit_while(self, node): + """increments the branchs counter""" + branchs = 1 + if node.else_: + branchs += 1 + self._inc_branch(branchs) + + visit_for = visit_while + + def _inc_branch(self, branchsnum=1): + """increments the branchs counter""" + branchs = self._branchs + for i in xrange(len(branchs)): + branchs[i] += branchsnum + + # FIXME: make a nice report... + +def register(linter): + """required method to auto register this checker """ + linter.register_checker(MisdesignChecker(linter)) diff --git a/checkers/exceptions.py b/checkers/exceptions.py new file mode 100644 index 0000000..0f5fddc --- /dev/null +++ b/checkers/exceptions.py @@ -0,0 +1,167 @@ +# 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., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +""" Copyright (c) 2003-2005 LOGILAB S.A. (Paris, FRANCE). + http://www.logilab.fr/ -- mailto:contact@logilab.fr + +exceptions checkers for Python code +""" + +__revision__ = '$Id: exceptions.py,v 1.27 2006-03-08 15:53:42 syt Exp $' + +from logilab.common.compat import enumerate +from logilab import astng +from logilab.astng.inference import unpack_infer + +from pylint.checkers import BaseChecker +from pylint.checkers.utils import is_empty +from pylint.interfaces import IASTNGChecker + +MSGS = { + 'E0701': ( + 'Bad except clauses order (%s)', + 'Used when except clauses are not in the correct order (from the \ + more specific to the more generic). If you don\'t fix the order, \ + some exceptions may not be catched by the most specific handler.'), + 'E0702': ('Raising %s while only classes, instances or string are allowed', + 'Used when something which is neither a class, an instance or a \ + string is raised (i.e. a `TypeError` will be raised).'), + + 'W0701': ('Raising a string exception', + 'Used when a string exception is raised.'), + 'W0702': ('No exception\'s type specified', + 'Used when an except clause doesn\'t specify exceptions type to \ + catch.'), + 'W0703': ('Catch "Exception"', + 'Used when an except catch Exception instances.'), + 'W0704': ('Except doesn\'t do anything', + 'Used when an except clause does nothing but "pass" and there is\ + no "else" clause.'), + 'W0706': ( + 'Identifier %s used to raise an exception is assigned to %s', + 'Used when a variable used to raise an exception is initially \ + assigned to a value which can\'t be used as an exception.'), + } + +def is_raising(stmt): + """return true if the given statement node raise an exception + """ + for node in stmt.nodes: + if isinstance(node, astng.Raise): + return 1 + return 0 + +class ExceptionsChecker(BaseChecker): + """checks for + * excepts without exception filter + * string exceptions + """ + + __implements__ = IASTNGChecker + + name = 'exceptions' + msgs = MSGS + priority = -4 + options = () + + def visit_raise(self, node): + """check for string exception + """ + # ignore empty raise + if node.expr1 is None: + return + expr = node.expr1 + if isinstance(expr, astng.Const): + value = expr.value + if isinstance(value, str): + self.add_message('W0701', node=node) + else: + self.add_message('E0702', node=node, + args=value.__class__.__name__) + elif isinstance(expr, astng.Name) and \ + expr.name in ('None', 'True', 'False'): + self.add_message('E0702', node=node, args=expr.name) + elif isinstance(expr, astng.Mod): + self.add_message('W0701', node=node) + else: + try: + value = unpack_infer(expr).next() + except astng.InferenceError: + return + if value is astng.YES: + return + # must to be carefull since Const, Dict, .. inherit from + # Instance now + if isinstance(value, (astng.Class, astng.Module)): + return + if isinstance(value, astng.Instance) and \ + isinstance(value._proxied, astng.Class): + return + if isinstance(value, astng.Const) and \ + (value.value is None or + value.value is True or value.value is False): + # this Const has been generated by resolve + # since None, True and False are represented by Name + # nodes in the ast, and so this const node doesn't + # have the necessary parent, lineno and so on attributes + assinfo = value.as_string() + else: + assinfo = '%s line %s' % (value.as_string(), + value.source_line()) + self.add_message('W0706', node=node, + args=(expr.as_string(), assinfo)) + + def visit_tryexcept(self, node): + """check for empty except + """ + exceptions_classes = [] + nb_handlers = len(node.handlers) + for index, handler in enumerate(node.handlers): + exc_type = handler[0] + stmt = handler[2] + # single except doing nothing but "pass" without else clause + if nb_handlers == 1 and is_empty(stmt) and not node.else_: + self.add_message('W0704', node=exc_type) + if exc_type is None: + if nb_handlers == 1 and not is_raising(stmt): + self.add_message('W0702', node=stmt.nodes[0]) + # check if a "except:" is followed by some other + # except + elif index < (nb_handlers - 1): + msg = 'empty except clause should always appears last' + self.add_message('E0701', node=node, args=msg) + else: + try: + excs = list(unpack_infer(exc_type)) + except astng.InferenceError: + continue + for exc in excs: + if exc is astng.YES: + continue + if not isinstance(exc, astng.Class): + continue # XXX + exc_ancestors = [anc for anc in exc.ancestors() + if isinstance(anc, astng.Class)] + for previous_exc in exceptions_classes: + if previous_exc in exc_ancestors: + msg = '%s is an ancestor class of %s' % ( + previous_exc.name, exc.name) + self.add_message('E0701', node=exc_type, args=msg) + if (exc.name == 'Exception' + and exc.root().name == 'exceptions' + and nb_handlers == 1 and not is_raising(stmt)): + self.add_message('W0703', node=exc_type) + exceptions_classes += excs + +def register(linter): + """required method to auto register this checker""" + linter.register_checker(ExceptionsChecker(linter)) diff --git a/checkers/format.py b/checkers/format.py new file mode 100644 index 0000000..9cfd63d --- /dev/null +++ b/checkers/format.py @@ -0,0 +1,325 @@ +# 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., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +""" Copyright (c) 2003-2005 LOGILAB S.A. (Paris, FRANCE). + http://www.logilab.fr/ -- mailto:contact@logilab.fr + +Python code format's checker. + +By default try to follow Guido's style guide : + +http://www.python.org/doc/essays/styleguide.html + +Some parts of the process_token method is based from The Tab Nanny std module. +""" + +__revision__ = "$Id: format.py,v 1.51 2006-03-14 15:08:10 syt Exp $" + +import re +import tokenize +if not hasattr(tokenize, 'NL'): + raise ValueError("tokenize.NL doesn't exist -- tokenize module too old") + +from logilab.common.textutils import pretty_match + +from pylint.interfaces import IRawChecker, IASTNGChecker +from pylint.checkers import BaseRawChecker + +MSGS = { + 'C0301': ('Line too long (%s/%s)', + 'Used when a line is longer than a given number of characters.'), + 'W0302': ('Too many lines in module (%s)', + 'Used when a module has too much lines, reducing its readibility.' + ), + + 'W0311': ('Bad indentation. Found %s %s, expected %s', + 'Used when an unexpected number of indentation\'s tabulations or\ + spaces has been found.'), + 'W0312': ('Found indentation with %ss instead of %ss', + 'Used when there are some mixed tabs and spaces in a module.'), + + 'F0321': ('Format detection error in %r', + 'Used when an unexpected error occured in bad format detection.\ + Please report the error if it occurs.'), + 'C0321': ('More than one statement on a single line', + 'Used when more than on statement are found on the same line.'), + 'C0322': ('Operator not preceded by a space\n%s', + 'Used when one of the following operator (!= | <= | == | >= | < \ + | > | = | \+= | -= | \*= | /= | %) is not preceded by a space.'), + 'C0323': ('Operator not followed by a space\n%s', + 'Used when one of the following operator (!= | <= | == | >= | < \ + | > | = | \+= | -= | \*= | /= | %) is not followed by a space.'), + 'C0324': ('Comma not followed by a space\n%s', + 'Used when a comma (",") is not followed by a space.'), + + 'W0331': ('Use of the <> operator', + 'Used when the deprecated "<>" operator is used instead \ + of "!=".'), + 'W0332': ('Use l as long integer identifier', + 'Used when a lower case "l" is used to mark a long integer. You \ +should use a upper case "L" since the letter "l" looks too much like the digit \ +"1"'), + } + +# simple quoted string rgx +SQSTRING_RGX = r'"([^"\\]|\\.)*("|\\$)' +# triple quoted string rgx +TQSTRING_RGX = r'"""([^"]|"(?!""))*("""|$)' +# simple apostrophed rgx +SASTRING_RGX = r"'([^'\\]|\\.)*('|\\$)" +# triple apostrophed string rgx # FIXMY english please +TASTRING_RGX = r"'''([^']|'(?!''))*('''|$)" + +# finally, the string regular expression +STRING_RGX = re.compile('%s|%s|%s|%s' % (TQSTRING_RGX, TASTRING_RGX, + SQSTRING_RGX, SASTRING_RGX)) + +COMMENT_RGX = re.compile("#.*$") + +OPERATORS = r'!=|<=|==|>=|<|>|=|\+=|-=|\*=|/=|%' + +OP_RGX_MATCH_1 = r'[^(]*(?<!\s|\^|<|>|=|\+|-|\*|/|!|%%|&|\|)(%s).*' % OPERATORS +OP_RGX_SEARCH_1 = r'(?<!\s|\^|<|>|=|\+|-|\*|/|!|%%|&|\|)(%s)' % OPERATORS + +OP_RGX_MATCH_2 = r'[^(]*(%s)(?!\s|=|>|<).*' % OPERATORS +OP_RGX_SEARCH_2 = r'(%s)(?!\s|=|>)' % OPERATORS + +BAD_CONSTRUCT_RGXS = ( +## (re.compile( +## r'\s*(class|def|if|for|while)\s+([^:\[\]]|\[.*:?.*\])*?:\s*\w+(\s|\w)*'), +## re.compile(r':\s*[^\s]+.*'), +## 'C0321'), + + (re.compile(OP_RGX_MATCH_1), + re.compile(OP_RGX_SEARCH_1), + 'C0322'), + + (re.compile(OP_RGX_MATCH_2), + re.compile(OP_RGX_SEARCH_2), + 'C0323'), + + (re.compile(r'.*,[^\s)].*'), + re.compile(r',[^\s)]'), + 'C0324'), + ) + + +def get_string_coords(line): + """return a list of string positions (tuple (start, end)) in the line + """ + result = [] + for match in re.finditer(STRING_RGX, line): + result.append( (match.start(), match.end()) ) + return result + +def in_coords(match, string_coords): + """return true if the match in in the string coord + """ + mstart = match.start() + for start, end in string_coords: + if mstart >= start and mstart < end: + return 1 + return 0 + +def check_line(line, writer): + """check a line for a bad construction + if it founds one, return a message describing the problem + else return None + """ + clean_str = STRING_RGX.sub('', line) + clean_str = COMMENT_RGX.sub('', clean_str) + for rgx_match, rgx_search, msg_id in BAD_CONSTRUCT_RGXS: + if rgx_match.match(clean_str): + string_positions = get_string_coords(line) + for match in re.finditer(rgx_search, line): + if not in_coords(match, string_positions): + return msg_id, pretty_match(match, line.rstrip()) + writer.add_message('F0321', line=line, args=line) + + + +class FormatChecker(BaseRawChecker): + """checks for : + * unauthorized constructions + * strict indentation + * line length + * use of <> instead of != + """ + + __implements__ = (IRawChecker, IASTNGChecker) + + # configuration section name + name = 'format' + # messages + msgs = MSGS + # configuration options + # for available dict keys/values see the optik parser 'add_option' method + options = (('max-line-length', + {'default' : 80, 'type' : "int", 'metavar' : '<int>', + 'help' : 'Maximum number of characters on a single line.'}), + ('max-module-lines', + {'default' : 1000, 'type' : 'int', 'metavar' : '<int>', + 'help': 'Maximum number of lines in a module'} + ), + ('indent-string', + {'default' : ' ', 'type' : "string", 'metavar' : '<string>', + 'help' : 'String used as indentation unit. This is usually \ +" " (4 spaces) or "\\t" (1 tab).'}), + ) + def __init__(self, linter=None): + BaseRawChecker.__init__(self, linter) + self._lines = None + self._visited_lines = None + + def new_line(self, tok_type, line, line_num, junk): + """a new line has been encountered, process it if necessary""" + if not tok_type in junk: + self._lines[line_num] = line + self.check_lines(line, line_num) + + def process_tokens(self, tokens): + """process tokens and search for : + + _ non strict indentation (i.e. not always using the <indent> parameter as + indent unit) + _ too long lines (i.e. longer than <max_chars>) + _ optionally bad construct (if given, bad_construct must be a compiled + regular expression). + """ + indent = tokenize.INDENT + dedent = tokenize.DEDENT + newline = tokenize.NEWLINE + junk = (tokenize.COMMENT, tokenize.NL) + indents = [0] + check_equal = 0 + line_num = 0 + self._lines = {} + self._visited_lines = {} + for (tok_type, token, start, _, line) in tokens: + if start[0] != line_num: + line_num = start[0] + self.new_line(tok_type, line, line_num, junk) + if tok_type == tokenize.OP: + if token == '<>': + self.add_message('W0331', line=line_num) + elif tok_type == tokenize.NUMBER: + if token.endswith('l'): + self.add_message('W0332', line=line_num) + + elif tok_type == newline: + # a program statement, or ENDMARKER, will eventually follow, + # after some (possibly empty) run of tokens of the form + # (NL | COMMENT)* (INDENT | DEDENT+)? + # If an INDENT appears, setting check_equal is wrong, and will + # be undone when we see the INDENT. + check_equal = 1 + + elif tok_type == indent: + check_equal = 0 + self.check_indent_level(token, indents[-1]+1, line_num) + indents.append(indents[-1]+1) + + elif tok_type == dedent: + # there's nothing we need to check here! what's important is + # that when the run of DEDENTs ends, the indentation of the + # program statement (or ENDMARKER) that triggered the run is + # equal to what's left at the top of the indents stack + check_equal = 1 + if len(indents) > 1: + del indents[-1] + + elif check_equal and tok_type not in junk: + # this is the first "real token" following a NEWLINE, so it + # must be the first token of the next program statement, or an + # ENDMARKER; the "line" argument exposes the leading whitespace + # for this statement; in the case of ENDMARKER, line is an empty + # string, so will properly match the empty string with which the + # "indents" stack was seeded + check_equal = 0 + self.check_indent_level(line, indents[-1], line_num) + + if line_num > self.config.max_module_lines: + self.add_message('W0302', args=line_num) + + def visit_default(self, node): + """check the node line number and check it if not yet done + """ + if not node.is_statement(): + return + prev_sibl = node.previous_sibling() + if prev_sibl is not None: + # don't use .source_line since it causes C0321 false positive ! + prev_line = prev_sibl.lineno + else: + # itou ? + prev_line = node.parent.statement().lineno + line = node.source_line() + if prev_line == line and self._visited_lines.get(line) != 2: + self.add_message('C0321', node=node) + self._visited_lines[line] = 2 + return + if self._visited_lines.has_key(line): + return + self._visited_lines[line] = 1 + #print 'checking line', self._lines[line] + #print node + try: + msg_def = check_line(self._lines[line], self) + if msg_def: + self.add_message(msg_def[0], node = node, args=msg_def[1]) + except KeyError: + # FIXME: internal error ! + pass + + def check_lines(self, lines, i): + """check lines have less than a maximum number of characters + """ + max_chars = self.config.max_line_length + for line in lines.splitlines(): + if len(line) > max_chars: + self.add_message('C0301', line=i, args=(len(line), max_chars)) + i += 1 + + def check_indent_level(self, string, expected, line_num): + """return the indent level of the string + """ + indent = self.config.indent_string + if indent == '\\t': # \t is not interpreted in the configuration file + indent = '\t' + level = 0 + unit_size = len(indent) + while string[:unit_size] == indent: + string = string[unit_size:] + level += 1 + suppl = '' + while string and string[0] in ' \t': + if string[0] != indent[0]: + if string[0] == '\t': + args = ('tab', 'space') + else: + args = ('space', 'tab') + self.add_message('W0312', args=args, line=line_num) + return level + suppl += string[0] + string = string [1:] + if level != expected or suppl: + i_type = 'spaces' + if indent[0] == '\t': + i_type = 'tabs' + self.add_message('W0311', line=line_num, + args=(level * unit_size + len(suppl), i_type, + expected * unit_size)) + + +def register(linter): + """required method to auto register this checker """ + linter.register_checker(FormatChecker(linter)) diff --git a/checkers/imports.py b/checkers/imports.py new file mode 100644 index 0000000..6ca248a --- /dev/null +++ b/checkers/imports.py @@ -0,0 +1,379 @@ +# Copyright (c) 2003-2005 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., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +"""imports checkers for Python code +""" + +__revision__ = "$Id: imports.py,v 1.46 2005-11-10 17:26:27 syt Exp $" + +from logilab.common import get_cycles +from logilab.common.modutils import is_standard_module, is_relative, \ + get_module_part +from logilab.common.ureports import VerbatimText, Paragraph + +from logilab import astng + +from pylint.interfaces import IASTNGChecker +from pylint.checkers import BaseChecker, EmptyReport +from pylint.checkers.utils import are_exclusive + +def get_first_import(context, name, base): + """return the node where [base.]<name> is imported or None if not found + """ + for node in context.values(): + if isinstance(node, astng.Import): + if name in [iname[0] for iname in node.names]: + return node + if isinstance(node, astng.From): + if base == node.modname and \ + name in [iname[0] for iname in node.names]: + return node + + +# utilities to represents import dependencies as tree and dot graph ########### + +def filter_dependencies_info(dep_info, package_dir, mode='external'): + """filter external or internal dependencies from dep_info (return a + new dictionary containing the filtered modules only) + """ + if mode == 'external': + filter_func = lambda x: not is_standard_module(x, (package_dir,)) + else: + assert mode == 'internal' + filter_func = lambda x: is_standard_module(x, (package_dir,)) + result = {} + for importee, importers in dep_info.items(): + if filter_func(importee): + result[importee] = importers + return result + +def make_tree_defs(mod_files_list): + """get a list of 2-uple (module, list_of_files_which_import_this_module), + it will return a dictionnary to represent this as a tree + """ + tree_defs = {} + for mod, files in mod_files_list: + node = (tree_defs, ()) + for prefix in mod.split('.'): + node = node[0].setdefault(prefix, [{}, []]) + node[1] += files + return tree_defs + +def repr_tree_defs(data, indent_str=None): + """return a string which represents imports as a tree""" + lines = [] + nodes = data.items() + for i in range(len(nodes)): + mod, (sub, files) = nodes[i] + if not files: + files = '' + else: + files = '(%s)' % ','.join(files) + if indent_str is None: + lines.append('%s %s' % (mod, files)) + sub_indent_str = ' ' + else: + lines.append('%s\-%s %s' % (indent_str, mod, files)) + if i == len(nodes)-1: + sub_indent_str = '%s ' % indent_str + else: + sub_indent_str = '%s| ' % indent_str + if sub: + lines.append(repr_tree_defs(sub, sub_indent_str)) + return '\n'.join(lines) + +def dot_node(modname): + """return the string representation for a dot node""" + return '"%s" [ label="%s" ];' % (modname, modname) + +def dot_edge(from_, to_): + """return the string representation for a dot edge between two nodes""" + return'"%s" -> "%s" [ ] ;' % (from_, to_) + +DOT_HEADERS = '''rankdir="LR" URL="." concentrate=false +edge[fontsize="10" ] +node[width="0" height="0" fontsize="12" fontcolor="black"]''' + +def dependencies_graph(filename, dep_info): + """write dependencies as defined in the dep_info dictionary as a dot + (graphviz) file + """ + done = {} + stream = open(filename, 'w') + print >> stream, "digraph g {" + print >> stream, DOT_HEADERS + for modname, dependencies in dep_info.items(): + done[modname] = 1 + print >> stream, dot_node(modname) + for modname in dependencies: + if not done.has_key(modname): + done[modname] = 1 + print >> stream, dot_node(modname) + for depmodname, dependencies in dep_info.items(): + for modname in dependencies: + print >> stream, dot_edge(modname, depmodname) + print >> stream,'}' + stream.close() + +def make_graph(filename, dep_info, sect, gtype): + """generate a dependencies graph and add some information about it in the + report's section + """ + dependencies_graph(filename, dep_info) + sect.append(Paragraph('%simports graph has been written to %s' + % (gtype, filename))) + + +# the import checker itself ################################################### + +MSGS = { + 'F0401': ('Unable to import %r (%s)' , + 'Used when pylint has been unable to import a module.'), + 'R0401': ('Cyclic import (%s)', + 'Used when a cyclic import between two or more modules is \ + detected.'), + + 'W0401': ('Wildcard import %s', + 'Used when `from module import *` is detected.'), + 'W0402': ('Uses of a deprecated module %r', + 'Used a module marked as deprecated is imported.'), + 'W0403': ('Relative import %r', + 'Used when an import relative to the package directory is \ + detected.'), + 'W0404': ('Reimport %r (imported line %s)', + 'Used when a module is reimported multiple times.'), + 'W0406': ('Module import itself', + 'Used when a module is importing itself.'), + + 'W0410': ('__future__ import is not the first non docstring statement', + 'Python 2.5 and greater require __future__ import to be the \ + first non docstring statement in the module.'), + } + +class ImportsChecker(BaseChecker): + """checks for + * external modules dependencies + * relative / wildcard imports + * cyclic imports + * uses of deprecated modules + """ + + __implements__ = IASTNGChecker + + name = 'imports' + msgs = MSGS + priority = -2 + + options = (('deprecated-modules', + {'default' : ('regsub','string', 'TERMIOS', + 'Bastion', 'rexec'), + 'type' : 'csv', + 'metavar' : '<modules>', + 'help' : 'Deprecated modules which should not be used, \ +separated by a comma'} + ), + ('import-graph', + {'default' : '', + 'type' : 'string', + 'metavar' : '<file.dot>', + 'help' : 'Create a graph of every (i.e. internal and \ +external) dependencies in the given file (report R0402 must not be disabled)'} + ), + ('ext-import-graph', + {'default' : '', + 'type' : 'string', + 'metavar' : '<file.dot>', + 'help' : 'Create a graph of external dependencies in the \ +given file (report R0402 must not be disabled)'} + ), + ('int-import-graph', + {'default' : '', + 'type' : 'string', + 'metavar' : '<file.dot>', + 'help' : 'Create a graph of internal dependencies in the \ +given file (report R0402 must not be disabled)'} + ), + + ) + + def __init__(self, linter=None): + BaseChecker.__init__(self, linter) + self.stats = None + self.import_graph = None + self.__int_dep_info = self.__ext_dep_info = None + self.reports = (('R0401', 'External dependencies', + self.report_external_dependencies), + ('R0402', 'Modules dependencies graph', + self.report_dependencies_graph), + ) + + def open(self): + """called before visiting project (i.e set of modules)""" + self.linter.add_stats(dependencies={}) + self.linter.add_stats(cycles=[]) + self.stats = self.linter.stats + self.import_graph = {} + + def close(self): + """called before visiting project (i.e set of modules)""" + for cycle in get_cycles(self.import_graph): + self.add_message('R0401', args=' -> '.join(cycle)) + + def visit_import(self, node): + """triggered when an import statement is seen""" + for name, _ in node.names: + self._check_deprecated(node, name) + relative = self._check_relative(node, name) + self._imported_module(node, name, relative) + # handle reimport + self._check_reimport(node, name) + + + def visit_from(self, node): + """triggered when an import statement is seen""" + basename = node.modname + if basename == '__future__': + # check this is the first non docstring statement in the module + if node.previous_sibling(): + self.add_message('W0410', node=node) + self._check_deprecated(node, basename) + relative = self._check_relative(node, basename) + for name, _ in node.names: + if name == '*': + self.add_message('W0401', args=basename, node=node) + continue + # handle reimport + self._check_reimport(node, name, basename) + # analyze dependencies + fullname = '%s.%s' % (basename, name) + if fullname.find('.') > -1: + try: + # XXXFIXME: don't use get_module_part which doesn't take + # care of package precedence + fullname = get_module_part(fullname, + context_file=node.root().file) + except ImportError, ex: + self.add_message('F0401', args=(fullname, ex), node=node) + continue + self._imported_module(node, fullname, relative) + + def _imported_module(self, node, mod_path, relative): + """notify an imported module, used to analyze dependencies + """ + context_name = node.root().name + if relative: + mod_path = '%s.%s' % ('.'.join(context_name.split('.')[:-1]), + mod_path) + if context_name == mod_path: + # module importing itself ! + self.add_message('W0406', node=node) + elif not is_standard_module(mod_path): + # handle dependencies + mod_paths = self.stats['dependencies'].setdefault(mod_path, []) + if not context_name in mod_paths: + mod_paths.append(context_name) + if is_standard_module( mod_path, (self.package_dir(),) ): + # update import graph + mgraph = self.import_graph.setdefault(context_name, []) + if not mod_path in mgraph: + mgraph.append(mod_path) + + def _check_relative(self, node, mod_path): + """check relative import module""" + # check for relative import + context_file = node.root().file + relative = is_relative(mod_path, context_file) + if relative: + self.add_message('W0403', args=mod_path, node=node) + return relative + + def _check_deprecated(self, node, mod_path): + """check if the module is deprecated""" + for mod_name in self.config.deprecated_modules: + if mod_path.startswith(mod_name) and \ + (len(mod_path) == len(mod_name) + or mod_path[len(mod_name)] == '.'): + self.add_message('W0402', node=node, args=mod_path) + + def _check_reimport(self, node, name, basename=None): + """check if the import is necessary (i.e. not already done) + """ + frame = node.frame() + first = get_first_import(frame, name, basename) + if isinstance(first, (astng.Import, astng.From)) and first is not node \ + and not are_exclusive(first, node): + self.add_message('W0404', node=node, args=(name, first.lineno)) + else: + root = node.root() + if root is frame: + return + first = get_first_import(root, name, basename) + if not isinstance(first, (astng.Import, astng.From)): + return + if first is not node and not are_exclusive(first, node): + self.add_message('W0404', node=node, + args=(name, first.lineno)) + + + def report_external_dependencies(self, sect, _, dummy): + """return a verbatim layout for displaying dependencies + """ + dep_info = make_tree_defs(self._external_dependencies_info().items()) + if not dep_info: + raise EmptyReport() + tree_str = repr_tree_defs(dep_info) + sect.append(VerbatimText(tree_str)) + + def report_dependencies_graph(self, sect, _, dummy): + """write dependencies as a dot (graphviz) file""" + dep_info = self.stats['dependencies'] + if not dep_info or not (self.config.import_graph + or self.config.ext_import_graph + or self.config.int_import_graph): + raise EmptyReport() + filename = self.config.import_graph + if filename: + make_graph(filename, dep_info, sect, '') + filename = self.config.ext_import_graph + if filename: + make_graph(filename, self._external_dependencies_info(), + sect, 'external ') + filename = self.config.int_import_graph + if filename: + make_graph(filename, self._internal_dependencies_info(), + sect, 'internal ') + + def _external_dependencies_info(self): + """return cached external dependencies information or build and + cache them + """ + if self.__ext_dep_info is None: + self.__ext_dep_info = filter_dependencies_info( + self.stats['dependencies'], self.package_dir(), 'external') + return self.__ext_dep_info + + def _internal_dependencies_info(self): + """return cached internal dependencies information or build and + cache them + """ + if self.__int_dep_info is None: + self.__int_dep_info = filter_dependencies_info( + self.stats['dependencies'], self.package_dir(), 'internal') + return self.__int_dep_info + + +def register(linter): + """required method to auto register this checker """ + linter.register_checker(ImportsChecker(linter)) diff --git a/checkers/misc.py b/checkers/misc.py new file mode 100644 index 0000000..7b59b95 --- /dev/null +++ b/checkers/misc.py @@ -0,0 +1,127 @@ +# pylint: disable-msg=W0511 +# 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., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +""" Copyright (c) 2000-2003 LOGILAB S.A. (Paris, FRANCE). + http://www.logilab.fr/ -- mailto:contact@logilab.fr + +Check source code is ascii only or has an encoding declaration (PEP 263) +""" + +__revision__ = '$Id: misc.py,v 1.19 2005-11-02 09:21:47 syt Exp $' + +import re + +from pylint.interfaces import IRawChecker +from pylint.checkers import BaseChecker + +def is_ascii(string): + """return true if non ascii characters are detected in the given string + """ + if string: + return max([ord(char) for char in string]) < 128 + return True + +# regexp matching both emacs and vim declaration +ENCODING_RGX = re.compile("[^#]*#*.*coding[:=]\s*([^\s]+)") + +def guess_encoding(string): + """try to guess encoding from a python file as string + return None if not found + """ + assert type(string) is type(''), type(string) + # check for UTF-8 byte-order mark + if string.startswith('\xef\xbb\xbf'): + return 'UTF-8' + first_lines = string.split('\n', 2)[:2] + for line in first_lines: + # check for emacs / vim encoding declaration + match = ENCODING_RGX.match(line) + if match is not None: + return match.group(1) + + +MSGS = { + 'E0501': ('Non ascii characters found but no encoding specified (PEP 263)', + 'Used when some non ascii characters are detected but now \ + encoding is specified, as explicited in the PEP 263.'), + 'E0502': ('Wrong encoding specified (%s)', + 'Used when a known encoding is specified but the file doesn\'t \ + seem to be actually in this encoding.'), + 'E0503': ('Unknown encoding specified (%s)', + 'Used when an encoding is specified, but it\'s unknown to Python.' + ), + + 'W0511': ('%s', + 'Used when a warning note as FIXME or XXX is detected.'), + } + +class EncodingChecker(BaseChecker): + """checks for: + * warning notes in the code like FIXME, XXX + * PEP 263: source code with non ascii character but no encoding declaration + """ + __implements__ = IRawChecker + + # configuration section name + name = 'miscellaneous' + msgs = MSGS + + options = (('notes', + {'type' : 'csv', 'metavar' : '<comma separated values>', + 'default' : ('FIXME', 'XXX', 'TODO'), + 'help' : 'List of note tags to take in consideration, \ +separated by a comma. Default to FIXME, XXX, TODO' + }), + ) + + def __init__(self, linter=None): + BaseChecker.__init__(self, linter) + + def process_module(self, stream): + """inspect the source file to found encoding problem or fixmes like + notes + """ + # source encoding + data = stream.read() + if not is_ascii(data): + encoding = guess_encoding(data) + if encoding is None: + self.add_message('E0501', line=1) + else: + try: + unicode(data, encoding) + except UnicodeError: + self.add_message('E0502', args=encoding, line=1) + except LookupError: + self.add_message('E0503', args=encoding, line=1) + del data + # warning notes in the code + stream.seek(0) + notes = [] + for note in self.config.notes: + notes.append(re.compile(note)) + linenum = 1 + for line in stream.readlines(): + for note in notes: + match = note.search(line) + if match: + self.add_message('W0511', args=line[match.start():-1], + line=linenum) + break + linenum += 1 + + + +def register(linter): + """required method to auto register this checker""" + linter.register_checker(EncodingChecker(linter)) diff --git a/checkers/newstyle.py b/checkers/newstyle.py new file mode 100644 index 0000000..540b60f --- /dev/null +++ b/checkers/newstyle.py @@ -0,0 +1,148 @@ +# Copyright (c) 2005-2006 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., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +"""check for new / old style related problems +""" + +__revision__ = "$Id: newstyle.py,v 1.8 2006-03-05 14:39:38 syt Exp $" + +from logilab import astng + +from pylint.interfaces import IASTNGChecker +from pylint.checkers import BaseChecker + +MSGS = { + 'E1001': ('Use __slots__ on an old style class', + 'Used when an old style class use the __slots__ attribute.'), + 'E1002': ('Use super on an old style class', + 'Used when an old style class use the super builtin.'), + 'E1003': ('Bad first argument %r given to super class', + 'Used when another argument than the current class is given as \ + first argument of the super builtin.'), + 'E1010': ('Raising a new style class', + 'Used when a new style class is raised since it\'s not yet \ + possible.'), + + 'W1001': ('Use of "property" on an old style class', + 'Used when PyLint detect the use of the builtin "property" \ + on an old style class while this is relying on new style \ + classes features'), + 'W1010': ('Exception doesn\'t inherit from standard "Exception" class', + 'Used when a custom exception class is raised but doesn\'t \ + inherit from the builtin "Exception" class.'), + } + + +class NewStyleConflictChecker(BaseChecker): + """checks for usage of new style capabilities on old style classes and + other new/old styles conflicts problems + * use of property, __slots__, super + * "super" usage + * raising a new style class as exception + """ + + __implements__ = (IASTNGChecker,) + + # configuration section name + name = 'newstyle' + # messages + msgs = MSGS + priority = -2 + # configuration options + options = () + +# def __init__(self, linter=None): +# BaseChecker.__init__(self, linter) + + def visit_class(self, node): + """check __slots__ usage + """ + if '__slots__' in node and not node.newstyle: + self.add_message('E1001', node=node) + + def visit_callfunc(self, node): + """check property usage""" + parent = node.parent.frame() + if (isinstance(parent, astng.Class) and + not parent.newstyle and + isinstance(node.node, astng.Name)): + name = node.node.name + if name == 'property': + self.add_message('W1001', node=node) + + def visit_raise(self, node): + """check for raising new style class + """ + # ignore empty raise + if node.expr1 is None: + return + if not isinstance(node.expr1, (astng.Const, astng.Mod)): + try: + name = node.expr1.nodes_of_class(astng.Name).next() + value = name.infer().next() + except (StopIteration, astng.ResolveError): + pass + else: + if isinstance(value, astng.Class): + if value.newstyle: + self.add_message('E1010', node=node) + elif not inherit_from_std_ex(value): + self.add_message('W1010', node=node) + + def visit_function(self, node): + """check use of super""" + # ignore actual functions or method within a new style class + if not node.is_method(): + return + klass = node.parent.frame() + for stmt in node.nodes_of_class(astng.CallFunc): + expr = stmt.node + if not isinstance(expr, astng.Getattr): + continue + call = expr.expr + # skip the test if using super + if isinstance(call, astng.CallFunc) and \ + isinstance(call.node, astng.Name) and \ + call.node.name == 'super': + if not klass.newstyle: + # super should not be used on an old style class + self.add_message('E1002', node=node) + else: + # super first arg should be the class + try: + supcls = (call.args and call.args[0].infer().next() + or None) + except astng.InferenceError: + continue + if klass is not supcls: + supcls = getattr(supcls, 'name', supcls) + self.add_message('E1003', node=node, args=supcls) + + + +def inherit_from_std_ex(node): + """return true if the given class node is subclass of + exceptions.Exception + """ + if node.name == 'Exception' and node.root().name == 'exceptions': + return True + for parent in node.ancestors(recurs=False): + if inherit_from_std_ex(parent): + return True + return False + +def register(linter): + """required method to auto register this checker """ + linter.register_checker(NewStyleConflictChecker(linter)) diff --git a/checkers/raw_metrics.py b/checkers/raw_metrics.py new file mode 100644 index 0000000..be10034 --- /dev/null +++ b/checkers/raw_metrics.py @@ -0,0 +1,130 @@ +# 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., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +""" Copyright (c) 2003-2005 LOGILAB S.A. (Paris, FRANCE). + http://www.logilab.fr/ -- mailto:contact@logilab.fr + +Raw metrics checker +""" + +__revision__ = "$Id: raw_metrics.py,v 1.15 2005-11-02 09:21:47 syt Exp $" + +import tokenize + +# pylint now requires pylint >= 2.2, so this is no longer necessary +#if not hasattr(tokenize, 'NL'): +# raise ValueError("tokenize.NL doesn't exist -- tokenize module too old") + +from logilab.common.ureports import Table + +from pylint.interfaces import IRawChecker +from pylint.checkers import BaseRawChecker, EmptyReport +from pylint.reporters import diff_string + + +def report_raw_stats(sect, stats, old_stats): + """calculate percentage of code / doc / comment / empty + """ + total_lines = stats['total_lines'] + if not total_lines: + raise EmptyReport() + sect.description = '%s lines have been analyzed' % total_lines + lines = ('type', 'number', '%', 'previous', 'difference') + for node_type in ('code', 'docstring', 'comment', 'empty'): + key = node_type + '_lines' + total = stats[key] + percent = float(total * 100) / total_lines + old = old_stats.get(key, None) + if old is not None: + diff_str = diff_string(old, total) + else: + old, diff_str = 'NC', 'NC' + lines += (node_type, str(total), '%.2f' % percent, + str(old), diff_str) + sect.append(Table(children=lines, cols=5, rheaders=1)) + + +class RawMetricsChecker(BaseRawChecker): + """does not check anything but gives some raw metrics : + * total number of lines + * total number of code lines + * total number of docstring lines + * total number of comments lines + * total number of empty lines + """ + + __implements__ = (IRawChecker,) + + # configuration section name + name = 'metrics' + # configuration options + options = ( ) + # messages + msgs = {} + # reports + reports = ( ('R0701', 'Raw metrics', report_raw_stats), ) + + def __init__(self, linter): + BaseRawChecker.__init__(self, linter) + self.stats = None + + def open(self): + """init statistics""" + self.stats = self.linter.add_stats(total_lines=0, code_lines=0, + empty_lines=0, docstring_lines=0, + comment_lines=0) + + def process_tokens(self, tokens): + """update stats""" + i = 0 + tokens = list(tokens) + while i < len(tokens): + i, lines_number, line_type = get_type(tokens, i) + self.stats['total_lines'] += lines_number + self.stats[line_type] += lines_number + + +JUNK = (tokenize.NL, tokenize.INDENT, tokenize.NEWLINE, tokenize.ENDMARKER) + +def get_type(tokens, start_index): + """return the line type : docstring, comment, code, empty + """ + i = start_index + tok_type = tokens[i][0] + start = tokens[i][2] + pos = start + line_type = None + while i < len(tokens) and tokens[i][2][0] == start[0]: + tok_type = tokens[i][0] + pos = tokens[i][3] + if line_type is None: + if tok_type == tokenize.STRING: + line_type = 'docstring_lines' + elif tok_type == tokenize.COMMENT: + line_type = 'comment_lines' + elif tok_type in JUNK: + pass + else: + line_type = 'code_lines' + i += 1 + + if line_type is None: + line_type = 'empty_lines' + elif i < len(tokens) and tok_type == tokenize.NEWLINE: + i += 1 + return i, pos[0] - start[0] + 1, line_type + + +def register(linter): + """ required method to auto register this checker """ + linter.register_checker(RawMetricsChecker(linter)) + diff --git a/checkers/similar.py b/checkers/similar.py new file mode 100644 index 0000000..f4b3a6c --- /dev/null +++ b/checkers/similar.py @@ -0,0 +1,329 @@ +# pylint: disable-msg=w0622 +# Copyright (c) 2004 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., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +"""a similarties / code duplication command line tool and pylint checker +""" +from __future__ import generators + +__revision__ = '$Id: similar.py,v 1.14 2006-03-29 08:24:32 syt Exp $' + +import sys + +from logilab.common.compat import set, izip, sum, enumerate +from logilab.common.ureports import Table + +from pylint.interfaces import IRawChecker +from pylint.checkers import BaseChecker, table_lines_from_stats + + +class Similar: + """finds copy-pasted lines of code in a project""" + + def __init__(self, min_lines=4, ignore_comments=False, ignore_docstrings=False): + self.min_lines = min_lines + self.ignore_comments = ignore_comments + self.ignore_docstrings = ignore_docstrings + self.linesets = [] + + def append_stream(self, streamid, stream): + """append a file to search for similarities""" + self.linesets.append(LineSet(streamid, + stream.readlines(), + self.ignore_comments, + self.ignore_docstrings)) + + def run(self): + """start looking for similarities and display results on stdout""" + self._display_sims(self._compute_sims()) + + def _compute_sims(self): + """compute similarities in appended files""" + no_duplicates = {} + for num, lineset1, idx1, lineset2, idx2 in self._iter_sims(): + duplicate = no_duplicates.setdefault(num, []) + for couples in duplicate: + if (lineset1, idx1) in couples or (lineset2, idx2) in couples: + couples.add( (lineset1, idx1) ) + couples.add( (lineset2, idx2) ) + break + else: + duplicate.append( set([(lineset1, idx1), (lineset2, idx2)]) ) + sims = [] + for num, ensembles in no_duplicates.iteritems(): + for couples in ensembles: + sims.append( (num, couples) ) + sims.sort() + sims.reverse() + return sims + + def _display_sims(self, sims): + """display computed similarities on stdout""" + nb_lignes_dupliquees = 0 + for num, couples in sims: + print + print num, "similar lines in", len(couples), "files" + couples = list(couples) + couples.sort() + for lineset, idx in couples: + print "==%s:%s" % (lineset.name, idx) + for line in lineset._real_lines[idx:idx+num]: + print " ", line, + nb_lignes_dupliquees += num * (len(couples)-1) + nb_total_lignes = sum([len(lineset) for lineset in self.linesets]) + print "TOTAL lines=%s duplicates=%s percent=%s" \ + % (nb_total_lignes, nb_lignes_dupliquees, + nb_lignes_dupliquees*1. / nb_total_lignes) + + def _find_common(self, lineset1, lineset2): + """find similarities in the two given linesets""" + lines1 = lineset1.enumerate_stripped + lines2 = lineset2.enumerate_stripped + find = lineset2.find + index1 = 0 + min_lines = self.min_lines + while index1 < len(lineset1): + skip = 1 + num = 0 + for index2 in find( lineset1[index1] ): + non_blank = 0 + for num, ((_, line1), (_, line2)) in enumerate( + izip(lines1(index1), lines2(index2))): + if line1 != line2: + if non_blank > min_lines: + yield num, lineset1, index1, lineset2, index2 + skip = max(skip, num) + break + if line1: + non_blank += 1 + else: + # we may have reach the end + num += 1 + if non_blank > min_lines: + yield num, lineset1, index1, lineset2, index2 + skip = max(skip, num) + index1 += skip + + def _iter_sims(self): + """iterate on similarities among all files, by making a cartesian + product + """ + for idx, lineset in enumerate(self.linesets[:-1]): + for lineset2 in self.linesets[idx+1:]: + for sim in self._find_common(lineset, lineset2): + yield sim + +def stripped_lines(lines, ignore_comments, ignore_docstrings): + strippedlines = [] + docstring = None + for line in lines: + line = line.strip() + if ignore_docstrings: + if not docstring and \ + (line.startswith('"""') or line.startswith("'''")): + docstring = line[:3] + line = line[3:] + if docstring: + if line.endswith(docstring): + docstring = None + line = '' + # XXX cut when a line begins with code but end with a comment + if ignore_comments and line.startswith('#'): + line = '' + strippedlines.append(line) + return strippedlines + +class LineSet: + """Holds and indexes all the lines of a single source file""" + def __init__(self, name, lines, ignore_comments=False, ignore_docstrings=False): + self.name = name + self._real_lines = lines + self._stripped_lines = stripped_lines(lines, ignore_comments, ignore_docstrings) + self._index = self._mk_index() + + def __str__(self): + return '<Lineset for %s>' % self.name + + def __len__(self): + return len(self._real_lines) + + def __getitem__(self, index): + return self._stripped_lines[index] + + def __cmp__(self, other): + return cmp(self.name, other.name) + + def __hash__(self): + return id(self) + + def enumerate_stripped(self, start_at=0): + """return an iterator on stripped lines, starting from a given index + if specified, else 0 + """ + idx = start_at + if start_at: + lines = self._stripped_lines[start_at:] + else: + lines = self._stripped_lines + for line in lines: + #if line: + yield idx, line + idx += 1 + + def find(self, stripped_line): + """return positions of the given stripped line in this set""" + return self._index.get(stripped_line, ()) + + def _mk_index(self): + """create the index for this set""" + index = {} + for line_no, line in enumerate(self._stripped_lines): + if line: + index.setdefault(line, []).append( line_no ) + return index + + +MSGS = {'R0801': ('Similar lines in %s files\n%s', + 'Indicates that a set of similar lines has been detected \ + among multiple file. This usually means that the code should \ + be refactored to avoid this duplication.')} + +def report_similarities(sect, stats, old_stats): + """make a layout with some stats about duplication""" + lines = ['', 'now', 'previous', 'difference'] + lines += table_lines_from_stats(stats, old_stats, + ('nb_duplicated_lines', + 'percent_duplicated_lines')) + sect.append(Table(children=lines, cols=4, rheaders=1, cheaders=1)) + + +# wrapper to get a pylint checker from the similar class +class SimilarChecker(BaseChecker, Similar): + """checks for similarities and duplicated code. This computation may be + memory / CPU intensive, so you should disable it if you experiments some + problems. + """ + + __implements__ = (IRawChecker,) + # configuration section name + name = 'similarities' + # messages + msgs = MSGS + # configuration options + # for available dict keys/values see the optik parser 'add_option' method + options = (('min-similarity-lines', + {'default' : 4, 'type' : "int", 'metavar' : '<int>', + 'help' : 'Minimum lines number of a similarity.'}), + ('ignore-comments', + {'default' : True, 'type' : 'yn', 'metavar' : '<y or n>', + 'help': 'Ignore comments when computing similarities.'} + ), + ('ignore-docstrings', + {'default' : True, 'type' : 'yn', 'metavar' : '<y or n>', + 'help': 'Ignore docstrings when computing similarities.'} + ), + ) + # reports + reports = ( ('R0801', 'Duplication', report_similarities), ) + + def __init__(self, linter=None): + BaseChecker.__init__(self, linter) + Similar.__init__(self, min_lines=4, + ignore_comments=True, ignore_docstrings=True) + self.stats = None + + def set_option(self, opt_name, value, action=None, opt_dict=None): + """method called to set an option (registered in the options list) + + overriden to report options setting to Similar + """ + BaseChecker.set_option(self, opt_name, value, action, opt_dict) + if opt_name == 'min-similarity-lines': + self.min_lines = self.config.min_similarity_lines + elif opt_name == 'ignore-comments': + self.ignore_comments = self.config.ignore_comments + elif opt_name == 'ignore-docstrings': + self.ignore_docstrings = self.config.ignore_docstrings + + def open(self): + """init the checkers: reset linesets and statistics information""" + self.linesets = [] + self.stats = self.linter.add_stats(nb_duplicated_lines=0, + percent_duplicated_lines=0) + + def process_module(self, stream): + """process a module + + the module's content is accessible via the stream object + + stream must implements the readlines method + """ + self.append_stream(self.linter.current_name, stream) + + def close(self): + """compute and display similarities on closing (i.e. end of parsing)""" + total = sum([len(lineset) for lineset in self.linesets]) + duplicated = 0 + stats = self.stats + for num, couples in self._compute_sims(): + msg = [] + for lineset, idx in couples: + msg.append("==%s:%s" % (lineset.name, idx)) + msg.sort() + for line in lineset._real_lines[idx:idx+num]: + msg.append(line.rstrip()) + self.add_message('R0801', args=(len(couples), '\n'.join(msg))) + duplicated += num * (len(couples) - 1) + stats['nb_duplicated_lines'] = duplicated + stats['percent_duplicated_lines'] = total and duplicated * 100. / total + + +def register(linter): + """required method to auto register this checker """ + linter.register_checker(SimilarChecker(linter)) + +def usage(status=0): + """display command line usage information""" + print "finds copy pasted blocks in a set of files" + print + print 'Usage: similar [-d|--duplicates min_duplicated_lines] \ +[--ignore-comments] file1...' + sys.exit(status) + +def run(argv=None): + """standalone command line access point""" + argv = argv or sys.argv[1:] + from getopt import getopt + s_opts = 'hd:' + l_opts = ('help', 'duplicates=', 'ignore-comments') + min_lines = 4 + ignore_comments = False + opts, args = getopt(argv, s_opts, l_opts) + for opt, val in opts: + if opt in ('-d', '--duplicates'): + min_lines = int(val) + elif opt in ('-h', '--help'): + usage() + elif opt == '--ignore-comments': + ignore_comments = True + if not args: + usage(1) + sim = Similar(min_lines, ignore_comments) + for filename in args: + sim.append_stream(filename, open(filename)) + sim.run() + +if __name__ == '__main__': + run() diff --git a/checkers/typecheck.py b/checkers/typecheck.py new file mode 100644 index 0000000..e8c4355 --- /dev/null +++ b/checkers/typecheck.py @@ -0,0 +1,181 @@ +# Copyright (c) 2006 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., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +"""try to find more bugs in the code using astng inference capabilities +""" + +__revision__ = "$Id: typecheck.py,v 1.12 2006-04-19 16:16:20 syt Exp $" + +from logilab import astng + +from pylint.interfaces import IASTNGChecker +from pylint.checkers import BaseChecker + +MSGS = { + 'E1101': ('%s %r has no %r member', + 'Used when a class is accessed for an unexistant member.'), + 'E1102': ('%s is not callable', + 'Used when an object being called has been infered to a non \ + callable object'), + 'E1111': ('Assigning to function call which doesn\'t return', + 'Used when an assigment is done on a function call but the \ + infered function doesn\'t return anything.'), + 'W1111': ('Assigning to function call which only returns None', + 'Used when an assigment is done on a function call but the \ + infered function returns nothing but None.'), + } + + +class TypeChecker(BaseChecker): + """try to find bugs in the code using type inference + """ + + __implements__ = (IASTNGChecker,) + + # configuration section name + name = 'typecheck' + # messages + msgs = MSGS + priority = -1 + # configuration options + options = ( + ('ignore-mixin-members', + {'default' : True, 'type' : 'yn', 'metavar': '<y_or_n>', + 'help' : 'Tells wether missing members accessed in mixin \ +class should be ignored. A mixin class is detected if its name ends with \ +"mixin" (case insensitive).'} + ), + + ('zope', + {'default' : False, 'type' : 'yn', 'metavar': '<y_or_n>', + 'help' : 'When zope mode is activated, consider the \ +acquired-members option to ignore access to some undefined attributes.'} + ), + ('acquired-members', + {'default' : ( + 'REQUEST', 'acl_users', 'aq_parent'), + 'type' : 'csv', + 'metavar' : '<members names>', + 'help' : 'List of members which are usually get through \ +zope\'s acquisition mecanism and so shouldn\'t trigger E0201 when accessed \ +(need zope=yes to be considered.'} + ), + ) + + def visit_getattr(self, node): + """check that the accessed attribute exists""" + # if we are running in zope mode, is it an acquired attribute ? + if self.config.zope and node.attrname in self.config.acquired_members: + return + try: + infered = list(node.expr.infer()) + for owner in infered: + # skip yes object + if owner is astng.YES: + continue + # if there is ambiguity, skip None + if len(infered) > 1 and isinstance(owner, astng.Const) \ + and owner.value is None: + continue + # XXX "super" call + owner_name = getattr(owner, 'name', 'None') + if owner_name == 'super' and \ + owner.root().name == '__builtin__': + continue + if getattr(owner, 'type', None) == 'metaclass': + continue + if self.config.ignore_mixin_members \ + and owner_name[-5:].lower() == 'mixin': + continue + #print owner.name, owner.root().name + try: + owner.getattr(node.attrname) + except AttributeError: + # XXX method / function + continue + except astng.NotFoundError: + if isinstance(owner, astng.Instance): + if hasattr(owner, 'has_dynamic_getattr') and owner.has_dynamic_getattr(): + continue + # XXX + if getattr(owner, 'name', None) == 'Values' and \ + owner.root().name == 'optparse': + continue + _type = 'Instance of' + elif isinstance(owner, astng.Module): + _type = 'Module' + else: + _type = 'Class' + self.add_message('E1101', node=node, + args=(_type, owner_name, node.attrname)) + # XXX: stop on the first found + # this is a bad solution to fix func_noerror_socket_member.py + break + except astng.InferenceError: + pass + + def visit_assign(self, node): + """check that if assigning to a function call, the function is + possibly returning something valuable + """ + if not isinstance(node.expr, astng.CallFunc): + return + function_node = self._safe_infer(node.expr.node) + # skip class, generator and uncomplete function definition + if not (isinstance(function_node, astng.Function) and + function_node.root().fully_defined()): + return + if function_node.is_generator() \ + or function_node.is_abstract(pass_is_abstract=False): + return + returns = list(function_node.nodes_of_class(astng.Return, + skip_klass=astng.Function)) + if len(returns) == 0: + self.add_message('E1111', node=node) + else: + for rnode in returns: + if not (isinstance(rnode.value, astng.Name) + and rnode.value.name == 'None'): + break + else: + self.add_message('W1111', node=node) + + def visit_callfunc(self, node): + """check that called method are infered to callable objects + """ + called = self._safe_infer(node.node) + # only function, generator and object defining __call__ are allowed + if called is not None and not called.callable(): + self.add_message('E1102', node=node, args=node.node.as_string()) + + def _safe_infer(self, node): + """return the infered value for the given node. + Return None if inference failed or if there is some ambiguity (more than + one node has been infered) + """ + try: + inferit = node.infer() + value = inferit.next() + except astng.InferenceError: + return + try: + inferit.next() + return # None if there is ambiguity on the infered node + except StopIteration: + return value + +def register(linter): + """required method to auto register this checker """ + linter.register_checker(TypeChecker(linter)) diff --git a/checkers/utils.py b/checkers/utils.py new file mode 100644 index 0000000..cb3a99b --- /dev/null +++ b/checkers/utils.py @@ -0,0 +1,144 @@ +# pylint: disable-msg=W0611 +# +# Copyright (c) 2003-2005 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., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +"""some functions that may be usefull for various checkers +""" + +__revision__ = '$Id: utils.py,v 1.16 2006-03-03 09:25:34 syt Exp $' + +from logilab import astng +from logilab.astng.utils import are_exclusive +try: + # python >= 2.4 + COMP_NODE_TYPES = (astng.ListComp, astng.GenExpr) + FOR_NODE_TYPES = (astng.For, astng.ListCompFor, astng.GenExprFor) +except AttributeError: + COMP_NODE_TYPES = astng.ListComp + FOR_NODE_TYPES = (astng.For, astng.ListCompFor) + +def is_error(node): + """return true if the function does nothing but raising an exception""" + for child_node in node.code.getChildNodes(): + if isinstance(child_node, astng.Raise): + return True + return False + +def is_empty(node): + """return true if the given node does nothing but 'pass'""" + for child_node in node.getChildNodes(): + if isinstance(child_node, astng.Pass): + return True + else: + return False + +builtins = __builtins__.copy() +SPECIAL_BUILTINS = ('__builtins__',) # '__path__', '__file__') + +def is_builtin(name): # was is_native_builtin + """return true if <name> could be considered as a builtin defined by python + """ + if builtins.has_key(name): + return True + if name in SPECIAL_BUILTINS: + return True + return False + +def is_defined_before(var_node, comp_node_types=COMP_NODE_TYPES): + """return True if the variable node is defined by a parent node (list + or generator comprehension, lambda) or in a previous sibling node + one the same line (statement_defining ; statement_using) + """ + varname = var_node.name + _node = var_node.parent + while _node: + if isinstance(_node, comp_node_types): + for ass_node in _node.nodes_of_class(astng.AssName): + if ass_node.name == varname: + return True + elif isinstance(_node, astng.For): + for ass_node in _node.assign.nodes_of_class(astng.AssName): + if ass_node.name == varname: + return True + elif isinstance(_node, (astng.Lambda, astng.Function)): + if varname in _node.argnames: + return True + if getattr(_node, 'name', None) == varname: + return True + break + _node = _node.parent + # possibly multiple statements on the same line using semi colon separator + stmt = var_node.statement() + _node = stmt.previous_sibling() + lineno = stmt.lineno + while _node and _node.lineno == lineno: + for ass_node in _node.nodes_of_class(astng.AssName): + if ass_node.name == varname: + return True + for imp_node in _node.nodes_of_class( (astng.From, astng.Import)): + if varname in [name[1] or name[0] for name in imp_node.names]: + return True + _node = _node.previous_sibling() + return False + +def is_func_default(node): + """return true if the name is used in function default argument's value + """ + parent = node.parent + if parent is None: + return 0 + if isinstance(parent, astng.Function) and parent.defaults and \ + node in parent.defaults: + return 1 + return is_func_default(parent) + +def is_ancestor_name(frame, node): + """return True if `frame` is a astng.Class node with `node` in the + subtree of its bases attribute + """ + try: + bases = frame.bases + except AttributeError: + return False + for base in bases: + if node in base.nodes_of_class(astng.Name): + return True + return False + +def assign_parent(node): + """return the higher parent which is not an AssName, AssTuple or AssList + node + """ + while node and isinstance(node, (astng.AssName, + astng.AssTuple, + astng.AssList)): + node = node.parent + return node + +def overrides_an_abstract_method(class_node, name): + """return True if pnode is a parent of node""" + for ancestor in class_node.ancestors(): + if name in ancestor and isinstance(ancestor[name], astng.Function) and \ + ancestor[name].is_abstract(pass_is_abstract=False): + return True + return False + +def overrides_a_method(class_node, name): + """return True if <name> is a method overriden from an ancestor""" + for ancestor in class_node.ancestors(): + if name in ancestor and isinstance(ancestor[name], astng.Function): + return True + return False diff --git a/checkers/variables.py b/checkers/variables.py new file mode 100644 index 0000000..134da99 --- /dev/null +++ b/checkers/variables.py @@ -0,0 +1,428 @@ +# Copyright (c) 2003-2006 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., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +"""variables checkers for Python code +""" + +__revision__ = "$Id: variables.py,v 1.69 2006-04-19 09:17:40 syt Exp $" + +from copy import copy + +from logilab.common.compat import enumerate +from logilab import astng +from logilab.astng.lookup import builtin_lookup + +from pylint.interfaces import IASTNGChecker +from pylint.checkers import BaseChecker +from pylint.checkers.utils import is_error, is_builtin, is_func_default, \ + is_ancestor_name, assign_parent, are_exclusive, \ + is_defined_before #, is_parent, FOR_NODE_TYPES + + + +MSGS = { + 'E0601': ('Using variable %r before assignment', + 'Used when a local variable is accessed before it\'s \ + assignment.'), + 'E0602': ('Undefined variable %r', + 'Used when an undefined variable is accessed.'), + + 'E0611': ('No name %r in module %r', + 'Used when a name cannot be found in a module.'), + + 'W0601': ('Global variable %r undefined at the module level', + 'Used when a variable is defined through the "global" statement \ + but the variable is not defined in the module scope.'), + 'W0602': ('Using global for %r but no assigment is done', + 'Used when a variable is defined through the "global" statement \ + but no assigment to this variable is done.'), + 'W0603': ('Using the global statement', # W0121 + 'Used when you use the "global" statement to update a global \ + variable. PyLint just try to discourage this \ + usage. That doesn\'t mean you can not use it !'), + 'W0604': ('Using the global statement at the module level', # W0103 + 'Used when you use the "global" statement at the module level \ + since it has no effect'), + 'W0611': ('Unused import %s', + 'Used when an imported module or variable is not used.'), + 'W0612': ('Unused variable %r', + 'Used when a variable is defined but not used.'), + 'W0613': ('Unused argument %r', + 'Used when a function or method argument is not used.'), + + 'W0621': ('Redefining name %r from outer scope (line %s)', + 'Used when a variable\'s name hide a name defined in the outer \ + scope.'), + 'W0622': ('Redefining built-in %r', + 'Used when a variable or function override a built-in.'), + + 'W0631': ('Using possibly undefined loop variable %r', + 'Used when an loop variable (i.e. defined by a for loop or \ + a list comprehension or a generator expression) is used outside \ + the loop.'), + } + +class VariablesChecker(BaseChecker): + """checks for + * unused variables / imports + * undefined variables + * redefinition of variable from builtins or from an outer scope + * use of variable before assigment + """ + + __implements__ = IASTNGChecker + + name = 'variables' + msgs = MSGS + priority = -1 + options = ( + ("init-import", + {'default': 0, 'type' : 'yn', 'metavar' : '<y_or_n>', + 'help' : 'Tells wether we should check for unused import in \ +__init__ files.'}), + ("dummy-variables-rgx", + {'default': ('_|dummy'), + 'type' :'regexp', 'metavar' : '<regexp>', + 'help' : 'A regular expression matching names used \ + for dummy variables (i.e. not used).'}), + ("additional-builtins", + {'default': (), 'type' : 'csv', + 'metavar' : '<comma separated list>', + 'help' : 'List of additional names supposed to be defined in \ +builtins. Remember that you should avoid to define new builtins when possible.' + }), + ) + def __init__(self, linter=None): + BaseChecker.__init__(self, linter) + self._to_consume = None + self._checking_mod_attr = None + self._vars = None + + def visit_module(self, node): + """visit module : update consumption analysis variable + checks globals doesn't overrides builtins + """ + self._to_consume = [(copy(node.locals), {}, 'module')] + self._vars = [] + for name, stmts in node.locals.items(): + if name in ('__name__', '__doc__', '__file__', '__path__') \ + and len(stmts) == 1: + # only the definition added by the astng builder, continue + continue + if self._is_builtin(name): + self.add_message('W0622', args=name, node=stmts[0]) + + def leave_module(self, node): + """leave module: check globals + """ + assert len(self._to_consume) == 1 + not_consumed = self._to_consume.pop()[0] + # don't check unused imports in __init__ files + if not self.config.init_import and node.package: + return + for name, stmts in not_consumed.items(): + stmt = stmts[0] + if isinstance(stmt, astng.Import) or ( + isinstance(stmt, astng.From) and stmt.modname != '__future__'): + self.add_message('W0611', args=name, node=stmt) + del self._to_consume + del self._vars + + def visit_class(self, node): + """visit class: update consumption analysis variable + """ + self._to_consume.append((copy(node.locals), {}, 'class')) + + def leave_class(self, _): + """leave class: update consumption analysis variable + """ + # do not check for not used locals here (no sense) + self._to_consume.pop() + + def visit_lambda(self, node): + """visit lambda: update consumption analysis variable + """ + self._to_consume.append((copy(node.locals), {}, 'lambda')) + + def leave_lambda(self, _): + """leave lambda: update consumption analysis variable + """ + # do not check for not used locals here + self._to_consume.pop() + + def visit_function(self, node): + """visit function: update consumption analysis variable and check locals + """ + globs = node.root().globals + for name, stmt in node.items(): + if globs.has_key(name) and not isinstance(stmt, astng.Global): + line = globs[name][0].lineno + self.add_message('W0621', args=(name, line), node=stmt) + elif self._is_builtin(name): + self.add_message('W0622', args=name, node=stmt) + self._to_consume.append((copy(node.locals), {}, 'function')) + self._vars.append({}) + + def leave_function(self, node): + """leave function: check function's locals are consumed + """ + not_consumed = self._to_consume.pop()[0] + self._vars.pop(0) + is_method = node.is_method() + klass = node.parent.frame() + # don't check arguments of abstract methods or within an interface + if is_method and (klass.type == 'interface' or node.is_abstract()): + return + if is_error(node): + return + authorized_rgx = self.config.dummy_variables_rgx + for name, stmts in not_consumed.items(): + # ignore some special names specified by user configuration + if authorized_rgx.match(name): + continue + # ignore names imported by the global statement + # FIXME: should only ignore them if it's assigned latter + stmt = stmts[0] + if isinstance(stmt, astng.Global): + continue + # care about functions with unknown argument (builtins) + if node.argnames is not None and name in node.argnames: + # don't warn if the first argument of a method is not used + if is_method and node.argnames and name == node.argnames[0]: + continue + # don't check callback arguments + if node.name.startswith('cb_') or \ + node.name.endswith('_cb'): + continue + self.add_message('W0613', args=name, node=node) + else: + self.add_message('W0612', args=name, node=stmt) + + def visit_global(self, node): + """check names imported exists in the global scope""" + frame = node.frame() + if isinstance(frame, astng.Module): + self.add_message('W0604', node=node) + return + module = frame.root() + default_message = True + for name in node.names: + try: + assign_nodes = module.getattr(name) + except astng.NotFoundError: + # unassigned global, skip + assign_nodes = [] + for anode in assign_nodes: + if anode.frame() is frame: + # same scope level assigment + break + else: + # global but no assigment + self.add_message('W0602', args=name, node=node) + default_message = False + if not assign_nodes: + continue + for anode in assign_nodes: + if anode.frame() is module: + # module level assigment + break + else: + # global undefined at the module scope + self.add_message('W0601', args=name, node=node) + default_message = False + if default_message: + self.add_message('W0603', node=node) + + def _loopvar_name(self, node, name): + # filter variables according to node's scope + astmts = [stmt for stmt in node.lookup(name)[1] + if hasattr(stmt, 'ass_type') and + not stmt.statement().parent_of(node)] + # filter variables according their respective scope + if not astmts or astmts[0].statement().parent_of(node): + _astmts = [] + else: + _astmts = astmts[:1] + for i, stmt in enumerate(astmts[1:]): + if astmts[i].statement().parent_of(stmt): + continue + _astmts.append(stmt) + astmts = _astmts + if len(astmts) == 1: + ass = astmts[0].ass_type() + if isinstance(ass, (astng.For, astng.ListCompFor, astng.GenExpr)) \ + and not ass.statement() is node.statement(): + self.add_message('W0631', args=name, node=node) + + def visit_name(self, node): + """check that a name is defined if the current scope and doesn't + redefine a built-in + """ + name = node.name + stmt = node.statement() + frame = stmt.frame() + # if the name node is used as a function default argument's value, then + # start from the parent frame of the function instead of the function + # frame + if is_func_default(node) or is_ancestor_name(frame, node): + start_index = len(self._to_consume) - 2 + else: + start_index = len(self._to_consume) - 1 + # iterates through parent scopes, from the inner to the outer + for i in range(start_index, -1, -1): + to_consume, consumed, scope_type = self._to_consume[i] + # if the current scope is a class scope but it's not the inner + # scope, ignore it. This prevents to access this scope instead of + # the globals one in function members when there are some common + # names + if scope_type == 'class' and i != start_index: + continue + # the name has already been consumed, only check it's not a loop + # variable used outside the loop + if consumed.has_key(name): + self._loopvar_name(node, name) + break + # mark the name as consumed if it's defined in this scope + # (ie no KeyError is raise by "to_consume[name]" + try: + consumed[name] = to_consume[name] + # checks for use before assigment + # FIXME: the last condition should just check attribute access + # is protected by a try: except NameError: (similar to #9219) + defnode = assign_parent(to_consume[name][0]) + if defnode is not None: + defstmt = defnode.statement() + defframe = defstmt.frame() + maybee0601 = True + if not frame is defframe: + maybee0601 = False + elif defframe.parent is None: + # we are at the module level, check the name is not + # defined in builtins + if builtin_lookup(name)[1]: + maybee0601 = False + else: + # we are in a local scope, check the name is not + # defined in global or builtin scope + if defframe.root().lookup(name)[1]: + maybee0601 = False + if (maybee0601 + and stmt.source_line() <= defstmt.source_line() + and not is_defined_before(node) + and not are_exclusive(stmt, defstmt)): + self.add_message('E0601', args=name, node=node) + del to_consume[name] + # check it's not a loop variable used outside the loop + self._loopvar_name(node, name) + break + except KeyError: + continue + else: + # we have not found the name, if it isn't a builtin, that's an + # undefined name ! + if not self._is_builtin(name): + self.add_message('E0602', args=name, node=node) + + def visit_import(self, node): + """check modules attribute accesses""" + for name, _ in node.names: + name_parts = name.split('.') + try: + module = node.infer(name_parts[0], asname=False).next() + except astng.ResolveError: + continue + self._check_module_attrs(node, module, name_parts[1:]) + + def visit_from(self, node): + """check modules attribute accesses""" + name_parts = node.modname.split('.') + try: + module = node.root().import_module(name_parts[0]) + except KeyboardInterrupt: + raise + except: + return + module = self._check_module_attrs(node, module, name_parts[1:]) + if not module: + return + for name, _ in node.names: + if name == '*': + continue + self._check_module_attrs(node, module, name.split('.')) + +## def leave_getattr(self, node): +## """check modules attribute accesses + +## this function is a "leave_" because when parsing 'a.b.c' +## we want to check the innermost expression first. +## """ +## if isinstance(node.expr, astng.Name): +## try: +## module = node.expr.infer().next() +## except astng.InferenceError: +## return +## if not isinstance(module, astng.Module): +## # Not a module, don't check +## return +## elif self._checking_mod_attr is not None: +## module = self._checking_mod_attr +## else: +## return +## self._checking_mod_attr = self._check_module_attrs(node, module, +## [node.attrname]) + +## def leave_default(self, node): +## """by default, reset the _checking_mod_attr attribute""" +## self._checking_mod_attr = None + + def _check_module_attrs(self, node, module, module_names): + """check that module_names (list of string) are accessible through the + given module + if the latest access name corresponds to a module, return it + """ + assert isinstance(module, astng.Module), module + while module_names: + name = module_names.pop(0) + if name == '__dict__': + module = None + break + try: + module = module.getattr(name)[0].infer().next() + except astng.NotFoundError: + self.add_message('E0611', args=(name, module.name), node=node) + return None + except astng.InferenceError: + return None + if module_names: + # FIXME: other message if name is not the latest part of + # module_names ? + modname = module and module.name or '__dict__' + self.add_message('E0611', node=node, + args=('.'.join(module_names), modname)) + return None + if isinstance(module, astng.Module): + return module + return None + + def _is_builtin(self, name): + """return True if the name is defined in the native builtin or + in the user specific builtins + """ + return is_builtin(name) or name in self.config.additional_builtins + + +def register(linter): + """required method to auto register this checker""" + linter.register_checker(VariablesChecker(linter)) diff --git a/config.py b/config.py new file mode 100644 index 0000000..d5b4e9b --- /dev/null +++ b/config.py @@ -0,0 +1,137 @@ +# 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., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +""" Copyright (c) 2003-2005 LOGILAB S.A. (Paris, FRANCE). + http://www.logilab.fr/ -- mailto:contact@logilab.fr + + utilities for PyLint configuration : + _ pylintrc + _ pylint.d (PYLINT_HOME) +""" + +__revision__ = "$Id: config.py,v 1.15 2005-12-13 16:26:35 syt Exp $" + +import pickle +import os +import sys +from os.path import exists, isfile, join, expanduser, abspath + +# pylint home is used to save old runs results ################################ + +if os.environ.has_key('PYLINTHOME'): + PYLINT_HOME = os.environ['PYLINTHOME'] +else: + USER_HOME = expanduser('~') + if USER_HOME == '~': + PYLINT_HOME = ".pylint.d" + else: + PYLINT_HOME = join(USER_HOME, '.pylint.d') + +if not exists(PYLINT_HOME): + try: + os.mkdir(PYLINT_HOME) + except OSError: + print >> sys.stderr, 'Unable to create directory %s' % PYLINT_HOME + +def get_pdata_path(base_name, recurs): + """return the path of the file which should contain old search data for the + given base_name with the given options values + """ + base_name = base_name.replace(os.sep, '_') + return join(PYLINT_HOME, "%s%s%s"%(base_name, recurs, '.stats')) + +def load_results(base): + """try to unpickle and return data from file if it exists and is not + corrupted + + return an empty dictionary if it doesn't exists + """ + data_file = get_pdata_path(base, 1) + try: + return pickle.load(open(data_file)) + except: + return {} + +def save_results(results, base): + """pickle results""" + data_file = get_pdata_path(base, 1) + try: + pickle.dump(results, open(data_file, 'w')) + except OSError: + print >> sys.stderr, 'Unable to create file %s' % data_file + +# location of the configuration file ########################################## + +# is there a pylint rc file in the current directory ? +if exists('pylintrc'): + PYLINTRC = abspath('pylintrc') +elif os.environ.has_key('PYLINTRC') and exists(os.environ['PYLINTRC']): + PYLINTRC = os.environ['PYLINTRC'] +else: + USER_HOME = expanduser('~') + if USER_HOME == '~' or USER_HOME == '/root': + PYLINTRC = ".pylintrc" + else: + PYLINTRC = join(USER_HOME, '.pylintrc') +if not isfile(PYLINTRC): + if isfile('/etc/pylintrc'): + PYLINTRC = '/etc/pylintrc' + else: + PYLINTRC = None + +ENV_HELP = ''' +The following environment variables are used : + * PYLINTHOME + path to the directory where data of persistent run will be stored. If not +found, it defaults to ~/.pylint.d/ or .pylint.d (in the current working +directory) . The current PYLINTHOME is %(PYLINT_HOME)s. + * PYLINTRC + path to the configuration file. If not found, it will use the first +existant file in ~/.pylintrc, /etc/pylintrc. The current PYLINTRC is +%(PYLINTRC)s. +''' % globals() + +# evaluation messages ######################################################### + +def get_note_message(note): + """return a message according to note + note is a float < 10 (10 is the highest note) + """ + assert note <= 10, "Note is %.2f. Either you cheated, or pylint's \ +broken!" % note + if note < 0: + msg = 'You have to do something quick !' + elif note < 1: + msg = 'Hey! This is really dreadful. Or maybe pylint is buggy?' + elif note < 2: + msg = "Come on! You can't be proud of this code" + elif note < 3: + msg = 'Hum... Needs work.' + elif note < 4: + msg = 'Wouldn\'t you be a bit lazy?' + elif note < 5: + msg = 'A little more work would make it acceptable.' + elif note < 6: + msg = 'Just the bare minimum. Give it a bit more polish. ' + elif note < 7: + msg = 'This is okay-ish, but I\'m sure you can do better.' + elif note < 8: + msg = 'If you commit now, people should not be making nasty \ +comments about you on c.l.py' + elif note < 9: + msg = 'That\'s pretty good. Good work mate.' + elif note < 10: + msg = 'So close to being perfect...' + else: + msg = 'Wow ! Now this deserves our uttermost respect.\nPlease send \ +your code to python-projects@logilab.org' + return msg diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..c28605e --- /dev/null +++ b/debian/changelog @@ -0,0 +1,205 @@ +pylint (0.11.0-1) unstable; urgency=low + + * new upstream release, depending on python-astng 0.16 + + -- Sylvain Thénault <sylvain.thenault@logilab.fr> Wed, 19 Apr 2006 18:10:47 +0200 + +pylint (0.10.0-1) unstable; urgency=low + + * new upstream release, depending on python-astng 0.15 + + -- Sylvain Thénault <sylvain.thenault@logilab.fr> Mon, 6 Mar 2006 09:43:19 +0100 + +pylint (0.9.0-3) unstable; urgency=low + + * Added missing provides/replaces/conflicts on pylint-test (closes: #352316) + + -- Alexandre Fayolle <afayolle@debian.org> Mon, 13 Feb 2006 10:07:26 +0100 + +pylint (0.9.0-2) unstable; urgency=low + + * Build a single package which installs modules in /usr/lib/site-python + (closes: #351130) + * Remove duplication from man page (closes: #349689) + * Fixed typo in control file + * upload new release to Debian + + -- Alexandre Fayolle <afayolle@debian.org> Fri, 10 Feb 2006 16:03:37 +0100 + +pylint (0.9.0-1) unstable; urgency=low + + * fix false positive with staticmethod used on a metaclass (closes: #341121) + * reorganization to install into site-python, removing the need for + pythonX.X- packages and for the pylint-common and pylint-test packages + + -- Sylvain Thénault <sylvain.thenault@logilab.fr> Tue, 10 Jan 2006 14:19:57 +0100 + +pylint (0.8.1-1) unstable; urgency=low + + * added missing dependancy to logilab-astng + * added missing .docs and .examples files + * update control'standards-version to 3.6.2 + * fixed FSF address in the copyright file + * new upstream release + + -- Sylvain Thénault <sylvain.thenault@logilab.fr> Mon, 7 Nov 2005 15:40:52 +0100 + +pylint (0.8.0-1) unstable; urgency=low + + * new upstream release + + -- Sylvain Thénault <sylvain.thenault@logilab.fr> Fri, 21 Oct 2005 18:44:24 +0200 + +pylint (0.7.0-1) unstable; urgency=low + + * new upstream release (closes: #310957) + + -- Sylvain Thénault <sylvain.thenault@logilab.fr> Fri, 27 May 2005 11:17:44 +0200 + +pylint (0.6.4-1) unstable; urgency=low + + * new upstream release + * added man page for pylint + + -- Sylvain Thénault <sylvain.thenault@logilab.fr> Thu, 14 Apr 2005 12:02:15 +0200 + +pylint (0.6.3-1) unstable; urgency=low + + * new upstream release + + -- Sylvain Thénault <sylvain.thenault@logilab.fr> Thu, 24 Feb 2005 17:44:35 +0100 + +pylint (0.6.2-1) unstable; urgency=low + + * new upstream release + + -- Sylvain Thénault <sylvain.thenault@logilab.fr> Wed, 16 Feb 2005 12:00:47 +0100 + +pylint (0.6.1-1) unstable; urgency=low + + * new upstream release + * added option to specify rc file location (closes: #265159) + + -- Sylvain Thénault <sylvain.thenault@logilab.fr> Fri, 4 Feb 2005 16:48:09 +0100 + +pylint (0.6.0-1) unstable; urgency=low + + * new upstream release + * build package for python 2.4 + * remove unused directory from logilab-common.dirs + * updated copyright + + -- Sylvain Thénault <sylvain.thenault@logilab.fr> Thu, 20 Jan 2005 18:06:29 +0100 + +pylint (0.5.0-2) unstable; urgency=low + + * Patched pylint.el using latest upsrteam CVS (closes: #280870) + + -- Alexandre Fayolle <afayolle@debian.org> Mon, 15 Nov 2004 10:59:51 +0100 + +pylint (0.5.0-1) unstable; urgency=low + + * use Build-depends instead of Build-depends-indep in control + * new upstream release + * updated debian/watch file to version 2 + + -- Alexandre Fayolle <afayolle@debian.org> Tue, 9 Nov 2004 16:22:47 +0100 + +pylint (0.4.2-2) unstable; urgency=low + + * fixed typos in debian/control (closes: #265156) + * updated description of pylint-test + * changed dependency on pylint-common to a recommendation (closes: #265157) + * updated maintainer address + + -- Alexandre Fayolle <afayolle@debian.org> Sun, 15 Aug 2004 10:39:06 +0200 + +pylint (0.4.2-1) unstable; urgency=low + + * new upstream release + * initial upload to Debian (closes: #258235) + + -- Alexandre Fayolle <alexandre.fayolle@logilab.fr> Thu, 8 Jul 2004 12:54:18 +0200 + +pylint (0.4.0-1) unstable; urgency=low + + * new upstream release + + -- Sylvain Thénault <sylvain.thenault@logilab.fr> Mon, 10 May 2004 17:03:04 +0200 + +pylint (0.3.3-1) unstable; urgency=low + + * new upstream release + * emacs lisp for pylint in a new pylint-common package + + -- Sylvain Thénault <sylvain.thenault@logilab.fr> Mon, 16 Feb 2004 18:09:23 +0100 + +pylint (0.3.2-1) unstable; urgency=low + + * new upstream release + + -- Sylvain Thénault <sylvain.thenault@logilab.fr> Tue, 23 Dec 2003 14:56:04 +0100 + +pylint (0.3.1-1) unstable; urgency=low + + * new upstream release + + -- Sylvain Thénault <sylvain.thenault@logilab.fr> Fri, 5 Dec 2003 16:20:44 +0100 + +pylint (0.3.0-1) unstable; urgency=low + + * new upstream release + * depends on logilab.common >= 0.4 + * build depends on debhelper >= 4.0 + + -- Sylvain Thénault <sylvain.thenault@logilab.fr> Wed, 19 Nov 2003 11:07:45 +0100 + +pylint (0.2.1-2) unstable; urgency=low + + * fixed dependency on logilab-common (>=0.3.4) since earlier versions + caused bugs with some python2.3 code + * included sample pylintrc files with the documentation + * added documentation that had disappeared in the previous 0.2.1-1 + * only puts html documentation in doc/html/, all others in doc/ + + -- Sylvain Thénault <sylvain.thenault@logilab.fr> Fri, 10 Oct 2003 12:11:53 +0200 + +pylint (0.2.1-1) unstable; urgency=low + + * new upstream release + * package renamed to pylint instead of logilab-pylint + * move tests in a separated package + + -- Sylvain Thénault <sylvain.thenault@logilab.fr> Fri, 10 Oct 2003 09:39:22 +0200 + +logilab-pylint (0.2.0-1) unstable; urgency=low + + * new upstream release + * dropped python2.1 support + + -- Sylvain Thénault <sylvain.thenault@logilab.fr> Fri, 12 Sep 2003 18:26:15 +0200 + +logilab-pylint (0.1.2-1) unstable; urgency=low + + * new upstream release + + -- Sylvain Thénault <sylvain.thenault@logilab.fr> Wed, 11 Jun 2003 15:21:44 +0200 + +logilab-pylint (0.1.1-2) unstable; urgency=low + + * fix dependencie to logilab.common + + -- Sylvain Thénault <sylvain.thenault@logilab.fr> Wed, 4 Jun 2003 18:07:45 +0200 + +logilab-pylint (0.1.1-1) unstable; urgency=low + + * New upstream release + + -- Sylvain Thénault <sylvain.thenault@logilab.fr> Mon, 19 May 2003 15:10:25 +0200 + +logilab-pylint (0.1.0-1) unstable; urgency=low + + * Initial Release. + + -- Sylvain Thénault <sylvain.thenault@logilab.fr> Wed, 23 Apr 2003 14:42:05 +0200 + diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..25560a0 --- /dev/null +++ b/debian/control @@ -0,0 +1,30 @@ +Source: pylint +Section: python +Priority: optional +Maintainer: Sylvain Thenault <sylvain.thenault@logilab.fr> +Uploaders: Alexandre Fayolle <afayolle@debian.org> +Build-Depends: debhelper (>= 4.0.0), python-dev +Standards-Version: 3.6.2 + +Package: pylint +Architecture: all +Depends: python, python-logilab-common (>= 0.13.0), python-logilab-astng (>= 0.16.0), python-tk, emacsen-common +Provides: python2.2-pylint, python2.3-pylint, python2.4-pylint, pylint-common, pylint-test +Conflicts: python2.2-pylint, python2.3-pylint, python2.4-pylint, pylint-common, pylint-test +Replaces: python2.2-pylint, python2.3-pylint, python2.4-pylint, pylint-common, pylint-test +Description: python code static checker + Pylint is a Python source code analyzer which looks for programming + errors, helps enforcing a coding standard and sniffs for some code + smells (as defined in Martin Fowler's Refactoring book) + . + Pylint can be seen as another PyChecker since nearly all tests you + can do with PyChecker can also be done with Pylint. However, Pylint + offers some more features, like checking length of lines of code, + checking if variable names are well-formed according to your coding + standard, or checking if declared interfaces are truly implemented, + and much more. + . + Additionally, it is possible to write plugins to add your own checks. + . + Homepage: http://www.logilab.org/projects/pylint + diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..e574a13 --- /dev/null +++ b/debian/copyright @@ -0,0 +1,29 @@ +This package was debianized by Sylvain Thenault <sylvain.thenault@logilab.fr> Sat, 13 Apr 2002 19:05:23 +0200. + +It was downloaded from ftp://ftp.logilab.org/pub/pylint + +Upstream Author: + + Sylvain Thenault <sylvain.thenault@logilab.fr> + +Copyright: + +Copyright (c) 2003-2006 Sylvain Thenault (thenault@gmail.com). +Copyright (c) 2003-2006 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 St, Fifth Floor, Boston, MA 02110-1301, USA. + +On Debian systems, the complete text of the GNU General Public License +may be found in '/usr/share/common-licenses/GPL'. diff --git a/debian/pylint.dirs b/debian/pylint.dirs new file mode 100644 index 0000000..3b56eda --- /dev/null +++ b/debian/pylint.dirs @@ -0,0 +1,7 @@ +usr/lib/site-python +usr/lib/site-python/pylint +usr/share/doc/pylint +usr/share/emacs/site-lisp/ +usr/share/emacs/site-lisp/pylint +usr/share/doc/pylint +usr/share/doc/pylint/test diff --git a/debian/pylint.docs b/debian/pylint.docs new file mode 100644 index 0000000..27ad787 --- /dev/null +++ b/debian/pylint.docs @@ -0,0 +1,6 @@ +doc/features.html +doc/quickstart.html +doc/FAQ.html +doc/FAQ.txt +doc/quickstart.txt +doc/features.txt diff --git a/debian/pylint.emacsen-install b/debian/pylint.emacsen-install new file mode 100644 index 0000000..66a8480 --- /dev/null +++ b/debian/pylint.emacsen-install @@ -0,0 +1,45 @@ +#! /bin/sh -e +# /usr/lib/emacsen-common/packages/install/#PACKAGE# + +# Written by Jim Van Zandt <jrv@vanzandt.mv.com>, borrowing heavily +# from the install scripts for gettext by Santiago Vila +# <sanvila@ctv.es> and octave by Dirk Eddelbuettel <edd@debian.org>. + +FLAVOR=$1 +PACKAGE=pylint + +if [ ${FLAVOR} = emacs ]; then exit 0; fi + +echo install/${PACKAGE}: Handling install for emacsen flavor ${FLAVOR} + +#FLAVORTEST=`echo $FLAVOR | cut -c-6` +#if [ ${FLAVORTEST} = xemacs ] ; then +# SITEFLAG="-no-site-file" +#else +# SITEFLAG="--no-site-file" +#fi +FLAGS="${SITEFLAG} -q -batch -l path.el -f batch-byte-compile" + +ELDIR=/usr/share/emacs/site-lisp/${PACKAGE} +ELCDIR=/usr/share/${FLAVOR}/site-lisp/${PACKAGE} + +# Install-info-altdir does not actually exist. +# Maybe somebody will write it. +if test -x /usr/sbin/install-info-altdir; then + echo install/${PACKAGE}: install Info links for ${FLAVOR} + install-info-altdir --quiet --section "" "" --dirname=${FLAVOR} /usr/info/${PACKAGE}.info.gz +fi + +install -m 755 -d ${ELCDIR} +cd ${ELDIR} +FILES=`echo *.el` +cp ${FILES} ${ELCDIR} +cd ${ELCDIR} + +cat << EOF > path.el +(setq load-path (cons "." load-path) byte-compile-warnings nil) +EOF +${FLAVOR} ${FLAGS} ${FILES} +rm -f *.el path.el + +exit 0 diff --git a/debian/pylint.emacsen-remove b/debian/pylint.emacsen-remove new file mode 100644 index 0000000..9795dc4 --- /dev/null +++ b/debian/pylint.emacsen-remove @@ -0,0 +1,14 @@ +#!/bin/sh -e + +FLAVOR=$1 +PACKAGE=pylint + +if [ ${FLAVOR} != emacs ]; then + if test -x /usr/sbin/install-info-altdir; then + echo remove/${PACKAGE}: removing Info links for ${FLAVOR} + install-info-altdir --quiet --remove --dirname=${FLAVOR} /usr/info/${PACKAGE}.info.gz + fi + + echo remove/${PACKAGE}: purging byte-compiled files for ${FLAVOR} + rm -rf /usr/share/${FLAVOR}/site-lisp/${PACKAGE} +fi diff --git a/debian/pylint.emacsen-startup b/debian/pylint.emacsen-startup new file mode 100644 index 0000000..646bd81 --- /dev/null +++ b/debian/pylint.emacsen-startup @@ -0,0 +1,17 @@ +;; -*-emacs-lisp-*- +;; +;; Emacs startup file for the Debian GNU/Linux pylint package +;; +;; Originally contributed by Nils Naumann <naumann@unileoben.ac.at> +;; Modified by Dirk Eddelbuettel <edd@debian.org> +;; Adapted for dh-make by Jim Van Zandt <jrv@vanzandt.mv.com> + +;; The pylint package follows the Debian/GNU Linux 'emacsen' policy and +;; byte-compiles its elisp files for each 'emacs flavor' (emacs19, +;; xemacs19, emacs20, xemacs20...). The compiled code is then +;; installed in a subdirectory of the respective site-lisp directory. +;; We have to add this to the load-path: +(setq load-path (cons (concat "/usr/share/" + (symbol-name flavor) + "/site-lisp/pylint") load-path)) +(load-library "pylint") diff --git a/debian/pylint.examples b/debian/pylint.examples new file mode 100644 index 0000000..e39721e --- /dev/null +++ b/debian/pylint.examples @@ -0,0 +1 @@ +examples/* diff --git a/debian/pylint.manpages b/debian/pylint.manpages new file mode 100644 index 0000000..12b9a33 --- /dev/null +++ b/debian/pylint.manpages @@ -0,0 +1 @@ +man/pylint.1 diff --git a/debian/pylint.postinst b/debian/pylint.postinst new file mode 100644 index 0000000..aa64362 --- /dev/null +++ b/debian/pylint.postinst @@ -0,0 +1,24 @@ +#! /bin/sh -e +# + + + +# precompile python files +VERSION=2.3 +PACKAGEDIR=/usr/lib/site-python/pylint +case "$1" in + configure|abort-upgrade|abort-remove|abort-deconfigure) + python$VERSION -O /usr/lib/python$VERSION/compileall.py -q $PACKAGEDIR + python$VERSION /usr/lib/python$VERSION/compileall.py -q $PACKAGEDIR + ;; + + *) + echo "postinst called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + + +#DEBHELPER# + +exit 0 diff --git a/debian/pylint.prerm b/debian/pylint.prerm new file mode 100644 index 0000000..dbe2144 --- /dev/null +++ b/debian/pylint.prerm @@ -0,0 +1,14 @@ +#! /bin/sh -e +# + +# remove .pyc and .pyo files +dpkg --listfiles pylint | + awk '$0~/\.py$/ {print $0"c\n" $0"o"}' | + xargs rm -f >&2 + + + + +#DEBHELPER# + +exit 0 diff --git a/debian/rules b/debian/rules new file mode 100644 index 0000000..35284a4 --- /dev/null +++ b/debian/rules @@ -0,0 +1,84 @@ +#!/usr/bin/make -f +# Sample debian/rules that uses debhelper. +# GNU copyright 1997 to 1999 by Joey Hess. +# +# adapted by Logilab for automatic generation by debianize +# (part of the devtools project, http://www.logilab.org/projects/devtools) +# +# Copyright (c) 2003-2005 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr + +# Uncomment this to turn on verbose mode. +#export DH_VERBOSE=1 + +# This is the debhelper compatability version to use. +export DH_COMPAT=4 + + + +build: build-stamp +build-stamp: + dh_testdir + python setup.py -q build + touch build-stamp + +clean: + dh_testdir + dh_testroot + rm -f build-stamp configure-stamp + rm -rf build + find . -name "*.pyc" | xargs rm -f + rm -f changelog.gz + dh_clean + +install: build + dh_testdir + dh_testroot + dh_clean -k + dh_installdirs + python setup.py -q install_lib --no-compile --install-dir=debian/pylint/usr/lib/site-python + python setup.py -q install_headers --install-dir=debian/pylint/usr/include/ + python setup.py -q install_scripts --install-dir=debian/pylint/usr/bin/ + # remove test directory (installed in a separated package) + rm -rf debian/pylint/usr/lib/site-python/pylint/test + if head -1 debian/pylint/usr/bin/pylint | grep "^#! */usr/bin" | grep "python" >/dev/null ; then \ + sed -i "s@^#! */usr/bin/env \+python\$$@#!/usr/bin/python@" debian/pylint/usr/bin/pylint; \ + fi + chmod a+x debian/pylint/usr/bin/pylint + if head -1 debian/pylint/usr/bin/pylint-gui | grep "^#! */usr/bin" | grep "python" >/dev/null ; then \ + sed -i "s@^#! */usr/bin/env \+python\$$@#!/usr/bin/python@" debian/pylint/usr/bin/pylint-gui; \ + fi + chmod a+x debian/pylint/usr/bin/pylint-gui + if head -1 debian/pylint/usr/bin/symilar | grep "^#! */usr/bin" | grep "python" >/dev/null ; then \ + sed -i "s@^#! */usr/bin/env \+python\$$@#!/usr/bin/python@" debian/pylint/usr/bin/symilar; \ + fi + chmod a+x debian/pylint/usr/bin/symilar + install -m 644 elisp/pylint.el debian/pylint/usr/share/emacs/site-lisp/pylint/ + # install tests + (cd test && find . -type f -not \( -path '*/CVS/*' -or -name '*.pyc' \) -exec install -D --mode=644 {} ../debian/pylint/usr/share/doc/pylint/test/{} \;) + + +# Build architecture-independent files here. +binary-indep: build install + dh_testdir + dh_testroot + dh_install -i + gzip -9 -c ChangeLog > changelog.gz + dh_installchangelogs -i + dh_installexamples -i + dh_installdocs -i README TODO changelog.gz + dh_installman -i + dh_installemacsen + dh_link -i + dh_compress -i -X.py -X.ini -X.xml -Xtest + dh_fixperms -i + dh_installdeb -i + dh_gencontrol -i + dh_md5sums -i + dh_builddeb -i + + + +binary: binary-indep +.PHONY: build clean binary binary-indep + diff --git a/debian/watch b/debian/watch new file mode 100644 index 0000000..52cc0a2 --- /dev/null +++ b/debian/watch @@ -0,0 +1,3 @@ +version=2 +ftp://ftp.logilab.org/pub/pylint/pylint-(.*)\.tar\.gz debian uupdate + diff --git a/doc/FAQ.txt b/doc/FAQ.txt new file mode 100644 index 0000000..b28f83e --- /dev/null +++ b/doc/FAQ.txt @@ -0,0 +1,154 @@ +Frequently Asked Questions / Usage tips for PyLint +================================================== + + +Question: + Is it possible to give file as argument to pylint, instead of module ? + +Answer: + pylint expects the name of a package or module as argument. As a convenience, + you can give to it a file name if it's possible to guess a module name from + the file's path, using the python path. Some examples : + + "pylint mymodule.py" should always works since the current working + directory is automatically added on top of the python path + + "pylint directory/mymodule.py" will work if "directory" is a python + package (i.e. has an __init__.py file) or if "directory" is in the + python path. + + "pylint /whatever/directory/mymodule.py" will work if either: + + - "/whatever/directory" is in the python path + + - your cwd is "/whatever/directory" + + - "directory" is a python package and "/whatever" is in the python + path + + - "directory" is a python package and your cwd is "/whatever" + and so on... + + + +Question: + I'm using psyobj from psyco_ and get a lot of spurious "unused variables + messages". Is it normal ? + +Answer: + Yes. That's actually due to a bug in psyco, making the locals() + function for objects inheriting from *psyobj* returning an empty + dictionary. For the moment, the only way to fix this is to use the + PYLINT_IMPORT environment variable to not use psyco during pylint + checking. Sample code :: + + import os + try: + if os.environ.has_key('PYLINT_IMPORT'): + raise ImportError() + from psyco.classes import psyobj + except ImportError: + class psyobj: + pass + + NOTICE: this problem should not occurs with pylint >= 0.5 since from + this version pylint is not looking anymore for information in living + objects (i.e. it doesn't anymore import analysed modules) + + + +Question: + I've a function / method which is a callback where I do not have any + control on received argument, and pylint is complaining about unused + arguments. What can I do to avoid those warnings ? + +Answer: + prefix (ui) the callback's name by `cb_`, as in cb_onclick(...). By + doing so arguments usage won't be checked. Another solution is to + use one of the name defined in the "dummy-variables" configuration + variable for unused argument ("_" and "dummy" by default). + + + +Question: + When pylint is considering a class as an interface ? + +Answer: + A class is considered as an interface if there is a class named + "Interface" somewhere in it ancestor's tree. + + + +Question: + When pylint is considering that a class is implementing a given + interface ? + +Answer: + Pylint is using the Zope 2 interfaces conventions, and so is + considering that a class is implementing interfaces listed in its + __implements__ attribute. + + + +Question: + When pylint is considering a class as an abstract class ? + +Answer: + A class is considered as an abstract class if at least one of its + methods is doing nothing but raising NotImplementedError + + + +Question: + Is there some way to disable some message for a particular module + only ? + +Answer: + Yes, you can disable or enable (globally disabled) message at the + module level by adding the corresponding option in a comment at the + top of the file: :: + + # pylint: disable-msg=W0401, E0202 + # pylint: enable-msg=C0302 + + + +Question: + I've a mixin class relying on attributes of the mixed class, and I + would like to not have the "access to undefined member" message on + this class. Is it possible ? + +Answer: + Yes :o) To do so you have to set the ignore-mixin-members option to + "yes" (this is the default value) and to name your mixin class with + a name which ends with "mixin" (whatever case) + + + +Question: + Is it possible to locally disabling a particular message for a block + of code or for a single line of code ? +Answer: + Yes, this feature has been added in pylint 0.11. This may be done by + adding "#pylint: disable-msg=W0123,E4567" at the desired block level + or at the end of the desired line of code + + + +Question: + Where are stored persistent data necessary to make comparison between + to successive run ? + +Answer: + Analysis data are stored as pickle file in a directory which is + localized using the following rules: + + * value of the PYLINTHOME environment variable if set + + * ".pylint.d" subdirectory of the user's home directory if it is found + (not always findable on Windows platforms) + + * ".pylint.d" directory in the current directory + + +.. _psyco: http://psyco.sf.net diff --git a/doc/features.txt b/doc/features.txt new file mode 100644 index 0000000..2cc41e0 --- /dev/null +++ b/doc/features.txt @@ -0,0 +1,678 @@ +PyLint features +=============== + +.. contents:: + +Master +------ + +Description +~~~~~~~~~~~ +lint Python modules using external checkers. + +This is the main checker controling the other ones and the reports +generation. It is itself both a raw checker and an astng checker in order +to: +* handle message activation / deactivation at the module level +* handle some basic but necessary stats'data (number of classes, methods...) + +This checker also defines the following reports: +* R0001: Total errors / warnings +* R0002: % errors / warnings by module +* R0003: Messages +* R0004: Global evaluation + +Messages +~~~~~~~~ +I0001: + Used to inform that a built-in module has not been checked using the raw + checkers. This message belongs to the master checker. + +I0010: + Used when an inline option is either badly formatted or can't be used inside + modules. This message belongs to the master checker. + +I0011: + Used when an inline option disable a message or a messages category. This + message belongs to the master checker. + +I0012: + Used when an inline option enable a message or a messages category. This + message belongs to the master checker. + +E0001: + Used when a syntax error is raised for a module. This message belongs to the + master checker. + +E0011: + Used when an unknown inline option is encountered. This message belongs to the + master checker. + +E0012: + Used when an bad value for an inline option is encountered. This message + belongs to the master checker. + +F0001: + Used when an error occured preventing the analyzing of a module (unable to + find it for instance). This message belongs to the master checker. + +F0002: + Used when an unexpected error occured while building the ASTNG representation. + This is usually accomopagned by a traceback. Please report such errors ! This + message belongs to the master checker. + +F0003: + Used to indicate that the user asked to analyze a builtin module which has + been skipped. This message belongs to the master checker. + + + +Basic +----- + +Description +~~~~~~~~~~~ +checks for : +* doc strings +* modules / classes / functions / methods / arguments / variables name +* number of arguments, local variables, branchs, returns and statements in +functions, methods +* required module attributes +* dangerous default values as arguments +* redefinition of function / method / class +* uses of the global statement + +This checker also defines the following reports: +* R0101: Statistics by type + +Messages +~~~~~~~~ +C0102: + Used when the name is listed in the black list (unauthorized names). This + message belongs to the basic checker. + +C0103: + Used when the name doesn't match the regular expression associated to its type + (constant, variable, class...). This message belongs to the basic checker. + +C0111: + Used when a module, function, class or method has no docstring. Some special + methods like __init__ doesn't necessary require a docstring. This message + belongs to the basic checker. + +C0112: + Used when a module, function, class or method has an empty docstring (it would + be to easy ;). This message belongs to the basic checker. + +C0121: + Used when an attribute required for modules is missing. This message belongs + to the basic checker. + +W0101: + Used when there is some code behind a "return" or "raise" statement, which + will never be accessed. This message belongs to the basic checker. + +W0102: + Used when a mutable value as list or dictionary is detected in a default value + for an argument. This message belongs to the basic checker. + +W0104: + Used when a statement doesn't have (or at least seems to) any effect. This + message belongs to the basic checker. + +W0122: + Used when you use the "exec" statement, to discourage its usage. That doesn't + mean you can not use it ! This message belongs to the basic checker. + +W0141: + Used when a black listed builtin function is used (see the bad-function + option). Usual black listed functions are the ones like map, or filter , where + Python offers now some cleaner alternative like list comprehension. This + message belongs to the basic checker. + +W0142: + Used when a function or method is called using `*args` or `**kwargs` to + dispatch arguments. This doesn't improve readility and should be used with + care. This message belongs to the basic checker. + +E0101: + Used when the special class method __ini__ has an explicit return value. This + message belongs to the basic checker. + +E0102: + Used when a function / class / method is redefined. This message belongs to + the basic checker. + +E0103: + Used when break or continue keywords are used outside a loop. This message + belongs to the basic checker. + + + +Variables +--------- + +Description +~~~~~~~~~~~ +checks for +* unused variables / imports +* undefined variables +* redefinition of variable from builtins or from an outer scope +* use of variable before assigment + + +Messages +~~~~~~~~ +W0601: + Used when a variable is defined through the "global" statement but the + variable is not defined in the module scope. This message belongs to the + variables checker. + +W0602: + Used when a variable is defined through the "global" statement but no + assigment to this variable is done. This message belongs to the variables + checker. + +W0603: + Used when you use the "global" statement to update a global variable. PyLint + just try to discourage this usage. That doesn't mean you can not use it ! This + message belongs to the variables checker. + +W0604: + Used when you use the "global" statement at the module level since it has no + effect This message belongs to the variables checker. + +W0611: + Used when an imported module or variable is not used. This message belongs to + the variables checker. + +W0612: + Used when a variable is defined but not used. This message belongs to the + variables checker. + +W0613: + Used when a function or method argument is not used. This message belongs to + the variables checker. + +W0621: + Used when a variable's name hide a name defined in the outer scope. This + message belongs to the variables checker. + +W0622: + Used when a variable or function override a built-in. This message belongs to + the variables checker. + +W0631: + Used when an loop variable (i.e. defined by a for loop or a list comprehension + or a generator expression) is used outside the loop. This message belongs to + the variables checker. + +E0601: + Used when a local variable is accessed before it's assignment. This message + belongs to the variables checker. + +E0602: + Used when an undefined variable is accessed. This message belongs to the + variables checker. + +E0611: + Used when a name cannot be found in a module. This message belongs to the + variables checker. + + + +Typecheck +--------- + +Description +~~~~~~~~~~~ +try to find bugs in the code using type inference + + +Messages +~~~~~~~~ +W1111: + Used when an assigment is done on a function call but the infered function + returns nothing but None. This message belongs to the typecheck checker. + +E1101: + Used when a class is accessed for an unexistant member. This message belongs + to the typecheck checker. + +E1102: + Used when an object being called has been infered to a non callable object + This message belongs to the typecheck checker. + +E1111: + Used when an assigment is done on a function call but the infered function + doesn't return anything. This message belongs to the typecheck checker. + + + +Design +------ + +Description +~~~~~~~~~~~ +checks for sign of poor/misdesign: +* number of methods, attributes, local variables... +* size, complexity of functions, methods + + +Messages +~~~~~~~~ +R0901: + Used when class has too many parent classes. This message belongs to the + design checker. + +R0902: + Used when class has too many instance attributes. This message belongs to the + design checker. + +R0903: + Used when class has not enough public methods. This message belongs to the + design checker. + +R0904: + Used when class has too many public methods. This message belongs to the + design checker. + +R0911: + Used when a function or method has too many return statement. This message + belongs to the design checker. + +R0912: + Used when a function or method has too many branches. This message belongs to + the design checker. + +R0913: + Used when a function or method takes too many arguments. This message belongs + to the design checker. + +R0914: + Used when a function or method has too many local variables. This message + belongs to the design checker. + +R0915: + Used when a function or method has too many statements. You should then split + it in smaller functions / methods. This message belongs to the design checker. + +R0921: + Used when an abstract class is not used as ancestor anywhere. This message + belongs to the design checker. + +R0922: + Used when an abstract class is used less than X times as ancestor. This + message belongs to the design checker. + +R0923: + Used when an interface class is not implemented anywhere. This message belongs + to the design checker. + + + +Classes +------- + +Description +~~~~~~~~~~~ +checks for : +* methods without self as first argument +* overriden methods signature +* access only to existant members via self +* attributes not defined in the __init__ method +* supported interfaces implementation +* unreachable code + + +Messages +~~~~~~~~ +C0202: + Used when a class method has an attribute different than "cls" as first + argument, to easily differentiate them from regular instance methods. This + message belongs to the classes checker. + +C0203: + Used when a metaclass method has an attribute different the "mcs" as first + argument. This message belongs to the classes checker. + +R0201: + Used when a method doesn't use its bound instance, and so could be written as + a function. This message belongs to the classes checker. + +W0201: + Used when an instance attribute is defined outside the __init__ method. This + message belongs to the classes checker. + +W0211: + Used when a static method has "self" or "cls" as first argument. This message + belongs to the classes checker. + +W0221: + Used when a method has a different number of arguments than in the implemented + interface or in an overriden method. This message belongs to the classes + checker. + +W0222: + Used when a method signature is different than in the implemented interface or + in an overriden method. This message belongs to the classes checker. + +W0223: + Used when an abstract method (ie raise NotImplementedError) is not overriden + in concrete class. This message belongs to the classes checker. + +W0231: + Used when an ancestor class method has an __init__ method which is not called + by a derived class. This message belongs to the classes checker. + +W0232: + Used when a class has no __init__ method, neither its parent classes. This + message belongs to the classes checker. + +W0233: + Used when an __init__ method is called on a class which is not in the direct + ancestors for the analysed class. This message belongs to the classes checker. + +E0202: + Used when a class defines a method which is hiden by an instance attribute + from an ancestor class. This message belongs to the classes checker. + +E0203: + Used when an instance member is accessed before it's actually assigned. This + message belongs to the classes checker. + +E0211: + Used when a method which should have the bound instance as first argument has + no argument defined. This message belongs to the classes checker. + +E0213: + Used when a method has an attribute different the "self" as first argument. + This message belongs to the classes checker. + +E0221: + Used when a class claims to implement an interface which is not a class. This + message belongs to the classes checker. + +E0222: + Used when a method declared in an interface is missing from a class + implementing this interface This message belongs to the classes checker. + +F0202: + 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. + This message belongs to the classes checker. + +F0220: + Used when a PyLint as failed to find interfaces implemented by a class This + message belongs to the classes checker. + + + +Imports +------- + +Description +~~~~~~~~~~~ +checks for +* external modules dependencies +* relative / wildcard imports +* cyclic imports +* uses of deprecated modules + +This checker also defines the following reports: +* R0401: External dependencies +* R0402: Modules dependencies graph + +Messages +~~~~~~~~ +R0401: + Used when a cyclic import between two or more modules is detected. This + message belongs to the imports checker. + +W0401: + Used when `from module import *` is detected. This message belongs to the + imports checker. + +W0402: + Used a module marked as deprecated is imported. This message belongs to the + imports checker. + +W0403: + Used when an import relative to the package directory is detected. This + message belongs to the imports checker. + +W0404: + Used when a module is reimported multiple times. This message belongs to the + imports checker. + +W0406: + Used when a module is importing itself. This message belongs to the imports + checker. + +W0410: + Python 2.5 and greater require __future__ import to be the first non docstring + statement in the module. This message belongs to the imports checker. + +F0401: + Used when pylint has been unable to import a module. This message belongs to + the imports checker. + + + +Newstyle +-------- + +Description +~~~~~~~~~~~ +checks for usage of new style capabilities on old style classes and +other new/old styles conflicts problems +* use of property, __slots__, super +* "super" usage +* raising a new style class as exception + + +Messages +~~~~~~~~ +W1001: + Used when PyLint detect the use of the builtin "property" on an old style + class while this is relying on new style classes features This message belongs + to the newstyle checker. + +W1010: + Used when a custom exception class is raised but doesn't inherit from the + builtin "Exception" class. This message belongs to the newstyle checker. + +E1001: + Used when an old style class use the __slots__ attribute. This message belongs + to the newstyle checker. + +E1002: + Used when an old style class use the super builtin. This message belongs to + the newstyle checker. + +E1003: + Used when another argument than the current class is given as first argument + of the super builtin. This message belongs to the newstyle checker. + +E1010: + Used when a new style class is raised since it's not yet possible. This + message belongs to the newstyle checker. + + + +Exceptions +---------- + +Description +~~~~~~~~~~~ +checks for +* excepts without exception filter +* string exceptions + + +Messages +~~~~~~~~ +W0701: + Used when a string exception is raised. This message belongs to the exceptions + checker. + +W0702: + Used when an except clause doesn't specify exceptions type to catch. This + message belongs to the exceptions checker. + +W0703: + Used when an except catch Exception instances. This message belongs to the + exceptions checker. + +W0704: + Used when an except clause does nothing but "pass" and there is no "else" + clause. This message belongs to the exceptions checker. + +W0706: + Used when a variable used to raise an exception is initially assigned to a + value which can't be used as an exception. This message belongs to the + exceptions checker. + +E0701: + Used when except clauses are not in the correct order (from the more specific + to the more generic). If you don't fix the order, some exceptions may not be + catched by the most specific handler. This message belongs to the exceptions + checker. + +E0702: + Used when something which is neither a class, an instance or a string is + raised (i.e. a `TypeError` will be raised). This message belongs to the + exceptions checker. + + + +Similarities +------------ + +Description +~~~~~~~~~~~ +checks for similarities and duplicated code. This computation may be +memory / CPU intensive, so you should disable it if you experiments some +problems. + +This checker also defines the following reports: +* R0801: Duplication + +Messages +~~~~~~~~ +R0801: + Indicates that a set of similar lines has been detected among multiple file. + This usually means that the code should be refactored to avoid this + duplication. This message belongs to the similarities checker. + + + +Format +------ + +Description +~~~~~~~~~~~ +checks for : +* unauthorized constructions +* strict indentation +* line length +* use of <> instead of != + + +Messages +~~~~~~~~ +C0301: + Used when a line is longer than a given number of characters. This message + belongs to the format checker. + +C0321: + Used when more than on statement are found on the same line. This message + belongs to the format checker. + +C0322: + Used when one of the following operator (!= | <= | == | >= | < | > | = | \+= | + -= | \*= | /= | %) is not preceded by a space. This message belongs to the + format checker. + +C0323: + Used when one of the following operator (!= | <= | == | >= | < | > | = | \+= | + -= | \*= | /= | %) is not followed by a space. This message belongs to the + format checker. + +C0324: + Used when a comma (",") is not followed by a space. This message belongs to + the format checker. + +W0302: + Used when a module has too much lines, reducing its readibility. This message + belongs to the format checker. + +W0311: + Used when an unexpected number of indentation's tabulations or spaces has been + found. This message belongs to the format checker. + +W0312: + Used when there are some mixed tabs and spaces in a module. This message + belongs to the format checker. + +W0331: + Used when the deprecated "<>" operator is used instead of "!=". This message + belongs to the format checker. + +W0332: + Used when a lower case "l" is used to mark a long integer. You should use a + upper case "L" since the letter "l" looks too much like the digit "1" This + message belongs to the format checker. + +F0321: + Used when an unexpected error occured in bad format detection. Please report + the error if it occurs. This message belongs to the format checker. + + + +Miscellaneous +------------- + +Description +~~~~~~~~~~~ +checks for: +* warning notes in the code like FIXME, XXX +* PEP 263: source code with non ascii character but no encoding declaration + + +Messages +~~~~~~~~ +W0511: + Used when a warning note as FIXME or XXX is detected. This message belongs to + the miscellaneous checker. + +E0501: + Used when some non ascii characters are detected but now encoding is + specified, as explicited in the PEP 263. This message belongs to the + miscellaneous checker. + +E0502: + Used when a known encoding is specified but the file doesn't seem to be + actually in this encoding. This message belongs to the miscellaneous checker. + +E0503: + Used when an encoding is specified, but it's unknown to Python. This message + belongs to the miscellaneous checker. + + + +Metrics +------- + +Description +~~~~~~~~~~~ +does not check anything but gives some raw metrics : +* total number of lines +* total number of code lines +* total number of docstring lines +* total number of comments lines +* total number of empty lines + +This checker also defines the following reports: +* R0701: Raw metrics + diff --git a/doc/makefile b/doc/makefile new file mode 100644 index 0000000..944893e --- /dev/null +++ b/doc/makefile @@ -0,0 +1,36 @@ +MKHTML=mkdoc +MKHTML_OPT=--doctype article --param toc.section.depth=1 --target html --stylesheet single-file + +SRC=. + + +all: quickstart.html features.html FAQ.html examples man + +FAQ.html: ${SRC}/FAQ.txt + ${MKHTML} ${MKHTML_OPT} ${SRC}/FAQ.txt + +quickstart.html: ${SRC}/quickstart.txt + ${MKHTML} ${MKHTML_OPT} ${SRC}/quickstart.txt + +features.html: + chmod u+w ${SRC}/features.txt + echo "PyLint features" > ${SRC}/features.txt + echo "===============" >> ${SRC}/features.txt + echo "" >> ${SRC}/features.txt + echo ".. contents::" >> ${SRC}/features.txt + echo "" >> ${SRC}/features.txt + pylint --list-msgs >> ${SRC}/features.txt + ${MKHTML} ${MKHTML_OPT} ${SRC}/features.txt + +examples: + chmod u+w ../examples/pylintrc + pylint --generate-rcfile > ../examples/pylintrc + +man: + chmod u+w ../man/pylint.1 + pylint --generate-man > ../man/pylint.1 + +clean: + rm -f *.html + +.PHONY: features.html diff --git a/doc/quickstart.txt b/doc/quickstart.txt new file mode 100644 index 0000000..042d377 --- /dev/null +++ b/doc/quickstart.txt @@ -0,0 +1,216 @@ +================= +Pylint Quickstart +================= + +:Author: Alexandre Fayolle +:Organization: Logilab +:Version: $Revision: 1.10 $ +:Date: $Date: 2005-04-15 10:40:17 $ + +.. contents:: + + +This document is meant to get you started with Pylint. It assumes that +you have installed pylint following the instructions in the README +document found in the source documentation. + + +What is pylint? +--------------- + +Pylint is a tool that checks for errors in python code, tries to +enforce a coding standard and looks for smelling code . This is +similar but nevertheless different from what pychecker_ provides, +especially since pychecker explicitely does not bother with coding +style. The default coding style used by pylint is close to +`Guido's style guide`_. For more information about code smells, refer +to Martin Fowler's `refactoring book`_ + +Pylint will display a number of errors and warnings as it analyzes the +code, as well as some statistics about the number of warnings and +errors found in different files. If you run pylint twice, it will +display the statistics from the previous run together with the ones +from the current run, so that you can see if the code has improved or +not. + +Last but not least, the code is given an overall mark, based on the +number an severity of the warnings and errors. This has proven to +be very motivating for programmers. + + +Invoking pylint +--------------- + +Pylint is meant to be called from the commant line. The usage is :: + + pylint [options] module_or_package + +You should give pylint the name of a Python package or module. Pylint +will ``import`` this package or module, so you should pay attention to +your ``PYTHONPATH``, since it is a common error to analyze an +installed version of a module instead of the development version. + +It is also possible to analyze python files, with a few +restriction. The thing to keep in mind is that pylint will try to +convert the file name to a module name, and only be able to process +the file if it succeeds. :: + + pylint mymodule.py + +should always works since the current working +directory is automatically added on top of the python path :: + + pylint directory/mymodule.py + +will work if "directory" is a python package (i.e. has an __init__.py +file) or if "directory" is in the python path. + +For more details on this see the FAQ_. + +You can also start a thin gui around pylint (require TkInter) by +typing :: + + pylint-gui + +This should open a window where you can enter the name of the package +or module to check, at pylint messages will be displayed in the user +interface. + + +Pylint output +------------- + +The default format for the output is raw text. But passing pylint the +``--html`` option will produce an HTML document. + +There are several sections in pylint's output. + +Source code analysis section +'''''''''''''''''''''''''''' + +For each python module, +pylint will first display a few '*' characters followed by the name +of the module. Then, a number of messages with the following +format: :: + + MESSAGE_TYPE: LINE_NUM:[OBJECT:] MESSAGE + +You can get another output format, useful since it's recognized by +most editors or other development tools using the ``--parseable=y`` +option. + +The message type can be: + + * [R]efactor for a "good practice" metric violation + * [C]onvention for coding standard violation + * [W]arning for stylistic problems, or minor programming issues + * [E]rror for important programming issues (i.e. most probably bug) + * [F]atal for errors which prevented further processing + +Sometimes the line of code which caused the error is displayed with +a caret pointing to the error. This may be generalized in future +versions of pylint. + +Example (extracted from a run of pylint on itself...): + +:: + + ************* Module pylint.checkers.format + W: 50: Too long line (86/80) + W:108: Operator not followed by a space + print >>sys.stderr, 'Unable to match %r', line + ^ + W:141: Too long line (81/80) + W: 74:searchall: Unreachable code + W:171:FormatChecker.process_tokens: Redefining built-in (type) + W:150:FormatChecker.process_tokens: Too many local variables (20/15) + W:150:FormatChecker.process_tokens: Too many branchs (13/12) + + +Reports section +''''''''''''''' + +Following the analysis message, pylint will display a set of report, +each one focusing on a particular aspect of the project, such as number +of messages by categories, modules dependancies... + +For instance, the metrics report displays summaries gathered from the +current +run. + + * the number of processed modules + * for each module, the percentage of errors and warnings + * the total number of errors and warnings + * percentage of classes, functions and modules with docstrings, and + a comparison from the previous run + * percentage of classes, functions and modules with correct name + * (according the the coding standard), and a comparison from the + previous run + * a list of external dependencies found in the code, and where they appear + +Also, a global evaluation for the code is computed, and an +optional witty comment is displayed (if ``--comment=y`` was +specified on the command line). + + +Command line options +-------------------- + +First of all, we have two basic (but useful) options. + +--version show program's version number and exit +-h, --help show help about the command line options + +Pylint is architectured around several checkers. By default all +checkers are enabled. You can disable a specific checker by specifying +``--enable-<checker>=n``, or disable all checkers using +``--disable-all`` and afterwards enable specific checkers with +``--enable-<checker>=y``. See the list of available features_ for a +description of provided checkers with their functionalities. + +Each checker has some specific options, which can take either a yes/no +value, an integer, a python regular expression, or a comma separated +list of values (which are generally used to override a regular +expression in special cases). For a full list of options, use ``--help`` + +Specifying all the options suitable for your setup and coding +standards can be tedious, so it is possible to use a rc file to +specify the default values. Pylint looks for /etc/pylintrc and +~/.pylintrc. The ``--generate-rcfile`` option will generate a +commented configuration file according to the current configuration on +standard output and exit. You can put other options before this one to +use them in the configuration, or start with the default values and +hand tune the configuration. + +Other useful global options include: + +--zope Initialize Zope products before starting +--ignore=file Add <file> (may be a directory) to the black + list. It should be a base name, not a path. + You may set this option multiple times. +--statistics=y_or_n Compute statistics on collected data. +--persistent=y_or_n Pickle collected data for later comparisons. +--comment=y_or_n Add a comment according to your evaluation note. +--parseable=y_or_n Use a parseable output format. +--html=y_or_n Use HTML as output format instead of text. +--enable-msg=msgids Enable the given messages. +--disable-msg=msgids Disable the given messages. +--enable-msg-cat=cats Enable all messages in the given categories. +--disable-msg-cat=cats Disable all messages in the given categories. + + + +Bug reports +----------- + +You think you have found a bug in Pylint? Well, this may be the case +since Pylint is under development. Please take the time to send a bug +report to python-projects@logilab.org. This mailing list is also a +nice place to discuss Pylint issues. + + +.. _pychecker: http://pychecker.sf.net +.. _features: features.html +.. _FAQ: FAQ.html +.. _`Guido's style guide`: http://www.python.org/doc/essays/styleguide.html +.. _`refactoring book`: http://www.refactoring.com/ diff --git a/elisp/pylint.el b/elisp/pylint.el new file mode 100644 index 0000000..84f5da6 --- /dev/null +++ b/elisp/pylint.el @@ -0,0 +1,37 @@ + +;; adapted from pychecker for pylint +(defun my-python-hook () + (defun pylint () + "Run pylint against the file behind the current buffer after + checking if unsaved buffers should be saved." + + (interactive) + (let* ((file (buffer-file-name (current-buffer))) + (command (concat "pylint --parseable=y \"" file "\""))) + (save-some-buffers (not compilation-ask-about-save) nil) ; save files. + (compile-internal command "No more errors or warnings" "pylint"))) + (local-set-key [f1] 'pylint) + (local-set-key [f2] 'previous-error) + (local-set-key [f3] 'next-error) + + (define-key + py-mode-map + [menu-bar Python pylint-separator] + '("--" . pylint-seperator)) + + (define-key + py-mode-map + [menu-bar Python next-error] + '("Next error" . next-error)) + (define-key + py-mode-map + [menu-bar Python prev-error] + '("Previous error" . previous-error)) + (define-key + py-mode-map + [menu-bar Python lint] + '("Pylint" . pylint)) + + ) + +(add-hook 'python-mode-hook 'my-python-hook) diff --git a/elisp/startup b/elisp/startup new file mode 100644 index 0000000..ad9d071 --- /dev/null +++ b/elisp/startup @@ -0,0 +1,17 @@ +;; -*-emacs-lisp-*- +;; +;; Emacs startup file for the Debian GNU/Linux %PACKAGE% package +;; +;; Originally contributed by Nils Naumann <naumann@unileoben.ac.at> +;; Modified by Dirk Eddelbuettel <edd@debian.org> +;; Adapted for dh-make by Jim Van Zandt <jrv@vanzandt.mv.com> + +;; The %PACKAGE% package follows the Debian/GNU Linux 'emacsen' policy and +;; byte-compiles its elisp files for each 'emacs flavor' (emacs19, +;; xemacs19, emacs20, xemacs20...). The compiled code is then +;; installed in a subdirectory of the respective site-lisp directory. +;; We have to add this to the load-path: +(setq load-path (cons (concat "/usr/share/" + (symbol-name flavor) + "/site-lisp/%PACKAGE%") load-path)) +(load-library "pylint") diff --git a/examples/custom.py b/examples/custom.py new file mode 100644 index 0000000..73b64e1 --- /dev/null +++ b/examples/custom.py @@ -0,0 +1,38 @@ +from logilab import astng + +from pylint.interfaces import IASTNGChecker +from pylint.checkers import BaseChecker + +class MyASTNGChecker(BaseChecker): + """add member attributes defined using my own "properties" function + to the class locals dictionary + """ + + __implements__ = IASTNGChecker + + name = 'custom' + msgs = {} + options = () + # this is important so that your checker is executed before others + priority = -1 + + def visit_callfunc(self, node): + """called when a CallFunc node is encountered. See compiler.ast + documentation for a description of available nodes: + http://www.python.org/doc/current/lib/module-compiler.ast.html + ) + """ + if not (isinstance(node.node, astng.Getattr) + and isinstance(node.node.expr, astng.Name) + and node.node.expr.name == 'properties' + and node.node.attrname == 'create'): + return + in_class = node.frame() + for param in node.args: + in_class.locals[param.name] = node + + +def register(linter): + """required method to auto register this checker""" + linter.register_checker(MyASTNGChecker(linter)) + diff --git a/examples/custom_raw.py b/examples/custom_raw.py new file mode 100644 index 0000000..701f6e9 --- /dev/null +++ b/examples/custom_raw.py @@ -0,0 +1,31 @@ +from pylint.interfaces import IRawChecker +from pylint.checkers import BaseChecker + +class MyRawChecker(BaseChecker): + """check for line continuations with '\' instead of using triple + quoted string or parenthesis + """ + + __implements__ = IRawChecker + + name = 'custom_raw' + msgs = {'W9901': ('use \\ for line continuation', + ('Used when a \\ is used for a line continuation instead' + ' of using triple quoted string or parenthesis.')), + } + options = () + + def process_module(self, stream): + """process a module + + the module's content is accessible via the stream object + """ + for (lineno, line) in enumerate(stream): + if line.rstrip().endswith('\\'): + self.add_message('W9901', line=lineno) + + +def register(linter): + """required method to auto register this checker""" + linter.register_checker(MyRawChecker(linter)) + diff --git a/examples/pylintrc b/examples/pylintrc new file mode 100644 index 0000000..fc07984 --- /dev/null +++ b/examples/pylintrc @@ -0,0 +1,352 @@ +# lint Python modules using external checkers. +# +# This is the main checker controling the other ones and the reports +# generation. It is itself both a raw checker and an astng checker in order +# to: +# * handle message activation / deactivation at the module level +# * handle some basic but necessary stats'data (number of classes, methods...) +# +# This checker also defines the following reports: +# * R0001: Total errors / warnings +# * R0002: % errors / warnings by module +# * R0003: Messages +# * R0004: Global evaluation +[MASTER] + +# Profiled execution. +profile=no + +# Add <file or directory> to the black list. It should be a base name, not a +# path. You may set this option multiple times. +ignore=CVS + +# Pickle collected data for later comparisons. +persistent=yes + +# Set the cache size for astng objects. +cache-size=500 + +# List of plugins (as comma separated values of python modules names) to load, +# usually to register additional checkers. +load-plugins= + + +[REPORTS] + +# Tells wether to display a full report or only the messages +reports=yes + +# Use HTML as output format instead of text +html=no + +# Use a parseable text output format, so your favorite text editor will be able +# to jump to the line corresponding to a message. +parseable=no + +# Colorizes text output using ansi escape codes +color=no + +# Put messages in a separate file for each module / package specified on the +# command line instead of printing them on stdout. Reports (if any) will be +# written in a file name "pylint_global.[txt|html]". +files-output=no + +# Python expression which should return a note less than 10 (10 is the highest +# note).You have access to the variables errors warning, statement which +# respectivly contain the number of errors / warnings messages and the total +# number of statements analyzed. This is used by the global evaluation report +# (R0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Add a comment according to your evaluation note. This is used by the global +# evaluation report (R0004). +comment=no + +# Include message's id in output +include-ids=no + + +# checks for : +# * doc strings +# * modules / classes / functions / methods / arguments / variables name +# * number of arguments, local variables, branchs, returns and statements in +# functions, methods +# * required module attributes +# * dangerous default values as arguments +# * redefinition of function / method / class +# * uses of the global statement +# +# This checker also defines the following reports: +# * R0101: Statistics by type +[BASIC] + +# Enable / disable this checker +enable-basic=yes + +# Required attributes for module, separated by a comma +required-attributes=__revision__ + +# Regular expression which should only match functions or classes name which do +# not require a docstring +no-docstring-rgx=__.*__ + +# Regular expression which should only match correct module names +module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ + +# Regular expression which should only match correct module level names +const-rgx=(([A-Z_][A-Z1-9_]*)|(__.*__))$ + +# Regular expression which should only match correct class names +class-rgx=[A-Z_][a-zA-Z0-9]+$ + +# Regular expression which should only match correct function names +function-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct method names +method-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct instance attribute names +attr-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct argument names +argument-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct variable names +variable-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct list comprehension / +# generator expression variable names +inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ + +# Good variable names which should always be accepted, separated by a comma +good-names=i,j,k,ex,Run,_ + +# Bad variable names which should always be refused, separated by a comma +bad-names=foo,bar,baz,toto,tutu,tata + +# List of builtins function names that should not be used, separated by a comma +bad-functions=map,filter,apply,input + + +# try to find bugs in the code using type inference +# +[TYPECHECK] + +# Enable / disable this checker +enable-typecheck=yes + +# Tells wether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# When zope mode is activated, consider the acquired-members option to ignore +# access to some undefined attributes. +zope=no + +# List of members which are usually get through zope's acquisition mecanism and +# so shouldn't trigger E0201 when accessed (need zope=yes to be considered. +acquired-members=REQUEST,acl_users,aq_parent + + +# checks for +# * unused variables / imports +# * undefined variables +# * redefinition of variable from builtins or from an outer scope +# * use of variable before assigment +# +[VARIABLES] + +# Enable / disable this checker +enable-variables=yes + +# Tells wether we should check for unused import in __init__ files. +init-import=no + +# A regular expression matching names used for dummy variables (i.e. not used). +dummy-variables-rgx=_|dummy + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid to define new builtins when possible. +additional-builtins= + + +# checks for sign of poor/misdesign: +# * number of methods, attributes, local variables... +# * size, complexity of functions, methods +# +[DESIGN] + +# Enable / disable this checker +enable-design=yes + +# Maximum number of arguments for function / method +max-args=5 + +# Maximum number of locals for function / method body +max-locals=15 + +# Maximum number of return / yield for function / method body +max-returns=6 + +# Maximum number of branch for function / method body +max-branchs=12 + +# Maximum number of statements in function / method body +max-statements=50 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + + +# checks for : +# * methods without self as first argument +# * overriden methods signature +# * access only to existant members via self +# * attributes not defined in the __init__ method +# * supported interfaces implementation +# * unreachable code +# +[CLASSES] + +# Enable / disable this checker +enable-classes=yes + +# List of interface methods to ignore, separated by a comma. This is used for +# instance to not check methods defines in Zope's Interface base class. +ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__,__new__,setUp + + +# checks for +# * external modules dependencies +# * relative / wildcard imports +# * cyclic imports +# * uses of deprecated modules +# +# This checker also defines the following reports: +# * R0401: External dependencies +# * R0402: Modules dependencies graph +[IMPORTS] + +# Enable / disable this checker +enable-imports=yes + +# Deprecated modules which should not be used, separated by a comma +deprecated-modules=regsub,string,TERMIOS,Bastion,rexec + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report R0402 must not be disabled) +import-graph= + +# Create a graph of external dependencies in the given file (report R0402 must +# not be disabled) +ext-import-graph= + +# Create a graph of internal dependencies in the given file (report R0402 must +# not be disabled) +int-import-graph= + + +# checks for usage of new style capabilities on old style classes and +# other new/old styles conflicts problems +# * use of property, __slots__, super +# * "super" usage +# * raising a new style class as exception +# +[NEWSTYLE] + +# Enable / disable this checker +enable-newstyle=yes + + +# checks for +# * excepts without exception filter +# * string exceptions +# +[EXCEPTIONS] + +# Enable / disable this checker +enable-exceptions=yes + + +# checks for similarities and duplicated code. This computation may be +# memory / CPU intensive, so you should disable it if you experiments some +# problems. +# +# This checker also defines the following reports: +# * R0801: Duplication +[SIMILARITIES] + +# Enable / disable this checker +enable-similarities=yes + +# Minimum lines number of a similarity. +min-similarity-lines=4 + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + + +# checks for : +# * unauthorized constructions +# * strict indentation +# * line length +# * use of <> instead of != +# +[FORMAT] + +# Enable / disable this checker +enable-format=yes + +# Maximum number of characters on a single line. +max-line-length=80 + +# Maximum number of lines in a module +max-module-lines=1000 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + + +# checks for: +# * warning notes in the code like FIXME, XXX +# * PEP 263: source code with non ascii character but no encoding declaration +# +[MISCELLANEOUS] + +# Enable / disable this checker +enable-miscellaneous=yes + +# List of note tags to take in consideration, separated by a comma. Default to +# FIXME, XXX, TODO +notes=FIXME,XXX,TODO + + +# does not check anything but gives some raw metrics : +# * total number of lines +# * total number of code lines +# * total number of docstring lines +# * total number of comments lines +# * total number of empty lines +# +# This checker also defines the following reports: +# * R0701: Raw metrics +[METRICS] + +# Enable / disable this checker +enable-metrics=yes diff --git a/examples/pylintrc_camelcase b/examples/pylintrc_camelcase new file mode 100644 index 0000000..0dd9266 --- /dev/null +++ b/examples/pylintrc_camelcase @@ -0,0 +1,24 @@ +# This pylintrc file will use the default settings except for the +# naming conventions, which will allow for camel case naming as found +# in Java code or several libraries such as PyQt, etc. + +[BASIC] +# Regular expression which should only match correct module names +module-rgx=(([a-z][a-z0-9]*)|([A-Z][a-zA-Z0-9]+))$ + +# Regular expression which should only match correct class names +class-rgx=[A-Z][a-zA-Z0-9]+$ + +# Regular expression which should only match correct function names +function-rgx=[a-z_][a-zA-Z0-9]*$ + +# Regular expression which should only match correct method names +method-rgx=[a-z_][a-zA-Z0-9]*$ + +# Regular expression which should only match correct argument names +argument-rgx=[a-z][a-zA-Z0-9]*$ + +# Regular expression which should only match correct variable names +variable-rgx=[a-z][a-zA-Z0-9]*$ + + @@ -0,0 +1,84 @@ +"""Tkinker gui for pylint""" + +__revision__ = '$Id: gui.py,v 1.8 2004-08-27 10:18:54 syt Exp $' + +from Tkinter import Tk, Frame, Listbox, Entry, Label, Button, Scrollbar +from Tkinter import TOP, LEFT, RIGHT, BOTTOM, END, X, Y, BOTH +import os +import sys + +if sys.platform.startswith('win'): + PYLINT = 'pylint.bat' +else: + PYLINT = 'pylint' + +class LintGui: + """Build and control a window to interact with pylint""" + + def __init__(self, root=None): + self.root = root or Tk() + self.root.title('Pylint') + top_frame = Frame(self.root) + res_frame = Frame(self.root) + btn_frame = Frame(self.root) + top_frame.pack(side=TOP, fill=X) + res_frame.pack(side=TOP, fill=BOTH, expand=True) + btn_frame.pack(side=TOP, fill=X) + + Label(top_frame, text='Module or package').pack(side=LEFT) + self.txtModule = Entry(top_frame, background='white') + self.txtModule.bind('<Return>', self.run_lint) + self.txtModule.pack(side=LEFT, expand=True, fill=X) + Button(top_frame, text='Run', command=self.run_lint).pack(side=LEFT) + + scrl = Scrollbar(res_frame) + self.results = Listbox(res_frame, + background='white', + font='fixed', + selectmode='browse', + yscrollcommand=scrl.set) + scrl.configure(command=self.results.yview) + self.results.pack(side=LEFT, expand=True, fill=BOTH) + scrl.pack(side=RIGHT, fill=Y) + + Button(btn_frame, text='Quit', command=self.quit).pack(side=BOTTOM) + #self.root.bind('<ctrl-q>', self.quit) + self.txtModule.focus_set() + + def mainloop(self): + """lauch the mainloop of the application""" + self.root.mainloop() + + def quit(self, _=None): + """quit the application""" + self.root.quit() + + def run_lint(self, _=None): + """lauches pylint""" + colors = {'W:':'red1', 'E:': 'red4', + 'W:': 'red3', '**': 'navy'} + + self.root.configure(cursor='watch') + self.results.focus_set() + self.results.delete(0, END) + self.results.update() + module = self.txtModule.get() + pout = os.popen('%s %s' % (PYLINT, module), 'r') + for line in pout.xreadlines(): + line = line.rstrip() + self.results.insert(END, line) + fg_color = colors.get(line[:2], 'black') + self.results.itemconfigure(END, fg=fg_color) + self.results.update() + self.root.configure(cursor='') + +def Run(args): + """launch pylint gui from args""" + if args: + print 'USAGE: pylint-gui\n launch a simple pylint gui using Tk' + return + gui = LintGui() + gui.mainloop() + +if __name__ == '__main__': + Run(sys.argv[1:]) diff --git a/interfaces.py b/interfaces.py new file mode 100644 index 0000000..9771aaf --- /dev/null +++ b/interfaces.py @@ -0,0 +1,98 @@ +# 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., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +""" Copyright (c) 2002-2003 LOGILAB S.A. (Paris, FRANCE). + http://www.logilab.fr/ -- mailto:contact@logilab.fr + +Interfaces for PyLint objects +""" + +__revision__ = "$Id: interfaces.py,v 1.9 2004-04-24 12:14:53 syt Exp $" + +from logilab.common.interface import Interface + + +class IChecker(Interface): + """This is an base interface, not designed to be used elsewhere than for + sub interfaces definition. + """ + + def open(self): + """called before visiting project (i.e set of modules)""" + + def close(self): + """called after visiting project (i.e set of modules)""" + +## def open_module(self): +## """called before visiting a module""" + +## def close_module(self): +## """called after visiting a module""" + + +class IRawChecker(IChecker): + """interface for checker which need to parse the raw file + """ + + def process_module(self, stream): + """ process a module + + the module's content is accessible via the stream object + """ + + +class IASTNGChecker(IChecker): + """ interface for checker which prefers receive events according to + statement type + """ + + +class ILinter(Interface): + """interface for the linter class + + the linter class will generate events to its registered checkers. + Each ckecker may interact with the linter instance using this API + """ + + def register_checker(self, checker): + """register a new checker class + + checker is a class implementing IrawChecker or / and IASTNGChecker + """ + + def add_message(self, msg_id, line=None, node=None, args=None): + """add the message corresponding to the given id. + + If provided, msg is expanded using args + + astng checkers should provide the node argument, + raw checkers should provide the line argument. + """ + + +class IReporter(Interface): + """ reporter collect messages and display results encapsulated in a layout + """ + def add_message(self, msg_id, location, msg): + """add a message of a given type + + msg_id is a message identifier + location is a 3-uple (module, object, line) + msg is the actual message + """ + + def display_results(self, layout): + """display results encapsulated in the layout tree + """ + + +__all__ = ('IRawChecker', 'IStatable', 'ILinter', 'IReporter') @@ -0,0 +1,879 @@ +# Copyright (c) 2003-2006 Sylvain Thenault (thenault@gmail.com). +# Copyright (c) 2003-2006 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., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +""" %prog [options] module_or_package + + Check that a module satisfy a coding standard (and more !). + + %prog --help + + Display this help message and exit. + + %prog --help-msg <msg-id>[,<msg-id>] + + Display help messages about given message identifiers and exit. +""" + +__revision__ = "$Id: lint.py,v 1.115 2006-04-19 09:17:40 syt Exp $" + +# import this first to avoid further builtins pollution possibilities +from pylint.checkers import utils + +import sys +import os +import re +import tokenize +from os.path import dirname, basename, splitext, exists, isdir, join, normpath + +from logilab.common.configuration import OptionsManagerMixIn, \ + check_yn, check_csv +from logilab.common.modutils import modpath_from_file, get_module_files, \ + file_from_modpath, load_module_from_name +from logilab.common.interface import implements +from logilab.common.textutils import get_csv +from logilab.common.fileutils import norm_open +from logilab.common.ureports import Table, Text +from logilab.common.compat import enumerate +from logilab.common.__pkginfo__ import version as common_version + +from logilab.astng import ASTNGManager +from logilab.astng.__pkginfo__ import version as astng_version + +from pylint.utils import UnknownMessage, MessagesHandlerMixIn, \ + ReportsHandlerMixIn, MSG_TYPES, sort_checkers +from pylint.interfaces import ILinter, IRawChecker, IASTNGChecker +from pylint.checkers import BaseRawChecker, EmptyReport, \ + table_lines_from_stats +from pylint.reporters.text import TextReporter, TextReporter2, \ + ColorizedTextReporter +from pylint.reporters.html import HTMLReporter +from pylint import config + +from pylint.__pkginfo__ import version + + +OPTION_RGX = re.compile('\s*#*\s*pylint:(.*)') +REPORTER_OPT_MAP = {'html': HTMLReporter, + 'parseable': TextReporter2, + 'color': ColorizedTextReporter} + +# Python Linter class ######################################################### + +MSGS = { + 'F0001': ('%s', + 'Used when an error occured preventing the analyzing of a \ + module (unable to find it for instance).'), + 'F0002': ('%s: %s', + 'Used when an unexpected error occured while building the ASTNG \ + representation. This is usually accomopagned by a traceback. \ + Please report such errors !'), + 'F0003': ('ignored builtin module %s', + 'Used to indicate that the user asked to analyze a builtin module\ + which has been skipped.'), + + 'I0001': ('Unable to run raw checkers on built-in module %s', + 'Used to inform that a built-in module has not been checked \ + using the raw checkers.'), + + 'I0010': ('Unable to consider inline option %r', + 'Used when an inline option is either badly formatted or can\'t \ +be used inside modules.'), + + 'I0011': ('Locally disabling %r', + 'Used when an inline option disable a message or a messages \ + category.'), + 'I0012': ('Locally enabling %r', + 'Used when an inline option enable a message or a messages \ + category.'), + + 'E0001': ('%s', + 'Used when a syntax error is raised for a module.'), + + 'E0011': ('Unrecognized file option %r', + 'Used when an unknown inline option is encountered.'), + 'E0012': ('Bad option value %r', + 'Used when an bad value for an inline option is encountered.'), + } + +class PyLinter(OptionsManagerMixIn, MessagesHandlerMixIn, ReportsHandlerMixIn, + BaseRawChecker): + """lint Python modules using external checkers. + + This is the main checker controling the other ones and the reports + generation. It is itself both a raw checker and an astng checker in order + to: + * handle message activation / deactivation at the module level + * handle some basic but necessary stats'data (number of classes, methods...) + """ + + __implements__ = (ILinter, IRawChecker, IASTNGChecker) + + name = 'master' + priority = 0 + msgs = MSGS + may_be_disabled = False + + options = (('ignore', + {'type' : 'csv', 'metavar' : '<file>', + 'dest' : 'black_list', 'default' : ('CVS',), + 'help' : 'Add <file or directory> to the black list. It \ +should be a base name, not a path. You may set this option multiple times.'}), + + ('persistent', + {'default': 1, 'type' : 'yn', 'metavar' : '<y_or_n>', + 'help' : 'Pickle collected data for later comparisons.'}), + + ('cache-size', + {'default': 500, 'type' : 'int', 'metavar': '<size>', + 'help' : 'Set the cache size for astng objects.'}), + + ('load-plugins', + {'type' : 'csv', 'metavar' : '<modules>', 'default' : (), + 'help' : 'List of plugins (as comma separated values of \ +python modules names) to load, usually to register additional checkers.'}), + + ('reports', + {'default': 1, 'type' : 'yn', 'metavar' : '<y_or_n>', + 'short': 'r', + 'group': 'Reports', + 'help' : 'Tells wether to display a full report or only the\ + messages'}), + ('html', + {'default': 0, 'type' : 'yn', 'metavar' : '<y_or_n>', + 'group': 'Reports', + 'help' : 'Use HTML as output format instead of text'}), + + ('parseable', + {'default': 0, 'type' : 'yn', 'metavar' : '<y_or_n>', + 'short': 'p', + 'group': 'Reports', + 'help' : 'Use a parseable text output format, so your favorite\ + text editor will be able to jump to the line corresponding to a message.'}), + + ('color', + {'default': 0, 'type' : 'yn', 'metavar' : '<y_or_n>', + 'group': 'Reports', + 'help' : 'Colorizes text output using ansi escape codes'}), + + ('files-output', + {'default': 0, 'type' : 'yn', 'metavar' : '<y_or_n>', + 'group': 'Reports', + 'help' : 'Put messages in a separate file for each module / \ +package specified on the command line instead of printing them on stdout. \ +Reports (if any) will be written in a file name "pylint_global.[txt|html]".'}), + + ('evaluation', + {'type' : 'string', 'metavar' : '<python_expression>', + 'group': 'Reports', + 'default': '10.0 - ((float(5 * error + warning + refactor + \ +convention) / statement) * 10)', + 'help' : 'Python expression which should return a note less \ +than 10 (10 is the highest note).You have access to the variables errors \ +warning, statement which respectivly contain the number of errors / warnings\ + messages and the total number of statements analyzed. This is used by the \ + global evaluation report (R0004).'}), + + ('comment', + {'default': 0, 'type' : 'yn', 'metavar' : '<y_or_n>', + 'group': 'Reports', + 'help' : 'Add a comment according to your evaluation note. \ +This is used by the global evaluation report (R0004).'}), + + ('include-ids', + {'type' : 'yn', 'metavar' : '<y_or_n>', 'default' : 0, + 'short': 'i', + 'group': 'Reports', + 'help' : 'Include message\'s id in output'}), + + ('enable-msg-cat', + {'type' : 'csv', 'metavar': '<msg cats>', + 'group': 'Reports', + 'help' : 'Enable all messages in the listed categories.'}), + + ('disable-msg-cat', + {'type' : 'csv', 'metavar': '<msg cats>', + 'group': 'Reports', + 'help' : 'Disable all messages in the listed categories.'}), + + ('enable-msg', + {'type' : 'csv', 'metavar': '<msg ids>', + 'group': 'Reports', + 'help' : 'Enable the message with the given id.'}), + + ('disable-msg', + {'type' : 'csv', 'metavar': '<msg ids>', + 'group': 'Reports', + 'help' : 'Disable the message with the given id.'}), + + ('enable-report', + {'type' : 'csv', 'metavar': '<rpt ids>', + 'group': 'Reports', + 'help' : 'Enable the report with the given id.'}), + + ('disable-report', + {'type' : 'csv', 'metavar': '<rpt ids>', + 'group': 'Reports', + 'help' : 'Disable the report with the given id.'}), + + ) + + option_groups = ( + ('Reports', 'Options related to messages / statistics reporting'), + ) + + def __init__(self, options=(), reporter=None, option_groups=(), + pylintrc=None): + # some stuff has to be done before ancestors initialization... + # + # checkers / reporter / astng manager + self.reporter = None + self.set_reporter(reporter or TextReporter(sys.stdout)) + self.manager = ASTNGManager() + self._checkers = {} + # visit variables + self.base_name = None + self.base_file = None + self.current_name = None + self.current_file = None + self.stats = None + # init options + self.options = options + PyLinter.options + self.option_groups = option_groups + PyLinter.option_groups + self._options_methods = { + 'enable-report': self.enable_report, + 'disable-report': self.disable_report, + 'enable-msg': self.enable_message, + 'disable-msg': self.disable_message, + 'enable-msg-cat': self.enable_message_category, + 'disable-msg-cat': self.disable_message_category} + full_version = '%%prog %s, \nastng %s, common %s\nPython %s' % ( + version, astng_version, common_version, sys.version) + OptionsManagerMixIn.__init__(self, usage=__doc__, + version=full_version, + config_file=pylintrc or config.PYLINTRC) + MessagesHandlerMixIn.__init__(self) + ReportsHandlerMixIn.__init__(self) + BaseRawChecker.__init__(self) + # provided reports + self.reports = (('R0001', 'Total errors / warnings', + report_error_warning_stats), + ('R0002', '% errors / warnings by module', + report_error_warning_by_module_stats), + ('R0003', 'Messages', + report_messages_stats), + ('R0004', 'Global evaluation', + self.report_evaluation), + ) + self.register_checker(self) + self._dynamic_plugins = [] + + def load_plugin_modules(self, modnames): + """take a list of module names which are pylint plugins and load + and register them + """ + for modname in modnames: + if modname in self._dynamic_plugins: + continue + self._dynamic_plugins.append(modname) + module = load_module_from_name(modname) + module.register(self) + + def set_reporter(self, reporter): + """set the reporter used to display messages and reports""" + self.reporter = reporter + reporter.linter = self + + def set_option(self, opt_name, value, action=None, opt_dict=None): + """overriden from configuration.OptionsProviderMixin to handle some + special options + """ + if opt_name in self._options_methods: + if value: + meth = self._options_methods[opt_name] + value = check_csv(None, opt_name, value) + if isinstance(value, (list, tuple)): + for _id in value : + meth(_id) + else : + meth(value) + elif opt_name == 'cache-size': + self.manager.set_cache_size(int(value)) +# elif opt_name = 'load-plugins': +# self.load_plugin_modules(get_csv(value)) + elif opt_name in REPORTER_OPT_MAP and check_yn(None, opt_name, value): + self.set_reporter(REPORTER_OPT_MAP[opt_name]()) + BaseRawChecker.set_option(self, opt_name, value, action, opt_dict) + + # checkers manipulation methods ########################################### + + def register_checker(self, checker): + """register a new checker + + checker is an object implementing IRawChecker or / and IASTNGChecker + """ + assert checker.priority <= 0, 'checker priority can\'t be >= 0' + self._checkers[checker] = 1 + if hasattr(checker, 'reports'): + for r_id, r_title, r_cb in checker.reports: + self.register_report(r_id, r_title, r_cb, checker) + if checker.__doc__ is None: + checker.__doc__ = 'no documentation available for this checker' + need_space = 80 - (len(checker.__doc__.splitlines()[-1]) % 80) + checker.__doc__ += """%s +This checker also defines the following reports: + * %s +""" % (' ' * need_space, + '\n * '.join(['% -76s' % ('%s: %s' % report[:2]) + for report in checker.reports])) + self.register_options_provider(checker) + if hasattr(checker, 'msgs'): + self.register_messages(checker) + + + def disable_all_checkers(self): + """disable all possible checkers """ + for checker in self._checkers.keys(): + checker.enable(False) + + def disable_noerror_checkers(self): + """disable all checkers without error messages, and the + 'miscellaneous' checker which can be safely deactivated in debug + mode + """ + for checker in self._checkers.keys(): + if checker.name == 'miscellaneous': + checker.enable(False) + continue + for msgid in getattr(checker, 'msgs', {}).keys(): + if msgid[0] == 'E': + checker.enable(True) + break + else: + checker.enable(False) + + # block level option handling ############################################# + # + # see func_block_disable_msg.py test case for expected behaviour + + def process_tokens(self, tokens): + """process tokens from the current module to search for module/block + level options + """ + comment = tokenize.COMMENT + newline = tokenize.NEWLINE + #line_num = 0 + for (tok_type, _, start, _, line) in tokens: + if tok_type not in (comment, newline): + continue + #if start[0] == line_num: + # continue + match = OPTION_RGX.search(line) + if match is None: + continue + try: + opt, value = match.group(1).split('=', 1) + except ValueError: + self.add_message('I0010', args=match.group(1).strip(), + line=start[0]) + continue + opt = opt.strip() + #line_num = start[0] + if opt in self._options_methods and not opt.endswith('-report'): + meth = self._options_methods[opt] + for msgid in get_csv(value): + try: + meth(msgid, 'module', start[0]) + except UnknownMessage: + self.add_message('E0012', args=msgid, line=start[0]) + else: + self.add_message('E0011', args=opt, line=start[0]) + + def collect_block_lines(self, node, msg_state): + """walk ast to collect block level options line numbers""" + # recurse on children (depth first) + for child in node.getChildNodes(): + self.collect_block_lines(child, msg_state) + for msgid, lines in msg_state.items(): + #if msg in self._module_msgs_state: + # continue + for lineno, state in lines.items(): + first = node.source_line() + last = node.last_source_line() + if lineno >= first and lineno <= last: + # set state for all lines for this block + first, last = node.block_range(lineno) + for line in xrange(first, last+1): + # do not override existing entries + if not line in self._module_msgs_state.get(msgid, ()): + try: + self._module_msgs_state[msgid][line] = state + except KeyError: + self._module_msgs_state[msgid] = {line: state} + del lines[lineno] + + + # code checking methods ################################################### + + def check(self, files_or_modules): + """main checking entry: check a list of files or modules from their + name. + """ + self.reporter.include_ids = self.config.include_ids + if not isinstance(files_or_modules, (list, tuple)): + files_or_modules = (files_or_modules,) + checkers = sort_checkers(self._checkers.keys()) + rev_checkers = checkers[:] + rev_checkers.reverse() + # notify global begin + for checker in checkers: + checker.open() + # check modules or packages + for something in files_or_modules: + self.base_name = self.base_file = normpath(something) + if exists(something): + # this is a file or a directory + try: + modname = '.'.join(modpath_from_file(something)) + except ImportError: + modname = splitext(basename(something))[0] + if isdir(something): + filepath = join(something, '__init__.py') + else: + filepath = something + else: + # suppose it's a module or package + modname = something + try: + filepath = file_from_modpath(modname.split('.')) + if filepath is None: + self.set_current_module(modname) + self.add_message('F0003', args=modname) + continue + except ImportError, ex: + #if __debug__: + # import traceback + # traceback.print_exc() + self.set_current_module(modname) + msg = str(ex).replace(os.getcwd() + os.sep, '') + self.add_message('F0001', args=msg) + continue + if self.config.files_output: + reportfile = 'pylint_%s.%s' % (modname, self.reporter.extension) + self.reporter.set_output(open(reportfile, 'w')) + self.check_file(filepath, modname, checkers) + # notify global end + for checker in rev_checkers: + checker.close() + + def check_file(self, filepath, modname, checkers): + """check a module or package from its name + if modname is a package, recurse on its subpackages / submodules + """ +## print 'CHECKING', filepath, modname + # get the given module representation + self.base_name = modname + self.base_file = normpath(filepath) + # check this module + astng = self._check_file(filepath, modname, checkers) + if astng is None: + return + # recurse in package except if __init__ was explicitly given + if not modname.endswith('.__init__') and astng.package: + for filepath in get_module_files(dirname(filepath), + self.config.black_list): + if filepath == self.base_file: + continue + modname = '.'.join(modpath_from_file(filepath)) + self._check_file(filepath, modname, checkers) + + def _check_file(self, filepath, modname, checkers): + """check a module by building its astng representation""" + self.set_current_module(modname, filepath) + # get the module representation + astng = self.get_astng(filepath, modname) + if astng is not None: + # set the base file if necessary + self.base_file = self.base_file or astng.file + # fix the current file (if the source file was not available or + # if its actually a c extension + self.current_file = astng.file + # and check it + self.check_astng_module(astng, checkers) + return astng + + def set_current_module(self, modname, filepath=None): + """set the name of the currently analyzed module and + init statistics for it + """ + self.current_name = modname + self.current_file = filepath or modname + self.stats['by_module'][modname] = {} + self.stats['by_module'][modname]['statement'] = 0 + for msg_cat in MSG_TYPES.values(): + self.stats['by_module'][modname][msg_cat] = 0 + self._module_msgs_state = {} + self._module_msg_cats_state = {} + + def get_astng(self, filepath, modname): + """return a astng representation for a module""" + try: + return self.manager.astng_from_file(filepath, modname) + except SyntaxError, ex: + self.add_message('E0001', line=ex.lineno, args=ex.msg) + except KeyboardInterrupt: + raise + except Exception, ex: + #if __debug__: + # import traceback + # traceback.print_exc() + self.add_message('F0002', args=(ex.__class__, ex)) + + + def check_astng_module(self, astng, checkers): + """check a module from its astng representation, real work""" + # call raw checkers if possible + if not astng.pure_python: + self.add_message('I0001', args=astng.name) + else: + #assert astng.file.endswith('.py') + stream = norm_open(astng.file) + # invoke IRawChecker interface on self to fetch module/block + # level options + self.process_module(stream) + # walk ast to collect line numbers + orig_state = self._module_msgs_state.copy() + self._module_msgs_state = {} + self.collect_block_lines(astng, orig_state) + + for checker in checkers: + if implements(checker, IRawChecker) and checker is not self: + stream.seek(0) + checker.process_module(stream) + # generate events to astng checkers + self.astng_events(astng, [checker for checker in checkers + if implements(checker, IASTNGChecker)]) + + def astng_events(self, astng, checkers, _reversed_checkers=None): + """generate event to astng checkers according to the current astng + node and recurse on its children + """ + if _reversed_checkers is None: + _reversed_checkers = checkers[:] + _reversed_checkers.reverse() + if astng.is_statement(): + self.stats['statement'] += 1 + # generate events for this node on each checkers + for checker in checkers: + checker.visit(astng) + # recurse on children + for child in astng.getChildNodes(): + self.astng_events(child, checkers, _reversed_checkers) + for checker in _reversed_checkers: + checker.leave(astng) + + + # IASTNGChecker interface ################################################# + + def open(self): + """initialize counters""" + self.stats = { 'by_module' : {}, + 'by_msg' : {}, + 'statement' : 0 + } + for msg_cat in MSG_TYPES.values(): + self.stats[msg_cat] = 0 + + def close(self): + """close the whole package /module, it's time to make reports ! + + if persistent run, pickle results for later comparison + """ + # load old results if any + old_stats = config.load_results(self.base_name) + if self.config.reports: + self.make_reports(self.stats, old_stats) + # save results if persistent run + if self.config.persistent: + config.save_results(self.stats, self.base_name) + + # specific reports ######################################################## + + def report_evaluation(self, sect, stats, old_stats): + """make the global evaluation report""" + # check with at least check 1 statements (usually 0 when there is a + # syntax error preventing pylint from further processing) + if stats['statement'] == 0: + raise EmptyReport() + # get a global note for the code + evaluation = self.config.evaluation + try: + note = eval(evaluation, {}, self.stats) + except Exception, ex: + msg = 'An exception occured while rating: %s' % ex + else: + stats['global_note'] = note + msg = 'Your code has been rated at %.2f/10' % note + if old_stats.has_key('global_note'): + msg += ' (previous run: %.2f/10)' % old_stats['global_note'] + if self.config.comment: + msg = '%s\n%s' % (msg, config.get_note_message(note)) + sect.append(Text(msg)) + +# some reporting functions #################################################### + +def report_error_warning_stats(sect, stats, old_stats): + """make total errors / warnings report""" + lines = ['type', 'number', 'previous', 'difference'] + lines += table_lines_from_stats(stats, old_stats, + ('convention', 'refactor', + 'warning', 'error')) + sect.append(Table(children=lines, cols=4, rheaders=1)) + +def report_messages_stats(sect, stats, _): + """make messages type report""" + if not stats['by_msg']: + # don't print this report when we didn't detected any errors + raise EmptyReport() + in_order = [(value, msg_id) + for msg_id, value in stats['by_msg'].items() + if not msg_id.startswith('I')] + in_order.sort() + in_order.reverse() + lines = ('message id', 'occurences') + for value, msg_id in in_order: + lines += (msg_id, str(value)) + sect.append(Table(children=lines, cols=2, rheaders=1)) + +def report_error_warning_by_module_stats(sect, stats, _): + """make errors / warnings by modules report""" + if len(stats['by_module']) == 1: + # don't print this report when we are analysing a single module + raise EmptyReport() + by_mod = {} + for m_type in ('fatal', 'error', 'warning', 'refactor', 'convention'): + total = stats[m_type] + for module in stats['by_module'].keys(): + mod_total = stats['by_module'][module][m_type] + if total == 0: + percent = 0 + else: + percent = float((mod_total)*100) / total + by_mod.setdefault(module, {})[m_type] = percent + sorted_result = [] + for module, mod_info in by_mod.items(): + sorted_result.append((mod_info['error'], + mod_info['warning'], + mod_info['refactor'], + mod_info['convention'], + module)) + sorted_result.sort() + sorted_result.reverse() + lines = ['module', 'error', 'warning', 'refactor', 'convention'] + for line in sorted_result: + if line[0] == 0 and line[1] == 0: + break + lines.append(line[-1]) + for val in line[:-1]: + lines.append('%.2f' % val) + if len(lines) == 5: + raise EmptyReport() + sect.append(Table(children=lines, cols=5, rheaders=1)) + + + +# utilities ################################################################### + +# this may help to import modules using gettext + +try: + __builtins__._ = str +except AttributeError: + __builtins__['_'] = str + + +def preprocess_options(args, search_for): + """look for some options (keys of <search_for>) which have to be processed + before others + + values of <search_for> are callback functions to call when the option is + found + """ + for i, arg in enumerate(args): + for option in search_for: + ##print arg, option + if arg.startswith('--%s=' % option): + search_for[option](option, arg[len(option)+3:]) + del args[i] + elif arg == '--%s' % option: + search_for[option](option, args[i + 1]) + del args[i:i+2] + +class Run: + """helper class to use as main for pylint : + + run(*sys.argv[1:]) + """ + + def __init__(self, args, reporter=None, quiet=0): + self._rcfile = None + self._plugins = [] + preprocess_options(args, {'rcfile': self.cb_set_rcfile, + 'load-plugins': self.cb_add_plugins}) + self.linter = linter = PyLinter(( + ('rcfile', + {'action' : 'callback', 'callback' : lambda *args: 1, + 'type': 'string', 'metavar': '<file>', + 'help' : 'Specify a configuration file.'}), + + ('disable-all', + {'action' : 'callback', + 'callback' : self.cb_disable_all_checkers, + 'help' : '''Disable all possible checkers. This option should + precede enable-* options.'''}), + + ('help-msg', + {'action' : 'callback', 'type' : 'string', 'metavar': '<msg-id>', + 'callback' : self.cb_help_message, + 'help' : '''Display a help message for the given message id and \ +exit. This option may be a comma separated list.'''}), + + ('list-msgs', + {'action' : 'callback', 'metavar': '<msg-id>', + 'callback' : self.cb_list_messages, + 'help' : 'List and explain every available messages.'}), + + ('generate-rcfile', + {'action' : 'callback', 'callback' : self.cb_generate_config, + 'help' : '''Generate a sample configuration file according to \ +the current configuration. You can put other options before this one to use \ +them in the configuration. This option causes the program to exit'''}), + + ('generate-man', + {'action' : 'callback', 'callback' : self.cb_generate_manpage, + 'help' : '''Generate a man page for pylint. This option causes \ +the program to exit'''}), + + ('debug-mode', + {'action' : 'callback', 'callback' : self.cb_debug_mode, + 'help' : '''In debug mode, checkers without error messages are \ +disabled and for others, only the ERROR messages are displayed, and no reports \ +are done by default'''}), + + ('profile', + {'type' : 'yn', 'metavar' : '<y_or_n>', + 'default': False, + 'help' : 'Profiled execution.'}), + + ), reporter=reporter, pylintrc=self._rcfile) + linter.quiet = quiet + # register standard checkers + from pylint import checkers + checkers.initialize(linter) + # load command line plugins + linter.load_plugin_modules(self._plugins) + # add some help section + linter.add_help_section('Environment variables', config.ENV_HELP) + linter.add_help_section('Output', ''' +Using the default text output, the message format is : + MESSAGE_TYPE: LINE_NUM:[OBJECT:] MESSAGE +There are 5 kind of message types : + * (C) convention, for programming standard violation + * (R) refactor, for bad code smell + * (W) warning, for python specific problems + * (E) error, for much probably bugs in the code + * (F) fatal, if an error occured which prevented pylint from doing further \ +processing. + ''') + # read configuration + linter.read_config_file() + # is there some additional plugins in the file configuration, in + config_parser = linter._config_parser + if config_parser.has_option('master', 'load-plugins'): + plugins = get_csv(config_parser.get('master', 'load-plugins')) + linter.load_plugin_modules(plugins) + # now we can load file config and command line, plugins (which can + # provide options) have been registered + linter.load_config_file() + args = linter.load_command_line_configuration(args) + if not args: + print linter.help() + sys.exit(1) + # insert current working directory to the python path to have a correct + # behaviour + sys.path.insert(0, os.getcwd()) + if self.linter.config.profile: + print >> sys.stderr, '** profiled run' + from hotshot import Profile, stats + prof = Profile('stones.prof') + prof.runcall(linter.check, args) + prof.close() + data = stats.load('stones.prof') + data.strip_dirs() + data.sort_stats('time', 'calls') + data.print_stats(30) + else: + linter.check(args) + sys.path.pop(0) + + def cb_set_rcfile(self, name, value): + """callback for option preprocessing (ie before optik parsing)""" + self._rcfile = value + + def cb_add_plugins(self, name, value): + """callback for option preprocessing (ie before optik parsing)""" + self._plugins.extend(get_csv(value)) + + def cb_disable_all_checkers(self, *args, **kwargs): + """optik callback for disabling all checkers""" + self.linter.disable_all_checkers() + + def cb_debug_mode(self, *args, **kwargs): + """debug mode: + * checkers without error messages are disabled + * for others, only the ERROR messages are displayed + * disable reports + * do not save execution information + """ + self.linter.disable_noerror_checkers() + self.linter.set_option('disable-msg-cat', ('W', 'C', 'R', 'F', 'I')) + self.linter.set_option('reports', False) + self.linter.set_option('persistent', False) + + + def cb_generate_config(self, *args, **kwargs): + """optik callback for sample config file generation""" + self.linter.generate_config() + sys.exit(0) + + def cb_generate_manpage(self, *args, **kwargs): + """optik callback for sample config file generation""" + from pylint import __pkginfo__ + self.linter.generate_manpage(__pkginfo__) + sys.exit(0) + + def cb_help_message(self, option, opt_name, value, parser): + """optik callback for printing some help about a particular message""" + self.linter.help_message(get_csv(value)) + sys.exit(0) + + def cb_list_messages(self, option, opt_name, value, parser): + """optik callback for printing available messages""" + self.linter.list_messages() + sys.exit(0) + + +if __name__ == '__main__': + Run(sys.argv[1:]) diff --git a/man/pylint.1 b/man/pylint.1 new file mode 100644 index 0000000..eefa920 --- /dev/null +++ b/man/pylint.1 @@ -0,0 +1,371 @@ +.TH pylint 1 "2006-4-20" pylint +.SH NAME +.B pylint +\- python code static checker + +.SH SYNOPSIS +.B pylint +[ +.I OPTIONS +] [ +.I <arguments> +] + +.SH DESCRIPTION +.B pylint +is a Python source code analyzer which looks for programming +errors, helps enforcing a coding standard and sniffs for some code +smells (as defined in Martin Fowler's Refactoring book) + +Pylint can be seen as another PyChecker since nearly all tests you +can do with PyChecker can also be done with Pylint. However, Pylint +offers some more features, like checking length of lines of code, +checking if variable names are well-formed according to your coding +standard, or checking if declared interfaces are truly implemented, +and much more. + +Additionally, it is possible to write plugins to add your own checks. + +.SH OPTIONS +.IP "--version" +show program's version number and exit +.IP "--help, -h" +show this help message and exit + +.SH MASTER +lint Python modules using external checkers. + + This is the main checker controling the other ones and the reports + generation. It is itself both a raw checker and an astng checker in order + to: + * handle message activation / deactivation at the module level + * handle some basic but necessary stats'data (number of classes, methods...) + +This checker also defines the following reports: + * R0001: Total errors / warnings + * R0002: % errors / warnings by module + * R0003: Messages + * R0004: Global evaluation + +.IP "--rcfile=<file>" +Specify a configuration file. +.IP "--disable-all" +Disable all possible checkers. This option should precede enable-* options. +.IP "--help-msg=<msg-id>" +Display a help message for the given message id and exit. This option may be a comma separated list. +.IP "--list-msgs" +List and explain every available messages. +.IP "--generate-rcfile" +Generate a sample configuration file according to the current configuration. You can put other options before this one to use them in the configuration. This option causes the program to exit +.IP "--generate-man" +Generate a man page for pylint. This option causes the program to exit +.IP "--debug-mode" +In debug mode, checkers without error messages are disabled and for others, only the ERROR messages are displayed, and no reports are done by default +.IP "--profile=<y_or_n>" +Profiled execution. +.IP "--ignore=<file>" +Add <file or directory> to the black list. It should be a base name, not a path. You may set this option multiple times. +.IP "--persistent=<y_or_n>" +Pickle collected data for later comparisons. +.IP "--cache-size=<size>" +Set the cache size for astng objects. +.IP "--load-plugins=<modules>" +List of plugins (as comma separated values of python modules names) to load, usually to register additional checkers. + +.SH REPORTS +Options related to messages / statistics reporting +.IP "--reports=<y_or_n>, -r<y_or_n>" +Tells wether to display a full report or only the messages +.IP "--html=<y_or_n>" +Use HTML as output format instead of text +.IP "--parseable=<y_or_n>, -p<y_or_n>" +Use a parseable text output format, so your favorite text editor will be able to jump to the line corresponding to a message. +.IP "--color=<y_or_n>" +Colorizes text output using ansi escape codes +.IP "--files-output=<y_or_n>" +Put messages in a separate file for each module / package specified on the command line instead of printing them on stdout. Reports (if any) will be written in a file name "pylint_global.[txt|html]". +.IP "--evaluation=<python_expression>" +Python expression which should return a note less than 10 (10 is the highest note).You have access to the variables errors warning, statement which respectivly contain the number of errors / warnings messages and the total number of statements analyzed. This is used by the global evaluation report (R0004). +.IP "--comment=<y_or_n>" +Add a comment according to your evaluation note. This is used by the global evaluation report (R0004). +.IP "--include-ids=<y_or_n>, -i<y_or_n>" +Include message's id in output +.IP "--enable-msg-cat=<msg cats>" +Enable all messages in the listed categories. +.IP "--disable-msg-cat=<msg cats>" +Disable all messages in the listed categories. +.IP "--enable-msg=<msg ids>" +Enable the message with the given id. +.IP "--disable-msg=<msg ids>" +Disable the message with the given id. +.IP "--enable-report=<rpt ids>" +Enable the report with the given id. +.IP "--disable-report=<rpt ids>" +Disable the report with the given id. + +.SH DESIGN +checks for sign of poor/misdesign: + * number of methods, attributes, local variables... + * size, complexity of functions, methods + +.IP "--enable-design=<y_or_n>" +Enable / disable this checker +.IP "--max-args=<int>" +Maximum number of arguments for function / method +.IP "--max-locals=<int>" +Maximum number of locals for function / method body +.IP "--max-returns=<int>" +Maximum number of return / yield for function / method body +.IP "--max-branchs=<int>" +Maximum number of branch for function / method body +.IP "--max-statements=<int>" +Maximum number of statements in function / method body +.IP "--max-parents=<num>" +Maximum number of parents for a class (see R0901). +.IP "--max-attributes=<num>" +Maximum number of attributes for a class (see R0902). +.IP "--min-public-methods=<num>" +Minimum number of public methods for a class (see R0903). +.IP "--max-public-methods=<num>" +Maximum number of public methods for a class (see R0904). + +.SH BASIC +checks for : + * doc strings + * modules / classes / functions / methods / arguments / variables name + * number of arguments, local variables, branchs, returns and statements in +functions, methods + * required module attributes + * dangerous default values as arguments + * redefinition of function / method / class + * uses of the global statement + +This checker also defines the following reports: + * R0101: Statistics by type + +.IP "--enable-basic=<y_or_n>" +Enable / disable this checker +.IP "--required-attributes=<attributes>" +Required attributes for module, separated by a comma +.IP "--no-docstring-rgx=<regexp>" +Regular expression which should only match functions or classes name which do not require a docstring +.IP "--module-rgx=<regexp>" +Regular expression which should only match correct module names +.IP "--const-rgx=<regexp>" +Regular expression which should only match correct module level names +.IP "--class-rgx=<regexp>" +Regular expression which should only match correct class names +.IP "--function-rgx=<regexp>" +Regular expression which should only match correct function names +.IP "--method-rgx=<regexp>" +Regular expression which should only match correct method names +.IP "--attr-rgx=<regexp>" +Regular expression which should only match correct instance attribute names +.IP "--argument-rgx=<regexp>" +Regular expression which should only match correct argument names +.IP "--variable-rgx=<regexp>" +Regular expression which should only match correct variable names +.IP "--inlinevar-rgx=<regexp>" +Regular expression which should only match correct list comprehension / generator expression variable names +.IP "--good-names=<names>" +Good variable names which should always be accepted, separated by a comma +.IP "--bad-names=<names>" +Bad variable names which should always be refused, separated by a comma +.IP "--bad-functions=<builtin function names>" +List of builtins function names that should not be used, separated by a comma + +.SH CLASSES +checks for : + * methods without self as first argument + * overriden methods signature + * access only to existant members via self + * attributes not defined in the __init__ method + * supported interfaces implementation + * unreachable code + +.IP "--enable-classes=<y_or_n>" +Enable / disable this checker +.IP "--ignore-iface-methods=<method names>" +List of interface methods to ignore, separated by a comma. This is used for instance to not check methods defines in Zope's Interface base class. +.IP "--defining-attr-methods=<method names>" +List of method names used to declare (i.e. assign) instance attributes. + +.SH SIMILARITIES +checks for similarities and duplicated code. This computation may be + memory / CPU intensive, so you should disable it if you experiments some + problems. + +This checker also defines the following reports: + * R0801: Duplication + +.IP "--enable-similarities=<y_or_n>" +Enable / disable this checker +.IP "--min-similarity-lines=<int>" +Minimum lines number of a similarity. +.IP "--ignore-comments=<y or n>" +Ignore comments when computing similarities. +.IP "--ignore-docstrings=<y or n>" +Ignore docstrings when computing similarities. + +.SH EXCEPTIONS +checks for + * excepts without exception filter + * string exceptions + +.IP "--enable-exceptions=<y_or_n>" +Enable / disable this checker + +.SH FORMAT +checks for : + * unauthorized constructions + * strict indentation + * line length + * use of <> instead of != + +.IP "--enable-format=<y_or_n>" +Enable / disable this checker +.IP "--max-line-length=<int>" +Maximum number of characters on a single line. +.IP "--max-module-lines=<int>" +Maximum number of lines in a module +.IP "--indent-string=<string>" +String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 tab). + +.SH IMPORTS +checks for + * external modules dependencies + * relative / wildcard imports + * cyclic imports + * uses of deprecated modules + +This checker also defines the following reports: + * R0401: External dependencies + * R0402: Modules dependencies graph + +.IP "--enable-imports=<y_or_n>" +Enable / disable this checker +.IP "--deprecated-modules=<modules>" +Deprecated modules which should not be used, separated by a comma +.IP "--import-graph=<file.dot>" +Create a graph of every (i.e. internal and external) dependencies in the given file (report R0402 must not be disabled) +.IP "--ext-import-graph=<file.dot>" +Create a graph of external dependencies in the given file (report R0402 must not be disabled) +.IP "--int-import-graph=<file.dot>" +Create a graph of internal dependencies in the given file (report R0402 must not be disabled) + +.SH MISCELLANEOUS +checks for: + * warning notes in the code like FIXME, XXX + * PEP 263: source code with non ascii character but no encoding declaration + +.IP "--enable-miscellaneous=<y_or_n>" +Enable / disable this checker +.IP "--notes=<comma separated values>" +List of note tags to take in consideration, separated by a comma. Default to FIXME, XXX, TODO + +.SH NEWSTYLE +checks for usage of new style capabilities on old style classes and + other new/old styles conflicts problems + * use of property, __slots__, super + * "super" usage + * raising a new style class as exception + +.IP "--enable-newstyle=<y_or_n>" +Enable / disable this checker + +.SH METRICS +does not check anything but gives some raw metrics : + * total number of lines + * total number of code lines + * total number of docstring lines + * total number of comments lines + * total number of empty lines + +This checker also defines the following reports: + * R0701: Raw metrics + +.IP "--enable-metrics=<y_or_n>" +Enable / disable this checker + +.SH TYPECHECK +try to find bugs in the code using type inference + +.IP "--enable-typecheck=<y_or_n>" +Enable / disable this checker +.IP "--ignore-mixin-members=<y_or_n>" +Tells wether missing members accessed in mixin class should be ignored. A mixin class is detected if its name ends with "mixin" (case insensitive). +.IP "--zope=<y_or_n>" +When zope mode is activated, consider the acquired-members option to ignore access to some undefined attributes. +.IP "--acquired-members=<members names>" +List of members which are usually get through zope's acquisition mecanism and so shouldn't trigger E0201 when accessed (need zope=yes to be considered. + +.SH VARIABLES +checks for + * unused variables / imports + * undefined variables + * redefinition of variable from builtins or from an outer scope + * use of variable before assigment + +.IP "--enable-variables=<y_or_n>" +Enable / disable this checker +.IP "--init-import=<y_or_n>" +Tells wether we should check for unused import in __init__ files. +.IP "--dummy-variables-rgx=<regexp>" +A regular expression matching names used for dummy variables (i.e. not used). +.IP "--additional-builtins=<comma separated list>" +List of additional names supposed to be defined in builtins. Remember that you should avoid to define new builtins when possible. + +.SH ENVIRONMENT VARIABLES + +The following environment variables are used : + * PYLINTHOME + path to the directory where data of persistent run will be stored. If not +found, it defaults to ~/.pylint.d/ or .pylint.d (in the current working +directory) . The current PYLINTHOME is /home/syt/.pylint.d. + * PYLINTRC + path to the configuration file. If not found, it will use the first +existant file in ~/.pylintrc, /etc/pylintrc. The current PYLINTRC is +None. + + +.SH OUTPUT + +Using the default text output, the message format is : + MESSAGE_TYPE: LINE_NUM:[OBJECT:] MESSAGE +There are 5 kind of message types : + * (C) convention, for programming standard violation + * (R) refactor, for bad code smell + * (W) warning, for python specific problems + * (E) error, for much probably bugs in the code + * (F) fatal, if an error occured which prevented pylint from doing further processing. + + +.SH SEE ALSO +/usr/share/doc/pythonX.Y-pylint/ + +.SH COPYRIGHT +Copyright (c) 2003-2006 Sylvain Thenault (thenault@gmail.com). +Copyright (c) 2003-2006 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., 59 Temple Place, Suite 330, Boston, +MA 02111-1307 USA. +.SH BUGS +Please report bugs on the project's mailing list: +mailto://python-projects@logilab.org + +.SH AUTHOR +Sylvain Thenault <sylvain.thenault@logilab.fr> + diff --git a/reporters/__init__.py b/reporters/__init__.py new file mode 100644 index 0000000..54d14bd --- /dev/null +++ b/reporters/__init__.py @@ -0,0 +1,67 @@ +# 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., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +"""utilities methods and classes for reporters + + Copyright (c) 2000-2003 LOGILAB S.A. (Paris, FRANCE). + http://www.logilab.fr/ -- mailto:contact@logilab.fr +""" + +__revision__ = "$Id: __init__.py,v 1.14 2005-01-20 15:12:56 syt Exp $" + +import sys + +CMPS = ['=', '-', '+'] + +def diff_string(old, new): + """given a old and new int value, return a string representing the + difference + """ + diff = abs(old - new) + diff_str = "%s%s" % (CMPS[cmp(old, new)], diff and ('%.2f' % diff) or '') + return diff_str + + +class EmptyReport(Exception): + """raised when a report is empty and so should not be displayed""" + +class BaseReporter: + """base class for reporters""" + + extension = '' + + def __init__(self, output=sys.stdout): + self.linter = None + self.include_ids = None + self.section = 0 + self.out = None + self.set_output(output) + + def set_output(self, output): + """set output stream""" + self.out = output + + def writeln(self, string=''): + """write a line in the output buffer""" + print >> self.out, string + + def display_results(self, layout): + """display results encapsulated in the layout tree""" + self.section = 0 + if self.include_ids and hasattr(layout, 'report_id'): + layout.children[0].children[0].data += ' (%s)' % layout.report_id + self._display(layout) + + def _display(self, layout): + """display the layout""" + raise NotImplementedError() + diff --git a/reporters/html.py b/reporters/html.py new file mode 100644 index 0000000..625c447 --- /dev/null +++ b/reporters/html.py @@ -0,0 +1,64 @@ +# 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., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +""" Copyright (c) 2002-2006 LOGILAB S.A. (Paris, FRANCE). + http://www.logilab.fr/ -- mailto:contact@logilab.fr + +HTML reporter +""" + +__revision__ = "$Id: html.py,v 1.14 2006-03-08 15:53:41 syt Exp $" + +import sys +from cgi import escape + +from logilab.common.ureports import HTMLWriter, Section, Table + +from pylint.interfaces import IReporter +from pylint.reporters import BaseReporter + + +class HTMLReporter(BaseReporter): + """report messages and layouts in HTML + """ + + __implements__ = IReporter + extension = 'html' + + def __init__(self, output=sys.stdout): + BaseReporter.__init__(self, output) + self.msgs = [] + + def add_message(self, msg_id, location, msg): + """manage message of different type and in the context of path""" + module, obj, line = location[1:] + if self.include_ids: + sigle = msg_id + else: + sigle = msg_id[0] + self.msgs += [sigle, module, obj, str(line), escape(msg)] + + def _display(self, layout): + """launch layouts display + + overriden from BaseReporter to add a blank line... + """ + if self.msgs: + # add stored messages to the layout + msgs = ['type', 'module', 'object', 'line', 'message'] + msgs += self.msgs + sect = Section('Messages') + layout.append(sect) + sect.append(Table(cols=5, children=msgs, rheaders=1)) + self.msgs = [] + HTMLWriter().format(layout, self.out) + diff --git a/reporters/text.py b/reporters/text.py new file mode 100644 index 0000000..f4dfd2c --- /dev/null +++ b/reporters/text.py @@ -0,0 +1,151 @@ +# 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., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +""" Copyright (c) 2000-2003 LOGILAB S.A. (Paris, FRANCE). + http://www.logilab.fr/ -- mailto:contact@logilab.fr + +Plain text reporter +""" + +__revision__ = "$Id: text.py,v 1.21 2005-12-28 00:24:35 syt Exp $" + +import os +import sys + +from logilab.common.ureports import TextWriter +from logilab.common.textutils import colorize_ansi + +from pylint.interfaces import IReporter +from pylint.reporters import BaseReporter + +TITLE_UNDERLINES = ['', '=', '-', '.'] + + +## def modname_to_path(modname, prefix=os.getcwd() + os.sep): +## """transform a module name into a path""" +## module = load_module_from_name(modname).__file__.replace(prefix, '') +## return module.replace('.pyc', '.py').replace('.pyo', '.py') + + +class TextReporter(BaseReporter): + """reports messages and layouts in plain text + """ + + __implements__ = IReporter + extension = 'txt' + + def __init__(self, output=sys.stdout): + BaseReporter.__init__(self, output) + self._modules = {} + + def add_message(self, msg_id, location, msg): + """manage message of different type and in the context of path""" + module, obj, line = location[1:] + if not self._modules.has_key(module): + self.writeln('************* Module %s' % module) + self._modules[module] = 1 + if obj: + obj = ':%s' % obj + if self.include_ids: + sigle = msg_id + else: + sigle = msg_id[0] + self.writeln('%s:%3s%s: %s' % (sigle, line, obj, msg)) + + def _display(self, layout): + """launch layouts display""" + print >> self.out + TextWriter().format(layout, self.out) + + +class TextReporter2(TextReporter): + """a reporter very similar to TextReporter, but display messages in a form + recognized by most text editors : + + <filename>:<linenum>:<msg> + """ + def __init__(self, output=sys.stdout, relative=True): + TextReporter.__init__(self, output) + if relative: + self._prefix = os.getcwd() + os.sep + else: + self._prefix = '' + + def add_message(self, msg_id, location, msg): + """manage message of different type and in the context of path""" + path, _, obj, line = location + if obj: + obj = ', %s' % obj + if self.include_ids: + sigle = msg_id + else: + sigle = msg_id[0] + if self._prefix: + path = path.replace(self._prefix, '') +## try: +## modpath = self._modules[module] +## except KeyError: +## modpath = self._modules[module] = self.linter.current_file or \ +## modname_to_path(module) + self.writeln('%s:%s: [%s%s] %s' % (path, line, sigle, obj, msg)) + + +class ColorizedTextReporter(TextReporter): + """Simple TextReporter that colorizes text output""" + + COLOR_MAPPING = { + "I" : ("green", None), + 'C' : (None, "bold"), + 'R' : ("magenta", "bold, italic"), + 'W' : ("blue", None), + 'E' : ("red", "bold"), + 'F' : ("red", "bold, underline"), + 'S' : ("yellow", "inverse"), # S stands for module Separator + } + + def __init__(self, output=sys.stdout, color_mapping = None): + TextReporter.__init__(self, output) + self.color_mapping = color_mapping or \ + dict(ColorizedTextReporter.COLOR_MAPPING) + + + def _get_decoration(self, msg_id): + """Returns the tuple color, style associated with msg_id as defined + in self.color_mapping + """ + try: + return self.color_mapping[msg_id] + except KeyError: + return None, None + + def add_message(self, msg_id, location, msg): + """manage message of different types, and colorize output + using ansi escape codes + """ + module, obj, line = location[1:] + if not self._modules.has_key(module): + color, style = self._get_decoration('S') + modsep = colorize_ansi('************* Module %s' % module, + color, style) + self.writeln(modsep) + self._modules[module] = 1 + if obj: + obj = ':%s' % obj + if self.include_ids: + sigle = msg_id + else: + sigle = msg_id[0] + color, style = self._get_decoration(sigle) + msg = colorize_ansi(msg, color, style) + sigle = colorize_ansi(sigle, color, style) + self.writeln('%s:%3s%s: %s' % (sigle, line, obj, msg)) + diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..78cfd76 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,3 @@ +[bdist_rpm] +packager = Sylvain Thenault <sylvain.thenault@logilab.fr> +provides = pylint diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..4127079 --- /dev/null +++ b/setup.py @@ -0,0 +1,187 @@ +#!/usr/bin/env python +# pylint: disable-msg=W0142,W0403,W0404,E0611,W0613,W0622,W0622,W0704,R0904 +# +# Copyright (c) 2003 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., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +""" Generic Setup script, takes package info from __pkginfo__.py file """ + +from __future__ import nested_scopes + +__revision__ = '$Id: setup.py,v 1.26 2006-03-05 15:47:17 syt Exp $' + +import os +import sys +import shutil +from distutils.core import setup +from distutils.command import install_lib +from os.path import isdir, exists, join, walk + +# import required features +from __pkginfo__ import modname, version, license, short_desc, long_desc, \ + web, author, author_email, classifiers +# import optional features +try: + from __pkginfo__ import distname +except ImportError: + distname = modname +try: + from __pkginfo__ import scripts +except ImportError: + scripts = [] +try: + from __pkginfo__ import data_files +except ImportError: + data_files = None +try: + from __pkginfo__ import subpackage_of +except ImportError: + subpackage_of = None +try: + from __pkginfo__ import include_dirs +except ImportError: + include_dirs = [] +try: + from __pkginfo__ import ext_modules +except ImportError: + ext_modules = None + +BASE_BLACKLIST = ('CVS', 'debian', 'dist', 'build', '__buildlog') +IGNORED_EXTENSIONS = ('.pyc', '.pyo', '.elc') + + +def ensure_scripts(linux_scripts): + """ + Creates the proper script names required for each platform + (taken from 4Suite) + """ + from distutils import util + if util.get_platform()[:3] == 'win': + scripts_ = [script + '.bat' for script in linux_scripts] + else: + scripts_ = linux_scripts + return scripts_ + + +def get_packages(directory, prefix): + """return a list of subpackages for the given directory + """ + result = [] + for package in os.listdir(directory): + absfile = join(directory, package) + if isdir(absfile): + if exists(join(absfile, '__init__.py')) or \ + package in ('test', 'tests'): + if prefix: + result.append('%s.%s' % (prefix, package)) + else: + result.append(package) + result += get_packages(absfile, result[-1]) + return result + +def export(from_dir, to_dir, + blacklist=BASE_BLACKLIST, + ignore_ext=IGNORED_EXTENSIONS): + """make a mirror of from_dir in to_dir, omitting directories and files + listed in the black list + """ + def make_mirror(arg, directory, fnames): + """walk handler""" + for norecurs in blacklist: + try: + fnames.remove(norecurs) + except ValueError: + pass + for filename in fnames: + # don't include binary files + if filename[-4:] in ignore_ext: + continue + if filename[-1] == '~': + continue + src = '%s/%s' % (directory, filename) + dest = to_dir + src[len(from_dir):] + print >> sys.stderr, src, '->', dest + if os.path.isdir(src): + if not exists(dest): + os.mkdir(dest) + else: + if exists(dest): + os.remove(dest) + shutil.copy2(src, dest) + try: + os.mkdir(to_dir) + except OSError, ex: + # file exists ? + import errno + if ex.errno != errno.EEXIST: + raise + walk(from_dir, make_mirror, None) + + +EMPTY_FILE = '"""generated file, don\'t modify or your data will be lost"""\n' + +class MyInstallLib(install_lib.install_lib): + """extend install_lib command to handle package __init__.py and + include_dirs variable if necessary + """ + def run(self): + """overriden from install_lib class""" + install_lib.install_lib.run(self) + # create Products.__init__.py if needed + if subpackage_of: + product_init = join(self.install_dir, subpackage_of, '__init__.py') + if not exists(product_init): + self.announce('creating %s' % product_init) + stream = open(product_init, 'w') + stream.write(EMPTY_FILE) + stream.close() + # manually install included directories if any + if include_dirs: + if subpackage_of: + base = join(subpackage_of, modname) + else: + base = modname + for directory in include_dirs: + dest = join(self.install_dir, base, directory) + export(directory, dest) + +def install(**kwargs): + """setup entry point""" + if subpackage_of: + package = subpackage_of + '.' + modname + kwargs['package_dir'] = {package : '.'} + packages = [package] + get_packages(os.getcwd(), package) + else: + kwargs['package_dir'] = {modname : '.'} + packages = [modname] + get_packages(os.getcwd(), modname) + kwargs['packages'] = packages + return setup(name = distname, + version = version, + license =license, + description = short_desc, + long_description = long_desc, + author = author, + author_email = author_email, + url = web, + classifiers = classifiers, + scripts = ensure_scripts(scripts), + data_files=data_files, + ext_modules=ext_modules, + cmdclass={'install_lib': MyInstallLib}, + **kwargs + ) + +if __name__ == '__main__' : + install() diff --git a/test/fulltest.sh b/test/fulltest.sh new file mode 100755 index 0000000..a625339 --- /dev/null +++ b/test/fulltest.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +if [ $@ ] ; then + PYVERSIONS=$@ +else + PYVERSIONS="2.2 2.3 2.4" +fi + +for ver in $PYVERSIONS; do + echo "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" + echo `python$ver -V` + echo "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^" + python$ver runtests.py + echo "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^" + echo `python$ver -V` -OO + python$ver -OO runtests.py +done
\ No newline at end of file diff --git a/test/func_test.py b/test/func_test.py new file mode 100644 index 0000000..3de8891 --- /dev/null +++ b/test/func_test.py @@ -0,0 +1,193 @@ +# Copyright (c) 2002-2005 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., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +"""functional/non regression tests for pylint""" + +__revision__ = '$Id: func_test.py,v 1.37 2005-12-28 14:58:22 syt Exp $' + +import unittest +import sys +import re +import new +from os import linesep +from os.path import exists + +from logilab.common import testlib + +from utils import get_tests_info, fix_path, TestReporter + +from pylint.lint import PyLinter +from pylint import checkers + +test_reporter = TestReporter() +linter = PyLinter() +linter.set_reporter(test_reporter) +linter.config.persistent = 0 +linter.quiet = 1 +checkers.initialize(linter) + +PY23 = sys.version_info >= (2, 3) +PY24 = sys.version_info >= (2, 4) + + +if linesep != '\n': + LINE_RGX = re.compile(linesep) + def ulines(string): + return LINE_RGX.sub('\n', string) +else: + def ulines(string): + return string + +INFO_TEST_RGX = re.compile('^func_i\d\d\d\d$') + +def exception_str(ex): + """function used to replace default __str__ method of exception instances""" + return 'in %s\n:: %s' % (ex.file, ', '.join(ex.args)) + +class LintTestUsingModule(testlib.TestCase): + + def test_functionality(self): + tocheck = ['input.'+self.module] + if self.depends: + tocheck += ['input.%s' % name.replace('.py', '') + for name, file in self.depends] + self._test(tocheck) + + def _test(self, tocheck): + if INFO_TEST_RGX.match(self.module): + linter.enable_message_category('I') + else: + linter.disable_message_category('I') + try: + linter.check(tocheck) + except Exception, ex: + # need finalization to restore a correct state + linter.reporter.finalize() + ex.file = tocheck + ex.__str__ = new.instancemethod(exception_str, ex, None) + raise + if self.module.startswith('func_noerror_'): + expected = '' + else: + output = open(self.output) + expected = output.read().strip() + output.close() + got = linter.reporter.finalize().strip() + try: + self.assertLinesEquals(got, expected) + except Exception, ex: + ex.file = tocheck + ex.__str__ = new.instancemethod(exception_str, ex, None) + raise # AssertionError('%s: %r\n!=\n%r\n\n%s' % (self.module, got, expected, ex)) + +class LintTestUsingFile(LintTestUsingModule): + + def test_functionality(self): + tocheck = ['input/' + self.module + '.py'] + if self.depends: + tocheck += ['input/%s' % name for name, file in self.depends] + self._test(tocheck) + + +class TestTests(unittest.TestCase): + """check that all testable messages have been checked""" + def test(self): + todo = linter._messages.keys() + for msg_id in test_reporter.message_ids.keys(): + todo.remove(msg_id) + todo.sort() + if PY23: + self.assertEqual(todo, ['E0503', 'F0002', 'F0202', 'F0321', 'I0001']) + else: + self.assertEqual(todo, ['F0002', 'F0202', 'F0321', 'I0001']) + + +def make_tests(filter_rgx): + """generate tests classes from test info + + return the list of generated test classes + """ + if filter_rgx: + is_to_run = re.compile(filter_rgx).match + else: + is_to_run = lambda x: 1 + tests = [] + for module_file, messages_file in get_tests_info('func_', '.py') + [('nonexistant', 'messages/nonexistant.txt')]: + # skip those tests with python >= 2.3 since py2.3 detects them by itself + if PY23 and module_file == "func_unknown_encoding.py": #"func_nonascii_noencoding.py"): + continue + if not PY24: + if module_file == "func_noerror_staticmethod_as_decorator.py" or \ + module_file.endswith('py24.py'): + continue + if not is_to_run(module_file): + continue + base = module_file.replace('func_', '').replace('.py', '') + dependancies = get_tests_info(base, '.py') + + class LintTestUsingModuleTC(LintTestUsingModule): + module = module_file.replace('.py', '') + output = messages_file + depends = dependancies or None + tests.append(LintTestUsingModuleTC) + + if MODULES_ONLY: + continue + + class LintTestUsingFileTC(LintTestUsingFile): + module = module_file.replace('.py', '') + output = exists(messages_file + '2') and (messages_file + '2') or messages_file + depends = dependancies or None + tests.append(LintTestUsingFileTC) + +## # special test for f0003 +## module_file, messages_file in get_tests_info('func_f0003', '.pyc') +## class LintTestSubclass(LintTest): +## module = module_file.replace('.pyc', '') +## output = messages_file +## depends = dependancies or None +## tests.append(LintTestSubclass) + + class LintBuiltinModuleTest(LintTestUsingModule): + output = 'messages/builtin_module.txt' + module = 'sys' + def test_functionality(self): + self._test(['sys']) + tests.append(LintBuiltinModuleTest) + + if not filter_rgx: + # test all features are tested :) + tests.append(TestTests) + + return tests + +FILTER_RGX = None +MODULES_ONLY = False + +def suite(): + return unittest.TestSuite([unittest.makeSuite(test) + for test in make_tests(FILTER_RGX)]) + +if __name__=='__main__': + if '-m' in sys.argv: + MODULES_ONLY = True + sys.argv.remove('-m') + + if len(sys.argv) > 1: + FILTER_RGX = sys.argv[1] + del sys.argv[1] + unittest.main(defaultTest='suite') + + diff --git a/test/func_test_sample_config.py b/test/func_test_sample_config.py new file mode 100644 index 0000000..a654827 --- /dev/null +++ b/test/func_test_sample_config.py @@ -0,0 +1,30 @@ +# Copyright (c) 2002-2004 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., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +"""functional tests using the sample configuration file, should behave exactly +as with the default configuration +""" +__revision__ = '$Id: func_test_sample_config.py,v 1.3 2005-04-15 10:40:24 syt Exp $' + +from func_test import * + +from os.path import join + +import pylint +sample_config = join(pylint.__path__[0], 'examples', 'pylintrc') +linter.load_file_configuration(sample_config) + +if __name__=='__main__': + unittest.main(defaultTest='suite') diff --git a/test/input/__init__.py b/test/input/__init__.py new file mode 100644 index 0000000..60e92b7 --- /dev/null +++ b/test/input/__init__.py @@ -0,0 +1 @@ +"""test""" diff --git a/test/input/func___future___import_not_first_stmt.py b/test/input/func___future___import_not_first_stmt.py new file mode 100644 index 0000000..2051a7b --- /dev/null +++ b/test/input/func___future___import_not_first_stmt.py @@ -0,0 +1,5 @@ +"""a docstring""" + +__revision__ = 1 +from __future__ import generators + diff --git a/test/input/func___name___access.py b/test/input/func___name___access.py new file mode 100644 index 0000000..25aed5c --- /dev/null +++ b/test/input/func___name___access.py @@ -0,0 +1,21 @@ +# pylint: disable-msg=R0903,W0142 +"""test access to __name__ gives undefined member on new/old class instances +but not on new/old class object +""" + +__revision__ = 1 + +class Aaaa: + """old class""" + def __init__(self): + print self.__name__ + print self.__class__.__name__ +class NewClass(object): + """new class""" + + def __new__(cls, *args, **kwargs): + print 'new', cls.__name__ + return object.__new__(cls, *args, **kwargs) + + def __init__(self): + print 'init', self.__name__ diff --git a/test/input/func_attrs_definition_order.py b/test/input/func_attrs_definition_order.py new file mode 100644 index 0000000..eda02e1 --- /dev/null +++ b/test/input/func_attrs_definition_order.py @@ -0,0 +1,15 @@ +# pylint: disable-msg=R0903 +"""yo""" + +__revision__ = '$I$' + +class Aaaa: + """class with attributes defined in wrong order""" + def __init__(self): + var1 = self._var2 + self._var2 = 3 + print var1 + +class Bbbb(object): + """hop""" + __revision__ = __revision__ # no problemo marge diff --git a/test/input/func_bad_assigment_to_exception_var.py b/test/input/func_bad_assigment_to_exception_var.py new file mode 100644 index 0000000..b404cb3 --- /dev/null +++ b/test/input/func_bad_assigment_to_exception_var.py @@ -0,0 +1,31 @@ +# pylint:disable-msg=C0103 +"""ho ho ho""" +__revision__ = 'toto' + +import sys + +e = 1 +e2 = 'yo' +e3 = None +try: + raise e, 'toto' +except Exception, ex: + print ex + _, _, tb = sys.exc_info() + raise e2 + + +def func(): + """bla bla bla""" + raise e3 + +def reraise(): + """reraise a catched exception instance""" + try: + raise Exception() + except Exception, exc: + print exc + raise exc + +raise e3 + diff --git a/test/input/func_base_stmt_without_effect.py b/test/input/func_base_stmt_without_effect.py new file mode 100644 index 0000000..f282ff7 --- /dev/null +++ b/test/input/func_base_stmt_without_effect.py @@ -0,0 +1,15 @@ +""" + 'W0103': ('Statement seems to have no effect', + 'Used when a statement doesn\'t have (or at least seems to) \ + any effect.'), +""" + +__revision__ = '' + +__revision__ + +__revision__ <= 1 + +__revision__.lower() # ok + +[i for i in __revision__] # ko diff --git a/test/input/func_block_disable_msg.py b/test/input/func_block_disable_msg.py new file mode 100644 index 0000000..f82fbd5 --- /dev/null +++ b/test/input/func_block_disable_msg.py @@ -0,0 +1,87 @@ +"""pylint option block-disable-msg""" + +__revision__ = None + +class Foo(object): + """block-disable-msg test""" + + def __init__(self): + pass + + def meth1(self, arg): + """this issues a message""" + print self + + def meth2(self, arg): + """and this one not""" + # pylint: disable-msg=W0613 + print self\ + + "foo" + + def meth3(self): + """test one line disabling""" + # no error + print self.bla # pylint: disable-msg=E1101 + # error + print self.blop + + def meth4(self): + """test re-enabling""" + # pylint: disable-msg=E1101 + # no error + print self.bla + print self.blop + # pylint: enable-msg=E1101 + # error + print self.blip + + def meth5(self): + """test IF sub-block re-enabling""" + # pylint: disable-msg=E1101 + # no error + print self.bla + if self.blop: + # pylint: enable-msg=E1101 + # error + print self.blip + else: + # no error + print self.blip + # no error + print self.blip + + def meth6(self): + """test TRY/EXCEPT sub-block re-enabling""" + # pylint: disable-msg=E1101 + # no error + print self.bla + try: + # pylint: enable-msg=E1101 + # error + print self.blip + except UndefinedName: # pylint: disable-msg=E0602 + # no error + print self.blip + # no error + print self.blip + + def meth7(self): + """test one line block opening disabling""" + if self.blop: # pylint: disable-msg=E1101 + # error + print self.blip + else: + # error + print self.blip + # error + print self.blip + + + def meth8(self): + """test late disabling""" + # error + print self.blip + # pylint: disable-msg=E1101 + # no error + print self.bla + print self.blop diff --git a/test/input/func_class_members.py b/test/input/func_class_members.py new file mode 100644 index 0000000..8b2e969 --- /dev/null +++ b/test/input/func_class_members.py @@ -0,0 +1,31 @@ +# pylint: disable-msg=R0903 +"""test class members""" + +__revision__ = '' + +class MyClass: + """class docstring""" + + def __init__(self): + """init""" + self.correct = 1 + + def test(self): + """test""" + self.correct += 2 + self.incorrect += 2 + self.nonexistent1.truc() + self.nonexistent2[1] = 'hehe' + +class XYZMixin: + """access to undefined members should be ignored in mixin classes by + default + """ + def __init__(self): + print self.nonexistent + + +class NewClass(object): + """use object.__setattr__""" + def __init__(self): + self.__setattr__('toto', 'tutu') diff --git a/test/input/func_continue_not_in_loop.py b/test/input/func_continue_not_in_loop.py new file mode 100644 index 0000000..4186aa5 --- /dev/null +++ b/test/input/func_continue_not_in_loop.py @@ -0,0 +1,14 @@ +"""this module produces a SyntaxError at execution time""" + +__revision__ = None + +def run(): + """simple function""" + if True: + continue + else: + break + +if __name__ == '__main__': + run() + diff --git a/test/input/func_dangerous_default.py b/test/input/func_dangerous_default.py new file mode 100644 index 0000000..0bf8727 --- /dev/null +++ b/test/input/func_dangerous_default.py @@ -0,0 +1,17 @@ +"""docstring""" + +__revision__ = '' + +HEHE = {} + +def function1(value = []): + """docstring""" + print value + +def function2(value = HEHE): + """docstring""" + print value + +def function3(value): + """docstring""" + print value diff --git a/test/input/func_docstring.py b/test/input/func_docstring.py new file mode 100644 index 0000000..7f28672 --- /dev/null +++ b/test/input/func_docstring.py @@ -0,0 +1,48 @@ +# pylint: disable-msg=R0201 + +__revision__ = '' + +def function1(value): + # missing docstring + print value + +def function2(value): + """docstring""" + print value + +def function3(value): + """docstring""" + print value + +class AAAA: + # missing docstring + +## class BBBB: +## # missing docstring +## pass + +## class CCCC: +## """yeah !""" +## def method1(self): +## pass + +## def method2(self): +## """ yeah !""" +## pass + + def method1(self): + pass + + def method2(self): + """ yeah !""" + pass + + def __init__(self): + pass + +class DDDD(AAAA): + """yeah !""" + + def __init__(self): + AAAA.__init__(self) + diff --git a/test/input/func_dotted_ancestor.py b/test/input/func_dotted_ancestor.py new file mode 100644 index 0000000..2d2f571 --- /dev/null +++ b/test/input/func_dotted_ancestor.py @@ -0,0 +1,11 @@ +"""bla""" + +__revision__ = 'yo' + + +from input import func_w0233 + +class Aaaa(func_w0233.AAAA): + """test dotted name in ancestors""" + def __init__(self): + func_w0233.AAAA.__init__(self) diff --git a/test/input/func_e0011.py b/test/input/func_e0011.py new file mode 100644 index 0000000..f2bb592 --- /dev/null +++ b/test/input/func_e0011.py @@ -0,0 +1,5 @@ +# pylint:bouboule=1 +"""check unknown option +""" +__revision__ = 1 + diff --git a/test/input/func_e0012.py b/test/input/func_e0012.py new file mode 100644 index 0000000..11ef99f --- /dev/null +++ b/test/input/func_e0012.py @@ -0,0 +1,5 @@ +# pylint:enable-msg=W04044 +"""check unknown option +""" +__revision__ = 1 + diff --git a/test/input/func_e0101.py b/test/input/func_e0101.py new file mode 100644 index 0000000..08e6402 --- /dev/null +++ b/test/input/func_e0101.py @@ -0,0 +1,11 @@ +# pylint: disable-msg=R0903 +"""test __init__ return +""" + +__revision__ = 'yo' + +class MyClass: + """dummy class""" + + def __init__(self): + return 1 diff --git a/test/input/func_e0203.py b/test/input/func_e0203.py new file mode 100644 index 0000000..d51de0d --- /dev/null +++ b/test/input/func_e0203.py @@ -0,0 +1,19 @@ +"""check for method without self as first argument +""" + +__revision__ = 0 + + +class Abcd: + """dummy class""" + def __init__(self): + pass + + def abcd(yoo): + """another test""" + + abcd = classmethod(abcd) + + def edf(self): + """justo ne more method""" + print 'yapudju in', self diff --git a/test/input/func_e0204.py b/test/input/func_e0204.py new file mode 100644 index 0000000..ecbc51c --- /dev/null +++ b/test/input/func_e0204.py @@ -0,0 +1,19 @@ +"""check for method without self as first argument +""" + +__revision__ = 0 + + +class Abcd: + """dummy class""" + + def __init__(truc): + """method without self""" + print 1 + + def abdc(yoo): + """another test""" + print yoo + def edf(self): + """just another method""" + print 'yapudju in', self diff --git a/test/input/func_e0205.py b/test/input/func_e0205.py new file mode 100644 index 0000000..2bb1089 --- /dev/null +++ b/test/input/func_e0205.py @@ -0,0 +1,17 @@ +# pylint: disable-msg=R0903 +"""check method hidding ancestor attribute +""" + +__revision__ = '' + +class Abcd: + """dummy""" + def __init__(self): + self.abcd = 1 + +class Cdef(Abcd): + """dummy""" + def abcd(self): + """test + """ + print self diff --git a/test/input/func_e0206.py b/test/input/func_e0206.py new file mode 100644 index 0000000..af32fd8 --- /dev/null +++ b/test/input/func_e0206.py @@ -0,0 +1,20 @@ +# pylint: disable-msg=R0903 +"""check for interface which are not classes""" + +__revision__ = None + +class Abcd: + """dummy""" + __implements__ = __revision__ + + def __init__(self): + self.attr = None + +class Cdef: + """dummy""" + __implements__ = (__revision__, Abcd) + + def __init__(self): + pass + + diff --git a/test/input/func_e0214.py b/test/input/func_e0214.py new file mode 100644 index 0000000..8a4ad32 --- /dev/null +++ b/test/input/func_e0214.py @@ -0,0 +1,18 @@ +"""mcs test""" + +__revision__ = 1 + +class MetaClass(type): + """a very intersting metaclass""" + def __new__(mcs, name, bases, cdict): + print mcs, name, bases, cdict + return type.__new__(mcs, name, bases, cdict) + + def whatever(self): + """should have mcs has first arg""" + print self + + def whatever_really(hop): + """could have anything has first arg""" + print hop + whatever_really = staticmethod(whatever_really) diff --git a/test/input/func_e0601.py b/test/input/func_e0601.py new file mode 100644 index 0000000..b8673df --- /dev/null +++ b/test/input/func_e0601.py @@ -0,0 +1,9 @@ +"""test local variable used before assigment +""" + +__revision__ = 0 + +def function(): + """dummy""" + print aaaa + aaaa = 1 diff --git a/test/input/func_empty_module.py b/test/input/func_empty_module.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/input/func_empty_module.py diff --git a/test/input/func_exceptions_raise_type_error.py b/test/input/func_exceptions_raise_type_error.py new file mode 100644 index 0000000..8c414b6 --- /dev/null +++ b/test/input/func_exceptions_raise_type_error.py @@ -0,0 +1,14 @@ +""" +'E0702': Raising an %s while only classes, instances or string are allowed + +Used when something which is neither a class, an instance or a string is +raised (i.e. a `TypeError` will be raised). +""" + +__revision__ = 1 + +if __revision__: + raise 1 + +if __revision__: + raise None diff --git a/test/input/func_f0001.py b/test/input/func_f0001.py new file mode 100644 index 0000000..af6de24 --- /dev/null +++ b/test/input/func_f0001.py @@ -0,0 +1,4 @@ +"""test astng error +""" +import whatever +__revision__ = None diff --git a/test/input/func_f0401.py b/test/input/func_f0401.py new file mode 100644 index 0000000..f8443fa --- /dev/null +++ b/test/input/func_f0401.py @@ -0,0 +1,9 @@ +"""tset failed import +""" + +__revision__ = 0 + +def function(): + """yo""" + from tutu import toto + print toto diff --git a/test/input/func_fixme.py b/test/input/func_fixme.py new file mode 100644 index 0000000..0cfe2bd --- /dev/null +++ b/test/input/func_fixme.py @@ -0,0 +1,9 @@ +"""docstring""" + +__revision__ = '' + +# FIXME: beep + +def function(): + '''XXX:bop''' + diff --git a/test/input/func_format.py b/test/input/func_format.py new file mode 100644 index 0000000..da66d63 --- /dev/null +++ b/test/input/func_format.py @@ -0,0 +1,61 @@ +# pylint:disable-msg=C0103,W0104 +"""Check format +""" +__revision__ = '' + +notpreceded= 1 +notfollowed =1 +notfollowed <=1 + +correct = 1 +correct >= 1 + +def func(arg, arg2): + """test named argument + """ + func(arg=arg+1, + arg2=arg2-arg) + +aaaa,bbbb = 1,2 +aaaa |= bbbb +aaaa &= bbbb + + +if aaaa: pass +else: + aaaa,bbbb = 1,2 + aaaa,bbbb = bbbb,aaaa + +bbbb = (1,2,3) + +aaaa = bbbb[1:] +aaaa = bbbb[:1] +aaaa = bbbb[:] + +aaaa = {aaaa:bbbb} + + +# allclose(x,y) uses |x-y|<ATOL+RTOL*|y| +"""docstring,should not match +isn't it:yes! +a=b +""" +aaaa = 'multiple lines\ +string,hehehe' + + +boo = 2 # allclose(x,y) uses |x-y|<ATOL+RTOL*|y| + +def other(funky): + """yo, test formatted result with indentation""" + funky= funky+2 + +html = """<option value="=">ist genau gleich</option> +yo+=4 +""" +html2 = """<option value='='>ist genau gleich</option> +yo+=4 +""" + +func('''<body>Hello +</body>''') diff --git a/test/input/func_globals.py b/test/input/func_globals.py new file mode 100644 index 0000000..f7e6c65 --- /dev/null +++ b/test/input/func_globals.py @@ -0,0 +1,40 @@ +""" +'W0601': ('global variable %s undefined at the module level', + 'Used when a variable is defined through the "global" statement \ + but the variable is not defined in the module scope.'), +'W0602': ('Using global for %s but no assigment is done', + 'Used when a variable is defined through the "global" statement \ + but no assigment to this variable is done.'), +'W0603': ('Using the global statement', # W0121 + 'Used when you use the "global" statement to update a global \ + variable. PyLint just try to discourage this \ + usage. That doesn\'t mean you can not use it !'), +'W0604': ('Using the global statement at the module level', # W0103 + 'Used when you use the "global" statement at the module level \ + since it has no effect'), +""" + +__revision__ = '' + +CONSTANT = 1 + +def fix_contant(value): + """all this is ok, but try not using global ;)""" + global CONSTANT + print CONSTANT + CONSTANT = value +global CSTE # useless +print CSTE # ko + +def other(): + """global behaviour test""" + global HOP + print HOP # ko + +other() + + +def define_constant(): + """ok but somevar is not defined at the module scope""" + global somevar + somevar = 2 diff --git a/test/input/func_i0010.py b/test/input/func_i0010.py new file mode 100644 index 0000000..8b2f436 --- /dev/null +++ b/test/input/func_i0010.py @@ -0,0 +1,3 @@ +# pylint: disable-all +"""disable-all is not usable as an inline option""" +__revision__ = None diff --git a/test/input/func_i0011.py b/test/input/func_i0011.py new file mode 100644 index 0000000..d8202f3 --- /dev/null +++ b/test/input/func_i0011.py @@ -0,0 +1,5 @@ +# pylint:disable-msg=W0404 +"""check warning on local disabling +""" +__revision__ = 1 + diff --git a/test/input/func_i0012.py b/test/input/func_i0012.py new file mode 100644 index 0000000..bf52138 --- /dev/null +++ b/test/input/func_i0012.py @@ -0,0 +1,5 @@ +# pylint:enable-msg=W0404 +"""check warning on local enabling +""" +__revision__ = 1 + diff --git a/test/input/func_indent.py b/test/input/func_indent.py new file mode 100644 index 0000000..57aae6e --- /dev/null +++ b/test/input/func_indent.py @@ -0,0 +1,22 @@ +"""docstring""" +__revision__ = '$Id: func_indent.py,v 1.4 2003-10-17 21:59:31 syt Exp $' + +def totoo(): + """docstring""" + print 'malindented' + +def tutuu(): + """docstring""" + print 'good indentation' + +def titii(): + """also malindented""" + +def tataa(kdict): + """blank line unindented""" + for key in [1, 2, 3]: + key = key.lower() + + if kdict.has_key(key): + del kdict[key] + diff --git a/test/input/func_init_vars.py b/test/input/func_init_vars.py new file mode 100644 index 0000000..9d89505 --- /dev/null +++ b/test/input/func_init_vars.py @@ -0,0 +1,46 @@ +"""Checks that class variables are seen as inherited ! +""" + +__revision__ = '' + + +class MyClass: + """Inherits from nothing + """ + + def __init__(self): + self.var = {} + + def met(self): + """Checks that base_var is seen as defined outside '__init__' + """ + self.var[1] = 'one' + self.base_var = 'one' + print self.base_var, self.var + + def met2(self): + """dummy method""" + print self +class MySubClass(MyClass): + """Inherits from MyClass + """ + class_attr = 1 + + def __init__(self): + MyClass.__init__(self) + self.var2 = 2 + print self.__doc__ + print self.__dict__ + print self.__class__ + + def met2(self): + """Checks that var is seen as defined outside '__init__' + """ + self.var[1] = 'one' + self.var2 += 1 + print self.class_attr + +if __name__ == '__main__': + OBJ = MyClass() + OBJ.met() + diff --git a/test/input/func_interfaces.py b/test/input/func_interfaces.py new file mode 100644 index 0000000..5081476 --- /dev/null +++ b/test/input/func_interfaces.py @@ -0,0 +1,99 @@ +# pylint:disable-msg=R0201 +"""docstring""" +__revision__ = '' + +class Interface: + """base class for interfaces""" + +class IMachin(Interface): + """docstring""" + def truc(self): + """docstring""" + + def troc(self, argument): + """docstring""" + +class Correct1: + """docstring""" + __implements__ = IMachin + + def __init__(self): + pass + + def truc(self): + """docstring""" + pass + + def troc(self, argument): + """docstring""" + pass + +class Correct2: + """docstring""" + __implements__ = (IMachin,) + + def __init__(self): + pass + + def truc(self): + """docstring""" + pass + + def troc(self, argument): + """docstring""" + print argument + +class MissingMethod: + """docstring""" + __implements__ = IMachin, + + def __init__(self): + pass + + def troc(self, argument): + """docstring""" + print argument + + def other(self): + """docstring""" + +class BadArgument: + """docstring""" + __implements__ = (IMachin,) + + def __init__(self): + pass + + def truc(self): + """docstring""" + pass + + def troc(self): + """docstring""" + pass + +class InterfaceCantBeFound: + """docstring""" + __implements__ = undefined + + def __init__(self): + """only to make pylint happier""" + + def please(self): + """public method 1/2""" + + def besilent(self): + """public method 2/2""" + +class InterfaceCantBeFound2: + """docstring""" + __implements__ = BadArgument.__implements__ + Correct2.__implements__ + + def __init__(self): + """only to make pylint happier""" + + def please(self): + """public method 1/2""" + + def besilent(self): + """public method 2/2""" diff --git a/test/input/func_method_could_be_function.py b/test/input/func_method_could_be_function.py new file mode 100644 index 0000000..5988b68 --- /dev/null +++ b/test/input/func_method_could_be_function.py @@ -0,0 +1,52 @@ +# pylint: disable-msg=R0903,R0922,W0232 +"""test detection of method which could be a function""" + +__revision__ = None + +class Toto(object): + """bla bal abl""" + + def __init__(self): + self.aaa = 2 + + def regular_method(self): + """this method is a real method since it access to self""" + self.function_method() + + def function_method(self): + """this method isn' a real method since it doesn't need self""" + print 'hello' + + +class Base: + """an abstract class""" + + def __init__(self): + self.aaa = 2 + + def check(self, arg): + """an abstract method, could not be a function""" + raise NotImplementedError + + +class Sub(Base): + """a concret class""" + + def check(self, arg): + """a concret method, could not be a function since it need + polymorphism benefits + """ + return arg == 0 + +class Super: + """same as before without abstract""" + x = 1 + def method(self): + """regular""" + print self.x + +class Sub1(Super): + """override method with need for self""" + def method(self): + """no i can not be a function""" + print 42 diff --git a/test/input/func_method_missing_self.py b/test/input/func_method_missing_self.py new file mode 100644 index 0000000..5bdbc69 --- /dev/null +++ b/test/input/func_method_missing_self.py @@ -0,0 +1,25 @@ +"""Checks that missing self in method defs don't crash Pylint ! +""" + +__revision__ = '' + + +class MyClass: + """SimpleClass + """ + + def __init__(self): + self.var = "var" + + def met(): + """Checks that missing self dont crash Pylint ! + """ + + def correct(self): + """yo""" + self.var = "correct" + +if __name__ == '__main__': + OBJ = MyClass() + OBJ.met() + diff --git a/test/input/func_method_without_self_but_self_assignment.py b/test/input/func_method_without_self_but_self_assignment.py new file mode 100644 index 0000000..e39266c --- /dev/null +++ b/test/input/func_method_without_self_but_self_assignment.py @@ -0,0 +1,15 @@ +# pylint: disable-msg=R0903 +"""regression test: setup() leads to "unable to load module..." +""" + +__revision__ = 1 + +class Example: + """bla""" + + def __init__(self): + pass + + def setup(): + "setup without self" + self.foo = 1 diff --git a/test/input/func_nameerror_on_string_substitution.py b/test/input/func_nameerror_on_string_substitution.py new file mode 100644 index 0000000..be7b5c8 --- /dev/null +++ b/test/input/func_nameerror_on_string_substitution.py @@ -0,0 +1,8 @@ +"""pylint doesn't see the NameError in this module""" + +__revision__ = None + +MSG = "hello %s" % MSG + +MSG2 = ("hello %s" % + MSG2) diff --git a/test/input/func_names_imported_from_module.py b/test/input/func_names_imported_from_module.py new file mode 100644 index 0000000..caf7387 --- /dev/null +++ b/test/input/func_names_imported_from_module.py @@ -0,0 +1,31 @@ +#pylint: disable-msg=W0401,W0611 +"""check unexistant names imported are reported""" + +__revision__ = None + +import logilab.common.tutu +from logilab.common import toto +toto.yo() + +from logilab.common import modutils +modutils.nonexistant_function() +modutils.another.nonexistant.function() +print logilab.common.modutils.yo + +import sys +print >> sys.stdout, 'hello world' +print >> sys.stdoout, 'bye bye world' + + +import re +re.finditer('*', 'yo') + +from rie import * +from re import findiiter, compiile + +import os +os.environ.has_key('SOMEVAR') + +import exceptions +print exceptions.__dict__ +print exceptions.__dict__.get('Exception') diff --git a/test/input/func_newstyle___slots__.py b/test/input/func_newstyle___slots__.py new file mode 100644 index 0000000..78909c0 --- /dev/null +++ b/test/input/func_newstyle___slots__.py @@ -0,0 +1,17 @@ +# pylint: disable-msg=R0903 +"""test __slots__ on old style class""" + +__revision__ = 1 + +class OkOk(object): + """correct usage""" + __slots__ = ('a', 'b') + +class HaNonNonNon: + """bad usage""" + __slots__ = ('a', 'b') + + def __init__(self): + pass + +__slots__ = 'hop' # pfff diff --git a/test/input/func_newstyle_exceptions.py b/test/input/func_newstyle_exceptions.py new file mode 100644 index 0000000..9a9cccd --- /dev/null +++ b/test/input/func_newstyle_exceptions.py @@ -0,0 +1,35 @@ +# pylint: disable-msg=C0103 +"""test pb with exceptions and old/new style classes""" + +__revision__ = 1 + +class OkException(Exception): + """bien bien bien""" + +class BofException: + """mouais""" + +class NewException(object): + """non !""" + +def fonctionOk(): + """raise""" + raise OkException('hop') + +def fonctionBof(): + """raise""" + raise BofException('hop') + +def fonctionNew(): + """raise""" + raise NewException() + +def fonctionBof2(): + """raise""" + raise BofException, 'hop' + +def fonctionNew2(): + """raise""" + raise NewException + + diff --git a/test/input/func_newstyle_property.py b/test/input/func_newstyle_property.py new file mode 100644 index 0000000..cdce8fa --- /dev/null +++ b/test/input/func_newstyle_property.py @@ -0,0 +1,19 @@ +# pylint: disable-msg=R0903 +"""test property on old style class""" + +__revision__ = 1 + +def getter(self): + """interesting""" + return self + +class OkOk(object): + """correct usage""" + method = property(getter, doc='hop') + +class HaNonNonNon: + """bad usage""" + method = property(getter, doc='hop') + + def __init__(self): + pass diff --git a/test/input/func_newstyle_super.py b/test/input/func_newstyle_super.py new file mode 100644 index 0000000..e84c908 --- /dev/null +++ b/test/input/func_newstyle_super.py @@ -0,0 +1,22 @@ +# pylint: disable-msg=R0903 +"""check use of super""" +__revision__ = None + +class Aaaa: + """old style""" + def hop(self): + """hop""" + super(Aaaa, self).hop() + + def __init__(self): + super(Aaaa, self).__init__() + +class NewAaaa(object): + """old style""" + def hop(self): + """hop""" + super(NewAaaa, self).hop() + + def __init__(self): + super(object, self).__init__() + diff --git a/test/input/func_noerror___future___import.py b/test/input/func_noerror___future___import.py new file mode 100644 index 0000000..5c77516 --- /dev/null +++ b/test/input/func_noerror___future___import.py @@ -0,0 +1,5 @@ +"""a docstring""" + +from __future__ import generators + +__revision__ = 1 diff --git a/test/input/func_noerror___init___return_from_inner_function.py b/test/input/func_noerror___init___return_from_inner_function.py new file mode 100644 index 0000000..30b2671 --- /dev/null +++ b/test/input/func_noerror___init___return_from_inner_function.py @@ -0,0 +1,13 @@ +# pylint: disable-msg=R0903 +"""#10075""" + +__revision__ = 1 + +class Aaa: + """docstring""" + def __init__(self): + def inner_function(arg): + """inner docstring""" + return arg + 4 + self.func = inner_function + diff --git a/test/input/func_noerror_access_attr_before_def_false_positive.py b/test/input/func_noerror_access_attr_before_def_false_positive.py new file mode 100644 index 0000000..048d938 --- /dev/null +++ b/test/input/func_noerror_access_attr_before_def_false_positive.py @@ -0,0 +1,100 @@ +#pylint: disable-msg=C0103,R0904,R0903,W0201 +""" +This module demonstrates a possible problem of pyLint with calling __init__ s +from inherited classes. +Initializations done there are not considered, which results in Error E0203 for +self.cookedq. +""" + +__revision__ = 'yo' + +import telnetlib + +class SeeTelnet(telnetlib.Telnet): + """ + Extension of telnetlib. + """ + + def __init__(self, host=None, port=0): + """ + Constructor. + When called without arguments, create an unconnected instance. + With a hostname argument, it connects the instance; a port + number is optional. + Parameter: + - host: IP address of the host + - port: Port number + """ + telnetlib.Telnet.__init__(self, host, port) + + def readUntilArray(self, matches, _=None): + """ + Read until a given string is encountered or until timeout. + ... + """ + self.process_rawq() + maxLength = 0 + index = -1 + for match in matches: + index += 1 + if len(match) > maxLength: + maxLength = len(match) + +class Base(object): + """bla bla""" + dougloup_papa = None + + def __init__(self): + self._var = False + +class Derived(Base): + """derived blabla""" + dougloup_moi = None + def Work(self): + """do something""" + # E0203 - Access to member '_var' before its definition + if self._var: + print "True" + else: + print "False" + self._var = True + + # E0203 - Access to member 'dougloup_papa' before its definition + if self.dougloup_papa: + print 'dougloup !' + self.dougloup_papa = True + # E0203 - Access to member 'dougloup_moi' before its definition + if self.dougloup_moi: + print 'dougloup !' + self.dougloup_moi = True + + +class QoSALConnection(object): + """blabla""" + + _the_instance = None + + def __new__(cls): + if cls._the_instance is None: + cls._the_instance = object.__new__(cls) + return cls._the_instance + + def __init__(self): + pass + +class DefinedOutsideInit: + """use_attr is seen as the method defining attr because its in + first position + """ + def __init__(self): + self.reset() + + def use_attr(self): + """use and set members""" + if self.attr: + print 'hop' + self.attr = 10 + + def reset(self): + """reset members""" + self.attr = 4 diff --git a/test/input/func_noerror_base_init_vars.py b/test/input/func_noerror_base_init_vars.py new file mode 100644 index 0000000..d83ebbc --- /dev/null +++ b/test/input/func_noerror_base_init_vars.py @@ -0,0 +1,36 @@ +# pylint:disable-msg=R0201 +"""Checks that class variables are seen as inherited ! +""" +__revision__ = '' + +class BaseClass: + """A simple base class + """ + + def __init__(self): + self.base_var = {} + + def met(self): + """yo""" + def meeting(self, with): + """ye""" + return with +class MyClass(BaseClass): + """Inherits from BaseClass + """ + + def __init__(self): + BaseClass.__init__(self) + self.var = {} + + def met(self): + """Checks that base_var is not seen as defined outsite '__init__' + """ + self.var[1] = 'one' + self.base_var[1] = 'one' + print self.base_var, self.var + +if __name__ == '__main__': + OBJ = MyClass() + OBJ.met() + diff --git a/test/input/func_noerror_builtin_module_test.py b/test/input/func_noerror_builtin_module_test.py new file mode 100644 index 0000000..4d6ba0a --- /dev/null +++ b/test/input/func_noerror_builtin_module_test.py @@ -0,0 +1,11 @@ +"""test import from a builtin module""" + +__revision__ = None + +from math import log10 + +def log10_2(): + """bla bla bla""" + return log10(2) + + diff --git a/test/input/func_noerror_defined_and_used_on_same_line.py b/test/input/func_noerror_defined_and_used_on_same_line.py new file mode 100644 index 0000000..87d3180 --- /dev/null +++ b/test/input/func_noerror_defined_and_used_on_same_line.py @@ -0,0 +1,20 @@ +#pylint: disable-msg=C0111,C0321 +"""pylint complains about 'index' being used before definition""" + +__revision__ = None + +print [index + for index in range(10)] + + +FILTER_FUNC = lambda x: not x + +def func(xxx): return xxx + +def func2(xxx): return xxx + func2(1) + +import sys; print sys.exc_info( ) + +for i in range(10): print i + +j = 4; LAMB = lambda x: x+j diff --git a/test/input/func_noerror_defined_and_used_on_same_line_py24.py b/test/input/func_noerror_defined_and_used_on_same_line_py24.py new file mode 100644 index 0000000..8032a8f --- /dev/null +++ b/test/input/func_noerror_defined_and_used_on_same_line_py24.py @@ -0,0 +1,7 @@ +#pylint: disable-msg=C0111,C0321 +"""pylint complains about 'index' being used before definition""" + +__revision__ = None + +print (index + for index in range(10)) diff --git a/test/input/func_noerror_e1101_13784.py b/test/input/func_noerror_e1101_13784.py new file mode 100644 index 0000000..b247b44 --- /dev/null +++ b/test/input/func_noerror_e1101_13784.py @@ -0,0 +1,15 @@ +"""cf #13784 +""" + +__revision__ = None + +def no_conjugate_member(magic_flag): + """should not raise E1101 on something.conjugate""" + if magic_flag: + something = 1.0 + else: + something = 1.0j + if isinstance(something, float): + return something + return something.conjugate() + diff --git a/test/input/func_noerror_e1101_but_getattr.py b/test/input/func_noerror_e1101_but_getattr.py new file mode 100644 index 0000000..143ddc0 --- /dev/null +++ b/test/input/func_noerror_e1101_but_getattr.py @@ -0,0 +1,23 @@ +"""don't want E1101 if __getattr__ is defined""" + +__revision__ = None + +class MyString: + """proxied string""" + + def __init__(self, string): + self.string = string + + def __getattr__(self, attr): + return getattr(self.string, attr) + + def lower(self): + """string.lower""" + return self.string.lower() + + def upper(self): + """string.upper""" + return self.string.upper() + +MYSTRING = MyString("abc") +print MYSTRING.title() diff --git a/test/input/func_noerror_encoding.py b/test/input/func_noerror_encoding.py new file mode 100644 index 0000000..2e945a5 --- /dev/null +++ b/test/input/func_noerror_encoding.py @@ -0,0 +1,6 @@ +# -*- coding: ISO-8859-1 -*- +""" check correct encoding declaration +""" + +__revision__ = 'éééé' + diff --git a/test/input/func_noerror_exception.py b/test/input/func_noerror_exception.py new file mode 100644 index 0000000..1c3d8b5 --- /dev/null +++ b/test/input/func_noerror_exception.py @@ -0,0 +1,7 @@ +""" module doc """ +__revision__ = '' + +class MyException(Exception): + """a custom exception with its *own* __init__ !!""" + def __init__(self, msg): + Exception.__init__(self, msg) diff --git a/test/input/func_noerror_indirect_interface.py b/test/input/func_noerror_indirect_interface.py new file mode 100644 index 0000000..2b240f9 --- /dev/null +++ b/test/input/func_noerror_indirect_interface.py @@ -0,0 +1,16 @@ +"""shows a bug where pylint can't find interfaces when they are +used indirectly. See input/indirect[123].py for details on the +setup""" + +__revision__ = None + +from input.indirect2 import AbstractToto + +class ConcreteToto(AbstractToto): + """abstract to implements an interface requiring machin to be defined""" + def __init__(self): + self.duh = 2 + + def machin(self): + """for ifacd""" + return self.helper()*2 diff --git a/test/input/func_noerror_inner_classes.py b/test/input/func_noerror_inner_classes.py new file mode 100644 index 0000000..a1a848d --- /dev/null +++ b/test/input/func_noerror_inner_classes.py @@ -0,0 +1,33 @@ +# pylint: disable-msg=R0903 +"""Backend Base Classes for the schwelm user DB""" + +__revision__ = "alpha" + +class Aaa(object): + """docstring""" + def __init__(self): + self.__setattr__('a','b') + pass + + def one_public(self): + """docstring""" + pass + + def another_public(self): + """docstring""" + pass + +class Bbb(Aaa): + """docstring""" + pass + +class Ccc(Aaa): + """docstring""" + + class Ddd(Aaa): + """docstring""" + pass + + class Eee(Ddd): + """docstring""" + pass diff --git a/test/input/func_noerror_mcs_attr_access.py b/test/input/func_noerror_mcs_attr_access.py new file mode 100644 index 0000000..c42c061 --- /dev/null +++ b/test/input/func_noerror_mcs_attr_access.py @@ -0,0 +1,20 @@ +# pylint: disable-msg=R0903 +"""test attribute access on metaclass""" + + +__revision__ = 'yo' + +class Meta(type): + """the meta class""" + def __init__(mcs, name, bases, dictionary): + super(Meta, mcs).__init__(name, bases, dictionary) + print mcs, mcs._meta_args + delattr(mcs, '_meta_args') + +class Test(object): + """metaclassed class""" + __metaclass__ = Meta + _meta_args = ('foo', 'bar') + + def __init__(self): + print '__init__', self diff --git a/test/input/func_noerror_nested_classes.py b/test/input/func_noerror_nested_classes.py new file mode 100644 index 0000000..96cd366 --- /dev/null +++ b/test/input/func_noerror_nested_classes.py @@ -0,0 +1,18 @@ +# pylint: disable-msg=R0903 +"""crash test""" + +__revision__ = 1 + +class Temelekefe: + """gloubliboulga""" + + def __init__(self): + """nested class with function raise error""" + class Toto: + """toto nested class""" + def __init__(self): + self.attr = 2 + def toto_method(self): + """toto nested class method""" + print self + print 'error ?', self, Toto diff --git a/test/input/func_noerror_new_style_class.py b/test/input/func_noerror_new_style_class.py new file mode 100644 index 0000000..8c9ed40 --- /dev/null +++ b/test/input/func_noerror_new_style_class.py @@ -0,0 +1,45 @@ +"""check builtin data descriptors such as mode and name attributes +on a file are correctly handler + +bug notified by Pierre Rouleau on 2005-04-24 +""" + +__revision__ = None + +class File(file): + """ Testing new-style class inheritance from file""" + + # + def __init__(self, name, mode="r", buffering=-1, verbose=False): + """Constructor""" + + self.was_modified = False + self.verbose = verbose + super(File, self).__init__(name, mode, buffering) + if self.verbose: + print "File %s is opened. The mode is: %s" % (self.name, +self.mode) + + # + def write(self, a_string): + """ Write a string to the file.""" + + super(File, self).write(a_string) + self.was_modified = True + + # + def writelines(self, sequence): + """ Write a sequence of strings to the file. """ + + super(File, self).writelines(sequence) + self.was_modified = True + + # + def close(self) : + """Close the file.""" + + if self.verbose: + print "Closing file %s" % self.name + + super(File, self).close() + self.was_modified = False diff --git a/test/input/func_noerror_object_as_class_attribute.py b/test/input/func_noerror_object_as_class_attribute.py new file mode 100644 index 0000000..6b442ea --- /dev/null +++ b/test/input/func_noerror_object_as_class_attribute.py @@ -0,0 +1,19 @@ +# pylint: disable-msg=R0903 +"""Test case for the problem described below : + - A class extends 'object' + - This class defines its own __init__() + * pylint will therefore check that baseclasses' init() + are called + - If this class defines an 'object' attribute, then pylint + will use this new definition when trying to retrieve + object.__init__() +""" + +__revision__ = None + +class Statement(object): + """ ... """ + def __init__(self): + pass + object = None + diff --git a/test/input/func_noerror_socket_member.py b/test/input/func_noerror_socket_member.py new file mode 100644 index 0000000..8c79ff5 --- /dev/null +++ b/test/input/func_noerror_socket_member.py @@ -0,0 +1,25 @@ +"""Testing Pylint with the socket module + +Pylint Problem +============== + +Version used: + + - Pylint 0.10.0 + - Logilab common 0.15.0 + - Logilab astng 0.15.1 + +False E1101 positive, line 23: + + Instance of '_socketobject' has no 'connect' member + +""" +__revision__ = None + +import socket + +if __name__ == "__main__": + + SCKT = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + SCKT.connect(('127.0.0.1', 80)) + SCKT.close() diff --git a/test/input/func_noerror_static_method.py b/test/input/func_noerror_static_method.py new file mode 100644 index 0000000..6184961 --- /dev/null +++ b/test/input/func_noerror_static_method.py @@ -0,0 +1,29 @@ +"""Checks if static / class methods works fine in Pylint +""" + +__revision__ = '' + +class MyClass: + """doc + """ + def __init__(self): + pass + + def static_met(var1, var2): + """This is a static method + """ + print var1, var2 + + def class_met(cls, var1): + """This is a class method + """ + print cls, var1 + + static_met = staticmethod(static_met) + class_met = classmethod(class_met) + +if __name__ == '__main__': + MyClass.static_met("var1","var2") + MyClass.class_met("var1") + + diff --git a/test/input/func_noerror_staticmethod_as_decorator.py b/test/input/func_noerror_staticmethod_as_decorator.py new file mode 100644 index 0000000..e5a44f2 --- /dev/null +++ b/test/input/func_noerror_staticmethod_as_decorator.py @@ -0,0 +1,35 @@ +# pylint: disable-msg=R0903 +"""test staticmethod and classmethod as decorator""" + +__revision__ = None + +class StaticMethod1(object): + """staticmethod test""" + def __init__(self): + pass + + @staticmethod + def do_work(): + "Working..." + + @staticmethod + def do_work_with_arg(job): + "Working on something" + print "Working on %s..." % job + + +class ClassMethod2(object): + """classmethod test""" + def __init__(self): + pass + + @classmethod + def do_work(cls): + "Working..." + + @classmethod + def do_work_with_arg(cls, job): + "Working on something" + print "Working on %s..." % job + + diff --git a/test/input/func_noerror_w0232.py b/test/input/func_noerror_w0232.py new file mode 100644 index 0000000..f4c4232 --- /dev/null +++ b/test/input/func_noerror_w0232.py @@ -0,0 +1,10 @@ +# pylint: disable-msg=R0903,R0923 +"""check interface and exception without __init__ doesn't print warnings +""" +__revision__ = '' + +class Interface: + """interface without docstring""" + +class MyError(Exception): + """exception without docstring""" diff --git a/test/input/func_nonascii_noencoding.py b/test/input/func_nonascii_noencoding.py new file mode 100644 index 0000000..1ba3578 --- /dev/null +++ b/test/input/func_nonascii_noencoding.py @@ -0,0 +1,5 @@ +"""test file with non ascii characters and no encoding declaration""" + +__revision__ = '' + +YOP = 'héhéhé' diff --git a/test/input/func_r0901.py b/test/input/func_r0901.py new file mode 100644 index 0000000..d30d270 --- /dev/null +++ b/test/input/func_r0901.py @@ -0,0 +1,27 @@ +# pylint: disable-msg=W0232, R0903 +"""test max parents""" +__revision__ = None + +class Aaaa: + """yo""" +class Bbbb: + """yo""" +class Cccc: + """yo""" +class Dddd: + """yo""" +class Eeee: + """yo""" +class Ffff: + """yo""" +class Gggg: + """yo""" +class Hhhh: + """yo""" + +class Iiii(Aaaa, Bbbb, Cccc, Dddd, Eeee, Ffff, Gggg, Hhhh): + """yo""" + +class Jjjj(Iiii): + """yo""" + diff --git a/test/input/func_r0902.py b/test/input/func_r0902.py new file mode 100644 index 0000000..59d4100 --- /dev/null +++ b/test/input/func_r0902.py @@ -0,0 +1,28 @@ +# pylint: disable-msg=R0903 +"""test max instance attributes""" +__revision__ = None + +class Aaaa: + """yo""" + def __init__(self): + self.aaaa = 1 + self.bbbb = 2 + self.cccc = 3 + self.dddd = 4 + self.eeee = 5 + self.ffff = 6 + self.gggg = 7 + self.hhhh = 8 + self.iiii = 9 + self.jjjj = 10 + self._aaaa = 1 + self._bbbb = 2 + self._cccc = 3 + self._dddd = 4 + self._eeee = 5 + self._ffff = 6 + self._gggg = 7 + self._hhhh = 8 + self._iiii = 9 + self._jjjj = 10 + self.tomuch = None diff --git a/test/input/func_r0903.py b/test/input/func_r0903.py new file mode 100644 index 0000000..4c11929 --- /dev/null +++ b/test/input/func_r0903.py @@ -0,0 +1,13 @@ +"""test min methods""" +__revision__ = None + +class Aaaa: + """yo""" + def __init__(self): + pass + def meth1(self): + """hehehe""" + print self + def _dontcount(self): + """not public""" + print self diff --git a/test/input/func_r0904.py b/test/input/func_r0904.py new file mode 100644 index 0000000..abb4d06 --- /dev/null +++ b/test/input/func_r0904.py @@ -0,0 +1,73 @@ +# pylint: disable-msg=R0201 +"""test max methods""" +__revision__ = None +class Aaaa: + """yo""" + def __init__(self): + pass + + def meth1(self): + """hehehe""" + + def meth2(self): + """hehehe""" + + def meth3(self): + """hehehe""" + + def meth4(self): + """hehehe""" + + def meth5(self): + """hehehe""" + + def meth6(self): + """hehehe""" + + def meth7(self): + """hehehe""" + + def meth8(self): + """hehehe""" + + def meth9(self): + """hehehe""" + + def meth10(self): + """hehehe""" + + def meth11(self): + """hehehe""" + + def meth12(self): + """hehehe""" + + def meth13(self): + """hehehe""" + + def meth14(self): + """hehehe""" + + def meth15(self): + """hehehe""" + + def meth16(self): + """hehehe""" + + def meth17(self): + """hehehe""" + + def meth18(self): + """hehehe""" + + def meth19(self): + """hehehe""" + + def meth20(self): + """hehehe""" + + def meth21(self): + """hehehe""" + + def _dontcount(self): + """not public""" diff --git a/test/input/func_r0921.py b/test/input/func_r0921.py new file mode 100644 index 0000000..b9f2de2 --- /dev/null +++ b/test/input/func_r0921.py @@ -0,0 +1,15 @@ +"""test max methods""" +__revision__ = None + +class Aaaa: + """yo""" + def __init__(self): + pass + + def meth1(self): + """hehehe""" + raise NotImplementedError + + def meth2(self): + """hehehe""" + return 'Yo', self diff --git a/test/input/func_r0922.py b/test/input/func_r0922.py new file mode 100644 index 0000000..da7dfcf --- /dev/null +++ b/test/input/func_r0922.py @@ -0,0 +1,21 @@ +"""test max methods""" +__revision__ = None + +class Aaaa: + """yo""" + def __init__(self): + pass + + def meth1(self): + """hehehe""" + raise NotImplementedError + + def meth2(self): + """hehehe""" + return 'Yo', self + +class Bbbb(Aaaa): + """yeah""" + def meth1(self): + """hehehe bis""" + return "yeah", self diff --git a/test/input/func_r0923.py b/test/input/func_r0923.py new file mode 100644 index 0000000..8dacf1e --- /dev/null +++ b/test/input/func_r0923.py @@ -0,0 +1,32 @@ +"""test max methods""" +__revision__ = None + +from logilab.common.interface import Interface + +class IAaaa(Interface): + """yo""" + + def meth1(self): + """hehehe""" + +class IBbbb(Interface): + """yo""" + + def meth1(self): + """hehehe""" + +class Concret: + """implements IBbbb""" + __implements__ = IBbbb + + def __init__(self): + pass + + def meth1(self): + """hehehe""" + return "et hop", self + + def meth2(self): + """hehehe""" + return "et hop", self + diff --git a/test/input/func_reqattrs.py b/test/input/func_reqattrs.py new file mode 100644 index 0000000..fb1e2b6 --- /dev/null +++ b/test/input/func_reqattrs.py @@ -0,0 +1 @@ +"""docstring""" diff --git a/test/input/func_scope_regrtest.py b/test/input/func_scope_regrtest.py new file mode 100644 index 0000000..98dd8be --- /dev/null +++ b/test/input/func_scope_regrtest.py @@ -0,0 +1,15 @@ +# pylint: disable-msg=R0903,W0232 +"""check for scope problems""" + +__revision__ = None + +class Well(object): + """well""" + class Data: + """base hidden class""" + class Sub(Data): + """whaou, is Data found???""" + yo = Data() + def func(self): + """check Sub is not defined here""" + return Sub(), self diff --git a/test/input/func_syntax_error.py b/test/input/func_syntax_error.py new file mode 100644 index 0000000..43fa087 --- /dev/null +++ b/test/input/func_syntax_error.py @@ -0,0 +1 @@ +def toto diff --git a/test/input/func_toolonglines.py b/test/input/func_toolonglines.py new file mode 100644 index 0000000..76a4018 --- /dev/null +++ b/test/input/func_toolonglines.py @@ -0,0 +1,4 @@ +########################################################################################## +""" that one is too long tooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo""" + +__revision__ = '' diff --git a/test/input/func_typecheck_callfunc_assigment.py b/test/input/func_typecheck_callfunc_assigment.py new file mode 100644 index 0000000..82130e3 --- /dev/null +++ b/test/input/func_typecheck_callfunc_assigment.py @@ -0,0 +1,56 @@ +# pylint: disable-msg=R0921 +"""check assigment to function call where the function doesn't return + + 'E1111': ('Assigning to function call which doesn\'t return', + 'Used when an assigment is done on a function call but the \ + infered function doesn\'t return anything.'), + 'W1111': ('Assigning to function call which only returns None', + 'Used when an assigment is done on a function call but the \ + infered function returns nothing but None.'), + +""" +from __future__ import generators +__revision__ = None + + +def func_no_return(): + """function without return""" + print 'dougloup' + +A = func_no_return() + + +def func_return_none(): + """function returning none""" + print 'dougloup' + return None + +A = func_return_none() + + +def func_return_none_and_smth(): + """function returning none and something else""" + print 'dougloup' + if __revision__: + return None + return 3 + +A = func_return_none_and_smth() + +def generator(): + """no problemo""" + yield __revision__ + +A = generator() + +class Abstract(object): + """bla bla""" + + def abstract_method(self): + """use to return something in concrete implementation""" + raise NotImplementedError + + def use_abstract(self): + """should not issue E1111""" + var = self.abstract_method() + print var diff --git a/test/input/func_typecheck_getattr.py b/test/input/func_typecheck_getattr.py new file mode 100644 index 0000000..dc50292 --- /dev/null +++ b/test/input/func_typecheck_getattr.py @@ -0,0 +1,60 @@ +# pylint: disable-msg= +"""check getattr if inference succeed""" + +__revision__ = None + +class Provider: + """provide some attributes and method""" + cattr = 4 + def __init__(self): + self.attr = 4 + def method(self, val): + """impressive method""" + return self.attr * val + def hophop(self): + """hop method""" + print 'hop hop hop', self + + +class Client: + """use provider class""" + + def __init__(self): + self._prov = Provider() + self._prov_attr = Provider.cattr + self._prov_attr2 = Provider.cattribute + + def use_method(self): + """use provider's method""" + self._prov.hophop() + self._prov.hophophop() + + def use_attr(self): + """use provider's attr""" + print self._prov.attr + print self._prov.attribute + + def debug(self): + """print debug information""" + print self.__class__.__name__ + print self.__doc__ + print self.__dict__ + print self.__module__ + + def test_bt_types(self): + """test access to unexistant member of builtin types""" + lis = [] + lis.apppend(self) + dic = {} + dic.set(self) + tup = () + tup.append(self) + string = 'toto' + print string.loower() + uni = u'toto' + print uni.loower() + integer = 1 + print integer.whatever + +print object.__init__ +print property.__init__ diff --git a/test/input/func_typecheck_non_callable_call.py b/test/input/func_typecheck_non_callable_call.py new file mode 100644 index 0000000..75b1224 --- /dev/null +++ b/test/input/func_typecheck_non_callable_call.py @@ -0,0 +1,37 @@ +# pylint: disable-msg=R0903 +""" + 'E1102': ('%s is not callable', + 'Used when an object being called has been infered to a non \ + callable object'), +""" + +__revision__ = None + +__revision__() + +def correct(): + """callable object""" + return 1 + +__revision__ = correct() + +class Correct(object): + """callable object""" + +class MetaCorrect(object): + """callable object""" + def __call__(self): + return self + +INSTANCE = Correct() +CALLABLE_INSTANCE = MetaCorrect() +CORRECT = CALLABLE_INSTANCE() +INCORRECT = INSTANCE() +LIST = [] +INCORRECT = LIST() +DICT = {} +INCORRECT = DICT() +TUPLE = () +INCORRECT = TUPLE() +INT = 1 +INCORRECT = INT() diff --git a/test/input/func_undefined_var.py b/test/input/func_undefined_var.py new file mode 100644 index 0000000..3166260 --- /dev/null +++ b/test/input/func_undefined_var.py @@ -0,0 +1,26 @@ +"""test access to undefined variables""" + +__revision__ = '$Id:' + +DEFINED = 1 + +if DEFINED != 1: + if DEFINED in (unknown, DEFINED): + DEFINED += 1 + + +def in_method(var): + """method doc""" + var = nomoreknown + assert var + +DEFINED = {DEFINED:__revision__} +DEFINED[__revision__] = OTHER = 'move this is astng test' + +OTHER += '$' + +def bad_default(var, default=unknown2): + """function with defaut arg's value set to an unexistant name""" + print var, default + print xxxx + print xxxx #see story #1000 diff --git a/test/input/func_unknown_encoding.py b/test/input/func_unknown_encoding.py new file mode 100644 index 0000000..31deabd --- /dev/null +++ b/test/input/func_unknown_encoding.py @@ -0,0 +1,6 @@ +# -*- coding: IBO-8859-1 -*- +""" check correct unknown encoding declaration +""" + +__revision__ = 'éééé' + diff --git a/test/input/func_unreachable.py b/test/input/func_unreachable.py new file mode 100644 index 0000000..1cabce7 --- /dev/null +++ b/test/input/func_unreachable.py @@ -0,0 +1,22 @@ +"""docstring""" + +__revision__ = '' + +def func1(): + """docstring""" + return 1 + print 'unreachable' + +def func2(): + """docstring""" + while 1: + break + print 'unreachable' + +def func3(): + """docstring""" + for i in (1, 2, 3): + print i + continue + print 'unreachable' + diff --git a/test/input/func_use_for_or_listcomp_var.py b/test/input/func_use_for_or_listcomp_var.py new file mode 100644 index 0000000..b0db336 --- /dev/null +++ b/test/input/func_use_for_or_listcomp_var.py @@ -0,0 +1,21 @@ +"""test a warning is triggered when using for a lists comprehension variable""" + +__revision__ = 'yo' + +TEST_LC = [C for C in __revision__ if C.isalpha()] +print C # WARN +C = 4 +print C # this one shouldn't trigger any warning + +B = [B for B in __revision__ if B.isalpha()] +print B # nor this one + +for var1, var2 in TEST_LC: + var1 = var2 + 4 +print var1 # WARN + +for note in __revision__: + note.something() +for line in __revision__: + for note in line: + A = note.anotherthing() diff --git a/test/input/func_w0101.py b/test/input/func_w0101.py new file mode 100644 index 0000000..fe543aa --- /dev/null +++ b/test/input/func_w0101.py @@ -0,0 +1,28 @@ +"""test max returns +""" + +__revision__ = '' + +def stupid_function(arg): + """reallly stupid function""" + if arg == 1: + return 1 + elif arg == 2: + return 2 + elif arg == 3: + return 3 + elif arg == 4: + return 4 + elif arg == 5: + return 5 + elif arg == 6: + return 6 + elif arg == 7: + return 7 + elif arg == 8: + return 8 + elif arg == 9: + return 9 + elif arg == 10: + return 10 + return None diff --git a/test/input/func_w0102.py b/test/input/func_w0102.py new file mode 100644 index 0000000..b096fff --- /dev/null +++ b/test/input/func_w0102.py @@ -0,0 +1,53 @@ +# pylint: disable-msg=R0201 +"""docstring""" +__revision__ = '' + +class AAAA: + """docstring""" + def __init__(self): + pass + def method1(self): + """docstring""" + + def method2(self): + """docstring""" + + def method2(self): + """docstring""" + +class AAAA: + """docstring""" + def __init__(self): + pass + def yeah(self): + """hehehe""" + def yoo(self): + """yoo""" +def func1(): + """docstring""" + +def func2(): + """docstring""" + +def func2(): + """docstring""" + __revision__ = 1 + return __revision__ + +if __revision__: + def exclusive_func(): + "docstring" +else: + def exclusive_func(): + "docstring" + +try: + def exclusive_func2(): + "docstring" +except TypeError: + def exclusive_func2(): + "docstring" +else: + def exclusive_func2(): + "this one redefine the one defined line 42" + diff --git a/test/input/func_w0103.py b/test/input/func_w0103.py new file mode 100644 index 0000000..9417cb5 --- /dev/null +++ b/test/input/func_w0103.py @@ -0,0 +1,8 @@ +"""test max branch +""" + +__revision__ = '' + +def stupid_function(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9): + """reallly stupid function""" + print arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9 diff --git a/test/input/func_w0104.py b/test/input/func_w0104.py new file mode 100644 index 0000000..bddf001 --- /dev/null +++ b/test/input/func_w0104.py @@ -0,0 +1,12 @@ +"""test max branch +""" + +__revision__ = '' + +def stupid_function(arg1, arg2, arg3, arg4, arg5): + """reallly stupid function""" + arg6, arg7, arg8, arg9 = arg1, arg2, arg3, arg4 + print arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9 + loc1, loc2, loc3, loc4, loc5, loc6, loc7 = arg1, arg2, arg3, arg4, arg5, \ + arg6, arg7 + print loc1, loc2, loc3, loc4, loc5, loc6, loc7 diff --git a/test/input/func_w0105.py b/test/input/func_w0105.py new file mode 100644 index 0000000..430cfd4 --- /dev/null +++ b/test/input/func_w0105.py @@ -0,0 +1,62 @@ +"""test max branch +""" + +__revision__ = '' + +def stupid_function(arg): + """reallly stupid function""" + if arg == 1: + print 1 + elif arg == 2: + print 2 + elif arg == 3: + print 3 + elif arg == 4: + print 4 + elif arg == 5: + print 5 + elif arg == 6: + print 6 + elif arg == 7: + print 7 + elif arg == 8: + print 8 + elif arg == 9: + print 9 + elif arg == 10: + print 10 + elif arg < 1: + print 0 + print 100 + arg = 0 + for val in range(arg): + print val + print val + print val + print val + print val + print val + print val + print val + print val + print val + print val + print val + print val + print val + print val + print val + print val + print val + print val + print val + print val + print val + print val + print val + print val + print val + print val + print val + print val + print val diff --git a/test/input/func_w0109.py b/test/input/func_w0109.py new file mode 100644 index 0000000..ba7a679 --- /dev/null +++ b/test/input/func_w0109.py @@ -0,0 +1,7 @@ +"""test empty docstrings +""" + +__revision__ = '' + +def function(): + """""" diff --git a/test/input/func_w0110.py b/test/input/func_w0110.py new file mode 100644 index 0000000..c9d9311 --- /dev/null +++ b/test/input/func_w0110.py @@ -0,0 +1,10 @@ +"""test too short name +""" + +__revision__ = 1 + +A = None + +def a(): + """yo""" + pass diff --git a/test/input/func_w0111.py b/test/input/func_w0111.py new file mode 100644 index 0000000..f8ad440 --- /dev/null +++ b/test/input/func_w0111.py @@ -0,0 +1,10 @@ +"""test black listed name +""" + +__revision__ = 1 + +A = None + +def baz(): + """yo""" + pass diff --git a/test/input/func_w0112.py b/test/input/func_w0112.py new file mode 100644 index 0000000..5cd01ae --- /dev/null +++ b/test/input/func_w0112.py @@ -0,0 +1,37 @@ +"""test max branch +""" + +__revision__ = '' + +def stupid_function(arg): + """reallly stupid function""" + if arg == 1: + print 1 + elif arg == 2: + print 2 + elif arg == 3: + print 3 + elif arg == 4: + print 4 + elif arg == 5: + print 5 + elif arg == 6: + print 6 + elif arg == 7: + print 7 + elif arg == 8: + print 8 + elif arg == 9: + print 9 + elif arg == 10: + print 10 + else: + if arg < 1: + print 0 + else: + print 100 + arg = 0 + if arg: + print None + else: + print arg diff --git a/test/input/func_w0122.py b/test/input/func_w0122.py new file mode 100644 index 0000000..178c252 --- /dev/null +++ b/test/input/func_w0122.py @@ -0,0 +1,13 @@ +"""test global statement""" + +__revision__ = 0 + +exec 'a = __revision__' +exec 'a = 1' in {} + +exec 'a = 1' in globals() + +def func(): + """exec in local scope""" + exec 'b = 1' + diff --git a/test/input/func_w0133.py b/test/input/func_w0133.py new file mode 100644 index 0000000..c0304ec --- /dev/null +++ b/test/input/func_w0133.py @@ -0,0 +1,54 @@ +# pylint: disable-msg=R0903,R0201 +"""test Invalid name""" + +__revision__ = 1 + +def Run(): + """method without any good name""" + class B: + """nested class should not be tested has a variable""" + def __init__(self): + pass + bBb = 1 + return A, bBb + +def run(): + """anothrer method without only good name""" + class Aaa: + """nested class should not be tested has a variable""" + def __init__(self): + pass + bbb = 1 + return Aaa(bbb) + +A = None + +def HOHOHOHO(): + """yo""" + HIHIHI = 1 + print HIHIHI + +class xyz: + """yo""" + def __init__(self): + pass + + def Youplapoum(self): + """bad method name""" + + +def nested_args(arg1, (arg21, arg22)): + """function with nested arguments""" + print arg1, arg21, arg22 + + +GOOD_CONST_NAME = '' +benpasceluila = 0 + +class Correct: + """yo""" + def __init__(self): + self.cava = 12 + self._Ca_va_Pas = None + +V = [WHAT_Ever_inListComp for WHAT_Ever_inListComp in GOOD_CONST_NAME] diff --git a/test/input/func_w0151.py b/test/input/func_w0151.py new file mode 100644 index 0000000..4c55446 --- /dev/null +++ b/test/input/func_w0151.py @@ -0,0 +1,7 @@ +"""check black listed builtins +""" + +__revision__ = apply(map, (str, (1, 2, 3))) + +YYYYY = map(str, (1, 2, 3)) + diff --git a/test/input/func_w0152.py b/test/input/func_w0152.py new file mode 100644 index 0000000..5054d90 --- /dev/null +++ b/test/input/func_w0152.py @@ -0,0 +1,14 @@ +"""check use of * or ** +""" + +from operator import add +__revision__ = reduce(*(add, (1, 2, 3))) + + +def func(arg1, arg2): + """magic function + """ + return arg2, arg1 + +MYDICT = {'arg1':2, 'arg2': 4} +func(**MYDICT) diff --git a/test/input/func_w0202.py b/test/input/func_w0202.py new file mode 100644 index 0000000..4d36d40 --- /dev/null +++ b/test/input/func_w0202.py @@ -0,0 +1,17 @@ +"""check static method with self or cls as first argument""" + +__revision__ = None + +class Abcd: + """dummy""" + + def method1(self): + """hehe""" + method1 = staticmethod(method1) + + def method2(cls): + """hehe""" + method2 = staticmethod(method2) + + def __init__(self): + pass diff --git a/test/input/func_w0205.py b/test/input/func_w0205.py new file mode 100644 index 0000000..277306a --- /dev/null +++ b/test/input/func_w0205.py @@ -0,0 +1,24 @@ +"""check different signatures""" + +__revision__ = 0 + +class Abcd: + '''dummy''' + def __init__(self): + self.aarg = False + def abcd(self, aaa, bbbb=None): + """hehehe""" + print self, aaa, bbbb + def args(self): + """gaarrrggll""" + self.aarg = True + +class Cdef(Abcd): + """dummy""" + def __init__(self, aaa): + Abcd.__init__(self) + self.aaa = aaa + + def abcd(self, aaa=1, bbbb=None): + """hehehe""" + print self, aaa, bbbb diff --git a/test/input/func_w0223.py b/test/input/func_w0223.py new file mode 100644 index 0000000..3696862 --- /dev/null +++ b/test/input/func_w0223.py @@ -0,0 +1,27 @@ +# pylint: disable-msg=R0903,R0922 +"""test overriding of abstract method +""" + +__revision__ = '$Id: func_w0223.py,v 1.2 2004-09-29 08:35:13 syt Exp $' + +class Abstract: + """abstract class + """ + def aaaa(self): + """should be overriden in concrete class""" + raise NotImplementedError() + + + def bbbb(self): + """should be overriden in concrete class""" + raise NotImplementedError() + + def __init__(self): + pass + +class Concret(Abstract): + """concret class""" + + def aaaa(self): + """overidden form Abstract""" + print self diff --git a/test/input/func_w0231.py b/test/input/func_w0231.py new file mode 100644 index 0000000..3f592ec --- /dev/null +++ b/test/input/func_w0231.py @@ -0,0 +1,38 @@ +# pylint: disable-msg=R0903 +"""test for __init__ not called +""" + +__revision__ = '$Id: func_w0231.py,v 1.3 2004-09-29 08:35:13 syt Exp $' + +class AAAA: + """ancestor 1""" + + def __init__(self): + print 'init', self + +class BBBB: + """ancestor 2""" + + def __init__(self): + print 'init', self + +class CCCC: + """ancestor 3""" + + +class ZZZZ(AAAA, BBBB, CCCC): + """derived class""" + + def __init__(self): + AAAA.__init__(self) + +class NewStyleA(object): + """new style class""" + def __init__(self): + super(NewStyleA, self).__init__() + print 'init', self + +class NewStyleB(NewStyleA): + """derived new style class""" + def __init__(self): + super(NewStyleB, self).__init__() diff --git a/test/input/func_w0233.py b/test/input/func_w0233.py new file mode 100644 index 0000000..763ccf0 --- /dev/null +++ b/test/input/func_w0233.py @@ -0,0 +1,19 @@ +# pylint: disable-msg=R0903 +"""test for call to __init__ from a non ancestor class +""" + +__revision__ = '$Id: func_w0233.py,v 1.2 2004-09-29 08:35:13 syt Exp $' + +class AAAA: + """ancestor 1""" + + def __init__(self): + print 'init', self + BBBB.__init__(self) + +class BBBB: + """ancestor 2""" + + def __init__(self): + print 'init', self + diff --git a/test/input/func_w0302.py b/test/input/func_w0302.py new file mode 100644 index 0000000..a78f479 --- /dev/null +++ b/test/input/func_w0302.py @@ -0,0 +1,1016 @@ +"""test too much line in modules +""" + +__revision__ = 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +ZERFZAER = 3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +HEHEHE = 2 diff --git a/test/input/func_w0312.py b/test/input/func_w0312.py new file mode 100644 index 0000000..06a8110 --- /dev/null +++ b/test/input/func_w0312.py @@ -0,0 +1,11 @@ +"""test mixed tabs and spaces""" + +__revision__ = 1 + +def spaces_func(): + """yo""" + print "yo" + +def tab_func(): + """yo""" + print "yo" diff --git a/test/input/func_w0331.py b/test/input/func_w0331.py new file mode 100644 index 0000000..7e34523 --- /dev/null +++ b/test/input/func_w0331.py @@ -0,0 +1,7 @@ +"""check use of <> +""" + +__revision__ = 1 + +if __revision__ <> 2: + __revision__ = 2 diff --git a/test/input/func_w0332.py b/test/input/func_w0332.py new file mode 100644 index 0000000..ff078ef --- /dev/null +++ b/test/input/func_w0332.py @@ -0,0 +1,4 @@ +"""check use of l as long int marker +""" + +__revision__ = 1l diff --git a/test/input/func_w0401.py b/test/input/func_w0401.py new file mode 100644 index 0000000..6b7a64b --- /dev/null +++ b/test/input/func_w0401.py @@ -0,0 +1,9 @@ +"""test cyclic import +""" +__revision__ = 0 + + +from input import w0401_cycle + +if __revision__: + print w0401_cycle diff --git a/test/input/func_w0402.py b/test/input/func_w0402.py new file mode 100644 index 0000000..53752db --- /dev/null +++ b/test/input/func_w0402.py @@ -0,0 +1,9 @@ +"""test wildard import +""" +__revision__ = 0 + +from input.func_fixme import * + +def abcd(): + """use imports""" + function() diff --git a/test/input/func_w0403.py b/test/input/func_w0403.py new file mode 100644 index 0000000..72fb795 --- /dev/null +++ b/test/input/func_w0403.py @@ -0,0 +1,12 @@ +"""test deprecated module +""" + +__revision__ = 0 + + +if __revision__: + import Bastion + print Bastion + # false positive (#10061) + import stringfile + print stringfile diff --git a/test/input/func_w0404.py b/test/input/func_w0404.py new file mode 100644 index 0000000..fd38e6a --- /dev/null +++ b/test/input/func_w0404.py @@ -0,0 +1,10 @@ +"""test relative import +""" + +__revision__ = 0 + +import func_w0302 + +if __revision__: + print func_w0302 + diff --git a/test/input/func_w0405.py b/test/input/func_w0405.py new file mode 100644 index 0000000..745c615 --- /dev/null +++ b/test/input/func_w0405.py @@ -0,0 +1,31 @@ +"""check reimport +""" + +__revision__ = 0 + +import os +from os.path import join, exists + +import os +import re as _re + +_re.match('yo', '.*') + +if __revision__: + print os + from os.path import exists + print join, exists + +def func(yooo): + """reimport in different scope""" + import os as ass + ass.remove(yooo) + import re + re.compile('.*') + +if 1: + import sys + print sys.modules +else: + print 'bla' + import sys diff --git a/test/input/func_w0406.py b/test/input/func_w0406.py new file mode 100644 index 0000000..e20508f --- /dev/null +++ b/test/input/func_w0406.py @@ -0,0 +1,9 @@ +"""test module importing itself +""" +__revision__ = 0 + +import func_w0406 + +if __revision__: + print func_w0406 + diff --git a/test/input/func_w0611.py b/test/input/func_w0611.py new file mode 100644 index 0000000..2c37966 --- /dev/null +++ b/test/input/func_w0611.py @@ -0,0 +1,22 @@ +"""check unused import +""" +__revision__ = 1 +import os +import sys + +class NonRegr: + """???""" + def __init__(self): + print 'initialized' + + def sys(self): + """should not get sys from there...""" + print self, sys + + def dummy(self, truc): + """yo""" + return self, truc + + def blop(self): + """yo""" + print self, 'blip' diff --git a/test/input/func_w0612.py b/test/input/func_w0612.py new file mode 100644 index 0000000..43b551c --- /dev/null +++ b/test/input/func_w0612.py @@ -0,0 +1,8 @@ +"""test unused variable +""" + +__revision__ = 0 + +def function(): + """"yo""" + aaaa = 1 diff --git a/test/input/func_w0613.py b/test/input/func_w0613.py new file mode 100644 index 0000000..0725335 --- /dev/null +++ b/test/input/func_w0613.py @@ -0,0 +1,18 @@ +# pylint: disable-msg=R0903 +"""test unused argument +""" + +__revision__ = 1 + +def function(arg=1): + """ignore arg""" + + +class AAAA: + """dummy class""" + + def method(self, arg): + """dummy method""" + print self + def __init__(self): + pass diff --git a/test/input/func_w0622.py b/test/input/func_w0622.py new file mode 100644 index 0000000..ea9d1b2 --- /dev/null +++ b/test/input/func_w0622.py @@ -0,0 +1,11 @@ +# pylint: disable-msg=C0103 +"""test built-in redefinition +""" +__revision__ = 0 + +def function(): + """yo""" + type = 1 + print type + +map = {} diff --git a/test/input/func_w0701.py b/test/input/func_w0701.py new file mode 100644 index 0000000..9c1b727 --- /dev/null +++ b/test/input/func_w0701.py @@ -0,0 +1,12 @@ +"""test string exception +""" + +__revision__ = '' + +def function1(): + """hehehe""" + raise "String Exception" + +def function2(): + """hehehe""" + raise 'exception', 'message' diff --git a/test/input/func_w0702.py b/test/input/func_w0702.py new file mode 100644 index 0000000..38a6417 --- /dev/null +++ b/test/input/func_w0702.py @@ -0,0 +1,17 @@ +"""check empty except statement +""" + +__revision__ = 0 + +if __revision__: + try: + print __revision__ + except: + print 'error' + +try: + __revision__ += 1 +except TypeError: + print 'error' +except Exception: + print 'error' diff --git a/test/input/func_w0703.py b/test/input/func_w0703.py new file mode 100644 index 0000000..4040540 --- /dev/null +++ b/test/input/func_w0703.py @@ -0,0 +1,9 @@ +"""check empty except statement +""" + +__revision__ = 0 + +try: + __revision__ += 1 +except Exception: + print 'error' diff --git a/test/input/func_w0704.py b/test/input/func_w0704.py new file mode 100644 index 0000000..e8c6432 --- /dev/null +++ b/test/input/func_w0704.py @@ -0,0 +1,16 @@ +"""test empty except +""" + +__revision__ = 1 + +try: + __revision__ += 1 +except TypeError: + pass + +try: + __revision__ += 1 +except TypeError: + pass +else: + __revision__ = None diff --git a/test/input/func_w0705.py b/test/input/func_w0705.py new file mode 100644 index 0000000..898e2b8 --- /dev/null +++ b/test/input/func_w0705.py @@ -0,0 +1,46 @@ +"""test misordered except +""" + +__revision__ = 1 + +try: + __revision__ += 1 +except Exception: + __revision__ = None +except TypeError: + __revision__ = None + +try: + __revision__ += 1 +except LookupError: + __revision__ = None +except IndexError: + __revision__ = None + +try: + __revision__ += 1 +except (LookupError, NameError): + __revision__ = None +except (IndexError, UnboundLocalError): + __revision__ = None + +try: + __revision__ += 1 +except: + pass +except Exception: + pass + +try: + __revision__ += 1 +except TypeError: + __revision__ = None +except: + __revision__ = None + +try: + __revision__ += 1 +except Exception: + pass +except: + pass diff --git a/test/input/func_w0801.py b/test/input/func_w0801.py new file mode 100644 index 0000000..cd386ff --- /dev/null +++ b/test/input/func_w0801.py @@ -0,0 +1,11 @@ +"""test code similarities +by defaut docstring are not considered +""" +__revision__ = 'id' +A = 2 +B = 3 +C = A + B +# need more than X lines to trigger the message +C *= 2 +A -= B +# all this should be detected diff --git a/test/input/func_wrong_encoding.py b/test/input/func_wrong_encoding.py new file mode 100644 index 0000000..267fa2c --- /dev/null +++ b/test/input/func_wrong_encoding.py @@ -0,0 +1,6 @@ +# -*- coding: UTF-8 -*- +""" check correct wrong encoding declaration +""" + +__revision__ = 'éééé' + diff --git a/test/input/indirect1.py b/test/input/indirect1.py new file mode 100644 index 0000000..eac6242 --- /dev/null +++ b/test/input/indirect1.py @@ -0,0 +1,4 @@ +class TotoInterface: + def machin(self): + raise NotImplementedError + diff --git a/test/input/indirect2.py b/test/input/indirect2.py new file mode 100644 index 0000000..6eefece --- /dev/null +++ b/test/input/indirect2.py @@ -0,0 +1,7 @@ +from indirect1 import TotoInterface + +class AbstractToto: + __implements__ = TotoInterface + + def helper(self): + return 'help' diff --git a/test/input/indirect3.py b/test/input/indirect3.py new file mode 100644 index 0000000..dac0853 --- /dev/null +++ b/test/input/indirect3.py @@ -0,0 +1,5 @@ +from indirect2 import AbstractToto + +class ConcreteToto(AbstractToto): + def machin(self): + return self.helper()*2 diff --git a/test/input/noext b/test/input/noext new file mode 100644 index 0000000..8aeda06 --- /dev/null +++ b/test/input/noext @@ -0,0 +1,4 @@ +#!/usr/bin/env python +"""a python file without .py extension""" + +__revision__ = None diff --git a/test/input/similar1 b/test/input/similar1 new file mode 100644 index 0000000..bc88187 --- /dev/null +++ b/test/input/similar1 @@ -0,0 +1,19 @@ +this file is used +to check the similar +command line tool + +see the similar2 file which is almost the +same file as this one. +more than 4 +identical lines should +be +detected + + +héhéhéh + + + + + +Yo ! diff --git a/test/input/similar2 b/test/input/similar2 new file mode 100644 index 0000000..56f9844 --- /dev/null +++ b/test/input/similar2 @@ -0,0 +1,19 @@ +this file is used +to check the similar +command line tool + +see the similar1 file which is almost the +same file as this one. +more than 4 +identical lines should +be +detected + + +hohohoh + + + + + +Yo ! diff --git a/test/input/w0401_cycle.py b/test/input/w0401_cycle.py new file mode 100644 index 0000000..1fbf880 --- /dev/null +++ b/test/input/w0401_cycle.py @@ -0,0 +1,9 @@ +"""w0401 dependancy +""" + +__revision__ = 0 + +import input.func_w0401 + +if __revision__: + print input diff --git a/test/input/w0801_same.py b/test/input/w0801_same.py new file mode 100644 index 0000000..cd386ff --- /dev/null +++ b/test/input/w0801_same.py @@ -0,0 +1,11 @@ +"""test code similarities +by defaut docstring are not considered +""" +__revision__ = 'id' +A = 2 +B = 3 +C = A + B +# need more than X lines to trigger the message +C *= 2 +A -= B +# all this should be detected diff --git a/test/messages/builtin_module.txt b/test/messages/builtin_module.txt new file mode 100644 index 0000000..8829318 --- /dev/null +++ b/test/messages/builtin_module.txt @@ -0,0 +1 @@ +F: 0: ignored builtin module sys diff --git a/test/messages/func___future___import_not_first_stmt.txt b/test/messages/func___future___import_not_first_stmt.txt new file mode 100644 index 0000000..ff96b55 --- /dev/null +++ b/test/messages/func___future___import_not_first_stmt.txt @@ -0,0 +1 @@ +W: 4: __future__ import is not the first non docstring statement diff --git a/test/messages/func___name___access.txt b/test/messages/func___name___access.txt new file mode 100644 index 0000000..51aeeaa --- /dev/null +++ b/test/messages/func___name___access.txt @@ -0,0 +1,3 @@ +E: 11:Aaaa.__init__: Instance of 'Aaaa' has no '__name__' member +E: 21:NewClass.__init__: Instance of 'NewClass' has no '__name__' member + diff --git a/test/messages/func_attrs_definition_order.txt b/test/messages/func_attrs_definition_order.txt new file mode 100644 index 0000000..895c315 --- /dev/null +++ b/test/messages/func_attrs_definition_order.txt @@ -0,0 +1 @@ +E: 9:Aaaa.__init__: Access to member '_var2' before its definition line 10 diff --git a/test/messages/func_bad_assigment_to_exception_var.txt b/test/messages/func_bad_assigment_to_exception_var.txt new file mode 100644 index 0000000..9b9bc02 --- /dev/null +++ b/test/messages/func_bad_assigment_to_exception_var.txt @@ -0,0 +1,5 @@ +W: 11: Identifier e used to raise an exception is assigned to 1 line 7 +W: 15: Identifier e2 used to raise an exception is assigned to 'yo' line 8 +W: 20:func: Identifier e3 used to raise an exception is assigned to None +W: 30: Identifier e3 used to raise an exception is assigned to None + diff --git a/test/messages/func_base_stmt_without_effect.txt b/test/messages/func_base_stmt_without_effect.txt new file mode 100644 index 0000000..b50fd82 --- /dev/null +++ b/test/messages/func_base_stmt_without_effect.txt @@ -0,0 +1,3 @@ +W: 9: Statement seems to have no effect +W: 11: Statement seems to have no effect +W: 15: Statement seems to have no effect diff --git a/test/messages/func_block_disable_msg.txt b/test/messages/func_block_disable_msg.txt new file mode 100644 index 0000000..0019f03 --- /dev/null +++ b/test/messages/func_block_disable_msg.txt @@ -0,0 +1,9 @@ +E: 26:Foo.meth3: Instance of 'Foo' has no 'blop' member +E: 36:Foo.meth4: Instance of 'Foo' has no 'blip' member +E: 46:Foo.meth5: Instance of 'Foo' has no 'blip' member +E: 61:Foo.meth6: Instance of 'Foo' has no 'blip' member +E: 72:Foo.meth7: Instance of 'Foo' has no 'blip' member +E: 75:Foo.meth7: Instance of 'Foo' has no 'blip' member +E: 77:Foo.meth7: Instance of 'Foo' has no 'blip' member +E: 83:Foo.meth8: Instance of 'Foo' has no 'blip' member +W: 11:Foo.meth1: Unused argument 'arg' diff --git a/test/messages/func_class_members.txt b/test/messages/func_class_members.txt new file mode 100644 index 0000000..ba2c639 --- /dev/null +++ b/test/messages/func_class_members.txt @@ -0,0 +1,3 @@ +E: 16:MyClass.test: Instance of 'MyClass' has no 'incorrect' member +E: 17:MyClass.test: Instance of 'MyClass' has no 'nonexistent1' member +E: 18:MyClass.test: Instance of 'MyClass' has no 'nonexistent2' member diff --git a/test/messages/func_continue_not_in_loop.txt b/test/messages/func_continue_not_in_loop.txt new file mode 100644 index 0000000..d3a3183 --- /dev/null +++ b/test/messages/func_continue_not_in_loop.txt @@ -0,0 +1,2 @@ +E: 8:run: 'continue' not properly in loop +E: 10:run: 'break' not properly in loop diff --git a/test/messages/func_dangerous_default.txt b/test/messages/func_dangerous_default.txt new file mode 100644 index 0000000..21d32e0 --- /dev/null +++ b/test/messages/func_dangerous_default.txt @@ -0,0 +1,2 @@ +W: 7:function1: Dangerous default value [] as argument +W: 11:function2: Dangerous default value HEHE ({}) as argument diff --git a/test/messages/func_docstring.txt b/test/messages/func_docstring.txt new file mode 100644 index 0000000..d379392 --- /dev/null +++ b/test/messages/func_docstring.txt @@ -0,0 +1,4 @@ +C: 0: Missing docstring +C: 5:function1: Missing docstring +C: 17:AAAA: Missing docstring +C: 33:AAAA.method1: Missing docstring diff --git a/test/messages/func_dotted_ancestor.txt b/test/messages/func_dotted_ancestor.txt new file mode 100644 index 0000000..84247b8 --- /dev/null +++ b/test/messages/func_dotted_ancestor.txt @@ -0,0 +1 @@ +R: 8:Aaaa: Not enough public methods (0/2) diff --git a/test/messages/func_e0011.txt b/test/messages/func_e0011.txt new file mode 100644 index 0000000..55f07b1 --- /dev/null +++ b/test/messages/func_e0011.txt @@ -0,0 +1 @@ +E: 1: Unrecognized file option 'bouboule' diff --git a/test/messages/func_e0012.txt b/test/messages/func_e0012.txt new file mode 100644 index 0000000..a6d1b69 --- /dev/null +++ b/test/messages/func_e0012.txt @@ -0,0 +1 @@ +E: 1: Bad option value 'W04044' diff --git a/test/messages/func_e0101.txt b/test/messages/func_e0101.txt new file mode 100644 index 0000000..53d007d --- /dev/null +++ b/test/messages/func_e0101.txt @@ -0,0 +1 @@ +E: 10:MyClass.__init__: Explicit return in __init__ diff --git a/test/messages/func_e0203.txt b/test/messages/func_e0203.txt new file mode 100644 index 0000000..869cf22 --- /dev/null +++ b/test/messages/func_e0203.txt @@ -0,0 +1,2 @@ +C: 12:Abcd.abcd: Class method should have "cls" as first argument + diff --git a/test/messages/func_e0204.txt b/test/messages/func_e0204.txt new file mode 100644 index 0000000..8e05efe --- /dev/null +++ b/test/messages/func_e0204.txt @@ -0,0 +1,3 @@ +E: 10:Abcd.__init__: Method should have "self" as first argument +E: 14:Abcd.abdc: Method should have "self" as first argument + diff --git a/test/messages/func_e0205.txt b/test/messages/func_e0205.txt new file mode 100644 index 0000000..1adf408 --- /dev/null +++ b/test/messages/func_e0205.txt @@ -0,0 +1,2 @@ +E: 14:Cdef.abcd: An attribute inherited from Abcd hide this method + diff --git a/test/messages/func_e0206.txt b/test/messages/func_e0206.txt new file mode 100644 index 0000000..c15f841 --- /dev/null +++ b/test/messages/func_e0206.txt @@ -0,0 +1,3 @@ +E: 6:Abcd: Interface resolved to None is not a class +E: 13:Cdef: Interface resolved to None is not a class + diff --git a/test/messages/func_e0214.txt b/test/messages/func_e0214.txt new file mode 100644 index 0000000..e3fb5f5 --- /dev/null +++ b/test/messages/func_e0214.txt @@ -0,0 +1,2 @@ +C: 11:MetaClass.whatever: Metaclass method should have "mcs" as first argument + diff --git a/test/messages/func_e0601.txt b/test/messages/func_e0601.txt new file mode 100644 index 0000000..321c731 --- /dev/null +++ b/test/messages/func_e0601.txt @@ -0,0 +1 @@ +E: 8:function: Using variable 'aaaa' before assignment diff --git a/test/messages/func_empty_module.txt b/test/messages/func_empty_module.txt new file mode 100644 index 0000000..2a8190d --- /dev/null +++ b/test/messages/func_empty_module.txt @@ -0,0 +1,2 @@ +C: 0: Missing docstring +C: 0: Missing required attribute "__revision__" diff --git a/test/messages/func_exceptions_raise_type_error.txt b/test/messages/func_exceptions_raise_type_error.txt new file mode 100644 index 0000000..8ac5778 --- /dev/null +++ b/test/messages/func_exceptions_raise_type_error.txt @@ -0,0 +1,2 @@ +E: 11: Raising int while only classes, instances or string are allowed +E: 14: Raising None while only classes, instances or string are allowed
\ No newline at end of file diff --git a/test/messages/func_f0001.txt b/test/messages/func_f0001.txt new file mode 100644 index 0000000..87c315d --- /dev/null +++ b/test/messages/func_f0001.txt @@ -0,0 +1 @@ +W: 3: Unused import whatever diff --git a/test/messages/func_f0401.txt b/test/messages/func_f0401.txt new file mode 100644 index 0000000..1ae5ea4 --- /dev/null +++ b/test/messages/func_f0401.txt @@ -0,0 +1,2 @@ +F: 8:function: Unable to import 'tutu.toto' (No module named tutu) + diff --git a/test/messages/func_fixme.txt b/test/messages/func_fixme.txt new file mode 100644 index 0000000..2544ce8 --- /dev/null +++ b/test/messages/func_fixme.txt @@ -0,0 +1,2 @@ +W: 5: FIXME: beep +W: 8: XXX:bop''' diff --git a/test/messages/func_format.txt b/test/messages/func_format.txt new file mode 100644 index 0000000..c87d01d --- /dev/null +++ b/test/messages/func_format.txt @@ -0,0 +1,25 @@ +C: 6: Operator not preceded by a space +notpreceded= 1 + ^ +C: 7: Operator not followed by a space +notfollowed =1 + ^ +C: 8: Operator not followed by a space +notfollowed <=1 + ^^ +C: 19: Comma not followed by a space +aaaa,bbbb = 1,2 + ^^ +C: 24: More than one statement on a single line +C: 26: Comma not followed by a space + aaaa,bbbb = 1,2 + ^^ +C: 27: Comma not followed by a space + aaaa,bbbb = bbbb,aaaa + ^^ +C: 29: Comma not followed by a space +bbbb = (1,2,3) + ^^ +C: 51:other: Operator not preceded by a space + funky= funky+2 + ^ diff --git a/test/messages/func_globals.txt b/test/messages/func_globals.txt new file mode 100644 index 0000000..40963f2 --- /dev/null +++ b/test/messages/func_globals.txt @@ -0,0 +1,6 @@ +E: 27: Undefined variable 'CSTE' +E: 32:other: Undefined variable 'HOP' +W: 23:fix_contant: Using the global statement +W: 26: Using the global statement at the module level +W: 31:other: Using global for 'HOP' but no assigment is done +W: 39:define_constant: Global variable 'somevar' undefined at the module level diff --git a/test/messages/func_i0010.txt b/test/messages/func_i0010.txt new file mode 100644 index 0000000..315f701 --- /dev/null +++ b/test/messages/func_i0010.txt @@ -0,0 +1 @@ +I: 1: Unable to consider inline option 'disable-all' diff --git a/test/messages/func_i0011.txt b/test/messages/func_i0011.txt new file mode 100644 index 0000000..e4d2975 --- /dev/null +++ b/test/messages/func_i0011.txt @@ -0,0 +1,2 @@ +I: 1: Locally disabling 'W0404' + diff --git a/test/messages/func_i0012.txt b/test/messages/func_i0012.txt new file mode 100644 index 0000000..0bb035c --- /dev/null +++ b/test/messages/func_i0012.txt @@ -0,0 +1,2 @@ +I: 1: Locally enabling 'W0404' + diff --git a/test/messages/func_indent.txt b/test/messages/func_indent.txt new file mode 100644 index 0000000..aa3645f --- /dev/null +++ b/test/messages/func_indent.txt @@ -0,0 +1,3 @@ +W: 5: Bad indentation. Found 1 spaces, expected 4 +W: 6: Bad indentation. Found 1 spaces, expected 4 +W: 13: Bad indentation. Found 5 spaces, expected 4 diff --git a/test/messages/func_init_vars.txt b/test/messages/func_init_vars.txt new file mode 100644 index 0000000..44ef6f3 --- /dev/null +++ b/test/messages/func_init_vars.txt @@ -0,0 +1 @@ +W: 18:MyClass.met: Attribute 'base_var' defined outside __init__ diff --git a/test/messages/func_interfaces.txt b/test/messages/func_interfaces.txt new file mode 100644 index 0000000..546acdd --- /dev/null +++ b/test/messages/func_interfaces.txt @@ -0,0 +1,5 @@ +E: 46:MissingMethod: Missing method 'truc' from IMachin interface +E: 77:InterfaceCantBeFound: Undefined variable 'undefined' +F: 77:InterfaceCantBeFound: failed to resolve interfaces implemented by InterfaceCantBeFound (undefined) +F: 90:InterfaceCantBeFound2: failed to resolve interfaces implemented by InterfaceCantBeFound2 ((BadArgument.__implements__) + (Correct2.__implements__)) +W: 71:BadArgument.troc: Arguments number differs from IMachin interface method diff --git a/test/messages/func_method_could_be_function.txt b/test/messages/func_method_could_be_function.txt new file mode 100644 index 0000000..1def89e --- /dev/null +++ b/test/messages/func_method_could_be_function.txt @@ -0,0 +1 @@ +R: 16:Toto.function_method: Method could be a function diff --git a/test/messages/func_method_missing_self.txt b/test/messages/func_method_missing_self.txt new file mode 100644 index 0000000..861871f --- /dev/null +++ b/test/messages/func_method_missing_self.txt @@ -0,0 +1 @@ +E: 14:MyClass.met: Method has no argument diff --git a/test/messages/func_method_without_self_but_self_assignment.txt b/test/messages/func_method_without_self_but_self_assignment.txt new file mode 100644 index 0000000..da5ee02 --- /dev/null +++ b/test/messages/func_method_without_self_but_self_assignment.txt @@ -0,0 +1,2 @@ +E: 13:Example.setup: Method has no argument +E: 15:Example.setup: Undefined variable 'self' diff --git a/test/messages/func_nameerror_on_string_substitution.txt b/test/messages/func_nameerror_on_string_substitution.txt new file mode 100644 index 0000000..aab2102 --- /dev/null +++ b/test/messages/func_nameerror_on_string_substitution.txt @@ -0,0 +1,2 @@ +E: 5: Using variable 'MSG' before assignment +E: 8: Using variable 'MSG2' before assignment diff --git a/test/messages/func_names_imported_from_module.txt b/test/messages/func_names_imported_from_module.txt new file mode 100644 index 0000000..62b0344 --- /dev/null +++ b/test/messages/func_names_imported_from_module.txt @@ -0,0 +1,8 @@ +E: 6: No name 'tutu' in module 'logilab.common' +E: 7: No name 'toto' in module 'logilab.common' +E: 11: Module 'logilab.common.modutils' has no 'nonexistant_function' member +E: 12: Module 'logilab.common.modutils' has no 'another' member +E: 13: Module 'logilab.common.modutils' has no 'yo' member +E: 17: Module 'sys' has no 'stdoout' member +E: 24: No name 'compiile' in module 're' +E: 24: No name 'findiiter' in module 're' diff --git a/test/messages/func_newstyle___slots__.txt b/test/messages/func_newstyle___slots__.txt new file mode 100644 index 0000000..be6b8d1 --- /dev/null +++ b/test/messages/func_newstyle___slots__.txt @@ -0,0 +1 @@ +E: 10:HaNonNonNon: Use __slots__ on an old style class diff --git a/test/messages/func_newstyle_exceptions.txt b/test/messages/func_newstyle_exceptions.txt new file mode 100644 index 0000000..d92e3bf --- /dev/null +++ b/test/messages/func_newstyle_exceptions.txt @@ -0,0 +1,4 @@ +E: 25:fonctionNew: Raising a new style class +E: 33:fonctionNew2: Raising a new style class +W: 21:fonctionBof: Exception doesn't inherit from standard "Exception" class +W: 29:fonctionBof2: Exception doesn't inherit from standard "Exception" class diff --git a/test/messages/func_newstyle_property.txt b/test/messages/func_newstyle_property.txt new file mode 100644 index 0000000..f0ef261 --- /dev/null +++ b/test/messages/func_newstyle_property.txt @@ -0,0 +1 @@ +W: 16:HaNonNonNon: Use of "property" on an old style class diff --git a/test/messages/func_newstyle_super.txt b/test/messages/func_newstyle_super.txt new file mode 100644 index 0000000..d0cdf78 --- /dev/null +++ b/test/messages/func_newstyle_super.txt @@ -0,0 +1,4 @@ +E: 7:Aaaa.hop: Use super on an old style class +E: 11:Aaaa.__init__: Use super on an old style class +E: 20:NewAaaa.__init__: Bad first argument 'object' given to super class + diff --git a/test/messages/func_nonascii_noencoding.txt b/test/messages/func_nonascii_noencoding.txt new file mode 100644 index 0000000..dd68a3c --- /dev/null +++ b/test/messages/func_nonascii_noencoding.txt @@ -0,0 +1 @@ +E: 1: Non ascii characters found but no encoding specified (PEP 263) diff --git a/test/messages/func_r0901.txt b/test/messages/func_r0901.txt new file mode 100644 index 0000000..d20460e --- /dev/null +++ b/test/messages/func_r0901.txt @@ -0,0 +1,2 @@ +R: 22:Iiii: Too many ancestors (8/7) +R: 25:Jjjj: Too many ancestors (9/7) diff --git a/test/messages/func_r0902.txt b/test/messages/func_r0902.txt new file mode 100644 index 0000000..5dcb669 --- /dev/null +++ b/test/messages/func_r0902.txt @@ -0,0 +1 @@ +R: 5:Aaaa: Too many instance attributes (21/7) diff --git a/test/messages/func_r0903.txt b/test/messages/func_r0903.txt new file mode 100644 index 0000000..6ab8888 --- /dev/null +++ b/test/messages/func_r0903.txt @@ -0,0 +1 @@ +R: 4:Aaaa: Not enough public methods (1/2) diff --git a/test/messages/func_r0904.txt b/test/messages/func_r0904.txt new file mode 100644 index 0000000..76baf72 --- /dev/null +++ b/test/messages/func_r0904.txt @@ -0,0 +1 @@ +R: 4:Aaaa: Too many public methods (21/20) diff --git a/test/messages/func_r0921.txt b/test/messages/func_r0921.txt new file mode 100644 index 0000000..7e9a442 --- /dev/null +++ b/test/messages/func_r0921.txt @@ -0,0 +1 @@ +R: 4:Aaaa: Abstract class not referenced diff --git a/test/messages/func_r0922.txt b/test/messages/func_r0922.txt new file mode 100644 index 0000000..70319ee --- /dev/null +++ b/test/messages/func_r0922.txt @@ -0,0 +1 @@ +R: 4:Aaaa: Abstract class is only referenced 1 times diff --git a/test/messages/func_r0923.txt b/test/messages/func_r0923.txt new file mode 100644 index 0000000..11ee61d --- /dev/null +++ b/test/messages/func_r0923.txt @@ -0,0 +1 @@ +R: 6:IAaaa: Interface not implemented diff --git a/test/messages/func_reqattrs.txt b/test/messages/func_reqattrs.txt new file mode 100644 index 0000000..7e99095 --- /dev/null +++ b/test/messages/func_reqattrs.txt @@ -0,0 +1 @@ +C: 0: Missing required attribute "__revision__" diff --git a/test/messages/func_scope_regrtest.txt b/test/messages/func_scope_regrtest.txt new file mode 100644 index 0000000..27ae207 --- /dev/null +++ b/test/messages/func_scope_regrtest.txt @@ -0,0 +1,2 @@ +E: 12:Well.Sub: Undefined variable 'Data' +E: 15:Well.func: Undefined variable 'Sub' diff --git a/test/messages/func_syntax_error.txt b/test/messages/func_syntax_error.txt new file mode 100644 index 0000000..deee535 --- /dev/null +++ b/test/messages/func_syntax_error.txt @@ -0,0 +1,2 @@ +E: 1: invalid syntax + diff --git a/test/messages/func_toolonglines.txt b/test/messages/func_toolonglines.txt new file mode 100644 index 0000000..29b16c6 --- /dev/null +++ b/test/messages/func_toolonglines.txt @@ -0,0 +1,2 @@ +C: 1: Line too long (90/80) +C: 2: Line too long (94/80) diff --git a/test/messages/func_typecheck_callfunc_assigment.txt b/test/messages/func_typecheck_callfunc_assigment.txt new file mode 100644 index 0000000..96ad43e --- /dev/null +++ b/test/messages/func_typecheck_callfunc_assigment.txt @@ -0,0 +1,2 @@ +E: 20: Assigning to function call which doesn't return +W: 28: Assigning to function call which only returns None diff --git a/test/messages/func_typecheck_getattr.txt b/test/messages/func_typecheck_getattr.txt new file mode 100644 index 0000000..6aa34d8 --- /dev/null +++ b/test/messages/func_typecheck_getattr.txt @@ -0,0 +1,9 @@ +E: 25:Client.__init__: Class 'Provider' has no 'cattribute' member +E: 30:Client.use_method: Instance of 'Provider' has no 'hophophop' member +E: 35:Client.use_attr: Instance of 'Provider' has no 'attribute' member +E: 47:Client.test_bt_types: Instance of 'list' has no 'apppend' member +E: 49:Client.test_bt_types: Instance of 'dict' has no 'set' member +E: 51:Client.test_bt_types: Instance of 'tuple' has no 'append' member +E: 53:Client.test_bt_types: Instance of 'str' has no 'loower' member +E: 55:Client.test_bt_types: Instance of 'unicode' has no 'loower' member +E: 57:Client.test_bt_types: Instance of 'int' has no 'whatever' member
\ No newline at end of file diff --git a/test/messages/func_typecheck_non_callable_call.txt b/test/messages/func_typecheck_non_callable_call.txt new file mode 100644 index 0000000..0218074 --- /dev/null +++ b/test/messages/func_typecheck_non_callable_call.txt @@ -0,0 +1,6 @@ +E: 10: __revision__ is not callable +E: 29: INSTANCE is not callable +E: 31: LIST is not callable +E: 33: DICT is not callable +E: 35: TUPLE is not callable +E: 37: INT is not callable diff --git a/test/messages/func_undefined_var.txt b/test/messages/func_undefined_var.txt new file mode 100644 index 0000000..07666f7 --- /dev/null +++ b/test/messages/func_undefined_var.txt @@ -0,0 +1,5 @@ +E: 8: Undefined variable 'unknown' +E: 14:in_method: Undefined variable 'nomoreknown' +E: 22:bad_default: Undefined variable 'unknown2' +E: 25:bad_default: Undefined variable 'xxxx' +E: 26:bad_default: Undefined variable 'xxxx' diff --git a/test/messages/func_unknown_encoding.txt b/test/messages/func_unknown_encoding.txt new file mode 100644 index 0000000..648af39 --- /dev/null +++ b/test/messages/func_unknown_encoding.txt @@ -0,0 +1 @@ +E: 1: Unknown encoding specified (IBO-8859-1) diff --git a/test/messages/func_unreachable.txt b/test/messages/func_unreachable.txt new file mode 100644 index 0000000..bb25be0 --- /dev/null +++ b/test/messages/func_unreachable.txt @@ -0,0 +1,3 @@ +W: 8:func1: Unreachable code +W: 14:func2: Unreachable code +W: 21:func3: Unreachable code diff --git a/test/messages/func_use_for_or_listcomp_var.txt b/test/messages/func_use_for_or_listcomp_var.txt new file mode 100644 index 0000000..14d61ad --- /dev/null +++ b/test/messages/func_use_for_or_listcomp_var.txt @@ -0,0 +1,3 @@ +W: 6: Using possibly undefined loop variable 'C' +W: 15: Using possibly undefined loop variable 'var1' + diff --git a/test/messages/func_w0101.txt b/test/messages/func_w0101.txt new file mode 100644 index 0000000..c42ec2c --- /dev/null +++ b/test/messages/func_w0101.txt @@ -0,0 +1 @@ +R: 6:stupid_function: Too many return statements (11/6) diff --git a/test/messages/func_w0102.txt b/test/messages/func_w0102.txt new file mode 100644 index 0000000..40b6190 --- /dev/null +++ b/test/messages/func_w0102.txt @@ -0,0 +1,5 @@ +E: 15:AAAA.method2: method already defined line 12 +E: 18:AAAA: class already defined line 5 +E: 32:func2: function already defined line 29 +E: 51:exclusive_func2: function already defined line 45 +W: 34:func2: Redefining name '__revision__' from outer scope (line 3) diff --git a/test/messages/func_w0103.txt b/test/messages/func_w0103.txt new file mode 100644 index 0000000..0d6da42 --- /dev/null +++ b/test/messages/func_w0103.txt @@ -0,0 +1 @@ +R: 6:stupid_function: Too many arguments (9/5) diff --git a/test/messages/func_w0104.txt b/test/messages/func_w0104.txt new file mode 100644 index 0000000..71f6f62 --- /dev/null +++ b/test/messages/func_w0104.txt @@ -0,0 +1 @@ +R: 6:stupid_function: Too many local variables (16/15) diff --git a/test/messages/func_w0105.txt b/test/messages/func_w0105.txt new file mode 100644 index 0000000..d664dd4 --- /dev/null +++ b/test/messages/func_w0105.txt @@ -0,0 +1 @@ +R: 6:stupid_function: Too many statements (55/50) diff --git a/test/messages/func_w0109.txt b/test/messages/func_w0109.txt new file mode 100644 index 0000000..4685d38 --- /dev/null +++ b/test/messages/func_w0109.txt @@ -0,0 +1 @@ +C: 6:function: Empty docstring diff --git a/test/messages/func_w0110.txt b/test/messages/func_w0110.txt new file mode 100644 index 0000000..28f48aa --- /dev/null +++ b/test/messages/func_w0110.txt @@ -0,0 +1 @@ +C: 8:a: Invalid name "a" (should match [a-z_][a-z0-9_]{2,30}$) diff --git a/test/messages/func_w0111.txt b/test/messages/func_w0111.txt new file mode 100644 index 0000000..b2d794b --- /dev/null +++ b/test/messages/func_w0111.txt @@ -0,0 +1 @@ +C: 8:baz: Black listed name "baz" diff --git a/test/messages/func_w0112.txt b/test/messages/func_w0112.txt new file mode 100644 index 0000000..19b2da5 --- /dev/null +++ b/test/messages/func_w0112.txt @@ -0,0 +1 @@ +R: 6:stupid_function: Too many branches (15/12) diff --git a/test/messages/func_w0122.txt b/test/messages/func_w0122.txt new file mode 100644 index 0000000..1522cac --- /dev/null +++ b/test/messages/func_w0122.txt @@ -0,0 +1,5 @@ +W: 5: Use of the exec statement +W: 6: Use of the exec statement +W: 8: Use of the exec statement +W: 12:func: Use of the exec statement + diff --git a/test/messages/func_w0133.txt b/test/messages/func_w0133.txt new file mode 100644 index 0000000..f2929ae --- /dev/null +++ b/test/messages/func_w0133.txt @@ -0,0 +1,9 @@ +C: 8:Run.B: Invalid name "B" (should match [A-Z_][a-zA-Z0-9]+$) +C: 12:Run: Invalid name "bBb" (should match [a-z_][a-z0-9_]{2,30}$) +C: 26:HOHOHOHO: Invalid name "HOHOHOHO" (should match [a-z_][a-z0-9_]{2,30}$) +C: 28:HOHOHOHO: Invalid name "HIHIHI" (should match [a-z_][a-z0-9_]{2,30}$) +C: 31:xyz: Invalid name "xyz" (should match [A-Z_][a-zA-Z0-9]+$) +C: 36:xyz.Youplapoum: Invalid name "Youplapoum" (should match [a-z_][a-z0-9_]{2,30}$) +C: 46: Invalid name "benpasceluila" (should match (([A-Z_][A-Z1-9_]*)|(__.*__))$) +C: 52:Correct.__init__: Invalid name "_Ca_va_Pas" (should match [a-z_][a-z0-9_]{2,30}$) +W: 8:Run.B: Unused variable 'B' diff --git a/test/messages/func_w0151.txt b/test/messages/func_w0151.txt new file mode 100644 index 0000000..dae33d9 --- /dev/null +++ b/test/messages/func_w0151.txt @@ -0,0 +1,2 @@ +W: 4: Used builtin function 'apply' +W: 6: Used builtin function 'map' diff --git a/test/messages/func_w0152.txt b/test/messages/func_w0152.txt new file mode 100644 index 0000000..ddf2d0b --- /dev/null +++ b/test/messages/func_w0152.txt @@ -0,0 +1,3 @@ +W: 5: Used * or ** magic +W: 14: Used * or ** magic + diff --git a/test/messages/func_w0202.txt b/test/messages/func_w0202.txt new file mode 100644 index 0000000..d10e5bc --- /dev/null +++ b/test/messages/func_w0202.txt @@ -0,0 +1,3 @@ +W: 8:Abcd.method1: Static method with 'self' as first argument +W: 12:Abcd.method2: Static method with 'cls' as first argument + diff --git a/test/messages/func_w0205.txt b/test/messages/func_w0205.txt new file mode 100644 index 0000000..5edbd0a --- /dev/null +++ b/test/messages/func_w0205.txt @@ -0,0 +1,2 @@ +W: 22:Cdef.abcd: Signature differs from overriden method + diff --git a/test/messages/func_w0223.txt b/test/messages/func_w0223.txt new file mode 100644 index 0000000..ece889b --- /dev/null +++ b/test/messages/func_w0223.txt @@ -0,0 +1,2 @@ +W: 22:Concret: Method 'bbbb' is abstract in class 'Abstract' but is not overriden + diff --git a/test/messages/func_w0231.txt b/test/messages/func_w0231.txt new file mode 100644 index 0000000..84e176d --- /dev/null +++ b/test/messages/func_w0231.txt @@ -0,0 +1,2 @@ +W: 19:CCCC: Class has no __init__ method +W: 26:ZZZZ.__init__: __init__ method from base class 'BBBB' is not called diff --git a/test/messages/func_w0233.txt b/test/messages/func_w0233.txt new file mode 100644 index 0000000..9857aa6 --- /dev/null +++ b/test/messages/func_w0233.txt @@ -0,0 +1 @@ +W: 12:AAAA.__init__: __init__ method from a non direct base class 'BBBB' is called diff --git a/test/messages/func_w0302.txt b/test/messages/func_w0302.txt new file mode 100644 index 0000000..ae8a99f --- /dev/null +++ b/test/messages/func_w0302.txt @@ -0,0 +1,2 @@ +W: 0: Too many lines in module (1017) + diff --git a/test/messages/func_w0312.txt b/test/messages/func_w0312.txt new file mode 100644 index 0000000..917e8d0 --- /dev/null +++ b/test/messages/func_w0312.txt @@ -0,0 +1,2 @@ +W: 10: Found indentation with tabs instead of spaces +W: 11: Found indentation with tabs instead of spaces diff --git a/test/messages/func_w0331.txt b/test/messages/func_w0331.txt new file mode 100644 index 0000000..8134a32 --- /dev/null +++ b/test/messages/func_w0331.txt @@ -0,0 +1 @@ +W: 6: Use of the <> operator diff --git a/test/messages/func_w0332.txt b/test/messages/func_w0332.txt new file mode 100644 index 0000000..b8984ad --- /dev/null +++ b/test/messages/func_w0332.txt @@ -0,0 +1 @@ +W: 4: Use l as long integer identifier diff --git a/test/messages/func_w0401.txt b/test/messages/func_w0401.txt new file mode 100644 index 0000000..9463727 --- /dev/null +++ b/test/messages/func_w0401.txt @@ -0,0 +1,2 @@ +R: 0: Cyclic import (input.func_w0401 -> input.w0401_cycle) +W: 6: Redefining built-in 'input' diff --git a/test/messages/func_w0402.txt b/test/messages/func_w0402.txt new file mode 100644 index 0000000..cf06fc4 --- /dev/null +++ b/test/messages/func_w0402.txt @@ -0,0 +1,2 @@ +W: 5: Wildcard import input.func_fixme + diff --git a/test/messages/func_w0403.txt b/test/messages/func_w0403.txt new file mode 100644 index 0000000..ef511f7 --- /dev/null +++ b/test/messages/func_w0403.txt @@ -0,0 +1 @@ +W: 8: Uses of a deprecated module 'Bastion' diff --git a/test/messages/func_w0404.txt b/test/messages/func_w0404.txt new file mode 100644 index 0000000..311056d --- /dev/null +++ b/test/messages/func_w0404.txt @@ -0,0 +1 @@ +W: 6: Relative import 'func_w0302' diff --git a/test/messages/func_w0405.txt b/test/messages/func_w0405.txt new file mode 100644 index 0000000..9555dfd --- /dev/null +++ b/test/messages/func_w0405.txt @@ -0,0 +1,4 @@ +W: 9: Reimport 'os' (imported line 6) +W: 16: Reimport 'exists' (imported line 7) +W: 21:func: Reimport 'os' (imported line 6) +W: 23:func: Reimport 're' (imported line 10) diff --git a/test/messages/func_w0406.txt b/test/messages/func_w0406.txt new file mode 100644 index 0000000..a1440b9 --- /dev/null +++ b/test/messages/func_w0406.txt @@ -0,0 +1,2 @@ +W: 5: Module import itself +W: 5: Relative import 'func_w0406' diff --git a/test/messages/func_w0611.txt b/test/messages/func_w0611.txt new file mode 100644 index 0000000..10952da --- /dev/null +++ b/test/messages/func_w0611.txt @@ -0,0 +1 @@ +W: 4: Unused import os diff --git a/test/messages/func_w0612.txt b/test/messages/func_w0612.txt new file mode 100644 index 0000000..b2d9cbb --- /dev/null +++ b/test/messages/func_w0612.txt @@ -0,0 +1 @@ +W: 8:function: Unused variable 'aaaa' diff --git a/test/messages/func_w0613.txt b/test/messages/func_w0613.txt new file mode 100644 index 0000000..b658b54 --- /dev/null +++ b/test/messages/func_w0613.txt @@ -0,0 +1,2 @@ +W: 7:function: Unused argument 'arg' +W: 14:AAAA.method: Unused argument 'arg' diff --git a/test/messages/func_w0622.txt b/test/messages/func_w0622.txt new file mode 100644 index 0000000..7191347 --- /dev/null +++ b/test/messages/func_w0622.txt @@ -0,0 +1,2 @@ +W: 8:function: Redefining built-in 'type' +W: 11: Redefining built-in 'map' diff --git a/test/messages/func_w0701.txt b/test/messages/func_w0701.txt new file mode 100644 index 0000000..677443b --- /dev/null +++ b/test/messages/func_w0701.txt @@ -0,0 +1,3 @@ +W: 8:function1: Raising a string exception +W: 12:function2: Raising a string exception + diff --git a/test/messages/func_w0702.txt b/test/messages/func_w0702.txt new file mode 100644 index 0000000..ab6d20a --- /dev/null +++ b/test/messages/func_w0702.txt @@ -0,0 +1 @@ +W: 10: No exception's type specified diff --git a/test/messages/func_w0703.txt b/test/messages/func_w0703.txt new file mode 100644 index 0000000..e37197a --- /dev/null +++ b/test/messages/func_w0703.txt @@ -0,0 +1 @@ +W: 8: Catch "Exception" diff --git a/test/messages/func_w0704.txt b/test/messages/func_w0704.txt new file mode 100644 index 0000000..1eca794 --- /dev/null +++ b/test/messages/func_w0704.txt @@ -0,0 +1 @@ +W: 8: Except doesn't do anything diff --git a/test/messages/func_w0705.txt b/test/messages/func_w0705.txt new file mode 100644 index 0000000..303e739 --- /dev/null +++ b/test/messages/func_w0705.txt @@ -0,0 +1,5 @@ +E: 10: Bad except clauses order (Exception is an ancestor class of TypeError) +E: 17: Bad except clauses order (LookupError is an ancestor class of IndexError) +E: 24: Bad except clauses order (LookupError is an ancestor class of IndexError) +E: 24: Bad except clauses order (NameError is an ancestor class of UnboundLocalError) +E: 27: Bad except clauses order (empty except clause should always appears last)
\ No newline at end of file diff --git a/test/messages/func_w0801.txt b/test/messages/func_w0801.txt new file mode 100644 index 0000000..c6ba231 --- /dev/null +++ b/test/messages/func_w0801.txt @@ -0,0 +1,11 @@ +R: 0: Similar lines in 2 files +==input.func_w0801:3 +==input.w0801_same:3 +__revision__ = 'id' +A = 2 +B = 3 +C = A + B +# need more than X lines to trigger the message +C *= 2 +A -= B +# all this should be detected diff --git a/test/messages/func_wrong_encoding.txt b/test/messages/func_wrong_encoding.txt new file mode 100644 index 0000000..10123a1 --- /dev/null +++ b/test/messages/func_wrong_encoding.txt @@ -0,0 +1 @@ +E: 1: Wrong encoding specified (UTF-8) diff --git a/test/messages/nonexistant.txt b/test/messages/nonexistant.txt new file mode 100644 index 0000000..0ccfb57 --- /dev/null +++ b/test/messages/nonexistant.txt @@ -0,0 +1 @@ +F: 0: No module named nonexistant diff --git a/test/messages/nonexistant.txt2 b/test/messages/nonexistant.txt2 new file mode 100644 index 0000000..ffe19c5 --- /dev/null +++ b/test/messages/nonexistant.txt2 @@ -0,0 +1 @@ +F: 0: No module named input/nonexistant diff --git a/test/regrtest.py b/test/regrtest.py new file mode 100644 index 0000000..927ab48 --- /dev/null +++ b/test/regrtest.py @@ -0,0 +1,131 @@ +# Copyright (c) 2005 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., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +"""non regression tests for pylint, which requires a too specific configuration +to be incorporated in the automatic functionnal test framework +""" +__revision__ = '$Id: regrtest.py,v 1.12 2006-03-03 09:25:34 syt Exp $' + +import sys +import os +import unittest +from os.path import abspath, join + +from logilab.common import testlib + +from utils import TestReporter + +from pylint.lint import PyLinter +from pylint import checkers + +test_reporter = TestReporter() +linter = PyLinter() +linter.set_reporter(test_reporter) +linter.disable_message_category('I') +linter.config.persistent = 0 +linter.quiet = 1 +checkers.initialize(linter) + +sys.path.insert(1, abspath('regrtest_data')) + +class NonRegrTC(testlib.TestCase): + def setUp(self): + """call reporter.finalize() to cleanup + pending messages if a test finished badly + """ + linter.reporter.finalize() + + def test_package___path___manipulation(self): + linter.check('package.__init__') + got = linter.reporter.finalize().strip() + self.failUnlessEqual(got, '') + + def test_package___init___precedence(self): + linter.check('precedence_test') + got = linter.reporter.finalize().strip() + self.failUnlessEqual(got, '') + + def test_check_package___init__(self): + for variation in ('package.__init__', 'regrtest_data/package/__init__.py'): + linter.check(variation) + got = linter.reporter.finalize().strip() + checked = linter.stats['by_module'].keys() + self.failUnlessEqual(checked, ['package.__init__'], + '%s: %s' % (variation, checked)) + cwd = os.getcwd() + os.chdir('regrtest_data/package') + sys.path.insert(0, '') + try: + for variation in ('__init__', '__init__.py'): + linter.check(variation) + got = linter.reporter.finalize().strip() + checked = linter.stats['by_module'].keys() + self.failUnlessEqual(checked, ['__init__'], + '%s: %s' % (variation, checked)) + finally: + sys.path.pop(0) + os.chdir(cwd) + + def test_gtk_import(self): + try: + import gtk + except ImportError: + self.skip('test skipped: gtk is not available') + except RuntimeError: # RuntimeError when missing display + pass + linter.check('regrtest_data/pygtk_import.py') + got = linter.reporter.finalize().strip() + self.failUnlessEqual(got, '') + + + def test_numarray_import(self): + try: + import numarray + except ImportError: + self.skip('test skipped: numarray is not available') + linter.check('regrtest_data/numarray_import.py') + got = linter.reporter.finalize().strip() + self.failUnlessEqual(got, '') + + def test_socketerror_import(self): + linter.check('regrtest_data/socketerror_import.py') + got = linter.reporter.finalize().strip() + self.failUnlessEqual(got, '') + + def test_class__doc__usage(self): + linter.check('regrtest_data/classdoc_usage.py') + got = linter.reporter.finalize().strip() + self.failUnlessEqual(got, '') + + def test_package_import_relative_subpackage_no_attribute_error(self): + linter.check('import_package_subpackage_module') + got = linter.reporter.finalize().strip() + self.failUnlessEqual(got, '') + + def test_module_global_crash(self): + linter.check('regrtest_data/module_global.py') + got = linter.reporter.finalize().strip() + self.failUnlessEqual(got, '') + + def test_descriptor_crash(self): + for fname in os.listdir('regrtest_data'): + if fname.endswith('_crash.py'): + linter.check(join('regrtest_data', fname)) + linter.reporter.finalize().strip() + + +if __name__ == '__main__': + from logilab.common.testlib import unittest_main + unittest_main() diff --git a/test/regrtest_data/application_crash.py b/test/regrtest_data/application_crash.py new file mode 100644 index 0000000..6e6044a --- /dev/null +++ b/test/regrtest_data/application_crash.py @@ -0,0 +1,12 @@ +class ErudiPublisher: + def __init__(self, config): + self.url_resolver = self.select_component('urlpublisher') + + def select_component(self, cid, *args, **kwargs): + try: + return self.select(self.registry_objects('components', cid), *args, **kwargs) + except NoSelectableObject: + return + + def main_publish(self, path, req): + ctrlid = self.url_resolver.process(req, path) diff --git a/test/regrtest_data/classdoc_usage.py b/test/regrtest_data/classdoc_usage.py new file mode 100644 index 0000000..ae8b9fe --- /dev/null +++ b/test/regrtest_data/classdoc_usage.py @@ -0,0 +1,17 @@ +"""ds""" + +__revision__ = None + +class SomeClass: + """cds""" + doc = __doc__ + + def __init__(self): + """only to make pylint happier""" + + def please(self): + """public method 1/2""" + + def besilent(self): + """public method 2/2""" + diff --git a/test/regrtest_data/descriptor_crash.py b/test/regrtest_data/descriptor_crash.py new file mode 100644 index 0000000..4b3adcc --- /dev/null +++ b/test/regrtest_data/descriptor_crash.py @@ -0,0 +1,20 @@ +# -*- coding: iso-8859-1 -*- + +import urllib + +class Page(object): + _urlOpen = staticmethod(urllib.urlopen) + + def getPage(self, url): + handle = self._urlOpen(url) + data = handle.read() + handle.close() + return data + #_getPage + +#Page + +if __name__ == "__main__": + import sys + p = Page() + print p.getPage(sys.argv[1]) diff --git a/test/regrtest_data/import_package_subpackage_module.py b/test/regrtest_data/import_package_subpackage_module.py new file mode 100644 index 0000000..b5fb219 --- /dev/null +++ b/test/regrtest_data/import_package_subpackage_module.py @@ -0,0 +1,49 @@ +# pylint: disable-msg=I0011,C0301,W0611 +"""I found some of my scripts trigger off an AttributeError in pylint +0.8.1 (with common 0.12.0 and astng 0.13.1). + +Traceback (most recent call last): + File "/usr/bin/pylint", line 4, in ? + lint.Run(sys.argv[1:]) + File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 729, in __init__ + linter.check(args) + File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 412, in check + self.check_file(filepath, modname, checkers) + File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 426, in check_file + astng = self._check_file(filepath, modname, checkers) + File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 450, in _check_file + self.check_astng_module(astng, checkers) + File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 494, in check_astng_module + self.astng_events(astng, [checker for checker in checkers + File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 511, in astng_events + self.astng_events(child, checkers, _reversed_checkers) + File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 511, in astng_events + self.astng_events(child, checkers, _reversed_checkers) + File "/usr/lib/python2.4/site-packages/pylint/lint.py", line 508, in astng_events + checker.visit(astng) + File "/usr/lib/python2.4/site-packages/logilab/astng/utils.py", line 84, in visit + method(node) + File "/usr/lib/python2.4/site-packages/pylint/checkers/variables.py", line 295, in visit_import + self._check_module_attrs(node, module, name_parts[1:]) + File "/usr/lib/python2.4/site-packages/pylint/checkers/variables.py", line 357, in _check_module_attrs + self.add_message('E0611', args=(name, module.name), +AttributeError: Import instance has no attribute 'name' + + +You can reproduce it by: +(1) create package structure like the following: + +package/ + __init__.py + subpackage/ + __init__.py + module.py + +(2) in package/__init__.py write: + +import subpackage + +(3) run pylint with a script importing package.subpackage.module. +""" +__revision__ = '$Id: import_package_subpackage_module.py,v 1.1 2005-11-10 16:08:54 syt Exp $' +import package.subpackage.module diff --git a/test/regrtest_data/module_global.py b/test/regrtest_data/module_global.py new file mode 100644 index 0000000..54fd46d --- /dev/null +++ b/test/regrtest_data/module_global.py @@ -0,0 +1,7 @@ +# pylint: disable-msg=W0603,W0601,W0604,E0602,W0104 +"""was causing infinite recursion +""" +__revision__ = 1 + +global bar +bar.foo diff --git a/test/regrtest_data/numarray_import.py b/test/regrtest_data/numarray_import.py new file mode 100644 index 0000000..3f0be8b --- /dev/null +++ b/test/regrtest_data/numarray_import.py @@ -0,0 +1,7 @@ +"""#10077""" + +__revision__ = 1 + +from numarray import zeros + +zeros() diff --git a/test/regrtest_data/package/AudioTime.py b/test/regrtest_data/package/AudioTime.py new file mode 100644 index 0000000..a1fde96 --- /dev/null +++ b/test/regrtest_data/package/AudioTime.py @@ -0,0 +1,3 @@ +"""test preceeded by the AudioTime class in __init__.py""" + +__revision__ = 0 diff --git a/test/regrtest_data/package/__init__.py b/test/regrtest_data/package/__init__.py new file mode 100644 index 0000000..9e1ebfa --- /dev/null +++ b/test/regrtest_data/package/__init__.py @@ -0,0 +1,14 @@ +# pylint: disable-msg=R0903,W0403 +"""package's __init__ file""" + +__revision__ = 0 + +# E0602 - Undefined variable '__path__' +__path__ += "folder" + +class AudioTime(object): + """test precedence over the AudioTime submodule""" + + DECIMAL = 3 + +import subpackage diff --git a/test/regrtest_data/package/subpackage/__init__.py b/test/regrtest_data/package/subpackage/__init__.py new file mode 100644 index 0000000..dc4782e --- /dev/null +++ b/test/regrtest_data/package/subpackage/__init__.py @@ -0,0 +1 @@ +"""package.subpackage""" diff --git a/test/regrtest_data/package/subpackage/module.py b/test/regrtest_data/package/subpackage/module.py new file mode 100644 index 0000000..4b7244b --- /dev/null +++ b/test/regrtest_data/package/subpackage/module.py @@ -0,0 +1 @@ +"""package.subpackage.module""" diff --git a/test/regrtest_data/precedence_test.py b/test/regrtest_data/precedence_test.py new file mode 100644 index 0000000..087b5cf --- /dev/null +++ b/test/regrtest_data/precedence_test.py @@ -0,0 +1,21 @@ +""" + # package/__init__.py + class AudioTime(object): + DECIMAL = 3 + + # package/AudioTime.py + class AudioTime(object): + pass + + # test.py + from package import AudioTime + # E0611 - No name 'DECIMAL' in module 'AudioTime.AudioTime' + print AudioTime.DECIMAL + +""" + +__revision__ = 0 + +from package import AudioTime + +print AudioTime.DECIMAL diff --git a/test/regrtest_data/pygtk_import.py b/test/regrtest_data/pygtk_import.py new file mode 100644 index 0000000..4ac9560 --- /dev/null +++ b/test/regrtest_data/pygtk_import.py @@ -0,0 +1,14 @@ +#pylint: disable-msg=R0903,R0904 +"""#10026""" +__revision__ = 1 +from gtk import VBox +import gtk + +class FooButton(gtk.Button): + """extend gtk.Button""" + def extend(self): + """hop""" + print self + +print gtk.Button +print VBox diff --git a/test/regrtest_data/socketerror_import.py b/test/regrtest_data/socketerror_import.py new file mode 100644 index 0000000..37310cf --- /dev/null +++ b/test/regrtest_data/socketerror_import.py @@ -0,0 +1,6 @@ +"""ds""" + +__revision__ = '$Id: socketerror_import.py,v 1.2 2005-12-28 14:58:22 syt Exp $' + +from socket import error +print error diff --git a/test/runtests.py b/test/runtests.py new file mode 100644 index 0000000..67d6636 --- /dev/null +++ b/test/runtests.py @@ -0,0 +1,5 @@ +from logilab.common.testlib import main + +if __name__ == '__main__': + import sys, os + main(os.path.dirname(sys.argv[0]) or '.') diff --git a/test/smoketest.py b/test/smoketest.py new file mode 100644 index 0000000..1954466 --- /dev/null +++ b/test/smoketest.py @@ -0,0 +1,74 @@ +# 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., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +""" Copyright (c) 2000-2003 LOGILAB S.A. (Paris, FRANCE). + http://www.logilab.fr/ -- mailto:contact@logilab.fr +""" + +__revision__ = "$Id: smoketest.py,v 1.6 2005-04-15 10:40:24 syt Exp $" + +import unittest +import sys +from cStringIO import StringIO + +from pylint.lint import Run +from pylint.reporters.text import TextReporter, TextReporter2, ColorizedTextReporter +from pylint.reporters.html import HTMLReporter + + +class LintSmokeTest(unittest.TestCase): + + def test1(self): + """make pylint checking itself""" + Run(['--include-ids=y', 'pylint'], reporter=TextReporter(StringIO()), quiet=1) + + def test2(self): + """make pylint checking itself""" + Run(['pylint.lint'], reporter=TextReporter2(StringIO()), quiet=1) + + def test3(self): + """make pylint checking itself""" + Run(['pylint.checkers'], reporter=HTMLReporter(StringIO()), quiet=1) + + def test4(self): + """make pylint checking itself""" + Run(['pylint.checkers'], reporter=ColorizedTextReporter(StringIO()), quiet=1) + + def test_generate_config_option(self): + """make pylint checking itself""" + sys.stdout = StringIO() + try: + self.assertRaises(SystemExit, Run, + ['--generate-rcfile'], + reporter=HTMLReporter(StringIO()), + quiet=1) + finally: + sys.stdout = sys.__stdout__ + + def test_help_message_option(self): + """make pylint checking itself""" + sys.stdout = StringIO() + try: + self.assertRaises(SystemExit, Run, + ['--help-msg', 'W0101'], + reporter=HTMLReporter(StringIO()), + quiet=1) + self.assertRaises(SystemExit, Run, + ['--help-msg', 'WX101'], + reporter=HTMLReporter(StringIO()), + quiet=1) + finally: + sys.stdout = sys.__stdout__ + + +if __name__ == '__main__': + unittest.main() diff --git a/test/test_encoding.py b/test/test_encoding.py new file mode 100644 index 0000000..ca5b53f --- /dev/null +++ b/test/test_encoding.py @@ -0,0 +1,62 @@ +# 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., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +""" Copyright (c) 2003-2005 LOGILAB S.A. (Paris, FRANCE). + http://www.logilab.fr/ -- mailto:contact@logilab.fr + +Check source code is ascii only or has an encoding declaration (PEP 263) +""" + +__revision__ = '$Id: test_encoding.py,v 1.6 2005-11-02 09:22:04 syt Exp $' + +import unittest +import sys +from pylint.checkers.misc import guess_encoding + +class TestGuessEncoding(unittest.TestCase): + + def testEmacs(self): + e = guess_encoding('# -*- coding: UTF-8 -*-') + self.failUnlessEqual(e, 'UTF-8') + e = guess_encoding('# -*- coding:UTF-8 -*-') + self.failUnlessEqual(e, 'UTF-8') + e = guess_encoding(''' + ### -*- coding: ISO-8859-1 -*- + ''') + self.failUnlessEqual(e, 'ISO-8859-1') + e = guess_encoding(''' + + ### -*- coding: ISO-8859-1 -*- + ''') + self.failUnlessEqual(e, None) + + def testVim(self): + e = guess_encoding('# vim:fileencoding=UTF-8') + self.failUnlessEqual(e, 'UTF-8') + e = guess_encoding(''' + ### vim:fileencoding=ISO-8859-1 + ''') + self.failUnlessEqual(e, 'ISO-8859-1') + e = guess_encoding(''' + + ### vim:fileencoding= ISO-8859-1 + ''') + self.failUnlessEqual(e, None) + + def testUTF8(self): + e = guess_encoding('\xef\xbb\xbf any UTF-8 data') + self.failUnlessEqual(e, 'UTF-8') + e = guess_encoding(' any UTF-8 data \xef\xbb\xbf') + self.failUnlessEqual(e, None) + +if __name__ == '__main__': + unittest.main() diff --git a/test/test_format.py b/test/test_format.py new file mode 100644 index 0000000..94a499f --- /dev/null +++ b/test/test_format.py @@ -0,0 +1,168 @@ +# 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., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +""" Copyright (c) 2000-2003 LOGILAB S.A. (Paris, FRANCE). + http://www.logilab.fr/ -- mailto:contact@logilab.fr + +Check format checker helper functions +""" + +__revision__ = '$Id: test_format.py,v 1.13 2005-11-02 09:22:06 syt Exp $' + +import unittest +import sys +import re +from os import linesep + +from pylint.checkers.format import * +from utils import TestReporter + +REPORTER = TestReporter() + +class StringRgxTest(unittest.TestCase): + """test the STRING_RGX regular expression""" + + def test_known_values_1(self): + self.assertEqual(STRING_RGX.sub('', '"yo"'), '') + + def test_known_values_2(self): + self.assertEqual(STRING_RGX.sub('', "'yo'"), '') + + def test_known_values_tq_1(self): + self.assertEqual(STRING_RGX.sub('', '"""yo"""'), '') + + def test_known_values_tq_2(self): + self.assertEqual(STRING_RGX.sub('', '"""yo\n'), '') + + def test_known_values_ta_1(self): + self.assertEqual(STRING_RGX.sub('', "'''yo'''"), '') + + def test_known_values_ta_2(self): + self.assertEqual(STRING_RGX.sub('', "'''yo\n"), '') + + def test_known_values_5(self): + self.assertEqual(STRING_RGX.sub('', r'"yo\"yo"'), '') + + def test_known_values_6(self): + self.assertEqual(STRING_RGX.sub('', r"'yo\'yo'"), '') + + def test_known_values_7(self): + self.assertEqual(STRING_RGX.sub('', '"yo"upi"yo"upi'), 'upiupi') + + def test_known_values_8(self): + self.assertEqual(STRING_RGX.sub('', "'yo\\'yo\\"), '') + + def test_known_values_9(self): + self.assertEqual(STRING_RGX.sub('', '"yoyo\\'), '') + + def test_known_values_10(self): + self.assertEqual(STRING_RGX.sub('', 'self.filterFunc = eval(\'lambda %s: %s\'%(\',\'.join(variables),formula),{},{})'), + 'self.filterFunc = eval(%(.join(variables),formula),{},{})') + + def test_known_values_11(self): + self.assertEqual(STRING_RGX.sub('', 'cond_list[index] = OLD_PROG.sub(r\'getattr(__old__,"\1")\',cond)'), + 'cond_list[index] = OLD_PROG.sub(r,cond)') + + +## def test_no_crash(self): +## crash_str = """wizardBmp = ('eJzdXc2R5DxyPZBR0WPBd+4rywOZsQasCevDHHTVdQ5jx1w3QnJBF7mwISdUUyAeXv4CIFndPYpA\ndLBR+H14TCQyAfDHf/7vcvu+h7ef7TkKI2leEU7WW7K//3r8vf/jn78f3n9tf/+f3w9vPx8P+zMi\n3389kpWUj7/8a3lWkSUll/NDAYv2lwc3huPLw1XPF4JsCmxQPJEEaMAZCRg0HgU9IiY7gy+A/X8T\nAKnk2P1v/2ooPZ6B8CM9pWTiWdgthhbtPw9Yl6v2tZKQ/u7s3/7117/9twva+vZfO/Ge8LoEtrxV\nXLWRfxiwjJ78+8Cn4FmoWB5AUX6wHCsEBvKPv4/nndUmC1hdfuWUoYx1mcBksCKRH87Hx+E3bslP\nt++/iVeRVJACKyaeJbAFTbzg8Yi4kQD20bAS0No/KBSKtn+fHSyB/y3PJc0DWPzL6UtRKjGK5QdR\n/tvPUiB+3YE1YPK/zJkP+BvVruMLCeltLZElBgRGZMmFlFwOMxzlFOI9nguTS2I841euCA8A9tMp\ndz4wxxSjkpQq0s2r0nf/ZcZ+OiyzGIKr65MnJZ6nG6YTXlIbOGURvCoSNUIsc43lGZF4gLr16VgN\n4snPETntw7UNyCUwIiFjPx23PEAdUmyEcGMxuFZWd8u00tjNhZQQrYrSiET2PwXYSOi5M77i9mD5\ng2m4xqSELwWs2wyQihmrmFa09DXQClQAqVhmPp5zUcw66moYi2Qo8zewX1JxtfNsxEyXQonUdXWG\nKcYmCb6+jAV/mHjMMZa0KqXSYJWqwM8Rq22A/GSt2MX2d5n/+OeX1QosPSLVMS/Bpsz/TUqbyvjV\nGMvLKLUggpKZCEMWC0oalwQR1fMmqdcnJy0D++l4JiitwxRVedeA8LZklT4h5whpF2ndmu51bFtQ\nkZGFyranTO5LsGafClBrFf9R5m5rJQWYLQ9qkbVIQ5ZaZK2kjaDM0GzIpjnXFsrxbjJVQgxv+asM\ndMXKx8ZVkZ3El1va4y8MfevTlL13v5qvuUbXEdBs2lIitbaRnRzDBMxDn9dLzdSENtN1qQb5b//+\nH2Vi3Q37yoqrHiK3QrEBPg16rpcqisQQPJphf2W3ws5zeBAiYF1DffdX+zCJMBrGjo9Hwwq8v2Oi\nVnVrJPeW9RuWRYFDPE4pueqyGrKCIz/TNVNNyuw+fjyUzha6alSnCn8CCwyVwxpsdF3bEVxKxpah\n55S7p+ZjgPVcPPvMUvpVnaT7orXS9fH3d/OemwH6GJrgOlv3yGcb9hrzlMbx7Q5Tf1/BQIPbT/lf\nCezvYa3/YtJpbX4+lyYVSBuwg6ia1iovbakFD3t71MRXFVQFrHJt20kQwIIGrro1okodVsygBbGF\nudBgb+Fzc0VB9XdT5XBwsa7mJnSMqhuwCFX6Q6grkuZgtTWhYsn3sWT/9AVCa1hRzh+oPl2cRRUs\nNqKz5c+vL1yQo/jFWz58CrCJgl2wLTMXRMExHApFS4xyIB4YGoiUe91CkOf6AGBL+RBiPL6LWSFi\nKm9awRhjlbjgks9wdbYEJQpeZITBXAZdscynK/k4QAOGSKlb3V5gOVDECK+V8FKcIe0amHSShr2a\nsUXxKChh6HmzhOLDozGPX4UoGBh0aK0F1aKkrVdw9XAhr2Zs6WYBimdYFllIDIgEsFU7CiF9ZsFk\ntyvncheHqmK2I9bdM2g2fBWwT/qVN7qpT7H0KxDxykuld6tgkpeMyHUJY21rR4o9IwqUNUk9rCRj\nuddlblqlAVlhVUZhRCvYB6J6Q3a7jXT8RS0fD+yUAWP3sQuKermMrQYBy1urFayVgV11q+AJCcCj\nBpV4kBhDKed1jA+YvPb5tMKF19yn65Pk2gjjLrvMEMB+Kyyx80ZyN0CfwbL3k4Et1HoaRZm35aH8\nZNPnMhavgJitnlqBVYyvosdsma8GtlBot3w+5ZLimLJUuKJAmzJqGraHN7NqVTngXrmmF9JBuSvh\nsZphtYJZwZ6nb+vBqmo++gvLFa+tkEBPXsoJSCYatkirfb94uEThsatFVgJdeH3XjOvwcl0ksUUR\ngg4PZQlWDFY8lzVrdrW5hYZuT8vdRQrZCblGYcyMrJoqjQwFKMeDkHr9Oj4vi21uHUWos3NR0dkk\nCGGoZ3PZKiUKEPSIPDO6ptf9TZltuUcV66DZnZuqZHp+iQehTlULuTbge2Vyuig5wFb0xFjcvqN8\nB1iWP6e747hvAGwQXuWacU+uPW2tnGaROhjM2lB3W/OCWW/xnCn7FNOVWpPdYV+kesVeCyy6YHxz\n7LNQwC71MGa3JTCwNNrfGmm5ImxCuLBrjoy9ENjcvVWf0QZ1tCppzKA3VnC11gX1WIiC2wBXXX+Z\nl8vuQBCv3nlgRxoZnzW7piLZX9fft89cl1bkvxQjwLrvjtpw4oZ8cPnY9dXAlnHUbuhECCiNK1Kx\nboa3kSiI8AGwqa47skZo6g0AJFeRiLw16aqUBIWYeGABHpVv/62ehbWag/aF28zaga067gLBXS7l\nwEJDTgDHK1nmcUzcWPJzJOYpbewqfrGKalEkmgKAgasKA8phVQEVFa1g/7Xu/UOZC/zCqfubQ7Sk\ndZEpz4dtBUt1ES5Pc6u2MkkLSRSJiR5t0Cpr/bVwuyw0GFJeow014ykbeZX6onAMWDXc6F1pPGwj\nI93czCG+xawFdkDqpGDLnALWdiF6nRVpt+ETZGs9NXNydEAnyLfyzH+1UJVyVb0LEau1gK0xXLUj\nabEwOdrTRRmCXuyaYSha78qOrEqwXKtUhax1ZgmJx6XBzvOsJdJ/0LyIioPMWY1r5gMYq8ax9J2f\nxZueOwff9vtDYCjQb30ZMpqdudjlNYZuW4VbnQbWaAWd8oM0apMbRzJhwKJWYNH6pGkIVi9oF816\nUFG9zx/XOhYi93cC1yWigMdUU6hnBme9CKuVBuyt2Wq0EYZk6esgXc1LMRgsYxUUg0uG4nxRXE12\n9TA5oUE1yYwDCDQBWU24tOpeT37Z6o5JOUc1pRsSlt6OuKbHnt4nqf4dYRELUiE5pZdWKQ9aW6i8\njRpzVbA96lY0KwoiAi/m+F5YQtWXeEpi9Hjvlp3l1VzGRphXQFoC/JKoqKvKHl950fqlLZ8H6Fpw\nYHxAy7W6FMHJxThwF2kb/1G3KLxa0q5S2A4ytpkp5CJ6lRSN7AZF/qxmA7xumJSfanrigN2Y0FoZ\n2IV2MAodjPQ6tnFdAilPGcpQYCm31G2cC2xf1rYmjWyigzDRkDFtrYcduF5ec4GNTYp67zsrCaiu\nFFVmK7VcVXYz0XJqreqOk9IzjYqtvDjHEZnHI2Ddurzul594T+YiLbGahy4UEbBxGtjwHQOLJUmE\n83iQzkRYt/Jc7gQxF8hlAGuwILaEC6JA2A28IUj8Nfe6Qwlnl6LJ7ppgTtQmrPCBTTchRAN6V6f/\n2DNS3dx6tkqD1mNtgupML/Mg29PYB6THN/dtJV5dewg3cKD4wEaeC9MYTN8LzCy0P6TYUVUtP5Q7\nuzfc0ApssK49a0V0sB1H1fxqN2w0GRsU2xpvJLbSE8QY0aqfu7nW7Y6Kez+qeR8czvqolrQRsM+H\nuzl7K96L6MKEm5xBeu48vIZ362HnlFQyGi+0lBhbq26V+QsifbcGV6qUOcVFyVXGwBBfxtaWN9ce\nWSZZ3+DsbtdGWMSdvcsjaUrXsiUoW8FhNY/XCAoo0c2qs4VFWcbaJfNbdQsSqWCsEHPZpSaaqbWz\nBdaCvJgVAWfh5R5sa40k5kXOUW08lyRHGixGVnkhnhIjwg5mmANCul1Wv6JHS90utcZLWmS8ymwY\njSCE6ng5i1S3wi4wf0gPKaYGsbgzQB3r3ZT90AW22ww7oGDOMWPIIlUjPbmb9tzpLLbzgkgLD2tu\n/ZYEo3CXpx1dKPj5pIxVYzfivuwWuMiV1xoTxtp8gC3ztiwi7vViBNvs2W6OhPOiwI7j9ndxzTKP\neDdykc70osKtZM1WWaCNMIF31aiqFne6YSZsmzkRg4sobY2rfDcRyTdPXqIVHBtWl95lndtsrdKG\nFlWneXtrbnFlT4DWnRei3hEb6b4+nFgBaxWAbrAE2OpnBXJyupHMNJgUVnnNqaUKqNvKKT5xcycS\n+x04i92McTdnKGyN3N+S91rG5pI2Z7I+CU6rgx/VbWRJYqll22DnMj6tE7EuomJSo5v9vIxly49i\noNi4LkdcybrdxFr3XTBdN3mzgaW6uvsU5bS2kT1BXZUG0/Hq3Z2uWG0vP2cyYzcO1imX6LFq7BQP\nRziw1lUVb1NUhajDXPZdsHJ75+17O6fDvI1kqfuW5UJY8fa8KGjDRzO4KzlHIsuDqxW40/FGn3tQ\ndFJUX8ie8MAWhlzrtVcEhnqwmtdwNS+suEo6vRtqkKg5V5OMopC3fR9sMxu+/XRJstCJzsiTqEU9\n6bfgbZEMlqJWAViMq3RExgoDmjKmKeMSLGNQJOqQCeEWGBzsT9r6pFZetAWOG8+EwWY8VZ1rAGS7\nkJAJXIU0cPErs0jvnq0IkWfdGdIf1B6OeZd69tjVHL1k1x5o2Q9GB9O8kuHuGrp7Xchx6zZSjaOo\n9Ci8vKRVP0H0cTfvfLAr9xSYWrQdrCKvRmGRPosueS5wwLm3Jp4rM3Mmvu1HdNtSOj8pk7Zc6WBJ\n4tY1OKZ73Ah/dZuq3BA37aXFUv1VwN6O+ExzBELeco3VKbNf8ztQbANKerX8mGfilexIdzLCYNV5\nf1qeWzK3nGBmyfduLdXnxcrPSE9tUHtIxNpBcqn59UizgjdEgaOAnVWxxPxL5rhBRcsviuihjKht\nFNg1oxYaN+k9JP0NJS/yHAROT4CxRVVAvUIbfG+n9bt9ObbyEtZdug3+7m1wmgZWigLWalCjWtJq\nlbVeHM0vuHIrqEh2QVrD2kp3zq9jZudLlrSaOYd4y/pSWyDc/FUhqogq6p4Fs5H2hpm86hFguf2u\nb6K7HGvLRnMJZANN/gWSIrICWyStW+9Gl5dinSuqo/0MvDrOgb3X2+ybmuE5TGHTjrhxOWPV82Fh\nu6iVl1pSYYHJN1FbKwpd39FOgSkZiw2K9LEArPXcDvLyNsfhKg+CayE5Ir3V5BWUAEloRSKviztq\nmxvkC8UdtHaeJPy4wjssqDKzqyHCVqhbnk3vToeUMY+3BJWNTWASRVfPXtEFYfZNtIw9BjKbiDGs\nhxUDwTSzUcRON2oSV9pFM+2aI2aq6ryDdkdKB9jrGOsOKBuCBksQwEozeDKPcwkQs2zTPtnNXA9x\ngB1g7IhWYA+crvUKPlUOy0NVS0GyiQIoDwYoNemzClHKKXoF6126ruGeQlVm67ebEdkv0Qp4QO2/\ns5J2sdatun9PM9AcZ26+G57CsHfxsKLSc3bbcJVWUILycbhKZhdVLQpqDDa0sKsRhgXe1bxKw6wa\npmNdu9dNHevYPeqREn5mZFk3OLIEMycTFZ7CllVPMTeVtYpl1g0QSoIzHZySsVcBW6pmvR1Kwjiq\nSt2yq/tG4+oS4vWUVU3vtNdISVpRdTyJ3+m4LvYdjQAL3ViYW61Zj030xrq42h1N9ZMH69uw0+St\nnaV1r0ABD4Whm+Y1t1jf3Jp4c3hTN94LaJLwm6dutYlVSTfIL7jBrXDvnYqyrbLAqjR89kGcpwvq\n0uWMexzgs1BfqUvdFhcDe/NO7sAuOgMsXKhaQ6CAipRFxRcIJFI0bj1goWJ1TOtU1ClglfOuaqFq\ncJuwHcT2OVsJ33SQEhUV0LpZEv7rfkkmYBrl13AnTMyN48CqYSIvGMN7p2tOW7PTMpWMTYZA9VpP\ncKaz4O0cZ+SFjZoknph1Ji/r85IJtNBmK+utqcT36n3b8DU9aPtqIGTgc1sKQxcBJGNgXdGxkEBo\nFkLbfdv3cuSqXkcJo4GTuD6zUr2adVnyb/S3BDG41eDJKorKaIf7bk9/G0j3iuTV345iJkvGgkIV\nZXvK6tZChm61pHWhuHblxdqs+zyox7ohqYsDjANlXYZXCWsHQelhuxDXtVaLUJLlKrOhDe5WwBE3\n3CCeXFHhPHioZihRrPwyBaSTaIC3Upj1k16+8nLH+rD1Y7yW5dacLEqzYmD/st9RmrHmzS5pLxcF\nCO5W2POnlmwtaiusWi8wttCaGqs95ynz1lrvXW+pBfZFqEbhcgK3OZoOy7ACIIC9SZPvcGNYwHYT\nK63gFWGTHyk4YEsc7LXYpksbNlQHm+KhFmLpa7s9750oz+My9nWiIALBfT5fLNQ2x+Qlty7wKvgu\nNx4nbb5cxp5BXp0F4O36KPySkb2bczrWJqYWC67+EBXO/qYuVh8jY4X6Fw/9JfBiSaWukEI3d4X2\nrd2cNu61WYYN+B8mCtTEvZL/8aryMXHvMXKF66q1yjExUvj24iv4zoSp6fVYwD4iV2WFHFaKWdLg\n2WZ/MLDs2jiz4TMKUDl4v7cyHaxvjquxJYi9M2wxGAT2Y7BVAlaJ2TPsZTSwvUHptG29UP0ObEiH\nL1KVZhvMpLWmJAtsM53FNkbfqJgGW+Miz9ew2xGJHVuc/eslWGmtt8nLo37QxV9AMvH+8JBxmVA8\nRGuNFfR610warGWGtSNnKA8EOpiM1Sujx1wVuq613wIxa+i2tmXZ+I8D9m3f9lOmDHG6h28JUy/O\noVq2emECgF1od8Emv6USOjqZh/IDTO2Ql8fVjwaWGsx0Yhrg8ObZKmr3Q0/uoIMGDVYXjaoDdF8B\nWLwvEALye3kNW9c3NBMAAp/M0v3ND4jV2u/18NpGdxqEosNWlExV7t+o2DEo2IUEGuy8lf6mY6iW\njm/qY2rjBJD488vVjuy583sXWBWfwKseBmlGzpRNfbYSlDgjFiJgXZbSHKqO6zqCxRC4RVpgLSYW\nLiSw/XUVjwFY3B0ITGCn0kjvMtWBWg4+GFl58JkhVSYd6Abtplyp1bRyCrBcy3MgmqCjyPYX8RzQ\nRykhrQLPWjd+5epUgsVbOCyR68QeYyT2in8h6lWbbdcsCHLVYBu84OqSeplSGYhuKAMnYiijUEqN\nZ9wNd+/QzVpXwWpb+OASrPzL+0a2+AYSVq1h59TLDe92lI0Ona3VslQ0OnHEqfrjwoE7GibWp4YP\nHH8m2FfApVz4ZkmKcrzN+6N+6oVZh9XKeHApjfjo2paIvWxLYY6tkxYb5hge1EsBccqRK1ldVDOU\nnSeKbDI2ES/nglBND1kLx3NFLHJp5r4Obi2Wn1G9+JcZ60rL8peFaiSHE/msWD3COpZdTCcl5SxF\nXTox8fhZidOkYV3XDBe+4btjPKHXSXBX1M/J1fDXN3HLQU7CEWrlRHKla543eSOS9OggLOfQAy1R\ncU8g+MYcYIkKzRlX5zHblQzf6JIl2zy+VqvRr95JyFvsUIhqWOmgldgLmaoGXx8laRO/zKK0Aitj\nFYE5UvJtlRJshLFW65glScLAXK4mhXd5m4tZFd9WXnXN3rQCXP5Z1VHWYIEqrrBrbKx67FqV/53A\npEKwyB1xKbJW4E7Hrhy2WywidTTnLbIgZSRmWSsQjK0TN4+1+ms55ovQSfk8wpmEIYM0Vg22uaI3\nKBHXKr7J2EB9tStTsbaSOz+V4nqvlzbPLd964s7O4As50yNW23Igt62Oam+YUa9MQlcIuo2+Rhox\nJxlrN17JTy1O5WVHVhR3eWslpyVSLleTMpPXIeGq+6vL2H19DeFQDSz3pz1hI68K7BjuWkz/K2Uv\naKBflmog6vIWZbIagDW7K045ICXzdqMlv2LjGpxMjFZe5TLVe71qpvzLwY2cSnbH95KM6Q+ocuLN\nGk6VeZAMthuZ71iJ2iPrSN3plDHk/51chMgF5nBilRKjwLW3csgFPAJdNzzKdEtr9A5CyWhLE9i6\nFmA6J8gOKTbzsnULkVt1Fm/kF9tgb68vLA/QpjwIVE6Rq+y+ZGDd3p2EtAFrbdTSQFpaLkjufqZK\n2c/f6PQB2VvQaziFl7qL3uUYp4QkKS1vznHpRXr8uosCHJAkVsNliUuV7YupYiIJ0Eb/9t2mGXSm\nqIoavJHzheQ8BKYSAou5pxSALPVKcE5Qfv1WL0NmVuMZWVRKUB2MFa19ekyEzl+xVQ9tLpOGejsW\n2UtN4ZuU9uWvz1tLYJbbUuK1d7kyHGwUMpYOkCoBrsRLGcTSQjCcx07seXYbT5E81wNDN+VSD7rq\n+Wgs2KFs2aVvq80vyuf1vttC99eTxAVDhyH4q34OrMAViVPmP4QAAwtpA7Ph4Huquxm9oQSOYLXn\nCHOBZUg5OzIuZkOafYaGyXqXlcbKaKNSKl3OUTzo7IO6VR7CqjWMJRgj5i5dXRft+y+dZWBDvngR\nTF1dVX9kFbB45hqOt5FLuoJwy+R9BVl/2Wz4D/qIp3v6DAyn0yt3GBUjB6s9V1tlYMnrKvlJgXlQ\ntsS1nosBAznZ6i2iVXb3yx3LyLoPFBpI34wwYH6wFg7rYsaS0zmvdBxYS8LouVuIjVzI/d1/T+t8\nhOl1CNs6R8MkPtpxyNtC+2ozHy9hFuprw4/hK/gOEI9HcJx4IqMn2D8XsQlgR+hauYeQp1SRELbb\nmFlbFXWXzh1M3F85RMA6Me/CXzNVi6VcUpHOGJC22+CvCawbYDa0go7VyCQjpvgMTLcE6dCZkrSf\nCOxgYqXXTeVSknYq+1rfF74044uHcVGwVlk3OzWv5Ltp9of5EsD2P4K004ydnNlF3p6c/JhCPhLY\nQY2L5+V1fr5gsySb6IcgJfWAbQgjuT4N2N4uVqiOYo42Y9HJXs+ucphQvd7N7pErDAivBTY3apFN\nhk15i7Uxps4XCElrod1H7Ub+BXVtGn6qdtFmP1fNUMZDjkzsRd3Gcxqb1ws+sEEuAWyUReWlfx8v\nMnsc7s+zV/3uqCCxFX4xbsMIUG5Pj6E9CKybkfwmRxgrSxjy18TjKwqJWjKIbbf9OfhB5BRjmSqi\nO8MdUf6ajh82LkRhK5g/0pguLd33zs01zti4SR2yDQBbPBdnSfv0nlgP5oGBPtCFTt5awtSRT3ZP\n+33Jw1MN0J7xqUJqpZ0BGgTWZV1O5ngGUWEKWLyD/is8hu2oa3u4kH0DQILb1E8Xwesc+Yyn1FAr\nUGpMGjJ9abwcIr8eoEihsj/lapjRAHVe+0BpEutWy+J6FW/fc/1c4wMAlYb/jIxKWNluBnBUdunZ\n/PSlgVggkE7udK0CItyCdTiQxS1BD1N1tXzDTrnAIxC1RzG/BDid3fa7MVy+ikxGOXlWRf0Y/nIH\nKHpmLbnT2DBtpDtOY7icD7/kKg85Y1Ufuya7hCENEOmUjMwFSXsKb9UeciUHRkiYj6ZtQPScADtY\nnbZXHyPtRYaUL26NEdd8GazEGNEGDHXBiDu+/tDc2sU1TP5EMkeoiutM65mIrwPvA9jxxIs8QXAs\nCOafwIFt3V/Q4g17bHd+hFlpi09zd4EqqIoNosmNgimNxfmF4QsNPhRYvNRyW0VoW/ao4iSW/66u\ndI0CXo23n/zXrWj9qoxVG4wh+pS7mfVG3l3pBpUsKcdJYyJVdlVFxFgr/KO38rDmkDy0GzZScg4x\n1gssNGbLn23Mlwr2voKEfnaHvCWSmz3hs3qIgq2IG5NvEP0sYN1eqH+/mTM1KrGLjH2XN3O4Jsmb\no81HS+7YZJ5YAnMbS2L1covq/W1HPtlComwmNV7bYzmjNdHwgyxHHbfR5XAD7F9ZSDsA4rbEDYNm\nwK5tMDXBzdpjxUkWi143vO+HlTTT5gPztu/lGcTqWPAIP+tBQHdCunaB7TolDwF7uJwXhQOumWZY\nPtQX0DW0/E8C296g89hexeGjwJ4HpDH/dDnZPgf7kkbeATcyD1Fp88DyLSWHacBqRjt9M08YuHjK\nA+JdTb67NFAPI39XubKwCwQMkFXGFngQWAfAinVGbxSgHdXtYR9rhVB7Pl13VXqsHWVnFIgkMCwf\nqE4zjVw8E9iyyb0eqPmzgFWYnGeILuTYLlnm/NuRgzwfBmxuoMMzL1HVr24WW8JCV2rcyQk4Cmm1\nwglrDK4a/kqkHT/c0ehBRryp4Nt5puTAe3NbMGPXr2c5hCgY6aBi7IEAvQKMdfHPC+FTY7CT57k+\nnsyzH/E5SY9LbH2LNJufac+rgR0c0GatOnpihR3ohwthC+TEcHwsaQ+c8zqsFTgy9rTj+yOxOgDs\nYAeZb9mCIvhJ2cajlPnQKA320wHMgR3sFHhy4HjgUm76lXSdXhp8eY+MAnawj00FjV0h0ToaGuxW\nL7myWTqo1qsPeME1kou13HyZ7yY48LDUr9W7vWPolCV/irGFafjglJjNg616bglLdU22ctJNkoMj\nnuOc/5S0Odt4rAxouYE6N0xJt45jHFO2OCBGJbA3U/sLum6prqGP7YqqtOinKOb5NwTWA4rtJ45V\nOcZW+f46bhRvXNhZ0Dl0cPjviJE2GiYVM3XOS+IzAYs5hdRB1QzQZi5dPOwp+7BwwOd14JyX8neH\neAYlqHE543f4NGATUSk9zj4yXsbsFbbAmkIUpLMvy3g7h34dLn/oQ2k1CB/TiJB8/7WMu1Ndxpr7\nJ884dueQH0E4fsuO7Svwp3UPWLWtYo5vUg/59ryIdVY+d5LJ9Ju6zBC3cdZlY6k9k/Du5BXjj3rD\nSdnLiKtZ23AMkq22X+1u8nU8XgVE/6p4ilnpxviFL42sDW6Qyuum2cBuEWhfR5ILW1G76o7ckuTk\nks4XJ1RYwrVSxbatBVTVAysCf61hNkmKf+tKRKWJnpMqsI2Tlz/cTvViNv2cRlwsYSRdsRWBX59o\nWYRBZ66iUiZqtHSyf13Y3StMwVtck77Q/ZBsLN3kdfSLvLi+rNZ3YBWY7jb426gzUTCTH7ob6S2x\niaJlRE4aXlxCWr5FJF/Ixu6yGjFlf6y61E7jXGUIG2H8ZteU6lacUWtJ5SobBjdzhutAwFuprjC1\nMHJT7VWoeOZkMLlzSrGNUzmU337yACEIG8g7HQ1IwixXibRn8IxIeDjB4Puy1C/QgVd3u/taflyD\nTXbdjdw5vRVP7MbjIqyOcXUz3+/ovjjWDrnIrf6Kyav3YS/I6vZ1JLY/5/Rj3o4kjmWjklGqDecp\nKuRbXOBJua3KKV3grfJMy4i3ZabeTwWaLyLd5b53sCViXQFQlXPS1bjKLxmhDUpUIjF/88tWGjFW\naQIuY4WATeWkKzm7GcVoEhVRiBbamGHPTVhLMN278ZeQlqsAYy1LrQCxp7+XesA2lLR0ifRCS4Ol\nfjKDk935E41HNQE7gycJFlIVIlnqlsNZNnyZwtUKxshmJeexsNI17EswR1xC1KlkLCQTVkfvAgec\n8xIiLlA+mX4rLWAVaV1twZKTBfIlt8KiYYN6yCq/N7GZ6/oX+b0JK6LdlVdjbF0WRaKVh4bHFHqs\nEpJq6Eek93mKRnRKuMopbS79wqai2MazEWahjZptg/H7L0hFpmLBhFOym2+tS2B1UoBPzIEhZxZW\nW/0sFxOGLU6rmQQ38z1QFyj3u4rR5xSREmKzfPAX5FG2+mYCfYatumbu9StC1hXVbsOjBKyDtX95\nOhs0qLKNpVKITSLa0MqlkSsfZhYAtQRXYVjwRcm09sR4LdW6ZRml4FIHCcfj7U/NVDULqUmM+Q7A\najxtgRW0Mqz7ys4rHNzY8HVI22BOX1I+yy/zBb7nFeHTBTBK6f69xrFSDbYQShub9A2eTLO9DVWy\nZbXUz0ttfEoocJTsoqCu33dRcPq7fgld+e8FkBJDFramIt71ZUgG3uv3zkpMUhGMDNuYt52tu9d+\njJKZicESjqoc22GPFU+g43n3pYdieFpRm4CGh7v0GloBTAEuVph9IrGg+Ckw7LYqclGlxCsybeFv\nkOXeVfvF1Wd8y84tqbMkT/ROLdwA1PIESnlpI8We7SpJGL0a2ky+I+oWJl9o75s8vKMWwgCnBemB\njWpvc9yzIqvLqUr5LwZC6LG3+L4Cfk7+qsSSABeG7irg8ryzueyFZkOUS99oO6wjbBzJuytIM/f8\nI7AtcSrXbJb2Bbqxgzy8RLqcgXnVLYbW1EcKlNaVtmi9tDtNFJBfyZVRayxSVKsOtNAt2Q2Fpcds\n4FhoT1HdLmnH++ieQTgjwV4aPl60Hg4W2Fe/5meEyTF/TWKvHsl7YEQe78XUcaQ/N3wYXTEFTJ2l\nPVVjKpwHw2HGWlH5uhpLXbNnaT8lHODbuN17pKipQspKxIqCV+jz58NG23gOZD8mKktdxeI9m/dP\nYewZ0p6s+lhRfwSwWHAdE5KQsQdQ7eaK3m5XKzhgIXldgGPocHb1MJ6FGzCF6uqeTHQtda8L0i1l\nLQZs1FKGHZ3R/ksm8ea7qSlRPv4FXEvdQs/qhLWeOXbLGjl1auZzAhxPrruwF3jv1mgHCSIxmoP0\neIbPZ2yvj8WjGprxE19MZV2B1PEadHsnTeI6vWtXr/9mp2Y+HdUS6gaGzT10k3oNtnpWQmyQGK66\n5Gr+xxwWmSBjrG1z0qrxBgdDPASsymgdOrfvLEvbriH2FlmSq5j6dhSq71UbNFbeY2Aa0GesN7mI\nxFGW6G+PZioUu/FSPy4D0Yfn/YGoVeRqgRRu2fJQZnk44ttunLqvVbwd1QMu3LvgP6Vcq2Uy8nl9\nwcA9gs8UICy8E6bCxcj8dm4+c32rG3jWsmFDpcSWA0qpgHVrBLBoW8l+jSg4LI27BUoMMUc3xnIk\n8dCSijc1NWBl4dj+YYHdf72Rt7d+m6zIBMVt9nl9heWAUr93RfTNOTDoaPIycXswu14hbFGIFpKy\nFq56UXYV8u80VnhGmE/H1q29SNpOxpt2rIvN2z3j0mIOZnYt5Alu41+g+9zwkUvaS4IrChbrBfs8\n8I9Z7RCgHhwD9vAe/rZhw7i5xfxlFa3Lg9LH5Jvbb4MXuZKE5DMjTqVek/zax/rifhKlTKDtJ3ou\nWxFe9VwrKjGIV//mLYQa6ZY82KSSXmVXDdDtkTH/ByrXy2U=\n', (115, 260), None)""" +## re.sub(SQSTRING_RGX, '', crash_str) +## re.sub(TQSTRING_RGX, '', crash_str) +## re.sub(SASTRING_RGX, '', crash_str) +## re.sub(TASTRING_RGX, '', crash_str) + + +if linesep != '\n': + LINE_RGX = re.compile(linesep) + def ulines(strings): + return strings[0], LINE_RGX.sub('\n', strings[1]) +else: + def ulines(strings): + return strings + +class ChecklineFunctionTest(unittest.TestCase): + """test the check_line method""" + + def test_known_values_opspace_1(self): + self.assertEqual(ulines(check_line('a=1', REPORTER)), ('C0322', 'a=1\n ^')) + + def test_known_values_opspace_2(self): + self.assertEqual(ulines(check_line('a= 1', REPORTER)), ('C0322', 'a= 1\n ^') ) + + def test_known_values_opspace_3(self): + self.assertEqual(ulines(check_line('a =1', REPORTER)), ('C0323', 'a =1\n ^')) + + def test_known_values_opspace_4(self): + self.assertEqual(check_line('f(a=1)', REPORTER), None) + + def test_known_values_opspace_4(self): + self.assertEqual(check_line('f(a=1)', REPORTER), None) + + +## def test_known_values_colonnl_1(self): +## self.assertEqual(check_line('if a: a = 1', REPORTER), +## ('W0321', 'if a: a = 1\n ^^^^^^^')) + +## def test_known_values_colonnl_2(self): +## self.assertEqual(check_line('a[:1]', REPORTER), None) + +## def test_known_values_colonnl_3(self): +## self.assertEqual(check_line('a[1:]', REPORTER), None) + +## def test_known_values_colonnl_4(self): +## self.assertEqual(check_line('a[1:2]', REPORTER), None) + +## def test_known_values_colonnl_5(self): +## self.assertEqual(check_line('def intersection(list1, list2):', REPORTER), None) + +## def test_known_values_colonnl_6(self): +## self.assertEqual(check_line('def intersection(list1, list2):\n', REPORTER), None) + +## def test_known_values_colonnl_7(self): +## self.assertEqual(check_line('if file[:pfx_len] == path:\n', REPORTER), None) + +## def test_known_values_colonnl_8(self): +## self.assertEqual(check_line('def intersection(list1, list2): pass\n', REPORTER), +## ('W0321', +## 'def intersection(list1, list2): pass\n ^^^^^^') ) + +## def test_known_values_colonnl_9(self): +## self.assertEqual(check_line('if file[:pfx_len[1]] == path:\n', REPORTER), None) + +## def test_known_values_colonnl_10(self): +## self.assertEqual(check_line('if file[pfx_len[1]] == path:\n', REPORTER), None) + + + def test_known_values_commaspace_1(self): + self.assertEqual(ulines(check_line('a, b = 1,2', REPORTER)), + ('C0324', 'a, b = 1,2\n ^^')) + + + def test_known_values_instring_1(self): + self.assertEqual(check_line('f("a=1")', REPORTER), None) + + def test_known_values_instring_2(self): + self.assertEqual(ulines(check_line('print >>1, ("a:1")', REPORTER)), + ('C0323', 'print >>1, ("a:1")\n ^')) + + def test_known_values_all_1(self): + self.assertEqual(ulines(check_line("self.filterFunc = eval('lambda %s: %s'%(','.join(variables),formula),{},{})", REPORTER)), + ('C0324', "self.filterFunc = eval('lambda %s: %s'%(','.join(variables),formula),{},{})\n ^^")) + + def test_known_values_tqstring(self): + self.assertEqual(check_line('print """<a="=")', REPORTER), None) + + def test_known_values_tastring(self): + self.assertEqual(check_line("print '''<a='=')", REPORTER), None) + +if __name__ == '__main__': + unittest.main() diff --git a/test/test_import_graph.py b/test/test_import_graph.py new file mode 100644 index 0000000..dd6446e --- /dev/null +++ b/test/test_import_graph.py @@ -0,0 +1,63 @@ +import sys +import os +import unittest +from os.path import exists +from cStringIO import StringIO + +from pylint.checkers import initialize, imports +from pylint.lint import PyLinter + +from utils import TestReporter + +class DependenciesGraphTC(unittest.TestCase): + """test the imports graph function""" + + dest = 'dependencies_graph.dot' + def tearDown(self): + os.remove(self.dest) + + def test_dependencies_graph(self): + imports.dependencies_graph(self.dest, {'labas': ['hoho', 'yep'], + 'hoho': ['yep']}) + self.assertEquals(open(self.dest).read().strip(), + ''' +digraph g { +rankdir="LR" URL="." concentrate=false +edge[fontsize="10" ] +node[width="0" height="0" fontsize="12" fontcolor="black"] +"hoho" [ label="hoho" ]; +"yep" [ label="yep" ]; +"labas" [ label="labas" ]; +"yep" -> "hoho" [ ] ; +"hoho" -> "labas" [ ] ; +"yep" -> "labas" [ ] ; +} +'''.strip()) + +class ImportCheckerTC(unittest.TestCase): + def setUp(self): + self.linter = l = PyLinter(reporter=TestReporter()) + initialize(l) + l.disable_all_checkers() + + def test_checker_dep_graphs(self): + l = self.linter + l.global_set_option('persistent', False) + l.global_set_option('enable-imports', True) + l.global_set_option('import-graph', 'import.dot') + l.global_set_option('ext-import-graph', 'ext_import.dot') + l.global_set_option('int-import-graph', 'int_import.dot') + try: + l.check('input') + self.assert_(exists('import.dot')) + self.assert_(exists('ext_import.dot')) + self.assert_(exists('int_import.dot')) + finally: + for fname in ('import.dot', 'ext_import.dot', 'int_import.dot'): + try: + os.remove(fname) + except: + pass + +if __name__ == '__main__': + unittest.main() diff --git a/test/test_similar.py b/test/test_similar.py new file mode 100644 index 0000000..457fafa --- /dev/null +++ b/test/test_similar.py @@ -0,0 +1,56 @@ +import sys +import unittest +from cStringIO import StringIO + +from pylint.checkers import similar + + +class SimilarTC(unittest.TestCase): + """test the similar command line utility""" + def test(self): + sys.stdout = StringIO() + try: + similar.run(['--ignore-comments', 'input/similar1', 'input/similar2']) + output = sys.stdout.getvalue() + finally: + sys.stdout = sys.__stdout__ + self.assertEquals(output.strip(), """ +7 similar lines in 2 files +==input/similar1:5 +==input/similar2:5 + same file as this one. + more than 4 + identical lines should + be + detected + + +TOTAL lines=38 duplicates=7 percent=0.184210526316 +""".strip()) + + def test_help(self): + sys.stdout = StringIO() + try: + try: + similar.run(['--help']) + except SystemExit, ex: + self.assertEquals(ex.code, 0) + else: + self.fail() + finally: + sys.stdout = sys.__stdout__ + + def test_no_args(self): + sys.stdout = StringIO() + try: + try: + similar.run([]) + except SystemExit, ex: + self.assertEquals(ex.code, 1) + else: + self.fail() + finally: + sys.stdout = sys.__stdout__ + +if __name__ == '__main__': + unittest.main() diff --git a/test/unittest_checkers_utils.py b/test/unittest_checkers_utils.py new file mode 100644 index 0000000..380b4cd --- /dev/null +++ b/test/unittest_checkers_utils.py @@ -0,0 +1,49 @@ +# Copyright (c) 2003-2005 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., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +"""test the pylint.checkers.utils module +""" + +__revision__ = '$Id: unittest_checkers_utils.py,v 1.6 2005-11-02 09:22:07 syt Exp $' + +import unittest +import sys + +from pylint.checkers import utils +try: + __builtins__.mybuiltin = 2 +except AttributeError: + __builtins__['mybuiltin'] = 2 + +class UtilsTC(unittest.TestCase): + +## def test_is_native_builtin(self): +## self.assertEquals(utils.is_native_builtin('min'), True) +## self.assertEquals(utils.is_native_builtin('__path__'), True) +## self.assertEquals(utils.is_native_builtin('__file__'), True) +## self.assertEquals(utils.is_native_builtin('whatever'), False) +## self.assertEquals(utils.is_native_builtin('mybuiltin'), False) + + def test_is_builtin(self): + self.assertEquals(utils.is_builtin('min'), True) + self.assertEquals(utils.is_builtin('__builtins__'), True) + self.assertEquals(utils.is_builtin('__path__'), False) + self.assertEquals(utils.is_builtin('__file__'), False) + self.assertEquals(utils.is_builtin('whatever'), False) + self.assertEquals(utils.is_builtin('mybuiltin'), False) + +if __name__ == '__main__': + unittest.main() + diff --git a/test/unittest_lint.py b/test/unittest_lint.py new file mode 100644 index 0000000..a71edee --- /dev/null +++ b/test/unittest_lint.py @@ -0,0 +1,268 @@ +# 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., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +""" Copyright (c) 2003-2005 LOGILAB S.A. (Paris, FRANCE). + http://www.logilab.fr/ -- mailto:contact@logilab.fr +""" + +__revision__ = '$Id: unittest_lint.py,v 1.16 2006-04-19 09:17:40 syt Exp $' + +import unittest +import sys +import os +import tempfile +from os.path import join +from cStringIO import StringIO + +from pylint.config import get_note_message +from pylint.lint import PyLinter, Run, sort_checkers, UnknownMessage +from pylint.utils import sort_msgs +from pylint import checkers + +class SortMessagesTC(unittest.TestCase): + + def test(self): + l = ['E0501', 'E0503', 'F0002', 'I0201', 'W0540', + 'R0202', 'F0203', 'R0220', 'W0321', 'I0001'] + self.assertEquals(sort_msgs(l), ['I0001', 'I0201', + 'R0202', 'R0220', + 'W0321', 'W0540', + 'E0501', 'E0503', + 'F0002', 'F0203']) + +try: + optimized = True + raise AssertionError +except AssertionError: + optimized = False + +class GetNoteMessageTC(unittest.TestCase): + def test(self): + msg = None + for note in range(-1, 11): + note_msg = get_note_message(note) + self.assertNotEquals(msg, note_msg) + msg = note_msg + if optimized: + self.assertRaises(AssertionError, get_note_message, 11) + +class RunTC(unittest.TestCase): + + def _test_run(self, args, exit_code=1, no_exit_fail=True): + sys.stdout = StringIO() + sys.sterr = StringIO() + try: + try: + Run(args, quiet=1) + except SystemExit, ex: + self.assertEquals(ex.code, exit_code) + else: + if no_exit_fail: + self.fail() + finally: + sys.stdout = sys.__stdout__ + sys.stderr = sys.__stderr__ + + def test_no_args(self): + self._test_run([], 1) + + def test_no_ext_file(self): + self._test_run([join('input', 'noext')], no_exit_fail=False) + + +class PyLinterTC(unittest.TestCase): + + def setUp(self): + self.linter = PyLinter() + self.linter.disable_message_category('I') + self.linter.config.persistent = 0 + # register checkers + checkers.initialize(self.linter) + + def test_disable_all(self): + self.linter.disable_all_checkers() + checkers = sort_checkers(self.linter._checkers, enabled_only=0) + self.assert_(len(checkers) > 1) + checkers = sort_checkers(self.linter._checkers, enabled_only=1) + self.assertEquals(checkers, [self.linter]) + + def test_message_help(self): + msg = self.linter.get_message_help('F0001') + expected = 'F0001:\n Used when an error occured preventing the analyzing of a module (unable to\n find it for instance). This message belongs to the master checker.' + self.assertEquals(' '.join(msg.splitlines()), ' '.join(expected.splitlines())) + self.assertRaises(UnknownMessage, self.linter.get_message_help, 'YB12') + + def test_enable_message(self): + linter = self.linter + linter.open() + linter.set_current_module('toto') + self.assert_(linter.is_message_enabled('W0101')) + self.assert_(linter.is_message_enabled('W0102')) + linter.disable_message('W0101', scope='package') + linter.disable_message('W0102', scope='module', line=1) + self.assert_(not linter.is_message_enabled('W0101')) + self.assert_(not linter.is_message_enabled('W0102', 1)) + linter.set_current_module('tutu') + self.assert_(not linter.is_message_enabled('W0101')) + self.assert_(linter.is_message_enabled('W0102')) + linter.enable_message('W0101', scope='package') + linter.enable_message('W0102', scope='module', line=1) + self.assert_(linter.is_message_enabled('W0101')) + self.assert_(linter.is_message_enabled('W0102', 1)) + + def test_enable_message_category(self): + linter = self.linter + linter.open() + linter.set_current_module('toto') + self.assert_(linter.is_message_enabled('W0101')) + self.assert_(linter.is_message_enabled('R0102')) + linter.disable_message_category('W', scope='package') + linter.disable_message_category('REFACTOR', scope='module') + self.assert_(not linter.is_message_enabled('W0101')) + self.assert_(not linter.is_message_enabled('R0102')) + linter.set_current_module('tutu') + self.assert_(not linter.is_message_enabled('W0101')) + self.assert_(linter.is_message_enabled('R0102')) + linter.enable_message_category('WARNING', scope='package') + linter.enable_message_category('R', scope='module') + self.assert_(linter.is_message_enabled('W0101')) + self.assert_(linter.is_message_enabled('R0102')) + + def test_enable_message_block(self): + linter = self.linter + linter.open() + filepath = join('input', 'func_block_disable_msg.py') + linter.set_current_module('func_block_disable_msg') + linter.process_module(open(filepath)) + orig_state = linter._module_msgs_state.copy() + linter._module_msgs_state = {} + linter.collect_block_lines(linter.get_astng(filepath, 'func_block_disable_msg'), orig_state) + # global (module level) + self.assert_(linter.is_message_enabled('W0613')) + self.assert_(linter.is_message_enabled('E1101')) + # meth1 + self.assert_(linter.is_message_enabled('W0613', 13)) + # meth2 + self.assert_(not linter.is_message_enabled('W0613', 18)) + # meth3 + self.assert_(not linter.is_message_enabled('E1101', 24)) + self.assert_(linter.is_message_enabled('E1101', 26)) + # meth4 + self.assert_(not linter.is_message_enabled('E1101', 32)) + self.assert_(linter.is_message_enabled('E1101', 36)) + # meth5 + self.assert_(not linter.is_message_enabled('E1101', 42)) + self.assert_(not linter.is_message_enabled('E1101', 43)) + self.assert_(linter.is_message_enabled('E1101', 46)) + self.assert_(not linter.is_message_enabled('E1101', 49)) + self.assert_(not linter.is_message_enabled('E1101', 51)) + # meth6 + self.assert_(not linter.is_message_enabled('E1101', 57)) + self.assert_(linter.is_message_enabled('E1101', 61)) + self.assert_(not linter.is_message_enabled('E1101', 64)) + self.assert_(not linter.is_message_enabled('E1101', 66)) + + self.assert_(linter.is_message_enabled('E0602', 57)) + self.assert_(linter.is_message_enabled('E0602', 61)) + self.assert_(not linter.is_message_enabled('E0602', 62)) + self.assert_(linter.is_message_enabled('E0602', 64)) + self.assert_(linter.is_message_enabled('E0602', 66)) + # meth7 + self.assert_(not linter.is_message_enabled('E1101', 70)) + self.assert_(linter.is_message_enabled('E1101', 72)) + self.assert_(linter.is_message_enabled('E1101', 75)) + self.assert_(linter.is_message_enabled('E1101', 77)) + + def test_list_messages(self): + sys.stdout = StringIO() + try: + # just invoke it, don't check the output + self.linter.list_messages() + finally: + sys.stdout = sys.__stdout__ + + def test_lint_ext_module_with_file_output(self): + self.linter.config.files_output = True + try: + self.linter.check('StringIO') + self.assert_(os.path.exists('pylint_StringIO.txt')) + self.assert_(os.path.exists('pylint_global.txt')) + finally: + try: + os.remove('pylint_StringIO.txt') + os.remove('pylint_global.txt') + except: + pass + + def test_enable_report(self): + self.assertEquals(self.linter.is_report_enabled('R0001'), True) + self.linter.disable_report('R0001') + self.assertEquals(self.linter.is_report_enabled('R0001'), False) + self.linter.enable_report('R0001') + self.assertEquals(self.linter.is_report_enabled('R0001'), True) + + def test_set_option_1(self): + linter = self.linter + linter.set_option('disable-msg', 'C0111,W0142') + self.assert_(not linter.is_message_enabled('C0111')) + self.assert_(not linter.is_message_enabled('W0142')) + self.assert_(linter.is_message_enabled('W0113')) + + def test_set_option_2(self): + linter = self.linter + linter.set_option('disable-msg', ('C0111', 'W0142') ) + self.assert_(not linter.is_message_enabled('C0111')) + self.assert_(not linter.is_message_enabled('W0142')) + self.assert_(linter.is_message_enabled('W0113')) + + +from pylint import config + +class ConfigTC(unittest.TestCase): + + def test_pylint_home(self): + uhome = os.path.expanduser('~') + if uhome == '~': + expected = '.pylint.d' + else: + expected = os.path.join(uhome, '.pylint.d') + self.assertEquals(config.PYLINT_HOME, expected) + + try: + pylintd = join(tempfile.gettempdir(), '.pylint.d') + os.environ['PYLINTHOME'] = pylintd + try: + reload(config) + self.assertEquals(config.PYLINT_HOME, pylintd) + finally: + try: + os.remove(pylintd) + except: + pass + finally: + del os.environ['PYLINTHOME'] + + def test_pylintrc(self): + try: + self.assertEquals(config.PYLINTRC, None) + os.environ['PYLINTRC'] = join(tempfile.gettempdir(), '.pylintrc') + reload(config) + self.assertEquals(config.PYLINTRC, None) + os.environ['PYLINTRC'] = '.' + reload(config) + self.assertEquals(config.PYLINTRC, None) + finally: + del os.environ['PYLINTRC'] + reload(config) + +if __name__ == '__main__': + unittest.main() diff --git a/test/utils.py b/test/utils.py new file mode 100644 index 0000000..06eb76e --- /dev/null +++ b/test/utils.py @@ -0,0 +1,66 @@ +"""some pylint test utilities +""" +from glob import glob +from os.path import join, abspath, dirname, basename +from cStringIO import StringIO + +from pylint.interfaces import IReporter +from pylint.reporters import BaseReporter + +PREFIX = abspath(dirname(__file__)) + +def fix_path(): + import sys + sys.path.insert(0, PREFIX) + +def get_tests_info(prefix=None, suffix=None): + pattern = '*' + if prefix: + pattern = prefix + pattern + if suffix: + pattern = pattern + suffix + result = [] + for file in glob(join(PREFIX, "input", pattern)): + infile = basename(file) + outfile = join(PREFIX, "messages", infile.replace(suffix, '.txt')) + result.append((infile, outfile)) + + return result + + +TITLE_UNDERLINES = ['', '=', '-', '.'] + +class TestReporter(BaseReporter): + """ store plain text messages + """ + + __implements____ = IReporter + + def __init__(self): + self.message_ids = {} + self.reset() + + def reset(self): + self.out = StringIO() + self.messages = [] + + def add_message(self, msg_id, location, msg): + """manage message of different type and in the context of path """ + fpath, module, object, line = location + self.message_ids[msg_id] = 1 + if object: + object = ':%s' % object + sigle = msg_id[0] + self.messages.append('%s:%3s%s: %s' % (sigle, line, object, msg)) + + def finalize(self): + self.messages.sort() + for msg in self.messages: + print >>self.out, msg + result = self.out.getvalue() + self.reset() + return result + + def display_results(self, layout): + """ignore layouts""" + diff --git a/utils.py b/utils.py new file mode 100644 index 0000000..5b39212 --- /dev/null +++ b/utils.py @@ -0,0 +1,353 @@ +# Copyright (c) 2003-2005 Sylvain Thenault (thenault@gmail.com). +# Copyright (c) 2003-2005 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., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +"""some various utilities and helper classes, most of them used in the +main pylint class +""" + +__revision__ = "$Id: utils.py,v 1.13 2006-04-19 09:17:40 syt Exp $" + +from os import linesep + +from logilab.astng import Module +from logilab.common.textutils import normalize_text +from logilab.common.ureports import Section + +from pylint.checkers import EmptyReport + +class UnknownMessage(Exception): + """raised when a unregistered message id is encountered""" + + +MSG_TYPES = { + 'I' : 'info', + 'C' : 'convention', + 'R' : 'refactor', + 'W' : 'warning', + 'E' : 'error', + 'F' : 'fatal' + } +MSG_CATEGORIES = MSG_TYPES.keys() + + +def sort_checkers(checkers, enabled_only=True): + """return a list of enabled checker sorted by priority""" + if enabled_only: + checkers = [(-checker.priority, checker) for checker in checkers + if checker.is_enabled()] + else: + checkers = [(-checker.priority, checker) for checker in checkers] + checkers.sort() + return [item[1] for item in checkers] + +def sort_msgs(msg_ids): + """sort message identifiers according to their category first""" + msg_order = ['I', 'C', 'R', 'W', 'E', 'F'] + def cmp_func(msgid1, msgid2): + """comparison function for two message identifiers""" + if msgid1[0] != msgid2[0]: + return cmp(msg_order.index(msgid1[0]), msg_order.index(msgid2[0])) + else: + return cmp(msgid1, msgid2) + msg_ids.sort(cmp_func) + return msg_ids + +def get_module_and_frameid(node): + """return the module name and the frame id in the module""" + frame = node.frame() + module, obj = '', [] + while frame: + if isinstance(frame, Module): + module = frame.name + else: + obj.append(getattr(frame, 'name', '<lambda>')) + try: + frame = frame.parent.frame() + except AttributeError: + frame = None + obj.reverse() + return module, '.'.join(obj) + + +class MessagesHandlerMixIn: + """a mix-in class containing all the messages related methods for the main + lint class + """ + + def __init__(self): + # dictionary of registered messages + self._messages = {} + self._messages_help = {} + self._msgs_state = {} + self._module_msgs_state = None + self._msg_cats_state = {} + self._module_msg_cats_state = None + + def register_messages(self, checker): + """register a dictionary of messages + + Keys are message ids, values are a 2-uple with the message type and the + message itself + + message ids should be a string of len 4, where the to first characters + are the checker id and the two last the message id in this checker + """ + msgs_dict = checker.msgs + chk_id = None + for msg_id, (msg, msg_help) in msgs_dict.items(): + # avoid duplicate / malformed ids + assert not self._messages.has_key(msg_id), \ + 'Message id %r is already defined' % msg_id + assert len(msg_id) == 5, 'Invalid message id %s' % msg_id + assert chk_id is None or chk_id == msg_id[1:3], \ + 'Inconsistent checker part in message id %r' %msg_id + assert msg_id[0] in MSG_CATEGORIES, \ + 'Bad message type %s in %r' % (msg_id[0], msg_id) + chk_id = msg_id[1:3] + if checker is not None: + add = ' This message belongs to the %s checker.' % checker.name + msg_help += add + self._messages_help[msg_id] = msg_help + self._messages[msg_id] = msg + + def get_message_help(self, msg_id): + """return the help string for the given message id""" + msg_id = self.check_message_id(msg_id) + msg = self._messages_help[msg_id] + msg = normalize_text(' '.join(msg.split()), indent=' ') + return '%s:\n%s' % (msg_id, msg) + + def disable_message(self, msg_id, scope='package', line=None): + """don't output message of the given id""" + assert scope in ('package', 'module') + 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} + + else: + msgs = self._msgs_state + msgs[msg_id] = False + # sync configuration object + self.config.disable_msg = [mid for mid, val in msgs.items() + if not val] + + def enable_message(self, msg_id, scope='package', line=None): + """reenable message of the given id""" + assert scope in ('package', 'module') + 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} + else: + msgs = self._msgs_state + msgs[msg_id] = True + # sync configuration object + self.config.enable_msg = [mid for mid, val in msgs.items() if val] + + def disable_message_category(self, msg_cat_id, scope='package', line=None): + """don't output message in the given category""" + assert scope in ('package', 'module') + msg_cat_id = msg_cat_id[0].upper() + if scope == 'module': + self.add_message('I0011', line=line, args=msg_cat_id) + self._module_msg_cats_state[msg_cat_id] = False + else: + self._msg_cats_state[msg_cat_id] = False + + def enable_message_category(self, msg_cat_id, scope='package', line=None): + """reenable message of the given category""" + assert scope in ('package', 'module') + msg_cat_id = msg_cat_id[0].upper() + if scope == 'module': + self.add_message('I0012', line=line, args=msg_cat_id) + self._module_msg_cats_state[msg_cat_id] = True + else: + self._msg_cats_state[msg_cat_id] = True + + def check_message_id(self, msg_id): + """raise UnknownMessage if the message id is not defined""" + msg_id = msg_id.upper() + if not self._messages.has_key(msg_id): + raise UnknownMessage('No such message id %s' % msg_id) + return msg_id + + def is_message_enabled(self, msg_id, line=None): + """return true if the message associated to the given message id is + enabled + """ + try: + if not self._module_msg_cats_state[msg_id[0]]: + return False + except (KeyError, TypeError): + if not self._msg_cats_state.get(msg_id[0], True): + return False + if line is None: + return self._msgs_state.get(msg_id, True) + try: + return self._module_msgs_state[msg_id][line] + except (KeyError, TypeError): + return self._msgs_state.get(msg_id, True) + + def add_message(self, msg_id, line=None, node=None, args=None): + """add the message corresponding to the given id. + + If provided, msg is expanded using args + + astng checkers should provide the node argument, raw checkers should + provide the line argument. + """ + if line is None and node is not None: + line = node.lineno or node.statement().lineno + #if not isinstance(node, Module): + # assert line > 0, node.__class__ + # should this message be displayed + if not self.is_message_enabled(msg_id, line): + return + # update stats + msg_cat = MSG_TYPES[msg_id[0]] + self.stats[msg_cat] += 1 + self.stats['by_module'][self.current_name][msg_cat] += 1 + try: + self.stats['by_msg'][msg_id] += 1 + except KeyError: + self.stats['by_msg'][msg_id] = 1 + msg = self._messages[msg_id] + # expand message ? + if args: + msg %= args + # get module and object + if node is None: + module, obj = self.current_name, '' + path = self.current_file + else: + module, obj = get_module_and_frameid(node) + path = node.root().file + # add the message + self.reporter.add_message(msg_id, (path, module, obj, line or 0), msg) + + def help_message(self, msgids): + """display help messages for the given message identifiers""" + for msg_id in msgids: + try: + print self.get_message_help(msg_id) + print + except UnknownMessage, ex: + print ex + print + continue + + def list_messages(self): + """list available messages""" + for checker in sort_checkers(self._checkers.keys()): + print checker.name.capitalize() + print '-' * len(checker.name) + print + if checker.__doc__: # __doc__ is None with -OO + print 'Description' + print '~~~~~~~~~~~' + print linesep.join([line.strip() + for line in checker.__doc__.splitlines()]) + print + if not checker.msgs: + continue + print 'Messages' + print '~~~~~~~~' + for msg_id in sort_msgs(checker.msgs.keys()): + print self.get_message_help(msg_id) + print + print + print + + +class ReportsHandlerMixIn: + """a mix-in class containing all the reports and stats manipulation + related methods for the main lint class + """ + def __init__(self): + self._reports = {} + self._reports_state = {} + + def register_report(self, r_id, r_title, r_cb, checker): + """register a report + + r_id is the unique identifier for the report + r_title the report's title + r_cb the method to call to make the report + checker is the checker defining the report + """ + r_id = r_id.upper() + self._reports.setdefault(checker, []).append( (r_id, r_title, r_cb) ) + + def enable_report(self, r_id): + """disable the report of the given id""" + r_id = r_id.upper() + self._reports_state[r_id] = True + + def disable_report(self, r_id): + """disable the report of the given id""" + r_id = r_id.upper() + self._reports_state[r_id] = False + + def is_report_enabled(self, r_id): + """return true if the report associated to the given identifier is + enabled + """ + return self._reports_state.get(r_id, True) + + def make_reports(self, stats, old_stats): + """render registered reports""" + if self.config.files_output: + filename = 'pylint_global.' + self.reporter.extension + self.reporter.set_output(open(filename, 'w')) + sect = Section('Report', + '%s statements analysed.'% (self.stats['statement'])) + checkers = sort_checkers(self._reports.keys()) + checkers.reverse() + for checker in checkers: + for r_id, r_title, r_cb in self._reports[checker]: + if not self.is_report_enabled(r_id): + continue + report_sect = Section(r_title) + try: + r_cb(report_sect, stats, old_stats) + except EmptyReport: + continue + report_sect.report_id = r_id + sect.append(report_sect) + self.reporter.display_results(sect) + + def add_stats(self, **kwargs): + """add some stats entries to the statistic dictionary + raise an AssertionError if there is a key conflict + """ + for key, value in kwargs.items(): + if key[-1] == '_': + key = key[:-1] + assert not self.stats.has_key(key) + self.stats[key] = value + return self.stats + |