diff options
author | Claudiu Popa <cpopa@cloudbasesolutions.com> | 2015-02-17 01:09:04 +0200 |
---|---|---|
committer | Claudiu Popa <cpopa@cloudbasesolutions.com> | 2015-02-17 01:09:04 +0200 |
commit | d00adfbdc7b77280f6a7c97e39cb882b853da1d8 (patch) | |
tree | 26722500b372c31cda2f8e49701fe5735171684b | |
parent | fe36ec0486cfc9aba10d8d87b0f42077ad1e78db (diff) | |
parent | 1dc09ef472a1bc0ca312c7cd8a1a3c700b510b47 (diff) | |
download | pylint-d00adfbdc7b77280f6a7c97e39cb882b853da1d8.tar.gz |
The HTML output accepts the `--msg-template` option.
Patch by Daniel Goldsmith. Closes issue #135.
959 files changed, 28588 insertions, 19533 deletions
@@ -10,3 +10,11 @@ ^pylint.egg-info/ .tox (^|/)\..*\.sw[a-z]$ +doc/features.rst +pyve +build-stamp +debian/files +debian/pylint\..*\.log +debian/pylint\..*\.debhelper +debian/pylint.substvars +debian/pylint$
\ No newline at end of file diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 8b28ae3..ed3560c 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -32,6 +32,24 @@ Order doesn't matter (not that much, at least ;) * Nathaniel Manista: suspicious lambda checking +* David Shea: invalid sequence and slice index + +* Carl Crowder: don't evaluate the value of arguments for 'dangerous-default-value' + +* Michal Nowikowski: wrong-spelling-in-comment, wrong-spelling-in-docstring and + other patches. + +* David Lindquist: logging-format-interpolation warning. + +* Brett Cannon: Port source code to be Python 2/3 compatible, Python 3 + checker. + +* Vlad Temian: redundant-unittest-assert and the JSON reporter. + +* Cosmin Poieana: unichr-builtin and improvements to bad-open-mode. + +* Viorel Stirbu: intern-builtin warning. + * Wolfgang Grafen, Axel Muller, Fabio Zadrozny, Pierre Rouleau, Maarten ter Huurne, Mirko Friedenhagen and all the Logilab's team (among others): bug reports, feedback, feature requests... Many other people have contributed @@ -2,6 +2,401 @@ ChangeLog for Pylint ==================== -- + * Don't require a docstring for empty modules. Closes issue #261. + + * Fix a false positive with `too-few-format-args` string warning, + emitted when the string format contained a normal positional + argument ('{0}'), mixed with a positional argument which did + an attribute access ('{0.__class__}'). + Closes issue #463. + + * Take in account all the methods from the ancestors + when checking for too-few-public-methods. Closes issue #471. + + * Catch enchant errors and emit 'invalid-characters-in-docstring' + when checking for spelling errors. Closes issue #469. + + * Use all the inferred statements for the super-init-not-called + check. Closes issue #389. + + * Add a new warning, 'unichr-builtin', emitted by the Python 3 + porting checker, when the unichr builtin is found. Closes issue #472. + + * Add a new warning, 'intern-builtin', emitted by the Python 3 + porting checker, when the intern builtin is found. Closes issue #473. + + * Add support for editable installations. + + * The HTML output accepts the `--msg-template` option. Patch by + Dan Goldsmith. + + +2015-01-16 -- 1.4.1 + + * Look only in the current function's scope for bad-super-call. + Closes issue #403. + + * Check the return of properties when checking for not-callable. + Closes issue #406. + + * Warn about using the input() or round() built-ins for Python 3. + Closes issue #411. + + * Proper abstract method lookup while checking for + abstract-class-instantiated. Closes issue #401. + + * Use a mro traversal for finding abstract methods. Closes issue #415. + + * Fix a false positive with catching-non-exception and tuples of + exceptions. + + * Fix a false negative with raising-non-exception, when the raise used + an uninferrable exception context. + + * Fix a false positive on Python 2 for raising-bad-type, when + raising tuples in the form 'raise (ZeroDivisionError, None)'. + + * Fix a false positive with invalid-slots-objects, where the slot entry + was an unicode string on Python 2. Closes issue #421. + + * Add a new warning, 'redundant-unittest-assert', emitted when using + unittest's methods assertTrue and assertFalse with constant value + as argument. Patch by Vlad Temian. + + * Add a new JSON reporter, usable through -f flag. + + * Add the method names for the 'signature-differs' and 'argument-differs' + warnings. Closes issue #433. + + * Don't compile test files when installing. + + * Fix a crash which occurred when using multiple jobs and the files + given as argument didn't exist at all. + + +2014-11-23 -- 1.4.0 + + * Added new options for controlling the loading of C extensions. + By default, only C extensions from the stdlib will be loaded + into the active Python interpreter for inspection, because they + can run arbitrary code on import. The option + `--extension-pkg-whitelist` can be used to specify modules + or packages that are safe to load. + + * Change default max-line-length to 100 rather than 80 + + * Drop BaseRawChecker class which were only there for backward + compat for a while now + + * Don't try to analyze string formatting with objects coming from + function arguments. Closes issue #373. + + * Port source code to be Python 2/3 compatible. This drops the + need for 2to3, but does drop support for Python 2.5. + + * Each message now comes with a confidence level attached, and + can be filtered base on this level. This allows to filter out + all messages that were emitted even though an inference failure + happened during checking. + + * Improved presenting unused-import message. Closes issue #293. + + * Add new checker for finding spelling errors. New messages: + wrong-spelling-in-comment, wrong-spelling-in-docstring. + New options: spelling-dict, spelling-ignore-words. + + * Add new '-j' option for running checks in sub-processes. + + * Added new checks for line endings if they are mixed (LF vs CRLF) + or if they are not as expected. New messages: mixed-line-endings, + unexpected-line-ending-format. New option: expected-line-ending-format. + + * 'dangerous-default-value' no longer evaluates the value of the arguments, + which could result in long error messages or sensitive data being leaked. + Closes issue #282 + + * Fix a false positive with string formatting checker, when + encountering a string which uses only position-based arguments. + Closes issue #285. + + * Fix a false positive with string formatting checker, when using + keyword argument packing. Closes issue #288. + + * Proper handle class level scope for lambdas. + + * Handle 'too-few-format-args' or 'too-many-format-args' for format + strings with both named and positional fields. Closes issue #286. + + * Analyze only strings by the string format checker. Closes issue #287. + + * Properly handle nested format string fields. Closes issue #294. + + * Don't emit 'attribute-defined-outside-init' if the attribute + was set by a function call in a defining method. Closes issue #192. + + * Properly handle unicode format strings for Python 2. + Closes issue #296. + + * Don't emit 'import-error' if an import was protected by a try-except, + which excepted ImportError. + + * Fix an 'unused-import' false positive, when the error was emitted + for all the members imported with 'from import' form. + Closes issue #304. + + * Don't emit 'invalid-name' when assigning a name in an + ImportError handler. Closes issue #302. + + * Don't count branches from nested functions. + + * Fix a false positive with 'too-few-format-args', when the format + strings contains duplicate manual position arguments. + Closes issue #310. + + * fixme regex handles comments without spaces after the hash. + Closes issue #311. + + * Don't emit 'unused-import' when a special object is imported + (__all__, __doc__ etc.). Closes issue #309. + + * Look in the metaclass, if defined, for members not found in the current + class. Closes issue #306. + + * Don't emit 'protected-access' if the attribute is accessed using + a property defined at the class level. + + * Detect calls of the parent's __init__, through a binded super() call. + + * Check that a class has an explicitly defined metaclass before + emitting 'old-style-class' for Python 2. + + * Emit 'catching-non-exception' for non-class nodes. Closes issue #303. + + * Order of reporting is consistent. + + * Add a new warning, 'boolean-datetime', emitted when an instance + of 'datetime.time' is used in a boolean context. Closes issue #239. + + * Fix a crash which ocurred while checking for 'method-hidden', + when the parent frame was something different than a function. + + * Generate html output for missing files. Closes issue #320. + + * Fix a false positive with 'too-many-format-args', when the format + string contains mixed attribute access arguments and manual + fields. Closes issue #322. + + * Extend the cases where 'undefined-variable' and 'used-before-assignment' + can be detected. Closes issue #291. + + * Add support for customising callback identifiers, by adding a new + '--callbacks' command line option. Closes issue #326. + + * Add a new warning, 'logging-format-interpolation', emitted when .format() + string interpolation is used within logging function calls. + + * Don't emit 'unbalanced-tuple-unpacking' when the rhs of the assignment + is a variable length argument. Closes issue #329. + + * Add a new warning, 'inherit-non-class', emitted when a class inherits + from something which is not a class. Closes issue #331. + + * Fix another false positives with 'undefined-variable', where the variable + can be found as a class assignment and used in a function annotation. + Closes issue #342. + + * Handle assignment of the string format method to a variable. + Closes issue #351. + + * Support wheel packaging format for PyPi. Closes issue #334. + + * Check that various built-ins that do not exist in Python 3 are not + used: apply, basestring, buffer, cmp, coerce, execfile, file, long + raw_input, reduce, StandardError, unicode, reload and xrange. + + * Warn for magic methods which are not used in any way in Python 3: + __coerce__, __delslice__, __getslice__, __setslice__, __cmp__, + __oct__, __nonzero__ and __hex__. + + * Don't emit 'assigning-non-slot' when the assignment is for a property. + Closes issue #359. + + * Fix for regression: '{path}' was no longer accepted in '--msg-template'. + + * Report the percentage of all messages, not just for errors and warnings. + Closes issue #319. + + * 'too-many-public-methods' is reported only for methods defined in a class, + not in its ancestors. Closes issue #248. + + * 'too-many-lines' disable pragma can be located on any line, not only the + first. Closes issue #321. + + * Warn in Python 2 when an import statement is found without a + corresponding `from __future__ import absolute_import`. + + * Warn in Python 2 when a non-floor division operation is found without + a corresponding `from __future__ import division`. + + * Add a new option, 'exclude-protected', for excluding members + from the protected-access warning. Closes issue #48. + + * Warn in Python 2 when using dict.iter*(), dict.view*(); none of these + methods are available in Python 3. + + * Warn in Python 2 when calling an object's next() method; Python 3 uses + __next__() instead. + + * Warn when assigning to __metaclass__ at a class scope; in Python 3 a + metaclass is specified as an argument to the 'class' statement. + + * Warn when performing parameter tuple unpacking; it is not supported in + Python 3. + + * 'abstract-class-instantiated' is also emitted for Python 2. + It was previously disabled. + + * Add 'long-suffix' error, emitted when encountering the long suffix + on numbers. + + * Add support for disabling a checker, by specifying an 'enabled' + attribute on the checker class. + + * Add a new CLI option, --py3k, for enabling Python 3 porting mode. This + mode will disable all other checkers and will emit warnings and + errors for constructs which are invalid or removed in Python 3. + + * Add 'old-octal-literal' to Python 3 porting checker, emitted when + encountering octals with the old syntax. + + * Add 'implicit-map-evaluation' to Python 3 porting checker, emitted + when encountering the use of map builtin, without explicit evaluation. + + + +2014-07-26 -- 1.3.0 + + * Allow hanging continued indentation for implicitly concatenated + strings. Closes issue #232. + + * Pylint works under Python 2.5 again, and its test suite passes. + + * Fix some false positives for the cellvar-from-loop warnings. + Closes issue #233. + + * Return new astroid class nodes when the inferencer can detect that + that result of a function invocation on a type (like `type` or + `abc.ABCMeta`) is requested. Closes #205. + + * Emit 'undefined-variable' for undefined names when using the + Python 3 `metaclass=` argument. + + * Checkers respect priority now. Close issue #229. + + * Fix a false positive regarding W0511. Closes issue #149. + + * Fix unused-import false positive with Python 3 metaclasses (#143). + + * Don't warn with 'bad-format-character' when encountering + the 'a' format on Python 3. + + * Add multiple checks for PEP 3101 advanced string formatting: + 'bad-format-string', 'missing-format-argument-key', + 'unused-format-string-argument', 'format-combined-specification', + 'missing-format-attribute' and 'invalid-format-index'. + + * Issue broad-except and bare-except even if the number + of except handlers is different than 1. Fixes issue #113. + + * Issue attribute-defined-outside-init for all cases, not just + for the last assignment. Closes issue #262. + + * Emit 'not-callable' when calling properties. Closes issue #268. + + * Fix a false positive with unbalanced iterable unpacking, + when encountering starred nodes. Closes issue #273. + + * Add new checks, 'invalid-slice-index' and 'invalid-sequence-index' + for invalid sequence and slice indices. + + * Add 'assigning-non-slot' warning, which detects assignments to + attributes not defined in slots. + + * Don't emit 'no-name-in-module' for ignored modules. + Closes issue #223. + + * Fix an 'unused-variable' false positive, where the variable is + assigned through an import. Closes issue #196. + + * Definition order is considered for classes, function arguments + and annotations. Closes issue #257. + + * Don't emit 'unused-variable' when assigning to a nonlocal. + Closes issue #275. + + * Do not let ImportError propagate from the import checker, leading to crash + in some namespace package related cases. Closes issue #203. + + * Don't emit 'pointless-string-statement' for attribute docstrings. + Closes issue #193. + + * Use the proper mode for pickle when opening and writing the stats file. + Closes issue #148. + + * Don't emit hidden-method message when the attribute has been + monkey-patched, you're on your own when you do that. + + * Only emit attribute-defined-outside-init for definition within the same + module as the offended class, avoiding to mangle the output in some cases. + + * Don't emit 'unnecessary-lambda' if the body of the lambda call contains + call chaining. Closes issue #243. + + * Don't emit 'missing-docstring' when the actual docstring uses `.format`. + Closes issue #281. + + +2014-04-30 -- 1.2.1 + * Restore the ability to specify the init-hook option via the + configuration file, which was accidentally broken in 1.2.0. + + * Add a new warning [bad-continuation] for badly indentend continued + lines. + + * Emit [assignment-from-none] when the function contains bare returns. + Fixes BitBucket issue #191. + + * Added a new warning for closing over variables that are + defined in loops. Fixes Bitbucket issue #176. + + * Do not warn about \u escapes in string literals when Unicode literals + are used for Python 2.*. Fixes BitBucket issue #151. + + * Extend the checking for unbalanced-tuple-unpacking and + unpacking-non-sequence to instance attribute unpacking as well. + + * Fix explicit checking of python script (1.2 regression, #219) + + * Restore --init-hook, renamed accidentally into --init-hooks in 1.2.0 + (#211) + + * Add 'indexing-exception' warning, which detects that indexing + an exception occurs in Python 2 (behaviour removed in Python 3). + + + +2014-04-18 -- 1.2.0 + * Pass the current python paths to pylint process when invoked via + epylint. Fixes BitBucket issue #133. + + * Add -i / --include-ids and -s / --symbols back as completely ignored + options. Fixes BitBucket issue #180. + + * Extend the number of cases in which logging calls are detected. Fixes + bitbucket issue #182. + + * Improve pragma handling to not detect pylint:* strings in non-comments. + Fixes BitBucket issue #79. * Do not crash with UnknownMessage if an unknown message ID/name appears in disable or enable in the configuration. Patch by Cole Robinson. @@ -75,6 +470,13 @@ ChangeLog for Pylint * Don't register the newstyle checker w/ python >= 3 + * Fix unused-import false positive w/ augment assignment (#78) + + * Fix access-member-before-definition false negative wrt aug assign (#164) + + * Do not attempt to analyze non python file, eg .so file (#122) + + 2013-12-22 -- 1.1.0 * Add new check for use of deprecated pragma directives "pylint:disable-msg" @@ -135,6 +537,7 @@ ChangeLog for Pylint * Fix issue #55 (false-positive trailing-whitespace on Windows) + 2013-08-06 -- 1.0.0 * Add check for the use of 'exec' function @@ -1381,6 +1784,3 @@ ChangeLog for Pylint 2003-05-19 -- 0.1 * initial release - - - @@ -1,3 +1,3 @@ python-logilab-common (>= 0.19.0) -python-astroid (>= 1.0.1) +python-astroid (>= 1.3.3) python-tk diff --git a/MANIFEST.in b/MANIFEST.in index 37c6e67..18480d0 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -7,6 +7,4 @@ include examples/*.py examples/pylintrc examples/pylintrc_camelcase include elisp/startup elisp/*.el include man/*.1 recursive-include doc *.rst *.jpeg Makefile *.html *.py -recursive-include test *.py *.txt *.dot *.sh -include test/input/similar* -include test/input/noext +graft pylint/test diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2622d38 --- /dev/null +++ b/Makefile @@ -0,0 +1,76 @@ +# Makefile for handling various tasks of Pylint sources +PYVE=pyve +PIP=$(PYVE)/bin/pip +TOX=$(PYVE)/bin/tox + +VERSION=$(shell PYTHONPATH=. python -c "from pylint.__pkginfo__ import version; print version") + +PKG_SDIST=dist/pylint-$(VERSION).tar.gz +PKG_DEB=../pylint_$(VERSION)-1_all.deb + +# this is default target, it should always be first in this Makefile +help: + @echo "Please use \`make <target>' where <target> is one of" + @echo " tests to run whole test suit of PyLint" + @echo " docs to generate all docs including man pages and exemplary pylintrc" + @echo " deb to build debian .deb package (it works only on Debian based Linux distros)" + @echo " sdist to build source .tar.gz package" + @echo " lint to check Pylint sources with itself" + @echo " all to run all targets" + + +$(PIP): + virtualenv $(PYVE) + +$(TOX): $(PIP) + $(PIP) install tox==1.7 + + +ifdef TOXENV +toxparams?=-e $(TOXENV) +endif + +tests: $(TOX) + $(TOX) $(toxparams) + +docs: $(PIP) + $(PIP) install . + $(PIP) install Sphinx + . $(PYVE)/bin/activate; make all -C doc + +deb: $(PKG_DEB) +$(PKG_DEB): /usr/bin/debuild /usr/bin/dh_pysupport + if [ -n "$$SUBVERSION" ]; then sed -i -e "0,/pylint (\(.*\))/s//pylint (\1.$${SUBVERSION})/" debian/changelog; fi + debuild -b -us -uc + +sdist: $(PKG_SDIST) +$(PKG_SDIST): + python setup.py sdist + +lint: $(PIP) + $(PIP) install . + $(PYVE)/bin/pylint lint.py || true # for now ignore errors + +clean: + rm -rf $(PYVE) + rm -rf .tox + rm -rf dist + rm -rf build + make clean -C doc + rm -rf $(PKG_DEB) ../pylint_*.changes ../pylint_*.build + debuild clean || true + +clobber: + hg purge -p + hg purge -a + + +/usr/bin/debuild: + sudo apt-get -y --force-yes install devscripts + +/usr/bin/dh_pysupport: + sudo apt-get -y --force-yes install python-support + +all: clean lint tests docs sdist deb + +.PHONY: help tests docs deb sdist lint clean clobber all @@ -1,50 +1,55 @@ -README for Pylint - http://www.pylint.org/ -========================================== - -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 has many rules enabled by default, way too much to silence them all on a -minimally sized program. It's highly configurable and handle pragmas to control -it from within your code. Additionally, it is possible to write plugins to add -your own checks. - -It's a free software distributed under the GNU Public Licence. - -Development is hosted on bitbucket: https://bitbucket.org/logilab/pylint/ - -You can use the code-quality@python.org mailing list to discuss about -Pylint. Subscribe at https://mail.python.org/mailman/listinfo/code-quality/ -or read the archives at https://mail.python.org/pipermail/code-quality/ - -Install -------- - -Pylint requires the astroid (the later the better; formerly known as -logilab-astng) and logilab-common (version >= 0.53) packages. - -* https://bitbucket.org/logilab/astroid -* http://www.logilab.org/projects/common - -From the source distribution, extract the tarball and run :: - - python setup.py install - -You'll have to install dependencies in a similar way. For debian and -rpm packages, use your usual tools according to your Linux distribution. - -More information about installation and available distribution format -may be found in the user manual in the *doc* subdirectory. - -Documentation -------------- - -Look in the doc/ subdirectory or at http://docs.pylint.org - -Pylint is shipped with following additional commands: - -* pyreverse: an UML diagram generator -* symilar: an independent similarities checker -* epylint: Emacs and Flymake compatible Pylint -* pylint-gui: a graphical interface +
+.. image:: https://drone.io/bitbucket.org/logilab/pylint/status.png
+ :alt: drone.io Build Status
+ :target: https://drone.io/bitbucket.org/logilab/pylint
+
+README for Pylint - http://www.pylint.org/
+==========================================
+
+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 has many rules enabled by default, way too much to silence them all on a
+minimally sized program. It's highly configurable and handle pragmas to control
+it from within your code. Additionally, it is possible to write plugins to add
+your own checks.
+
+It's a free software distributed under the GNU Public Licence.
+
+Development is hosted on bitbucket: https://bitbucket.org/logilab/pylint/
+
+You can use the code-quality@python.org mailing list to discuss about
+Pylint. Subscribe at https://mail.python.org/mailman/listinfo/code-quality/
+or read the archives at https://mail.python.org/pipermail/code-quality/
+
+Install
+-------
+
+Pylint requires the astroid (the later the better; formerly known as
+logilab-astng) and logilab-common (version >= 0.53) packages.
+
+* https://bitbucket.org/logilab/astroid
+* http://www.logilab.org/projects/common
+
+From the source distribution, extract the tarball and run ::
+
+ python setup.py install
+
+You'll have to install dependencies in a similar way. For debian and
+rpm packages, use your usual tools according to your Linux distribution.
+
+More information about installation and available distribution format
+may be found in the user manual in the *doc* subdirectory.
+
+Documentation
+-------------
+
+Look in the doc/ subdirectory or at http://docs.pylint.org
+
+Pylint is shipped with following additional commands:
+
+* pyreverse: an UML diagram generator
+* symilar: an independent similarities checker
+* epylint: Emacs and Flymake compatible Pylint
+* pylint-gui: a graphical interface
\ No newline at end of file diff --git a/README.Python3 b/README.Python3 index aee0f2b..897e946 100644 --- a/README.Python3 +++ b/README.Python3 @@ -10,17 +10,13 @@ Please, consider python3 >= 3.2 only. Approach -------- -We maintain a Python 2 base and use 2to3 to generate Python 3 code. - -2to3 is integrated into the distutils installation process and will be run as a -build step when invoked by the python3 interpreter:: - - NO_SETUPTOOLS=1 python3 setup.py install --no-compile +We maintain a code base that is Python 2/3 compatible simultaneously. In order to run pylint locally, you have to install the dependencies:: easy_install-3.2 logilab-common easy_install-3.2 astroid + easy_install-3.2 six Debian @@ -34,4 +30,4 @@ the debian/ folder:: Resources --------- -http://wiki.python.org/moin/PortingPythonToPy3k +https://docs.python.org/3/howto/pyporting.html diff --git a/__init__.py b/__init__.py deleted file mode 100644 index eed1b62..0000000 --- a/__init__.py +++ /dev/null @@ -1,44 +0,0 @@ -# Copyright (c) 2003-2012 LOGILAB S.A. (Paris, FRANCE). -# http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free Software -# Foundation; either version 2 of the License, or (at your option) any later -# version. -# -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -import sys - -def run_pylint(): - """run pylint""" - from pylint.lint import Run - Run(sys.argv[1:]) - -def run_pylint_gui(): - """run pylint-gui""" - try: - from pylint.gui import Run - Run(sys.argv[1:]) - except ImportError: - sys.exit('tkinter is not available') - -def run_epylint(): - """run pylint""" - from pylint.epylint import Run - Run() - -def run_pyreverse(): - """run pyreverse""" - from pylint.pyreverse.main import Run - Run(sys.argv[1:]) - -def run_symilar(): - """run symilar""" - from pylint.checkers.similar import Run - Run(sys.argv[1:]) diff --git a/__pkginfo__.py b/__pkginfo__.py deleted file mode 100644 index 0aa48a9..0000000 --- a/__pkginfo__.py +++ /dev/null @@ -1,74 +0,0 @@ -# pylint: disable=W0622,C0103 -# Copyright (c) 2003-2014 LOGILAB S.A. (Paris, FRANCE). -# http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free Software -# Foundation; either version 2 of the License, or (at your option) any later -# version. -# -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -"""pylint packaging information""" -import sys - -modname = distname = 'pylint' - -numversion = (1, 1, 0) -version = '.'.join([str(num) for num in numversion]) - -if sys.version_info < (2, 6): - install_requires = ['logilab-common >= 0.53.0', 'astroid >= 1.0.1', - 'StringFormat'] -else: - install_requires = ['logilab-common >= 0.53.0', 'astroid >= 1.0.1'] - -license = 'GPL' -description = "python code static checker" -web = 'http://www.pylint.org' -mailinglist = "mailto://code-quality@python.org" -author = 'Logilab' -author_email = 'python-projects@lists.logilab.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', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 3', - 'Topic :: Software Development :: Debuggers', - 'Topic :: Software Development :: Quality Assurance', - 'Topic :: Software Development :: Testing', - ] - - -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. - . - Pylint is shipped with "pylint-gui", "pyreverse" (UML diagram generator) - and "symilar" (an independent similarities checker).""" - -from os.path import join -scripts = [join('bin', filename) - for filename in ('pylint', 'pylint-gui', "symilar", "epylint", - "pyreverse")] - -include_dirs = ['test'] diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..7613e62 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,44 @@ +environment: + matrix: + - PYTHON: "C:\\Python27" + TOXENV: "py27" + + - PYTHON: "C:\\Python33" + TOXENV: "py33" + + - PYTHON: "C:\\Python34" + TOXENV: "py34" + +install: + - ECHO "Filesystem root:" + - ps: "ls \"C:/\"" + + # Install pip when not already installed. + - "powershell ./appveyor/install.ps1" + + # Prepend newly installed Python to the PATH of this build (this cannot be + # done from inside the powershell script as it would require to restart + # the parent CMD process). + - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" + + # Check that we have the expected version and architecture for Python + - "python --version" + - "python -c \"import struct; print(struct.calcsize('P') * 8)\"" + + # Install the build dependencies of the project. + - "pip install tox==1.7.2" + - "pip install wheel" + +build: false + +test_script: + - "tox" + +after_test: + # If tests are successful, create a whl package for the project. + - "python setup.py bdist_wheel bdist_wininst" + - ps: "ls dist" + +artifacts: + # Archive the generated wheel package in the ci.appveyor.com build report. + - path: dist\* diff --git a/appveyor/install.ps1 b/appveyor/install.ps1 new file mode 100644 index 0000000..e13c388 --- /dev/null +++ b/appveyor/install.ps1 @@ -0,0 +1,27 @@ +# Sample script to install pip under Windows +# Authors: Olivier Grisel, Jonathan Helmus and Kyle Kastner +# License: CC0 1.0 Universal: http://creativecommons.org/publicdomain/zero/1.0/ + +$GET_PIP_URL = "https://bootstrap.pypa.io/get-pip.py" +$GET_PIP_PATH = "C:\get-pip.py" + + +function InstallPip ($python_home) { + $pip_path = $python_home + "\Scripts\pip.exe" + $python_path = $python_home + "\python.exe" + if (-not(Test-Path $pip_path)) { + Write-Host "Installing pip..." + $webclient = New-Object System.Net.WebClient + $webclient.DownloadFile($GET_PIP_URL, $GET_PIP_PATH) + Write-Host "Executing:" $python_path $GET_PIP_PATH + Start-Process -FilePath "$python_path" -ArgumentList "$GET_PIP_PATH" -Wait -Passthru + } else { + Write-Host "pip already installed." + } +} + +function main () { + InstallPip $env:PYTHON +} + +main diff --git a/checkers/__init__.py b/checkers/__init__.py deleted file mode 100644 index 9346904..0000000 --- a/checkers/__init__.py +++ /dev/null @@ -1,147 +0,0 @@ -# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). -# http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free Software -# Foundation; either version 2 of the License, or (at your option) any later -# version. -# -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -"""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 -11: typecheck -12: logging -13: string_format -14: string_constant -15-50: not yet used: reserved for future internal checkers. -51-99: perhaps used: reserved for external checkers - -The raw_metrics checker has no number associated since it doesn't emit any -messages nor reports. XXX not true, emit a 07 report ! - -""" - -import sys -import tokenize -import warnings - -from astroid.utils import ASTWalker -from logilab.common.configuration import OptionsProviderMixIn - -from pylint.reporters import diff_string -from pylint.utils import register_plugins - -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""" - # checker name (you may reuse an existing one) - name = None - # options level (0 will be displaying in --help, 1 in --long-help) - level = 1 - # ordered list of options to control the ckecker behaviour - options = () - # messages issued by this checker - msgs = {} - # reports issued by this checker - reports = () - - 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() - OptionsProviderMixIn.__init__(self) - self.linter = linter - # messages that are active for the current check - self.active_msgs = set() - - 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) - - # 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, node): - """process a module - - the module's content is accessible via the stream object - - stream must implement the readline method - """ - warnings.warn("Modules that need access to the tokens should " - "use the ITokenChecker interface.", - DeprecationWarning) - stream = node.file_stream - stream.seek(0) # XXX may be removed with astroid > 0.23 - if sys.version_info <= (3, 0): - self.process_tokens(tokenize.generate_tokens(stream.readline)) - else: - self.process_tokens(tokenize.tokenize(stream.readline)) - - def process_tokens(self, tokens): - """should be overridden by subclasses""" - raise NotImplementedError() - - -class BaseTokenChecker(BaseChecker): - """Base class for checkers that want to have access to the token stream.""" - - def process_tokens(self, tokens): - """Should be overridden by subclasses.""" - raise NotImplementedError() - - -def initialize(linter): - """initialize linter with checkers in this package """ - register_plugins(linter, __path__[0]) - -__all__ = ('BaseChecker', 'initialize') diff --git a/checkers/base.py b/checkers/base.py deleted file mode 100644 index 497aa40..0000000 --- a/checkers/base.py +++ /dev/null @@ -1,1138 +0,0 @@ -# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). -# http://www.logilab.fr/ -- mailto:contact@logilab.fr -# Copyright (c) 2009-2010 Arista Networks, Inc. -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free Software -# Foundation; either version 2 of the License, or (at your option) any later -# version. -# -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -"""basic checker for Python code""" - -import sys -import astroid -from logilab.common.ureports import Table -from astroid import are_exclusive, InferenceError -import astroid.bases - -from pylint.interfaces import IAstroidChecker -from pylint.utils import EmptyReport -from pylint.reporters import diff_string -from pylint.checkers import BaseChecker -from pylint.checkers.utils import ( - check_messages, - clobber_in_except, - is_builtin_object, - is_inside_except, - overrides_a_method, - safe_infer, - get_argument_from_call, - NoSuchArgumentError, - ) - - -import re - -# regex for class/function/variable/constant name -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-Z0-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}$') -CLASS_ATTRIBUTE_RGX = re.compile(r'([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$') -# do not require a doc string on system methods -NO_REQUIRED_DOC_RGX = re.compile('__.*__') -REVERSED_METHODS = (('__getitem__', '__len__'), - ('__reversed__', )) - -PY33 = sys.version_info >= (3, 3) -BAD_FUNCTIONS = ['map', 'filter', 'apply'] -if sys.version_info < (3, 0): - BAD_FUNCTIONS.append('input') - BAD_FUNCTIONS.append('file') - -# Name categories that are always consistent with all naming conventions. -EXEMPT_NAME_CATEGORIES = {'exempt', 'ignore'} - -del re - -def in_loop(node): - """return True if the node is inside a kind of for loop""" - parent = node.parent - while parent is not None: - if isinstance(parent, (astroid.For, astroid.ListComp, astroid.SetComp, - astroid.DictComp, astroid.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 _loop_exits_early(loop): - """Returns true if a loop has a break statement in its body.""" - loop_nodes = (astroid.For, astroid.While) - # Loop over body explicitly to avoid matching break statements - # in orelse. - for child in loop.body: - if isinstance(child, loop_nodes): - # break statement may be in orelse of child loop. - for orelse in (child.orelse or ()): - for _ in orelse.nodes_of_class(astroid.Break, skip_klass=loop_nodes): - return True - continue - for _ in child.nodes_of_class(astroid.Break, skip_klass=loop_nodes): - return True - return False - -if sys.version_info < (3, 0): - PROPERTY_CLASSES = set(('__builtin__.property', 'abc.abstractproperty')) -else: - PROPERTY_CLASSES = set(('builtins.property', 'abc.abstractproperty')) -ABC_METHODS = set(('abc.abstractproperty', 'abc.abstractmethod', - 'abc.abstractclassmethod', 'abc.abstractstaticmethod')) - -def _determine_function_name_type(node): - """Determine the name type whose regex the a function's name should match. - - :param node: A function node. - :returns: One of ('function', 'method', 'attr') - """ - if not node.is_method(): - return 'function' - if node.decorators: - decorators = node.decorators.nodes - else: - decorators = [] - for decorator in decorators: - # If the function is a property (decorated with @property - # or @abc.abstractproperty), the name type is 'attr'. - if (isinstance(decorator, astroid.Name) or - (isinstance(decorator, astroid.Getattr) and - decorator.attrname == 'abstractproperty')): - infered = safe_infer(decorator) - if infered and infered.qname() in PROPERTY_CLASSES: - return 'attr' - # If the function is decorated using the prop_method.{setter,getter} - # form, treat it like an attribute as well. - elif (isinstance(decorator, astroid.Getattr) and - decorator.attrname in ('setter', 'deleter')): - return 'attr' - return 'method' - -def decorated_with_abc(func): - """ Determine if the `func` node is decorated - with `abc` decorators (abstractmethod et co.) - """ - if func.decorators: - for node in func.decorators.nodes: - try: - infered = node.infer().next() - except InferenceError: - continue - if infered and infered.qname() in ABC_METHODS: - return True - -def has_abstract_methods(node): - """ Determine if the given `node` has - abstract methods, defined with `abc` module. - """ - return any(decorated_with_abc(meth) - for meth in node.mymethods()) - -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'): - try: - total = stats[node_type] - except KeyError: - raise EmptyReport() - nice_stats[node_type] = {} - if total != 0: - try: - documented = total - stats['undocumented_'+node_type] - percent = (documented * 100.) / total - nice_stats[node_type]['percent_documented'] = '%.2f' % percent - except KeyError: - nice_stats[node_type]['percent_documented'] = 'NC' - try: - percent = (stats['badname_'+node_type] * 100.) / total - nice_stats[node_type]['percent_badname'] = '%.2f' % percent - except KeyError: - nice_stats[node_type]['percent_badname'] = 'NC' - 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, - nice_stats[node_type].get('percent_documented', '0'), - nice_stats[node_type].get('percent_badname', '0')) - sect.append(Table(children=lines, cols=6, rheaders=1)) - -def redefined_by_decorator(node): - """return True if the object is a method redefined via decorator. - - For example: - @property - def x(self): return self._x - @x.setter - def x(self, value): self._x = value - """ - if node.decorators: - for decorator in node.decorators.nodes: - if (isinstance(decorator, astroid.Getattr) and - getattr(decorator.expr, 'name', None) == node.name): - return True - return False - -class _BasicChecker(BaseChecker): - __implements__ = IAstroidChecker - name = 'basic' - -class BasicErrorChecker(_BasicChecker): - msgs = { - 'E0100': ('__init__ method is a generator', - 'init-is-generator', - 'Used when the special class method __init__ is turned into a ' - 'generator by a yield in its body.'), - 'E0101': ('Explicit return in __init__', - 'return-in-init', - 'Used when the special class method __init__ has an explicit \ - return value.'), - 'E0102': ('%s already defined line %s', - 'function-redefined', - 'Used when a function / class / method is redefined.'), - 'E0103': ('%r not properly in loop', - 'not-in-loop', - 'Used when break or continue keywords are used outside a loop.'), - - 'E0104': ('Return outside function', - 'return-outside-function', - 'Used when a "return" statement is found outside a function or ' - 'method.'), - 'E0105': ('Yield outside function', - 'yield-outside-function', - 'Used when a "yield" statement is found outside a function or ' - 'method.'), - 'E0106': ('Return with argument inside generator', - 'return-arg-in-generator', - 'Used when a "return" statement with an argument is found ' - 'outside in a generator function or method (e.g. with some ' - '"yield" statements).', - {'maxversion': (3, 3)}), - 'E0107': ("Use of the non-existent %s operator", - 'nonexistent-operator', - "Used when you attempt to use the C-style pre-increment or" - "pre-decrement operator -- and ++, which doesn't exist in Python."), - 'E0108': ('Duplicate argument name %s in function definition', - 'duplicate-argument-name', - 'Duplicate argument names in function definitions are syntax' - ' errors.'), - 'E0110': ('Abstract class with abstract methods instantiated', - 'abstract-class-instantiated', - 'Used when an abstract class with `abc.ABCMeta` as metaclass ' - 'has abstract methods and is instantiated.', - {'minversion': (3, 0)}), - 'W0120': ('Else clause on loop without a break statement', - 'useless-else-on-loop', - 'Loops should only have an else clause if they can exit early ' - 'with a break statement, otherwise the statements under else ' - 'should be on the same scope as the loop itself.'), - } - - def __init__(self, linter): - _BasicChecker.__init__(self, linter) - - @check_messages('function-redefined') - def visit_class(self, node): - self._check_redefinition('class', node) - - @check_messages('init-is-generator', 'return-in-init', - 'function-redefined', 'return-arg-in-generator', - 'duplicate-argument-name') - def visit_function(self, node): - if not redefined_by_decorator(node): - self._check_redefinition(node.is_method() and 'method' or 'function', node) - # checks for max returns, branch, return in __init__ - returns = node.nodes_of_class(astroid.Return, - skip_klass=(astroid.Function, astroid.Class)) - if node.is_method() and node.name == '__init__': - if node.is_generator(): - self.add_message('init-is-generator', node=node) - else: - values = [r.value for r in returns] - # Are we returning anything but None from constructors - if [v for v in values if - not (v is None or - (isinstance(v, astroid.Const) and v.value is None) or - (isinstance(v, astroid.Name) and v.name == 'None') - )]: - self.add_message('return-in-init', node=node) - elif node.is_generator(): - # make sure we don't mix non-None returns and yields - if not PY33: - for retnode in returns: - if isinstance(retnode.value, astroid.Const) and \ - retnode.value.value is not None: - self.add_message('return-arg-in-generator', node=node, - line=retnode.fromlineno) - # Check for duplicate names - args = set() - for name in node.argnames(): - if name in args: - self.add_message('duplicate-argument-name', node=node, args=(name,)) - else: - args.add(name) - - - @check_messages('return-outside-function') - def visit_return(self, node): - if not isinstance(node.frame(), astroid.Function): - self.add_message('return-outside-function', node=node) - - @check_messages('yield-outside-function') - def visit_yield(self, node): - if not isinstance(node.frame(), (astroid.Function, astroid.Lambda)): - self.add_message('yield-outside-function', node=node) - - @check_messages('not-in-loop') - def visit_continue(self, node): - self._check_in_loop(node, 'continue') - - @check_messages('not-in-loop') - def visit_break(self, node): - self._check_in_loop(node, 'break') - - @check_messages('useless-else-on-loop') - def visit_for(self, node): - self._check_else_on_loop(node) - - @check_messages('useless-else-on-loop') - def visit_while(self, node): - self._check_else_on_loop(node) - - @check_messages('nonexistent-operator') - def visit_unaryop(self, node): - """check use of the non-existent ++ and -- operator operator""" - if ((node.op in '+-') and - isinstance(node.operand, astroid.UnaryOp) and - (node.operand.op == node.op)): - self.add_message('nonexistent-operator', node=node, args=node.op*2) - - @check_messages('abstract-class-instantiated') - def visit_callfunc(self, node): - """ Check instantiating abstract class with - abc.ABCMeta as metaclass. - """ - try: - infered = node.func.infer().next() - except astroid.InferenceError: - return - if not isinstance(infered, astroid.Class): - return - # __init__ was called - metaclass = infered.metaclass() - if metaclass is None: - # Python 3.4 has `abc.ABC`, which won't be detected - # by ClassNode.metaclass() - for ancestor in infered.ancestors(): - if (ancestor.qname() == 'abc.ABC' and - has_abstract_methods(infered)): - - self.add_message('abstract-class-instantiated', node=node) - break - return - if (metaclass.qname() == 'abc.ABCMeta' and - has_abstract_methods(infered)): - - self.add_message('abstract-class-instantiated', node=node) - - def _check_else_on_loop(self, node): - """Check that any loop with an else clause has a break statement.""" - if node.orelse and not _loop_exits_early(node): - self.add_message('useless-else-on-loop', node=node, - # This is not optimal, but the line previous - # to the first statement in the else clause - # will usually be the one that contains the else:. - line=node.orelse[0].lineno - 1) - - 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, (astroid.For, astroid.While)): - break - _node = _node.parent - else: - self.add_message('not-in-loop', node=node, args=node_name) - - def _check_redefinition(self, redeftype, 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('function-redefined', node=node, - args=(redeftype, defined_self.fromlineno)) - - - -class BasicChecker(_BasicChecker): - """checks for : - * doc strings - * number of arguments, local variables, branches, 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__ = IAstroidChecker - - name = 'basic' - msgs = { - 'W0101': ('Unreachable code', - 'unreachable', - 'Used when there is some code behind a "return" or "raise" \ - statement, which will never be accessed.'), - 'W0102': ('Dangerous default value %s as argument', - 'dangerous-default-value', - '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', - 'pointless-statement', - 'Used when a statement doesn\'t have (or at least seems to) \ - any effect.'), - 'W0105': ('String statement has no effect', - 'pointless-string-statement', - 'Used when a string is used as a statement (which of course \ - has no effect). This is a particular case of W0104 with its \ - own message so you can easily disable it if you\'re using \ - those strings as documentation, instead of comments.'), - 'W0106': ('Expression "%s" is assigned to nothing', - 'expression-not-assigned', - 'Used when an expression that is not a function call is assigned\ - to nothing. Probably something else was intended.'), - 'W0108': ('Lambda may not be necessary', - 'unnecessary-lambda', - 'Used when the body of a lambda expression is a function call \ - on the same argument list as the lambda itself; such lambda \ - expressions are in all but a few cases replaceable with the \ - function being called in the body of the lambda.'), - 'W0109': ("Duplicate key %r in dictionary", - 'duplicate-key', - "Used when a dictionary expression binds the same key multiple \ - times."), - 'W0122': ('Use of exec', - 'exec-used', - 'Used when you use the "exec" statement (function for Python 3), to discourage its \ - usage. That doesn\'t mean you can not use it !'), - 'W0123': ('Use of eval', - 'eval-used', - 'Used when you use the "eval" function, to discourage its ' - 'usage. Consider using `ast.literal_eval` for safely evaluating ' - 'strings containing Python expressions ' - 'from untrusted sources. '), - 'W0141': ('Used builtin function %r', - 'bad-builtin', - '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', - 'star-args', - 'Used when a function or method is called using `*args` or ' - '`**kwargs` to dispatch arguments. This doesn\'t improve ' - 'readability and should be used with care.'), - 'W0150': ("%s statement in finally block may swallow exception", - 'lost-exception', - "Used when a break or a return statement is found inside the \ - finally clause of a try...finally block: the exceptions raised \ - in the try clause will be silently swallowed instead of being \ - re-raised."), - 'W0199': ('Assert called on a 2-uple. Did you mean \'assert x,y\'?', - 'assert-on-tuple', - 'A call of assert on a tuple will always evaluate to true if ' - 'the tuple is not empty, and will always evaluate to false if ' - 'it is.'), - 'W0121': ('Use raise ErrorClass(args) instead of raise ErrorClass, args.', - 'old-raise-syntax', - "Used when the alternate raise syntax 'raise foo, bar' is used " - "instead of 'raise foo(bar)'.", - {'maxversion': (3, 0)}), - - 'C0121': ('Missing required attribute "%s"', # W0103 - 'missing-module-attribute', - 'Used when an attribute required for modules is missing.'), - - 'E0109': ('Missing argument to reversed()', - 'missing-reversed-argument', - 'Used when reversed() builtin didn\'t receive an argument.'), - 'E0111': ('The first reversed() argument is not a sequence', - 'bad-reversed-sequence', - 'Used when the first argument to reversed() builtin ' - 'isn\'t a sequence (does not implement __reversed__, ' - 'nor __getitem__ and __len__'), - - } - - options = (('required-attributes', - {'default' : (), 'type' : 'csv', - 'metavar' : '<attributes>', - 'help' : 'Required attributes for module, separated by a ' - 'comma'} - ), - ('bad-functions', - {'default' : BAD_FUNCTIONS, - 'type' :'csv', 'metavar' : '<builtin function names>', - 'help' : 'List of builtins function names that should not be ' - 'used, separated by a comma'} - ), - ) - reports = (('RP0101', 'Statistics by type', report_by_type_stats),) - - def __init__(self, linter): - _BasicChecker.__init__(self, linter) - self.stats = None - self._tryfinallys = None - - def open(self): - """initialize visit variables and statistics - """ - self._tryfinallys = [] - self.stats = self.linter.add_stats(module=0, function=0, - method=0, class_=0) - @check_messages('missing-module-attribute') - def visit_module(self, node): - """check module name, docstring and required arguments - """ - self.stats['module'] += 1 - for attr in self.config.required_attributes: - if attr not in node: - self.add_message('missing-module-attribute', node=node, args=attr) - - def visit_class(self, node): - """check module name, docstring and redefinition - increment branch counter - """ - self.stats['class'] += 1 - - @check_messages('pointless-statement', 'pointless-string-statement', - 'expression-not-assigned') - def visit_discard(self, node): - """check for various kind of statements without effect""" - expr = node.value - if isinstance(expr, astroid.Const) and isinstance(expr.value, - basestring): - # treat string statement in a separated message - self.add_message('pointless-string-statement', node=node) - return - # ignore if this is : - # * a direct function call - # * the unique child of a try/except body - # * a yield (which are wrapped by a discard node in _ast XXX) - # warn W0106 if we have any underlying function call (we can't predict - # side effects), else pointless-statement - if (isinstance(expr, (astroid.Yield, astroid.CallFunc)) or - (isinstance(node.parent, astroid.TryExcept) and - node.parent.body == [node])): - return - if any(expr.nodes_of_class(astroid.CallFunc)): - self.add_message('expression-not-assigned', node=node, args=expr.as_string()) - else: - self.add_message('pointless-statement', node=node) - - @check_messages('unnecessary-lambda') - def visit_lambda(self, node): - """check whether or not the lambda is suspicious - """ - # if the body of the lambda is a call expression with the same - # argument list as the lambda itself, then the lambda is - # possibly unnecessary and at least suspicious. - if node.args.defaults: - # If the arguments of the lambda include defaults, then a - # judgment cannot be made because there is no way to check - # that the defaults defined by the lambda are the same as - # the defaults defined by the function called in the body - # of the lambda. - return - call = node.body - if not isinstance(call, astroid.CallFunc): - # The body of the lambda must be a function call expression - # for the lambda to be unnecessary. - return - # XXX are lambda still different with astroid >= 0.18 ? - # *args and **kwargs need to be treated specially, since they - # are structured differently between the lambda and the function - # call (in the lambda they appear in the args.args list and are - # indicated as * and ** by two bits in the lambda's flags, but - # in the function call they are omitted from the args list and - # are indicated by separate attributes on the function call node). - ordinary_args = list(node.args.args) - if node.args.kwarg: - if (not call.kwargs - or not isinstance(call.kwargs, astroid.Name) - or node.args.kwarg != call.kwargs.name): - return - elif call.kwargs: - return - if node.args.vararg: - if (not call.starargs - or not isinstance(call.starargs, astroid.Name) - or node.args.vararg != call.starargs.name): - return - elif call.starargs: - return - # The "ordinary" arguments must be in a correspondence such that: - # ordinary_args[i].name == call.args[i].name. - if len(ordinary_args) != len(call.args): - return - for i in xrange(len(ordinary_args)): - if not isinstance(call.args[i], astroid.Name): - return - if node.args.args[i].name != call.args[i].name: - return - self.add_message('unnecessary-lambda', line=node.fromlineno, node=node) - - @check_messages('dangerous-default-value') - def visit_function(self, node): - """check function name, docstring, arguments, redefinition, - variable names, max locals - """ - self.stats[node.is_method() and 'method' or 'function'] += 1 - # check for dangerous default values as arguments - for default in node.args.defaults: - try: - value = default.infer().next() - except astroid.InferenceError: - continue - builtins = astroid.bases.BUILTINS - if (isinstance(value, astroid.Instance) and - value.qname() in ['.'.join([builtins, x]) for x in ('set', 'dict', 'list')]): - if value is default: - msg = default.as_string() - elif type(value) is astroid.Instance: - msg = '%s (%s)' % (default.as_string(), value.qname()) - else: - msg = '%s (%s)' % (default.as_string(), value.as_string()) - self.add_message('dangerous-default-value', node=node, args=(msg,)) - - @check_messages('unreachable', 'lost-exception') - def visit_return(self, node): - """1 - check is the node has a right sibling (if so, that's some - unreachable code) - 2 - check is the node is inside the finally clause of a try...finally - block - """ - self._check_unreachable(node) - # Is it inside final body of a try...finally bloc ? - self._check_not_in_finally(node, 'return', (astroid.Function,)) - - @check_messages('unreachable') - def visit_continue(self, node): - """check is the node has a right sibling (if so, that's some unreachable - code) - """ - self._check_unreachable(node) - - @check_messages('unreachable', 'lost-exception') - def visit_break(self, node): - """1 - check is the node has a right sibling (if so, that's some - unreachable code) - 2 - check is the node is inside the finally clause of a try...finally - block - """ - # 1 - Is it right sibling ? - self._check_unreachable(node) - # 2 - Is it inside final body of a try...finally bloc ? - self._check_not_in_finally(node, 'break', (astroid.For, astroid.While,)) - - @check_messages('unreachable', 'old-raise-syntax') - def visit_raise(self, node): - """check if the node has a right sibling (if so, that's some unreachable - code) - """ - self._check_unreachable(node) - if sys.version_info >= (3, 0): - return - if node.exc is not None and node.inst is not None and node.tback is None: - self.add_message('old-raise-syntax', node=node) - - @check_messages('exec-used') - def visit_exec(self, node): - """just print a warning on exec statements""" - self.add_message('exec-used', node=node) - - @check_messages('bad-builtin', 'star-args', 'eval-used', - 'exec-used', 'missing-reversed-argument', - 'bad-reversed-sequence') - 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.func, astroid.Name): - name = node.func.name - # ignore the name if it's not a builtin (i.e. not defined in the - # locals nor globals scope) - if not (name in node.frame() or - name in node.root()): - if name == 'exec': - self.add_message('exec-used', node=node) - elif name == 'reversed': - self._check_reversed(node) - elif name == 'eval': - self.add_message('eval-used', node=node) - if name in self.config.bad_functions: - self.add_message('bad-builtin', node=node, args=name) - if node.starargs or node.kwargs: - scope = node.scope() - if isinstance(scope, astroid.Function): - toprocess = [(n, vn) for (n, vn) in ((node.starargs, scope.args.vararg), - (node.kwargs, scope.args.kwarg)) if n] - if toprocess: - for cfnode, fargname in toprocess[:]: - if getattr(cfnode, 'name', None) == fargname: - toprocess.remove((cfnode, fargname)) - if not toprocess: - return # star-args can be skipped - self.add_message('star-args', node=node.func) - - @check_messages('assert-on-tuple') - def visit_assert(self, node): - """check the use of an assert statement on a tuple.""" - if node.fail is None and isinstance(node.test, astroid.Tuple) and \ - len(node.test.elts) == 2: - self.add_message('assert-on-tuple', node=node) - - @check_messages('duplicate-key') - def visit_dict(self, node): - """check duplicate key in dictionary""" - keys = set() - for k, _ in node.items: - if isinstance(k, astroid.Const): - key = k.value - if key in keys: - self.add_message('duplicate-key', node=node, args=key) - keys.add(key) - - def visit_tryfinally(self, node): - """update try...finally flag""" - self._tryfinallys.append(node) - - def leave_tryfinally(self, node): - """update try...finally flag""" - self._tryfinallys.pop() - - def _check_unreachable(self, node): - """check unreachable code""" - unreach_stmt = node.next_sibling() - if unreach_stmt is not None: - self.add_message('unreachable', node=unreach_stmt) - - def _check_not_in_finally(self, node, node_name, breaker_classes=()): - """check that a node is not inside a finally clause of a - try...finally statement. - If we found before a try...finally bloc a parent which its type is - in breaker_classes, we skip the whole check.""" - # if self._tryfinallys is empty, we're not a in try...finally bloc - if not self._tryfinallys: - return - # the node could be a grand-grand...-children of the try...finally - _parent = node.parent - _node = node - while _parent and not isinstance(_parent, breaker_classes): - if hasattr(_parent, 'finalbody') and _node in _parent.finalbody: - self.add_message('lost-exception', node=node, args=node_name) - return - _node = _parent - _parent = _node.parent - - def _check_reversed(self, node): - """ check that the argument to `reversed` is a sequence """ - try: - argument = safe_infer(get_argument_from_call(node, position=0)) - except NoSuchArgumentError: - self.add_message('missing-reversed-argument', node=node) - else: - if argument is astroid.YES: - return - if argument is None: - # nothing was infered - # try to see if we have iter() - if (isinstance(node.args[0], astroid.CallFunc) and - node.args[0].func.name == 'iter'): - func = node.args[0].func.infer().next() - if is_builtin_object(func): - self.add_message('bad-reversed-sequence', node=node) - return - - if isinstance(argument, astroid.Instance): - if (argument._proxied.name == 'dict' and - is_builtin_object(argument._proxied)): - self.add_message('bad-reversed-sequence', node=node) - return - elif any(ancestor.name == 'dict' and is_builtin_object(ancestor) - for ancestor in argument._proxied.ancestors()): - # mappings aren't accepted by reversed() - self.add_message('bad-reversed-sequence', node=node) - return - - for methods in REVERSED_METHODS: - for meth in methods: - try: - argument.getattr(meth) - except astroid.NotFoundError: - break - else: - break - else: - # check if it is a .deque. It doesn't seem that - # we can retrieve special methods - # from C implemented constructs - if argument._proxied.qname().endswith(".deque"): - return - self.add_message('bad-reversed-sequence', node=node) - elif not isinstance(argument, (astroid.List, astroid.Tuple)): - # everything else is not a proper sequence for reversed() - self.add_message('bad-reversed-sequence', node=node) - -_NAME_TYPES = { - 'module': (MOD_NAME_RGX, 'module'), - 'const': (CONST_NAME_RGX, 'constant'), - 'class': (CLASS_NAME_RGX, 'class'), - 'function': (DEFAULT_NAME_RGX, 'function'), - 'method': (DEFAULT_NAME_RGX, 'method'), - 'attr': (DEFAULT_NAME_RGX, 'attribute'), - 'argument': (DEFAULT_NAME_RGX, 'argument'), - 'variable': (DEFAULT_NAME_RGX, 'variable'), - 'class_attribute': (CLASS_ATTRIBUTE_RGX, 'class attribute'), - 'inlinevar': (COMP_VAR_RGX, 'inline iteration'), -} - -def _create_naming_options(): - name_options = [] - for name_type, (rgx, human_readable_name) in _NAME_TYPES.iteritems(): - name_type = name_type.replace('_', '-') - name_options.append(( - '%s-rgx' % (name_type,), - {'default': rgx, 'type': 'regexp', 'metavar': '<regexp>', - 'help': 'Regular expression matching correct %s names' % (human_readable_name,)})) - name_options.append(( - '%s-name-hint' % (name_type,), - {'default': rgx.pattern, 'type': 'string', 'metavar': '<string>', - 'help': 'Naming hint for %s names' % (human_readable_name,)})) - - return tuple(name_options) - -class NameChecker(_BasicChecker): - msgs = { - 'C0102': ('Black listed name "%s"', - 'blacklisted-name', - 'Used when the name is listed in the black list (unauthorized \ - names).'), - 'C0103': ('Invalid %s name "%s"%s', - 'invalid-name', - 'Used when the name doesn\'t match the regular expression \ - associated to its type (constant, variable, class...).'), - } - - options = (# XXX use set - ('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'} - ), - ('name-group', - {'default' : (), - 'type' :'csv', 'metavar' : '<name1:name2>', - 'help' : ('Colon-delimited sets of names that determine each' - ' other\'s naming style when the name regexes' - ' allow several styles.')} - ), - ('include-naming-hint', - {'default': False, 'type' : 'yn', 'metavar' : '<y_or_n>', - 'help': 'Include a hint for the correct naming format with invalid-name'} - ), - ) + _create_naming_options() - - - def __init__(self, linter): - _BasicChecker.__init__(self, linter) - self._name_category = {} - self._name_group = {} - - def open(self): - self.stats = self.linter.add_stats(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, - badname_class_attribute=0) - for group in self.config.name_group: - for name_type in group.split(':'): - self._name_group[name_type] = 'group_%s' % (group,) - - @check_messages('blacklisted-name', 'invalid-name') - def visit_module(self, node): - self._check_name('module', node.name.split('.')[-1], node) - - @check_messages('blacklisted-name', 'invalid-name') - def visit_class(self, node): - self._check_name('class', node.name, node) - for attr, anodes in node.instance_attrs.iteritems(): - if not list(node.instance_attr_ancestors(attr)): - self._check_name('attr', attr, anodes[0]) - - @check_messages('blacklisted-name', 'invalid-name') - def visit_function(self, node): - # Do not emit any warnings if the method is just an implementation - # of a base class method. - if node.is_method() and overrides_a_method(node.parent.frame(), node.name): - return - self._check_name(_determine_function_name_type(node), - node.name, node) - # Check argument names - args = node.args.args - if args is not None: - self._recursive_check_names(args, node) - - @check_messages('blacklisted-name', 'invalid-name') - def visit_global(self, node): - for name in node.names: - self._check_name('const', name, node) - - @check_messages('blacklisted-name', 'invalid-name') - def visit_assname(self, node): - """check module level assigned names""" - frame = node.frame() - ass_type = node.ass_type() - if isinstance(ass_type, astroid.Comprehension): - self._check_name('inlinevar', node.name, node) - elif isinstance(frame, astroid.Module): - if isinstance(ass_type, astroid.Assign) and not in_loop(ass_type): - if isinstance(safe_infer(ass_type.value), astroid.Class): - self._check_name('class', node.name, node) - else: - self._check_name('const', node.name, node) - elif isinstance(ass_type, astroid.ExceptHandler): - self._check_name('variable', node.name, node) - elif isinstance(frame, astroid.Function): - # global introduced variable aren't in the function locals - if node.name in frame and node.name not in frame.argnames(): - self._check_name('variable', node.name, node) - elif isinstance(frame, astroid.Class): - if not list(frame.local_attr_ancestors(node.name)): - self._check_name('class_attribute', node.name, node) - - def _recursive_check_names(self, args, node): - """check names in a possibly recursive list <arg>""" - for arg in args: - if isinstance(arg, astroid.AssName): - self._check_name('argument', arg.name, node) - else: - self._recursive_check_names(arg.elts, node) - - def _find_name_group(self, node_type): - return self._name_group.get(node_type, node_type) - - def _is_multi_naming_match(self, match): - return (match is not None and - match.lastgroup is not None and - match.lastgroup not in EXEMPT_NAME_CATEGORIES) - - def _check_name(self, node_type, name, node): - """check for a name using the type's regexp""" - if is_inside_except(node): - clobbering, _ = clobber_in_except(node) - if clobbering: - return - if name in self.config.good_names: - return - if name in self.config.bad_names: - self.stats['badname_' + node_type] += 1 - self.add_message('blacklisted-name', node=node, args=name) - return - regexp = getattr(self.config, node_type + '_rgx') - match = regexp.match(name) - - if self._is_multi_naming_match(match): - name_group = self._find_name_group(node_type) - if name_group not in self._name_category: - self._name_category[name_group] = match.lastgroup - elif self._name_category[name_group] != match.lastgroup: - match = None - - if match is None: - type_label = _NAME_TYPES[node_type][1] - hint = '' - if self.config.include_naming_hint: - hint = ' (hint: %s)' % (getattr(self.config, node_type + '_name_hint')) - self.add_message('invalid-name', node=node, args=(type_label, name, hint)) - self.stats['badname_' + node_type] += 1 - - -class DocStringChecker(_BasicChecker): - msgs = { - 'C0111': ('Missing %s docstring', # W0131 - 'missing-docstring', - 'Used when a module, function, class or method has no docstring.\ - Some special methods like __init__ doesn\'t necessary require a \ - docstring.'), - 'C0112': ('Empty %s docstring', # W0132 - 'empty-docstring', - 'Used when a module, function, class or method has an empty \ - docstring (it would be too easy ;).'), - } - options = (('no-docstring-rgx', - {'default' : NO_REQUIRED_DOC_RGX, - 'type' : 'regexp', 'metavar' : '<regexp>', - 'help' : 'Regular expression which should only match ' - 'function or class names that do not require a ' - 'docstring.'} - ), - ('docstring-min-length', - {'default' : -1, - 'type' : 'int', 'metavar' : '<int>', - 'help': ('Minimum line length for functions/classes that' - ' require docstrings, shorter ones are exempt.')} - ), - ) - - - def open(self): - self.stats = self.linter.add_stats(undocumented_module=0, - undocumented_function=0, - undocumented_method=0, - undocumented_class=0) - @check_messages('missing-docstring', 'empty-docstring') - def visit_module(self, node): - self._check_docstring('module', node) - - @check_messages('missing-docstring', 'empty-docstring') - def visit_class(self, node): - if self.config.no_docstring_rgx.match(node.name) is None: - self._check_docstring('class', node) - @check_messages('missing-docstring', 'empty-docstring') - def visit_function(self, node): - if self.config.no_docstring_rgx.match(node.name) is None: - ftype = node.is_method() and 'method' or 'function' - if isinstance(node.parent.frame(), astroid.Class): - overridden = False - # check if node is from a method overridden by its ancestor - for ancestor in node.parent.frame().ancestors(): - if node.name in ancestor and \ - isinstance(ancestor[node.name], astroid.Function): - overridden = True - break - self._check_docstring(ftype, node, - report_missing=not overridden) - else: - self._check_docstring(ftype, node) - - def _check_docstring(self, node_type, node, report_missing=True): - """check the node has a non empty docstring""" - docstring = node.doc - if docstring is None: - if not report_missing: - return - if node.body: - lines = node.body[-1].lineno - node.body[0].lineno + 1 - else: - lines = 0 - max_lines = self.config.docstring_min_length - - if node_type != 'module' and max_lines > -1 and lines < max_lines: - return - self.stats['undocumented_'+node_type] += 1 - self.add_message('missing-docstring', node=node, args=(node_type,)) - elif not docstring.strip(): - self.stats['undocumented_'+node_type] += 1 - self.add_message('empty-docstring', node=node, args=(node_type,)) - - -class PassChecker(_BasicChecker): - """check if the pass statement is really necessary""" - msgs = {'W0107': ('Unnecessary pass statement', - 'unnecessary-pass', - 'Used when a "pass" statement that can be avoided is ' - 'encountered.'), - } - @check_messages('unnecessary-pass') - def visit_pass(self, node): - if len(node.parent.child_sequence(node)) > 1: - self.add_message('unnecessary-pass', node=node) - - -class LambdaForComprehensionChecker(_BasicChecker): - """check for using a lambda where a comprehension would do. - - See <http://www.artima.com/weblogs/viewpost.jsp?thread=98196> - where GvR says comprehensions would be clearer. - """ - - msgs = {'W0110': ('map/filter on lambda could be replaced by comprehension', - 'deprecated-lambda', - 'Used when a lambda is the first argument to "map" or ' - '"filter". It could be clearer as a list ' - 'comprehension or generator expression.', - {'maxversion': (3, 0)}), - } - - @check_messages('deprecated-lambda') - def visit_callfunc(self, node): - """visit a CallFunc node, check if map or filter are called with a - lambda - """ - if not node.args: - return - if not isinstance(node.args[0], astroid.Lambda): - return - infered = safe_infer(node.func) - if (is_builtin_object(infered) - and infered.name in ['map', 'filter']): - self.add_message('deprecated-lambda', node=node) - - -def register(linter): - """required method to auto register this checker""" - linter.register_checker(BasicErrorChecker(linter)) - linter.register_checker(BasicChecker(linter)) - linter.register_checker(NameChecker(linter)) - linter.register_checker(DocStringChecker(linter)) - linter.register_checker(PassChecker(linter)) - linter.register_checker(LambdaForComprehensionChecker(linter)) diff --git a/checkers/classes.py b/checkers/classes.py deleted file mode 100644 index 85a8507..0000000 --- a/checkers/classes.py +++ /dev/null @@ -1,767 +0,0 @@ -# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). -# http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free Software -# Foundation; either version 2 of the License, or (at your option) any later -# version. -# -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -"""classes checker for Python code -""" -from __future__ import generators -import sys -import astroid -from astroid import YES, Instance, are_exclusive, AssAttr -from astroid.bases import Generator - -from pylint.interfaces import IAstroidChecker -from pylint.checkers import BaseChecker -from pylint.checkers.utils import (PYMETHODS, overrides_a_method, - check_messages, is_attr_private, is_attr_protected, node_frame_class) - -if sys.version_info >= (3, 0): - NEXT_METHOD = '__next__' -else: - NEXT_METHOD = 'next' -ITER_METHODS = ('__iter__', '__getitem__') - -def class_is_abstract(node): - """return true if the given class node should be considered as an abstract - class - """ - for method in node.methods(): - if method.parent.frame() is node: - if method.is_abstract(pass_is_abstract=False): - return True - return False - - -MSGS = { - 'F0202': ('Unable to check methods signature (%s / %s)', - 'method-check-failed', - 'Used when PyLint has been unable to check methods signature \ - compatibility for an unexpected reason. Please report this kind \ - if you don\'t make sense of it.'), - - 'E0202': ('An attribute defined in %s line %s hides this method', - 'method-hidden', - 'Used when a class defines a method which is hidden by an ' - 'instance attribute from an ancestor class or set by some ' - 'client code.'), - 'E0203': ('Access to member %r before its definition line %s', - 'access-member-before-definition', - 'Used when an instance member is accessed before it\'s actually\ - assigned.'), - 'W0201': ('Attribute %r defined outside __init__', - 'attribute-defined-outside-init', - 'Used when an instance attribute is defined outside the __init__\ - method.'), - - 'W0212': ('Access to a protected member %s of a client class', # E0214 - 'protected-access', - 'Used when a protected member (i.e. class member with a name \ - beginning with an underscore) is access outside the class or a \ - descendant of the class where it\'s defined.'), - - 'E0211': ('Method has no argument', - 'no-method-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', - 'no-self-argument', - 'Used when a method has an attribute different the "self" as\ - first argument. This is considered as an error since this is\ - a so common convention that you shouldn\'t break it!'), - 'C0202': ('Class method %s should have %s as first argument', # E0212 - 'bad-classmethod-argument', - 'Used when a class method has a first argument named differently ' - 'than the value specified in valid-classmethod-first-arg option ' - '(default to "cls"), recommended to easily differentiate them ' - 'from regular instance methods.'), - 'C0203': ('Metaclass method %s should have %s as first argument', # E0214 - 'bad-mcs-method-argument', - 'Used when a metaclass method has a first agument named ' - 'differently than the value specified in valid-classmethod-first' - '-arg option (default to "cls"), recommended to easily ' - 'differentiate them from regular instance methods.'), - 'C0204': ('Metaclass class method %s should have %s as first argument', - 'bad-mcs-classmethod-argument', - 'Used when a metaclass class method has a first argument named ' - 'differently than the value specified in valid-metaclass-' - 'classmethod-first-arg option (default to "mcs"), recommended to ' - 'easily differentiate them from regular instance methods.'), - - 'W0211': ('Static method with %r as first argument', - 'bad-staticmethod-argument', - 'Used when a static method has "self" or a value specified in ' - 'valid-classmethod-first-arg option or ' - 'valid-metaclass-classmethod-first-arg option as first argument.' - ), - 'R0201': ('Method could be a function', - 'no-self-use', - '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', - 'interface-is-not-class', - 'Used when a class claims to implement an interface which is not \ - a class.'), - 'E0222': ('Missing method %r from %s interface', - 'missing-interface-method', - 'Used when a method declared in an interface is missing from a \ - class implementing this interface'), - 'W0221': ('Arguments number differs from %s method', - 'arguments-differ', - 'Used when a method has a different number of arguments than in \ - the implemented interface or in an overridden method.'), - 'W0222': ('Signature differs from %s method', - 'signature-differs', - 'Used when a method signature is different than in the \ - implemented interface or in an overridden method.'), - 'W0223': ('Method %r is abstract in class %r but is not overridden', - 'abstract-method', - 'Used when an abstract method (i.e. raise NotImplementedError) is \ - not overridden in concrete class.' - ), - 'F0220': ('failed to resolve interfaces implemented by %s (%s)', # W0224 - 'unresolved-interface', - 'Used when a PyLint as failed to find interfaces implemented by \ - a class'), - - - 'W0231': ('__init__ method from base class %r is not called', - 'super-init-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', - 'no-init', - 'Used when a class has no __init__ method, neither its parent \ - classes.'), - 'W0233': ('__init__ method from a non direct base class %r is called', - 'non-parent-init-called', - 'Used when an __init__ method is called on a class which is not \ - in the direct ancestors for the analysed class.'), - 'W0234': ('__iter__ returns non-iterator', - 'non-iterator-returned', - 'Used when an __iter__ method returns something which is not an \ - iterable (i.e. has no `%s` method)' % NEXT_METHOD), - 'E0235': ('__exit__ must accept 3 arguments: type, value, traceback', - 'bad-context-manager', - 'Used when the __exit__ special method, belonging to a \ - context manager, does not accept 3 arguments \ - (type, value, traceback).'), - 'E0236': ('Invalid object %r in __slots__, must contain ' - 'only non empty strings', - 'invalid-slots-object', - 'Used when an invalid (non-string) object occurs in __slots__.'), - 'E0238': ('Invalid __slots__ object', - 'invalid-slots', - 'Used when an invalid __slots__ is found in class. ' - 'Only a string, an iterable or a sequence is permitted.') - - - } - - -class ClassChecker(BaseChecker): - """checks for : - * methods without self as first argument - * overridden methods signature - * access only to existent members via self - * attributes not defined in the __init__ method - * supported interfaces implementation - * unreachable code - """ - - __implements__ = (IAstroidChecker,) - - # 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.'} - ), - ('valid-classmethod-first-arg', - {'default' : ('cls',), - 'type' : 'csv', - 'metavar' : '<argument names>', - 'help' : 'List of valid names for the first argument in \ -a class method.'} - ), - ('valid-metaclass-classmethod-first-arg', - {'default' : ('mcs',), - 'type' : 'csv', - 'metavar' : '<argument names>', - 'help' : 'List of valid names for the first argument in \ -a metaclass class method.'} - ), - - ) - - 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 astroid.NotFoundError: - self.add_message('W0232', args=node, node=node) - self._check_slots(node) - - @check_messages('E0203', 'W0201') - def leave_class(self, cnode): - """close a class node: - check that instance attributes are defined in __init__ and check - access to existent members - """ - # check access to existent members on non metaclass classes - accessed = self._accessed.pop() - if cnode.type != 'metaclass': - self._check_accessed_members(cnode, accessed) - # checks attributes are defined in an allowed method such as __init__ - if 'W0201' not in self.active_msgs: - return - defining_methods = self.config.defining_attr_methods - for attr, nodes in cnode.instance_attrs.iteritems(): - nodes = [n for n in nodes if not - isinstance(n.statement(), (astroid.Delete, astroid.AugAssign))] - if not nodes: - continue # error detected by typechecking - attr_defined = False - # check if any method attr is defined in is a defining method - for node in nodes: - if node.frame().name in defining_methods: - attr_defined = True - if not attr_defined: - # check attribute is defined in a parent's __init__ - for parent in cnode.instance_attr_ancestors(attr): - attr_defined = False - # check if any parent method attr is defined in is a defining method - for node in parent.instance_attrs[attr]: - if node.frame().name in defining_methods: - attr_defined = True - if attr_defined: - # we're done :) - break - else: - # check attribute is defined as a class attribute - try: - cnode.local_attr(attr) - except astroid.NotFoundError: - self.add_message('W0201', args=attr, node=node) - - def visit_function(self, node): - """check method arguments, overriding""" - # ignore actual functions - if not node.is_method(): - return - klass = node.parent.frame() - self._meth_could_be_func = True - # check first argument is self if this is actually a method - self._check_first_arg_for_type(node, klass.type == 'metaclass') - if node.name == '__init__': - self._check_init(node) - return - # check signature if the method overloads inherited method - for overridden in klass.local_attr_ancestors(node.name): - # get astroid for the searched method - try: - meth_node = overridden[node.name] - except KeyError: - # we have found the method but it's not in the local - # dictionary. - # This may happen with astroid build from living objects - continue - if not isinstance(meth_node, astroid.Function): - continue - self._check_signature(node, meth_node, 'overridden') - break - if node.decorators: - for decorator in node.decorators.nodes: - if isinstance(decorator, astroid.Getattr) and \ - decorator.attrname in ('getter', 'setter', 'deleter'): - # attribute affectation will call this method, not hiding it - return - if isinstance(decorator, astroid.Name) and decorator.name == 'property': - # attribute affectation will either call a setter or raise - # an attribute error, anyway not hiding the function - return - # check if the method is hidden by an attribute - try: - overridden = klass.instance_attr(node.name)[0] # XXX - args = (overridden.root().name, overridden.fromlineno) - self.add_message('E0202', args=args, node=node) - except astroid.NotFoundError: - pass - - # check non-iterators in __iter__ - if node.name == '__iter__': - self._check_iter(node) - elif node.name == '__exit__': - self._check_exit(node) - - def _check_slots(self, node): - if '__slots__' not in node.locals: - return - for slots in node.igetattr('__slots__'): - # check if __slots__ is a valid type - for meth in ITER_METHODS: - try: - slots.getattr(meth) - break - except astroid.NotFoundError: - continue - else: - self.add_message('invalid-slots', node=node) - continue - - if isinstance(slots, astroid.Const): - # a string, ignore the following checks - continue - if not hasattr(slots, 'itered'): - # we can't obtain the values, maybe a .deque? - continue - - if isinstance(slots, astroid.Dict): - values = [item[0] for item in slots.items] - else: - values = slots.itered() - if values is YES: - return - - for elt in values: - if elt is YES: - continue - if (not isinstance(elt, astroid.Const) or - not isinstance(elt.value, str)): - self.add_message('invalid-slots-object', - args=elt.as_string(), - node=elt) - continue - if not elt.value: - self.add_message('invalid-slots-object', - args=elt.as_string(), - node=elt) - - def _check_iter(self, node): - try: - infered = node.infer_call_result(node) - except astroid.InferenceError: - return - - for infered_node in infered: - if (infered_node is YES - or isinstance(infered_node, Generator)): - continue - if isinstance(infered_node, astroid.Instance): - try: - infered_node.local_attr(NEXT_METHOD) - except astroid.NotFoundError: - self.add_message('non-iterator-returned', - node=node) - break - - def _check_exit(self, node): - positional = sum(1 for arg in node.args.args if arg.name != 'self') - if positional < 3 and not node.args.vararg: - self.add_message('bad-context-manager', - node=node) - elif positional > 3: - self.add_message('bad-context-manager', - node=node) - - 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 overridden from a parent class and any - kind of method defined in an interface for this warning - """ - if node.is_method(): - if node.args.args is not None: - self._first_attrs.pop() - if 'R0201' not in self.active_msgs: - return - class_node = node.parent.frame() - if (self._meth_could_be_func and node.type == 'method' - and not node.name in PYMETHODS - 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 getattr is an access to a class member - if so, register it. Also check for access to protected - class member from outside its class (but ignore __special__ - methods) - """ - attrname = node.attrname - # Check self - if self.is_first_attr(node): - self._accessed[-1].setdefault(attrname, []).append(node) - return - if 'W0212' not in self.active_msgs: - return - - self._check_protected_attribute_access(node) - - def visit_assign(self, assign_node): - if 'W0212' not in self.active_msgs: - return - - node = assign_node.targets[0] - if not isinstance(node, AssAttr): - return - - if self.is_first_attr(node): - return - - self._check_protected_attribute_access(node) - - def _check_protected_attribute_access(self, node): - '''Given an attribute access node (set or get), check if attribute - access is legitimate. Call _check_first_attr with node before calling - this method. Valid cases are: - * self._attr in a method or cls._attr in a classmethod. Checked by - _check_first_attr. - * Klass._attr inside "Klass" class. - * Klass2._attr inside "Klass" class when Klass2 is a base class of - Klass. - ''' - attrname = node.attrname - - if is_attr_protected(attrname): - - klass = node_frame_class(node) - - # XXX infer to be more safe and less dirty ?? - # in classes, check we are not getting a parent method - # through the class object or through super - callee = node.expr.as_string() - - # We are not in a class, no remaining valid case - if klass is None: - self.add_message('W0212', node=node, args=attrname) - return - - # If the expression begins with a call to super, that's ok. - if isinstance(node.expr, astroid.CallFunc) and \ - isinstance(node.expr.func, astroid.Name) and \ - node.expr.func.name == 'super': - return - - # We are in a class, one remaining valid cases, Klass._attr inside - # Klass - if not (callee == klass.name or callee in klass.basenames): - self.add_message('W0212', node=node, args=attrname) - - def visit_name(self, node): - """check if the name handle an access to a class member - if so, register it - """ - 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.iteritems(): - # deactivate "except doesn't do anything", that's expected - # pylint: disable=W0704 - # is it a class attribute ? - try: - node.local_attr(attr) - # yes, stop here - continue - except astroid.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: - defstmts = node.instance_attr(attr) - except astroid.NotFoundError: - pass - else: - if len(defstmts) == 1: - defstmt = defstmts[0] - # check that if the node is accessed in the same method as - # it's defined, it's accessed after the initial assignment - frame = defstmt.frame() - lno = defstmt.fromlineno - for _node in nodes: - if _node.frame() is frame and _node.fromlineno < lno \ - and not are_exclusive(_node.statement(), defstmt, ('AttributeError', 'Exception', 'BaseException')): - 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 or a metaclass regular method (actually - valid-classmethod-first-arg value) - * 'mcs' for a metaclass class method (actually - valid-metaclass-classmethod-first-arg) - * not one of the above for a static method - """ - # don't care about functions with unknown argument (builtins) - if node.args.args is None: - return - first_arg = node.args.args and node.argnames()[0] - self._first_attrs.append(first_arg) - first = self._first_attrs[-1] - # static method - if node.type == 'staticmethod': - if (first_arg == 'self' or - first_arg in self.config.valid_classmethod_first_arg or - first_arg in self.config.valid_metaclass_classmethod_first_arg): - self.add_message('W0211', args=first, node=node) - return - self._first_attrs[-1] = None - # class / regular method with no args - elif not node.args.args: - self.add_message('E0211', node=node) - # metaclass - elif metaclass: - # metaclass __new__ or classmethod - if node.type == 'classmethod': - self._check_first_arg_config(first, - self.config.valid_metaclass_classmethod_first_arg, node, - 'C0204', node.name) - # metaclass regular method - else: - self._check_first_arg_config(first, - self.config.valid_classmethod_first_arg, node, 'C0203', - node.name) - # regular class - else: - # class method - if node.type == 'classmethod': - self._check_first_arg_config(first, - self.config.valid_classmethod_first_arg, node, 'C0202', - node.name) - # regular method without self as argument - elif first != 'self': - self.add_message('E0213', node=node) - - def _check_first_arg_config(self, first, config, node, message, - method_name): - if first not in config: - if len(config) == 1: - valid = repr(config[0]) - else: - valid = ', '.join( - repr(v) - for v in config[:-1]) - valid = '%s or %r' % ( - valid, config[-1]) - self.add_message(message, args=(method_name, valid), node=node) - - def _check_bases_classes(self, node): - """check that the given class node implements abstract methods from - base classes - """ - # check if this class abstract - if class_is_abstract(node): - return - 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.name in node.locals: - # it is redefined as an attribute or with a descriptor - continue - 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, astroid.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 beginning with an underscore, - # usually belonging to the interface implementation - continue - # get class method astroid - try: - method = node_method(node, name) - except astroid.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 astroid.InferenceError: - if e0221_hack[0]: - return - implements = Instance(node).getattr('__implements__')[0] - assignment = implements.parent - assert isinstance(assignment, astroid.Assign) - # assignment.expr can be a Name or a Tuple 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.value.as_string())) - - def _check_init(self, node): - """check that the __init__ method call super or ancestors'__init__ - method - """ - if not set(('W0231', 'W0233')) & self.active_msgs: - return - klass_node = node.parent.frame() - to_call = _ancestors_to_call(klass_node) - not_called_yet = dict(to_call) - for stmt in node.nodes_of_class(astroid.CallFunc): - expr = stmt.func - if not isinstance(expr, astroid.Getattr) \ - or expr.attrname != '__init__': - continue - # skip the test if using super - if isinstance(expr.expr, astroid.CallFunc) and \ - isinstance(expr.expr.func, astroid.Name) and \ - expr.expr.func.name == 'super': - return - try: - klass = expr.expr.infer().next() - if klass is YES: - continue - try: - del not_called_yet[klass] - except KeyError: - if klass not in to_call: - self.add_message('W0233', node=expr, args=klass.name) - except astroid.InferenceError: - continue - for klass, method in not_called_yet.iteritems(): - if klass.name == 'object' or method.parent.name == 'object': - continue - self.add_message('W0231', args=klass.name, node=node) - - def _check_signature(self, method1, refmethod, class_type): - """check that the signature of the two given methods match - - class_type is in 'class', 'interface' - """ - if not (isinstance(method1, astroid.Function) - and isinstance(refmethod, astroid.Function)): - self.add_message('F0202', args=(method1, refmethod), node=method1) - return - # don't care about functions with unknown argument (builtins) - if method1.args.args is None or refmethod.args.args is None: - return - # if we use *args, **kwargs, skip the below checks - if method1.args.vararg or method1.args.kwarg: - return - if is_attr_private(method1.name): - return - if len(method1.args.args) != len(refmethod.args.args): - self.add_message('W0221', args=class_type, node=method1) - elif len(method1.args.defaults) < len(refmethod.args.defaults): - self.add_message('W0222', args=class_type, node=method1) - - def is_first_attr(self, node): - """Check that attribute lookup name use first attribute variable name - (self for method, cls for classmethod and mcs for metaclass). - """ - return self._first_attrs and isinstance(node.expr, astroid.Name) and \ - node.expr.name == self._first_attrs[-1] - -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: - to_call[base_node] = base_node.igetattr(method).next() - except astroid.InferenceError: - continue - return to_call - - -def node_method(node, method_name): - """get astroid for <method_name> on the given class node, ensuring it - is a Function node - """ - for n in node.local_attr(method_name): - if isinstance(n, astroid.Function): - return n - raise astroid.NotFoundError(method_name) - -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 deleted file mode 100644 index cfd2d80..0000000 --- a/checkers/design_analysis.py +++ /dev/null @@ -1,360 +0,0 @@ -# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). -# http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free Software -# Foundation; either version 2 of the License, or (at your option) any later -# version. -# -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -"""check for signs of poor design""" - -from astroid import Function, If, InferenceError - -from pylint.interfaces import IAstroidChecker -from pylint.checkers import BaseChecker -from pylint.checkers.utils import check_messages - -import re - -# regexp for ignored argument name -IGNORED_ARGUMENT_NAMES = re.compile('_.*') - - -def class_is_abstract(klass): - """return true if the given class node should be considered as an abstract - class - """ - for attr in klass.values(): - if isinstance(attr, Function): - if attr.is_abstract(pass_is_abstract=False): - return True - return False - - -MSGS = { - 'R0901': ('Too many ancestors (%s/%s)', - 'too-many-ancestors', - 'Used when class has too many parent classes, try to reduce \ - this to get a simpler (and so easier to use) class.'), - 'R0902': ('Too many instance attributes (%s/%s)', - 'too-many-instance-attributes', - 'Used when class has too many instance attributes, try to reduce \ - this to get a simpler (and so easier to use) class.'), - 'R0903': ('Too few public methods (%s/%s)', - 'too-few-public-methods', - 'Used when class has too few public methods, so be sure it\'s \ - really worth it.'), - 'R0904': ('Too many public methods (%s/%s)', - 'too-many-public-methods', - 'Used when class has too many public methods, try to reduce \ - this to get a simpler (and so easier to use) class.'), - - 'R0911': ('Too many return statements (%s/%s)', - 'too-many-return-statements', - 'Used when a function or method has too many return statement, \ - making it hard to follow.'), - 'R0912': ('Too many branches (%s/%s)', - 'too-many-branches', - 'Used when a function or method has too many branches, \ - making it hard to follow.'), - 'R0913': ('Too many arguments (%s/%s)', - 'too-many-arguments', - 'Used when a function or method takes too many arguments.'), - 'R0914': ('Too many local variables (%s/%s)', - 'too-many-locals', - 'Used when a function or method has too many local variables.'), - 'R0915': ('Too many statements (%s/%s)', - 'too-many-statements', - 'Used when a function or method has too many statements. You \ - should then split it in smaller functions / methods.'), - - 'R0921': ('Abstract class not referenced', - 'abstract-class-not-used', - 'Used when an abstract class is not used as ancestor anywhere.'), - 'R0922': ('Abstract class is only referenced %s times', - 'abstract-class-little-used', - 'Used when an abstract class is used less than X times as \ - ancestor.'), - 'R0923': ('Interface not implemented', - 'interface-not-implemented', - 'Used when an interface class is not implemented anywhere.'), - } - - -class MisdesignChecker(BaseChecker): - """checks for sign of poor/misdesign: - * number of methods, attributes, local variables... - * size, complexity of functions, methods - """ - - __implements__ = (IAstroidChecker,) - - # configuration section name - name = 'design' - # messages - msgs = MSGS - priority = -2 - # configuration options - options = (('max-args', - {'default' : 5, 'type' : 'int', 'metavar' : '<int>', - 'help': 'Maximum number of arguments for function / method'} - ), - ('ignored-argument-names', - {'default' : IGNORED_ARGUMENT_NAMES, - 'type' :'regexp', 'metavar' : '<regexp>', - 'help' : 'Argument names that match this expression will be ' - 'ignored. Default to name with leading underscore'} - ), - ('max-locals', - {'default' : 15, 'type' : 'int', 'metavar' : '<int>', - 'help': 'Maximum number of locals for function / method body'} - ), - ('max-returns', - {'default' : 6, 'type' : 'int', 'metavar' : '<int>', - 'help': 'Maximum number of return / yield for function / ' - 'method body'} - ), - ('max-branches', - {'default' : 12, 'type' : 'int', 'metavar' : '<int>', - 'help': 'Maximum number of branch for function / method body'} - ), - ('max-statements', - {'default' : 50, 'type' : 'int', 'metavar' : '<int>', - 'help': 'Maximum number of statements in function / method ' - 'body'} - ), - ('max-parents', - {'default' : 7, - 'type' : 'int', - 'metavar' : '<num>', - 'help' : 'Maximum number of parents for a class (see R0901).'} - ), - ('max-attributes', - {'default' : 7, - 'type' : 'int', - 'metavar' : '<num>', - 'help' : 'Maximum number of attributes for a class \ -(see R0902).'} - ), - ('min-public-methods', - {'default' : 2, - 'type' : 'int', - 'metavar' : '<num>', - 'help' : 'Minimum number of public methods for a class \ -(see R0903).'} - ), - ('max-public-methods', - {'default' : 20, - 'type' : 'int', - 'metavar' : '<num>', - 'help' : 'Maximum number of public methods for a class \ -(see R0904).'} - ), - ) - - def __init__(self, linter=None): - BaseChecker.__init__(self, linter) - self.stats = None - self._returns = None - self._branches = None - self._used_abstracts = None - self._used_ifaces = None - self._abstracts = None - self._ifaces = None - self._stmts = 0 - - def open(self): - """initialize visit variables""" - self.stats = self.linter.add_stats() - self._returns = [] - self._branches = [] - self._used_abstracts = {} - self._used_ifaces = {} - self._abstracts = [] - self._ifaces = [] - - # Check 'R0921', 'R0922', 'R0923' - def close(self): - """check that abstract/interface classes are used""" - for abstract in self._abstracts: - if not abstract in self._used_abstracts: - self.add_message('R0921', node=abstract) - elif self._used_abstracts[abstract] < 2: - self.add_message('R0922', node=abstract, - args=self._used_abstracts[abstract]) - for iface in self._ifaces: - if not iface in self._used_ifaces: - self.add_message('R0923', node=iface) - - @check_messages('R0901', 'R0902', 'R0903', 'R0904', 'R0921', 'R0922', 'R0923') - def visit_class(self, node): - """check size of inheritance hierarchy and number of instance attributes - """ - self._inc_branch() - # Is the total inheritance hierarchy is 7 or less? - nb_parents = len(list(node.ancestors())) - if nb_parents > self.config.max_parents: - self.add_message('R0901', node=node, - args=(nb_parents, self.config.max_parents)) - # Does the class contain less than 20 attributes for - # non-GUI classes (40 for GUI)? - # FIXME detect gui classes - if len(node.instance_attrs) > self.config.max_attributes: - self.add_message('R0902', node=node, - args=(len(node.instance_attrs), - self.config.max_attributes)) - # update abstract / interface classes structures - if class_is_abstract(node): - self._abstracts.append(node) - elif node.type == 'interface' and node.name != 'Interface': - self._ifaces.append(node) - for parent in node.ancestors(False): - if parent.name == 'Interface': - continue - self._used_ifaces[parent] = 1 - try: - for iface in node.interfaces(): - self._used_ifaces[iface] = 1 - except InferenceError: - # XXX log ? - pass - for parent in node.ancestors(): - try: - self._used_abstracts[parent] += 1 - except KeyError: - self._used_abstracts[parent] = 1 - - @check_messages('R0901', 'R0902', 'R0903', 'R0904', 'R0921', 'R0922', 'R0923') - def leave_class(self, node): - """check number of public methods""" - nb_public_methods = 0 - special_methods = set() - for method in node.methods(): - if not method.name.startswith('_'): - nb_public_methods += 1 - if method.name.startswith("__"): - special_methods.add(method.name) - # Does the class contain less than 20 public methods ? - if nb_public_methods > self.config.max_public_methods: - self.add_message('R0904', node=node, - args=(nb_public_methods, - self.config.max_public_methods)) - # stop here for exception, metaclass and interface classes - if node.type != 'class': - return - # Does the class contain more than 5 public methods ? - if nb_public_methods < self.config.min_public_methods: - self.add_message('R0903', node=node, - args=(nb_public_methods, - self.config.min_public_methods)) - - @check_messages('R0911', 'R0912', 'R0913', 'R0914', 'R0915') - def visit_function(self, node): - """check function name, docstring, arguments, redefinition, - variable names, max locals - """ - self._inc_branch() - # init branch and returns counters - self._returns.append(0) - self._branches.append(0) - # check number of arguments - args = node.args.args - if args is not None: - ignored_args_num = len( - [arg for arg in args - if self.config.ignored_argument_names.match(arg.name)]) - argnum = len(args) - ignored_args_num - if argnum > self.config.max_args: - self.add_message('R0913', node=node, - args=(len(args), self.config.max_args)) - else: - ignored_args_num = 0 - # check number of local variables - locnum = len(node.locals) - ignored_args_num - if locnum > self.config.max_locals: - self.add_message('R0914', node=node, - args=(locnum, self.config.max_locals)) - # init statements counter - self._stmts = 1 - - @check_messages('R0911', 'R0912', 'R0913', 'R0914', 'R0915') - def leave_function(self, node): - """most of the work is done here on close: - checks for max returns, branch, return in __init__ - """ - returns = self._returns.pop() - if returns > self.config.max_returns: - self.add_message('R0911', node=node, - args=(returns, self.config.max_returns)) - branches = self._branches.pop() - if branches > self.config.max_branches: - self.add_message('R0912', node=node, - args=(branches, self.config.max_branches)) - # check number of statements - if self._stmts > self.config.max_statements: - self.add_message('R0915', node=node, - args=(self._stmts, self.config.max_statements)) - - def visit_return(self, _): - """count number of returns""" - if not self._returns: - return # return outside function, reported by the base checker - self._returns[-1] += 1 - - def visit_default(self, node): - """default visit method -> increments the statements counter if - necessary - """ - if node.is_statement: - self._stmts += 1 - - def visit_tryexcept(self, node): - """increments the branches counter""" - branches = len(node.handlers) - if node.orelse: - branches += 1 - self._inc_branch(branches) - self._stmts += branches - - def visit_tryfinally(self, _): - """increments the branches counter""" - self._inc_branch(2) - self._stmts += 2 - - def visit_if(self, node): - """increments the branches counter""" - branches = 1 - # don't double count If nodes coming from some 'elif' - if node.orelse and (len(node.orelse) > 1 or - not isinstance(node.orelse[0], If)): - branches += 1 - self._inc_branch(branches) - self._stmts += branches - - def visit_while(self, node): - """increments the branches counter""" - branches = 1 - if node.orelse: - branches += 1 - self._inc_branch(branches) - - visit_for = visit_while - - def _inc_branch(self, branchesnum=1): - """increments the branches counter""" - branches = self._branches - for i in xrange(len(branches)): - branches[i] += branchesnum - - # FIXME: make a nice report... - -def register(linter): - """required method to auto register this checker """ - linter.register_checker(MisdesignChecker(linter)) diff --git a/checkers/exceptions.py b/checkers/exceptions.py deleted file mode 100644 index 30b3fa8..0000000 --- a/checkers/exceptions.py +++ /dev/null @@ -1,286 +0,0 @@ -# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). -# http://www.logilab.fr/ -- mailto:contact@logilab.fr -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free Software -# Foundation; either version 2 of the License, or (at your option) any later -# version. -# -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -"""exceptions handling (raising, catching, exceptions classes) checker -""" -import sys - -from logilab.common.compat import builtins -BUILTINS_NAME = builtins.__name__ -import astroid -from astroid import YES, Instance, unpack_infer - -from pylint.checkers import BaseChecker -from pylint.checkers.utils import is_empty, is_raising, check_messages -from pylint.interfaces import IAstroidChecker - -def infer_bases(klass): - """ Fully infer the bases of the klass node. - - This doesn't use .ancestors(), because we need - the non-inferable nodes (YES nodes), - which can't be retrieved from .ancestors() - """ - for base in klass.bases: - try: - inferit = base.infer().next() - except astroid.InferenceError: - continue - if inferit is YES: - yield inferit - else: - for base in infer_bases(inferit): - yield base - -PY3K = sys.version_info >= (3, 0) -OVERGENERAL_EXCEPTIONS = ('Exception',) - -MSGS = { - 'E0701': ('Bad except clauses order (%s)', - 'bad-except-order', - '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', - 'raising-bad-type', - 'Used when something which is neither a class, an instance or a \ - string is raised (i.e. a `TypeError` will be raised).'), - 'E0703': ('Exception context set to something which is not an ' - 'exception, nor None', - 'bad-exception-context', - 'Used when using the syntax "raise ... from ...", ' - 'where the exception context is not an exception, ' - 'nor None.', - {'minversion': (3, 0)}), - 'E0710': ('Raising a new style class which doesn\'t inherit from BaseException', - 'raising-non-exception', - 'Used when a new style class which doesn\'t inherit from \ - BaseException is raised.'), - 'E0711': ('NotImplemented raised - should raise NotImplementedError', - 'notimplemented-raised', - 'Used when NotImplemented is raised instead of \ - NotImplementedError'), - 'E0712': ('Catching an exception which doesn\'t inherit from BaseException: %s', - 'catching-non-exception', - 'Used when a class which doesn\'t inherit from \ - BaseException is used as an exception in an except clause.'), - - 'W0701': ('Raising a string exception', - 'raising-string', - 'Used when a string exception is raised.'), - 'W0702': ('No exception type(s) specified', - 'bare-except', - 'Used when an except clause doesn\'t specify exceptions type to \ - catch.'), - 'W0703': ('Catching too general exception %s', - 'broad-except', - 'Used when an except catches a too general exception, \ - possibly burying unrelated errors.'), - 'W0704': ('Except doesn\'t do anything', - 'pointless-except', - 'Used when an except clause does nothing but "pass" and there is\ - no "else" clause.'), - 'W0710': ('Exception doesn\'t inherit from standard "Exception" class', - 'nonstandard-exception', - 'Used when a custom exception class is raised but doesn\'t \ - inherit from the builtin "Exception" class.'), - 'W0711': ('Exception to catch is the result of a binary "%s" operation', - 'binary-op-exception', - 'Used when the exception to catch is of the form \ - "except A or B:". If intending to catch multiple, \ - rewrite as "except (A, B):"'), - 'W0712': ('Implicit unpacking of exceptions is not supported in Python 3', - 'unpacking-in-except', - 'Python3 will not allow implicit unpacking of exceptions in except ' - 'clauses. ' - 'See http://www.python.org/dev/peps/pep-3110/', - {'maxversion': (3, 0)}), - } - - -if sys.version_info < (3, 0): - EXCEPTIONS_MODULE = "exceptions" -else: - EXCEPTIONS_MODULE = "builtins" - -class ExceptionsChecker(BaseChecker): - """checks for - * excepts without exception filter - * type of raise argument : string, Exceptions, other values - """ - - __implements__ = IAstroidChecker - - name = 'exceptions' - msgs = MSGS - priority = -4 - options = (('overgeneral-exceptions', - {'default' : OVERGENERAL_EXCEPTIONS, - 'type' :'csv', 'metavar' : '<comma-separated class names>', - 'help' : 'Exceptions that will emit a warning ' - 'when being caught. Defaults to "%s"' % ( - ', '.join(OVERGENERAL_EXCEPTIONS),)} - ), - ) - - @check_messages('raising-string', 'nonstandard-exception', 'raising-bad-type', - 'raising-non-exception', 'notimplemented-raised', 'bad-exception-context') - def visit_raise(self, node): - """visit raise possibly inferring value""" - # ignore empty raise - if node.exc is None: - return - if PY3K and node.cause: - try: - cause = node.cause.infer().next() - except astroid.InferenceError: - pass - else: - if isinstance(cause, astroid.Const): - if cause.value is not None: - self.add_message('bad-exception-context', - node=node) - elif (not isinstance(cause, astroid.Class) and - not inherit_from_std_ex(cause)): - self.add_message('bad-exception-context', - node=node) - expr = node.exc - if self._check_raise_value(node, expr): - return - else: - try: - value = unpack_infer(expr).next() - except astroid.InferenceError: - return - self._check_raise_value(node, value) - - def _check_raise_value(self, node, expr): - """check for bad values, string exception and class inheritance - """ - value_found = True - if isinstance(expr, astroid.Const): - value = expr.value - if isinstance(value, str): - self.add_message('raising-string', node=node) - else: - self.add_message('raising-bad-type', node=node, - args=value.__class__.__name__) - elif (isinstance(expr, astroid.Name) and \ - expr.name in ('None', 'True', 'False')) or \ - isinstance(expr, (astroid.List, astroid.Dict, astroid.Tuple, - astroid.Module, astroid.Function)): - self.add_message('raising-bad-type', node=node, args=expr.name) - elif ((isinstance(expr, astroid.Name) and expr.name == 'NotImplemented') - or (isinstance(expr, astroid.CallFunc) and - isinstance(expr.func, astroid.Name) and - expr.func.name == 'NotImplemented')): - self.add_message('notimplemented-raised', node=node) - elif isinstance(expr, astroid.BinOp) and expr.op == '%': - self.add_message('raising-string', node=node) - elif isinstance(expr, (Instance, astroid.Class)): - if isinstance(expr, Instance): - expr = expr._proxied - if (isinstance(expr, astroid.Class) and - not inherit_from_std_ex(expr) and - expr.root().name != BUILTINS_NAME): - if expr.newstyle: - self.add_message('raising-non-exception', node=node) - else: - self.add_message('nonstandard-exception', node=node) - else: - value_found = False - else: - value_found = False - return value_found - - @check_messages('unpacking-in-except') - def visit_excepthandler(self, node): - """Visit an except handler block and check for exception unpacking.""" - if isinstance(node.name, (astroid.Tuple, astroid.List)): - self.add_message('unpacking-in-except', node=node) - - - @check_messages('bare-except', 'broad-except', 'pointless-except', 'binary-op-exception', 'bad-except-order', - 'catching-non-exception') - def visit_tryexcept(self, node): - """check for empty except""" - exceptions_classes = [] - nb_handlers = len(node.handlers) - for index, handler in enumerate(node.handlers): - # single except doing nothing but "pass" without else clause - if nb_handlers == 1 and is_empty(handler.body) and not node.orelse: - self.add_message('pointless-except', node=handler.type or handler.body[0]) - if handler.type is None: - if nb_handlers == 1 and not is_raising(handler.body): - self.add_message('bare-except', node=handler) - # check if a "except:" is followed by some other - # except - elif index < (nb_handlers - 1): - msg = 'empty except clause should always appear last' - self.add_message('bad-except-order', node=node, args=msg) - - elif isinstance(handler.type, astroid.BoolOp): - self.add_message('binary-op-exception', node=handler, args=handler.type.op) - else: - try: - excs = list(unpack_infer(handler.type)) - except astroid.InferenceError: - continue - for exc in excs: - # XXX skip other non class nodes - if exc is YES or not isinstance(exc, astroid.Class): - continue - exc_ancestors = [anc for anc in exc.ancestors() - if isinstance(anc, astroid.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('bad-except-order', node=handler.type, args=msg) - if (exc.name in self.config.overgeneral_exceptions - and exc.root().name == EXCEPTIONS_MODULE - and nb_handlers == 1 and not is_raising(handler.body)): - self.add_message('broad-except', args=exc.name, node=handler.type) - - if (not inherit_from_std_ex(exc) and - exc.root().name != BUILTINS_NAME): - # try to see if the exception is based on a C based - # exception, by infering all the base classes and - # looking for inference errors - bases = infer_bases(exc) - fully_infered = all(inferit is not YES - for inferit in bases) - if fully_infered: - self.add_message('catching-non-exception', - node=handler.type, - args=(exc.name, )) - - exceptions_classes += excs - - -def inherit_from_std_ex(node): - """return true if the given class node is subclass of - exceptions.Exception - """ - if node.name in ('Exception', 'BaseException') \ - and node.root().name == EXCEPTIONS_MODULE: - 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(ExceptionsChecker(linter)) diff --git a/checkers/format.py b/checkers/format.py deleted file mode 100644 index abe38a5..0000000 --- a/checkers/format.py +++ /dev/null @@ -1,621 +0,0 @@ -# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free Software -# Foundation; either version 2 of the License, or (at your option) any later -# version. -# -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -"""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. -""" - -import keyword -import sys -import tokenize - -if not hasattr(tokenize, 'NL'): - raise ValueError("tokenize.NL doesn't exist -- tokenize module too old") - -from astroid import nodes - -from pylint.interfaces import ITokenChecker, IAstroidChecker, IRawChecker -from pylint.checkers import BaseTokenChecker -from pylint.checkers.utils import check_messages -from pylint.utils import WarningScope, OPTION_RGX - -_KEYWORD_TOKENS = ['assert', 'del', 'elif', 'except', 'for', 'if', 'in', 'not', - 'raise', 'return', 'while', 'yield'] -if sys.version_info < (3, 0): - _KEYWORD_TOKENS.append('print') - -_SPACED_OPERATORS = ['==', '<', '>', '!=', '<>', '<=', '>=', - '+=', '-=', '*=', '**=', '/=', '//=', '&=', '|=', '^=', - '%=', '>>=', '<<='] -_OPENING_BRACKETS = ['(', '[', '{'] -_CLOSING_BRACKETS = [')', ']', '}'] - -_EOL = frozenset([tokenize.NEWLINE, tokenize.NL, tokenize.COMMENT]) - -# Whitespace checking policy constants -_MUST = 0 -_MUST_NOT = 1 -_IGNORE = 2 - -# Whitespace checking config constants -_DICT_SEPARATOR = 'dict-separator' -_TRAILING_COMMA = 'trailing-comma' -_NO_SPACE_CHECK_CHOICES = [_TRAILING_COMMA, _DICT_SEPARATOR] - -MSGS = { - 'C0301': ('Line too long (%s/%s)', - 'line-too-long', - 'Used when a line is longer than a given number of characters.'), - 'C0302': ('Too many lines in module (%s)', # was W0302 - 'too-many-lines', - 'Used when a module has too much lines, reducing its readability.' - ), - 'C0303': ('Trailing whitespace', - 'trailing-whitespace', - 'Used when there is whitespace between the end of a line and the ' - 'newline.'), - 'C0304': ('Final newline missing', - 'missing-final-newline', - 'Used when the last line in a file is missing a newline.'), - 'W0311': ('Bad indentation. Found %s %s, expected %s', - 'bad-indentation', - 'Used when an unexpected number of indentation\'s tabulations or ' - 'spaces has been found.'), - 'W0312': ('Found indentation with %ss instead of %ss', - 'mixed-indentation', - 'Used when there are some mixed tabs and spaces in a module.'), - 'W0301': ('Unnecessary semicolon', # was W0106 - 'unnecessary-semicolon', - 'Used when a statement is ended by a semi-colon (";"), which \ - isn\'t necessary (that\'s python, not C ;).'), - 'C0321': ('More than one statement on a single line', - 'multiple-statements', - 'Used when more than on statement are found on the same line.', - {'scope': WarningScope.NODE}), - 'C0325' : ('Unnecessary parens after %r keyword', - 'superfluous-parens', - 'Used when a single item in parentheses follows an if, for, or ' - 'other keyword.'), - 'C0326': ('%s space %s %s %s\n%s', - 'bad-whitespace', - ('Used when a wrong number of spaces is used around an operator, ' - 'bracket or block opener.'), - {'old_names': [('C0323', 'no-space-after-operator'), - ('C0324', 'no-space-after-comma'), - ('C0322', 'no-space-before-operator')]}) - } - - -if sys.version_info < (3, 0): - - MSGS.update({ - 'W0331': ('Use of the <> operator', - 'old-ne-operator', - 'Used when the deprecated "<>" operator is used instead \ - of "!=".'), - 'W0332': ('Use of "l" as long integer identifier', - 'lowercase-l-suffix', - '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"'), - 'W0333': ('Use of the `` operator', - 'backtick', - 'Used when the deprecated "``" (backtick) operator is used ' - 'instead of the str() function.', - {'scope': WarningScope.NODE}), - }) - - -def _underline_token(token): - length = token[3][1] - token[2][1] - offset = token[2][1] - return token[4] + (' ' * offset) + ('^' * length) - - -def _column_distance(token1, token2): - if token1 == token2: - return 0 - if token2[3] < token1[3]: - token1, token2 = token2, token1 - if token1[3][0] != token2[2][0]: - return None - return token2[2][1] - token1[3][1] - - -class FormatChecker(BaseTokenChecker): - """checks for : - * unauthorized constructions - * strict indentation - * line length - * use of <> instead of != - """ - - __implements__ = (ITokenChecker, IAstroidChecker, IRawChecker) - - # 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.'}), - ('ignore-long-lines', - {'type': 'regexp', 'metavar': '<regexp>', - 'default': r'^\s*(# )?<?https?://\S+>?$', - 'help': ('Regexp for a line that is allowed to be longer than ' - 'the limit.')}), - ('single-line-if-stmt', - {'default': False, 'type' : 'yn', 'metavar' : '<y_or_n>', - 'help' : ('Allow the body of an if to be on the same ' - 'line as the test if there is no else.')}), - ('no-space-check', - {'default': ','.join(_NO_SPACE_CHECK_CHOICES), - 'type': 'multiple_choice', - 'choices': _NO_SPACE_CHECK_CHOICES, - 'help': ('List of optional constructs for which whitespace ' - 'checking is disabled')}), - ('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): - BaseTokenChecker.__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.split('\n')[0] - self.check_lines(line, line_num) - - def process_module(self, module): - self._keywords_with_parens = set() - for node in module.body: - if (isinstance(node, nodes.From) and node.modname == '__future__' - and any(name == 'print_function' for name, _ in node.names)): - self._keywords_with_parens.add('print') - - def _check_keyword_parentheses(self, tokens, start): - """Check that there are not unnecessary parens after a keyword. - - Parens are unnecessary if there is exactly one balanced outer pair on a - line, and it is followed by a colon, and contains no commas (i.e. is not a - tuple). - - Args: - tokens: list of Tokens; the entire list of Tokens. - start: int; the position of the keyword in the token list. - """ - # If the next token is not a paren, we're fine. - if tokens[start+1][1] != '(': - return - - found_and_or = False - depth = 0 - keyword_token = tokens[start][1] - line_num = tokens[start][2][0] - - for i in xrange(start, len(tokens) - 1): - token = tokens[i] - - # If we hit a newline, then assume any parens were for continuation. - if token[0] == tokenize.NL: - return - - if token[1] == '(': - depth += 1 - elif token[1] == ')': - depth -= 1 - if not depth: - # ')' can't happen after if (foo), since it would be a syntax error. - if (tokens[i+1][1] in (':', ')', ']', '}', 'in') or - tokens[i+1][0] in (tokenize.NEWLINE, tokenize.ENDMARKER, - tokenize.COMMENT)): - # The empty tuple () is always accepted. - if i == start + 2: - return - if keyword_token == 'not': - if not found_and_or: - self.add_message('C0325', line=line_num, - args=keyword_token) - elif keyword_token in ('return', 'yield'): - self.add_message('C0325', line=line_num, - args=keyword_token) - elif keyword_token not in self._keywords_with_parens: - if not (tokens[i+1][1] == 'in' and found_and_or): - self.add_message('C0325', line=line_num, - args=keyword_token) - return - elif depth == 1: - # This is a tuple, which is always acceptable. - if token[1] == ',': - return - # 'and' and 'or' are the only boolean operators with lower precedence - # than 'not', so parens are only required when they are found. - elif token[1] in ('and', 'or'): - found_and_or = True - # A yield inside an expression must always be in parentheses, - # quit early without error. - elif token[1] == 'yield': - return - # A generator expression always has a 'for' token in it, and - # the 'for' token is only legal inside parens when it is in a - # generator expression. The parens are necessary here, so bail - # without an error. - elif token[1] == 'for': - return - - def _opening_bracket(self, tokens, i): - self._bracket_stack.append(tokens[i][1]) - # Special case: ignore slices - if tokens[i][1] == '[' and tokens[i+1][1] == ':': - return - - if (i > 0 and (tokens[i-1][0] == tokenize.NAME and - not (keyword.iskeyword(tokens[i-1][1])) - or tokens[i-1][1] in _CLOSING_BRACKETS)): - self._check_space(tokens, i, (_MUST_NOT, _MUST_NOT)) - else: - self._check_space(tokens, i, (_IGNORE, _MUST_NOT)) - - def _closing_bracket(self, tokens, i): - self._bracket_stack.pop() - # Special case: ignore slices - if tokens[i-1][1] == ':' and tokens[i][1] == ']': - return - policy_before = _MUST_NOT - if tokens[i][1] in _CLOSING_BRACKETS and tokens[i-1][1] == ',': - if _TRAILING_COMMA in self.config.no_space_check: - policy_before = _IGNORE - - self._check_space(tokens, i, (policy_before, _IGNORE)) - - def _check_equals_spacing(self, tokens, i): - """Check the spacing of a single equals sign.""" - if self._inside_brackets('(') or self._inside_brackets('lambda'): - self._check_space(tokens, i, (_MUST_NOT, _MUST_NOT)) - else: - self._check_space(tokens, i, (_MUST, _MUST)) - - def _open_lambda(self, tokens, i): # pylint:disable=unused-argument - self._bracket_stack.append('lambda') - - def _handle_colon(self, tokens, i): - # Special case: ignore slices - if self._inside_brackets('['): - return - if (self._inside_brackets('{') and - _DICT_SEPARATOR in self.config.no_space_check): - policy = (_IGNORE, _IGNORE) - else: - policy = (_MUST_NOT, _MUST) - self._check_space(tokens, i, policy) - - if self._inside_brackets('lambda'): - self._bracket_stack.pop() - - def _handle_comma(self, tokens, i): - # Only require a following whitespace if this is - # not a hanging comma before a closing bracket. - if tokens[i+1][1] in _CLOSING_BRACKETS: - self._check_space(tokens, i, (_MUST_NOT, _IGNORE)) - else: - self._check_space(tokens, i, (_MUST_NOT, _MUST)) - - def _check_surrounded_by_space(self, tokens, i): - """Check that a binary operator is surrounded by exactly one space.""" - self._check_space(tokens, i, (_MUST, _MUST)) - - def _check_space(self, tokens, i, policies): - def _policy_string(policy): - if policy == _MUST: - return 'Exactly one', 'required' - else: - return 'No', 'allowed' - - def _name_construct(token): - if tokens[i][1] == ',': - return 'comma' - elif tokens[i][1] == ':': - return ':' - elif tokens[i][1] in '()[]{}': - return 'bracket' - elif tokens[i][1] in ('<', '>', '<=', '>=', '!=', '=='): - return 'comparison' - else: - if self._inside_brackets('('): - return 'keyword argument assignment' - else: - return 'assignment' - - good_space = [True, True] - pairs = [(tokens[i-1], tokens[i]), (tokens[i], tokens[i+1])] - - for other_idx, (policy, token_pair) in enumerate(zip(policies, pairs)): - if token_pair[other_idx][0] in _EOL or policy == _IGNORE: - continue - - distance = _column_distance(*token_pair) - if distance is None: - continue - good_space[other_idx] = ( - (policy == _MUST and distance == 1) or - (policy == _MUST_NOT and distance == 0)) - - warnings = [] - if not any(good_space) and policies[0] == policies[1]: - warnings.append((policies[0], 'around')) - else: - for ok, policy, position in zip(good_space, policies, ('before', 'after')): - if not ok: - warnings.append((policy, position)) - for policy, position in warnings: - construct = _name_construct(tokens[i]) - count, state = _policy_string(policy) - self.add_message('C0326', line=tokens[i][2][0], - args=(count, state, position, construct, - _underline_token(tokens[i]))) - - def _inside_brackets(self, left): - return self._bracket_stack[-1] == left - - def _prepare_token_dispatcher(self): - raw = [ - (_KEYWORD_TOKENS, - self._check_keyword_parentheses), - - (_OPENING_BRACKETS, self._opening_bracket), - - (_CLOSING_BRACKETS, self._closing_bracket), - - (['='], self._check_equals_spacing), - - (_SPACED_OPERATORS, self._check_surrounded_by_space), - - ([','], self._handle_comma), - - ([':'], self._handle_colon), - - (['lambda'], self._open_lambda), - ] - - dispatch = {} - for tokens, handler in raw: - for token in tokens: - dispatch[token] = handler - return dispatch - - 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). - """ - self._bracket_stack = [None] - indent = tokenize.INDENT - dedent = tokenize.DEDENT - newline = tokenize.NEWLINE - junk = (tokenize.COMMENT, tokenize.NL) - indents = [0] - check_equal = 0 - line_num = 0 - previous = None - self._lines = {} - self._visited_lines = {} - new_line_delay = False - token_handlers = self._prepare_token_dispatcher() - for idx, (tok_type, token, start, _, line) in enumerate(tokens): - if new_line_delay: - new_line_delay = False - self.new_line(tok_type, line, line_num, junk) - if start[0] != line_num: - if previous is not None and previous[0] == tokenize.OP and previous[1] == ';': - self.add_message('W0301', line=previous[2]) - previous = None - line_num = start[0] - # A tokenizer oddity: if an indented line contains a multi-line - # docstring, the line member of the INDENT token does not contain - # the full line; therefore we delay checking the new line until - # the next token. - if tok_type == tokenize.INDENT: - new_line_delay = True - else: - self.new_line(tok_type, line, line_num, junk) - if tok_type not in (indent, dedent, newline) + junk: - previous = tok_type, token, start[0] - - 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) - - try: - handler = token_handlers[token] - except KeyError: - pass - else: - handler(tokens, idx) - - line_num -= 1 # to be ok with "wc -l" - if line_num > self.config.max_module_lines: - self.add_message('C0302', args=line_num, line=1) - - @check_messages('C0321', 'C03232', 'C0323', 'C0324') - def visit_default(self, node): - """check the node line number and check it if not yet done""" - if not node.is_statement: - return - if not node.root().pure_python: - return # XXX block visit of child nodes - prev_sibl = node.previous_sibling() - if prev_sibl is not None: - prev_line = prev_sibl.fromlineno - else: - # The line on which a finally: occurs in a try/finally - # is not directly represented in the AST. We infer it - # by taking the last line of the body and adding 1, which - # should be the line of finally: - if (isinstance(node.parent, nodes.TryFinally) - and node in node.parent.finalbody): - prev_line = node.parent.body[0].tolineno + 1 - else: - prev_line = node.parent.statement().fromlineno - line = node.fromlineno - assert line, node - if prev_line == line and self._visited_lines.get(line) != 2: - self._check_multi_statement_line(node, line) - return - if line in self._visited_lines: - return - try: - tolineno = node.blockstart_tolineno - except AttributeError: - tolineno = node.tolineno - assert tolineno, node - lines = [] - for line in xrange(line, tolineno + 1): - self._visited_lines[line] = 1 - try: - lines.append(self._lines[line].rstrip()) - except KeyError: - lines.append('') - - def _check_multi_statement_line(self, node, line): - """Check for lines containing multiple statements.""" - # Do not warn about multiple nested context managers - # in with statements. - if isinstance(node, nodes.With): - return - # For try... except... finally..., the two nodes - # appear to be on the same line due to how the AST is built. - if (isinstance(node, nodes.TryExcept) and - isinstance(node.parent, nodes.TryFinally)): - return - if (isinstance(node.parent, nodes.If) and not node.parent.orelse - and self.config.single_line_if_stmt): - return - self.add_message('C0321', node=node) - self._visited_lines[line] = 2 - - @check_messages('W0333') - def visit_backquote(self, node): - self.add_message('W0333', node=node) - - def check_lines(self, lines, i): - """check lines have less than a maximum number of characters - """ - max_chars = self.config.max_line_length - ignore_long_line = self.config.ignore_long_lines - - for line in lines.splitlines(True): - if not line.endswith('\n'): - self.add_message('C0304', line=i) - else: - stripped_line = line.rstrip() - if line[len(stripped_line):] not in ('\n', '\r\n'): - self.add_message('C0303', line=i) - # Don't count excess whitespace in the line length. - line = stripped_line - mobj = OPTION_RGX.search(line) - if mobj and mobj.group(1).split('=', 1)[0].strip() == 'disable': - line = line.split('#')[0].rstrip() - - if len(line) > max_chars and not ignore_long_line.search(line): - 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 deleted file mode 100644 index fd897a8..0000000 --- a/checkers/imports.py +++ /dev/null @@ -1,387 +0,0 @@ -# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). -# http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free Software -# Foundation; either version 2 of the License, or (at your option) any later -# version. -# -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -"""imports checkers for Python code""" - -from logilab.common.graph import get_cycles, DotBackend -from logilab.common.modutils import get_module_part, is_standard_module -from logilab.common.ureports import VerbatimText, Paragraph - -import astroid -from astroid import are_exclusive - -from pylint.interfaces import IAstroidChecker -from pylint.utils import EmptyReport -from pylint.checkers import BaseChecker -from pylint.checkers.utils import check_messages - - -def get_first_import(node, context, name, base, level): - """return the node where [base.]<name> is imported or None if not found - """ - fullname = '%s.%s' % (base, name) if base else name - - first = None - found = False - for first in context.body: - if first is node: - continue - if first.scope() is node.scope() and first.fromlineno > node.fromlineno: - continue - if isinstance(first, astroid.Import): - if any(fullname == iname[0] for iname in first.names): - found = True - break - elif isinstance(first, astroid.From): - if level == first.level and any( - fullname == '%s.%s' % (first.modname, iname[0]) for iname in first.names): - found = True - break - if found and not are_exclusive(first, node): - return first - -# utilities to represents import dependencies as tree and dot graph ########### - -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 dictionary 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, (mod, (sub, files)) in enumerate(sorted(nodes, key=lambda x: x[0])): - 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(r'%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 dependencies_graph(filename, dep_info): - """write dependencies as a dot (graphviz) file - """ - done = {} - printer = DotBackend(filename[:-4], rankdir='LR') - printer.emit('URL="." node[shape="box"]') - for modname, dependencies in sorted(dep_info.iteritems()): - done[modname] = 1 - printer.emit_node(modname) - for modname in dependencies: - if modname not in done: - done[modname] = 1 - printer.emit_node(modname) - for depmodname, dependencies in sorted(dep_info.iteritems()): - for modname in dependencies: - printer.emit_edge(modname, depmodname) - printer.generate(filename) - - -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 %s', - 'import-error', - 'Used when pylint has been unable to import a module.'), - 'R0401': ('Cyclic import (%s)', - 'cyclic-import', - 'Used when a cyclic import between two or more modules is \ - detected.'), - - 'W0401': ('Wildcard import %s', - 'wildcard-import', - 'Used when `from module import *` is detected.'), - 'W0402': ('Uses of a deprecated module %r', - 'deprecated-module', - 'Used a module marked as deprecated is imported.'), - 'W0403': ('Relative import %r, should be %r', - 'relative-import', - 'Used when an import relative to the package directory is \ - detected.'), - 'W0404': ('Reimport %r (imported line %s)', - 'reimported', - 'Used when a module is reimported multiple times.'), - 'W0406': ('Module import itself', - 'import-self', - 'Used when a module is importing itself.'), - - 'W0410': ('__future__ import is not the first non docstring statement', - 'misplaced-future', - '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__ = IAstroidChecker - - name = 'imports' - msgs = MSGS - priority = -2 - - options = (('deprecated-modules', - {'default' : ('regsub', '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 RP0402 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 RP0402 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 RP0402 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 = (('RP0401', 'External dependencies', - self.report_external_dependencies), - ('RP0402', '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)""" - # don't try to compute cycles if the associated message is disabled - if self.linter.is_message_enabled('R0401'): - 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""" - modnode = node.root() - for name, _ in node.names: - importedmodnode = self.get_imported_module(modnode, node, name) - if importedmodnode is None: - continue - self._check_relative_import(modnode, node, importedmodnode, name) - self._add_imported_module(node, importedmodnode.name) - self._check_deprecated_module(node, name) - self._check_reimport(node, name) - - # TODO This appears to be the list of all messages of the checker... - # @check_messages('W0410', 'W0401', 'W0403', 'W0402', 'W0404', 'W0406', 'F0401') - @check_messages(*(MSGS.keys())) - def visit_from(self, node): - """triggered when a from statement is seen""" - basename = node.modname - if basename == '__future__': - # check if this is the first non-docstring statement in the module - prev = node.previous_sibling() - if prev: - # consecutive future statements are possible - if not (isinstance(prev, astroid.From) - and prev.modname == '__future__'): - self.add_message('W0410', node=node) - return - for name, _ in node.names: - if name == '*': - self.add_message('W0401', args=basename, node=node) - modnode = node.root() - importedmodnode = self.get_imported_module(modnode, node, basename) - if importedmodnode is None: - return - self._check_relative_import(modnode, node, importedmodnode, basename) - self._check_deprecated_module(node, basename) - for name, _ in node.names: - if name != '*': - self._add_imported_module(node, '%s.%s' % (importedmodnode.name, name)) - self._check_reimport(node, name, basename, node.level) - - def get_imported_module(self, modnode, importnode, modname): - try: - return importnode.do_import_module(modname) - except astroid.InferenceError, ex: - if str(ex) != modname: - args = '%r (%s)' % (modname, ex) - else: - args = repr(modname) - self.add_message("F0401", args=args, node=importnode) - - def _check_relative_import(self, modnode, importnode, importedmodnode, - importedasname): - """check relative import. node is either an Import or From node, modname - the imported module name. - """ - if 'W0403' not in self.active_msgs: - return - if importedmodnode.file is None: - return False # built-in module - if modnode is importedmodnode: - return False # module importing itself - if modnode.absolute_import_activated() or getattr(importnode, 'level', None): - return False - if importedmodnode.name != importedasname: - # this must be a relative import... - self.add_message('W0403', args=(importedasname, importedmodnode.name), - node=importnode) - - def _add_imported_module(self, node, importedmodname): - """notify an imported module, used to analyze dependencies""" - importedmodname = get_module_part(importedmodname) - context_name = node.root().name - if context_name == importedmodname: - # module importing itself ! - self.add_message('W0406', node=node) - elif not is_standard_module(importedmodname): - # handle dependencies - importedmodnames = self.stats['dependencies'].setdefault( - importedmodname, set()) - if not context_name in importedmodnames: - importedmodnames.add(context_name) - # update import graph - mgraph = self.import_graph.setdefault(context_name, set()) - if not importedmodname in mgraph: - mgraph.add(importedmodname) - - def _check_deprecated_module(self, node, mod_path): - """check if the module is deprecated""" - for mod_name in self.config.deprecated_modules: - if mod_path == mod_name or mod_path.startswith(mod_name + '.'): - self.add_message('W0402', node=node, args=mod_path) - - def _check_reimport(self, node, name, basename=None, level=None): - """check if the import is necessary (i.e. not already done)""" - if 'W0404' not in self.active_msgs: - return - frame = node.frame() - root = node.root() - contexts = [(frame, level)] - if root is not frame: - contexts.append((root, None)) - for context, level in contexts: - first = get_first_import(node, context, name, basename, level) - if first is not None: - self.add_message('W0404', node=node, - args=(name, first.fromlineno)) - - - def report_external_dependencies(self, sect, _, dummy): - """return a verbatim layout for displaying dependencies""" - dep_info = make_tree_defs(self._external_dependencies_info().iteritems()) - 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: - package = self.linter.base_name - self.__ext_dep_info = result = {} - for importee, importers in self.stats['dependencies'].iteritems(): - if not importee.startswith(package): - result[importee] = importers - 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: - package = self.linter.base_name - self.__int_dep_info = result = {} - for importee, importers in self.stats['dependencies'].iteritems(): - if importee.startswith(package): - result[importee] = importers - return self.__int_dep_info - - -def register(linter): - """required method to auto register this checker """ - linter.register_checker(ImportsChecker(linter)) diff --git a/checkers/logging.py b/checkers/logging.py deleted file mode 100644 index cc8967d..0000000 --- a/checkers/logging.py +++ /dev/null @@ -1,206 +0,0 @@ -# Copyright (c) 2009-2010 Google, Inc. -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free Software -# Foundation; either version 2 of the License, or (at your option) any later -# version. -# -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -"""checker for use of Python logging -""" - -import astroid -from pylint import checkers -from pylint import interfaces -from pylint.checkers import utils -from pylint.checkers.utils import check_messages - -MSGS = { - 'W1201': ('Specify string format arguments as logging function parameters', - 'logging-not-lazy', - 'Used when a logging statement has a call form of ' - '"logging.<logging method>(format_string % (format_args...))". ' - 'Such calls should leave string interpolation to the logging ' - 'method itself and be written ' - '"logging.<logging method>(format_string, format_args...)" ' - 'so that the program may avoid incurring the cost of the ' - 'interpolation in those cases in which no message will be ' - 'logged. For more, see ' - 'http://www.python.org/dev/peps/pep-0282/.'), - 'E1200': ('Unsupported logging format character %r (%#02x) at index %d', - 'logging-unsupported-format', - 'Used when an unsupported format character is used in a logging\ - statement format string.'), - 'E1201': ('Logging format string ends in middle of conversion specifier', - 'logging-format-truncated', - 'Used when a logging statement format string terminates before\ - the end of a conversion specifier.'), - 'E1205': ('Too many arguments for logging format string', - 'logging-too-many-args', - 'Used when a logging format string is given too few arguments.'), - 'E1206': ('Not enough arguments for logging format string', - 'logging-too-few-args', - 'Used when a logging format string is given too many arguments'), - } - - -CHECKED_CONVENIENCE_FUNCTIONS = set([ - 'critical', 'debug', 'error', 'exception', 'fatal', 'info', 'warn', - 'warning']) - - -class LoggingChecker(checkers.BaseChecker): - """Checks use of the logging module.""" - - __implements__ = interfaces.IAstroidChecker - name = 'logging' - msgs = MSGS - - options = (('logging-modules', - {'default' : ('logging',), - 'type' : 'csv', - 'metavar' : '<comma separated list>', - 'help' : ('Logging modules to check that the string format ' - 'arguments are in logging function parameter format')} - ), - ) - - def visit_module(self, unused_node): - """Clears any state left in this checker from last module checked.""" - # The code being checked can just as easily "import logging as foo", - # so it is necessary to process the imports and store in this field - # what name the logging module is actually given. - self._logging_names = set() - logging_mods = self.config.logging_modules - - self._logging_modules = set(logging_mods) - self._from_imports = {} - for logging_mod in logging_mods: - parts = logging_mod.rsplit('.', 1) - if len(parts) > 1: - self._from_imports[parts[0]] = parts[1] - - def visit_from(self, node): - """Checks to see if a module uses a non-Python logging module.""" - try: - logging_name = self._from_imports[node.modname] - for module, as_name in node.names: - if module == logging_name: - self._logging_names.add(as_name or module) - except KeyError: - pass - - def visit_import(self, node): - """Checks to see if this module uses Python's built-in logging.""" - for module, as_name in node.names: - if module in self._logging_modules: - self._logging_names.add(as_name or module) - - @check_messages(*(MSGS.keys())) - def visit_callfunc(self, node): - """Checks calls to (simple forms of) logging methods.""" - if (not isinstance(node.func, astroid.Getattr) - or not isinstance(node.func.expr, astroid.Name)): - return - try: - logger_class = [inferred for inferred in node.func.expr.infer() if ( - isinstance(inferred, astroid.Instance) - and any(ancestor for ancestor in inferred._proxied.ancestors() if ( - ancestor.name == 'Logger' - and ancestor.parent.name == 'logging')))] - except astroid.exceptions.InferenceError: - return - if node.func.expr.name not in self._logging_names and not logger_class: - return - self._check_convenience_methods(node) - self._check_log_methods(node) - - def _check_convenience_methods(self, node): - """Checks calls to logging convenience methods (like logging.warn).""" - if node.func.attrname not in CHECKED_CONVENIENCE_FUNCTIONS: - return - if node.starargs or node.kwargs or not node.args: - # Either no args, star args, or double-star args. Beyond the - # scope of this checker. - return - if isinstance(node.args[0], astroid.BinOp) and node.args[0].op == '%': - self.add_message('W1201', node=node) - elif isinstance(node.args[0], astroid.Const): - self._check_format_string(node, 0) - - def _check_log_methods(self, node): - """Checks calls to logging.log(level, format, *format_args).""" - if node.func.attrname != 'log': - return - if node.starargs or node.kwargs or len(node.args) < 2: - # Either a malformed call, star args, or double-star args. Beyond - # the scope of this checker. - return - if isinstance(node.args[1], astroid.BinOp) and node.args[1].op == '%': - self.add_message('W1201', node=node) - elif isinstance(node.args[1], astroid.Const): - self._check_format_string(node, 1) - - def _check_format_string(self, node, format_arg): - """Checks that format string tokens match the supplied arguments. - - Args: - node: AST node to be checked. - format_arg: Index of the format string in the node arguments. - """ - num_args = _count_supplied_tokens(node.args[format_arg + 1:]) - if not num_args: - # If no args were supplied, then all format strings are valid - - # don't check any further. - return - format_string = node.args[format_arg].value - if not isinstance(format_string, basestring): - # If the log format is constant non-string (e.g. logging.debug(5)), - # ensure there are no arguments. - required_num_args = 0 - else: - try: - keyword_args, required_num_args = \ - utils.parse_format_string(format_string) - if keyword_args: - # Keyword checking on logging strings is complicated by - # special keywords - out of scope. - return - except utils.UnsupportedFormatCharacter, ex: - char = format_string[ex.index] - self.add_message('E1200', node=node, - args=(char, ord(char), ex.index)) - return - except utils.IncompleteFormatString: - self.add_message('E1201', node=node) - return - if num_args > required_num_args: - self.add_message('E1205', node=node) - elif num_args < required_num_args: - self.add_message('E1206', node=node) - - -def _count_supplied_tokens(args): - """Counts the number of tokens in an args list. - - The Python log functions allow for special keyword arguments: func, - exc_info and extra. To handle these cases correctly, we only count - arguments that aren't keywords. - - Args: - args: List of AST nodes that are arguments for a log format string. - - Returns: - Number of AST nodes that aren't keywords. - """ - return sum(1 for arg in args if not isinstance(arg, astroid.Keyword)) - - -def register(linter): - """Required method to auto-register this checker.""" - linter.register_checker(LoggingChecker(linter)) diff --git a/checkers/misc.py b/checkers/misc.py deleted file mode 100644 index 9c49825..0000000 --- a/checkers/misc.py +++ /dev/null @@ -1,90 +0,0 @@ -# pylint: disable=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., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -""" Copyright (c) 2000-2010 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) -""" - -import re - -from pylint.interfaces import IRawChecker -from pylint.checkers import BaseChecker - - -MSGS = { - 'W0511': ('%s', - 'fixme', - 'Used when a warning note as FIXME or XXX is detected.'), - 'W0512': ('Cannot decode using encoding "%s", unexpected byte at position %d', - 'invalid-encoded-data', - 'Used when a source line cannot be decoded using the specified ' - 'source file encoding.', - {'maxversion': (3, 0)}), - } - - -class EncodingChecker(BaseChecker): - """checks for: - * warning notes in the code like FIXME, XXX - * encoding issues. - """ - __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.' - }), - ) - - def _check_note(self, notes, lineno, line): - match = notes.search(line) - if match: - self.add_message('W0511', args=line[match.start():-1], line=lineno) - - def _check_encoding(self, lineno, line, file_encoding): - try: - return unicode(line, file_encoding) - except UnicodeDecodeError, ex: - self.add_message('W0512', line=lineno, - args=(file_encoding, ex.args[2])) - - def process_module(self, module): - """inspect the source file to find encoding problem or fixmes like - notes - """ - stream = module.file_stream - stream.seek(0) # XXX may be removed with astroid > 0.23 - if self.config.notes: - notes = re.compile('|'.join(self.config.notes)) - else: - notes = None - if module.file_encoding: - encoding = module.file_encoding - else: - encoding = 'ascii' - for lineno, line in enumerate(stream): - line = self._check_encoding(lineno+1, line, encoding) - if line is not None and notes: - self._check_note(notes, lineno+1, line) - -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 deleted file mode 100644 index e583126..0000000 --- a/checkers/newstyle.py +++ /dev/null @@ -1,137 +0,0 @@ -# Copyright (c) 2005-2014 LOGILAB S.A. (Paris, FRANCE). -# http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free Software -# Foundation; either version 2 of the License, or (at your option) any later -# version. -# -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -"""check for new / old style related problems -""" -import sys - -import astroid - -from pylint.interfaces import IAstroidChecker -from pylint.checkers import BaseChecker -from pylint.checkers.utils import check_messages - -MSGS = { - 'E1001': ('Use of __slots__ on an old style class', - 'slots-on-old-class', - 'Used when an old style class uses the __slots__ attribute.'), - 'E1002': ('Use of super on an old style class', - 'super-on-old-class', - 'Used when an old style class uses the super builtin.'), - 'E1003': ('Bad first argument %r given to super()', - 'bad-super-call', - 'Used when another argument than the current class is given as \ - first argument of the super builtin.'), - 'E1004': ('Missing argument to super()', - 'missing-super-argument', - 'Used when the super builtin didn\'t receive an \ - argument on Python 2'), - 'W1001': ('Use of "property" on an old style class', - 'property-on-old-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'), - 'C1001': ('Old-style class defined.', - 'old-style-class', - 'Used when a class is defined that does not inherit from another' - 'class and does not inherit explicitly from "object".') - } - - -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 - """ - - __implements__ = (IAstroidChecker,) - - # configuration section name - name = 'newstyle' - # messages - msgs = MSGS - priority = -2 - # configuration options - options = () - - @check_messages('E1001', 'C1001') - def visit_class(self, node): - """check __slots__ usage - """ - if '__slots__' in node and not node.newstyle: - self.add_message('E1001', node=node) - # The node type could be class, exception, metaclass, or - # interface. Presumably, the non-class-type nodes would always - # have an explicit base class anyway. - if not node.bases and node.type == 'class': - self.add_message('C1001', node=node) - - @check_messages('W1001') - def visit_callfunc(self, node): - """check property usage""" - parent = node.parent.frame() - if (isinstance(parent, astroid.Class) and - not parent.newstyle and - isinstance(node.func, astroid.Name)): - name = node.func.name - if name == 'property': - self.add_message('W1001', node=node) - - @check_messages('E1002', 'E1003', 'E1004') - 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(astroid.CallFunc): - expr = stmt.func - if not isinstance(expr, astroid.Getattr): - continue - call = expr.expr - # skip the test if using super - if isinstance(call, astroid.CallFunc) and \ - isinstance(call.func, astroid.Name) and \ - call.func.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 - if not call.args and sys.version_info[0] == 3: - # unless Python 3 - continue - - try: - supcls = (call.args and call.args[0].infer().next() - or None) - except astroid.InferenceError: - continue - - if supcls is None: - self.add_message('missing-super-argument', node=call) - continue - - if klass is not supcls: - self.add_message('E1003', node=call, - args=(call.args[0].name, )) - - -def register(linter): - """required method to auto register this checker """ - import sys - if sys.version_info < (3, 0): - linter.register_checker(NewStyleConflictChecker(linter)) diff --git a/checkers/similar.py b/checkers/similar.py deleted file mode 100644 index cf671bf..0000000 --- a/checkers/similar.py +++ /dev/null @@ -1,365 +0,0 @@ -# pylint: disable=W0622 -# Copyright (c) 2004-2013 LOGILAB S.A. (Paris, FRANCE). -# http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free Software -# Foundation; either version 2 of the License, or (at your option) any later -# version. -# -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -"""a similarities / code duplication command line tool and pylint checker -""" -import sys -from itertools import izip - -from logilab.common.ureports import Table - -from pylint.interfaces import IRawChecker -from pylint.checkers import BaseChecker, table_lines_from_stats - - -class Similar(object): - """finds copy-pasted lines of code in a project""" - - def __init__(self, min_lines=4, ignore_comments=False, - ignore_docstrings=False, ignore_imports=False): - self.min_lines = min_lines - self.ignore_comments = ignore_comments - self.ignore_docstrings = ignore_docstrings - self.ignore_imports = ignore_imports - self.linesets = [] - - def append_stream(self, streamid, stream, encoding=None): - """append a file to search for similarities""" - stream.seek(0) # XXX may be removed with astroid > 0.23 - if encoding is None: - readlines = stream.readlines - else: - readlines = lambda: [line.decode(encoding) for line in stream] - try: - self.linesets.append(LineSet(streamid, - readlines(), - self.ignore_comments, - self.ignore_docstrings, - self.ignore_imports)) - except UnicodeDecodeError: - pass - - 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 = sorted(couples) - for lineset, idx in couples: - print "==%s:%s" % (lineset.name, idx) - # pylint: disable=W0631 - for line in lineset._real_lines[idx:idx+num]: - print " ", line.rstrip() - 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=%.2f" \ - % (nb_total_lignes, nb_lignes_dupliquees, - nb_lignes_dupliquees*100. / 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, ignore_imports): - """return lines with leading/trailing whitespace and any ignored code - features removed - """ - - 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 = '' - if ignore_imports: - if line.startswith("import ") or line.startswith("from "): - line = '' - if ignore_comments: - # XXX should use regex in checkers/format to avoid cutting - # at a "#" in a string - line = line.split('#', 1)[0].strip() - strippedlines.append(line) - return strippedlines - - -class LineSet(object): - """Holds and indexes all the lines of a single source file""" - def __init__(self, name, lines, ignore_comments=False, - ignore_docstrings=False, ignore_imports=False): - self.name = name - self._real_lines = lines - self._stripped_lines = stripped_lines(lines, ignore_comments, - ignore_docstrings, - ignore_imports) - 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 __lt__(self, other): - return 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', - 'duplicate-code', - '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 experiment 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.'} - ), - ('ignore-imports', - {'default' : False, 'type' : 'yn', 'metavar' : '<y or n>', - 'help': 'Ignore imports when computing similarities.'} - ), - ) - # reports - reports = (('RP0801', '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, optname, value, action=None, optdict=None): - """method called to set an option (registered in the options list) - - overridden to report options setting to Similar - """ - BaseChecker.set_option(self, optname, value, action, optdict) - if optname == 'min-similarity-lines': - self.min_lines = self.config.min_similarity_lines - elif optname == 'ignore-comments': - self.ignore_comments = self.config.ignore_comments - elif optname == 'ignore-docstrings': - self.ignore_docstrings = self.config.ignore_docstrings - elif optname == 'ignore-imports': - self.ignore_imports = self.config.ignore_imports - - 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, node): - """process a module - - the module's content is accessible via the stream object - - stream must implement the readlines method - """ - self.append_stream(self.linter.current_name, node.file_stream, node.file_encoding) - - 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() - # pylint: disable=W0631 - 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: symilar [-d|--duplicates min_duplicated_lines] \ -[-i|--ignore-comments] [--ignore-docstrings] [--ignore-imports] file1...' - sys.exit(status) - -def Run(argv=None): - """standalone command line access point""" - if argv is None: - argv = sys.argv[1:] - from getopt import getopt - s_opts = 'hdi' - l_opts = ('help', 'duplicates=', 'ignore-comments', 'ignore-imports', - 'ignore-docstrings') - min_lines = 4 - ignore_comments = False - ignore_docstrings = False - ignore_imports = 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 in ('-i', '--ignore-comments'): - ignore_comments = True - elif opt in ('--ignore-docstrings',): - ignore_docstrings = True - elif opt in ('--ignore-imports',): - ignore_imports = True - if not args: - usage(1) - sim = Similar(min_lines, ignore_comments, ignore_docstrings, ignore_imports) - for filename in args: - sim.append_stream(filename, open(filename)) - sim.run() - sys.exit(0) - -if __name__ == '__main__': - Run() diff --git a/checkers/stdlib.py b/checkers/stdlib.py deleted file mode 100644 index 8cb78f4..0000000 --- a/checkers/stdlib.py +++ /dev/null @@ -1,69 +0,0 @@ -# Copyright 2012 Google Inc. -# -# http://www.logilab.fr/ -- mailto:contact@logilab.fr -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free Software -# Foundation; either version 2 of the License, or (at your option) any later -# version. -# -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -"""Checkers for various standard library functions.""" - -import re -import sys - -import astroid - -from pylint.interfaces import IAstroidChecker -from pylint.checkers import BaseChecker -from pylint.checkers import utils - -_VALID_OPEN_MODE_REGEX = r'^(r?U|[rwa]\+?b?)$' - -if sys.version_info >= (3, 0): - OPEN_MODULE = '_io' -else: - OPEN_MODULE = '__builtin__' - -class OpenModeChecker(BaseChecker): - __implements__ = (IAstroidChecker,) - name = 'open_mode' - - msgs = { - 'W1501': ('"%s" is not a valid mode for open.', - 'bad-open-mode', - 'Python supports: r, w, a modes with b, +, and U options. ' - 'See http://docs.python.org/2/library/functions.html#open'), - } - - @utils.check_messages('W1501') - def visit_callfunc(self, node): - """Visit a CallFunc node.""" - if hasattr(node, 'func'): - infer = utils.safe_infer(node.func) - if infer and infer.root().name == OPEN_MODULE: - if getattr(node.func, 'name', None) in ('open', 'file'): - self._check_open_mode(node) - - def _check_open_mode(self, node): - """Check that the mode argument of an open or file call is valid.""" - try: - mode_arg = utils.get_argument_from_call(node, position=1, keyword='mode') - if mode_arg: - mode_arg = utils.safe_infer(mode_arg) - if (isinstance(mode_arg, astroid.Const) - and not re.match(_VALID_OPEN_MODE_REGEX, mode_arg.value)): - self.add_message('W1501', node=node, args=(mode_arg.value)) - except (utils.NoSuchArgumentError, TypeError): - pass - -def register(linter): - """required method to auto register this checker """ - linter.register_checker(OpenModeChecker(linter)) - diff --git a/checkers/strings.py b/checkers/strings.py deleted file mode 100644 index 663d61d..0000000 --- a/checkers/strings.py +++ /dev/null @@ -1,301 +0,0 @@ -# Copyright (c) 2009-2010 Arista Networks, Inc. - James Lingard -# Copyright (c) 2004-2013 LOGILAB S.A. (Paris, FRANCE). -# Copyright 2012 Google Inc. -# -# http://www.logilab.fr/ -- mailto:contact@logilab.fr -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free Software -# Foundation; either version 2 of the License, or (at your option) any later -# version. -# -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -"""Checker for string formatting operations. -""" - -import sys -import tokenize - -import astroid - -from pylint.interfaces import ITokenChecker, IAstroidChecker -from pylint.checkers import BaseChecker, BaseTokenChecker -from pylint.checkers import utils -from pylint.checkers.utils import check_messages - -_PY3K = sys.version_info >= (3, 0) - -MSGS = { - 'E1300': ("Unsupported format character %r (%#02x) at index %d", - "bad-format-character", - "Used when a unsupported format character is used in a format\ - string."), - 'E1301': ("Format string ends in middle of conversion specifier", - "truncated-format-string", - "Used when a format string terminates before the end of a \ - conversion specifier."), - 'E1302': ("Mixing named and unnamed conversion specifiers in format string", - "mixed-format-string", - "Used when a format string contains both named (e.g. '%(foo)d') \ - and unnamed (e.g. '%d') conversion specifiers. This is also \ - used when a named conversion specifier contains * for the \ - minimum field width and/or precision."), - 'E1303': ("Expected mapping for format string, not %s", - "format-needs-mapping", - "Used when a format string that uses named conversion specifiers \ - is used with an argument that is not a mapping."), - 'W1300': ("Format string dictionary key should be a string, not %s", - "bad-format-string-key", - "Used when a format string that uses named conversion specifiers \ - is used with a dictionary whose keys are not all strings."), - 'W1301': ("Unused key %r in format string dictionary", - "unused-format-string-key", - "Used when a format string that uses named conversion specifiers \ - is used with a dictionary that conWtains keys not required by the \ - format string."), - 'E1304': ("Missing key %r in format string dictionary", - "missing-format-string-key", - "Used when a format string that uses named conversion specifiers \ - is used with a dictionary that doesn't contain all the keys \ - required by the format string."), - 'E1305': ("Too many arguments for format string", - "too-many-format-args", - "Used when a format string that uses unnamed conversion \ - specifiers is given too many arguments."), - 'E1306': ("Not enough arguments for format string", - "too-few-format-args", - "Used when a format string that uses unnamed conversion \ - specifiers is given too few arguments"), - } - -OTHER_NODES = (astroid.Const, astroid.List, astroid.Backquote, - astroid.Lambda, astroid.Function, - astroid.ListComp, astroid.SetComp, astroid.GenExpr) - -class StringFormatChecker(BaseChecker): - """Checks string formatting operations to ensure that the format string - is valid and the arguments match the format string. - """ - - __implements__ = (IAstroidChecker,) - name = 'string' - msgs = MSGS - - @check_messages(*(MSGS.keys())) - def visit_binop(self, node): - if node.op != '%': - return - left = node.left - args = node.right - - if not (isinstance(left, astroid.Const) - and isinstance(left.value, basestring)): - return - format_string = left.value - try: - required_keys, required_num_args = \ - utils.parse_format_string(format_string) - except utils.UnsupportedFormatCharacter, e: - c = format_string[e.index] - self.add_message('bad-format-character', node=node, args=(c, ord(c), e.index)) - return - except utils.IncompleteFormatString: - self.add_message('truncated-format-string', node=node) - return - if required_keys and required_num_args: - # The format string uses both named and unnamed format - # specifiers. - self.add_message('mixed-format-string', node=node) - elif required_keys: - # The format string uses only named format specifiers. - # Check that the RHS of the % operator is a mapping object - # that contains precisely the set of keys required by the - # format string. - if isinstance(args, astroid.Dict): - keys = set() - unknown_keys = False - for k, _ in args.items: - if isinstance(k, astroid.Const): - key = k.value - if isinstance(key, basestring): - keys.add(key) - else: - self.add_message('bad-format-string-key', node=node, args=key) - else: - # One of the keys was something other than a - # constant. Since we can't tell what it is, - # supress checks for missing keys in the - # dictionary. - unknown_keys = True - if not unknown_keys: - for key in required_keys: - if key not in keys: - self.add_message('missing-format-string-key', node=node, args=key) - for key in keys: - if key not in required_keys: - self.add_message('unused-format-string-key', node=node, args=key) - elif isinstance(args, OTHER_NODES + (astroid.Tuple,)): - type_name = type(args).__name__ - self.add_message('format-needs-mapping', node=node, args=type_name) - # else: - # The RHS of the format specifier is a name or - # expression. It may be a mapping object, so - # there's nothing we can check. - else: - # The format string uses only unnamed format specifiers. - # Check that the number of arguments passed to the RHS of - # the % operator matches the number required by the format - # string. - if isinstance(args, astroid.Tuple): - num_args = len(args.elts) - elif isinstance(args, OTHER_NODES + (astroid.Dict, astroid.DictComp)): - num_args = 1 - else: - # The RHS of the format specifier is a name or - # expression. It could be a tuple of unknown size, so - # there's nothing we can check. - num_args = None - if num_args is not None: - if num_args > required_num_args: - self.add_message('too-many-format-args', node=node) - elif num_args < required_num_args: - self.add_message('too-few-format-args', node=node) - - -class StringMethodsChecker(BaseChecker): - __implements__ = (IAstroidChecker,) - name = 'string' - msgs = { - 'E1310': ("Suspicious argument in %s.%s call", - "bad-str-strip-call", - "The argument to a str.{l,r,}strip call contains a" - " duplicate character, "), - } - - @check_messages(*(MSGS.keys())) - def visit_callfunc(self, node): - func = utils.safe_infer(node.func) - if (isinstance(func, astroid.BoundMethod) - and isinstance(func.bound, astroid.Instance) - and func.bound.name in ('str', 'unicode', 'bytes') - and func.name in ('strip', 'lstrip', 'rstrip') - and node.args): - arg = utils.safe_infer(node.args[0]) - if not isinstance(arg, astroid.Const): - return - if len(arg.value) != len(set(arg.value)): - self.add_message('bad-str-strip-call', node=node, - args=(func.bound.name, func.name)) - - -class StringConstantChecker(BaseTokenChecker): - """Check string literals""" - __implements__ = (ITokenChecker,) - name = 'string_constant' - msgs = { - 'W1401': ('Anomalous backslash in string: \'%s\'. ' - 'String constant might be missing an r prefix.', - 'anomalous-backslash-in-string', - 'Used when a backslash is in a literal string but not as an ' - 'escape.'), - 'W1402': ('Anomalous Unicode escape in byte string: \'%s\'. ' - 'String constant might be missing an r or u prefix.', - 'anomalous-unicode-escape-in-string', - 'Used when an escape like \\u is encountered in a byte ' - 'string where it has no effect.'), - } - - # Characters that have a special meaning after a backslash in either - # Unicode or byte strings. - ESCAPE_CHARACTERS = 'abfnrtvx\n\r\t\\\'\"01234567' - - # TODO(mbp): Octal characters are quite an edge case today; people may - # prefer a separate warning where they occur. \0 should be allowed. - - # Characters that have a special meaning after a backslash but only in - # Unicode strings. - UNICODE_ESCAPE_CHARACTERS = 'uUN' - - def process_tokens(self, tokens): - for (tok_type, token, (start_row, start_col), _, _) in tokens: - if tok_type == tokenize.STRING: - # 'token' is the whole un-parsed token; we can look at the start - # of it to see whether it's a raw or unicode string etc. - self.process_string_token(token, start_row, start_col) - - def process_string_token(self, token, start_row, start_col): - for i, c in enumerate(token): - if c in '\'\"': - quote_char = c - break - prefix = token[:i].lower() # markers like u, b, r. - after_prefix = token[i:] - if after_prefix[:3] == after_prefix[-3:] == 3 * quote_char: - string_body = after_prefix[3:-3] - else: - string_body = after_prefix[1:-1] # Chop off quotes - # No special checks on raw strings at the moment. - if 'r' not in prefix: - self.process_non_raw_string_token(prefix, string_body, - start_row, start_col) - - def process_non_raw_string_token(self, prefix, string_body, start_row, - start_col): - """check for bad escapes in a non-raw string. - - prefix: lowercase string of eg 'ur' string prefix markers. - string_body: the un-parsed body of the string, not including the quote - marks. - start_row: integer line number in the source. - start_col: integer column number in the source. - """ - # Walk through the string; if we see a backslash then escape the next - # character, and skip over it. If we see a non-escaped character, - # alert, and continue. - # - # Accept a backslash when it escapes a backslash, or a quote, or - # end-of-line, or one of the letters that introduce a special escape - # sequence <http://docs.python.org/reference/lexical_analysis.html> - # - # TODO(mbp): Maybe give a separate warning about the rarely-used - # \a \b \v \f? - # - # TODO(mbp): We could give the column of the problem character, but - # add_message doesn't seem to have a way to pass it through at present. - i = 0 - while True: - i = string_body.find('\\', i) - if i == -1: - break - # There must be a next character; having a backslash at the end - # of the string would be a SyntaxError. - next_char = string_body[i+1] - match = string_body[i:i+2] - if next_char in self.UNICODE_ESCAPE_CHARACTERS: - if 'u' in prefix: - pass - elif _PY3K and 'b' not in prefix: - pass # unicode by default - else: - self.add_message('anomalous-unicode-escape-in-string', - line=start_row, args=(match, )) - elif next_char not in self.ESCAPE_CHARACTERS: - self.add_message('anomalous-backslash-in-string', - line=start_row, args=(match, )) - # Whether it was a valid escape or not, backslash followed by - # another character can always be consumed whole: the second - # character can never be the start of a new backslash escape. - i += 2 - - - -def register(linter): - """required method to auto register this checker """ - linter.register_checker(StringFormatChecker(linter)) - linter.register_checker(StringMethodsChecker(linter)) - linter.register_checker(StringConstantChecker(linter)) diff --git a/checkers/typecheck.py b/checkers/typecheck.py deleted file mode 100644 index a775e6c..0000000 --- a/checkers/typecheck.py +++ /dev/null @@ -1,450 +0,0 @@ -# Copyright (c) 2006-2013 LOGILAB S.A. (Paris, FRANCE). -# http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free Software -# Foundation; either version 2 of the License, or (at your option) any later -# version. -# -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -"""try to find more bugs in the code using astroid inference capabilities -""" - -import re -import shlex - -import astroid -from astroid import InferenceError, NotFoundError, YES, Instance - -from pylint.interfaces import IAstroidChecker -from pylint.checkers import BaseChecker -from pylint.checkers.utils import safe_infer, is_super, check_messages - -MSGS = { - 'E1101': ('%s %r has no %r member', - 'no-member', - 'Used when a variable is accessed for an unexistent member.'), - 'E1102': ('%s is not callable', - 'not-callable', - 'Used when an object being called has been inferred to a non \ - callable object'), - 'E1103': ('%s %r has no %r member (but some types could not be inferred)', - 'maybe-no-member', - 'Used when a variable is accessed for an unexistent member, but \ - astroid was not able to interpret all possible types of this \ - variable.'), - 'E1111': ('Assigning to function call which doesn\'t return', - 'assignment-from-no-return', - 'Used when an assignment is done on a function call but the \ - inferred function doesn\'t return anything.'), - 'W1111': ('Assigning to function call which only returns None', - 'assignment-from-none', - 'Used when an assignment is done on a function call but the \ - inferred function returns nothing but None.'), - - 'E1120': ('No value for argument %s in %s call', - 'no-value-for-parameter', - 'Used when a function call passes too few arguments.'), - 'E1121': ('Too many positional arguments for %s call', - 'too-many-function-args', - 'Used when a function call passes too many positional \ - arguments.'), - 'E1122': ('Duplicate keyword argument %r in %s call', - 'duplicate-keyword-arg', - 'Used when a function call passes the same keyword argument \ - multiple times.', - {'maxversion': (2, 6)}), - 'E1123': ('Unexpected keyword argument %r in %s call', - 'unexpected-keyword-arg', - 'Used when a function call passes a keyword argument that \ - doesn\'t correspond to one of the function\'s parameter names.'), - 'E1124': ('Argument %r passed by position and keyword in %s call', - 'redundant-keyword-arg', - 'Used when a function call would result in assigning multiple \ - values to a function parameter, one value from a positional \ - argument and one from a keyword argument.'), - 'E1125': ('Missing mandatory keyword argument %r in %s call', - 'missing-kwoa', - ('Used when a function call does not pass a mandatory' - ' keyword-only argument.'), - {'minversion': (3, 0)}), - } - -def _determine_callable(callable_obj): - # Note that BoundMethod is a subclass of UnboundMethod (huh?), so must - # come first in this 'if..else'. - if isinstance(callable_obj, astroid.BoundMethod): - # Bound methods have an extra implicit 'self' argument. - return callable_obj, 1, 'method' - elif isinstance(callable_obj, astroid.UnboundMethod): - if callable_obj.decorators is not None: - for d in callable_obj.decorators.nodes: - if isinstance(d, astroid.Name) and d.name == 'classmethod': - # Class methods have an extra implicit 'cls' argument. - return called, 1, 'class method' - elif isinstance(d, astroid.Name) and d.name == 'staticmethod': - return called, 0, 'static method' - else: - return callable_obj, 0, 'unbound method' - elif isinstance(callable_obj, astroid.Function): - return callable_obj, 0, 'function' - elif isinstance(callable_obj, astroid.Lambda): - return callable_obj, 0, 'lambda' - elif isinstance(callable_obj, astroid.Class): - # Class instantiation, lookup __new__ instead. - # If we only find object.__new__, we can safely check __init__ - # instead. - try: - # Use the last definition of __new__. - new = callable_obj.local_attr('__new__')[-1] - except astroid.NotFoundError: - new = None - - if not new or new.parent.name == 'object': - try: - # Use the last definition of __init__. - callable_obj = callable_obj.local_attr('__init__')[-1] - except astroid.NotFoundError: - # do nothing, covered by no-init. - raise ValueError - else: - callable_obj = new - - if not isinstance(callable_obj, astroid.Function): - raise ValueError - # both have an extra implicit 'cls'/'self' argument. - return callable_obj, 1, 'constructor' - else: - raise ValueError - -class TypeChecker(BaseChecker): - """try to find bugs in the code using type inference - """ - - __implements__ = (IAstroidChecker,) - - # 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 whether missing members accessed in mixin \ -class should be ignored. A mixin class is detected if its name ends with \ -"mixin" (case insensitive).'} - ), - - ('ignored-classes', - {'default' : ('SQLObject',), - 'type' : 'csv', - 'metavar' : '<members names>', - 'help' : 'List of classes names for which member attributes \ -should not be checked (useful for classes with attributes dynamically set).'} - ), - - ('zope', - {'default' : False, 'type' : 'yn', 'metavar': '<y_or_n>', - 'help' : 'When zope mode is activated, add a predefined set \ -of Zope acquired attributes to generated-members.'} - ), - ('generated-members', - {'default' : ( - 'REQUEST', 'acl_users', 'aq_parent'), - 'type' : 'string', - 'metavar' : '<members names>', - 'help' : 'List of members which are set dynamically and \ -missed by pylint inference system, and so shouldn\'t trigger E0201 when \ -accessed. Python regular expressions are accepted.'} - ), - ) - - def open(self): - # do this in open since config not fully initialized in __init__ - self.generated_members = list(self.config.generated_members) - if self.config.zope: - self.generated_members.extend(('REQUEST', 'acl_users', 'aq_parent')) - - def visit_assattr(self, node): - if isinstance(node.ass_type(), astroid.AugAssign): - self.visit_getattr(node) - - def visit_delattr(self, node): - self.visit_getattr(node) - - @check_messages('no-member', 'maybe-no-member') - def visit_getattr(self, node): - """check that the accessed attribute exists - - to avoid to much false positives for now, we'll consider the code as - correct if a single of the inferred nodes has the accessed attribute. - - function/method, super call and metaclasses are ignored - """ - # generated_members may containt regular expressions - # (surrounded by quote `"` and followed by a comma `,`) - # REQUEST,aq_parent,"[a-zA-Z]+_set{1,2}"' => - # ('REQUEST', 'aq_parent', '[a-zA-Z]+_set{1,2}') - if isinstance(self.config.generated_members, str): - gen = shlex.shlex(self.config.generated_members) - gen.whitespace += ',' - gen.wordchars += '[]-+' - self.config.generated_members = tuple(tok.strip('"') for tok in gen) - for pattern in self.config.generated_members: - # attribute is marked as generated, stop here - if re.match(pattern, node.attrname): - return - try: - infered = list(node.expr.infer()) - except InferenceError: - return - # list of (node, nodename) which are missing the attribute - missingattr = set() - ignoremim = self.config.ignore_mixin_members - inference_failure = False - for owner in infered: - # skip yes object - if owner is YES: - inference_failure = True - continue - # skip None anyway - if isinstance(owner, astroid.Const) and owner.value is None: - continue - # XXX "super" / metaclass call - if is_super(owner) or getattr(owner, 'type', None) == 'metaclass': - continue - name = getattr(owner, 'name', 'None') - if name in self.config.ignored_classes: - continue - if ignoremim and name[-5:].lower() == 'mixin': - continue - try: - if not [n for n in owner.getattr(node.attrname) - if not isinstance(n.statement(), astroid.AugAssign)]: - missingattr.add((owner, name)) - continue - except AttributeError: - # XXX method / function - continue - except NotFoundError: - if isinstance(owner, astroid.Function) and owner.decorators: - continue - if isinstance(owner, Instance) and owner.has_dynamic_getattr(): - continue - # explicit skipping of optparse'Values class - if owner.name == 'Values' and owner.root().name == 'optparse': - continue - missingattr.add((owner, name)) - continue - # stop on the first found - break - else: - # we have not found any node with the attributes, display the - # message for infered nodes - done = set() - for owner, name in missingattr: - if isinstance(owner, Instance): - actual = owner._proxied - else: - actual = owner - if actual in done: - continue - done.add(actual) - if inference_failure: - msgid = 'maybe-no-member' - else: - msgid = 'no-member' - self.add_message(msgid, node=node, - args=(owner.display_type(), name, - node.attrname)) - - @check_messages('assignment-from-no-return', 'assignment-from-none') - def visit_assign(self, node): - """check that if assigning to a function call, the function is - possibly returning something valuable - """ - if not isinstance(node.value, astroid.CallFunc): - return - function_node = safe_infer(node.value.func) - # skip class, generator and incomplete function definition - if not (isinstance(function_node, astroid.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(astroid.Return, - skip_klass=astroid.Function)) - if len(returns) == 0: - self.add_message('assignment-from-no-return', node=node) - else: - for rnode in returns: - if not (isinstance(rnode.value, astroid.Const) - and rnode.value.value is None): - break - else: - self.add_message('assignment-from-none', node=node) - - @check_messages(*(MSGS.keys())) - def visit_callfunc(self, node): - """check that called functions/methods are inferred to callable objects, - and that the arguments passed to the function match the parameters in - the inferred function's definition - """ - # Build the set of keyword arguments, checking for duplicate keywords, - # and count the positional arguments. - keyword_args = set() - num_positional_args = 0 - for arg in node.args: - if isinstance(arg, astroid.Keyword): - keyword = arg.arg - if keyword in keyword_args: - self.add_message('duplicate-keyword-arg', node=node, args=keyword) - keyword_args.add(keyword) - else: - num_positional_args += 1 - - called = safe_infer(node.func) - # only function, generator and object defining __call__ are allowed - if called is not None and not called.callable(): - self.add_message('not-callable', node=node, args=node.func.as_string()) - - try: - called, implicit_args, callable_name = _determine_callable(called) - except ValueError: - # Any error occurred during determining the function type, most of - # those errors are handled by different warnings. - return - num_positional_args += implicit_args - if called.args.args is None: - # Built-in functions have no argument information. - return - - if len(called.argnames()) != len(set(called.argnames())): - # Duplicate parameter name (see E9801). We can't really make sense - # of the function call in this case, so just return. - return - - # Analyze the list of formal parameters. - num_mandatory_parameters = len(called.args.args) - len(called.args.defaults) - parameters = [] - parameter_name_to_index = {} - for i, arg in enumerate(called.args.args): - if isinstance(arg, astroid.Tuple): - name = None - # Don't store any parameter names within the tuple, since those - # are not assignable from keyword arguments. - else: - if isinstance(arg, astroid.Keyword): - name = arg.arg - else: - assert isinstance(arg, astroid.AssName) - # This occurs with: - # def f( (a), (b) ): pass - name = arg.name - parameter_name_to_index[name] = i - if i >= num_mandatory_parameters: - defval = called.args.defaults[i - num_mandatory_parameters] - else: - defval = None - parameters.append([(name, defval), False]) - - kwparams = {} - for i, arg in enumerate(called.args.kwonlyargs): - if isinstance(arg, astroid.Keyword): - name = arg.arg - else: - assert isinstance(arg, astroid.AssName) - name = arg.name - kwparams[name] = [called.args.kw_defaults[i], False] - - # Match the supplied arguments against the function parameters. - - # 1. Match the positional arguments. - for i in range(num_positional_args): - if i < len(parameters): - parameters[i][1] = True - elif called.args.vararg is not None: - # The remaining positional arguments get assigned to the *args - # parameter. - break - else: - # Too many positional arguments. - self.add_message('too-many-function-args', node=node, args=(callable_name,)) - break - - # 2. Match the keyword arguments. - for keyword in keyword_args: - if keyword in parameter_name_to_index: - i = parameter_name_to_index[keyword] - if parameters[i][1]: - # Duplicate definition of function parameter. - self.add_message('redundant-keyword-arg', node=node, args=(keyword, callable_name)) - else: - parameters[i][1] = True - elif keyword in kwparams: - if kwparams[keyword][1]: # XXX is that even possible? - # Duplicate definition of function parameter. - self.add_message('redundant-keyword-arg', node=node, args=(keyword, callable_name)) - else: - kwparams[keyword][1] = True - elif called.args.kwarg is not None: - # The keyword argument gets assigned to the **kwargs parameter. - pass - else: - # Unexpected keyword argument. - self.add_message('unexpected-keyword-arg', node=node, args=(keyword, callable_name)) - - # 3. Match the *args, if any. Note that Python actually processes - # *args _before_ any keyword arguments, but we wait until after - # looking at the keyword arguments so as to make a more conservative - # guess at how many values are in the *args sequence. - if node.starargs is not None: - for i in range(num_positional_args, len(parameters)): - [(name, defval), assigned] = parameters[i] - # Assume that *args provides just enough values for all - # non-default parameters after the last parameter assigned by - # the positional arguments but before the first parameter - # assigned by the keyword arguments. This is the best we can - # get without generating any false positives. - if (defval is not None) or assigned: - break - parameters[i][1] = True - - # 4. Match the **kwargs, if any. - if node.kwargs is not None: - for i, [(name, defval), assigned] in enumerate(parameters): - # Assume that *kwargs provides values for all remaining - # unassigned named parameters. - if name is not None: - parameters[i][1] = True - else: - # **kwargs can't assign to tuples. - pass - - # Check that any parameters without a default have been assigned - # values. - for [(name, defval), assigned] in parameters: - if (defval is None) and not assigned: - if name is None: - display_name = '<tuple>' - else: - display_name = repr(name) - self.add_message('no-value-for-parameter', node=node, args=(display_name, callable_name)) - - for name in kwparams: - defval, assigned = kwparams[name] - if defval is None and not assigned: - self.add_message('missing-kwoa', node=node, args=(name, callable_name)) - - -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 deleted file mode 100644 index e7d85d4..0000000 --- a/checkers/utils.py +++ /dev/null @@ -1,416 +0,0 @@ -# pylint: disable=W0611 -# -# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). -# http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free Software -# Foundation; either version 2 of the License, or (at your option) any later -# version. -# -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -"""some functions that may be useful for various checkers -""" - -import re -import string - -import astroid -from astroid import scoped_nodes -from logilab.common.compat import builtins - -BUILTINS_NAME = builtins.__name__ - -COMP_NODE_TYPES = astroid.ListComp, astroid.SetComp, astroid.DictComp, astroid.GenExpr - - -class NoSuchArgumentError(Exception): - pass - -def is_inside_except(node): - """Returns true if node is inside the name of an except handler.""" - current = node - while current and not isinstance(current.parent, astroid.ExceptHandler): - current = current.parent - - return current and current is current.parent.name - - -def get_all_elements(node): - """Recursively returns all atoms in nested lists and tuples.""" - if isinstance(node, (astroid.Tuple, astroid.List)): - for child in node.elts: - for e in get_all_elements(child): - yield e - else: - yield node - - -def clobber_in_except(node): - """Checks if an assignment node in an except handler clobbers an existing - variable. - - Returns (True, args for W0623) if assignment clobbers an existing variable, - (False, None) otherwise. - """ - if isinstance(node, astroid.AssAttr): - return (True, (node.attrname, 'object %r' % (node.expr.as_string(),))) - elif isinstance(node, astroid.AssName): - name = node.name - if is_builtin(name): - return (True, (name, 'builtins')) - else: - scope, stmts = node.lookup(name) - if (stmts and - not isinstance(stmts[0].ass_type(), - (astroid.Assign, astroid.AugAssign, - astroid.ExceptHandler))): - return (True, (name, 'outer scope (line %s)' % stmts[0].fromlineno)) - return (False, None) - - -def safe_infer(node): - """return the inferred value for the given node. - Return None if inference failed or if there is some ambiguity (more than - one node has been inferred) - """ - try: - inferit = node.infer() - value = inferit.next() - except astroid.InferenceError: - return - try: - inferit.next() - return # None if there is ambiguity on the inferred node - except astroid.InferenceError: - return # there is some kind of ambiguity - except StopIteration: - return value - -def is_super(node): - """return True if the node is referencing the "super" builtin function - """ - if getattr(node, 'name', None) == 'super' and \ - node.root().name == BUILTINS_NAME: - return True - return False - -def is_error(node): - """return true if the function does nothing but raising an exception""" - for child_node in node.get_children(): - if isinstance(child_node, astroid.Raise): - return True - return False - -def is_raising(body): - """return true if the given statement node raise an exception""" - for node in body: - if isinstance(node, astroid.Raise): - return True - return False - -def is_empty(body): - """return true if the given node does nothing but 'pass'""" - return len(body) == 1 and isinstance(body[0], astroid.Pass) - -builtins = builtins.__dict__.copy() -SPECIAL_BUILTINS = ('__builtins__',) # '__path__', '__file__') - -def is_builtin_object(node): - """Returns True if the given node is an object from the __builtin__ module.""" - return node and node.root().name == BUILTINS_NAME - -def is_builtin(name): # was is_native_builtin - """return true if <name> could be considered as a builtin defined by python - """ - if name in builtins: - return True - if name in SPECIAL_BUILTINS: - return True - return False - -def is_defined_before(var_node): - """return True if the variable node is defined by a parent node (list, - set, dict, or generator comprehension, lambda) or in a previous sibling - node on 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(astroid.AssName): - if ass_node.name == varname: - return True - elif isinstance(_node, astroid.For): - for ass_node in _node.target.nodes_of_class(astroid.AssName): - if ass_node.name == varname: - return True - elif isinstance(_node, astroid.With): - for expr, vars in _node.items: - if expr.parent_of(var_node): - break - if (vars and - isinstance(vars, astroid.AssName) and - vars.name == varname): - return True - elif isinstance(_node, (astroid.Lambda, astroid.Function)): - if _node.args.is_argument(varname): - return True - if getattr(_node, 'name', None) == varname: - return True - break - elif isinstance(_node, astroid.ExceptHandler): - if isinstance(_node.name, astroid.AssName): - ass_node = _node.name - if ass_node.name == varname: - return True - _node = _node.parent - # possibly multiple statements on the same line using semi colon separator - stmt = var_node.statement() - _node = stmt.previous_sibling() - lineno = stmt.fromlineno - while _node and _node.fromlineno == lineno: - for ass_node in _node.nodes_of_class(astroid.AssName): - if ass_node.name == varname: - return True - for imp_node in _node.nodes_of_class((astroid.From, astroid.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 given Name node is used in function default argument's - value - """ - parent = node.scope() - if isinstance(parent, astroid.Function): - for default_node in parent.args.defaults: - for default_name_node in default_node.nodes_of_class(astroid.Name): - if default_name_node is node: - return True - return False - -def is_func_decorator(node): - """return true if the name is used in function decorator""" - parent = node.parent - while parent is not None: - if isinstance(parent, astroid.Decorators): - return True - if (parent.is_statement or - isinstance(parent, astroid.Lambda) or - isinstance(parent, (scoped_nodes.ComprehensionScope, - scoped_nodes.ListComp))): - break - parent = parent.parent - return False - -def is_ancestor_name(frame, node): - """return True if `frame` is a astroid.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(astroid.Name): - return True - return False - -def assign_parent(node): - """return the higher parent which is not an AssName, Tuple or List node - """ - while node and isinstance(node, (astroid.AssName, - astroid.Tuple, - astroid.List)): - 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], astroid.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 overridden from an ancestor""" - for ancestor in class_node.ancestors(): - if name in ancestor and isinstance(ancestor[name], astroid.Function): - return True - return False - -PYMETHODS = set(('__new__', '__init__', '__del__', '__hash__', - '__str__', '__repr__', - '__len__', '__iter__', - '__delete__', '__get__', '__set__', - '__getitem__', '__setitem__', '__delitem__', '__contains__', - '__getattribute__', '__getattr__', '__setattr__', '__delattr__', - '__call__', - '__enter__', '__exit__', - '__cmp__', '__ge__', '__gt__', '__le__', '__lt__', '__eq__', - '__nonzero__', '__neg__', '__invert__', - '__mul__', '__imul__', '__rmul__', - '__div__', '__idiv__', '__rdiv__', - '__add__', '__iadd__', '__radd__', - '__sub__', '__isub__', '__rsub__', - '__pow__', '__ipow__', '__rpow__', - '__mod__', '__imod__', '__rmod__', - '__and__', '__iand__', '__rand__', - '__or__', '__ior__', '__ror__', - '__xor__', '__ixor__', '__rxor__', - # XXX To be continued - )) - -def check_messages(*messages): - """decorator to store messages that are handled by a checker method""" - - def store_messages(func): - func.checks_msgs = messages - return func - return store_messages - -class IncompleteFormatString(Exception): - """A format string ended in the middle of a format specifier.""" - pass - -class UnsupportedFormatCharacter(Exception): - """A format character in a format string is not one of the supported - format characters.""" - def __init__(self, index): - Exception.__init__(self, index) - self.index = index - -def parse_format_string(format_string): - """Parses a format string, returning a tuple of (keys, num_args), where keys - is the set of mapping keys in the format string, and num_args is the number - of arguments required by the format string. Raises - IncompleteFormatString or UnsupportedFormatCharacter if a - parse error occurs.""" - keys = set() - num_args = 0 - def next_char(i): - i += 1 - if i == len(format_string): - raise IncompleteFormatString - return (i, format_string[i]) - i = 0 - while i < len(format_string): - char = format_string[i] - if char == '%': - i, char = next_char(i) - # Parse the mapping key (optional). - key = None - if char == '(': - depth = 1 - i, char = next_char(i) - key_start = i - while depth != 0: - if char == '(': - depth += 1 - elif char == ')': - depth -= 1 - i, char = next_char(i) - key_end = i - 1 - key = format_string[key_start:key_end] - - # Parse the conversion flags (optional). - while char in '#0- +': - i, char = next_char(i) - # Parse the minimum field width (optional). - if char == '*': - num_args += 1 - i, char = next_char(i) - else: - while char in string.digits: - i, char = next_char(i) - # Parse the precision (optional). - if char == '.': - i, char = next_char(i) - if char == '*': - num_args += 1 - i, char = next_char(i) - else: - while char in string.digits: - i, char = next_char(i) - # Parse the length modifier (optional). - if char in 'hlL': - i, char = next_char(i) - # Parse the conversion type (mandatory). - if char not in 'diouxXeEfFgGcrs%': - raise UnsupportedFormatCharacter(i) - if key: - keys.add(key) - elif char != '%': - num_args += 1 - i += 1 - return keys, num_args - -def is_attr_protected(attrname): - """return True if attribute name is protected (start with _ and some other - details), False otherwise. - """ - return attrname[0] == '_' and not attrname == '_' and not ( - attrname.startswith('__') and attrname.endswith('__')) - -def node_frame_class(node): - """return klass node for a method node (or a staticmethod or a - classmethod), return null otherwise - """ - klass = node.frame() - - while klass is not None and not isinstance(klass, astroid.Class): - if klass.parent is None: - klass = None - else: - klass = klass.parent.frame() - - return klass - -def is_super_call(expr): - """return True if expression node is a function call and if function name - is super. Check before that you're in a method. - """ - return (isinstance(expr, astroid.CallFunc) and - isinstance(expr.func, astroid.Name) and - expr.func.name == 'super') - -def is_attr_private(attrname): - """Check that attribute name is private (at least two leading underscores, - at most one trailing underscore) - """ - regex = re.compile('^_{2,}.*[^_]+_?$') - return regex.match(attrname) - -def get_argument_from_call(callfunc_node, position=None, keyword=None): - """Returns the specified argument from a function call. - - :param callfunc_node: Node representing a function call to check. - :param int position: position of the argument. - :param str keyword: the keyword of the argument. - - :returns: The node representing the argument, None if the argument is not found. - :raises ValueError: if both position and keyword are None. - :raises NoSuchArgumentError: if no argument at the provided position or with - the provided keyword. - """ - if position is None and keyword is None: - raise ValueError('Must specify at least one of: position or keyword.') - try: - if position is not None and not isinstance(callfunc_node.args[position], astroid.Keyword): - return callfunc_node.args[position] - except IndexError, error: - raise NoSuchArgumentError(error) - if keyword: - for arg in callfunc_node.args: - if isinstance(arg, astroid.Keyword) and arg.arg == keyword: - return arg.value - raise NoSuchArgumentError diff --git a/checkers/variables.py b/checkers/variables.py deleted file mode 100644 index 7c489e8..0000000 --- a/checkers/variables.py +++ /dev/null @@ -1,693 +0,0 @@ -# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). -# http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free Software -# Foundation; either version 2 of the License, or (at your option) any later -# version. -# -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -"""variables checkers for Python code -""" -import os -import sys -from copy import copy - -import astroid -from astroid import are_exclusive, builtin_lookup, AstroidBuildingException - -from logilab.common.modutils import file_from_modpath - -from pylint.interfaces import IAstroidChecker -from pylint.checkers import BaseChecker -from pylint.checkers.utils import (PYMETHODS, is_ancestor_name, is_builtin, - is_defined_before, is_error, is_func_default, is_func_decorator, - assign_parent, check_messages, is_inside_except, clobber_in_except, - get_all_elements) - - -def in_for_else_branch(parent, stmt): - """Returns True if stmt in inside the else branch for a parent For stmt.""" - return (isinstance(parent, astroid.For) and - any(else_stmt.parent_of(stmt) for else_stmt in parent.orelse)) - -def overridden_method(klass, name): - """get overridden method if any""" - try: - parent = klass.local_attr_ancestors(name).next() - except (StopIteration, KeyError): - return None - try: - meth_node = parent[name] - except KeyError: - # We have found an ancestor defining <name> but it's not in the local - # dictionary. This may happen with astroid built from living objects. - return None - if isinstance(meth_node, astroid.Function): - return meth_node - return None - -def _get_unpacking_extra_info(node, infered): - """return extra information to add to the message for unpacking-non-sequence - and unbalanced-tuple-unpacking errors - """ - more = '' - infered_module = infered.root().name - if node.root().name == infered_module: - if node.lineno == infered.lineno: - more = ' %s' % infered.as_string() - elif infered.lineno: - more = ' defined at line %s' % infered.lineno - elif infered.lineno: - more = ' defined at line %s of %s' % (infered.lineno, infered_module) - return more - -MSGS = { - 'E0601': ('Using variable %r before assignment', - 'used-before-assignment', - 'Used when a local variable is accessed before it\'s \ - assignment.'), - 'E0602': ('Undefined variable %r', - 'undefined-variable', - 'Used when an undefined variable is accessed.'), - 'E0603': ('Undefined variable name %r in __all__', - 'undefined-all-variable', - 'Used when an undefined variable name is referenced in __all__.'), - 'E0604': ('Invalid object %r in __all__, must contain only strings', - 'invalid-all-object', - 'Used when an invalid (non-string) object occurs in __all__.'), - 'E0611': ('No name %r in module %r', - 'no-name-in-module', - 'Used when a name cannot be found in a module.'), - - 'W0601': ('Global variable %r undefined at the module level', - 'global-variable-undefined', - '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 assignment is done', - 'global-variable-not-assigned', - 'Used when a variable is defined through the "global" statement \ - but no assignment to this variable is done.'), - 'W0603': ('Using the global statement', # W0121 - 'global-statement', - '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 - 'global-at-module-level', - 'Used when you use the "global" statement at the module level \ - since it has no effect'), - 'W0611': ('Unused import %s', - 'unused-import', - 'Used when an imported module or variable is not used.'), - 'W0612': ('Unused variable %r', - 'unused-variable', - 'Used when a variable is defined but not used.'), - 'W0613': ('Unused argument %r', - 'unused-argument', - 'Used when a function or method argument is not used.'), - 'W0614': ('Unused import %s from wildcard import', - 'unused-wildcard-import', - 'Used when an imported module or variable is not used from a \ - \'from X import *\' style import.'), - - 'W0621': ('Redefining name %r from outer scope (line %s)', - 'redefined-outer-name', - 'Used when a variable\'s name hide a name defined in the outer \ - scope.'), - 'W0622': ('Redefining built-in %r', - 'redefined-builtin', - 'Used when a variable or function override a built-in.'), - 'W0623': ('Redefining name %r from %s in exception handler', - 'redefine-in-handler', - 'Used when an exception handler assigns the exception \ - to an existing name'), - - 'W0631': ('Using possibly undefined loop variable %r', - 'undefined-loop-variable', - '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.'), - - 'W0632': ('Possible unbalanced tuple unpacking with ' - 'sequence%s: ' - 'left side has %d label(s), right side has %d value(s)', - 'unbalanced-tuple-unpacking', - 'Used when there is an unbalanced tuple unpacking in assignment'), - - 'W0633': ('Attempting to unpack a non-sequence%s', - 'unpacking-non-sequence', - 'Used when something which is not ' - 'a sequence is used in an unpack assignment'), - - } - -class VariablesChecker(BaseChecker): - """checks for - * unused variables / imports - * undefined variables - * redefinition of variable from builtins or from an outer scope - * use of variable before assignment - * __all__ consistency - """ - - __implements__ = IAstroidChecker - - name = 'variables' - msgs = MSGS - priority = -1 - options = ( - ("init-import", - {'default': 0, 'type' : 'yn', 'metavar' : '<y_or_n>', - 'help' : 'Tells whether we should check for unused import in \ -__init__ files.'}), - ("dummy-variables-rgx", - {'default': ('_$|dummy'), - 'type' :'regexp', 'metavar' : '<regexp>', - 'help' : 'A regular expression matching the beginning of \ - the name of 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 - - def visit_module(self, node): - """visit module : update consumption analysis variable - checks globals doesn't overrides builtins - """ - self._to_consume = [(copy(node.locals), {}, 'module')] - for name, stmts in node.locals.iteritems(): - if is_builtin(name) and not is_inside_except(stmts[0]): - # do not print Redefining builtin for additional builtins - self.add_message('W0622', args=name, node=stmts[0]) - - @check_messages('W0611', 'W0614', 'W0622', 'E0603', 'E0604') - def leave_module(self, node): - """leave module: check globals - """ - assert len(self._to_consume) == 1 - not_consumed = self._to_consume.pop()[0] - # attempt to check for __all__ if defined - if '__all__' in node.locals: - assigned = node.igetattr('__all__').next() - if assigned is not astroid.YES: - for elt in getattr(assigned, 'elts', ()): - try: - elt_name = elt.infer().next() - except astroid.InferenceError: - continue - - if not isinstance(elt_name, astroid.Const) or not isinstance(elt_name.value, basestring): - self.add_message('E0604', args=elt.as_string(), node=elt) - continue - elt_name = elt.value - # If elt is in not_consumed, remove it from not_consumed - if elt_name in not_consumed: - del not_consumed[elt_name] - continue - if elt_name not in node.locals: - if not node.package: - self.add_message('undefined-all-variable', - args=elt_name, - node=elt) - else: - basename = os.path.splitext(node.file)[0] - if os.path.basename(basename) == '__init__': - name = node.name + "." + elt_name - try: - file_from_modpath(name.split(".")) - except ImportError: - self.add_message('undefined-all-variable', - args=elt_name, - node=elt) - except SyntaxError, exc: - # don't yield an syntax-error warning, - # because it will be later yielded - # when the file will be checked - pass - # don't check unused imports in __init__ files - if not self.config.init_import and node.package: - return - for name, stmts in not_consumed.iteritems(): - stmt = stmts[0] - if isinstance(stmt, astroid.Import): - self.add_message('W0611', args=name, node=stmt) - elif isinstance(stmt, astroid.From) and stmt.modname != '__future__': - if stmt.names[0][0] == '*': - self.add_message('W0614', args=name, node=stmt) - else: - self.add_message('W0611', args=name, node=stmt) - del self._to_consume - - 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_genexpr(self, node): - """visit genexpr: update consumption analysis variable - """ - self._to_consume.append((copy(node.locals), {}, 'comprehension')) - - def leave_genexpr(self, _): - """leave genexpr: update consumption analysis variable - """ - # do not check for not used locals here - self._to_consume.pop() - - def visit_dictcomp(self, node): - """visit dictcomp: update consumption analysis variable - """ - self._to_consume.append((copy(node.locals), {}, 'comprehension')) - - def leave_dictcomp(self, _): - """leave dictcomp: update consumption analysis variable - """ - # do not check for not used locals here - self._to_consume.pop() - - def visit_setcomp(self, node): - """visit setcomp: update consumption analysis variable - """ - self._to_consume.append((copy(node.locals), {}, 'comprehension')) - - def leave_setcomp(self, _): - """leave setcomp: 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 - """ - self._to_consume.append((copy(node.locals), {}, 'function')) - if not set(('W0621', 'W0622')) & self.active_msgs: - return - globs = node.root().globals - for name, stmt in node.items(): - if is_inside_except(stmt): - continue - if name in globs and not isinstance(stmt, astroid.Global): - line = globs[name][0].fromlineno - dummy_rgx = self.config.dummy_variables_rgx - if not dummy_rgx.match(name): - self.add_message('W0621', args=(name, line), node=stmt) - elif is_builtin(name): - # do not print Redefining builtin for additional builtins - self.add_message('W0622', args=name, node=stmt) - - def leave_function(self, node): - """leave function: check function's locals are consumed""" - not_consumed = self._to_consume.pop()[0] - if not set(('W0612', 'W0613')) & self.active_msgs: - return - # don't check arguments of function which are only raising an exception - if is_error(node): - return - # don't check arguments of abstract methods or within an interface - is_method = node.is_method() - klass = node.parent.frame() - if is_method and (klass.type == 'interface' or node.is_abstract()): - return - authorized_rgx = self.config.dummy_variables_rgx - called_overridden = False - argnames = node.argnames() - for name, stmts in not_consumed.iteritems(): - # 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, astroid.Global): - continue - # care about functions with unknown argument (builtins) - if name in argnames: - if is_method: - # don't warn for the first argument of a (non static) method - if node.type != 'staticmethod' and name == argnames[0]: - continue - # don't warn for argument of an overridden method - if not called_overridden: - overridden = overridden_method(klass, node.name) - called_overridden = True - if overridden is not None and name in overridden.argnames(): - continue - if node.name in PYMETHODS and node.name not in ('__init__', '__new__'): - continue - # don't check callback arguments XXX should be configurable - if node.name.startswith('cb_') or node.name.endswith('_cb'): - continue - self.add_message('W0613', args=name, node=stmt) - else: - self.add_message('W0612', args=name, node=stmt) - - @check_messages('W0601', 'W0602', 'W0603', 'W0604', 'W0622') - def visit_global(self, node): - """check names imported exists in the global scope""" - frame = node.frame() - if isinstance(frame, astroid.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 astroid.NotFoundError: - # unassigned global, skip - assign_nodes = [] - for anode in assign_nodes: - if anode.parent is None: - # node returned for builtin attribute such as __file__, - # __doc__, etc... - continue - if anode.frame() is frame: - # same scope level assignment - break - else: - # global but no assignment - self.add_message('W0602', args=name, node=node) - default_message = False - if not assign_nodes: - continue - for anode in assign_nodes: - if anode.parent is None: - self.add_message('W0622', args=name, node=node) - break - if anode.frame() is module: - # module level assignment - 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 - # XXX used to filter parents but don't remember why, and removing this - # fixes a W0631 false positive reported by Paul Hachmann on 2008/12 on - # python-projects (added to func_use_for_or_listcomp_var test) - #astmts = [stmt for stmt in node.lookup(name)[1] - # if hasattr(stmt, 'ass_type')] and - # not stmt.statement().parent_of(node)] - if 'W0631' not in self.active_msgs: - return - astmts = [stmt for stmt in node.lookup(name)[1] - if hasattr(stmt, 'ass_type')] - # filter variables according their respective scope test is_statement - # and parent to avoid #74747. This is not a total fix, which would - # introduce a mechanism similar to special attribute lookup in - # modules. Also, in order to get correct inference in this case, the - # scope lookup rules would need to be changed to return the initial - # assignment (which does not exist in code per se) as well as any later - # modifications. - if not astmts or (astmts[0].is_statement or astmts[0].parent) \ - and 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) - and not in_for_else_branch(astmts[i].statement(), stmt)): - continue - _astmts.append(stmt) - astmts = _astmts - if len(astmts) == 1: - ass = astmts[0].ass_type() - if isinstance(ass, (astroid.For, astroid.Comprehension, astroid.GenExpr)) \ - and not ass.statement() is node.statement(): - self.add_message('W0631', args=name, node=node) - - @check_messages('W0623') - def visit_excepthandler(self, node): - for name in get_all_elements(node.name): - clobbering, args = clobber_in_except(name) - if clobbering: - self.add_message('W0623', args=args, node=name) - - def visit_assname(self, node): - if isinstance(node.ass_type(), astroid.AugAssign): - self.visit_name(node) - - def visit_delname(self, node): - self.visit_name(node) - - @check_messages(*(MSGS.keys())) - def visit_name(self, node): - """check that a name is defined if the current scope and doesn't - redefine a built-in - """ - stmt = node.statement() - if stmt.fromlineno is None: - # name node from a astroid built from live code, skip - assert not stmt.root().file.endswith('.py') - return - name = node.name - frame = stmt.scope() - # if the name node is used as a function default argument's value or as - # a decorator, then start from the parent frame of the function instead - # of the function frame - and thus open an inner class scope - if (is_func_default(node) or is_func_decorator(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 - base_scope_type = self._to_consume[start_index][-1] - 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. The only exception is when the starting scope is a - # comprehension and its direct outer scope is a class - if scope_type == 'class' and i != start_index and not ( - base_scope_type == 'comprehension' and i == start_index-1): - # XXX find a way to handle class scope in a smoother way - continue - # the name has already been consumed, only check it's not a loop - # variable used outside the loop - if name in consumed: - self._loopvar_name(node, name) - break - # mark the name as consumed if it's defined in this scope - # (i.e. no KeyError is raised by "to_consume[name]") - try: - consumed[name] = to_consume[name] - except KeyError: - continue - # checks for use before assignment - 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 name in defframe.scope_attrs or 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 - else: - # check if we have a nonlocal - if name in defframe.locals: - maybee0601 = not any(isinstance(child, astroid.Nonlocal) - and name in child.names - for child in defframe.get_children()) - if (maybee0601 - and stmt.fromlineno <= defstmt.fromlineno - and not is_defined_before(node) - and not are_exclusive(stmt, defstmt, ('NameError', 'Exception', 'BaseException'))): - if defstmt is stmt and isinstance(node, (astroid.DelName, - astroid.AssName)): - self.add_message('E0602', args=name, node=node) - elif self._to_consume[-1][-1] != 'lambda': - # E0601 may *not* occurs in lambda scope - self.add_message('E0601', args=name, node=node) - if not isinstance(node, astroid.AssName): # Aug AssName - del to_consume[name] - else: - del consumed[name] - # check it's not a loop variable used outside the loop - self._loopvar_name(node, name) - break - else: - # we have not found the name, if it isn't a builtin, that's an - # undefined name ! - if not (name in astroid.Module.scope_attrs or is_builtin(name) - or name in self.config.additional_builtins): - self.add_message('E0602', args=name, node=node) - - @check_messages('E0611') - def visit_import(self, node): - """check modules attribute accesses""" - for name, _ in node.names: - parts = name.split('.') - try: - module = node.infer_name_module(parts[0]).next() - except astroid.ResolveError: - continue - self._check_module_attrs(node, module, parts[1:]) - - @check_messages('E0611') - def visit_from(self, node): - """check modules attribute accesses""" - name_parts = node.modname.split('.') - level = getattr(node, 'level', None) - try: - module = node.root().import_module(name_parts[0], level=level) - except AstroidBuildingException: - return - except Exception, exc: - print 'Unhandled exception in VariablesChecker:', exc - 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('.')) - - @check_messages('unbalanced-tuple-unpacking', 'unpacking-non-sequence') - def visit_assign(self, node): - """Check unbalanced tuple unpacking for assignments - and unpacking non-sequences. - """ - if not isinstance(node.targets[0], (astroid.Tuple, astroid.List)): - return - - targets = node.targets[0].itered() - if any(not isinstance(target_node, astroid.AssName) - for target_node in targets): - return - - try: - for infered in node.value.infer(): - self._check_unpacking(infered, node, targets) - except astroid.InferenceError: - return - - def _check_unpacking(self, infered, node, targets): - """ Check for unbalanced tuple unpacking - and unpacking non sequences. - """ - if infered is astroid.YES: - return - if isinstance(infered, (astroid.Tuple, astroid.List)): - # attempt to check unpacking is properly balanced - values = infered.itered() - if len(targets) != len(values): - self.add_message('unbalanced-tuple-unpacking', node=node, - args=(_get_unpacking_extra_info(node, infered), - len(targets), - len(values))) - # attempt to check unpacking may be possible (ie RHS is iterable) - elif isinstance(infered, astroid.Instance): - for meth in ('__iter__', '__getitem__'): - try: - infered.getattr(meth) - break - except astroid.NotFoundError: - continue - else: - self.add_message('unpacking-non-sequence', node=node, - args=(_get_unpacking_extra_info(node, infered),)) - else: - self.add_message('unpacking-non-sequence', node=node, - args=(_get_unpacking_extra_info(node, infered),)) - - - 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, astroid.Module), module - while module_names: - name = module_names.pop(0) - if name == '__dict__': - module = None - break - try: - module = module.getattr(name)[0].infer().next() - if module is astroid.YES: - return None - except astroid.NotFoundError: - self.add_message('E0611', args=(name, module.name), node=node) - return None - except astroid.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, astroid.Module): - return module - return None - - -class VariablesChecker3k(VariablesChecker): - '''Modified variables checker for 3k''' - # listcomp have now also their scope - - def visit_listcomp(self, node): - """visit dictcomp: update consumption analysis variable - """ - self._to_consume.append((copy(node.locals), {}, 'comprehension')) - - def leave_listcomp(self, _): - """leave dictcomp: update consumption analysis variable - """ - # do not check for not used locals here - self._to_consume.pop() - -if sys.version_info >= (3, 0): - VariablesChecker = VariablesChecker3k - - -def register(linter): - """required method to auto register this checker""" - linter.register_checker(VariablesChecker(linter)) diff --git a/config.py b/config.py deleted file mode 100644 index 992c293..0000000 --- a/config.py +++ /dev/null @@ -1,156 +0,0 @@ -# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free Software -# Foundation; either version 2 of the License, or (at your option) any later -# version. -# -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -"""utilities for Pylint configuration : - -* pylintrc -* pylint.d (PYLINTHOME) -""" -from __future__ import with_statement - -import pickle -import os -import sys -from os.path import exists, isfile, join, expanduser, abspath, dirname - -# pylint home is used to save old runs results ################################ - -USER_HOME = expanduser('~') -if 'PYLINTHOME' in os.environ: - PYLINT_HOME = os.environ['PYLINTHOME'] - if USER_HOME == '~': - USER_HOME = dirname(PYLINT_HOME) -elif USER_HOME == '~': - PYLINT_HOME = ".pylint.d" -else: - PYLINT_HOME = join(USER_HOME, '.pylint.d') - -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: - with open(data_file) as stream: - return pickle.load(stream) - except: - return {} - -if sys.version_info < (3, 0): - _PICK_MOD = 'w' -else: - _PICK_MOD = 'wb' - -def save_results(results, base): - """pickle results""" - if not exists(PYLINT_HOME): - try: - os.mkdir(PYLINT_HOME) - except OSError: - print >> sys.stderr, 'Unable to create directory %s' % PYLINT_HOME - data_file = get_pdata_path(base, 1) - try: - with open(data_file, _PICK_MOD) as stream: - pickle.dump(results, stream) - except (IOError, OSError), ex: - print >> sys.stderr, 'Unable to create file %s: %s' % (data_file, ex) - -# location of the configuration file ########################################## - - -def find_pylintrc(): - """search the pylint rc file and return its path if it find it, else None - """ - # is there a pylint rc file in the current directory ? - if exists('pylintrc'): - return abspath('pylintrc') - if isfile('__init__.py'): - curdir = abspath(os.getcwd()) - while isfile(join(curdir, '__init__.py')): - curdir = abspath(join(curdir, '..')) - if isfile(join(curdir, 'pylintrc')): - return join(curdir, 'pylintrc') - if 'PYLINTRC' in os.environ 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): - pylintrc = join(user_home, '.config', 'pylintrc') - if not isfile(pylintrc): - if isfile('/etc/pylintrc'): - pylintrc = '/etc/pylintrc' - else: - pylintrc = None - return pylintrc - -PYLINTRC = find_pylintrc() - -ENV_HELP = ''' -The following environment variables are used: - * PYLINTHOME - Path to the directory where the persistent for the run will be stored. If -not found, it defaults to ~/.pylint.d/ or .pylint.d (in the current working -directory). - * PYLINTRC - Path to the configuration file. See the documentation for the method used -to search for configuration file. -''' % 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.sid/control b/debian.sid/control index f062531..3f6ad93 100644 --- a/debian.sid/control +++ b/debian.sid/control @@ -13,7 +13,7 @@ Vcs-Browser: http://hg.logilab.org/pylint Package: pylint Architecture: all Depends: ${python:Depends}, ${misc:Depends}, python-logilab-common (>= 0.53), python-astroid -Recommends: python-tk +Recommends: python-tk, python-enchant XB-Python-Version: ${python:Versions} Description: python code static checker and UML diagram generator Pylint is a Python source code analyzer which looks for programming @@ -38,7 +38,7 @@ Description: python code static checker and UML diagram generator Package: pylint3 Architecture: all Depends: ${python3:Depends}, ${misc:Depends}, python3-logilab-common (>= 0.53), python3-astroid -Recommends: python3-tk +Recommends: python3-tk, python3-enchant XB-Python-Version: ${python3:Versions} Description: python code static checker and UML diagram generator Pylint is a Python source code analyzer which looks for programming diff --git a/debian/changelog b/debian/changelog index f9d42f2..16320f6 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,21 @@ +pylint (1.4.1-1) precise; urgency=low + + * new upstream release + + -- Ionel Cristian Maries <contact@ionelmc.ro> Sun, 15 Feb 2015 12:11:44 +0200 + +pylint (1.2.1-1) unstable; urgency=low + + * new upstream release + + -- Sylvain Thénault <sylvain.thenault@logilab.fr> Wed, 30 Apr 2014 17:56:29 +0200 + +pylint (1.2.0-1) unstable; urgency=low + + * new upstream release + + -- Sylvain Thénault <sylvain.thenault@logilab.fr> Fri, 18 Apr 2014 15:32:32 +0200 + pylint (1.1.0-1) unstable; urgency=low * new upstream release diff --git a/debian/control b/debian/control index 313445a..660eda1 100644 --- a/debian/control +++ b/debian/control @@ -19,8 +19,8 @@ Architecture: all Depends: ${python:Depends}, ${misc:Depends}, python-logilab-common (>= 0.53.0), - python-astroid (>= 1.0.1) -Suggests: python-tk + python-astroid (>= 1.2) +Suggests: python-tk, python-enchant XB-Python-Version: ${python:Versions} Description: python code static checker and UML diagram generator Pylint is a Python source code analyzer which looks for programming diff --git a/debian/rules b/debian/rules index 7eac66e..ba0448a 100755 --- a/debian/rules +++ b/debian/rules @@ -59,8 +59,8 @@ get-orig-source: # Build architecture-independent files here. binary-indep: build install - dh_testdir - dh_testroot + dh_testdir + dh_testroot dh_install -i dh_pysupport -i dh_installchangelogs -i ChangeLog @@ -72,7 +72,7 @@ binary-indep: build install dh_compress -i -X.py -X.ini -X.xml -Xtest dh_fixperms -i dh_installdeb -i - dh_gencontrol -i + dh_gencontrol -i dh_md5sums -i dh_builddeb -i diff --git a/doc/Makefile b/doc/Makefile index 98076a6..d483f63 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -12,7 +12,7 @@ PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . -.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest all help: @echo "Please use \`make <target>' where <target> is one of" diff --git a/doc/conf.py b/doc/conf.py index b4b8fbd..d89693e 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -48,9 +48,9 @@ copyright = u'2013-2014, Logilab and contributors' # built documents. # # The short X.Y version. -version = '1.1.0' +from pylint.__pkginfo__ import version # The full version, including alpha/beta/rc tags. -release = '1.1.0' +release = version # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/doc/contribute.rst b/doc/contribute.rst index 6a45dea..43e5dfb 100644 --- a/doc/contribute.rst +++ b/doc/contribute.rst @@ -47,7 +47,7 @@ http://lists.logilab.org/pipermail/python-projects/ Forge ----- -Pylint is developped using the mercurial_ distributed version control system. +Pylint is developed using the mercurial_ distributed version control system. You can clone Pylint and its dependencies from :: @@ -64,7 +64,7 @@ your patch gets accepted. - Pylint keeps a set of unit tests in the /test directory. The `test_func.py` module uses external files to have some kind of easy - functionnal testing. To get your patch accepted you must write (or change) + functional testing. To get your patch accepted you must write (or change) a test input file in the `test/input` directory and message file in the `test/messages` directory. Then run `python test_func.py` to ensure that your test is green. @@ -117,7 +117,7 @@ without installing them. You can run all the unit tests like so:: The -S flag keeps distutils from interfering with sys.path. YMMV. -Adding new functionnal tests +Adding new functional tests ---------------------------- Pylint comes with an easy way to write functional tests for new checks: diff --git a/doc/extend.rst b/doc/extend.rst index 476c994..b767852 100644 --- a/doc/extend.rst +++ b/doc/extend.rst @@ -8,7 +8,8 @@ You can find some simple examples in the examples directory of the distribution (custom.py and custom_raw.py). I'll try to quickly explain the essentials here. -First, there are two kinds of checkers : +First, there are two kinds of checkers: + * raw checkers, which are analysing each module as a raw file stream * ast checkers, which are working on an ast representation of the module @@ -25,8 +26,8 @@ Checkers are ordered by priority. For each module, Pylint's engine: 1. give the module source file as a stream to raw checkers 2. get an ast representation for the module -3. make a depth first descent of the tree, calling visit_<> on each AST - checker when entering a node, and living_<> on the back traversal +3. make a depth first descent of the tree, calling ``visit_<>`` on each AST + checker when entering a node, and ``leave_<>`` on the back traversal Notice that the source code is probably the best source of documentation, it should be clear and well documented. Don't hesitate to diff --git a/doc/faq.rst b/doc/faq.rst index e3cd73e..a42080e 100644 --- a/doc/faq.rst +++ b/doc/faq.rst @@ -1,5 +1,7 @@ .. -*- coding: utf-8 -*- +.. _faq: + ========================== Frequently Asked Questions ========================== @@ -65,11 +67,23 @@ Pylint from the repository, simply invoke :: ----------------------------------- Pylint requires the latest `astroid`_ and `logilab-common`_ packages. It should be -compatible with any Python version greater than 2.5.0. +compatible with any Python version greater than 2.7.0. .. _`astroid`: https://bitbucket.org/logilab/astroid .. _`logilab-common`: http://www.logilab.org/project/logilab-common +2.4 What versions of Python is Pylint supporting? +-------------------------------------------------- + +Since Pylint 1.4, we support only Python 2.7+ and Python 3.3+. +Using this strategy really helps in maintaining a code base compatible +with both versions and from this benefits not only the maintainers, +but the end users as well, because it's easier to add and test +new features. +If support for Python 2.6 is absolutely required, then the version +from pylint-1.3 branch can be used. It will receive backports of +bug fixes for a while. + 3. Running Pylint ================= @@ -169,7 +183,6 @@ No, starting from 0.25.3, you can use symbolic names for messages:: # pylint: disable=fixme, line-too-long -You can show these symbols in the output with the `-sy` option. 4.5 I have a callback function where I have no control over received arguments. How do I avoid getting unused argument warnings? ---------------------------------------------------------------------------------------------------------------------------------- @@ -192,30 +205,6 @@ tricks like: :: method-hidden, too-many-lines -4.7 Why do I get a lot of spurious "unused variables messages" when using psyobj from psyco_? ----------------------------------------------------------------------------------------------- - -This is 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 occur with Pylint >= 0.5 since from -this version Pylint is not looking anymore for information in living -objects (i.e. it no longer imports analysed modules) - -.. _psyco: http://psyco.sf.net - 5. Classes and Inheritance ========================== @@ -259,7 +248,7 @@ lower bound on it. By default, the formula to calculate score is :: 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) However, this option can be changed in the Pylint rc file. If having negative -values really bugs you, you can set the formula to be the minimum of 0 and the +values really bugs you, you can set the formula to be the maximum of 0 and the above expression. diff --git a/doc/ide-integration.rst b/doc/ide-integration.rst index de5116c..30b48e5 100644 --- a/doc/ide-integration.rst +++ b/doc/ide-integration.rst @@ -3,35 +3,33 @@ IDE integration ================= -To use Pylint with Emacs, see http://www.emacswiki.org/emacs/PythonProgrammingInEmacs#toc8 +To use Pylint with: -To use Pylint with Vim, see -http://www.vim.org/scripts/script.php?script_id=891 + - Emacs_, see http://www.emacswiki.org/emacs/PythonProgrammingInEmacs#toc8, + - Vim_, see http://www.vim.org/scripts/script.php?script_id=891, + - Eclipse_ and PyDev_, see http://pydev.org/manual_adv_pylint.html, + - Komodo_, see http://mateusz.loskot.net/posts/2006/01/15/running-pylint-from-komodo/, + - gedit_, see https://launchpad.net/gedit-pylint-2 or https://wiki.gnome.org/Apps/Gedit/PylintPlugin, + - WingIDE_, see http://www.wingware.com/doc/edit/pylint, + - PyCharm_, see http://blog.saturnlaboratories.co.za/archive/2012/09/10/running-pylint-pycharm. -To use Pylint with Eclipse, see http://pydev.org +Pylint is integrated in: -To use Pylint with Komodo_, see -http://mateusz.loskot.net/2006/01/15/running-pylint-from-komodo/ - -To use Pylint with gedit_, see -http://live.gnome.org/Gedit/PylintPlugin - -To use Pylint with WingIDE_, see -http://www.wingware.com/doc/edit/pylint - -Pylint is integrated in Eric_ IDE, see the `Project > Check` menu. - -Pylint is integrated in Spyder_, see http://packages.python.org/spyder/pylint.html - -Pylint is integrated in pyscripter_, see the `Tool -> Tools` menu. + - Eric_ IDE, see the `Project > Check` menu, + - Spyder_, see http://packages.python.org/spyder/pylint.html, + - pyscripter_, see the `Tool -> Tools` menu. +.. _Emacs: http://www.gnu.org/software/emacs/ +.. _Vim: http://www.vim.org/ +.. _Eclipse: https://www.eclipse.org/ .. _Eric: http://eric-ide.python-projects.org/ .. _pyscripter: http://code.google.com/p/pyscripter/ .. _pydev: http://pydev.org .. _Komodo: http://www.activestate.com/Products/Komodo/ -.. _gedit: http://www.gnome.org/projects/gedit/ +.. _gedit: https://wiki.gnome.org/Apps/Gedit .. _WingIDE: http://www.wingware.com/ .. _spyder: http://code.google.com/p/spyderlib/ +.. _PyCharm: http://www.jetbrains.com/pycharm/ Using Pylint thru flymake in Emacs ================================== @@ -41,7 +39,6 @@ To enable flymake for Python, insert the following into your .emacs: .. sourcecode:: common-lisp ;; Configure flymake for Python - (setq pylint "epylint") (when (load "flymake" t) (defun flymake-pylint-init () (let* ((temp-file (flymake-init-create-temp-buffer-copy @@ -49,7 +46,7 @@ To enable flymake for Python, insert the following into your .emacs: (local-file (file-relative-name temp-file (file-name-directory buffer-file-name)))) - (list (expand-file-name pylint "") (list local-file)))) + (list "epylint" (list local-file)))) (add-to-list 'flymake-allowed-file-name-masks '("\\.py\\'" flymake-pylint-init))) diff --git a/doc/installation.rst b/doc/installation.rst index 5992bfc..eca0fa3 100644 --- a/doc/installation.rst +++ b/doc/installation.rst @@ -1,4 +1,3 @@ - Installation ------------ @@ -6,7 +5,7 @@ Dependencies '''''''''''' Pylint requires the latest `astroid`_ and `logilab-common`_ -packages. It should be compatible with any Python version >= 2.5. +packages. It should be compatible with any Python version >= 2.7. .. _`astroid`: https://bitbucket.org/logilab/astroid .. _`logilab-common`: http://www.logilab.org/project/logilab-common @@ -63,16 +62,16 @@ 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* +directory, or on your system path. (*setup.py* will 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 +1. Change to the appropriate directory before running pylint.bat -2. add the Scripts directory to your path statement in your autoexec.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 +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= diff --git a/doc/make.bat b/doc/make.bat new file mode 100644 index 0000000..9482679 --- /dev/null +++ b/doc/make.bat @@ -0,0 +1,190 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=_build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . +set I18NSPHINXOPTS=%SPHINXOPTS% . +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% + set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^<target^>` where ^<target^> is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. texinfo to make Texinfo files + echo. gettext to make PO message catalogs + echo. changes to make an overview over all changed/added/deprecated items + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "singlehtml" ( + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\a.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\a.ghc + goto end +) + +if "%1" == "devhelp" ( + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. + goto end +) + +if "%1" == "epub" ( + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "text" ( + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end +) + +if "%1" == "man" ( + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end +) + +if "%1" == "texinfo" ( + %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. + goto end +) + +if "%1" == "gettext" ( + %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The message catalogs are in %BUILDDIR%/locale. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + if errorlevel 1 exit /b 1 + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + if errorlevel 1 exit /b 1 + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + if errorlevel 1 exit /b 1 + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +:end diff --git a/doc/message-control.rst b/doc/message-control.rst index d93b452..203229d 100644 --- a/doc/message-control.rst +++ b/doc/message-control.rst @@ -22,34 +22,34 @@ An example available from the examples directory: def meth2(self, arg): """and this one not""" - # pylint: disable=W0613 + # pylint: disable=unused-argument print self\ + "foo" def meth3(self): """test one line disabling""" # no error - print self.bla # pylint: disable=E1101 + print self.bla # pylint: disable=no-member # error print self.blop def meth4(self): """test re-enabling""" - # pylint: disable=E1101 + # pylint: disable=no-member # no error print self.bla print self.blop - # pylint: enable=E1101 + # pylint: enable=no-member # error print self.blip def meth5(self): """test IF sub-block re-enabling""" - # pylint: disable=E1101 + # pylint: disable=no-member # no error print self.bla if self.blop: - # pylint: enable=E1101 + # pylint: enable=no-member # error print self.blip else: @@ -60,14 +60,14 @@ An example available from the examples directory: def meth6(self): """test TRY/EXCEPT sub-block re-enabling""" - # pylint: disable=E1101 + # pylint: disable=no-member # no error print self.bla try: - pylint: enable=E1101 + # pylint: enable=no-member # error print self.blip - except UndefinedName: # pylint: disable=E0602 + except UndefinedName: # pylint: disable=undefined-variable # no error print self.blip # no error @@ -75,7 +75,7 @@ An example available from the examples directory: def meth7(self): """test one line block opening disabling""" - if self.blop: # pylint: disable=E1101 + if self.blop: # pylint: disable=no-member # error print self.blip else: @@ -89,7 +89,7 @@ An example available from the examples directory: """test late disabling""" # error print self.blip - # pylint: disable=E1101 + # pylint: disable=no-member # no error print self.bla print self.blop diff --git a/doc/options.rst b/doc/options.rst index 4a159f2..d005dd2 100644 --- a/doc/options.rst +++ b/doc/options.rst @@ -1,13 +1,13 @@ .. -*- coding: utf-8 -*- =============== - Configuration + Configuration =============== Naming Styles ------------- -PyLint recognizes a number of different name types internally. With a few +Pylint recognizes a number of different name types internally. With a few exceptions, the type of the name is governed by the location the assignment to a name is found in, and not the type of object assigned. @@ -88,11 +88,11 @@ Large code bases that have been worked on for multiple years often exhibit an evolution in style as well. In some cases, modules can be in the same package, but still have different naming style based on the stratum they belong to. However, intra-module consistency should still be required, to make changes -inside a single file easier. For this case, PyLint supports regular expression -with several named capturing group. +inside a single file easier. For this case, Pylint supports regular expression +with several named capturing group. -The capturing group of the first valid match taints the module and enforces the -same group to be triggered on every subsequent occurrence of this name. +Rather than emitting name warnings immediately, Pylint will determine the +prevalent naming style inside each module and enforce it on all names. Consider the following (simplified) example:: @@ -101,16 +101,17 @@ Consider the following (simplified) example:: The regular expression defines two naming styles, ``snake`` for snake-case names, and ``camel`` for camel-case names. -In ``sample.py``, the function name on line 1 will taint the module and enforce -the match of named group ``snake`` for the remainder of the module:: +In ``sample.py``, the function name on line 1 and 7 will mark the module +and enforce the match of named group ``snake`` for the remaining names in +the module:: - def trigger_snake_case(arg): + def valid_snake_case(arg): ... def InvalidCamelCase(arg): ... - def valid_snake_case(arg): + def more_valid_snake_case(arg): ... Because of this, the name on line 4 will trigger an ``invalid-name`` warning, diff --git a/doc/output.rst b/doc/output.rst index 47d29c0..389becd 100644 --- a/doc/output.rst +++ b/doc/output.rst @@ -33,7 +33,7 @@ C category fullname of the message category -For exemple the former (pre 1.0) default format can be obtained with:: +For example, the former (pre 1.0) default format can be obtained with:: pylint --msg-template='{msg_id}:{line:3d},{column}: {obj}: {msg}' diff --git a/doc/release.txt b/doc/release.txt new file mode 100644 index 0000000..96d4186 --- /dev/null +++ b/doc/release.txt @@ -0,0 +1,23 @@ +Release Process +=============== + +1. Preparation + 1. Check if the dependencies of the package are correct + 2. Update the version number in __pkginfo__ + 3. Put the version number and the release date into the changelog + 4. Submit your changes. + +2. Make sure the tests are passing on drone.io: + https://drone.io/bitbucket.org/logilab/pylint + +3. Add a new tag 'pylint-$VERSION' + +5. Publish all remaining changes to the Bitbucket repository: + https://bitbucket.org/logilab/pylint + +4. Run + + $ python setup.py register sdist --formats=gztar bdist_wheel upload + + to release a new version to PyPI. +
\ No newline at end of file diff --git a/doc/run.rst b/doc/run.rst index 4e752b0..85db098 100644 --- a/doc/run.rst +++ b/doc/run.rst @@ -10,9 +10,11 @@ Pylint is meant to be called from the command 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. +``will not import`` this package or module, though uses Python internals +to locate them and as such is subject to the same rules and configuration. +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 restrictions. The thing to keep in mind is that Pylint will try to @@ -29,9 +31,9 @@ directory is automatically added on top of the python path :: 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 Frequently Asked Questions. +For more details on this see the :ref:`faq`. -You can also start a thin gui around Pylint (require TkInter) by +You can also start a thin gui around Pylint (require tkinter) by typing :: pylint-gui @@ -41,7 +43,7 @@ or module to check, at Pylint messages will be displayed in the user interface. It is also possible to call Pylint from an other python program, -thanks to ``py_run()`` function in ``lint`` module, +thanks to ``py_run()`` function in ``epylint`` module, assuming Pylint options are stored in ``pylint_options`` string, as: .. sourcecode:: python @@ -50,12 +52,12 @@ assuming Pylint options are stored in ``pylint_options`` string, as: lint.py_run(pylint_options) To silently run Pylint on a ``module_name.py`` module, -and get its standart output and error: +and get its standard output and error: .. sourcecode:: python from pylint import epylint as lint - (pylint_stdout, pylint_stderr) = lint.py_run('module_name.py', True) + (pylint_stdout, pylint_stderr) = lint.py_run('module_name.py', return_std=True) Command line options @@ -88,7 +90,7 @@ 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 configuration file to -specify the default values. You can specify a configuration file on the +specify the default values. You can specify a configuration file on the command line using the ``--rcfile`` option. Otherwise, Pylint searches for a configuration file in the following order and uses the first one it finds: @@ -104,7 +106,6 @@ configuration file in the following order and uses the first one it finds: #. ``.pylintrc`` in your home directory #. ``.config/pylintrc`` in your home directory - else, ``.pylintrc`` in the current working directory #. ``/etc/pylintrc`` The ``--generate-rcfile`` option will generate a commented configuration file @@ -115,16 +116,41 @@ includes: * Options appearing before ``--generate-rcfile`` on the Pylint command line Of course you can also start with the default values and hand tune the -configuration. +configuration. Other useful global options include: ---ignore=file Add <file> (may be a directory) to the black +--ignore=<file[,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. + Multiple entries can be given, separated by + comma. --persistent=y_or_n Pickle collected data for later comparisons. --output-format=<format> Select output format (text, html, custom). --msg-template=<template> Modifiy text output message template. --list-msgs Generate pylint's messages. ---full-documentation Generate pylint's full documentation, in reST +--full-documentation Generate pylint's full documentation, in reST format. + +Parallel execution +------------------ + +It is possible to speed up the execution of Pylint. If the running computer +has more CPUs than one, then the files to be checked could be spread on all +processors to Pylint sub-processes. +This functionality is exposed via ``-j`` command line parameter. +It takes a number of sub-processes that should be spawned. +If the provided number is 0 then the number of CPUs will be used. +The default number of workers is 1. + +Example:: + + pylint -j 4 mymodule1.py mymodule2.py mymodule3.py mymodule4.py + +This will spawn 4 parallel Pylint sub-process, where each provided module will +be checked in parallel. Discovered problems by checkers are not displayed +immediately. They are shown just after completing checking a module. + +There are some limitations in running checks in parallel in current +implementation. It is not possible to use custom plugins +(i.e. ``--load-plugins`` option), nor it is not possible to use +initialization hooks (i.e. ``--init-hook`` option). diff --git a/elisp/pylint.el b/elisp/pylint.el index 17132e4..90a586c 100644 --- a/elisp/pylint.el +++ b/elisp/pylint.el @@ -34,6 +34,7 @@ ;;; Code: (require 'compile) +(require 'tramp) (defgroup pylint nil "Minor mode for running the Pylint Python checker" @@ -114,7 +115,7 @@ output buffer, to go to the lines where pylint found matches. 'identity (list pylint-command (mapconcat 'identity pylint-options " ") - (comint-quote-filename file)) " "))) + (shell-quote-argument file)) " "))) (compilation-start command 'pylint-mode))) diff --git a/epylint.py b/epylint.py deleted file mode 100755 index ca245cc..0000000 --- a/epylint.py +++ /dev/null @@ -1,169 +0,0 @@ -# -*- coding: utf-8; mode: python; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- vim:fenc=utf-8:ft=python:et:sw=4:ts=4:sts=4 -# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). -# http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free Software -# Foundation; either version 2 of the License, or (at your option) any later -# version. -# -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -"""Emacs and Flymake compatible Pylint. - -This script is for integration with emacs and is compatible with flymake mode. - -epylint walks out of python packages before invoking pylint. This avoids -reporting import errors that occur when a module within a package uses the -absolute import path to get another module within this package. - -For example: - - Suppose a package is structured as - - a/__init__.py - a/b/x.py - a/c/y.py - - - Then if y.py imports x as "from a.b import x" the following produces pylint - errors - - cd a/c; pylint y.py - - - The following obviously doesn't - - pylint a/c/y.py - - - As this script will be invoked by emacs within the directory of the file - we are checking we need to go out of it to avoid these false positives. - - -You may also use py_run to run pylint with desired options and get back (or not) -its output. -""" - -import sys, os, re -import os.path as osp -from subprocess import Popen, PIPE - - -def lint(filename, options=None): - """Pylint the given file. - - When run from emacs we will be in the directory of a file, and passed its - filename. If this file is part of a package and is trying to import other - modules from within its own package or another package rooted in a directory - below it, pylint will classify it as a failed import. - - To get around this, we traverse down the directory tree to find the root of - the package this module is in. We then invoke pylint from this directory. - - Finally, we must correct the filenames in the output generated by pylint so - Emacs doesn't become confused (it will expect just the original filename, - while pylint may extend it with extra directories if we've traversed down - the tree) - """ - # traverse downwards until we are out of a python package - full_path = osp.abspath(filename) - parent_path = osp.dirname(full_path) - child_path = osp.basename(full_path) - - while parent_path != "/" and osp.exists(osp.join(parent_path, '__init__.py')): - child_path = osp.join(osp.basename(parent_path), child_path) - parent_path = osp.dirname(parent_path) - - # Start pylint - # Ensure we use the python and pylint associated with the running epylint - from pylint import lint as lint_mod - lint_path = lint_mod.__file__ - options = options or ['--disable=C,R,I'] - cmd = [sys.executable, lint_path] + options + [ - '--msg-template', '{path}:{line}: {category} ({msg_id}, {symbol}, {obj}) {msg}', - '-r', 'n', child_path] - process = Popen(cmd, stdout=PIPE, cwd=parent_path, universal_newlines=True) - - for line in process.stdout: - # remove pylintrc warning - if line.startswith("No config file found"): - continue - - # modify the file name thats output to reverse the path traversal we made - parts = line.split(":") - if parts and parts[0] == child_path: - line = ":".join([filename] + parts[1:]) - print line, - - process.wait() - return process.returncode - - -def py_run(command_options='', return_std=False, stdout=None, stderr=None, - script='epylint'): - """Run pylint from python - - ``command_options`` is a string containing ``pylint`` command line options; - ``return_std`` (boolean) indicates return of created standart output - and error (see below); - ``stdout`` and ``stderr`` are 'file-like' objects in which standart output - could be written. - - Calling agent is responsible for stdout/err management (creation, close). - Default standart output and error are those from sys, - or standalone ones (``subprocess.PIPE``) are used - if they are not set and ``return_std``. - - If ``return_std`` is set to ``True``, this function returns a 2-uple - containing standart output and error related to created process, - as follows: ``(stdout, stderr)``. - - A trivial usage could be as follows: - >>> py_run( '--version') - No config file found, using default configuration - pylint 0.18.1, - ... - - To silently run Pylint on a module, and get its standart output and error: - >>> (pylint_stdout, pylint_stderr) = py_run( 'module_name.py', True) - """ - # Create command line to call pylint - if os.name == 'nt': - script += '.bat' - command_line = script + ' ' + command_options - # Providing standart output and/or error if not set - if stdout is None: - if return_std: - stdout = PIPE - else: - stdout = sys.stdout - if stderr is None: - if return_std: - stderr = PIPE - else: - stderr = sys.stderr - # Call pylint in a subprocess - p = Popen(command_line, shell=True, stdout=stdout, stderr=stderr, - universal_newlines=True) - p.wait() - # Return standart output and error - if return_std: - return (p.stdout, p.stderr) - - -def Run(): - if len(sys.argv) == 1: - print "Usage: %s <filename> [options]" % sys.argv[0] - sys.exit(1) - elif not osp.exists(sys.argv[1]): - print "%s does not exist" % sys.argv[1] - sys.exit(1) - else: - sys.exit(lint(sys.argv[1], sys.argv[2:])) - - -if __name__ == '__main__': - Run() - diff --git a/examples/custom_raw.py b/examples/custom_raw.py index 00fbe89..30e90bf 100644 --- a/examples/custom_raw.py +++ b/examples/custom_raw.py @@ -10,6 +10,7 @@ class MyRawChecker(BaseChecker): name = 'custom_raw' msgs = {'W9901': ('use \\ for line continuation', + 'backslash-line-continuation', ('Used when a \\ is used for a line continuation instead' ' of using triple quoted string or parenthesis.')), } @@ -18,11 +19,13 @@ class MyRawChecker(BaseChecker): def process_module(self, node): """process a module - the module's content is accessible via node.file_stream object + the module's content is accessible via node.stream() function """ - for (lineno, line) in enumerate(node.file_stream): - if line.rstrip().endswith('\\'): - self.add_message('W9901', line=lineno) + with module.stream() as stream: + for (lineno, line) in enumerate(stream): + if line.rstrip().endswith('\\'): + self.add_message('backslash-line-continuation', + line=lineno) def register(linter): diff --git a/examples/pylintrc b/examples/pylintrc index d560f9e..69f0ee1 100644 --- a/examples/pylintrc +++ b/examples/pylintrc @@ -21,9 +21,22 @@ persistent=yes # usually to register additional checkers. load-plugins= +# DEPRECATED +include-ids=no + +# DEPRECATED +symbols=no + +# Use multiple processes to speed up Pylint. +jobs=1 + [MESSAGES CONTROL] +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED +confidence= + # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option # multiple time. See also the "--disable" option for examples. @@ -48,12 +61,6 @@ load-plugins= # mypackage.mymodule.MyReporterClass. output-format=text -# Include message's id in output -include-ids=no - -# Include symbolic ids of messages in output -symbols=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]". @@ -73,21 +80,35 @@ evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / stateme # evaluation report (RP0004). comment=no +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details +#msg-template= -[FORMAT] -# Maximum number of characters on a single line. -max-line-length=80 +[LOGGING] -# Maximum number of lines in a module -max-module-lines=1000 +# Logging modules to check that the string format arguments are in logging +# function parameter format +logging-modules=logging -# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 -# tab). -indent-string=' ' -# Regexp for a line that is allowed to be longer than the limit. -ignore-long-lines=^\s*(# )?<?https?://\S+>?$ +[VARIABLES] + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# A regular expression matching the name of dummy variables (i.e. expectedly +# 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= + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_,_cb + [BASIC] @@ -95,60 +116,88 @@ ignore-long-lines=^\s*(# )?<?https?://\S+>?$ required-attributes= # List of builtins function names that should not be used, separated by a comma -bad-functions=map,filter,apply,input +bad-functions=map,filter,input -# Regular expression which should only match correct module names -module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ +# Good variable names which should always be accepted, separated by a comma +good-names=i,j,k,ex,Run,_ -# Regular expression which should only match correct module level names -const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ +# Bad variable names which should always be refused, separated by a comma +bad-names=foo,bar,baz,toto,tutu,tata -# Regular expression which should only match correct class names -class-rgx=[A-Z_][a-zA-Z0-9]+$ +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Include a hint for the correct naming format with invalid-name +include-naming-hint=no -# Regular expression which should only match correct function names +# Regular expression matching 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}$ +# Naming hint for function names +function-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching correct variable names +variable-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for variable names +variable-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching correct constant names +const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ + +# Naming hint for constant names +const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ -# Regular expression which should only match correct instance attribute names +# Regular expression matching correct attribute names attr-rgx=[a-z_][a-z0-9_]{2,30}$ -# Regular expression which should only match correct argument names +# Naming hint for attribute names +attr-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching 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}$ +# Naming hint for argument names +argument-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching correct class attribute names +class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ -# Regular expression which should only match correct list comprehension / -# generator expression variable names +# Naming hint for class attribute names +class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ + +# Regular expression matching correct inline iteration 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,_ +# Naming hint for inline iteration names +inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ -# Bad variable names which should always be refused, separated by a comma -bad-names=foo,bar,baz,toto,tutu,tata +# Regular expression matching correct class names +class-rgx=[A-Z_][a-zA-Z0-9]+$ -# Regular expression which should only match functions or classes name which do -# not require a docstring -no-docstring-rgx=__.*__ +# Naming hint for class names +class-name-hint=[A-Z_][a-zA-Z0-9]+$ +# Regular expression matching correct module names +module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ -[SIMILARITIES] +# Naming hint for module names +module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ -# Minimum lines number of a similarity. -min-similarity-lines=4 +# Regular expression matching correct method names +method-rgx=[a-z_][a-z0-9_]{2,30}$ -# Ignore comments when computing similarities. -ignore-comments=yes +# Naming hint for method names +method-name-hint=[a-z_][a-z0-9_]{2,30}$ -# Ignore docstrings when computing similarities. -ignore-docstrings=yes +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=__.*__ -# Ignore imports when computing similarities. -ignore-imports=no +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 [MISCELLANEOUS] @@ -163,6 +212,11 @@ notes=FIXME,XXX,TODO # mixin class is detected if its name ends with "mixin" (case insensitive). ignore-mixin-members=yes +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis +ignored-modules= + # List of classes names for which member attributes should not be checked # (useful for classes with attributes dynamically set). ignored-classes=SQLObject @@ -177,36 +231,85 @@ zope=no generated-members=REQUEST,acl_users,aq_parent -[VARIABLES] +[SPELLING] -# Tells whether we should check for unused import in __init__ files. -init-import=no +# Spelling dictionary name. Available dictionaries: none. To make it working +# install python-enchant package. +spelling-dict= -# A regular expression matching the beginning of the name of dummy variables -# (i.e. not used). -dummy-variables-rgx=_|dummy +# List of comma separated words that should not be checked. +spelling-ignore-words= -# List of additional names supposed to be defined in builtins. Remember that -# you should avoid to define new builtins when possible. -additional-builtins= +# A path to a file that contains private dictionary; one word per line. +spelling-private-dict-file= +# Tells whether to store unknown words to indicated private dictionary in +# --spelling-private-dict-file option instead of raising a message. +spelling-store-unknown-words=no -[IMPORTS] -# Deprecated modules which should not be used, separated by a comma -deprecated-modules=regsub,string,TERMIOS,Bastion,rexec +[FORMAT] -# Create a graph of every (i.e. internal and external) dependencies in the -# given file (report RP0402 must not be disabled) -import-graph= +# Maximum number of characters on a single line. +max-line-length=80 -# Create a graph of external dependencies in the given file (report RP0402 must -# not be disabled) -ext-import-graph= +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )?<?https?://\S+>?$ -# Create a graph of internal dependencies in the given file (report RP0402 must -# not be disabled) -int-import-graph= +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + +# List of optional constructs for which whitespace checking is disabled +no-space-check=trailing-comma,dict-separator + +# 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=' ' + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + + +[SIMILARITIES] + +# 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 + +# Ignore imports when computing similarities. +ignore-imports=no + + +[CLASSES] + +# 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 + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=mcs + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict,_fields,_replace,_source,_make [DESIGN] @@ -225,7 +328,7 @@ max-locals=15 max-returns=6 # Maximum number of branch for function / method body -max-branchs=12 +max-branches=12 # Maximum number of statements in function / method body max-statements=50 @@ -243,20 +346,22 @@ min-public-methods=2 max-public-methods=20 -[CLASSES] +[IMPORTS] -# 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 +# Deprecated modules which should not be used, separated by a comma +deprecated-modules=regsub,TERMIOS,Bastion,rexec -# List of method names used to declare (i.e. assign) instance attributes. -defining-attr-methods=__init__,__new__,setUp +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled) +import-graph= -# List of valid names for the first argument in a class method. -valid-classmethod-first-arg=cls +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled) +ext-import-graph= -# List of valid names for the first argument in a metaclass class method. -valid-metaclass-classmethod-first-arg=mcs +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled) +int-import-graph= [EXCEPTIONS] @@ -1,508 +0,0 @@ -# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). -# http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free Software -# Foundation; either version 2 of the License, or (at your option) any later -# version. -# -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -"""Tkinker gui for pylint""" - -import os -import sys -import re -import Queue -from threading import Thread -from Tkinter import (Tk, Frame, Listbox, Entry, Label, Button, Scrollbar, - Checkbutton, Radiobutton, IntVar, StringVar) -from Tkinter import (TOP, LEFT, RIGHT, BOTTOM, END, X, Y, BOTH, SUNKEN, W, - HORIZONTAL, DISABLED, NORMAL, W) -from tkFileDialog import askopenfilename, askdirectory - -import pylint.lint -from pylint.reporters.guireporter import GUIReporter - -HOME = os.path.expanduser('~/') -HISTORY = '.pylint-gui-history' -COLORS = {'(I)':'lightblue', - '(C)':'blue', '(R)':'darkblue', - '(W)':'black', '(E)':'darkred', - '(F)':'red'} - - -def convert_to_string(msg): - """make a string representation of a message""" - module_object = msg.module - if msg.obj: - module_object += ".%s" % msg.obj - return "(%s) %s [%d]: %s" % (msg.C, module_object, msg.line, msg.msg) - -class BasicStream(object): - ''' - used in gui reporter instead of writing to stdout, it is written to - this stream and saved in contents - ''' - def __init__(self, gui): - """init""" - self.curline = "" - self.gui = gui - self.contents = [] - self.outdict = {} - self.currout = None - self.next_title = None - - def write(self, text): - """write text to the stream""" - if re.match('^--+$', text.strip()) or re.match('^==+$', text.strip()): - if self.currout: - self.outdict[self.currout].remove(self.next_title) - self.outdict[self.currout].pop() - self.currout = self.next_title - self.outdict[self.currout] = [''] - - if text.strip(): - self.next_title = text.strip() - - if text.startswith(os.linesep): - self.contents.append('') - if self.currout: - self.outdict[self.currout].append('') - self.contents[-1] += text.strip(os.linesep) - if self.currout: - self.outdict[self.currout][-1] += text.strip(os.linesep) - if text.endswith(os.linesep) and text.strip(): - self.contents.append('') - if self.currout: - self.outdict[self.currout].append('') - - def fix_contents(self): - """finalize what the contents of the dict should look like before output""" - for item in self.outdict: - num_empty = self.outdict[item].count('') - for _ in xrange(num_empty): - self.outdict[item].remove('') - if self.outdict[item]: - self.outdict[item].pop(0) - - def output_contents(self): - """output contents of dict to the gui, and set the rating""" - self.fix_contents() - self.gui.tabs = self.outdict - try: - self.gui.rating.set(self.outdict['Global evaluation'][0]) - except: - self.gui.rating.set('Error') - self.gui.refresh_results_window() - - #reset stream variables for next run - self.contents = [] - self.outdict = {} - self.currout = None - self.next_title = None - - -class LintGui(object): - """Build and control a window to interact with pylint""" - - def __init__(self, root=None): - """init""" - self.root = root or Tk() - self.root.title('Pylint') - #reporter - self.reporter = None - #message queue for output from reporter - self.msg_queue = Queue.Queue() - self.msgs = [] - self.visible_msgs = [] - self.filenames = [] - self.rating = StringVar() - self.tabs = {} - self.report_stream = BasicStream(self) - #gui objects - self.lb_messages = None - self.showhistory = None - self.results = None - self.btnRun = None - self.information_box = None - self.convention_box = None - self.refactor_box = None - self.warning_box = None - self.error_box = None - self.fatal_box = None - self.txtModule = None - self.status = None - self.msg_type_dict = None - self.init_gui() - - def init_gui(self): - """init helper""" - #setting up frames - top_frame = Frame(self.root) - mid_frame = Frame(self.root) - radio_frame = Frame(self.root) - res_frame = Frame(self.root) - msg_frame = Frame(self.root) - check_frame = Frame(self.root) - history_frame = Frame(self.root) - btn_frame = Frame(self.root) - rating_frame = Frame(self.root) - top_frame.pack(side=TOP, fill=X) - mid_frame.pack(side=TOP, fill=X) - history_frame.pack(side=TOP, fill=BOTH, expand=True) - radio_frame.pack(side=TOP, fill=BOTH, expand=True) - rating_frame.pack(side=TOP, fill=BOTH, expand=True) - res_frame.pack(side=TOP, fill=BOTH, expand=True) - check_frame.pack(side=TOP, fill=BOTH, expand=True) - msg_frame.pack(side=TOP, fill=BOTH, expand=True) - btn_frame.pack(side=TOP, fill=X) - - # Binding F5 application-wide to run lint - self.root.bind('<F5>', self.run_lint) - - #Message ListBox - rightscrollbar = Scrollbar(msg_frame) - rightscrollbar.pack(side=RIGHT, fill=Y) - bottomscrollbar = Scrollbar(msg_frame, orient=HORIZONTAL) - bottomscrollbar.pack(side=BOTTOM, fill=X) - self.lb_messages = Listbox(msg_frame, - yscrollcommand=rightscrollbar.set, - xscrollcommand=bottomscrollbar.set, - bg="white") - self.lb_messages.bind("<Double-Button-1>", self.show_sourcefile) - self.lb_messages.pack(expand=True, fill=BOTH) - rightscrollbar.config(command=self.lb_messages.yview) - bottomscrollbar.config(command=self.lb_messages.xview) - - #History ListBoxes - rightscrollbar2 = Scrollbar(history_frame) - rightscrollbar2.pack(side=RIGHT, fill=Y) - bottomscrollbar2 = Scrollbar(history_frame, orient=HORIZONTAL) - bottomscrollbar2.pack(side=BOTTOM, fill=X) - self.showhistory = Listbox(history_frame, - yscrollcommand=rightscrollbar2.set, - xscrollcommand=bottomscrollbar2.set, - bg="white") - self.showhistory.pack(expand=True, fill=BOTH) - rightscrollbar2.config(command=self.showhistory.yview) - bottomscrollbar2.config(command=self.showhistory.xview) - self.showhistory.bind('<Double-Button-1>', self.select_recent_file) - self.set_history_window() - - #status bar - self.status = Label(self.root, text="", bd=1, relief=SUNKEN, anchor=W) - self.status.pack(side=BOTTOM, fill=X) - - #labelbl_ratingls - lbl_rating_label = Label(rating_frame, text='Rating:') - lbl_rating_label.pack(side=LEFT) - lbl_rating = Label(rating_frame, textvariable=self.rating) - lbl_rating.pack(side=LEFT) - Label(mid_frame, text='Recently Used:').pack(side=LEFT) - Label(top_frame, text='Module or package').pack(side=LEFT) - - #file textbox - self.txt_module = Entry(top_frame, background='white') - self.txt_module.bind('<Return>', self.run_lint) - self.txt_module.pack(side=LEFT, expand=True, fill=X) - - #results box - rightscrollbar = Scrollbar(res_frame) - rightscrollbar.pack(side=RIGHT, fill=Y) - bottomscrollbar = Scrollbar(res_frame, orient=HORIZONTAL) - bottomscrollbar.pack(side=BOTTOM, fill=X) - self.results = Listbox(res_frame, - yscrollcommand=rightscrollbar.set, - xscrollcommand=bottomscrollbar.set, - bg="white", font="Courier") - self.results.pack(expand=True, fill=BOTH, side=BOTTOM) - rightscrollbar.config(command=self.results.yview) - bottomscrollbar.config(command=self.results.xview) - - #buttons - Button(top_frame, text='Open', command=self.file_open).pack(side=LEFT) - Button(top_frame, text='Open Package', - command=(lambda: self.file_open(package=True))).pack(side=LEFT) - - self.btnRun = Button(top_frame, text='Run', command=self.run_lint) - self.btnRun.pack(side=LEFT) - Button(btn_frame, text='Quit', command=self.quit).pack(side=BOTTOM) - - #radio buttons - self.information_box = IntVar() - self.convention_box = IntVar() - self.refactor_box = IntVar() - self.warning_box = IntVar() - self.error_box = IntVar() - self.fatal_box = IntVar() - i = Checkbutton(check_frame, text="Information", fg=COLORS['(I)'], - variable=self.information_box, command=self.refresh_msg_window) - c = Checkbutton(check_frame, text="Convention", fg=COLORS['(C)'], - variable=self.convention_box, command=self.refresh_msg_window) - r = Checkbutton(check_frame, text="Refactor", fg=COLORS['(R)'], - variable=self.refactor_box, command=self.refresh_msg_window) - w = Checkbutton(check_frame, text="Warning", fg=COLORS['(W)'], - variable=self.warning_box, command=self.refresh_msg_window) - e = Checkbutton(check_frame, text="Error", fg=COLORS['(E)'], - variable=self.error_box, command=self.refresh_msg_window) - f = Checkbutton(check_frame, text="Fatal", fg=COLORS['(F)'], - variable=self.fatal_box, command=self.refresh_msg_window) - i.select() - c.select() - r.select() - w.select() - e.select() - f.select() - i.pack(side=LEFT) - c.pack(side=LEFT) - r.pack(side=LEFT) - w.pack(side=LEFT) - e.pack(side=LEFT) - f.pack(side=LEFT) - - #check boxes - self.box = StringVar() - # XXX should be generated - report = Radiobutton( - radio_frame, text="Report", variable=self.box, - value="Report", command=self.refresh_results_window) - raw_met = Radiobutton( - radio_frame, text="Raw metrics", variable=self.box, - value="Raw metrics", command=self.refresh_results_window) - dup = Radiobutton( - radio_frame, text="Duplication", variable=self.box, - value="Duplication", command=self.refresh_results_window) - ext = Radiobutton( - radio_frame, text="External dependencies", - variable=self.box, value="External dependencies", - command=self.refresh_results_window) - stat = Radiobutton( - radio_frame, text="Statistics by type", - variable=self.box, value="Statistics by type", - command=self.refresh_results_window) - msg_cat = Radiobutton( - radio_frame, text="Messages by category", - variable=self.box, value="Messages by category", - command=self.refresh_results_window) - msg = Radiobutton( - radio_frame, text="Messages", variable=self.box, - value="Messages", command=self.refresh_results_window) - source_file = Radiobutton( - radio_frame, text="Source File", variable=self.box, - value="Source File", command=self.refresh_results_window) - report.select() - report.grid(column=0, row=0, sticky=W) - raw_met.grid(column=1, row=0, sticky=W) - dup.grid(column=2, row=0, sticky=W) - msg.grid(column=3, row=0, sticky=W) - stat.grid(column=0, row=1, sticky=W) - msg_cat.grid(column=1, row=1, sticky=W) - ext.grid(column=2, row=1, sticky=W) - source_file.grid(column=3, row=1, sticky=W) - - #dictionary for check boxes and associated error term - self.msg_type_dict = { - 'I': lambda: self.information_box.get() == 1, - 'C': lambda: self.convention_box.get() == 1, - 'R': lambda: self.refactor_box.get() == 1, - 'E': lambda: self.error_box.get() == 1, - 'W': lambda: self.warning_box.get() == 1, - 'F': lambda: self.fatal_box.get() == 1 - } - self.txt_module.focus_set() - - - def select_recent_file(self, event): - """adds the selected file in the history listbox to the Module box""" - if not self.showhistory.size(): - return - - selected = self.showhistory.curselection() - item = self.showhistory.get(selected) - #update module - self.txt_module.delete(0, END) - self.txt_module.insert(0, item) - - def refresh_msg_window(self): - """refresh the message window with current output""" - #clear the window - self.lb_messages.delete(0, END) - self.visible_msgs = [] - for msg in self.msgs: - if self.msg_type_dict.get(msg.C)(): - self.visible_msgs.append(msg) - msg_str = convert_to_string(msg) - self.lb_messages.insert(END, msg_str) - fg_color = COLORS.get(msg_str[:3], 'black') - self.lb_messages.itemconfigure(END, fg=fg_color) - - def refresh_results_window(self): - """refresh the results window with current output""" - #clear the window - self.results.delete(0, END) - try: - for res in self.tabs[self.box.get()]: - self.results.insert(END, res) - except: - pass - - def process_incoming(self): - """process the incoming messages from running pylint""" - while self.msg_queue.qsize(): - try: - msg = self.msg_queue.get(0) - if msg == "DONE": - self.report_stream.output_contents() - return False - - #adding message to list of msgs - self.msgs.append(msg) - - #displaying msg if message type is selected in check box - if self.msg_type_dict.get(msg.C)(): - self.visible_msgs.append(msg) - msg_str = convert_to_string(msg) - self.lb_messages.insert(END, msg_str) - fg_color = COLORS.get(msg_str[:3], 'black') - self.lb_messages.itemconfigure(END, fg=fg_color) - - except Queue.Empty: - pass - return True - - def periodic_call(self): - """determine when to unlock the run button""" - if self.process_incoming(): - self.root.after(100, self.periodic_call) - else: - #enabling button so it can be run again - self.btnRun.config(state=NORMAL) - - def mainloop(self): - """launch the mainloop of the application""" - self.root.mainloop() - - def quit(self, _=None): - """quit the application""" - self.root.quit() - - def halt(self): - """program halt placeholder""" - return - - def file_open(self, package=False, _=None): - """launch a file browser""" - if not package: - filename = askopenfilename(parent=self.root, filetypes=[('pythonfiles', '*.py'), - ('allfiles', '*')], title='Select Module') - else: - filename = askdirectory(title="Select A Folder", mustexist=1) - - if filename == (): - return - - self.txt_module.delete(0, END) - self.txt_module.insert(0, filename) - - def update_filenames(self): - """update the list of recent filenames""" - filename = self.txt_module.get() - if not filename: - filename = os.getcwd() - if filename+'\n' in self.filenames: - index = self.filenames.index(filename+'\n') - self.filenames.pop(index) - - #ensure only 10 most recent are stored - if len(self.filenames) == 10: - self.filenames.pop() - self.filenames.insert(0, filename+'\n') - - def set_history_window(self): - """update the history window with info from the history file""" - #clear the window - self.showhistory.delete(0, END) - # keep the last 10 most recent files - try: - view_history = open(HOME+HISTORY, 'r') - for hist in view_history.readlines(): - if not hist in self.filenames: - self.filenames.append(hist) - self.showhistory.insert(END, hist.split('\n')[0]) - view_history.close() - except IOError: - # do nothing since history file will be created later - return - - def run_lint(self, _=None): - """launches pylint""" - self.update_filenames() - self.root.configure(cursor='watch') - self.reporter = GUIReporter(self, output=self.report_stream) - module = self.txt_module.get() - if not module: - module = os.getcwd() - - #cleaning up msgs and windows - self.msgs = [] - self.visible_msgs = [] - self.lb_messages.delete(0, END) - self.tabs = {} - self.results.delete(0, END) - self.btnRun.config(state=DISABLED) - - #setting up a worker thread to run pylint - worker = Thread(target=lint_thread, args=(module, self.reporter, self,)) - self.periodic_call() - worker.start() - - # Overwrite the .pylint-gui-history file with all the new recently added files - # in order from filenames but only save last 10 files - write_history = open(HOME+HISTORY, 'w') - write_history.writelines(self.filenames) - write_history.close() - self.set_history_window() - - self.root.configure(cursor='') - - def show_sourcefile(self, event=None): - selected = self.lb_messages.curselection() - if not selected: - return - - msg = self.visible_msgs[int(selected[0])] - scroll = msg.line - 3 - if scroll < 0: - scroll = 0 - - self.tabs["Source File"] = open(msg.path, "r").readlines() - self.box.set("Source File") - self.refresh_results_window() - self.results.yview(scroll) - self.results.select_set(msg.line - 1) - - -def lint_thread(module, reporter, gui): - """thread for pylint""" - gui.status.text = "processing module(s)" - pylint.lint.Run(args=[module], reporter=reporter, exit=False) - gui.msg_queue.put("DONE") - - -def Run(args): - """launch pylint gui from args""" - if args: - print 'USAGE: pylint-gui\n launch a simple pylint gui using Tk' - sys.exit(1) - gui = LintGui() - gui.mainloop() - sys.exit(0) - -if __name__ == '__main__': - Run(sys.argv[1:]) diff --git a/interfaces.py b/interfaces.py deleted file mode 100644 index 50f2c83..0000000 --- a/interfaces.py +++ /dev/null @@ -1,72 +0,0 @@ -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free Software -# Foundation; either version 2 of the License, or (at your option) any later -# version. -# -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -"""Interfaces for PyLint objects""" - -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)""" - - -class IRawChecker(IChecker): - """interface for checker which need to parse the raw file - """ - - def process_module(self, astroid): - """ process a module - - the module's content is accessible via astroid.file_stream - """ - - -class ITokenChecker(IChecker): - """Interface for checkers that need access to the token list.""" - def process_tokens(self, tokens): - """Process a module. - - tokens is a list of all source code tokens in the file. - """ - - -class IAstroidChecker(IChecker): - """ interface for checker which prefers receive events according to - statement type - """ - - -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', 'IAstroidChecker', 'ITokenChecker', 'IReporter') diff --git a/lint.py b/lint.py deleted file mode 100644 index 0e1afa0..0000000 --- a/lint.py +++ /dev/null @@ -1,1103 +0,0 @@ -# Copyright (c) 2003-2014 LOGILAB S.A. (Paris, FRANCE). -# http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free Software -# Foundation; either version 2 of the License, or (at your option) any later -# version. -# -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -""" %prog [options] module_or_package - - Check that a module satisfies 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. -""" - -# import this first to avoid builtin namespace pollution -from pylint.checkers import utils - -import functools -import sys -import os -import tokenize -from warnings import warn - -from logilab.common.configuration import UnsupportedAction, OptionsManagerMixIn -from logilab.common.optik_ext import check_csv -from logilab.common.modutils import load_module_from_name, get_module_part -from logilab.common.interface import implements -from logilab.common.textutils import splitstrip -from logilab.common.ureports import Table, Text, Section -from logilab.common.__pkginfo__ import version as common_version - -from astroid import MANAGER, nodes, AstroidBuildingException -from astroid.__pkginfo__ import version as astroid_version - -from pylint.utils import ( - MSG_TYPES, OPTION_RGX, - PyLintASTWalker, UnknownMessage, MessagesHandlerMixIn, ReportsHandlerMixIn, - EmptyReport, WarningScope, - expand_modules, tokenize_module) -from pylint.interfaces import IRawChecker, ITokenChecker, IAstroidChecker -from pylint.checkers import (BaseTokenChecker, - table_lines_from_stats, - initialize as checkers_initialize) -from pylint.reporters import initialize as reporters_initialize -from pylint import config - -from pylint.__pkginfo__ import version - - - -def _get_python_path(filepath): - dirname = os.path.dirname(os.path.realpath( - os.path.expanduser(filepath))) - while True: - if not os.path.exists(os.path.join(dirname, "__init__.py")): - return dirname - old_dirname = dirname - dirname = os.path.dirname(dirname) - if old_dirname == dirname: - return os.getcwd() - - -# Python Linter class ######################################################### - -MSGS = { - 'F0001': ('%s', - 'fatal', - 'Used when an error occurred preventing the analysis of a \ - module (unable to find it for instance).'), - 'F0002': ('%s: %s', - 'astroid-error', - 'Used when an unexpected error occurred while building the ' - 'Astroid representation. This is usually accompanied by a ' - 'traceback. Please report such errors !'), - 'F0003': ('ignored builtin module %s', - 'ignored-builtin-module', - 'Used to indicate that the user asked to analyze a builtin ' - 'module which has been skipped.'), - 'F0004': ('unexpected inferred value %s', - 'unexpected-inferred-value', - 'Used to indicate that some value of an unexpected type has been ' - 'inferred.'), - 'F0010': ('error while code parsing: %s', - 'parse-error', - 'Used when an exception occured while building the Astroid ' - 'representation which could be handled by astroid.'), - - 'I0001': ('Unable to run raw checkers on built-in module %s', - 'raw-checker-failed', - 'Used to inform that a built-in module has not been checked ' - 'using the raw checkers.'), - - 'I0010': ('Unable to consider inline option %r', - 'bad-inline-option', - 'Used when an inline option is either badly formatted or can\'t ' - 'be used inside modules.'), - - 'I0011': ('Locally disabling %s', - 'locally-disabled', - 'Used when an inline option disables a message or a messages ' - 'category.'), - 'I0012': ('Locally enabling %s', - 'locally-enabled', - 'Used when an inline option enables a message or a messages ' - 'category.'), - 'I0013': ('Ignoring entire file', - 'file-ignored', - 'Used to inform that the file will not be checked'), - 'I0014': ('Used deprecated directive "pylint:disable-all" or ' - '"pylint:disable=all"', - 'deprecated-disable-all', - 'You should preferably use "pylint:skip-file" as this directive ' - 'has a less confusing name. Do this only if you are sure that ' - 'all people running Pylint on your code have version >= 0.26'), - 'I0020': ('Suppressed %s (from line %d)', - 'suppressed-message', - 'A message was triggered on a line, but suppressed explicitly ' - 'by a disable= comment in the file. This message is not ' - 'generated for messages that are ignored due to configuration ' - 'settings.'), - 'I0021': ('Useless suppression of %s', - 'useless-suppression', - 'Reported when a message is explicitly disabled for a line or ' - 'a block of code, but never triggered.'), - 'I0022': ('Deprecated pragma "pylint:disable-msg" or "pylint:enable-msg"', - 'deprecated-pragma', - 'You should preferably use "pylint:disable" or "pylint:enable" ' - 'instead of the deprecated suppression pragma style ' - '"pylint:disable-msg" or "pylint:enable-msg"'), - - 'E0001': ('%s', - 'syntax-error', - 'Used when a syntax error is raised for a module.'), - - 'E0011': ('Unrecognized file option %r', - 'unrecognized-inline-option', - 'Used when an unknown inline option is encountered.'), - 'E0012': ('Bad option value %r', - 'bad-option-value', - 'Used when a bad value for an inline option is encountered.'), - } - - -class PyLinter(OptionsManagerMixIn, MessagesHandlerMixIn, ReportsHandlerMixIn, - BaseTokenChecker): - """lint Python modules using external checkers. - - This is the main checker controlling the other ones and the reports - generation. It is itself both a raw checker and an astroid checker in order - to: - * handle message activation / deactivation at the module level - * handle some basic but necessary stats'data (number of classes, methods...) - - IDE plugins developpers: you may have to call - `astroid.builder.MANAGER.astroid_cache.clear()` accross run if you want - to ensure the latest code version is actually checked. - """ - - __implements__ = (ITokenChecker,) - - name = 'master' - priority = 0 - level = 0 - msgs = MSGS - may_be_disabled = False - - @staticmethod - def make_options(): - return (('ignore', - {'type' : 'csv', 'metavar' : '<file>[,<file>...]', - 'dest' : 'black_list', 'default' : ('CVS',), - 'help' : 'Add files or directories to the blacklist. ' - 'They should be base names, not paths.'}), - ('persistent', - {'default': True, 'type' : 'yn', 'metavar' : '<y_or_n>', - 'level': 1, - 'help' : 'Pickle collected data for later comparisons.'}), - - ('load-plugins', - {'type' : 'csv', 'metavar' : '<modules>', 'default' : (), - 'level': 1, - 'help' : 'List of plugins (as comma separated values of ' - 'python modules names) to load, usually to register ' - 'additional checkers.'}), - - ('output-format', - {'default': 'text', 'type': 'string', 'metavar' : '<format>', - 'short': 'f', - 'group': 'Reports', - 'help' : 'Set the output format. Available formats are text,' - ' parseable, colorized, msvs (visual studio) and html. You ' - 'can also give a reporter class, eg mypackage.mymodule.' - 'MyReporterClass.'}), - - ('files-output', - {'default': 0, 'type' : 'yn', 'metavar' : '<y_or_n>', - 'group': 'Reports', 'level': 1, - '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]".'}), - - ('reports', - {'default': 1, 'type' : 'yn', 'metavar' : '<y_or_n>', - 'short': 'r', - 'group': 'Reports', - 'help' : 'Tells whether to display a full report or only the ' - 'messages'}), - - ('evaluation', - {'type' : 'string', 'metavar' : '<python_expression>', - 'group': 'Reports', 'level': 1, - '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 respectively contain the number of errors / warnings\ - messages and the total number of statements analyzed. This is used by the \ - global evaluation report (RP0004).'}), - - ('comment', - {'default': 0, 'type' : 'yn', 'metavar' : '<y_or_n>', - 'group': 'Reports', 'level': 1, - 'help' : 'Add a comment according to your evaluation note. ' - 'This is used by the global evaluation report (RP0004).'}), - - ('enable', - {'type' : 'csv', 'metavar': '<msg ids>', - 'short': 'e', - 'group': 'Messages control', - 'help' : 'Enable the message, report, category or checker with the ' - 'given id(s). You can either give multiple identifier ' - 'separated by comma (,) or put this option multiple time. ' - 'See also the "--disable" option for examples. '}), - - ('disable', - {'type' : 'csv', 'metavar': '<msg ids>', - 'short': 'd', - 'group': 'Messages control', - 'help' : 'Disable the message, report, category or checker ' - 'with the given id(s). You can either give multiple identifiers' - ' separated by comma (,) or put this option multiple times ' - '(only on the command line, not in the configuration file ' - 'where it should appear only once).' - 'You can also use "--disable=all" to disable everything first ' - 'and then reenable specific checks. For example, if you want ' - 'to run only the similarities checker, you can use ' - '"--disable=all --enable=similarities". ' - 'If you want to run only the classes checker, but have no ' - 'Warning level messages displayed, use' - '"--disable=all --enable=classes --disable=W"'}), - - ('msg-template', - {'type' : 'string', 'metavar': '<template>', - #'short': 't', - 'group': 'Reports', - 'help' : ('Template used to display messages. ' - 'This is a python new-style format string ' - 'used to format the message information. ' - 'See doc for all details') - }), # msg-template - ) - - option_groups = ( - ('Messages control', 'Options controling analysis messages'), - ('Reports', 'Options related to output formating and reporting'), - ) - - def __init__(self, options=(), reporter=None, option_groups=(), - pylintrc=None): - # some stuff has to be done before ancestors initialization... - # - # checkers / reporter / astroid manager - self.reporter = None - self._reporter_name = None - self._reporters = {} - self._checkers = {} - self._ignore_file = False - # 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.make_options() - self.option_groups = option_groups + PyLinter.option_groups - self._options_methods = { - 'enable': self.enable, - 'disable': self.disable} - self._bw_options_methods = {'disable-msg': self.disable, - 'enable-msg': self.enable} - full_version = '%%prog %s, \nastroid %s, common %s\nPython %s' % ( - version, astroid_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) - BaseTokenChecker.__init__(self) - # provided reports - self.reports = (('RP0001', 'Messages by category', - report_total_messages_stats), - ('RP0002', '% errors / warnings by module', - report_messages_by_module_stats), - ('RP0003', 'Messages', - report_messages_stats), - ('RP0004', 'Global evaluation', - self.report_evaluation), - ) - self.register_checker(self) - self._dynamic_plugins = set() - self.load_provider_defaults() - if reporter: - self.set_reporter(reporter) - - def load_default_plugins(self): - checkers_initialize(self) - reporters_initialize(self) - # Make sure to load the default reporter, because - # the option has been set before the plugins had been loaded. - if not self.reporter: - self._load_reporter() - - def prepare_import_path(self, args): - """Prepare sys.path for running the linter checks.""" - if len(args) == 1: - sys.path.insert(0, _get_python_path(args[0])) - else: - sys.path.insert(0, os.getcwd()) - - def cleanup_import_path(self): - """Revert any changes made to sys.path in prepare_import_path.""" - sys.path.pop(0) - - 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.add(modname) - module = load_module_from_name(modname) - module.register(self) - - def _load_reporter(self): - name = self._reporter_name.lower() - if name in self._reporters: - self.set_reporter(self._reporters[name]()) - else: - qname = self._reporter_name - module = load_module_from_name(get_module_part(qname)) - class_name = qname.split('.')[-1] - reporter_class = getattr(module, class_name) - self.set_reporter(reporter_class()) - - def set_reporter(self, reporter): - """set the reporter used to display messages and reports""" - self.reporter = reporter - reporter.linter = self - - def set_option(self, optname, value, action=None, optdict=None): - """overridden from configuration.OptionsProviderMixin to handle some - special options - """ - if optname in self._options_methods or \ - optname in self._bw_options_methods: - if value: - try: - meth = self._options_methods[optname] - except KeyError: - meth = self._bw_options_methods[optname] - warn('%s is deprecated, replace it by %s' % ( - optname, optname.split('-')[0]), DeprecationWarning) - value = check_csv(None, optname, value) - if isinstance(value, (list, tuple)): - for _id in value: - meth(_id, ignore_unknown=True) - else: - meth(value) - elif optname == 'output-format': - self._reporter_name = value - # If the reporters are already available, load - # the reporter class. - if self._reporters: - self._load_reporter() - try: - BaseTokenChecker.set_option(self, optname, value, action, optdict) - except UnsupportedAction: - print >> sys.stderr, 'option %s can\'t be read from config file' % \ - optname - - def register_reporter(self, reporter_class): - self._reporters[reporter_class.name] = reporter_class - - # checkers manipulation methods ############################################ - - def register_checker(self, checker): - """register a new checker - - checker is an object implementing IRawChecker or / and IAstroidChecker - """ - assert checker.priority <= 0, 'checker priority can\'t be >= 0' - self._checkers.setdefault(checker.name, []).append(checker) - for r_id, r_title, r_cb in checker.reports: - self.register_report(r_id, r_title, r_cb, checker) - self.register_options_provider(checker) - if hasattr(checker, 'msgs'): - self.register_messages(checker) - checker.load_defaults() - - def disable_noerror_messages(self): - for msgcat, msgids in self._msgs_by_category.iteritems(): - if msgcat == 'E': - for msgid in msgids: - self.enable(msgid) - else: - for msgid in msgids: - self.disable(msgid) - - def disable_reporters(self): - """disable all reporters""" - for reporters in self._reports.itervalues(): - for report_id, _title, _cb in reporters: - self.disable_report(report_id) - - def error_mode(self): - """error mode: enable only errors; no reports, no persistent""" - self.disable_noerror_messages() - self.disable('miscellaneous') - self.set_option('reports', False) - self.set_option('persistent', 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 - for (tok_type, _, start, _, line) in tokens: - if tok_type not in (comment, newline): - continue - match = OPTION_RGX.search(line) - if match is None: - continue - if match.group(1).strip() == "disable-all" or \ - match.group(1).strip() == 'skip-file': - if match.group(1).strip() == "disable-all": - self.add_message('I0014', line=start[0]) - self.add_message('I0013', line=start[0]) - self._ignore_file = True - return - 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() - if opt in self._options_methods or opt in self._bw_options_methods: - try: - meth = self._options_methods[opt] - except KeyError: - meth = self._bw_options_methods[opt] - # found a "(dis|en)able-msg" pragma deprecated suppresssion - self.add_message('deprecated-pragma', line=start[0]) - for msgid in splitstrip(value): - try: - if (opt, msgid) == ('disable', 'all'): - self.add_message('I0014', line=start[0]) - self.add_message('I0013', line=start[0]) - self._ignore_file = True - return - 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.get_children(): - self.collect_block_lines(child, msg_state) - first = node.fromlineno - last = node.tolineno - # first child line number used to distinguish between disable - # which are the first child of scoped node with those defined later. - # For instance in the code below: - # - # 1. def meth8(self): - # 2. """test late disabling""" - # 3. # pylint: disable=E1102 - # 4. print self.blip - # 5. # pylint: disable=E1101 - # 6. print self.bla - # - # E1102 should be disabled from line 1 to 6 while E1101 from line 5 to 6 - # - # this is necessary to disable locally messages applying to class / - # function using their fromlineno - if isinstance(node, (nodes.Module, nodes.Class, nodes.Function)) and node.body: - firstchildlineno = node.body[0].fromlineno - else: - firstchildlineno = last - for msgid, lines in msg_state.iteritems(): - for lineno, state in lines.items(): - original_lineno = lineno - if first <= lineno <= last: - # Set state for all lines for this block, if the - # warning is applied to nodes. - if self.check_message_id(msgid).scope == WarningScope.NODE: - if lineno > firstchildlineno: - state = True - first_, last_ = node.block_range(lineno) - else: - first_ = lineno - last_ = last - for line in xrange(first_, last_+1): - # do not override existing entries - if not line in self._module_msgs_state.get(msgid, ()): - if line in lines: # state change in the same block - state = lines[line] - original_lineno = line - if not state: - self._suppression_mapping[(msgid, line)] = original_lineno - try: - self._module_msgs_state[msgid][line] = state - except KeyError: - self._module_msgs_state[msgid] = {line: state} - del lines[lineno] - - - # code checking methods ################################################### - - def get_checkers(self): - """return all available checkers as a list""" - return [self] + [c for checkers in self._checkers.itervalues() - for c in checkers if c is not self] - - def prepare_checkers(self): - """return checkers needed for activated messages and reports""" - if not self.config.reports: - self.disable_reporters() - # get needed checkers - neededcheckers = [self] - for checker in self.get_checkers()[1:]: - # fatal errors should not trigger enable / disabling a checker - messages = set(msg for msg in checker.msgs - if msg[0] != 'F' and self.is_message_enabled(msg)) - if (messages or - any(self.report_is_enabled(r[0]) for r in checker.reports)): - neededcheckers.append(checker) - checker.active_msgs = messages - return neededcheckers - - def should_analyze_file(self, modname, path): # pylint: disable=unused-argument - """Returns whether or not a module should be checked. - - This implementation returns True for all inputs, indicating that all - files should be linted. - - Subclasses may override this method to indicate that modules satisfying - certain conditions should not be linted. - - :param str modname: The name of the module to be checked. - :param str path: The full path to the source code of the module. - :returns: True if the module should be checked. - :rtype: bool - """ - return True - - def check(self, files_or_modules): - """main checking entry: check a list of files or modules from their - name. - """ - if not isinstance(files_or_modules, (list, tuple)): - files_or_modules = (files_or_modules,) - walker = PyLintASTWalker(self) - checkers = self.prepare_checkers() - tokencheckers = [c for c in checkers if implements(c, ITokenChecker) - and c is not self] - rawcheckers = [c for c in checkers if implements(c, IRawChecker)] - # notify global begin - for checker in checkers: - checker.open() - if implements(checker, IAstroidChecker): - walker.add_checker(checker) - # build ast and check modules or packages - for descr in self.expand_files(files_or_modules): - modname, filepath = descr['name'], descr['path'] - if not self.should_analyze_file(modname, filepath): - continue - if self.config.files_output: - reportfile = 'pylint_%s.%s' % (modname, self.reporter.extension) - self.reporter.set_output(open(reportfile, 'w')) - self.set_current_module(modname, filepath) - # get the module representation - astroid = self.get_ast(filepath, modname) - if astroid is None: - continue - self.base_name = descr['basename'] - self.base_file = descr['basepath'] - self._ignore_file = False - # fix the current file (if the source file was not available or - # if it's actually a c extension) - self.current_file = astroid.file # pylint: disable=maybe-no-member - self.check_astroid_module(astroid, walker, rawcheckers, tokencheckers) - self._add_suppression_messages() - # notify global end - self.set_current_module('') - self.stats['statement'] = walker.nbstatements - checkers.reverse() - for checker in checkers: - checker.close() - - def expand_files(self, modules): - """get modules and errors from a list of modules and handle errors - """ - result, errors = expand_modules(modules, self.config.black_list) - for error in errors: - message = modname = error["mod"] - key = error["key"] - self.set_current_module(modname) - if key == "F0001": - message = str(error["ex"]).replace(os.getcwd() + os.sep, '') - self.add_message(key, args=message) - return result - - def set_current_module(self, modname, filepath=None): - """set the name of the currently analyzed module and - init statistics for it - """ - if not modname and filepath is None: - return - self.reporter.on_set_current_module(modname, filepath) - 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.itervalues(): - self.stats['by_module'][modname][msg_cat] = 0 - # XXX hack, to be correct we need to keep module_msgs_state - # for every analyzed module (the problem stands with localized - # messages which are only detected in the .close step) - if modname: - self._module_msgs_state = {} - self._raw_module_msgs_state = {} - self._ignored_msgs = {} - - def get_ast(self, filepath, modname): - """return a ast(roid) representation for a module""" - try: - return MANAGER.ast_from_file(filepath, modname, source=True) - except SyntaxError, ex: - self.add_message('E0001', line=ex.lineno, args=ex.msg) - except AstroidBuildingException, ex: - self.add_message('F0010', args=ex) - except Exception, ex: - import traceback - traceback.print_exc() - self.add_message('F0002', args=(ex.__class__, ex)) - - def check_astroid_module(self, astroid, walker, rawcheckers, tokencheckers): - """check a module from its astroid representation, real work""" - # call raw checkers if possible - try: - tokens = tokenize_module(astroid) - except tokenize.TokenError, ex: - self.add_message('E0001', line=ex.args[1][0], args=ex.args[0]) - return - - if not astroid.pure_python: - self.add_message('I0001', args=astroid.name) - else: - #assert astroid.file.endswith('.py') - # invoke ITokenChecker interface on self to fetch module/block - # level options - self.process_tokens(tokens) - if self._ignore_file: - return False - # walk ast to collect line numbers - for msg, lines in self._module_msgs_state.iteritems(): - self._raw_module_msgs_state[msg] = lines.copy() - orig_state = self._module_msgs_state.copy() - self._module_msgs_state = {} - self._suppression_mapping = {} - self.collect_block_lines(astroid, orig_state) - for checker in rawcheckers: - checker.process_module(astroid) - for checker in tokencheckers: - checker.process_tokens(tokens) - # generate events to astroid checkers - walker.walk(astroid) - return True - - # IAstroidChecker interface ################################################# - - def open(self): - """initialize counters""" - self.stats = {'by_module' : {}, - 'by_msg' : {}, - } - for msg_cat in MSG_TYPES.itervalues(): - 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 - """ - if self.base_name is not None: - # load previous results if any - previous_stats = config.load_results(self.base_name) - # XXX code below needs refactoring to be more reporter agnostic - self.reporter.on_close(self.stats, previous_stats) - if self.config.reports: - sect = self.make_reports(self.stats, previous_stats) - if self.config.files_output: - filename = 'pylint_global.' + self.reporter.extension - self.reporter.set_output(open(filename, 'w')) - else: - sect = Section() - if self.config.reports or self.config.output_format == 'html': - self.reporter.display_results(sect) - # save results if persistent run - if self.config.persistent: - config.save_results(self.stats, self.base_name) - else: - self.reporter.on_close(self.stats, {}) - - # specific reports ######################################################## - - def _add_suppression_messages(self): - for warning, lines in self._raw_module_msgs_state.iteritems(): - for line, enable in lines.iteritems(): - if not enable and (warning, line) not in self._ignored_msgs: - self.add_message('I0021', line, None, - (self.get_msg_display_string(warning),)) - # don't use iteritems here, _ignored_msgs may be modified by add_message - for (warning, from_), lines in self._ignored_msgs.items(): - for line in lines: - self.add_message('I0020', line, None, - (self.get_msg_display_string(warning), from_)) - - def report_evaluation(self, sect, stats, previous_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 occurred while rating: %s' % ex - else: - stats['global_note'] = note - msg = 'Your code has been rated at %.2f/10' % note - pnote = previous_stats.get('global_note') - if pnote is not None: - msg += ' (previous run: %.2f/10, %+.2f)' % (pnote, note - pnote) - if self.config.comment: - msg = '%s\n%s' % (msg, config.get_note_message(note)) - sect.append(Text(msg)) - -# some reporting functions #################################################### - -def report_total_messages_stats(sect, stats, previous_stats): - """make total errors / warnings report""" - lines = ['type', 'number', 'previous', 'difference'] - lines += table_lines_from_stats(stats, previous_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 = sorted([(value, msg_id) - for msg_id, value in stats['by_msg'].iteritems() - if not msg_id.startswith('I')]) - in_order.reverse() - lines = ('message id', 'occurrences') - for value, msg_id in in_order: - lines += (msg_id, str(value)) - sect.append(Table(children=lines, cols=2, rheaders=1)) - -def report_messages_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'].iterkeys(): - 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.iteritems(): - 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 -# XXX syt, actually needed since we don't import code? - -from logilab.common.compat import builtins -builtins._ = str - - -class ArgumentPreprocessingError(Exception): - """Raised if an error occurs during argument preprocessing.""" - - -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 - """ - i = 0 - while i < len(args): - arg = args[i] - if arg.startswith('--'): - try: - option, val = arg[2:].split('=', 1) - except ValueError: - option, val = arg[2:], None - try: - cb, takearg = search_for[option] - except KeyError: - i += 1 - else: - del args[i] - if takearg and val is None: - if i >= len(args) or args[i].startswith('-'): - msg = 'Option %s expects a value' % option - raise ArgumentPreprocessingError(msg) - val = args[i] - del args[i] - elif not takearg and val is not None: - msg = "Option %s doesn't expects a value" % option - raise ArgumentPreprocessingError(msg) - cb(option, val) - else: - i += 1 - -class Run(object): - """helper class to use as main for pylint : - - run(*sys.argv[1:]) - """ - LinterClass = PyLinter - option_groups = ( - ('Commands', 'Options which are actually commands. Options in this \ -group are mutually exclusive.'), - ) - - def __init__(self, args, reporter=None, exit=True): - self._rcfile = None - self._plugins = [] - try: - preprocess_options(args, { - # option: (callback, takearg) - 'init-hooks': (cb_init_hook, True), - 'rcfile': (self.cb_set_rcfile, True), - 'load-plugins': (self.cb_add_plugins, True), - }) - except ArgumentPreprocessingError, ex: - print >> sys.stderr, ex - sys.exit(32) - - self.linter = linter = self.LinterClass(( - ('rcfile', - {'action' : 'callback', 'callback' : lambda *args: 1, - 'type': 'string', 'metavar': '<file>', - 'help' : 'Specify a configuration file.'}), - - ('init-hook', - {'action' : 'callback', 'callback' : lambda *args: 1, - 'type' : 'string', 'metavar': '<code>', - 'level': 1, - 'help' : 'Python code to execute, usually for sys.path \ -manipulation such as pygtk.require().'}), - - ('help-msg', - {'action' : 'callback', 'type' : 'string', 'metavar': '<msg-id>', - 'callback' : self.cb_help_message, - 'group': 'Commands', - 'help' : '''Display a help message for the given message id and \ -exit. The value may be a comma separated list of message ids.'''}), - - ('list-msgs', - {'action' : 'callback', 'metavar': '<msg-id>', - 'callback' : self.cb_list_messages, - 'group': 'Commands', 'level': 1, - 'help' : "Generate pylint's messages."}), - - ('full-documentation', - {'action' : 'callback', 'metavar': '<msg-id>', - 'callback' : self.cb_full_documentation, - 'group': 'Commands', 'level': 1, - 'help' : "Generate pylint's full documentation."}), - - ('generate-rcfile', - {'action' : 'callback', 'callback' : self.cb_generate_config, - 'group': 'Commands', - 'help' : '''Generate a sample configuration file according to \ -the current configuration. You can put other options before this one to get \ -them in the generated configuration.'''}), - - ('generate-man', - {'action' : 'callback', 'callback' : self.cb_generate_manpage, - 'group': 'Commands', - 'help' : "Generate pylint's man page.", 'hide': True}), - - ('errors-only', - {'action' : 'callback', 'callback' : self.cb_error_mode, - 'short': 'E', - 'help' : '''In error 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, 'hide': True, - 'help' : 'Profiled execution.'}), - - ), option_groups=self.option_groups, pylintrc=self._rcfile) - # register standard checkers - linter.load_default_plugins() - # load command line plugins - linter.load_plugin_modules(self._plugins) - # add some help section - linter.add_help_section('Environment variables', config.ENV_HELP, level=1) - linter.add_help_section('Output', -'Using the default text output, the message format is : \n' -' \n' -' MESSAGE_TYPE: LINE_NUM:[OBJECT:] MESSAGE \n' -' \n' -'There are 5 kind of message types : \n' -' * (C) convention, for programming standard violation \n' -' * (R) refactor, for bad code smell \n' -' * (W) warning, for python specific problems \n' -' * (E) error, for probable bugs in the code \n' -' * (F) fatal, if an error occurred which prevented pylint from doing further\n' -'processing.\n' - , level=1) - linter.add_help_section('Output status code', -'Pylint should leave with following status code: \n' -' * 0 if everything went fine \n' -' * 1 if a fatal message was issued \n' -' * 2 if an error message was issued \n' -' * 4 if a warning message was issued \n' -' * 8 if a refactor message was issued \n' -' * 16 if a convention message was issued \n' -' * 32 on usage error \n' -' \n' -'status 1 to 16 will be bit-ORed so you can know which different categories has\n' -'been issued by analysing pylint output status code\n', - level=1) - # read configuration - linter.disable('W0704') - linter.disable('I0020') - linter.disable('I0021') - linter.read_config_file() - # is there some additional plugins in the file configuration, in - config_parser = linter.cfgfile_parser - if config_parser.has_option('MASTER', 'load-plugins'): - plugins = splitstrip(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() - if reporter: - # if a custom reporter is provided as argument, it may be overridden - # by file parameters, so re-set it here, but before command line - # parsing so it's still overrideable by command line option - linter.set_reporter(reporter) - try: - args = linter.load_command_line_configuration(args) - except SystemExit, exc: - if exc.code == 2: # bad options - exc.code = 32 - raise - if not args: - print linter.help() - sys.exit(32) - # insert current working directory to the python path to have a correct - # behaviour - linter.prepare_import_path(args) - if self.linter.config.profile: - print >> sys.stderr, '** profiled run' - import cProfile, pstats - cProfile.runctx('linter.check(%r)' % args, globals(), locals(), - 'stones.prof') - data = pstats.Stats('stones.prof') - data.strip_dirs() - data.sort_stats('time', 'calls') - data.print_stats(30) - else: - linter.check(args) - linter.cleanup_import_path() - if exit: - sys.exit(self.linter.msg_status) - - def cb_set_rcfile(self, name, value): - """callback for option preprocessing (i.e. before option parsing)""" - self._rcfile = value - - def cb_add_plugins(self, name, value): - """callback for option preprocessing (i.e. before option parsing)""" - self._plugins.extend(splitstrip(value)) - - def cb_error_mode(self, *args, **kwargs): - """error mode: - * disable all but error messages - * disable the 'miscellaneous' checker which can be safely deactivated in - debug - * disable reports - * do not save execution information - """ - self.linter.error_mode() - - def cb_generate_config(self, *args, **kwargs): - """optik callback for sample config file generation""" - self.linter.generate_config(skipsections=('COMMANDS',)) - 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, optname, value, parser): - """optik callback for printing some help about a particular message""" - self.linter.help_message(splitstrip(value)) - sys.exit(0) - - def cb_full_documentation(self, option, optname, value, parser): - """optik callback for printing full documentation""" - self.linter.print_full_documentation() - sys.exit(0) - - def cb_list_messages(self, option, optname, value, parser): # FIXME - """optik callback for printing available messages""" - self.linter.list_messages() - sys.exit(0) - -def cb_init_hook(optname, value): - """exec arbitrary code to set sys.path for instance""" - exec value - - -if __name__ == '__main__': - Run(sys.argv[1:]) diff --git a/man/pylint.1 b/man/pylint.1 index f80c61c..3a5c3e9 100644 --- a/man/pylint.1 +++ b/man/pylint.1 @@ -1,4 +1,4 @@ -.TH pylint 1 "2014-4-11" pylint +.TH pylint 1 "2014-11-4" pylint .SH NAME .B pylint \- python code static checker @@ -45,23 +45,29 @@ Python code to execute, usually for sys.path manipulation such as pygtk.require( .IP "--errors-only, -E" In error mode, checkers without error messages are disabled and for others, only the ERROR messages are displayed, and no reports are done by default .IP "--ignore=<file>[,<file>...]" -Add files or directories to the blacklist. They should be base names, not paths. [current: .hg,test] +Add files or directories to the blacklist. They should be base names, not paths. [current: CVS] .IP "--persistent=<y_or_n>" Pickle collected data for later comparisons. [current: yes] .IP "--load-plugins=<modules>" List of plugins (as comma separated values of python modules names) to load, usually to register additional checkers. [current: none] +.IP "--jobs=<n-processes>, -j <n-processes>" +Use multiple processes to speed up Pylint. [current: 1] .SH COMMANDS .IP "--help-msg=<msg-id>" Display a help message for the given message id and exit. The value may be a comma separated list of message ids. .IP "--list-msgs" Generate pylint's messages. +.IP "--list-conf-levels" +Generate pylint's messages. .IP "--full-documentation" Generate pylint's full documentation. .IP "--generate-rcfile" Generate a sample configuration file according to the current configuration. You can put other options before this one to get them in the generated configuration. .SH MESSAGES CONTROL +.IP "--confidence=<levels>" +Only show warnings with the listed confidence levels. Leave empty to show all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED [current: none] .IP "--enable=<msg ids>, -e <msg ids>" Enable the message, report, category or checker with the given id(s). You can either give multiple identifier separated by comma (,) or put this option multiple time. See also the "--disable" option for examples. .IP "--disable=<msg ids>, -d <msg ids>" @@ -75,26 +81,42 @@ Put messages in a separate file for each module / package specified on the comma .IP "--reports=<y_or_n>, -r <y_or_n>" Tells whether to display a full report or only the messages [current: yes] .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 respectively contain the number of errors / warnings messages and the total number of statements analyzed. This is used by the global evaluation report (RP0004). [current: 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)] +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 respectively contain the number of errors / warnings messages and the total number of statements analyzed. This is used by the global evaluation report (RP0004). [current: 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)] .IP "--comment=<y_or_n>" Add a comment according to your evaluation note. This is used by the global evaluation report (RP0004). [current: no] .IP "--msg-template=<template>" Template used to display messages. This is a python new-style format string used to format the message information. See doc for all details -.SH IMPORTS -.IP "--deprecated-modules=<modules>" -Deprecated modules which should not be used, separated by a comma [current: regsub,string,TERMIOS,Bastion,rexec] -.IP "--import-graph=<file.dot>" -Create a graph of every (i.e. internal and external) dependencies in the given file (report RP0402 must not be disabled) [current: none] -.IP "--ext-import-graph=<file.dot>" -Create a graph of external dependencies in the given file (report RP0402 must not be disabled) [current: none] -.IP "--int-import-graph=<file.dot>" -Create a graph of internal dependencies in the given file (report RP0402 must not be disabled) [current: none] +.SH EXCEPTIONS +.IP "--overgeneral-exceptions=<comma-separated class names>" +Exceptions that will emit a warning when being caught. Defaults to "Exception" [current: Exception] + +.SH CLASSES +.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. [current: isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by] +.IP "--defining-attr-methods=<method names>" +List of method names used to declare (i.e. assign) instance attributes. [current: __init__,__new__,setUp] +.IP "--valid-classmethod-first-arg=<argument names>" +List of valid names for the first argument in a class method. [current: cls] +.IP "--valid-metaclass-classmethod-first-arg=<argument names>" +List of valid names for the first argument in a metaclass class method. [current: mcs] +.IP "--exclude-protected=<protected access exclusions>" +List of member names, which should be excluded from the protected access warning. [current: _asdict,_fields,_replace,_source,_make] .SH LOGGING .IP "--logging-modules=<comma separated list>" Logging modules to check that the string format arguments are in logging function parameter format [current: logging] +.SH VARIABLES +.IP "--init-import=<y_or_n>" +Tells whether we should check for unused import in __init__ files. [current: no] +.IP "--dummy-variables-rgx=<regexp>" +A regular expression matching the name of dummy variables (i.e. expectedly not used). [current: _$|dummy] +.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. [current: none] +.IP "--callbacks=<callbacks>" +List of strings which can identify a callback function by name. A callback name must start or end with one of those strings. [current: cb_,_cb] + .SH DESIGN .IP "--max-args=<int>" Maximum number of arguments for function / method [current: 5] @@ -117,25 +139,11 @@ Minimum number of public methods for a class (see R0903). [current: 2] .IP "--max-public-methods=<num>" Maximum number of public methods for a class (see R0904). [current: 20] -.SH FORMAT -.IP "--max-line-length=<int>" -Maximum number of characters on a single line. [current: 80] -.IP "--ignore-long-lines=<regexp>" -Regexp for a line that is allowed to be longer than the limit. [current: ^\s*(# )?<?https?://\S+>?$] -.IP "--single-line-if-stmt=<y_or_n>" -Allow the body of an if to be on the same line as the test if there is no else. [current: no] -.IP "--no-space-check=NO_SPACE_CHECK" -List of optional constructs for which whitespace checking is disabled [current: trailing-comma,dict-separator] -.IP "--max-module-lines=<int>" -Maximum number of lines in a module [current: 1000] -.IP "--indent-string=<string>" -String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 tab). [current: ' '] - .SH BASIC .IP "--required-attributes=<attributes>" Required attributes for module, separated by a comma [current: none] .IP "--bad-functions=<builtin function names>" -List of builtins function names that should not be used, separated by a comma [current: map,filter,apply,input] +List of builtins function names that should not be used, separated by a comma [current: map,filter,input] .IP "--good-names=<names>" Good variable names which should always be accepted, separated by a comma [current: i,j,k,ex,Run,_] .IP "--bad-names=<names>" @@ -189,20 +197,6 @@ Regular expression which should only match function or class names that do not r .IP "--docstring-min-length=<int>" Minimum line length for functions/classes that require docstrings, shorter ones are exempt. [current: -1] -.SH EXCEPTIONS -.IP "--overgeneral-exceptions=<comma-separated class names>" -Exceptions that will emit a warning when being caught. Defaults to "Exception" [current: Exception] - -.SH SIMILARITIES -.IP "--min-similarity-lines=<int>" -Minimum lines number of a similarity. [current: 4] -.IP "--ignore-comments=<y or n>" -Ignore comments when computing similarities. [current: yes] -.IP "--ignore-docstrings=<y or n>" -Ignore docstrings when computing similarities. [current: yes] -.IP "--ignore-imports=<y or n>" -Ignore imports when computing similarities. [current: no] - .SH MISCELLANEOUS .IP "--notes=<comma separated values>" List of note tags to take in consideration, separated by a comma. [current: FIXME,XXX,TODO] @@ -210,6 +204,8 @@ List of note tags to take in consideration, separated by a comma. [current: FIXM .SH TYPECHECK .IP "--ignore-mixin-members=<y_or_n>" Tells whether missing members accessed in mixin class should be ignored. A mixin class is detected if its name ends with "mixin" (case insensitive). [current: yes] +.IP "--ignored-modules=<module names>" +List of module names for which member attributes should not be checked (useful for modules/projects where namespaces are manipulated during runtime and thus existing member attributes cannot be deduced by static analysis [current: none] .IP "--ignored-classes=<members names>" List of classes names for which member attributes should not be checked (useful for classes with attributes dynamically set). [current: SQLObject] .IP "--zope=<y_or_n>" @@ -217,23 +213,53 @@ When zope mode is activated, add a predefined set of Zope acquired attributes to .IP "--generated-members=<members names>" List of members which are set dynamically and missed by pylint inference system, and so shouldn't trigger E0201 when accessed. Python regular expressions are accepted. [current: REQUEST,acl_users,aq_parent] -.SH VARIABLES -.IP "--init-import=<y_or_n>" -Tells whether we should check for unused import in __init__ files. [current: no] -.IP "--dummy-variables-rgx=<regexp>" -A regular expression matching the beginning of the name of dummy variables (i.e. not used). [current: _|dummy] -.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. [current: none] +.SH SPELLING +.IP "--spelling-dict=<dict name>" +Spelling dictionary name. Available dictionaries: none. To make it working install python-enchant package. [current: none] +.IP "--spelling-ignore-words=<comma separated words>" +List of comma separated words that should not be checked. [current: none] +.IP "--spelling-private-dict-file=<path to file>" +A path to a file that contains private dictionary; one word per line. [current: none] +.IP "--spelling-store-unknown-words=<y_or_n>" +Tells whether to store unknown words to indicated private dictionary in --spelling-private-dict-file option instead of raising a message. [current: no] -.SH CLASSES -.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. [current: isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by] -.IP "--defining-attr-methods=<method names>" -List of method names used to declare (i.e. assign) instance attributes. [current: __init__,__new__,setUp] -.IP "--valid-classmethod-first-arg=<argument names>" -List of valid names for the first argument in a class method. [current: cls] -.IP "--valid-metaclass-classmethod-first-arg=<argument names>" -List of valid names for the first argument in a metaclass class method. [current: mcs] +.SH FORMAT +.IP "--max-line-length=<int>" +Maximum number of characters on a single line. [current: 80] +.IP "--ignore-long-lines=<regexp>" +Regexp for a line that is allowed to be longer than the limit. [current: ^\s*(# )?<?https?://\S+>?$] +.IP "--single-line-if-stmt=<y_or_n>" +Allow the body of an if to be on the same line as the test if there is no else. [current: no] +.IP "--no-space-check=NO_SPACE_CHECK" +List of optional constructs for which whitespace checking is disabled [current: trailing-comma,dict-separator] +.IP "--max-module-lines=<int>" +Maximum number of lines in a module [current: 1000] +.IP "--indent-string=<string>" +String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 tab). [current: ' '] +.IP "--indent-after-paren=<int>" +Number of spaces of indent required inside a hanging or continued line. [current: 4] +.IP "--expected-line-ending-format=<empty or LF or CRLF>" +Expected format of line ending, e.g. empty (any line ending), LF or CRLF. [current: none] + +.SH IMPORTS +.IP "--deprecated-modules=<modules>" +Deprecated modules which should not be used, separated by a comma [current: regsub,TERMIOS,Bastion,rexec] +.IP "--import-graph=<file.dot>" +Create a graph of every (i.e. internal and external) dependencies in the given file (report RP0402 must not be disabled) [current: none] +.IP "--ext-import-graph=<file.dot>" +Create a graph of external dependencies in the given file (report RP0402 must not be disabled) [current: none] +.IP "--int-import-graph=<file.dot>" +Create a graph of internal dependencies in the given file (report RP0402 must not be disabled) [current: none] + +.SH SIMILARITIES +.IP "--min-similarity-lines=<int>" +Minimum lines number of a similarity. [current: 4] +.IP "--ignore-comments=<y or n>" +Ignore comments when computing similarities. [current: yes] +.IP "--ignore-docstrings=<y or n>" +Ignore docstrings when computing similarities. [current: yes] +.IP "--ignore-imports=<y or n>" +Ignore imports when computing similarities. [current: no] .SH ENVIRONMENT VARIABLES diff --git a/pylint/__init__.py b/pylint/__init__.py new file mode 100644 index 0000000..82e557d --- /dev/null +++ b/pylint/__init__.py @@ -0,0 +1,46 @@ +# Copyright (c) 2003-2012 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +import sys + +from .__pkginfo__ import version as __version__ + +def run_pylint(): + """run pylint""" + from pylint.lint import Run + Run(sys.argv[1:]) + +def run_pylint_gui(): + """run pylint-gui""" + try: + from pylint.gui import Run + Run(sys.argv[1:]) + except ImportError: + sys.exit('tkinter is not available') + +def run_epylint(): + """run pylint""" + from pylint.epylint import Run + Run() + +def run_pyreverse(): + """run pyreverse""" + from pylint.pyreverse.main import Run + Run(sys.argv[1:]) + +def run_symilar(): + """run symilar""" + from pylint.checkers.similar import Run + Run(sys.argv[1:]) diff --git a/__main__.py b/pylint/__main__.py index 7716361..7716361 100644 --- a/__main__.py +++ b/pylint/__main__.py diff --git a/pylint/__pkginfo__.py b/pylint/__pkginfo__.py new file mode 100644 index 0000000..ebbbb79 --- /dev/null +++ b/pylint/__pkginfo__.py @@ -0,0 +1,70 @@ +# pylint: disable=W0622,C0103 +# Copyright (c) 2003-2014 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +"""pylint packaging information""" +from __future__ import absolute_import + +modname = distname = 'pylint' + +numversion = (1, 4, 1) +version = '.'.join([str(num) for num in numversion]) + +install_requires = ['logilab-common >= 0.53.0', 'astroid >= 1.3.3', 'six'] + +license = 'GPL' +description = "python code static checker" +web = 'http://www.pylint.org' +mailinglist = "mailto://code-quality@python.org" +author = 'Logilab' +author_email = 'python-projects@lists.logilab.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', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 3', + 'Topic :: Software Development :: Debuggers', + 'Topic :: Software Development :: Quality Assurance', + 'Topic :: Software Development :: Testing' + ] + + +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. + . + Pylint is shipped with "pylint-gui", "pyreverse" (UML diagram generator) + and "symilar" (an independent similarities checker).""" + +from os.path import join +scripts = [join('bin', filename) + for filename in ('pylint', 'pylint-gui', "symilar", "epylint", + "pyreverse")] + +include_dirs = [join('pylint', 'test')] diff --git a/pylint/checkers/__init__.py b/pylint/checkers/__init__.py new file mode 100644 index 0000000..51adb4d --- /dev/null +++ b/pylint/checkers/__init__.py @@ -0,0 +1,124 @@ +# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +"""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 +11: typecheck +12: logging +13: string_format +14: string_constant +15: stdlib +16: python3 +17-50: not yet used: reserved for future internal checkers. +51-99: perhaps used: reserved for external checkers + +The raw_metrics checker has no number associated since it doesn't emit any +messages nor reports. XXX not true, emit a 07 report ! + +""" + +import sys +import tokenize +import warnings + +from logilab.common.configuration import OptionsProviderMixIn + +from pylint.reporters import diff_string +from pylint.utils import register_plugins +from pylint.interfaces import UNDEFINED + + +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 # pylint: disable=redefined-builtin + 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): + """base class for checkers""" + # checker name (you may reuse an existing one) + name = None + # options level (0 will be displaying in --help, 1 in --long-help) + level = 1 + # ordered list of options to control the ckecker behaviour + options = () + # messages issued by this checker + msgs = {} + # reports issued by this checker + reports = () + # mark this checker as enabled or not. + enabled = True + + def __init__(self, linter=None): + """checker instances should have the linter as argument + + linter is an object implementing ILinter + """ + self.name = self.name.lower() + OptionsProviderMixIn.__init__(self) + self.linter = linter + + def add_message(self, msg_id, line=None, node=None, args=None, confidence=UNDEFINED): + """add a message of a given type""" + self.linter.add_message(msg_id, line, node, args, confidence) + + # 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 BaseTokenChecker(BaseChecker): + """Base class for checkers that want to have access to the token stream.""" + + def process_tokens(self, tokens): + """Should be overridden by subclasses.""" + raise NotImplementedError() + + +def initialize(linter): + """initialize linter with checkers in this package """ + register_plugins(linter, __path__[0]) + +__all__ = ('BaseChecker', 'initialize') diff --git a/pylint/checkers/base.py b/pylint/checkers/base.py new file mode 100644 index 0000000..404f9de --- /dev/null +++ b/pylint/checkers/base.py @@ -0,0 +1,1253 @@ +# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# Copyright (c) 2009-2010 Arista Networks, Inc. +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +"""basic checker for Python code""" + +import collections +import itertools +import sys +import re + +import six +from six.moves import zip # pylint: disable=redefined-builtin + +from logilab.common.ureports import Table + +import astroid +import astroid.bases +from astroid import are_exclusive, InferenceError + +from pylint.interfaces import IAstroidChecker, INFERENCE, INFERENCE_FAILURE, HIGH +from pylint.utils import EmptyReport +from pylint.reporters import diff_string +from pylint.checkers import BaseChecker +from pylint.checkers.utils import ( + check_messages, + clobber_in_except, + is_builtin_object, + is_inside_except, + overrides_a_method, + safe_infer, + get_argument_from_call, + has_known_bases, + NoSuchArgumentError, + is_import_error, + unimplemented_abstract_methods, + ) + + +# regex for class/function/variable/constant name +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-Z0-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}$') +CLASS_ATTRIBUTE_RGX = re.compile(r'([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$') +# do not require a doc string on system methods +NO_REQUIRED_DOC_RGX = re.compile('__.*__') +REVERSED_METHODS = (('__getitem__', '__len__'), + ('__reversed__', )) + +PY33 = sys.version_info >= (3, 3) +PY3K = sys.version_info >= (3, 0) +BAD_FUNCTIONS = ['map', 'filter'] +if sys.version_info < (3, 0): + BAD_FUNCTIONS.append('input') + +# Name categories that are always consistent with all naming conventions. +EXEMPT_NAME_CATEGORIES = set(('exempt', 'ignore')) + +# A mapping from builtin-qname -> symbol, to be used when generating messages +# about dangerous default values as arguments +DEFAULT_ARGUMENT_SYMBOLS = dict( + zip(['.'.join([astroid.bases.BUILTINS, x]) for x in ('set', 'dict', 'list')], + ['set()', '{}', '[]']) +) + +del re + +def _redefines_import(node): + """ Detect that the given node (AssName) is inside an + exception handler and redefines an import from the tryexcept body. + Returns True if the node redefines an import, False otherwise. + """ + current = node + while current and not isinstance(current.parent, astroid.ExceptHandler): + current = current.parent + if not current or not is_import_error(current.parent): + return False + try_block = current.parent.parent + for import_node in try_block.nodes_of_class((astroid.From, astroid.Import)): + for name, alias in import_node.names: + if alias: + if alias == node.name: + return True + elif name == node.name: + return True + return False + +def in_loop(node): + """return True if the node is inside a kind of for loop""" + parent = node.parent + while parent is not None: + if isinstance(parent, (astroid.For, astroid.ListComp, astroid.SetComp, + astroid.DictComp, astroid.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 _loop_exits_early(loop): + """Returns true if a loop has a break statement in its body.""" + loop_nodes = (astroid.For, astroid.While) + # Loop over body explicitly to avoid matching break statements + # in orelse. + for child in loop.body: + if isinstance(child, loop_nodes): + # break statement may be in orelse of child loop. + # pylint: disable=superfluous-parens + for orelse in (child.orelse or ()): + for _ in orelse.nodes_of_class(astroid.Break, skip_klass=loop_nodes): + return True + continue + for _ in child.nodes_of_class(astroid.Break, skip_klass=loop_nodes): + return True + return False + +def _is_multi_naming_match(match, node_type, confidence): + return (match is not None and + match.lastgroup is not None and + match.lastgroup not in EXEMPT_NAME_CATEGORIES + and (node_type != 'method' or confidence != INFERENCE_FAILURE)) + + +if sys.version_info < (3, 0): + PROPERTY_CLASSES = set(('__builtin__.property', 'abc.abstractproperty')) +else: + PROPERTY_CLASSES = set(('builtins.property', 'abc.abstractproperty')) + + +def _determine_function_name_type(node): + """Determine the name type whose regex the a function's name should match. + + :param node: A function node. + :returns: One of ('function', 'method', 'attr') + """ + if not node.is_method(): + return 'function' + if node.decorators: + decorators = node.decorators.nodes + else: + decorators = [] + for decorator in decorators: + # If the function is a property (decorated with @property + # or @abc.abstractproperty), the name type is 'attr'. + if (isinstance(decorator, astroid.Name) or + (isinstance(decorator, astroid.Getattr) and + decorator.attrname == 'abstractproperty')): + infered = safe_infer(decorator) + if infered and infered.qname() in PROPERTY_CLASSES: + return 'attr' + # If the function is decorated using the prop_method.{setter,getter} + # form, treat it like an attribute as well. + elif (isinstance(decorator, astroid.Getattr) and + decorator.attrname in ('setter', 'deleter')): + return 'attr' + return 'method' + + + +def _has_abstract_methods(node): + """ + Determine if the given `node` has abstract methods. + + The methods should be made abstract by decorating them + with `abc` decorators. + """ + return len(unimplemented_abstract_methods(node)) > 0 + + +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'): + try: + total = stats[node_type] + except KeyError: + raise EmptyReport() + nice_stats[node_type] = {} + if total != 0: + try: + documented = total - stats['undocumented_'+node_type] + percent = (documented * 100.) / total + nice_stats[node_type]['percent_documented'] = '%.2f' % percent + except KeyError: + nice_stats[node_type]['percent_documented'] = 'NC' + try: + percent = (stats['badname_'+node_type] * 100.) / total + nice_stats[node_type]['percent_badname'] = '%.2f' % percent + except KeyError: + nice_stats[node_type]['percent_badname'] = 'NC' + 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, + nice_stats[node_type].get('percent_documented', '0'), + nice_stats[node_type].get('percent_badname', '0')) + sect.append(Table(children=lines, cols=6, rheaders=1)) + +def redefined_by_decorator(node): + """return True if the object is a method redefined via decorator. + + For example: + @property + def x(self): return self._x + @x.setter + def x(self, value): self._x = value + """ + if node.decorators: + for decorator in node.decorators.nodes: + if (isinstance(decorator, astroid.Getattr) and + getattr(decorator.expr, 'name', None) == node.name): + return True + return False + +class _BasicChecker(BaseChecker): + __implements__ = IAstroidChecker + name = 'basic' + +class BasicErrorChecker(_BasicChecker): + msgs = { + 'E0100': ('__init__ method is a generator', + 'init-is-generator', + 'Used when the special class method __init__ is turned into a ' + 'generator by a yield in its body.'), + 'E0101': ('Explicit return in __init__', + 'return-in-init', + 'Used when the special class method __init__ has an explicit ' + 'return value.'), + 'E0102': ('%s already defined line %s', + 'function-redefined', + 'Used when a function / class / method is redefined.'), + 'E0103': ('%r not properly in loop', + 'not-in-loop', + 'Used when break or continue keywords are used outside a loop.'), + 'E0104': ('Return outside function', + 'return-outside-function', + 'Used when a "return" statement is found outside a function or ' + 'method.'), + 'E0105': ('Yield outside function', + 'yield-outside-function', + 'Used when a "yield" statement is found outside a function or ' + 'method.'), + 'E0106': ('Return with argument inside generator', + 'return-arg-in-generator', + 'Used when a "return" statement with an argument is found ' + 'outside in a generator function or method (e.g. with some ' + '"yield" statements).', + {'maxversion': (3, 3)}), + 'E0107': ("Use of the non-existent %s operator", + 'nonexistent-operator', + "Used when you attempt to use the C-style pre-increment or" + "pre-decrement operator -- and ++, which doesn't exist in Python."), + 'E0108': ('Duplicate argument name %s in function definition', + 'duplicate-argument-name', + 'Duplicate argument names in function definitions are syntax' + ' errors.'), + 'E0110': ('Abstract class %r with abstract methods instantiated', + 'abstract-class-instantiated', + 'Used when an abstract class with `abc.ABCMeta` as metaclass ' + 'has abstract methods and is instantiated.'), + 'W0120': ('Else clause on loop without a break statement', + 'useless-else-on-loop', + 'Loops should only have an else clause if they can exit early ' + 'with a break statement, otherwise the statements under else ' + 'should be on the same scope as the loop itself.'), + } + + @check_messages('function-redefined') + def visit_class(self, node): + self._check_redefinition('class', node) + + @check_messages('init-is-generator', 'return-in-init', + 'function-redefined', 'return-arg-in-generator', + 'duplicate-argument-name') + def visit_function(self, node): + if not redefined_by_decorator(node): + self._check_redefinition(node.is_method() and 'method' or 'function', node) + # checks for max returns, branch, return in __init__ + returns = node.nodes_of_class(astroid.Return, + skip_klass=(astroid.Function, astroid.Class)) + if node.is_method() and node.name == '__init__': + if node.is_generator(): + self.add_message('init-is-generator', node=node) + else: + values = [r.value for r in returns] + # Are we returning anything but None from constructors + if [v for v in values + if not (v is None or + (isinstance(v, astroid.Const) and v.value is None) or + (isinstance(v, astroid.Name) and v.name == 'None') + )]: + self.add_message('return-in-init', node=node) + elif node.is_generator(): + # make sure we don't mix non-None returns and yields + if not PY33: + for retnode in returns: + if isinstance(retnode.value, astroid.Const) and \ + retnode.value.value is not None: + self.add_message('return-arg-in-generator', node=node, + line=retnode.fromlineno) + # Check for duplicate names + args = set() + for name in node.argnames(): + if name in args: + self.add_message('duplicate-argument-name', node=node, args=(name,)) + else: + args.add(name) + + + @check_messages('return-outside-function') + def visit_return(self, node): + if not isinstance(node.frame(), astroid.Function): + self.add_message('return-outside-function', node=node) + + @check_messages('yield-outside-function') + def visit_yield(self, node): + if not isinstance(node.frame(), (astroid.Function, astroid.Lambda)): + self.add_message('yield-outside-function', node=node) + + @check_messages('not-in-loop') + def visit_continue(self, node): + self._check_in_loop(node, 'continue') + + @check_messages('not-in-loop') + def visit_break(self, node): + self._check_in_loop(node, 'break') + + @check_messages('useless-else-on-loop') + def visit_for(self, node): + self._check_else_on_loop(node) + + @check_messages('useless-else-on-loop') + def visit_while(self, node): + self._check_else_on_loop(node) + + @check_messages('nonexistent-operator') + def visit_unaryop(self, node): + """check use of the non-existent ++ and -- operator operator""" + if ((node.op in '+-') and + isinstance(node.operand, astroid.UnaryOp) and + (node.operand.op == node.op)): + self.add_message('nonexistent-operator', node=node, args=node.op*2) + + @check_messages('abstract-class-instantiated') + def visit_callfunc(self, node): + """ Check instantiating abstract class with + abc.ABCMeta as metaclass. + """ + try: + infered = next(node.func.infer()) + except astroid.InferenceError: + return + if not isinstance(infered, astroid.Class): + return + # __init__ was called + metaclass = infered.metaclass() + abstract_methods = _has_abstract_methods(infered) + if metaclass is None: + # Python 3.4 has `abc.ABC`, which won't be detected + # by ClassNode.metaclass() + for ancestor in infered.ancestors(): + if ancestor.qname() == 'abc.ABC' and abstract_methods: + self.add_message('abstract-class-instantiated', + args=(infered.name, ), + node=node) + break + return + if metaclass.qname() == 'abc.ABCMeta' and abstract_methods: + self.add_message('abstract-class-instantiated', + args=(infered.name, ), + node=node) + + def _check_else_on_loop(self, node): + """Check that any loop with an else clause has a break statement.""" + if node.orelse and not _loop_exits_early(node): + self.add_message('useless-else-on-loop', node=node, + # This is not optimal, but the line previous + # to the first statement in the else clause + # will usually be the one that contains the else:. + line=node.orelse[0].lineno - 1) + + 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, (astroid.For, astroid.While)): + break + _node = _node.parent + else: + self.add_message('not-in-loop', node=node, args=node_name) + + def _check_redefinition(self, redeftype, 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('function-redefined', node=node, + args=(redeftype, defined_self.fromlineno)) + + + +class BasicChecker(_BasicChecker): + """checks for : + * doc strings + * number of arguments, local variables, branches, 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__ = IAstroidChecker + + name = 'basic' + msgs = { + 'W0101': ('Unreachable code', + 'unreachable', + 'Used when there is some code behind a "return" or "raise" ' + 'statement, which will never be accessed.'), + 'W0102': ('Dangerous default value %s as argument', + 'dangerous-default-value', + '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', + 'pointless-statement', + 'Used when a statement doesn\'t have (or at least seems to) ' + 'any effect.'), + 'W0105': ('String statement has no effect', + 'pointless-string-statement', + 'Used when a string is used as a statement (which of course ' + 'has no effect). This is a particular case of W0104 with its ' + 'own message so you can easily disable it if you\'re using ' + 'those strings as documentation, instead of comments.'), + 'W0106': ('Expression "%s" is assigned to nothing', + 'expression-not-assigned', + 'Used when an expression that is not a function call is assigned ' + 'to nothing. Probably something else was intended.'), + 'W0108': ('Lambda may not be necessary', + 'unnecessary-lambda', + 'Used when the body of a lambda expression is a function call ' + 'on the same argument list as the lambda itself; such lambda ' + 'expressions are in all but a few cases replaceable with the ' + 'function being called in the body of the lambda.'), + 'W0109': ("Duplicate key %r in dictionary", + 'duplicate-key', + 'Used when a dictionary expression binds the same key multiple ' + 'times.'), + 'W0122': ('Use of exec', + 'exec-used', + 'Used when you use the "exec" statement (function for Python ' + '3), to discourage its usage. That doesn\'t ' + 'mean you can not use it !'), + 'W0123': ('Use of eval', + 'eval-used', + 'Used when you use the "eval" function, to discourage its ' + 'usage. Consider using `ast.literal_eval` for safely evaluating ' + 'strings containing Python expressions ' + 'from untrusted sources. '), + 'W0141': ('Used builtin function %r', + 'bad-builtin', + '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', + 'star-args', + 'Used when a function or method is called using `*args` or ' + '`**kwargs` to dispatch arguments. This doesn\'t improve ' + 'readability and should be used with care.'), + 'W0150': ("%s statement in finally block may swallow exception", + 'lost-exception', + 'Used when a break or a return statement is found inside the ' + 'finally clause of a try...finally block: the exceptions raised ' + 'in the try clause will be silently swallowed instead of being ' + 're-raised.'), + 'W0199': ('Assert called on a 2-uple. Did you mean \'assert x,y\'?', + 'assert-on-tuple', + 'A call of assert on a tuple will always evaluate to true if ' + 'the tuple is not empty, and will always evaluate to false if ' + 'it is.'), + 'C0121': ('Missing required attribute "%s"', # W0103 + 'missing-module-attribute', + 'Used when an attribute required for modules is missing.'), + + 'E0109': ('Missing argument to reversed()', + 'missing-reversed-argument', + 'Used when reversed() builtin didn\'t receive an argument.'), + 'E0111': ('The first reversed() argument is not a sequence', + 'bad-reversed-sequence', + 'Used when the first argument to reversed() builtin ' + 'isn\'t a sequence (does not implement __reversed__, ' + 'nor __getitem__ and __len__'), + + } + + options = (('required-attributes', + {'default' : (), 'type' : 'csv', + 'metavar' : '<attributes>', + 'help' : 'Required attributes for module, separated by a ' + 'comma'} + ), + ('bad-functions', + {'default' : BAD_FUNCTIONS, + 'type' :'csv', 'metavar' : '<builtin function names>', + 'help' : 'List of builtins function names that should not be ' + 'used, separated by a comma'} + ), + ) + reports = (('RP0101', 'Statistics by type', report_by_type_stats),) + + def __init__(self, linter): + _BasicChecker.__init__(self, linter) + self.stats = None + self._tryfinallys = None + + def open(self): + """initialize visit variables and statistics + """ + self._tryfinallys = [] + self.stats = self.linter.add_stats(module=0, function=0, + method=0, class_=0) + + @check_messages('missing-module-attribute') + def visit_module(self, node): + """check module name, docstring and required arguments + """ + self.stats['module'] += 1 + for attr in self.config.required_attributes: + if attr not in node: + self.add_message('missing-module-attribute', node=node, args=attr) + + def visit_class(self, node): # pylint: disable=unused-argument + """check module name, docstring and redefinition + increment branch counter + """ + self.stats['class'] += 1 + + @check_messages('pointless-statement', 'pointless-string-statement', + 'expression-not-assigned') + def visit_discard(self, node): + """check for various kind of statements without effect""" + expr = node.value + if isinstance(expr, astroid.Const) and isinstance(expr.value, + six.string_types): + # treat string statement in a separated message + # Handle PEP-257 attribute docstrings. + # An attribute docstring is defined as being a string right after + # an assignment at the module level, class level or __init__ level. + scope = expr.scope() + if isinstance(scope, (astroid.Class, astroid.Module, astroid.Function)): + if isinstance(scope, astroid.Function) and scope.name != '__init__': + pass + else: + sibling = expr.previous_sibling() + if (sibling is not None and sibling.scope() is scope and + isinstance(sibling, astroid.Assign)): + return + self.add_message('pointless-string-statement', node=node) + return + # ignore if this is : + # * a direct function call + # * the unique child of a try/except body + # * a yield (which are wrapped by a discard node in _ast XXX) + # warn W0106 if we have any underlying function call (we can't predict + # side effects), else pointless-statement + if (isinstance(expr, (astroid.Yield, astroid.CallFunc)) or + (isinstance(node.parent, astroid.TryExcept) and + node.parent.body == [node])): + return + if any(expr.nodes_of_class(astroid.CallFunc)): + self.add_message('expression-not-assigned', node=node, + args=expr.as_string()) + else: + self.add_message('pointless-statement', node=node) + + @check_messages('unnecessary-lambda') + def visit_lambda(self, node): + """check whether or not the lambda is suspicious + """ + # if the body of the lambda is a call expression with the same + # argument list as the lambda itself, then the lambda is + # possibly unnecessary and at least suspicious. + if node.args.defaults: + # If the arguments of the lambda include defaults, then a + # judgment cannot be made because there is no way to check + # that the defaults defined by the lambda are the same as + # the defaults defined by the function called in the body + # of the lambda. + return + call = node.body + if not isinstance(call, astroid.CallFunc): + # The body of the lambda must be a function call expression + # for the lambda to be unnecessary. + return + # XXX are lambda still different with astroid >= 0.18 ? + # *args and **kwargs need to be treated specially, since they + # are structured differently between the lambda and the function + # call (in the lambda they appear in the args.args list and are + # indicated as * and ** by two bits in the lambda's flags, but + # in the function call they are omitted from the args list and + # are indicated by separate attributes on the function call node). + ordinary_args = list(node.args.args) + if node.args.kwarg: + if (not call.kwargs + or not isinstance(call.kwargs, astroid.Name) + or node.args.kwarg != call.kwargs.name): + return + elif call.kwargs: + return + if node.args.vararg: + if (not call.starargs + or not isinstance(call.starargs, astroid.Name) + or node.args.vararg != call.starargs.name): + return + elif call.starargs: + return + # The "ordinary" arguments must be in a correspondence such that: + # ordinary_args[i].name == call.args[i].name. + if len(ordinary_args) != len(call.args): + return + for i in range(len(ordinary_args)): + if not isinstance(call.args[i], astroid.Name): + return + if node.args.args[i].name != call.args[i].name: + return + if (isinstance(node.body.func, astroid.Getattr) and + isinstance(node.body.func.expr, astroid.CallFunc)): + # Chained call, the intermediate call might + # return something else (but we don't check that, yet). + return + self.add_message('unnecessary-lambda', line=node.fromlineno, node=node) + + @check_messages('dangerous-default-value') + def visit_function(self, node): + """check function name, docstring, arguments, redefinition, + variable names, max locals + """ + self.stats[node.is_method() and 'method' or 'function'] += 1 + self._check_dangerous_default(node) + + def _check_dangerous_default(self, node): + # check for dangerous default values as arguments + is_iterable = lambda n: isinstance(n, (astroid.List, + astroid.Set, + astroid.Dict)) + for default in node.args.defaults: + try: + value = next(default.infer()) + except astroid.InferenceError: + continue + + if (isinstance(value, astroid.Instance) and + value.qname() in DEFAULT_ARGUMENT_SYMBOLS): + + if value is default: + msg = DEFAULT_ARGUMENT_SYMBOLS[value.qname()] + elif type(value) is astroid.Instance or is_iterable(value): + # We are here in the following situation(s): + # * a dict/set/list/tuple call which wasn't inferred + # to a syntax node ({}, () etc.). This can happen + # when the arguments are invalid or unknown to + # the inference. + # * a variable from somewhere else, which turns out to be a list + # or a dict. + if is_iterable(default): + msg = value.pytype() + elif isinstance(default, astroid.CallFunc): + msg = '%s() (%s)' % (value.name, value.qname()) + else: + msg = '%s (%s)' % (default.as_string(), value.qname()) + else: + # this argument is a name + msg = '%s (%s)' % (default.as_string(), + DEFAULT_ARGUMENT_SYMBOLS[value.qname()]) + self.add_message('dangerous-default-value', + node=node, + args=(msg, )) + + @check_messages('unreachable', 'lost-exception') + def visit_return(self, node): + """1 - check is the node has a right sibling (if so, that's some + unreachable code) + 2 - check is the node is inside the finally clause of a try...finally + block + """ + self._check_unreachable(node) + # Is it inside final body of a try...finally bloc ? + self._check_not_in_finally(node, 'return', (astroid.Function,)) + + @check_messages('unreachable') + def visit_continue(self, node): + """check is the node has a right sibling (if so, that's some unreachable + code) + """ + self._check_unreachable(node) + + @check_messages('unreachable', 'lost-exception') + def visit_break(self, node): + """1 - check is the node has a right sibling (if so, that's some + unreachable code) + 2 - check is the node is inside the finally clause of a try...finally + block + """ + # 1 - Is it right sibling ? + self._check_unreachable(node) + # 2 - Is it inside final body of a try...finally bloc ? + self._check_not_in_finally(node, 'break', (astroid.For, astroid.While,)) + + @check_messages('unreachable') + def visit_raise(self, node): + """check if the node has a right sibling (if so, that's some unreachable + code) + """ + self._check_unreachable(node) + + @check_messages('exec-used') + def visit_exec(self, node): + """just print a warning on exec statements""" + self.add_message('exec-used', node=node) + + @check_messages('bad-builtin', 'star-args', 'eval-used', + 'exec-used', 'missing-reversed-argument', + 'bad-reversed-sequence') + 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.func, astroid.Name): + name = node.func.name + # ignore the name if it's not a builtin (i.e. not defined in the + # locals nor globals scope) + if not (name in node.frame() or + name in node.root()): + if name == 'exec': + self.add_message('exec-used', node=node) + elif name == 'reversed': + self._check_reversed(node) + elif name == 'eval': + self.add_message('eval-used', node=node) + if name in self.config.bad_functions: + self.add_message('bad-builtin', node=node, args=name) + if node.starargs or node.kwargs: + scope = node.scope() + if isinstance(scope, astroid.Function): + toprocess = [(n, vn) for (n, vn) in ((node.starargs, scope.args.vararg), + (node.kwargs, scope.args.kwarg)) if n] + if toprocess: + for cfnode, fargname in toprocess[:]: + if getattr(cfnode, 'name', None) == fargname: + toprocess.remove((cfnode, fargname)) + if not toprocess: + return # star-args can be skipped + self.add_message('star-args', node=node.func) + + @check_messages('assert-on-tuple') + def visit_assert(self, node): + """check the use of an assert statement on a tuple.""" + if node.fail is None and isinstance(node.test, astroid.Tuple) and \ + len(node.test.elts) == 2: + self.add_message('assert-on-tuple', node=node) + + @check_messages('duplicate-key') + def visit_dict(self, node): + """check duplicate key in dictionary""" + keys = set() + for k, _ in node.items: + if isinstance(k, astroid.Const): + key = k.value + if key in keys: + self.add_message('duplicate-key', node=node, args=key) + keys.add(key) + + def visit_tryfinally(self, node): + """update try...finally flag""" + self._tryfinallys.append(node) + + def leave_tryfinally(self, node): # pylint: disable=unused-argument + """update try...finally flag""" + self._tryfinallys.pop() + + def _check_unreachable(self, node): + """check unreachable code""" + unreach_stmt = node.next_sibling() + if unreach_stmt is not None: + self.add_message('unreachable', node=unreach_stmt) + + def _check_not_in_finally(self, node, node_name, breaker_classes=()): + """check that a node is not inside a finally clause of a + try...finally statement. + If we found before a try...finally bloc a parent which its type is + in breaker_classes, we skip the whole check.""" + # if self._tryfinallys is empty, we're not a in try...finally bloc + if not self._tryfinallys: + return + # the node could be a grand-grand...-children of the try...finally + _parent = node.parent + _node = node + while _parent and not isinstance(_parent, breaker_classes): + if hasattr(_parent, 'finalbody') and _node in _parent.finalbody: + self.add_message('lost-exception', node=node, args=node_name) + return + _node = _parent + _parent = _node.parent + + def _check_reversed(self, node): + """ check that the argument to `reversed` is a sequence """ + try: + argument = safe_infer(get_argument_from_call(node, position=0)) + except NoSuchArgumentError: + self.add_message('missing-reversed-argument', node=node) + else: + if argument is astroid.YES: + return + if argument is None: + # Nothing was infered. + # Try to see if we have iter(). + if isinstance(node.args[0], astroid.CallFunc): + try: + func = next(node.args[0].func.infer()) + except InferenceError: + return + if (getattr(func, 'name', None) == 'iter' and + is_builtin_object(func)): + self.add_message('bad-reversed-sequence', node=node) + return + + if isinstance(argument, astroid.Instance): + if (argument._proxied.name == 'dict' and + is_builtin_object(argument._proxied)): + self.add_message('bad-reversed-sequence', node=node) + return + elif any(ancestor.name == 'dict' and is_builtin_object(ancestor) + for ancestor in argument._proxied.ancestors()): + # mappings aren't accepted by reversed() + self.add_message('bad-reversed-sequence', node=node) + return + + for methods in REVERSED_METHODS: + for meth in methods: + try: + argument.getattr(meth) + except astroid.NotFoundError: + break + else: + break + else: + # Check if it is a .deque. It doesn't seem that + # we can retrieve special methods + # from C implemented constructs. + if argument._proxied.qname().endswith(".deque"): + return + self.add_message('bad-reversed-sequence', node=node) + elif not isinstance(argument, (astroid.List, astroid.Tuple)): + # everything else is not a proper sequence for reversed() + self.add_message('bad-reversed-sequence', node=node) + +_NAME_TYPES = { + 'module': (MOD_NAME_RGX, 'module'), + 'const': (CONST_NAME_RGX, 'constant'), + 'class': (CLASS_NAME_RGX, 'class'), + 'function': (DEFAULT_NAME_RGX, 'function'), + 'method': (DEFAULT_NAME_RGX, 'method'), + 'attr': (DEFAULT_NAME_RGX, 'attribute'), + 'argument': (DEFAULT_NAME_RGX, 'argument'), + 'variable': (DEFAULT_NAME_RGX, 'variable'), + 'class_attribute': (CLASS_ATTRIBUTE_RGX, 'class attribute'), + 'inlinevar': (COMP_VAR_RGX, 'inline iteration'), +} + +def _create_naming_options(): + name_options = [] + for name_type, (rgx, human_readable_name) in six.iteritems(_NAME_TYPES): + name_type = name_type.replace('_', '-') + name_options.append(( + '%s-rgx' % (name_type,), + {'default': rgx, 'type': 'regexp', 'metavar': '<regexp>', + 'help': 'Regular expression matching correct %s names' % (human_readable_name,)})) + name_options.append(( + '%s-name-hint' % (name_type,), + {'default': rgx.pattern, 'type': 'string', 'metavar': '<string>', + 'help': 'Naming hint for %s names' % (human_readable_name,)})) + return tuple(name_options) + +class NameChecker(_BasicChecker): + msgs = { + 'C0102': ('Black listed name "%s"', + 'blacklisted-name', + 'Used when the name is listed in the black list (unauthorized ' + 'names).'), + 'C0103': ('Invalid %s name "%s"%s', + 'invalid-name', + 'Used when the name doesn\'t match the regular expression ' + 'associated to its type (constant, variable, class...).'), + } + + options = (('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'} + ), + ('name-group', + {'default' : (), + 'type' :'csv', 'metavar' : '<name1:name2>', + 'help' : ('Colon-delimited sets of names that determine each' + ' other\'s naming style when the name regexes' + ' allow several styles.')} + ), + ('include-naming-hint', + {'default': False, 'type' : 'yn', 'metavar' : '<y_or_n>', + 'help': 'Include a hint for the correct naming format with invalid-name'} + ), + ) + _create_naming_options() + + + def __init__(self, linter): + _BasicChecker.__init__(self, linter) + self._name_category = {} + self._name_group = {} + self._bad_names = {} + + def open(self): + self.stats = self.linter.add_stats(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, + badname_class_attribute=0) + for group in self.config.name_group: + for name_type in group.split(':'): + self._name_group[name_type] = 'group_%s' % (group,) + + @check_messages('blacklisted-name', 'invalid-name') + def visit_module(self, node): + self._check_name('module', node.name.split('.')[-1], node) + self._bad_names = {} + + def leave_module(self, node): # pylint: disable=unused-argument + for all_groups in six.itervalues(self._bad_names): + if len(all_groups) < 2: + continue + groups = collections.defaultdict(list) + min_warnings = sys.maxsize + for group in six.itervalues(all_groups): + groups[len(group)].append(group) + min_warnings = min(len(group), min_warnings) + if len(groups[min_warnings]) > 1: + by_line = sorted(groups[min_warnings], + key=lambda group: min(warning[0].lineno for warning in group)) + warnings = itertools.chain(*by_line[1:]) + else: + warnings = groups[min_warnings][0] + for args in warnings: + self._raise_name_warning(*args) + + @check_messages('blacklisted-name', 'invalid-name') + def visit_class(self, node): + self._check_name('class', node.name, node) + for attr, anodes in six.iteritems(node.instance_attrs): + if not list(node.instance_attr_ancestors(attr)): + self._check_name('attr', attr, anodes[0]) + + @check_messages('blacklisted-name', 'invalid-name') + def visit_function(self, node): + # Do not emit any warnings if the method is just an implementation + # of a base class method. + confidence = HIGH + if node.is_method(): + if overrides_a_method(node.parent.frame(), node.name): + return + confidence = (INFERENCE if has_known_bases(node.parent.frame()) + else INFERENCE_FAILURE) + + self._check_name(_determine_function_name_type(node), + node.name, node, confidence) + # Check argument names + args = node.args.args + if args is not None: + self._recursive_check_names(args, node) + + @check_messages('blacklisted-name', 'invalid-name') + def visit_global(self, node): + for name in node.names: + self._check_name('const', name, node) + + @check_messages('blacklisted-name', 'invalid-name') + def visit_assname(self, node): + """check module level assigned names""" + frame = node.frame() + ass_type = node.ass_type() + if isinstance(ass_type, astroid.Comprehension): + self._check_name('inlinevar', node.name, node) + elif isinstance(frame, astroid.Module): + if isinstance(ass_type, astroid.Assign) and not in_loop(ass_type): + if isinstance(safe_infer(ass_type.value), astroid.Class): + self._check_name('class', node.name, node) + else: + if not _redefines_import(node): + # Don't emit if the name redefines an import + # in an ImportError except handler. + self._check_name('const', node.name, node) + elif isinstance(ass_type, astroid.ExceptHandler): + self._check_name('variable', node.name, node) + elif isinstance(frame, astroid.Function): + # global introduced variable aren't in the function locals + if node.name in frame and node.name not in frame.argnames(): + if not _redefines_import(node): + self._check_name('variable', node.name, node) + elif isinstance(frame, astroid.Class): + if not list(frame.local_attr_ancestors(node.name)): + self._check_name('class_attribute', node.name, node) + + def _recursive_check_names(self, args, node): + """check names in a possibly recursive list <arg>""" + for arg in args: + if isinstance(arg, astroid.AssName): + self._check_name('argument', arg.name, node) + else: + self._recursive_check_names(arg.elts, node) + + def _find_name_group(self, node_type): + return self._name_group.get(node_type, node_type) + + def _raise_name_warning(self, node, node_type, name, confidence): + type_label = _NAME_TYPES[node_type][1] + hint = '' + if self.config.include_naming_hint: + hint = ' (hint: %s)' % (getattr(self.config, node_type + '_name_hint')) + self.add_message('invalid-name', node=node, args=(type_label, name, hint), + confidence=confidence) + self.stats['badname_' + node_type] += 1 + + def _check_name(self, node_type, name, node, confidence=HIGH): + """check for a name using the type's regexp""" + if is_inside_except(node): + clobbering, _ = clobber_in_except(node) + if clobbering: + return + if name in self.config.good_names: + return + if name in self.config.bad_names: + self.stats['badname_' + node_type] += 1 + self.add_message('blacklisted-name', node=node, args=name) + return + regexp = getattr(self.config, node_type + '_rgx') + match = regexp.match(name) + + if _is_multi_naming_match(match, node_type, confidence): + name_group = self._find_name_group(node_type) + bad_name_group = self._bad_names.setdefault(name_group, {}) + warnings = bad_name_group.setdefault(match.lastgroup, []) + warnings.append((node, node_type, name, confidence)) + + if match is None: + self._raise_name_warning(node, node_type, name, confidence) + + +class DocStringChecker(_BasicChecker): + msgs = { + 'C0111': ('Missing %s docstring', # W0131 + 'missing-docstring', + 'Used when a module, function, class or method has no docstring.' + 'Some special methods like __init__ doesn\'t necessary require a ' + 'docstring.'), + 'C0112': ('Empty %s docstring', # W0132 + 'empty-docstring', + 'Used when a module, function, class or method has an empty ' + 'docstring (it would be too easy ;).'), + } + options = (('no-docstring-rgx', + {'default' : NO_REQUIRED_DOC_RGX, + 'type' : 'regexp', 'metavar' : '<regexp>', + 'help' : 'Regular expression which should only match ' + 'function or class names that do not require a ' + 'docstring.'} + ), + ('docstring-min-length', + {'default' : -1, + 'type' : 'int', 'metavar' : '<int>', + 'help': ('Minimum line length for functions/classes that' + ' require docstrings, shorter ones are exempt.')} + ), + ) + + + def open(self): + self.stats = self.linter.add_stats(undocumented_module=0, + undocumented_function=0, + undocumented_method=0, + undocumented_class=0) + @check_messages('missing-docstring', 'empty-docstring') + def visit_module(self, node): + self._check_docstring('module', node) + + @check_messages('missing-docstring', 'empty-docstring') + def visit_class(self, node): + if self.config.no_docstring_rgx.match(node.name) is None: + self._check_docstring('class', node) + + @check_messages('missing-docstring', 'empty-docstring') + def visit_function(self, node): + if self.config.no_docstring_rgx.match(node.name) is None: + ftype = node.is_method() and 'method' or 'function' + if isinstance(node.parent.frame(), astroid.Class): + overridden = False + confidence = (INFERENCE if has_known_bases(node.parent.frame()) + else INFERENCE_FAILURE) + # check if node is from a method overridden by its ancestor + for ancestor in node.parent.frame().ancestors(): + if node.name in ancestor and \ + isinstance(ancestor[node.name], astroid.Function): + overridden = True + break + self._check_docstring(ftype, node, + report_missing=not overridden, + confidence=confidence) + else: + self._check_docstring(ftype, node) + + def _check_docstring(self, node_type, node, report_missing=True, + confidence=HIGH): + """check the node has a non empty docstring""" + docstring = node.doc + if docstring is None: + if not report_missing: + return + if node.body: + lines = node.body[-1].lineno - node.body[0].lineno + 1 + else: + lines = 0 + + if node_type == 'module' and not lines: + # If the module has no body, there's no reason + # to require a docstring. + return + max_lines = self.config.docstring_min_length + + if node_type != 'module' and max_lines > -1 and lines < max_lines: + return + self.stats['undocumented_'+node_type] += 1 + if (node.body and isinstance(node.body[0], astroid.Discard) and + isinstance(node.body[0].value, astroid.CallFunc)): + # Most likely a string with a format call. Let's see. + func = safe_infer(node.body[0].value.func) + if (isinstance(func, astroid.BoundMethod) + and isinstance(func.bound, astroid.Instance)): + # Strings in Python 3, others in Python 2. + if PY3K and func.bound.name == 'str': + return + elif func.bound.name in ('str', 'unicode', 'bytes'): + return + self.add_message('missing-docstring', node=node, args=(node_type,), + confidence=confidence) + elif not docstring.strip(): + self.stats['undocumented_'+node_type] += 1 + self.add_message('empty-docstring', node=node, args=(node_type,), + confidence=confidence) + + +class PassChecker(_BasicChecker): + """check if the pass statement is really necessary""" + msgs = {'W0107': ('Unnecessary pass statement', + 'unnecessary-pass', + 'Used when a "pass" statement that can be avoided is ' + 'encountered.'), + } + @check_messages('unnecessary-pass') + def visit_pass(self, node): + if len(node.parent.child_sequence(node)) > 1: + self.add_message('unnecessary-pass', node=node) + + +class LambdaForComprehensionChecker(_BasicChecker): + """check for using a lambda where a comprehension would do. + + See <http://www.artima.com/weblogs/viewpost.jsp?thread=98196> + where GvR says comprehensions would be clearer. + """ + + msgs = {'W0110': ('map/filter on lambda could be replaced by comprehension', + 'deprecated-lambda', + 'Used when a lambda is the first argument to "map" or ' + '"filter". It could be clearer as a list ' + 'comprehension or generator expression.', + {'maxversion': (3, 0)}), + } + + @check_messages('deprecated-lambda') + def visit_callfunc(self, node): + """visit a CallFunc node, check if map or filter are called with a + lambda + """ + if not node.args: + return + if not isinstance(node.args[0], astroid.Lambda): + return + infered = safe_infer(node.func) + if (is_builtin_object(infered) + and infered.name in ['map', 'filter']): + self.add_message('deprecated-lambda', node=node) + + +def register(linter): + """required method to auto register this checker""" + linter.register_checker(BasicErrorChecker(linter)) + linter.register_checker(BasicChecker(linter)) + linter.register_checker(NameChecker(linter)) + linter.register_checker(DocStringChecker(linter)) + linter.register_checker(PassChecker(linter)) + linter.register_checker(LambdaForComprehensionChecker(linter)) diff --git a/pylint/checkers/classes.py b/pylint/checkers/classes.py new file mode 100644 index 0000000..8e51a47 --- /dev/null +++ b/pylint/checkers/classes.py @@ -0,0 +1,982 @@ +# Copyright (c) 2003-2014 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +"""classes checker for Python code +""" +from __future__ import generators + +import sys +from collections import defaultdict + +import astroid +from astroid import YES, Instance, are_exclusive, AssAttr, Class +from astroid.bases import Generator, BUILTINS +from astroid.inference import InferenceContext + +from pylint.interfaces import IAstroidChecker +from pylint.checkers import BaseChecker +from pylint.checkers.utils import ( + PYMETHODS, overrides_a_method, check_messages, is_attr_private, + is_attr_protected, node_frame_class, safe_infer, is_builtin_object, + decorated_with_property, unimplemented_abstract_methods) +import six + +if sys.version_info >= (3, 0): + NEXT_METHOD = '__next__' +else: + NEXT_METHOD = 'next' +ITER_METHODS = ('__iter__', '__getitem__') + +def _called_in_methods(func, klass, methods): + """ Check if the func was called in any of the given methods, + belonging to the *klass*. Returns True if so, False otherwise. + """ + if not isinstance(func, astroid.Function): + return False + for method in methods: + try: + infered = klass.getattr(method) + except astroid.NotFoundError: + continue + for infer_method in infered: + for callfunc in infer_method.nodes_of_class(astroid.CallFunc): + try: + bound = next(callfunc.func.infer()) + except (astroid.InferenceError, StopIteration): + continue + if not isinstance(bound, astroid.BoundMethod): + continue + func_obj = bound._proxied + if isinstance(func_obj, astroid.UnboundMethod): + func_obj = func_obj._proxied + if func_obj.name == func.name: + return True + return False + +def class_is_abstract(node): + """return true if the given class node should be considered as an abstract + class + """ + for method in node.methods(): + if method.parent.frame() is node: + if method.is_abstract(pass_is_abstract=False): + return True + return False + +def _is_attribute_property(name, klass): + """ Check if the given attribute *name* is a property + in the given *klass*. + + It will look for `property` calls or for functions + with the given name, decorated by `property` or `property` + subclasses. + Returns ``True`` if the name is a property in the given klass, + ``False`` otherwise. + """ + + try: + attributes = klass.getattr(name) + except astroid.NotFoundError: + return False + property_name = "{0}.property".format(BUILTINS) + for attr in attributes: + try: + infered = next(attr.infer()) + except astroid.InferenceError: + continue + if (isinstance(infered, astroid.Function) and + decorated_with_property(infered)): + return True + if infered.pytype() == property_name: + return True + return False + + +MSGS = { + 'F0202': ('Unable to check methods signature (%s / %s)', + 'method-check-failed', + 'Used when Pylint has been unable to check methods signature \ + compatibility for an unexpected reason. Please report this kind \ + if you don\'t make sense of it.'), + + 'E0202': ('An attribute defined in %s line %s hides this method', + 'method-hidden', + 'Used when a class defines a method which is hidden by an ' + 'instance attribute from an ancestor class or set by some ' + 'client code.'), + 'E0203': ('Access to member %r before its definition line %s', + 'access-member-before-definition', + 'Used when an instance member is accessed before it\'s actually\ + assigned.'), + 'W0201': ('Attribute %r defined outside __init__', + 'attribute-defined-outside-init', + 'Used when an instance attribute is defined outside the __init__\ + method.'), + + 'W0212': ('Access to a protected member %s of a client class', # E0214 + 'protected-access', + 'Used when a protected member (i.e. class member with a name \ + beginning with an underscore) is access outside the class or a \ + descendant of the class where it\'s defined.'), + + 'E0211': ('Method has no argument', + 'no-method-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', + 'no-self-argument', + 'Used when a method has an attribute different the "self" as\ + first argument. This is considered as an error since this is\ + a so common convention that you shouldn\'t break it!'), + 'C0202': ('Class method %s should have %s as first argument', # E0212 + 'bad-classmethod-argument', + 'Used when a class method has a first argument named differently ' + 'than the value specified in valid-classmethod-first-arg option ' + '(default to "cls"), recommended to easily differentiate them ' + 'from regular instance methods.'), + 'C0203': ('Metaclass method %s should have %s as first argument', # E0214 + 'bad-mcs-method-argument', + 'Used when a metaclass method has a first agument named ' + 'differently than the value specified in valid-classmethod-first' + '-arg option (default to "cls"), recommended to easily ' + 'differentiate them from regular instance methods.'), + 'C0204': ('Metaclass class method %s should have %s as first argument', + 'bad-mcs-classmethod-argument', + 'Used when a metaclass class method has a first argument named ' + 'differently than the value specified in valid-metaclass-' + 'classmethod-first-arg option (default to "mcs"), recommended to ' + 'easily differentiate them from regular instance methods.'), + + 'W0211': ('Static method with %r as first argument', + 'bad-staticmethod-argument', + 'Used when a static method has "self" or a value specified in ' + 'valid-classmethod-first-arg option or ' + 'valid-metaclass-classmethod-first-arg option as first argument.' + ), + 'R0201': ('Method could be a function', + 'no-self-use', + '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', + 'interface-is-not-class', + 'Used when a class claims to implement an interface which is not \ + a class.'), + 'E0222': ('Missing method %r from %s interface', + 'missing-interface-method', + 'Used when a method declared in an interface is missing from a \ + class implementing this interface'), + 'W0221': ('Arguments number differs from %s %r method', + 'arguments-differ', + 'Used when a method has a different number of arguments than in \ + the implemented interface or in an overridden method.'), + 'W0222': ('Signature differs from %s %r method', + 'signature-differs', + 'Used when a method signature is different than in the \ + implemented interface or in an overridden method.'), + 'W0223': ('Method %r is abstract in class %r but is not overridden', + 'abstract-method', + 'Used when an abstract method (i.e. raise NotImplementedError) is \ + not overridden in concrete class.' + ), + 'F0220': ('failed to resolve interfaces implemented by %s (%s)', # W0224 + 'unresolved-interface', + 'Used when a Pylint as failed to find interfaces implemented by \ + a class'), + + + 'W0231': ('__init__ method from base class %r is not called', + 'super-init-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', + 'no-init', + 'Used when a class has no __init__ method, neither its parent \ + classes.'), + 'W0233': ('__init__ method from a non direct base class %r is called', + 'non-parent-init-called', + 'Used when an __init__ method is called on a class which is not \ + in the direct ancestors for the analysed class.'), + 'W0234': ('__iter__ returns non-iterator', + 'non-iterator-returned', + 'Used when an __iter__ method returns something which is not an \ + iterable (i.e. has no `%s` method)' % NEXT_METHOD), + 'E0235': ('__exit__ must accept 3 arguments: type, value, traceback', + 'bad-context-manager', + 'Used when the __exit__ special method, belonging to a \ + context manager, does not accept 3 arguments \ + (type, value, traceback).'), + 'E0236': ('Invalid object %r in __slots__, must contain ' + 'only non empty strings', + 'invalid-slots-object', + 'Used when an invalid (non-string) object occurs in __slots__.'), + 'E0237': ('Assigning to attribute %r not defined in class slots', + 'assigning-non-slot', + 'Used when assigning to an attribute not defined ' + 'in the class slots.'), + 'E0238': ('Invalid __slots__ object', + 'invalid-slots', + 'Used when an invalid __slots__ is found in class. ' + 'Only a string, an iterable or a sequence is permitted.'), + 'E0239': ('Inheriting %r, which is not a class.', + 'inherit-non-class', + 'Used when a class inherits from something which is not a ' + 'class.'), + + + } + + +class ClassChecker(BaseChecker): + """checks for : + * methods without self as first argument + * overridden methods signature + * access only to existent members via self + * attributes not defined in the __init__ method + * supported interfaces implementation + * unreachable code + """ + + __implements__ = (IAstroidChecker,) + + # 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.'} + ), + ('valid-classmethod-first-arg', + {'default' : ('cls',), + 'type' : 'csv', + 'metavar' : '<argument names>', + 'help' : 'List of valid names for the first argument in \ +a class method.'} + ), + ('valid-metaclass-classmethod-first-arg', + {'default' : ('mcs',), + 'type' : 'csv', + 'metavar' : '<argument names>', + 'help' : 'List of valid names for the first argument in \ +a metaclass class method.'} + ), + ('exclude-protected', + { + 'default': ( + # namedtuple public API. + '_asdict', '_fields', '_replace', '_source', '_make'), + 'type': 'csv', + 'metavar': '<protected access exclusions>', + 'help': ('List of member names, which should be excluded ' + 'from the protected access warning.')} + )) + + 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(defaultdict(list)) + 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 astroid.NotFoundError: + self.add_message('no-init', args=node, node=node) + self._check_slots(node) + self._check_proper_bases(node) + + @check_messages('inherit-non-class') + def _check_proper_bases(self, node): + """ + Detect that a class inherits something which is not + a class or a type. + """ + for base in node.bases: + ancestor = safe_infer(base) + if ancestor in (YES, None): + continue + if (isinstance(ancestor, astroid.Instance) and + ancestor.is_subtype_of('%s.type' % (BUILTINS,))): + continue + if not isinstance(ancestor, astroid.Class): + self.add_message('inherit-non-class', + args=base.as_string(), node=node) + + @check_messages('access-member-before-definition', + 'attribute-defined-outside-init') + def leave_class(self, cnode): + """close a class node: + check that instance attributes are defined in __init__ and check + access to existent members + """ + # check access to existent members on non metaclass classes + accessed = self._accessed.pop() + if cnode.type != 'metaclass': + self._check_accessed_members(cnode, accessed) + # checks attributes are defined in an allowed method such as __init__ + if not self.linter.is_message_enabled('attribute-defined-outside-init'): + return + defining_methods = self.config.defining_attr_methods + current_module = cnode.root() + for attr, nodes in six.iteritems(cnode.instance_attrs): + # skip nodes which are not in the current module and it may screw up + # the output, while it's not worth it + nodes = [n for n in nodes if not + isinstance(n.statement(), (astroid.Delete, astroid.AugAssign)) + and n.root() is current_module] + if not nodes: + continue # error detected by typechecking + # check if any method attr is defined in is a defining method + if any(node.frame().name in defining_methods + for node in nodes): + continue + + # check attribute is defined in a parent's __init__ + for parent in cnode.instance_attr_ancestors(attr): + attr_defined = False + # check if any parent method attr is defined in is a defining method + for node in parent.instance_attrs[attr]: + if node.frame().name in defining_methods: + attr_defined = True + if attr_defined: + # we're done :) + break + else: + # check attribute is defined as a class attribute + try: + cnode.local_attr(attr) + except astroid.NotFoundError: + for node in nodes: + if node.frame().name not in defining_methods: + # If the attribute was set by a callfunc in any + # of the defining methods, then don't emit + # the warning. + if _called_in_methods(node.frame(), cnode, + defining_methods): + continue + self.add_message('attribute-defined-outside-init', + args=attr, node=node) + + def visit_function(self, node): + """check method arguments, overriding""" + # ignore actual functions + if not node.is_method(): + return + klass = node.parent.frame() + self._meth_could_be_func = True + # check first argument is self if this is actually a method + self._check_first_arg_for_type(node, klass.type == 'metaclass') + if node.name == '__init__': + self._check_init(node) + return + # check signature if the method overloads inherited method + for overridden in klass.local_attr_ancestors(node.name): + # get astroid for the searched method + try: + meth_node = overridden[node.name] + except KeyError: + # we have found the method but it's not in the local + # dictionary. + # This may happen with astroid build from living objects + continue + if not isinstance(meth_node, astroid.Function): + continue + self._check_signature(node, meth_node, 'overridden') + break + if node.decorators: + for decorator in node.decorators.nodes: + if isinstance(decorator, astroid.Getattr) and \ + decorator.attrname in ('getter', 'setter', 'deleter'): + # attribute affectation will call this method, not hiding it + return + if isinstance(decorator, astroid.Name) and decorator.name == 'property': + # attribute affectation will either call a setter or raise + # an attribute error, anyway not hiding the function + return + # check if the method is hidden by an attribute + try: + overridden = klass.instance_attr(node.name)[0] # XXX + overridden_frame = overridden.frame() + if (isinstance(overridden_frame, astroid.Function) + and overridden_frame.type == 'method'): + overridden_frame = overridden_frame.parent.frame() + if (isinstance(overridden_frame, Class) + and klass.is_subtype_of(overridden_frame.qname())): + args = (overridden.root().name, overridden.fromlineno) + self.add_message('method-hidden', args=args, node=node) + except astroid.NotFoundError: + pass + + # check non-iterators in __iter__ + if node.name == '__iter__': + self._check_iter(node) + elif node.name == '__exit__': + self._check_exit(node) + + def _check_slots(self, node): + if '__slots__' not in node.locals: + return + for slots in node.igetattr('__slots__'): + # check if __slots__ is a valid type + for meth in ITER_METHODS: + try: + slots.getattr(meth) + break + except astroid.NotFoundError: + continue + else: + self.add_message('invalid-slots', node=node) + continue + + if isinstance(slots, astroid.Const): + # a string, ignore the following checks + continue + if not hasattr(slots, 'itered'): + # we can't obtain the values, maybe a .deque? + continue + + if isinstance(slots, astroid.Dict): + values = [item[0] for item in slots.items] + else: + values = slots.itered() + if values is YES: + return + + for elt in values: + try: + self._check_slots_elt(elt) + except astroid.InferenceError: + continue + + def _check_slots_elt(self, elt): + for infered in elt.infer(): + if infered is YES: + continue + if (not isinstance(infered, astroid.Const) or + not isinstance(infered.value, six.string_types)): + self.add_message('invalid-slots-object', + args=infered.as_string(), + node=elt) + continue + if not infered.value: + self.add_message('invalid-slots-object', + args=infered.as_string(), + node=elt) + + def _check_iter(self, node): + try: + infered = node.infer_call_result(node) + except astroid.InferenceError: + return + + for infered_node in infered: + if (infered_node is YES + or isinstance(infered_node, Generator)): + continue + if isinstance(infered_node, astroid.Instance): + try: + infered_node.local_attr(NEXT_METHOD) + except astroid.NotFoundError: + self.add_message('non-iterator-returned', + node=node) + break + + def _check_exit(self, node): + positional = sum(1 for arg in node.args.args if arg.name != 'self') + if positional < 3 and not node.args.vararg: + self.add_message('bad-context-manager', + node=node) + elif positional > 3: + self.add_message('bad-context-manager', + node=node) + + 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 overridden from a parent class and any + kind of method defined in an interface for this warning + """ + if node.is_method(): + if node.args.args is not None: + self._first_attrs.pop() + if not self.linter.is_message_enabled('no-self-use'): + return + class_node = node.parent.frame() + if (self._meth_could_be_func and node.type == 'method' + and not node.name in PYMETHODS + and not (node.is_abstract() or + overrides_a_method(class_node, node.name)) + and class_node.type != 'interface'): + self.add_message('no-self-use', node=node) + + def visit_getattr(self, node): + """check if the getattr is an access to a class member + if so, register it. Also check for access to protected + class member from outside its class (but ignore __special__ + methods) + """ + attrname = node.attrname + # Check self + if self.is_first_attr(node): + self._accessed[-1][attrname].append(node) + return + if not self.linter.is_message_enabled('protected-access'): + return + + self._check_protected_attribute_access(node) + + def visit_assattr(self, node): + if isinstance(node.ass_type(), astroid.AugAssign) and self.is_first_attr(node): + self._accessed[-1][node.attrname].append(node) + self._check_in_slots(node) + + def _check_in_slots(self, node): + """ Check that the given assattr node + is defined in the class slots. + """ + infered = safe_infer(node.expr) + if infered and isinstance(infered, Instance): + klass = infered._proxied + if '__slots__' not in klass.locals or not klass.newstyle: + return + + slots = klass.slots() + if slots is None: + return + # If any ancestor doesn't use slots, the slots + # defined for this class are superfluous. + if any('__slots__' not in ancestor.locals and + ancestor.name != 'object' + for ancestor in klass.ancestors()): + return + + if not any(slot.value == node.attrname for slot in slots): + # If we have a '__dict__' in slots, then + # assigning any name is valid. + if not any(slot.value == '__dict__' for slot in slots): + if _is_attribute_property(node.attrname, klass): + # Properties circumvent the slots mechanism, + # so we should not emit a warning for them. + return + self.add_message('assigning-non-slot', + args=(node.attrname, ), node=node) + + @check_messages('protected-access') + def visit_assign(self, assign_node): + node = assign_node.targets[0] + if not isinstance(node, AssAttr): + return + + if self.is_first_attr(node): + return + + self._check_protected_attribute_access(node) + + def _check_protected_attribute_access(self, node): + '''Given an attribute access node (set or get), check if attribute + access is legitimate. Call _check_first_attr with node before calling + this method. Valid cases are: + * self._attr in a method or cls._attr in a classmethod. Checked by + _check_first_attr. + * Klass._attr inside "Klass" class. + * Klass2._attr inside "Klass" class when Klass2 is a base class of + Klass. + ''' + attrname = node.attrname + + if (is_attr_protected(attrname) and + attrname not in self.config.exclude_protected): + + klass = node_frame_class(node) + + # XXX infer to be more safe and less dirty ?? + # in classes, check we are not getting a parent method + # through the class object or through super + callee = node.expr.as_string() + + # We are not in a class, no remaining valid case + if klass is None: + self.add_message('protected-access', node=node, args=attrname) + return + + # If the expression begins with a call to super, that's ok. + if isinstance(node.expr, astroid.CallFunc) and \ + isinstance(node.expr.func, astroid.Name) and \ + node.expr.func.name == 'super': + return + + # We are in a class, one remaining valid cases, Klass._attr inside + # Klass + if not (callee == klass.name or callee in klass.basenames): + # Detect property assignments in the body of the class. + # This is acceptable: + # + # class A: + # b = property(lambda: self._b) + + stmt = node.parent.statement() + try: + if (isinstance(stmt, astroid.Assign) and + (stmt in klass.body or klass.parent_of(stmt)) and + isinstance(stmt.value, astroid.CallFunc) and + isinstance(stmt.value.func, astroid.Name) and + stmt.value.func.name == 'property' and + is_builtin_object(next(stmt.value.func.infer(), None))): + return + except astroid.InferenceError: + pass + self.add_message('protected-access', node=node, args=attrname) + + 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 six.iteritems(accessed): + # deactivate "except doesn't do anything", that's expected + # pylint: disable=W0704 + try: + # is it a class attribute ? + node.local_attr(attr) + # yes, stop here + continue + except astroid.NotFoundError: + pass + # is it an instance attribute of a parent class ? + try: + next(node.instance_attr_ancestors(attr)) + # yes, stop here + continue + except StopIteration: + pass + # is it an instance attribute ? + try: + defstmts = node.instance_attr(attr) + except astroid.NotFoundError: + pass + else: + # filter out augment assignment nodes + defstmts = [stmt for stmt in defstmts if stmt not in nodes] + if not defstmts: + # only augment assignment for this node, no-member should be + # triggered by the typecheck checker + continue + # filter defstmts to only pick the first one when there are + # several assignments in the same scope + scope = defstmts[0].scope() + defstmts = [stmt for i, stmt in enumerate(defstmts) + if i == 0 or stmt.scope() is not scope] + # if there are still more than one, don't attempt to be smarter + # than we can be + if len(defstmts) == 1: + defstmt = defstmts[0] + # check that if the node is accessed in the same method as + # it's defined, it's accessed after the initial assignment + frame = defstmt.frame() + lno = defstmt.fromlineno + for _node in nodes: + if _node.frame() is frame and _node.fromlineno < lno \ + and not are_exclusive(_node.statement(), defstmt, + ('AttributeError', 'Exception', 'BaseException')): + self.add_message('access-member-before-definition', + 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 or a metaclass regular method (actually + valid-classmethod-first-arg value) + * 'mcs' for a metaclass class method (actually + valid-metaclass-classmethod-first-arg) + * not one of the above for a static method + """ + # don't care about functions with unknown argument (builtins) + if node.args.args is None: + return + first_arg = node.args.args and node.argnames()[0] + self._first_attrs.append(first_arg) + first = self._first_attrs[-1] + # static method + if node.type == 'staticmethod': + if (first_arg == 'self' or + first_arg in self.config.valid_classmethod_first_arg or + first_arg in self.config.valid_metaclass_classmethod_first_arg): + self.add_message('bad-staticmethod-argument', args=first, node=node) + return + self._first_attrs[-1] = None + # class / regular method with no args + elif not node.args.args: + self.add_message('no-method-argument', node=node) + # metaclass + elif metaclass: + # metaclass __new__ or classmethod + if node.type == 'classmethod': + self._check_first_arg_config( + first, + self.config.valid_metaclass_classmethod_first_arg, node, + 'bad-mcs-classmethod-argument', node.name) + # metaclass regular method + else: + self._check_first_arg_config( + first, + self.config.valid_classmethod_first_arg, node, + 'bad-mcs-method-argument', + node.name) + # regular class + else: + # class method + if node.type == 'classmethod': + self._check_first_arg_config( + first, + self.config.valid_classmethod_first_arg, node, + 'bad-classmethod-argument', + node.name) + # regular method without self as argument + elif first != 'self': + self.add_message('no-self-argument', node=node) + + def _check_first_arg_config(self, first, config, node, message, + method_name): + if first not in config: + if len(config) == 1: + valid = repr(config[0]) + else: + valid = ', '.join(repr(v) for v in config[:-1]) + valid = '%s or %r' % (valid, config[-1]) + self.add_message(message, args=(method_name, valid), node=node) + + def _check_bases_classes(self, node): + """check that the given class node implements abstract methods from + base classes + """ + def is_abstract(method): + return method.is_abstract(pass_is_abstract=False) + + # check if this class abstract + if class_is_abstract(node): + return + + methods = sorted( + unimplemented_abstract_methods(node, is_abstract).items(), + key=lambda item: item[0], + ) + for name, method in 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 name in node.locals: + # it is redefined as an attribute or with a descriptor + continue + self.add_message('abstract-method', node=node, + args=(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, astroid.Class): + e0221_hack[0] = True + self.add_message('interface-is-not-class', 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 beginning with an underscore, + # usually belonging to the interface implementation + continue + # get class method astroid + try: + method = node_method(node, name) + except astroid.NotFoundError: + self.add_message('missing-interface-method', + 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 astroid.InferenceError: + if e0221_hack[0]: + return + implements = Instance(node).getattr('__implements__')[0] + assignment = implements.parent + assert isinstance(assignment, astroid.Assign) + # assignment.expr can be a Name or a Tuple or whatever. + # Use as_string() for the message + # FIXME: in case of multiple interfaces, find which one could not + # be resolved + self.add_message('unresolved-interface', node=implements, + args=(node.name, assignment.value.as_string())) + + def _check_init(self, node): + """check that the __init__ method call super or ancestors'__init__ + method + """ + if (not self.linter.is_message_enabled('super-init-not-called') and + not self.linter.is_message_enabled('non-parent-init-called')): + return + klass_node = node.parent.frame() + to_call = _ancestors_to_call(klass_node) + not_called_yet = dict(to_call) + for stmt in node.nodes_of_class(astroid.CallFunc): + expr = stmt.func + if not isinstance(expr, astroid.Getattr) \ + or expr.attrname != '__init__': + continue + # skip the test if using super + if isinstance(expr.expr, astroid.CallFunc) and \ + isinstance(expr.expr.func, astroid.Name) and \ + expr.expr.func.name == 'super': + return + try: + for klass in expr.expr.infer(): + if klass is YES: + continue + # The infered klass can be super(), which was + # assigned to a variable and the `__init__` + # was called later. + # + # base = super() + # base.__init__(...) + + if (isinstance(klass, astroid.Instance) and + isinstance(klass._proxied, astroid.Class) and + is_builtin_object(klass._proxied) and + klass._proxied.name == 'super'): + return + try: + del not_called_yet[klass] + except KeyError: + if klass not in to_call: + self.add_message('non-parent-init-called', + node=expr, args=klass.name) + except astroid.InferenceError: + continue + for klass, method in six.iteritems(not_called_yet): + if klass.name == 'object' or method.parent.name == 'object': + continue + self.add_message('super-init-not-called', args=klass.name, node=node) + + def _check_signature(self, method1, refmethod, class_type): + """check that the signature of the two given methods match + + class_type is in 'class', 'interface' + """ + if not (isinstance(method1, astroid.Function) + and isinstance(refmethod, astroid.Function)): + self.add_message('method-check-failed', + args=(method1, refmethod), node=method1) + return + # don't care about functions with unknown argument (builtins) + if method1.args.args is None or refmethod.args.args is None: + return + # if we use *args, **kwargs, skip the below checks + if method1.args.vararg or method1.args.kwarg: + return + if is_attr_private(method1.name): + return + if len(method1.args.args) != len(refmethod.args.args): + self.add_message('arguments-differ', + args=(class_type, method1.name), + node=method1) + elif len(method1.args.defaults) < len(refmethod.args.defaults): + self.add_message('signature-differs', + args=(class_type, method1.name), + node=method1) + + def is_first_attr(self, node): + """Check that attribute lookup name use first attribute variable name + (self for method, cls for classmethod and mcs for metaclass). + """ + return self._first_attrs and isinstance(node.expr, astroid.Name) and \ + node.expr.name == self._first_attrs[-1] + +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: + to_call[base_node] = next(base_node.igetattr(method)) + except astroid.InferenceError: + continue + return to_call + + +def node_method(node, method_name): + """get astroid for <method_name> on the given class node, ensuring it + is a Function node + """ + for n in node.local_attr(method_name): + if isinstance(n, astroid.Function): + return n + raise astroid.NotFoundError(method_name) + +def register(linter): + """required method to auto register this checker """ + linter.register_checker(ClassChecker(linter)) diff --git a/pylint/checkers/design_analysis.py b/pylint/checkers/design_analysis.py new file mode 100644 index 0000000..e361cca --- /dev/null +++ b/pylint/checkers/design_analysis.py @@ -0,0 +1,370 @@ +# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +"""check for signs of poor design""" + +import re +from collections import defaultdict + +from astroid import Function, If, InferenceError + +from pylint.interfaces import IAstroidChecker +from pylint.checkers import BaseChecker +from pylint.checkers.utils import check_messages + +# regexp for ignored argument name +IGNORED_ARGUMENT_NAMES = re.compile('_.*') + + +def class_is_abstract(klass): + """return true if the given class node should be considered as an abstract + class + """ + for attr in klass.values(): + if isinstance(attr, Function): + if attr.is_abstract(pass_is_abstract=False): + return True + return False + + +MSGS = { + 'R0901': ('Too many ancestors (%s/%s)', + 'too-many-ancestors', + 'Used when class has too many parent classes, try to reduce \ + this to get a simpler (and so easier to use) class.'), + 'R0902': ('Too many instance attributes (%s/%s)', + 'too-many-instance-attributes', + 'Used when class has too many instance attributes, try to reduce \ + this to get a simpler (and so easier to use) class.'), + 'R0903': ('Too few public methods (%s/%s)', + 'too-few-public-methods', + 'Used when class has too few public methods, so be sure it\'s \ + really worth it.'), + 'R0904': ('Too many public methods (%s/%s)', + 'too-many-public-methods', + 'Used when class has too many public methods, try to reduce \ + this to get a simpler (and so easier to use) class.'), + + 'R0911': ('Too many return statements (%s/%s)', + 'too-many-return-statements', + 'Used when a function or method has too many return statement, \ + making it hard to follow.'), + 'R0912': ('Too many branches (%s/%s)', + 'too-many-branches', + 'Used when a function or method has too many branches, \ + making it hard to follow.'), + 'R0913': ('Too many arguments (%s/%s)', + 'too-many-arguments', + 'Used when a function or method takes too many arguments.'), + 'R0914': ('Too many local variables (%s/%s)', + 'too-many-locals', + 'Used when a function or method has too many local variables.'), + 'R0915': ('Too many statements (%s/%s)', + 'too-many-statements', + 'Used when a function or method has too many statements. You \ + should then split it in smaller functions / methods.'), + + 'R0921': ('Abstract class not referenced', + 'abstract-class-not-used', + 'Used when an abstract class is not used as ancestor anywhere.'), + 'R0922': ('Abstract class is only referenced %s times', + 'abstract-class-little-used', + 'Used when an abstract class is used less than X times as \ + ancestor.'), + 'R0923': ('Interface not implemented', + 'interface-not-implemented', + 'Used when an interface class is not implemented anywhere.'), + } + + +class MisdesignChecker(BaseChecker): + """checks for sign of poor/misdesign: + * number of methods, attributes, local variables... + * size, complexity of functions, methods + """ + + __implements__ = (IAstroidChecker,) + + # configuration section name + name = 'design' + # messages + msgs = MSGS + priority = -2 + # configuration options + options = (('max-args', + {'default' : 5, 'type' : 'int', 'metavar' : '<int>', + 'help': 'Maximum number of arguments for function / method'} + ), + ('ignored-argument-names', + {'default' : IGNORED_ARGUMENT_NAMES, + 'type' :'regexp', 'metavar' : '<regexp>', + 'help' : 'Argument names that match this expression will be ' + 'ignored. Default to name with leading underscore'} + ), + ('max-locals', + {'default' : 15, 'type' : 'int', 'metavar' : '<int>', + 'help': 'Maximum number of locals for function / method body'} + ), + ('max-returns', + {'default' : 6, 'type' : 'int', 'metavar' : '<int>', + 'help': 'Maximum number of return / yield for function / ' + 'method body'} + ), + ('max-branches', + {'default' : 12, 'type' : 'int', 'metavar' : '<int>', + 'help': 'Maximum number of branch for function / method body'} + ), + ('max-statements', + {'default' : 50, 'type' : 'int', 'metavar' : '<int>', + 'help': 'Maximum number of statements in function / method ' + 'body'} + ), + ('max-parents', + {'default' : 7, + 'type' : 'int', + 'metavar' : '<num>', + 'help' : 'Maximum number of parents for a class (see R0901).'} + ), + ('max-attributes', + {'default' : 7, + 'type' : 'int', + 'metavar' : '<num>', + 'help' : 'Maximum number of attributes for a class \ +(see R0902).'} + ), + ('min-public-methods', + {'default' : 2, + 'type' : 'int', + 'metavar' : '<num>', + 'help' : 'Minimum number of public methods for a class \ +(see R0903).'} + ), + ('max-public-methods', + {'default' : 20, + 'type' : 'int', + 'metavar' : '<num>', + 'help' : 'Maximum number of public methods for a class \ +(see R0904).'} + ), + ) + + def __init__(self, linter=None): + BaseChecker.__init__(self, linter) + self.stats = None + self._returns = None + self._branches = None + self._used_abstracts = None + self._used_ifaces = None + self._abstracts = None + self._ifaces = None + self._stmts = 0 + + def open(self): + """initialize visit variables""" + self.stats = self.linter.add_stats() + self._returns = [] + self._branches = defaultdict(int) + self._used_abstracts = {} + self._used_ifaces = {} + self._abstracts = [] + self._ifaces = [] + + # Check 'R0921', 'R0922', 'R0923' + def close(self): + """check that abstract/interface classes are used""" + for abstract in self._abstracts: + if not abstract in self._used_abstracts: + self.add_message('abstract-class-not-used', node=abstract) + elif self._used_abstracts[abstract] < 2: + self.add_message('abstract-class-little-used', node=abstract, + args=self._used_abstracts[abstract]) + for iface in self._ifaces: + if not iface in self._used_ifaces: + self.add_message('interface-not-implemented', node=iface) + + @check_messages('too-many-ancestors', 'too-many-instance-attributes', + 'too-few-public-methods', 'too-many-public-methods', + 'abstract-class-not-used', 'abstract-class-little-used', + 'interface-not-implemented') + def visit_class(self, node): + """check size of inheritance hierarchy and number of instance attributes + """ + # 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('too-many-ancestors', 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('too-many-instance-attributes', node=node, + args=(len(node.instance_attrs), + self.config.max_attributes)) + # update abstract / interface classes structures + if class_is_abstract(node): + self._abstracts.append(node) + elif node.type == 'interface' and node.name != 'Interface': + self._ifaces.append(node) + for parent in node.ancestors(False): + if parent.name == 'Interface': + continue + self._used_ifaces[parent] = 1 + try: + for iface in node.interfaces(): + self._used_ifaces[iface] = 1 + except InferenceError: + # XXX log ? + pass + for parent in node.ancestors(): + try: + self._used_abstracts[parent] += 1 + except KeyError: + self._used_abstracts[parent] = 1 + + @check_messages('too-few-public-methods', 'too-many-public-methods') + def leave_class(self, node): + """check number of public methods""" + my_methods = sum(1 for method in node.mymethods() + if not method.name.startswith('_')) + all_methods = sum(1 for method in node.methods() + if not method.name.startswith('_')) + + # Does the class contain less than n public methods ? + # This checks only the methods defined in the current class, + # since the user might not have control over the classes + # from the ancestors. It avoids some false positives + # for classes such as unittest.TestCase, which provides + # a lot of assert methods. It doesn't make sense to warn + # when the user subclasses TestCase to add his own tests. + if my_methods > self.config.max_public_methods: + self.add_message('too-many-public-methods', node=node, + args=(my_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 n public methods ? + # This checks all the methods defined by ancestors and + # by the current class. + if all_methods < self.config.min_public_methods: + self.add_message('too-few-public-methods', node=node, + args=(all_methods, + self.config.min_public_methods)) + + @check_messages('too-many-return-statements', 'too-many-branches', + 'too-many-arguments', 'too-many-locals', + 'too-many-statements') + def visit_function(self, node): + """check function name, docstring, arguments, redefinition, + variable names, max locals + """ + # init branch and returns counters + self._returns.append(0) + # check number of arguments + args = node.args.args + if args is not None: + ignored_args_num = len( + [arg for arg in args + if self.config.ignored_argument_names.match(arg.name)]) + argnum = len(args) - ignored_args_num + if argnum > self.config.max_args: + self.add_message('too-many-arguments', node=node, + args=(len(args), self.config.max_args)) + else: + ignored_args_num = 0 + # check number of local variables + locnum = len(node.locals) - ignored_args_num + if locnum > self.config.max_locals: + self.add_message('too-many-locals', node=node, + args=(locnum, self.config.max_locals)) + # init statements counter + self._stmts = 1 + + @check_messages('too-many-return-statements', 'too-many-branches', + 'too-many-arguments', 'too-many-locals', + 'too-many-statements') + 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('too-many-return-statements', node=node, + args=(returns, self.config.max_returns)) + branches = self._branches[node] + if branches > self.config.max_branches: + self.add_message('too-many-branches', node=node, + args=(branches, self.config.max_branches)) + # check number of statements + if self._stmts > self.config.max_statements: + self.add_message('too-many-statements', node=node, + args=(self._stmts, self.config.max_statements)) + + def visit_return(self, _): + """count number of returns""" + if not self._returns: + return # return outside function, reported by the base checker + self._returns[-1] += 1 + + def visit_default(self, node): + """default visit method -> increments the statements counter if + necessary + """ + if node.is_statement: + self._stmts += 1 + + def visit_tryexcept(self, node): + """increments the branches counter""" + branches = len(node.handlers) + if node.orelse: + branches += 1 + self._inc_branch(node, branches) + self._stmts += branches + + def visit_tryfinally(self, node): + """increments the branches counter""" + self._inc_branch(node, 2) + self._stmts += 2 + + def visit_if(self, node): + """increments the branches counter""" + branches = 1 + # don't double count If nodes coming from some 'elif' + if node.orelse and (len(node.orelse) > 1 or + not isinstance(node.orelse[0], If)): + branches += 1 + self._inc_branch(node, branches) + self._stmts += branches + + def visit_while(self, node): + """increments the branches counter""" + branches = 1 + if node.orelse: + branches += 1 + self._inc_branch(node, branches) + + visit_for = visit_while + + def _inc_branch(self, node, branchesnum=1): + """increments the branches counter""" + self._branches[node.scope()] += branchesnum + + # FIXME: make a nice report... + +def register(linter): + """required method to auto register this checker """ + linter.register_checker(MisdesignChecker(linter)) diff --git a/pylint/checkers/exceptions.py b/pylint/checkers/exceptions.py new file mode 100644 index 0000000..88a8f22 --- /dev/null +++ b/pylint/checkers/exceptions.py @@ -0,0 +1,332 @@ +# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +"""exceptions handling (raising, catching, exceptions classes) checker +""" +import sys + +import astroid +from astroid import YES, Instance, unpack_infer, List, Tuple +from logilab.common.compat import builtins + +from pylint.checkers import BaseChecker +from pylint.checkers.utils import ( + is_empty, + is_raising, + check_messages, + inherit_from_std_ex, + EXCEPTIONS_MODULE, + has_known_bases, + safe_infer) +from pylint.interfaces import IAstroidChecker, INFERENCE, INFERENCE_FAILURE + + +def _annotated_unpack_infer(stmt, context=None): + """ + Recursively generate nodes inferred by the given statement. + If the inferred value is a list or a tuple, recurse on the elements. + Returns an iterator which yields tuples in the format + ('original node', 'infered node'). + """ + if isinstance(stmt, (List, Tuple)): + for elt in stmt.elts: + inferred = safe_infer(elt) + if inferred and inferred is not YES: + yield elt, inferred + return + for infered in stmt.infer(context): + if infered is YES: + continue + yield stmt, infered + + +PY3K = sys.version_info >= (3, 0) +OVERGENERAL_EXCEPTIONS = ('Exception',) +BUILTINS_NAME = builtins.__name__ +MSGS = { + 'E0701': ('Bad except clauses order (%s)', + 'bad-except-order', + '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 or instances are allowed', + 'raising-bad-type', + 'Used when something which is neither a class, an instance or a \ + string is raised (i.e. a `TypeError` will be raised).'), + 'E0703': ('Exception context set to something which is not an ' + 'exception, nor None', + 'bad-exception-context', + 'Used when using the syntax "raise ... from ...", ' + 'where the exception context is not an exception, ' + 'nor None.', + {'minversion': (3, 0)}), + 'E0710': ('Raising a new style class which doesn\'t inherit from BaseException', + 'raising-non-exception', + 'Used when a new style class which doesn\'t inherit from \ + BaseException is raised.'), + 'E0711': ('NotImplemented raised - should raise NotImplementedError', + 'notimplemented-raised', + 'Used when NotImplemented is raised instead of \ + NotImplementedError'), + 'E0712': ('Catching an exception which doesn\'t inherit from BaseException: %s', + 'catching-non-exception', + 'Used when a class which doesn\'t inherit from \ + BaseException is used as an exception in an except clause.'), + 'W0702': ('No exception type(s) specified', + 'bare-except', + 'Used when an except clause doesn\'t specify exceptions type to \ + catch.'), + 'W0703': ('Catching too general exception %s', + 'broad-except', + 'Used when an except catches a too general exception, \ + possibly burying unrelated errors.'), + 'W0704': ('Except doesn\'t do anything', + 'pointless-except', + 'Used when an except clause does nothing but "pass" and there is\ + no "else" clause.'), + 'W0710': ('Exception doesn\'t inherit from standard "Exception" class', + 'nonstandard-exception', + 'Used when a custom exception class is raised but doesn\'t \ + inherit from the builtin "Exception" class.', + {'maxversion': (3, 0)}), + 'W0711': ('Exception to catch is the result of a binary "%s" operation', + 'binary-op-exception', + 'Used when the exception to catch is of the form \ + "except A or B:". If intending to catch multiple, \ + rewrite as "except (A, B):"'), + } + + +class ExceptionsChecker(BaseChecker): + """checks for + * excepts without exception filter + * type of raise argument : string, Exceptions, other values + """ + + __implements__ = IAstroidChecker + + name = 'exceptions' + msgs = MSGS + priority = -4 + options = (('overgeneral-exceptions', + {'default' : OVERGENERAL_EXCEPTIONS, + 'type' :'csv', 'metavar' : '<comma-separated class names>', + 'help' : 'Exceptions that will emit a warning ' + 'when being caught. Defaults to "%s"' % ( + ', '.join(OVERGENERAL_EXCEPTIONS),)} + ), + ) + + @check_messages('nonstandard-exception', + 'raising-bad-type', 'raising-non-exception', + 'notimplemented-raised', 'bad-exception-context') + def visit_raise(self, node): + """visit raise possibly inferring value""" + # ignore empty raise + if node.exc is None: + return + if PY3K and node.cause: + self._check_bad_exception_context(node) + + expr = node.exc + if self._check_raise_value(node, expr): + return + else: + try: + value = next(unpack_infer(expr)) + except astroid.InferenceError: + return + self._check_raise_value(node, value) + + def _check_bad_exception_context(self, node): + """Verify that the exception context is properly set. + + An exception context can be only `None` or an exception. + """ + cause = safe_infer(node.cause) + if cause in (YES, None): + return + if isinstance(cause, astroid.Const): + if cause.value is not None: + self.add_message('bad-exception-context', + node=node) + elif (not isinstance(cause, astroid.Class) and + not inherit_from_std_ex(cause)): + self.add_message('bad-exception-context', + node=node) + + def _check_raise_value(self, node, expr): + """check for bad values, string exception and class inheritance + """ + value_found = True + if isinstance(expr, astroid.Const): + value = expr.value + if not isinstance(value, str): + # raising-string will be emitted from python3 porting checker. + self.add_message('raising-bad-type', node=node, + args=value.__class__.__name__) + elif ((isinstance(expr, astroid.Name) and + expr.name in ('None', 'True', 'False')) or + isinstance(expr, (astroid.List, astroid.Dict, astroid.Tuple, + astroid.Module, astroid.Function))): + emit = True + if not PY3K and isinstance(expr, astroid.Tuple): + # On Python 2, using the following is not an error: + # raise (ZeroDivisionError, None) + # raise (ZeroDivisionError, ) + # What's left to do is to check that the first + # argument is indeed an exception. + # Verifying the other arguments is not + # the scope of this check. + first = expr.elts[0] + inferred = safe_infer(first) + if isinstance(inferred, Instance): + # pylint: disable=protected-access + inferred = inferred._proxied + if (inferred is YES or + isinstance(inferred, astroid.Class) + and inherit_from_std_ex(inferred)): + emit = False + if emit: + self.add_message('raising-bad-type', + node=node, + args=expr.name) + elif ((isinstance(expr, astroid.Name) and expr.name == 'NotImplemented') + or (isinstance(expr, astroid.CallFunc) and + isinstance(expr.func, astroid.Name) and + expr.func.name == 'NotImplemented')): + self.add_message('notimplemented-raised', node=node) + elif isinstance(expr, (Instance, astroid.Class)): + if isinstance(expr, Instance): + # pylint: disable=protected-access + expr = expr._proxied + if (isinstance(expr, astroid.Class) and + not inherit_from_std_ex(expr)): + if expr.newstyle: + self.add_message('raising-non-exception', node=node) + else: + if has_known_bases(expr): + confidence = INFERENCE + else: + confidence = INFERENCE_FAILURE + self.add_message( + 'nonstandard-exception', node=node, + confidence=confidence) + else: + value_found = False + else: + value_found = False + return value_found + + def _check_catching_non_exception(self, handler, exc, part): + if isinstance(exc, astroid.Tuple): + # Check if it is a tuple of exceptions. + inferred = [safe_infer(elt) for elt in exc.elts] + if any(node is astroid.YES for node in inferred): + # Don't emit if we don't know every component. + return + if all(node and inherit_from_std_ex(node) + for node in inferred): + return + + if not isinstance(exc, astroid.Class): + # Don't emit the warning if the infered stmt + # is None, but the exception handler is something else, + # maybe it was redefined. + if (isinstance(exc, astroid.Const) and + exc.value is None): + if ((isinstance(handler.type, astroid.Const) and + handler.type.value is None) or + handler.type.parent_of(exc)): + # If the exception handler catches None or + # the exception component, which is None, is + # defined by the entire exception handler, then + # emit a warning. + self.add_message('catching-non-exception', + node=handler.type, + args=(part.as_string(), )) + else: + self.add_message('catching-non-exception', + node=handler.type, + args=(part.as_string(), )) + return + if (not inherit_from_std_ex(exc) and + exc.root().name != BUILTINS_NAME): + if has_known_bases(exc): + self.add_message('catching-non-exception', + node=handler.type, + args=(exc.name, )) + + @check_messages('bare-except', 'broad-except', 'pointless-except', + 'binary-op-exception', 'bad-except-order', + 'catching-non-exception') + def visit_tryexcept(self, node): + """check for empty except""" + exceptions_classes = [] + nb_handlers = len(node.handlers) + for index, handler in enumerate(node.handlers): + # single except doing nothing but "pass" without else clause + if is_empty(handler.body) and not node.orelse: + self.add_message('pointless-except', + node=handler.type or handler.body[0]) + if handler.type is None: + if not is_raising(handler.body): + self.add_message('bare-except', node=handler) + # check if a "except:" is followed by some other + # except + if index < (nb_handlers - 1): + msg = 'empty except clause should always appear last' + self.add_message('bad-except-order', node=node, args=msg) + + elif isinstance(handler.type, astroid.BoolOp): + self.add_message('binary-op-exception', + node=handler, args=handler.type.op) + else: + try: + excs = list(_annotated_unpack_infer(handler.type)) + except astroid.InferenceError: + continue + for part, exc in excs: + if exc is YES: + continue + if (isinstance(exc, astroid.Instance) + and inherit_from_std_ex(exc)): + # pylint: disable=protected-access + exc = exc._proxied + + self._check_catching_non_exception(handler, exc, part) + + if not isinstance(exc, astroid.Class): + continue + + exc_ancestors = [anc for anc in exc.ancestors() + if isinstance(anc, astroid.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('bad-except-order', + node=handler.type, args=msg) + if (exc.name in self.config.overgeneral_exceptions + and exc.root().name == EXCEPTIONS_MODULE + and not is_raising(handler.body)): + self.add_message('broad-except', + args=exc.name, node=handler.type) + + exceptions_classes += [exc for _, exc in excs] + + +def register(linter): + """required method to auto register this checker""" + linter.register_checker(ExceptionsChecker(linter)) diff --git a/pylint/checkers/format.py b/pylint/checkers/format.py new file mode 100644 index 0000000..94a9e8e --- /dev/null +++ b/pylint/checkers/format.py @@ -0,0 +1,964 @@ +# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +"""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. +""" + +import keyword +import sys +import tokenize +from functools import reduce # pylint: disable=redefined-builtin + +import six +from six.moves import zip, map, filter # pylint: disable=redefined-builtin + +from astroid import nodes + +from pylint.interfaces import ITokenChecker, IAstroidChecker, IRawChecker +from pylint.checkers import BaseTokenChecker +from pylint.checkers.utils import check_messages +from pylint.utils import WarningScope, OPTION_RGX + +_CONTINUATION_BLOCK_OPENERS = ['elif', 'except', 'for', 'if', 'while', 'def', 'class'] +_KEYWORD_TOKENS = ['assert', 'del', 'elif', 'except', 'for', 'if', 'in', 'not', + 'raise', 'return', 'while', 'yield'] +if sys.version_info < (3, 0): + _KEYWORD_TOKENS.append('print') + +_SPACED_OPERATORS = ['==', '<', '>', '!=', '<>', '<=', '>=', + '+=', '-=', '*=', '**=', '/=', '//=', '&=', '|=', '^=', + '%=', '>>=', '<<='] +_OPENING_BRACKETS = ['(', '[', '{'] +_CLOSING_BRACKETS = [')', ']', '}'] +_TAB_LENGTH = 8 + +_EOL = frozenset([tokenize.NEWLINE, tokenize.NL, tokenize.COMMENT]) +_JUNK_TOKENS = (tokenize.COMMENT, tokenize.NL) + +# Whitespace checking policy constants +_MUST = 0 +_MUST_NOT = 1 +_IGNORE = 2 + +# Whitespace checking config constants +_DICT_SEPARATOR = 'dict-separator' +_TRAILING_COMMA = 'trailing-comma' +_NO_SPACE_CHECK_CHOICES = [_TRAILING_COMMA, _DICT_SEPARATOR] + +MSGS = { + 'C0301': ('Line too long (%s/%s)', + 'line-too-long', + 'Used when a line is longer than a given number of characters.'), + 'C0302': ('Too many lines in module (%s/%s)', # was W0302 + 'too-many-lines', + 'Used when a module has too much lines, reducing its readability.' + ), + 'C0303': ('Trailing whitespace', + 'trailing-whitespace', + 'Used when there is whitespace between the end of a line and the ' + 'newline.'), + 'C0304': ('Final newline missing', + 'missing-final-newline', + 'Used when the last line in a file is missing a newline.'), + 'W0311': ('Bad indentation. Found %s %s, expected %s', + 'bad-indentation', + 'Used when an unexpected number of indentation\'s tabulations or ' + 'spaces has been found.'), + 'C0330': ('Wrong %s indentation%s.\n%s%s', + 'bad-continuation', + 'TODO'), + 'W0312': ('Found indentation with %ss instead of %ss', + 'mixed-indentation', + 'Used when there are some mixed tabs and spaces in a module.'), + 'W0301': ('Unnecessary semicolon', # was W0106 + 'unnecessary-semicolon', + 'Used when a statement is ended by a semi-colon (";"), which \ + isn\'t necessary (that\'s python, not C ;).'), + 'C0321': ('More than one statement on a single line', + 'multiple-statements', + 'Used when more than on statement are found on the same line.', + {'scope': WarningScope.NODE}), + 'C0325' : ('Unnecessary parens after %r keyword', + 'superfluous-parens', + 'Used when a single item in parentheses follows an if, for, or ' + 'other keyword.'), + 'C0326': ('%s space %s %s %s\n%s', + 'bad-whitespace', + ('Used when a wrong number of spaces is used around an operator, ' + 'bracket or block opener.'), + {'old_names': [('C0323', 'no-space-after-operator'), + ('C0324', 'no-space-after-comma'), + ('C0322', 'no-space-before-operator')]}), + 'W0332': ('Use of "l" as long integer identifier', + 'lowercase-l-suffix', + '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"', + {'maxversion': (3, 0)}), + 'C0327': ('Mixed line endings LF and CRLF', + 'mixed-line-endings', + 'Used when there are mixed (LF and CRLF) newline signs in a file.'), + 'C0328': ('Unexpected line ending format. There is \'%s\' while it should be \'%s\'.', + 'unexpected-line-ending-format', + 'Used when there is different newline than expected.'), + } + + +def _underline_token(token): + length = token[3][1] - token[2][1] + offset = token[2][1] + return token[4] + (' ' * offset) + ('^' * length) + + +def _column_distance(token1, token2): + if token1 == token2: + return 0 + if token2[3] < token1[3]: + token1, token2 = token2, token1 + if token1[3][0] != token2[2][0]: + return None + return token2[2][1] - token1[3][1] + + +def _last_token_on_line_is(tokens, line_end, token): + return (line_end > 0 and tokens.token(line_end-1) == token or + line_end > 1 and tokens.token(line_end-2) == token + and tokens.type(line_end-1) == tokenize.COMMENT) + + +def _token_followed_by_eol(tokens, position): + return (tokens.type(position+1) == tokenize.NL or + tokens.type(position+1) == tokenize.COMMENT and + tokens.type(position+2) == tokenize.NL) + + +def _get_indent_length(line): + """Return the length of the indentation on the given token's line.""" + result = 0 + for char in line: + if char == ' ': + result += 1 + elif char == '\t': + result += _TAB_LENGTH + else: + break + return result + + +def _get_indent_hint_line(bar_positions, bad_position): + """Return a line with |s for each of the positions in the given lists.""" + if not bar_positions: + return '' + markers = [(pos, '|') for pos in bar_positions] + markers.append((bad_position, '^')) + markers.sort() + line = [' '] * (markers[-1][0] + 1) + for position, marker in markers: + line[position] = marker + return ''.join(line) + + +class _ContinuedIndent(object): + __slots__ = ('valid_outdent_offsets', + 'valid_continuation_offsets', + 'context_type', + 'token', + 'position') + + def __init__(self, + context_type, + token, + position, + valid_outdent_offsets, + valid_continuation_offsets): + self.valid_outdent_offsets = valid_outdent_offsets + self.valid_continuation_offsets = valid_continuation_offsets + self.context_type = context_type + self.position = position + self.token = token + + +# The contexts for hanging indents. +# A hanging indented dictionary value after : +HANGING_DICT_VALUE = 'dict-value' +# Hanging indentation in an expression. +HANGING = 'hanging' +# Hanging indentation in a block header. +HANGING_BLOCK = 'hanging-block' +# Continued indentation inside an expression. +CONTINUED = 'continued' +# Continued indentation in a block header. +CONTINUED_BLOCK = 'continued-block' + +SINGLE_LINE = 'single' +WITH_BODY = 'multi' + +_CONTINUATION_MSG_PARTS = { + HANGING_DICT_VALUE: ('hanging', ' in dict value'), + HANGING: ('hanging', ''), + HANGING_BLOCK: ('hanging', ' before block'), + CONTINUED: ('continued', ''), + CONTINUED_BLOCK: ('continued', ' before block'), +} + + +def _Offsets(*args): + """Valid indentation offsets for a continued line.""" + return dict((a, None) for a in args) + + +def _BeforeBlockOffsets(single, with_body): + """Valid alternative indent offsets for continued lines before blocks. + + :param single: Valid offset for statements on a single logical line. + :param with_body: Valid offset for statements on several lines. + """ + return {single: SINGLE_LINE, with_body: WITH_BODY} + + +class TokenWrapper(object): + """A wrapper for readable access to token information.""" + + def __init__(self, tokens): + self._tokens = tokens + + def token(self, idx): + return self._tokens[idx][1] + + def type(self, idx): + return self._tokens[idx][0] + + def start_line(self, idx): + return self._tokens[idx][2][0] + + def start_col(self, idx): + return self._tokens[idx][2][1] + + def line(self, idx): + return self._tokens[idx][4] + + +class ContinuedLineState(object): + """Tracker for continued indentation inside a logical line.""" + + def __init__(self, tokens, config): + self._line_start = -1 + self._cont_stack = [] + self._is_block_opener = False + self.retained_warnings = [] + self._config = config + self._tokens = TokenWrapper(tokens) + + @property + def has_content(self): + return bool(self._cont_stack) + + @property + def _block_indent_size(self): + return len(self._config.indent_string.replace('\t', ' ' * _TAB_LENGTH)) + + @property + def _continuation_size(self): + return self._config.indent_after_paren + + def handle_line_start(self, pos): + """Record the first non-junk token at the start of a line.""" + if self._line_start > -1: + return + self._is_block_opener = self._tokens.token(pos) in _CONTINUATION_BLOCK_OPENERS + self._line_start = pos + + def next_physical_line(self): + """Prepares the tracker for a new physical line (NL).""" + self._line_start = -1 + self._is_block_opener = False + + def next_logical_line(self): + """Prepares the tracker for a new logical line (NEWLINE). + + A new logical line only starts with block indentation. + """ + self.next_physical_line() + self.retained_warnings = [] + self._cont_stack = [] + + def add_block_warning(self, token_position, state, valid_offsets): + self.retained_warnings.append((token_position, state, valid_offsets)) + + def get_valid_offsets(self, idx): + """"Returns the valid offsets for the token at the given position.""" + # The closing brace on a dict or the 'for' in a dict comprehension may + # reset two indent levels because the dict value is ended implicitly + stack_top = -1 + if self._tokens.token(idx) in ('}', 'for') and self._cont_stack[-1].token == ':': + stack_top = -2 + indent = self._cont_stack[stack_top] + if self._tokens.token(idx) in _CLOSING_BRACKETS: + valid_offsets = indent.valid_outdent_offsets + else: + valid_offsets = indent.valid_continuation_offsets + return indent, valid_offsets.copy() + + def _hanging_indent_after_bracket(self, bracket, position): + """Extracts indentation information for a hanging indent.""" + indentation = _get_indent_length(self._tokens.line(position)) + if self._is_block_opener and self._continuation_size == self._block_indent_size: + return _ContinuedIndent( + HANGING_BLOCK, + bracket, + position, + _Offsets(indentation + self._continuation_size, indentation), + _BeforeBlockOffsets(indentation + self._continuation_size, + indentation + self._continuation_size * 2)) + elif bracket == ':': + # If the dict key was on the same line as the open brace, the new + # correct indent should be relative to the key instead of the + # current indent level + paren_align = self._cont_stack[-1].valid_outdent_offsets + next_align = self._cont_stack[-1].valid_continuation_offsets.copy() + next_align_keys = list(next_align.keys()) + next_align[next_align_keys[0] + self._continuation_size] = True + # Note that the continuation of + # d = { + # 'a': 'b' + # 'c' + # } + # is handled by the special-casing for hanging continued string indents. + return _ContinuedIndent(HANGING_DICT_VALUE, bracket, position, paren_align, next_align) + else: + return _ContinuedIndent( + HANGING, + bracket, + position, + _Offsets(indentation, indentation + self._continuation_size), + _Offsets(indentation + self._continuation_size)) + + def _continuation_inside_bracket(self, bracket, pos): + """Extracts indentation information for a continued indent.""" + indentation = _get_indent_length(self._tokens.line(pos)) + if self._is_block_opener and self._tokens.start_col(pos+1) - indentation == self._block_indent_size: + return _ContinuedIndent( + CONTINUED_BLOCK, + bracket, + pos, + _Offsets(self._tokens.start_col(pos)), + _BeforeBlockOffsets(self._tokens.start_col(pos+1), + self._tokens.start_col(pos+1) + self._continuation_size)) + else: + return _ContinuedIndent( + CONTINUED, + bracket, + pos, + _Offsets(self._tokens.start_col(pos)), + _Offsets(self._tokens.start_col(pos+1))) + + def pop_token(self): + self._cont_stack.pop() + + def push_token(self, token, position): + """Pushes a new token for continued indentation on the stack. + + Tokens that can modify continued indentation offsets are: + * opening brackets + * 'lambda' + * : inside dictionaries + + push_token relies on the caller to filter out those + interesting tokens. + + :param token: The concrete token + :param position: The position of the token in the stream. + """ + if _token_followed_by_eol(self._tokens, position): + self._cont_stack.append( + self._hanging_indent_after_bracket(token, position)) + else: + self._cont_stack.append( + self._continuation_inside_bracket(token, position)) + + +class FormatChecker(BaseTokenChecker): + """checks for : + * unauthorized constructions + * strict indentation + * line length + """ + + __implements__ = (ITokenChecker, IAstroidChecker, IRawChecker) + + # 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' : 100, 'type' : "int", 'metavar' : '<int>', + 'help' : 'Maximum number of characters on a single line.'}), + ('ignore-long-lines', + {'type': 'regexp', 'metavar': '<regexp>', + 'default': r'^\s*(# )?<?https?://\S+>?$', + 'help': ('Regexp for a line that is allowed to be longer than ' + 'the limit.')}), + ('single-line-if-stmt', + {'default': False, 'type' : 'yn', 'metavar' : '<y_or_n>', + 'help' : ('Allow the body of an if to be on the same ' + 'line as the test if there is no else.')}), + ('no-space-check', + {'default': ','.join(_NO_SPACE_CHECK_CHOICES), + 'type': 'multiple_choice', + 'choices': _NO_SPACE_CHECK_CHOICES, + 'help': ('List of optional constructs for which whitespace ' + 'checking is disabled')}), + ('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).'}), + ('indent-after-paren', + {'type': 'int', 'metavar': '<int>', 'default': 4, + 'help': 'Number of spaces of indent required inside a hanging ' + ' or continued line.'}), + ('expected-line-ending-format', + {'type': 'choice', 'metavar': '<empty or LF or CRLF>', 'default': '', + 'choices': ['', 'LF', 'CRLF'], + 'help': 'Expected format of line ending, e.g. empty (any line ending), LF or CRLF.'}), + ) + + def __init__(self, linter=None): + BaseTokenChecker.__init__(self, linter) + self._lines = None + self._visited_lines = None + self._bracket_stack = [None] + + def _pop_token(self): + self._bracket_stack.pop() + self._current_line.pop_token() + + def _push_token(self, token, idx): + self._bracket_stack.append(token) + self._current_line.push_token(token, idx) + + def new_line(self, tokens, line_end, line_start): + """a new line has been encountered, process it if necessary""" + if _last_token_on_line_is(tokens, line_end, ';'): + self.add_message('unnecessary-semicolon', line=tokens.start_line(line_end)) + + line_num = tokens.start_line(line_start) + line = tokens.line(line_start) + if tokens.type(line_start) not in _JUNK_TOKENS: + self._lines[line_num] = line.split('\n')[0] + self.check_lines(line, line_num) + + def process_module(self, module): + self._keywords_with_parens = set() + if 'print_function' in module.future_imports: + self._keywords_with_parens.add('print') + + def _check_keyword_parentheses(self, tokens, start): + """Check that there are not unnecessary parens after a keyword. + + Parens are unnecessary if there is exactly one balanced outer pair on a + line, and it is followed by a colon, and contains no commas (i.e. is not a + tuple). + + Args: + tokens: list of Tokens; the entire list of Tokens. + start: int; the position of the keyword in the token list. + """ + # If the next token is not a paren, we're fine. + if self._inside_brackets(':') and tokens[start][1] == 'for': + self._pop_token() + if tokens[start+1][1] != '(': + return + + found_and_or = False + depth = 0 + keyword_token = tokens[start][1] + line_num = tokens[start][2][0] + + for i in range(start, len(tokens) - 1): + token = tokens[i] + + # If we hit a newline, then assume any parens were for continuation. + if token[0] == tokenize.NL: + return + + if token[1] == '(': + depth += 1 + elif token[1] == ')': + depth -= 1 + if not depth: + # ')' can't happen after if (foo), since it would be a syntax error. + if (tokens[i+1][1] in (':', ')', ']', '}', 'in') or + tokens[i+1][0] in (tokenize.NEWLINE, + tokenize.ENDMARKER, + tokenize.COMMENT)): + # The empty tuple () is always accepted. + if i == start + 2: + return + if keyword_token == 'not': + if not found_and_or: + self.add_message('superfluous-parens', line=line_num, + args=keyword_token) + elif keyword_token in ('return', 'yield'): + self.add_message('superfluous-parens', line=line_num, + args=keyword_token) + elif keyword_token not in self._keywords_with_parens: + if not (tokens[i+1][1] == 'in' and found_and_or): + self.add_message('superfluous-parens', line=line_num, + args=keyword_token) + return + elif depth == 1: + # This is a tuple, which is always acceptable. + if token[1] == ',': + return + # 'and' and 'or' are the only boolean operators with lower precedence + # than 'not', so parens are only required when they are found. + elif token[1] in ('and', 'or'): + found_and_or = True + # A yield inside an expression must always be in parentheses, + # quit early without error. + elif token[1] == 'yield': + return + # A generator expression always has a 'for' token in it, and + # the 'for' token is only legal inside parens when it is in a + # generator expression. The parens are necessary here, so bail + # without an error. + elif token[1] == 'for': + return + + def _opening_bracket(self, tokens, i): + self._push_token(tokens[i][1], i) + # Special case: ignore slices + if tokens[i][1] == '[' and tokens[i+1][1] == ':': + return + + if (i > 0 and (tokens[i-1][0] == tokenize.NAME and + not (keyword.iskeyword(tokens[i-1][1])) + or tokens[i-1][1] in _CLOSING_BRACKETS)): + self._check_space(tokens, i, (_MUST_NOT, _MUST_NOT)) + else: + self._check_space(tokens, i, (_IGNORE, _MUST_NOT)) + + def _closing_bracket(self, tokens, i): + if self._inside_brackets(':'): + self._pop_token() + self._pop_token() + # Special case: ignore slices + if tokens[i-1][1] == ':' and tokens[i][1] == ']': + return + policy_before = _MUST_NOT + if tokens[i][1] in _CLOSING_BRACKETS and tokens[i-1][1] == ',': + if _TRAILING_COMMA in self.config.no_space_check: + policy_before = _IGNORE + + self._check_space(tokens, i, (policy_before, _IGNORE)) + + def _check_equals_spacing(self, tokens, i): + """Check the spacing of a single equals sign.""" + if self._inside_brackets('(') or self._inside_brackets('lambda'): + self._check_space(tokens, i, (_MUST_NOT, _MUST_NOT)) + else: + self._check_space(tokens, i, (_MUST, _MUST)) + + def _open_lambda(self, tokens, i): # pylint:disable=unused-argument + self._push_token('lambda', i) + + def _handle_colon(self, tokens, i): + # Special case: ignore slices + if self._inside_brackets('['): + return + if (self._inside_brackets('{') and + _DICT_SEPARATOR in self.config.no_space_check): + policy = (_IGNORE, _IGNORE) + else: + policy = (_MUST_NOT, _MUST) + self._check_space(tokens, i, policy) + + if self._inside_brackets('lambda'): + self._pop_token() + elif self._inside_brackets('{'): + self._push_token(':', i) + + def _handle_comma(self, tokens, i): + # Only require a following whitespace if this is + # not a hanging comma before a closing bracket. + if tokens[i+1][1] in _CLOSING_BRACKETS: + self._check_space(tokens, i, (_MUST_NOT, _IGNORE)) + else: + self._check_space(tokens, i, (_MUST_NOT, _MUST)) + if self._inside_brackets(':'): + self._pop_token() + + def _check_surrounded_by_space(self, tokens, i): + """Check that a binary operator is surrounded by exactly one space.""" + self._check_space(tokens, i, (_MUST, _MUST)) + + def _check_space(self, tokens, i, policies): + def _policy_string(policy): + if policy == _MUST: + return 'Exactly one', 'required' + else: + return 'No', 'allowed' + + def _name_construct(token): + if token[1] == ',': + return 'comma' + elif token[1] == ':': + return ':' + elif token[1] in '()[]{}': + return 'bracket' + elif token[1] in ('<', '>', '<=', '>=', '!=', '=='): + return 'comparison' + else: + if self._inside_brackets('('): + return 'keyword argument assignment' + else: + return 'assignment' + + good_space = [True, True] + token = tokens[i] + pairs = [(tokens[i-1], token), (token, tokens[i+1])] + + for other_idx, (policy, token_pair) in enumerate(zip(policies, pairs)): + if token_pair[other_idx][0] in _EOL or policy == _IGNORE: + continue + + distance = _column_distance(*token_pair) + if distance is None: + continue + good_space[other_idx] = ( + (policy == _MUST and distance == 1) or + (policy == _MUST_NOT and distance == 0)) + + warnings = [] + if not any(good_space) and policies[0] == policies[1]: + warnings.append((policies[0], 'around')) + else: + for ok, policy, position in zip(good_space, policies, ('before', 'after')): + if not ok: + warnings.append((policy, position)) + for policy, position in warnings: + construct = _name_construct(token) + count, state = _policy_string(policy) + self.add_message('bad-whitespace', line=token[2][0], + args=(count, state, position, construct, + _underline_token(token))) + + def _inside_brackets(self, left): + return self._bracket_stack[-1] == left + + def _prepare_token_dispatcher(self): + raw = [ + (_KEYWORD_TOKENS, + self._check_keyword_parentheses), + + (_OPENING_BRACKETS, self._opening_bracket), + + (_CLOSING_BRACKETS, self._closing_bracket), + + (['='], self._check_equals_spacing), + + (_SPACED_OPERATORS, self._check_surrounded_by_space), + + ([','], self._handle_comma), + + ([':'], self._handle_colon), + + (['lambda'], self._open_lambda), + + ] + + dispatch = {} + for tokens, handler in raw: + for token in tokens: + dispatch[token] = handler + return dispatch + + 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). + """ + self._bracket_stack = [None] + indents = [0] + check_equal = False + line_num = 0 + self._lines = {} + self._visited_lines = {} + token_handlers = self._prepare_token_dispatcher() + self._last_line_ending = None + + self._current_line = ContinuedLineState(tokens, self.config) + for idx, (tok_type, token, start, _, line) in enumerate(tokens): + if start[0] != line_num: + line_num = start[0] + # A tokenizer oddity: if an indented line contains a multi-line + # docstring, the line member of the INDENT token does not contain + # the full line; therefore we check the next token on the line. + if tok_type == tokenize.INDENT: + self.new_line(TokenWrapper(tokens), idx-1, idx+1) + else: + self.new_line(TokenWrapper(tokens), idx-1, idx) + + if tok_type == tokenize.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 = True + self._process_retained_warnings(TokenWrapper(tokens), idx) + self._current_line.next_logical_line() + self._check_line_ending(token, line_num) + elif tok_type == tokenize.INDENT: + check_equal = False + self.check_indent_level(token, indents[-1]+1, line_num) + indents.append(indents[-1]+1) + elif tok_type == tokenize.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 = True + if len(indents) > 1: + del indents[-1] + elif tok_type == tokenize.NL: + self._check_continued_indentation(TokenWrapper(tokens), idx+1) + self._current_line.next_physical_line() + elif tok_type != tokenize.COMMENT: + self._current_line.handle_line_start(idx) + # This is the first concrete 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 + if check_equal: + check_equal = False + self.check_indent_level(line, indents[-1], line_num) + + if tok_type == tokenize.NUMBER and token.endswith('l'): + self.add_message('lowercase-l-suffix', line=line_num) + + try: + handler = token_handlers[token] + except KeyError: + pass + else: + handler(tokens, idx) + + line_num -= 1 # to be ok with "wc -l" + if line_num > self.config.max_module_lines: + # Get the line where the too-many-lines (or its message id) + # was disabled or default to 1. + symbol = self.linter.msgs_store.check_message_id('too-many-lines') + names = (symbol.msgid, 'too-many-lines') + line = next(filter(None, + map(self.linter._pragma_lineno.get, names)), 1) + self.add_message('too-many-lines', + args=(line_num, self.config.max_module_lines), + line=line) + + def _check_line_ending(self, line_ending, line_num): + # check if line endings are mixed + if self._last_line_ending is not None: + if line_ending != self._last_line_ending: + self.add_message('mixed-line-endings', line=line_num) + + self._last_line_ending = line_ending + + # check if line ending is as expected + expected = self.config.expected_line_ending_format + if expected: + line_ending = reduce(lambda x, y: x + y if x != y else x, line_ending, "") # reduce multiple \n\n\n\n to one \n + line_ending = 'LF' if line_ending == '\n' else 'CRLF' + if line_ending != expected: + self.add_message('unexpected-line-ending-format', args=(line_ending, expected), line=line_num) + + + def _process_retained_warnings(self, tokens, current_pos): + single_line_block_stmt = not _last_token_on_line_is(tokens, current_pos, ':') + + for indent_pos, state, offsets in self._current_line.retained_warnings: + block_type = offsets[tokens.start_col(indent_pos)] + hints = dict((k, v) for k, v in six.iteritems(offsets) + if v != block_type) + if single_line_block_stmt and block_type == WITH_BODY: + self._add_continuation_message(state, hints, tokens, indent_pos) + elif not single_line_block_stmt and block_type == SINGLE_LINE: + self._add_continuation_message(state, hints, tokens, indent_pos) + + def _check_continued_indentation(self, tokens, next_idx): + def same_token_around_nl(token_type): + return (tokens.type(next_idx) == token_type and + tokens.type(next_idx-2) == token_type) + + # Do not issue any warnings if the next line is empty. + if not self._current_line.has_content or tokens.type(next_idx) == tokenize.NL: + return + + state, valid_offsets = self._current_line.get_valid_offsets(next_idx) + # Special handling for hanging comments and strings. If the last line ended + # with a comment (string) and the new line contains only a comment, the line + # may also be indented to the start of the previous token. + if same_token_around_nl(tokenize.COMMENT) or same_token_around_nl(tokenize.STRING): + valid_offsets[tokens.start_col(next_idx-2)] = True + + # We can only decide if the indentation of a continued line before opening + # a new block is valid once we know of the body of the block is on the + # same line as the block opener. Since the token processing is single-pass, + # emitting those warnings is delayed until the block opener is processed. + if (state.context_type in (HANGING_BLOCK, CONTINUED_BLOCK) + and tokens.start_col(next_idx) in valid_offsets): + self._current_line.add_block_warning(next_idx, state, valid_offsets) + elif tokens.start_col(next_idx) not in valid_offsets: + self._add_continuation_message(state, valid_offsets, tokens, next_idx) + + def _add_continuation_message(self, state, offsets, tokens, position): + readable_type, readable_position = _CONTINUATION_MSG_PARTS[state.context_type] + hint_line = _get_indent_hint_line(offsets, tokens.start_col(position)) + self.add_message( + 'bad-continuation', + line=tokens.start_line(position), + args=(readable_type, readable_position, tokens.line(position), hint_line)) + + @check_messages('multiple-statements') + def visit_default(self, node): + """check the node line number and check it if not yet done""" + if not node.is_statement: + return + if not node.root().pure_python: + return # XXX block visit of child nodes + prev_sibl = node.previous_sibling() + if prev_sibl is not None: + prev_line = prev_sibl.fromlineno + else: + # The line on which a finally: occurs in a try/finally + # is not directly represented in the AST. We infer it + # by taking the last line of the body and adding 1, which + # should be the line of finally: + if (isinstance(node.parent, nodes.TryFinally) + and node in node.parent.finalbody): + prev_line = node.parent.body[0].tolineno + 1 + else: + prev_line = node.parent.statement().fromlineno + line = node.fromlineno + assert line, node + if prev_line == line and self._visited_lines.get(line) != 2: + self._check_multi_statement_line(node, line) + return + if line in self._visited_lines: + return + try: + tolineno = node.blockstart_tolineno + except AttributeError: + tolineno = node.tolineno + assert tolineno, node + lines = [] + for line in range(line, tolineno + 1): + self._visited_lines[line] = 1 + try: + lines.append(self._lines[line].rstrip()) + except KeyError: + lines.append('') + + def _check_multi_statement_line(self, node, line): + """Check for lines containing multiple statements.""" + # Do not warn about multiple nested context managers + # in with statements. + if isinstance(node, nodes.With): + return + # For try... except... finally..., the two nodes + # appear to be on the same line due to how the AST is built. + if (isinstance(node, nodes.TryExcept) and + isinstance(node.parent, nodes.TryFinally)): + return + if (isinstance(node.parent, nodes.If) and not node.parent.orelse + and self.config.single_line_if_stmt): + return + self.add_message('multiple-statements', node=node) + self._visited_lines[line] = 2 + + def check_lines(self, lines, i): + """check lines have less than a maximum number of characters + """ + max_chars = self.config.max_line_length + ignore_long_line = self.config.ignore_long_lines + + for line in lines.splitlines(True): + if not line.endswith('\n'): + self.add_message('missing-final-newline', line=i) + else: + stripped_line = line.rstrip() + if line[len(stripped_line):] not in ('\n', '\r\n'): + self.add_message('trailing-whitespace', line=i) + # Don't count excess whitespace in the line length. + line = stripped_line + mobj = OPTION_RGX.search(line) + if mobj and mobj.group(1).split('=', 1)[0].strip() == 'disable': + line = line.split('#')[0].rstrip() + + if len(line) > max_chars and not ignore_long_line.search(line): + self.add_message('line-too-long', 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('mixed-indentation', 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('bad-indentation', 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/pylint/checkers/imports.py b/pylint/checkers/imports.py new file mode 100644 index 0000000..1969eeb --- /dev/null +++ b/pylint/checkers/imports.py @@ -0,0 +1,413 @@ +# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +"""imports checkers for Python code""" + +import sys +from collections import defaultdict + +import six +from six.moves import map # pylint: disable=redefined-builtin + +from logilab.common.graph import get_cycles, DotBackend +from logilab.common.ureports import VerbatimText, Paragraph + +import astroid +from astroid import are_exclusive +from astroid.modutils import get_module_part, is_standard_module + +from pylint.interfaces import IAstroidChecker +from pylint.utils import EmptyReport +from pylint.checkers import BaseChecker +from pylint.checkers.utils import check_messages, is_import_error + +def _except_import_error(node): + """ + Check if the try-except node has an ImportError handler. + Return True if an ImportError handler was infered, False otherwise. + """ + if not isinstance(node, astroid.TryExcept): + return + return any(map(is_import_error, node.handlers)) + +def get_first_import(node, context, name, base, level): + """return the node where [base.]<name> is imported or None if not found + """ + fullname = '%s.%s' % (base, name) if base else name + + first = None + found = False + for first in context.body: + if first is node: + continue + if first.scope() is node.scope() and first.fromlineno > node.fromlineno: + continue + if isinstance(first, astroid.Import): + if any(fullname == iname[0] for iname in first.names): + found = True + break + elif isinstance(first, astroid.From): + if level == first.level and any( + fullname == '%s.%s' % (first.modname, iname[0]) + for iname in first.names): + found = True + break + if found and not are_exclusive(first, node): + return first + +# utilities to represents import dependencies as tree and dot graph ########### + +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 dictionary 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, (mod, (sub, files)) in enumerate(sorted(nodes, key=lambda x: x[0])): + 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(r'%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 dependencies_graph(filename, dep_info): + """write dependencies as a dot (graphviz) file + """ + done = {} + printer = DotBackend(filename[:-4], rankdir='LR') + printer.emit('URL="." node[shape="box"]') + for modname, dependencies in sorted(six.iteritems(dep_info)): + done[modname] = 1 + printer.emit_node(modname) + for modname in dependencies: + if modname not in done: + done[modname] = 1 + printer.emit_node(modname) + for depmodname, dependencies in sorted(six.iteritems(dep_info)): + for modname in dependencies: + printer.emit_edge(modname, depmodname) + printer.generate(filename) + + +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 %s', + 'import-error', + 'Used when pylint has been unable to import a module.'), + 'R0401': ('Cyclic import (%s)', + 'cyclic-import', + 'Used when a cyclic import between two or more modules is \ + detected.'), + + 'W0401': ('Wildcard import %s', + 'wildcard-import', + 'Used when `from module import *` is detected.'), + 'W0402': ('Uses of a deprecated module %r', + 'deprecated-module', + 'Used a module marked as deprecated is imported.'), + 'W0403': ('Relative import %r, should be %r', + 'relative-import', + 'Used when an import relative to the package directory is ' + 'detected.', + {'maxversion': (3, 0)}), + 'W0404': ('Reimport %r (imported line %s)', + 'reimported', + 'Used when a module is reimported multiple times.'), + 'W0406': ('Module import itself', + 'import-self', + 'Used when a module is importing itself.'), + + 'W0410': ('__future__ import is not the first non docstring statement', + 'misplaced-future', + 'Python 2.5 and greater require __future__ import to be the \ + first non docstring statement in the module.', + {'maxversion': (3, 0)}), + } + +class ImportsChecker(BaseChecker): + """checks for + * external modules dependencies + * relative / wildcard imports + * cyclic imports + * uses of deprecated modules + """ + + __implements__ = IAstroidChecker + + name = 'imports' + msgs = MSGS + priority = -2 + + if sys.version_info < (3,): + deprecated_modules = ('regsub', 'TERMIOS', 'Bastion', 'rexec') + else: + deprecated_modules = ('stringprep', 'optparse') + options = (('deprecated-modules', + {'default' : deprecated_modules, + '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 RP0402 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 RP0402 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 RP0402 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 = (('RP0401', 'External dependencies', + self.report_external_dependencies), + ('RP0402', '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 = defaultdict(set) + + def close(self): + """called before visiting project (i.e set of modules)""" + # don't try to compute cycles if the associated message is disabled + if self.linter.is_message_enabled('cyclic-import'): + vertices = list(self.import_graph) + for cycle in get_cycles(self.import_graph, vertices=vertices): + self.add_message('cyclic-import', args=' -> '.join(cycle)) + + def visit_import(self, node): + """triggered when an import statement is seen""" + modnode = node.root() + for name, _ in node.names: + importedmodnode = self.get_imported_module(node, name) + if importedmodnode is None: + continue + self._check_relative_import(modnode, node, importedmodnode, name) + self._add_imported_module(node, importedmodnode.name) + self._check_deprecated_module(node, name) + self._check_reimport(node, name) + + # TODO This appears to be the list of all messages of the checker... + # @check_messages('W0410', 'W0401', 'W0403', 'W0402', 'W0404', 'W0406', 'F0401') + @check_messages(*(MSGS.keys())) + def visit_from(self, node): + """triggered when a from statement is seen""" + basename = node.modname + if basename == '__future__': + # check if this is the first non-docstring statement in the module + prev = node.previous_sibling() + if prev: + # consecutive future statements are possible + if not (isinstance(prev, astroid.From) + and prev.modname == '__future__'): + self.add_message('misplaced-future', node=node) + return + for name, _ in node.names: + if name == '*': + self.add_message('wildcard-import', args=basename, node=node) + modnode = node.root() + importedmodnode = self.get_imported_module(node, basename) + if importedmodnode is None: + return + self._check_relative_import(modnode, node, importedmodnode, basename) + self._check_deprecated_module(node, basename) + for name, _ in node.names: + if name != '*': + self._add_imported_module(node, '%s.%s' % (importedmodnode.name, name)) + self._check_reimport(node, name, basename, node.level) + + def get_imported_module(self, importnode, modname): + try: + return importnode.do_import_module(modname) + except astroid.InferenceError as ex: + if str(ex) != modname: + args = '%r (%s)' % (modname, ex) + else: + args = repr(modname) + if not _except_import_error(importnode.parent): + self.add_message("import-error", args=args, node=importnode) + + def _check_relative_import(self, modnode, importnode, importedmodnode, + importedasname): + """check relative import. node is either an Import or From node, modname + the imported module name. + """ + if not self.linter.is_message_enabled('relative-import'): + return + if importedmodnode.file is None: + return False # built-in module + if modnode is importedmodnode: + return False # module importing itself + if modnode.absolute_import_activated() or getattr(importnode, 'level', None): + return False + if importedmodnode.name != importedasname: + # this must be a relative import... + self.add_message('relative-import', + args=(importedasname, importedmodnode.name), + node=importnode) + + def _add_imported_module(self, node, importedmodname): + """notify an imported module, used to analyze dependencies""" + try: + importedmodname = get_module_part(importedmodname) + except ImportError: + pass + context_name = node.root().name + if context_name == importedmodname: + # module importing itself ! + self.add_message('import-self', node=node) + elif not is_standard_module(importedmodname): + # handle dependencies + importedmodnames = self.stats['dependencies'].setdefault( + importedmodname, set()) + if not context_name in importedmodnames: + importedmodnames.add(context_name) + # update import graph + mgraph = self.import_graph[context_name] + if importedmodname not in mgraph: + mgraph.add(importedmodname) + + def _check_deprecated_module(self, node, mod_path): + """check if the module is deprecated""" + for mod_name in self.config.deprecated_modules: + if mod_path == mod_name or mod_path.startswith(mod_name + '.'): + self.add_message('deprecated-module', node=node, args=mod_path) + + def _check_reimport(self, node, name, basename=None, level=None): + """check if the import is necessary (i.e. not already done)""" + if not self.linter.is_message_enabled('reimported'): + return + frame = node.frame() + root = node.root() + contexts = [(frame, level)] + if root is not frame: + contexts.append((root, None)) + for context, level in contexts: + first = get_first_import(node, context, name, basename, level) + if first is not None: + self.add_message('reimported', node=node, + args=(name, first.fromlineno)) + + + def report_external_dependencies(self, sect, _, dummy): + """return a verbatim layout for displaying dependencies""" + dep_info = make_tree_defs(six.iteritems(self._external_dependencies_info())) + 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: + package = self.linter.current_name + self.__ext_dep_info = result = {} + for importee, importers in six.iteritems(self.stats['dependencies']): + if not importee.startswith(package): + result[importee] = importers + 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: + package = self.linter.current_name + self.__int_dep_info = result = {} + for importee, importers in six.iteritems(self.stats['dependencies']): + if importee.startswith(package): + result[importee] = importers + return self.__int_dep_info + + +def register(linter): + """required method to auto register this checker """ + linter.register_checker(ImportsChecker(linter)) diff --git a/pylint/checkers/logging.py b/pylint/checkers/logging.py new file mode 100644 index 0000000..897c1c7 --- /dev/null +++ b/pylint/checkers/logging.py @@ -0,0 +1,256 @@ +# Copyright (c) 2009-2010 Google, Inc. +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +"""checker for use of Python logging +""" + +import astroid +from pylint import checkers +from pylint import interfaces +from pylint.checkers import utils +from pylint.checkers.utils import check_messages + +import six + + +MSGS = { + 'W1201': ('Specify string format arguments as logging function parameters', + 'logging-not-lazy', + 'Used when a logging statement has a call form of ' + '"logging.<logging method>(format_string % (format_args...))". ' + 'Such calls should leave string interpolation to the logging ' + 'method itself and be written ' + '"logging.<logging method>(format_string, format_args...)" ' + 'so that the program may avoid incurring the cost of the ' + 'interpolation in those cases in which no message will be ' + 'logged. For more, see ' + 'http://www.python.org/dev/peps/pep-0282/.'), + 'W1202': ('Use % formatting in logging functions but pass the % ' + 'parameters as arguments', + 'logging-format-interpolation', + 'Used when a logging statement has a call form of ' + '"logging.<logging method>(format_string.format(format_args...))"' + '. Such calls should use % formatting instead, but leave ' + 'interpolation to the logging function by passing the parameters ' + 'as arguments.'), + 'E1200': ('Unsupported logging format character %r (%#02x) at index %d', + 'logging-unsupported-format', + 'Used when an unsupported format character is used in a logging\ + statement format string.'), + 'E1201': ('Logging format string ends in middle of conversion specifier', + 'logging-format-truncated', + 'Used when a logging statement format string terminates before\ + the end of a conversion specifier.'), + 'E1205': ('Too many arguments for logging format string', + 'logging-too-many-args', + 'Used when a logging format string is given too few arguments.'), + 'E1206': ('Not enough arguments for logging format string', + 'logging-too-few-args', + 'Used when a logging format string is given too many arguments'), + } + + +CHECKED_CONVENIENCE_FUNCTIONS = set([ + 'critical', 'debug', 'error', 'exception', 'fatal', 'info', 'warn', + 'warning']) + +def is_method_call(callfunc_node, types=(), methods=()): + """Determines if a CallFunc node represents a method call. + + Args: + callfunc_node: The CallFunc AST node to check. + types: Optional sequence of caller type names to restrict check. + methods: Optional sequence of method names to restrict check. + + Returns: + True, if the node represents a method call for the given type and + method names, False otherwise. + """ + if not isinstance(callfunc_node, astroid.CallFunc): + return False + func = utils.safe_infer(callfunc_node.func) + return (isinstance(func, astroid.BoundMethod) + and isinstance(func.bound, astroid.Instance) + and (func.bound.name in types if types else True) + and (func.name in methods if methods else True)) + + + +class LoggingChecker(checkers.BaseChecker): + """Checks use of the logging module.""" + + __implements__ = interfaces.IAstroidChecker + name = 'logging' + msgs = MSGS + + options = (('logging-modules', + {'default': ('logging',), + 'type': 'csv', + 'metavar': '<comma separated list>', + 'help': 'Logging modules to check that the string format ' + 'arguments are in logging function parameter format'} + ), + ) + + def visit_module(self, node): # pylint: disable=unused-argument + """Clears any state left in this checker from last module checked.""" + # The code being checked can just as easily "import logging as foo", + # so it is necessary to process the imports and store in this field + # what name the logging module is actually given. + self._logging_names = set() + logging_mods = self.config.logging_modules + + self._logging_modules = set(logging_mods) + self._from_imports = {} + for logging_mod in logging_mods: + parts = logging_mod.rsplit('.', 1) + if len(parts) > 1: + self._from_imports[parts[0]] = parts[1] + + def visit_from(self, node): + """Checks to see if a module uses a non-Python logging module.""" + try: + logging_name = self._from_imports[node.modname] + for module, as_name in node.names: + if module == logging_name: + self._logging_names.add(as_name or module) + except KeyError: + pass + + def visit_import(self, node): + """Checks to see if this module uses Python's built-in logging.""" + for module, as_name in node.names: + if module in self._logging_modules: + self._logging_names.add(as_name or module) + + @check_messages(*(MSGS.keys())) + def visit_callfunc(self, node): + """Checks calls to logging methods.""" + def is_logging_name(): + return (isinstance(node.func, astroid.Getattr) and + isinstance(node.func.expr, astroid.Name) and + node.func.expr.name in self._logging_names) + + def is_logger_class(): + try: + for inferred in node.func.infer(): + if isinstance(inferred, astroid.BoundMethod): + parent = inferred._proxied.parent + if (isinstance(parent, astroid.Class) and + (parent.qname() == 'logging.Logger' or + any(ancestor.qname() == 'logging.Logger' + for ancestor in parent.ancestors()))): + return True, inferred._proxied.name + except astroid.exceptions.InferenceError: + pass + return False, None + + if is_logging_name(): + name = node.func.attrname + else: + result, name = is_logger_class() + if not result: + return + self._check_log_method(node, name) + + def _check_log_method(self, node, name): + """Checks calls to logging.log(level, format, *format_args).""" + if name == 'log': + if node.starargs or node.kwargs or len(node.args) < 2: + # Either a malformed call, star args, or double-star args. Beyond + # the scope of this checker. + return + format_pos = 1 + elif name in CHECKED_CONVENIENCE_FUNCTIONS: + if node.starargs or node.kwargs or not node.args: + # Either no args, star args, or double-star args. Beyond the + # scope of this checker. + return + format_pos = 0 + else: + return + + if isinstance(node.args[format_pos], astroid.BinOp) and node.args[format_pos].op == '%': + self.add_message('logging-not-lazy', node=node) + elif isinstance(node.args[format_pos], astroid.CallFunc): + self._check_call_func(node.args[format_pos]) + elif isinstance(node.args[format_pos], astroid.Const): + self._check_format_string(node, format_pos) + + def _check_call_func(self, callfunc_node): + """Checks that function call is not format_string.format(). + + Args: + callfunc_node: CallFunc AST node to be checked. + """ + if is_method_call(callfunc_node, ('str', 'unicode'), ('format',)): + self.add_message('logging-format-interpolation', node=callfunc_node) + + def _check_format_string(self, node, format_arg): + """Checks that format string tokens match the supplied arguments. + + Args: + node: AST node to be checked. + format_arg: Index of the format string in the node arguments. + """ + num_args = _count_supplied_tokens(node.args[format_arg + 1:]) + if not num_args: + # If no args were supplied, then all format strings are valid - + # don't check any further. + return + format_string = node.args[format_arg].value + if not isinstance(format_string, six.string_types): + # If the log format is constant non-string (e.g. logging.debug(5)), + # ensure there are no arguments. + required_num_args = 0 + else: + try: + keyword_args, required_num_args = \ + utils.parse_format_string(format_string) + if keyword_args: + # Keyword checking on logging strings is complicated by + # special keywords - out of scope. + return + except utils.UnsupportedFormatCharacter as ex: + char = format_string[ex.index] + self.add_message('logging-unsupported-format', node=node, + args=(char, ord(char), ex.index)) + return + except utils.IncompleteFormatString: + self.add_message('logging-format-truncated', node=node) + return + if num_args > required_num_args: + self.add_message('logging-too-many-args', node=node) + elif num_args < required_num_args: + self.add_message('logging-too-few-args', node=node) + + +def _count_supplied_tokens(args): + """Counts the number of tokens in an args list. + + The Python log functions allow for special keyword arguments: func, + exc_info and extra. To handle these cases correctly, we only count + arguments that aren't keywords. + + Args: + args: List of AST nodes that are arguments for a log format string. + + Returns: + Number of AST nodes that aren't keywords. + """ + return sum(1 for arg in args if not isinstance(arg, astroid.Keyword)) + + +def register(linter): + """Required method to auto-register this checker.""" + linter.register_checker(LoggingChecker(linter)) diff --git a/pylint/checkers/misc.py b/pylint/checkers/misc.py new file mode 100644 index 0000000..7fbe70b --- /dev/null +++ b/pylint/checkers/misc.py @@ -0,0 +1,104 @@ +# pylint: disable=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., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +""" Copyright (c) 2000-2010 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) +""" + +import re + +from pylint.interfaces import IRawChecker +from pylint.checkers import BaseChecker +import six + + +MSGS = { + 'W0511': ('%s', + 'fixme', + 'Used when a warning note as FIXME or XXX is detected.'), + 'W0512': ('Cannot decode using encoding "%s", unexpected byte at position %d', + 'invalid-encoded-data', + 'Used when a source line cannot be decoded using the specified ' + 'source file encoding.', + {'maxversion': (3, 0)}), +} + + +class EncodingChecker(BaseChecker): + + """checks for: + * warning notes in the code like FIXME, XXX + * encoding issues. + """ + __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.')}),) + + def _check_note(self, notes, lineno, line): + # First, simply check if the notes are in the line at all. This is an + # optimisation to prevent using the regular expression on every line, + # but rather only on lines which may actually contain one of the notes. + # This prevents a pathological problem with lines that are hundreds + # of thousands of characters long. + for note in self.config.notes: + if note in line: + break + else: + return + + match = notes.search(line) + if not match: + return + self.add_message('fixme', args=line[match.start(1):-1], line=lineno) + + def _check_encoding(self, lineno, line, file_encoding): + try: + return six.text_type(line, file_encoding) + except UnicodeDecodeError as ex: + self.add_message('invalid-encoded-data', line=lineno, + args=(file_encoding, ex.args[2])) + + def process_module(self, module): + """inspect the source file to find encoding problem or fixmes like + notes + """ + if self.config.notes: + notes = re.compile( + r'.*?#\s*(%s)(:*\s*.+)' % "|".join(self.config.notes)) + else: + notes = None + if module.file_encoding: + encoding = module.file_encoding + else: + encoding = 'ascii' + + with module.stream() as stream: + for lineno, line in enumerate(stream): + line = self._check_encoding(lineno + 1, line, encoding) + if line is not None and notes: + self._check_note(notes, lineno + 1, line) + + +def register(linter): + """required method to auto register this checker""" + linter.register_checker(EncodingChecker(linter)) diff --git a/pylint/checkers/newstyle.py b/pylint/checkers/newstyle.py new file mode 100644 index 0000000..f74e7f1 --- /dev/null +++ b/pylint/checkers/newstyle.py @@ -0,0 +1,172 @@ +# Copyright (c) 2005-2014 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +"""check for new / old style related problems +""" +import sys + +import astroid + +from pylint.interfaces import IAstroidChecker, INFERENCE, INFERENCE_FAILURE, HIGH +from pylint.checkers import BaseChecker +from pylint.checkers.utils import ( + check_messages, + has_known_bases, + node_frame_class, +) + +MSGS = { + 'E1001': ('Use of __slots__ on an old style class', + 'slots-on-old-class', + 'Used when an old style class uses the __slots__ attribute.', + {'maxversion': (3, 0)}), + 'E1002': ('Use of super on an old style class', + 'super-on-old-class', + 'Used when an old style class uses the super builtin.', + {'maxversion': (3, 0)}), + 'E1003': ('Bad first argument %r given to super()', + 'bad-super-call', + 'Used when another argument than the current class is given as \ + first argument of the super builtin.'), + 'E1004': ('Missing argument to super()', + 'missing-super-argument', + 'Used when the super builtin didn\'t receive an \ + argument.', + {'maxversion': (3, 0)}), + 'W1001': ('Use of "property" on an old style class', + 'property-on-old-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.', + {'maxversion': (3, 0)}), + 'C1001': ('Old-style class defined.', + 'old-style-class', + 'Used when a class is defined that does not inherit from another' + 'class and does not inherit explicitly from "object".', + {'maxversion': (3, 0)}) + } + + +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 + """ + + __implements__ = (IAstroidChecker,) + + # configuration section name + name = 'newstyle' + # messages + msgs = MSGS + priority = -2 + # configuration options + options = () + + @check_messages('slots-on-old-class', 'old-style-class') + def visit_class(self, node): + """ Check __slots__ in old style classes and old + style class definition. + """ + if '__slots__' in node and not node.newstyle: + confidence = (INFERENCE if has_known_bases(node) + else INFERENCE_FAILURE) + self.add_message('slots-on-old-class', node=node, + confidence=confidence) + # The node type could be class, exception, metaclass, or + # interface. Presumably, the non-class-type nodes would always + # have an explicit base class anyway. + if not node.bases and node.type == 'class' and not node.metaclass(): + # We use confidence HIGH here because this message should only ever + # be emitted for classes at the root of the inheritance hierarchyself. + self.add_message('old-style-class', node=node, confidence=HIGH) + + @check_messages('property-on-old-class') + def visit_callfunc(self, node): + """check property usage""" + parent = node.parent.frame() + if (isinstance(parent, astroid.Class) and + not parent.newstyle and + isinstance(node.func, astroid.Name)): + confidence = (INFERENCE if has_known_bases(parent) + else INFERENCE_FAILURE) + name = node.func.name + if name == 'property': + self.add_message('property-on-old-class', node=node, + confidence=confidence) + + @check_messages('super-on-old-class', 'bad-super-call', 'missing-super-argument') + 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(astroid.CallFunc): + if node_frame_class(stmt) != node_frame_class(node): + # Don't look down in other scopes. + continue + expr = stmt.func + if not isinstance(expr, astroid.Getattr): + continue + call = expr.expr + # skip the test if using super + if isinstance(call, astroid.CallFunc) and \ + isinstance(call.func, astroid.Name) and \ + call.func.name == 'super': + confidence = (INFERENCE if has_known_bases(klass) + else INFERENCE_FAILURE) + if not klass.newstyle: + # super should not be used on an old style class + self.add_message('super-on-old-class', node=node, + confidence=confidence) + else: + # super first arg should be the class + if not call.args and sys.version_info[0] == 3: + # unless Python 3 + continue + + try: + supcls = (call.args and next(call.args[0].infer()) + or None) + except astroid.InferenceError: + continue + + if supcls is None: + self.add_message('missing-super-argument', node=call, + confidence=confidence) + continue + + if klass is not supcls: + name = None + # if supcls is not YES, then supcls was infered + # and use its name. Otherwise, try to look + # for call.args[0].name + if supcls is not astroid.YES: + name = supcls.name + else: + if hasattr(call.args[0], 'name'): + name = call.args[0].name + if name is not None: + self.add_message('bad-super-call', + node=call, + args=(name, ), + confidence=confidence) + + +def register(linter): + """required method to auto register this checker """ + linter.register_checker(NewStyleConflictChecker(linter)) diff --git a/pylint/checkers/python3.py b/pylint/checkers/python3.py new file mode 100644 index 0000000..880d5c5 --- /dev/null +++ b/pylint/checkers/python3.py @@ -0,0 +1,488 @@ +# Copyright 2014 Google Inc. +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +"""Check Python 2 code for Python 2/3 source-compatible issues.""" +from __future__ import absolute_import + +import re +import tokenize + +import astroid +from pylint import checkers, interfaces +from pylint.utils import WarningScope +from pylint.checkers import utils + + +_ZERO = re.compile("^0+$") + +def _is_old_octal(literal): + if _ZERO.match(literal): + return False + if re.match('0\d+', literal): + try: + int(literal, 8) + except ValueError: + return False + return True + +def _check_dict_node(node): + inferred_types = set() + try: + inferred = node.infer() + for inferred_node in inferred: + inferred_types.add(inferred_node) + except (astroid.InferenceError, astroid.UnresolvableName): + pass + return (not inferred_types + or any(isinstance(x, astroid.Dict) for x in inferred_types)) + + +class Python3Checker(checkers.BaseChecker): + + __implements__ = interfaces.IAstroidChecker + enabled = False + name = 'python3' + + msgs = { + # Errors for what will syntactically break in Python 3, warnings for + # everything else. + 'E1601': ('print statement used', + 'print-statement', + 'Used when a print statement is used ' + '(`print` is a function in Python 3)', + {'maxversion': (3, 0)}), + 'E1602': ('Parameter unpacking specified', + 'parameter-unpacking', + 'Used when parameter unpacking is specified for a function' + "(Python 3 doesn't allow it)", + {'maxversion': (3, 0)}), + 'E1603': ('Implicit unpacking of exceptions is not supported ' + 'in Python 3', + 'unpacking-in-except', + 'Python3 will not allow implicit unpacking of ' + 'exceptions in except clauses. ' + 'See http://www.python.org/dev/peps/pep-3110/', + {'maxversion': (3, 0), + 'old_names': [('W0712', 'unpacking-in-except')]}), + 'E1604': ('Use raise ErrorClass(args) instead of ' + 'raise ErrorClass, args.', + 'old-raise-syntax', + "Used when the alternate raise syntax " + "'raise foo, bar' is used " + "instead of 'raise foo(bar)'.", + {'maxversion': (3, 0), + 'old_names': [('W0121', 'old-raise-syntax')]}), + 'E1605': ('Use of the `` operator', + 'backtick', + 'Used when the deprecated "``" (backtick) operator is used ' + 'instead of the str() function.', + {'scope': WarningScope.NODE, + 'maxversion': (3, 0), + 'old_names': [('W0333', 'backtick')]}), + 'W1601': ('apply built-in referenced', + 'apply-builtin', + 'Used when the apply built-in function is referenced ' + '(missing from Python 3)', + {'maxversion': (3, 0)}), + 'W1602': ('basestring built-in referenced', + 'basestring-builtin', + 'Used when the basestring built-in function is referenced ' + '(missing from Python 3)', + {'maxversion': (3, 0)}), + 'W1603': ('buffer built-in referenced', + 'buffer-builtin', + 'Used when the buffer built-in function is referenced ' + '(missing from Python 3)', + {'maxversion': (3, 0)}), + 'W1604': ('cmp built-in referenced', + 'cmp-builtin', + 'Used when the cmp built-in function is referenced ' + '(missing from Python 3)', + {'maxversion': (3, 0)}), + 'W1605': ('coerce built-in referenced', + 'coerce-builtin', + 'Used when the coerce built-in function is referenced ' + '(missing from Python 3)', + {'maxversion': (3, 0)}), + 'W1606': ('execfile built-in referenced', + 'execfile-builtin', + 'Used when the execfile built-in function is referenced ' + '(missing from Python 3)', + {'maxversion': (3, 0)}), + 'W1607': ('file built-in referenced', + 'file-builtin', + 'Used when the file built-in function is referenced ' + '(missing from Python 3)', + {'maxversion': (3, 0)}), + 'W1608': ('long built-in referenced', + 'long-builtin', + 'Used when the long built-in function is referenced ' + '(missing from Python 3)', + {'maxversion': (3, 0)}), + 'W1609': ('raw_input built-in referenced', + 'raw_input-builtin', + 'Used when the raw_input built-in function is referenced ' + '(missing from Python 3)', + {'maxversion': (3, 0)}), + 'W1610': ('reduce built-in referenced', + 'reduce-builtin', + 'Used when the reduce built-in function is referenced ' + '(missing from Python 3)', + {'maxversion': (3, 0)}), + 'W1611': ('StandardError built-in referenced', + 'standarderror-builtin', + 'Used when the StandardError built-in function is referenced ' + '(missing from Python 3)', + {'maxversion': (3, 0)}), + 'W1612': ('unicode built-in referenced', + 'unicode-builtin', + 'Used when the unicode built-in function is referenced ' + '(missing from Python 3)', + {'maxversion': (3, 0)}), + 'W1613': ('xrange built-in referenced', + 'xrange-builtin', + 'Used when the xrange built-in function is referenced ' + '(missing from Python 3)', + {'maxversion': (3, 0)}), + 'W1614': ('__coerce__ method defined', + 'coerce-method', + 'Used when a __coerce__ method is defined ' + '(method is not used by Python 3)', + {'maxversion': (3, 0)}), + 'W1615': ('__delslice__ method defined', + 'delslice-method', + 'Used when a __delslice__ method is defined ' + '(method is not used by Python 3)', + {'maxversion': (3, 0)}), + 'W1616': ('__getslice__ method defined', + 'getslice-method', + 'Used when a __getslice__ method is defined ' + '(method is not used by Python 3)', + {'maxversion': (3, 0)}), + 'W1617': ('__setslice__ method defined', + 'setslice-method', + 'Used when a __setslice__ method is defined ' + '(method is not used by Python 3)', + {'maxversion': (3, 0)}), + 'W1618': ('import missing `from __future__ import absolute_import`', + 'no-absolute-import', + 'Used when an import is not accompanied by ' + '`from __future__ import absolute_import`' + ' (default behaviour in Python 3)', + {'maxversion': (3, 0)}), + 'W1619': ('division w/o __future__ statement', + 'old-division', + 'Used for non-floor division w/o a float literal or ' + '``from __future__ import division``' + '(Python 3 returns a float for int division unconditionally)', + {'maxversion': (3, 0)}), + 'W1620': ('Calling a dict.iter*() method', + 'dict-iter-method', + 'Used for calls to dict.iterkeys(), itervalues() or iteritems() ' + '(Python 3 lacks these methods)', + {'maxversion': (3, 0)}), + 'W1621': ('Calling a dict.view*() method', + 'dict-view-method', + 'Used for calls to dict.viewkeys(), viewvalues() or viewitems() ' + '(Python 3 lacks these methods)', + {'maxversion': (3, 0)}), + 'W1622': ('Called a next() method on an object', + 'next-method-called', + "Used when an object's next() method is called " + '(Python 3 uses the next() built-in function)', + {'maxversion': (3, 0)}), + 'W1623': ("Assigning to a class' __metaclass__ attribute", + 'metaclass-assignment', + "Used when a metaclass is specified by assigning to __metaclass__ " + '(Python 3 specifies the metaclass as a class statement argument)', + {'maxversion': (3, 0)}), + 'W1624': ('Indexing exceptions will not work on Python 3', + 'indexing-exception', + 'Indexing exceptions will not work on Python 3. Use ' + '`exception.args[index]` instead.', + {'maxversion': (3, 0), + 'old_names': [('W0713', 'indexing-exception')]}), + 'W1625': ('Raising a string exception', + 'raising-string', + 'Used when a string exception is raised. This will not ' + 'work on Python 3.', + {'maxversion': (3, 0), + 'old_names': [('W0701', 'raising-string')]}), + 'W1626': ('reload built-in referenced', + 'reload-builtin', + 'Used when the reload built-in function is referenced ' + '(missing from Python 3). You can use instead imp.reload ' + 'or importlib.reload.', + {'maxversion': (3, 0)}), + 'W1627': ('__oct__ method defined', + 'oct-method', + 'Used when a __oct__ method is defined ' + '(method is not used by Python 3)', + {'maxversion': (3, 0)}), + 'W1628': ('__hex__ method defined', + 'hex-method', + 'Used when a __hex__ method is defined ' + '(method is not used by Python 3)', + {'maxversion': (3, 0)}), + 'W1629': ('__nonzero__ method defined', + 'nonzero-method', + 'Used when a __nonzero__ method is defined ' + '(method is not used by Python 3)', + {'maxversion': (3, 0)}), + 'W1630': ('__cmp__ method defined', + 'cmp-method', + 'Used when a __cmp__ method is defined ' + '(method is not used by Python 3)', + {'maxversion': (3, 0)}), + 'W1631': ('map is used as implicitly evaluated call', + 'implicit-map-evaluation', + 'Used when the map builtin is used as implicitly ' + 'evaluated call, as in "map(func, args)" on a single line. ' + 'This behaviour will not work in Python 3, where ' + 'map is a generator and must be evaluated. ' + 'Prefer a for-loop as alternative.', + {'maxversion': (3, 0)}), + 'W1632': ('input built-in referenced', + 'input-builtin', + 'Used when the input built-in is referenced ' + '(backwards-incompatible semantics in Python 3)', + {'maxversion': (3, 0)}), + 'W1633': ('round built-in referenced', + 'round-builtin', + 'Used when the round built-in is referenced ' + '(backwards-incompatible semantics in Python 3)', + {'maxversion': (3, 0)}), + 'W1634': ('intern built-in referenced', + 'intern-builtin', + 'Used when the intern built-in is referenced ' + '(Moved to sys.intern in Python 3)', + {'maxversion': (3, 0)}), + 'W1635': ('unichr built-in referenced', + 'unichr-builtin', + 'Used when the unichr built-in is referenced ' + '(Use chr in Python 3)', + {'maxversion': (3, 0)}), + } + + _bad_builtins = frozenset([ + 'apply', + 'basestring', + 'buffer', + 'cmp', + 'coerce', + 'execfile', + 'file', + 'input', # Not missing, but incompatible semantics + 'intern', + 'long', + 'raw_input', + 'reduce', + 'round', # Not missing, but incompatible semantics + 'StandardError', + 'unichr', + 'unicode', + 'xrange', + 'reload', + ]) + + _unused_magic_methods = frozenset([ + '__coerce__', + '__delslice__', + '__getslice__', + '__setslice__', + '__oct__', + '__hex__', + '__nonzero__', + '__cmp__', + ]) + + def __init__(self, *args, **kwargs): + self._future_division = False + self._future_absolute_import = False + super(Python3Checker, self).__init__(*args, **kwargs) + + def visit_function(self, node): + if node.is_method() and node.name in self._unused_magic_methods: + method_name = node.name + if node.name.startswith('__'): + method_name = node.name[2:-2] + self.add_message(method_name + '-method', node=node) + + @utils.check_messages('parameter-unpacking') + def visit_arguments(self, node): + for arg in node.args: + if isinstance(arg, astroid.Tuple): + self.add_message('parameter-unpacking', node=arg) + + @utils.check_messages('implicit-map-evaluation') + def visit_discard(self, node): + if (isinstance(node.value, astroid.CallFunc) and + isinstance(node.value.func, astroid.Name) and + node.value.func.name == 'map'): + module = node.value.func.lookup('map')[0] + if getattr(module, 'name', None) == '__builtin__': + self.add_message('implicit-map-evaluation', node=node) + + def visit_name(self, node): + """Detect when a "bad" built-in is referenced.""" + found_node = node.lookup(node.name)[0] + if getattr(found_node, 'name', None) == '__builtin__': + if node.name in self._bad_builtins: + message = node.name.lower() + '-builtin' + self.add_message(message, node=node) + + @utils.check_messages('print-statement') + def visit_print(self, node): + self.add_message('print-statement', node=node) + + @utils.check_messages('no-absolute-import') + def visit_from(self, node): + if node.modname == '__future__': + for name, _ in node.names: + if name == 'division': + self._future_division = True + elif name == 'absolute_import': + self._future_absolute_import = True + elif not self._future_absolute_import: + self.add_message('no-absolute-import', node=node) + + @utils.check_messages('no-absolute-import') + def visit_import(self, node): + if not self._future_absolute_import: + self.add_message('no-absolute-import', node=node) + + @utils.check_messages('metaclass-assignment') + def visit_class(self, node): + if '__metaclass__' in node.locals: + self.add_message('metaclass-assignment', node=node) + + @utils.check_messages('old-division') + def visit_binop(self, node): + if not self._future_division and node.op == '/': + for arg in (node.left, node.right): + if isinstance(arg, astroid.Const) and isinstance(arg.value, float): + break + else: + self.add_message('old-division', node=node) + + @utils.check_messages('next-method-called', + 'dict-iter-method', + 'dict-view-method') + def visit_callfunc(self, node): + if not isinstance(node.func, astroid.Getattr): + return + if any([node.args, node.starargs, node.kwargs]): + return + if node.func.attrname == 'next': + self.add_message('next-method-called', node=node) + else: + if _check_dict_node(node.func.expr): + if node.func.attrname in ('iterkeys', 'itervalues', 'iteritems'): + self.add_message('dict-iter-method', node=node) + elif node.func.attrname in ('viewkeys', 'viewvalues', 'viewitems'): + self.add_message('dict-view-method', node=node) + + @utils.check_messages('indexing-exception') + def visit_subscript(self, node): + """ Look for indexing exceptions. """ + try: + for infered in node.value.infer(): + if not isinstance(infered, astroid.Instance): + continue + if utils.inherit_from_std_ex(infered): + self.add_message('indexing-exception', node=node) + except astroid.InferenceError: + return + + @utils.check_messages('unpacking-in-except') + def visit_excepthandler(self, node): + """Visit an except handler block and check for exception unpacking.""" + if isinstance(node.name, (astroid.Tuple, astroid.List)): + self.add_message('unpacking-in-except', node=node) + + @utils.check_messages('backtick') + def visit_backquote(self, node): + self.add_message('backtick', node=node) + + @utils.check_messages('raising-string', 'old-raise-syntax') + def visit_raise(self, node): + """Visit a raise statement and check for raising + strings or old-raise-syntax. + """ + if (node.exc is not None and + node.inst is not None and + node.tback is None): + self.add_message('old-raise-syntax', node=node) + + # Ignore empty raise. + if node.exc is None: + return + expr = node.exc + if self._check_raise_value(node, expr): + return + else: + try: + value = next(astroid.unpack_infer(expr)) + except astroid.InferenceError: + return + self._check_raise_value(node, value) + + def _check_raise_value(self, node, expr): + if isinstance(expr, astroid.Const): + value = expr.value + if isinstance(value, str): + self.add_message('raising-string', node=node) + return True + + +class Python3TokenChecker(checkers.BaseTokenChecker): + __implements__ = interfaces.ITokenChecker + name = 'python3' + enabled = False + + msgs = { + 'E1606': ('Use of long suffix', + 'long-suffix', + 'Used when "l" or "L" is used to mark a long integer. ' + 'This will not work in Python 3, since `int` and `long` ' + 'types have merged.', + {'maxversion': (3, 0)}), + 'E1607': ('Use of the <> operator', + 'old-ne-operator', + 'Used when the deprecated "<>" operator is used instead ' + 'of "!=". This is removed in Python 3.', + {'maxversion': (3, 0), + 'old_names': [('W0331', 'old-ne-operator')]}), + 'E1608': ('Use of old octal literal', + 'old-octal-literal', + 'Usen when encountering the old octal syntax, ' + 'removed in Python 3. To use the new syntax, ' + 'prepend 0o on the number.', + {'maxversion': (3, 0)}), + } + + def process_tokens(self, tokens): + for idx, (tok_type, token, start, _, _) in enumerate(tokens): + if tok_type == tokenize.NUMBER: + if token.lower().endswith('l'): + # This has a different semantic than lowercase-l-suffix. + self.add_message('long-suffix', line=start[0]) + elif _is_old_octal(token): + self.add_message('old-octal-literal', line=start[0]) + if tokens[idx][1] == '<>': + self.add_message('old-ne-operator', line=tokens[idx][2][0]) + + +def register(linter): + linter.register_checker(Python3Checker(linter)) + linter.register_checker(Python3TokenChecker(linter)) diff --git a/checkers/raw_metrics.py b/pylint/checkers/raw_metrics.py index 71fecf6..71fecf6 100644 --- a/checkers/raw_metrics.py +++ b/pylint/checkers/raw_metrics.py diff --git a/pylint/checkers/similar.py b/pylint/checkers/similar.py new file mode 100644 index 0000000..9542077 --- /dev/null +++ b/pylint/checkers/similar.py @@ -0,0 +1,372 @@ +# pylint: disable=W0622 +# Copyright (c) 2004-2013 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +"""a similarities / code duplication command line tool and pylint checker +""" +from __future__ import print_function +import sys +from collections import defaultdict + +from logilab.common.ureports import Table + +from pylint.interfaces import IRawChecker +from pylint.checkers import BaseChecker, table_lines_from_stats + +import six +from six.moves import zip + + +class Similar(object): + """finds copy-pasted lines of code in a project""" + + def __init__(self, min_lines=4, ignore_comments=False, + ignore_docstrings=False, ignore_imports=False): + self.min_lines = min_lines + self.ignore_comments = ignore_comments + self.ignore_docstrings = ignore_docstrings + self.ignore_imports = ignore_imports + self.linesets = [] + + def append_stream(self, streamid, stream, encoding=None): + """append a file to search for similarities""" + if encoding is None: + readlines = stream.readlines + else: + readlines = lambda: [line.decode(encoding) for line in stream] + try: + self.linesets.append(LineSet(streamid, + readlines(), + self.ignore_comments, + self.ignore_docstrings, + self.ignore_imports)) + except UnicodeDecodeError: + pass + + 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 = defaultdict(list) + for num, lineset1, idx1, lineset2, idx2 in self._iter_sims(): + duplicate = no_duplicates[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 six.iteritems(no_duplicates): + 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 = sorted(couples) + for lineset, idx in couples: + print("==%s:%s" % (lineset.name, idx)) + # pylint: disable=W0631 + for line in lineset._real_lines[idx:idx+num]: + print(" ", line.rstrip()) + 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=%.2f" \ + % (nb_total_lignes, nb_lignes_dupliquees, + nb_lignes_dupliquees*100. / 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( + zip(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, ignore_imports): + """return lines with leading/trailing whitespace and any ignored code + features removed + """ + + 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 = '' + if ignore_imports: + if line.startswith("import ") or line.startswith("from "): + line = '' + if ignore_comments: + # XXX should use regex in checkers/format to avoid cutting + # at a "#" in a string + line = line.split('#', 1)[0].strip() + strippedlines.append(line) + return strippedlines + + +class LineSet(object): + """Holds and indexes all the lines of a single source file""" + def __init__(self, name, lines, ignore_comments=False, + ignore_docstrings=False, ignore_imports=False): + self.name = name + self._real_lines = lines + self._stripped_lines = stripped_lines(lines, ignore_comments, + ignore_docstrings, + ignore_imports) + 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 __lt__(self, other): + return 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 = defaultdict(list) + for line_no, line in enumerate(self._stripped_lines): + if line: + index[line].append(line_no) + return index + + +MSGS = {'R0801': ('Similar lines in %s files\n%s', + 'duplicate-code', + '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 experiment 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.'} + ), + ('ignore-imports', + {'default' : False, 'type' : 'yn', 'metavar' : '<y or n>', + 'help': 'Ignore imports when computing similarities.'} + ), + ) + # reports + reports = (('RP0801', '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, optname, value, action=None, optdict=None): + """method called to set an option (registered in the options list) + + overridden to report options setting to Similar + """ + BaseChecker.set_option(self, optname, value, action, optdict) + if optname == 'min-similarity-lines': + self.min_lines = self.config.min_similarity_lines + elif optname == 'ignore-comments': + self.ignore_comments = self.config.ignore_comments + elif optname == 'ignore-docstrings': + self.ignore_docstrings = self.config.ignore_docstrings + elif optname == 'ignore-imports': + self.ignore_imports = self.config.ignore_imports + + 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, node): + """process a module + + the module's content is accessible via the stream object + + stream must implement the readlines method + """ + with node.stream() as stream: + self.append_stream(self.linter.current_name, + stream, + node.file_encoding) + + 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() + # pylint: disable=W0631 + 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: symilar [-d|--duplicates min_duplicated_lines] \ +[-i|--ignore-comments] [--ignore-docstrings] [--ignore-imports] file1...') + sys.exit(status) + +def Run(argv=None): + """standalone command line access point""" + if argv is None: + argv = sys.argv[1:] + from getopt import getopt + s_opts = 'hdi' + l_opts = ('help', 'duplicates=', 'ignore-comments', 'ignore-imports', + 'ignore-docstrings') + min_lines = 4 + ignore_comments = False + ignore_docstrings = False + ignore_imports = 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 in ('-i', '--ignore-comments'): + ignore_comments = True + elif opt in ('--ignore-docstrings',): + ignore_docstrings = True + elif opt in ('--ignore-imports',): + ignore_imports = True + if not args: + usage(1) + sim = Similar(min_lines, ignore_comments, ignore_docstrings, ignore_imports) + for filename in args: + with open(filename) as stream: + sim.append_stream(filename, stream) + sim.run() + sys.exit(0) + +if __name__ == '__main__': + Run() diff --git a/pylint/checkers/spelling.py b/pylint/checkers/spelling.py new file mode 100644 index 0000000..f6edd5d --- /dev/null +++ b/pylint/checkers/spelling.py @@ -0,0 +1,250 @@ +# Copyright 2014 Michal Nowikowski. +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +"""Checker for spelling errors in comments and docstrings. +""" + +import sys +import tokenize +import string +import re + +if sys.version_info[0] >= 3: + maketrans = str.maketrans +else: + maketrans = string.maketrans + +from pylint.interfaces import ITokenChecker, IAstroidChecker +from pylint.checkers import BaseTokenChecker +from pylint.checkers.utils import check_messages + +try: + import enchant +except ImportError: + enchant = None + +if enchant is not None: + br = enchant.Broker() + dicts = br.list_dicts() + dict_choices = [''] + [d[0] for d in dicts] + dicts = ["%s (%s)" % (d[0], d[1].name) for d in dicts] + dicts = ", ".join(dicts) + instr = "" +else: + dicts = "none" + dict_choices = [''] + instr = " To make it working install python-enchant package." + +table = maketrans("", "") + +class SpellingChecker(BaseTokenChecker): + """Check spelling in comments and docstrings""" + __implements__ = (ITokenChecker, IAstroidChecker) + name = 'spelling' + msgs = { + 'C0401': ('Wrong spelling of a word \'%s\' in a comment:\n%s\n' + '%s\nDid you mean: \'%s\'?', + 'wrong-spelling-in-comment', + 'Used when a word in comment is not spelled correctly.'), + 'C0402': ('Wrong spelling of a word \'%s\' in a docstring:\n%s\n' + '%s\nDid you mean: \'%s\'?', + 'wrong-spelling-in-docstring', + 'Used when a word in docstring is not spelled correctly.'), + 'C0403': ('Invalid characters %r in a docstring', + 'invalid-characters-in-docstring', + 'Used when a word in docstring cannot be checked by enchant.'), + } + options = (('spelling-dict', + {'default' : '', 'type' : 'choice', 'metavar' : '<dict name>', + 'choices': dict_choices, + 'help' : 'Spelling dictionary name. ' + 'Available dictionaries: %s.%s' % (dicts, instr)}), + ('spelling-ignore-words', + {'default' : '', + 'type' : 'string', + 'metavar' : '<comma separated words>', + 'help' : 'List of comma separated words that ' + 'should not be checked.'}), + ('spelling-private-dict-file', + {'default' : '', + 'type' : 'string', + 'metavar' : '<path to file>', + 'help' : 'A path to a file that contains private ' + 'dictionary; one word per line.'}), + ('spelling-store-unknown-words', + {'default' : 'n', 'type' : 'yn', 'metavar' : '<y_or_n>', + 'help' : 'Tells whether to store unknown words to ' + 'indicated private dictionary in ' + '--spelling-private-dict-file option instead of ' + 'raising a message.'}), + ) + + def open(self): + self.initialized = False + self.private_dict_file = None + + if enchant is None: + return + dict_name = self.config.spelling_dict + if not dict_name: + return + + self.ignore_list = [w.strip() for w in self.config.spelling_ignore_words.split(",")] + # "param" appears in docstring in param description and + # "pylint" appears in comments in pylint pragmas. + self.ignore_list.extend(["param", "pylint"]) + + if self.config.spelling_private_dict_file: + self.spelling_dict = enchant.DictWithPWL( + dict_name, self.config.spelling_private_dict_file) + self.private_dict_file = open( + self.config.spelling_private_dict_file, "a") + else: + self.spelling_dict = enchant.Dict(dict_name) + + if self.config.spelling_store_unknown_words: + self.unknown_words = set() + + # Prepare regex for stripping punctuation signs from text. + # ' and _ are treated in a special way. + puncts = string.punctuation.replace("'", "").replace("_", "") + self.punctuation_regex = re.compile('[%s]' % re.escape(puncts)) + self.initialized = True + + def close(self): + if self.private_dict_file: + self.private_dict_file.close() + + def _check_spelling(self, msgid, line, line_num): + line2 = line.strip() + # Replace ['afadf with afadf (but preserve don't) + line2 = re.sub("'([^a-zA-Z]|$)", " ", line2) + # Replace afadf'] with afadf (but preserve don't) + line2 = re.sub("([^a-zA-Z]|^)'", " ", line2) + # Replace punctuation signs with space e.g. and/or -> and or + line2 = self.punctuation_regex.sub(' ', line2) + + words = [] + for word in line2.split(): + # Skip words with digits. + if len(re.findall(r"\d", word)) > 0: + continue + + # Skip words with mixed big and small letters, + # they are probaly class names. + if (len(re.findall("[A-Z]", word)) > 0 and + len(re.findall("[a-z]", word)) > 0 and + len(word) > 2): + continue + + # Skip words with _ - they are probably function parameter names. + if word.count('_') > 0: + continue + + words.append(word) + + # Go through words and check them. + for word in words: + # Skip words from ignore list. + if word in self.ignore_list: + continue + + orig_word = word + word = word.lower() + + # Strip starting u' from unicode literals and r' from raw strings. + if (word.startswith("u'") or + word.startswith('u"') or + word.startswith("r'") or + word.startswith('r"')) and len(word) > 2: + word = word[2:] + + # If it is a known word, then continue. + try: + if self.spelling_dict.check(word): + continue + except enchant.errors.Error: + # this can only happen in docstrings, not comments + self.add_message('invalid-characters-in-docstring', + line=line_num, args=(word,)) + continue + + # Store word to private dict or raise a message. + if self.config.spelling_store_unknown_words: + if word not in self.unknown_words: + self.private_dict_file.write("%s\n" % word) + self.unknown_words.add(word) + else: + # Present up to 4 suggestions. + # TODO: add support for customising this. + suggestions = self.spelling_dict.suggest(word)[:4] + + m = re.search(r"(\W|^)(%s)(\W|$)" % word, line.lower()) + if m: + # Start position of second group in regex. + col = m.regs[2][0] + else: + col = line.lower().index(word) + indicator = (" " * col) + ("^" * len(word)) + + self.add_message(msgid, line=line_num, + args=(orig_word, line, + indicator, + "' or '".join(suggestions))) + + def process_tokens(self, tokens): + if not self.initialized: + return + + # Process tokens and look for comments. + for (tok_type, token, (start_row, _), _, _) in tokens: + if tok_type == tokenize.COMMENT: + self._check_spelling('wrong-spelling-in-comment', + token, start_row) + + @check_messages('wrong-spelling-in-docstring') + def visit_module(self, node): + if not self.initialized: + return + self._check_docstring(node) + + @check_messages('wrong-spelling-in-docstring') + def visit_class(self, node): + if not self.initialized: + return + self._check_docstring(node) + + @check_messages('wrong-spelling-in-docstring') + def visit_function(self, node): + if not self.initialized: + return + self._check_docstring(node) + + def _check_docstring(self, node): + """check the node has any spelling errors""" + docstring = node.doc + if not docstring: + return + + start_line = node.lineno + 1 + + # Go through lines of docstring + for idx, line in enumerate(docstring.splitlines()): + self._check_spelling('wrong-spelling-in-docstring', + line, start_line + idx) + + +def register(linter): + """required method to auto register this checker """ + linter.register_checker(SpellingChecker(linter)) diff --git a/pylint/checkers/stdlib.py b/pylint/checkers/stdlib.py new file mode 100644 index 0000000..b6b8026 --- /dev/null +++ b/pylint/checkers/stdlib.py @@ -0,0 +1,173 @@ +# Copyright 2012 Google Inc. +# +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +"""Checkers for various standard library functions.""" + +import re +import six +import sys + +import astroid +from astroid.bases import Instance + +from pylint.interfaces import IAstroidChecker +from pylint.checkers import BaseChecker +from pylint.checkers import utils + + +if sys.version_info >= (3, 0): + OPEN_MODULE = '_io' +else: + OPEN_MODULE = '__builtin__' + + +def _check_mode_str(mode): + # check type + if not isinstance(mode, six.string_types): + return False + # check syntax + modes = set(mode) + _mode = "rwatb+U" + creating = False + if six.PY3: + _mode += "x" + creating = "x" in modes + if modes - set(_mode) or len(mode) > len(modes): + return False + # check logic + reading = "r" in modes + writing = "w" in modes + appending = "a" in modes + updating = "+" in modes + text = "t" in modes + binary = "b" in modes + if "U" in modes: + if writing or appending or creating and six.PY3: + return False + reading = True + if not six.PY3: + binary = True + if text and binary: + return False + total = reading + writing + appending + (creating if six.PY3 else 0) + if total > 1: + return False + if not (reading or writing or appending or creating and six.PY3): + return False + # other 2.x constraints + if not six.PY3: + if "U" in mode: + mode = mode.replace("U", "") + if "r" not in mode: + mode = "r" + mode + return mode[0] in ("r", "w", "a", "U") + return True + + +class StdlibChecker(BaseChecker): + __implements__ = (IAstroidChecker,) + name = 'stdlib' + + msgs = { + 'W1501': ('"%s" is not a valid mode for open.', + 'bad-open-mode', + 'Python supports: r, w, a[, x] modes with b, +, ' + 'and U (only with r) options. ' + 'See http://docs.python.org/2/library/functions.html#open'), + 'W1502': ('Using datetime.time in a boolean context.', + 'boolean-datetime', + 'Using datetetime.time in a boolean context can hide ' + 'subtle bugs when the time they represent matches ' + 'midnight UTC. This behaviour was fixed in Python 3.5. ' + 'See http://bugs.python.org/issue13936 for reference.', + {'maxversion': (3, 5)}), + 'W1503': ('Redundant use of %s with constant ' + 'value %r', + 'redundant-unittest-assert', + 'The first argument of assertTrue and assertFalse is' + 'a condition. If a constant is passed as parameter, that' + 'condition will be always true. In this case a warning ' + 'should be emitted.') + } + + @utils.check_messages('bad-open-mode', 'redundant-unittest-assert') + def visit_callfunc(self, node): + """Visit a CallFunc node.""" + if hasattr(node, 'func'): + infer = utils.safe_infer(node.func) + if infer: + if infer.root().name == OPEN_MODULE: + if getattr(node.func, 'name', None) in ('open', 'file'): + self._check_open_mode(node) + if infer.root().name == 'unittest.case': + self._check_redundant_assert(node, infer) + + @utils.check_messages('boolean-datetime') + def visit_unaryop(self, node): + if node.op == 'not': + self._check_datetime(node.operand) + + @utils.check_messages('boolean-datetime') + def visit_if(self, node): + self._check_datetime(node.test) + + @utils.check_messages('boolean-datetime') + def visit_ifexp(self, node): + self._check_datetime(node.test) + + @utils.check_messages('boolean-datetime') + def visit_boolop(self, node): + for value in node.values: + self._check_datetime(value) + + def _check_redundant_assert(self, node, infer): + if (isinstance(infer, astroid.BoundMethod) and + node.args and isinstance(node.args[0], astroid.Const) and + infer.name in ['assertTrue', 'assertFalse']): + self.add_message('redundant-unittest-assert', + args=(infer.name, node.args[0].value, ), + node=node) + + def _check_datetime(self, node): + """ Check that a datetime was infered. + If so, emit boolean-datetime warning. + """ + try: + infered = next(node.infer()) + except astroid.InferenceError: + return + if (isinstance(infered, Instance) and + infered.qname() == 'datetime.time'): + self.add_message('boolean-datetime', node=node) + + + def _check_open_mode(self, node): + """Check that the mode argument of an open or file call is valid.""" + try: + mode_arg = utils.get_argument_from_call(node, position=1, + keyword='mode') + except utils.NoSuchArgumentError: + return + if mode_arg: + mode_arg = utils.safe_infer(mode_arg) + if (isinstance(mode_arg, astroid.Const) + and not _check_mode_str(mode_arg.value)): + self.add_message('bad-open-mode', node=node, + args=mode_arg.value) + + +def register(linter): + """required method to auto register this checker """ + linter.register_checker(StdlibChecker(linter)) diff --git a/pylint/checkers/strings.py b/pylint/checkers/strings.py new file mode 100644 index 0000000..8892c2c --- /dev/null +++ b/pylint/checkers/strings.py @@ -0,0 +1,615 @@ +# Copyright (c) 2009-2010 Arista Networks, Inc. - James Lingard +# Copyright (c) 2004-2013 LOGILAB S.A. (Paris, FRANCE). +# Copyright 2012 Google Inc. +# +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +"""Checker for string formatting operations. +""" + +import sys +import tokenize +import string +import numbers + +import astroid + +from pylint.interfaces import ITokenChecker, IAstroidChecker, IRawChecker +from pylint.checkers import BaseChecker, BaseTokenChecker +from pylint.checkers import utils +from pylint.checkers.utils import check_messages + +import six + + +_PY3K = sys.version_info[:2] >= (3, 0) +_PY27 = sys.version_info[:2] == (2, 7) + +MSGS = { + 'E1300': ("Unsupported format character %r (%#02x) at index %d", + "bad-format-character", + "Used when a unsupported format character is used in a format\ + string."), + 'E1301': ("Format string ends in middle of conversion specifier", + "truncated-format-string", + "Used when a format string terminates before the end of a \ + conversion specifier."), + 'E1302': ("Mixing named and unnamed conversion specifiers in format string", + "mixed-format-string", + "Used when a format string contains both named (e.g. '%(foo)d') \ + and unnamed (e.g. '%d') conversion specifiers. This is also \ + used when a named conversion specifier contains * for the \ + minimum field width and/or precision."), + 'E1303': ("Expected mapping for format string, not %s", + "format-needs-mapping", + "Used when a format string that uses named conversion specifiers \ + is used with an argument that is not a mapping."), + 'W1300': ("Format string dictionary key should be a string, not %s", + "bad-format-string-key", + "Used when a format string that uses named conversion specifiers \ + is used with a dictionary whose keys are not all strings."), + 'W1301': ("Unused key %r in format string dictionary", + "unused-format-string-key", + "Used when a format string that uses named conversion specifiers \ + is used with a dictionary that conWtains keys not required by the \ + format string."), + 'E1304': ("Missing key %r in format string dictionary", + "missing-format-string-key", + "Used when a format string that uses named conversion specifiers \ + is used with a dictionary that doesn't contain all the keys \ + required by the format string."), + 'E1305': ("Too many arguments for format string", + "too-many-format-args", + "Used when a format string that uses unnamed conversion \ + specifiers is given too many arguments."), + 'E1306': ("Not enough arguments for format string", + "too-few-format-args", + "Used when a format string that uses unnamed conversion \ + specifiers is given too few arguments"), + + 'W1302': ("Invalid format string", + "bad-format-string", + "Used when a PEP 3101 format string is invalid.", + {'minversion': (2, 7)}), + 'W1303': ("Missing keyword argument %r for format string", + "missing-format-argument-key", + "Used when a PEP 3101 format string that uses named fields " + "doesn't receive one or more required keywords.", + {'minversion': (2, 7)}), + 'W1304': ("Unused format argument %r", + "unused-format-string-argument", + "Used when a PEP 3101 format string that uses named " + "fields is used with an argument that " + "is not required by the format string.", + {'minversion': (2, 7)}), + 'W1305': ("Format string contains both automatic field numbering " + "and manual field specification", + "format-combined-specification", + "Usen when a PEP 3101 format string contains both automatic " + "field numbering (e.g. '{}') and manual field " + "specification (e.g. '{0}').", + {'minversion': (2, 7)}), + 'W1306': ("Missing format attribute %r in format specifier %r", + "missing-format-attribute", + "Used when a PEP 3101 format string uses an " + "attribute specifier ({0.length}), but the argument " + "passed for formatting doesn't have that attribute.", + {'minversion': (2, 7)}), + 'W1307': ("Using invalid lookup key %r in format specifier %r", + "invalid-format-index", + "Used when a PEP 3101 format string uses a lookup specifier " + "({a[1]}), but the argument passed for formatting " + "doesn't contain or doesn't have that key as an attribute.", + {'minversion': (2, 7)}) + } + +OTHER_NODES = (astroid.Const, astroid.List, astroid.Backquote, + astroid.Lambda, astroid.Function, + astroid.ListComp, astroid.SetComp, astroid.GenExpr) + +if _PY3K: + import _string + + def split_format_field_names(format_string): + return _string.formatter_field_name_split(format_string) +else: + def _field_iterator_convertor(iterator): + for is_attr, key in iterator: + if isinstance(key, numbers.Number): + yield is_attr, int(key) + else: + yield is_attr, key + + def split_format_field_names(format_string): + keyname, fielditerator = format_string._formatter_field_name_split() + # it will return longs, instead of ints, which will complicate + # the output + return keyname, _field_iterator_convertor(fielditerator) + + +def collect_string_fields(format_string): + """ Given a format string, return an iterator + of all the valid format fields. It handles nested fields + as well. + """ + + formatter = string.Formatter() + try: + parseiterator = formatter.parse(format_string) + for result in parseiterator: + if all(item is None for item in result[1:]): + # not a replacement format + continue + name = result[1] + nested = result[2] + yield name + if nested: + for field in collect_string_fields(nested): + yield field + except ValueError: + # probably the format string is invalid + # should we check the argument of the ValueError? + raise utils.IncompleteFormatString(format_string) + +def parse_format_method_string(format_string): + """ + Parses a PEP 3101 format string, returning a tuple of + (keys, num_args, manual_pos_arg), + where keys is the set of mapping keys in the format string, num_args + is the number of arguments required by the format string and + manual_pos_arg is the number of arguments passed with the position. + """ + keys = [] + num_args = 0 + manual_pos_arg = set() + for name in collect_string_fields(format_string): + if name and str(name).isdigit(): + manual_pos_arg.add(str(name)) + elif name: + keyname, fielditerator = split_format_field_names(name) + if isinstance(keyname, numbers.Number): + # In Python 2 it will return long which will lead + # to different output between 2 and 3 + manual_pos_arg.add(str(keyname)) + keyname = int(keyname) + keys.append((keyname, list(fielditerator))) + else: + num_args += 1 + return keys, num_args, len(manual_pos_arg) + +def get_args(callfunc): + """ Get the arguments from the given `CallFunc` node. + Return a tuple, where the first element is the + number of positional arguments and the second element + is the keyword arguments in a dict. + """ + positional = 0 + named = {} + + for arg in callfunc.args: + if isinstance(arg, astroid.Keyword): + named[arg.arg] = utils.safe_infer(arg.value) + else: + positional += 1 + return positional, named + +def get_access_path(key, parts): + """ Given a list of format specifiers, returns + the final access path (e.g. a.b.c[0][1]). + """ + path = [] + for is_attribute, specifier in parts: + if is_attribute: + path.append(".{}".format(specifier)) + else: + path.append("[{!r}]".format(specifier)) + return str(key) + "".join(path) + + +class StringFormatChecker(BaseChecker): + """Checks string formatting operations to ensure that the format string + is valid and the arguments match the format string. + """ + + __implements__ = (IAstroidChecker,) + name = 'string' + msgs = MSGS + + @check_messages(*(MSGS.keys())) + def visit_binop(self, node): + if node.op != '%': + return + left = node.left + args = node.right + + if not (isinstance(left, astroid.Const) + and isinstance(left.value, six.string_types)): + return + format_string = left.value + try: + required_keys, required_num_args = \ + utils.parse_format_string(format_string) + except utils.UnsupportedFormatCharacter as e: + c = format_string[e.index] + self.add_message('bad-format-character', + node=node, args=(c, ord(c), e.index)) + return + except utils.IncompleteFormatString: + self.add_message('truncated-format-string', node=node) + return + if required_keys and required_num_args: + # The format string uses both named and unnamed format + # specifiers. + self.add_message('mixed-format-string', node=node) + elif required_keys: + # The format string uses only named format specifiers. + # Check that the RHS of the % operator is a mapping object + # that contains precisely the set of keys required by the + # format string. + if isinstance(args, astroid.Dict): + keys = set() + unknown_keys = False + for k, _ in args.items: + if isinstance(k, astroid.Const): + key = k.value + if isinstance(key, six.string_types): + keys.add(key) + else: + self.add_message('bad-format-string-key', + node=node, args=key) + else: + # One of the keys was something other than a + # constant. Since we can't tell what it is, + # supress checks for missing keys in the + # dictionary. + unknown_keys = True + if not unknown_keys: + for key in required_keys: + if key not in keys: + self.add_message('missing-format-string-key', + node=node, args=key) + for key in keys: + if key not in required_keys: + self.add_message('unused-format-string-key', + node=node, args=key) + elif isinstance(args, OTHER_NODES + (astroid.Tuple,)): + type_name = type(args).__name__ + self.add_message('format-needs-mapping', + node=node, args=type_name) + # else: + # The RHS of the format specifier is a name or + # expression. It may be a mapping object, so + # there's nothing we can check. + else: + # The format string uses only unnamed format specifiers. + # Check that the number of arguments passed to the RHS of + # the % operator matches the number required by the format + # string. + if isinstance(args, astroid.Tuple): + num_args = len(args.elts) + elif isinstance(args, OTHER_NODES + (astroid.Dict, astroid.DictComp)): + num_args = 1 + else: + # The RHS of the format specifier is a name or + # expression. It could be a tuple of unknown size, so + # there's nothing we can check. + num_args = None + if num_args is not None: + if num_args > required_num_args: + self.add_message('too-many-format-args', node=node) + elif num_args < required_num_args: + self.add_message('too-few-format-args', node=node) + + +class StringMethodsChecker(BaseChecker): + __implements__ = (IAstroidChecker,) + name = 'string' + msgs = { + 'E1310': ("Suspicious argument in %s.%s call", + "bad-str-strip-call", + "The argument to a str.{l,r,}strip call contains a" + " duplicate character, "), + } + + @check_messages(*(MSGS.keys())) + def visit_callfunc(self, node): + func = utils.safe_infer(node.func) + if (isinstance(func, astroid.BoundMethod) + and isinstance(func.bound, astroid.Instance) + and func.bound.name in ('str', 'unicode', 'bytes')): + if func.name in ('strip', 'lstrip', 'rstrip') and node.args: + arg = utils.safe_infer(node.args[0]) + if not isinstance(arg, astroid.Const): + return + if len(arg.value) != len(set(arg.value)): + self.add_message('bad-str-strip-call', node=node, + args=(func.bound.name, func.name)) + elif func.name == 'format': + if _PY27 or _PY3K: + self._check_new_format(node, func) + + def _check_new_format(self, node, func): + """ Check the new string formatting. """ + # TODO: skip (for now) format nodes which don't have + # an explicit string on the left side of the format operation. + # We do this because our inference engine can't properly handle + # redefinitions of the original string. + # For more details, see issue 287. + # + # Note that there may not be any left side at all, if the format method + # has been assigned to another variable. See issue 351. For example: + # + # fmt = 'some string {}'.format + # fmt('arg') + if (isinstance(node.func, astroid.Getattr) + and not isinstance(node.func.expr, astroid.Const)): + return + try: + strnode = next(func.bound.infer()) + except astroid.InferenceError: + return + if not isinstance(strnode, astroid.Const): + return + if node.starargs or node.kwargs: + # TODO: Don't complicate the logic, skip these for now. + return + try: + positional, named = get_args(node) + except astroid.InferenceError: + return + try: + fields, num_args, manual_pos = parse_format_method_string(strnode.value) + except utils.IncompleteFormatString: + self.add_message('bad-format-string', node=node) + return + + named_fields = set(field[0] for field in fields + if isinstance(field[0], six.string_types)) + if num_args and manual_pos: + self.add_message('format-combined-specification', + node=node) + return + + check_args = False + # Consider "{[0]} {[1]}" as num_args. + num_args += sum(1 for field in named_fields + if field == '') + if named_fields: + for field in named_fields: + if field not in named and field: + self.add_message('missing-format-argument-key', + node=node, + args=(field, )) + for field in named: + if field not in named_fields: + self.add_message('unused-format-string-argument', + node=node, + args=(field, )) + # num_args can be 0 if manual_pos is not. + num_args = num_args or manual_pos + if positional or num_args: + empty = any(True for field in named_fields + if field == '') + if named or empty: + # Verify the required number of positional arguments + # only if the .format got at least one keyword argument. + # This means that the format strings accepts both + # positional and named fields and we should warn + # when one of the them is missing or is extra. + check_args = True + else: + check_args = True + if check_args: + # num_args can be 0 if manual_pos is not. + num_args = num_args or manual_pos + if positional > num_args: + self.add_message('too-many-format-args', node=node) + elif positional < num_args: + self.add_message('too-few-format-args', node=node) + + self._check_new_format_specifiers(node, fields, named) + + def _check_new_format_specifiers(self, node, fields, named): + """ + Check attribute and index access in the format + string ("{0.a}" and "{0[a]}"). + """ + for key, specifiers in fields: + # Obtain the argument. If it can't be obtained + # or infered, skip this check. + if key == '': + # {[0]} will have an unnamed argument, defaulting + # to 0. It will not be present in `named`, so use the value + # 0 for it. + key = 0 + if isinstance(key, numbers.Number): + try: + argname = utils.get_argument_from_call(node, key) + except utils.NoSuchArgumentError: + continue + else: + if key not in named: + continue + argname = named[key] + if argname in (astroid.YES, None): + continue + try: + argument = next(argname.infer()) + except astroid.InferenceError: + continue + if not specifiers or argument is astroid.YES: + # No need to check this key if it doesn't + # use attribute / item access + continue + if argument.parent and isinstance(argument.parent, astroid.Arguments): + # Ignore any object coming from an argument, + # because we can't infer its value properly. + continue + previous = argument + parsed = [] + for is_attribute, specifier in specifiers: + if previous is astroid.YES: + break + parsed.append((is_attribute, specifier)) + if is_attribute: + try: + previous = previous.getattr(specifier)[0] + except astroid.NotFoundError: + if (hasattr(previous, 'has_dynamic_getattr') and + previous.has_dynamic_getattr()): + # Don't warn if the object has a custom __getattr__ + break + path = get_access_path(key, parsed) + self.add_message('missing-format-attribute', + args=(specifier, path), + node=node) + break + else: + warn_error = False + if hasattr(previous, 'getitem'): + try: + previous = previous.getitem(specifier) + except (IndexError, TypeError): + warn_error = True + else: + try: + # Lookup __getitem__ in the current node, + # but skip further checks, because we can't + # retrieve the looked object + previous.getattr('__getitem__') + break + except astroid.NotFoundError: + warn_error = True + if warn_error: + path = get_access_path(key, parsed) + self.add_message('invalid-format-index', + args=(specifier, path), + node=node) + break + + try: + previous = next(previous.infer()) + except astroid.InferenceError: + # can't check further if we can't infer it + break + + + +class StringConstantChecker(BaseTokenChecker): + """Check string literals""" + __implements__ = (ITokenChecker, IRawChecker) + name = 'string_constant' + msgs = { + 'W1401': ('Anomalous backslash in string: \'%s\'. ' + 'String constant might be missing an r prefix.', + 'anomalous-backslash-in-string', + 'Used when a backslash is in a literal string but not as an ' + 'escape.'), + 'W1402': ('Anomalous Unicode escape in byte string: \'%s\'. ' + 'String constant might be missing an r or u prefix.', + 'anomalous-unicode-escape-in-string', + 'Used when an escape like \\u is encountered in a byte ' + 'string where it has no effect.'), + } + + # Characters that have a special meaning after a backslash in either + # Unicode or byte strings. + ESCAPE_CHARACTERS = 'abfnrtvx\n\r\t\\\'\"01234567' + + # TODO(mbp): Octal characters are quite an edge case today; people may + # prefer a separate warning where they occur. \0 should be allowed. + + # Characters that have a special meaning after a backslash but only in + # Unicode strings. + UNICODE_ESCAPE_CHARACTERS = 'uUN' + + def process_module(self, module): + self._unicode_literals = 'unicode_literals' in module.future_imports + + def process_tokens(self, tokens): + for (tok_type, token, (start_row, _), _, _) in tokens: + if tok_type == tokenize.STRING: + # 'token' is the whole un-parsed token; we can look at the start + # of it to see whether it's a raw or unicode string etc. + self.process_string_token(token, start_row) + + def process_string_token(self, token, start_row): + for i, c in enumerate(token): + if c in '\'\"': + quote_char = c + break + # pylint: disable=undefined-loop-variable + prefix = token[:i].lower() # markers like u, b, r. + after_prefix = token[i:] + if after_prefix[:3] == after_prefix[-3:] == 3 * quote_char: + string_body = after_prefix[3:-3] + else: + string_body = after_prefix[1:-1] # Chop off quotes + # No special checks on raw strings at the moment. + if 'r' not in prefix: + self.process_non_raw_string_token(prefix, string_body, start_row) + + def process_non_raw_string_token(self, prefix, string_body, start_row): + """check for bad escapes in a non-raw string. + + prefix: lowercase string of eg 'ur' string prefix markers. + string_body: the un-parsed body of the string, not including the quote + marks. + start_row: integer line number in the source. + """ + # Walk through the string; if we see a backslash then escape the next + # character, and skip over it. If we see a non-escaped character, + # alert, and continue. + # + # Accept a backslash when it escapes a backslash, or a quote, or + # end-of-line, or one of the letters that introduce a special escape + # sequence <http://docs.python.org/reference/lexical_analysis.html> + # + # TODO(mbp): Maybe give a separate warning about the rarely-used + # \a \b \v \f? + # + # TODO(mbp): We could give the column of the problem character, but + # add_message doesn't seem to have a way to pass it through at present. + i = 0 + while True: + i = string_body.find('\\', i) + if i == -1: + break + # There must be a next character; having a backslash at the end + # of the string would be a SyntaxError. + next_char = string_body[i+1] + match = string_body[i:i+2] + if next_char in self.UNICODE_ESCAPE_CHARACTERS: + if 'u' in prefix: + pass + elif (_PY3K or self._unicode_literals) and 'b' not in prefix: + pass # unicode by default + else: + self.add_message('anomalous-unicode-escape-in-string', + line=start_row, args=(match, )) + elif next_char not in self.ESCAPE_CHARACTERS: + self.add_message('anomalous-backslash-in-string', + line=start_row, args=(match, )) + # Whether it was a valid escape or not, backslash followed by + # another character can always be consumed whole: the second + # character can never be the start of a new backslash escape. + i += 2 + + + +def register(linter): + """required method to auto register this checker """ + linter.register_checker(StringFormatChecker(linter)) + linter.register_checker(StringMethodsChecker(linter)) + linter.register_checker(StringConstantChecker(linter)) diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py new file mode 100644 index 0000000..9f074ae --- /dev/null +++ b/pylint/checkers/typecheck.py @@ -0,0 +1,627 @@ +# Copyright (c) 2006-2013 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +"""try to find more bugs in the code using astroid inference capabilities +""" + +import re +import shlex + +import astroid +from astroid import InferenceError, NotFoundError, YES, Instance +from astroid.bases import BUILTINS + +from pylint.interfaces import IAstroidChecker, INFERENCE, INFERENCE_FAILURE +from pylint.checkers import BaseChecker +from pylint.checkers.utils import ( + safe_infer, is_super, + check_messages, decorated_with_property) + +MSGS = { + 'E1101': ('%s %r has no %r member', + 'no-member', + 'Used when a variable is accessed for an unexistent member.', + {'old_names': [('E1103', 'maybe-no-member')]}), + 'E1102': ('%s is not callable', + 'not-callable', + 'Used when an object being called has been inferred to a non \ + callable object'), + 'E1111': ('Assigning to function call which doesn\'t return', + 'assignment-from-no-return', + 'Used when an assignment is done on a function call but the \ + inferred function doesn\'t return anything.'), + 'W1111': ('Assigning to function call which only returns None', + 'assignment-from-none', + 'Used when an assignment is done on a function call but the \ + inferred function returns nothing but None.'), + + 'E1120': ('No value for argument %s in %s call', + 'no-value-for-parameter', + 'Used when a function call passes too few arguments.'), + 'E1121': ('Too many positional arguments for %s call', + 'too-many-function-args', + 'Used when a function call passes too many positional \ + arguments.'), + 'E1123': ('Unexpected keyword argument %r in %s call', + 'unexpected-keyword-arg', + 'Used when a function call passes a keyword argument that \ + doesn\'t correspond to one of the function\'s parameter names.'), + 'E1124': ('Argument %r passed by position and keyword in %s call', + 'redundant-keyword-arg', + 'Used when a function call would result in assigning multiple \ + values to a function parameter, one value from a positional \ + argument and one from a keyword argument.'), + 'E1125': ('Missing mandatory keyword argument %r in %s call', + 'missing-kwoa', + ('Used when a function call does not pass a mandatory' + ' keyword-only argument.'), + {'minversion': (3, 0)}), + 'E1126': ('Sequence index is not an int, slice, or instance with __index__', + 'invalid-sequence-index', + 'Used when a sequence type is indexed with an invalid type. ' + 'Valid types are ints, slices, and objects with an __index__ ' + 'method.'), + 'E1127': ('Slice index is not an int, None, or instance with __index__', + 'invalid-slice-index', + 'Used when a slice index is not an integer, None, or an object \ + with an __index__ method.'), + } + +# builtin sequence types in Python 2 and 3. +SEQUENCE_TYPES = set(['str', 'unicode', 'list', 'tuple', 'bytearray', + 'xrange', 'range', 'bytes', 'memoryview']) + +def _determine_callable(callable_obj): + # Ordering is important, since BoundMethod is a subclass of UnboundMethod, + # and Function inherits Lambda. + if isinstance(callable_obj, astroid.BoundMethod): + # Bound methods have an extra implicit 'self' argument. + return callable_obj, 1, callable_obj.type + elif isinstance(callable_obj, astroid.UnboundMethod): + return callable_obj, 0, 'unbound method' + elif isinstance(callable_obj, astroid.Function): + return callable_obj, 0, callable_obj.type + elif isinstance(callable_obj, astroid.Lambda): + return callable_obj, 0, 'lambda' + elif isinstance(callable_obj, astroid.Class): + # Class instantiation, lookup __new__ instead. + # If we only find object.__new__, we can safely check __init__ + # instead. + try: + # Use the last definition of __new__. + new = callable_obj.local_attr('__new__')[-1] + except astroid.NotFoundError: + new = None + + if not new or new.parent.scope().name == 'object': + try: + # Use the last definition of __init__. + callable_obj = callable_obj.local_attr('__init__')[-1] + except astroid.NotFoundError: + # do nothing, covered by no-init. + raise ValueError + else: + callable_obj = new + + if not isinstance(callable_obj, astroid.Function): + raise ValueError + # both have an extra implicit 'cls'/'self' argument. + return callable_obj, 1, 'constructor' + else: + raise ValueError + +class TypeChecker(BaseChecker): + """try to find bugs in the code using type inference + """ + + __implements__ = (IAstroidChecker,) + + # 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 whether missing members accessed in mixin \ +class should be ignored. A mixin class is detected if its name ends with \ +"mixin" (case insensitive).'} + ), + ('ignored-modules', + {'default': (), + 'type': 'csv', + 'metavar': '<module names>', + 'help': 'List of module names for which member attributes \ +should not be checked (useful for modules/projects where namespaces are \ +manipulated during runtime and thus existing member attributes cannot be \ +deduced by static analysis'}, + ), + ('ignored-classes', + {'default' : ('SQLObject',), + 'type' : 'csv', + 'metavar' : '<members names>', + 'help' : 'List of classes names for which member attributes \ +should not be checked (useful for classes with attributes dynamically set).'} + ), + + ('zope', + {'default' : False, 'type' : 'yn', 'metavar': '<y_or_n>', + 'help' : 'When zope mode is activated, add a predefined set \ +of Zope acquired attributes to generated-members.'} + ), + ('generated-members', + {'default' : ('REQUEST', 'acl_users', 'aq_parent'), + 'type' : 'string', + 'metavar' : '<members names>', + 'help' : 'List of members which are set dynamically and \ +missed by pylint inference system, and so shouldn\'t trigger E0201 when \ +accessed. Python regular expressions are accepted.'} + ), + ) + + def open(self): + # do this in open since config not fully initialized in __init__ + self.generated_members = list(self.config.generated_members) + if self.config.zope: + self.generated_members.extend(('REQUEST', 'acl_users', 'aq_parent')) + + def visit_assattr(self, node): + if isinstance(node.ass_type(), astroid.AugAssign): + self.visit_getattr(node) + + def visit_delattr(self, node): + self.visit_getattr(node) + + @check_messages('no-member') + def visit_getattr(self, node): + """check that the accessed attribute exists + + to avoid to much false positives for now, we'll consider the code as + correct if a single of the inferred nodes has the accessed attribute. + + function/method, super call and metaclasses are ignored + """ + # generated_members may containt regular expressions + # (surrounded by quote `"` and followed by a comma `,`) + # REQUEST,aq_parent,"[a-zA-Z]+_set{1,2}"' => + # ('REQUEST', 'aq_parent', '[a-zA-Z]+_set{1,2}') + if isinstance(self.config.generated_members, str): + gen = shlex.shlex(self.config.generated_members) + gen.whitespace += ',' + gen.wordchars += '[]-+' + self.config.generated_members = tuple(tok.strip('"') for tok in gen) + for pattern in self.config.generated_members: + # attribute is marked as generated, stop here + if re.match(pattern, node.attrname): + return + try: + infered = list(node.expr.infer()) + except InferenceError: + return + # list of (node, nodename) which are missing the attribute + missingattr = set() + ignoremim = self.config.ignore_mixin_members + inference_failure = False + for owner in infered: + # skip yes object + if owner is YES: + inference_failure = True + continue + # skip None anyway + if isinstance(owner, astroid.Const) and owner.value is None: + continue + # XXX "super" / metaclass call + if is_super(owner) or getattr(owner, 'type', None) == 'metaclass': + continue + name = getattr(owner, 'name', 'None') + if name in self.config.ignored_classes: + continue + if ignoremim and name[-5:].lower() == 'mixin': + continue + try: + if not [n for n in owner.getattr(node.attrname) + if not isinstance(n.statement(), astroid.AugAssign)]: + missingattr.add((owner, name)) + continue + except AttributeError: + # XXX method / function + continue + except NotFoundError: + if isinstance(owner, astroid.Function) and owner.decorators: + continue + if isinstance(owner, Instance) and owner.has_dynamic_getattr(): + continue + # explicit skipping of module member access + if owner.root().name in self.config.ignored_modules: + continue + if isinstance(owner, astroid.Class): + # Look up in the metaclass only if the owner is itself + # a class. + # TODO: getattr doesn't return by default members + # from the metaclass, because handling various cases + # of methods accessible from the metaclass itself + # and/or subclasses only is too complicated for little to + # no benefit. + metaclass = owner.metaclass() + try: + if metaclass and metaclass.getattr(node.attrname): + continue + except NotFoundError: + pass + missingattr.add((owner, name)) + continue + # stop on the first found + break + else: + # we have not found any node with the attributes, display the + # message for infered nodes + done = set() + for owner, name in missingattr: + if isinstance(owner, Instance): + actual = owner._proxied + else: + actual = owner + if actual in done: + continue + done.add(actual) + confidence = INFERENCE if not inference_failure else INFERENCE_FAILURE + self.add_message('no-member', node=node, + args=(owner.display_type(), name, + node.attrname), + confidence=confidence) + + @check_messages('assignment-from-no-return', 'assignment-from-none') + def visit_assign(self, node): + """check that if assigning to a function call, the function is + possibly returning something valuable + """ + if not isinstance(node.value, astroid.CallFunc): + return + function_node = safe_infer(node.value.func) + # skip class, generator and incomplete function definition + if not (isinstance(function_node, astroid.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(astroid.Return, + skip_klass=astroid.Function)) + if len(returns) == 0: + self.add_message('assignment-from-no-return', node=node) + else: + for rnode in returns: + if not (isinstance(rnode.value, astroid.Const) + and rnode.value.value is None + or rnode.value is None): + break + else: + self.add_message('assignment-from-none', node=node) + + def _check_uninferable_callfunc(self, node): + """ + Check that the given uninferable CallFunc node does not + call an actual function. + """ + if not isinstance(node.func, astroid.Getattr): + return + + # Look for properties. First, obtain + # the lhs of the Getattr node and search the attribute + # there. If that attribute is a property or a subclass of properties, + # then most likely it's not callable. + + # TODO: since astroid doesn't understand descriptors very well + # we will not handle them here, right now. + + expr = node.func.expr + klass = safe_infer(expr) + if (klass is None or klass is astroid.YES or + not isinstance(klass, astroid.Instance)): + return + + try: + attrs = klass._proxied.getattr(node.func.attrname) + except astroid.NotFoundError: + return + + for attr in attrs: + if attr is astroid.YES: + continue + if not isinstance(attr, astroid.Function): + continue + + # Decorated, see if it is decorated with a property. + # Also, check the returns and see if they are callable. + if decorated_with_property(attr): + if all(return_node.callable() + for return_node in attr.infer_call_result(node)): + continue + else: + self.add_message('not-callable', node=node, + args=node.func.as_string()) + break + + @check_messages(*(list(MSGS.keys()))) + def visit_callfunc(self, node): + """check that called functions/methods are inferred to callable objects, + and that the arguments passed to the function match the parameters in + the inferred function's definition + """ + # Build the set of keyword arguments, checking for duplicate keywords, + # and count the positional arguments. + keyword_args = set() + num_positional_args = 0 + for arg in node.args: + if isinstance(arg, astroid.Keyword): + keyword_args.add(arg.arg) + else: + num_positional_args += 1 + + called = safe_infer(node.func) + # only function, generator and object defining __call__ are allowed + if called is not None and not called.callable(): + self.add_message('not-callable', node=node, + args=node.func.as_string()) + + self._check_uninferable_callfunc(node) + + try: + called, implicit_args, callable_name = _determine_callable(called) + except ValueError: + # Any error occurred during determining the function type, most of + # those errors are handled by different warnings. + return + num_positional_args += implicit_args + if called.args.args is None: + # Built-in functions have no argument information. + return + + if len(called.argnames()) != len(set(called.argnames())): + # Duplicate parameter name (see E9801). We can't really make sense + # of the function call in this case, so just return. + return + + # Analyze the list of formal parameters. + num_mandatory_parameters = len(called.args.args) - len(called.args.defaults) + parameters = [] + parameter_name_to_index = {} + for i, arg in enumerate(called.args.args): + if isinstance(arg, astroid.Tuple): + name = None + # Don't store any parameter names within the tuple, since those + # are not assignable from keyword arguments. + else: + if isinstance(arg, astroid.Keyword): + name = arg.arg + else: + assert isinstance(arg, astroid.AssName) + # This occurs with: + # def f( (a), (b) ): pass + name = arg.name + parameter_name_to_index[name] = i + if i >= num_mandatory_parameters: + defval = called.args.defaults[i - num_mandatory_parameters] + else: + defval = None + parameters.append([(name, defval), False]) + + kwparams = {} + for i, arg in enumerate(called.args.kwonlyargs): + if isinstance(arg, astroid.Keyword): + name = arg.arg + else: + assert isinstance(arg, astroid.AssName) + name = arg.name + kwparams[name] = [called.args.kw_defaults[i], False] + + # Match the supplied arguments against the function parameters. + + # 1. Match the positional arguments. + for i in range(num_positional_args): + if i < len(parameters): + parameters[i][1] = True + elif called.args.vararg is not None: + # The remaining positional arguments get assigned to the *args + # parameter. + break + else: + # Too many positional arguments. + self.add_message('too-many-function-args', + node=node, args=(callable_name,)) + break + + # 2. Match the keyword arguments. + for keyword in keyword_args: + if keyword in parameter_name_to_index: + i = parameter_name_to_index[keyword] + if parameters[i][1]: + # Duplicate definition of function parameter. + self.add_message('redundant-keyword-arg', + node=node, args=(keyword, callable_name)) + else: + parameters[i][1] = True + elif keyword in kwparams: + if kwparams[keyword][1]: # XXX is that even possible? + # Duplicate definition of function parameter. + self.add_message('redundant-keyword-arg', node=node, + args=(keyword, callable_name)) + else: + kwparams[keyword][1] = True + elif called.args.kwarg is not None: + # The keyword argument gets assigned to the **kwargs parameter. + pass + else: + # Unexpected keyword argument. + self.add_message('unexpected-keyword-arg', node=node, + args=(keyword, callable_name)) + + # 3. Match the *args, if any. Note that Python actually processes + # *args _before_ any keyword arguments, but we wait until after + # looking at the keyword arguments so as to make a more conservative + # guess at how many values are in the *args sequence. + if node.starargs is not None: + for i in range(num_positional_args, len(parameters)): + [(name, defval), assigned] = parameters[i] + # Assume that *args provides just enough values for all + # non-default parameters after the last parameter assigned by + # the positional arguments but before the first parameter + # assigned by the keyword arguments. This is the best we can + # get without generating any false positives. + if (defval is not None) or assigned: + break + parameters[i][1] = True + + # 4. Match the **kwargs, if any. + if node.kwargs is not None: + for i, [(name, defval), assigned] in enumerate(parameters): + # Assume that *kwargs provides values for all remaining + # unassigned named parameters. + if name is not None: + parameters[i][1] = True + else: + # **kwargs can't assign to tuples. + pass + + # Check that any parameters without a default have been assigned + # values. + for [(name, defval), assigned] in parameters: + if (defval is None) and not assigned: + if name is None: + display_name = '<tuple>' + else: + display_name = repr(name) + self.add_message('no-value-for-parameter', node=node, + args=(display_name, callable_name)) + + for name in kwparams: + defval, assigned = kwparams[name] + if defval is None and not assigned: + self.add_message('missing-kwoa', node=node, + args=(name, callable_name)) + + @check_messages('invalid-sequence-index') + def visit_extslice(self, node): + # Check extended slice objects as if they were used as a sequence + # index to check if the object being sliced can support them + return self.visit_index(node) + + @check_messages('invalid-sequence-index') + def visit_index(self, node): + if not node.parent or not hasattr(node.parent, "value"): + return + + # Look for index operations where the parent is a sequence type. + # If the types can be determined, only allow indices to be int, + # slice or instances with __index__. + + parent_type = safe_infer(node.parent.value) + if not isinstance(parent_type, (astroid.Class, astroid.Instance)): + return + + # Determine what method on the parent this index will use + # The parent of this node will be a Subscript, and the parent of that + # node determines if the Subscript is a get, set, or delete operation. + operation = node.parent.parent + if isinstance(operation, astroid.Assign): + methodname = '__setitem__' + elif isinstance(operation, astroid.Delete): + methodname = '__delitem__' + else: + methodname = '__getitem__' + + # Check if this instance's __getitem__, __setitem__, or __delitem__, as + # appropriate to the statement, is implemented in a builtin sequence + # type. This way we catch subclasses of sequence types but skip classes + # that override __getitem__ and which may allow non-integer indices. + try: + methods = parent_type.getattr(methodname) + if methods is astroid.YES: + return + itemmethod = methods[0] + except (astroid.NotFoundError, IndexError): + return + + if not isinstance(itemmethod, astroid.Function): + return + if itemmethod.root().name != BUILTINS: + return + if not itemmethod.parent: + return + if itemmethod.parent.name not in SEQUENCE_TYPES: + return + + # For ExtSlice objects coming from visit_extslice, no further + # inference is necessary, since if we got this far the ExtSlice + # is an error. + if isinstance(node, astroid.ExtSlice): + index_type = node + else: + index_type = safe_infer(node) + if index_type is None or index_type is astroid.YES: + return + + # Constants must be of type int + if isinstance(index_type, astroid.Const): + if isinstance(index_type.value, int): + return + # Instance values must be int, slice, or have an __index__ method + elif isinstance(index_type, astroid.Instance): + if index_type.pytype() in (BUILTINS + '.int', BUILTINS + '.slice'): + return + try: + index_type.getattr('__index__') + return + except astroid.NotFoundError: + pass + + # Anything else is an error + self.add_message('invalid-sequence-index', node=node) + + @check_messages('invalid-slice-index') + def visit_slice(self, node): + # Check the type of each part of the slice + for index in (node.lower, node.upper, node.step): + if index is None: + continue + + index_type = safe_infer(index) + if index_type is None or index_type is astroid.YES: + continue + + # Constants must of type int or None + if isinstance(index_type, astroid.Const): + if isinstance(index_type.value, (int, type(None))): + continue + # Instance values must be of type int, None or an object + # with __index__ + elif isinstance(index_type, astroid.Instance): + if index_type.pytype() in (BUILTINS + '.int', + BUILTINS + '.NoneType'): + continue + + try: + index_type.getattr('__index__') + return + except astroid.NotFoundError: + pass + + # Anything else is an error + self.add_message('invalid-slice-index', node=node) + +def register(linter): + """required method to auto register this checker """ + linter.register_checker(TypeChecker(linter)) diff --git a/pylint/checkers/utils.py b/pylint/checkers/utils.py new file mode 100644 index 0000000..2cb01d5 --- /dev/null +++ b/pylint/checkers/utils.py @@ -0,0 +1,564 @@ +# pylint: disable=W0611 +# +# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +"""some functions that may be useful for various checkers +""" + +import re +import sys +import string + +import astroid +from astroid import scoped_nodes +from logilab.common.compat import builtins + +BUILTINS_NAME = builtins.__name__ +COMP_NODE_TYPES = astroid.ListComp, astroid.SetComp, astroid.DictComp, astroid.GenExpr +PY3K = sys.version_info[0] == 3 + +if not PY3K: + EXCEPTIONS_MODULE = "exceptions" +else: + EXCEPTIONS_MODULE = "builtins" +ABC_METHODS = set(('abc.abstractproperty', 'abc.abstractmethod', + 'abc.abstractclassmethod', 'abc.abstractstaticmethod')) + + +class NoSuchArgumentError(Exception): + pass + +def is_inside_except(node): + """Returns true if node is inside the name of an except handler.""" + current = node + while current and not isinstance(current.parent, astroid.ExceptHandler): + current = current.parent + + return current and current is current.parent.name + + +def get_all_elements(node): + """Recursively returns all atoms in nested lists and tuples.""" + if isinstance(node, (astroid.Tuple, astroid.List)): + for child in node.elts: + for e in get_all_elements(child): + yield e + else: + yield node + + +def clobber_in_except(node): + """Checks if an assignment node in an except handler clobbers an existing + variable. + + Returns (True, args for W0623) if assignment clobbers an existing variable, + (False, None) otherwise. + """ + if isinstance(node, astroid.AssAttr): + return (True, (node.attrname, 'object %r' % (node.expr.as_string(),))) + elif isinstance(node, astroid.AssName): + name = node.name + if is_builtin(name): + return (True, (name, 'builtins')) + else: + stmts = node.lookup(name)[1] + if (stmts and not isinstance(stmts[0].ass_type(), + (astroid.Assign, astroid.AugAssign, + astroid.ExceptHandler))): + return (True, (name, 'outer scope (line %s)' % stmts[0].fromlineno)) + return (False, None) + + +def safe_infer(node): + """return the inferred value for the given node. + Return None if inference failed or if there is some ambiguity (more than + one node has been inferred) + """ + try: + inferit = node.infer() + value = next(inferit) + except astroid.InferenceError: + return + try: + next(inferit) + return # None if there is ambiguity on the inferred node + except astroid.InferenceError: + return # there is some kind of ambiguity + except StopIteration: + return value + +def is_super(node): + """return True if the node is referencing the "super" builtin function + """ + if getattr(node, 'name', None) == 'super' and \ + node.root().name == BUILTINS_NAME: + return True + return False + +def is_error(node): + """return true if the function does nothing but raising an exception""" + for child_node in node.get_children(): + if isinstance(child_node, astroid.Raise): + return True + return False + +def is_raising(body): + """return true if the given statement node raise an exception""" + for node in body: + if isinstance(node, astroid.Raise): + return True + return False + +def is_empty(body): + """return true if the given node does nothing but 'pass'""" + return len(body) == 1 and isinstance(body[0], astroid.Pass) + +builtins = builtins.__dict__.copy() +SPECIAL_BUILTINS = ('__builtins__',) # '__path__', '__file__') + +def is_builtin_object(node): + """Returns True if the given node is an object from the __builtin__ module.""" + return node and node.root().name == BUILTINS_NAME + +def is_builtin(name): # was is_native_builtin + """return true if <name> could be considered as a builtin defined by python + """ + if name in builtins: + return True + if name in SPECIAL_BUILTINS: + return True + return False + +def is_defined_before(var_node): + """return True if the variable node is defined by a parent node (list, + set, dict, or generator comprehension, lambda) or in a previous sibling + node on 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(astroid.AssName): + if ass_node.name == varname: + return True + elif isinstance(_node, astroid.For): + for ass_node in _node.target.nodes_of_class(astroid.AssName): + if ass_node.name == varname: + return True + elif isinstance(_node, astroid.With): + for expr, ids in _node.items: + if expr.parent_of(var_node): + break + if (ids and + isinstance(ids, astroid.AssName) and + ids.name == varname): + return True + elif isinstance(_node, (astroid.Lambda, astroid.Function)): + if _node.args.is_argument(varname): + return True + if getattr(_node, 'name', None) == varname: + return True + break + elif isinstance(_node, astroid.ExceptHandler): + if isinstance(_node.name, astroid.AssName): + ass_node = _node.name + if ass_node.name == varname: + return True + _node = _node.parent + # possibly multiple statements on the same line using semi colon separator + stmt = var_node.statement() + _node = stmt.previous_sibling() + lineno = stmt.fromlineno + while _node and _node.fromlineno == lineno: + for ass_node in _node.nodes_of_class(astroid.AssName): + if ass_node.name == varname: + return True + for imp_node in _node.nodes_of_class((astroid.From, astroid.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 given Name node is used in function default argument's + value + """ + parent = node.scope() + if isinstance(parent, astroid.Function): + for default_node in parent.args.defaults: + for default_name_node in default_node.nodes_of_class(astroid.Name): + if default_name_node is node: + return True + return False + +def is_func_decorator(node): + """return true if the name is used in function decorator""" + parent = node.parent + while parent is not None: + if isinstance(parent, astroid.Decorators): + return True + if (parent.is_statement or + isinstance(parent, astroid.Lambda) or + isinstance(parent, (scoped_nodes.ComprehensionScope, + scoped_nodes.ListComp))): + break + parent = parent.parent + return False + +def is_ancestor_name(frame, node): + """return True if `frame` is a astroid.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(astroid.Name): + return True + return False + +def assign_parent(node): + """return the higher parent which is not an AssName, Tuple or List node + """ + while node and isinstance(node, (astroid.AssName, + astroid.Tuple, + astroid.List)): + 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], astroid.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 overridden from an ancestor""" + for ancestor in class_node.ancestors(): + if name in ancestor and isinstance(ancestor[name], astroid.Function): + return True + return False + +PYMETHODS = set(('__new__', '__init__', '__del__', '__hash__', + '__str__', '__repr__', + '__len__', '__iter__', + '__delete__', '__get__', '__set__', + '__getitem__', '__setitem__', '__delitem__', '__contains__', + '__getattribute__', '__getattr__', '__setattr__', '__delattr__', + '__call__', + '__enter__', '__exit__', + '__cmp__', '__ge__', '__gt__', '__le__', '__lt__', '__eq__', + '__nonzero__', '__neg__', '__invert__', + '__mul__', '__imul__', '__rmul__', + '__div__', '__idiv__', '__rdiv__', + '__add__', '__iadd__', '__radd__', + '__sub__', '__isub__', '__rsub__', + '__pow__', '__ipow__', '__rpow__', + '__mod__', '__imod__', '__rmod__', + '__and__', '__iand__', '__rand__', + '__or__', '__ior__', '__ror__', + '__xor__', '__ixor__', '__rxor__', + # XXX To be continued + )) + +def check_messages(*messages): + """decorator to store messages that are handled by a checker method""" + + def store_messages(func): + func.checks_msgs = messages + return func + return store_messages + +class IncompleteFormatString(Exception): + """A format string ended in the middle of a format specifier.""" + pass + +class UnsupportedFormatCharacter(Exception): + """A format character in a format string is not one of the supported + format characters.""" + def __init__(self, index): + Exception.__init__(self, index) + self.index = index + +def parse_format_string(format_string): + """Parses a format string, returning a tuple of (keys, num_args), where keys + is the set of mapping keys in the format string, and num_args is the number + of arguments required by the format string. Raises + IncompleteFormatString or UnsupportedFormatCharacter if a + parse error occurs.""" + keys = set() + num_args = 0 + def next_char(i): + i += 1 + if i == len(format_string): + raise IncompleteFormatString + return (i, format_string[i]) + i = 0 + while i < len(format_string): + char = format_string[i] + if char == '%': + i, char = next_char(i) + # Parse the mapping key (optional). + key = None + if char == '(': + depth = 1 + i, char = next_char(i) + key_start = i + while depth != 0: + if char == '(': + depth += 1 + elif char == ')': + depth -= 1 + i, char = next_char(i) + key_end = i - 1 + key = format_string[key_start:key_end] + + # Parse the conversion flags (optional). + while char in '#0- +': + i, char = next_char(i) + # Parse the minimum field width (optional). + if char == '*': + num_args += 1 + i, char = next_char(i) + else: + while char in string.digits: + i, char = next_char(i) + # Parse the precision (optional). + if char == '.': + i, char = next_char(i) + if char == '*': + num_args += 1 + i, char = next_char(i) + else: + while char in string.digits: + i, char = next_char(i) + # Parse the length modifier (optional). + if char in 'hlL': + i, char = next_char(i) + # Parse the conversion type (mandatory). + if PY3K: + flags = 'diouxXeEfFgGcrs%a' + else: + flags = 'diouxXeEfFgGcrs%' + if char not in flags: + raise UnsupportedFormatCharacter(i) + if key: + keys.add(key) + elif char != '%': + num_args += 1 + i += 1 + return keys, num_args + + +def is_attr_protected(attrname): + """return True if attribute name is protected (start with _ and some other + details), False otherwise. + """ + return attrname[0] == '_' and not attrname == '_' and not ( + attrname.startswith('__') and attrname.endswith('__')) + +def node_frame_class(node): + """return klass node for a method node (or a staticmethod or a + classmethod), return null otherwise + """ + klass = node.frame() + + while klass is not None and not isinstance(klass, astroid.Class): + if klass.parent is None: + klass = None + else: + klass = klass.parent.frame() + + return klass + +def is_super_call(expr): + """return True if expression node is a function call and if function name + is super. Check before that you're in a method. + """ + return (isinstance(expr, astroid.CallFunc) and + isinstance(expr.func, astroid.Name) and + expr.func.name == 'super') + +def is_attr_private(attrname): + """Check that attribute name is private (at least two leading underscores, + at most one trailing underscore) + """ + regex = re.compile('^_{2,}.*[^_]+_?$') + return regex.match(attrname) + +def get_argument_from_call(callfunc_node, position=None, keyword=None): + """Returns the specified argument from a function call. + + :param callfunc_node: Node representing a function call to check. + :param int position: position of the argument. + :param str keyword: the keyword of the argument. + + :returns: The node representing the argument, None if the argument is not found. + :raises ValueError: if both position and keyword are None. + :raises NoSuchArgumentError: if no argument at the provided position or with + the provided keyword. + """ + if position is None and keyword is None: + raise ValueError('Must specify at least one of: position or keyword.') + try: + if position is not None and not isinstance(callfunc_node.args[position], astroid.Keyword): + return callfunc_node.args[position] + except IndexError as error: + raise NoSuchArgumentError(error) + if keyword: + for arg in callfunc_node.args: + if isinstance(arg, astroid.Keyword) and arg.arg == keyword: + return arg.value + raise NoSuchArgumentError + +def inherit_from_std_ex(node): + """ + Return true if the given class node is subclass of + exceptions.Exception. + """ + if node.name in ('Exception', 'BaseException') \ + and node.root().name == EXCEPTIONS_MODULE: + return True + return any(inherit_from_std_ex(parent) + for parent in node.ancestors(recurs=False)) + +def is_import_error(handler): + """ + Check if the given exception handler catches + ImportError. + + :param handler: A node, representing an ExceptHandler node. + :returns: True if the handler catches ImportError, False otherwise. + """ + names = None + if isinstance(handler.type, astroid.Tuple): + names = [name for name in handler.type.elts + if isinstance(name, astroid.Name)] + elif isinstance(handler.type, astroid.Name): + names = [handler.type] + else: + # Don't try to infer that. + return + for name in names: + try: + for infered in name.infer(): + if (isinstance(infered, astroid.Class) and + inherit_from_std_ex(infered) and + infered.name == 'ImportError'): + return True + except astroid.InferenceError: + continue + +def has_known_bases(klass): + """Returns true if all base classes of a class could be inferred.""" + try: + return klass._all_bases_known + except AttributeError: + pass + for base in klass.bases: + result = safe_infer(base) + # TODO: check for A->B->A->B pattern in class structure too? + if (not isinstance(result, astroid.Class) or + result is klass or + not has_known_bases(result)): + klass._all_bases_known = False + return False + klass._all_bases_known = True + return True + +def decorated_with_property(node): + """ Detect if the given function node is decorated with a property. """ + if not node.decorators: + return False + for decorator in node.decorators.nodes: + if not isinstance(decorator, astroid.Name): + continue + try: + for infered in decorator.infer(): + if isinstance(infered, astroid.Class): + if (infered.root().name == BUILTINS_NAME and + infered.name == 'property'): + return True + for ancestor in infered.ancestors(): + if (ancestor.name == 'property' and + ancestor.root().name == BUILTINS_NAME): + return True + except astroid.InferenceError: + pass + + +def decorated_with_abc(func): + """Determine if the `func` node is decorated with `abc` decorators.""" + if func.decorators: + for node in func.decorators.nodes: + try: + infered = next(node.infer()) + except astroid.InferenceError: + continue + if infered and infered.qname() in ABC_METHODS: + return True + + +def unimplemented_abstract_methods(node, is_abstract_cb=decorated_with_abc): + """ + Get the unimplemented abstract methods for the given *node*. + + A method can be considered abstract if the callback *is_abstract_cb* + returns a ``True`` value. The check defaults to verifying that + a method is decorated with abstract methods. + The function will work only for new-style classes. For old-style + classes, it will simply return an empty dictionary. + For the rest of them, it will return a dictionary of abstract method + names and their inferred objects. + """ + visited = {} + try: + mro = reversed(node.mro()) + except NotImplementedError: + # Old style class, it will not have a mro. + return {} + except astroid.ResolveError: + # Probably inconsistent hierarchy, don'try + # to figure this out here. + return {} + for ancestor in mro: + for obj in ancestor.values(): + infered = obj + if isinstance(obj, astroid.AssName): + infered = safe_infer(obj) + if not infered: + continue + if not isinstance(infered, astroid.Function): + if obj.name in visited: + del visited[obj.name] + if isinstance(infered, astroid.Function): + # It's critical to use the original name, + # since after inferring, an object can be something + # else than expected, as in the case of the + # following assignment. + # + # class A: + # def keys(self): pass + # __iter__ = keys + abstract = is_abstract_cb(infered) + if abstract: + visited[obj.name] = infered + elif not abstract and obj.name in visited: + del visited[obj.name] + return visited diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py new file mode 100644 index 0000000..8f6f957 --- /dev/null +++ b/pylint/checkers/variables.py @@ -0,0 +1,1069 @@ +# Copyright (c) 2003-2014 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +"""variables checkers for Python code +""" +import os +import sys +import re +from copy import copy + +import astroid +from astroid import are_exclusive, builtin_lookup +from astroid import modutils + +from pylint.interfaces import IAstroidChecker, INFERENCE, INFERENCE_FAILURE, HIGH +from pylint.utils import get_global_option +from pylint.checkers import BaseChecker +from pylint.checkers.utils import ( + PYMETHODS, is_ancestor_name, is_builtin, + is_defined_before, is_error, is_func_default, is_func_decorator, + assign_parent, check_messages, is_inside_except, clobber_in_except, + get_all_elements, has_known_bases) +import six + +SPECIAL_OBJ = re.compile("^_{2}[a-z]+_{2}$") + +PY3K = sys.version_info >= (3, 0) + +def in_for_else_branch(parent, stmt): + """Returns True if stmt in inside the else branch for a parent For stmt.""" + return (isinstance(parent, astroid.For) and + any(else_stmt.parent_of(stmt) for else_stmt in parent.orelse)) + +def overridden_method(klass, name): + """get overridden method if any""" + try: + parent = next(klass.local_attr_ancestors(name)) + except (StopIteration, KeyError): + return None + try: + meth_node = parent[name] + except KeyError: + # We have found an ancestor defining <name> but it's not in the local + # dictionary. This may happen with astroid built from living objects. + return None + if isinstance(meth_node, astroid.Function): + return meth_node + return None + +def _get_unpacking_extra_info(node, infered): + """return extra information to add to the message for unpacking-non-sequence + and unbalanced-tuple-unpacking errors + """ + more = '' + infered_module = infered.root().name + if node.root().name == infered_module: + if node.lineno == infered.lineno: + more = ' %s' % infered.as_string() + elif infered.lineno: + more = ' defined at line %s' % infered.lineno + elif infered.lineno: + more = ' defined at line %s of %s' % (infered.lineno, infered_module) + return more + +def _detect_global_scope(node, frame, defframe): + """ Detect that the given frames shares a global + scope. + + Two frames shares a global scope when neither + of them are hidden under a function scope, as well + as any of parent scope of them, until the root scope. + In this case, depending from something defined later on + will not work, because it is still undefined. + + Example: + class A: + # B has the same global scope as `C`, leading to a NameError. + class B(C): ... + class C: ... + + """ + def_scope = scope = None + if frame and frame.parent: + scope = frame.parent.scope() + if defframe and defframe.parent: + def_scope = defframe.parent.scope() + if isinstance(frame, astroid.Function): + # If the parent of the current node is a + # function, then it can be under its scope + # (defined in, which doesn't concern us) or + # the `->` part of annotations. The same goes + # for annotations of function arguments, they'll have + # their parent the Arguments node. + if not isinstance(node.parent, + (astroid.Function, astroid.Arguments)): + return False + elif any(not isinstance(f, (astroid.Class, astroid.Module)) + for f in (frame, defframe)): + # Not interested in other frames, since they are already + # not in a global scope. + return False + + break_scopes = [] + for s in (scope, def_scope): + # Look for parent scopes. If there is anything different + # than a module or a class scope, then they frames don't + # share a global scope. + parent_scope = s + while parent_scope: + if not isinstance(parent_scope, (astroid.Class, astroid.Module)): + break_scopes.append(parent_scope) + break + if parent_scope.parent: + parent_scope = parent_scope.parent.scope() + else: + break + if break_scopes and len(set(break_scopes)) != 1: + # Store different scopes than expected. + # If the stored scopes are, in fact, the very same, then it means + # that the two frames (frame and defframe) shares the same scope, + # and we could apply our lineno analysis over them. + # For instance, this works when they are inside a function, the node + # that uses a definition and the definition itself. + return False + # At this point, we are certain that frame and defframe shares a scope + # and the definition of the first depends on the second. + return frame.lineno < defframe.lineno + +def _fix_dot_imports(not_consumed): + """ Try to fix imports with multiple dots, by returning a dictionary + with the import names expanded. The function unflattens root imports, + like 'xml' (when we have both 'xml.etree' and 'xml.sax'), to 'xml.etree' + and 'xml.sax' respectively. + """ + # TODO: this should be improved in issue astroid #46 + names = {} + for name, stmts in six.iteritems(not_consumed): + if any(isinstance(stmt, astroid.AssName) + and isinstance(stmt.ass_type(), astroid.AugAssign) + for stmt in stmts): + continue + for stmt in stmts: + if not isinstance(stmt, (astroid.From, astroid.Import)): + continue + for imports in stmt.names: + second_name = None + if imports[0] == "*": + # In case of wildcard imports, + # pick the name from inside the imported module. + second_name = name + else: + if imports[0].find(".") > -1 or name in imports: + # Most likely something like 'xml.etree', + # which will appear in the .locals as 'xml'. + # Only pick the name if it wasn't consumed. + second_name = imports[0] + if second_name and second_name not in names: + names[second_name] = stmt + return sorted(names.items(), key=lambda a: a[1].fromlineno) + +def _find_frame_imports(name, frame): + """ + Detect imports in the frame, with the required + *name*. Such imports can be considered assignments. + Returns True if an import for the given name was found. + """ + imports = frame.nodes_of_class((astroid.Import, astroid.From)) + for import_node in imports: + for import_name, import_alias in import_node.names: + # If the import uses an alias, check only that. + # Otherwise, check only the import name. + if import_alias: + if import_alias == name: + return True + elif import_name and import_name == name: + return True + + +MSGS = { + 'E0601': ('Using variable %r before assignment', + 'used-before-assignment', + 'Used when a local variable is accessed before it\'s \ + assignment.'), + 'E0602': ('Undefined variable %r', + 'undefined-variable', + 'Used when an undefined variable is accessed.'), + 'E0603': ('Undefined variable name %r in __all__', + 'undefined-all-variable', + 'Used when an undefined variable name is referenced in __all__.'), + 'E0604': ('Invalid object %r in __all__, must contain only strings', + 'invalid-all-object', + 'Used when an invalid (non-string) object occurs in __all__.'), + 'E0611': ('No name %r in module %r', + 'no-name-in-module', + 'Used when a name cannot be found in a module.'), + + 'W0601': ('Global variable %r undefined at the module level', + 'global-variable-undefined', + '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 assignment is done', + 'global-variable-not-assigned', + 'Used when a variable is defined through the "global" statement \ + but no assignment to this variable is done.'), + 'W0603': ('Using the global statement', # W0121 + 'global-statement', + '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 + 'global-at-module-level', + 'Used when you use the "global" statement at the module level \ + since it has no effect'), + 'W0611': ('Unused %s', + 'unused-import', + 'Used when an imported module or variable is not used.'), + 'W0612': ('Unused variable %r', + 'unused-variable', + 'Used when a variable is defined but not used.'), + 'W0613': ('Unused argument %r', + 'unused-argument', + 'Used when a function or method argument is not used.'), + 'W0614': ('Unused import %s from wildcard import', + 'unused-wildcard-import', + 'Used when an imported module or variable is not used from a \ + \'from X import *\' style import.'), + + 'W0621': ('Redefining name %r from outer scope (line %s)', + 'redefined-outer-name', + 'Used when a variable\'s name hide a name defined in the outer \ + scope.'), + 'W0622': ('Redefining built-in %r', + 'redefined-builtin', + 'Used when a variable or function override a built-in.'), + 'W0623': ('Redefining name %r from %s in exception handler', + 'redefine-in-handler', + 'Used when an exception handler assigns the exception \ + to an existing name'), + + 'W0631': ('Using possibly undefined loop variable %r', + 'undefined-loop-variable', + '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.'), + + 'W0632': ('Possible unbalanced tuple unpacking with ' + 'sequence%s: ' + 'left side has %d label(s), right side has %d value(s)', + 'unbalanced-tuple-unpacking', + 'Used when there is an unbalanced tuple unpacking in assignment'), + + 'W0633': ('Attempting to unpack a non-sequence%s', + 'unpacking-non-sequence', + 'Used when something which is not ' + 'a sequence is used in an unpack assignment'), + + 'W0640': ('Cell variable %s defined in loop', + 'cell-var-from-loop', + 'A variable used in a closure is defined in a loop. ' + 'This will result in all closures using the same value for ' + 'the closed-over variable.'), + + } + +class VariablesChecker(BaseChecker): + """checks for + * unused variables / imports + * undefined variables + * redefinition of variable from builtins or from an outer scope + * use of variable before assignment + * __all__ consistency + """ + + __implements__ = IAstroidChecker + + name = 'variables' + msgs = MSGS + priority = -1 + options = (("init-import", + {'default': 0, 'type' : 'yn', 'metavar' : '<y_or_n>', + 'help' : 'Tells whether we should check for unused import in \ +__init__ files.'}), + ("dummy-variables-rgx", + {'default': ('_$|dummy'), + 'type' :'regexp', 'metavar' : '<regexp>', + 'help' : 'A regular expression matching the name of dummy \ +variables (i.e. expectedly 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.' + }), + ("callbacks", + {'default' : ('cb_', '_cb'), 'type' : 'csv', + 'metavar' : '<callbacks>', + 'help' : 'List of strings which can identify a callback ' + 'function by name. A callback name must start or ' + 'end with one of those strings.'} + ) + ) + def __init__(self, linter=None): + BaseChecker.__init__(self, linter) + self._to_consume = None + self._checking_mod_attr = 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')] + for name, stmts in six.iteritems(node.locals): + if is_builtin(name) and not is_inside_except(stmts[0]): + # do not print Redefining builtin for additional builtins + self.add_message('redefined-builtin', args=name, node=stmts[0]) + + @check_messages('unused-import', 'unused-wildcard-import', + 'redefined-builtin', 'undefined-all-variable', + 'invalid-all-object') + def leave_module(self, node): + """leave module: check globals + """ + assert len(self._to_consume) == 1 + not_consumed = self._to_consume.pop()[0] + # attempt to check for __all__ if defined + if '__all__' in node.locals: + assigned = next(node.igetattr('__all__')) + if assigned is not astroid.YES: + for elt in getattr(assigned, 'elts', ()): + try: + elt_name = next(elt.infer()) + except astroid.InferenceError: + continue + + if not isinstance(elt_name, astroid.Const) \ + or not isinstance(elt_name.value, six.string_types): + self.add_message('invalid-all-object', + args=elt.as_string(), node=elt) + continue + elt_name = elt_name.value + # If elt is in not_consumed, remove it from not_consumed + if elt_name in not_consumed: + del not_consumed[elt_name] + continue + if elt_name not in node.locals: + if not node.package: + self.add_message('undefined-all-variable', + args=elt_name, + node=elt) + else: + basename = os.path.splitext(node.file)[0] + if os.path.basename(basename) == '__init__': + name = node.name + "." + elt_name + try: + modutils.file_from_modpath(name.split(".")) + except ImportError: + self.add_message('undefined-all-variable', + args=elt_name, + node=elt) + except SyntaxError: + # don't yield an syntax-error warning, + # because it will be later yielded + # when the file will be checked + pass + # don't check unused imports in __init__ files + if not self.config.init_import and node.package: + return + + self._check_imports(not_consumed) + + def _check_imports(self, not_consumed): + local_names = _fix_dot_imports(not_consumed) + checked = set() + for name, stmt in local_names: + for imports in stmt.names: + real_name = imported_name = imports[0] + if imported_name == "*": + real_name = name + as_name = imports[1] + if real_name in checked: + continue + if name not in (real_name, as_name): + continue + checked.add(real_name) + + if (isinstance(stmt, astroid.Import) or + (isinstance(stmt, astroid.From) and + not stmt.modname)): + if (isinstance(stmt, astroid.From) and + SPECIAL_OBJ.search(imported_name)): + # Filter special objects (__doc__, __all__) etc., + # because they can be imported for exporting. + continue + if as_name is None: + msg = "import %s" % imported_name + else: + msg = "%s imported as %s" % (imported_name, as_name) + self.add_message('unused-import', args=msg, node=stmt) + elif isinstance(stmt, astroid.From) and stmt.modname != '__future__': + if SPECIAL_OBJ.search(imported_name): + # Filter special objects (__doc__, __all__) etc., + # because they can be imported for exporting. + continue + if imported_name == '*': + self.add_message('unused-wildcard-import', + args=name, node=stmt) + else: + if as_name is None: + msg = "%s imported from %s" % (imported_name, stmt.modname) + else: + fields = (imported_name, stmt.modname, as_name) + msg = "%s imported from %s as %s" % fields + self.add_message('unused-import', args=msg, node=stmt) + del self._to_consume + + 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_genexpr(self, node): + """visit genexpr: update consumption analysis variable + """ + self._to_consume.append((copy(node.locals), {}, 'comprehension')) + + def leave_genexpr(self, _): + """leave genexpr: update consumption analysis variable + """ + # do not check for not used locals here + self._to_consume.pop() + + def visit_dictcomp(self, node): + """visit dictcomp: update consumption analysis variable + """ + self._to_consume.append((copy(node.locals), {}, 'comprehension')) + + def leave_dictcomp(self, _): + """leave dictcomp: update consumption analysis variable + """ + # do not check for not used locals here + self._to_consume.pop() + + def visit_setcomp(self, node): + """visit setcomp: update consumption analysis variable + """ + self._to_consume.append((copy(node.locals), {}, 'comprehension')) + + def leave_setcomp(self, _): + """leave setcomp: 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 + """ + self._to_consume.append((copy(node.locals), {}, 'function')) + if not (self.linter.is_message_enabled('redefined-outer-name') or + self.linter.is_message_enabled('redefined-builtin')): + return + globs = node.root().globals + for name, stmt in node.items(): + if is_inside_except(stmt): + continue + if name in globs and not isinstance(stmt, astroid.Global): + line = globs[name][0].fromlineno + dummy_rgx = self.config.dummy_variables_rgx + if not dummy_rgx.match(name): + self.add_message('redefined-outer-name', args=(name, line), node=stmt) + elif is_builtin(name): + # do not print Redefining builtin for additional builtins + self.add_message('redefined-builtin', args=name, node=stmt) + + def leave_function(self, node): + """leave function: check function's locals are consumed""" + not_consumed = self._to_consume.pop()[0] + if not (self.linter.is_message_enabled('unused-variable') or + self.linter.is_message_enabled('unused-argument')): + return + # don't check arguments of function which are only raising an exception + if is_error(node): + return + # don't check arguments of abstract methods or within an interface + is_method = node.is_method() + klass = node.parent.frame() + if is_method and (klass.type == 'interface' or node.is_abstract()): + return + if is_method and isinstance(klass, astroid.Class): + confidence = INFERENCE if has_known_bases(klass) else INFERENCE_FAILURE + else: + confidence = HIGH + authorized_rgx = self.config.dummy_variables_rgx + called_overridden = False + argnames = node.argnames() + global_names = set() + nonlocal_names = set() + for global_stmt in node.nodes_of_class(astroid.Global): + global_names.update(set(global_stmt.names)) + for nonlocal_stmt in node.nodes_of_class(astroid.Nonlocal): + nonlocal_names.update(set(nonlocal_stmt.names)) + + for name, stmts in six.iteritems(not_consumed): + # 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, astroid.Global): + continue + if isinstance(stmt, (astroid.Import, astroid.From)): + # Detect imports, assigned to global statements. + if global_names: + skip = False + for import_name, import_alias in stmt.names: + # If the import uses an alias, check only that. + # Otherwise, check only the import name. + if import_alias: + if import_alias in global_names: + skip = True + break + elif import_name in global_names: + skip = True + break + if skip: + continue + + # care about functions with unknown argument (builtins) + if name in argnames: + if is_method: + # don't warn for the first argument of a (non static) method + if node.type != 'staticmethod' and name == argnames[0]: + continue + # don't warn for argument of an overridden method + if not called_overridden: + overridden = overridden_method(klass, node.name) + called_overridden = True + if overridden is not None and name in overridden.argnames(): + continue + if node.name in PYMETHODS and node.name not in ('__init__', '__new__'): + continue + # don't check callback arguments + if any(node.name.startswith(cb) or node.name.endswith(cb) + for cb in self.config.callbacks): + continue + self.add_message('unused-argument', args=name, node=stmt, + confidence=confidence) + else: + if stmt.parent and isinstance(stmt.parent, astroid.Assign): + if name in nonlocal_names: + continue + self.add_message('unused-variable', args=name, node=stmt) + + @check_messages('global-variable-undefined', 'global-variable-not-assigned', 'global-statement', + 'global-at-module-level', 'redefined-builtin') + def visit_global(self, node): + """check names imported exists in the global scope""" + frame = node.frame() + if isinstance(frame, astroid.Module): + self.add_message('global-at-module-level', node=node) + return + module = frame.root() + default_message = True + for name in node.names: + try: + assign_nodes = module.getattr(name) + except astroid.NotFoundError: + # unassigned global, skip + assign_nodes = [] + for anode in assign_nodes: + if anode.parent is None: + # node returned for builtin attribute such as __file__, + # __doc__, etc... + continue + if anode.frame() is frame: + # same scope level assignment + break + else: + if not _find_frame_imports(name, frame): + self.add_message('global-variable-not-assigned', + args=name, node=node) + default_message = False + if not assign_nodes: + continue + for anode in assign_nodes: + if anode.parent is None: + self.add_message('redefined-builtin', args=name, node=node) + break + if anode.frame() is module: + # module level assignment + break + else: + # global undefined at the module scope + self.add_message('global-variable-undefined', args=name, node=node) + default_message = False + if default_message: + self.add_message('global-statement', node=node) + + def _check_late_binding_closure(self, node, assignment_node): + def _is_direct_lambda_call(): + return (isinstance(node_scope.parent, astroid.CallFunc) + and node_scope.parent.func is node_scope) + + node_scope = node.scope() + if not isinstance(node_scope, (astroid.Lambda, astroid.Function)): + return + if isinstance(node.parent, astroid.Arguments): + return + + if isinstance(assignment_node, astroid.Comprehension): + if assignment_node.parent.parent_of(node.scope()): + self.add_message('cell-var-from-loop', node=node, args=node.name) + else: + assign_scope = assignment_node.scope() + maybe_for = assignment_node + while not isinstance(maybe_for, astroid.For): + if maybe_for is assign_scope: + break + maybe_for = maybe_for.parent + else: + if (maybe_for.parent_of(node_scope) + and not _is_direct_lambda_call() + and not isinstance(node_scope.statement(), astroid.Return)): + self.add_message('cell-var-from-loop', node=node, args=node.name) + + def _loopvar_name(self, node, name): + # filter variables according to node's scope + # XXX used to filter parents but don't remember why, and removing this + # fixes a W0631 false positive reported by Paul Hachmann on 2008/12 on + # python-projects (added to func_use_for_or_listcomp_var test) + #astmts = [stmt for stmt in node.lookup(name)[1] + # if hasattr(stmt, 'ass_type')] and + # not stmt.statement().parent_of(node)] + if not self.linter.is_message_enabled('undefined-loop-variable'): + return + astmts = [stmt for stmt in node.lookup(name)[1] + if hasattr(stmt, 'ass_type')] + # filter variables according their respective scope test is_statement + # and parent to avoid #74747. This is not a total fix, which would + # introduce a mechanism similar to special attribute lookup in + # modules. Also, in order to get correct inference in this case, the + # scope lookup rules would need to be changed to return the initial + # assignment (which does not exist in code per se) as well as any later + # modifications. + if not astmts or (astmts[0].is_statement or astmts[0].parent) \ + and 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) + and not in_for_else_branch(astmts[i].statement(), stmt)): + continue + _astmts.append(stmt) + astmts = _astmts + if len(astmts) == 1: + ass = astmts[0].ass_type() + if isinstance(ass, (astroid.For, astroid.Comprehension, astroid.GenExpr)) \ + and not ass.statement() is node.statement(): + self.add_message('undefined-loop-variable', args=name, node=node) + + @check_messages('redefine-in-handler') + def visit_excepthandler(self, node): + for name in get_all_elements(node.name): + clobbering, args = clobber_in_except(name) + if clobbering: + self.add_message('redefine-in-handler', args=args, node=name) + + def visit_assname(self, node): + if isinstance(node.ass_type(), astroid.AugAssign): + self.visit_name(node) + + def visit_delname(self, node): + self.visit_name(node) + + @check_messages(*(MSGS.keys())) + def visit_name(self, node): + """check that a name is defined if the current scope and doesn't + redefine a built-in + """ + stmt = node.statement() + if stmt.fromlineno is None: + # name node from a astroid built from live code, skip + assert not stmt.root().file.endswith('.py') + return + name = node.name + frame = stmt.scope() + # if the name node is used as a function default argument's value or as + # a decorator, then start from the parent frame of the function instead + # of the function frame - and thus open an inner class scope + if (is_func_default(node) or is_func_decorator(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 + base_scope_type = self._to_consume[start_index][-1] + 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. The only exception is when the starting scope is a + # comprehension and its direct outer scope is a class + if scope_type == 'class' and i != start_index and not ( + base_scope_type == 'comprehension' and i == start_index-1): + # Detect if we are in a local class scope, as an assignment. + # For example, the following is fair game. + # + # class A: + # b = 1 + # c = lambda b=b: b * b + # + # class B: + # tp = 1 + # def func(self, arg: tp): + # ... + + in_annotation = ( + PY3K and isinstance(frame, astroid.Function) + and node.statement() is frame and + (node in frame.args.annotations + or node is frame.args.varargannotation + or node is frame.args.kwargannotation)) + if in_annotation: + frame_locals = frame.parent.scope().locals + else: + frame_locals = frame.locals + if not ((isinstance(frame, astroid.Class) or in_annotation) + and name in frame_locals): + continue + # the name has already been consumed, only check it's not a loop + # variable used outside the loop + if name in consumed: + defnode = assign_parent(consumed[name][0]) + self._check_late_binding_closure(node, defnode) + self._loopvar_name(node, name) + break + # mark the name as consumed if it's defined in this scope + # (i.e. no KeyError is raised by "to_consume[name]") + try: + consumed[name] = to_consume[name] + except KeyError: + continue + # checks for use before assignment + defnode = assign_parent(to_consume[name][0]) + if defnode is not None: + self._check_late_binding_closure(node, defnode) + defstmt = defnode.statement() + defframe = defstmt.frame() + maybee0601 = True + if not frame is defframe: + maybee0601 = _detect_global_scope(node, frame, defframe) + elif defframe.parent is None: + # we are at the module level, check the name is not + # defined in builtins + if name in defframe.scope_attrs or 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 + else: + # check if we have a nonlocal + if name in defframe.locals: + maybee0601 = not any(isinstance(child, astroid.Nonlocal) + and name in child.names + for child in defframe.get_children()) + + # Handle a couple of class scoping issues. + annotation_return = False + # The class reuses itself in the class scope. + recursive_klass = (frame is defframe and + defframe.parent_of(node) and + isinstance(defframe, astroid.Class) and + node.name == defframe.name) + if (self._to_consume[-1][-1] == 'lambda' and + isinstance(frame, astroid.Class) + and name in frame.locals): + maybee0601 = True + elif (isinstance(defframe, astroid.Class) and + isinstance(frame, astroid.Function)): + # Special rule for function return annotations, + # which uses the same name as the class where + # the function lives. + if (PY3K and node is frame.returns and + defframe.parent_of(frame.returns)): + maybee0601 = annotation_return = True + + if (maybee0601 and defframe.name in defframe.locals and + defframe.locals[name][0].lineno < frame.lineno): + # Detect class assignments with the same + # name as the class. In this case, no warning + # should be raised. + maybee0601 = False + elif recursive_klass: + maybee0601 = True + else: + maybee0601 = maybee0601 and stmt.fromlineno <= defstmt.fromlineno + + if (maybee0601 + and not is_defined_before(node) + and not are_exclusive(stmt, defstmt, ('NameError', + 'Exception', + 'BaseException'))): + if recursive_klass or (defstmt is stmt and + isinstance(node, (astroid.DelName, + astroid.AssName))): + self.add_message('undefined-variable', args=name, node=node) + elif annotation_return: + self.add_message('undefined-variable', args=name, node=node) + elif self._to_consume[-1][-1] != 'lambda': + # E0601 may *not* occurs in lambda scope. + self.add_message('used-before-assignment', args=name, node=node) + elif self._to_consume[-1][-1] == 'lambda': + # E0601 can occur in class-level scope in lambdas, as in + # the following example: + # class A: + # x = lambda attr: f + attr + # f = 42 + if isinstance(frame, astroid.Class) and name in frame.locals: + if isinstance(node.parent, astroid.Arguments): + # Doing the following is fine: + # class A: + # x = 42 + # y = lambda attr=x: attr + if stmt.fromlineno <= defstmt.fromlineno: + self.add_message('used-before-assignment', + args=name, node=node) + else: + self.add_message('undefined-variable', + args=name, node=node) + + if isinstance(node, astroid.AssName): # Aug AssName + del consumed[name] + else: + del to_consume[name] + # check it's not a loop variable used outside the loop + self._loopvar_name(node, name) + break + else: + # we have not found the name, if it isn't a builtin, that's an + # undefined name ! + if not (name in astroid.Module.scope_attrs or is_builtin(name) + or name in self.config.additional_builtins): + self.add_message('undefined-variable', args=name, node=node) + + @check_messages('no-name-in-module') + def visit_import(self, node): + """check modules attribute accesses""" + for name, _ in node.names: + parts = name.split('.') + try: + module = next(node.infer_name_module(parts[0])) + except astroid.ResolveError: + continue + self._check_module_attrs(node, module, parts[1:]) + + @check_messages('no-name-in-module') + def visit_from(self, node): + """check modules attribute accesses""" + name_parts = node.modname.split('.') + level = getattr(node, 'level', None) + try: + module = node.root().import_module(name_parts[0], level=level) + except Exception: # pylint: disable=broad-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('.')) + + @check_messages('unbalanced-tuple-unpacking', 'unpacking-non-sequence') + def visit_assign(self, node): + """Check unbalanced tuple unpacking for assignments + and unpacking non-sequences. + """ + if not isinstance(node.targets[0], (astroid.Tuple, astroid.List)): + return + + targets = node.targets[0].itered() + try: + for infered in node.value.infer(): + self._check_unpacking(infered, node, targets) + except astroid.InferenceError: + return + + def _check_unpacking(self, infered, node, targets): + """ Check for unbalanced tuple unpacking + and unpacking non sequences. + """ + if infered is astroid.YES: + return + if (isinstance(infered.parent, astroid.Arguments) and + isinstance(node.value, astroid.Name) and + node.value.name == infered.parent.vararg): + # Variable-length argument, we can't determine the length. + return + if isinstance(infered, (astroid.Tuple, astroid.List)): + # attempt to check unpacking is properly balanced + values = infered.itered() + if len(targets) != len(values): + # Check if we have starred nodes. + if any(isinstance(target, astroid.Starred) + for target in targets): + return + self.add_message('unbalanced-tuple-unpacking', node=node, + args=(_get_unpacking_extra_info(node, infered), + len(targets), + len(values))) + # attempt to check unpacking may be possible (ie RHS is iterable) + elif isinstance(infered, astroid.Instance): + for meth in ('__iter__', '__getitem__'): + try: + infered.getattr(meth) + break + except astroid.NotFoundError: + continue + else: + self.add_message('unpacking-non-sequence', node=node, + args=(_get_unpacking_extra_info(node, infered),)) + else: + self.add_message('unpacking-non-sequence', node=node, + args=(_get_unpacking_extra_info(node, infered),)) + + + 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, astroid.Module), module + ignored_modules = get_global_option(self, 'ignored-modules', + default=[]) + while module_names: + name = module_names.pop(0) + if name == '__dict__': + module = None + break + try: + module = next(module.getattr(name)[0].infer()) + if module is astroid.YES: + return None + except astroid.NotFoundError: + if module.name in ignored_modules: + return None + self.add_message('no-name-in-module', + args=(name, module.name), node=node) + return None + except astroid.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('no-name-in-module', node=node, + args=('.'.join(module_names), modname)) + return None + if isinstance(module, astroid.Module): + return module + return None + + +class VariablesChecker3k(VariablesChecker): + '''Modified variables checker for 3k''' + # listcomp have now also their scope + + def visit_listcomp(self, node): + """visit dictcomp: update consumption analysis variable + """ + self._to_consume.append((copy(node.locals), {}, 'comprehension')) + + def leave_listcomp(self, _): + """leave dictcomp: update consumption analysis variable + """ + # do not check for not used locals here + self._to_consume.pop() + + def leave_module(self, node): + """ Update consumption analysis variable + for metaclasses. + """ + module_locals = self._to_consume[0][0] + module_imports = self._to_consume[0][1] + consumed = {} + + for klass in node.nodes_of_class(astroid.Class): + found = metaclass = name = None + if not klass._metaclass: + # Skip if this class doesn't use + # explictly a metaclass, but inherits it from ancestors + continue + + metaclass = klass.metaclass() + + # Look the name in the already found locals. + # If it's not found there, look in the module locals + # and in the imported modules. + if isinstance(klass._metaclass, astroid.Name): + name = klass._metaclass.name + elif metaclass: + # if it uses a `metaclass=module.Class` + name = metaclass.root().name + + if name: + found = consumed.setdefault( + name, module_locals.get(name, module_imports.get(name))) + + if found is None and not metaclass: + name = None + if isinstance(klass._metaclass, astroid.Name): + name = klass._metaclass.name + elif isinstance(klass._metaclass, astroid.Getattr): + name = klass._metaclass.as_string() + + if name is not None: + if not (name in astroid.Module.scope_attrs or + is_builtin(name) or + name in self.config.additional_builtins or + name in node.locals): + self.add_message('undefined-variable', + node=klass, + args=(name, )) + # Pop the consumed items, in order to + # avoid having unused-import false positives + for name in consumed: + module_locals.pop(name, None) + super(VariablesChecker3k, self).leave_module(node) + +if sys.version_info >= (3, 0): + VariablesChecker = VariablesChecker3k + + +def register(linter): + """required method to auto register this checker""" + linter.register_checker(VariablesChecker(linter)) diff --git a/pylint/config.py b/pylint/config.py new file mode 100644 index 0000000..ebfe578 --- /dev/null +++ b/pylint/config.py @@ -0,0 +1,157 @@ +# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +"""utilities for Pylint configuration : + +* pylintrc +* pylint.d (PYLINTHOME) +""" +from __future__ import with_statement +from __future__ import print_function + +import pickle +import os +import sys +from os.path import exists, isfile, join, expanduser, abspath, dirname + +# pylint home is used to save old runs results ################################ + +USER_HOME = expanduser('~') +if 'PYLINTHOME' in os.environ: + PYLINT_HOME = os.environ['PYLINTHOME'] + if USER_HOME == '~': + USER_HOME = dirname(PYLINT_HOME) +elif USER_HOME == '~': + PYLINT_HOME = ".pylint.d" +else: + PYLINT_HOME = join(USER_HOME, '.pylint.d') + +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: + with open(data_file, _PICK_LOAD) as stream: + return pickle.load(stream) + except Exception: # pylint: disable=broad-except + return {} + +if sys.version_info < (3, 0): + _PICK_DUMP, _PICK_LOAD = 'w', 'r' +else: + _PICK_DUMP, _PICK_LOAD = 'wb', 'rb' + +def save_results(results, base): + """pickle results""" + if not exists(PYLINT_HOME): + try: + os.mkdir(PYLINT_HOME) + except OSError: + print('Unable to create directory %s' % PYLINT_HOME, file=sys.stderr) + data_file = get_pdata_path(base, 1) + try: + with open(data_file, _PICK_DUMP) as stream: + pickle.dump(results, stream) + except (IOError, OSError) as ex: + print('Unable to create file %s: %s' % (data_file, ex), file=sys.stderr) + +# location of the configuration file ########################################## + + +def find_pylintrc(): + """search the pylint rc file and return its path if it find it, else None + """ + # is there a pylint rc file in the current directory ? + if exists('pylintrc'): + return abspath('pylintrc') + if isfile('__init__.py'): + curdir = abspath(os.getcwd()) + while isfile(join(curdir, '__init__.py')): + curdir = abspath(join(curdir, '..')) + if isfile(join(curdir, 'pylintrc')): + return join(curdir, 'pylintrc') + if 'PYLINTRC' in os.environ 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): + pylintrc = join(user_home, '.config', 'pylintrc') + if not isfile(pylintrc): + if isfile('/etc/pylintrc'): + pylintrc = '/etc/pylintrc' + else: + pylintrc = None + return pylintrc + +PYLINTRC = find_pylintrc() + +ENV_HELP = ''' +The following environment variables are used: + * PYLINTHOME + Path to the directory where the persistent for the run will be stored. If +not found, it defaults to ~/.pylint.d/ or .pylint.d (in the current working +directory). + * PYLINTRC + Path to the configuration file. See the documentation for the method used +to search for configuration file. +''' % 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/pylint/epylint.py b/pylint/epylint.py new file mode 100755 index 0000000..4fd683e --- /dev/null +++ b/pylint/epylint.py @@ -0,0 +1,176 @@ +# -*- coding: utf-8; mode: python; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- vim:fenc=utf-8:ft=python:et:sw=4:ts=4:sts=4 +# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +"""Emacs and Flymake compatible Pylint. + +This script is for integration with emacs and is compatible with flymake mode. + +epylint walks out of python packages before invoking pylint. This avoids +reporting import errors that occur when a module within a package uses the +absolute import path to get another module within this package. + +For example: + - Suppose a package is structured as + + a/__init__.py + a/b/x.py + a/c/y.py + + - Then if y.py imports x as "from a.b import x" the following produces pylint + errors + + cd a/c; pylint y.py + + - The following obviously doesn't + + pylint a/c/y.py + + - As this script will be invoked by emacs within the directory of the file + we are checking we need to go out of it to avoid these false positives. + + +You may also use py_run to run pylint with desired options and get back (or not) +its output. +""" +from __future__ import print_function + +import sys, os +import os.path as osp +from subprocess import Popen, PIPE + +def _get_env(): + '''Extracts the environment PYTHONPATH and appends the current sys.path to + those.''' + env = dict(os.environ) + env['PYTHONPATH'] = os.pathsep.join(sys.path) + return env + +def lint(filename, options=None): + """Pylint the given file. + + When run from emacs we will be in the directory of a file, and passed its + filename. If this file is part of a package and is trying to import other + modules from within its own package or another package rooted in a directory + below it, pylint will classify it as a failed import. + + To get around this, we traverse down the directory tree to find the root of + the package this module is in. We then invoke pylint from this directory. + + Finally, we must correct the filenames in the output generated by pylint so + Emacs doesn't become confused (it will expect just the original filename, + while pylint may extend it with extra directories if we've traversed down + the tree) + """ + # traverse downwards until we are out of a python package + full_path = osp.abspath(filename) + parent_path = osp.dirname(full_path) + child_path = osp.basename(full_path) + + while parent_path != "/" and osp.exists(osp.join(parent_path, '__init__.py')): + child_path = osp.join(osp.basename(parent_path), child_path) + parent_path = osp.dirname(parent_path) + + # Start pylint + # Ensure we use the python and pylint associated with the running epylint + from pylint import lint as lint_mod + lint_path = lint_mod.__file__ + options = options or ['--disable=C,R,I'] + cmd = [sys.executable, lint_path] + options + [ + '--msg-template', '{path}:{line}: {category} ({msg_id}, {symbol}, {obj}) {msg}', + '-r', 'n', child_path] + process = Popen(cmd, stdout=PIPE, cwd=parent_path, env=_get_env(), + universal_newlines=True) + + for line in process.stdout: + # remove pylintrc warning + if line.startswith("No config file found"): + continue + + # modify the file name thats output to reverse the path traversal we made + parts = line.split(":") + if parts and parts[0] == child_path: + line = ":".join([filename] + parts[1:]) + print(line, end=' ') + + process.wait() + return process.returncode + + +def py_run(command_options='', return_std=False, stdout=None, stderr=None, + script='epylint'): + """Run pylint from python + + ``command_options`` is a string containing ``pylint`` command line options; + ``return_std`` (boolean) indicates return of created standard output + and error (see below); + ``stdout`` and ``stderr`` are 'file-like' objects in which standard output + could be written. + + Calling agent is responsible for stdout/err management (creation, close). + Default standard output and error are those from sys, + or standalone ones (``subprocess.PIPE``) are used + if they are not set and ``return_std``. + + If ``return_std`` is set to ``True``, this function returns a 2-uple + containing standard output and error related to created process, + as follows: ``(stdout, stderr)``. + + A trivial usage could be as follows: + >>> py_run( '--version') + No config file found, using default configuration + pylint 0.18.1, + ... + + To silently run Pylint on a module, and get its standard output and error: + >>> (pylint_stdout, pylint_stderr) = py_run( 'module_name.py', True) + """ + # Create command line to call pylint + if os.name == 'nt': + script += '.bat' + command_line = script + ' ' + command_options + # Providing standard output and/or error if not set + if stdout is None: + if return_std: + stdout = PIPE + else: + stdout = sys.stdout + if stderr is None: + if return_std: + stderr = PIPE + else: + stderr = sys.stderr + # Call pylint in a subprocess + p = Popen(command_line, shell=True, stdout=stdout, stderr=stderr, + env=_get_env(), universal_newlines=True) + p.wait() + # Return standard output and error + if return_std: + return (p.stdout, p.stderr) + + +def Run(): + if len(sys.argv) == 1: + print("Usage: %s <filename> [options]" % sys.argv[0]) + sys.exit(1) + elif not osp.exists(sys.argv[1]): + print("%s does not exist" % sys.argv[1]) + sys.exit(1) + else: + sys.exit(lint(sys.argv[1], sys.argv[2:])) + + +if __name__ == '__main__': + Run() diff --git a/pylint/gui.py b/pylint/gui.py new file mode 100644 index 0000000..8327e0e --- /dev/null +++ b/pylint/gui.py @@ -0,0 +1,531 @@ +# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +"""Tkinker gui for pylint""" +from __future__ import print_function + +import os +import sys +import re +from threading import Thread + +import six + +from six.moves.tkinter import ( + Tk, Frame, Listbox, Entry, Label, Button, Scrollbar, + Checkbutton, Radiobutton, IntVar, StringVar, PanedWindow, + TOP, LEFT, RIGHT, BOTTOM, END, X, Y, BOTH, SUNKEN, W, + HORIZONTAL, DISABLED, NORMAL, W, +) +from six.moves.tkinter_tkfiledialog import ( + askopenfilename, askdirectory, +) + +import pylint.lint +from pylint.reporters.guireporter import GUIReporter + +HOME = os.path.expanduser('~/') +HISTORY = '.pylint-gui-history' +COLORS = {'(I)':'green', + '(C)':'blue', '(R)':'darkblue', + '(W)':'black', '(E)':'darkred', + '(F)':'red'} + + +def convert_to_string(msg): + """make a string representation of a message""" + module_object = msg.module + if msg.obj: + module_object += ".%s" % msg.obj + return "(%s) %s [%d]: %s" % (msg.C, module_object, msg.line, msg.msg) + +class BasicStream(object): + ''' + used in gui reporter instead of writing to stdout, it is written to + this stream and saved in contents + ''' + def __init__(self, gui): + """init""" + self.curline = "" + self.gui = gui + self.contents = [] + self.outdict = {} + self.currout = None + self.next_title = None + + def write(self, text): + """write text to the stream""" + if re.match('^--+$', text.strip()) or re.match('^==+$', text.strip()): + if self.currout: + self.outdict[self.currout].remove(self.next_title) + self.outdict[self.currout].pop() + self.currout = self.next_title + self.outdict[self.currout] = [''] + + if text.strip(): + self.next_title = text.strip() + + if text.startswith(os.linesep): + self.contents.append('') + if self.currout: + self.outdict[self.currout].append('') + self.contents[-1] += text.strip(os.linesep) + if self.currout: + self.outdict[self.currout][-1] += text.strip(os.linesep) + if text.endswith(os.linesep) and text.strip(): + self.contents.append('') + if self.currout: + self.outdict[self.currout].append('') + + def fix_contents(self): + """finalize what the contents of the dict should look like before output""" + for item in self.outdict: + num_empty = self.outdict[item].count('') + for _ in range(num_empty): + self.outdict[item].remove('') + if self.outdict[item]: + self.outdict[item].pop(0) + + def output_contents(self): + """output contents of dict to the gui, and set the rating""" + self.fix_contents() + self.gui.tabs = self.outdict + try: + self.gui.rating.set(self.outdict['Global evaluation'][0]) + except KeyError: + self.gui.rating.set('Error') + self.gui.refresh_results_window() + + #reset stream variables for next run + self.contents = [] + self.outdict = {} + self.currout = None + self.next_title = None + + +class LintGui(object): + """Build and control a window to interact with pylint""" + + def __init__(self, root=None): + """init""" + self.root = root or Tk() + self.root.title('Pylint') + #reporter + self.reporter = None + #message queue for output from reporter + self.msg_queue = six.moves.queue.Queue() + self.msgs = [] + self.visible_msgs = [] + self.filenames = [] + self.rating = StringVar() + self.tabs = {} + self.report_stream = BasicStream(self) + #gui objects + self.lb_messages = None + self.showhistory = None + self.results = None + self.btnRun = None + self.information_box = None + self.convention_box = None + self.refactor_box = None + self.warning_box = None + self.error_box = None + self.fatal_box = None + self.txtModule = None + self.status = None + self.msg_type_dict = None + self.init_gui() + + def init_gui(self): + """init helper""" + + window = PanedWindow(self.root, orient="vertical") + window.pack(side=TOP, fill=BOTH, expand=True) + + top_pane = Frame(window) + window.add(top_pane) + mid_pane = Frame(window) + window.add(mid_pane) + bottom_pane = Frame(window) + window.add(bottom_pane) + + #setting up frames + top_frame = Frame(top_pane) + mid_frame = Frame(top_pane) + history_frame = Frame(top_pane) + radio_frame = Frame(mid_pane) + rating_frame = Frame(mid_pane) + res_frame = Frame(mid_pane) + check_frame = Frame(bottom_pane) + msg_frame = Frame(bottom_pane) + btn_frame = Frame(bottom_pane) + top_frame.pack(side=TOP, fill=X) + mid_frame.pack(side=TOP, fill=X) + history_frame.pack(side=TOP, fill=BOTH, expand=True) + radio_frame.pack(side=TOP, fill=X) + rating_frame.pack(side=TOP, fill=X) + res_frame.pack(side=TOP, fill=BOTH, expand=True) + check_frame.pack(side=TOP, fill=X) + msg_frame.pack(side=TOP, fill=BOTH, expand=True) + btn_frame.pack(side=TOP, fill=X) + + # Binding F5 application-wide to run lint + self.root.bind('<F5>', self.run_lint) + + #Message ListBox + rightscrollbar = Scrollbar(msg_frame) + rightscrollbar.pack(side=RIGHT, fill=Y) + bottomscrollbar = Scrollbar(msg_frame, orient=HORIZONTAL) + bottomscrollbar.pack(side=BOTTOM, fill=X) + self.lb_messages = Listbox( + msg_frame, + yscrollcommand=rightscrollbar.set, + xscrollcommand=bottomscrollbar.set, + bg="white") + self.lb_messages.bind("<Double-Button-1>", self.show_sourcefile) + self.lb_messages.pack(expand=True, fill=BOTH) + rightscrollbar.config(command=self.lb_messages.yview) + bottomscrollbar.config(command=self.lb_messages.xview) + + #History ListBoxes + rightscrollbar2 = Scrollbar(history_frame) + rightscrollbar2.pack(side=RIGHT, fill=Y) + bottomscrollbar2 = Scrollbar(history_frame, orient=HORIZONTAL) + bottomscrollbar2.pack(side=BOTTOM, fill=X) + self.showhistory = Listbox( + history_frame, + yscrollcommand=rightscrollbar2.set, + xscrollcommand=bottomscrollbar2.set, + bg="white") + self.showhistory.pack(expand=True, fill=BOTH) + rightscrollbar2.config(command=self.showhistory.yview) + bottomscrollbar2.config(command=self.showhistory.xview) + self.showhistory.bind('<Double-Button-1>', self.select_recent_file) + self.set_history_window() + + #status bar + self.status = Label(self.root, text="", bd=1, relief=SUNKEN, anchor=W) + self.status.pack(side=BOTTOM, fill=X) + + #labelbl_ratingls + lbl_rating_label = Label(rating_frame, text='Rating:') + lbl_rating_label.pack(side=LEFT) + lbl_rating = Label(rating_frame, textvariable=self.rating) + lbl_rating.pack(side=LEFT) + Label(mid_frame, text='Recently Used:').pack(side=LEFT) + Label(top_frame, text='Module or package').pack(side=LEFT) + + #file textbox + self.txt_module = Entry(top_frame, background='white') + self.txt_module.bind('<Return>', self.run_lint) + self.txt_module.pack(side=LEFT, expand=True, fill=X) + + #results box + rightscrollbar = Scrollbar(res_frame) + rightscrollbar.pack(side=RIGHT, fill=Y) + bottomscrollbar = Scrollbar(res_frame, orient=HORIZONTAL) + bottomscrollbar.pack(side=BOTTOM, fill=X) + self.results = Listbox( + res_frame, + yscrollcommand=rightscrollbar.set, + xscrollcommand=bottomscrollbar.set, + bg="white", font="Courier") + self.results.pack(expand=True, fill=BOTH, side=BOTTOM) + rightscrollbar.config(command=self.results.yview) + bottomscrollbar.config(command=self.results.xview) + + #buttons + Button(top_frame, text='Open', command=self.file_open).pack(side=LEFT) + Button(top_frame, text='Open Package', + command=(lambda: self.file_open(package=True))).pack(side=LEFT) + + self.btnRun = Button(top_frame, text='Run', command=self.run_lint) + self.btnRun.pack(side=LEFT) + Button(btn_frame, text='Quit', command=self.quit).pack(side=BOTTOM) + + #radio buttons + self.information_box = IntVar() + self.convention_box = IntVar() + self.refactor_box = IntVar() + self.warning_box = IntVar() + self.error_box = IntVar() + self.fatal_box = IntVar() + i = Checkbutton(check_frame, text="Information", fg=COLORS['(I)'], + variable=self.information_box, command=self.refresh_msg_window) + c = Checkbutton(check_frame, text="Convention", fg=COLORS['(C)'], + variable=self.convention_box, command=self.refresh_msg_window) + r = Checkbutton(check_frame, text="Refactor", fg=COLORS['(R)'], + variable=self.refactor_box, command=self.refresh_msg_window) + w = Checkbutton(check_frame, text="Warning", fg=COLORS['(W)'], + variable=self.warning_box, command=self.refresh_msg_window) + e = Checkbutton(check_frame, text="Error", fg=COLORS['(E)'], + variable=self.error_box, command=self.refresh_msg_window) + f = Checkbutton(check_frame, text="Fatal", fg=COLORS['(F)'], + variable=self.fatal_box, command=self.refresh_msg_window) + i.select() + c.select() + r.select() + w.select() + e.select() + f.select() + i.pack(side=LEFT) + c.pack(side=LEFT) + r.pack(side=LEFT) + w.pack(side=LEFT) + e.pack(side=LEFT) + f.pack(side=LEFT) + + #check boxes + self.box = StringVar() + # XXX should be generated + report = Radiobutton( + radio_frame, text="Report", variable=self.box, + value="Report", command=self.refresh_results_window) + raw_met = Radiobutton( + radio_frame, text="Raw metrics", variable=self.box, + value="Raw metrics", command=self.refresh_results_window) + dup = Radiobutton( + radio_frame, text="Duplication", variable=self.box, + value="Duplication", command=self.refresh_results_window) + ext = Radiobutton( + radio_frame, text="External dependencies", + variable=self.box, value="External dependencies", + command=self.refresh_results_window) + stat = Radiobutton( + radio_frame, text="Statistics by type", + variable=self.box, value="Statistics by type", + command=self.refresh_results_window) + msg_cat = Radiobutton( + radio_frame, text="Messages by category", + variable=self.box, value="Messages by category", + command=self.refresh_results_window) + msg = Radiobutton( + radio_frame, text="Messages", variable=self.box, + value="Messages", command=self.refresh_results_window) + source_file = Radiobutton( + radio_frame, text="Source File", variable=self.box, + value="Source File", command=self.refresh_results_window) + report.select() + report.grid(column=0, row=0, sticky=W) + raw_met.grid(column=1, row=0, sticky=W) + dup.grid(column=2, row=0, sticky=W) + msg.grid(column=3, row=0, sticky=W) + stat.grid(column=0, row=1, sticky=W) + msg_cat.grid(column=1, row=1, sticky=W) + ext.grid(column=2, row=1, sticky=W) + source_file.grid(column=3, row=1, sticky=W) + + #dictionary for check boxes and associated error term + self.msg_type_dict = { + 'I': lambda: self.information_box.get() == 1, + 'C': lambda: self.convention_box.get() == 1, + 'R': lambda: self.refactor_box.get() == 1, + 'E': lambda: self.error_box.get() == 1, + 'W': lambda: self.warning_box.get() == 1, + 'F': lambda: self.fatal_box.get() == 1 + } + self.txt_module.focus_set() + + + def select_recent_file(self, event): # pylint: disable=unused-argument + """adds the selected file in the history listbox to the Module box""" + if not self.showhistory.size(): + return + + selected = self.showhistory.curselection() + item = self.showhistory.get(selected) + #update module + self.txt_module.delete(0, END) + self.txt_module.insert(0, item) + + def refresh_msg_window(self): + """refresh the message window with current output""" + #clear the window + self.lb_messages.delete(0, END) + self.visible_msgs = [] + for msg in self.msgs: + if self.msg_type_dict.get(msg.C)(): + self.visible_msgs.append(msg) + msg_str = convert_to_string(msg) + self.lb_messages.insert(END, msg_str) + fg_color = COLORS.get(msg_str[:3], 'black') + self.lb_messages.itemconfigure(END, fg=fg_color) + + def refresh_results_window(self): + """refresh the results window with current output""" + #clear the window + self.results.delete(0, END) + try: + for res in self.tabs[self.box.get()]: + self.results.insert(END, res) + except KeyError: + pass + + def process_incoming(self): + """process the incoming messages from running pylint""" + while self.msg_queue.qsize(): + try: + msg = self.msg_queue.get(0) + if msg == "DONE": + self.report_stream.output_contents() + return False + + #adding message to list of msgs + self.msgs.append(msg) + + #displaying msg if message type is selected in check box + if self.msg_type_dict.get(msg.C)(): + self.visible_msgs.append(msg) + msg_str = convert_to_string(msg) + self.lb_messages.insert(END, msg_str) + fg_color = COLORS.get(msg_str[:3], 'black') + self.lb_messages.itemconfigure(END, fg=fg_color) + + except six.moves.queue.Empty: + pass + return True + + def periodic_call(self): + """determine when to unlock the run button""" + if self.process_incoming(): + self.root.after(100, self.periodic_call) + else: + #enabling button so it can be run again + self.btnRun.config(state=NORMAL) + + def mainloop(self): + """launch the mainloop of the application""" + self.root.mainloop() + + def quit(self, _=None): + """quit the application""" + self.root.quit() + + def halt(self): # pylint: disable=no-self-use + """program halt placeholder""" + return + + def file_open(self, package=False, _=None): + """launch a file browser""" + if not package: + filename = askopenfilename(parent=self.root, + filetypes=[('pythonfiles', '*.py'), + ('allfiles', '*')], + title='Select Module') + else: + filename = askdirectory(title="Select A Folder", mustexist=1) + + if filename == (): + return + + self.txt_module.delete(0, END) + self.txt_module.insert(0, filename) + + def update_filenames(self): + """update the list of recent filenames""" + filename = self.txt_module.get() + if not filename: + filename = os.getcwd() + if filename+'\n' in self.filenames: + index = self.filenames.index(filename+'\n') + self.filenames.pop(index) + + #ensure only 10 most recent are stored + if len(self.filenames) == 10: + self.filenames.pop() + self.filenames.insert(0, filename+'\n') + + def set_history_window(self): + """update the history window with info from the history file""" + #clear the window + self.showhistory.delete(0, END) + # keep the last 10 most recent files + try: + view_history = open(HOME+HISTORY, 'r') + for hist in view_history.readlines(): + if not hist in self.filenames: + self.filenames.append(hist) + self.showhistory.insert(END, hist.split('\n')[0]) + view_history.close() + except IOError: + # do nothing since history file will be created later + return + + def run_lint(self, _=None): + """launches pylint""" + self.update_filenames() + self.root.configure(cursor='watch') + self.reporter = GUIReporter(self, output=self.report_stream) + module = self.txt_module.get() + if not module: + module = os.getcwd() + + #cleaning up msgs and windows + self.msgs = [] + self.visible_msgs = [] + self.lb_messages.delete(0, END) + self.tabs = {} + self.results.delete(0, END) + self.btnRun.config(state=DISABLED) + + #setting up a worker thread to run pylint + worker = Thread(target=lint_thread, args=(module, self.reporter, self,)) + self.periodic_call() + worker.start() + + # Overwrite the .pylint-gui-history file with all the new recently added files + # in order from filenames but only save last 10 files + write_history = open(HOME+HISTORY, 'w') + write_history.writelines(self.filenames) + write_history.close() + self.set_history_window() + + self.root.configure(cursor='') + + def show_sourcefile(self, event=None): # pylint: disable=unused-argument + selected = self.lb_messages.curselection() + if not selected: + return + + msg = self.visible_msgs[int(selected[0])] + scroll = msg.line - 3 + if scroll < 0: + scroll = 0 + + self.tabs["Source File"] = open(msg.path, "r").readlines() + self.box.set("Source File") + self.refresh_results_window() + self.results.yview(scroll) + self.results.select_set(msg.line - 1) + + +def lint_thread(module, reporter, gui): + """thread for pylint""" + gui.status.text = "processing module(s)" + pylint.lint.Run(args=[module], reporter=reporter, exit=False) + gui.msg_queue.put("DONE") + + +def Run(args): + """launch pylint gui from args""" + if args: + print('USAGE: pylint-gui\n launch a simple pylint gui using Tk') + sys.exit(1) + gui = LintGui() + gui.mainloop() + sys.exit(0) + +if __name__ == '__main__': + Run(sys.argv[1:]) diff --git a/pylint/interfaces.py b/pylint/interfaces.py new file mode 100644 index 0000000..64f5a95 --- /dev/null +++ b/pylint/interfaces.py @@ -0,0 +1,84 @@ +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +"""Interfaces for Pylint objects""" +from collections import namedtuple + +from logilab.common.interface import Interface + +Confidence = namedtuple('Confidence', ['name', 'description']) +# Warning Certainties +HIGH = Confidence('HIGH', 'No false positive possible.') +INFERENCE = Confidence('INFERENCE', 'Warning based on inference result.') +INFERENCE_FAILURE = Confidence('INFERENCE_FAILURE', + 'Warning based on inference with failures.') +UNDEFINED = Confidence('UNDEFINED', + 'Warning without any associated confidence level.') + +CONFIDENCE_LEVELS = [HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED] + + +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)""" + + +class IRawChecker(IChecker): + """interface for checker which need to parse the raw file + """ + + def process_module(self, astroid): + """ process a module + + the module's content is accessible via astroid.stream + """ + + +class ITokenChecker(IChecker): + """Interface for checkers that need access to the token list.""" + def process_tokens(self, tokens): + """Process a module. + + tokens is a list of all source code tokens in the file. + """ + + +class IAstroidChecker(IChecker): + """ interface for checker which prefers receive events according to + statement type + """ + + +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', 'IAstroidChecker', 'ITokenChecker', 'IReporter') diff --git a/pylint/lint.py b/pylint/lint.py new file mode 100644 index 0000000..e10ae56 --- /dev/null +++ b/pylint/lint.py @@ -0,0 +1,1332 @@ +# Copyright (c) 2003-2014 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +""" %prog [options] module_or_package + + Check that a module satisfies 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. +""" +from __future__ import print_function + +import collections +import contextlib +import itertools +import operator +import os +try: + import multiprocessing +except ImportError: + multiprocessing = None +import sys +import tokenize +import warnings + +import astroid +from astroid.__pkginfo__ import version as astroid_version +from astroid import modutils +from logilab.common import configuration +from logilab.common import optik_ext +from logilab.common import interface +from logilab.common import textutils +from logilab.common import ureports +from logilab.common.__pkginfo__ import version as common_version +import six + +from pylint import checkers +from pylint import interfaces +from pylint import reporters +from pylint import utils +from pylint import config +from pylint.__pkginfo__ import version + + +MANAGER = astroid.MANAGER + +def _get_new_args(message): + location = ( + message.abspath, + message.path, + message.module, + message.obj, + message.line, + message.column, + ) + return ( + message.msg_id, + message.symbol, + location, + message.msg, + message.confidence, + ) + +def _get_python_path(filepath): + dirname = os.path.realpath(os.path.expanduser(filepath)) + if not os.path.isdir(dirname): + dirname = os.path.dirname(dirname) + while True: + if not os.path.exists(os.path.join(dirname, "__init__.py")): + return dirname + old_dirname = dirname + dirname = os.path.dirname(dirname) + if old_dirname == dirname: + return os.getcwd() + + +def _merge_stats(stats): + merged = {} + for stat in stats: + for key, item in six.iteritems(stat): + if key not in merged: + merged[key] = item + else: + if isinstance(item, dict): + merged[key].update(item) + else: + merged[key] = merged[key] + item + return merged + + +# Python Linter class ######################################################### + +MSGS = { + 'F0001': ('%s', + 'fatal', + 'Used when an error occurred preventing the analysis of a \ + module (unable to find it for instance).'), + 'F0002': ('%s: %s', + 'astroid-error', + 'Used when an unexpected error occurred while building the ' + 'Astroid representation. This is usually accompanied by a ' + 'traceback. Please report such errors !'), + 'F0003': ('ignored builtin module %s', + 'ignored-builtin-module', + 'Used to indicate that the user asked to analyze a builtin ' + 'module which has been skipped.'), + 'F0010': ('error while code parsing: %s', + 'parse-error', + 'Used when an exception occured while building the Astroid ' + 'representation which could be handled by astroid.'), + + 'I0001': ('Unable to run raw checkers on built-in module %s', + 'raw-checker-failed', + 'Used to inform that a built-in module has not been checked ' + 'using the raw checkers.'), + + 'I0010': ('Unable to consider inline option %r', + 'bad-inline-option', + 'Used when an inline option is either badly formatted or can\'t ' + 'be used inside modules.'), + + 'I0011': ('Locally disabling %s (%s)', + 'locally-disabled', + 'Used when an inline option disables a message or a messages ' + 'category.'), + 'I0012': ('Locally enabling %s (%s)', + 'locally-enabled', + 'Used when an inline option enables a message or a messages ' + 'category.'), + 'I0013': ('Ignoring entire file', + 'file-ignored', + 'Used to inform that the file will not be checked'), + 'I0020': ('Suppressed %s (from line %d)', + 'suppressed-message', + 'A message was triggered on a line, but suppressed explicitly ' + 'by a disable= comment in the file. This message is not ' + 'generated for messages that are ignored due to configuration ' + 'settings.'), + 'I0021': ('Useless suppression of %s', + 'useless-suppression', + 'Reported when a message is explicitly disabled for a line or ' + 'a block of code, but never triggered.'), + 'I0022': ('Pragma "%s" is deprecated, use "%s" instead', + 'deprecated-pragma', + 'Some inline pylint options have been renamed or reworked, ' + 'only the most recent form should be used. ' + 'NOTE:skip-all is only available with pylint >= 0.26', + {'old_names': [('I0014', 'deprecated-disable-all')]}), + + 'E0001': ('%s', + 'syntax-error', + 'Used when a syntax error is raised for a module.'), + + 'E0011': ('Unrecognized file option %r', + 'unrecognized-inline-option', + 'Used when an unknown inline option is encountered.'), + 'E0012': ('Bad option value %r', + 'bad-option-value', + 'Used when a bad value for an inline option is encountered.'), + } + + +def _deprecated_option(shortname, opt_type): + def _warn_deprecated(option, optname, *args): # pylint: disable=unused-argument + sys.stderr.write('Warning: option %s is deprecated and ignored.\n' % (optname,)) + return {'short': shortname, 'help': 'DEPRECATED', 'hide': True, + 'type': opt_type, 'action': 'callback', 'callback': _warn_deprecated} + + +if multiprocessing is not None: + class ChildLinter(multiprocessing.Process): # pylint: disable=no-member + def run(self): + tasks_queue, results_queue, self._config = self._args # pylint: disable=no-member + + self._config["jobs"] = 1 # Child does not parallelize any further. + + # Run linter for received files/modules. + for file_or_module in iter(tasks_queue.get, 'STOP'): + result = self._run_linter(file_or_module[0]) + try: + results_queue.put(result) + except Exception as ex: + print("internal error with sending report for module %s" % file_or_module, file=sys.stderr) + print(ex, file=sys.stderr) + results_queue.put({}) + + def _run_linter(self, file_or_module): + linter = PyLinter() + + # Register standard checkers. + linter.load_default_plugins() + # Load command line plugins. + # TODO linter.load_plugin_modules(self._plugins) + + linter.load_configuration(**self._config) + linter.set_reporter(reporters.CollectingReporter()) + + # Run the checks. + linter.check(file_or_module) + + msgs = [_get_new_args(m) for m in linter.reporter.messages] + return (file_or_module, linter.file_state.base_name, linter.current_name, + msgs, linter.stats, linter.msg_status) + + +class PyLinter(configuration.OptionsManagerMixIn, + utils.MessagesHandlerMixIn, + utils.ReportsHandlerMixIn, + checkers.BaseTokenChecker): + """lint Python modules using external checkers. + + This is the main checker controlling the other ones and the reports + generation. It is itself both a raw checker and an astroid checker in order + to: + * handle message activation / deactivation at the module level + * handle some basic but necessary stats'data (number of classes, methods...) + + IDE plugins developpers: you may have to call + `astroid.builder.MANAGER.astroid_cache.clear()` accross run if you want + to ensure the latest code version is actually checked. + """ + + __implements__ = (interfaces.ITokenChecker, ) + + name = 'master' + priority = 0 + level = 0 + msgs = MSGS + + @staticmethod + def make_options(): + return (('ignore', + {'type' : 'csv', 'metavar' : '<file>[,<file>...]', + 'dest' : 'black_list', 'default' : ('CVS',), + 'help' : 'Add files or directories to the blacklist. ' + 'They should be base names, not paths.'}), + ('persistent', + {'default': True, 'type' : 'yn', 'metavar' : '<y_or_n>', + 'level': 1, + 'help' : 'Pickle collected data for later comparisons.'}), + + ('load-plugins', + {'type' : 'csv', 'metavar' : '<modules>', 'default' : (), + 'level': 1, + 'help' : 'List of plugins (as comma separated values of ' + 'python modules names) to load, usually to register ' + 'additional checkers.'}), + + ('output-format', + {'default': 'text', 'type': 'string', 'metavar' : '<format>', + 'short': 'f', + 'group': 'Reports', + 'help' : 'Set the output format. Available formats are text,' + ' parseable, colorized, msvs (visual studio) and html. You ' + 'can also give a reporter class, eg mypackage.mymodule.' + 'MyReporterClass.'}), + + ('files-output', + {'default': 0, 'type' : 'yn', 'metavar' : '<y_or_n>', + 'group': 'Reports', 'level': 1, + '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]".'}), + + ('reports', + {'default': 1, 'type' : 'yn', 'metavar' : '<y_or_n>', + 'short': 'r', + 'group': 'Reports', + 'help' : 'Tells whether to display a full report or only the ' + 'messages'}), + + ('evaluation', + {'type' : 'string', 'metavar' : '<python_expression>', + 'group': 'Reports', 'level': 1, + '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 ' + 'respectively contain the number of errors / ' + 'warnings messages and the total number of ' + 'statements analyzed. This is used by the global ' + 'evaluation report (RP0004).'}), + + ('comment', + {'default': 0, 'type' : 'yn', 'metavar' : '<y_or_n>', + 'group': 'Reports', 'level': 1, + 'help' : 'Add a comment according to your evaluation note. ' + 'This is used by the global evaluation report (RP0004).'}), + + ('confidence', + {'type' : 'multiple_choice', 'metavar': '<levels>', + 'default': '', + 'choices': [c.name for c in interfaces.CONFIDENCE_LEVELS], + 'group': 'Messages control', + 'help' : 'Only show warnings with the listed confidence levels.' + ' Leave empty to show all. Valid levels: %s' % ( + ', '.join(c.name for c in interfaces.CONFIDENCE_LEVELS),)}), + + ('enable', + {'type' : 'csv', 'metavar': '<msg ids>', + 'short': 'e', + 'group': 'Messages control', + 'help' : 'Enable the message, report, category or checker with the ' + 'given id(s). You can either give multiple identifier ' + 'separated by comma (,) or put this option multiple time. ' + 'See also the "--disable" option for examples. '}), + + ('disable', + {'type' : 'csv', 'metavar': '<msg ids>', + 'short': 'd', + 'group': 'Messages control', + 'help' : 'Disable the message, report, category or checker ' + 'with the given id(s). You can either give multiple identifiers' + ' separated by comma (,) or put this option multiple times ' + '(only on the command line, not in the configuration file ' + 'where it should appear only once).' + 'You can also use "--disable=all" to disable everything first ' + 'and then reenable specific checks. For example, if you want ' + 'to run only the similarities checker, you can use ' + '"--disable=all --enable=similarities". ' + 'If you want to run only the classes checker, but have no ' + 'Warning level messages displayed, use' + '"--disable=all --enable=classes --disable=W"'}), + + ('msg-template', + {'type' : 'string', 'metavar': '<template>', + 'group': 'Reports', + 'help' : ('Template used to display messages. ' + 'This is a python new-style format string ' + 'used to format the message information. ' + 'See doc for all details') + }), + + ('include-ids', _deprecated_option('i', 'yn')), + ('symbols', _deprecated_option('s', 'yn')), + + ('jobs', + {'type' : 'int', 'metavar': '<n-processes>', + 'short': 'j', + 'default': 1, + 'help' : '''Use multiple processes to speed up Pylint.''', + }), + + ('unsafe-load-any-extension', + {'type': 'yn', 'metavar': '<yn>', 'default': False, 'hide': True, + 'help': ('Allow loading of arbitrary C extensions. Extensions' + ' are imported into the active Python interpreter and' + ' may run arbitrary code.')}), + + ('extension-pkg-whitelist', + {'type': 'csv', 'metavar': '<pkg[,pkg]>', 'default': [], + 'help': ('A comma-separated list of package or module names' + ' from where C extensions may be loaded. Extensions are' + ' loading into the active Python interpreter and may run' + ' arbitrary code')} + ), + ) + + option_groups = ( + ('Messages control', 'Options controling analysis messages'), + ('Reports', 'Options related to output formating and reporting'), + ) + + def __init__(self, options=(), reporter=None, option_groups=(), + pylintrc=None): + # some stuff has to be done before ancestors initialization... + # + # messages store / checkers / reporter / astroid manager + self.msgs_store = utils.MessagesStore() + self.reporter = None + self._reporter_name = None + self._reporters = {} + self._checkers = collections.defaultdict(list) + self._pragma_lineno = {} + self._ignore_file = False + # visit variables + self.file_state = utils.FileState() + self.current_name = None + self.current_file = None + self.stats = None + # init options + self._external_opts = options + self.options = options + PyLinter.make_options() + self.option_groups = option_groups + PyLinter.option_groups + self._options_methods = { + 'enable': self.enable, + 'disable': self.disable} + self._bw_options_methods = {'disable-msg': self.disable, + 'enable-msg': self.enable} + full_version = '%%prog %s, \nastroid %s, common %s\nPython %s' % ( + version, astroid_version, common_version, sys.version) + configuration.OptionsManagerMixIn.__init__( + self, usage=__doc__, + version=full_version, + config_file=pylintrc or config.PYLINTRC) + utils.MessagesHandlerMixIn.__init__(self) + utils.ReportsHandlerMixIn.__init__(self) + checkers.BaseTokenChecker.__init__(self) + # provided reports + self.reports = (('RP0001', 'Messages by category', + report_total_messages_stats), + ('RP0002', '% errors / warnings by module', + report_messages_by_module_stats), + ('RP0003', 'Messages', + report_messages_stats), + ('RP0004', 'Global evaluation', + self.report_evaluation), + ) + self.register_checker(self) + self._dynamic_plugins = set() + self.load_provider_defaults() + if reporter: + self.set_reporter(reporter) + + def load_default_plugins(self): + checkers.initialize(self) + reporters.initialize(self) + # Make sure to load the default reporter, because + # the option has been set before the plugins had been loaded. + if not self.reporter: + self._load_reporter() + + 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.add(modname) + module = modutils.load_module_from_name(modname) + module.register(self) + + def _load_reporter(self): + name = self._reporter_name.lower() + if name in self._reporters: + self.set_reporter(self._reporters[name]()) + else: + qname = self._reporter_name + module = modutils.load_module_from_name( + modutils.get_module_part(qname)) + class_name = qname.split('.')[-1] + reporter_class = getattr(module, class_name) + self.set_reporter(reporter_class()) + + def set_reporter(self, reporter): + """set the reporter used to display messages and reports""" + self.reporter = reporter + reporter.linter = self + + def set_option(self, optname, value, action=None, optdict=None): + """overridden from configuration.OptionsProviderMixin to handle some + special options + """ + if optname in self._options_methods or \ + optname in self._bw_options_methods: + if value: + try: + meth = self._options_methods[optname] + except KeyError: + meth = self._bw_options_methods[optname] + warnings.warn('%s is deprecated, replace it by %s' % ( + optname, optname.split('-')[0]), + DeprecationWarning) + value = optik_ext.check_csv(None, optname, value) + if isinstance(value, (list, tuple)): + for _id in value: + meth(_id, ignore_unknown=True) + else: + meth(value) + return # no need to call set_option, disable/enable methods do it + elif optname == 'output-format': + self._reporter_name = value + # If the reporters are already available, load + # the reporter class. + if self._reporters: + self._load_reporter() + + try: + checkers.BaseTokenChecker.set_option(self, optname, + value, action, optdict) + except configuration.UnsupportedAction: + print('option %s can\'t be read from config file' % \ + optname, file=sys.stderr) + + def register_reporter(self, reporter_class): + self._reporters[reporter_class.name] = reporter_class + + def report_order(self): + reports = sorted(self._reports, key=lambda x: getattr(x, 'name', '')) + try: + # Remove the current reporter and add it + # at the end of the list. + reports.pop(reports.index(self)) + except ValueError: + pass + else: + reports.append(self) + return reports + + # checkers manipulation methods ############################################ + + def register_checker(self, checker): + """register a new checker + + checker is an object implementing IRawChecker or / and IAstroidChecker + """ + assert checker.priority <= 0, 'checker priority can\'t be >= 0' + self._checkers[checker.name].append(checker) + for r_id, r_title, r_cb in checker.reports: + self.register_report(r_id, r_title, r_cb, checker) + self.register_options_provider(checker) + if hasattr(checker, 'msgs'): + self.msgs_store.register_messages(checker) + checker.load_defaults() + + # Register the checker, but disable all of its messages. + # TODO(cpopa): we should have a better API for this. + if not getattr(checker, 'enabled', True): + self.disable(checker.name) + + def disable_noerror_messages(self): + for msgcat, msgids in six.iteritems(self.msgs_store._msgs_by_category): + if msgcat == 'E': + for msgid in msgids: + self.enable(msgid) + else: + for msgid in msgids: + self.disable(msgid) + + def disable_reporters(self): + """disable all reporters""" + for reporters in six.itervalues(self._reports): + for report_id, _, _ in reporters: + self.disable_report(report_id) + + def error_mode(self): + """error mode: enable only errors; no reports, no persistent""" + self.disable_noerror_messages() + self.disable('miscellaneous') + self.set_option('reports', False) + self.set_option('persistent', 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 + """ + control_pragmas = {'disable', 'enable'} + for (tok_type, content, start, _, _) in tokens: + if tok_type != tokenize.COMMENT: + continue + match = utils.OPTION_RGX.search(content) + if match is None: + continue + if match.group(1).strip() == "disable-all" or \ + match.group(1).strip() == 'skip-file': + if match.group(1).strip() == "disable-all": + self.add_message('deprecated-pragma', line=start[0], + args=('disable-all', 'skip-file')) + self.add_message('file-ignored', line=start[0]) + self._ignore_file = True + return + try: + opt, value = match.group(1).split('=', 1) + except ValueError: + self.add_message('bad-inline-option', args=match.group(1).strip(), + line=start[0]) + continue + opt = opt.strip() + if opt in self._options_methods or opt in self._bw_options_methods: + try: + meth = self._options_methods[opt] + except KeyError: + meth = self._bw_options_methods[opt] + # found a "(dis|en)able-msg" pragma deprecated suppresssion + self.add_message('deprecated-pragma', line=start[0], args=(opt, opt.replace('-msg', ''))) + for msgid in textutils.splitstrip(value): + # Add the line where a control pragma was encountered. + if opt in control_pragmas: + self._pragma_lineno[msgid] = start[0] + + try: + if (opt, msgid) == ('disable', 'all'): + self.add_message('deprecated-pragma', line=start[0], args=('disable=all', 'skip-file')) + self.add_message('file-ignored', line=start[0]) + self._ignore_file = True + return + meth(msgid, 'module', start[0]) + except utils.UnknownMessage: + self.add_message('bad-option-value', args=msgid, line=start[0]) + else: + self.add_message('unrecognized-inline-option', args=opt, line=start[0]) + + + # code checking methods ################################################### + + def get_checkers(self): + """return all available checkers as a list""" + return [self] + [c for checkers in six.itervalues(self._checkers) + for c in checkers if c is not self] + + def prepare_checkers(self): + """return checkers needed for activated messages and reports""" + if not self.config.reports: + self.disable_reporters() + # get needed checkers + neededcheckers = [self] + for checker in self.get_checkers()[1:]: + # fatal errors should not trigger enable / disabling a checker + messages = set(msg for msg in checker.msgs + if msg[0] != 'F' and self.is_message_enabled(msg)) + if (messages or + any(self.report_is_enabled(r[0]) for r in checker.reports)): + neededcheckers.append(checker) + # Sort checkers by priority + neededcheckers = sorted(neededcheckers, + key=operator.attrgetter('priority'), + reverse=True) + return neededcheckers + + def should_analyze_file(self, modname, path): # pylint: disable=unused-argument, no-self-use + """Returns whether or not a module should be checked. + + This implementation returns True for all python source file, indicating + that all files should be linted. + + Subclasses may override this method to indicate that modules satisfying + certain conditions should not be linted. + + :param str modname: The name of the module to be checked. + :param str path: The full path to the source code of the module. + :returns: True if the module should be checked. + :rtype: bool + """ + return path.endswith('.py') + + def check(self, files_or_modules): + """main checking entry: check a list of files or modules from their + name. + """ + # initialize msgs_state now that all messages have been registered into + # the store + for msg in self.msgs_store.messages: + if not msg.may_be_emitted(): + self._msgs_state[msg.msgid] = False + + if not isinstance(files_or_modules, (list, tuple)): + files_or_modules = (files_or_modules,) + + if self.config.jobs == 1: + self._do_check(files_or_modules) + else: + # Hack that permits running pylint, on Windows, with -m switch + # and with --jobs, as in 'python -2 -m pylint .. --jobs'. + # For more details why this is needed, + # see Python issue http://bugs.python.org/issue10845. + + mock_main = __name__ != '__main__' # -m switch + if mock_main: + sys.modules['__main__'] = sys.modules[__name__] + try: + self._parallel_check(files_or_modules) + finally: + if mock_main: + sys.modules.pop('__main__') + + def _parallel_task(self, files_or_modules): + # Prepare configuration for child linters. + filter_options = {'symbols', 'include-ids', 'long-help'} + filter_options.update([opt_name for opt_name, _ in self._external_opts]) + config = {} + for opt_providers in six.itervalues(self._all_options): + for optname, optdict, val in opt_providers.options_and_values(): + if optname not in filter_options: + config[optname] = configuration.format_option_value(optdict, val) + + childs = [] + manager = multiprocessing.Manager() # pylint: disable=no-member + tasks_queue = manager.Queue() # pylint: disable=no-member + results_queue = manager.Queue() # pylint: disable=no-member + + for _ in range(self.config.jobs): + cl = ChildLinter(args=(tasks_queue, results_queue, config)) + cl.start() # pylint: disable=no-member + childs.append(cl) + + # send files to child linters + for files_or_module in files_or_modules: + tasks_queue.put([files_or_module]) + + # collect results from child linters + failed = False + for _ in files_or_modules: + try: + result = results_queue.get() + except Exception as ex: + print("internal error while receiving results from child linter", + file=sys.stderr) + print(ex, file=sys.stderr) + failed = True + break + yield result + + # Stop child linters and wait for their completion. + for _ in range(self.config.jobs): + tasks_queue.put('STOP') + for cl in childs: + cl.join() + + if failed: + print("Error occured, stopping the linter.", file=sys.stderr) + sys.exit(32) + + def _parallel_check(self, files_or_modules): + # Reset stats. + self.open() + + all_stats = [] + for result in self._parallel_task(files_or_modules): + ( + file_or_module, + self.file_state.base_name, + module, + messages, + stats, + msg_status + ) = result + + if file_or_module == files_or_modules[-1]: + last_module = module + + for msg in messages: + msg = utils.Message(*msg) + self.set_current_module(module) + self.reporter.handle_message(msg) + + all_stats.append(stats) + self.msg_status |= msg_status + + self.stats = _merge_stats(itertools.chain(all_stats, [self.stats])) + self.current_name = last_module + + # Insert stats data to local checkers. + for checker in self.get_checkers(): + if checker is not self: + checker.stats = self.stats + + def _do_check(self, files_or_modules): + walker = utils.PyLintASTWalker(self) + checkers = self.prepare_checkers() + tokencheckers = [c for c in checkers + if interface.implements(c, interfaces.ITokenChecker) + and c is not self] + rawcheckers = [c for c in checkers + if interface.implements(c, interfaces.IRawChecker)] + # notify global begin + for checker in checkers: + checker.open() + if interface.implements(checker, interfaces.IAstroidChecker): + walker.add_checker(checker) + # build ast and check modules or packages + for descr in self.expand_files(files_or_modules): + modname, filepath = descr['name'], descr['path'] + if not descr['isarg'] and not self.should_analyze_file(modname, filepath): + continue + if self.config.files_output: + reportfile = 'pylint_%s.%s' % (modname, self.reporter.extension) + self.reporter.set_output(open(reportfile, 'w')) + self.set_current_module(modname, filepath) + # get the module representation + ast_node = self.get_ast(filepath, modname) + if ast_node is None: + continue + # XXX to be correct we need to keep module_msgs_state for every + # analyzed module (the problem stands with localized messages which + # are only detected in the .close step) + self.file_state = utils.FileState(descr['basename']) + self._ignore_file = False + # fix the current file (if the source file was not available or + # if it's actually a c extension) + self.current_file = ast_node.file # pylint: disable=maybe-no-member + self.check_astroid_module(ast_node, walker, rawcheckers, tokencheckers) + # warn about spurious inline messages handling + for msgid, line, args in self.file_state.iter_spurious_suppression_messages(self.msgs_store): + self.add_message(msgid, line, None, args) + # notify global end + self.stats['statement'] = walker.nbstatements + checkers.reverse() + for checker in checkers: + checker.close() + + def expand_files(self, modules): + """get modules and errors from a list of modules and handle errors + """ + result, errors = utils.expand_modules(modules, self.config.black_list) + for error in errors: + message = modname = error["mod"] + key = error["key"] + self.set_current_module(modname) + if key == "fatal": + message = str(error["ex"]).replace(os.getcwd() + os.sep, '') + self.add_message(key, args=message) + return result + + def set_current_module(self, modname, filepath=None): + """set the name of the currently analyzed module and + init statistics for it + """ + if not modname and filepath is None: + return + self.reporter.on_set_current_module(modname, filepath) + 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 six.itervalues(utils.MSG_TYPES): + self.stats['by_module'][modname][msg_cat] = 0 + + def get_ast(self, filepath, modname): + """return a ast(roid) representation for a module""" + try: + return MANAGER.ast_from_file(filepath, modname, source=True) + except SyntaxError as ex: + self.add_message('syntax-error', line=ex.lineno, args=ex.msg) + except astroid.AstroidBuildingException as ex: + self.add_message('parse-error', args=ex) + except Exception as ex: # pylint: disable=broad-except + import traceback + traceback.print_exc() + self.add_message('astroid-error', args=(ex.__class__, ex)) + + def check_astroid_module(self, ast_node, walker, + rawcheckers, tokencheckers): + """Check a module from its astroid representation.""" + try: + tokens = utils.tokenize_module(ast_node) + except tokenize.TokenError as ex: + self.add_message('syntax-error', line=ex.args[1][0], args=ex.args[0]) + return + + if not ast_node.pure_python: + self.add_message('raw-checker-failed', args=ast_node.name) + else: + #assert astroid.file.endswith('.py') + # invoke ITokenChecker interface on self to fetch module/block + # level options + self.process_tokens(tokens) + if self._ignore_file: + return False + # walk ast to collect line numbers + self.file_state.collect_block_lines(self.msgs_store, ast_node) + # run raw and tokens checkers + for checker in rawcheckers: + checker.process_module(ast_node) + for checker in tokencheckers: + checker.process_tokens(tokens) + # generate events to astroid checkers + walker.walk(ast_node) + return True + + # IAstroidChecker interface ################################################# + + def open(self): + """initialize counters""" + self.stats = {'by_module' : {}, + 'by_msg' : {}, + } + MANAGER.always_load_extensions = self.config.unsafe_load_any_extension + MANAGER.extension_package_whitelist.update( + self.config.extension_pkg_whitelist) + for msg_cat in six.itervalues(utils.MSG_TYPES): + self.stats[msg_cat] = 0 + + def generate_reports(self): + """close the whole package /module, it's time to make reports ! + + if persistent run, pickle results for later comparison + """ + if self.file_state.base_name is not None: + # load previous results if any + previous_stats = config.load_results(self.file_state.base_name) + # XXX code below needs refactoring to be more reporter agnostic + self.reporter.on_close(self.stats, previous_stats) + if self.config.reports: + sect = self.make_reports(self.stats, previous_stats) + if self.config.files_output: + filename = 'pylint_global.' + self.reporter.extension + self.reporter.set_output(open(filename, 'w')) + else: + sect = ureports.Section() + if self.config.reports or self.config.output_format == 'html': + self.reporter.display_results(sect) + # save results if persistent run + if self.config.persistent: + config.save_results(self.stats, self.file_state.base_name) + else: + if self.config.output_format == 'html': + # No output will be emitted for the html + # reporter if the file doesn't exist, so emit + # the results here. + self.reporter.display_results(ureports.Section()) + self.reporter.on_close(self.stats, {}) + + # specific reports ######################################################## + + def report_evaluation(self, sect, stats, previous_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 utils.EmptyReport() + # get a global note for the code + evaluation = self.config.evaluation + try: + note = eval(evaluation, {}, self.stats) # pylint: disable=eval-used + except Exception as ex: # pylint: disable=broad-except + msg = 'An exception occurred while rating: %s' % ex + else: + stats['global_note'] = note + msg = 'Your code has been rated at %.2f/10' % note + pnote = previous_stats.get('global_note') + if pnote is not None: + msg += ' (previous run: %.2f/10, %+.2f)' % (pnote, note - pnote) + if self.config.comment: + msg = '%s\n%s' % (msg, config.get_note_message(note)) + sect.append(ureports.Text(msg)) + +# some reporting functions #################################################### + +def report_total_messages_stats(sect, stats, previous_stats): + """make total errors / warnings report""" + lines = ['type', 'number', 'previous', 'difference'] + lines += checkers.table_lines_from_stats(stats, previous_stats, + ('convention', 'refactor', + 'warning', 'error')) + sect.append(ureports.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 utils.EmptyReport() + in_order = sorted([(value, msg_id) + for msg_id, value in six.iteritems(stats['by_msg']) + if not msg_id.startswith('I')]) + in_order.reverse() + lines = ('message id', 'occurrences') + for value, msg_id in in_order: + lines += (msg_id, str(value)) + sect.append(ureports.Table(children=lines, cols=2, rheaders=1)) + +def report_messages_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 utils.EmptyReport() + by_mod = collections.defaultdict(dict) + for m_type in ('fatal', 'error', 'warning', 'refactor', 'convention'): + total = stats[m_type] + for module in six.iterkeys(stats['by_module']): + mod_total = stats['by_module'][module][m_type] + if total == 0: + percent = 0 + else: + percent = float((mod_total)*100) / total + by_mod[module][m_type] = percent + sorted_result = [] + for module, mod_info in six.iteritems(by_mod): + 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: + # Don't report clean modules. + if all(entry == 0 for entry in line[:-1]): + continue + lines.append(line[-1]) + for val in line[:-1]: + lines.append('%.2f' % val) + if len(lines) == 5: + raise utils.EmptyReport() + sect.append(ureports.Table(children=lines, cols=5, rheaders=1)) + + +# utilities ################################################################### + + +class ArgumentPreprocessingError(Exception): + """Raised if an error occurs during argument preprocessing.""" + + +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 + """ + i = 0 + while i < len(args): + arg = args[i] + if arg.startswith('--'): + try: + option, val = arg[2:].split('=', 1) + except ValueError: + option, val = arg[2:], None + try: + cb, takearg = search_for[option] + except KeyError: + i += 1 + else: + del args[i] + if takearg and val is None: + if i >= len(args) or args[i].startswith('-'): + msg = 'Option %s expects a value' % option + raise ArgumentPreprocessingError(msg) + val = args[i] + del args[i] + elif not takearg and val is not None: + msg = "Option %s doesn't expects a value" % option + raise ArgumentPreprocessingError(msg) + cb(option, val) + else: + i += 1 + + +@contextlib.contextmanager +def fix_import_path(args): + """Prepare sys.path for running the linter checks. + + Within this context, each of the given arguments is importable. + Paths are added to sys.path in corresponding order to the arguments. + We avoid adding duplicate directories to sys.path. + `sys.path` is reset to its original value upon exitign this context. + """ + orig = list(sys.path) + changes = [] + for arg in args: + path = _get_python_path(arg) + if path in changes: + continue + else: + changes.append(path) + sys.path[:] = changes + sys.path + try: + yield + finally: + sys.path[:] = orig + + +class Run(object): + """helper class to use as main for pylint : + + run(*sys.argv[1:]) + """ + LinterClass = PyLinter + option_groups = ( + ('Commands', 'Options which are actually commands. Options in this \ +group are mutually exclusive.'), + ) + + def __init__(self, args, reporter=None, exit=True): + self._rcfile = None + self._plugins = [] + try: + preprocess_options(args, { + # option: (callback, takearg) + 'init-hook': (cb_init_hook, True), + 'rcfile': (self.cb_set_rcfile, True), + 'load-plugins': (self.cb_add_plugins, True), + }) + except ArgumentPreprocessingError as ex: + print(ex, file=sys.stderr) + sys.exit(32) + + self.linter = linter = self.LinterClass(( + ('rcfile', + {'action' : 'callback', 'callback' : lambda *args: 1, + 'type': 'string', 'metavar': '<file>', + 'help' : 'Specify a configuration file.'}), + + ('init-hook', + {'action' : 'callback', 'callback' : lambda *args: 1, + 'type' : 'string', 'metavar': '<code>', + 'level': 1, + 'help' : 'Python code to execute, usually for sys.path ' + 'manipulation such as pygtk.require().'}), + + ('help-msg', + {'action' : 'callback', 'type' : 'string', 'metavar': '<msg-id>', + 'callback' : self.cb_help_message, + 'group': 'Commands', + 'help' : 'Display a help message for the given message id and ' + 'exit. The value may be a comma separated list of message ids.'}), + + ('list-msgs', + {'action' : 'callback', 'metavar': '<msg-id>', + 'callback' : self.cb_list_messages, + 'group': 'Commands', 'level': 1, + 'help' : "Generate pylint's messages."}), + + ('list-conf-levels', + {'action' : 'callback', + 'callback' : cb_list_confidence_levels, + 'group': 'Commands', 'level': 1, + 'help' : "Generate pylint's messages."}), + + ('full-documentation', + {'action' : 'callback', 'metavar': '<msg-id>', + 'callback' : self.cb_full_documentation, + 'group': 'Commands', 'level': 1, + 'help' : "Generate pylint's full documentation."}), + + ('generate-rcfile', + {'action' : 'callback', 'callback' : self.cb_generate_config, + 'group': 'Commands', + 'help' : 'Generate a sample configuration file according to ' + 'the current configuration. You can put other options ' + 'before this one to get them in the generated ' + 'configuration.'}), + + ('generate-man', + {'action' : 'callback', 'callback' : self.cb_generate_manpage, + 'group': 'Commands', + 'help' : "Generate pylint's man page.", 'hide': True}), + + ('errors-only', + {'action' : 'callback', 'callback' : self.cb_error_mode, + 'short': 'E', + 'help' : 'In error mode, checkers without error messages are ' + 'disabled and for others, only the ERROR messages are ' + 'displayed, and no reports are done by default'''}), + + ('py3k', + {'action' : 'callback', 'callback' : self.cb_python3_porting_mode, + 'help' : 'In Python 3 porting mode, all checkers will be ' + 'disabled and only messages emitted by the porting ' + 'checker will be displayed'}), + + ('profile', + {'type' : 'yn', 'metavar' : '<y_or_n>', + 'default': False, 'hide': True, + 'help' : 'Profiled execution.'}), + + ), option_groups=self.option_groups, pylintrc=self._rcfile) + # register standard checkers + linter.load_default_plugins() + # load command line plugins + linter.load_plugin_modules(self._plugins) + # add some help section + linter.add_help_section('Environment variables', config.ENV_HELP, level=1) + # pylint: disable=bad-continuation + linter.add_help_section('Output', +'Using the default text output, the message format is : \n' +' \n' +' MESSAGE_TYPE: LINE_NUM:[OBJECT:] MESSAGE \n' +' \n' +'There are 5 kind of message types : \n' +' * (C) convention, for programming standard violation \n' +' * (R) refactor, for bad code smell \n' +' * (W) warning, for python specific problems \n' +' * (E) error, for probable bugs in the code \n' +' * (F) fatal, if an error occurred which prevented pylint from doing further\n' +'processing.\n' + , level=1) + linter.add_help_section('Output status code', +'Pylint should leave with following status code: \n' +' * 0 if everything went fine \n' +' * 1 if a fatal message was issued \n' +' * 2 if an error message was issued \n' +' * 4 if a warning message was issued \n' +' * 8 if a refactor message was issued \n' +' * 16 if a convention message was issued \n' +' * 32 on usage error \n' +' \n' +'status 1 to 16 will be bit-ORed so you can know which different categories has\n' +'been issued by analysing pylint output status code\n', + level=1) + # read configuration + linter.disable('pointless-except') + linter.disable('suppressed-message') + linter.disable('useless-suppression') + linter.read_config_file() + config_parser = linter.cfgfile_parser + # run init hook, if present, before loading plugins + if config_parser.has_option('MASTER', 'init-hook'): + cb_init_hook('init-hook', + textutils.unquote(config_parser.get('MASTER', + 'init-hook'))) + # is there some additional plugins in the file configuration, in + if config_parser.has_option('MASTER', 'load-plugins'): + plugins = textutils.splitstrip( + 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() + if reporter: + # if a custom reporter is provided as argument, it may be overridden + # by file parameters, so re-set it here, but before command line + # parsing so it's still overrideable by command line option + linter.set_reporter(reporter) + try: + args = linter.load_command_line_configuration(args) + except SystemExit as exc: + if exc.code == 2: # bad options + exc.code = 32 + raise + if not args: + print(linter.help()) + sys.exit(32) + + if linter.config.jobs < 0: + print("Jobs number (%d) should be greater than 0" + % linter.config.jobs, file=sys.stderr) + sys.exit(32) + if linter.config.jobs > 1 or linter.config.jobs == 0: + if multiprocessing is None: + print("Multiprocessing library is missing, " + "fallback to single process", file=sys.stderr) + linter.set_option("jobs", 1) + else: + if linter.config.jobs == 0: + linter.config.jobs = multiprocessing.cpu_count() + + # insert current working directory to the python path to have a correct + # behaviour + with fix_import_path(args): + if self.linter.config.profile: + print('** profiled run', file=sys.stderr) + import cProfile, pstats + cProfile.runctx('linter.check(%r)' % args, globals(), locals(), + 'stones.prof') + data = pstats.Stats('stones.prof') + data.strip_dirs() + data.sort_stats('time', 'calls') + data.print_stats(30) + else: + linter.check(args) + linter.generate_reports() + if exit: + sys.exit(self.linter.msg_status) + + def cb_set_rcfile(self, name, value): + """callback for option preprocessing (i.e. before option parsing)""" + self._rcfile = value + + def cb_add_plugins(self, name, value): + """callback for option preprocessing (i.e. before option parsing)""" + self._plugins.extend(textutils.splitstrip(value)) + + def cb_error_mode(self, *args, **kwargs): + """error mode: + * disable all but error messages + * disable the 'miscellaneous' checker which can be safely deactivated in + debug + * disable reports + * do not save execution information + """ + self.linter.error_mode() + + def cb_generate_config(self, *args, **kwargs): + """optik callback for sample config file generation""" + self.linter.generate_config(skipsections=('COMMANDS',)) + 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, optname, value, parser): + """optik callback for printing some help about a particular message""" + self.linter.msgs_store.help_message(textutils.splitstrip(value)) + sys.exit(0) + + def cb_full_documentation(self, option, optname, value, parser): + """optik callback for printing full documentation""" + self.linter.print_full_documentation() + sys.exit(0) + + def cb_list_messages(self, option, optname, value, parser): # FIXME + """optik callback for printing available messages""" + self.linter.msgs_store.list_messages() + sys.exit(0) + + def cb_python3_porting_mode(self, *args, **kwargs): + """Activate only the python3 porting checker.""" + self.linter.disable('all') + self.linter.enable('python3') + + +def cb_list_confidence_levels(option, optname, value, parser): + for level in interfaces.CONFIDENCE_LEVELS: + print('%-18s: %s' % level) + sys.exit(0) + +def cb_init_hook(optname, value): + """exec arbitrary code to set sys.path for instance""" + exec(value) # pylint: disable=exec-used + + +if __name__ == '__main__': + Run(sys.argv[1:]) diff --git a/pyreverse/__init__.py b/pylint/pyreverse/__init__.py index 8c32ad9..8c32ad9 100644 --- a/pyreverse/__init__.py +++ b/pylint/pyreverse/__init__.py diff --git a/pylint/pyreverse/diadefslib.py b/pylint/pyreverse/diadefslib.py new file mode 100644 index 0000000..e0dc8cf --- /dev/null +++ b/pylint/pyreverse/diadefslib.py @@ -0,0 +1,233 @@ +# Copyright (c) 2000-2013 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +"""handle diagram generation options for class diagram or default diagrams +""" + +from logilab.common.compat import builtins + +import astroid +from astroid.utils import LocalsVisitor + +from pylint.pyreverse.diagrams import PackageDiagram, ClassDiagram + +BUILTINS_NAME = builtins.__name__ + +# diagram generators ########################################################## + +class DiaDefGenerator(object): + """handle diagram generation options""" + + def __init__(self, linker, handler): + """common Diagram Handler initialization""" + self.config = handler.config + self._set_default_options() + self.linker = linker + self.classdiagram = None # defined by subclasses + + def get_title(self, node): + """get title for objects""" + title = node.name + if self.module_names: + title = '%s.%s' % (node.root().name, title) + return title + + def _set_option(self, option): + """activate some options if not explicitly deactivated""" + # if we have a class diagram, we want more information by default; + # so if the option is None, we return True + if option is None: + if self.config.classes: + return True + else: + return False + return option + + def _set_default_options(self): + """set different default options with _default dictionary""" + self.module_names = self._set_option(self.config.module_names) + all_ancestors = self._set_option(self.config.all_ancestors) + all_associated = self._set_option(self.config.all_associated) + anc_level, ass_level = (0, 0) + if all_ancestors: + anc_level = -1 + if all_associated: + ass_level = -1 + if self.config.show_ancestors is not None: + anc_level = self.config.show_ancestors + if self.config.show_associated is not None: + ass_level = self.config.show_associated + self.anc_level, self.ass_level = anc_level, ass_level + + def _get_levels(self): + """help function for search levels""" + return self.anc_level, self.ass_level + + def show_node(self, node): + """true if builtins and not show_builtins""" + if self.config.show_builtin: + return True + return node.root().name != BUILTINS_NAME + + def add_class(self, node): + """visit one class and add it to diagram""" + self.linker.visit(node) + self.classdiagram.add_object(self.get_title(node), node) + + def get_ancestors(self, node, level): + """return ancestor nodes of a class node""" + if level == 0: + return + for ancestor in node.ancestors(recurs=False): + if not self.show_node(ancestor): + continue + yield ancestor + + def get_associated(self, klass_node, level): + """return associated nodes of a class node""" + if level == 0: + return + for ass_nodes in list(klass_node.instance_attrs_type.values()) + \ + list(klass_node.locals_type.values()): + for ass_node in ass_nodes: + if isinstance(ass_node, astroid.Instance): + ass_node = ass_node._proxied + if not (isinstance(ass_node, astroid.Class) + and self.show_node(ass_node)): + continue + yield ass_node + + def extract_classes(self, klass_node, anc_level, ass_level): + """extract recursively classes related to klass_node""" + if self.classdiagram.has_node(klass_node) or not self.show_node(klass_node): + return + self.add_class(klass_node) + + for ancestor in self.get_ancestors(klass_node, anc_level): + self.extract_classes(ancestor, anc_level-1, ass_level) + + for ass_node in self.get_associated(klass_node, ass_level): + self.extract_classes(ass_node, anc_level, ass_level-1) + + +class DefaultDiadefGenerator(LocalsVisitor, DiaDefGenerator): + """generate minimum diagram definition for the project : + + * a package diagram including project's modules + * a class diagram including project's classes + """ + + def __init__(self, linker, handler): + DiaDefGenerator.__init__(self, linker, handler) + LocalsVisitor.__init__(self) + + def visit_project(self, node): + """visit an astroid.Project node + + create a diagram definition for packages + """ + mode = self.config.mode + if len(node.modules) > 1: + self.pkgdiagram = PackageDiagram('packages %s' % node.name, mode) + else: + self.pkgdiagram = None + self.classdiagram = ClassDiagram('classes %s' % node.name, mode) + + def leave_project(self, node): # pylint: disable=unused-argument + """leave the astroid.Project node + + return the generated diagram definition + """ + if self.pkgdiagram: + return self.pkgdiagram, self.classdiagram + return self.classdiagram, + + def visit_module(self, node): + """visit an astroid.Module node + + add this class to the package diagram definition + """ + if self.pkgdiagram: + self.linker.visit(node) + self.pkgdiagram.add_object(node.name, node) + + def visit_class(self, node): + """visit an astroid.Class node + + add this class to the class diagram definition + """ + anc_level, ass_level = self._get_levels() + self.extract_classes(node, anc_level, ass_level) + + def visit_from(self, node): + """visit astroid.From and catch modules for package diagram + """ + if self.pkgdiagram: + self.pkgdiagram.add_from_depend(node, node.modname) + + +class ClassDiadefGenerator(DiaDefGenerator): + """generate a class diagram definition including all classes related to a + given class + """ + + def __init__(self, linker, handler): + DiaDefGenerator.__init__(self, linker, handler) + + def class_diagram(self, project, klass): + """return a class diagram definition for the given klass and its + related klasses + """ + + self.classdiagram = ClassDiagram(klass, self.config.mode) + if len(project.modules) > 1: + module, klass = klass.rsplit('.', 1) + module = project.get_module(module) + else: + module = project.modules[0] + klass = klass.split('.')[-1] + klass = next(module.ilookup(klass)) + + anc_level, ass_level = self._get_levels() + self.extract_classes(klass, anc_level, ass_level) + return self.classdiagram + +# diagram handler ############################################################# + +class DiadefsHandler(object): + """handle diagram definitions : + + get it from user (i.e. xml files) or generate them + """ + + def __init__(self, config): + self.config = config + + def get_diadefs(self, project, linker): + """get the diagrams configuration data + :param linker: astroid.inspector.Linker(IdGeneratorMixIn, LocalsVisitor) + :param project: astroid.manager.Project + """ + + # read and interpret diagram definitions (Diadefs) + diagrams = [] + generator = ClassDiadefGenerator(linker, self) + for klass in self.config.classes: + diagrams.append(generator.class_diagram(project, klass)) + if not diagrams: + diagrams = DefaultDiadefGenerator(linker, self).visit(project) + for diagram in diagrams: + diagram.extract_relationships() + return diagrams diff --git a/pylint/pyreverse/diagrams.py b/pylint/pyreverse/diagrams.py new file mode 100644 index 0000000..f0d7a92 --- /dev/null +++ b/pylint/pyreverse/diagrams.py @@ -0,0 +1,247 @@ +# Copyright (c) 2004-2013 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +"""diagram objects +""" + +import astroid +from pylint.pyreverse.utils import is_interface, FilterMixIn + +class Figure(object): + """base class for counter handling""" + +class Relationship(Figure): + """a relation ship from an object in the diagram to another + """ + def __init__(self, from_object, to_object, relation_type, name=None): + Figure.__init__(self) + self.from_object = from_object + self.to_object = to_object + self.type = relation_type + self.name = name + + +class DiagramEntity(Figure): + """a diagram object, i.e. a label associated to an astroid node + """ + def __init__(self, title='No name', node=None): + Figure.__init__(self) + self.title = title + self.node = node + +class ClassDiagram(Figure, FilterMixIn): + """main class diagram handling + """ + TYPE = 'class' + def __init__(self, title, mode): + FilterMixIn.__init__(self, mode) + Figure.__init__(self) + self.title = title + self.objects = [] + self.relationships = {} + self._nodes = {} + self.depends = [] + + def get_relationships(self, role): + # sorted to get predictable (hence testable) results + return sorted(self.relationships.get(role, ()), + key=lambda x: (x.from_object.fig_id, x.to_object.fig_id)) + + def add_relationship(self, from_object, to_object, + relation_type, name=None): + """create a relation ship + """ + rel = Relationship(from_object, to_object, relation_type, name) + self.relationships.setdefault(relation_type, []).append(rel) + + def get_relationship(self, from_object, relation_type): + """return a relation ship or None + """ + for rel in self.relationships.get(relation_type, ()): + if rel.from_object is from_object: + return rel + raise KeyError(relation_type) + + def get_attrs(self, node): + """return visible attributes, possibly with class name""" + attrs = [] + for node_name, ass_nodes in list(node.instance_attrs_type.items()) + \ + list(node.locals_type.items()): + if not self.show_attr(node_name): + continue + names = self.class_names(ass_nodes) + if names: + node_name = "%s : %s" % (node_name, ", ".join(names)) + attrs.append(node_name) + return sorted(attrs) + + def get_methods(self, node): + """return visible methods""" + methods = [ + m for m in node.values() + if isinstance(m, astroid.Function) and self.show_attr(m.name) + ] + return sorted(methods, key=lambda n: n.name) + + def add_object(self, title, node): + """create a diagram object + """ + assert node not in self._nodes + ent = DiagramEntity(title, node) + self._nodes[node] = ent + self.objects.append(ent) + + def class_names(self, nodes): + """return class names if needed in diagram""" + names = [] + for ass_node in nodes: + if isinstance(ass_node, astroid.Instance): + ass_node = ass_node._proxied + if isinstance(ass_node, astroid.Class) \ + and hasattr(ass_node, "name") and not self.has_node(ass_node): + if ass_node.name not in names: + ass_name = ass_node.name + names.append(ass_name) + return names + + def nodes(self): + """return the list of underlying nodes + """ + return self._nodes.keys() + + def has_node(self, node): + """return true if the given node is included in the diagram + """ + return node in self._nodes + + def object_from_node(self, node): + """return the diagram object mapped to node + """ + return self._nodes[node] + + def classes(self): + """return all class nodes in the diagram""" + return [o for o in self.objects if isinstance(o.node, astroid.Class)] + + def classe(self, name): + """return a class by its name, raise KeyError if not found + """ + for klass in self.classes(): + if klass.node.name == name: + return klass + raise KeyError(name) + + def extract_relationships(self): + """extract relation ships between nodes in the diagram + """ + for obj in self.classes(): + node = obj.node + obj.attrs = self.get_attrs(node) + obj.methods = self.get_methods(node) + # shape + if is_interface(node): + obj.shape = 'interface' + else: + obj.shape = 'class' + # inheritance link + for par_node in node.ancestors(recurs=False): + try: + par_obj = self.object_from_node(par_node) + self.add_relationship(obj, par_obj, 'specialization') + except KeyError: + continue + # implements link + for impl_node in node.implements: + try: + impl_obj = self.object_from_node(impl_node) + self.add_relationship(obj, impl_obj, 'implements') + except KeyError: + continue + # associations link + for name, values in list(node.instance_attrs_type.items()) + \ + list(node.locals_type.items()): + for value in values: + if value is astroid.YES: + continue + if isinstance(value, astroid.Instance): + value = value._proxied + try: + ass_obj = self.object_from_node(value) + self.add_relationship(ass_obj, obj, 'association', name) + except KeyError: + continue + + +class PackageDiagram(ClassDiagram): + """package diagram handling + """ + TYPE = 'package' + + def modules(self): + """return all module nodes in the diagram""" + return [o for o in self.objects if isinstance(o.node, astroid.Module)] + + def module(self, name): + """return a module by its name, raise KeyError if not found + """ + for mod in self.modules(): + if mod.node.name == name: + return mod + raise KeyError(name) + + def get_module(self, name, node): + """return a module by its name, looking also for relative imports; + raise KeyError if not found + """ + for mod in self.modules(): + mod_name = mod.node.name + if mod_name == name: + return mod + #search for fullname of relative import modules + package = node.root().name + if mod_name == "%s.%s" % (package, name): + return mod + if mod_name == "%s.%s" % (package.rsplit('.', 1)[0], name): + return mod + raise KeyError(name) + + def add_from_depend(self, node, from_module): + """add dependencies created by from-imports + """ + mod_name = node.root().name + obj = self.module(mod_name) + if from_module not in obj.node.depends: + obj.node.depends.append(from_module) + + def extract_relationships(self): + """extract relation ships between nodes in the diagram + """ + ClassDiagram.extract_relationships(self) + for obj in self.classes(): + # ownership + try: + mod = self.object_from_node(obj.node.root()) + self.add_relationship(obj, mod, 'ownership') + except KeyError: + continue + for obj in self.modules(): + obj.shape = 'package' + # dependencies + for dep_name in obj.node.depends: + try: + dep = self.get_module(dep_name, obj.node) + except KeyError: + continue + self.add_relationship(obj, dep, 'depends') diff --git a/pylint/pyreverse/main.py b/pylint/pyreverse/main.py new file mode 100644 index 0000000..408c172 --- /dev/null +++ b/pylint/pyreverse/main.py @@ -0,0 +1,124 @@ +# # Copyright (c) 2000-2013 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +""" + %prog [options] <packages> + + create UML diagrams for classes and modules in <packages> +""" +from __future__ import print_function + +import sys, os +from logilab.common.configuration import ConfigurationMixIn +from astroid.manager import AstroidManager +from astroid.inspector import Linker + +from pylint.pyreverse.diadefslib import DiadefsHandler +from pylint.pyreverse import writer +from pylint.pyreverse.utils import insert_default_options + +OPTIONS = ( + ("filter-mode", + dict(short='f', default='PUB_ONLY', dest='mode', type='string', + action='store', metavar='<mode>', + help="""filter attributes and functions according to + <mode>. Correct modes are : + 'PUB_ONLY' filter all non public attributes + [DEFAULT], equivalent to PRIVATE+SPECIAL_A + 'ALL' no filter + 'SPECIAL' filter Python special functions + except constructor + 'OTHER' filter protected and private + attributes""")), + + ("class", + dict(short='c', action="append", metavar="<class>", dest="classes", default=[], + help="create a class diagram with all classes related to <class>;\ + this uses by default the options -ASmy")), + + ("show-ancestors", + dict(short="a", action="store", metavar='<ancestor>', type='int', + help='show <ancestor> generations of ancestor classes not in <projects>')), + ("all-ancestors", + dict(short="A", default=None, + help="show all ancestors off all classes in <projects>")), + ("show-associated", + dict(short='s', action="store", metavar='<ass_level>', type='int', + help='show <ass_level> levels of associated classes not in <projects>')), + ("all-associated", + dict(short='S', default=None, + help='show recursively all associated off all associated classes')), + ("show-builtin", + dict(short="b", action="store_true", default=False, + help='include builtin objects in representation of classes')), + + ("module-names", + dict(short="m", default=None, type='yn', metavar='[yn]', + help='include module name in representation of classes')), + # TODO : generate dependencies like in pylint + # ("package-dependencies", + # dict(short="M", action="store", metavar='<package_depth>', type='int', + # help='show <package_depth> module dependencies beyond modules in \ + # <projects> (for the package diagram)')), + ("only-classnames", + dict(short='k', action="store_true", default=False, + help="don't show attributes and methods in the class boxes; \ +this disables -f values")), + ("output", dict(short="o", dest="output_format", action="store", + default="dot", metavar="<format>", + help="create a *.<format> output file if format available.")), +) +# FIXME : quiet mode +#( ('quiet', + #dict(help='run quietly', action='store_true', short='q')), ) + +class Run(ConfigurationMixIn): + """base class providing common behaviour for pyreverse commands""" + + options = OPTIONS + + def __init__(self, args): + ConfigurationMixIn.__init__(self, usage=__doc__) + insert_default_options() + self.manager = AstroidManager() + self.register_options_provider(self.manager) + args = self.load_command_line_configuration() + sys.exit(self.run(args)) + + def run(self, args): + """checking arguments and run project""" + if not args: + print(self.help()) + return 1 + # insert current working directory to the python path to recognize + # dependencies to local modules even if cwd is not in the PYTHONPATH + sys.path.insert(0, os.getcwd()) + try: + project = self.manager.project_from_files(args) + linker = Linker(project, tag=True) + handler = DiadefsHandler(self.config) + diadefs = handler.get_diadefs(project, linker) + finally: + sys.path.pop(0) + + if self.config.output_format == "vcg": + writer.VCGWriter(self.config).write(diadefs) + else: + writer.DotWriter(self.config).write(diadefs) + return 0 + + +if __name__ == '__main__': + Run(sys.argv[1:]) diff --git a/pylint/pyreverse/utils.py b/pylint/pyreverse/utils.py new file mode 100644 index 0000000..5d6d133 --- /dev/null +++ b/pylint/pyreverse/utils.py @@ -0,0 +1,132 @@ +# Copyright (c) 2002-2013 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +""" +generic classes/functions for pyreverse core/extensions +""" +from __future__ import print_function + +import sys +import re +import os + +########### pyreverse option utils ############################## + + +RCFILE = '.pyreverserc' + +def get_default_options(): + """ + Read config file and return list of options + """ + options = [] + home = os.environ.get('HOME', '') + if home: + rcfile = os.path.join(home, RCFILE) + try: + options = open(rcfile).read().split() + except IOError: + pass # ignore if no config file found + return options + +def insert_default_options(): + """insert default options to sys.argv + """ + options = get_default_options() + options.reverse() + for arg in options: + sys.argv.insert(1, arg) + + + +# astroid utilities ########################################################### + +SPECIAL = re.compile('^__[A-Za-z0-9]+[A-Za-z0-9_]*__$') +PRIVATE = re.compile('^__[_A-Za-z0-9]*[A-Za-z0-9]+_?$') +PROTECTED = re.compile('^_[_A-Za-z0-9]*$') + +def get_visibility(name): + """return the visibility from a name: public, protected, private or special + """ + if SPECIAL.match(name): + visibility = 'special' + elif PRIVATE.match(name): + visibility = 'private' + elif PROTECTED.match(name): + visibility = 'protected' + + else: + visibility = 'public' + return visibility + +ABSTRACT = re.compile('^.*Abstract.*') +FINAL = re.compile('^[A-Z_]*$') + +def is_abstract(node): + """return true if the given class node correspond to an abstract class + definition + """ + return ABSTRACT.match(node.name) + +def is_final(node): + """return true if the given class/function node correspond to final + definition + """ + return FINAL.match(node.name) + +def is_interface(node): + # bw compat + return node.type == 'interface' + +def is_exception(node): + # bw compat + return node.type == 'exception' + + +# Helpers ##################################################################### + +_CONSTRUCTOR = 1 +_SPECIAL = 2 +_PROTECTED = 4 +_PRIVATE = 8 +MODES = { + 'ALL' : 0, + 'PUB_ONLY' : _SPECIAL + _PROTECTED + _PRIVATE, + 'SPECIAL' : _SPECIAL, + 'OTHER' : _PROTECTED + _PRIVATE, +} +VIS_MOD = {'special': _SPECIAL, 'protected': _PROTECTED, + 'private': _PRIVATE, 'public': 0} + +class FilterMixIn(object): + """filter nodes according to a mode and nodes' visibility + """ + def __init__(self, mode): + "init filter modes" + __mode = 0 + for nummod in mode.split('+'): + try: + __mode += MODES[nummod] + except KeyError as ex: + print('Unknown filter mode %s' % ex, file=sys.stderr) + self.__mode = __mode + + + def show_attr(self, node): + """return true if the node should be treated + """ + visibility = get_visibility(getattr(node, 'name', node)) + return not self.__mode & VIS_MOD[visibility] + diff --git a/pylint/pyreverse/writer.py b/pylint/pyreverse/writer.py new file mode 100644 index 0000000..8628a8c --- /dev/null +++ b/pylint/pyreverse/writer.py @@ -0,0 +1,199 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2008-2013 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +"""Utilities for creating VCG and Dot diagrams""" + +from logilab.common.vcgutils import VCGPrinter +from logilab.common.graph import DotBackend + +from pylint.pyreverse.utils import is_exception + +class DiagramWriter(object): + """base class for writing project diagrams + """ + def __init__(self, config, styles): + self.config = config + self.pkg_edges, self.inh_edges, self.imp_edges, self.ass_edges = styles + self.printer = None # defined in set_printer + + def write(self, diadefs): + """write files for <project> according to <diadefs> + """ + for diagram in diadefs: + basename = diagram.title.strip().replace(' ', '_') + file_name = '%s.%s' % (basename, self.config.output_format) + self.set_printer(file_name, basename) + if diagram.TYPE == 'class': + self.write_classes(diagram) + else: + self.write_packages(diagram) + self.close_graph() + + def write_packages(self, diagram): + """write a package diagram""" + # sorted to get predictable (hence testable) results + for i, obj in enumerate(sorted(diagram.modules(), key=lambda x: x.title)): + self.printer.emit_node(i, label=self.get_title(obj), shape='box') + obj.fig_id = i + # package dependencies + for rel in diagram.get_relationships('depends'): + self.printer.emit_edge(rel.from_object.fig_id, rel.to_object.fig_id, + **self.pkg_edges) + + def write_classes(self, diagram): + """write a class diagram""" + # sorted to get predictable (hence testable) results + for i, obj in enumerate(sorted(diagram.objects, key=lambda x: x.title)): + self.printer.emit_node(i, **self.get_values(obj)) + obj.fig_id = i + # inheritance links + for rel in diagram.get_relationships('specialization'): + self.printer.emit_edge(rel.from_object.fig_id, rel.to_object.fig_id, + **self.inh_edges) + # implementation links + for rel in diagram.get_relationships('implements'): + self.printer.emit_edge(rel.from_object.fig_id, rel.to_object.fig_id, + **self.imp_edges) + # generate associations + for rel in diagram.get_relationships('association'): + self.printer.emit_edge(rel.from_object.fig_id, rel.to_object.fig_id, + label=rel.name, **self.ass_edges) + + def set_printer(self, file_name, basename): + """set printer""" + raise NotImplementedError + + def get_title(self, obj): + """get project title""" + raise NotImplementedError + + def get_values(self, obj): + """get label and shape for classes.""" + raise NotImplementedError + + def close_graph(self): + """finalize the graph""" + raise NotImplementedError + + +class DotWriter(DiagramWriter): + """write dot graphs from a diagram definition and a project + """ + + def __init__(self, config): + styles = [dict(arrowtail='none', arrowhead="open"), + dict(arrowtail='none', arrowhead='empty'), + dict(arrowtail='node', arrowhead='empty', style='dashed'), + dict(fontcolor='green', arrowtail='none', + arrowhead='diamond', style='solid'), + ] + DiagramWriter.__init__(self, config, styles) + + def set_printer(self, file_name, basename): + """initialize DotWriter and add options for layout. + """ + layout = dict(rankdir="BT") + self.printer = DotBackend(basename, additionnal_param=layout) + self.file_name = file_name + + def get_title(self, obj): + """get project title""" + return obj.title + + def get_values(self, obj): + """get label and shape for classes. + + The label contains all attributes and methods + """ + label = obj.title + if obj.shape == 'interface': + label = u'«interface»\\n%s' % label + if not self.config.only_classnames: + label = r'%s|%s\l|' % (label, r'\l'.join(obj.attrs)) + for func in obj.methods: + label = r'%s%s()\l' % (label, func.name) + label = '{%s}' % label + if is_exception(obj.node): + return dict(fontcolor='red', label=label, shape='record') + return dict(label=label, shape='record') + + def close_graph(self): + """print the dot graph into <file_name>""" + self.printer.generate(self.file_name) + + +class VCGWriter(DiagramWriter): + """write vcg graphs from a diagram definition and a project + """ + def __init__(self, config): + styles = [dict(arrowstyle='solid', backarrowstyle='none', + backarrowsize=0), + dict(arrowstyle='solid', backarrowstyle='none', + backarrowsize=10), + dict(arrowstyle='solid', backarrowstyle='none', + linestyle='dotted', backarrowsize=10), + dict(arrowstyle='solid', backarrowstyle='none', + textcolor='green'), + ] + DiagramWriter.__init__(self, config, styles) + + def set_printer(self, file_name, basename): + """initialize VCGWriter for a UML graph""" + self.graph_file = open(file_name, 'w+') + self.printer = VCGPrinter(self.graph_file) + self.printer.open_graph(title=basename, layoutalgorithm='dfs', + late_edge_labels='yes', port_sharing='no', + manhattan_edges='yes') + self.printer.emit_node = self.printer.node + self.printer.emit_edge = self.printer.edge + + def get_title(self, obj): + """get project title in vcg format""" + return r'\fb%s\fn' % obj.title + + def get_values(self, obj): + """get label and shape for classes. + + The label contains all attributes and methods + """ + if is_exception(obj.node): + label = r'\fb\f09%s\fn' % obj.title + else: + label = r'\fb%s\fn' % obj.title + if obj.shape == 'interface': + shape = 'ellipse' + else: + shape = 'box' + if not self.config.only_classnames: + attrs = obj.attrs + methods = [func.name for func in obj.methods] + # box width for UML like diagram + maxlen = max(len(name) for name in [obj.title] + methods + attrs) + line = '_' * (maxlen + 2) + label = r'%s\n\f%s' % (label, line) + for attr in attrs: + label = r'%s\n\f08%s' % (label, attr) + if attrs: + label = r'%s\n\f%s' % (label, line) + for func in methods: + label = r'%s\n\f10%s()' % (label, func) + return dict(label=label, shape=shape) + + def close_graph(self): + """close graph and file""" + self.printer.close_graph() + self.graph_file.close() + diff --git a/pylint/reporters/__init__.py b/pylint/reporters/__init__.py new file mode 100644 index 0000000..ea3281f --- /dev/null +++ b/pylint/reporters/__init__.py @@ -0,0 +1,133 @@ +# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +"""utilities methods and classes for reporters""" +from __future__ import print_function + +import sys +import locale +import os + + +from pylint import utils + +CMPS = ['=', '-', '+'] + +# py3k has no more cmp builtin +if sys.version_info >= (3, 0): + def cmp(a, b): # pylint: disable=redefined-builtin + return (a > b) - (a < b) + +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 BaseReporter(object): + """base class for reporters + + symbols: show short symbolic names for messages. + """ + + extension = '' + + def __init__(self, output=None): + self.linter = None + # self.include_ids = None # Deprecated + # self.symbols = None # Deprecated + self.section = 0 + self.out = None + self.out_encoding = None + self.encode = None + self.set_output(output) + # Build the path prefix to strip to get relative paths + self.path_strip_prefix = os.getcwd() + os.sep + + def handle_message(self, msg): + """Handle a new message triggered on the current file. + + Invokes the legacy add_message API by default.""" + self.add_message( + msg.msg_id, (msg.abspath, msg.module, msg.obj, msg.line, msg.column), + msg.msg) + + def add_message(self, msg_id, location, msg): + """Deprecated, do not use.""" + raise NotImplementedError + + def set_output(self, output=None): + """set output stream""" + self.out = output or sys.stdout + # py3k streams handle their encoding : + if sys.version_info >= (3, 0): + self.encode = lambda x: x + return + + def encode(string): + if not isinstance(string, unicode): + return string + encoding = (getattr(self.out, 'encoding', None) or + locale.getdefaultlocale()[1] or + sys.getdefaultencoding()) + # errors=replace, we don't want to crash when attempting to show + # source code line that can't be encoded with the current locale + # settings + return string.encode(encoding, 'replace') + self.encode = encode + + def writeln(self, string=''): + """write a line in the output buffer""" + print(self.encode(string), file=self.out) + + def display_results(self, layout): + """display results encapsulated in the layout tree""" + self.section = 0 + if 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() + + # Event callbacks + + def on_set_current_module(self, module, filepath): + """starting analyzis of a module""" + pass + + def on_close(self, stats, previous_stats): + """global end of analyzis""" + pass + + +class CollectingReporter(BaseReporter): + """collects messages""" + + name = 'collector' + + def __init__(self): + BaseReporter.__init__(self) + self.messages = [] + + def handle_message(self, msg): + self.messages.append(msg) + + +def initialize(linter): + """initialize linter with reporters in this package """ + utils.register_plugins(linter, __path__[0]) diff --git a/pylint/reporters/guireporter.py b/pylint/reporters/guireporter.py new file mode 100644 index 0000000..4ad4ebb --- /dev/null +++ b/pylint/reporters/guireporter.py @@ -0,0 +1,27 @@ +""" reporter used by gui.py """ + +import sys + +from pylint.interfaces import IReporter +from pylint.reporters import BaseReporter +from logilab.common.ureports import TextWriter + + +class GUIReporter(BaseReporter): + """saves messages""" + + __implements__ = IReporter + extension = '' + + def __init__(self, gui, output=sys.stdout): + """init""" + BaseReporter.__init__(self, output) + self.gui = gui + + def handle_message(self, msg): + """manage message of different type and in the context of path""" + self.gui.msg_queue.put(msg) + + def _display(self, layout): + """launch layouts display""" + TextWriter().format(layout, self.out) diff --git a/pylint/reporters/html.py b/pylint/reporters/html.py new file mode 100644 index 0000000..1e050d3 --- /dev/null +++ b/pylint/reporters/html.py @@ -0,0 +1,101 @@ +# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +"""HTML reporter""" + +import itertools +import string +import sys + +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 + name = 'html' + extension = 'html' + + def __init__(self, output=sys.stdout): + BaseReporter.__init__(self, output) + self.msgs = [] + # Add placeholders for title and parsed messages + self.header = None + self.msgargs = [] + + @staticmethod + def _parse_msg_template(msg_template): + formatter = string.Formatter() + parsed = formatter.parse(msg_template) + for item in parsed: + if item[1]: + yield item[1] + + def _parse_template(self): + """Helper function to parse the message template""" + self.header = [] + if self.linter.config.msg_template: + msg_template = self.linter.config.msg_template + else: + msg_template = '{category}{module}{obj}{line}{column}{msg}' + + _header, _msgs = itertools.tee(self._parse_msg_template(msg_template)) + self.header = list(_header) + self.msgargs = list(_msgs) + + def handle_message(self, msg): + """manage message of different type and in the context of path""" + + # It would be better to do this in init, but currently we do not + # have access to the linter (as it is setup in lint.set_reporter() + # Therefore we try to parse just the once. + if self.header is None: + self._parse_template() + + # We want to add the lines given by the template + self.msgs += [str(getattr(msg, field)) for field in self.msgargs] + + def set_output(self, output=None): + """set output stream + + messages buffered for old output is processed first""" + if self.out and self.msgs: + self._display(Section()) + BaseReporter.set_output(self, output) + + def _display(self, layout): + """launch layouts display + + overridden from BaseReporter to add insert the messages section + (in add_message, message is not displayed, just collected so it + can be displayed in an html table) + """ + if self.msgs: + # add stored messages to the layout + msgs = self.header + cols = len(self.header) + msgs += self.msgs + sect = Section('Messages') + layout.append(sect) + sect.append(Table(cols=cols, children=msgs, rheaders=1)) + self.msgs = [] + HTMLWriter().format(layout, self.out) + + +def register(linter): + """Register the reporter classes with the linter.""" + linter.register_reporter(HTMLReporter) diff --git a/pylint/reporters/json.py b/pylint/reporters/json.py new file mode 100644 index 0000000..7dba52b --- /dev/null +++ b/pylint/reporters/json.py @@ -0,0 +1,58 @@ +# Copyright (c) 2003-2014 LOGILAB S.A. (Paris, FRANCE). +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +"""JSON reporter""" +from __future__ import absolute_import, print_function + +import json +import sys +from cgi import escape + +from pylint.interfaces import IReporter +from pylint.reporters import BaseReporter + + +class JSONReporter(BaseReporter): + """Report messages and layouts in JSON.""" + + __implements__ = IReporter + name = 'json' + extension = 'json' + + def __init__(self, output=sys.stdout): + BaseReporter.__init__(self, output) + self.messages = [] + + def handle_message(self, message): + """Manage message of different type and in the context of path.""" + + self.messages.append({ + 'type': message.category, + 'module': message.module, + 'obj': message.obj, + 'line': message.line, + 'column': message.column, + 'path': message.path, + 'symbol': message.symbol, + 'message': escape(message.msg or ''), + }) + + def _display(self, layout): + """Launch layouts display""" + if self.messages: + print(json.dumps(self.messages, indent=4), file=self.out) + + +def register(linter): + """Register the reporter classes with the linter.""" + linter.register_reporter(JSONReporter) diff --git a/pylint/reporters/text.py b/pylint/reporters/text.py new file mode 100644 index 0000000..53c4a8d --- /dev/null +++ b/pylint/reporters/text.py @@ -0,0 +1,146 @@ +# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +"""Plain text reporters: + +:text: the default one grouping messages by module +:colorized: an ANSI colorized text reporter +""" +from __future__ import print_function + +import warnings + +from logilab.common.ureports import TextWriter +from logilab.common.textutils import colorize_ansi + +from pylint.interfaces import IReporter +from pylint.reporters import BaseReporter +import six + +TITLE_UNDERLINES = ['', '=', '-', '.'] + + +class TextReporter(BaseReporter): + """reports messages and layouts in plain text""" + + __implements__ = IReporter + name = 'text' + extension = 'txt' + line_format = '{C}:{line:3d},{column:2d}: {msg} ({symbol})' + + def __init__(self, output=None): + BaseReporter.__init__(self, output) + self._modules = set() + self._template = None + + def on_set_current_module(self, module, filepath): + self._template = six.text_type(self.linter.config.msg_template or self.line_format) + + def write_message(self, msg): + """Convenience method to write a formated message with class default template""" + self.writeln(msg.format(self._template)) + + def handle_message(self, msg): + """manage message of different type and in the context of path""" + if msg.module not in self._modules: + if msg.module: + self.writeln('************* Module %s' % msg.module) + self._modules.add(msg.module) + else: + self.writeln('************* ') + self.write_message(msg) + + def _display(self, layout): + """launch layouts display""" + print(file=self.out) + TextWriter().format(layout, self.out) + + +class ParseableTextReporter(TextReporter): + """a reporter very similar to TextReporter, but display messages in a form + recognized by most text editors : + + <filename>:<linenum>:<msg> + """ + name = 'parseable' + line_format = '{path}:{line}: [{msg_id}({symbol}), {obj}] {msg}' + + def __init__(self, output=None): + warnings.warn('%s output format is deprecated. This is equivalent ' + 'to --msg-template=%s' % (self.name, self.line_format), + DeprecationWarning) + TextReporter.__init__(self, output) + + +class VSTextReporter(ParseableTextReporter): + """Visual studio text reporter""" + name = 'msvs' + line_format = '{path}({line}): [{msg_id}({symbol}){obj}] {msg}' + + +class ColorizedTextReporter(TextReporter): + """Simple TextReporter that colorizes text output""" + + name = 'colorized' + 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=None, 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[0]] + except KeyError: + return None, None + + def handle_message(self, msg): + """manage message of different types, and colorize output + using ansi escape codes + """ + if msg.module not in self._modules: + color, style = self._get_decoration('S') + if msg.module: + modsep = colorize_ansi('************* Module %s' % msg.module, + color, style) + else: + modsep = colorize_ansi('************* %s' % msg.module, + color, style) + self.writeln(modsep) + self._modules.add(msg.module) + color, style = self._get_decoration(msg.C) + + msg = msg._replace( + **{attr: colorize_ansi(getattr(msg, attr), color, style) + for attr in ('msg', 'symbol', 'category', 'C')}) + self.write_message(msg) + + +def register(linter): + """Register the reporter classes with the linter.""" + linter.register_reporter(TextReporter) + linter.register_reporter(ParseableTextReporter) + linter.register_reporter(VSTextReporter) + linter.register_reporter(ColorizedTextReporter) diff --git a/test/data/__init__.py b/pylint/test/data/__init__.py index e69de29..e69de29 100644 --- a/test/data/__init__.py +++ b/pylint/test/data/__init__.py diff --git a/pylint/test/data/ascript b/pylint/test/data/ascript new file mode 100755 index 0000000..f401ebc --- /dev/null +++ b/pylint/test/data/ascript @@ -0,0 +1,2 @@ +#!/usr/bin/python +"""ttttttttttttttttttttoooooooooooooooooooooooooooooooooooooooooooooooooooooo lllllllllllooooooooooooooooooooooooooooonnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnngggggggggggggggggg""" diff --git a/test/data/classes_No_Name.dot b/pylint/test/data/classes_No_Name.dot index 51b42e7..51b42e7 100644 --- a/test/data/classes_No_Name.dot +++ b/pylint/test/data/classes_No_Name.dot diff --git a/test/data/clientmodule_test.py b/pylint/test/data/clientmodule_test.py index e520e88..e520e88 100644 --- a/test/data/clientmodule_test.py +++ b/pylint/test/data/clientmodule_test.py diff --git a/test/data/packages_No_Name.dot b/pylint/test/data/packages_No_Name.dot index 1ceeb72..1ceeb72 100644 --- a/test/data/packages_No_Name.dot +++ b/pylint/test/data/packages_No_Name.dot diff --git a/test/data/suppliermodule_test.py b/pylint/test/data/suppliermodule_test.py index 24dc9a0..24dc9a0 100644 --- a/test/data/suppliermodule_test.py +++ b/pylint/test/data/suppliermodule_test.py diff --git a/test/input/func_empty_module.py b/pylint/test/functional/__init__.py index e69de29..e69de29 100644 --- a/test/input/func_empty_module.py +++ b/pylint/test/functional/__init__.py diff --git a/pylint/test/functional/abstract_abc_methods.py b/pylint/test/functional/abstract_abc_methods.py new file mode 100644 index 0000000..6a21d90 --- /dev/null +++ b/pylint/test/functional/abstract_abc_methods.py @@ -0,0 +1,17 @@ +""" This should not warn about `prop` being abstract in Child """
+# pylint: disable=too-few-public-methods,abstract-class-little-used,no-absolute-import,metaclass-assignment
+
+import abc
+
+class Parent(object):
+ """Abstract Base Class """
+ __metaclass__ = abc.ABCMeta
+
+ @property
+ @abc.abstractmethod
+ def prop(self):
+ """ Abstract """
+
+class Child(Parent):
+ """ No warning for the following. """
+ prop = property(lambda self: 1)
diff --git a/pylint/test/functional/abstract_class_instantiated_py2.py b/pylint/test/functional/abstract_class_instantiated_py2.py new file mode 100644 index 0000000..670abd4 --- /dev/null +++ b/pylint/test/functional/abstract_class_instantiated_py2.py @@ -0,0 +1,68 @@ +"""Check that instantiating a class with +`abc.ABCMeta` as metaclass fails if it defines +abstract methods. +""" + +# pylint: disable=too-few-public-methods, missing-docstring, abstract-class-not-used +# pylint: disable=no-absolute-import, metaclass-assignment, abstract-class-little-used +# pylint: disable=abstract-method + +__revision__ = 0 + +import abc +from abc import ABCMeta + +class GoodClass(object): + __metaclass__ = abc.ABCMeta + +class SecondGoodClass(object): + __metaclass__ = abc.ABCMeta + + def test(self): + """ do nothing. """ + +class ThirdGoodClass(object): + __metaclass__ = abc.ABCMeta + + def test(self): + raise NotImplementedError() + +class FourthGoodClass(object): + __metaclass__ = ABCMeta + +class BadClass(object): + __metaclass__ = abc.ABCMeta + + @abc.abstractmethod + def test(self): + """ do nothing. """ + +class SecondBadClass(object): + __metaclass__ = abc.ABCMeta + + @property + @abc.abstractmethod + def test(self): + """ do nothing. """ + +class ThirdBadClass(object): + __metaclass__ = ABCMeta + + @abc.abstractmethod + def test(self): + pass + +class FourthBadClass(ThirdBadClass): + pass + + +def main(): + """ do nothing """ + GoodClass() + SecondGoodClass() + ThirdGoodClass() + FourthGoodClass() + BadClass() # [abstract-class-instantiated] + SecondBadClass() # [abstract-class-instantiated] + ThirdBadClass() # [abstract-class-instantiated] + FourthBadClass() # [abstract-class-instantiated] diff --git a/pylint/test/functional/abstract_class_instantiated_py2.rc b/pylint/test/functional/abstract_class_instantiated_py2.rc new file mode 100644 index 0000000..b11e16d --- /dev/null +++ b/pylint/test/functional/abstract_class_instantiated_py2.rc @@ -0,0 +1,2 @@ +[testoptions] +max_pyver=3.0
\ No newline at end of file diff --git a/pylint/test/functional/abstract_class_instantiated_py2.txt b/pylint/test/functional/abstract_class_instantiated_py2.txt new file mode 100644 index 0000000..68e7eb5 --- /dev/null +++ b/pylint/test/functional/abstract_class_instantiated_py2.txt @@ -0,0 +1,4 @@ +abstract-class-instantiated:65:main:Abstract class 'BadClass' with abstract methods instantiated
+abstract-class-instantiated:66:main:Abstract class 'SecondBadClass' with abstract methods instantiated
+abstract-class-instantiated:67:main:Abstract class 'ThirdBadClass' with abstract methods instantiated
+abstract-class-instantiated:68:main:Abstract class 'FourthBadClass' with abstract methods instantiated
diff --git a/pylint/test/functional/abstract_class_instantiated_py3.py b/pylint/test/functional/abstract_class_instantiated_py3.py new file mode 100644 index 0000000..8d94733 --- /dev/null +++ b/pylint/test/functional/abstract_class_instantiated_py3.py @@ -0,0 +1,98 @@ +"""Check that instantiating a class with +`abc.ABCMeta` as metaclass fails if it defines +abstract methods. +""" + +# pylint: disable=too-few-public-methods, missing-docstring +# pylint: disable=abstract-class-not-used, abstract-class-little-used +# pylint: disable=abstract-method + +__revision__ = 0 + +import abc +import weakref + +class GoodClass(object, metaclass=abc.ABCMeta): + pass + +class SecondGoodClass(object, metaclass=abc.ABCMeta): + def test(self): + """ do nothing. """ + +class ThirdGoodClass(object, metaclass=abc.ABCMeta): + """ This should not raise the warning. """ + def test(self): + raise NotImplementedError() + +class BadClass(object, metaclass=abc.ABCMeta): + @abc.abstractmethod + def test(self): + """ do nothing. """ + +class SecondBadClass(object, metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def test(self): + """ do nothing. """ + +class ThirdBadClass(SecondBadClass): + pass + + +class Structure(object, metaclass=abc.ABCMeta): + @abc.abstractmethod + def __iter__(self): + pass + @abc.abstractmethod + def __len__(self): + pass + @abc.abstractmethod + def __contains__(self): + pass + @abc.abstractmethod + def __hash__(self): + pass + +class Container(Structure): + def __contains__(self): + pass + +class Sizable(Structure): + def __len__(self): + pass + +class Hashable(Structure): + __hash__ = 42 + + +class Iterator(Structure): + def keys(self): # pylint: disable=no-self-use + return iter([1, 2, 3]) + + __iter__ = keys + +class AbstractSizable(Structure): + @abc.abstractmethod + def length(self): + pass + __len__ = length + +class NoMroAbstractMethods(Container, Iterator, Sizable, Hashable): + pass + +class BadMroAbstractMethods(Container, Iterator, AbstractSizable): + pass + +def main(): + """ do nothing """ + GoodClass() + SecondGoodClass() + ThirdGoodClass() + weakref.WeakKeyDictionary() + weakref.WeakValueDictionary() + NoMroAbstractMethods() + + BadMroAbstractMethods() # [abstract-class-instantiated] + BadClass() # [abstract-class-instantiated] + SecondBadClass() # [abstract-class-instantiated] + ThirdBadClass() # [abstract-class-instantiated] diff --git a/pylint/test/functional/abstract_class_instantiated_py3.rc b/pylint/test/functional/abstract_class_instantiated_py3.rc new file mode 100644 index 0000000..a2ab06c --- /dev/null +++ b/pylint/test/functional/abstract_class_instantiated_py3.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.0
\ No newline at end of file diff --git a/pylint/test/functional/abstract_class_instantiated_py3.txt b/pylint/test/functional/abstract_class_instantiated_py3.txt new file mode 100644 index 0000000..b2e70a0 --- /dev/null +++ b/pylint/test/functional/abstract_class_instantiated_py3.txt @@ -0,0 +1,4 @@ +abstract-class-instantiated:95:main:Abstract class 'BadMroAbstractMethods' with abstract methods instantiated
+abstract-class-instantiated:96:main:Abstract class 'BadClass' with abstract methods instantiated
+abstract-class-instantiated:97:main:Abstract class 'SecondBadClass' with abstract methods instantiated
+abstract-class-instantiated:98:main:Abstract class 'ThirdBadClass' with abstract methods instantiated
diff --git a/pylint/test/functional/abstract_class_instantiated_py34.py b/pylint/test/functional/abstract_class_instantiated_py34.py new file mode 100644 index 0000000..8f710a2 --- /dev/null +++ b/pylint/test/functional/abstract_class_instantiated_py34.py @@ -0,0 +1,19 @@ +"""
+Check that instantiating a class with `abc.ABCM` as ancestor fails if it
+defines abstract methods.
+"""
+
+# pylint: disable=too-few-public-methods, missing-docstring, abstract-class-not-used, no-init
+
+__revision__ = 0
+
+import abc
+
+class BadClass(abc.ABC):
+ @abc.abstractmethod
+ def test(self):
+ pass
+
+def main():
+ """ do nothing """
+ BadClass() # [abstract-class-instantiated]
diff --git a/pylint/test/functional/abstract_class_instantiated_py34.rc b/pylint/test/functional/abstract_class_instantiated_py34.rc new file mode 100644 index 0000000..1fb7b87 --- /dev/null +++ b/pylint/test/functional/abstract_class_instantiated_py34.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.4
\ No newline at end of file diff --git a/pylint/test/functional/abstract_class_instantiated_py34.txt b/pylint/test/functional/abstract_class_instantiated_py34.txt new file mode 100644 index 0000000..afee975 --- /dev/null +++ b/pylint/test/functional/abstract_class_instantiated_py34.txt @@ -0,0 +1 @@ +abstract-class-instantiated:19:main:Abstract class 'BadClass' with abstract methods instantiated
diff --git a/pylint/test/functional/abstract_method_py2.py b/pylint/test/functional/abstract_method_py2.py new file mode 100644 index 0000000..d71faba --- /dev/null +++ b/pylint/test/functional/abstract_method_py2.py @@ -0,0 +1,91 @@ +"""Test abstract-method warning.""" +from __future__ import print_function + +# pylint: disable=missing-docstring, no-init, no-self-use +# pylint: disable=too-few-public-methods +# pylint: disable=abstract-class-little-used, abstract-class-not-used +import abc + +class Abstract(object): + def aaaa(self): + """should be overridden in concrete class""" + raise NotImplementedError() + + def bbbb(self): + """should be overridden in concrete class""" + raise NotImplementedError() + + +class AbstractB(Abstract): + """Abstract class. + + this class is checking that it does not output an error msg for + unimplemeted methods in abstract classes + """ + def cccc(self): + """should be overridden in concrete class""" + raise NotImplementedError() + +class Concret(Abstract): # [abstract-method] + """Concrete class""" + + def aaaa(self): + """overidden form Abstract""" + + +class Structure(object): + __metaclass__ = abc.ABCMeta + + @abc.abstractmethod + def __iter__(self): + pass + @abc.abstractmethod + def __len__(self): + pass + @abc.abstractmethod + def __contains__(self): + pass + @abc.abstractmethod + def __hash__(self): + pass + + +# +1: [abstract-method, abstract-method, abstract-method] +class Container(Structure): + def __contains__(self): + pass + + +# +1: [abstract-method, abstract-method, abstract-method] +class Sizable(Structure): + def __len__(self): + pass + + +# +1: [abstract-method, abstract-method, abstract-method] +class Hashable(Structure): + __hash__ = 42 + + +# +1: [abstract-method, abstract-method, abstract-method] +class Iterator(Structure): + def keys(self): + return iter([1, 2, 3]) + + __iter__ = keys + + +class AbstractSizable(Structure): + @abc.abstractmethod + def length(self): + pass + __len__ = length + + +class GoodComplexMRO(Container, Iterator, Sizable, Hashable): + pass + + +# +1: [abstract-method, abstract-method, abstract-method] +class BadComplexMro(Container, Iterator, AbstractSizable): + pass diff --git a/pylint/test/functional/abstract_method_py2.rc b/pylint/test/functional/abstract_method_py2.rc new file mode 100644 index 0000000..b11e16d --- /dev/null +++ b/pylint/test/functional/abstract_method_py2.rc @@ -0,0 +1,2 @@ +[testoptions] +max_pyver=3.0
\ No newline at end of file diff --git a/pylint/test/functional/abstract_method_py2.txt b/pylint/test/functional/abstract_method_py2.txt new file mode 100644 index 0000000..4ccf9ca --- /dev/null +++ b/pylint/test/functional/abstract_method_py2.txt @@ -0,0 +1,16 @@ +abstract-method:29:Concret:"Method 'bbbb' is abstract in class 'Abstract' but is not overridden"
+abstract-method:54:Container:"Method '__hash__' is abstract in class 'Structure' but is not overridden"
+abstract-method:54:Container:"Method '__iter__' is abstract in class 'Structure' but is not overridden"
+abstract-method:54:Container:"Method '__len__' is abstract in class 'Structure' but is not overridden"
+abstract-method:60:Sizable:"Method '__contains__' is abstract in class 'Structure' but is not overridden"
+abstract-method:60:Sizable:"Method '__hash__' is abstract in class 'Structure' but is not overridden"
+abstract-method:60:Sizable:"Method '__iter__' is abstract in class 'Structure' but is not overridden"
+abstract-method:66:Hashable:"Method '__contains__' is abstract in class 'Structure' but is not overridden"
+abstract-method:66:Hashable:"Method '__iter__' is abstract in class 'Structure' but is not overridden"
+abstract-method:66:Hashable:"Method '__len__' is abstract in class 'Structure' but is not overridden"
+abstract-method:71:Iterator:"Method '__contains__' is abstract in class 'Structure' but is not overridden"
+abstract-method:71:Iterator:"Method '__hash__' is abstract in class 'Structure' but is not overridden"
+abstract-method:71:Iterator:"Method '__len__' is abstract in class 'Structure' but is not overridden"
+abstract-method:90:BadComplexMro:"Method '__hash__' is abstract in class 'Structure' but is not overridden"
+abstract-method:90:BadComplexMro:"Method '__len__' is abstract in class 'AbstractSizable' but is not overridden"
+abstract-method:90:BadComplexMro:"Method 'length' is abstract in class 'AbstractSizable' but is not overridden"
diff --git a/pylint/test/functional/abstract_method_py3.py b/pylint/test/functional/abstract_method_py3.py new file mode 100644 index 0000000..308a6e5 --- /dev/null +++ b/pylint/test/functional/abstract_method_py3.py @@ -0,0 +1,89 @@ +"""Test abstract-method warning.""" +from __future__ import print_function + +# pylint: disable=missing-docstring, no-init, no-self-use +# pylint: disable=too-few-public-methods +# pylint: disable=abstract-class-little-used, abstract-class-not-used +import abc + +class Abstract(object): + def aaaa(self): + """should be overridden in concrete class""" + raise NotImplementedError() + + def bbbb(self): + """should be overridden in concrete class""" + raise NotImplementedError() + + +class AbstractB(Abstract): + """Abstract class. + + this class is checking that it does not output an error msg for + unimplemeted methods in abstract classes + """ + def cccc(self): + """should be overridden in concrete class""" + raise NotImplementedError() + +class Concret(Abstract): # [abstract-method] + """Concrete class""" + + def aaaa(self): + """overidden form Abstract""" + + +class Structure(object, metaclass=abc.ABCMeta): + @abc.abstractmethod + def __iter__(self): + pass + @abc.abstractmethod + def __len__(self): + pass + @abc.abstractmethod + def __contains__(self): + pass + @abc.abstractmethod + def __hash__(self): + pass + + +# +1: [abstract-method, abstract-method, abstract-method] +class Container(Structure): + def __contains__(self): + pass + + +# +1: [abstract-method, abstract-method, abstract-method] +class Sizable(Structure): + def __len__(self): + pass + + +# +1: [abstract-method, abstract-method, abstract-method] +class Hashable(Structure): + __hash__ = 42 + + +# +1: [abstract-method, abstract-method, abstract-method] +class Iterator(Structure): + def keys(self): + return iter([1, 2, 3]) + + __iter__ = keys + + +class AbstractSizable(Structure): + @abc.abstractmethod + def length(self): + pass + __len__ = length + + +class GoodComplexMRO(Container, Iterator, Sizable, Hashable): + pass + + +# +1: [abstract-method, abstract-method, abstract-method] +class BadComplexMro(Container, Iterator, AbstractSizable): + pass diff --git a/pylint/test/functional/abstract_method_py3.rc b/pylint/test/functional/abstract_method_py3.rc new file mode 100644 index 0000000..a2ab06c --- /dev/null +++ b/pylint/test/functional/abstract_method_py3.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.0
\ No newline at end of file diff --git a/pylint/test/functional/abstract_method_py3.txt b/pylint/test/functional/abstract_method_py3.txt new file mode 100644 index 0000000..fd01e9a --- /dev/null +++ b/pylint/test/functional/abstract_method_py3.txt @@ -0,0 +1,16 @@ +abstract-method:29:Concret:"Method 'bbbb' is abstract in class 'Abstract' but is not overridden"
+abstract-method:52:Container:"Method '__hash__' is abstract in class 'Structure' but is not overridden"
+abstract-method:52:Container:"Method '__iter__' is abstract in class 'Structure' but is not overridden"
+abstract-method:52:Container:"Method '__len__' is abstract in class 'Structure' but is not overridden"
+abstract-method:58:Sizable:"Method '__contains__' is abstract in class 'Structure' but is not overridden"
+abstract-method:58:Sizable:"Method '__hash__' is abstract in class 'Structure' but is not overridden"
+abstract-method:58:Sizable:"Method '__iter__' is abstract in class 'Structure' but is not overridden"
+abstract-method:64:Hashable:"Method '__contains__' is abstract in class 'Structure' but is not overridden"
+abstract-method:64:Hashable:"Method '__iter__' is abstract in class 'Structure' but is not overridden"
+abstract-method:64:Hashable:"Method '__len__' is abstract in class 'Structure' but is not overridden"
+abstract-method:69:Iterator:"Method '__contains__' is abstract in class 'Structure' but is not overridden"
+abstract-method:69:Iterator:"Method '__hash__' is abstract in class 'Structure' but is not overridden"
+abstract-method:69:Iterator:"Method '__len__' is abstract in class 'Structure' but is not overridden"
+abstract-method:88:BadComplexMro:"Method '__hash__' is abstract in class 'Structure' but is not overridden"
+abstract-method:88:BadComplexMro:"Method '__len__' is abstract in class 'AbstractSizable' but is not overridden"
+abstract-method:88:BadComplexMro:"Method 'length' is abstract in class 'AbstractSizable' but is not overridden"
diff --git a/pylint/test/functional/access_to__name__.py b/pylint/test/functional/access_to__name__.py new file mode 100644 index 0000000..45e500e --- /dev/null +++ b/pylint/test/functional/access_to__name__.py @@ -0,0 +1,21 @@ +# pylint: disable=too-few-public-methods,star-args, print-statement +"""test access to __name__ gives undefined member on new/old class instances +but not on new/old class object +""" + + +class Aaaa: # <3.0:[old-style-class] + """old class""" + def __init__(self): + print self.__name__ # [no-member] + 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__ # [no-member] diff --git a/pylint/test/functional/access_to__name__.txt b/pylint/test/functional/access_to__name__.txt new file mode 100644 index 0000000..ecf5ffd --- /dev/null +++ b/pylint/test/functional/access_to__name__.txt @@ -0,0 +1,3 @@ +old-style-class:7:Aaaa:Old-style class defined. +no-member:10:Aaaa.__init__:Instance of 'Aaaa' has no '__name__' member:INFERENCE +no-member:21:NewClass.__init__:Instance of 'NewClass' has no '__name__' member:INFERENCE diff --git a/pylint/test/functional/access_to_protected_members.py b/pylint/test/functional/access_to_protected_members.py new file mode 100644 index 0000000..809e7dc --- /dev/null +++ b/pylint/test/functional/access_to_protected_members.py @@ -0,0 +1,38 @@ +# pylint: disable=too-few-public-methods, W0231, print-statement +"""Test external access to protected class members.""" + + +class MyClass(object): + """Class with protected members.""" + _cls_protected = 5 + + def __init__(self, other): + MyClass._cls_protected = 6 + self._protected = 1 + self.public = other + self.attr = 0 + + def test(self): + """Docstring.""" + self._protected += self._cls_protected + print self.public._haha # [protected-access] + + def clsmeth(cls): + """Docstring.""" + cls._cls_protected += 1 + print cls._cls_protected + clsmeth = classmethod(clsmeth) + +class Subclass(MyClass): + """Subclass with protected members.""" + + def __init__(self): + MyClass._protected = 5 + +INST = Subclass() +INST.attr = 1 +print INST.attr +INST._protected = 2 # [protected-access] +print INST._protected # [protected-access] +INST._cls_protected = 3 # [protected-access] +print INST._cls_protected # [protected-access] diff --git a/pylint/test/functional/access_to_protected_members.txt b/pylint/test/functional/access_to_protected_members.txt new file mode 100644 index 0000000..b933c23 --- /dev/null +++ b/pylint/test/functional/access_to_protected_members.txt @@ -0,0 +1,5 @@ +protected-access:18:MyClass.test:Access to a protected member _haha of a client class +protected-access:35::Access to a protected member _protected of a client class +protected-access:36::Access to a protected member _protected of a client class +protected-access:37::Access to a protected member _cls_protected of a client class +protected-access:38::Access to a protected member _cls_protected of a client class diff --git a/pylint/test/functional/anomalous_unicode_escape.py b/pylint/test/functional/anomalous_unicode_escape.py new file mode 100644 index 0000000..19a6912 --- /dev/null +++ b/pylint/test/functional/anomalous_unicode_escape.py @@ -0,0 +1,20 @@ +# pylint:disable=W0105, W0511, C0121 +"""Test for backslash escapes in byte vs unicode strings""" + +# Would be valid in Unicode, but probably not what you want otherwise +BAD_UNICODE = b'\u0042' # [anomalous-unicode-escape-in-string] +BAD_LONG_UNICODE = b'\U00000042' # [anomalous-unicode-escape-in-string] +# +1:[anomalous-unicode-escape-in-string] +BAD_NAMED_UNICODE = b'\N{GREEK SMALL LETTER ALPHA}' + +GOOD_UNICODE = u'\u0042' +GOOD_LONG_UNICODE = u'\U00000042' +GOOD_NAMED_UNICODE = u'\N{GREEK SMALL LETTER ALPHA}' + + +# Valid raw strings +RAW_BACKSLASHES = r'raw' +RAW_UNICODE = ur"\u0062\n" + +# In a comment you can have whatever you want: \ \\ \n \m +# even things that look like bad strings: "C:\Program Files" diff --git a/pylint/test/functional/anomalous_unicode_escape.txt b/pylint/test/functional/anomalous_unicode_escape.txt new file mode 100644 index 0000000..c242cb9 --- /dev/null +++ b/pylint/test/functional/anomalous_unicode_escape.txt @@ -0,0 +1,3 @@ +anomalous-unicode-escape-in-string:5::"Anomalous Unicode escape in byte string: '\u'. String constant might be missing an r or u prefix." +anomalous-unicode-escape-in-string:6::"Anomalous Unicode escape in byte string: '\U'. String constant might be missing an r or u prefix." +anomalous-unicode-escape-in-string:8::"Anomalous Unicode escape in byte string: '\N'. String constant might be missing an r or u prefix." diff --git a/pylint/test/functional/arguments.py b/pylint/test/functional/arguments.py new file mode 100644 index 0000000..4517f69 --- /dev/null +++ b/pylint/test/functional/arguments.py @@ -0,0 +1,98 @@ +# pylint: disable=too-few-public-methods, no-absolute-import +"""Test function argument checker""" + +def decorator(fun): + """Decorator""" + return fun + + +class DemoClass(object): + """Test class for method invocations.""" + + @staticmethod + def static_method(arg): + """static method.""" + return arg + arg + + @classmethod + def class_method(cls, arg): + """class method""" + return arg + arg + + def method(self, arg): + """method.""" + return (self, arg) + + @decorator + def decorated_method(self, arg): + """decorated method.""" + return (self, arg) + + +def function_1_arg(first_argument): + """one argument function""" + return first_argument + +def function_3_args(first_argument, second_argument, third_argument): + """three arguments function""" + return first_argument, second_argument, third_argument + +def function_default_arg(one=1, two=2): + """fonction with default value""" + return two, one + + +function_1_arg(420) +function_1_arg() # [no-value-for-parameter] +function_1_arg(1337, 347) # [too-many-function-args] + +function_3_args(420, 789) # [no-value-for-parameter] +# +1:[no-value-for-parameter,no-value-for-parameter,no-value-for-parameter] +function_3_args() +function_3_args(1337, 347, 456) +function_3_args('bab', 'bebe', None, 5.6) # [too-many-function-args] + +function_default_arg(1, two=5) +function_default_arg(two=5) + +function_1_arg(bob=4) # [unexpected-keyword-arg,no-value-for-parameter] +function_default_arg(1, 4, coin="hello") # [unexpected-keyword-arg] + +function_default_arg(1, one=5) # [redundant-keyword-arg] + +# Remaining tests are for coverage of correct names in messages. +LAMBDA = lambda arg: 1 + +LAMBDA() # [no-value-for-parameter] + +def method_tests(): + """"Method invocations.""" + demo = DemoClass() + demo.static_method() # [no-value-for-parameter] + DemoClass.static_method() # [no-value-for-parameter] + + demo.class_method() # [no-value-for-parameter] + DemoClass.class_method() # [no-value-for-parameter] + + demo.method() # [no-value-for-parameter] + DemoClass.method(demo) # [no-value-for-parameter] + + demo.decorated_method() # [no-value-for-parameter] + DemoClass.decorated_method(demo) # [no-value-for-parameter] + +# Test a regression (issue #234) +import sys + +class Text(object): + """ Regression """ + + if sys.version_info > (3,): + def __new__(cls): + """ empty """ + return object.__new__(cls) + else: + def __new__(cls): + """ empty """ + return object.__new__(cls) + +Text() diff --git a/pylint/test/functional/arguments.txt b/pylint/test/functional/arguments.txt new file mode 100644 index 0000000..050f2c4 --- /dev/null +++ b/pylint/test/functional/arguments.txt @@ -0,0 +1,20 @@ +no-value-for-parameter:46::No value for argument 'first_argument' in function call +too-many-function-args:47::Too many positional arguments for function call +no-value-for-parameter:49::No value for argument 'third_argument' in function call +no-value-for-parameter:51::No value for argument 'first_argument' in function call +no-value-for-parameter:51::No value for argument 'second_argument' in function call +no-value-for-parameter:51::No value for argument 'third_argument' in function call +too-many-function-args:53::Too many positional arguments for function call +no-value-for-parameter:58::No value for argument 'first_argument' in function call +unexpected-keyword-arg:58::Unexpected keyword argument 'bob' in function call +unexpected-keyword-arg:59::Unexpected keyword argument 'coin' in function call +redundant-keyword-arg:61::Argument 'one' passed by position and keyword in function call +no-value-for-parameter:66::No value for argument 'arg' in lambda call +no-value-for-parameter:71:method_tests:No value for argument 'arg' in staticmethod call +no-value-for-parameter:72:method_tests:No value for argument 'arg' in staticmethod call +no-value-for-parameter:74:method_tests:No value for argument 'arg' in classmethod call +no-value-for-parameter:75:method_tests:No value for argument 'arg' in classmethod call +no-value-for-parameter:77:method_tests:No value for argument 'arg' in method call +no-value-for-parameter:78:method_tests:No value for argument 'arg' in unbound method call +no-value-for-parameter:80:method_tests:No value for argument 'arg' in method call +no-value-for-parameter:81:method_tests:No value for argument 'arg' in unbound method call diff --git a/pylint/test/functional/assigning_non_slot.py b/pylint/test/functional/assigning_non_slot.py new file mode 100644 index 0000000..b80c32e --- /dev/null +++ b/pylint/test/functional/assigning_non_slot.py @@ -0,0 +1,100 @@ +""" Checks assigning attributes not found in class slots +will trigger assigning-non-slot warning. +""" +# pylint: disable=too-few-public-methods, no-init, missing-docstring, no-absolute-import +from collections import deque + +__revision__ = 0 + +class Empty(object): + """ empty """ + +class Bad(object): + """ missing not in slots. """ + + __slots__ = ['member'] + + def __init__(self): + self.missing = 42 # [assigning-non-slot] + +class Bad2(object): + """ missing not in slots """ + __slots__ = [deque.__name__, 'member'] + + def __init__(self): + self.deque = 42 + self.missing = 42 # [assigning-non-slot] + +class Bad3(Bad): + """ missing not found in slots """ + + __slots__ = ['component'] + + def __init__(self): + self.component = 42 + self.member = 24 + self.missing = 42 # [assigning-non-slot] + super(Bad3, self).__init__() + +class Good(Empty): + """ missing not in slots, but Empty doesn't + specify __slots__. + """ + __slots__ = ['a'] + + def __init__(self): + self.missing = 42 + +class Good2(object): + """ Using __dict__ in slots will be safe. """ + + __slots__ = ['__dict__', 'comp'] + + def __init__(self): + self.comp = 4 + self.missing = 5 + +class PropertyGood(object): + """ Using properties is safe. """ + + __slots__ = ['tmp', '_value'] + + @property + def test(self): + return self._value + + @test.setter + def test(self, value): + # pylint: disable=attribute-defined-outside-init + self._value = value + + def __init__(self): + self.test = 42 + +class PropertyGood2(object): + """ Using properties in the body of the class is safe. """ + __slots__ = ['_value'] + + def _getter(self): + return self._value + + def _setter(self, value): + # pylint: disable=attribute-defined-outside-init + self._value = value + + test = property(_getter, _setter) + + def __init__(self): + self.test = 24 + +class UnicodeSlots(object): + """Using unicode objects in __slots__ is okay. + + On Python 3.3 onward, u'' is equivalent to '', + so this test should be safe for both versions. + """ + __slots__ = (u'first', u'second') + + def __init__(self): + self.first = 42 + self.second = 24 diff --git a/pylint/test/functional/assigning_non_slot.txt b/pylint/test/functional/assigning_non_slot.txt new file mode 100644 index 0000000..ad32066 --- /dev/null +++ b/pylint/test/functional/assigning_non_slot.txt @@ -0,0 +1,3 @@ +assigning-non-slot:18:Bad.__init__:Assigning to attribute 'missing' not defined in class slots
+assigning-non-slot:26:Bad2.__init__:Assigning to attribute 'missing' not defined in class slots
+assigning-non-slot:36:Bad3.__init__:Assigning to attribute 'missing' not defined in class slots
\ No newline at end of file diff --git a/pylint/test/functional/bad_context_manager.py b/pylint/test/functional/bad_context_manager.py new file mode 100644 index 0000000..9bddbcf --- /dev/null +++ b/pylint/test/functional/bad_context_manager.py @@ -0,0 +1,60 @@ +"""Check that __exit__ special method accepts 3 arguments """ + +# pylint: disable=too-few-public-methods, invalid-name + +__revision__ = 0 + +class FirstGoodContextManager(object): + """ 3 arguments """ + + def __enter__(self): + return self + + def __exit__(self, exc_type, value, tb): + pass + +class SecondGoodContextManager(object): + """ 3 keyword arguments """ + + def __enter__(self): + return self + + def __exit__(self, exc_type=None, value=None, tb=None): + pass + +class ThirdGoodContextManager(object): + """ 1 argument and variable arguments """ + + def __enter__(self): + return self + + def __exit__(self, exc_type, *args): + pass + +class FirstBadContextManager(object): + """ 1 argument """ + + def __enter__(self): + return self + + def __exit__(self, exc_type): # [bad-context-manager] + pass + +class SecondBadContextManager(object): + """ Too many arguments """ + + def __enter__(self): + return self + + def __exit__(self, exc_type, value, tb, stack): # [bad-context-manager] + pass + +class ThirdBadContextManager(object): + """ Too many arguments and variable arguments """ + + def __enter__(self): + return self + + # +1: [bad-context-manager] + def __exit__(self, exc_type, value, tb, stack, *args): + pass diff --git a/pylint/test/functional/bad_context_manager.txt b/pylint/test/functional/bad_context_manager.txt new file mode 100644 index 0000000..b305484 --- /dev/null +++ b/pylint/test/functional/bad_context_manager.txt @@ -0,0 +1,3 @@ +bad-context-manager:40:FirstBadContextManager.__exit__:"__exit__ must accept 3 arguments: type, value, traceback" +bad-context-manager:49:SecondBadContextManager.__exit__:"__exit__ must accept 3 arguments: type, value, traceback" +bad-context-manager:59:ThirdBadContextManager.__exit__:"__exit__ must accept 3 arguments: type, value, traceback" diff --git a/pylint/test/functional/bad_continuation.py b/pylint/test/functional/bad_continuation.py new file mode 100644 index 0000000..d7e90c0 --- /dev/null +++ b/pylint/test/functional/bad_continuation.py @@ -0,0 +1,189 @@ +"""Regression test case for bad-continuation.""" +# pylint: disable=print-statement +# Various alignment for brackets +LIST0 = [ + 1, 2, 3 +] +LIST1 = [ + 1, 2, 3 + ] +LIST2 = [ + 1, 2, 3 + ] # [bad-continuation] + +# Alignment inside literals +W0 = [1, 2, 3, + 4, 5, 6, + 7, # [bad-continuation] + 8, 9, 10, + 11, 12, 13, + # and a comment + 14, 15, 16] + +W1 = { + 'a': 1, + 'b': 2, # [bad-continuation] + 'c': 3, + } + +W2 = { + 'a': 1, + 'b': 2, # [bad-continuation] + 'c': 3, + } + +W2 = ['some', 'contents' # with a continued comment that may be aligned + # under the previous comment (optionally) + 'and', + 'more', # but this + # [bad-continuation] is not accepted + 'contents', # [bad-continuation] nor this. + ] + +# Values in dictionaries should be indented 4 spaces further if they are on a +# different line than their key +W4 = { + 'key1': + 'value1', # Grandfather in the old style + 'key2': + 'value2', # [bad-continuation] + 'key3': + 'value3', # Comma here + } + +# And should follow the same rules as continuations within parens +W5 = { + 'key1': 'long value' + 'long continuation', + 'key2': 'breaking' + 'wrong', # [bad-continuation] + 'key3': 2*( + 2+2), + 'key4': ('parenthesis', + 'continuation') # No comma here + } + +# Allow values to line up with their keys when the key is next to the brace +W6 = {'key1': + 'value1', + 'key2': + 'value2', + } + +# Or allow them to be indented +W7 = {'key1': + 'value1', + 'key2': + 'value2' + } + +# Bug that caused a warning on the previous two cases permitted these odd +# incorrect indentations +W8 = {'key1': +'value1', # [bad-continuation] + } + +W9 = {'key1': + 'value1', # [bad-continuation] + } + +# Alignment of arguments in function definitions +def continue1(some_arg, + some_other_arg): + """A function with well-aligned arguments.""" + print some_arg, some_other_arg + + +def continue2( + some_arg, + some_other_arg): + """A function with well-aligned arguments.""" + print some_arg, some_other_arg + +def continue3( + some_arg, # [bad-continuation] + some_other_arg): # [bad-continuation] + """A function with misaligned arguments""" + print some_arg, some_other_arg + +def continue4( # pylint:disable=missing-docstring + arg1, + arg2): print arg1, arg2 + + +def callee(*args): + """noop""" + print args + + +callee( + "a", + "b" + ) + +callee("a", + "b") # [bad-continuation] + +callee(5, {'a': 'b', + 'c': 'd'}) + +if ( + 1 + ): pass + +if ( + 1 +): pass +if ( + 1 + ): pass # [bad-continuation] + +if (1 and + 2): # [bad-continuation] + pass + +while (1 and + 2): + pass + +while (1 and + 2 and # [bad-continuation] + 3): + pass + +if ( + 2): pass # [bad-continuation] + +if (1 or + 2 or + 3): pass + +if (1 or + 2 or # [bad-continuation] + 3): print 1, 2 + +if (1 and + 2): pass # [bad-continuation] + +if ( + 2): pass + +if ( + 2): # [bad-continuation] + pass + +L1 = (lambda a, + b: a + b) + +if not (1 and + 2): + print 3 + +if not (1 and + 2): # [bad-continuation] + print 3 + +continue2("foo", + some_other_arg="this " + "is " + "fine") diff --git a/pylint/test/functional/bad_continuation.txt b/pylint/test/functional/bad_continuation.txt new file mode 100644 index 0000000..f970780 --- /dev/null +++ b/pylint/test/functional/bad_continuation.txt @@ -0,0 +1,63 @@ +bad-continuation:12::"Wrong hanging indentation. + ] # [bad-continuation] +| ^|" +bad-continuation:17::"Wrong continued indentation. + 7, # [bad-continuation] + | ^" +bad-continuation:25::"Wrong hanging indentation. + 'b': 2, # [bad-continuation] + ^|" +bad-continuation:31::"Wrong hanging indentation. + 'b': 2, # [bad-continuation] + ^|" +bad-continuation:39::"Wrong continued indentation. + # [bad-continuation] is not accepted + | | ^" +bad-continuation:40::"Wrong continued indentation. + 'contents', # [bad-continuation] nor this. + | ^" +bad-continuation:49::"Wrong hanging indentation in dict value. + 'value2', # [bad-continuation] + | ^ |" +bad-continuation:59::"Wrong continued indentation. + 'wrong', # [bad-continuation] + ^ |" +bad-continuation:83::"Wrong hanging indentation in dict value. +'value1', # [bad-continuation] +^ | |" +bad-continuation:87::"Wrong hanging indentation in dict value. + 'value1', # [bad-continuation] + ^ | |" +bad-continuation:104::"Wrong hanging indentation before block. + some_arg, # [bad-continuation] + ^ |" +bad-continuation:105::"Wrong hanging indentation before block. + some_other_arg): # [bad-continuation] + ^ |" +bad-continuation:125::"Wrong continued indentation. + ""b"") # [bad-continuation] + ^ |" +bad-continuation:139::"Wrong hanging indentation before block. + ): pass # [bad-continuation] +| ^|" +bad-continuation:142::"Wrong continued indentation before block. + 2): # [bad-continuation] + ^ |" +bad-continuation:150::"Wrong continued indentation. + 2 and # [bad-continuation] + | ^" +bad-continuation:155::"Wrong hanging indentation before block. + 2): pass # [bad-continuation] + ^ | |" +bad-continuation:162::"Wrong continued indentation before block. + 2 or # [bad-continuation] + |^ |" +bad-continuation:166::"Wrong continued indentation before block. + 2): pass # [bad-continuation] + ^ | |" +bad-continuation:172::"Wrong hanging indentation before block. + 2): # [bad-continuation] + ^ | |" +bad-continuation:183::"Wrong continued indentation. + 2): # [bad-continuation] + ^ |" diff --git a/pylint/test/functional/bad_inline_option.py b/pylint/test/functional/bad_inline_option.py new file mode 100644 index 0000000..be6e240 --- /dev/null +++ b/pylint/test/functional/bad_inline_option.py @@ -0,0 +1,5 @@ +"""errors-only is not usable as an inline option""" +# +1: [bad-inline-option] +# pylint: errors-only + +CONST = "This is not a pylint: inline option." diff --git a/pylint/test/functional/bad_inline_option.rc b/pylint/test/functional/bad_inline_option.rc new file mode 100644 index 0000000..b211d72 --- /dev/null +++ b/pylint/test/functional/bad_inline_option.rc @@ -0,0 +1,2 @@ +[Messages Control] +enable=I diff --git a/pylint/test/functional/bad_inline_option.txt b/pylint/test/functional/bad_inline_option.txt new file mode 100644 index 0000000..91ac1af --- /dev/null +++ b/pylint/test/functional/bad_inline_option.txt @@ -0,0 +1 @@ +bad-inline-option:3::Unable to consider inline option 'errors-only' diff --git a/pylint/test/functional/bad_open_mode.py b/pylint/test/functional/bad_open_mode.py new file mode 100644 index 0000000..6b749de --- /dev/null +++ b/pylint/test/functional/bad_open_mode.py @@ -0,0 +1,37 @@ +"""Warnings for using open() with an invalid mode string.""" + +open('foo.bar', 'w', 2) +open('foo.bar', 'rw') # [bad-open-mode] +open(name='foo.bar', buffering=10, mode='rw') # [bad-open-mode] +open(mode='rw', name='foo.bar') # [bad-open-mode] +open('foo.bar', 'U+') +open('foo.bar', 'rb+') +open('foo.bar', 'Uw') # [bad-open-mode] +open('foo.bar', 2) # [bad-open-mode] +open('foo.bar', buffering=2) +WRITE_MODE = 'w' +open('foo.bar', 'U' + WRITE_MODE + 'z') # [bad-open-mode] +open('foo.bar', 'br') # [bad-open-mode] +open('foo.bar', 'wU') # [bad-open-mode] +open('foo.bar', 'r+b') +open('foo.bar', 'r+') +open('foo.bar', 'w+') +open('foo.bar', 'xb') # [bad-open-mode] +open('foo.bar', 'rx') # [bad-open-mode] +open('foo.bar', 'Ur') +open('foo.bar', 'rU') +open('foo.bar', 'rUb') +open('foo.bar', 'rUb+') +open('foo.bar', 'rU+b') +open('foo.bar', 'r+Ub') +open('foo.bar', '+rUb') # [bad-open-mode] +open('foo.bar', 'ab+') +open('foo.bar', 'a+b') +open('foo.bar', 'aU') # [bad-open-mode] +open('foo.bar', 'U+b') +open('foo.bar', '+Ub') +open('foo.bar', 'b+U') +open('foo.bar', 'Urb+') +open('foo.bar', 'Ur+b') +open('foo.bar', 'Ubr') # [bad-open-mode] +open('foo.bar', 'Ut') # [bad-open-mode] diff --git a/pylint/test/functional/bad_open_mode.rc b/pylint/test/functional/bad_open_mode.rc new file mode 100644 index 0000000..b9ab977 --- /dev/null +++ b/pylint/test/functional/bad_open_mode.rc @@ -0,0 +1,2 @@ +[testoptions]
+max_pyver=3.0
diff --git a/pylint/test/functional/bad_open_mode.txt b/pylint/test/functional/bad_open_mode.txt new file mode 100644 index 0000000..b022286 --- /dev/null +++ b/pylint/test/functional/bad_open_mode.txt @@ -0,0 +1,14 @@ +bad-open-mode:4::"""rw"" is not a valid mode for open." +bad-open-mode:5::"""rw"" is not a valid mode for open." +bad-open-mode:6::"""rw"" is not a valid mode for open." +bad-open-mode:9::"""Uw"" is not a valid mode for open." +bad-open-mode:10::"""2"" is not a valid mode for open." +bad-open-mode:13::"""Uwz"" is not a valid mode for open." +bad-open-mode:14::"""br"" is not a valid mode for open." +bad-open-mode:15::"""wU"" is not a valid mode for open." +bad-open-mode:19::"""xb"" is not a valid mode for open." +bad-open-mode:20::"""rx"" is not a valid mode for open." +bad-open-mode:27::"""+rUb"" is not a valid mode for open." +bad-open-mode:30::"""aU"" is not a valid mode for open." +bad-open-mode:36::"""Ubr"" is not a valid mode for open." +bad-open-mode:37::"""Ut"" is not a valid mode for open." diff --git a/pylint/test/functional/bad_open_mode_py3.py b/pylint/test/functional/bad_open_mode_py3.py new file mode 100644 index 0000000..0812f5a --- /dev/null +++ b/pylint/test/functional/bad_open_mode_py3.py @@ -0,0 +1,23 @@ +"""Warnings for using open() with an invalid mode string."""
+
+NAME = "foo.bar"
+open(NAME, "wb")
+open(NAME, "w")
+open(NAME, "rb")
+open(NAME, "x")
+open(NAME, "br")
+open(NAME, "+r")
+open(NAME, "xb")
+open(NAME, "rwx") # [bad-open-mode]
+open(NAME, "rr") # [bad-open-mode]
+open(NAME, "+") # [bad-open-mode]
+open(NAME, "xw") # [bad-open-mode]
+open(NAME, "ab+")
+open(NAME, "a+b")
+open(NAME, "+ab")
+open(NAME, "+rUb")
+open(NAME, "x+b")
+open(NAME, "Ua") # [bad-open-mode]
+open(NAME, "Ur++") # [bad-open-mode]
+open(NAME, "Ut")
+open(NAME, "Ubr")
diff --git a/pylint/test/functional/bad_open_mode_py3.rc b/pylint/test/functional/bad_open_mode_py3.rc new file mode 100644 index 0000000..c4033f8 --- /dev/null +++ b/pylint/test/functional/bad_open_mode_py3.rc @@ -0,0 +1,2 @@ +[testoptions]
+min_pyver=3.0
diff --git a/pylint/test/functional/bad_open_mode_py3.txt b/pylint/test/functional/bad_open_mode_py3.txt new file mode 100644 index 0000000..0be0ea8 --- /dev/null +++ b/pylint/test/functional/bad_open_mode_py3.txt @@ -0,0 +1,6 @@ +bad-open-mode:11::"""rwx"" is not a valid mode for open."
+bad-open-mode:12::"""rr"" is not a valid mode for open."
+bad-open-mode:13::"""+"" is not a valid mode for open."
+bad-open-mode:14::"""xw"" is not a valid mode for open."
+bad-open-mode:20::"""Ua"" is not a valid mode for open."
+bad-open-mode:21::"""Ur++"" is not a valid mode for open."
diff --git a/pylint/test/functional/bad_reversed_sequence.py b/pylint/test/functional/bad_reversed_sequence.py new file mode 100644 index 0000000..6fda2c0 --- /dev/null +++ b/pylint/test/functional/bad_reversed_sequence.py @@ -0,0 +1,60 @@ +""" Checks that reversed() receive proper argument """ + +# pylint: disable=too-few-public-methods,no-self-use,no-absolute-import +from collections import deque + +__revision__ = 0 + +class GoodReversed(object): + """ Implements __reversed__ """ + def __reversed__(self): + return [1, 2, 3] + +class SecondGoodReversed(object): + """ Implements __len__ and __getitem__ """ + def __len__(self): + return 3 + + def __getitem__(self, index): + return index + +class BadReversed(object): + """ implements only len() """ + def __len__(self): + return 3 + +class SecondBadReversed(object): + """ implements only __getitem__ """ + def __getitem__(self, index): + return index + +class ThirdBadReversed(dict): + """ dict subclass """ + +def uninferable(seq): + """ This can't be infered at this moment, + make sure we don't have a false positive. + """ + return reversed(seq) + +def test(path): + """ test function """ + seq = reversed() # [missing-reversed-argument] + seq = reversed(None) # [bad-reversed-sequence] + seq = reversed([1, 2, 3]) + seq = reversed((1, 2, 3)) + seq = reversed(set()) # [bad-reversed-sequence] + seq = reversed({'a': 1, 'b': 2}) # [bad-reversed-sequence] + seq = reversed(iter([1, 2, 3])) # [bad-reversed-sequence] + seq = reversed(GoodReversed()) + seq = reversed(SecondGoodReversed()) + seq = reversed(BadReversed()) # [bad-reversed-sequence] + seq = reversed(SecondBadReversed()) # [bad-reversed-sequence] + seq = reversed(range(100)) + seq = reversed(ThirdBadReversed()) # [bad-reversed-sequence] + seq = reversed(lambda: None) # [bad-reversed-sequence] + seq = reversed(deque([])) + seq = reversed("123") + seq = uninferable([1, 2, 3]) + seq = reversed(path.split("/")) + return seq diff --git a/pylint/test/functional/bad_reversed_sequence.txt b/pylint/test/functional/bad_reversed_sequence.txt new file mode 100644 index 0000000..ccdec39 --- /dev/null +++ b/pylint/test/functional/bad_reversed_sequence.txt @@ -0,0 +1,9 @@ +missing-reversed-argument:42:test:Missing argument to reversed()
+bad-reversed-sequence:43:test:The first reversed() argument is not a sequence
+bad-reversed-sequence:46:test:The first reversed() argument is not a sequence
+bad-reversed-sequence:47:test:The first reversed() argument is not a sequence
+bad-reversed-sequence:48:test:The first reversed() argument is not a sequence
+bad-reversed-sequence:51:test:The first reversed() argument is not a sequence
+bad-reversed-sequence:52:test:The first reversed() argument is not a sequence
+bad-reversed-sequence:54:test:The first reversed() argument is not a sequence
+bad-reversed-sequence:55:test:The first reversed() argument is not a sequence
diff --git a/pylint/test/functional/boolean_datetime.py b/pylint/test/functional/boolean_datetime.py new file mode 100644 index 0000000..0ca5c38 --- /dev/null +++ b/pylint/test/functional/boolean_datetime.py @@ -0,0 +1,30 @@ +""" Checks for boolean uses of datetime.time. """
+# pylint: disable=superfluous-parens,print-statement,no-absolute-import
+import datetime
+
+if datetime.time(0, 0, 0): # [boolean-datetime]
+ print("datetime.time(0,0,0) is not a bug!")
+else:
+ print("datetime.time(0,0,0) is a bug!")
+
+if not datetime.time(0, 0, 1): # [boolean-datetime]
+ print("datetime.time(0,0,1) is not a bug!")
+else:
+ print("datetime.time(0,0,1) is a bug!")
+
+DATA = not datetime.time(0, 0, 0) # [boolean-datetime]
+DATA = True if datetime.time(0, 0, 0) else False # [boolean-datetime]
+DATA = datetime.time(0, 0, 0) or True # [boolean-datetime]
+DATA = datetime.time(0, 0, 0) and True # [boolean-datetime]
+DATA = False or True or datetime.time(0, 0, 0) # [boolean-datetime]
+DATA = False and datetime.time(0, 0, 0) or True # [boolean-datetime]
+
+
+def cant_infer(data):
+ """ Can't infer what data is """
+ hophop = not data
+ troptrop = True if data else False
+ toptop = data or True
+ return hophop, troptrop, toptop
+
+cant_infer(datetime.time(0, 0, 0))
diff --git a/pylint/test/functional/boolean_datetime.txt b/pylint/test/functional/boolean_datetime.txt new file mode 100644 index 0000000..f3287d4 --- /dev/null +++ b/pylint/test/functional/boolean_datetime.txt @@ -0,0 +1,8 @@ +boolean-datetime:5::Using datetime.time in a boolean context. +boolean-datetime:10::Using datetime.time in a boolean context. +boolean-datetime:15::Using datetime.time in a boolean context. +boolean-datetime:16::Using datetime.time in a boolean context. +boolean-datetime:17::Using datetime.time in a boolean context. +boolean-datetime:18::Using datetime.time in a boolean context. +boolean-datetime:19::Using datetime.time in a boolean context. +boolean-datetime:20::Using datetime.time in a boolean context. diff --git a/pylint/test/functional/cellvar_escaping_loop.py b/pylint/test/functional/cellvar_escaping_loop.py new file mode 100644 index 0000000..dfacaf4 --- /dev/null +++ b/pylint/test/functional/cellvar_escaping_loop.py @@ -0,0 +1,129 @@ +# pylint: disable=print-statement +"""Tests for loopvar-in-closure.""" + +def good_case(): + """No problems here.""" + lst = [] + for i in range(10): + lst.append(i) + + +def good_case2(): + """No problems here.""" + return [i for i in range(10)] + + +def good_case3(): + """No problems here.""" + lst = [] + for i in range(10): # [unused-variable] + lst.append(lambda i=i: i) + + +def good_case4(): + """No problems here.""" + lst = [] + for i in range(10): + print i + lst.append(lambda i: i) + + +def good_case5(): + """No problems here.""" + return (i for i in range(10)) + + +def good_case6(): + """Accept use of the variable after the loop. + + There's already a warning about possibly undefined loop variables, and + the value will not change any more.""" + for i in range(10): + print i + return lambda: i # [undefined-loop-variable] + + +def good_case7(): + """Accept use of the variable inside return.""" + for i in range(10): + if i == 8: + return lambda: i + return lambda: -1 + + +def good_case8(): + """Lambda defined and called in loop.""" + for i in range(10): + print (lambda x: i + x)(1) + + +def good_case9(): + """Another eager binding of the cell variable.""" + funs = [] + for i in range(10): + def func(bound_i=i): + """Ignore.""" + return bound_i + funs.append(func) + return funs + + +def bad_case(): + """Closing over a loop variable.""" + lst = [] + for i in range(10): + print i + lst.append(lambda: i) # [cell-var-from-loop] + + +def bad_case2(): + """Closing over a loop variable.""" + return [lambda: i for i in range(10)] # [cell-var-from-loop] + + +def bad_case3(): + """Closing over variable defined in loop.""" + lst = [] + for i in range(10): + j = i * i + lst.append(lambda: j) # [cell-var-from-loop] + return lst + + +def bad_case4(): + """Closing over variable defined in loop.""" + lst = [] + for i in range(10): + def nested(): + """Nested function.""" + return i**2 # [cell-var-from-loop] + lst.append(nested) + return lst + + +def bad_case5(): + """Problematic case. + + If this function is used as + + >>> [x() for x in bad_case5()] + + it behaves 'as expected', i.e. the result is range(10). + + If it's used with + + >>> lst = list(bad_case5()) + >>> [x() for x in lst] + + the result is [9] * 10 again. + """ + return (lambda: i for i in range(10)) # [cell-var-from-loop] + + +def bad_case6(): + """Closing over variable defined in loop.""" + lst = [] + for i, j in zip(range(10), range(10, 20)): + print j + lst.append(lambda: i) # [cell-var-from-loop] + return lst diff --git a/pylint/test/functional/cellvar_escaping_loop.txt b/pylint/test/functional/cellvar_escaping_loop.txt new file mode 100644 index 0000000..90d787e --- /dev/null +++ b/pylint/test/functional/cellvar_escaping_loop.txt @@ -0,0 +1,8 @@ +unused-variable:19:good_case3:Unused variable 'i' +undefined-loop-variable:43:good_case6.<lambda>:Using possibly undefined loop variable 'i' +cell-var-from-loop:76:bad_case.<lambda>:Cell variable i defined in loop +cell-var-from-loop:81:bad_case2.<lambda>:Cell variable i defined in loop +cell-var-from-loop:89:bad_case3.<lambda>:Cell variable j defined in loop +cell-var-from-loop:99:bad_case4.nested:Cell variable i defined in loop +cell-var-from-loop:120:bad_case5.<lambda>:Cell variable i defined in loop +cell-var-from-loop:128:bad_case6.<lambda>:Cell variable i defined in loop diff --git a/pylint/test/functional/class_members_py27.py b/pylint/test/functional/class_members_py27.py new file mode 100644 index 0000000..40423b1 --- /dev/null +++ b/pylint/test/functional/class_members_py27.py @@ -0,0 +1,51 @@ +""" Various tests for class members access. """
+# pylint: disable=R0903,print-statement,no-absolute-import, metaclass-assignment
+
+class MyClass(object):
+ """class docstring"""
+
+ def __init__(self):
+ """init"""
+ self.correct = 1
+
+ def test(self):
+ """test"""
+ self.correct += 2
+ self.incorrect += 2 # [no-member]
+ del self.havenot # [no-member]
+ self.nonexistent1.truc() # [no-member]
+ self.nonexistent2[1] = 'hehe' # [no-member]
+
+class XYZMixin(object):
+ """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')
+
+from abc import ABCMeta
+
+class TestMetaclass(object):
+ """ Test attribute access for metaclasses. """
+ __metaclass__ = ABCMeta
+
+class Metaclass(type):
+ """ metaclass """
+ @classmethod
+ def test(mcs):
+ """ classmethod """
+
+class UsingMetaclass(object):
+ """ empty """
+ __metaclass__ = Metaclass
+
+TestMetaclass.register(int)
+UsingMetaclass.test()
+TestMetaclass().register(int) # [no-member]
+UsingMetaclass().test() # [no-member]
diff --git a/pylint/test/functional/class_members_py27.rc b/pylint/test/functional/class_members_py27.rc new file mode 100644 index 0000000..80170b7 --- /dev/null +++ b/pylint/test/functional/class_members_py27.rc @@ -0,0 +1,3 @@ +[testoptions] +min_pyver=2.7
+max_pyver=3.0
\ No newline at end of file diff --git a/pylint/test/functional/class_members_py27.txt b/pylint/test/functional/class_members_py27.txt new file mode 100644 index 0000000..e5e6005 --- /dev/null +++ b/pylint/test/functional/class_members_py27.txt @@ -0,0 +1,6 @@ +no-member:14:MyClass.test:Instance of 'MyClass' has no 'incorrect' member:INFERENCE +no-member:15:MyClass.test:Instance of 'MyClass' has no 'havenot' member:INFERENCE +no-member:16:MyClass.test:Instance of 'MyClass' has no 'nonexistent1' member:INFERENCE +no-member:17:MyClass.test:Instance of 'MyClass' has no 'nonexistent2' member:INFERENCE +no-member:50::Instance of 'TestMetaclass' has no 'register' member:INFERENCE +no-member:51::Instance of 'UsingMetaclass' has no 'test' member:INFERENCE diff --git a/pylint/test/functional/class_members_py30.py b/pylint/test/functional/class_members_py30.py new file mode 100644 index 0000000..c9b2612 --- /dev/null +++ b/pylint/test/functional/class_members_py30.py @@ -0,0 +1,49 @@ +""" Various tests for class members access. """
+# pylint: disable=R0903
+
+class MyClass(object):
+ """class docstring"""
+
+ def __init__(self):
+ """init"""
+ self.correct = 1
+
+ def test(self):
+ """test"""
+ self.correct += 2
+ self.incorrect += 2 # [no-member]
+ del self.havenot # [no-member]
+ self.nonexistent1.truc() # [no-member]
+ self.nonexistent2[1] = 'hehe' # [no-member]
+
+class XYZMixin(object):
+ """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')
+
+from abc import ABCMeta
+
+class TestMetaclass(object, metaclass=ABCMeta):
+ """ Test attribute access for metaclasses. """
+
+class Metaclass(type):
+ """ metaclass """
+ @classmethod
+ def test(mcs):
+ """ classmethod """
+
+class UsingMetaclass(object, metaclass=Metaclass):
+ """ empty """
+
+TestMetaclass.register(int)
+UsingMetaclass.test()
+TestMetaclass().register(int) # [no-member]
+UsingMetaclass().test() # [no-member]
diff --git a/pylint/test/functional/class_members_py30.rc b/pylint/test/functional/class_members_py30.rc new file mode 100644 index 0000000..8c6eb56 --- /dev/null +++ b/pylint/test/functional/class_members_py30.rc @@ -0,0 +1,2 @@ +[testoptions]
+min_pyver=3.0
\ No newline at end of file diff --git a/pylint/test/functional/class_members_py30.txt b/pylint/test/functional/class_members_py30.txt new file mode 100644 index 0000000..4696579 --- /dev/null +++ b/pylint/test/functional/class_members_py30.txt @@ -0,0 +1,6 @@ +no-member:14:MyClass.test:Instance of 'MyClass' has no 'incorrect' member:INFERENCE
+no-member:15:MyClass.test:Instance of 'MyClass' has no 'havenot' member:INFERENCE
+no-member:16:MyClass.test:Instance of 'MyClass' has no 'nonexistent1' member:INFERENCE
+no-member:17:MyClass.test:Instance of 'MyClass' has no 'nonexistent2' member:INFERENCE
+no-member:48::Instance of 'TestMetaclass' has no 'register' member:INFERENCE
+no-member:49::Instance of 'UsingMetaclass' has no 'test' member:INFERENCE
diff --git a/pylint/test/functional/class_scope.py b/pylint/test/functional/class_scope.py new file mode 100644 index 0000000..68956c6 --- /dev/null +++ b/pylint/test/functional/class_scope.py @@ -0,0 +1,22 @@ +# pylint: disable=R0903,W0232 +"""check for scope problems""" + +__revision__ = None + +class Well(object): + """well""" + attr = 42 + get_attr = lambda arg=attr: arg * 24 + # +1: [used-before-assignment] + get_attr_bad = lambda arg=revattr: revattr * 42 + revattr = 24 + bad_lambda = lambda: get_attr_bad # [undefined-variable] + + class Data(object): + """base hidden class""" + class Sub(Data): # [undefined-variable + """whaou, is Data found???""" + attr = Data() # [undefined-variable] + def func(self): + """check Sub is not defined here""" + return Sub(), self # [undefined-variable] diff --git a/pylint/test/functional/class_scope.txt b/pylint/test/functional/class_scope.txt new file mode 100644 index 0000000..ea6c45a --- /dev/null +++ b/pylint/test/functional/class_scope.txt @@ -0,0 +1,4 @@ +used-before-assignment:11:Well.<lambda>:Using variable 'revattr' before assignment +undefined-variable:13:Well.<lambda>:Undefined variable 'get_attr_bad' +undefined-variable:19:Well.Sub:Undefined variable 'Data' +undefined-variable:22:Well.func:Undefined variable 'Sub' diff --git a/pylint/test/functional/confidence_filter.py b/pylint/test/functional/confidence_filter.py new file mode 100644 index 0000000..64cc8c4 --- /dev/null +++ b/pylint/test/functional/confidence_filter.py @@ -0,0 +1,14 @@ +"""Test for the confidence filter.""" + +class Client(object): + """use provider class""" + + def __init__(self): + self.set_later = 0 + + def set_set_later(self, value): + """set set_later attribute (introduce an inference ambiguity)""" + self.set_later = value + +print Client().set_later.lower() +print Client().foo # [no-member] diff --git a/pylint/test/functional/confidence_filter.rc b/pylint/test/functional/confidence_filter.rc new file mode 100644 index 0000000..74953b6 --- /dev/null +++ b/pylint/test/functional/confidence_filter.rc @@ -0,0 +1,3 @@ +[Messages Control] +disable=no-init,too-few-public-methods,undefined-variable,print-statement +confidence=INFERENCE,HIGH,UNDEFINED diff --git a/pylint/test/functional/confidence_filter.txt b/pylint/test/functional/confidence_filter.txt new file mode 100644 index 0000000..308035d --- /dev/null +++ b/pylint/test/functional/confidence_filter.txt @@ -0,0 +1 @@ +no-member:14::Instance of 'Client' has no 'foo' member:INFERENCE diff --git a/pylint/test/functional/crash_missing_module_type.py b/pylint/test/functional/crash_missing_module_type.py new file mode 100644 index 0000000..a471ad8 --- /dev/null +++ b/pylint/test/functional/crash_missing_module_type.py @@ -0,0 +1,18 @@ +""" Test for a crash found in
+https://bitbucket.org/logilab/astroid/issue/45/attributeerror-module-object-has-no#comment-11944673
+"""
+# pylint: disable=no-init, invalid-name, too-few-public-methods, redefined-outer-name
+def decor(trop):
+ """ decorator """
+ return trop
+
+class Foo(object):
+ """ Class """
+ @decor
+ def prop(self):
+ """ method """
+ return self
+
+if __name__ == '__main__':
+ trop = Foo()
+ trop.prop = 42
diff --git a/pylint/test/functional/crash_missing_module_type.txt b/pylint/test/functional/crash_missing_module_type.txt new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/pylint/test/functional/crash_missing_module_type.txt diff --git a/pylint/test/functional/ctor_arguments.py b/pylint/test/functional/ctor_arguments.py new file mode 100644 index 0000000..7dc0a09 --- /dev/null +++ b/pylint/test/functional/ctor_arguments.py @@ -0,0 +1,78 @@ +"""Test function argument checker on __init__ + +Based on test/functional/arguments.py +""" +# pylint: disable=C0111,R0903,W0231 + + +class Class1Arg(object): + def __init__(self, first_argument): + """one argument function""" + +class Class3Arg(object): + def __init__(self, first_argument, second_argument, third_argument): + """three arguments function""" + +class ClassDefaultArg(object): + def __init__(self, one=1, two=2): + """function with default value""" + +class Subclass1Arg(Class1Arg): + pass + +class ClassAllArgs(Class1Arg): + def __init__(self, *args, **kwargs): + pass + +class ClassMultiInheritance(Class1Arg, Class3Arg): + pass + +class ClassNew(object): + def __new__(cls, first_argument, kwarg=None): + return first_argument, kwarg + +Class1Arg(420) +Class1Arg() # [no-value-for-parameter] +Class1Arg(1337, 347) # [too-many-function-args] + +Class3Arg(420, 789) # [no-value-for-parameter] +# +1:[no-value-for-parameter,no-value-for-parameter,no-value-for-parameter] +Class3Arg() +Class3Arg(1337, 347, 456) +Class3Arg('bab', 'bebe', None, 5.6) # [too-many-function-args] + +ClassDefaultArg(1, two=5) +ClassDefaultArg(two=5) + +Class1Arg(bob=4) # [no-value-for-parameter,unexpected-keyword-arg] +ClassDefaultArg(1, 4, coin="hello") # [unexpected-keyword-arg] + +ClassDefaultArg(1, one=5) # [redundant-keyword-arg] + +Subclass1Arg(420) +Subclass1Arg() # [no-value-for-parameter] +Subclass1Arg(1337, 347) # [too-many-function-args] + +ClassAllArgs() +ClassAllArgs(1, 2, 3, even=4, more=5) + +ClassMultiInheritance(1) +ClassMultiInheritance(1, 2, 3) # [too-many-function-args] + +ClassNew(1, kwarg=1) +ClassNew(1, 2, 3) # [too-many-function-args] +ClassNew(one=2) # [no-value-for-parameter,unexpected-keyword-arg] + + +class Metaclass(type): + def __new__(mcs, name, bases, namespace): + return type.__new__(mcs, name, bases, namespace) + +def with_metaclass(meta, base=object): + """Create a new type that can be used as a metaclass.""" + return meta("NewBase", (base, ), {}) + +class ClassWithMeta(with_metaclass(Metaclass)): + pass + +ClassWithMeta() diff --git a/pylint/test/functional/ctor_arguments.txt b/pylint/test/functional/ctor_arguments.txt new file mode 100644 index 0000000..85cf139 --- /dev/null +++ b/pylint/test/functional/ctor_arguments.txt @@ -0,0 +1,17 @@ +no-value-for-parameter:35::No value for argument 'first_argument' in constructor call +too-many-function-args:36::Too many positional arguments for constructor call +no-value-for-parameter:38::No value for argument 'third_argument' in constructor call +no-value-for-parameter:40::No value for argument 'first_argument' in constructor call +no-value-for-parameter:40::No value for argument 'second_argument' in constructor call +no-value-for-parameter:40::No value for argument 'third_argument' in constructor call +too-many-function-args:42::Too many positional arguments for constructor call +no-value-for-parameter:47::No value for argument 'first_argument' in constructor call +unexpected-keyword-arg:47::Unexpected keyword argument 'bob' in constructor call +unexpected-keyword-arg:48::Unexpected keyword argument 'coin' in constructor call +redundant-keyword-arg:50::Argument 'one' passed by position and keyword in constructor call +no-value-for-parameter:53::No value for argument 'first_argument' in constructor call +too-many-function-args:54::Too many positional arguments for constructor call +too-many-function-args:60::Too many positional arguments for constructor call +too-many-function-args:63::Too many positional arguments for constructor call +no-value-for-parameter:64::No value for argument 'first_argument' in constructor call +unexpected-keyword-arg:64::Unexpected keyword argument 'one' in constructor call diff --git a/pylint/test/functional/defined_and_used_on_same_line.py b/pylint/test/functional/defined_and_used_on_same_line.py new file mode 100644 index 0000000..fc19dc7 --- /dev/null +++ b/pylint/test/functional/defined_and_used_on_same_line.py @@ -0,0 +1,30 @@ +"""Check for definitions and usage happening on the same line.""" +#pylint: disable=missing-docstring,multiple-statements,print-statement,no-absolute-import,parameter-unpacking + +print [index + for index in range(10)] + +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 + +FUNC4 = lambda a, b: a != b +FUNC3 = lambda (a, b): a != b + +# test http://www.logilab.org/ticket/6954: + +with open('f') as f: print f.read() + +with open('f') as f, open(f.read()) as g: + print g.read() diff --git a/pylint/test/functional/docstrings.py b/pylint/test/functional/docstrings.py new file mode 100644 index 0000000..8b92b08 --- /dev/null +++ b/pylint/test/functional/docstrings.py @@ -0,0 +1,83 @@ +# pylint: disable=R0201,print-statement
+# -1: [missing-docstring]
+__revision__ = ''
+
+# +1: [empty-docstring]
+def function0():
+ """"""
+
+# +1: [missing-docstring]
+def function1(value):
+ # missing docstring
+ print value
+
+def function2(value):
+ """docstring"""
+ print value
+
+def function3(value):
+ """docstring"""
+ print value
+
+# +1: [missing-docstring]
+class AAAA(object):
+ # missing docstring
+
+## class BBBB:
+## # missing docstring
+## pass
+
+## class CCCC:
+## """yeah !"""
+## def method1(self):
+## pass
+
+## def method2(self):
+## """ yeah !"""
+## pass
+
+ # +1: [missing-docstring]
+ def method1(self):
+ pass
+
+ def method2(self):
+ """ yeah !"""
+ pass
+
+ # +1: [empty-docstring]
+ def method3(self):
+ """"""
+ pass
+
+ def __init__(self):
+ pass
+
+class DDDD(AAAA):
+ """yeah !"""
+
+ def __init__(self):
+ AAAA.__init__(self)
+
+ # +1: [empty-docstring]
+ def method2(self):
+ """"""
+ pass
+
+ def method3(self):
+ pass
+
+ # +1: [missing-docstring]
+ def method4(self):
+ pass
+
+# pylint: disable=missing-docstring
+def function4():
+ pass
+
+# pylint: disable=empty-docstring
+def function5():
+ """"""
+ pass
+
+def function6():
+ """ I am a {} docstring.""".format("good")
diff --git a/pylint/test/functional/docstrings.txt b/pylint/test/functional/docstrings.txt new file mode 100644 index 0000000..e30e462 --- /dev/null +++ b/pylint/test/functional/docstrings.txt @@ -0,0 +1,8 @@ +missing-docstring:1::Missing module docstring +empty-docstring:6:function0:Empty function docstring +missing-docstring:10:function1:Missing function docstring +missing-docstring:23:AAAA:Missing class docstring +missing-docstring:40:AAAA.method1:Missing method docstring:INFERENCE +empty-docstring:48:AAAA.method3:Empty method docstring:INFERENCE +empty-docstring:62:DDDD.method2:Empty method docstring:INFERENCE +missing-docstring:70:DDDD.method4:Missing method docstring:INFERENCE diff --git a/pylint/test/functional/duplicate_dict_literal_key.py b/pylint/test/functional/duplicate_dict_literal_key.py new file mode 100644 index 0000000..f74e10e --- /dev/null +++ b/pylint/test/functional/duplicate_dict_literal_key.py @@ -0,0 +1,14 @@ +"""Check multiple key definition""" +#pylint: disable=C0103 + +correct_dict = { + 'tea': 'for two', + 'two': 'for tea', +} + +wrong_dict = { # [duplicate-key] + 'tea': 'for two', + 'two': 'for tea', + 'tea': 'time', + +} diff --git a/pylint/test/functional/duplicate_dict_literal_key.txt b/pylint/test/functional/duplicate_dict_literal_key.txt new file mode 100644 index 0000000..9780480 --- /dev/null +++ b/pylint/test/functional/duplicate_dict_literal_key.txt @@ -0,0 +1 @@ +duplicate-key:9::Duplicate key 'tea' in dictionary diff --git a/pylint/test/functional/exception_is_binary_op.py b/pylint/test/functional/exception_is_binary_op.py new file mode 100644 index 0000000..b9c51d4 --- /dev/null +++ b/pylint/test/functional/exception_is_binary_op.py @@ -0,0 +1,12 @@ +"""Warn about binary operations used as exceptions.""" +from __future__ import print_function +try: + pass +except Exception or BaseException: # [binary-op-exception] + print("caught1") +except Exception and BaseException: # [binary-op-exception] + print("caught2") +except Exception or BaseException: # [binary-op-exception] + print("caught3") +except (Exception or BaseException), exc: # [binary-op-exception] + print("caught4") diff --git a/pylint/test/functional/exception_is_binary_op.txt b/pylint/test/functional/exception_is_binary_op.txt new file mode 100644 index 0000000..e6512c9 --- /dev/null +++ b/pylint/test/functional/exception_is_binary_op.txt @@ -0,0 +1,4 @@ +binary-op-exception:5::"Exception to catch is the result of a binary ""or"" operation" +binary-op-exception:7::"Exception to catch is the result of a binary ""and"" operation" +binary-op-exception:9::"Exception to catch is the result of a binary ""or"" operation" +binary-op-exception:11::"Exception to catch is the result of a binary ""or"" operation" diff --git a/pylint/test/functional/formatting.txt b/pylint/test/functional/formatting.txt new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/pylint/test/functional/formatting.txt diff --git a/pylint/test/functional/future_import.py b/pylint/test/functional/future_import.py new file mode 100644 index 0000000..e4d3785 --- /dev/null +++ b/pylint/test/functional/future_import.py @@ -0,0 +1,3 @@ +"""a docstring""" + +from __future__ import generators diff --git a/pylint/test/functional/future_unicode_literals.py b/pylint/test/functional/future_unicode_literals.py new file mode 100644 index 0000000..30c2bd6 --- /dev/null +++ b/pylint/test/functional/future_unicode_literals.py @@ -0,0 +1,6 @@ +"""Unicode literals in Python 2.*""" +from __future__ import unicode_literals + + +BAD_STRING = b'\u1234' # >= 2.7.4:[anomalous-unicode-escape-in-string] +GOOD_STRING = '\u1234' diff --git a/pylint/test/functional/future_unicode_literals.txt b/pylint/test/functional/future_unicode_literals.txt new file mode 100644 index 0000000..60d291e --- /dev/null +++ b/pylint/test/functional/future_unicode_literals.txt @@ -0,0 +1 @@ +anomalous-unicode-escape-in-string:5::"Anomalous Unicode escape in byte string: '\u'. String constant might be missing an r or u prefix." diff --git a/pylint/test/functional/generated_members.py b/pylint/test/functional/generated_members.py new file mode 100644 index 0000000..e7b1def --- /dev/null +++ b/pylint/test/functional/generated_members.py @@ -0,0 +1,7 @@ +"""Test the generated-members config option.""" + +class Klass(object): + """A class with a generated member.""" + +print Klass().DoesNotExist +print Klass().aBC_set1 diff --git a/pylint/test/functional/generated_members.rc b/pylint/test/functional/generated_members.rc new file mode 100644 index 0000000..4657c4e --- /dev/null +++ b/pylint/test/functional/generated_members.rc @@ -0,0 +1,5 @@ +[Messages Control] +disable=too-few-public-methods,print-statement + +[typecheck] +generated-members=DoesNotExist,"[a-zA-Z]+_set{1,2}"" diff --git a/pylint/test/functional/genexpr_variable_scope.py b/pylint/test/functional/genexpr_variable_scope.py new file mode 100644 index 0000000..a00d72d --- /dev/null +++ b/pylint/test/functional/genexpr_variable_scope.py @@ -0,0 +1,5 @@ +"""test name defined in generator expression are not available +outside the genexpr scope +""" +from __future__ import print_function +print(n) # [undefined-variable] diff --git a/pylint/test/functional/genexpr_variable_scope.txt b/pylint/test/functional/genexpr_variable_scope.txt new file mode 100644 index 0000000..3cfa6c5 --- /dev/null +++ b/pylint/test/functional/genexpr_variable_scope.txt @@ -0,0 +1 @@ +undefined-variable:5::Undefined variable 'n' diff --git a/pylint/test/functional/globals.py b/pylint/test/functional/globals.py new file mode 100644 index 0000000..c2844b1 --- /dev/null +++ b/pylint/test/functional/globals.py @@ -0,0 +1,25 @@ +"""Warnings about global statements and usage of global variables.""" +from __future__ import print_function + +global CSTE # [global-at-module-level] +print(CSTE) # [undefined-variable] + +CONSTANT = 1 + +def fix_contant(value): + """all this is ok, but try not using global ;)""" + global CONSTANT # [global-statement] + print(CONSTANT) + CONSTANT = value + + +def other(): + """global behaviour test""" + global HOP # [global-variable-not-assigned] + print(HOP) # [undefined-variable] + + +def define_constant(): + """ok but somevar is not defined at the module scope""" + global SOMEVAR # [global-variable-undefined] + SOMEVAR = 2 diff --git a/pylint/test/functional/globals.txt b/pylint/test/functional/globals.txt new file mode 100644 index 0000000..c675906 --- /dev/null +++ b/pylint/test/functional/globals.txt @@ -0,0 +1,6 @@ +global-at-module-level:4::Using the global statement at the module level +undefined-variable:5::Undefined variable 'CSTE' +global-statement:11:fix_contant:Using the global statement +global-variable-not-assigned:18:other:Using global for 'HOP' but no assignment is done +undefined-variable:19:other:Undefined variable 'HOP' +global-variable-undefined:24:define_constant:Global variable 'SOMEVAR' undefined at the module level diff --git a/pylint/test/functional/import_error.py b/pylint/test/functional/import_error.py new file mode 100644 index 0000000..e44f16f --- /dev/null +++ b/pylint/test/functional/import_error.py @@ -0,0 +1,18 @@ +""" Test that import errors are detected. """
+# pylint: disable=invalid-name, unused-import, no-absolute-import
+import totally_missing # [import-error]
+
+try:
+ import maybe_missing
+except ImportError:
+ maybe_missing = None
+
+try:
+ import maybe_missing_1
+except (ImportError, SyntaxError):
+ maybe_missing_1 = None
+
+try:
+ import maybe_missing_2 # [import-error]
+except ValueError:
+ maybe_missing_2 = None
diff --git a/pylint/test/functional/import_error.txt b/pylint/test/functional/import_error.txt new file mode 100644 index 0000000..0602d2a --- /dev/null +++ b/pylint/test/functional/import_error.txt @@ -0,0 +1,2 @@ +import-error:3::Unable to import 'totally_missing' +import-error:16::Unable to import 'maybe_missing_2' diff --git a/pylint/test/functional/indexing_exception.py b/pylint/test/functional/indexing_exception.py new file mode 100644 index 0000000..7ea50d5 --- /dev/null +++ b/pylint/test/functional/indexing_exception.py @@ -0,0 +1,15 @@ +""" +Check for indexing exceptions. +""" +# pylint: disable=import-error, no-absolute-import +__revision__ = 0 + +from unknown import ExtensionException + +class SubException(IndexError): + """ empty """ + +_ = IndexError("test")[0] # [indexing-exception] +_ = ZeroDivisionError("error")[0] # [indexing-exception] +_ = ExtensionException("error")[0] +_ = SubException("error")[1] # [indexing-exception] diff --git a/pylint/test/functional/indexing_exception.rc b/pylint/test/functional/indexing_exception.rc new file mode 100644 index 0000000..9540bc9 --- /dev/null +++ b/pylint/test/functional/indexing_exception.rc @@ -0,0 +1,5 @@ +[testoptions] +max_pyver=3.0 + +[Messages Control] +enable=python3
\ No newline at end of file diff --git a/pylint/test/functional/indexing_exception.txt b/pylint/test/functional/indexing_exception.txt new file mode 100644 index 0000000..9f30678 --- /dev/null +++ b/pylint/test/functional/indexing_exception.txt @@ -0,0 +1,3 @@ +indexing-exception:12::Indexing exceptions will not work on Python 3
+indexing-exception:13::Indexing exceptions will not work on Python 3
+indexing-exception:15::Indexing exceptions will not work on Python 3
\ No newline at end of file diff --git a/pylint/test/functional/inherit_non_class.py b/pylint/test/functional/inherit_non_class.py new file mode 100644 index 0000000..73b064b --- /dev/null +++ b/pylint/test/functional/inherit_non_class.py @@ -0,0 +1,65 @@ +"""Test that inheriting from something which is not
+a class emits a warning. """
+
+# pylint: disable=no-init, import-error, invalid-name
+# pylint: disable=missing-docstring, too-few-public-methods, no-absolute-import
+
+from missing import Missing
+
+if 1:
+ Ambiguous = None
+else:
+ Ambiguous = int
+
+class Empty(object):
+ """ Empty class. """
+
+def return_class():
+ """ Return a class. """
+ return Good3
+
+class Bad(1): # [inherit-non-class]
+ """ Can't inherit from instance. """
+
+class Bad1(lambda abc: 42): # [inherit-non-class]
+ """ Can't inherit from lambda. """
+
+class Bad2(object()): # [inherit-non-class]
+ """ Can't inherit from an instance of object. """
+
+class Bad3(return_class): # [inherit-non-class]
+ """ Can't inherit from function. """
+
+class Bad4(Empty()): # [inherit-non-class]
+ """ Can't inherit from instance. """
+
+class Good(object):
+ pass
+
+class Good1(int):
+ pass
+
+class Good2(type):
+ pass
+
+class Good3(type(int)):
+ pass
+
+class Good4(return_class()):
+ pass
+
+class Good5(Good4, int, object):
+ pass
+
+class Good6(Ambiguous):
+ """ Inherits from something ambiguous.
+
+ This could emit a warning when we will have
+ flow detection.
+ """
+
+class Unknown(Missing):
+ pass
+
+class Unknown1(Good5 if True else Bad1):
+ pass
diff --git a/pylint/test/functional/inherit_non_class.txt b/pylint/test/functional/inherit_non_class.txt new file mode 100644 index 0000000..6821f3b --- /dev/null +++ b/pylint/test/functional/inherit_non_class.txt @@ -0,0 +1,5 @@ +inherit-non-class:21:Bad:Inheriting '1', which is not a class. +inherit-non-class:24:Bad1:"Inheriting 'lambda abc: 42', which is not a class." +inherit-non-class:27:Bad2:Inheriting 'object()', which is not a class. +inherit-non-class:30:Bad3:Inheriting 'return_class', which is not a class. +inherit-non-class:33:Bad4:Inheriting 'Empty()', which is not a class.
\ No newline at end of file diff --git a/pylint/test/functional/init_not_called.py b/pylint/test/functional/init_not_called.py new file mode 100644 index 0000000..e22be39 --- /dev/null +++ b/pylint/test/functional/init_not_called.py @@ -0,0 +1,59 @@ +# pylint: disable=R0903 +"""test for __init__ not called +""" +from __future__ import print_function + +class AAAA: # <3.0:[old-style-class] + """ancestor 1""" + + def __init__(self): + print('init', self) + +class BBBB: # <3.0:[old-style-class] + """ancestor 2""" + + def __init__(self): + print('init', self) + +class CCCC: # <3.0:[old-style-class,no-init] + """ancestor 3""" + + +class ZZZZ(AAAA, BBBB, CCCC): + """derived class""" + + def __init__(self): # [super-init-not-called] + 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__() + +class NoInit(object): + """No __init__ defined""" + +class Init(NoInit): + """Don't complain for not calling the super __init__""" + + def __init__(self, arg): + self.arg = arg + +class NewStyleC(object): + """__init__ defined by assignemnt.""" + def xx_init(self): + """Initializer.""" + pass + + __init__ = xx_init + +class AssignedInit(NewStyleC): + """No init called.""" + def __init__(self): # [super-init-not-called] + self.arg = 0 diff --git a/pylint/test/functional/init_not_called.txt b/pylint/test/functional/init_not_called.txt new file mode 100644 index 0000000..b0bc418 --- /dev/null +++ b/pylint/test/functional/init_not_called.txt @@ -0,0 +1,6 @@ +old-style-class:6:AAAA:Old-style class defined. +old-style-class:12:BBBB:Old-style class defined. +no-init:18:CCCC:Class has no __init__ method +old-style-class:18:CCCC:Old-style class defined. +super-init-not-called:25:ZZZZ.__init__:__init__ method from base class 'BBBB' is not called +super-init-not-called:58:AssignedInit.__init__:__init__ method from base class 'NewStyleC' is not called diff --git a/pylint/test/functional/invalid__all__object.py b/pylint/test/functional/invalid__all__object.py new file mode 100644 index 0000000..2a1a592 --- /dev/null +++ b/pylint/test/functional/invalid__all__object.py @@ -0,0 +1,3 @@ +"""Test that non-inferable __all__ variables do not make Pylint crash.""" + +__all__ = [SomeUndefinedName] # [undefined-variable] diff --git a/pylint/test/functional/invalid__all__object.txt b/pylint/test/functional/invalid__all__object.txt new file mode 100644 index 0000000..03ad496 --- /dev/null +++ b/pylint/test/functional/invalid__all__object.txt @@ -0,0 +1 @@ +undefined-variable:3::Undefined variable 'SomeUndefinedName' diff --git a/pylint/test/functional/invalid_encoded_data.py b/pylint/test/functional/invalid_encoded_data.py new file mode 100644 index 0000000..743031c --- /dev/null +++ b/pylint/test/functional/invalid_encoded_data.py @@ -0,0 +1,5 @@ +# coding: utf-8 +"""Test data file with encoding errors.""" + + +STR = 'СуÑеÑтвительное' # [invalid-encoded-data] diff --git a/pylint/test/functional/invalid_encoded_data.txt b/pylint/test/functional/invalid_encoded_data.txt new file mode 100644 index 0000000..c6e69dc --- /dev/null +++ b/pylint/test/functional/invalid_encoded_data.txt @@ -0,0 +1 @@ +invalid-encoded-data:5::"Cannot decode using encoding ""utf-8"", unexpected byte at position 11" diff --git a/pylint/test/functional/invalid_exceptions_caught.py b/pylint/test/functional/invalid_exceptions_caught.py new file mode 100644 index 0000000..c58d2e9 --- /dev/null +++ b/pylint/test/functional/invalid_exceptions_caught.py @@ -0,0 +1,92 @@ +"""Test for catching non-exceptions.""" +# pylint: disable=too-many-ancestors, print-statement, no-absolute-import, import-error +import socket + +class MyException(object): + """Custom 'exception'.""" + +class MySecondException(object): + """Custom 'exception'.""" + +class MyGoodException(Exception): + """Custom exception.""" + +class MySecondGoodException(MyGoodException): + """Custom exception.""" + +class SkipException(socket.error): + """Not an exception for Python 2, but one in 3.""" + +class SecondSkipException(SkipException): + """Also a good exception.""" + +try: + 1 + 1 +except MyException: # [catching-non-exception] + print "caught" + +try: + 1 + 2 +# +1:[catching-non-exception,catching-non-exception] +except (MyException, MySecondException): + print "caught" + +try: + 1 + 3 +except MyGoodException: + print "caught" + +try: + 1 + 3 +except (MyGoodException, MySecondGoodException): + print "caught" + +try: + 1 + 3 +except (SkipException, SecondSkipException): + print "caught" + +try: + 1 + 42 +# +1:[catching-non-exception,catching-non-exception] +except (None, list()): + print "caught" + +try: + 1 + 24 +except None: # [catching-non-exception] + print "caught" + +EXCEPTION = None +EXCEPTION = ZeroDivisionError +try: + 1 + 46 +except EXCEPTION: + print "caught" + +try: + 1 + 42 +# +1:[catching-non-exception,catching-non-exception,catching-non-exception] +except (list([4, 5, 6]), None, ZeroDivisionError, 4): + print "caught" + +EXCEPTION_TUPLE = (ZeroDivisionError, OSError) +NON_EXCEPTION_TUPLE = (ZeroDivisionError, OSError, 4) + +try: + 1 + 42 +except EXCEPTION_TUPLE: + print "caught" + +try: + 1 + 42 +except NON_EXCEPTION_TUPLE: # [catching-non-exception] + print "caught" + +from missing_import import UnknownError +UNKNOWN_COMPONENTS = (ZeroDivisionError, UnknownError) + +try: + 1 + 42 +except UNKNOWN_COMPONENTS: + print "caught" diff --git a/pylint/test/functional/invalid_exceptions_caught.txt b/pylint/test/functional/invalid_exceptions_caught.txt new file mode 100644 index 0000000..d2e6f3c --- /dev/null +++ b/pylint/test/functional/invalid_exceptions_caught.txt @@ -0,0 +1,10 @@ +catching-non-exception:25::"Catching an exception which doesn't inherit from BaseException: MyException" +catching-non-exception:31::"Catching an exception which doesn't inherit from BaseException: MyException" +catching-non-exception:31::"Catching an exception which doesn't inherit from BaseException: MySecondException" +catching-non-exception:52::"Catching an exception which doesn't inherit from BaseException: None" +catching-non-exception:52::"Catching an exception which doesn't inherit from BaseException: list()" +catching-non-exception:57::"Catching an exception which doesn't inherit from BaseException: None" +catching-non-exception:70::"Catching an exception which doesn't inherit from BaseException: 4" +catching-non-exception:70::"Catching an exception which doesn't inherit from BaseException: None" +catching-non-exception:70::"Catching an exception which doesn't inherit from BaseException: list([4, 5, 6])" +catching-non-exception:83::"Catching an exception which doesn't inherit from BaseException: NON_EXCEPTION_TUPLE" diff --git a/pylint/test/functional/invalid_exceptions_raised.py b/pylint/test/functional/invalid_exceptions_raised.py new file mode 100644 index 0000000..ee03de8 --- /dev/null +++ b/pylint/test/functional/invalid_exceptions_raised.py @@ -0,0 +1,76 @@ +# pylint:disable=too-few-public-methods,old-style-class,no-init +"""test pb with exceptions and old/new style classes""" + + +class ValidException(Exception): + """Valid Exception.""" + +class OldStyleClass: + """Not an exception.""" + +class NewStyleClass(object): + """Not an exception.""" + + +def good_case(): + """raise""" + raise ValidException('hop') + +def good_case1(): + """zlib.error is defined in C module.""" + import zlib + raise zlib.error(4) + +def good_case2(): + """decimal.DivisionByZero is defined in C on Python 3.""" + import decimal + raise decimal.DivisionByZero(4) + +def good_case3(): + """io.BlockingIOError is defined in C.""" + import io + raise io.BlockingIOError + +def bad_case0(): + """raise""" + # +2:<3.0:[nonstandard-exception] + # +1:>=3.0:[raising-non-exception] + raise OldStyleClass('hop') + +def bad_case1(): + """raise""" + raise NewStyleClass() # [raising-non-exception] + +def bad_case2(): + """raise""" + # +2:<3.0:[nonstandard-exception] + # +1:>=3.0:[raising-non-exception] + raise OldStyleClass, 'hop' + +def bad_case3(): + """raise""" + raise NewStyleClass # [raising-non-exception] + +def bad_case4(): + """raise""" + raise NotImplemented, 'hop' # [notimplemented-raised] + +def bad_case5(): + """raise""" + raise 1 # [raising-bad-type] + +def bad_case6(): + """raise""" + raise None # [raising-bad-type] + +def bad_case7(): + """raise list""" + raise list # [raising-non-exception] + +def bad_case8(): + """raise tuple""" + raise tuple # [raising-non-exception] + +def bad_case9(): + """raise dict""" + raise dict # [raising-non-exception] diff --git a/pylint/test/functional/invalid_exceptions_raised.txt b/pylint/test/functional/invalid_exceptions_raised.txt new file mode 100644 index 0000000..2ec253c --- /dev/null +++ b/pylint/test/functional/invalid_exceptions_raised.txt @@ -0,0 +1,12 @@ +nonstandard-exception:38:bad_case0:"Exception doesn't inherit from standard ""Exception"" class":INFERENCE +raising-non-exception:38:bad_case0:Raising a new style class which doesn't inherit from BaseException +raising-non-exception:42:bad_case1:Raising a new style class which doesn't inherit from BaseException +nonstandard-exception:48:bad_case2:"Exception doesn't inherit from standard ""Exception"" class":INFERENCE +raising-non-exception:48:bad_case2:Raising a new style class which doesn't inherit from BaseException +raising-non-exception:52:bad_case3:Raising a new style class which doesn't inherit from BaseException +notimplemented-raised:56:bad_case4:NotImplemented raised - should raise NotImplementedError +raising-bad-type:60:bad_case5:Raising int while only classes or instances are allowed +raising-bad-type:64:bad_case6:Raising NoneType while only classes or instances are allowed +raising-non-exception:68:bad_case7:Raising a new style class which doesn't inherit from BaseException +raising-non-exception:72:bad_case8:Raising a new style class which doesn't inherit from BaseException +raising-non-exception:76:bad_case9:Raising a new style class which doesn't inherit from BaseException
\ No newline at end of file diff --git a/pylint/test/functional/invalid_name.py b/pylint/test/functional/invalid_name.py new file mode 100644 index 0000000..e86cfb3 --- /dev/null +++ b/pylint/test/functional/invalid_name.py @@ -0,0 +1,28 @@ +""" Tests for invalid-name checker. """
+# pylint: disable=unused-import, no-absolute-import
+
+AAA = 24
+try:
+ import collections
+except ImportError:
+ collections = None
+
+aaa = 42 # [invalid-name]
+try:
+ import time
+except ValueError:
+ time = None # [invalid-name]
+
+try:
+ from sys import argv, executable as python
+except ImportError:
+ argv = 42
+ python = 24
+
+def test():
+ """ Shouldn't emit an invalid-name here. """
+ try:
+ import re
+ except ImportError:
+ re = None
+ return re
diff --git a/pylint/test/functional/invalid_name.txt b/pylint/test/functional/invalid_name.txt new file mode 100644 index 0000000..0c8eafe --- /dev/null +++ b/pylint/test/functional/invalid_name.txt @@ -0,0 +1,2 @@ +invalid-name:10::"Invalid constant name ""aaa""" +invalid-name:14::"Invalid constant name ""time""" diff --git a/pylint/test/functional/invalid_slice_index.py b/pylint/test/functional/invalid_slice_index.py new file mode 100644 index 0000000..63ba252 --- /dev/null +++ b/pylint/test/functional/invalid_slice_index.py @@ -0,0 +1,60 @@ +"""Errors for invalid slice indices""" +# pylint: disable=too-few-public-methods, no-self-use + + +TESTLIST = [1, 2, 3] + +# Invalid indices +def function1(): + """functions used as indices""" + return TESTLIST[id:id:] # [invalid-slice-index,invalid-slice-index] + +def function2(): + """strings used as indices""" + return TESTLIST['0':'1':] # [invalid-slice-index,invalid-slice-index] + +def function3(): + """class without __index__ used as index""" + + class NoIndexTest(object): + """Class with no __index__ method""" + pass + + return TESTLIST[NoIndexTest()::] # [invalid-slice-index] + +# Valid indices +def function4(): + """integers used as indices""" + return TESTLIST[0:0:0] # no error + +def function5(): + """None used as indices""" + return TESTLIST[None:None:None] # no error + +def function6(): + """class with __index__ used as index""" + class IndexTest(object): + """Class with __index__ method""" + def __index__(self): + """Allow objects of this class to be used as slice indices""" + return 0 + + return TESTLIST[IndexTest():None:None] # no error + +def function7(): + """class with __index__ in superclass used as index""" + class IndexType(object): + """Class with __index__ method""" + def __index__(self): + """Allow objects of this class to be used as slice indices""" + return 0 + + class IndexSubType(IndexType): + """Class with __index__ in parent""" + pass + + return TESTLIST[IndexSubType():None:None] # no error + +def function8(): + """slice object used as index""" + return TESTLIST[slice(1, 2, 3)] # no error diff --git a/pylint/test/functional/invalid_slice_index.txt b/pylint/test/functional/invalid_slice_index.txt new file mode 100644 index 0000000..cd4dc6e --- /dev/null +++ b/pylint/test/functional/invalid_slice_index.txt @@ -0,0 +1,5 @@ +invalid-slice-index:10:function1:Slice index is not an int, None, or instance with __index__ +invalid-slice-index:10:function1:Slice index is not an int, None, or instance with __index__ +invalid-slice-index:14:function2:Slice index is not an int, None, or instance with __index__ +invalid-slice-index:14:function2:Slice index is not an int, None, or instance with __index__ +invalid-slice-index:23:function3:Slice index is not an int, None, or instance with __index__ diff --git a/pylint/test/functional/line_endings.py b/pylint/test/functional/line_endings.py new file mode 100644 index 0000000..d6bc3fc --- /dev/null +++ b/pylint/test/functional/line_endings.py @@ -0,0 +1,4 @@ +"mixing line endings are not welcome" +# +1: [unexpected-line-ending-format, mixed-line-endings] +CONST = 1
+ diff --git a/pylint/test/functional/line_endings.rc b/pylint/test/functional/line_endings.rc new file mode 100644 index 0000000..95532ea --- /dev/null +++ b/pylint/test/functional/line_endings.rc @@ -0,0 +1,2 @@ +[Format] +expected-line-ending-format=LF diff --git a/pylint/test/functional/line_endings.txt b/pylint/test/functional/line_endings.txt new file mode 100644 index 0000000..a18a872 --- /dev/null +++ b/pylint/test/functional/line_endings.txt @@ -0,0 +1,2 @@ +mixed-line-endings:3::Mixed line endings LF and CRLF +unexpected-line-ending-format:3::Unexpected line ending format. There is 'CRLF' while it should be 'LF'. diff --git a/pylint/test/functional/long_lines_with_utf8.py b/pylint/test/functional/long_lines_with_utf8.py new file mode 100644 index 0000000..a1d90ed --- /dev/null +++ b/pylint/test/functional/long_lines_with_utf8.py @@ -0,0 +1,7 @@ +# coding: utf-8 +"""Test data file files with non-ASCII content.""" + + +THIS_IS_A_LONG_VARIABLE_NAME = 'СущеÑтвительное ЧаÑтица' # but the line is okay + +THIS_IS_A_VERY_LONG_VARIABLE_NAME = 'СущеÑтвительное ЧаÑтица' # and the line is not okay # [line-too-long] diff --git a/pylint/test/functional/long_lines_with_utf8.txt b/pylint/test/functional/long_lines_with_utf8.txt new file mode 100644 index 0000000..42a7976 --- /dev/null +++ b/pylint/test/functional/long_lines_with_utf8.txt @@ -0,0 +1 @@ +line-too-long:7::Line too long (108/100) diff --git a/pylint/test/functional/member_checks.py b/pylint/test/functional/member_checks.py new file mode 100644 index 0000000..78c4625 --- /dev/null +++ b/pylint/test/functional/member_checks.py @@ -0,0 +1,63 @@ +# pylint: disable=print-statement +"""check getattr if inference succeed""" + + +class Provider(object): + """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(object): + """use provider class""" + + def __init__(self): + self._prov = Provider() + self._prov_attr = Provider.cattr + self._prov_attr2 = Provider.cattribute # [no-member] + self.set_later = 0 + + def set_set_later(self, value): + """set set_later attribute (introduce an inference ambiguity)""" + self.set_later = value + + def use_method(self): + """use provider's method""" + self._prov.hophop() + self._prov.hophophop() # [no-member] + + def use_attr(self): + """use provider's attr""" + print self._prov.attr + print self._prov.attribute # [no-member] + + 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) # [no-member] + dic = {} + dic.set(self) # [no-member] + tup = () + tup.append(self) # [no-member] + string = 'toto' + print string.loower() # [no-member] + integer = 1 + print integer.whatever # [no-member] + +print object.__init__ +print property.__init__ +print Client().set_later.lower() # [no-member] diff --git a/pylint/test/functional/member_checks.txt b/pylint/test/functional/member_checks.txt new file mode 100644 index 0000000..12fe6ee --- /dev/null +++ b/pylint/test/functional/member_checks.txt @@ -0,0 +1,9 @@ +no-member:24:Client.__init__:Class 'Provider' has no 'cattribute' member:INFERENCE +no-member:34:Client.use_method:Instance of 'Provider' has no 'hophophop' member:INFERENCE +no-member:39:Client.use_attr:Instance of 'Provider' has no 'attribute' member:INFERENCE +no-member:51:Client.test_bt_types:Instance of 'list' has no 'apppend' member:INFERENCE +no-member:53:Client.test_bt_types:Instance of 'dict' has no 'set' member:INFERENCE +no-member:55:Client.test_bt_types:Instance of 'tuple' has no 'append' member:INFERENCE +no-member:57:Client.test_bt_types:Instance of 'str' has no 'loower' member:INFERENCE +no-member:59:Client.test_bt_types:Instance of 'int' has no 'whatever' member:INFERENCE +no-member:63::Instance of 'int' has no 'lower' member:INFERENCE_FAILURE diff --git a/pylint/test/functional/method_hidden.py b/pylint/test/functional/method_hidden.py new file mode 100644 index 0000000..98fd4c7 --- /dev/null +++ b/pylint/test/functional/method_hidden.py @@ -0,0 +1,15 @@ +# pylint: disable=too-few-public-methods,print-statement +"""check method hidding ancestor attribute +""" + +class Abcd(object): + """dummy""" + def __init__(self): + self.abcd = 1 + +class Cdef(Abcd): + """dummy""" + def abcd(self): # [method-hidden] + """test + """ + print self diff --git a/pylint/test/functional/method_hidden.txt b/pylint/test/functional/method_hidden.txt new file mode 100644 index 0000000..c15f3bc --- /dev/null +++ b/pylint/test/functional/method_hidden.txt @@ -0,0 +1 @@ +method-hidden:12:Cdef.abcd:An attribute defined in functional.method_hidden line 8 hides this method diff --git a/pylint/test/functional/missing_final_newline.py b/pylint/test/functional/missing_final_newline.py new file mode 100644 index 0000000..370f902 --- /dev/null +++ b/pylint/test/functional/missing_final_newline.py @@ -0,0 +1,4 @@ +"""This file does not have a final newline.""" +from __future__ import print_function +# +1:[missing-final-newline] +print(1)
\ No newline at end of file diff --git a/pylint/test/functional/missing_final_newline.txt b/pylint/test/functional/missing_final_newline.txt new file mode 100644 index 0000000..b53c980 --- /dev/null +++ b/pylint/test/functional/missing_final_newline.txt @@ -0,0 +1 @@ +missing-final-newline:4::Final newline missing diff --git a/pylint/test/functional/missing_self_argument.py b/pylint/test/functional/missing_self_argument.py new file mode 100644 index 0000000..6477fcf --- /dev/null +++ b/pylint/test/functional/missing_self_argument.py @@ -0,0 +1,20 @@ +"""Checks that missing self in method defs don't crash Pylint.""" + + + +class MyClass(object): + """A class with some methods missing self args.""" + + def __init__(self): + self.var = "var" + + def method(): # [no-method-argument] + """A method without a self argument.""" + + def setup(): # [no-method-argument] + """A method without a self argument, but usage.""" + self.var = 1 # [undefined-variable] + + def correct(self): + """Correct.""" + self.var = "correct" diff --git a/pylint/test/functional/missing_self_argument.txt b/pylint/test/functional/missing_self_argument.txt new file mode 100644 index 0000000..1deef18 --- /dev/null +++ b/pylint/test/functional/missing_self_argument.txt @@ -0,0 +1,6 @@ +no-method-argument:11:MyClass.method:Method has no argument +no-method-argument:13:MyClass.met:"""Method has no argument +"" +" +no-method-argument:14:MyClass.setup:Method has no argument +undefined-variable:16:MyClass.setup:Undefined variable 'self' diff --git a/pylint/test/functional/name_styles.py b/pylint/test/functional/name_styles.py new file mode 100644 index 0000000..05abeb7 --- /dev/null +++ b/pylint/test/functional/name_styles.py @@ -0,0 +1,118 @@ +"""Test for the invalid-name warning.""" +# pylint: disable=print-statement, no-absolute-import +import abc +import collections + +GOOD_CONST_NAME = '' +bad_const_name = 0 # [invalid-name] + + +def BADFUNCTION_name(): # [invalid-name] + """Bad function name.""" + BAD_LOCAL_VAR = 1 # [invalid-name] + print BAD_LOCAL_VAR + + +def func_bad_argname(NOT_GOOD): # [invalid-name] + """Function with a badly named argument.""" + return NOT_GOOD + + +def no_nested_args(arg1, arg21, arg22): + """Well-formed function.""" + print arg1, arg21, arg22 + + +class bad_class_name(object): # [invalid-name] + """Class with a bad name.""" + + +class CorrectClassName(object): + """Class with a good name.""" + + def __init__(self): + self._good_private_name = 10 + self.__good_real_private_name = 11 + self.good_attribute_name = 12 + self._Bad_AtTR_name = None # [invalid-name] + self.Bad_PUBLIC_name = None # [invalid-name] + + zz = 'Bad Class Attribute' # [invalid-name] + GOOD_CLASS_ATTR = 'Good Class Attribute' + + def BadMethodName(self): # [invalid-name] + """A Method with a bad name.""" + + def good_method_name(self): + """A method with a good name.""" + + def __DunDER_IS_not_free_for_all__(self): # [invalid-name] + """Another badly named method.""" + + +class DerivedFromCorrect(CorrectClassName): + """A derived class with an invalid inherited members. + + Derived attributes and methods with invalid names do not trigger warnings. + """ + zz = 'Now a good class attribute' + + def __init__(self): + super(DerivedFromCorrect, self).__init__() + self._Bad_AtTR_name = None # Ignored + + def BadMethodName(self): + """Ignored since the method is in the interface.""" + + +V = [WHAT_Ever_inListComp for WHAT_Ever_inListComp in GOOD_CONST_NAME] + +def class_builder(): + """Function returning a class object.""" + + class EmbeddedClass(object): + """Useless class.""" + + return EmbeddedClass + +# +1:[invalid-name] +BAD_NAME_FOR_CLASS = collections.namedtuple('Named', ['tuple']) +NEXT_BAD_NAME_FOR_CLASS = class_builder() # [invalid-name] + +GoodName = collections.namedtuple('Named', ['tuple']) +ToplevelClass = class_builder() + +# Aliases for classes have the same name constraints. +AlsoCorrect = CorrectClassName +NOT_CORRECT = CorrectClassName # [invalid-name] + + +def test_globals(): + """Names in global statements are also checked.""" + global NOT_CORRECT + global AlsoCorrect # [invalid-name] + NOT_CORRECT = 1 + AlsoCorrect = 2 + + +class FooClass(object): + """A test case for property names. + + Since by default, the regex for attributes is the same as the one + for method names, we check the warning messages to contain the + string 'attribute'. + """ + @property + def PROPERTY_NAME(self): # [invalid-name] + """Ignored.""" + pass + + @abc.abstractproperty + def ABSTRACT_PROPERTY_NAME(self): # [invalid-name] + """Ignored.""" + pass + + @PROPERTY_NAME.setter + def PROPERTY_NAME_SETTER(self): # [invalid-name] + """Ignored.""" + pass diff --git a/pylint/test/functional/name_styles.rc b/pylint/test/functional/name_styles.rc new file mode 100644 index 0000000..1a63e67 --- /dev/null +++ b/pylint/test/functional/name_styles.rc @@ -0,0 +1,5 @@ +[testoptions] +min_pyver=2.6 + +[Messages Control] +disable=too-few-public-methods,abstract-class-not-used,global-statement diff --git a/pylint/test/functional/name_styles.txt b/pylint/test/functional/name_styles.txt new file mode 100644 index 0000000..0d578c8 --- /dev/null +++ b/pylint/test/functional/name_styles.txt @@ -0,0 +1,17 @@ +invalid-name:7::"Invalid constant name ""bad_const_name""" +invalid-name:10:BADFUNCTION_name:"Invalid function name ""BADFUNCTION_name""" +invalid-name:12:BADFUNCTION_name:"Invalid variable name ""BAD_LOCAL_VAR""" +invalid-name:16:func_bad_argname:"Invalid argument name ""NOT_GOOD""" +invalid-name:26:bad_class_name:"Invalid class name ""bad_class_name""" +invalid-name:37:CorrectClassName.__init__:"Invalid attribute name ""_Bad_AtTR_name""" +invalid-name:38:CorrectClassName.__init__:"Invalid attribute name ""Bad_PUBLIC_name""" +invalid-name:40:CorrectClassName:"Invalid class attribute name ""zz""" +invalid-name:43:CorrectClassName.BadMethodName:"Invalid method name ""BadMethodName""":INFERENCE +invalid-name:49:CorrectClassName.__DunDER_IS_not_free_for_all__:"Invalid method name ""__DunDER_IS_not_free_for_all__""":INFERENCE +invalid-name:79::"Invalid class name ""BAD_NAME_FOR_CLASS""" +invalid-name:80::"Invalid class name ""NEXT_BAD_NAME_FOR_CLASS""" +invalid-name:87::"Invalid class name ""NOT_CORRECT""" +invalid-name:93:test_globals:"Invalid constant name ""AlsoCorrect""" +invalid-name:106:FooClass.PROPERTY_NAME:"Invalid attribute name ""PROPERTY_NAME""":INFERENCE +invalid-name:111:FooClass.ABSTRACT_PROPERTY_NAME:"Invalid attribute name ""ABSTRACT_PROPERTY_NAME""":INFERENCE +invalid-name:116:FooClass.PROPERTY_NAME_SETTER:"Invalid attribute name ""PROPERTY_NAME_SETTER""":INFERENCE diff --git a/pylint/test/functional/namedtuple_member_inference.py b/pylint/test/functional/namedtuple_member_inference.py new file mode 100644 index 0000000..7283db2 --- /dev/null +++ b/pylint/test/functional/namedtuple_member_inference.py @@ -0,0 +1,23 @@ +"""Test namedtuple attributes. + +Regression test for: +https://bitbucket.org/logilab/pylint/issue/93/pylint-crashes-on-namedtuple-attribute +""" +from __future__ import absolute_import, print_function + +__revision__ = None + +from collections import namedtuple +Thing = namedtuple('Thing', ()) + +Fantastic = namedtuple('Fantastic', ['foo']) + +def test(): + """Test member access in named tuples.""" + print(Thing.x) # [no-member] + fan = Fantastic(1) + print(fan.foo) + # Should not raise protected-access. + fan2 = fan._replace(foo=2) + # This is a bug. + print(fan2.foo) # [no-member] diff --git a/pylint/test/functional/namedtuple_member_inference.txt b/pylint/test/functional/namedtuple_member_inference.txt new file mode 100644 index 0000000..1532814 --- /dev/null +++ b/pylint/test/functional/namedtuple_member_inference.txt @@ -0,0 +1,2 @@ +no-member:17:test:Class 'Thing' has no 'x' member:INFERENCE +no-member:23:test:Instance of 'Fantastic' has no 'foo' member:INFERENCE diff --git a/pylint/test/functional/names_in__all__.py b/pylint/test/functional/names_in__all__.py new file mode 100644 index 0000000..6d365e0 --- /dev/null +++ b/pylint/test/functional/names_in__all__.py @@ -0,0 +1,46 @@ +# pylint: disable=too-few-public-methods,no-self-use, no-absolute-import +"""Test Pylint's use of __all__. + +* NonExistant is not defined in this module, and it is listed in + __all__. An error is expected. + +* This module imports path and republished it in __all__. No errors + are expected. +""" +from __future__ import print_function + +from os import path +from collections import deque + +__all__ = [ + 'Dummy', + 'NonExistant', # [undefined-all-variable] + 'path', + 'func', # [undefined-all-variable] + 'inner', # [undefined-all-variable] + 'InnerKlass', deque.__name__] # [undefined-all-variable] + + +class Dummy(object): + """A class defined in this module.""" + pass + +DUMMY = Dummy() + +def function(): + """Function docstring + """ + pass + +function() + +class Klass(object): + """A klass which contains a function""" + def func(self): + """A klass method""" + inner = None + print(inner) + + class InnerKlass(object): + """A inner klass""" + pass diff --git a/pylint/test/functional/names_in__all__.txt b/pylint/test/functional/names_in__all__.txt new file mode 100644 index 0000000..6cd7ae5 --- /dev/null +++ b/pylint/test/functional/names_in__all__.txt @@ -0,0 +1,4 @@ +undefined-all-variable:17::Undefined variable name 'NonExistant' in __all__ +undefined-all-variable:19::Undefined variable name 'func' in __all__ +undefined-all-variable:20::Undefined variable name 'inner' in __all__ +undefined-all-variable:21::Undefined variable name 'InnerKlass' in __all__ diff --git a/pylint/test/functional/newstyle__slots__.py b/pylint/test/functional/newstyle__slots__.py new file mode 100644 index 0000000..306d6a1 --- /dev/null +++ b/pylint/test/functional/newstyle__slots__.py @@ -0,0 +1,17 @@ +# pylint: disable=R0903 +"""test __slots__ on old style class""" + + +class NewStyleClass(object): + """correct usage""" + __slots__ = ('a', 'b') + + +class OldStyleClass: # <3.0:[old-style-class,slots-on-old-class] + """bad usage""" + __slots__ = ('a', 'b') + + def __init__(self): + pass + +__slots__ = 'hop' diff --git a/pylint/test/functional/newstyle__slots__.txt b/pylint/test/functional/newstyle__slots__.txt new file mode 100644 index 0000000..4320390 --- /dev/null +++ b/pylint/test/functional/newstyle__slots__.txt @@ -0,0 +1,2 @@ +old-style-class:10:OldStyleClass:Old-style class defined. +slots-on-old-class:10:OldStyleClass:Use of __slots__ on an old style class:INFERENCE diff --git a/pylint/test/functional/newstyle_properties.py b/pylint/test/functional/newstyle_properties.py new file mode 100644 index 0000000..110fa3b --- /dev/null +++ b/pylint/test/functional/newstyle_properties.py @@ -0,0 +1,53 @@ +# pylint: disable=too-few-public-methods +"""Test properties on old style classes and property.setter/deleter usage""" + + +def getter(self): + """interesting""" + return self + +class CorrectClass(object): + """correct usage""" + method = property(getter, doc='hop') + +class OldStyleClass: # <3.0:[old-style-class] + """bad usage""" + method = property(getter, doc='hop') # <3.0:[property-on-old-class] + + def __init__(self): + pass + + +def decorator(func): + """Redefining decorator.""" + def wrapped(self): + """Wrapper function.""" + return func(self) + return wrapped + + +class SomeClass(object): + """another docstring""" + + def __init__(self): + self._prop = None + + @property + def prop(self): + """I'm the 'prop' property.""" + return self._prop + + @prop.setter + def prop(self, value): + """I'm the 'prop' property.""" + self._prop = value + + @prop.deleter + def prop(self): + """I'm the 'prop' property.""" + del self._prop + + @decorator + def noregr(self): + """Just a normal method with a decorator.""" + return self.prop diff --git a/pylint/test/functional/newstyle_properties.txt b/pylint/test/functional/newstyle_properties.txt new file mode 100644 index 0000000..a16686b --- /dev/null +++ b/pylint/test/functional/newstyle_properties.txt @@ -0,0 +1,2 @@ +old-style-class:13:OldStyleClass:Old-style class defined. +property-on-old-class:15:OldStyleClass:"Use of ""property"" on an old style class":INFERENCE diff --git a/pylint/test/functional/no_name_in_module.py b/pylint/test/functional/no_name_in_module.py new file mode 100644 index 0000000..dc384b3 --- /dev/null +++ b/pylint/test/functional/no_name_in_module.py @@ -0,0 +1,26 @@ +#pylint: disable=W0401,W0611,print-statement,no-absolute-import +"""check unexistant names imported are reported""" + + +import logilab.common.tutu # [no-name-in-module,import-error] +from logilab.common import toto # [no-name-in-module] +toto.yo() + +from logilab.common import modutils +modutils.nonexistant_function() # [no-member] +modutils.another.nonexistant.function() # [no-member] +print logilab.common.modutils.yo # [no-member] + +import sys +print >> sys.stdout, 'hello world' +print >> sys.stdoout, 'bye bye world' # [no-member] + + +import re +re.finditer('*', 'yo') + +from rie import * # [import-error] +from re import findiiter, compiile # [no-name-in-module,no-name-in-module] + +import os +'SOMEVAR' in os.environ # [pointless-statement] diff --git a/pylint/test/functional/no_name_in_module.txt b/pylint/test/functional/no_name_in_module.txt new file mode 100644 index 0000000..6961d83 --- /dev/null +++ b/pylint/test/functional/no_name_in_module.txt @@ -0,0 +1,11 @@ +import-error:5::Unable to import 'logilab.common.tutu' +no-name-in-module:5::No name 'tutu' in module 'logilab.common' +no-name-in-module:6::No name 'toto' in module 'logilab.common' +no-member:10::Module 'logilab.common.modutils' has no 'nonexistant_function' member:INFERENCE +no-member:11::Module 'logilab.common.modutils' has no 'another' member:INFERENCE +no-member:12::Module 'logilab.common.modutils' has no 'yo' member:INFERENCE +no-member:16::Module 'sys' has no 'stdoout' member:INFERENCE +import-error:22::Unable to import 'rie' +no-name-in-module:23::No name 'compiile' in module 're' +no-name-in-module:23::No name 'findiiter' in module 're' +pointless-statement:26::Statement seems to have no effect diff --git a/pylint/test/functional/old_style_class_py27.py b/pylint/test/functional/old_style_class_py27.py new file mode 100644 index 0000000..1149325 --- /dev/null +++ b/pylint/test/functional/old_style_class_py27.py @@ -0,0 +1,18 @@ +""" Tests for old style classes. """
+# pylint: disable=no-init, too-few-public-methods, invalid-name, metaclass-assignment
+
+class Old: # [old-style-class]
+ """ old style class """
+
+class Child(Old):
+ """ Old style class, but don't emit for it. """
+
+class NotOldStyle2:
+ """ Because I have a metaclass at class level. """
+ __metaclass__ = type
+
+# pylint: disable=redefined-builtin
+__metaclass__ = type
+
+class NotOldStyle:
+ """ Because I have a metaclass at global level. """
diff --git a/pylint/test/functional/old_style_class_py27.rc b/pylint/test/functional/old_style_class_py27.rc new file mode 100644 index 0000000..a650233 --- /dev/null +++ b/pylint/test/functional/old_style_class_py27.rc @@ -0,0 +1,2 @@ +[testoptions] +max_pyver=3.0 diff --git a/pylint/test/functional/old_style_class_py27.txt b/pylint/test/functional/old_style_class_py27.txt new file mode 100644 index 0000000..c46a7b6 --- /dev/null +++ b/pylint/test/functional/old_style_class_py27.txt @@ -0,0 +1,2 @@ +old-style-class:4:Old:Old-style class defined. +old-style-class:7:Child:Old-style class defined. diff --git a/test/regrtest_data/pygtk_enum_crash.py b/pylint/test/functional/pygtk_enum_crash.py index 471afe3..471afe3 100644 --- a/test/regrtest_data/pygtk_enum_crash.py +++ b/pylint/test/functional/pygtk_enum_crash.py diff --git a/pylint/test/functional/pygtk_enum_crash.rc b/pylint/test/functional/pygtk_enum_crash.rc new file mode 100644 index 0000000..7a6c0af --- /dev/null +++ b/pylint/test/functional/pygtk_enum_crash.rc @@ -0,0 +1,2 @@ +[testoptions] +requires=gtk diff --git a/pylint/test/functional/pygtk_import.py b/pylint/test/functional/pygtk_import.py new file mode 100644 index 0000000..231885e --- /dev/null +++ b/pylint/test/functional/pygtk_import.py @@ -0,0 +1,14 @@ +"""Import PyGTK.""" +#pylint: disable=too-few-public-methods,too-many-public-methods + +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/pylint/test/functional/pygtk_import.rc b/pylint/test/functional/pygtk_import.rc new file mode 100644 index 0000000..7a6c0af --- /dev/null +++ b/pylint/test/functional/pygtk_import.rc @@ -0,0 +1,2 @@ +[testoptions] +requires=gtk diff --git a/pylint/test/functional/raising_non_exception_py3.py b/pylint/test/functional/raising_non_exception_py3.py new file mode 100644 index 0000000..f7013e2 --- /dev/null +++ b/pylint/test/functional/raising_non_exception_py3.py @@ -0,0 +1,13 @@ +"""The following code should emit a raising-non-exception. + +Previously, it didn't, due to a bug in the check for bad-exception-context, +which prevented further checking on the Raise node. +""" +# pylint: disable=import-error, too-few-public-methods + +from missing_module import missing + +class Exc(object): + """Not an actual exception.""" + +raise Exc from missing # [raising-non-exception] diff --git a/pylint/test/functional/raising_non_exception_py3.rc b/pylint/test/functional/raising_non_exception_py3.rc new file mode 100644 index 0000000..c093be2 --- /dev/null +++ b/pylint/test/functional/raising_non_exception_py3.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.0 diff --git a/pylint/test/functional/raising_non_exception_py3.txt b/pylint/test/functional/raising_non_exception_py3.txt new file mode 100644 index 0000000..12c8862 --- /dev/null +++ b/pylint/test/functional/raising_non_exception_py3.txt @@ -0,0 +1 @@ +raising-non-exception:13::Raising a new style class which doesn't inherit from BaseException
\ No newline at end of file diff --git a/pylint/test/functional/redefined_builtin.py b/pylint/test/functional/redefined_builtin.py new file mode 100644 index 0000000..c41d06d --- /dev/null +++ b/pylint/test/functional/redefined_builtin.py @@ -0,0 +1,9 @@ +"""Tests for redefining builtins.""" + +def function(): + """Redefined local.""" + type = 1 # [redefined-builtin] + print type # pylint: disable=print-statement + +# pylint:disable=invalid-name +map = {} # [redefined-builtin] diff --git a/pylint/test/functional/redefined_builtin.txt b/pylint/test/functional/redefined_builtin.txt new file mode 100644 index 0000000..71518ce --- /dev/null +++ b/pylint/test/functional/redefined_builtin.txt @@ -0,0 +1,2 @@ +redefined-builtin:5:function:Redefining built-in 'type' +redefined-builtin:9::Redefining built-in 'map' diff --git a/pylint/test/functional/redundant_unittest_assert.py b/pylint/test/functional/redundant_unittest_assert.py new file mode 100644 index 0000000..2eb73e3 --- /dev/null +++ b/pylint/test/functional/redundant_unittest_assert.py @@ -0,0 +1,38 @@ +# pylint: disable=missing-docstring,too-few-public-methods +""" +http://www.logilab.org/ticket/355 +If you are using assertTrue or assertFalse and the first argument is a +constant(like a string), then the assert will always be true. Therefore, +it should emit a warning message. +""" + +import unittest + +@unittest.skip("don't run this") +class Tests(unittest.TestCase): + def test_something(self): + ''' Simple test ''' + some_var = 'It should be assertEqual' + # +1:[redundant-unittest-assert] + self.assertTrue('I meant assertEqual not assertTrue', some_var) + # +1:[redundant-unittest-assert] + self.assertFalse('I meant assertEqual not assertFalse', some_var) + # +1:[redundant-unittest-assert] + self.assertTrue(True, some_var) + # +1:[redundant-unittest-assert] + self.assertFalse(False, some_var) + # +1:[redundant-unittest-assert] + self.assertFalse(None, some_var) + # +1:[redundant-unittest-assert] + self.assertTrue(0, some_var) + + self.assertTrue('should be' in some_var, some_var) + self.assertTrue(some_var, some_var) + + +@unittest.skip("don't run this") +class RegressionWithArgs(unittest.TestCase): + '''Don't fail if the bound method doesn't have arguments.''' + + def test(self): + self.run() diff --git a/pylint/test/functional/redundant_unittest_assert.txt b/pylint/test/functional/redundant_unittest_assert.txt new file mode 100644 index 0000000..889d395 --- /dev/null +++ b/pylint/test/functional/redundant_unittest_assert.txt @@ -0,0 +1,6 @@ +redundant-unittest-assert:17:Tests.test_something:"Redundant use of assertTrue with constant value 'I meant assertEqual not assertTrue'" +redundant-unittest-assert:19:Tests.test_something:"Redundant use of assertFalse with constant value 'I meant assertEqual not assertFalse'" +redundant-unittest-assert:21:Tests.test_something:"Redundant use of assertTrue with constant value True" +redundant-unittest-assert:23:Tests.test_something:"Redundant use of assertFalse with constant value False" +redundant-unittest-assert:25:Tests.test_something:"Redundant use of assertFalse with constant value None" +redundant-unittest-assert:27:Tests.test_something:"Redundant use of assertTrue with constant value 0"
\ No newline at end of file diff --git a/pylint/test/functional/slots_checks.py b/pylint/test/functional/slots_checks.py new file mode 100644 index 0000000..d49681b --- /dev/null +++ b/pylint/test/functional/slots_checks.py @@ -0,0 +1,61 @@ +""" Checks that classes uses valid __slots__ """ + +# pylint: disable=too-few-public-methods, missing-docstring, no-absolute-import + +from collections import deque + +def func(): + if True: + return ("a", "b", "c") + else: + return [str(var) for var in range(3)] + + +class NotIterable(object): + def __iter_(self): + """ do nothing """ + +class Good(object): + __slots__ = () + +class SecondGood(object): + __slots__ = [] + +class ThirdGood(object): + __slots__ = ['a'] + +class FourthGood(object): + __slots__ = ('a%s' % i for i in range(10)) + +class FifthGood(object): + __slots__ = "a" + +class SixthGood(object): + __slots__ = deque(["a", "b", "c"]) + +class SeventhGood(object): + __slots__ = {"a": "b", "c": "d"} + +class Bad(object): + __slots__ = list + +class SecondBad(object): # [invalid-slots] + __slots__ = 1 + +class ThirdBad(object): + __slots__ = ('a', 2) # [invalid-slots-object] + +class FourthBad(object): # [invalid-slots] + __slots__ = NotIterable() + +class FifthBad(object): + __slots__ = ("a", "b", "") # [invalid-slots-object] + +class PotentiallyGood(object): + __slots__ = func() + +class PotentiallySecondGood(object): + __slots__ = ('a', deque.__name__) + +class PotentiallyThirdGood(object): + __slots__ = deque.__name__ diff --git a/pylint/test/functional/slots_checks.txt b/pylint/test/functional/slots_checks.txt new file mode 100644 index 0000000..7e90a4a --- /dev/null +++ b/pylint/test/functional/slots_checks.txt @@ -0,0 +1,4 @@ +invalid-slots:42:SecondBad:Invalid __slots__ object +invalid-slots-object:46:ThirdBad:Invalid object '2' in __slots__, must contain only non empty strings +invalid-slots:48:FourthBad:Invalid __slots__ object +invalid-slots-object:52:FifthBad:"Invalid object ""''"" in __slots__, must contain only non empty strings" diff --git a/pylint/test/functional/socketerror_import.py b/pylint/test/functional/socketerror_import.py new file mode 100644 index 0000000..5028f02 --- /dev/null +++ b/pylint/test/functional/socketerror_import.py @@ -0,0 +1,6 @@ +"""ds""" +from __future__ import absolute_import, print_function +__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/pylint/test/functional/statement_without_effect.py b/pylint/test/functional/statement_without_effect.py new file mode 100644 index 0000000..b1aaadf --- /dev/null +++ b/pylint/test/functional/statement_without_effect.py @@ -0,0 +1,65 @@ +"""Test for statements without effects.""" +# pylint: disable=too-few-public-methods + +# +1:[pointless-string-statement] +"""inline doc string should use a separated message""" + +__revision__ = '' + +__revision__ # [pointless-statement] + +__revision__ <= 1 # [pointless-statement] + +__revision__.lower() + +[i for i in __revision__] # [pointless-statement] + +# +1:[pointless-string-statement] +"""inline doc string should use a separated message""" + + +__revision__.lower(); # [unnecessary-semicolon] + +list() and tuple() # [expression-not-assigned] + +def to_be(): + """return 42""" + return "42" + +ANSWER = to_be() # ok +ANSWER == to_be() # [expression-not-assigned] + +to_be() or not to_be() # [expression-not-assigned] +to_be().title # [expression-not-assigned] + +GOOD_ATTRIBUTE_DOCSTRING = 42 +"""Module level attribute docstring is fine. """ + +class ClassLevelAttributeTest(object): + """ test attribute docstrings. """ + + good_attribute_docstring = 24 + """ class level attribute docstring is fine either. """ + second_good_attribute_docstring = 42 + # Comments are good. + + # empty lines are good, too. + """ Still a valid class level attribute docstring. """ + + def __init__(self): + self.attr = 42 + """ Good attribute docstring """ + attr = 24 + """ Still a good __init__ level attribute docstring. """ + val = 0 + for val in range(42): + val += attr + # +1:[pointless-string-statement] + """ Invalid attribute docstring """ + self.val = val + + def test(self): + """ invalid attribute docstrings here. """ + self.val = 42 + # +1:[pointless-string-statement] + """ this is an invalid attribute docstring. """ diff --git a/pylint/test/functional/statement_without_effect.txt b/pylint/test/functional/statement_without_effect.txt new file mode 100644 index 0000000..81fc51b --- /dev/null +++ b/pylint/test/functional/statement_without_effect.txt @@ -0,0 +1,60 @@ +pointless-string-statement:5::String statement has no effect +pointless-statement:6::"""Statement seems to have no effect +"" +" +pointless-statement:8::"""Statement seems to have no effect +"" +" +pointless-statement:9::Statement seems to have no effect +pointless-statement:11::Statement seems to have no effect +pointless-statement:12::"""Statement seems to have no effect +"" +" +pointless-statement:15::Statement seems to have no effect +pointless-string-statement:15::"""String statement has no effect +"" +" +unnecessary-semicolon:17::"""Unnecessary semicolon +"" +" +pointless-string-statement:18::String statement has no effect +unnecessary-semicolon:18::"""Unnecessary semicolon +"" +" +expression-not-assigned:19::"""Expression """"(list()) and (tuple())"""" is assigned to nothing +"" +" +expression-not-assigned:20::"""Expression """"(list()) and (tuple())"""" is assigned to nothing +"" +" +unnecessary-semicolon:21::Unnecessary semicolon +expression-not-assigned:23::"Expression ""(list()) and (tuple())"" is assigned to nothing" +expression-not-assigned:26::"""Expression """"ANSWER == to_be()"""" is assigned to nothing +"" +" +expression-not-assigned:27::"""Expression """"ANSWER == to_be()"""" is assigned to nothing +"" +" +expression-not-assigned:28::"""Expression """"(to_be()) or (not to_be())"""" is assigned to nothing +"" +" +expression-not-assigned:29::"""Expression """"(to_be()) or (not to_be())"""" is assigned to nothing +"" +" +expression-not-assigned:30::"Expression ""ANSWER == to_be()"" is assigned to nothing" +expression-not-assigned:32::"Expression ""(to_be()) or (not to_be())"" is assigned to nothing" +expression-not-assigned:33::"Expression ""to_be().title"" is assigned to nothing" +pointless-string-statement:54:ClassLevelAttributeTest.__init__:"""String statement has no effect +"" +" +pointless-string-statement:55:ClassLevelAttributeTest.__init__:"""String statement has no effect +"" +" +pointless-string-statement:58:ClassLevelAttributeTest.__init__:String statement has no effect +pointless-string-statement:61:ClassLevelAttributeTest.test:"""String statement has no effect +"" +" +pointless-string-statement:62:ClassLevelAttributeTest.test:"""String statement has no effect +"" +" +pointless-string-statement:65:ClassLevelAttributeTest.test:String statement has no effect diff --git a/pylint/test/functional/string_formatting.py b/pylint/test/functional/string_formatting.py new file mode 100644 index 0000000..dd7e4ea --- /dev/null +++ b/pylint/test/functional/string_formatting.py @@ -0,0 +1,183 @@ +"""test for Python 3 string formatting error
+"""
+# pylint: disable=too-few-public-methods, import-error, unused-argument, star-args, line-too-long, no-absolute-import
+import os
+from missing import Missing
+
+__revision__ = 1
+
+class Custom(object):
+ """ Has a __getattr__ """
+ def __getattr__(self):
+ return self
+
+class Test(object):
+ """ test format attribute access """
+ custom = Custom()
+ ids = [1, 2, 3, [4, 5, 6]]
+
+class Getitem(object):
+ """ test custom getitem for lookup access """
+ def __getitem__(self, index):
+ return 42
+
+class ReturnYes(object):
+ """ can't be properly infered """
+ missing = Missing()
+
+def log(message, message_type="error"):
+ """ Test """
+ return message
+
+def print_good():
+ """ Good format strings """
+ "{0} {1}".format(1, 2)
+ "{0!r:20}".format("Hello")
+ "{!r:20}".format("Hello")
+ "{a!r:20}".format(a="Hello")
+ "{pid}".format(pid=os.getpid())
+ str("{}").format(2)
+ "{0.missing.length}".format(ReturnYes())
+ "{1.missing.length}".format(ReturnYes())
+ "{a.ids[3][1]}".format(a=Test())
+ "{a[0][0]}".format(a=[[1]])
+ "{[0][0]}".format({0: {0: 1}})
+ "{a.test}".format(a=Custom())
+ "{a.__len__}".format(a=[])
+ "{a.ids.__len__}".format(a=Test())
+ "{a[0]}".format(a=Getitem())
+ "{a[0][0]}".format(a=[Getitem()])
+ "{[0][0]}".format(["test"])
+ # these are skipped
+ "{0} {1}".format(*[1, 2])
+ "{a} {b}".format(**{'a': 1, 'b': 2})
+ "{a}".format(a=Missing())
+
+def pprint_bad():
+ """Test string format """
+ "{{}}".format(1) # [too-many-format-args]
+ "{} {".format() # [bad-format-string]
+ "{} }".format() # [bad-format-string]
+ "{0} {}".format(1, 2) # [format-combined-specification]
+ # +1: [missing-format-argument-key, unused-format-string-argument]
+ "{a} {b}".format(a=1, c=2)
+ "{} {a}".format(1, 2) # [missing-format-argument-key]
+ "{} {}".format(1) # [too-few-format-args]
+ "{} {}".format(1, 2, 3) # [too-many-format-args]
+ # +1: [missing-format-argument-key,missing-format-argument-key,missing-format-argument-key]
+ "{a} {b} {c}".format()
+ "{} {}".format(a=1, b=2) # [too-few-format-args]
+ # +1: [missing-format-argument-key, missing-format-argument-key]
+ "{a} {b}".format(1, 2)
+ "{0} {1} {a}".format(1, 2, 3) # [missing-format-argument-key]
+ # +1: [missing-format-attribute]
+ "{a.ids.__len__.length}".format(a=Test())
+ "{a.ids[3][400]}".format(a=Test()) # [invalid-format-index]
+ "{a.ids[3]['string']}".format(a=Test()) # [invalid-format-index]
+ "{[0][1]}".format(["a"]) # [invalid-format-index]
+ "{[0][0]}".format(((1, ))) # [invalid-format-index]
+ # +1: [missing-format-argument-key, unused-format-string-argument]
+ "{b[0]}".format(a=23)
+ "{a[0]}".format(a=object) # [invalid-format-index]
+ log("{}".format(2, "info")) # [too-many-format-args]
+ "{0.missing}".format(2) # [missing-format-attribute]
+ "{0} {1} {2}".format(1, 2) # [too-few-format-args]
+ "{0} {1}".format(1, 2, 3) # [too-many-format-args]
+ "{0} {a}".format(a=4) # [too-few-format-args]
+ "{[0]} {}".format([4]) # [too-few-format-args]
+ "{[0]} {}".format([4], 5, 6) # [too-many-format-args]
+
+def good_issue288(*args, **kwargs):
+ """ Test that using kwargs does not emit a false
+ positive.
+ """
+ 'Hello John Doe {0[0]}'.format(args)
+ 'Hello {0[name]}'.format(kwargs)
+
+def good_issue287():
+ """ Test that the string format checker skips
+ format nodes which don't have a string as a parent
+ (but a subscript, name etc).
+ """
+ name = 'qwerty'
+ ret = {'comment': ''}
+ ret['comment'] = 'MySQL grant {0} is set to be revoked'
+ ret['comment'] = ret['comment'].format(name)
+ return ret, name
+
+def nested_issue294():
+ """ Test nested format fields. """
+ '{0:>{1}}'.format(42, 24)
+ '{0:{a[1]}} {a}'.format(1, a=[1, 2])
+ '{:>{}}'.format(42, 24)
+ '{0:>{1}}'.format(42) # [too-few-format-args]
+ '{0:>{1}}'.format(42, 24, 54) # [too-many-format-args]
+ '{0:{a[1]}}'.format(1) # [missing-format-argument-key]
+ '{0:{a.x}}'.format(1, a=2) # [missing-format-attribute]
+
+def issue310():
+ """ Test a regression using duplicate manual position arguments. """
+ '{0} {1} {0}'.format(1, 2)
+ '{0} {1} {0}'.format(1) # [too-few-format-args]
+
+def issue322():
+ """ Test a regression using mixed manual position arguments
+ and attribute access arguments.
+ """
+ '{0}{1[FOO]}'.format(123, {'FOO': 456})
+ '{0}{1[FOO]}'.format(123, {'FOO': 456}, 321) # [too-many-format-args]
+ '{0}{1[FOO]}'.format(123) # [too-few-format-args]
+
+def issue338():
+ """
+ Check that using a namedtuple subclass doesn't crash when
+ trying to infer EmptyNodes (resulted after mocking the
+ members of namedtuples).
+ """
+ from collections import namedtuple
+
+ class Crash(namedtuple("C", "foo bar")):
+ """ Looking for attributes in __str__ will crash,
+ because EmptyNodes can't be infered.
+ """
+ def __str__(self):
+ return "{0.foo}: {0.bar}".format(self)
+ return Crash
+
+def issue351():
+ """
+ Check that the format method can be assigned to a variable, ie:
+ """
+ fmt = 'test {} {}'.format
+ fmt('arg1') # [too-few-format-args]
+ fmt('arg1', 'arg2')
+ fmt('arg1', 'arg2', 'arg3') # [too-many-format-args]
+
+def issue373():
+ """
+ Ignore any object coming from an argument.
+ """
+ class SomeClass(object):
+ """ empty docstring. """
+ def __init__(self, opts=None):
+ self.opts = opts
+
+ def dunc(self, arg):
+ """Don't try to analyze this."""
+ return "A{0}{1}".format(arg, self.opts)
+
+ def func(self):
+ """Don't try to analyze the following string."""
+ return 'AAA{0[iface]}BBB{0[port]}'.format(self.opts)
+
+ return SomeClass
+
+def issue_463():
+ """
+ Mix positional arguments, `{0}`, with positional
+ arguments with attribute access, `{0.__x__}`.
+ """
+ data = "{0.__class__.__name__}: {0}".format(42)
+ data2 = "{0[0]}: {0}".format([1])
+ return (data, data2)
+
diff --git a/pylint/test/functional/string_formatting.txt b/pylint/test/functional/string_formatting.txt new file mode 100644 index 0000000..b2f1849 --- /dev/null +++ b/pylint/test/functional/string_formatting.txt @@ -0,0 +1,40 @@ +too-many-format-args:58:pprint_bad:Too many arguments for format string +bad-format-string:59:pprint_bad:Invalid format string +bad-format-string:60:pprint_bad:Invalid format string +format-combined-specification:61:pprint_bad:Format string contains both automatic field numbering and manual field specification +missing-format-argument-key:63:pprint_bad:Missing keyword argument 'b' for format string +unused-format-string-argument:63:pprint_bad:Unused format argument 'c' +missing-format-argument-key:64:pprint_bad:Missing keyword argument 'a' for format string +too-few-format-args:65:pprint_bad:Not enough arguments for format string +too-many-format-args:66:pprint_bad:Too many arguments for format string +missing-format-argument-key:68:pprint_bad:Missing keyword argument 'a' for format string +missing-format-argument-key:68:pprint_bad:Missing keyword argument 'b' for format string +missing-format-argument-key:68:pprint_bad:Missing keyword argument 'c' for format string +too-few-format-args:69:pprint_bad:Not enough arguments for format string +missing-format-argument-key:71:pprint_bad:Missing keyword argument 'a' for format string +missing-format-argument-key:71:pprint_bad:Missing keyword argument 'b' for format string +missing-format-argument-key:72:pprint_bad:Missing keyword argument 'a' for format string +missing-format-attribute:74:pprint_bad:Missing format attribute 'length' in format specifier 'a.ids.__len__.length' +invalid-format-index:75:pprint_bad:Using invalid lookup key 400 in format specifier 'a.ids[3][400]' +invalid-format-index:76:pprint_bad:"Using invalid lookup key ""'string'"" in format specifier 'a.ids[3][""\'string\'""]'" +invalid-format-index:77:pprint_bad:Using invalid lookup key 1 in format specifier '0[0][1]' +invalid-format-index:78:pprint_bad:Using invalid lookup key 0 in format specifier '0[0][0]' +missing-format-argument-key:80:pprint_bad:Missing keyword argument 'b' for format string +unused-format-string-argument:80:pprint_bad:Unused format argument 'a' +invalid-format-index:81:pprint_bad:Using invalid lookup key 0 in format specifier 'a[0]' +too-many-format-args:82:pprint_bad:Too many arguments for format string +missing-format-attribute:83:pprint_bad:Missing format attribute 'missing' in format specifier '0.missing' +too-few-format-args:84:pprint_bad:Not enough arguments for format string +too-many-format-args:85:pprint_bad:Too many arguments for format string +too-few-format-args:86:pprint_bad:Not enough arguments for format string +too-few-format-args:87:pprint_bad:Not enough arguments for format string +too-many-format-args:88:pprint_bad:Too many arguments for format string +too-few-format-args:113:nested_issue294:Not enough arguments for format string +too-many-format-args:114:nested_issue294:Too many arguments for format string +missing-format-argument-key:115:nested_issue294:Missing keyword argument 'a' for format string +missing-format-attribute:116:nested_issue294:Missing format attribute 'x' in format specifier 'a.x' +too-few-format-args:121:issue310:Not enough arguments for format string +too-many-format-args:128:issue322:Too many arguments for format string +too-few-format-args:129:issue322:Not enough arguments for format string +too-few-format-args:152:issue351:Not enough arguments for format string +too-many-format-args:154:issue351:Too many arguments for format string diff --git a/pylint/test/functional/string_formatting_py27.py b/pylint/test/functional/string_formatting_py27.py new file mode 100644 index 0000000..d6aeae7 --- /dev/null +++ b/pylint/test/functional/string_formatting_py27.py @@ -0,0 +1,23 @@ +"""test for Python 2 string formatting error
+"""
+from __future__ import unicode_literals
+# pylint: disable=line-too-long
+__revision__ = 1
+
+def pprint_bad():
+ """Test string format """
+ "{{}}".format(1) # [too-many-format-args]
+ "{} {".format() # [bad-format-string]
+ "{} }".format() # [bad-format-string]
+ "{0} {}".format(1, 2) # [format-combined-specification]
+ # +1: [missing-format-argument-key, unused-format-string-argument]
+ "{a} {b}".format(a=1, c=2)
+ "{} {a}".format(1, 2) # [missing-format-argument-key]
+ "{} {}".format(1) # [too-few-format-args]
+ "{} {}".format(1, 2, 3) # [too-many-format-args]
+ # +1: [missing-format-argument-key,missing-format-argument-key,missing-format-argument-key]
+ "{a} {b} {c}".format()
+ "{} {}".format(a=1, b=2) # [too-few-format-args]
+ # +1: [missing-format-argument-key, missing-format-argument-key]
+ "{a} {b}".format(1, 2)
+
diff --git a/pylint/test/functional/string_formatting_py27.rc b/pylint/test/functional/string_formatting_py27.rc new file mode 100644 index 0000000..80170b7 --- /dev/null +++ b/pylint/test/functional/string_formatting_py27.rc @@ -0,0 +1,3 @@ +[testoptions] +min_pyver=2.7
+max_pyver=3.0
\ No newline at end of file diff --git a/pylint/test/functional/string_formatting_py27.txt b/pylint/test/functional/string_formatting_py27.txt new file mode 100644 index 0000000..47f21de --- /dev/null +++ b/pylint/test/functional/string_formatting_py27.txt @@ -0,0 +1,15 @@ +too-many-format-args:9:pprint_bad:Too many arguments for format string +bad-format-string:10:pprint_bad:Invalid format string +bad-format-string:11:pprint_bad:Invalid format string +format-combined-specification:12:pprint_bad:Format string contains both automatic field numbering and manual field specification +missing-format-argument-key:14:pprint_bad:Missing keyword argument u'b' for format string +unused-format-string-argument:14:pprint_bad:Unused format argument 'c' +missing-format-argument-key:15:pprint_bad:Missing keyword argument u'a' for format string +too-few-format-args:16:pprint_bad:Not enough arguments for format string +too-many-format-args:17:pprint_bad:Too many arguments for format string +missing-format-argument-key:19:pprint_bad:Missing keyword argument u'a' for format string +missing-format-argument-key:19:pprint_bad:Missing keyword argument u'b' for format string +missing-format-argument-key:19:pprint_bad:Missing keyword argument u'c' for format string +too-few-format-args:20:pprint_bad:Not enough arguments for format string +missing-format-argument-key:22:pprint_bad:Missing keyword argument u'a' for format string +missing-format-argument-key:22:pprint_bad:Missing keyword argument u'b' for format string diff --git a/pylint/test/functional/super_checks.py b/pylint/test/functional/super_checks.py new file mode 100644 index 0000000..368c60c --- /dev/null +++ b/pylint/test/functional/super_checks.py @@ -0,0 +1,62 @@ +# pylint: disable=too-few-public-methods,import-error, no-absolute-import +"""check use of super""" + +from unknown import Missing + +class Aaaa: # <3.0:[old-style-class] + """old style""" + def hop(self): # <3.0:[super-on-old-class] + """hop""" + super(Aaaa, self).hop() + + def __init__(self): # <3.0:[super-on-old-class] + super(Aaaa, self).__init__() + +class NewAaaa(object): + """old style""" + def hop(self): + """hop""" + super(NewAaaa, self).hop() + + def __init__(self): + super(object, self).__init__() # [bad-super-call] + +class Py3kAaaa(NewAaaa): + """new style""" + def __init__(self): + super().__init__() # <3.0:[missing-super-argument] + +class Py3kWrongSuper(Py3kAaaa): + """new style""" + def __init__(self): + super(NewAaaa, self).__init__() # [bad-super-call] + +class WrongNameRegression(Py3kAaaa): + """ test a regression with the message """ + def __init__(self): + super(Missing, self).__init__() # [bad-super-call] + +class Getattr(object): + """ crash """ + name = NewAaaa + +class CrashSuper(object): + """ test a crash with this checker """ + def __init__(self): + super(Getattr.name, self).__init__() # [bad-super-call] + +class Empty(object): + """Just an empty class.""" + +class SuperDifferentScope(object): + """Don'emit bad-super-call when the super call is in another scope. + For reference, see https://bitbucket.org/logilab/pylint/issue/403. + """ + @staticmethod + def test(): + """Test that a bad-super-call is not emitted for this case.""" + class FalsePositive(Empty): + """The following super is in another scope than `test`.""" + def __init__(self, arg): + super(FalsePositive, self).__init__(arg) + super(object, 1).__init__() # [bad-super-call] diff --git a/pylint/test/functional/super_checks.txt b/pylint/test/functional/super_checks.txt new file mode 100644 index 0000000..8e7da6c --- /dev/null +++ b/pylint/test/functional/super_checks.txt @@ -0,0 +1,9 @@ +old-style-class:6:Aaaa:Old-style class defined. +super-on-old-class:8:Aaaa.hop:Use of super on an old style class:INFERENCE +super-on-old-class:12:Aaaa.__init__:Use of super on an old style class:INFERENCE +bad-super-call:22:NewAaaa.__init__:Bad first argument 'object' given to super():INFERENCE +missing-super-argument:27:Py3kAaaa.__init__:Missing argument to super():INFERENCE +bad-super-call:32:Py3kWrongSuper.__init__:Bad first argument 'NewAaaa' given to super():INFERENCE +bad-super-call:37:WrongNameRegression.__init__:Bad first argument 'Missing' given to super():INFERENCE +bad-super-call:46:CrashSuper.__init__:Bad first argument 'NewAaaa' given to super():INFERENCE +bad-super-call:62:SuperDifferentScope.test:Bad first argument 'object' given to super():INFERENCE diff --git a/pylint/test/functional/superfluous_parens.py b/pylint/test/functional/superfluous_parens.py new file mode 100644 index 0000000..8bb3dd6 --- /dev/null +++ b/pylint/test/functional/superfluous_parens.py @@ -0,0 +1,18 @@ +"""Test the superfluous-parens warning.""" +from __future__ import print_function + +if (3 == 5): # [superfluous-parens] + pass +if not (3 == 5): # [superfluous-parens] + pass +if not (3 or 5): + pass +for (x) in (1, 2, 3): # [superfluous-parens] + print(x) +if (1) in (1, 2, 3): # [superfluous-parens] + pass +if (1, 2) in (1, 2, 3): + pass +DICT = {'a': 1, 'b': 2} +del(DICT['b']) # [superfluous-parens] +del DICT['a'] diff --git a/pylint/test/functional/superfluous_parens.txt b/pylint/test/functional/superfluous_parens.txt new file mode 100644 index 0000000..9b203ee --- /dev/null +++ b/pylint/test/functional/superfluous_parens.txt @@ -0,0 +1,5 @@ +superfluous-parens:4::Unnecessary parens after 'if' keyword +superfluous-parens:6::Unnecessary parens after 'not' keyword +superfluous-parens:10::Unnecessary parens after 'for' keyword +superfluous-parens:12::Unnecessary parens after 'if' keyword +superfluous-parens:17::Unnecessary parens after 'del' keyword diff --git a/pylint/test/functional/suspicious_str_strip_call.py b/pylint/test/functional/suspicious_str_strip_call.py new file mode 100644 index 0000000..e859f25 --- /dev/null +++ b/pylint/test/functional/suspicious_str_strip_call.py @@ -0,0 +1,9 @@ +"""Suspicious str.strip calls.""" +__revision__ = 1 + +''.strip('yo') +''.strip() + +u''.strip('http://') # [bad-str-strip-call] +u''.lstrip('http://') # [bad-str-strip-call] +b''.rstrip('http://') # [bad-str-strip-call] diff --git a/pylint/test/functional/suspicious_str_strip_call.rc b/pylint/test/functional/suspicious_str_strip_call.rc new file mode 100644 index 0000000..a650233 --- /dev/null +++ b/pylint/test/functional/suspicious_str_strip_call.rc @@ -0,0 +1,2 @@ +[testoptions] +max_pyver=3.0 diff --git a/pylint/test/functional/suspicious_str_strip_call.txt b/pylint/test/functional/suspicious_str_strip_call.txt new file mode 100644 index 0000000..ad714cc --- /dev/null +++ b/pylint/test/functional/suspicious_str_strip_call.txt @@ -0,0 +1,3 @@ +bad-str-strip-call:7::Suspicious argument in unicode.strip call +bad-str-strip-call:8::Suspicious argument in unicode.lstrip call +bad-str-strip-call:9::Suspicious argument in str.rstrip call diff --git a/pylint/test/functional/suspicious_str_strip_call_py3.py b/pylint/test/functional/suspicious_str_strip_call_py3.py new file mode 100644 index 0000000..e859f25 --- /dev/null +++ b/pylint/test/functional/suspicious_str_strip_call_py3.py @@ -0,0 +1,9 @@ +"""Suspicious str.strip calls.""" +__revision__ = 1 + +''.strip('yo') +''.strip() + +u''.strip('http://') # [bad-str-strip-call] +u''.lstrip('http://') # [bad-str-strip-call] +b''.rstrip('http://') # [bad-str-strip-call] diff --git a/pylint/test/functional/suspicious_str_strip_call_py3.rc b/pylint/test/functional/suspicious_str_strip_call_py3.rc new file mode 100644 index 0000000..c093be2 --- /dev/null +++ b/pylint/test/functional/suspicious_str_strip_call_py3.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.0 diff --git a/pylint/test/functional/suspicious_str_strip_call_py3.txt b/pylint/test/functional/suspicious_str_strip_call_py3.txt new file mode 100644 index 0000000..81f32cf --- /dev/null +++ b/pylint/test/functional/suspicious_str_strip_call_py3.txt @@ -0,0 +1,3 @@ +bad-str-strip-call:7::Suspicious argument in str.strip call +bad-str-strip-call:8::Suspicious argument in str.lstrip call +bad-str-strip-call:9::Suspicious argument in bytes.rstrip call diff --git a/pylint/test/functional/too_many_branches.py b/pylint/test/functional/too_many_branches.py new file mode 100644 index 0000000..54428fd --- /dev/null +++ b/pylint/test/functional/too_many_branches.py @@ -0,0 +1,69 @@ +""" Test for too many branches. """
+
+def wrong(): # [too-many-branches]
+ """ Has too many branches. """
+ if 1:
+ pass
+ elif 1:
+ pass
+ elif 1:
+ pass
+ elif 1:
+ pass
+ elif 1:
+ pass
+ elif 1:
+ pass
+ try:
+ pass
+ finally:
+ pass
+ if 2:
+ pass
+ while True:
+ pass
+ if 1:
+ pass
+ elif 2:
+ pass
+ elif 3:
+ pass
+
+def good():
+ """ Too many branches only if we take
+ into consideration the nested functions.
+ """
+ def nested_1():
+ """ empty """
+ if 1:
+ pass
+ elif 2:
+ pass
+ elif 3:
+ pass
+ elif 4:
+ pass
+
+ nested_1()
+ try:
+ pass
+ finally:
+ pass
+ try:
+ pass
+ finally:
+ pass
+ if 1:
+ pass
+ elif 2:
+ pass
+ elif 3:
+ pass
+ elif 4:
+ pass
+ elif 5:
+ pass
+ elif 6:
+ pass
+ elif 7:
+ pass
diff --git a/pylint/test/functional/too_many_branches.txt b/pylint/test/functional/too_many_branches.txt new file mode 100644 index 0000000..fbc82cc --- /dev/null +++ b/pylint/test/functional/too_many_branches.txt @@ -0,0 +1 @@ +too-many-branches:3:wrong:Too many branches (13/12) diff --git a/pylint/test/functional/too_many_lines_disabled.py b/pylint/test/functional/too_many_lines_disabled.py new file mode 100644 index 0000000..1b0866d --- /dev/null +++ b/pylint/test/functional/too_many_lines_disabled.py @@ -0,0 +1,1018 @@ +"""Test that disabling too-many-lines on any line works.""" + + + +# pylint: disable=too-many-lines +__revision__ = 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +ZERFZAER = 3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +HEHEHE = 2 diff --git a/pylint/test/functional/unbalanced_tuple_unpacking.py b/pylint/test/functional/unbalanced_tuple_unpacking.py new file mode 100644 index 0000000..f21c9d2 --- /dev/null +++ b/pylint/test/functional/unbalanced_tuple_unpacking.py @@ -0,0 +1,88 @@ +"""Check possible unbalanced tuple unpacking """ +from __future__ import absolute_import +from functional.unpacking import unpack + +__revision__ = 0 + +def do_stuff(): + """This is not right.""" + first, second = 1, 2, 3 # [unbalanced-tuple-unpacking] + return first + second + +def do_stuff1(): + """This is not right.""" + first, second = [1, 2, 3] # [unbalanced-tuple-unpacking] + return first + second + +def do_stuff2(): + """This is not right.""" + (first, second) = 1, 2, 3 # [unbalanced-tuple-unpacking] + return first + second + +def do_stuff3(): + """This is not right.""" + first, second = range(100) + return first + second + +def do_stuff4(): + """ This is right """ + first, second = 1, 2 + return first + second + +def do_stuff5(): + """ This is also right """ + first, second = (1, 2) + return first + second + +def do_stuff6(): + """ This is right """ + (first, second) = (1, 2) + return first + second + +def temp(): + """ This is not weird """ + if True: + return [1, 2] + return [2, 3, 4] + +def do_stuff7(): + """ This is not right """ + first, second = temp() # [unbalanced-tuple-unpacking] + return first + second + +def temp2(): + """ This is weird, but correct """ + if True: + return (1, 2) + else: + if True: + return (2, 3) + return (4, 5) + +def do_stuff8(): + """ This is correct """ + first, second = temp2() + return first + second + +def do_stuff9(): + """ This is not correct """ + first, second = unpack() # [unbalanced-tuple-unpacking] + return first + second + +class UnbalancedUnpacking(object): + """ Test unbalanced tuple unpacking in instance attributes. """ + # pylint: disable=attribute-defined-outside-init, invalid-name, too-few-public-methods + def test(self): + """ unpacking in instance attributes """ + self.a, self.b = temp() # [unbalanced-tuple-unpacking] + self.a, self.b = temp2() + self.a, self.b = unpack() # [unbalanced-tuple-unpacking] + + +def issue329(*args): + """ Don't emit unbalanced tuple unpacking if the + rhs of the assignment is a variable-length argument, + because we don't know the actual length of the tuple. + """ + first, second, third = args + return first, second, third diff --git a/pylint/test/functional/unbalanced_tuple_unpacking.txt b/pylint/test/functional/unbalanced_tuple_unpacking.txt new file mode 100644 index 0000000..23038fb --- /dev/null +++ b/pylint/test/functional/unbalanced_tuple_unpacking.txt @@ -0,0 +1,8 @@ +unbalanced-tuple-unpacking:9:do_stuff:"Possible unbalanced tuple unpacking with sequence (1, 2, 3): left side has 2 label(s), right side has 3 value(s)" +unbalanced-tuple-unpacking:14:do_stuff1:"Possible unbalanced tuple unpacking with sequence [1, 2, 3]: left side has 2 label(s), right side has 3 value(s)" +unbalanced-tuple-unpacking:19:do_stuff2:"Possible unbalanced tuple unpacking with sequence (1, 2, 3): left side has 2 label(s), right side has 3 value(s)" +unbalanced-tuple-unpacking:24:do_stuff3:"Possible unbalanced tuple unpacking with sequence (1, 2, 3): left side has 2 label(s), right side has 3 value(s)" +unbalanced-tuple-unpacking:50:do_stuff7:"Possible unbalanced tuple unpacking with sequence defined at line 46: left side has 2 label(s), right side has 3 value(s)" +unbalanced-tuple-unpacking:69:do_stuff9:"Possible unbalanced tuple unpacking with sequence defined at line 7 of functional.unpacking: left side has 2 label(s), right side has 3 value(s)" +unbalanced-tuple-unpacking:77:UnbalancedUnpacking.test:"Possible unbalanced tuple unpacking with sequence defined at line 46: left side has 2 label(s), right side has 3 value(s)" +unbalanced-tuple-unpacking:79:UnbalancedUnpacking.test:"Possible unbalanced tuple unpacking with sequence defined at line 7 of functional.unpacking: left side has 2 label(s), right side has 3 value(s)"
\ No newline at end of file diff --git a/pylint/test/functional/unbalanced_tuple_unpacking_py30.py b/pylint/test/functional/unbalanced_tuple_unpacking_py30.py new file mode 100644 index 0000000..68f5fb7 --- /dev/null +++ b/pylint/test/functional/unbalanced_tuple_unpacking_py30.py @@ -0,0 +1,11 @@ +""" Test that using starred nodes in unpacking +does not trigger a false positive on Python 3. +""" + +__revision__ = 1 + +def test(): + """ Test that starred expressions don't give false positives. """ + first, second, *last = (1, 2, 3, 4) + *last, = (1, 2) + return (first, second, last) diff --git a/pylint/test/functional/unbalanced_tuple_unpacking_py30.rc b/pylint/test/functional/unbalanced_tuple_unpacking_py30.rc new file mode 100644 index 0000000..8c6eb56 --- /dev/null +++ b/pylint/test/functional/unbalanced_tuple_unpacking_py30.rc @@ -0,0 +1,2 @@ +[testoptions]
+min_pyver=3.0
\ No newline at end of file diff --git a/pylint/test/functional/undefined_variable.py b/pylint/test/functional/undefined_variable.py new file mode 100644 index 0000000..efaa1b8 --- /dev/null +++ b/pylint/test/functional/undefined_variable.py @@ -0,0 +1,128 @@ +"""Test warnings about access to undefined variables.""" +# pylint: disable=too-few-public-methods, no-init, no-self-use, old-style-class,print-statement + +DEFINED = 1 + +if DEFINED != 1: + if DEFINED in (unknown, DEFINED): # [undefined-variable] + DEFINED += 1 + + +def in_method(var): + """method doc""" + var = nomoreknown # [undefined-variable] + assert var + +DEFINED = {DEFINED:__revision__} # [undefined-variable] +# +1:[undefined-variable] +DEFINED[__revision__] = OTHER = 'move this is astroid test' + +OTHER += '$' + +def bad_default(var, default=unknown2): # [undefined-variable] + """function with defaut arg's value set to an unexistant name""" + print var, default + print xxxx # [undefined-variable] + augvar += 1 # [undefined-variable,unused-variable] + del vardel # [undefined-variable] + +LMBD = lambda x, y=doesnotexist: x+y # [undefined-variable] +LMBD2 = lambda x, y: x+z # [undefined-variable] + +try: + POUET # don't catch me +except NameError: + POUET = 'something' + +try: + POUETT # don't catch me +except Exception: # pylint:disable = broad-except + POUETT = 'something' + +try: + POUETTT # don't catch me +except: # pylint:disable = bare-except + POUETTT = 'something' + +print POUET, POUETT, POUETTT + + +try: + PLOUF # [used-before-assignment] +except ValueError: + PLOUF = 'something' + +print PLOUF + +def if_branch_test(something): + """hop""" + if something == 0: + if xxx == 1: # [used-before-assignment] + pass + else: + print xxx + xxx = 3 + + +def decorator(arg): + """Decorator with one argument.""" + return lambda: list(arg) + + +@decorator(arg=[i * 2 for i in range(15)]) +def func1(): + """A function with a decorator that contains a listcomp.""" + +@decorator(arg=(i * 2 for i in range(15))) +def func2(): + """A function with a decorator that contains a genexpr.""" + +@decorator(lambda x: x > 0) +def main(): + """A function with a decorator that contains a lambda.""" + +# Test shared scope. + +def test_arguments(arg=TestClass): # [used-before-assignment] + """ TestClass isn't defined yet. """ + return arg + +class TestClass(Ancestor): # [used-before-assignment] + """ contains another class, which uses an undefined ancestor. """ + + class MissingAncestor(Ancestor1): # [used-before-assignment] + """ no op """ + + def test1(self): + """ It should trigger here, because the two classes + have the same scope. + """ + class UsingBeforeDefinition(Empty): # [used-before-assignment] + """ uses Empty before definition """ + class Empty(object): + """ no op """ + return UsingBeforeDefinition + + def test(self): + """ Ancestor isn't defined yet, but we don't care. """ + class MissingAncestor1(Ancestor): + """ no op """ + return MissingAncestor1 + +class Self(object): + """ Detect when using the same name inside the class scope. """ + obj = Self # [undefined-variable] + +class Self1(object): + """ No error should be raised here. """ + + def test(self): + """ empty """ + return Self1 + + +class Ancestor(object): + """ No op """ + +class Ancestor1(object): + """ No op """ diff --git a/pylint/test/functional/undefined_variable.txt b/pylint/test/functional/undefined_variable.txt new file mode 100644 index 0000000..497f320 --- /dev/null +++ b/pylint/test/functional/undefined_variable.txt @@ -0,0 +1,18 @@ +undefined-variable:7::Undefined variable 'unknown' +undefined-variable:13:in_method:Undefined variable 'nomoreknown' +undefined-variable:16::Undefined variable '__revision__' +undefined-variable:18::Undefined variable '__revision__' +undefined-variable:22:bad_default:Undefined variable 'unknown2' +undefined-variable:25:bad_default:Undefined variable 'xxxx' +undefined-variable:26:bad_default:Undefined variable 'augvar' +unused-variable:26:bad_default:Unused variable 'augvar' +undefined-variable:27:bad_default:Undefined variable 'vardel' +undefined-variable:29:<lambda>:Undefined variable 'doesnotexist' +undefined-variable:30:<lambda>:Undefined variable 'z' +used-before-assignment:51::Using variable 'PLOUF' before assignment +used-before-assignment:60:if_branch_test:Using variable 'xxx' before assignment +used-before-assignment:86:test_arguments:Using variable 'TestClass' before assignment +used-before-assignment:90:TestClass:Using variable 'Ancestor' before assignment +used-before-assignment:93:TestClass.MissingAncestor:Using variable 'Ancestor1' before assignment +used-before-assignment:100:TestClass.test1.UsingBeforeDefinition:Using variable 'Empty' before assignment +undefined-variable:114:Self:Undefined variable 'Self'
\ No newline at end of file diff --git a/pylint/test/functional/undefined_variable_py30.py b/pylint/test/functional/undefined_variable_py30.py new file mode 100644 index 0000000..40cbdcc --- /dev/null +++ b/pylint/test/functional/undefined_variable_py30.py @@ -0,0 +1,58 @@ +"""Test warnings about access to undefined variables +for various Python 3 constructs. """ +# pylint: disable=too-few-public-methods, no-init, no-self-use + +class Undefined: + """ test various annotation problems. """ + + def test(self)->Undefined: # [undefined-variable] + """ used Undefined, which is Undefined in this scope. """ + + Undefined = True + + def test1(self)->Undefined: + """ This Undefined exists at local scope. """ + + def test2(self): + """ This should not emit. """ + def func()->Undefined: + """ empty """ + return + return func + + +class Undefined1: + """ Other annotation problems. """ + + Undef = 42 + ABC = 42 + + class InnerScope: + """ Test inner scope definition. """ + + def test_undefined(self)->Undef: # [undefined-variable] + """ Looking at a higher scope is impossible. """ + + def test1(self)->ABC: # [undefined-variable] + """ Triggers undefined-variable. """ + + +class FalsePositive342(object): + # pylint: disable=line-too-long + """ Fix some false positives found in + https://bitbucket.org/logilab/pylint/issue/342/spurious-undefined-variable-for-class + """ + + top = 42 + + def test_good(self, abc: top): + """ top is defined at this moment. """ + + def test_bad(self, abc: trop): # [undefined-variable] + """ trop is undefined at this moment. """ + + def test_bad1(self, *args: trop1): # [undefined-variable] + """ trop1 is undefined at this moment. """ + + def test_bad2(self, **abc: trop2): # [undefined-variable] + """ trop2 is undefined at this moment. """ diff --git a/pylint/test/functional/undefined_variable_py30.rc b/pylint/test/functional/undefined_variable_py30.rc new file mode 100644 index 0000000..c093be2 --- /dev/null +++ b/pylint/test/functional/undefined_variable_py30.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.0 diff --git a/pylint/test/functional/undefined_variable_py30.txt b/pylint/test/functional/undefined_variable_py30.txt new file mode 100644 index 0000000..f6a7596 --- /dev/null +++ b/pylint/test/functional/undefined_variable_py30.txt @@ -0,0 +1,6 @@ +undefined-variable:8:Undefined.test:Undefined variable 'Undefined'
+undefined-variable:33:Undefined1.InnerScope.test_undefined:Undefined variable 'Undef'
+undefined-variable:36:Undefined1.InnerScope.test1:Undefined variable 'ABC'
+undefined-variable:51:FalsePositive342.test_bad:Undefined variable 'trop'
+undefined-variable:54:FalsePositive342.test_bad1:Undefined variable 'trop1'
+undefined-variable:57:FalsePositive342.test_bad2:Undefined variable 'trop2'
\ No newline at end of file diff --git a/pylint/test/functional/uninferable_all_object.py b/pylint/test/functional/uninferable_all_object.py new file mode 100644 index 0000000..3e565f9 --- /dev/null +++ b/pylint/test/functional/uninferable_all_object.py @@ -0,0 +1,9 @@ +"""Test that non-inferable __all__ variables do not make Pylint crash.""" + +__all__ = sorted([ + 'Dummy', + 'NonExistant', + 'path', + 'func', + 'inner', + 'InnerKlass']) diff --git a/pylint/test/functional/unknown_encoding_py29.py b/pylint/test/functional/unknown_encoding_py29.py new file mode 100644 index 0000000..aeb2be6 --- /dev/null +++ b/pylint/test/functional/unknown_encoding_py29.py @@ -0,0 +1,7 @@ +# [syntax-error]
+# -*- coding: IBO-8859-1 -*-
+""" check correct unknown encoding declaration
+"""
+
+__revision__ = '×™×™×™×™'
+
diff --git a/pylint/test/functional/unknown_encoding_py29.rc b/pylint/test/functional/unknown_encoding_py29.rc new file mode 100644 index 0000000..1c066fb --- /dev/null +++ b/pylint/test/functional/unknown_encoding_py29.rc @@ -0,0 +1,3 @@ +[testoptions] +max_pyver=3.0 +except_implementations=PyPy
\ No newline at end of file diff --git a/pylint/test/functional/unknown_encoding_py29.txt b/pylint/test/functional/unknown_encoding_py29.txt new file mode 100644 index 0000000..751d696 --- /dev/null +++ b/pylint/test/functional/unknown_encoding_py29.txt @@ -0,0 +1 @@ +E: 1: unknown encoding: IBO-8859-1
diff --git a/pylint/test/functional/unknown_encoding_py30.py b/pylint/test/functional/unknown_encoding_py30.py new file mode 100644 index 0000000..2ee5f69 --- /dev/null +++ b/pylint/test/functional/unknown_encoding_py30.py @@ -0,0 +1,7 @@ +# +1: [syntax-error]
+# -*- coding: IBO-8859-1 -*-
+""" check correct unknown encoding declaration
+"""
+
+__revision__ = '×™×™×™×™'
+
diff --git a/pylint/test/functional/unknown_encoding_py30.rc b/pylint/test/functional/unknown_encoding_py30.rc new file mode 100644 index 0000000..504d932 --- /dev/null +++ b/pylint/test/functional/unknown_encoding_py30.rc @@ -0,0 +1,3 @@ +[testoptions] +min_pyver=3.0 +except_implementations=PyPy
\ No newline at end of file diff --git a/test/messages/func_unknown_encoding_py30.txt b/pylint/test/functional/unknown_encoding_py30.txt index 9a0a628..9a0a628 100644 --- a/test/messages/func_unknown_encoding_py30.txt +++ b/pylint/test/functional/unknown_encoding_py30.txt diff --git a/pylint/test/functional/unknown_encoding_pypy.py b/pylint/test/functional/unknown_encoding_pypy.py new file mode 100644 index 0000000..2ee5f69 --- /dev/null +++ b/pylint/test/functional/unknown_encoding_pypy.py @@ -0,0 +1,7 @@ +# +1: [syntax-error]
+# -*- coding: IBO-8859-1 -*-
+""" check correct unknown encoding declaration
+"""
+
+__revision__ = '×™×™×™×™'
+
diff --git a/pylint/test/functional/unknown_encoding_pypy.rc b/pylint/test/functional/unknown_encoding_pypy.rc new file mode 100644 index 0000000..5bc0b70 --- /dev/null +++ b/pylint/test/functional/unknown_encoding_pypy.rc @@ -0,0 +1,3 @@ +[testoptions] +min_pyver=3.0 +except_implementations=CPython, Jython, IronPython
\ No newline at end of file diff --git a/pylint/test/functional/unknown_encoding_pypy.txt b/pylint/test/functional/unknown_encoding_pypy.txt new file mode 100644 index 0000000..489a8c6 --- /dev/null +++ b/pylint/test/functional/unknown_encoding_pypy.txt @@ -0,0 +1 @@ +E: 1: Unknown encoding: ibo-8859-1
\ No newline at end of file diff --git a/pylint/test/functional/unnecessary_lambda.py b/pylint/test/functional/unnecessary_lambda.py new file mode 100644 index 0000000..1c9b424 --- /dev/null +++ b/pylint/test/functional/unnecessary_lambda.py @@ -0,0 +1,50 @@ +# pylint: disable=star-args, undefined-variable
+"""test suspicious lambda expressions
+"""
+
+__revision__ = ''
+
+# Some simple examples of the most commonly encountered forms.
+# +1: [unnecessary-lambda]
+_ = lambda: list() # replaceable with "list"
+# +1: [unnecessary-lambda]
+_ = lambda x: hash(x) # replaceable with "hash"
+# +1: [unnecessary-lambda]
+_ = lambda x, y: min(x, y) # replaceable with "min"
+
+# A function that can take any arguments given to it.
+_ANYARGS = lambda *args, **kwargs: 'completely arbitrary return value'
+
+# Some more complex forms of unnecessary lambda expressions.
+# +1: [unnecessary-lambda]
+_ = lambda *args: _ANYARGS(*args)
+# +1: [unnecessary-lambda]
+_ = lambda **kwargs: _ANYARGS(**kwargs)
+# +1: [unnecessary-lambda]
+_ = lambda *args, **kwargs: _ANYARGS(*args, **kwargs)
+# +1: [unnecessary-lambda]
+_ = lambda x, y, z, *args, **kwargs: _ANYARGS(x, y, z, *args, **kwargs)
+
+# Lambdas that are *not* unnecessary and should *not* trigger warnings.
+_ = lambda x: x
+_ = lambda x: x()
+_ = lambda x=4: hash(x)
+_ = lambda x, y: list(range(y, x))
+_ = lambda x: list(range(5, x))
+_ = lambda x, y: list(range(x, 5))
+_ = lambda x, y, z: x.y(z)
+_ = lambda: 5
+_ = lambda **kwargs: _ANYARGS()
+_ = lambda **kwargs: _ANYARGS(**dict([('three', 3)]))
+_ = lambda **kwargs: _ANYARGS(**{'three': 3})
+_ = lambda dict_arg, **kwargs: _ANYARGS(kwargs, **dict_arg)
+_ = lambda *args: _ANYARGS()
+_ = lambda *args: _ANYARGS(*list([3, 4]))
+_ = lambda *args: _ANYARGS(*[3, 4])
+_ = lambda list_arg, *args: _ANYARGS(args, *list_arg)
+_ = lambda: _ANYARGS(*[3])
+_ = lambda: _ANYARGS(**{'three': 3})
+_ = lambda: _ANYARGS(*[3], **{'three': 3})
+
+# Don't warn about this.
+_ = lambda: code().analysis()
diff --git a/pylint/test/functional/unnecessary_lambda.txt b/pylint/test/functional/unnecessary_lambda.txt new file mode 100644 index 0000000..de13882 --- /dev/null +++ b/pylint/test/functional/unnecessary_lambda.txt @@ -0,0 +1,7 @@ +unnecessary-lambda:9:<lambda>:Lambda may not be necessary +unnecessary-lambda:11:<lambda>:Lambda may not be necessary +unnecessary-lambda:13:<lambda>:Lambda may not be necessary +unnecessary-lambda:20:<lambda>:Lambda may not be necessary +unnecessary-lambda:22:<lambda>:Lambda may not be necessary +unnecessary-lambda:24:<lambda>:Lambda may not be necessary +unnecessary-lambda:26:<lambda>:Lambda may not be necessary diff --git a/pylint/test/functional/unpacked_exceptions.py b/pylint/test/functional/unpacked_exceptions.py new file mode 100644 index 0000000..1eb6675 --- /dev/null +++ b/pylint/test/functional/unpacked_exceptions.py @@ -0,0 +1,11 @@ +"""Test for redefine-in-handler, overwriting names in exception handlers.""" + +def new_style(): + """Some exceptions can be unpacked.""" + try: + pass + except IOError, (errno, message): # [unpacking-in-except] + print errno, message # pylint: disable=print-statement + # +1: [redefine-in-handler,redefine-in-handler,unpacking-in-except] + except IOError, (new_style, tuple): + print new_style, tuple # pylint: disable=print-statement diff --git a/pylint/test/functional/unpacked_exceptions.rc b/pylint/test/functional/unpacked_exceptions.rc new file mode 100644 index 0000000..9540bc9 --- /dev/null +++ b/pylint/test/functional/unpacked_exceptions.rc @@ -0,0 +1,5 @@ +[testoptions] +max_pyver=3.0 + +[Messages Control] +enable=python3
\ No newline at end of file diff --git a/pylint/test/functional/unpacked_exceptions.txt b/pylint/test/functional/unpacked_exceptions.txt new file mode 100644 index 0000000..1a1b034 --- /dev/null +++ b/pylint/test/functional/unpacked_exceptions.txt @@ -0,0 +1,4 @@ +unpacking-in-except:7:new_style:Implicit unpacking of exceptions is not supported in Python 3 +redefine-in-handler:10:new_style:Redefining name 'new_style' from outer scope (line 3) in exception handler +redefine-in-handler:10:new_style:Redefining name 'tuple' from builtins in exception handler +unpacking-in-except:10:new_style:Implicit unpacking of exceptions is not supported in Python 3 diff --git a/pylint/test/functional/unpacking.py b/pylint/test/functional/unpacking.py new file mode 100644 index 0000000..59d9abb --- /dev/null +++ b/pylint/test/functional/unpacking.py @@ -0,0 +1,11 @@ +""" Code for checking the display of the module
+for unbalanced-tuple-unpacking and unpacking-non-sequence
+"""
+
+def unpack():
+ """ Return something"""
+ return (1, 2, 3)
+
+def nonseq():
+ """ Return non sequence """
+ return 1
diff --git a/pylint/test/functional/unpacking_non_sequence.py b/pylint/test/functional/unpacking_non_sequence.py new file mode 100644 index 0000000..7b1d057 --- /dev/null +++ b/pylint/test/functional/unpacking_non_sequence.py @@ -0,0 +1,86 @@ +"""Check unpacking non-sequences in assignments. """ + +# pylint: disable=too-few-public-methods, invalid-name, attribute-defined-outside-init, unused-variable, no-absolute-import + +from os import rename as nonseq_func +from functional.unpacking import nonseq + +__revision__ = 0 + +# Working + +class Seq(object): + """ sequence """ + def __init__(self): + self.items = range(2) + + def __getitem__(self, item): + return self.items[item] + + def __len__(self): + return len(self.items) + +class Iter(object): + """ Iterator """ + def __iter__(self): + for number in range(2): + yield number + +def good_unpacking(): + """ returns should be unpackable """ + if True: + return [1, 2] + else: + return (3, 4) + +def good_unpacking2(): + """ returns should be unpackable """ + return good_unpacking() + +a, b = [1, 2] +a, b = (1, 2) +a, b = set([1, 2]) +a, b = {1: 2, 2: 3} +a, b = "xy" +a, b = Seq() +a, b = Iter() +a, b = (number for number in range(2)) +a, b = good_unpacking() +a, b = good_unpacking2() + +# Not working +class NonSeq(object): + """ does nothing """ + +def bad_unpacking(): + """ one return isn't unpackable """ + if True: + return None + return [1, 2] + +a, b = NonSeq() # [unpacking-non-sequence] +a, b = ValueError # [unpacking-non-sequence] +a, b = None # [unpacking-non-sequence] +a, b = 1 # [unpacking-non-sequence] +a, b = nonseq # [unpacking-non-sequence] +a, b = nonseq() # [unpacking-non-sequence] +a, b = bad_unpacking() # [unpacking-non-sequence] +a, b = nonseq_func # [unpacking-non-sequence] + +class ClassUnpacking(object): + """ Check unpacking as instance attributes. """ + + def test(self): + """ test unpacking in instance attributes. """ + + self.a, self.b = 1, 2 + self.a, self.b = {1: 2, 2: 3} + self.a, self.b = "xy" + self.a, c = "xy" + c, self.a = good_unpacking() + self.a, self.b = Iter() + + self.a, self.b = NonSeq() # [unpacking-non-sequence] + self.a, self.b = ValueError # [unpacking-non-sequence] + self.a, self.b = bad_unpacking() # [unpacking-non-sequence] + self.a, c = nonseq_func # [unpacking-non-sequence] diff --git a/pylint/test/functional/unpacking_non_sequence.txt b/pylint/test/functional/unpacking_non_sequence.txt new file mode 100644 index 0000000..af58686 --- /dev/null +++ b/pylint/test/functional/unpacking_non_sequence.txt @@ -0,0 +1,12 @@ +unpacking-non-sequence:61::Attempting to unpack a non-sequence defined at line 52 +unpacking-non-sequence:62::Attempting to unpack a non-sequence +unpacking-non-sequence:63::Attempting to unpack a non-sequence None +unpacking-non-sequence:64::Attempting to unpack a non-sequence 1 +unpacking-non-sequence:65::Attempting to unpack a non-sequence defined at line 9 of functional.unpacking +unpacking-non-sequence:66::Attempting to unpack a non-sequence defined at line 11 of functional.unpacking +unpacking-non-sequence:67::Attempting to unpack a non-sequence defined at line 58 +unpacking-non-sequence:68::Attempting to unpack a non-sequence +unpacking-non-sequence:83:ClassUnpacking.test:Attempting to unpack a non-sequence defined at line 52 +unpacking-non-sequence:84:ClassUnpacking.test:Attempting to unpack a non-sequence +unpacking-non-sequence:85:ClassUnpacking.test:Attempting to unpack a non-sequence defined at line 58 +unpacking-non-sequence:86:ClassUnpacking.test:Attempting to unpack a non-sequence diff --git a/pylint/test/functional/unused_import.py b/pylint/test/functional/unused_import.py new file mode 100644 index 0000000..ef54000 --- /dev/null +++ b/pylint/test/functional/unused_import.py @@ -0,0 +1,12 @@ +"""unused import""" +# pylint: disable=undefined-all-variable, import-error, no-absolute-import +import xml.etree # [unused-import] +import xml.sax # [unused-import] +import os.path as test # [unused-import] +from sys import argv as test2 # [unused-import] +from sys import flags # [unused-import] +# +1:[unused-import,unused-import] +from collections import deque, OrderedDict, Counter +DATA = Counter() + +from never import __all__ diff --git a/pylint/test/functional/unused_import.txt b/pylint/test/functional/unused_import.txt new file mode 100644 index 0000000..b2b79e0 --- /dev/null +++ b/pylint/test/functional/unused_import.txt @@ -0,0 +1,7 @@ +unused-import:3::Unused import xml.etree +unused-import:4::Unused import xml.sax +unused-import:5::Unused os.path imported as test +unused-import:6::Unused argv imported from sys as test2 +unused-import:7::Unused flags imported from sys +unused-import:9::Unused OrderedDict imported from collections +unused-import:9::Unused deque imported from collections diff --git a/pylint/test/functional/useless_else_on_loop.py b/pylint/test/functional/useless_else_on_loop.py new file mode 100644 index 0000000..2fcde8b --- /dev/null +++ b/pylint/test/functional/useless_else_on_loop.py @@ -0,0 +1,55 @@ +"""Check for else branches on loops with break an return only.""" +from __future__ import print_function +__revision__ = 0 + +def test_return_for(): + """else + return is not accetable.""" + for i in range(10): + if i % 2: + return i + else: # [useless-else-on-loop] + print('math is broken') + +def test_return_while(): + """else + return is not accetable.""" + while True: + return 1 + else: # [useless-else-on-loop] + print('math is broken') + + +while True: + def short_fun(): + """A function with a loop.""" + for _ in range(10): + break +else: # [useless-else-on-loop] + print('or else!') + + +while True: + while False: + break +else: # [useless-else-on-loop] + print('or else!') + +for j in range(10): + pass +else: # [useless-else-on-loop] + print('fat chance') + for j in range(10): + break + +def test_return_for2(): + """no false positive for break in else + + https://bitbucket.org/logilab/pylint/issue/117/useless-else-on-loop-false-positives + """ + for i in range(10): + for i in range(i): + if i % 2: + break + else: + break + else: + print('great math') diff --git a/pylint/test/functional/useless_else_on_loop.txt b/pylint/test/functional/useless_else_on_loop.txt new file mode 100644 index 0000000..93309b6 --- /dev/null +++ b/pylint/test/functional/useless_else_on_loop.txt @@ -0,0 +1,5 @@ +useless-else-on-loop:10:test_return_for:Else clause on loop without a break statement +useless-else-on-loop:17:test_return_while:Else clause on loop without a break statement +useless-else-on-loop:26::Else clause on loop without a break statement +useless-else-on-loop:33::Else clause on loop without a break statement +useless-else-on-loop:38::Else clause on loop without a break statement diff --git a/pylint/test/functional/with_used_before_assign.py b/pylint/test/functional/with_used_before_assign.py new file mode 100644 index 0000000..64a475a --- /dev/null +++ b/pylint/test/functional/with_used_before_assign.py @@ -0,0 +1,11 @@ +''' +Regression test for +https://bitbucket.org/logilab/pylint/issue/128/attributeerror-when-parsing +''' +from __future__ import with_statement + +def do_nothing(): + """ empty """ + with open("") as ctx.obj: # [undefined-variable] + context.do() # [used-before-assignment] + context = None diff --git a/pylint/test/functional/with_used_before_assign.txt b/pylint/test/functional/with_used_before_assign.txt new file mode 100644 index 0000000..783ae73 --- /dev/null +++ b/pylint/test/functional/with_used_before_assign.txt @@ -0,0 +1,2 @@ +undefined-variable:9:do_nothing:Undefined variable 'ctx' +used-before-assignment:10:do_nothing:Using variable 'context' before assignment diff --git a/pylint/test/functional/yield_outside_func.py b/pylint/test/functional/yield_outside_func.py new file mode 100644 index 0000000..5824bc0 --- /dev/null +++ b/pylint/test/functional/yield_outside_func.py @@ -0,0 +1,4 @@ +"""This is gramatically correct, but it's still a SyntaxError""" +yield 1 # [yield-outside-function] + +LAMBDA_WITH_YIELD = lambda: (yield) diff --git a/pylint/test/functional/yield_outside_func.txt b/pylint/test/functional/yield_outside_func.txt new file mode 100644 index 0000000..fa3a7fc --- /dev/null +++ b/pylint/test/functional/yield_outside_func.txt @@ -0,0 +1 @@ +yield-outside-function:2::Yield outside function diff --git a/test/input/__init__.py b/pylint/test/input/__init__.py index 60e92b7..60e92b7 100644 --- a/test/input/__init__.py +++ b/pylint/test/input/__init__.py diff --git a/pylint/test/input/func_3k_removed_stuff_py_30.py b/pylint/test/input/func_3k_removed_stuff_py_30.py new file mode 100644 index 0000000..75f7eb8 --- /dev/null +++ b/pylint/test/input/func_3k_removed_stuff_py_30.py @@ -0,0 +1,13 @@ +"""test relative import""" +# pylint: disable=no-absolute-import +__revision__ = filter(None, map(str, (1, 2, 3))) +from __future__ import generators, print_function + +import func_w0302 + +def function(): + """something""" + print(func_w0302) + unic = u"unicode" + low = unic.looower + return low diff --git a/test/input/func_assert_2uple.py b/pylint/test/input/func_assert_2uple.py index de93d3b..de93d3b 100644 --- a/test/input/func_assert_2uple.py +++ b/pylint/test/input/func_assert_2uple.py diff --git a/pylint/test/input/func_attrs_definition_order.py b/pylint/test/input/func_attrs_definition_order.py new file mode 100644 index 0000000..896426d --- /dev/null +++ b/pylint/test/input/func_attrs_definition_order.py @@ -0,0 +1,33 @@ +# pylint: disable=R0903, print-statement +"""yo""" + +__revision__ = '$I$' + +class Aaaa(object): + """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 + + def __getattr__(self, attr): + # pylint: disable=W0201 + try: + return self.__repo + except AttributeError: + self.__repo = attr + return attr + + + def catchme(self, attr): + """no AttributeError catched""" + # pylint: disable=W0201 + try: + return self._repo + except ValueError: + self._repo = attr + return attr diff --git a/pylint/test/input/func_bad_assigment_to_exception_var.py b/pylint/test/input/func_bad_assigment_to_exception_var.py new file mode 100644 index 0000000..b147446 --- /dev/null +++ b/pylint/test/input/func_bad_assigment_to_exception_var.py @@ -0,0 +1,30 @@ +# pylint:disable=C0103, print-statement, no-absolute-import +"""ho ho ho""" +__revision__ = 'toto' + +import sys + +e = 1 +e2 = 'yo' +e3 = None +try: + raise e +except Exception, ex: + print ex + _, _, tb = sys.exc_info() + + + +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/pylint/test/input/func_bad_cont_dictcomp_py27.py b/pylint/test/input/func_bad_cont_dictcomp_py27.py new file mode 100644 index 0000000..a552710 --- /dev/null +++ b/pylint/test/input/func_bad_cont_dictcomp_py27.py @@ -0,0 +1,38 @@ +"""Bad continuations in dictionary comprehensions.""" + +__revision__ = 0 + +# Dictionary comprehensions should not require extra indentation when breaking +# before the 'for', which is not part of the value +C1 = {'key{}'.format(x): 'value{}'.format(x) + for x in range(3)} + +C2 = {'key{}'.format(x): 'value{}'.format(x) for x in + range(3)} + +# Dictionary comprehensions with multiple loops broken in different places +C3 = {x*y: (x, y) for x in range(3) for y in range(3)} + +C4 = {x*y: (x, y) + for x in range(3) for y in range(3)} + +C5 = {x*y: (x, y) for x + in range(3) for y in range(3)} + +C6 = {x*y: (x, y) for x in range(3) + for y in range(3)} + +C7 = {key: + key ** 2 + for key in range(10)} + +C8 = { + key: key ** 2 + for key in range(10)} + +# Misaligned cases for dict comprehensions +C9 = {'key{}'.format(x): 'value{}'.format(x) + for x in range(3)} # [bad-continuation] + +C9 = {'key{}'.format(x): 'value{}'.format(x) + for x in range(3)} # [bad-continuation] diff --git a/pylint/test/input/func_bad_exception_context_py30.py b/pylint/test/input/func_bad_exception_context_py30.py new file mode 100644 index 0000000..98b44ee --- /dev/null +++ b/pylint/test/input/func_bad_exception_context_py30.py @@ -0,0 +1,24 @@ +"""Check that raise ... from .. uses a proper exception context """ + +# pylint: disable=unreachable, import-error + +import socket, unknown + +__revision__ = 0 + +class ExceptionSubclass(Exception): + """ subclass """ + +def test(): + """ docstring """ + raise IndexError from 1 + raise IndexError from None + raise IndexError from ZeroDivisionError + raise IndexError from object() + raise IndexError from ExceptionSubclass + raise IndexError from socket.error + raise IndexError() from None + raise IndexError() from ZeroDivisionError + raise IndexError() from ZeroDivisionError() + raise IndexError() from object() + raise IndexError() from unknown diff --git a/pylint/test/input/func_base_useless_pass.py b/pylint/test/input/func_base_useless_pass.py new file mode 100644 index 0000000..9faae59 --- /dev/null +++ b/pylint/test/input/func_base_useless_pass.py @@ -0,0 +1,9 @@ +"""W0107: unnecessary pass statement +""" +__revision__ = None + +try: + A = 2 +except ValueError: + print A # pylint: disable=print-statement + pass diff --git a/pylint/test/input/func_block_disable_msg.py b/pylint/test/input/func_block_disable_msg.py new file mode 100644 index 0000000..927b218 --- /dev/null +++ b/pylint/test/input/func_block_disable_msg.py @@ -0,0 +1,1026 @@ +# pylint: disable=C0302,bare-except,print-statement +"""pylint option block-disable""" +__revision__ = None + +class Foo(object): + """block-disable 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=W0613 + print self\ + + "foo" + + def meth3(self): + """test one line disabling""" + # no error + print self.bla # pylint: disable=E1101 + # error + print self.blop + + def meth4(self): + """test re-enabling""" + # pylint: disable=E1101 + # no error + print self.bla + print self.blop + # pylint: enable=E1101 + # error + print self.blip + + def meth5(self): + """test IF sub-block re-enabling""" + # pylint: disable=E1101 + # no error + print self.bla + if self.blop: + # pylint: enable=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=E1101 + # no error + print self.bla + try: + # pylint: enable=E1101 + # error + print self.blip + except UndefinedName: # pylint: disable=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=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=E1101 + # no error + print self.bla + print self.blop + + def meth9(self): + """test re-enabling right after a block with whitespace""" + eris = 5 + + if eris: + print "In block" + + # pylint: disable=E1101 + # no error + print self.bla + print self.blu + # pylint: enable=E1101 + # error + print self.blip + + def meth10(self): + """Test double disable""" + # pylint: disable=E1101 + # no error + print self.bla + # pylint: disable=E1101 + print self.blu + + +class ClassLevelMessage(object): + """shouldn't display to much attributes/not enough methods messages + """ + # pylint: disable=R0902,R0903 + + def __init__(self): + self.attr1 = 1 + self.attr2 = 1 + self.attr3 = 1 + self.attr4 = 1 + self.attr5 = 1 + self.attr6 = 1 + self.attr7 = 1 + self.attr8 = 1 + self.attr9 = 1 + self.attr0 = 1 + + def too_complex_but_thats_ok(self, attr1, attr2): + """THIS Method has too much branches and returns but i don't care + """ + # pylint: disable=R0912,R0911 + try: + attr3 = attr1+attr2 + except ValueError: + attr3 = None + except: + return 'duh', self + if attr1: + for i in attr1: + if attr2: + return i + else: + return 'duh' + elif attr2: + for i in attr2: + if attr2: + return i + else: + return 'duh' + else: + for i in range(15): + if attr3: + return i + else: + return 'doh' + return None + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +print 'hop, too many lines but i don\'t care' diff --git a/pylint/test/input/func_break_or_return_in_try_finally.py b/pylint/test/input/func_break_or_return_in_try_finally.py new file mode 100644 index 0000000..d9a50f9 --- /dev/null +++ b/pylint/test/input/func_break_or_return_in_try_finally.py @@ -0,0 +1,44 @@ +'Exeptions may be silently swallowed' +from __future__ import print_function +__revision__ = None + +def insidious_break_and_return(): + """I found you !""" + for i in range(0, -5, -1): + my_var = 0 + print(i) + try: + my_var += 1.0/i + if i < -3: + break # :D + else: + return my_var # :D + finally: + if i > -2: + break # :( + else: + return my_var # :( + return None + +def break_and_return(): + """I found you !""" + for i in range(0, -5, -1): + my_var = 0 + if i: + break # :D + try: + my_var += 1.0/i + finally: + for i in range(2): + if True: + break # :D + else: + def strange(): + """why not ?""" + if True: + return my_var # :D + strange() + if i: + break # :D + else: + return # :D diff --git a/pylint/test/input/func_bug113231.py b/pylint/test/input/func_bug113231.py new file mode 100644 index 0000000..80996c3 --- /dev/null +++ b/pylint/test/input/func_bug113231.py @@ -0,0 +1,24 @@ +# pylint: disable=E1101 +# pylint: disable=C0103 +# pylint: disable=R0903 +"""test bugfix for #113231 in logging checker +""" +from __future__ import absolute_import +__revision__ = '' + +# Muck up the names in an effort to confuse... +import logging as renamed_logging + +class Logger(object): + """Fake logger""" + pass + +logger = renamed_logging.getLogger(__name__) +fake_logger = Logger() + +# Statements that should be flagged: +renamed_logging.warn('%s, %s' % (4, 5)) +logger.warn('%s' % 5) + +# Statements that should not be flagged: +fake_logger.warn('%s' % 5) diff --git a/test/input/func_continue_not_in_loop.py b/pylint/test/input/func_continue_not_in_loop.py index 4186aa5..4186aa5 100644 --- a/test/input/func_continue_not_in_loop.py +++ b/pylint/test/input/func_continue_not_in_loop.py diff --git a/pylint/test/input/func_dangerous_default.py b/pylint/test/input/func_dangerous_default.py new file mode 100644 index 0000000..dcf7380 --- /dev/null +++ b/pylint/test/input/func_dangerous_default.py @@ -0,0 +1,76 @@ +"""docstring""" +# pylint: disable=print-statement +__revision__ = '' + +HEHE = {} + +def function1(value=[]): + """docstring""" + print value + +def function2(value=HEHE): + """docstring""" + print value + +def function3(value): + """docstring""" + print value + +def function4(value=set()): + """set is mutable and dangerous.""" + print value + +def function5(value=frozenset()): + """frozenset is immutable and safe.""" + print value + +GLOBAL_SET = set() + +def function6(value=GLOBAL_SET): + """set is mutable and dangerous.""" + print value + +def function7(value=dict()): + """dict is mutable and dangerous.""" + print value + +def function8(value=list()): + """list is mutable and dangerous.""" + print value + +def function9(value=[1, 2, 3, 4]): + """list with items should not output item values in error message""" + print value + +def function10(value={'a': 1, 'b': 2}): + """dictionaries with items should not output item values in error message""" + print value + +def function11(value=list([1, 2, 3])): + """list with items should not output item values in error message""" + print value + +def function12(value=dict([('a', 1), ('b', 2)])): + """dictionaries with items should not output item values in error message""" + print value + +OINK = { + 'a': 1, + 'b': 2 +} + +def function13(value=OINK): + """dictionaries with items should not output item values in error message""" + print value + +def function14(value=dict([(1, 2), (1, 2, 3)])): + """a dictionary which will not be inferred to a syntax AST, but to an + astroid.Instance. + """ + return value + +INVALID_DICT = dict([(1, 2), (1, 2, 3)]) + +def function15(value=INVALID_DICT): + """The same situation as function14.""" + return value diff --git a/pylint/test/input/func_defining-attr-methods_order.py b/pylint/test/input/func_defining-attr-methods_order.py new file mode 100644 index 0000000..28918f2 --- /dev/null +++ b/pylint/test/input/func_defining-attr-methods_order.py @@ -0,0 +1,73 @@ +# pylint: disable=C0103, too-few-public-methods + +''' Test that y is defined properly, z is not. + Default defining methods are __init__, + __new__, and setUp. + Order of methods should not matter. ''' + +__revision__ = '' + +class A(object): + ''' class A ''' + + def __init__(self): + ''' __init__ docstring filler ''' + self.x = 0 + self.setUp() + + def set_y(self, y): + ''' set_y docstring filler ''' + self.y = y + + def set_x(self, x): + ''' set_x docstring filler ''' + self.x = x + + def set_z(self, z): + ''' set_z docstring filler ''' + self.z = z + self.z = z + + def setUp(self): + ''' setUp docstring filler ''' + self.x = 0 + self.y = 0 + +class B(A): + ''' class B ''' + + def test(self): + """ test """ + self.z = 44 + +class C(object): + ''' class C ''' + + def __init__(self): + self._init() + + def _init(self): + ''' called by __init__ ''' + self.z = 44 + +class D(object): + ''' class D ''' + + def setUp(self): + ''' defining method ''' + self.set_z() + + def set_z(self): + ''' called by the parent. ''' + self.z = 42 + +class E(object): + ''' Reassign the function. ''' + + def __init__(self): + i = self._init + i() + + def _init(self): + ''' called by __init__ ''' + self.z = 44 diff --git a/pylint/test/input/func_deprecated_lambda_py_30.py b/pylint/test/input/func_deprecated_lambda_py_30.py new file mode 100644 index 0000000..74b3241 --- /dev/null +++ b/pylint/test/input/func_deprecated_lambda_py_30.py @@ -0,0 +1,24 @@ +# pylint: disable=missing-docstring,bad-builtin,invalid-name,no-absolute-import +__revision__ = "$Id$" + +import functools + +# Don't do this, use a comprehension instead. +assert map(lambda x: x*2, [1, 2, 3]) == [2, 4, 6] + +assert filter(lambda x: x != 1, [1, 2, 3]) == [2, 3] + +# It's still ok to use map and filter with anything but an inline lambda. +double = lambda x: x * 2 +assert map(double, [1, 2, 3]) == [2, 4, 6] + +# It's also ok to pass lambdas to other functions. +assert functools.reduce(lambda x, y: x * y, [1, 2, 3, 4]) == 24 + +# Or to a undefined function or one with varargs +def f(*a): + return len(a) + +f(lambda x, y: x + y, [1, 2, 3]) + +undefined_function(lambda: 2) # pylint: disable=undefined-variable diff --git a/pylint/test/input/func_deprecated_module_py30.py b/pylint/test/input/func_deprecated_module_py30.py new file mode 100644 index 0000000..cadbac6 --- /dev/null +++ b/pylint/test/input/func_deprecated_module_py30.py @@ -0,0 +1,12 @@ +"""test deprecated module +""" + +__revision__ = 0 + + +if __revision__: + import optparse + print optparse + # false positive (#10061) + import stringfile + print stringfile diff --git a/pylint/test/input/func_deprecated_module_py_30.py b/pylint/test/input/func_deprecated_module_py_30.py new file mode 100644 index 0000000..7d2c19f --- /dev/null +++ b/pylint/test/input/func_deprecated_module_py_30.py @@ -0,0 +1,12 @@ +"""test deprecated module +""" +from __future__ import absolute_import, print_function +__revision__ = 0 + + +if __revision__: + import Bastion + print(Bastion) + # false positive (#10061) + import stringfile + print(stringfile) diff --git a/pylint/test/input/func_disable_linebased.py b/pylint/test/input/func_disable_linebased.py new file mode 100644 index 0000000..953848b --- /dev/null +++ b/pylint/test/input/func_disable_linebased.py @@ -0,0 +1,14 @@ +# This is a very very very very very very very very very very very very very very very very very very very very very long line. +# pylint: disable=line-too-long, print-statement +"""Make sure enable/disable pragmas work for messages that are applied to lines and not syntax nodes. + +A disable pragma for a message that applies to nodes is applied to the whole +block if it comes before the first statement (excluding the docstring). For +line-based messages, this behavior needs to be altered to really only apply to +the enclosed lines. +""" +# pylint: enable=line-too-long + +__revision__ = '1' + +print 'This is a very long line which the linter will warn about, now that line-too-long has been enabled again.' diff --git a/pylint/test/input/func_dotted_ancestor.py b/pylint/test/input/func_dotted_ancestor.py new file mode 100644 index 0000000..0603524 --- /dev/null +++ b/pylint/test/input/func_dotted_ancestor.py @@ -0,0 +1,11 @@ +"""bla""" +# pylint: disable=no-absolute-import +__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_e0001_py30.py b/pylint/test/input/func_e0001_py30.py index 9c1b727..9c1b727 100644 --- a/test/input/func_e0001_py30.py +++ b/pylint/test/input/func_e0001_py30.py diff --git a/test/input/func_e0011.py b/pylint/test/input/func_e0011.py index f2bb592..f2bb592 100644 --- a/test/input/func_e0011.py +++ b/pylint/test/input/func_e0011.py diff --git a/test/input/func_e0012.py b/pylint/test/input/func_e0012.py index b50c9e5..b50c9e5 100644 --- a/test/input/func_e0012.py +++ b/pylint/test/input/func_e0012.py diff --git a/test/input/func_e0101.py b/pylint/test/input/func_e0101.py index 2bf5a28..2bf5a28 100644 --- a/test/input/func_e0101.py +++ b/pylint/test/input/func_e0101.py diff --git a/test/input/func_e0108.py b/pylint/test/input/func_e0108.py index 2ed2ce1..2ed2ce1 100644 --- a/test/input/func_e0108.py +++ b/pylint/test/input/func_e0108.py diff --git a/pylint/test/input/func_e0203.py b/pylint/test/input/func_e0203.py new file mode 100644 index 0000000..d86f479 --- /dev/null +++ b/pylint/test/input/func_e0203.py @@ -0,0 +1,19 @@ +"""check for method without self as first argument +""" +from __future__ import print_function +__revision__ = 0 + + +class Abcd(object): + """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/pylint/test/input/func_e0204.py b/pylint/test/input/func_e0204.py new file mode 100644 index 0000000..7ab9cff --- /dev/null +++ b/pylint/test/input/func_e0204.py @@ -0,0 +1,19 @@ +"""check for method without self as first argument +""" +from __future__ import print_function +__revision__ = 0 + + +class Abcd(object): + """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_e0206.py b/pylint/test/input/func_e0206.py index a9f5790..a9f5790 100644 --- a/test/input/func_e0206.py +++ b/pylint/test/input/func_e0206.py diff --git a/pylint/test/input/func_e0601.py b/pylint/test/input/func_e0601.py new file mode 100644 index 0000000..122ba01 --- /dev/null +++ b/pylint/test/input/func_e0601.py @@ -0,0 +1,9 @@ +"""test local variable used before assignment +""" +from __future__ import print_function +__revision__ = 0 + +def function(): + """dummy""" + print(aaaa) + aaaa = 1 diff --git a/test/input/func_e0604.py b/pylint/test/input/func_e0604.py index d077f31..d077f31 100644 --- a/test/input/func_e0604.py +++ b/pylint/test/input/func_e0604.py diff --git a/pylint/test/input/func_e12xx.py b/pylint/test/input/func_e12xx.py new file mode 100644 index 0000000..67475d1 --- /dev/null +++ b/pylint/test/input/func_e12xx.py @@ -0,0 +1,27 @@ +# pylint: disable=E1101, no-absolute-import +"""Test checking of log format strings +""" + +__revision__ = '' + +import logging + +def pprint(): + """Test string format in logging statements. + """ + # These should all emit lint errors: + logging.info(0, '') # 1205 + logging.info('', '') # 1205 + logging.info('%s%', '') # 1201 + logging.info('%s%s', '') # 1206 + logging.info('%s%y', '', '') # 1200 + logging.info('%s%s', '', '', '') # 1205 + + # These should be okay: + logging.info(1) + logging.info(True) + logging.info('') + logging.info('%s%') + logging.info('%s', '') + logging.info('%s%%', '') + logging.info('%s%s', '', '') diff --git a/pylint/test/input/func_e13xx.py b/pylint/test/input/func_e13xx.py new file mode 100644 index 0000000..e2bd77e --- /dev/null +++ b/pylint/test/input/func_e13xx.py @@ -0,0 +1,21 @@ +"""test string format error +""" +# pylint: disable=print-statement +__revision__ = 1 + +PARG_1 = PARG_2 = PARG_3 = 1 + +def pprint(): + """Test string format + """ + print "%s %s" % {'PARG_1': 1, 'PARG_2': 2} # E1306 + print "%s" % (PARG_1, PARG_2) # E1305 + print "%(PARG_1)d %d" % {'PARG_1': 1, 'PARG_2': 2} # E1302 + print "%(PARG_1)d %(PARG_2)d" % {'PARG_1': 1} # E1304 + print "%(PARG_1)d %(PARG_2)d" % {'PARG_1': 1, 'PARG_2':2, 'PARG_3':3}#W1301 + print "%(PARG_1)d %(PARG_2)d" % {'PARG_1': 1, 2:3} # W1300 E1304 + print "%(PARG_1)d %(PARG_2)d" % (2, 3) # 1303 + print "%(PARG_1)d %(PARG_2)d" % [2, 3] # 1303 + print "%2z" % PARG_1 + print "strange format %2" % PARG_2 + print "works in 3 %a" % 1 diff --git a/pylint/test/input/func_empty_module.py b/pylint/test/input/func_empty_module.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/pylint/test/input/func_empty_module.py diff --git a/test/input/func_eval_used.py b/pylint/test/input/func_eval_used.py index c58b69c..c58b69c 100644 --- a/test/input/func_eval_used.py +++ b/pylint/test/input/func_eval_used.py diff --git a/test/input/func_excess_escapes.py b/pylint/test/input/func_excess_escapes.py index 178ace8..178ace8 100644 --- a/test/input/func_excess_escapes.py +++ b/pylint/test/input/func_excess_escapes.py diff --git a/test/input/func_exec_used_py30.py b/pylint/test/input/func_exec_used_py30.py index dbcc024..dbcc024 100644 --- a/test/input/func_exec_used_py30.py +++ b/pylint/test/input/func_exec_used_py30.py diff --git a/pylint/test/input/func_f0401.py b/pylint/test/input/func_f0401.py new file mode 100644 index 0000000..66e1240 --- /dev/null +++ b/pylint/test/input/func_f0401.py @@ -0,0 +1,9 @@ +"""test failed import +""" +from __future__ import absolute_import, print_function +__revision__ = 0 + +def function(): + """yo""" + from tutu import toto + print(toto) diff --git a/test/input/func_first_arg.py b/pylint/test/input/func_first_arg.py index efff51d..efff51d 100644 --- a/test/input/func_first_arg.py +++ b/pylint/test/input/func_first_arg.py diff --git a/pylint/test/input/func_fixme.py b/pylint/test/input/func_fixme.py new file mode 100644 index 0000000..9210be6 --- /dev/null +++ b/pylint/test/input/func_fixme.py @@ -0,0 +1,15 @@ +"""docstring""" +# pylint: disable=W0612 +__revision__ = '' + +# FIXME: beep + + +def function(): + '''XXX:bop''' + variable = "FIXME: Ignore me!" + test = "text" # FIXME: Valid test + + # TODO: Do something with the variables + xxx = "n/a" # XXX: Fix this later + #FIXME: no space after hash diff --git a/test/input/func_i0011.py b/pylint/test/input/func_i0011.py index 2b2f445..2b2f445 100644 --- a/test/input/func_i0011.py +++ b/pylint/test/input/func_i0011.py diff --git a/test/input/func_i0012.py b/pylint/test/input/func_i0012.py index aa646c6..aa646c6 100644 --- a/test/input/func_i0012.py +++ b/pylint/test/input/func_i0012.py diff --git a/test/input/func_i0013.py b/pylint/test/input/func_i0013.py index 0a3f833..0a3f833 100644 --- a/test/input/func_i0013.py +++ b/pylint/test/input/func_i0013.py diff --git a/test/input/func_i0014.py b/pylint/test/input/func_i0014.py index 63ff37c..63ff37c 100644 --- a/test/input/func_i0014.py +++ b/pylint/test/input/func_i0014.py diff --git a/test/input/func_i0020.py b/pylint/test/input/func_i0020.py index 8c0fe9f..8c0fe9f 100644 --- a/test/input/func_i0020.py +++ b/pylint/test/input/func_i0020.py diff --git a/test/input/func_i0022.py b/pylint/test/input/func_i0022.py index f5b308f..f5b308f 100644 --- a/test/input/func_i0022.py +++ b/pylint/test/input/func_i0022.py diff --git a/pylint/test/input/func_import_syntax_error.py b/pylint/test/input/func_import_syntax_error.py new file mode 100644 index 0000000..42af4d0 --- /dev/null +++ b/pylint/test/input/func_import_syntax_error.py @@ -0,0 +1,2 @@ +# pylint: disable=no-absolute-import +import syntax_error diff --git a/pylint/test/input/func_indent.py b/pylint/test/input/func_indent.py new file mode 100644 index 0000000..c593b49 --- /dev/null +++ b/pylint/test/input/func_indent.py @@ -0,0 +1,24 @@ +# pylint: disable=print-statement +"""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""" + 1 # and this. + +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/pylint/test/input/func_init_vars.py b/pylint/test/input/func_init_vars.py new file mode 100644 index 0000000..154fe3e --- /dev/null +++ b/pylint/test/input/func_init_vars.py @@ -0,0 +1,45 @@ +"""Checks that class variables are seen as inherited ! +""" +# pylint: disable=print-statement, too-few-public-methods +__revision__ = '' + + +class MyClass(object): + """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/pylint/test/input/func_interfaces.py b/pylint/test/input/func_interfaces.py new file mode 100644 index 0000000..21f42fb --- /dev/null +++ b/pylint/test/input/func_interfaces.py @@ -0,0 +1,112 @@ +# pylint:disable=R0201, print-statement +"""docstring""" +__revision__ = '' + +class Interface(object): + """base class for interfaces""" + +class IMachin(Interface): + """docstring""" + def truc(self): + """docstring""" + + def troc(self, argument): + """docstring""" + +class Correct1(object): + """docstring""" + __implements__ = IMachin + + def __init__(self): + pass + + def truc(self): + """docstring""" + pass + + def troc(self, argument): + """docstring""" + pass + +class Correct2(object): + """docstring""" + __implements__ = (IMachin,) + + def __init__(self): + pass + + def truc(self): + """docstring""" + pass + + def troc(self, argument): + """docstring""" + print argument + +class MissingMethod(object): + """docstring""" + __implements__ = IMachin, + + def __init__(self): + pass + + def troc(self, argument): + """docstring""" + print argument + + def other(self): + """docstring""" + +class BadArgument(object): + """docstring""" + __implements__ = (IMachin,) + + def __init__(self): + pass + + def truc(self): + """docstring""" + pass + + def troc(self): + """docstring""" + pass + +class InterfaceCantBeFound(object): + """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 InterfaceCanNowBeFound(object): + """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""" + + +class EmptyImplements(object): + """no pb""" + __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/pylint/test/input/func_invalid_sequence_index.py b/pylint/test/input/func_invalid_sequence_index.py new file mode 100644 index 0000000..b60e0b5 --- /dev/null +++ b/pylint/test/input/func_invalid_sequence_index.py @@ -0,0 +1,210 @@ +"""Errors for invalid sequence indices""" +# pylint: disable=too-few-public-methods, no-self-use + +__revision__ = 0 + +TESTLIST = [1, 2, 3] +TESTTUPLE = (1, 2, 3) +TESTSTR = '123' + +# getitem tests with bad indices +def function1(): + """list index is a function""" + return TESTLIST[id] + +def function2(): + """list index is None""" + return TESTLIST[None] + +def function3(): + """list index is a float expression""" + return TESTLIST[float(0)] + +def function4(): + """list index is a str constant""" + return TESTLIST['0'] + +def function5(): + """list index does not implement __index__""" + class NonIndexType(object): + """Class without __index__ method""" + pass + + return TESTLIST[NonIndexType()] + +def function6(): + """Tuple index is None""" + return TESTTUPLE[None] + +def function7(): + """String index is None""" + return TESTSTR[None] + +def function8(): + """Index of subclass of tuple is None""" + class TupleTest(tuple): + """Subclass of tuple""" + pass + return TupleTest()[None] + +# getitem tests with good indices +def function9(): + """list index is an int constant""" + return TESTLIST[0] # no error + +def function10(): + """list index is a integer expression""" + return TESTLIST[int(0.0)] # no error + +def function11(): + """list index is a slice""" + return TESTLIST[slice(1, 2, 3)] # no error + +def function12(): + """list index implements __index__""" + class IndexType(object): + """Class with __index__ method""" + def __index__(self): + """Allow objects of this class to be used as slice indices""" + return 0 + + return TESTLIST[IndexType()] # no error + +def function13(): + """list index implements __index__ in a superclass""" + class IndexType(object): + """Class with __index__ method""" + def __index__(self): + """Allow objects of this class to be used as slice indices""" + return 0 + + class IndexSubType(IndexType): + """Class with __index__ in parent""" + pass + + return TESTLIST[IndexSubType()] # no error + +def function14(): + """Tuple index is an int constant""" + return TESTTUPLE[0] + +def function15(): + """String index is an int constant""" + return TESTSTR[0] + +def function16(): + """Index of subclass of tuple is an int constant""" + class TupleTest(tuple): + """Subclass of tuple""" + pass + return TupleTest()[0] # no error + +def function17(): + """Index of subclass of tuple with custom __getitem__ is None""" + class TupleTest(tuple): + """Subclass of tuple with custom __getitem__""" + def __getitem__(self, index): + """Allow non-integer indices""" + return 0 + return TupleTest()[None] # no error + +def function18(): + """Index of subclass of tuple with __getitem__ in superclass is None""" + class TupleTest(tuple): + """Subclass of tuple with custom __getitem__""" + def __getitem__(self, index): + """Allow non-integer indices""" + return 0 + + class SubTupleTest(TupleTest): + """Subclass of a subclass of tuple""" + pass + + return SubTupleTest()[None] # no error + +# Test with set and delete statements +def function19(): + """Set with None and integer indices""" + TESTLIST[None] = 0 + TESTLIST[0] = 0 # no error + +def function20(): + """Delete with None and integer indicies""" + del TESTLIST[None] + del TESTLIST[0] # no error + +def function21(): + """Set and delete on a subclass of list""" + class ListTest(list): + """Inherit all list get/set/del handlers""" + pass + test = ListTest() + + # Set and delete with invalid indices + test[None] = 0 + del test[None] + + # Set and delete with valid indices + test[0] = 0 # no error + del test[0] # no error + +def function22(): + """Get, set, and delete on a subclass of list that overrides __setitem__""" + class ListTest(list): + """Override setitem but not get or del""" + def __setitem__(self, key, value): + pass + test = ListTest() + + test[None][0] = 0 # failure on the getitem with None + del test[None] + + test[0][0] = 0 # getitem with int and setitem with int, no error + test[None] = 0 # setitem overridden, no error + test[0] = 0 # setitem with int, no error + del test[0] # delitem with int, no error + +def function23(): + """Get, set, and delete on a subclass of list that overrides __delitem__""" + class ListTest(list): + """Override delitem but not get or set""" + def __delitem__(self, key): + pass + test = ListTest() + + test[None][0] = 0 # failure on the getitem with None + test[None] = 0 # setitem with invalid index + + test[0][0] = 0 # getitem with int and setitem with int, no error + test[0] = 0 # setitem with int, no error + del test[None] # delitem overriden, no error + del test[0] # delitem with int, no error + +def function24(): + """Get, set, and delete on a subclass of list that overrides __getitem__""" + class ListTest(list): + """Override gelitem but not del or set""" + def __getitem__(self, key): + pass + test = ListTest() + + test[None] = 0 # setitem with invalid index + del test[None] # delitem with invalid index + + test[None][0] = 0 # getitem overriden, no error + test[0][0] = 0 # getitem with int and setitem with int, no error + test[0] = 0 # setitem with int, no error + del test[0] # delitem with int, no error + +# Teest ExtSlice usage +def function25(): + """Extended slice used with a list""" + return TESTLIST[..., 0] + +def function26(): + """Extended slice used with an object that implements __getitem__""" + class ExtSliceTest(object): + """Permit extslice syntax by implementing __getitem__""" + def __getitem__(self, index): + return 0 + return ExtSliceTest[..., 0] # no error diff --git a/test/input/func_keyword_repeat.py b/pylint/test/input/func_keyword_repeat.py index eeb733a..eeb733a 100644 --- a/test/input/func_keyword_repeat.py +++ b/pylint/test/input/func_keyword_repeat.py diff --git a/test/input/func_kwoa_py30.py b/pylint/test/input/func_kwoa_py30.py index 4be5302..4be5302 100644 --- a/test/input/func_kwoa_py30.py +++ b/pylint/test/input/func_kwoa_py30.py diff --git a/pylint/test/input/func_logging_not_lazy_with_logger.py b/pylint/test/input/func_logging_not_lazy_with_logger.py new file mode 100644 index 0000000..afbe285 --- /dev/null +++ b/pylint/test/input/func_logging_not_lazy_with_logger.py @@ -0,0 +1,14 @@ +"""Logging warnings using a logger class.""" +from __future__ import absolute_import +__revision__ = '' + +import logging + + +LOG = logging.getLogger("domain") +LOG.debug("%s" % "junk") +LOG.log(logging.DEBUG, "%s" % "junk") +LOG2 = LOG.debug +LOG2("%s" % "junk") + +logging.getLogger("domain").debug("%s" % "junk") diff --git a/pylint/test/input/func_loopvar_in_dict_comp_py27.py b/pylint/test/input/func_loopvar_in_dict_comp_py27.py new file mode 100644 index 0000000..312eee7 --- /dev/null +++ b/pylint/test/input/func_loopvar_in_dict_comp_py27.py @@ -0,0 +1,8 @@ +"""Tests for loopvar-in-closure.""" + +__revision__ = 0 + + +def bad_case(): + """Loop variable from dict comprehension.""" + return {x: lambda: x for x in range(10)} diff --git a/pylint/test/input/func_method_could_be_function.py b/pylint/test/input/func_method_could_be_function.py new file mode 100644 index 0000000..7544793 --- /dev/null +++ b/pylint/test/input/func_method_could_be_function.py @@ -0,0 +1,60 @@ +# pylint: disable=R0903,R0922,W0232,print-statement +"""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(object): + """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(object): + """same as before without abstract""" + attr = 1 + def method(self): + """regular""" + print self.attr + +class Sub1(Super): + """override method with need for self""" + def method(self): + """no i can not be a function""" + print 42 + + def __len__(self): + """no i can not be a function""" + print 42 + + def __cmp__(self, other): + """no i can not be a function""" + print 42 diff --git a/pylint/test/input/func_module___dict__.py b/pylint/test/input/func_module___dict__.py new file mode 100644 index 0000000..5451422 --- /dev/null +++ b/pylint/test/input/func_module___dict__.py @@ -0,0 +1,9 @@ +"""http://www.logilab.org/ticket/6949.""" +from __future__ import print_function +__revision__ = None + +print(__dict__ is not None) + +__dict__ = {} + +print(__dict__ is not None) diff --git a/test/input/func_more_e0604.py b/pylint/test/input/func_more_e0604.py index 6c39e1c..6c39e1c 100644 --- a/test/input/func_more_e0604.py +++ b/pylint/test/input/func_more_e0604.py diff --git a/test/input/func_nameerror_on_string_substitution.py b/pylint/test/input/func_nameerror_on_string_substitution.py index be7b5c8..be7b5c8 100644 --- a/test/input/func_nameerror_on_string_substitution.py +++ b/pylint/test/input/func_nameerror_on_string_substitution.py diff --git a/pylint/test/input/func_no_dummy_redefined.py b/pylint/test/input/func_no_dummy_redefined.py new file mode 100644 index 0000000..7d40f81 --- /dev/null +++ b/pylint/test/input/func_no_dummy_redefined.py @@ -0,0 +1,14 @@ +"""Make sure warnings about redefinitions do not trigger for dummy variables.""" +__revision__ = 0 + + +_, INTERESTING = 'a=b'.split('=') + +value = 10 + + +def clobbering(): + """Clobbers a dummy name from the outer scope.""" + value = 9 + for _ in range(7): + print value # pylint: disable=print-statement diff --git a/test/input/func_noerror___init___return_from_inner_function.py b/pylint/test/input/func_noerror___init___return_from_inner_function.py index 397b0fc..397b0fc 100644 --- a/test/input/func_noerror___init___return_from_inner_function.py +++ b/pylint/test/input/func_noerror___init___return_from_inner_function.py diff --git a/pylint/test/input/func_noerror_access_attr_before_def_false_positive.py b/pylint/test/input/func_noerror_access_attr_before_def_false_positive.py new file mode 100644 index 0000000..8c28599 --- /dev/null +++ b/pylint/test/input/func_noerror_access_attr_before_def_false_positive.py @@ -0,0 +1,98 @@ +#pylint: disable=C0103,R0904,R0903,W0201,old-style-class,print-statement,no-absolute-import +""" +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 + for match in matches: + 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(object): + """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/pylint/test/input/func_noerror_base_init_vars.py b/pylint/test/input/func_noerror_base_init_vars.py new file mode 100644 index 0000000..f552eef --- /dev/null +++ b/pylint/test/input/func_noerror_base_init_vars.py @@ -0,0 +1,35 @@ +# pylint:disable=R0201, print-statement, too-few-public-methods +"""Checks that class variables are seen as inherited ! +""" +__revision__ = '' + +class BaseClass(object): + """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/pylint/test/input/func_noerror_builtin_module_test.py b/pylint/test/input/func_noerror_builtin_module_test.py new file mode 100644 index 0000000..f73694a --- /dev/null +++ b/pylint/test/input/func_noerror_builtin_module_test.py @@ -0,0 +1,9 @@ +"""test import from a builtin module""" +from __future__ import absolute_import +__revision__ = None + +from math import log10 + +def log10_2(): + """bla bla bla""" + return log10(2) diff --git a/pylint/test/input/func_noerror_class_attributes.py b/pylint/test/input/func_noerror_class_attributes.py new file mode 100644 index 0000000..75fb435 --- /dev/null +++ b/pylint/test/input/func_noerror_class_attributes.py @@ -0,0 +1,17 @@ +"""Test that valid class attribute doesn't trigger errors""" +__revision__ = 'sponge bob' + +class Clazz(object): + "dummy class" + + def __init__(self): + self.topic = 5 + self._data = 45 + + def change_type(self, new_class): + """Change type""" + self.__class__ = new_class + + def do_nothing(self): + "I do nothing useful" + return self.topic + 56 diff --git a/pylint/test/input/func_noerror_class_decorators_py26.py b/pylint/test/input/func_noerror_class_decorators_py26.py new file mode 100644 index 0000000..541bb45 --- /dev/null +++ b/pylint/test/input/func_noerror_class_decorators_py26.py @@ -0,0 +1,8 @@ +"""test class decorator are recognized""" +# pylint: disable=R0903,W0232,C,no-absolute-import + +from logilab.common.deprecation import deprecated, class_moved + +@deprecated('This is a bad class name; use %s to rename' % class_moved) +class Foo: + '''foo goo''' diff --git a/test/input/func_noerror_classes_meth_could_be_a_function.py b/pylint/test/input/func_noerror_classes_meth_could_be_a_function.py index bdf0da4..bdf0da4 100644 --- a/test/input/func_noerror_classes_meth_could_be_a_function.py +++ b/pylint/test/input/func_noerror_classes_meth_could_be_a_function.py diff --git a/test/input/func_noerror_classes_meth_signature.py b/pylint/test/input/func_noerror_classes_meth_signature.py index 2aa5b18..2aa5b18 100644 --- a/test/input/func_noerror_classes_meth_signature.py +++ b/pylint/test/input/func_noerror_classes_meth_signature.py diff --git a/pylint/test/input/func_noerror_classes_protected_member_access.py b/pylint/test/input/func_noerror_classes_protected_member_access.py new file mode 100644 index 0000000..eeff97d --- /dev/null +++ b/pylint/test/input/func_noerror_classes_protected_member_access.py @@ -0,0 +1,25 @@ +""" +#3123: W0212 false positive on static method +""" +__revision__ = 1 + +class A3123(object): + """oypuee""" + _protected = 1 + def __init__(self): + pass + + + def cmeth(cls, val): + """set protected member""" + cls._protected = +val + + cmeth = classmethod(cmeth) + + def smeth(val): + """set protected member""" + A3123._protected += val + + smeth = staticmethod(smeth) + + prop = property(lambda self: self._protected) diff --git a/pylint/test/input/func_noerror_crash_122793.py b/pylint/test/input/func_noerror_crash_122793.py new file mode 100644 index 0000000..2bb3d2e --- /dev/null +++ b/pylint/test/input/func_noerror_crash_122793.py @@ -0,0 +1,9 @@ +# pylint: disable=C0121 +"""https://www.logilab.org/ticket/122793""" + +def gen(): + """dumb generator""" + yield + +GEN = gen() +next(GEN) diff --git a/test/input/func_noerror_crash_127416.py b/pylint/test/input/func_noerror_crash_127416.py index 3a04363..3a04363 100644 --- a/test/input/func_noerror_crash_127416.py +++ b/pylint/test/input/func_noerror_crash_127416.py diff --git a/pylint/test/input/func_noerror_decorator_scope.py b/pylint/test/input/func_noerror_decorator_scope.py new file mode 100644 index 0000000..9c84d61 --- /dev/null +++ b/pylint/test/input/func_noerror_decorator_scope.py @@ -0,0 +1,19 @@ +# -*- pylint: disable=W0232,R0903,print-statement +"""Test that decorators sees the class namespace - just like +function default values does but function body doesn't. + +https://www.logilab.net/elo/ticket/3711 - bug finding decorator arguments +https://www.logilab.net/elo/ticket/5626 - name resolution bug inside classes +""" + +__revision__ = 0 + +class Test(object): + """test class""" + ident = lambda x: x + + @ident(ident) + def method(self, val=ident(7), func=ident): + """hop""" + print self + return func(val) diff --git a/test/input/func_noerror_e1101_13784.py b/pylint/test/input/func_noerror_e1101_13784.py index b247b44..b247b44 100644 --- a/test/input/func_noerror_e1101_13784.py +++ b/pylint/test/input/func_noerror_e1101_13784.py diff --git a/test/input/func_noerror_e1101_9588_base_attr_aug_assign.py b/pylint/test/input/func_noerror_e1101_9588_base_attr_aug_assign.py index 03106a5..03106a5 100644 --- a/test/input/func_noerror_e1101_9588_base_attr_aug_assign.py +++ b/pylint/test/input/func_noerror_e1101_9588_base_attr_aug_assign.py diff --git a/pylint/test/input/func_noerror_e1101_but_getattr.py b/pylint/test/input/func_noerror_e1101_but_getattr.py new file mode 100644 index 0000000..529b413 --- /dev/null +++ b/pylint/test/input/func_noerror_e1101_but_getattr.py @@ -0,0 +1,23 @@ +"""don't want E1101 if __getattr__ is defined""" +from __future__ import print_function +__revision__ = None + +class MyString(object): + """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/pylint/test/input/func_noerror_encoding.py index 2e945a5..2e945a5 100644 --- a/test/input/func_noerror_encoding.py +++ b/pylint/test/input/func_noerror_encoding.py diff --git a/pylint/test/input/func_noerror_except_pass.py b/pylint/test/input/func_noerror_except_pass.py new file mode 100644 index 0000000..5bb8011 --- /dev/null +++ b/pylint/test/input/func_noerror_except_pass.py @@ -0,0 +1,12 @@ +""" +#3205: W0704 (except doesn't do anything) false positive if some statements +follow a "pass" +""" +from __future__ import print_function +__revision__ = None + +try: + A = 2 +except ValueError: + pass # pylint: disable=W0107 + print(A) diff --git a/test/input/func_noerror_exception.py b/pylint/test/input/func_noerror_exception.py index 1c3d8b5..1c3d8b5 100644 --- a/test/input/func_noerror_exception.py +++ b/pylint/test/input/func_noerror_exception.py diff --git a/test/input/func_noerror_external_classmethod_crash.py b/pylint/test/input/func_noerror_external_classmethod_crash.py index 318f01c..318f01c 100644 --- a/test/input/func_noerror_external_classmethod_crash.py +++ b/pylint/test/input/func_noerror_external_classmethod_crash.py diff --git a/pylint/test/input/func_noerror_factory_method.py b/pylint/test/input/func_noerror_factory_method.py new file mode 100644 index 0000000..a7cff43 --- /dev/null +++ b/pylint/test/input/func_noerror_factory_method.py @@ -0,0 +1,23 @@ +# pylint: disable=R0903, print-statement +"""use new astroid context sensitive inference""" +__revision__ = 1 + +class Super(object): + """super class""" + def __init__(self): + self.bla = None + + def instance(cls): + """factory method""" + return cls() + instance = classmethod(instance) + +class Sub(Super): + """dub class""" + def method(self): + """specific method""" + print 'method called', self + +# should see the Sub.instance() is returning a Sub instance, not a Super +# instance +Sub.instance().method() diff --git a/pylint/test/input/func_noerror_function_as_method.py b/pylint/test/input/func_noerror_function_as_method.py new file mode 100644 index 0000000..e59fb5c --- /dev/null +++ b/pylint/test/input/func_noerror_function_as_method.py @@ -0,0 +1,18 @@ +# pylint: disable=R0903, print-statement +'''Test that a function is considered a method when looked up through a class. +''' +__revision__ = 1 + +class Clazz(object): + 'test class' + + def __init__(self, value): + self.value = value + +def func(arg1, arg2): + 'function that will be used as a method' + return arg1.value + arg2 + +Clazz.method = func + +print Clazz(1).method(2) diff --git a/test/input/func_noerror_genexp_in_class_scope.py b/pylint/test/input/func_noerror_genexp_in_class_scope.py index 5631026..5631026 100644 --- a/test/input/func_noerror_genexp_in_class_scope.py +++ b/pylint/test/input/func_noerror_genexp_in_class_scope.py diff --git a/pylint/test/input/func_noerror_indirect_interface.py b/pylint/test/input/func_noerror_indirect_interface.py new file mode 100644 index 0000000..cbd1ae4 --- /dev/null +++ b/pylint/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""" +# pylint: disable=old-style-class, too-few-public-methods, no-absolute-import +__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/pylint/test/input/func_noerror_inner_classes.py index 84fb43d..84fb43d 100644 --- a/test/input/func_noerror_inner_classes.py +++ b/pylint/test/input/func_noerror_inner_classes.py diff --git a/pylint/test/input/func_noerror_lambda_use_before_assign.py b/pylint/test/input/func_noerror_lambda_use_before_assign.py new file mode 100644 index 0000000..af2775c --- /dev/null +++ b/pylint/test/input/func_noerror_lambda_use_before_assign.py @@ -0,0 +1,8 @@ +"""https://www.logilab.net/elo/ticket/18862""" +from __future__ import print_function +__revision__ = 1 +def function(): + """hop""" + ggg = lambda: xxx + xxx = 1 + print(ggg()) diff --git a/test/input/func_noerror_long_utf8_line.py b/pylint/test/input/func_noerror_long_utf8_line.py index 6fba949..6fba949 100644 --- a/test/input/func_noerror_long_utf8_line.py +++ b/pylint/test/input/func_noerror_long_utf8_line.py diff --git a/pylint/test/input/func_noerror_mcs_attr_access.py b/pylint/test/input/func_noerror_mcs_attr_access.py new file mode 100644 index 0000000..3310c49 --- /dev/null +++ b/pylint/test/input/func_noerror_mcs_attr_access.py @@ -0,0 +1,20 @@ +# pylint: disable=R0903, print-statement, metaclass-assignment +"""test attribute access on metaclass""" + + +__revision__ = 'yo' + +class Meta(type): + """the meta class""" + def __init__(cls, name, bases, dictionary): + super(Meta, cls).__init__(name, bases, dictionary) + print cls, cls._meta_args + delattr(cls, '_meta_args') + +class Test(object): + """metaclassed class""" + __metaclass__ = Meta + _meta_args = ('foo', 'bar') + + def __init__(self): + print '__init__', self diff --git a/pylint/test/input/func_noerror_nested_classes.py b/pylint/test/input/func_noerror_nested_classes.py new file mode 100644 index 0000000..56e57bb --- /dev/null +++ b/pylint/test/input/func_noerror_nested_classes.py @@ -0,0 +1,18 @@ +# pylint: disable=R0903, print-statement +"""crash test""" + +__revision__ = 1 + +class Temelekefe(object): + """gloubliboulga""" + + def __init__(self): + """nested class with function raise error""" + class Toto(object): + """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/pylint/test/input/func_noerror_new_style_class_py_30.py b/pylint/test/input/func_noerror_new_style_class_py_30.py new file mode 100644 index 0000000..a33ae19 --- /dev/null +++ b/pylint/test/input/func_noerror_new_style_class_py_30.py @@ -0,0 +1,45 @@ +"""check builtin data descriptors such as mode and name attributes +on a file are correctly handled + +bug notified by Pierre Rouleau on 2005-04-24 +""" +from __future__ import print_function +__revision__ = None + +class File(file): # pylint: disable=file-builtin + """ 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/pylint/test/input/func_noerror_no_warning_docstring.py b/pylint/test/input/func_noerror_no_warning_docstring.py new file mode 100644 index 0000000..f8ee9bc --- /dev/null +++ b/pylint/test/input/func_noerror_no_warning_docstring.py @@ -0,0 +1,42 @@ +''' Test for inheritence ''' +from __future__ import print_function +__revision__ = 1 +# pylint: disable=too-few-public-methods +class AAAA(object): + ''' class AAAA ''' + + def __init__(self): + pass + + def method1(self): + ''' method 1 ''' + print(self) + + def method2(self): + ''' method 2 ''' + print(self) + +class BBBB(AAAA): + ''' class BBBB ''' + + def __init__(self): + AAAA.__init__(self) + + # should ignore docstring calling from class AAAA + def method1(self): + AAAA.method1(self) + +class CCCC(BBBB): + ''' class CCCC ''' + + def __init__(self): + BBBB.__init__(self) + + # should ignore docstring since CCCC is inherited from BBBB which is + # inherited from AAAA containing method2 + if __revision__: + def method2(self): + AAAA.method2(self) + else: + def method2(self): + AAAA.method1(self) diff --git a/test/input/func_noerror_nonregr.py b/pylint/test/input/func_noerror_nonregr.py index c4c8c38..c4c8c38 100644 --- a/test/input/func_noerror_nonregr.py +++ b/pylint/test/input/func_noerror_nonregr.py diff --git a/test/input/func_noerror_object_as_class_attribute.py b/pylint/test/input/func_noerror_object_as_class_attribute.py index c69b2b9..c69b2b9 100644 --- a/test/input/func_noerror_object_as_class_attribute.py +++ b/pylint/test/input/func_noerror_object_as_class_attribute.py diff --git a/pylint/test/input/func_noerror_overloaded_operator.py b/pylint/test/input/func_noerror_overloaded_operator.py new file mode 100644 index 0000000..4e29946 --- /dev/null +++ b/pylint/test/input/func_noerror_overloaded_operator.py @@ -0,0 +1,21 @@ +# pylint: disable=C0111,R0903,print-statement +"""#3291""" +__revision__ = 1 + +class Myarray(object): + def __init__(self, array): + self.array = array + + def __mul__(self, val): + return Myarray(val) + + def astype(self): + return "ASTYPE", self + +def randint(maximum): + if maximum is not None: + return Myarray([1, 2, 3]) * 2 + else: + return int(5) + +print randint(1).astype() # we don't wan't an error for astype access diff --git a/test/input/func_noerror_overriden_method_varargs.py b/pylint/test/input/func_noerror_overriden_method_varargs.py index afe3086..afe3086 100644 --- a/test/input/func_noerror_overriden_method_varargs.py +++ b/pylint/test/input/func_noerror_overriden_method_varargs.py diff --git a/test/input/func_noerror_property_affectation_py26.py b/pylint/test/input/func_noerror_property_affectation_py26.py index d91f455..d91f455 100644 --- a/test/input/func_noerror_property_affectation_py26.py +++ b/pylint/test/input/func_noerror_property_affectation_py26.py diff --git a/test/input/func_noerror_raise_return_self.py b/pylint/test/input/func_noerror_raise_return_self.py index 2834c3c..2834c3c 100644 --- a/test/input/func_noerror_raise_return_self.py +++ b/pylint/test/input/func_noerror_raise_return_self.py diff --git a/pylint/test/input/func_noerror_socket_member.py b/pylint/test/input/func_noerror_socket_member.py new file mode 100644 index 0000000..1cdafe6 --- /dev/null +++ b/pylint/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 astroid 0.15.1 + +False E1101 positive, line 23: + + Instance of '_socketobject' has no 'connect' member + +""" +from __future__ import absolute_import +__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/pylint/test/input/func_noerror_static_method.py b/pylint/test/input/func_noerror_static_method.py new file mode 100644 index 0000000..ef21cb9 --- /dev/null +++ b/pylint/test/input/func_noerror_static_method.py @@ -0,0 +1,27 @@ +"""Checks if static / class methods works fine in Pylint +""" +from __future__ import print_function +__revision__ = '' + +class MyClass(object): + """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/pylint/test/input/func_noerror_staticmethod_as_decorator_py24.py b/pylint/test/input/func_noerror_staticmethod_as_decorator_py24.py new file mode 100644 index 0000000..7884cbd --- /dev/null +++ b/pylint/test/input/func_noerror_staticmethod_as_decorator_py24.py @@ -0,0 +1,33 @@ +# pylint: disable=R0903, print-statement +"""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/pylint/test/input/func_noerror_super_protected.py b/pylint/test/input/func_noerror_super_protected.py new file mode 100644 index 0000000..b0961be --- /dev/null +++ b/pylint/test/input/func_noerror_super_protected.py @@ -0,0 +1,22 @@ +"""Accessing a protected method through super() is ok.""" + +# pylint: disable=missing-docstring,too-few-public-methods, print-statement + +__revision__ = None + +class Alpha(object): + + _secret = 2 + + def test(self): + print "test %s" % self + + +class Beta(Alpha): + + def test(self): + print super(Beta, self)._secret + super(Beta, self).test() + + +Beta().test() diff --git a/pylint/test/input/func_noerror_unused_variable_py30.py b/pylint/test/input/func_noerror_unused_variable_py30.py new file mode 100644 index 0000000..ffcc978 --- /dev/null +++ b/pylint/test/input/func_noerror_unused_variable_py30.py @@ -0,0 +1,14 @@ +""" Test nonlocal uses and unused-variable. """ + +__revision__ = 1 + +def test_nonlocal(): + """ Test that assigning to a nonlocal does not trigger + an 'unused-variable' warnings. + """ + attr = True + def set_value(val): + """ Set the value in a nonlocal. """ + nonlocal attr + attr = val + return set_value diff --git a/pylint/test/input/func_noerror_used_before_assignment.py b/pylint/test/input/func_noerror_used_before_assignment.py new file mode 100644 index 0000000..844ee05 --- /dev/null +++ b/pylint/test/input/func_noerror_used_before_assignment.py @@ -0,0 +1,5 @@ +# pylint: disable = line-too-long, multiple-statements, missing-module-attribute, print-statement +"""https://bitbucket.org/logilab/pylint/issue/111/false-positive-used-before-assignment-with""" + +try: raise IOError(1, "a") +except IOError, err: print err diff --git a/test/input/func_noerror_w0232.py b/pylint/test/input/func_noerror_w0232.py index df93855..df93855 100644 --- a/test/input/func_noerror_w0232.py +++ b/pylint/test/input/func_noerror_w0232.py diff --git a/pylint/test/input/func_noerror_yield_assign_py25.py b/pylint/test/input/func_noerror_yield_assign_py25.py new file mode 100644 index 0000000..011634b --- /dev/null +++ b/pylint/test/input/func_noerror_yield_assign_py25.py @@ -0,0 +1,21 @@ +"""http://www.logilab.org/ticket/8771""" +# pylint: disable=print-statement +__revision__ = 2 + +def generator(): + """yield as assignment""" + yield 45 + xxxx = yield 123 + print xxxx + +def generator_fp1(seq): + """W0631 false positive""" + for val in seq: + pass + for val in seq: + yield val + +def generator_fp2(): + """E0601 false positive""" + xxxx = 12 + yield xxxx diff --git a/test/input/func_noerror_yield_return_mix.py b/pylint/test/input/func_noerror_yield_return_mix.py index bd78633..bd78633 100644 --- a/test/input/func_noerror_yield_return_mix.py +++ b/pylint/test/input/func_noerror_yield_return_mix.py diff --git a/test/input/func_non_iterator_returned_py30.py b/pylint/test/input/func_non_iterator_returned_py30.py index 6915768..6915768 100644 --- a/test/input/func_non_iterator_returned_py30.py +++ b/pylint/test/input/func_non_iterator_returned_py30.py diff --git a/test/input/func_non_iterator_returned_py_30.py b/pylint/test/input/func_non_iterator_returned_py_30.py index ff75941..ff75941 100644 --- a/test/input/func_non_iterator_returned_py_30.py +++ b/pylint/test/input/func_non_iterator_returned_py_30.py diff --git a/test/input/func_nonregr___file___global.py b/pylint/test/input/func_nonregr___file___global.py index 910033c..910033c 100644 --- a/test/input/func_nonregr___file___global.py +++ b/pylint/test/input/func_nonregr___file___global.py diff --git a/test/input/func_operators.py b/pylint/test/input/func_operators.py index 48606bf..48606bf 100644 --- a/test/input/func_operators.py +++ b/pylint/test/input/func_operators.py diff --git a/test/input/func_r0901.py b/pylint/test/input/func_r0901.py index ac69fb6..ac69fb6 100644 --- a/test/input/func_r0901.py +++ b/pylint/test/input/func_r0901.py diff --git a/test/input/func_r0902.py b/pylint/test/input/func_r0902.py index d0a7483..d0a7483 100644 --- a/test/input/func_r0902.py +++ b/pylint/test/input/func_r0902.py diff --git a/pylint/test/input/func_r0903.py b/pylint/test/input/func_r0903.py new file mode 100644 index 0000000..49008f9 --- /dev/null +++ b/pylint/test/input/func_r0903.py @@ -0,0 +1,30 @@ +"""test min methods""" +from __future__ import print_function +__revision__ = None + +class Aaaa(object): + """yo""" + def __init__(self): + pass + def meth1(self): + """hehehe""" + print(self) + def _dontcount(self): + """not public""" + print(self) + + +# Don't emit for these cases. +class Klass(object): + """docstring""" + + def meth1(self): + """first""" + + def meth2(self): + """second""" + + +class EnoughPublicMethods(Klass): + """We shouldn't emit too-few-public-methods for this.""" + diff --git a/pylint/test/input/func_r0904.py b/pylint/test/input/func_r0904.py new file mode 100644 index 0000000..85daf61 --- /dev/null +++ b/pylint/test/input/func_r0904.py @@ -0,0 +1,80 @@ +# pylint: disable=R0201 +"""test max methods""" +__revision__ = None +class Aaaa(object): + """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""" + +class BBB(Aaaa): + """Don't emit for methods defined in the parent.""" + def meth1(self): + """trop""" + def meth2(self): + """tzop""" diff --git a/test/input/func_r0921.py b/pylint/test/input/func_r0921.py index 1baf0a8..1baf0a8 100644 --- a/test/input/func_r0921.py +++ b/pylint/test/input/func_r0921.py diff --git a/pylint/test/input/func_r0922.py b/pylint/test/input/func_r0922.py new file mode 100644 index 0000000..705ef3a --- /dev/null +++ b/pylint/test/input/func_r0922.py @@ -0,0 +1,21 @@ +"""test max methods""" +__revision__ = None +# pylint: disable=too-few-public-methods +class Aaaa(object): + """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/pylint/test/input/func_r0923.py b/pylint/test/input/func_r0923.py new file mode 100644 index 0000000..8ab44a1 --- /dev/null +++ b/pylint/test/input/func_r0923.py @@ -0,0 +1,31 @@ +"""test max methods""" +from __future__ import absolute_import +__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(object): + """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/pylint/test/input/func_reqattrs.py index fb1e2b6..fb1e2b6 100644 --- a/test/input/func_reqattrs.py +++ b/pylint/test/input/func_reqattrs.py diff --git a/test/input/func_return_outside_func.py b/pylint/test/input/func_return_outside_func.py index 440798d..440798d 100644 --- a/test/input/func_return_outside_func.py +++ b/pylint/test/input/func_return_outside_func.py diff --git a/test/input/func_return_yield_mix_py_33.py b/pylint/test/input/func_return_yield_mix_py_33.py index 1a3cd5d..1a3cd5d 100644 --- a/test/input/func_return_yield_mix_py_33.py +++ b/pylint/test/input/func_return_yield_mix_py_33.py diff --git a/pylint/test/input/func_set_literal_as_default_py27.py b/pylint/test/input/func_set_literal_as_default_py27.py new file mode 100644 index 0000000..1bd3f7e --- /dev/null +++ b/pylint/test/input/func_set_literal_as_default_py27.py @@ -0,0 +1,7 @@ +"""docstring""" +from __future__ import print_function +__revision__ = '' + +def function1(value={1}): + """set is mutable and dangerous.""" + print(value) diff --git a/test/input/func_syntax_error.py b/pylint/test/input/func_syntax_error.py index 43fa087..43fa087 100644 --- a/test/input/func_syntax_error.py +++ b/pylint/test/input/func_syntax_error.py diff --git a/test/input/func_tokenize_error.py b/pylint/test/input/func_tokenize_error.py index 1b52cb9..1b52cb9 100644 --- a/test/input/func_tokenize_error.py +++ b/pylint/test/input/func_tokenize_error.py diff --git a/test/input/func_too_many_locals_arguments.py b/pylint/test/input/func_too_many_locals_arguments.py index f63a5ee..f63a5ee 100644 --- a/test/input/func_too_many_locals_arguments.py +++ b/pylint/test/input/func_too_many_locals_arguments.py diff --git a/test/input/func_too_many_returns_yields.py b/pylint/test/input/func_too_many_returns_yields.py index c61bd7d..c61bd7d 100644 --- a/test/input/func_too_many_returns_yields.py +++ b/pylint/test/input/func_too_many_returns_yields.py diff --git a/pylint/test/input/func_toolonglines.py b/pylint/test/input/func_toolonglines.py new file mode 100644 index 0000000..272395f --- /dev/null +++ b/pylint/test/input/func_toolonglines.py @@ -0,0 +1,27 @@ +##################################################################################################### +""" that one is too long tooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo loooooong""" + +__revision__ = '' + +# The next line is exactly 80 characters long. +A = '--------------------------------------------------------------------------' + +# The next line is longer than 80 characters, because the file is encoded +# in ASCII. +THIS_IS_A_VERY_LONG_VARIABLE_NAME = 'СущеÑтвительное ЧаÑтица' # With warnings. + +# Do not trigger the line-too-long warning if the only token that makes the +# line longer than 80 characters is a trailing pylint disable. +var = 'This line has a disable pragma and whitespace trailing beyond 80 chars. ' # pylint:disable=invalid-name + +badname = 'This line is already longer than 100 characters even without the pragma. Trust me. Please.' # pylint:disable=invalid-name + +# http://example.com/this/is/a/very/long/url?but=splitting&urls=is&a=pain&so=they&can=be&long + + +def function(): + """This is a docstring. + + That contains a very, very long line that exceeds the 100 characters limit by a good margin. So good? + """ + pass diff --git a/pylint/test/input/func_trailing_whitespace.py b/pylint/test/input/func_trailing_whitespace.py new file mode 100644 index 0000000..946d748 --- /dev/null +++ b/pylint/test/input/func_trailing_whitespace.py @@ -0,0 +1,8 @@ +"""Regression test for trailing-whitespace (C0303).""" +# pylint: disable=mixed-line-endings, print-statement +__revision__ = 0 + +print 'some trailing whitespace' +print 'trailing whitespace does not count towards the line length limit' +print 'windows line ends are ok'
+print 'but trailing whitespace on win is not'
diff --git a/pylint/test/input/func_typecheck_callfunc_assigment.py b/pylint/test/input/func_typecheck_callfunc_assigment.py new file mode 100644 index 0000000..3a668ab --- /dev/null +++ b/pylint/test/input/func_typecheck_callfunc_assigment.py @@ -0,0 +1,63 @@ +# pylint: disable=R0921, print-statement +"""check assignment to function call where the function doesn't return + + 'E1111': ('Assigning to function call which doesn\'t return', + 'Used when an assignment 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 assignment 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_implicit_return_none(): + """Function returning None from bare return statement.""" + return + +A = func_implicit_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/pylint/test/input/func_typecheck_non_callable_call.py b/pylint/test/input/func_typecheck_non_callable_call.py new file mode 100644 index 0000000..832657d --- /dev/null +++ b/pylint/test/input/func_typecheck_non_callable_call.py @@ -0,0 +1,118 @@ +# pylint: disable=R0903,missing-docstring,no-self-use +""" + '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() + +# Test calling properties. Pylint can detect when using only the +# getter, but it doesn't infer properly when having a getter +# and a setter. +class MyProperty(property): + """ test subclasses """ + +class PropertyTest(object): + """ class """ + + def __init__(self): + self.attr = 4 + + @property + def test(self): + """ Get the attribute """ + return self.attr + + @test.setter + def test(self, value): + """ Set the attribute """ + self.attr = value + + @MyProperty + def custom(self): + """ Get the attribute """ + return self.attr + + @custom.setter + def custom(self, value): + """ Set the attribute """ + self.attr = value + +PROP = PropertyTest() +PROP.test(40) +PROP.custom() + +# Safe from not-callable when using properties. + +class SafeProperty(object): + @property + def static(self): + return staticmethod + + @property + def klass(self): + return classmethod + + @property + def get_lambda(self): + return lambda: None + + @property + def other_function(self): + def function(arg): + return arg + return function + + @property + def dict_builtin(self): + return dict + + @property + def range_builtin(self): + return range + + @property + def instance(self): + class Empty(object): + def __call__(self): + return 42 + return Empty() + +PROP1 = SafeProperty() +PROP1.static(2) +PROP1.klass(2) +PROP1.get_lambda() +PROP1.other_function(4) +PROP1.dict_builtin() +PROP1.range_builtin(4) +PROP1.instance() diff --git a/pylint/test/input/func_undefined_metaclass_var_py30.py b/pylint/test/input/func_undefined_metaclass_var_py30.py new file mode 100644 index 0000000..307a431 --- /dev/null +++ b/pylint/test/input/func_undefined_metaclass_var_py30.py @@ -0,0 +1,27 @@ +"""test access to undefined variables in Python 3 metaclass syntax """ +# pylint: disable=no-init, invalid-name, too-few-public-methods +__revision__ = '$Id:' + +import abc +from abc import ABCMeta + +class Bad(metaclass=ABCMet): + """ Notice the typo """ + +class SecondBad(metaclass=ab.ABCMeta): + """ Notice the `ab` module. """ + +class Good(metaclass=int): + """ int is not a proper metaclass, but it is defined. """ + +class SecondGood(metaclass=Good): + """ empty """ + +class ThirdGood(metaclass=ABCMeta): + """ empty """ + +class FourthGood(ThirdGood): + """ This should not trigger anything. """ + +data = abc +testdata = ABCMeta diff --git a/pylint/test/input/func_unreachable.py b/pylint/test/input/func_unreachable.py new file mode 100644 index 0000000..4034e4f --- /dev/null +++ b/pylint/test/input/func_unreachable.py @@ -0,0 +1,21 @@ +"""docstring""" +from __future__ import print_function +__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/pylint/test/input/func_unused_import_py30.py b/pylint/test/input/func_unused_import_py30.py new file mode 100644 index 0000000..d7a38c5 --- /dev/null +++ b/pylint/test/input/func_unused_import_py30.py @@ -0,0 +1,21 @@ +"""check unused import for metaclasses +""" +# pylint: disable=too-few-public-methods +__revision__ = 1 +import abc +import sys +from abc import ABCMeta +from abc import ABCMeta as SomethingElse + +class Meta(metaclass=abc.ABCMeta): + """ Test """ + def __init__(self): + self.data = sys.executable + self.test = abc + +class Meta2(metaclass=ABCMeta): + """ Test """ + +class Meta3(metaclass=SomethingElse): + """ test """ + diff --git a/pylint/test/input/func_unused_overridden_argument.py b/pylint/test/input/func_unused_overridden_argument.py new file mode 100644 index 0000000..85430d4 --- /dev/null +++ b/pylint/test/input/func_unused_overridden_argument.py @@ -0,0 +1,31 @@ +# pylint: disable=R0903, print-statement +"""for Sub.inherited, only the warning for "aay" is desired. +The warnings for "aab" and "aac" are most likely false positives though, +because there could be another subclass that overrides the same method and does +use the arguments (eg Sub2) +""" + +__revision__ = 'thx to Maarten ter Huurne' + +class Base(object): + "parent" + def inherited(self, aaa, aab, aac): + "abstract method" + raise NotImplementedError + +class Sub(Base): + "child 1" + def inherited(self, aaa, aab, aac): + "overridden method, though don't use every argument" + return aaa + + def newmethod(self, aax, aay): + "another method, warning for aay desired" + print self, aax + +class Sub2(Base): + "child 1" + + def inherited(self, aaa, aab, aac): + "overridden method, use every argument" + return aaa + aab + aac diff --git a/pylint/test/input/func_use_for_or_listcomp_var.py b/pylint/test/input/func_use_for_or_listcomp_var.py new file mode 100644 index 0000000..5690a2c --- /dev/null +++ b/pylint/test/input/func_use_for_or_listcomp_var.py @@ -0,0 +1,27 @@ +"""test a warning is triggered when using for a lists comprehension variable""" +# pylint: disable=print-statement +__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() + + +for x in []: + pass +for x in range(3): + print (lambda: x)() # OK diff --git a/pylint/test/input/func_used_before_assignment_py30.py b/pylint/test/input/func_used_before_assignment_py30.py new file mode 100644 index 0000000..ae979a1 --- /dev/null +++ b/pylint/test/input/func_used_before_assignment_py30.py @@ -0,0 +1,48 @@ +"""Check for nonlocal and used-before-assignment"""
+# pylint: disable=missing-docstring, unused-variable, no-init, too-few-public-methods
+
+__revision__ = 0
+
+def test_ok():
+ """ uses nonlocal """
+ cnt = 1
+ def wrap():
+ nonlocal cnt
+ cnt = cnt + 1
+ wrap()
+
+def test_fail():
+ """ doesn't use nonlocal """
+ cnt = 1
+ def wrap():
+ cnt = cnt + 1
+ wrap()
+
+def test_fail2():
+ """ use nonlocal, but for other variable """
+ cnt = 1
+ count = 1
+ def wrap():
+ nonlocal count
+ cnt = cnt + 1
+ wrap()
+
+def test_fail3(arg: test_fail4):
+ """ Depends on `test_fail4`, in argument annotation. """
+ return arg
+
+def test_fail4(*args: test_fail5, **kwargs: undefined):
+ """ Depends on `test_fail5` and `undefined` in
+ variable and named arguments annotations.
+ """
+ return args, kwargs
+
+def test_fail5()->undefined1:
+ """ Depends on `undefined1` in function return annotation. """
+
+def undefined():
+ """ no op """
+
+def undefined1():
+ """ no op """
+
diff --git a/pylint/test/input/func_variables_unused_name_from_wilcard_import.py b/pylint/test/input/func_variables_unused_name_from_wilcard_import.py new file mode 100644 index 0000000..e5763ad --- /dev/null +++ b/pylint/test/input/func_variables_unused_name_from_wilcard_import.py @@ -0,0 +1,4 @@ +"""check unused import from a wildcard import""" +# pylint: disable=no-absolute-import +from input.func_w0611 import * +__revision__ = None diff --git a/test/input/func_w0101.py b/pylint/test/input/func_w0101.py index fe543aa..fe543aa 100644 --- a/test/input/func_w0101.py +++ b/pylint/test/input/func_w0101.py diff --git a/test/input/func_w0102.py b/pylint/test/input/func_w0102.py index 8a8ae4d..8a8ae4d 100644 --- a/test/input/func_w0102.py +++ b/pylint/test/input/func_w0102.py diff --git a/pylint/test/input/func_w0103.py b/pylint/test/input/func_w0103.py new file mode 100644 index 0000000..04cb4c1 --- /dev/null +++ b/pylint/test/input/func_w0103.py @@ -0,0 +1,8 @@ +"""test max branch +""" +from __future__ import print_function +__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/pylint/test/input/func_w0104.py b/pylint/test/input/func_w0104.py new file mode 100644 index 0000000..6588e98 --- /dev/null +++ b/pylint/test/input/func_w0104.py @@ -0,0 +1,12 @@ +"""test max branch +""" +# pylint: disable=print-statement +__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/pylint/test/input/func_w0105.py b/pylint/test/input/func_w0105.py new file mode 100644 index 0000000..c40870e --- /dev/null +++ b/pylint/test/input/func_w0105.py @@ -0,0 +1,62 @@ +"""test max branch +""" +# pylint: disable=print-statement +__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_w0110.py b/pylint/test/input/func_w0110.py index c9d9311..c9d9311 100644 --- a/test/input/func_w0110.py +++ b/pylint/test/input/func_w0110.py diff --git a/test/input/func_w0111.py b/pylint/test/input/func_w0111.py index f8ad440..f8ad440 100644 --- a/test/input/func_w0111.py +++ b/pylint/test/input/func_w0111.py diff --git a/pylint/test/input/func_w0112.py b/pylint/test/input/func_w0112.py new file mode 100644 index 0000000..6bddf00 --- /dev/null +++ b/pylint/test/input/func_w0112.py @@ -0,0 +1,37 @@ +"""test max branch +""" +# pylint: disable=print-statement +__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_30.py b/pylint/test/input/func_w0122_py_30.py index 178c252..178c252 100644 --- a/test/input/func_w0122_py_30.py +++ b/pylint/test/input/func_w0122_py_30.py diff --git a/test/input/func_w0151.py b/pylint/test/input/func_w0151.py index e6862c5..e6862c5 100644 --- a/test/input/func_w0151.py +++ b/pylint/test/input/func_w0151.py diff --git a/pylint/test/input/func_w0152.py b/pylint/test/input/func_w0152.py new file mode 100644 index 0000000..9fff4a6 --- /dev/null +++ b/pylint/test/input/func_w0152.py @@ -0,0 +1,18 @@ +"""check use of * or ** +""" +# pylint: disable=no-absolute-import +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) + +def coolfunc(*args, **kwargs): + """magic function""" + return func(*args, **kwargs) diff --git a/test/input/func_w0202.py b/pylint/test/input/func_w0202.py index 00b5b0f..00b5b0f 100644 --- a/test/input/func_w0202.py +++ b/pylint/test/input/func_w0202.py diff --git a/pylint/test/input/func_w0205.py b/pylint/test/input/func_w0205.py new file mode 100644 index 0000000..896be6c --- /dev/null +++ b/pylint/test/input/func_w0205.py @@ -0,0 +1,24 @@ +"""check different signatures""" +from __future__ import print_function +__revision__ = 0 +# pylint: disable=too-few-public-methods +class Abcd(object): + '''dummy''' + def __init__(self): + self.aarg = False + def abcd(self, aaa=1, 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, bbbb=None): + """hehehe""" + print(self, aaa, bbbb) diff --git a/pylint/test/input/func_w0233.py b/pylint/test/input/func_w0233.py new file mode 100644 index 0000000..fe679bb --- /dev/null +++ b/pylint/test/input/func_w0233.py @@ -0,0 +1,50 @@ +# pylint: disable=R0903,W0212,W0403,W0406,no-absolute-import +"""test for call to __init__ from a non ancestor class +""" +from __future__ import print_function +__revision__ = '$Id: func_w0233.py,v 1.2 2004-09-29 08:35:13 syt Exp $' + +class AAAA(object): + """ancestor 1""" + + def __init__(self): + print('init', self) + BBBBMixin.__init__(self) + +class BBBBMixin(object): + """ancestor 2""" + + def __init__(self): + print('init', self) + +import nonexistant +import func_w0233 +class CCC(BBBBMixin, func_w0233.AAAA, func_w0233.BBBB, nonexistant.AClass): + """mix different things, some inferable some not""" + def __init__(self): + BBBBMixin.__init__(self) + func_w0233.AAAA.__init__(self) + func_w0233.BBBB.__init__(self) + nonexistant.AClass.__init__(self) + +class DDDD(AAAA): + """call superclass constructor in disjunct branches""" + def __init__(self, value): + if value: + AAAA.__init__(self) + else: + AAAA.__init__(self) + +class Super(dict): + """ test late binding super() call """ + def __init__(self): + base = super() + base.__init__() + +class Super2(dict): + """ Using the same idiom as Super, but without calling + the __init__ method. + """ + def __init__(self): + base = super() + base.__woohoo__() diff --git a/test/input/func_w0302.py b/pylint/test/input/func_w0302.py index a78f479..a78f479 100644 --- a/test/input/func_w0302.py +++ b/pylint/test/input/func_w0302.py diff --git a/pylint/test/input/func_w0312.py b/pylint/test/input/func_w0312.py new file mode 100644 index 0000000..491f75b --- /dev/null +++ b/pylint/test/input/func_w0312.py @@ -0,0 +1,11 @@ +"""test mixed tabs and spaces""" +from __future__ import print_function +__revision__ = 1 + +def spaces_func(): + """yo""" + print("yo") + +def tab_func(): + """yo""" + print("yo") diff --git a/pylint/test/input/func_w0332_py_30.py b/pylint/test/input/func_w0332_py_30.py new file mode 100644 index 0000000..6a38e8c --- /dev/null +++ b/pylint/test/input/func_w0332_py_30.py @@ -0,0 +1,4 @@ +"""check use of l as long int marker +""" +# pylint: disable=long-suffix +__revision__ = 1l diff --git a/pylint/test/input/func_w0401.py b/pylint/test/input/func_w0401.py new file mode 100644 index 0000000..caf59c9 --- /dev/null +++ b/pylint/test/input/func_w0401.py @@ -0,0 +1,9 @@ +"""test cyclic import +""" +# pylint: disable=print-statement, no-absolute-import +__revision__ = 0 + +from input import w0401_cycle + +if __revision__: + print w0401_cycle diff --git a/test/input/func_w0401_package/__init__.py b/pylint/test/input/func_w0401_package/__init__.py index dedef66..dedef66 100644 --- a/test/input/func_w0401_package/__init__.py +++ b/pylint/test/input/func_w0401_package/__init__.py diff --git a/pylint/test/input/func_w0401_package/all_the_things.py b/pylint/test/input/func_w0401_package/all_the_things.py new file mode 100644 index 0000000..67a627e --- /dev/null +++ b/pylint/test/input/func_w0401_package/all_the_things.py @@ -0,0 +1,9 @@ +"""All the things!""" +# pylint: disable=no-absolute-import +__revision__ = None +from .thing1 import THING1 +from .thing2 import THING2 +from .thing2 import THING1_PLUS_THING2 + +_ = (THING1, THING2, THING1_PLUS_THING2) +del _ diff --git a/test/input/func_w0401_package/thing1.py b/pylint/test/input/func_w0401_package/thing1.py index 34972a7..34972a7 100644 --- a/test/input/func_w0401_package/thing1.py +++ b/pylint/test/input/func_w0401_package/thing1.py diff --git a/pylint/test/input/func_w0401_package/thing2.py b/pylint/test/input/func_w0401_package/thing2.py new file mode 100644 index 0000000..987965e --- /dev/null +++ b/pylint/test/input/func_w0401_package/thing2.py @@ -0,0 +1,7 @@ +"""The second thing.""" +# pylint: disable=no-absolute-import +__revision__ = None +from .all_the_things import THING1 + +THING2 = "I am thing2" +THING1_PLUS_THING2 = "%s, plus %s" % (THING1, THING2) diff --git a/pylint/test/input/func_w0402.py b/pylint/test/input/func_w0402.py new file mode 100644 index 0000000..9f7136e --- /dev/null +++ b/pylint/test/input/func_w0402.py @@ -0,0 +1,12 @@ +"""test wildard import +""" +__revision__ = 0 +# pylint: disable=no-absolute-import +from input.func_fixme import * +# This is an unresolved import which still generates the wildcard-import +# warning. +from unknown.package import * + +def abcd(): + """use imports""" + function() diff --git a/pylint/test/input/func_w0404.py b/pylint/test/input/func_w0404.py new file mode 100644 index 0000000..d65da51 --- /dev/null +++ b/pylint/test/input/func_w0404.py @@ -0,0 +1,27 @@ +"""Unittests for W0404 (reimport)""" +from __future__ import absolute_import, print_function +__revision__ = 0 + +import sys + +import xml.etree.ElementTree +from xml.etree import ElementTree + +from email import encoders +import email.encoders + +import sys + +def no_reimport(): + """docstring""" + import os + print(os) + + +def reimport(): + """This function contains a reimport.""" + import sys + del sys + + +del sys, ElementTree, xml.etree.ElementTree, encoders, email.encoders diff --git a/pylint/test/input/func_w0405.py b/pylint/test/input/func_w0405.py new file mode 100644 index 0000000..8071ab2 --- /dev/null +++ b/pylint/test/input/func_w0405.py @@ -0,0 +1,31 @@ +"""check reimport +""" +from __future__ import absolute_import, print_function +__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/pylint/test/input/func_w0406.py b/pylint/test/input/func_w0406.py new file mode 100644 index 0000000..84a5550 --- /dev/null +++ b/pylint/test/input/func_w0406.py @@ -0,0 +1,9 @@ +"""test module importing itself""" +# pylint: disable=no-absolute-import +from __future__ import print_function +__revision__ = 0 + +import func_w0406 + +if __revision__: + print(func_w0406) diff --git a/pylint/test/input/func_w0611.py b/pylint/test/input/func_w0611.py new file mode 100644 index 0000000..44b24d9 --- /dev/null +++ b/pylint/test/input/func_w0611.py @@ -0,0 +1,25 @@ +"""check unused import +""" +# pylint: disable=print-statement, no-absolute-import + +__revision__ = 1 + +import os +import sys + +class NonRegr(object): + """???""" + 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/pylint/test/input/func_w0612.py b/pylint/test/input/func_w0612.py new file mode 100644 index 0000000..678185b --- /dev/null +++ b/pylint/test/input/func_w0612.py @@ -0,0 +1,37 @@ +"""test unused variable +""" +# pylint: disable=invalid-name, redefined-outer-name, print-statement, no-absolute-import +__revision__ = 0 +PATH = OS = collections = deque = None + +def function(matches): + """"yo""" + aaaa = 1 + index = -1 + for match in matches: + index += 1 + print match + +def visit_if(self, node): + """increments the branches counter""" + branches = 1 + # don't double count If nodes coming from some 'elif' + if node.orelse and len(node.orelse) > 1: + branches += 1 + self.inc_branch(branches) + self.stmts += branches + +def test_global(): + """ Test various assignments of global + variables through imports. + """ + global PATH, OS, collections, deque + from os import path as PATH + import os as OS + import collections + from collections import deque + # make sure that these triggers unused-variable + from sys import platform + from sys import version as VERSION + import this + import re as RE diff --git a/pylint/test/input/func_w0613.py b/pylint/test/input/func_w0613.py new file mode 100644 index 0000000..333699c --- /dev/null +++ b/pylint/test/input/func_w0613.py @@ -0,0 +1,42 @@ +# pylint: disable=R0903, print-statement +"""test unused argument +""" + +__revision__ = 1 + +def function(arg=1): + """ignore arg""" + + +class AAAA(object): + """dummy class""" + + def method(self, arg): + """dummy method""" + print self + def __init__(self, *unused_args, **unused_kwargs): + pass + + @classmethod + def selected(cls, *args, **kwargs): + """called by the registry when the vobject has been selected. + """ + return cls + + def using_inner_function(self, etype, size=1): + """return a fake result set for a particular entity type""" + rset = AAAA([('A',)]*size, '%s X' % etype, + description=[(etype,)]*size) + def inner(row, col=0, etype=etype, req=self, rset=rset): + """inner using all its argument""" + # pylint: disable = E1103 + return req.vreg.etype_class(etype)(req, rset, row, col) + # pylint: disable = W0201 + rset.get_entity = inner + +class BBBB(object): + """dummy class""" + + def __init__(self, arg): + """Constructor with an extra parameter. Should raise a warning""" + self.spam = 1 diff --git a/test/input/func_w0623_py30.py b/pylint/test/input/func_w0623_py30.py index f6c5f57..f6c5f57 100644 --- a/test/input/func_w0623_py30.py +++ b/pylint/test/input/func_w0623_py30.py diff --git a/pylint/test/input/func_w0623_py_30.py b/pylint/test/input/func_w0623_py_30.py new file mode 100644 index 0000000..e5c2231 --- /dev/null +++ b/pylint/test/input/func_w0623_py_30.py @@ -0,0 +1,67 @@ +"""Test for W0623, overwriting names in exception handlers.""" +# pylint: disable=broad-except,bare-except,pointless-except,print-statement,no-absolute-import +__revision__ = '' + +import exceptions + +class MyError(Exception): + """Special exception class.""" + pass + + +def some_function(): + """A function.""" + exc = None + + try: + {}["a"] + except KeyError, exceptions.RuntimeError: # W0623 + pass + except KeyError, OSError: # W0623 + pass + except KeyError, MyError: # W0623 + pass + except KeyError, exc: # this is fine + print exc + except KeyError, exc1: # this is fine + print exc1 + except KeyError, FOO: # C0103 + print FOO + + try: + pass + except KeyError, exc1: # this is fine + print exc1 + +class MyOtherError(Exception): + """Special exception class.""" + pass + + +exc3 = None + +try: + pass +except KeyError, exceptions.RuntimeError: # W0623 + pass +except KeyError, exceptions.RuntimeError.args: # W0623 + pass +except KeyError, OSError: # W0623 + pass +except KeyError, MyOtherError: # W0623 + pass +except KeyError, exc3: # this is fine + print exc3 +except KeyError, exc4: # this is fine + print exc4 +except KeyError, OOPS: # C0103 + print OOPS + +try: + pass +except KeyError, exc4: # this is fine + print exc4 +except IOError, exc5: # this is fine + print exc5 +except MyOtherError, exc5: # this is fine + print exc5 diff --git a/pylint/test/input/func_w0631.py b/pylint/test/input/func_w0631.py new file mode 100644 index 0000000..0390ac7 --- /dev/null +++ b/pylint/test/input/func_w0631.py @@ -0,0 +1,20 @@ +"""Check possible undefined loopvar +""" +from __future__ import print_function +__revision__ = 0 + +def do_stuff(some_random_list): + """This is not right.""" + for var in some_random_list: + pass + print(var) + + +def do_else(some_random_list): + """This is not right.""" + for var in some_random_list: + if var == 42: + break + else: + var = 84 + print(var) diff --git a/pylint/test/input/func_w0702.py b/pylint/test/input/func_w0702.py new file mode 100644 index 0000000..1f82a23 --- /dev/null +++ b/pylint/test/input/func_w0702.py @@ -0,0 +1,17 @@ +"""check empty except statement +""" +from __future__ import print_function +__revision__ = 0 + +if __revision__: + try: + print(__revision__) + except: + print('error') + +try: + __revision__ += 1 +except TypeError: + print('error') +except Exception: + print('error') diff --git a/pylint/test/input/func_w0703.py b/pylint/test/input/func_w0703.py new file mode 100644 index 0000000..fb633fd --- /dev/null +++ b/pylint/test/input/func_w0703.py @@ -0,0 +1,9 @@ +"""check empty except statement +""" +from __future__ import print_function +__revision__ = 0 + +try: + __revision__ += 1 +except Exception: + print('error') diff --git a/test/input/func_w0704.py b/pylint/test/input/func_w0704.py index 524df17..524df17 100644 --- a/test/input/func_w0704.py +++ b/pylint/test/input/func_w0704.py diff --git a/test/input/func_w0705.py b/pylint/test/input/func_w0705.py index 0a77ac2..0a77ac2 100644 --- a/test/input/func_w0705.py +++ b/pylint/test/input/func_w0705.py diff --git a/test/input/func_w0801.py b/pylint/test/input/func_w0801.py index cd386ff..cd386ff 100644 --- a/test/input/func_w0801.py +++ b/pylint/test/input/func_w0801.py diff --git a/pylint/test/input/func_w1201.py b/pylint/test/input/func_w1201.py new file mode 100644 index 0000000..a9791d9 --- /dev/null +++ b/pylint/test/input/func_w1201.py @@ -0,0 +1,21 @@ +# pylint: disable=E1101, no-absolute-import +"""test checking use of the logging module +""" + +__revision__ = '' + +# Muck up the names in an effort to confuse... +import logging as renamed_logging +import os as logging + +# Statements that should be flagged: +renamed_logging.warn('%s, %s' % (4, 5)) +renamed_logging.exception('%s' % 'Exceptional!') +renamed_logging.log(renamed_logging.INFO, 'msg: %s' % 'Run!') + +# Statements that should not be flagged: +renamed_logging.warn('%s, %s', 4, 5) +renamed_logging.log(renamed_logging.INFO, 'msg: %s', 'Run!') +renamed_logging.warn('%s' + ' the rest of a single string') +logging.warn('%s, %s' % (4, 5)) +logging.log(logging.INFO, 'msg: %s' % 'Run!') diff --git a/pylint/test/input/func_w1202.py b/pylint/test/input/func_w1202.py new file mode 100644 index 0000000..efe6b1a --- /dev/null +++ b/pylint/test/input/func_w1202.py @@ -0,0 +1,26 @@ +# pylint: disable=E1101, no-absolute-import +"""test checking use of the logging module +""" + +__revision__ = '' + +import __builtin__ + +# Muck up the names in an effort to confuse... +import logging as renamed_logging +import os as logging + +FORMAT_STR = '{0}, {1}' + +# Statements that should be flagged: +renamed_logging.debug('{0}, {1}'.format(4, 5)) +renamed_logging.log(renamed_logging.DEBUG, 'msg: {}'.format('Run!')) +renamed_logging.debug(FORMAT_STR.format(4, 5)) +renamed_logging.log(renamed_logging.DEBUG, FORMAT_STR.format(4, 5)) + +# Statements that should not be flagged: +renamed_logging.debug(format(66, 'x')) +renamed_logging.debug(__builtin__.format(66, 'x')) +renamed_logging.log(renamed_logging.DEBUG, 'msg: Run!'.upper()) +logging.debug('{0}, {1}'.format(4, 5)) +logging.log(logging.DEBUG, 'msg: {}'.format('Run!')) diff --git a/pylint/test/input/func_with_without_as_py25.py b/pylint/test/input/func_with_without_as_py25.py new file mode 100644 index 0000000..7ecf574 --- /dev/null +++ b/pylint/test/input/func_with_without_as_py25.py @@ -0,0 +1,12 @@ +'''This is a little non regression test on a with statement +without 'as'. +''' +from __future__ import with_statement, print_function +__revision__ = 32321313 + +def do_nothing(arg): + 'ho foo' + print(arg) + with open('x'): + base.baz + base = 7 diff --git a/test/input/ignore_except_pass_by_default.py b/pylint/test/input/ignore_except_pass_by_default.py index 444e540..444e540 100644 --- a/test/input/ignore_except_pass_by_default.py +++ b/pylint/test/input/ignore_except_pass_by_default.py diff --git a/test/input/indirect1.py b/pylint/test/input/indirect1.py index eac6242..eac6242 100644 --- a/test/input/indirect1.py +++ b/pylint/test/input/indirect1.py diff --git a/test/input/indirect2.py b/pylint/test/input/indirect2.py index 6eefece..6eefece 100644 --- a/test/input/indirect2.py +++ b/pylint/test/input/indirect2.py diff --git a/test/input/indirect3.py b/pylint/test/input/indirect3.py index dac0853..dac0853 100644 --- a/test/input/indirect3.py +++ b/pylint/test/input/indirect3.py diff --git a/test/input/noext b/pylint/test/input/noext index 8aeda06..8aeda06 100644 --- a/test/input/noext +++ b/pylint/test/input/noext diff --git a/test/input/similar1 b/pylint/test/input/similar1 index 2b04ee2..2b04ee2 100644 --- a/test/input/similar1 +++ b/pylint/test/input/similar1 diff --git a/test/input/similar2 b/pylint/test/input/similar2 index 77f5f1e..77f5f1e 100644 --- a/test/input/similar2 +++ b/pylint/test/input/similar2 diff --git a/test/input/syntax_error.py b/pylint/test/input/syntax_error.py index 6c1e7a7..6c1e7a7 100644 --- a/test/input/syntax_error.py +++ b/pylint/test/input/syntax_error.py diff --git a/pylint/test/input/w0401_cycle.py b/pylint/test/input/w0401_cycle.py new file mode 100644 index 0000000..cea6b93 --- /dev/null +++ b/pylint/test/input/w0401_cycle.py @@ -0,0 +1,9 @@ +"""w0401 dependency +""" +# pylint: disable=print-statement, no-absolute-import +__revision__ = 0 + +import input.func_w0401 + +if __revision__: + print input diff --git a/test/input/w0801_same.py b/pylint/test/input/w0801_same.py index cd386ff..cd386ff 100644 --- a/test/input/w0801_same.py +++ b/pylint/test/input/w0801_same.py diff --git a/test/messages/builtin_module.txt b/pylint/test/messages/builtin_module.txt index 2616c0e..2616c0e 100644 --- a/test/messages/builtin_module.txt +++ b/pylint/test/messages/builtin_module.txt diff --git a/pylint/test/messages/func_3k_removed_stuff_py_30.txt b/pylint/test/messages/func_3k_removed_stuff_py_30.txt new file mode 100644 index 0000000..e7ede06 --- /dev/null +++ b/pylint/test/messages/func_3k_removed_stuff_py_30.txt @@ -0,0 +1,5 @@ +E: 12:function: Instance of 'unicode' has no 'looower' member +W: 3: Used builtin function 'filter' +W: 3: Used builtin function 'map' +W: 4: __future__ import is not the first non docstring statement +W: 6: Relative import 'func_w0302', should be 'input.func_w0302' diff --git a/test/messages/func_assert_2uple.txt b/pylint/test/messages/func_assert_2uple.txt index 3031ea7..3031ea7 100644 --- a/test/messages/func_assert_2uple.txt +++ b/pylint/test/messages/func_assert_2uple.txt diff --git a/test/messages/func_attrs_definition_order.txt b/pylint/test/messages/func_attrs_definition_order.txt index d168e9a..d168e9a 100644 --- a/test/messages/func_attrs_definition_order.txt +++ b/pylint/test/messages/func_attrs_definition_order.txt diff --git a/pylint/test/messages/func_bad_assigment_to_exception_var.txt b/pylint/test/messages/func_bad_assigment_to_exception_var.txt new file mode 100644 index 0000000..119fbe2 --- /dev/null +++ b/pylint/test/messages/func_bad_assigment_to_exception_var.txt @@ -0,0 +1,5 @@ +E: 11: Raising int while only classes or instances are allowed +E: 20:func: Raising NoneType while only classes or instances are allowed +E: 30: Raising NoneType while only classes or instances are allowed +W: 12: Catching too general exception Exception + diff --git a/pylint/test/messages/func_bad_cont_dictcomp_py27.txt b/pylint/test/messages/func_bad_cont_dictcomp_py27.txt new file mode 100644 index 0000000..7443d55 --- /dev/null +++ b/pylint/test/messages/func_bad_cont_dictcomp_py27.txt @@ -0,0 +1,6 @@ +C: 35: Wrong continued indentation. + for x in range(3)} # [bad-continuation] + ^ | +C: 38: Wrong continued indentation. + for x in range(3)} # [bad-continuation] + | ^ diff --git a/test/messages/func_bad_exception_context_py30.txt b/pylint/test/messages/func_bad_exception_context_py30.txt index 241195d..241195d 100644 --- a/test/messages/func_bad_exception_context_py30.txt +++ b/pylint/test/messages/func_bad_exception_context_py30.txt diff --git a/test/messages/func_base_useless_pass.txt b/pylint/test/messages/func_base_useless_pass.txt index 4398e3f..4398e3f 100644 --- a/test/messages/func_base_useless_pass.txt +++ b/pylint/test/messages/func_base_useless_pass.txt diff --git a/test/messages/func_block_disable_msg.txt b/pylint/test/messages/func_block_disable_msg.txt index 1a16fbe..1a16fbe 100644 --- a/test/messages/func_block_disable_msg.txt +++ b/pylint/test/messages/func_block_disable_msg.txt diff --git a/pylint/test/messages/func_break_or_return_in_try_finally.txt b/pylint/test/messages/func_break_or_return_in_try_finally.txt new file mode 100644 index 0000000..04f27fe --- /dev/null +++ b/pylint/test/messages/func_break_or_return_in_try_finally.txt @@ -0,0 +1,3 @@ +W: 18:insidious_break_and_return: break statement in finally block may swallow exception +W: 20:insidious_break_and_return: return statement in finally block may swallow exception +W: 39:break_and_return.strange: Cell variable my_var defined in loop diff --git a/test/messages/func_bug113231.txt b/pylint/test/messages/func_bug113231.txt index a9d3f7e..a9d3f7e 100644 --- a/test/messages/func_bug113231.txt +++ b/pylint/test/messages/func_bug113231.txt diff --git a/test/messages/func_continue_not_in_loop.txt b/pylint/test/messages/func_continue_not_in_loop.txt index d3a3183..d3a3183 100644 --- a/test/messages/func_continue_not_in_loop.txt +++ b/pylint/test/messages/func_continue_not_in_loop.txt diff --git a/pylint/test/messages/func_dangerous_default.txt b/pylint/test/messages/func_dangerous_default.txt new file mode 100644 index 0000000..8980b7c --- /dev/null +++ b/pylint/test/messages/func_dangerous_default.txt @@ -0,0 +1,13 @@ +W: 7:function1: Dangerous default value [] as argument +W: 11:function2: Dangerous default value HEHE (__builtin__.dict) as argument +W: 19:function4: Dangerous default value set() (__builtin__.set) as argument +W: 29:function6: Dangerous default value GLOBAL_SET (__builtin__.set) as argument +W: 33:function7: Dangerous default value dict() (__builtin__.dict) as argument +W: 37:function8: Dangerous default value list() (__builtin__.list) as argument +W: 41:function9: Dangerous default value [] as argument +W: 45:function10: Dangerous default value {} as argument +W: 49:function11: Dangerous default value list() (__builtin__.list) as argument +W: 53:function12: Dangerous default value dict() (__builtin__.dict) as argument +W: 62:function13: Dangerous default value OINK (__builtin__.dict) as argument +W: 66:function14: Dangerous default value dict() (__builtin__.dict) as argument +W: 74:function15: Dangerous default value INVALID_DICT (__builtin__.dict) as argument diff --git a/pylint/test/messages/func_dangerous_default_py30.txt b/pylint/test/messages/func_dangerous_default_py30.txt new file mode 100644 index 0000000..eb05c87 --- /dev/null +++ b/pylint/test/messages/func_dangerous_default_py30.txt @@ -0,0 +1,13 @@ +W: 7:function1: Dangerous default value [] as argument +W: 11:function2: Dangerous default value HEHE (builtins.dict) as argument +W: 19:function4: Dangerous default value set() (builtins.set) as argument +W: 29:function6: Dangerous default value GLOBAL_SET (builtins.set) as argument +W: 33:function7: Dangerous default value dict() (builtins.dict) as argument +W: 37:function8: Dangerous default value list() (builtins.list) as argument +W: 41:function9: Dangerous default value [] as argument +W: 45:function10: Dangerous default value {} as argument +W: 49:function11: Dangerous default value list() (builtins.list) as argument +W: 53:function12: Dangerous default value dict() (builtins.dict) as argument +W: 62:function13: Dangerous default value OINK (builtins.dict) as argument +W: 66:function14: Dangerous default value dict() (builtins.dict) as argument +W: 74:function15: Dangerous default value INVALID_DICT (builtins.dict) as argument diff --git a/pylint/test/messages/func_defining-attr-methods_order.txt b/pylint/test/messages/func_defining-attr-methods_order.txt new file mode 100644 index 0000000..5588319 --- /dev/null +++ b/pylint/test/messages/func_defining-attr-methods_order.txt @@ -0,0 +1,3 @@ +W: 28:A.set_z: Attribute 'z' defined outside __init__ +W: 29:A.set_z: Attribute 'z' defined outside __init__ +W: 41:B.test: Attribute 'z' defined outside __init__ diff --git a/pylint/test/messages/func_deprecated_lambda_py_30.txt b/pylint/test/messages/func_deprecated_lambda_py_30.txt new file mode 100644 index 0000000..be68e16 --- /dev/null +++ b/pylint/test/messages/func_deprecated_lambda_py_30.txt @@ -0,0 +1,2 @@ +W: 7: map/filter on lambda could be replaced by comprehension +W: 9: map/filter on lambda could be replaced by comprehension diff --git a/pylint/test/messages/func_deprecated_module_py30.txt b/pylint/test/messages/func_deprecated_module_py30.txt new file mode 100644 index 0000000..d4c88cf --- /dev/null +++ b/pylint/test/messages/func_deprecated_module_py30.txt @@ -0,0 +1,2 @@ +F: 11: Unable to import 'stringfile' +W: 8: Uses of a deprecated module 'optparse' diff --git a/test/messages/func_w0403_py29.txt b/pylint/test/messages/func_deprecated_module_py_30.txt index 70679ec..70679ec 100644 --- a/test/messages/func_w0403_py29.txt +++ b/pylint/test/messages/func_deprecated_module_py_30.txt diff --git a/pylint/test/messages/func_disable_linebased.txt b/pylint/test/messages/func_disable_linebased.txt new file mode 100644 index 0000000..e417c5d --- /dev/null +++ b/pylint/test/messages/func_disable_linebased.txt @@ -0,0 +1,2 @@ +C: 1: Line too long (127/100) +C: 14: Line too long (113/100) diff --git a/pylint/test/messages/func_disable_linebased_py30.txt b/pylint/test/messages/func_disable_linebased_py30.txt new file mode 100644 index 0000000..06e391d --- /dev/null +++ b/pylint/test/messages/func_disable_linebased_py30.txt @@ -0,0 +1,2 @@ +C: 1: Line too long (127/100) +C: 14: Line too long (114/100) diff --git a/test/messages/func_dotted_ancestor.txt b/pylint/test/messages/func_dotted_ancestor.txt index 6e2c6fa..6e2c6fa 100644 --- a/test/messages/func_dotted_ancestor.txt +++ b/pylint/test/messages/func_dotted_ancestor.txt diff --git a/test/messages/func_e0001_py30.txt b/pylint/test/messages/func_e0001_py30.txt index 1cf05ca..1cf05ca 100644 --- a/test/messages/func_e0001_py30.txt +++ b/pylint/test/messages/func_e0001_py30.txt diff --git a/test/messages/func_e0011.txt b/pylint/test/messages/func_e0011.txt index 55f07b1..55f07b1 100644 --- a/test/messages/func_e0011.txt +++ b/pylint/test/messages/func_e0011.txt diff --git a/test/messages/func_e0012.txt b/pylint/test/messages/func_e0012.txt index a6d1b69..a6d1b69 100644 --- a/test/messages/func_e0012.txt +++ b/pylint/test/messages/func_e0012.txt diff --git a/test/messages/func_e0101.txt b/pylint/test/messages/func_e0101.txt index 203cd32..203cd32 100644 --- a/test/messages/func_e0101.txt +++ b/pylint/test/messages/func_e0101.txt diff --git a/test/messages/func_e0108.txt b/pylint/test/messages/func_e0108.txt index dfbafe5..dfbafe5 100644 --- a/test/messages/func_e0108.txt +++ b/pylint/test/messages/func_e0108.txt diff --git a/test/messages/func_e0203.txt b/pylint/test/messages/func_e0203.txt index c330ffa..c330ffa 100644 --- a/test/messages/func_e0203.txt +++ b/pylint/test/messages/func_e0203.txt diff --git a/test/messages/func_e0204.txt b/pylint/test/messages/func_e0204.txt index 8e05efe..8e05efe 100644 --- a/test/messages/func_e0204.txt +++ b/pylint/test/messages/func_e0204.txt diff --git a/test/messages/func_e0206.txt b/pylint/test/messages/func_e0206.txt index c15f841..c15f841 100644 --- a/test/messages/func_e0206.txt +++ b/pylint/test/messages/func_e0206.txt diff --git a/test/messages/func_e0601.txt b/pylint/test/messages/func_e0601.txt index 321c731..321c731 100644 --- a/test/messages/func_e0601.txt +++ b/pylint/test/messages/func_e0601.txt diff --git a/test/messages/func_e0604.txt b/pylint/test/messages/func_e0604.txt index e232633..e232633 100644 --- a/test/messages/func_e0604.txt +++ b/pylint/test/messages/func_e0604.txt diff --git a/pylint/test/messages/func_e12xx.txt b/pylint/test/messages/func_e12xx.txt new file mode 100644 index 0000000..d0a8b9c --- /dev/null +++ b/pylint/test/messages/func_e12xx.txt @@ -0,0 +1,6 @@ +E: 13:pprint: Too many arguments for logging format string +E: 14:pprint: Too many arguments for logging format string +E: 15:pprint: Logging format string ends in middle of conversion specifier +E: 16:pprint: Not enough arguments for logging format string +E: 17:pprint: Unsupported logging format character 'y' (0x79) at index 3 +E: 18:pprint: Too many arguments for logging format string diff --git a/pylint/test/messages/func_e13xx.txt b/pylint/test/messages/func_e13xx.txt new file mode 100644 index 0000000..f2d0d36 --- /dev/null +++ b/pylint/test/messages/func_e13xx.txt @@ -0,0 +1,13 @@ +E: 11:pprint: Not enough arguments for format string +E: 12:pprint: Too many arguments for format string +E: 13:pprint: Mixing named and unnamed conversion specifiers in format string +E: 14:pprint: Missing key 'PARG_2' in format string dictionary +E: 16:pprint: Missing key 'PARG_2' in format string dictionary +E: 17:pprint: Expected mapping for format string, not Tuple +E: 18:pprint: Expected mapping for format string, not List +E: 19:pprint: Unsupported format character 'z' (0x7a) at index 2 +E: 20:pprint: Format string ends in middle of conversion specifier +E: 21:pprint: Unsupported format character 'a' (0x61) at index 12 +W: 15:pprint: Unused key 'PARG_3' in format string dictionary +W: 16:pprint: Format string dictionary key should be a string, not 2 + diff --git a/pylint/test/messages/func_e13xx_py30.txt b/pylint/test/messages/func_e13xx_py30.txt new file mode 100644 index 0000000..7ac9fb1 --- /dev/null +++ b/pylint/test/messages/func_e13xx_py30.txt @@ -0,0 +1,11 @@ +E: 11:pprint: Not enough arguments for format string +E: 12:pprint: Too many arguments for format string +E: 13:pprint: Mixing named and unnamed conversion specifiers in format string +E: 14:pprint: Missing key 'PARG_2' in format string dictionary +E: 16:pprint: Missing key 'PARG_2' in format string dictionary +E: 17:pprint: Expected mapping for format string, not Tuple +E: 18:pprint: Expected mapping for format string, not List +E: 19:pprint: Unsupported format character 'z' (0x7a) at index 2 +E: 20:pprint: Format string ends in middle of conversion specifier +W: 15:pprint: Unused key 'PARG_3' in format string dictionary +W: 16:pprint: Format string dictionary key should be a string, not 2
\ No newline at end of file diff --git a/test/messages/func_reqattrs.txt b/pylint/test/messages/func_empty_module.txt index 0563049..0563049 100644 --- a/test/messages/func_reqattrs.txt +++ b/pylint/test/messages/func_empty_module.txt diff --git a/test/messages/func_eval_used.txt b/pylint/test/messages/func_eval_used.txt index ab65307..ab65307 100644 --- a/test/messages/func_eval_used.txt +++ b/pylint/test/messages/func_eval_used.txt diff --git a/test/messages/func_excess_escapes.txt b/pylint/test/messages/func_excess_escapes.txt index 2f07722..2f07722 100644 --- a/test/messages/func_excess_escapes.txt +++ b/pylint/test/messages/func_excess_escapes.txt diff --git a/test/messages/func_exec_used_py30.txt b/pylint/test/messages/func_exec_used_py30.txt index 362da68..362da68 100644 --- a/test/messages/func_exec_used_py30.txt +++ b/pylint/test/messages/func_exec_used_py30.txt diff --git a/test/messages/func_f0401.txt b/pylint/test/messages/func_f0401.txt index 27dc386..27dc386 100644 --- a/test/messages/func_f0401.txt +++ b/pylint/test/messages/func_f0401.txt diff --git a/test/messages/func_first_arg.txt b/pylint/test/messages/func_first_arg.txt index 75090dd..75090dd 100644 --- a/test/messages/func_first_arg.txt +++ b/pylint/test/messages/func_first_arg.txt diff --git a/pylint/test/messages/func_fixme.txt b/pylint/test/messages/func_fixme.txt new file mode 100644 index 0000000..deade52 --- /dev/null +++ b/pylint/test/messages/func_fixme.txt @@ -0,0 +1,5 @@ +W: 5: FIXME: beep +W: 11: FIXME: Valid test +W: 13: TODO: Do something with the variables +W: 14: XXX: Fix this later +W: 15: FIXME: no space after hash
\ No newline at end of file diff --git a/pylint/test/messages/func_i0011.txt b/pylint/test/messages/func_i0011.txt new file mode 100644 index 0000000..6c1071d --- /dev/null +++ b/pylint/test/messages/func_i0011.txt @@ -0,0 +1,2 @@ +I: 1: Locally disabling reimported (W0404) +I: 1: Useless suppression of 'reimported' diff --git a/pylint/test/messages/func_i0012.txt b/pylint/test/messages/func_i0012.txt new file mode 100644 index 0000000..76b0c77 --- /dev/null +++ b/pylint/test/messages/func_i0012.txt @@ -0,0 +1 @@ +I: 1: Locally enabling reimported (W0404) diff --git a/test/messages/func_i0013.txt b/pylint/test/messages/func_i0013.txt index 75d7afd..75d7afd 100644 --- a/test/messages/func_i0013.txt +++ b/pylint/test/messages/func_i0013.txt diff --git a/pylint/test/messages/func_i0014.txt b/pylint/test/messages/func_i0014.txt new file mode 100644 index 0000000..c3b521d --- /dev/null +++ b/pylint/test/messages/func_i0014.txt @@ -0,0 +1,2 @@ +I: 1: Ignoring entire file +I: 1: Pragma "disable-all" is deprecated, use "skip-file" instead diff --git a/pylint/test/messages/func_i0020.txt b/pylint/test/messages/func_i0020.txt new file mode 100644 index 0000000..73f83a4 --- /dev/null +++ b/pylint/test/messages/func_i0020.txt @@ -0,0 +1,2 @@ +I: 7: Locally disabling unused-variable (W0612) +I: 8: Suppressed 'unused-variable' (from line 7) diff --git a/pylint/test/messages/func_i0022.txt b/pylint/test/messages/func_i0022.txt new file mode 100644 index 0000000..fb527b1 --- /dev/null +++ b/pylint/test/messages/func_i0022.txt @@ -0,0 +1,21 @@ +I: 5: Locally disabling invalid-name (C0103) +I: 5: Suppressed 'invalid-name' (from line 5) +I: 6: Locally disabling invalid-name (C0103) +I: 6: Pragma "disable-msg" is deprecated, use "disable" instead +I: 6: Suppressed 'invalid-name' (from line 6) +I: 8: Locally disabling invalid-name (C0103) +I: 9: Suppressed 'invalid-name' (from line 8) +I: 10: Locally enabling invalid-name (C0103) +I: 12: Locally disabling invalid-name (C0103) +I: 12: Pragma "disable-msg" is deprecated, use "disable" instead +I: 13: Suppressed 'invalid-name' (from line 12) +I: 14: Locally enabling invalid-name (C0103) +I: 14: Pragma "enable-msg" is deprecated, use "enable" instead +I: 16: Locally disabling invalid-name (C0103) +I: 16: Pragma "disable-msg" is deprecated, use "disable" instead +I: 17: Suppressed 'invalid-name' (from line 16) +I: 18: Locally enabling invalid-name (C0103) +I: 18: Pragma "enable-msg" is deprecated, use "enable" instead +I: 20: Locally disabling invalid-name (C0103) +I: 21: Suppressed 'invalid-name' (from line 20) +I: 22: Locally enabling invalid-name (C0103) diff --git a/pylint/test/messages/func_import_syntax_error.txt b/pylint/test/messages/func_import_syntax_error.txt new file mode 100644 index 0000000..885378b --- /dev/null +++ b/pylint/test/messages/func_import_syntax_error.txt @@ -0,0 +1,4 @@ +C: 1: Missing module docstring +C: 1: Missing required attribute "__revision__" +F: 2: Unable to import 'syntax_error' (expected an indented block (<string>, line 2)) +W: 2: Unused import syntax_error diff --git a/pylint/test/messages/func_import_syntax_error_py30.txt b/pylint/test/messages/func_import_syntax_error_py30.txt new file mode 100644 index 0000000..00e7da0 --- /dev/null +++ b/pylint/test/messages/func_import_syntax_error_py30.txt @@ -0,0 +1,4 @@ +C: 1: Missing module docstring +C: 1: Missing required attribute "__revision__" +E: 2: No name 'syntax_error' in module 'input' +W: 2: Unused import syntax_error diff --git a/pylint/test/messages/func_indent.txt b/pylint/test/messages/func_indent.txt new file mode 100644 index 0000000..c8fe133 --- /dev/null +++ b/pylint/test/messages/func_indent.txt @@ -0,0 +1,5 @@ +W: 6: Bad indentation. Found 1 spaces, expected 4 +W: 7: Bad indentation. Found 1 spaces, expected 4 +W: 14: Bad indentation. Found 5 spaces, expected 4 +W: 15: Bad indentation. Found 5 spaces, expected 4 +W: 15:titii: Statement seems to have no effect diff --git a/test/messages/func_init_vars.txt b/pylint/test/messages/func_init_vars.txt index 44ef6f3..44ef6f3 100644 --- a/test/messages/func_init_vars.txt +++ b/pylint/test/messages/func_init_vars.txt diff --git a/pylint/test/messages/func_interfaces.txt b/pylint/test/messages/func_interfaces.txt new file mode 100644 index 0000000..a4cd21d --- /dev/null +++ b/pylint/test/messages/func_interfaces.txt @@ -0,0 +1,6 @@ +E: 46:MissingMethod: Missing method 'truc' from IMachin interface +E: 77:InterfaceCantBeFound: Undefined variable 'undefined' +E: 88:InterfaceCanNowBeFound: Missing method 'troc' from IMachin interface +E: 88:InterfaceCanNowBeFound: Missing method 'truc' from IMachin interface +F: 77:InterfaceCantBeFound: failed to resolve interfaces implemented by InterfaceCantBeFound (undefined) +W: 71:BadArgument.troc: Arguments number differs from IMachin interface 'troc' method diff --git a/pylint/test/messages/func_invalid_sequence_index.txt b/pylint/test/messages/func_invalid_sequence_index.txt new file mode 100644 index 0000000..db9edab --- /dev/null +++ b/pylint/test/messages/func_invalid_sequence_index.txt @@ -0,0 +1,19 @@ +E: 13:function1: Sequence index is not an int, slice, or instance with __index__ +E: 17:function2: Sequence index is not an int, slice, or instance with __index__ +E: 21:function3: Sequence index is not an int, slice, or instance with __index__ +E: 25:function4: Sequence index is not an int, slice, or instance with __index__ +E: 33:function5: Sequence index is not an int, slice, or instance with __index__ +E: 37:function6: Sequence index is not an int, slice, or instance with __index__ +E: 41:function7: Sequence index is not an int, slice, or instance with __index__ +E: 48:function8: Sequence index is not an int, slice, or instance with __index__ +E:128:function19: Sequence index is not an int, slice, or instance with __index__ +E:133:function20: Sequence index is not an int, slice, or instance with __index__ +E:144:function21: Sequence index is not an int, slice, or instance with __index__ +E:145:function21: Sequence index is not an int, slice, or instance with __index__ +E:159:function22: Sequence index is not an int, slice, or instance with __index__ +E:160:function22: Sequence index is not an int, slice, or instance with __index__ +E:175:function23: Sequence index is not an int, slice, or instance with __index__ +E:176:function23: Sequence index is not an int, slice, or instance with __index__ +E:191:function24: Sequence index is not an int, slice, or instance with __index__ +E:192:function24: Sequence index is not an int, slice, or instance with __index__ +E:202:function25: Sequence index is not an int, slice, or instance with __index__ diff --git a/test/messages/func_keyword_repeat_py26.txt b/pylint/test/messages/func_keyword_repeat_py26.txt index f2652f0..f2652f0 100644 --- a/test/messages/func_keyword_repeat_py26.txt +++ b/pylint/test/messages/func_keyword_repeat_py26.txt diff --git a/test/messages/func_kwoa_py30.txt b/pylint/test/messages/func_kwoa_py30.txt index 08dd8c5..08dd8c5 100644 --- a/test/messages/func_kwoa_py30.txt +++ b/pylint/test/messages/func_kwoa_py30.txt diff --git a/pylint/test/messages/func_logging_not_lazy_with_logger.txt b/pylint/test/messages/func_logging_not_lazy_with_logger.txt new file mode 100644 index 0000000..8b483a7 --- /dev/null +++ b/pylint/test/messages/func_logging_not_lazy_with_logger.txt @@ -0,0 +1,4 @@ +W: 9: Specify string format arguments as logging function parameters +W: 10: Specify string format arguments as logging function parameters +W: 12: Specify string format arguments as logging function parameters +W: 14: Specify string format arguments as logging function parameters diff --git a/pylint/test/messages/func_loopvar_in_dict_comp_py27.txt b/pylint/test/messages/func_loopvar_in_dict_comp_py27.txt new file mode 100644 index 0000000..bc11121 --- /dev/null +++ b/pylint/test/messages/func_loopvar_in_dict_comp_py27.txt @@ -0,0 +1 @@ +W: 8:bad_case.<lambda>: Cell variable x defined in loop diff --git a/test/messages/func_method_could_be_function.txt b/pylint/test/messages/func_method_could_be_function.txt index 1def89e..1def89e 100644 --- a/test/messages/func_method_could_be_function.txt +++ b/pylint/test/messages/func_method_could_be_function.txt diff --git a/test/messages/func_module___dict__.txt b/pylint/test/messages/func_module___dict__.txt index 4169824..4169824 100644 --- a/test/messages/func_module___dict__.txt +++ b/pylint/test/messages/func_module___dict__.txt diff --git a/test/messages/func_more_e0604.txt b/pylint/test/messages/func_more_e0604.txt index 080c59f..080c59f 100644 --- a/test/messages/func_more_e0604.txt +++ b/pylint/test/messages/func_more_e0604.txt diff --git a/test/messages/func_nameerror_on_string_substitution.txt b/pylint/test/messages/func_nameerror_on_string_substitution.txt index aab2102..aab2102 100644 --- a/test/messages/func_nameerror_on_string_substitution.txt +++ b/pylint/test/messages/func_nameerror_on_string_substitution.txt diff --git a/test/messages/func_no_dummy_redefined.txt b/pylint/test/messages/func_no_dummy_redefined.txt index 9ef7891..9ef7891 100644 --- a/test/messages/func_no_dummy_redefined.txt +++ b/pylint/test/messages/func_no_dummy_redefined.txt diff --git a/test/messages/func_non_iterator_returned_py30.txt b/pylint/test/messages/func_non_iterator_returned_py30.txt index b41b14b..b41b14b 100644 --- a/test/messages/func_non_iterator_returned_py30.txt +++ b/pylint/test/messages/func_non_iterator_returned_py30.txt diff --git a/test/messages/func_non_iterator_returned_py_30.txt b/pylint/test/messages/func_non_iterator_returned_py_30.txt index b41b14b..b41b14b 100644 --- a/test/messages/func_non_iterator_returned_py_30.txt +++ b/pylint/test/messages/func_non_iterator_returned_py_30.txt diff --git a/test/messages/func_nonregr___file___global.txt b/pylint/test/messages/func_nonregr___file___global.txt index c0d7340..c0d7340 100644 --- a/test/messages/func_nonregr___file___global.txt +++ b/pylint/test/messages/func_nonregr___file___global.txt diff --git a/test/messages/func_operators.txt b/pylint/test/messages/func_operators.txt index ba23690..ba23690 100644 --- a/test/messages/func_operators.txt +++ b/pylint/test/messages/func_operators.txt diff --git a/test/messages/func_r0901.txt b/pylint/test/messages/func_r0901.txt index 1f4007f..1f4007f 100644 --- a/test/messages/func_r0901.txt +++ b/pylint/test/messages/func_r0901.txt diff --git a/test/messages/func_r0902.txt b/pylint/test/messages/func_r0902.txt index 5dcb669..5dcb669 100644 --- a/test/messages/func_r0902.txt +++ b/pylint/test/messages/func_r0902.txt diff --git a/pylint/test/messages/func_r0903.txt b/pylint/test/messages/func_r0903.txt new file mode 100644 index 0000000..3ca8004 --- /dev/null +++ b/pylint/test/messages/func_r0903.txt @@ -0,0 +1 @@ +R: 5:Aaaa: Too few public methods (1/2) diff --git a/test/messages/func_r0904.txt b/pylint/test/messages/func_r0904.txt index 76baf72..76baf72 100644 --- a/test/messages/func_r0904.txt +++ b/pylint/test/messages/func_r0904.txt diff --git a/test/messages/func_r0921.txt b/pylint/test/messages/func_r0921.txt index 7e9a442..7e9a442 100644 --- a/test/messages/func_r0921.txt +++ b/pylint/test/messages/func_r0921.txt diff --git a/test/messages/func_r0922.txt b/pylint/test/messages/func_r0922.txt index 70319ee..70319ee 100644 --- a/test/messages/func_r0922.txt +++ b/pylint/test/messages/func_r0922.txt diff --git a/test/messages/func_r0923.txt b/pylint/test/messages/func_r0923.txt index 11ee61d..11ee61d 100644 --- a/test/messages/func_r0923.txt +++ b/pylint/test/messages/func_r0923.txt diff --git a/test/messages/func_raw_escapes.txt b/pylint/test/messages/func_raw_escapes.txt index 991fba7..991fba7 100644 --- a/test/messages/func_raw_escapes.txt +++ b/pylint/test/messages/func_raw_escapes.txt diff --git a/pylint/test/messages/func_reqattrs.txt b/pylint/test/messages/func_reqattrs.txt new file mode 100644 index 0000000..0563049 --- /dev/null +++ b/pylint/test/messages/func_reqattrs.txt @@ -0,0 +1 @@ +C: 1: Missing required attribute "__revision__" diff --git a/test/messages/func_return_outside_func.txt b/pylint/test/messages/func_return_outside_func.txt index e61be76..e61be76 100644 --- a/test/messages/func_return_outside_func.txt +++ b/pylint/test/messages/func_return_outside_func.txt diff --git a/test/messages/func_return_yield_mix_py_33.txt b/pylint/test/messages/func_return_yield_mix_py_33.txt index d81ce5c..d81ce5c 100644 --- a/test/messages/func_return_yield_mix_py_33.txt +++ b/pylint/test/messages/func_return_yield_mix_py_33.txt diff --git a/pylint/test/messages/func_set_literal_as_default_py27.txt b/pylint/test/messages/func_set_literal_as_default_py27.txt new file mode 100644 index 0000000..ba38ec4 --- /dev/null +++ b/pylint/test/messages/func_set_literal_as_default_py27.txt @@ -0,0 +1 @@ +W: 5:function1: Dangerous default value set() as argument diff --git a/test/messages/func_syntax_error.txt b/pylint/test/messages/func_syntax_error.txt index f2fb1ec..f2fb1ec 100644 --- a/test/messages/func_syntax_error.txt +++ b/pylint/test/messages/func_syntax_error.txt diff --git a/test/messages/func_tokenize_error.txt b/pylint/test/messages/func_tokenize_error.txt index a6c5cda..a6c5cda 100644 --- a/test/messages/func_tokenize_error.txt +++ b/pylint/test/messages/func_tokenize_error.txt diff --git a/test/messages/func_too_many_locals_arguments.txt b/pylint/test/messages/func_too_many_locals_arguments.txt index 8f236c2..8f236c2 100644 --- a/test/messages/func_too_many_locals_arguments.txt +++ b/pylint/test/messages/func_too_many_locals_arguments.txt diff --git a/test/messages/func_too_many_returns_yields.txt b/pylint/test/messages/func_too_many_returns_yields.txt index f8a0f0d..f8a0f0d 100644 --- a/test/messages/func_too_many_returns_yields.txt +++ b/pylint/test/messages/func_too_many_returns_yields.txt diff --git a/pylint/test/messages/func_toolonglines.txt b/pylint/test/messages/func_toolonglines.txt new file mode 100644 index 0000000..ddf05af --- /dev/null +++ b/pylint/test/messages/func_toolonglines.txt @@ -0,0 +1,6 @@ +C: 1: Line too long (101/100) +C: 2: Line too long (104/100) +C: 11: Line too long (101/100) +C: 17: Line too long (102/100) +C: 25: Line too long (105/100) +W: 11: Cannot decode using encoding "ascii", unexpected byte at position 37 diff --git a/pylint/test/messages/func_toolonglines_py30.txt b/pylint/test/messages/func_toolonglines_py30.txt new file mode 100644 index 0000000..cd594f5 --- /dev/null +++ b/pylint/test/messages/func_toolonglines_py30.txt @@ -0,0 +1,4 @@ +C: 1: Line too long (101/100) +C: 2: Line too long (104/100) +C: 17: Line too long (102/100) +C: 25: Line too long (105/100) diff --git a/test/messages/func_trailing_whitespace.txt b/pylint/test/messages/func_trailing_whitespace.txt index bfac360..bfac360 100644 --- a/test/messages/func_trailing_whitespace.txt +++ b/pylint/test/messages/func_trailing_whitespace.txt diff --git a/pylint/test/messages/func_typecheck_callfunc_assigment.txt b/pylint/test/messages/func_typecheck_callfunc_assigment.txt new file mode 100644 index 0000000..1d84510 --- /dev/null +++ b/pylint/test/messages/func_typecheck_callfunc_assigment.txt @@ -0,0 +1,3 @@ +E: 20: Assigning to function call which doesn't return +W: 28: Assigning to function call which only returns None +W: 35: Assigning to function call which only returns None diff --git a/pylint/test/messages/func_typecheck_getattr_py30.txt b/pylint/test/messages/func_typecheck_getattr_py30.txt new file mode 100644 index 0000000..b6bf150 --- /dev/null +++ b/pylint/test/messages/func_typecheck_getattr_py30.txt @@ -0,0 +1,9 @@ +E: 25:Client.__init__: Class 'Provider' has no 'cattribute' member +E: 35:Client.use_method: Instance of 'Provider' has no 'hophophop' member +E: 40:Client.use_attr: Instance of 'Provider' has no 'attribute' member +E: 52:Client.test_bt_types: Instance of 'list' has no 'apppend' member +E: 54:Client.test_bt_types: Instance of 'dict' has no 'set' member +E: 56:Client.test_bt_types: Instance of 'tuple' has no 'append' member +E: 58:Client.test_bt_types: Instance of 'str' has no 'loower' member +E: 62:Client.test_bt_types: Instance of 'int' has no 'whatever' member +E: 66: Instance of 'int' has no 'lower' member (but some types could not be inferred) diff --git a/pylint/test/messages/func_typecheck_non_callable_call.txt b/pylint/test/messages/func_typecheck_non_callable_call.txt new file mode 100644 index 0000000..8baa237 --- /dev/null +++ b/pylint/test/messages/func_typecheck_non_callable_call.txt @@ -0,0 +1,8 @@ +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 +E: 72: PROP.test is not callable +E: 73: PROP.custom is not callable
\ No newline at end of file diff --git a/pylint/test/messages/func_undefined_metaclass_var_py30.txt b/pylint/test/messages/func_undefined_metaclass_var_py30.txt new file mode 100644 index 0000000..a82ac6a --- /dev/null +++ b/pylint/test/messages/func_undefined_metaclass_var_py30.txt @@ -0,0 +1,2 @@ +E: 8:Bad: Undefined variable 'ABCMet'
+E: 11:SecondBad: Undefined variable 'ab.ABCMeta'
\ No newline at end of file diff --git a/pylint/test/messages/func_unicode_literal_py26.txt b/pylint/test/messages/func_unicode_literal_py26.txt new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/pylint/test/messages/func_unicode_literal_py26.txt diff --git a/pylint/test/messages/func_unicode_literal_py274.txt b/pylint/test/messages/func_unicode_literal_py274.txt new file mode 100644 index 0000000..25efa99 --- /dev/null +++ b/pylint/test/messages/func_unicode_literal_py274.txt @@ -0,0 +1 @@ +W: 6: Anomalous Unicode escape in byte string: '\u'. String constant might be missing an r or u prefix. diff --git a/test/messages/func_unreachable.txt b/pylint/test/messages/func_unreachable.txt index bb25be0..bb25be0 100644 --- a/test/messages/func_unreachable.txt +++ b/pylint/test/messages/func_unreachable.txt diff --git a/pylint/test/messages/func_unused_import_py30.txt b/pylint/test/messages/func_unused_import_py30.txt new file mode 100644 index 0000000..1868abc --- /dev/null +++ b/pylint/test/messages/func_unused_import_py30.txt @@ -0,0 +1 @@ +W: 8: Reimport 'ABCMeta' (imported line 7)
diff --git a/test/messages/func_unused_overridden_argument.txt b/pylint/test/messages/func_unused_overridden_argument.txt index d7da45b..d7da45b 100644 --- a/test/messages/func_unused_overridden_argument.txt +++ b/pylint/test/messages/func_unused_overridden_argument.txt diff --git a/test/messages/func_use_for_or_listcomp_var_py29.txt b/pylint/test/messages/func_use_for_or_listcomp_var_py29.txt index 14d61ad..14d61ad 100644 --- a/test/messages/func_use_for_or_listcomp_var_py29.txt +++ b/pylint/test/messages/func_use_for_or_listcomp_var_py29.txt diff --git a/test/messages/func_use_for_or_listcomp_var_py30.txt b/pylint/test/messages/func_use_for_or_listcomp_var_py30.txt index 46d3430..46d3430 100644 --- a/test/messages/func_use_for_or_listcomp_var_py30.txt +++ b/pylint/test/messages/func_use_for_or_listcomp_var_py30.txt diff --git a/pylint/test/messages/func_used_before_assignment_py30.txt b/pylint/test/messages/func_used_before_assignment_py30.txt new file mode 100644 index 0000000..8bb131d --- /dev/null +++ b/pylint/test/messages/func_used_before_assignment_py30.txt @@ -0,0 +1,6 @@ +E: 18:test_fail.wrap: Using variable 'cnt' before assignment
+E: 27:test_fail2.wrap: Using variable 'cnt' before assignment
+E: 30:test_fail3: Using variable 'test_fail4' before assignment
+E: 34:test_fail4: Using variable 'test_fail5' before assignment
+E: 34:test_fail4: Using variable 'undefined' before assignment
+E: 40:test_fail5: Using variable 'undefined1' before assignment
\ No newline at end of file diff --git a/pylint/test/messages/func_variables_unused_name_from_wilcard_import.txt b/pylint/test/messages/func_variables_unused_name_from_wilcard_import.txt new file mode 100644 index 0000000..01c57af --- /dev/null +++ b/pylint/test/messages/func_variables_unused_name_from_wilcard_import.txt @@ -0,0 +1,4 @@ +W: 3: Unused import NonRegr from wildcard import +W: 3: Unused import os from wildcard import +W: 3: Unused import sys from wildcard import +W: 3: Wildcard import input.func_w0611 diff --git a/test/messages/func_w0101.txt b/pylint/test/messages/func_w0101.txt index c42ec2c..c42ec2c 100644 --- a/test/messages/func_w0101.txt +++ b/pylint/test/messages/func_w0101.txt diff --git a/test/messages/func_w0102.txt b/pylint/test/messages/func_w0102.txt index 40b6190..40b6190 100644 --- a/test/messages/func_w0102.txt +++ b/pylint/test/messages/func_w0102.txt diff --git a/test/messages/func_w0103.txt b/pylint/test/messages/func_w0103.txt index 0d6da42..0d6da42 100644 --- a/test/messages/func_w0103.txt +++ b/pylint/test/messages/func_w0103.txt diff --git a/test/messages/func_w0104.txt b/pylint/test/messages/func_w0104.txt index 71f6f62..71f6f62 100644 --- a/test/messages/func_w0104.txt +++ b/pylint/test/messages/func_w0104.txt diff --git a/test/messages/func_w0105.txt b/pylint/test/messages/func_w0105.txt index d664dd4..d664dd4 100644 --- a/test/messages/func_w0105.txt +++ b/pylint/test/messages/func_w0105.txt diff --git a/test/messages/func_w0110.txt b/pylint/test/messages/func_w0110.txt index b61400e..b61400e 100644 --- a/test/messages/func_w0110.txt +++ b/pylint/test/messages/func_w0110.txt diff --git a/test/messages/func_w0111.txt b/pylint/test/messages/func_w0111.txt index b2d794b..b2d794b 100644 --- a/test/messages/func_w0111.txt +++ b/pylint/test/messages/func_w0111.txt diff --git a/test/messages/func_w0112.txt b/pylint/test/messages/func_w0112.txt index 19b2da5..19b2da5 100644 --- a/test/messages/func_w0112.txt +++ b/pylint/test/messages/func_w0112.txt diff --git a/test/messages/func_w0122_py_30.txt b/pylint/test/messages/func_w0122_py_30.txt index d833076..d833076 100644 --- a/test/messages/func_w0122_py_30.txt +++ b/pylint/test/messages/func_w0122_py_30.txt diff --git a/test/messages/func_w0151.txt b/pylint/test/messages/func_w0151.txt index 9a53967..9a53967 100644 --- a/test/messages/func_w0151.txt +++ b/pylint/test/messages/func_w0151.txt diff --git a/pylint/test/messages/func_w0152_py29.txt b/pylint/test/messages/func_w0152_py29.txt new file mode 100644 index 0000000..484d5bf --- /dev/null +++ b/pylint/test/messages/func_w0152_py29.txt @@ -0,0 +1,2 @@ +W: 5: Used * or ** magic +W: 14: Used * or ** magic diff --git a/test/messages/func_w0152_py30.txt b/pylint/test/messages/func_w0152_py30.txt index 4bec00a..4bec00a 100644 --- a/test/messages/func_w0152_py30.txt +++ b/pylint/test/messages/func_w0152_py30.txt diff --git a/test/messages/func_w0202.txt b/pylint/test/messages/func_w0202.txt index d10e5bc..d10e5bc 100644 --- a/test/messages/func_w0202.txt +++ b/pylint/test/messages/func_w0202.txt diff --git a/pylint/test/messages/func_w0205.txt b/pylint/test/messages/func_w0205.txt new file mode 100644 index 0000000..83ac5de --- /dev/null +++ b/pylint/test/messages/func_w0205.txt @@ -0,0 +1,2 @@ +W: 22:Cdef.abcd: Signature differs from overridden 'abcd' method + diff --git a/pylint/test/messages/func_w0233.txt b/pylint/test/messages/func_w0233.txt new file mode 100644 index 0000000..157b270 --- /dev/null +++ b/pylint/test/messages/func_w0233.txt @@ -0,0 +1,5 @@ +E: 22:CCC: Module 'input.func_w0233' has no 'BBBB' member +E: 27:CCC.__init__: Module 'input.func_w0233' has no 'BBBB' member +F: 20: Unable to import 'nonexistant' +W: 12:AAAA.__init__: __init__ method from a non direct base class 'BBBBMixin' is called +W: 48:Super2.__init__: __init__ method from base class 'dict' is not called
\ No newline at end of file diff --git a/pylint/test/messages/func_w0302.txt b/pylint/test/messages/func_w0302.txt new file mode 100644 index 0000000..26d509c --- /dev/null +++ b/pylint/test/messages/func_w0302.txt @@ -0,0 +1,2 @@ +C: 1: Too many lines in module (1016/1000) + diff --git a/test/messages/func_w0312.txt b/pylint/test/messages/func_w0312.txt index 917e8d0..917e8d0 100644 --- a/test/messages/func_w0312.txt +++ b/pylint/test/messages/func_w0312.txt diff --git a/test/messages/func_w0332_py_30.txt b/pylint/test/messages/func_w0332_py_30.txt index 16f1d80..16f1d80 100644 --- a/test/messages/func_w0332_py_30.txt +++ b/pylint/test/messages/func_w0332_py_30.txt diff --git a/test/messages/func_w0401.txt b/pylint/test/messages/func_w0401.txt index 74b14dc..74b14dc 100644 --- a/test/messages/func_w0401.txt +++ b/pylint/test/messages/func_w0401.txt diff --git a/test/messages/func_w0401_package.txt b/pylint/test/messages/func_w0401_package.txt index 4b1145b..4b1145b 100644 --- a/test/messages/func_w0401_package.txt +++ b/pylint/test/messages/func_w0401_package.txt diff --git a/test/messages/func_w0402.txt b/pylint/test/messages/func_w0402.txt index 453fc06..453fc06 100644 --- a/test/messages/func_w0402.txt +++ b/pylint/test/messages/func_w0402.txt diff --git a/pylint/test/messages/func_w0404.txt b/pylint/test/messages/func_w0404.txt new file mode 100644 index 0000000..cd7f3e2 --- /dev/null +++ b/pylint/test/messages/func_w0404.txt @@ -0,0 +1,5 @@ +W: 8: Reimport 'ElementTree' (imported line 7) +W: 11: Reimport 'email.encoders' (imported line 10) +W: 13: Reimport 'sys' (imported line 5) +W: 23:reimport: Redefining name 'sys' from outer scope (line 5) +W: 23:reimport: Reimport 'sys' (imported line 5) diff --git a/test/messages/func_w0405.txt b/pylint/test/messages/func_w0405.txt index 9555dfd..9555dfd 100644 --- a/test/messages/func_w0405.txt +++ b/pylint/test/messages/func_w0405.txt diff --git a/pylint/test/messages/func_w0406.txt b/pylint/test/messages/func_w0406.txt new file mode 100644 index 0000000..8779d98 --- /dev/null +++ b/pylint/test/messages/func_w0406.txt @@ -0,0 +1 @@ +W: 6: Module import itself diff --git a/pylint/test/messages/func_w0611.txt b/pylint/test/messages/func_w0611.txt new file mode 100644 index 0000000..2be0d0a --- /dev/null +++ b/pylint/test/messages/func_w0611.txt @@ -0,0 +1 @@ +W: 7: Unused import os diff --git a/pylint/test/messages/func_w0612.txt b/pylint/test/messages/func_w0612.txt new file mode 100644 index 0000000..c81b4f9 --- /dev/null +++ b/pylint/test/messages/func_w0612.txt @@ -0,0 +1,6 @@ +W: 9:function: Unused variable 'aaaa' +W: 10:function: Unused variable 'index' +W: 34:test_global: Unused variable 'platform' +W: 35:test_global: Unused variable 'VERSION' +W: 36:test_global: Unused variable 'this' +W: 37:test_global: Unused variable 'RE' diff --git a/test/messages/func_w0613.txt b/pylint/test/messages/func_w0613.txt index 36cb380..36cb380 100644 --- a/test/messages/func_w0613.txt +++ b/pylint/test/messages/func_w0613.txt diff --git a/test/messages/func_w0622.txt b/pylint/test/messages/func_w0622.txt index 7191347..7191347 100644 --- a/test/messages/func_w0622.txt +++ b/pylint/test/messages/func_w0622.txt diff --git a/test/messages/func_w0623.txt b/pylint/test/messages/func_w0623.txt index b764def..b764def 100644 --- a/test/messages/func_w0623.txt +++ b/pylint/test/messages/func_w0623.txt diff --git a/test/messages/func_w0623_py30.txt b/pylint/test/messages/func_w0623_py30.txt index adcd208..adcd208 100644 --- a/test/messages/func_w0623_py30.txt +++ b/pylint/test/messages/func_w0623_py30.txt diff --git a/test/messages/func_w0623_py_30.txt b/pylint/test/messages/func_w0623_py_30.txt index a2923f1..a2923f1 100644 --- a/test/messages/func_w0623_py_30.txt +++ b/pylint/test/messages/func_w0623_py_30.txt diff --git a/test/messages/func_w0631.txt b/pylint/test/messages/func_w0631.txt index af335a1..af335a1 100644 --- a/test/messages/func_w0631.txt +++ b/pylint/test/messages/func_w0631.txt diff --git a/pylint/test/messages/func_w0702.txt b/pylint/test/messages/func_w0702.txt new file mode 100644 index 0000000..d40a837 --- /dev/null +++ b/pylint/test/messages/func_w0702.txt @@ -0,0 +1,2 @@ +W: 9: No exception type(s) specified +W: 16: Catching too general exception Exception diff --git a/test/messages/func_w0703.txt b/pylint/test/messages/func_w0703.txt index 6135664..6135664 100644 --- a/test/messages/func_w0703.txt +++ b/pylint/test/messages/func_w0703.txt diff --git a/test/messages/func_w0704.txt b/pylint/test/messages/func_w0704.txt index 1eca794..1eca794 100644 --- a/test/messages/func_w0704.txt +++ b/pylint/test/messages/func_w0704.txt diff --git a/pylint/test/messages/func_w0705.txt b/pylint/test/messages/func_w0705.txt new file mode 100644 index 0000000..0393a88 --- /dev/null +++ b/pylint/test/messages/func_w0705.txt @@ -0,0 +1,15 @@ +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 appear last) +W: 8: Catching too general exception Exception +W: 29: No exception type(s) specified +W: 30: Except doesn't do anything +W: 31: Catching too general exception Exception +W: 31: Except doesn't do anything +W: 38: No exception type(s) specified +W: 43: Catching too general exception Exception +W: 43: Except doesn't do anything +W: 45: No exception type(s) specified +W: 46: Except doesn't do anything
\ No newline at end of file diff --git a/test/messages/func_w0801.txt b/pylint/test/messages/func_w0801.txt index 203ce92..203ce92 100644 --- a/test/messages/func_w0801.txt +++ b/pylint/test/messages/func_w0801.txt diff --git a/test/messages/func_w1201.txt b/pylint/test/messages/func_w1201.txt index 45a742e..45a742e 100644 --- a/test/messages/func_w1201.txt +++ b/pylint/test/messages/func_w1201.txt diff --git a/pylint/test/messages/func_w1202.txt b/pylint/test/messages/func_w1202.txt new file mode 100644 index 0000000..9dbc26b --- /dev/null +++ b/pylint/test/messages/func_w1202.txt @@ -0,0 +1,4 @@ +W: 16: Use % formatting in logging functions but pass the % parameters as arguments +W: 17: Use % formatting in logging functions but pass the % parameters as arguments +W: 18: Use % formatting in logging functions but pass the % parameters as arguments +W: 19: Use % formatting in logging functions but pass the % parameters as arguments diff --git a/test/messages/func_with_without_as_py25.txt b/pylint/test/messages/func_with_without_as_py25.txt index 18ca371..18ca371 100644 --- a/test/messages/func_with_without_as_py25.txt +++ b/pylint/test/messages/func_with_without_as_py25.txt diff --git a/test/regrtest_data/absimp/__init__.py b/pylint/test/regrtest_data/absimp/__init__.py index b98444d..b98444d 100644 --- a/test/regrtest_data/absimp/__init__.py +++ b/pylint/test/regrtest_data/absimp/__init__.py diff --git a/pylint/test/regrtest_data/absimp/string.py b/pylint/test/regrtest_data/absimp/string.py new file mode 100644 index 0000000..f47e9a5 --- /dev/null +++ b/pylint/test/regrtest_data/absimp/string.py @@ -0,0 +1,7 @@ +""" +http://www.logilab.org/ticket/70495 +http://www.logilab.org/ticket/70565 +""" +from __future__ import absolute_import, print_function +import string +print(string) diff --git a/test/regrtest_data/application_crash.py b/pylint/test/regrtest_data/application_crash.py index 6e6044a..6e6044a 100644 --- a/test/regrtest_data/application_crash.py +++ b/pylint/test/regrtest_data/application_crash.py diff --git a/test/regrtest_data/classdoc_usage.py b/pylint/test/regrtest_data/classdoc_usage.py index 7c30f7e..7c30f7e 100644 --- a/test/regrtest_data/classdoc_usage.py +++ b/pylint/test/regrtest_data/classdoc_usage.py diff --git a/pylint/test/regrtest_data/decimal_inference.py b/pylint/test/regrtest_data/decimal_inference.py new file mode 100644 index 0000000..00c9130 --- /dev/null +++ b/pylint/test/regrtest_data/decimal_inference.py @@ -0,0 +1,10 @@ +"""hum E1011 on .prec member is justifiable since Context instance are built +using setattr/locals :( + +2007/02/17 update: .prec attribute is now detected by astroid :o) +""" +from __future__ import print_function +import decimal + +decimal.getcontext().prec = 200 +print(decimal.getcontext().prec) diff --git a/test/regrtest_data/descriptor_crash.py b/pylint/test/regrtest_data/descriptor_crash.py index 4b3adcc..4b3adcc 100644 --- a/test/regrtest_data/descriptor_crash.py +++ b/pylint/test/regrtest_data/descriptor_crash.py diff --git a/test/regrtest_data/import_assign.py b/pylint/test/regrtest_data/import_assign.py index c01cd52..c01cd52 100644 --- a/test/regrtest_data/import_assign.py +++ b/pylint/test/regrtest_data/import_assign.py diff --git a/test/regrtest_data/import_package_subpackage_module.py b/pylint/test/regrtest_data/import_package_subpackage_module.py index 937c8c3..937c8c3 100644 --- a/test/regrtest_data/import_package_subpackage_module.py +++ b/pylint/test/regrtest_data/import_package_subpackage_module.py diff --git a/test/regrtest_data/module_global.py b/pylint/test/regrtest_data/module_global.py index 107d652..107d652 100644 --- a/test/regrtest_data/module_global.py +++ b/pylint/test/regrtest_data/module_global.py diff --git a/test/regrtest_data/no_stdout_encoding.py b/pylint/test/regrtest_data/no_stdout_encoding.py index 002d19b..002d19b 100644 --- a/test/regrtest_data/no_stdout_encoding.py +++ b/pylint/test/regrtest_data/no_stdout_encoding.py diff --git a/test/regrtest_data/numarray_import.py b/pylint/test/regrtest_data/numarray_import.py index e89757f..e89757f 100644 --- a/test/regrtest_data/numarray_import.py +++ b/pylint/test/regrtest_data/numarray_import.py diff --git a/test/regrtest_data/numarray_inf.py b/pylint/test/regrtest_data/numarray_inf.py index 4ea22a9..4ea22a9 100644 --- a/test/regrtest_data/numarray_inf.py +++ b/pylint/test/regrtest_data/numarray_inf.py diff --git a/test/regrtest_data/package/AudioTime.py b/pylint/test/regrtest_data/package/AudioTime.py index a1fde96..a1fde96 100644 --- a/test/regrtest_data/package/AudioTime.py +++ b/pylint/test/regrtest_data/package/AudioTime.py diff --git a/test/regrtest_data/package/__init__.py b/pylint/test/regrtest_data/package/__init__.py index 7041268..7041268 100644 --- a/test/regrtest_data/package/__init__.py +++ b/pylint/test/regrtest_data/package/__init__.py diff --git a/test/regrtest_data/package/subpackage/__init__.py b/pylint/test/regrtest_data/package/subpackage/__init__.py index dc4782e..dc4782e 100644 --- a/test/regrtest_data/package/subpackage/__init__.py +++ b/pylint/test/regrtest_data/package/subpackage/__init__.py diff --git a/test/regrtest_data/package/subpackage/module.py b/pylint/test/regrtest_data/package/subpackage/module.py index 4b7244b..4b7244b 100644 --- a/test/regrtest_data/package/subpackage/module.py +++ b/pylint/test/regrtest_data/package/subpackage/module.py diff --git a/test/regrtest_data/package_all/__init__.py b/pylint/test/regrtest_data/package_all/__init__.py index 4e3696b..4e3696b 100644 --- a/test/regrtest_data/package_all/__init__.py +++ b/pylint/test/regrtest_data/package_all/__init__.py diff --git a/test/regrtest_data/package_all/notmissing.py b/pylint/test/regrtest_data/package_all/notmissing.py index 7cf8543..7cf8543 100644 --- a/test/regrtest_data/package_all/notmissing.py +++ b/pylint/test/regrtest_data/package_all/notmissing.py diff --git a/pylint/test/regrtest_data/precedence_test.py b/pylint/test/regrtest_data/precedence_test.py new file mode 100644 index 0000000..260eeb3 --- /dev/null +++ b/pylint/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 + +""" +from __future__ import print_function +__revision__ = 0 + +from package import AudioTime + +print(AudioTime.DECIMAL) diff --git a/test/regrtest_data/special_attr_scope_lookup_crash.py b/pylint/test/regrtest_data/special_attr_scope_lookup_crash.py index b693a9f..b693a9f 100644 --- a/test/regrtest_data/special_attr_scope_lookup_crash.py +++ b/pylint/test/regrtest_data/special_attr_scope_lookup_crash.py diff --git a/test/regrtest_data/try_finally_disable_msg_crash.py b/pylint/test/regrtest_data/try_finally_disable_msg_crash.py index 1719308..1719308 100644 --- a/test/regrtest_data/try_finally_disable_msg_crash.py +++ b/pylint/test/regrtest_data/try_finally_disable_msg_crash.py diff --git a/pylint/test/test_func.py b/pylint/test/test_func.py new file mode 100644 index 0000000..f9b3522 --- /dev/null +++ b/pylint/test/test_func.py @@ -0,0 +1,95 @@ +# Copyright (c) 2003-2014 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +"""functional/non regression tests for pylint""" + +import unittest +import sys +import re + +from os import getcwd +from os.path import abspath, dirname, join + +from pylint.testutils import (make_tests, LintTestUsingModule, LintTestUsingFile, + LintTestUpdate, cb_test_gen, linter, test_reporter) + +PY3K = sys.version_info >= (3, 0) + +# Configure paths +INPUT_DIR = join(dirname(abspath(__file__)), 'input') +MSG_DIR = join(dirname(abspath(__file__)), 'messages') + +# Classes + +quote = "'" if sys.version_info >= (3, 3) else '' + +class LintTestNonExistentModuleTC(LintTestUsingModule): + module = 'nonexistent' + _get_expected = lambda self: 'F: 1: No module named %snonexistent%s\n' % (quote, quote) + + + +class LintBuiltinModuleTest(LintTestUsingModule): + output = join(MSG_DIR, 'builtin_module.txt') + module = 'sys' + def test_functionality(self): + self._test(['sys']) + + +def gen_tests(filter_rgx): + if UPDATE: + callbacks = [cb_test_gen(LintTestUpdate)] + else: + callbacks = [cb_test_gen(LintTestUsingModule)] + tests = make_tests(INPUT_DIR, MSG_DIR, filter_rgx, callbacks) + if UPDATE: + return tests + + if filter_rgx: + is_to_run = re.compile(filter_rgx).search + else: + is_to_run = lambda x: 1 + + if is_to_run('nonexistent'): + tests.append(LintTestNonExistentModuleTC) + + tests.append(LintBuiltinModuleTest) + + assert len(tests) < 196, "Please do not add new test cases here." + return tests + +# Create suite + +FILTER_RGX = None +UPDATE = False + +def suite(): + return unittest.TestSuite([unittest.makeSuite(test, suiteClass=unittest.TestSuite) + for test in gen_tests(FILTER_RGX)]) + + +def load_tests(loader, tests, pattern): + return suite() + + +if __name__=='__main__': + if '-u' in sys.argv: + UPDATE = True + sys.argv.remove('-u') + + if len(sys.argv) > 1: + FILTER_RGX = sys.argv[1] + del sys.argv[1] + unittest.main(defaultTest='suite') diff --git a/pylint/test/test_functional.py b/pylint/test/test_functional.py new file mode 100644 index 0000000..f9e33fa --- /dev/null +++ b/pylint/test/test_functional.py @@ -0,0 +1,369 @@ +"""Functional full-module tests for PyLint.""" +from __future__ import unicode_literals +import csv +import collections +import io +import operator +import os +import re +import sys +import platform +import unittest + +import six +from six.moves import configparser + +from pylint import checkers +from pylint import interfaces +from pylint import lint +from pylint import reporters +from pylint import utils + +class test_dialect(csv.excel): + if sys.version_info[0] < 3: + delimiter = b':' + lineterminator = b'\n' + else: + delimiter = ':' + lineterminator = '\n' + + +csv.register_dialect('test', test_dialect) + + +class NoFileError(Exception): + pass + +# Notes: +# - for the purpose of this test, the confidence levels HIGH and UNDEFINED +# are treated as the same. + +# TODOs +# - implement exhaustivity tests + +# If message files should be updated instead of checked. +UPDATE = False + +class OutputLine(collections.namedtuple('OutputLine', + ['symbol', 'lineno', 'object', 'msg', 'confidence'])): + @classmethod + def from_msg(cls, msg): + return cls( + msg.symbol, msg.line, msg.obj or '', msg.msg.replace("\r\n", "\n"), + msg.confidence.name + if msg.confidence != interfaces.UNDEFINED else interfaces.HIGH.name) + + @classmethod + def from_csv(cls, row): + confidence = row[4] if len(row) == 5 else interfaces.HIGH.name + return cls(row[0], int(row[1]), row[2], row[3], confidence) + + def to_csv(self): + if self.confidence == interfaces.HIGH.name: + return self[:-1] + else: + return self + + +# Common sub-expressions. +_MESSAGE = {'msg': r'[a-z][a-z\-]+'} +# Matches a #, +# - followed by a comparison operator and a Python version (optional), +# - followed by an line number with a +/- (optional), +# - followed by a list of bracketed message symbols. +# Used to extract expected messages from testdata files. +_EXPECTED_RE = re.compile( + r'\s*#\s*(?:(?P<line>[+-]?[0-9]+):)?' + r'(?:(?P<op>[><=]+) *(?P<version>[0-9.]+):)?' + r'\s*\[(?P<msgs>%(msg)s(?:,\s*%(msg)s)*)\]' % _MESSAGE) + + +def parse_python_version(str): + return tuple(int(digit) for digit in str.split('.')) + + +class TestReporter(reporters.BaseReporter): + def handle_message(self, msg): + self.messages.append(msg) + + def on_set_current_module(self, module, filepath): + self.messages = [] + + def display_results(self, layout): + """Ignore layouts.""" + + +class TestFile(object): + """A single functional test case file with options.""" + + _CONVERTERS = { + 'min_pyver': parse_python_version, + 'max_pyver': parse_python_version, + 'requires': lambda s: s.split(',') + } + + + def __init__(self, directory, filename): + self._directory = directory + self.base = filename.replace('.py', '') + self.options = { + 'min_pyver': (2, 5), + 'max_pyver': (4, 0), + 'requires': [], + 'except_implementations': [], + } + self._parse_options() + + def _parse_options(self): + cp = configparser.ConfigParser() + cp.add_section('testoptions') + try: + cp.read(self.option_file) + except NoFileError: + pass + + for name, value in cp.items('testoptions'): + conv = self._CONVERTERS.get(name, lambda v: v) + self.options[name] = conv(value) + + @property + def option_file(self): + return self._file_type('.rc') + + @property + def module(self): + package = os.path.basename(self._directory) + return '.'.join([package, self.base]) + + @property + def expected_output(self): + return self._file_type('.txt', check_exists=False) + + @property + def source(self): + return self._file_type('.py') + + def _file_type(self, ext, check_exists=True): + name = os.path.join(self._directory, self.base + ext) + if not check_exists or os.path.exists(name): + return name + else: + raise NoFileError + + +_OPERATORS = { + '>': operator.gt, + '<': operator.lt, + '>=': operator.ge, + '<=': operator.le, +} + +def parse_expected_output(stream): + return [OutputLine.from_csv(row) for row in csv.reader(stream, 'test')] + + +def get_expected_messages(stream): + """Parses a file and get expected messages. + + :param stream: File-like input stream. + :returns: A dict mapping line,msg-symbol tuples to the count on this line. + """ + messages = collections.Counter() + for i, line in enumerate(stream): + match = _EXPECTED_RE.search(line) + if match is None: + continue + line = match.group('line') + if line is None: + line = i + 1 + elif line.startswith('+') or line.startswith('-'): + line = i + 1 + int(line) + else: + line = int(line) + + version = match.group('version') + op = match.group('op') + if version: + required = parse_python_version(version) + if not _OPERATORS[op](sys.version_info, required): + continue + + for msg_id in match.group('msgs').split(','): + messages[line, msg_id.strip()] += 1 + return messages + + +def multiset_difference(left_op, right_op): + """Takes two multisets and compares them. + + A multiset is a dict with the cardinality of the key as the value. + + :param left_op: The expected entries. + :param right_op: Actual entries. + + :returns: The two multisets of missing and unexpected messages. + """ + missing = left_op.copy() + missing.subtract(right_op) + unexpected = {} + for key, value in list(six.iteritems(missing)): + if value <= 0: + missing.pop(key) + if value < 0: + unexpected[key] = -value + return missing, unexpected + + +class LintModuleTest(unittest.TestCase): + maxDiff = None + + def __init__(self, test_file): + super(LintModuleTest, self).__init__('_runTest') + test_reporter = TestReporter() + self._linter = lint.PyLinter() + self._linter.set_reporter(test_reporter) + self._linter.config.persistent = 0 + checkers.initialize(self._linter) + self._linter.disable('I') + try: + self._linter.load_file_configuration(test_file.option_file) + except NoFileError: + pass + self._test_file = test_file + + def setUp(self): + if (sys.version_info < self._test_file.options['min_pyver'] + or sys.version_info >= self._test_file.options['max_pyver']): + self.skipTest( + 'Test cannot run with Python %s.' % (sys.version.split(' ')[0],)) + missing = [] + for req in self._test_file.options['requires']: + try: + __import__(req) + except ImportError: + missing.append(req) + if missing: + self.skipTest('Requires %s to be present.' % (','.join(missing),)) + if self._test_file.options['except_implementations']: + implementations = [ + item.strip for item in + self._test_file.options['except_implementations'].split(",") + ] + implementation = platform.python_implementation() + if implementation not in implementations: + self.skipTest( + 'Test cannot run with Python implementation %r' + % (implementation, )) + + def __str__(self): + return "%s (%s.%s)" % (self._test_file.base, self.__class__.__module__, + self.__class__.__name__) + + def _open_expected_file(self): + return open(self._test_file.expected_output) + + def _open_source_file(self): + if self._test_file.base == "invalid_encoded_data": + return open(self._test_file.source) + else: + return io.open(self._test_file.source, encoding="utf8") + + def _get_expected(self): + with self._open_source_file() as fobj: + expected_msgs = get_expected_messages(fobj) + + if expected_msgs: + with self._open_expected_file() as fobj: + expected_output_lines = parse_expected_output(fobj) + else: + expected_output_lines = [] + return expected_msgs, expected_output_lines + + def _get_received(self): + messages = self._linter.reporter.messages + messages.sort(key=lambda m: (m.line, m.symbol, m.msg)) + received_msgs = collections.Counter() + received_output_lines = [] + for msg in messages: + received_msgs[msg.line, msg.symbol] += 1 + received_output_lines.append(OutputLine.from_msg(msg)) + return received_msgs, received_output_lines + + def _runTest(self): + self._linter.check([self._test_file.module]) + + expected_messages, expected_text = self._get_expected() + received_messages, received_text = self._get_received() + + if expected_messages != received_messages: + msg = ['Wrong results for file "%s":' % (self._test_file.base)] + missing, unexpected = multiset_difference(expected_messages, + received_messages) + if missing: + msg.append('\nExpected in testdata:') + msg.extend(' %3d: %s' % msg for msg in sorted(missing)) + if unexpected: + msg.append('\nUnexpected in testdata:') + msg.extend(' %3d: %s' % msg for msg in sorted(unexpected)) + self.fail('\n'.join(msg)) + self._check_output_text(expected_messages, expected_text, received_text) + + def _split_lines(self, expected_messages, lines): + emitted, omitted = [], [] + for msg in lines: + if (msg[1], msg[0]) in expected_messages: + emitted.append(msg) + else: + omitted.append(msg) + return emitted, omitted + + def _check_output_text(self, expected_messages, expected_lines, + received_lines): + self.assertSequenceEqual( + self._split_lines(expected_messages, expected_lines)[0], + received_lines) + + +class LintModuleOutputUpdate(LintModuleTest): + def _open_expected_file(self): + try: + return super(LintModuleOutputUpdate, self)._open_expected_file() + except IOError: + return io.StringIO() + + def _check_output_text(self, expected_messages, expected_lines, + received_lines): + if not expected_messages: + return + emitted, remaining = self._split_lines(expected_messages, expected_lines) + if emitted != received_lines: + remaining.extend(received_lines) + remaining.sort(key=lambda m: (m[1], m[0], m[3])) + with open(self._test_file.expected_output, 'w') as fobj: + writer = csv.writer(fobj, dialect='test') + for line in remaining: + writer.writerow(line.to_csv()) + +def suite(): + input_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), + 'functional') + suite = unittest.TestSuite() + for fname in os.listdir(input_dir): + if fname != '__init__.py' and fname.endswith('.py'): + test_file = TestFile(input_dir, fname) + if UPDATE: + suite.addTest(LintModuleOutputUpdate(test_file)) + else: + suite.addTest(LintModuleTest(test_file)) + return suite + + +def load_tests(loader, tests, pattern): + return suite() + + +if __name__=='__main__': + if '-u' in sys.argv: + UPDATE = True + sys.argv.remove('-u') + unittest.main(defaultTest='suite') diff --git a/pylint/test/test_import_graph.py b/pylint/test/test_import_graph.py new file mode 100644 index 0000000..2b41536 --- /dev/null +++ b/pylint/test/test_import_graph.py @@ -0,0 +1,68 @@ +import sys +import os +from os.path import exists +import unittest + +import six + +from pylint.checkers import initialize, imports +from pylint.lint import PyLinter + +from pylint.testutils 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']}) + with open(self.dest) as stream: + self.assertEqual(stream.read().strip(), + ''' +digraph "dependencies_graph" { +rankdir=LR +charset="utf-8" +URL="." node[shape="box"] +"hoho" []; +"yep" []; +"labas" []; +"yep" -> "hoho" []; +"hoho" -> "labas" []; +"yep" -> "labas" []; +} +'''.strip()) + +class ImportCheckerTC(unittest.TestCase): + def setUp(self): + self.linter = l = PyLinter(reporter=TestReporter()) + initialize(l) + + def test_checker_dep_graphs(self): + l = self.linter + l.global_set_option('persistent', False) + l.global_set_option('enable', 'imports') + 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') + l.global_set_option('int-import-graph', 'int_import.dot') + # ignore this file causing spurious MemoryError w/ some python version (>=2.3?) + l.global_set_option('ignore', ('func_unknown_encoding.py',)) + try: + l.check('input') + l.generate_reports() + self.assertTrue(exists('import.dot')) + self.assertTrue(exists('ext_import.dot')) + self.assertTrue(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/pylint/test/test_regr.py b/pylint/test/test_regr.py new file mode 100644 index 0000000..6fb0fc5 --- /dev/null +++ b/pylint/test/test_regr.py @@ -0,0 +1,142 @@ +# Copyright (c) 2005-2014 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +"""non regression tests for pylint, which requires a too specific configuration +to be incorporated in the automatic functional test framework +""" + +import sys +import os +from os.path import abspath, dirname, join +import unittest + +from pylint.testutils import TestReporter +from pylint.lint import PyLinter +from pylint import checkers + +test_reporter = TestReporter() +linter = PyLinter() +linter.set_reporter(test_reporter) +linter.disable('I') +linter.config.persistent = 0 +checkers.initialize(linter) + +REGR_DATA = join(dirname(abspath(__file__)), 'regrtest_data') +sys.path.insert(1, REGR_DATA) + +class NonRegrTC(unittest.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.assertEqual(got, '') + + def test_package___init___precedence(self): + linter.check('precedence_test') + got = linter.reporter.finalize().strip() + self.assertEqual(got, '') + + def test_check_package___init__(self): + for variation in ('package.__init__', join(REGR_DATA, 'package', '__init__.py')): + linter.check(variation) + got = linter.reporter.finalize().strip() + checked = linter.stats['by_module'].keys() + self.assertEqual(checked, ['package.__init__'], + '%s: %s' % (variation, checked)) + cwd = os.getcwd() + os.chdir(join(REGR_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.assertEqual(checked, ['__init__'], + '%s: %s' % (variation, checked)) + finally: + sys.path.pop(0) + os.chdir(cwd) + + def test_numarray_inference(self): + try: + from numarray import random_array + except ImportError: + self.skipTest('test skipped: numarray.random_array is not available') + linter.check(join(REGR_DATA, 'numarray_inf.py')) + got = linter.reporter.finalize().strip() + self.assertEqual(got, "E: 5: Instance of 'int' has no 'astype' member (but some types could not be inferred)") + + def test_numarray_import(self): + try: + import numarray + except ImportError: + self.skipTest('test skipped: numarray is not available') + linter.check(join(REGR_DATA, 'numarray_import.py')) + got = linter.reporter.finalize().strip() + self.assertEqual(got, '') + + def test_class__doc__usage(self): + linter.check(join(REGR_DATA, 'classdoc_usage.py')) + got = linter.reporter.finalize().strip() + self.assertEqual(got, '') + + def test_package_import_relative_subpackage_no_attribute_error(self): + linter.check('import_package_subpackage_module') + got = linter.reporter.finalize().strip() + self.assertEqual(got, '') + + def test_import_assign_crash(self): + linter.check(join(REGR_DATA, 'import_assign.py')) + + def test_special_attr_scope_lookup_crash(self): + linter.check(join(REGR_DATA, 'special_attr_scope_lookup_crash.py')) + + def test_module_global_crash(self): + linter.check(join(REGR_DATA, 'module_global.py')) + got = linter.reporter.finalize().strip() + self.assertEqual(got, '') + + def test_decimal_inference(self): + linter.check(join(REGR_DATA, 'decimal_inference.py')) + got = linter.reporter.finalize().strip() + self.assertEqual(got, "") + + def test_descriptor_crash(self): + for fname in os.listdir(REGR_DATA): + if fname.endswith('_crash.py'): + linter.check(join(REGR_DATA, fname)) + linter.reporter.finalize().strip() + + def test_try_finally_disable_msg_crash(self): + linter.check(join(REGR_DATA, 'try_finally_disable_msg_crash')) + + def test___path__(self): + linter.check('pylint.checkers.__init__') + messages = linter.reporter.finalize().strip() + self.assertFalse('__path__' in messages, messages) + + def test_absolute_import(self): + linter.check(join(REGR_DATA, 'absimp', 'string.py')) + got = linter.reporter.finalize().strip() + self.assertEqual(got, "") + + +if __name__ == '__main__': + unittest.main() diff --git a/pylint/test/test_self.py b/pylint/test/test_self.py new file mode 100644 index 0000000..e94d958 --- /dev/null +++ b/pylint/test/test_self.py @@ -0,0 +1,152 @@ +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. + +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +import sys +import os +from os.path import join, dirname, abspath +import tempfile +import unittest + +import six + +from pylint.lint import Run +from pylint.reporters import BaseReporter +from pylint.reporters.text import * +from pylint.reporters.html import HTMLReporter +from pylint.reporters.json import JSONReporter + +HERE = abspath(dirname(__file__)) + + +class MultiReporter(BaseReporter): + def __init__(self, reporters): + self._reporters = reporters + self.path_strip_prefix = os.getcwd() + os.sep + + def on_set_current_module(self, *args, **kwargs): + for rep in self._reporters: + rep.on_set_current_module(*args, **kwargs) + + def handle_message(self, msg): + for rep in self._reporters: + rep.handle_message(msg) + + def display_results(self, layout): + pass + + @property + def out(self): + return self._reporters[0].out + + @property + def linter(self): + return self._linter + + @linter.setter + def linter(self, value): + self._linter = value + for rep in self._reporters: + rep.linter = value + + +class RunTC(unittest.TestCase): + + def _runtest(self, args, reporter=None, out=None, code=28): + if out is None: + out = six.StringIO() + try: + sys.stderr = sys.stdout = out + try: + Run(args, reporter=reporter) + except SystemExit as ex: + if reporter: + output = reporter.out.getvalue() + elif hasattr(out, 'getvalue'): + output = out.getvalue() + else: + output = None + msg = 'expected output status %s, got %s' % (code, ex.code) + if output is not None: + msg = '%s. Below pylint output: \n%s' % (msg, output) + self.assertEqual(ex.code, code, msg) + else: + self.fail('expected system exit') + finally: + sys.stderr = sys.__stderr__ + sys.stdout = sys.__stdout__ + + def test_pkginfo(self): + """Make pylint check itself.""" + self._runtest(['pylint.__pkginfo__'], reporter=TextReporter(six.StringIO()), + code=0) + + def test_all(self): + """Make pylint check itself.""" + reporters = [ + TextReporter(six.StringIO()), + HTMLReporter(six.StringIO()), + ColorizedTextReporter(six.StringIO()), + JSONReporter(six.StringIO()) + ] + self._runtest(['pylint/test/functional/arguments.py'], + reporter=MultiReporter(reporters), code=1) + + def test_no_ext_file(self): + self._runtest([join(HERE, 'input', 'noext')], code=0) + + def test_w0704_ignored(self): + self._runtest([join(HERE, 'input', 'ignore_except_pass_by_default.py')], code=0) + + def test_generate_config_option(self): + self._runtest(['--generate-rcfile'], code=0) + + def test_help_message_option(self): + self._runtest(['--help-msg', 'W0101'], code=0) + + def test_error_help_message_option(self): + self._runtest(['--help-msg', 'WX101'], code=0) + + def test_error_missing_arguments(self): + self._runtest([], code=32) + + def test_no_out_encoding(self): + """test redirection of stdout with non ascii caracters + """ + #This test reproduces bug #48066 ; it happens when stdout is redirected + # through '>' : the sys.stdout.encoding becomes then None, and if the + # output contains non ascii, pylint will crash + if sys.version_info < (3, 0): + strio = tempfile.TemporaryFile() + else: + strio = six.StringIO() + assert strio.encoding is None + self._runtest([join(HERE, 'regrtest_data/no_stdout_encoding.py')], + out=strio) + + def test_parallel_execution(self): + self._runtest(['-j 2', 'pylint/test/functional/arguments.py', + 'pylint/test/functional/bad_continuation.py'], code=1) + + def test_parallel_execution_missing_arguments(self): + self._runtest(['-j 2', 'not_here', 'not_here_too'], code=1) + + def test_py3k_option(self): + # Test that --py3k flag works. + rc_code = 2 if six.PY2 else 0 + self._runtest([join(HERE, 'functional', 'unpacked_exceptions.py'), + '--py3k'], + code=rc_code) + + +if __name__ == '__main__': + unittest.main() diff --git a/pylint/test/unittest_checker_base.py b/pylint/test/unittest_checker_base.py new file mode 100644 index 0000000..b74ac5a --- /dev/null +++ b/pylint/test/unittest_checker_base.py @@ -0,0 +1,233 @@ +"""Unittest for the base checker.""" + +import re +import sys +import unittest + +from astroid import test_utils +from pylint.checkers import base +from pylint.testutils import CheckerTestCase, Message, set_config + + +class DocstringTest(CheckerTestCase): + CHECKER_CLASS = base.DocStringChecker + + def test_missing_docstring_module(self): + module = test_utils.build_module("something") + with self.assertAddsMessages(Message('missing-docstring', node=module, args=('module',))): + self.checker.visit_module(module) + + def test_missing_docstring_emtpy_module(self): + module = test_utils.build_module("") + with self.assertNoMessages(): + self.checker.visit_module(module) + + def test_empty_docstring_module(self): + module = test_utils.build_module("''''''") + with self.assertAddsMessages(Message('empty-docstring', node=module, args=('module',))): + self.checker.visit_module(module) + + def test_empty_docstring_function(self): + func = test_utils.extract_node(""" + def func(tion): + pass""") + with self.assertAddsMessages(Message('missing-docstring', node=func, args=('function',))): + self.checker.visit_function(func) + + @set_config(docstring_min_length=2) + def test_short_function_no_docstring(self): + func = test_utils.extract_node(""" + def func(tion): + pass""") + with self.assertNoMessages(): + self.checker.visit_function(func) + + @set_config(docstring_min_length=2) + def test_function_no_docstring_by_name(self): + func = test_utils.extract_node(""" + def __fun__(tion): + pass""") + with self.assertNoMessages(): + self.checker.visit_function(func) + + def test_class_no_docstring(self): + klass = test_utils.extract_node(""" + class Klass(object): + pass""") + with self.assertAddsMessages(Message('missing-docstring', node=klass, args=('class',))): + self.checker.visit_class(klass) + + +class NameCheckerTest(CheckerTestCase): + CHECKER_CLASS = base.NameChecker + CONFIG = { + 'bad_names': set(), + } + + @set_config(include_naming_hint=True) + def test_naming_hint(self): + const = test_utils.extract_node(""" + const = "CONSTANT" #@ + """) + with self.assertAddsMessages( + Message('invalid-name', node=const.targets[0], + args=('constant', 'const', ' (hint: (([A-Z_][A-Z0-9_]*)|(__.*__))$)'))): + self.checker.visit_assname(const.targets[0]) + + @set_config(include_naming_hint=True, + const_name_hint='CONSTANT') + def test_naming_hint_configured_hint(self): + const = test_utils.extract_node(""" + const = "CONSTANT" #@ + """) + with self.assertAddsMessages( + Message('invalid-name', node=const.targets[0], + args=('constant', 'const', ' (hint: CONSTANT)'))): + self.checker.visit_assname(const.targets[0]) + + @set_config(attr_rgx=re.compile('[A-Z]+')) + def test_property_names(self): + # If a method is annotated with @property, it's name should + # match the attr regex. Since by default the attribute regex is the same + # as the method regex, we override it here. + methods = test_utils.extract_node(""" + import abc + + class FooClass(object): + @property + def FOO(self): #@ + pass + + @property + def bar(self): #@ + pass + + @abc.abstractproperty + def BAZ(self): #@ + pass + """) + with self.assertNoMessages(): + self.checker.visit_function(methods[0]) + self.checker.visit_function(methods[2]) + with self.assertAddsMessages(Message('invalid-name', node=methods[1], + args=('attribute', 'bar', ''))): + self.checker.visit_function(methods[1]) + + @set_config(attr_rgx=re.compile('[A-Z]+')) + def test_property_setters(self): + method = test_utils.extract_node(""" + class FooClass(object): + @property + def foo(self): pass + + @foo.setter + def FOOSETTER(self): #@ + pass + """) + with self.assertNoMessages(): + self.checker.visit_function(method) + + def test_module_level_names(self): + assign = test_utils.extract_node(""" + import collections + Class = collections.namedtuple("a", ("b", "c")) #@ + """) + with self.assertNoMessages(): + self.checker.visit_assname(assign.targets[0]) + + assign = test_utils.extract_node(""" + class ClassA(object): + pass + ClassB = ClassA + """) + with self.assertNoMessages(): + self.checker.visit_assname(assign.targets[0]) + + module = test_utils.build_module(""" + def A(): + return 1, 2, 3 + CONSTA, CONSTB, CONSTC = A() + CONSTD = A()""") + with self.assertNoMessages(): + self.checker.visit_assname(module.body[1].targets[0].elts[0]) + self.checker.visit_assname(module.body[2].targets[0]) + + assign = test_utils.extract_node(""" + CONST = "12 34 ".rstrip().split()""") + with self.assertNoMessages(): + self.checker.visit_assname(assign.targets[0]) + + +class MultiNamingStyleTest(CheckerTestCase): + CHECKER_CLASS = base.NameChecker + + MULTI_STYLE_RE = re.compile('(?:(?P<UP>[A-Z]+)|(?P<down>[a-z]+))$') + + @set_config(class_rgx=MULTI_STYLE_RE) + def test_multi_name_detection_majority(self): + classes = test_utils.extract_node(""" + class classb(object): #@ + pass + class CLASSA(object): #@ + pass + class CLASSC(object): #@ + pass + """) + with self.assertAddsMessages(Message('invalid-name', node=classes[0], args=('class', 'classb', ''))): + for cls in classes: + self.checker.visit_class(cls) + self.checker.leave_module(cls.root) + + @set_config(class_rgx=MULTI_STYLE_RE) + def test_multi_name_detection_first_invalid(self): + classes = test_utils.extract_node(""" + class class_a(object): #@ + pass + class classb(object): #@ + pass + class CLASSC(object): #@ + pass + """) + with self.assertAddsMessages(Message('invalid-name', node=classes[0], args=('class', 'class_a', '')), + Message('invalid-name', node=classes[2], args=('class', 'CLASSC', ''))): + for cls in classes: + self.checker.visit_class(cls) + self.checker.leave_module(cls.root) + + @set_config(method_rgx=MULTI_STYLE_RE, + function_rgx=MULTI_STYLE_RE, + name_group=('function:method',)) + def test_multi_name_detection_group(self): + function_defs = test_utils.extract_node(""" + class First(object): + def func(self): #@ + pass + + def FUNC(): #@ + pass + """, module_name='test') + with self.assertAddsMessages(Message('invalid-name', node=function_defs[1], args=('function', 'FUNC', ''))): + for func in function_defs: + self.checker.visit_function(func) + self.checker.leave_module(func.root) + + @set_config(function_rgx=re.compile('(?:(?P<ignore>FOO)|(?P<UP>[A-Z]+)|(?P<down>[a-z]+))$')) + def test_multi_name_detection_exempt(self): + function_defs = test_utils.extract_node(""" + def FOO(): #@ + pass + def lower(): #@ + pass + def FOO(): #@ + pass + def UPPER(): #@ + pass + """) + with self.assertAddsMessages(Message('invalid-name', node=function_defs[3], args=('function', 'UPPER', ''))): + for func in function_defs: + self.checker.visit_function(func) + self.checker.leave_module(func.root) + + +if __name__ == '__main__': + unittest.main() diff --git a/pylint/test/unittest_checker_classes.py b/pylint/test/unittest_checker_classes.py new file mode 100644 index 0000000..f21cfc1 --- /dev/null +++ b/pylint/test/unittest_checker_classes.py @@ -0,0 +1,82 @@ +"""Unit tests for the variables checker.""" +import unittest +import sys + +from astroid import test_utils +from pylint.checkers import classes +from pylint.testutils import CheckerTestCase, Message, set_config + +class VariablesCheckerTC(CheckerTestCase): + + CHECKER_CLASS = classes.ClassChecker + + def test_bitbucket_issue_164(self): + """Issue 164 report a false negative for access-member-before-definition""" + n1, n2 = test_utils.extract_node(""" + class MyClass1(object): + def __init__(self): + self.first += 5 #@ + self.first = 0 #@ + """) + with self.assertAddsMessages(Message('access-member-before-definition', + node=n1.target, args=('first', n2.lineno))): + self.walk(n1.root()) + + @set_config(exclude_protected=('_meta', '_manager')) + def test_exclude_protected(self): + """Test that exclude-protected can be used to + exclude names from protected-access warning. + """ + + node = test_utils.build_module(""" + class Protected(object): + '''empty''' + def __init__(self): + self._meta = 42 + self._manager = 24 + self._teta = 29 + OBJ = Protected() + OBJ._meta + OBJ._manager + OBJ._teta + """) + with self.assertAddsMessages( + Message('protected-access', + node=node.body[-1].value, + args='_teta')): + self.walk(node.root()) + + @unittest.skipUnless(sys.version_info[0] == 3, + "The test works on Python 3.") + def test_regression_non_parent_init_called_tracemalloc(self): + # This used to raise a non-parent-init-called on Pylint 1.3 + # See issue https://bitbucket.org/logilab/pylint/issue/308/ + # for reference. + node = test_utils.extract_node(""" + from tracemalloc import Sequence + class _Traces(Sequence): + def __init__(self, traces): #@ + Sequence.__init__(self) + """) + with self.assertNoMessages(): + self.checker.visit_function(node) + + def test_super_init_not_called_regression(self): + # This should not emit a super-init-not-called + # warning. It previously did this, because + # ``next(node.infer())`` was used in that checker's + # logic and the first inferred node was an YES object, + # leading to this false positive. + node = test_utils.extract_node(""" + import ctypes + + class Foo(ctypes.BigEndianStructure): + def __init__(self): #@ + ctypes.BigEndianStructure.__init__(self) + """) + with self.assertNoMessages(): + self.checker.visit_function(node) + + +if __name__ == '__main__': + unittest.main() diff --git a/pylint/test/unittest_checker_exceptions.py b/pylint/test/unittest_checker_exceptions.py new file mode 100644 index 0000000..544e179 --- /dev/null +++ b/pylint/test/unittest_checker_exceptions.py @@ -0,0 +1,61 @@ +# Copyright (c) 2003-2015 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +"""Tests for pylint.checkers.exceptions.""" + +import sys +import unittest + +from astroid import test_utils +from pylint.checkers import exceptions +from pylint.testutils import CheckerTestCase, Message + + +class ExceptionsCheckerTest(CheckerTestCase): + """Tests for pylint.checkers.exceptions.""" + + CHECKER_CLASS = exceptions.ExceptionsChecker + + # These tests aren't in the functional test suite, + # since they will be converted with 2to3 for Python 3 + # and `raise (Error, ...)` will be converted to + # `raise Error(...)`, so it beats the purpose of the test. + + @unittest.skipUnless(sys.version_info[0] == 3, + "The test should emit an error on Python 3.") + def test_raising_bad_type_python3(self): + node = test_utils.extract_node('raise (ZeroDivisionError, None) #@') + message = Message('raising-bad-type', node=node, args='tuple') + with self.assertAddsMessages(message): + self.checker.visit_raise(node) + + @unittest.skipUnless(sys.version_info[0] == 2, + "The test is valid only on Python 2.") + def test_raising_bad_type_python2(self): + nodes = test_utils.extract_node(''' + raise (ZeroDivisionError, None) #@ + from something import something + raise (something, None) #@ + + raise (4, None) #@ + ''') + with self.assertNoMessages(): + self.checker.visit_raise(nodes[0]) + with self.assertNoMessages(): + self.checker.visit_raise(nodes[1]) + + message = Message('raising-bad-type', node=nodes[2], args='tuple') + with self.assertAddsMessages(message): + self.checker.visit_raise(nodes[2]) diff --git a/pylint/test/unittest_checker_format.py b/pylint/test/unittest_checker_format.py new file mode 100644 index 0000000..7d5a32f --- /dev/null +++ b/pylint/test/unittest_checker_format.py @@ -0,0 +1,253 @@ +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +""" Copyright (c) 2000-2011 LOGILAB S.A. (Paris, FRANCE). + http://www.logilab.fr/ -- mailto:contact@logilab.fr + +Check format checker helper functions +""" +from __future__ import unicode_literals +from os import linesep +import re +import sys + +from astroid import test_utils + +from pylint.checkers.format import * + +from pylint.testutils import ( + CheckerTestCase, Message, set_config, tokenize_str, +) + + +class MultiStatementLineTest(CheckerTestCase): + CHECKER_CLASS = FormatChecker + + def testSingleLineIfStmts(self): + stmt = test_utils.extract_node(""" + if True: pass #@ + """) + with self.assertAddsMessages(Message('multiple-statements', node=stmt.body[0])): + self.checker.process_tokens([]) + self.checker.visit_default(stmt.body[0]) + self.checker.config.single_line_if_stmt = True + with self.assertNoMessages(): + self.checker.process_tokens([]) + self.checker.visit_default(stmt.body[0]) + stmt = test_utils.extract_node(""" + if True: pass #@ + else: + pass + """) + with self.assertAddsMessages(Message('multiple-statements', node=stmt.body[0])): + self.checker.process_tokens([]) + self.checker.visit_default(stmt.body[0]) + + def testTryExceptFinallyNoMultipleStatement(self): + tree = test_utils.extract_node(""" + try: #@ + pass + except: + pass + finally: + pass""") + with self.assertNoMessages(): + self.checker.process_tokens([]) + self.checker.visit_default(tree.body[0]) + + + +class SuperfluousParenthesesTest(CheckerTestCase): + CHECKER_CLASS = FormatChecker + + def testCheckKeywordParensHandlesValidCases(self): + self.checker._keywords_with_parens = set() + cases = [ + 'if foo:', + 'if foo():', + 'if (x and y) or z:', + 'assert foo()', + 'assert ()', + 'if (1, 2) in (3, 4):', + 'if (a or b) in c:', + 'return (x for x in x)', + 'if (x for x in x):', + 'for x in (x for x in x):', + 'not (foo or bar)', + 'not (foo or bar) and baz', + ] + with self.assertNoMessages(): + for code in cases: + self.checker._check_keyword_parentheses(tokenize_str(code), 0) + + def testCheckKeywordParensHandlesUnnecessaryParens(self): + self.checker._keywords_with_parens = set() + cases = [ + (Message('superfluous-parens', line=1, args='if'), + 'if (foo):', 0), + (Message('superfluous-parens', line=1, args='if'), + 'if ((foo, bar)):', 0), + (Message('superfluous-parens', line=1, args='if'), + 'if (foo(bar)):', 0), + (Message('superfluous-parens', line=1, args='return'), + 'return ((x for x in x))', 0), + (Message('superfluous-parens', line=1, args='not'), + 'not (foo)', 0), + (Message('superfluous-parens', line=1, args='not'), + 'if not (foo):', 1), + (Message('superfluous-parens', line=1, args='if'), + 'if (not (foo)):', 0), + (Message('superfluous-parens', line=1, args='not'), + 'if (not (foo)):', 2), + ] + for msg, code, offset in cases: + with self.assertAddsMessages(msg): + self.checker._check_keyword_parentheses(tokenize_str(code), offset) + + def testFuturePrintStatementWithoutParensWarning(self): + code = """from __future__ import print_function +print('Hello world!') +""" + tree = test_utils.build_module(code) + with self.assertNoMessages(): + self.checker.process_module(tree) + self.checker.process_tokens(tokenize_str(code)) + + +class CheckSpaceTest(CheckerTestCase): + CHECKER_CLASS = FormatChecker + + def testParenthesesGood(self): + good_cases = [ + '(a)\n', + '(a * (b + c))\n', + '(#\n a)\n', + ] + with self.assertNoMessages(): + for code in good_cases: + self.checker.process_tokens(tokenize_str(code)) + + def testParenthesesBad(self): + with self.assertAddsMessages( + Message('bad-whitespace', line=1, + args=('No', 'allowed', 'after', 'bracket', '( a)\n^'))): + self.checker.process_tokens(tokenize_str('( a)\n')) + + with self.assertAddsMessages( + Message('bad-whitespace', line=1, + args=('No', 'allowed', 'before', 'bracket', '(a )\n ^'))): + self.checker.process_tokens(tokenize_str('(a )\n')) + + with self.assertAddsMessages( + Message('bad-whitespace', line=1, + args=('No', 'allowed', 'before', 'bracket', 'foo (a)\n ^'))): + self.checker.process_tokens(tokenize_str('foo (a)\n')) + + with self.assertAddsMessages( + Message('bad-whitespace', line=1, + args=('No', 'allowed', 'before', 'bracket', '{1: 2} [1]\n ^'))): + self.checker.process_tokens(tokenize_str('{1: 2} [1]\n')) + + def testTrailingCommaGood(self): + with self.assertNoMessages(): + self.checker.process_tokens(tokenize_str('(a, )\n')) + self.checker.process_tokens(tokenize_str('(a,)\n')) + + self.checker.config.no_space_check = [] + with self.assertNoMessages(): + self.checker.process_tokens(tokenize_str('(a,)\n')) + + @set_config(no_space_check=[]) + def testTrailingCommaBad(self): + with self.assertAddsMessages( + Message('bad-whitespace', line=1, + args=('No', 'allowed', 'before', 'bracket', '(a, )\n ^'))): + self.checker.process_tokens(tokenize_str('(a, )\n')) + + def testComma(self): + with self.assertAddsMessages( + Message('bad-whitespace', line=1, + args=('No', 'allowed', 'before', 'comma', '(a , b)\n ^'))): + self.checker.process_tokens(tokenize_str('(a , b)\n')) + + def testSpacesAllowedInsideSlices(self): + good_cases = [ + '[a:b]\n', + '[a : b]\n', + '[a : ]\n', + '[:a]\n', + '[:]\n', + '[::]\n', + ] + with self.assertNoMessages(): + for code in good_cases: + self.checker.process_tokens(tokenize_str(code)) + + def testKeywordSpacingGood(self): + with self.assertNoMessages(): + self.checker.process_tokens(tokenize_str('foo(foo=bar)\n')) + self.checker.process_tokens(tokenize_str('lambda x=1: x\n')) + + def testKeywordSpacingBad(self): + with self.assertAddsMessages( + Message('bad-whitespace', line=1, + args=('No', 'allowed', 'before', 'keyword argument assignment', + '(foo =bar)\n ^'))): + self.checker.process_tokens(tokenize_str('(foo =bar)\n')) + + with self.assertAddsMessages( + Message('bad-whitespace', line=1, + args=('No', 'allowed', 'after', 'keyword argument assignment', + '(foo= bar)\n ^'))): + self.checker.process_tokens(tokenize_str('(foo= bar)\n')) + + with self.assertAddsMessages( + Message('bad-whitespace', line=1, + args=('No', 'allowed', 'around', 'keyword argument assignment', + '(foo = bar)\n ^'))): + self.checker.process_tokens(tokenize_str('(foo = bar)\n')) + + def testOperatorSpacingGood(self): + good_cases = [ + 'a = b\n' + 'a < b\n' + 'a\n< b\n', + ] + with self.assertNoMessages(): + for code in good_cases: + self.checker.process_tokens(tokenize_str(code)) + + def testOperatorSpacingBad(self): + with self.assertAddsMessages( + Message('bad-whitespace', line=1, + args=('Exactly one', 'required', 'before', 'comparison', 'a< b\n ^'))): + self.checker.process_tokens(tokenize_str('a< b\n')) + + with self.assertAddsMessages( + Message('bad-whitespace', line=1, + args=('Exactly one', 'required', 'after', 'comparison', 'a <b\n ^'))): + self.checker.process_tokens(tokenize_str('a <b\n')) + + with self.assertAddsMessages( + Message('bad-whitespace', line=1, + args=('Exactly one', 'required', 'around', 'comparison', 'a<b\n ^'))): + self.checker.process_tokens(tokenize_str('a<b\n')) + + with self.assertAddsMessages( + Message('bad-whitespace', line=1, + args=('Exactly one', 'required', 'around', 'comparison', 'a< b\n ^'))): + self.checker.process_tokens(tokenize_str('a< b\n')) + + +if __name__ == '__main__': + import unittest + unittest.main() diff --git a/pylint/test/unittest_checker_logging.py b/pylint/test/unittest_checker_logging.py new file mode 100644 index 0000000..e25daac --- /dev/null +++ b/pylint/test/unittest_checker_logging.py @@ -0,0 +1,47 @@ +# Copyright 2014 Google Inc. All Rights Reserved. +"""Unittest for the logging checker.""" +import unittest +from astroid import test_utils + +from pylint.checkers import logging + +from pylint.testutils import CheckerTestCase, Message, set_config + + +class LoggingModuleDetectionTest(CheckerTestCase): + CHECKER_CLASS = logging.LoggingChecker + + def test_detects_standard_logging_module(self): + stmts = test_utils.extract_node(""" + import logging #@ + logging.warn('%s' % '%s') #@ + """) + self.checker.visit_module(None) + self.checker.visit_import(stmts[0]) + with self.assertAddsMessages(Message('logging-not-lazy', node=stmts[1])): + self.checker.visit_callfunc(stmts[1]) + + def test_detects_renamed_standard_logging_module(self): + stmts = test_utils.extract_node(""" + import logging as blogging #@ + blogging.warn('%s' % '%s') #@ + """) + self.checker.visit_module(None) + self.checker.visit_import(stmts[0]) + with self.assertAddsMessages(Message('logging-not-lazy', node=stmts[1])): + self.checker.visit_callfunc(stmts[1]) + + @set_config(logging_modules=['logging', 'my.logging']) + def test_nonstandard_logging_module(self): + stmts = test_utils.extract_node(""" + from my import logging as blogging #@ + blogging.warn('%s' % '%s') #@ + """) + self.checker.visit_module(None) + self.checker.visit_import(stmts[0]) + with self.assertAddsMessages(Message('logging-not-lazy', node=stmts[1])): + self.checker.visit_callfunc(stmts[1]) + + +if __name__ == '__main__': + unittest.main() diff --git a/pylint/test/unittest_checker_misc.py b/pylint/test/unittest_checker_misc.py new file mode 100644 index 0000000..03e3cc2 --- /dev/null +++ b/pylint/test/unittest_checker_misc.py @@ -0,0 +1,49 @@ +# Copyright 2013 Google Inc. All Rights Reserved. +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +"""Tests for the misc checker.""" + +import unittest + +from pylint.checkers import misc +from pylint.testutils import ( + CheckerTestCase, Message, + set_config, create_file_backed_module, +) + + + +class FixmeTest(CheckerTestCase): + CHECKER_CLASS = misc.EncodingChecker + + def test_fixme(self): + with create_file_backed_module( + """a = 1 + # FIXME """) as module: + with self.assertAddsMessages( + Message(msg_id='fixme', line=2, args=u'FIXME')): + self.checker.process_module(module) + + @set_config(notes=[]) + def test_empty_fixme_regex(self): + with create_file_backed_module( + """a = 1 + # fixme + """) as module: + with self.assertNoMessages(): + self.checker.process_module(module) + + +if __name__ == '__main__': + unittest.main() diff --git a/pylint/test/unittest_checker_python3.py b/pylint/test/unittest_checker_python3.py new file mode 100644 index 0000000..a7d31ba --- /dev/null +++ b/pylint/test/unittest_checker_python3.py @@ -0,0 +1,301 @@ +# Copyright 2014 Google Inc. +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +"""Tests for the python3 checkers.""" +from __future__ import absolute_import + +import sys +import unittest +import textwrap + +from astroid import test_utils + +from pylint import testutils +from pylint.checkers import python3 as checker + + +def python2_only(test): + """Decorator for any tests that will fail under Python 3.""" + return unittest.skipIf(sys.version_info[0] > 2, 'Python 2 only')(test) + +# TODO(cpopa): Port these to the functional test framework instead. + +class Python3CheckerTest(testutils.CheckerTestCase): + CHECKER_CLASS = checker.Python3Checker + + def check_bad_builtin(self, builtin_name): + node = test_utils.extract_node(builtin_name + ' #@') + message = builtin_name.lower() + '-builtin' + with self.assertAddsMessages(testutils.Message(message, node=node)): + self.checker.visit_name(node) + + @python2_only + def test_bad_builtins(self): + builtins = [ + 'apply', + 'buffer', + 'cmp', + 'coerce', + 'execfile', + 'file', + 'input', + 'intern', + 'long', + 'raw_input', + 'round', + 'reduce', + 'StandardError', + 'unichr', + 'unicode', + 'xrange', + 'reload', + ] + for builtin in builtins: + self.check_bad_builtin(builtin) + + def _test_defined_method(self, method, warning): + node = test_utils.extract_node(""" + class Foo(object): + def __{0}__(self, other): #@ + pass""".format(method)) + message = testutils.Message(warning, node=node) + with self.assertAddsMessages(message): + self.checker.visit_function(node) + + def test_delslice_method(self): + self._test_defined_method('delslice', 'delslice-method') + + def test_getslice_method(self): + self._test_defined_method('getslice', 'getslice-method') + + def test_setslice_method(self): + self._test_defined_method('setslice', 'setslice-method') + + def test_coerce_method(self): + self._test_defined_method('coerce', 'coerce-method') + + def test_oct_method(self): + self._test_defined_method('oct', 'oct-method') + + def test_hex_method(self): + self._test_defined_method('hex', 'hex-method') + + def test_nonzero_method(self): + self._test_defined_method('nonzero', 'nonzero-method') + + def test_cmp_method(self): + self._test_defined_method('cmp', 'cmp-method') + + @python2_only + def test_print_statement(self): + node = test_utils.extract_node('print "Hello, World!" #@') + message = testutils.Message('print-statement', node=node) + with self.assertAddsMessages(message): + self.checker.visit_print(node) + + @python2_only + def test_backtick(self): + node = test_utils.extract_node('`test`') + message = testutils.Message('backtick', node=node) + with self.assertAddsMessages(message): + self.checker.visit_backquote(node) + + def test_relative_import(self): + node = test_utils.extract_node('import string #@') + message = testutils.Message('no-absolute-import', node=node) + with self.assertAddsMessages(message): + self.checker.visit_import(node) + + def test_relative_from_import(self): + node = test_utils.extract_node('from os import path #@') + message = testutils.Message('no-absolute-import', node=node) + with self.assertAddsMessages(message): + self.checker.visit_import(node) + + def test_absolute_import(self): + module_import = test_utils.build_module( + 'from __future__ import absolute_import; import os') + module_from = test_utils.build_module( + 'from __future__ import absolute_import; from os import path') + with self.assertNoMessages(): + for module in (module_import, module_from): + self.walk(module) + + def test_division(self): + node = test_utils.extract_node('3 / 2 #@') + message = testutils.Message('old-division', node=node) + with self.assertAddsMessages(message): + self.checker.visit_binop(node) + + def test_division_with_future_statement(self): + module = test_utils.build_module('from __future__ import division; 3 / 2') + with self.assertNoMessages(): + self.walk(module) + + def test_floor_division(self): + node = test_utils.extract_node(' 3 // 2 #@') + with self.assertNoMessages(): + self.checker.visit_binop(node) + + def test_division_by_float(self): + left_node = test_utils.extract_node('3.0 / 2 #@') + right_node = test_utils.extract_node(' 3 / 2.0 #@') + with self.assertNoMessages(): + for node in (left_node, right_node): + self.checker.visit_binop(node) + + def test_dict_iter_method(self): + for meth in ('keys', 'values', 'items'): + node = test_utils.extract_node('x.iter%s() #@' % meth) + message = testutils.Message('dict-iter-method', node=node) + with self.assertAddsMessages(message): + self.checker.visit_callfunc(node) + + def test_dict_iter_method_on_dict(self): + node = test_utils.extract_node('{}.iterkeys()') + message = testutils.Message('dict-iter-method', node=node) + with self.assertAddsMessages(message): + self.checker.visit_callfunc(node) + + def test_dict_not_iter_method(self): + arg_node = test_utils.extract_node('x.iterkeys(x) #@') + stararg_node = test_utils.extract_node('x.iterkeys(*x) #@') + kwarg_node = test_utils.extract_node('x.iterkeys(y=x) #@') + non_dict_node = test_utils.extract_node('x=[]\nx.iterkeys() #@') + with self.assertNoMessages(): + for node in (arg_node, stararg_node, kwarg_node, non_dict_node): + self.checker.visit_callfunc(node) + + def test_dict_view_method(self): + for meth in ('keys', 'values', 'items'): + node = test_utils.extract_node('x.view%s() #@' % meth) + message = testutils.Message('dict-view-method', node=node) + with self.assertAddsMessages(message): + self.checker.visit_callfunc(node) + + def test_dict_view_method_on_dict(self): + node = test_utils.extract_node('{}.viewkeys()') + message = testutils.Message('dict-view-method', node=node) + with self.assertAddsMessages(message): + self.checker.visit_callfunc(node) + + def test_dict_not_view_method(self): + arg_node = test_utils.extract_node('x.viewkeys(x) #@') + stararg_node = test_utils.extract_node('x.viewkeys(*x) #@') + kwarg_node = test_utils.extract_node('x.viewkeys(y=x) #@') + non_dict_node = test_utils.extract_node('x=[]\nx.viewkeys() #@') + with self.assertNoMessages(): + for node in (arg_node, stararg_node, kwarg_node, non_dict_node): + self.checker.visit_callfunc(node) + + def test_next_method(self): + node = test_utils.extract_node('x.next() #@') + message = testutils.Message('next-method-called', node=node) + with self.assertAddsMessages(message): + self.checker.visit_callfunc(node) + + @python2_only + def test_implicit_map_evaluation(self): + node = test_utils.extract_node('map(str, [1, 2, 3])') + discard = node.parent + message = testutils.Message('implicit-map-evaluation', node=discard) + with self.assertAddsMessages(message): + # Use node.parent because extract_node returns the value + # of a discard node, not the discard itself. + self.checker.visit_discard(discard) + + def test_not_next_method(self): + arg_node = test_utils.extract_node('x.next(x) #@') + stararg_node = test_utils.extract_node('x.next(*x) #@') + kwarg_node = test_utils.extract_node('x.next(y=x) #@') + with self.assertNoMessages(): + for node in (arg_node, stararg_node, kwarg_node): + self.checker.visit_callfunc(node) + + def test_metaclass_assignment(self): + node = test_utils.extract_node(""" + class Foo(object): #@ + __metaclass__ = type""") + message = testutils.Message('metaclass-assignment', node=node) + with self.assertAddsMessages(message): + self.checker.visit_class(node) + + def test_metaclass_global_assignment(self): + module = test_utils.build_module('__metaclass__ = type') + with self.assertNoMessages(): + self.walk(module) + + @python2_only + def test_parameter_unpacking(self): + node = test_utils.extract_node('def func((a, b)):#@\n pass') + arg = node.args.args[0] + with self.assertAddsMessages(testutils.Message('parameter-unpacking', node=arg)): + self.checker.visit_arguments(node.args) + + @python2_only + def test_old_raise_syntax(self): + node = test_utils.extract_node('raise Exception, "test"') + message = testutils.Message('old-raise-syntax', node=node) + with self.assertAddsMessages(message): + self.checker.visit_raise(node) + + @python2_only + def test_raising_string(self): + node = test_utils.extract_node('raise "Test"') + message = testutils.Message('raising-string', node=node) + with self.assertAddsMessages(message): + self.checker.visit_raise(node) + + @python2_only + def test_checker_disabled_by_default(self): + node = test_utils.build_module(textwrap.dedent(""" + abc = 1l + raise Exception, "test" + raise "test" + `abc` + """)) + with self.assertNoMessages(): + self.walk(node) + + +@python2_only +class Python3TokenCheckerTest(testutils.CheckerTestCase): + + CHECKER_CLASS = checker.Python3TokenChecker + + def _test_token_message(self, code, symbolic_message): + tokens = testutils.tokenize_str(code) + message = testutils.Message(symbolic_message, line=1) + with self.assertAddsMessages(message): + self.checker.process_tokens(tokens) + + def test_long_suffix(self): + for code in ("1l", "1L"): + self._test_token_message(code, 'long-suffix') + + def test_old_ne_operator(self): + self._test_token_message("1 <> 2", "old-ne-operator") + + def test_old_octal_literal(self): + for octal in ("045", "055", "075", "077", "076543"): + self._test_token_message(octal, "old-octal-literal") + + # Make sure we are catching only octals. + for non_octal in ("45", "00", "085", "08", "1"): + tokens = testutils.tokenize_str(non_octal) + with self.assertNoMessages(): + self.checker.process_tokens(tokens) + + +if __name__ == '__main__': + unittest.main() diff --git a/pylint/test/unittest_checker_similar.py b/pylint/test/unittest_checker_similar.py new file mode 100644 index 0000000..4cd48cc --- /dev/null +++ b/pylint/test/unittest_checker_similar.py @@ -0,0 +1,142 @@ +import sys +from os.path import join, dirname, abspath +import unittest + +import six + +from pylint.checkers import similar + +SIMILAR1 = join(dirname(abspath(__file__)), 'input', 'similar1') +SIMILAR2 = join(dirname(abspath(__file__)), 'input', 'similar2') + +class SimilarTC(unittest.TestCase): + """test the similar command line utility""" + + def test_ignore_comments(self): + sys.stdout = six.StringIO() + try: + similar.Run(['--ignore-comments', SIMILAR1, SIMILAR2]) + except SystemExit as ex: + self.assertEqual(ex.code, 0) + output = sys.stdout.getvalue() + else: + self.fail('not system exit') + finally: + sys.stdout = sys.__stdout__ + self.assertMultiLineEqual(output.strip(), (""" +10 similar lines in 2 files +==%s:0 +==%s:0 + import one + from two import two + three + four + five + six + seven + eight + nine + ''' ten +TOTAL lines=44 duplicates=10 percent=22.73 +""" % (SIMILAR1, SIMILAR2)).strip()) + + + def test_ignore_docsrings(self): + sys.stdout = six.StringIO() + try: + similar.Run(['--ignore-docstrings', SIMILAR1, SIMILAR2]) + except SystemExit as ex: + self.assertEqual(ex.code, 0) + output = sys.stdout.getvalue() + else: + self.fail('not system exit') + finally: + sys.stdout = sys.__stdout__ + self.assertMultiLineEqual(output.strip(), (""" +8 similar lines in 2 files +==%s:6 +==%s:6 + seven + eight + nine + ''' ten + ELEVEN + twelve ''' + thirteen + fourteen + +5 similar lines in 2 files +==%s:0 +==%s:0 + import one + from two import two + three + four + five +TOTAL lines=44 duplicates=13 percent=29.55 +""" % ((SIMILAR1, SIMILAR2) * 2)).strip()) + + + def test_ignore_imports(self): + sys.stdout = six.StringIO() + try: + similar.Run(['--ignore-imports', SIMILAR1, SIMILAR2]) + except SystemExit as ex: + self.assertEqual(ex.code, 0) + output = sys.stdout.getvalue() + else: + self.fail('not system exit') + finally: + sys.stdout = sys.__stdout__ + self.assertMultiLineEqual(output.strip(), """ +TOTAL lines=44 duplicates=0 percent=0.00 +""".strip()) + + + def test_ignore_nothing(self): + sys.stdout = six.StringIO() + try: + similar.Run([SIMILAR1, SIMILAR2]) + except SystemExit as ex: + self.assertEqual(ex.code, 0) + output = sys.stdout.getvalue() + else: + self.fail('not system exit') + finally: + sys.stdout = sys.__stdout__ + self.assertMultiLineEqual(output.strip(), (""" +5 similar lines in 2 files +==%s:0 +==%s:0 + import one + from two import two + three + four + five +TOTAL lines=44 duplicates=5 percent=11.36 +""" % (SIMILAR1, SIMILAR2)).strip()) + + def test_help(self): + sys.stdout = six.StringIO() + try: + similar.Run(['--help']) + except SystemExit as ex: + self.assertEqual(ex.code, 0) + else: + self.fail('not system exit') + finally: + sys.stdout = sys.__stdout__ + + def test_no_args(self): + sys.stdout = six.StringIO() + try: + similar.Run([]) + except SystemExit as ex: + self.assertEqual(ex.code, 1) + else: + self.fail('not system exit') + finally: + sys.stdout = sys.__stdout__ + +if __name__ == '__main__': + unittest.main() diff --git a/pylint/test/unittest_checker_spelling.py b/pylint/test/unittest_checker_spelling.py new file mode 100644 index 0000000..c630eb2 --- /dev/null +++ b/pylint/test/unittest_checker_spelling.py @@ -0,0 +1,93 @@ +# Copyright 2014 Michal Nowikowski. +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +"""Unittest for the spelling checker.""" + +import unittest + +from astroid import test_utils + +from pylint.checkers import spelling +from pylint.testutils import ( + CheckerTestCase, Message, set_config, tokenize_str, +) + +# try to create enchant dictionary +try: + import enchant +except ImportError: + enchant = None + +spell_dict = None +if enchant is not None: + try: + enchant.Dict("en_US") + spell_dict = "en_US" + except enchant.DictNotFoundError: + pass + + +class SpellingCheckerTest(CheckerTestCase): + CHECKER_CLASS = spelling.SpellingChecker + + @unittest.skipIf(spell_dict is None, + "missing python-enchant package or missing " + "spelling dictionaries") + @set_config(spelling_dict=spell_dict) + def test_check_bad_coment(self): + with self.assertAddsMessages( + Message('wrong-spelling-in-comment', line=1, + args=('coment', '# bad coment', + ' ^^^^^^', + "comet' or 'comment' or 'moment' or 'foment"))): + self.checker.process_tokens(tokenize_str("# bad coment")) + + @unittest.skipIf(spell_dict is None, + "missing python-enchant package or missing " + "spelling dictionaries") + @set_config(spelling_dict=spell_dict) + def test_check_bad_docstring(self): + stmt = test_utils.extract_node( + 'def fff():\n """bad coment"""\n pass') + with self.assertAddsMessages( + Message('wrong-spelling-in-docstring', line=2, + args=('coment', 'bad coment', + ' ^^^^^^', + "comet' or 'comment' or 'moment' or 'foment"))): + self.checker.visit_function(stmt) + + stmt = test_utils.extract_node( + 'class Abc(object):\n """bad coment"""\n pass') + with self.assertAddsMessages( + Message('wrong-spelling-in-docstring', line=2, + args=('coment', 'bad coment', + ' ^^^^^^', + "comet' or 'comment' or 'moment' or 'foment"))): + self.checker.visit_class(stmt) + + @unittest.skipIf(spell_dict is None, + "missing python-enchant package or missing " + "spelling dictionaries") + @set_config(spelling_dict=spell_dict) + def test_invalid_docstring_characters(self): + stmt = test_utils.extract_node( + 'def fff():\n """test\\x00"""\n pass') + with self.assertAddsMessages( + Message('invalid-characters-in-docstring', line=2, + args=('test\x00',))): + self.checker.visit_function(stmt) + + +if __name__ == '__main__': + unittest.main() diff --git a/pylint/test/unittest_checker_typecheck.py b/pylint/test/unittest_checker_typecheck.py new file mode 100644 index 0000000..33efe5d --- /dev/null +++ b/pylint/test/unittest_checker_typecheck.py @@ -0,0 +1,42 @@ +"""Unittest for the type checker.""" +import unittest + +from astroid import test_utils +from pylint.checkers import typecheck +from pylint.testutils import CheckerTestCase, Message, set_config + +class TypeCheckerTest(CheckerTestCase): + "Tests for pylint.checkers.typecheck" + CHECKER_CLASS = typecheck.TypeChecker + + def test_no_member_in_getattr(self): + """Make sure that a module attribute access is checked by pylint. + """ + + node = test_utils.extract_node(""" + import optparse + optparse.THIS_does_not_EXIST + """) + with self.assertAddsMessages( + Message( + 'no-member', + node=node, + args=('Module', 'optparse', 'THIS_does_not_EXIST'))): + self.checker.visit_getattr(node) + + @set_config(ignored_modules=('argparse',)) + def test_no_member_in_getattr_ignored(self): + """Make sure that a module attribute access check is omitted with a + module that is configured to be ignored. + """ + + node = test_utils.extract_node(""" + import argparse + argparse.THIS_does_not_EXIST + """) + with self.assertNoMessages(): + self.checker.visit_getattr(node) + + +if __name__ == '__main__': + unittest.main() diff --git a/pylint/test/unittest_checker_variables.py b/pylint/test/unittest_checker_variables.py new file mode 100644 index 0000000..e3dd939 --- /dev/null +++ b/pylint/test/unittest_checker_variables.py @@ -0,0 +1,99 @@ +"""Unit tests for the variables checker.""" +import sys +import os +import unittest + +from astroid import test_utils +from pylint.checkers import variables +from pylint.testutils import CheckerTestCase, linter, set_config, Message + +class VariablesCheckerTC(CheckerTestCase): + + CHECKER_CLASS = variables.VariablesChecker + + def test_bitbucket_issue_78(self): + """ Issue 78 report a false positive for unused-module """ + module = test_utils.build_module(""" + from sys import path + path += ['stuff'] + def func(): + other = 1 + return len(other) + """) + with self.assertNoMessages(): + self.walk(module) + + @set_config(ignored_modules=('argparse',)) + def test_no_name_in_module_skipped(self): + """Make sure that 'from ... import ...' does not emit a + 'no-name-in-module' with a module that is configured + to be ignored. + """ + + node = test_utils.extract_node(""" + from argparse import THIS_does_not_EXIST + """) + with self.assertNoMessages(): + self.checker.visit_from(node) + + @set_config(callbacks=('callback_', '_callback')) + def test_custom_callback_string(self): + """ Test the --calbacks option works. """ + def cleanup(): + self.checker._to_consume = _to_consume + _to_consume = self.checker._to_consume + self.checker._to_consume = [] + self.addCleanup(cleanup) + + node = test_utils.extract_node(""" + def callback_one(abc): + ''' should not emit unused-argument. ''' + """) + with self.assertNoMessages(): + self.checker.visit_function(node) + self.checker.leave_function(node) + + node = test_utils.extract_node(""" + def two_callback(abc, defg): + ''' should not emit unused-argument. ''' + """) + with self.assertNoMessages(): + self.checker.visit_function(node) + self.checker.leave_function(node) + + node = test_utils.extract_node(""" + def normal_func(abc): + ''' should emit unused-argument. ''' + """) + with self.assertAddsMessages( + Message('unused-argument', node=node['abc'], args='abc')): + self.checker.visit_function(node) + self.checker.leave_function(node) + + node = test_utils.extract_node(""" + def cb_func(abc): + ''' Previous callbacks are overriden. ''' + """) + with self.assertAddsMessages( + Message('unused-argument', node=node['abc'], args='abc')): + self.checker.visit_function(node) + self.checker.leave_function(node) + + +class MissingSubmoduleTest(CheckerTestCase): + CHECKER_CLASS = variables.VariablesChecker + + def test_package_all(self): + regr_data = os.path.join(os.path.dirname(os.path.abspath(__file__)), + 'regrtest_data') + sys.path.insert(0, regr_data) + try: + linter.check(os.path.join(regr_data, 'package_all')) + got = linter.reporter.finalize().strip() + self.assertEqual(got, "E: 3: Undefined variable name " + "'missing' in __all__") + finally: + sys.path.pop(0) + +if __name__ == '__main__': + unittest.main() diff --git a/pylint/test/unittest_checkers_utils.py b/pylint/test/unittest_checkers_utils.py new file mode 100644 index 0000000..5909cd7 --- /dev/null +++ b/pylint/test/unittest_checkers_utils.py @@ -0,0 +1,71 @@ +# 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., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +"""Tests for the pylint.checkers.utils module.""" + +import sys +import unittest + +from astroid import test_utils + +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.assertEqual(utils.is_native_builtin('min'), True) +## self.assertEqual(utils.is_native_builtin('__path__'), True) +## self.assertEqual(utils.is_native_builtin('__file__'), True) +## self.assertEqual(utils.is_native_builtin('whatever'), False) +## self.assertEqual(utils.is_native_builtin('mybuiltin'), False) + + def test_is_builtin(self): + self.assertEqual(utils.is_builtin('min'), True) + self.assertEqual(utils.is_builtin('__builtins__'), True) + self.assertEqual(utils.is_builtin('__path__'), False) + self.assertEqual(utils.is_builtin('__file__'), False) + self.assertEqual(utils.is_builtin('whatever'), False) + self.assertEqual(utils.is_builtin('mybuiltin'), False) + + def testGetArgumentFromCall(self): + node = test_utils.extract_node('foo(bar=3)') + self.assertIsNotNone(utils.get_argument_from_call(node, keyword='bar')) + with self.assertRaises(utils.NoSuchArgumentError): + node = test_utils.extract_node('foo(3)') + utils.get_argument_from_call(node, keyword='bar') + with self.assertRaises(utils.NoSuchArgumentError): + node = test_utils.extract_node('foo(one=a, two=b, three=c)') + utils.get_argument_from_call(node, position=1) + node = test_utils.extract_node('foo(a, b, c)') + self.assertIsNotNone(utils.get_argument_from_call(node, position=1)) + node = test_utils.extract_node('foo(a, not_this_one=1, this_one=2)') + arg = utils.get_argument_from_call(node, position=1, keyword='this_one') + self.assertEqual(2, arg.value) + node = test_utils.extract_node('foo(a)') + with self.assertRaises(utils.NoSuchArgumentError): + utils.get_argument_from_call(node, position=1) + with self.assertRaises(ValueError): + utils.get_argument_from_call(node, None, None) + + name = utils.get_argument_from_call(node, position=0) + self.assertEqual(name.name, 'a') + +if __name__ == '__main__': + unittest.main() + diff --git a/pylint/test/unittest_lint.py b/pylint/test/unittest_lint.py new file mode 100644 index 0000000..ed2f20c --- /dev/null +++ b/pylint/test/unittest_lint.py @@ -0,0 +1,697 @@ +# Copyright (c) 2003-2014 LOGILAB S.A. (Paris, FRANCE). +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +from contextlib import contextmanager +import sys +import os +import tempfile +from shutil import rmtree +from os import getcwd, chdir +from os.path import join, basename, dirname, isdir, abspath, sep +import unittest + +import six + +from logilab.common.compat import reload + +from pylint import config, lint +from pylint.lint import PyLinter, Run, preprocess_options, \ + ArgumentPreprocessingError +from pylint.utils import MSG_STATE_SCOPE_CONFIG, MSG_STATE_SCOPE_MODULE, MSG_STATE_CONFIDENCE, \ + MessagesStore, PyLintASTWalker, MessageDefinition, FileState, \ + build_message_def, tokenize_module, UnknownMessage +from pylint.testutils import TestReporter +from pylint.reporters import text, html +from pylint import checkers +from pylint import interfaces + +if sys.platform == 'win32': + HOME = 'USERPROFILE' +else: + HOME = 'HOME' + +def remove(file): + try: + os.remove(file) + except OSError: + pass + +class GetNoteMessageTC(unittest.TestCase): + def test(self): + msg = None + for note in range(-1, 11): + note_msg = config.get_note_message(note) + self.assertNotEqual(msg, note_msg) + msg = note_msg + + +HERE = abspath(dirname(__file__)) +INPUTDIR = join(HERE, 'input') + + +@contextmanager +def tempdir(): + """Create a temp directory and change the current location to it. + + This is supposed to be used with a *with* statement. + """ + tmp = tempfile.mkdtemp() + + # Get real path of tempfile, otherwise test fail on mac os x + current_dir = getcwd() + chdir(tmp) + abs_tmp = abspath('.') + + try: + yield abs_tmp + finally: + chdir(current_dir) + rmtree(abs_tmp) + + +def create_files(paths, chroot='.'): + """Creates directories and files found in <path>. + + :param paths: list of relative paths to files or directories + :param chroot: the root directory in which paths will be created + + >>> from os.path import isdir, isfile + >>> isdir('/tmp/a') + False + >>> create_files(['a/b/foo.py', 'a/b/c/', 'a/b/c/d/e.py'], '/tmp') + >>> isdir('/tmp/a') + True + >>> isdir('/tmp/a/b/c') + True + >>> isfile('/tmp/a/b/c/d/e.py') + True + >>> isfile('/tmp/a/b/foo.py') + True + """ + dirs, files = set(), set() + for path in paths: + path = join(chroot, path) + filename = basename(path) + # path is a directory path + if filename == '': + dirs.add(path) + # path is a filename path + else: + dirs.add(dirname(path)) + files.add(path) + for dirpath in dirs: + if not isdir(dirpath): + os.makedirs(dirpath) + for filepath in files: + open(filepath, 'w').close() + + +class SysPathFixupTC(unittest.TestCase): + def setUp(self): + self.orig = list(sys.path) + self.fake = [1, 2, 3] + sys.path[:] = self.fake + + def tearDown(self): + sys.path[:] = self.orig + + def test_no_args(self): + with lint.fix_import_path([]): + self.assertEqual(sys.path, self.fake) + self.assertEqual(sys.path, self.fake) + + def test_one_arg(self): + with tempdir() as chroot: + create_files(['a/b/__init__.py']) + expected = [join(chroot, 'a')] + self.fake + + cases = ( + ['a/b/'], + ['a/b'], + ['a/b/__init__.py'], + ['a/'], + ['a'], + ) + + self.assertEqual(sys.path, self.fake) + for case in cases: + with lint.fix_import_path(case): + self.assertEqual(sys.path, expected) + self.assertEqual(sys.path, self.fake) + + def test_two_similar_args(self): + with tempdir() as chroot: + create_files(['a/b/__init__.py', 'a/c/__init__.py']) + expected = [join(chroot, 'a')] + self.fake + + cases = ( + ['a/b', 'a/c'], + ['a/c/', 'a/b/'], + ['a/b/__init__.py', 'a/c/__init__.py'], + ['a', 'a/c/__init__.py'], + ) + + self.assertEqual(sys.path, self.fake) + for case in cases: + with lint.fix_import_path(case): + self.assertEqual(sys.path, expected) + self.assertEqual(sys.path, self.fake) + + def test_more_args(self): + with tempdir() as chroot: + create_files(['a/b/c/__init__.py', 'a/d/__init__.py', 'a/e/f.py']) + expected = [ + join(chroot, suffix) + for suffix in [sep.join(('a', 'b')), 'a', sep.join(('a', 'e'))] + ] + self.fake + + cases = ( + ['a/b/c/__init__.py', 'a/d/__init__.py', 'a/e/f.py'], + ['a/b/c', 'a', 'a/e'], + ['a/b/c', 'a', 'a/b/c', 'a/e', 'a'], + ) + + self.assertEqual(sys.path, self.fake) + for case in cases: + with lint.fix_import_path(case): + self.assertEqual(sys.path, expected) + self.assertEqual(sys.path, self.fake) + + +class PyLinterTC(unittest.TestCase): + + def setUp(self): + self.linter = PyLinter() + self.linter.disable('I') + self.linter.config.persistent = 0 + # register checkers + checkers.initialize(self.linter) + self.linter.set_reporter(TestReporter()) + + def init_linter(self): + linter = self.linter + linter.open() + linter.set_current_module('toto') + linter.file_state = FileState('toto') + return linter + + def test_enable_message(self): + linter = self.init_linter() + self.assertTrue(linter.is_message_enabled('W0101')) + self.assertTrue(linter.is_message_enabled('W0102')) + linter.disable('W0101', scope='package') + linter.disable('W0102', scope='module', line=1) + self.assertFalse(linter.is_message_enabled('W0101')) + self.assertFalse(linter.is_message_enabled('W0102', 1)) + linter.set_current_module('tutu') + self.assertFalse(linter.is_message_enabled('W0101')) + self.assertTrue(linter.is_message_enabled('W0102')) + linter.enable('W0101', scope='package') + linter.enable('W0102', scope='module', line=1) + self.assertTrue(linter.is_message_enabled('W0101')) + self.assertTrue(linter.is_message_enabled('W0102', 1)) + + def test_enable_message_category(self): + linter = self.init_linter() + self.assertTrue(linter.is_message_enabled('W0101')) + self.assertTrue(linter.is_message_enabled('C0121')) + linter.disable('W', scope='package') + linter.disable('C', scope='module', line=1) + self.assertFalse(linter.is_message_enabled('W0101')) + self.assertTrue(linter.is_message_enabled('C0121')) + self.assertFalse(linter.is_message_enabled('C0121', line=1)) + linter.set_current_module('tutu') + self.assertFalse(linter.is_message_enabled('W0101')) + self.assertTrue(linter.is_message_enabled('C0121')) + linter.enable('W', scope='package') + linter.enable('C', scope='module', line=1) + self.assertTrue(linter.is_message_enabled('W0101')) + self.assertTrue(linter.is_message_enabled('C0121')) + self.assertTrue(linter.is_message_enabled('C0121', line=1)) + + def test_message_state_scope(self): + class FakeConfig(object): + confidence = ['HIGH'] + + linter = self.init_linter() + linter.disable('C0121') + self.assertEqual(MSG_STATE_SCOPE_CONFIG, + linter.get_message_state_scope('C0121')) + linter.disable('W0101', scope='module', line=3) + self.assertEqual(MSG_STATE_SCOPE_CONFIG, + linter.get_message_state_scope('C0121')) + self.assertEqual(MSG_STATE_SCOPE_MODULE, + linter.get_message_state_scope('W0101', 3)) + linter.enable('W0102', scope='module', line=3) + self.assertEqual(MSG_STATE_SCOPE_MODULE, + linter.get_message_state_scope('W0102', 3)) + linter.config = FakeConfig() + self.assertEqual( + MSG_STATE_CONFIDENCE, + linter.get_message_state_scope('this-is-bad', + confidence=interfaces.INFERENCE)) + + def test_enable_message_block(self): + linter = self.init_linter() + linter.open() + filepath = join(INPUTDIR, 'func_block_disable_msg.py') + linter.set_current_module('func_block_disable_msg') + astroid = linter.get_ast(filepath, 'func_block_disable_msg') + linter.process_tokens(tokenize_module(astroid)) + fs = linter.file_state + fs.collect_block_lines(linter.msgs_store, astroid) + # global (module level) + self.assertTrue(linter.is_message_enabled('W0613')) + self.assertTrue(linter.is_message_enabled('E1101')) + # meth1 + self.assertTrue(linter.is_message_enabled('W0613', 13)) + # meth2 + self.assertFalse(linter.is_message_enabled('W0613', 18)) + # meth3 + self.assertFalse(linter.is_message_enabled('E1101', 24)) + self.assertTrue(linter.is_message_enabled('E1101', 26)) + # meth4 + self.assertFalse(linter.is_message_enabled('E1101', 32)) + self.assertTrue(linter.is_message_enabled('E1101', 36)) + # meth5 + self.assertFalse(linter.is_message_enabled('E1101', 42)) + self.assertFalse(linter.is_message_enabled('E1101', 43)) + self.assertTrue(linter.is_message_enabled('E1101', 46)) + self.assertFalse(linter.is_message_enabled('E1101', 49)) + self.assertFalse(linter.is_message_enabled('E1101', 51)) + # meth6 + self.assertFalse(linter.is_message_enabled('E1101', 57)) + self.assertTrue(linter.is_message_enabled('E1101', 61)) + self.assertFalse(linter.is_message_enabled('E1101', 64)) + self.assertFalse(linter.is_message_enabled('E1101', 66)) + + self.assertTrue(linter.is_message_enabled('E0602', 57)) + self.assertTrue(linter.is_message_enabled('E0602', 61)) + self.assertFalse(linter.is_message_enabled('E0602', 62)) + self.assertTrue(linter.is_message_enabled('E0602', 64)) + self.assertTrue(linter.is_message_enabled('E0602', 66)) + # meth7 + self.assertFalse(linter.is_message_enabled('E1101', 70)) + self.assertTrue(linter.is_message_enabled('E1101', 72)) + self.assertTrue(linter.is_message_enabled('E1101', 75)) + self.assertTrue(linter.is_message_enabled('E1101', 77)) + + fs = linter.file_state + self.assertEqual(17, fs._suppression_mapping['W0613', 18]) + self.assertEqual(30, fs._suppression_mapping['E1101', 33]) + self.assertTrue(('E1101', 46) not in fs._suppression_mapping) + self.assertEqual(1, fs._suppression_mapping['C0302', 18]) + self.assertEqual(1, fs._suppression_mapping['C0302', 50]) + # This is tricky. While the disable in line 106 is disabling + # both 108 and 110, this is usually not what the user wanted. + # Therefore, we report the closest previous disable comment. + self.assertEqual(106, fs._suppression_mapping['E1101', 108]) + self.assertEqual(109, fs._suppression_mapping['E1101', 110]) + + def test_enable_by_symbol(self): + """messages can be controlled by symbolic names. + + The state is consistent across symbols and numbers. + """ + linter = self.init_linter() + self.assertTrue(linter.is_message_enabled('W0101')) + self.assertTrue(linter.is_message_enabled('unreachable')) + self.assertTrue(linter.is_message_enabled('W0102')) + self.assertTrue(linter.is_message_enabled('dangerous-default-value')) + linter.disable('unreachable', scope='package') + linter.disable('dangerous-default-value', scope='module', line=1) + self.assertFalse(linter.is_message_enabled('W0101')) + self.assertFalse(linter.is_message_enabled('unreachable')) + self.assertFalse(linter.is_message_enabled('W0102', 1)) + self.assertFalse(linter.is_message_enabled('dangerous-default-value', 1)) + linter.set_current_module('tutu') + self.assertFalse(linter.is_message_enabled('W0101')) + self.assertFalse(linter.is_message_enabled('unreachable')) + self.assertTrue(linter.is_message_enabled('W0102')) + self.assertTrue(linter.is_message_enabled('dangerous-default-value')) + linter.enable('unreachable', scope='package') + linter.enable('dangerous-default-value', scope='module', line=1) + self.assertTrue(linter.is_message_enabled('W0101')) + self.assertTrue(linter.is_message_enabled('unreachable')) + self.assertTrue(linter.is_message_enabled('W0102', 1)) + self.assertTrue(linter.is_message_enabled('dangerous-default-value', 1)) + + def test_lint_ext_module_with_file_output(self): + self.linter.set_reporter(text.TextReporter()) + if sys.version_info < (3, 0): + strio = 'StringIO' + else: + strio = 'io' + self.linter.config.files_output = True + pylint_strio = 'pylint_%s.txt' % strio + files = [pylint_strio, 'pylint_global.txt'] + for file in files: + self.addCleanup(remove, file) + + self.linter.check(strio) + self.linter.generate_reports() + for f in files: + self.assertTrue(os.path.exists(f)) + + def test_lint_should_analyze_file(self): + self.linter.set_reporter(text.TextReporter()) + self.linter.config.files_output = True + self.linter.should_analyze_file = lambda *args: False + self.addCleanup(remove, 'pylint_logilab.txt') + + self.linter.check('logilab') + self.assertTrue(os.path.exists('pylint_logilab.txt')) + self.assertFalse(os.path.exists('pylint_logilab_common.txt')) + + def test_enable_report(self): + self.assertEqual(self.linter.report_is_enabled('RP0001'), True) + self.linter.disable('RP0001') + self.assertEqual(self.linter.report_is_enabled('RP0001'), False) + self.linter.enable('RP0001') + self.assertEqual(self.linter.report_is_enabled('RP0001'), True) + + def test_report_output_format_aliased(self): + text.register(self.linter) + self.linter.set_option('output-format', 'text') + self.assertEqual(self.linter.reporter.__class__.__name__, 'TextReporter') + + def test_report_output_format_custom(self): + this_module = sys.modules[__name__] + class TestReporter(object): + pass + this_module.TestReporter = TestReporter + class_name = ".".join((this_module.__name__, 'TestReporter')) + self.linter.set_option('output-format', class_name) + self.assertEqual(self.linter.reporter.__class__.__name__, 'TestReporter') + + def test_set_option_1(self): + linter = self.linter + linter.set_option('disable', 'C0111,W0142') + self.assertFalse(linter.is_message_enabled('C0111')) + self.assertFalse(linter.is_message_enabled('W0142')) + self.assertTrue(linter.is_message_enabled('W0113')) + self.assertFalse(linter.is_message_enabled('missing-docstring')) + self.assertFalse(linter.is_message_enabled('star-args')) + # no name for W0113 + + def test_set_option_2(self): + linter = self.linter + linter.set_option('disable', ('C0111', 'W0142') ) + self.assertFalse(linter.is_message_enabled('C0111')) + self.assertFalse(linter.is_message_enabled('W0142')) + self.assertTrue(linter.is_message_enabled('W0113')) + self.assertFalse(linter.is_message_enabled('missing-docstring')) + self.assertFalse(linter.is_message_enabled('star-args')) + # no name for W0113 + + def test_enable_checkers(self): + self.linter.disable('design') + self.assertFalse('design' in [c.name for c in self.linter.prepare_checkers()]) + self.linter.enable('design') + self.assertTrue('design' in [c.name for c in self.linter.prepare_checkers()]) + + def test_errors_only(self): + linter = self.linter + self.linter.error_mode() + checkers = self.linter.prepare_checkers() + checker_names = set(c.name for c in checkers) + should_not = set(('design', 'format', 'imports', 'metrics', + 'miscellaneous', 'similarities')) + self.assertSetEqual(set(), should_not & checker_names) + + def test_disable_similar(self): + self.linter.set_option('disable', 'RP0801') + self.linter.set_option('disable', 'R0801') + self.assertFalse('similarities' in [c.name for c in self.linter.prepare_checkers()]) + + def test_disable_alot(self): + """check that we disabled a lot of checkers""" + self.linter.set_option('reports', False) + self.linter.set_option('disable', 'R,C,W') + checker_names = [c.name for c in self.linter.prepare_checkers()] + for cname in ('design', 'metrics', 'similarities', + 'imports'): # as a Fatal message that should be ignored + self.assertFalse(cname in checker_names, cname) + + def test_addmessage(self): + self.linter.set_reporter(TestReporter()) + self.linter.open() + self.linter.set_current_module('0123') + self.linter.add_message('C0301', line=1, args=(1, 2)) + self.linter.add_message('line-too-long', line=2, args=(3, 4)) + self.assertEqual( + ['C: 1: Line too long (1/2)', 'C: 2: Line too long (3/4)'], + self.linter.reporter.messages) + + def test_init_hooks_called_before_load_plugins(self): + self.assertRaises(RuntimeError, + Run, ['--load-plugins', 'unexistant', '--init-hook', 'raise RuntimeError']) + self.assertRaises(RuntimeError, + Run, ['--init-hook', 'raise RuntimeError', '--load-plugins', 'unexistant']) + + + def test_analyze_explicit_script(self): + self.linter.set_reporter(TestReporter()) + self.linter.check(os.path.join(os.path.dirname(__file__), 'data', 'ascript')) + self.assertEqual( + ['C: 2: Line too long (175/100)'], + self.linter.reporter.messages) + + def test_html_reporter_missing_files(self): + output = six.StringIO() + self.linter.set_reporter(html.HTMLReporter(output)) + self.linter.set_option('output-format', 'html') + self.linter.check('troppoptop.py') + self.linter.generate_reports() + value = output.getvalue() + self.assertIn('troppoptop.py', value) + self.assertIn('fatal', value) + + def test_python3_checker_disabled(self): + checker_names = [c.name for c in self.linter.prepare_checkers()] + self.assertNotIn('python3', checker_names) + + self.linter.set_option('enable', 'python3') + checker_names = [c.name for c in self.linter.prepare_checkers()] + self.assertIn('python3', checker_names) + + +class ConfigTC(unittest.TestCase): + + def setUp(self): + os.environ.pop('PYLINTRC', None) + + def test_pylint_home(self): + uhome = os.path.expanduser('~') + if uhome == '~': + expected = '.pylint.d' + else: + expected = os.path.join(uhome, '.pylint.d') + self.assertEqual(config.PYLINT_HOME, expected) + + try: + pylintd = join(tempfile.gettempdir(), '.pylint.d') + os.environ['PYLINTHOME'] = pylintd + try: + reload(config) + self.assertEqual(config.PYLINT_HOME, pylintd) + finally: + try: + os.remove(pylintd) + except: + pass + finally: + del os.environ['PYLINTHOME'] + + def test_pylintrc(self): + fake_home = tempfile.mkdtemp('fake-home') + home = os.environ[HOME] + try: + os.environ[HOME] = fake_home + self.assertEqual(config.find_pylintrc(), None) + os.environ['PYLINTRC'] = join(tempfile.gettempdir(), '.pylintrc') + self.assertEqual(config.find_pylintrc(), None) + os.environ['PYLINTRC'] = '.' + self.assertEqual(config.find_pylintrc(), None) + finally: + os.environ.pop('PYLINTRC', '') + os.environ[HOME] = home + rmtree(fake_home, ignore_errors=True) + reload(config) + + def test_pylintrc_parentdir(self): + with tempdir() as chroot: + + create_files(['a/pylintrc', 'a/b/__init__.py', 'a/b/pylintrc', + 'a/b/c/__init__.py', 'a/b/c/d/__init__.py']) + fake_home = tempfile.mkdtemp('fake-home') + home = os.environ[HOME] + try: + os.environ[HOME] = fake_home + self.assertEqual(config.find_pylintrc(), None) + finally: + os.environ[HOME] = home + os.rmdir(fake_home) + results = {'a' : join(chroot, 'a', 'pylintrc'), + 'a/b' : join(chroot, 'a', 'b', 'pylintrc'), + 'a/b/c' : join(chroot, 'a', 'b', 'pylintrc'), + 'a/b/c/d' : join(chroot, 'a', 'b', 'pylintrc'), + } + for basedir, expected in results.items(): + os.chdir(join(chroot, basedir)) + self.assertEqual(config.find_pylintrc(), expected) + + def test_pylintrc_parentdir_no_package(self): + with tempdir() as chroot: + fake_home = tempfile.mkdtemp('fake-home') + home = os.environ[HOME] + os.environ[HOME] = fake_home + try: + create_files(['a/pylintrc', 'a/b/pylintrc', 'a/b/c/d/__init__.py']) + self.assertEqual(config.find_pylintrc(), None) + results = {'a' : join(chroot, 'a', 'pylintrc'), + 'a/b' : join(chroot, 'a', 'b', 'pylintrc'), + 'a/b/c' : None, + 'a/b/c/d' : None, + } + for basedir, expected in results.items(): + os.chdir(join(chroot, basedir)) + self.assertEqual(config.find_pylintrc(), expected) + finally: + os.environ[HOME] = home + rmtree(fake_home, ignore_errors=True) + + +class PreprocessOptionsTC(unittest.TestCase): + def _callback(self, name, value): + self.args.append((name, value)) + + def test_value_equal(self): + self.args = [] + preprocess_options(['--foo', '--bar=baz', '--qu=ux'], + {'foo' : (self._callback, False), + 'qu' : (self._callback, True)}) + self.assertEqual( + [('foo', None), ('qu', 'ux')], self.args) + + def test_value_space(self): + self.args = [] + preprocess_options(['--qu', 'ux'], + {'qu' : (self._callback, True)}) + self.assertEqual( + [('qu', 'ux')], self.args) + + def test_error_missing_expected_value(self): + self.assertRaises( + ArgumentPreprocessingError, + preprocess_options, + ['--foo', '--bar', '--qu=ux'], + {'bar' : (None, True)}) + self.assertRaises( + ArgumentPreprocessingError, + preprocess_options, + ['--foo', '--bar'], + {'bar' : (None, True)}) + + def test_error_unexpected_value(self): + self.assertRaises( + ArgumentPreprocessingError, + preprocess_options, + ['--foo', '--bar=spam', '--qu=ux'], + {'bar' : (None, False)}) + + +class MessagesStoreTC(unittest.TestCase): + def setUp(self): + self.store = MessagesStore() + class Checker(object): + name = 'achecker' + msgs = { + 'W1234': ('message', 'msg-symbol', 'msg description.', + {'old_names': [('W0001', 'old-symbol')]}), + 'E1234': ('Duplicate keyword argument %r in %s call', + 'duplicate-keyword-arg', + 'Used when a function call passes the same keyword argument multiple times.', + {'maxversion': (2, 6)}), + } + self.store.register_messages(Checker()) + + def _compare_messages(self, desc, msg, checkerref=False): + # replace \r\n with \n, because + # logilab.common.textutils.normalize_text + # uses os.linesep, which will + # not properly compare with triple + # quoted multilines used in these tests + self.assertMultiLineEqual( + desc, + msg.format_help(checkerref=checkerref).replace('\r\n', '\n')) + + def test_check_message_id(self): + self.assertIsInstance(self.store.check_message_id('W1234'), + MessageDefinition) + self.assertRaises(UnknownMessage, + self.store.check_message_id, 'YB12') + + def test_message_help(self): + msg = self.store.check_message_id('W1234') + self._compare_messages( + ''':msg-symbol (W1234): *message* + msg description. This message belongs to the achecker checker.''', + msg, checkerref=True) + self._compare_messages( + ''':msg-symbol (W1234): *message* + msg description.''', + msg, checkerref=False) + + def test_message_help_minmax(self): + # build the message manually to be python version independant + msg = self.store.check_message_id('E1234') + self._compare_messages( + ''':duplicate-keyword-arg (E1234): *Duplicate keyword argument %r in %s call* + Used when a function call passes the same keyword argument multiple times. + This message belongs to the achecker checker. It can't be emitted when using + Python >= 2.6.''', + msg, checkerref=True) + self._compare_messages( + ''':duplicate-keyword-arg (E1234): *Duplicate keyword argument %r in %s call* + Used when a function call passes the same keyword argument multiple times. + This message can't be emitted when using Python >= 2.6.''', + msg, checkerref=False) + + def test_list_messages(self): + sys.stdout = six.StringIO() + try: + self.store.list_messages() + output = sys.stdout.getvalue() + finally: + sys.stdout = sys.__stdout__ + # cursory examination of the output: we're mostly testing it completes + self.assertIn(':msg-symbol (W1234): *message*', output) + + def test_add_renamed_message(self): + self.store.add_renamed_message('W1234', 'old-bad-name', 'msg-symbol') + self.assertEqual('msg-symbol', + self.store.check_message_id('W1234').symbol) + self.assertEqual('msg-symbol', + self.store.check_message_id('old-bad-name').symbol) + + def test_renamed_message_register(self): + self.assertEqual('msg-symbol', + self.store.check_message_id('W0001').symbol) + self.assertEqual('msg-symbol', + self.store.check_message_id('old-symbol').symbol) + + +if __name__ == '__main__': + unittest.main() diff --git a/pylint/test/unittest_pyreverse_diadefs.py b/pylint/test/unittest_pyreverse_diadefs.py new file mode 100644 index 0000000..5f775aa --- /dev/null +++ b/pylint/test/unittest_pyreverse_diadefs.py @@ -0,0 +1,171 @@ +# Copyright (c) 2000-2013 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +""" +unittest for the extensions.diadefslib modules +""" + +import unittest +import sys + +import six + +import astroid +from astroid import MANAGER +from astroid.inspector import Linker + +from pylint.pyreverse.diadefslib import * + +from unittest_pyreverse_writer import Config, get_project + +PROJECT = get_project('data') +HANDLER = DiadefsHandler(Config()) + +def _process_classes(classes): + """extract class names of a list""" + return sorted([(isinstance(c.node, astroid.Class), c.title) for c in classes]) + +def _process_relations(relations): + """extract relation indices from a relation list""" + result = [] + for rel_type, rels in six.iteritems(relations): + for rel in rels: + result.append( (rel_type, rel.from_object.title, + rel.to_object.title) ) + result.sort() + return result + + +class DiaDefGeneratorTC(unittest.TestCase): + def test_option_values(self): + """test for ancestor, associated and module options""" + handler = DiadefsHandler(Config()) + df_h = DiaDefGenerator(Linker(PROJECT), handler) + cl_config = Config() + cl_config.classes = ['Specialization'] + cl_h = DiaDefGenerator(Linker(PROJECT), DiadefsHandler(cl_config) ) + self.assertEqual( (0, 0), df_h._get_levels()) + self.assertEqual( False, df_h.module_names) + self.assertEqual( (-1, -1), cl_h._get_levels()) + self.assertEqual( True, cl_h.module_names) + for hndl in [df_h, cl_h]: + hndl.config.all_ancestors = True + hndl.config.all_associated = True + hndl.config.module_names = True + hndl._set_default_options() + self.assertEqual( (-1, -1), hndl._get_levels()) + self.assertEqual( True, hndl.module_names) + handler = DiadefsHandler( Config()) + df_h = DiaDefGenerator(Linker(PROJECT), handler) + cl_config = Config() + cl_config.classes = ['Specialization'] + cl_h = DiaDefGenerator(Linker(PROJECT), DiadefsHandler(cl_config) ) + for hndl in [df_h, cl_h]: + hndl.config.show_ancestors = 2 + hndl.config.show_associated = 1 + hndl.config.module_names = False + hndl._set_default_options() + self.assertEqual( (2, 1), hndl._get_levels()) + self.assertEqual( False, hndl.module_names) + #def test_default_values(self): + """test efault values for package or class diagrams""" + # TODO : should test difference between default values for package + # or class diagrams + +class DefaultDiadefGeneratorTC(unittest.TestCase): + def test_known_values1(self): + dd = DefaultDiadefGenerator(Linker(PROJECT), HANDLER).visit(PROJECT) + self.assertEqual(len(dd), 2) + keys = [d.TYPE for d in dd] + self.assertEqual(keys, ['package', 'class']) + pd = dd[0] + self.assertEqual(pd.title, 'packages No Name') + modules = sorted([(isinstance(m.node, astroid.Module), m.title) + for m in pd.objects]) + self.assertEqual(modules, [(True, 'data'), + (True, 'data.clientmodule_test'), + (True, 'data.suppliermodule_test')]) + cd = dd[1] + self.assertEqual(cd.title, 'classes No Name') + classes = _process_classes(cd.objects) + self.assertEqual(classes, [(True, 'Ancestor'), + (True, 'DoNothing'), + (True, 'Interface'), + (True, 'Specialization')] + ) + + _should_rels = [('association', 'DoNothing', 'Ancestor'), + ('association', 'DoNothing', 'Specialization'), + ('implements', 'Ancestor', 'Interface'), + ('specialization', 'Specialization', 'Ancestor')] + def test_exctract_relations(self): + """test extract_relations between classes""" + cd = DefaultDiadefGenerator(Linker(PROJECT), HANDLER).visit(PROJECT)[1] + cd.extract_relationships() + relations = _process_relations(cd.relationships) + self.assertEqual(relations, self._should_rels) + + def test_functional_relation_extraction(self): + """functional test of relations extraction; + different classes possibly in different modules""" + # XXX should be catching pyreverse environnement problem but doesn't + # pyreverse doesn't extracts the relations but this test ok + project = get_project('data') + handler = DiadefsHandler(Config()) + diadefs = handler.get_diadefs(project, Linker(project, tag=True) ) + cd = diadefs[1] + relations = _process_relations(cd.relationships) + self.assertEqual(relations, self._should_rels) + + def test_known_values2(self): + project = get_project('data.clientmodule_test') + dd = DefaultDiadefGenerator(Linker(project), HANDLER).visit(project) + self.assertEqual(len(dd), 1) + keys = [d.TYPE for d in dd] + self.assertEqual(keys, ['class']) + cd = dd[0] + self.assertEqual(cd.title, 'classes No Name') + classes = _process_classes(cd.objects) + self.assertEqual(classes, [(True, 'Ancestor'), + (True, 'DoNothing'), + (True, 'Specialization')] + ) + +class ClassDiadefGeneratorTC(unittest.TestCase): + def test_known_values1(self): + HANDLER.config.classes = ['Specialization'] + cdg = ClassDiadefGenerator(Linker(PROJECT), HANDLER) + special = 'data.clientmodule_test.Specialization' + cd = cdg.class_diagram(PROJECT, special) + self.assertEqual(cd.title, special) + classes = _process_classes(cd.objects) + self.assertEqual(classes, [(True, 'data.clientmodule_test.Ancestor'), + (True, special), + (True, 'data.suppliermodule_test.DoNothing'), + ]) + + def test_known_values2(self): + HANDLER.config.module_names = False + cd = ClassDiadefGenerator(Linker(PROJECT), HANDLER).class_diagram(PROJECT, 'data.clientmodule_test.Specialization') + self.assertEqual(cd.title, 'data.clientmodule_test.Specialization') + classes = _process_classes(cd.objects) + self.assertEqual(classes, [(True, 'Ancestor'), + (True, 'DoNothing'), + (True, 'Specialization') + ]) + + +if __name__ == '__main__': + unittest.main() diff --git a/pylint/test/unittest_pyreverse_writer.py b/pylint/test/unittest_pyreverse_writer.py new file mode 100644 index 0000000..b4da835 --- /dev/null +++ b/pylint/test/unittest_pyreverse_writer.py @@ -0,0 +1,136 @@ +# Copyright (c) 2000-2013 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +""" +unittest for visitors.diadefs and extensions.diadefslib modules +""" + + +import os +import sys +import codecs +from os.path import join, dirname, abspath +from difflib import unified_diff +import unittest + +from astroid import MANAGER +from astroid.inspector import Linker + +from pylint.pyreverse.diadefslib import DefaultDiadefGenerator, DiadefsHandler +from pylint.pyreverse.writer import DotWriter +from pylint.pyreverse.utils import get_visibility + + +_DEFAULTS = { + 'all_ancestors': None, 'show_associated': None, + 'module_names': None, + 'output_format': 'dot', 'diadefs_file': None, 'quiet': 0, + 'show_ancestors': None, 'classes': (), 'all_associated': None, + 'mode': 'PUB_ONLY', 'show_builtin': False, 'only_classnames': False + } + +class Config(object): + """config object for tests""" + def __init__(self): + for attr, value in _DEFAULTS.items(): + setattr(self, attr, value) + + +def _file_lines(path): + # we don't care about the actual encoding, but python3 forces us to pick one + with codecs.open(path, encoding='latin1') as stream: + lines = [line.strip() for line in stream.readlines() + if (line.find('squeleton generated by ') == -1 and + not line.startswith('__revision__ = "$Id:'))] + return [line for line in lines if line] + +def get_project(module, name=None): + """return a astroid project representation""" + # flush cache + MANAGER._modules_by_name = {} + def _astroid_wrapper(func, modname): + return func(modname) + return MANAGER.project_from_files([module], _astroid_wrapper, + project_name=name) + +CONFIG = Config() + +class DotWriterTC(unittest.TestCase): + + @classmethod + def setUpClass(cls): + project = get_project(os.path.join(os.path.dirname(__file__), 'data')) + linker = Linker(project) + handler = DiadefsHandler(CONFIG) + dd = DefaultDiadefGenerator(linker, handler).visit(project) + for diagram in dd: + diagram.extract_relationships() + writer = DotWriter(CONFIG) + writer.write(dd) + + @classmethod + def tearDownClass(cls): + for fname in ('packages_No_Name.dot', 'classes_No_Name.dot',): + try: + os.remove(fname) + except: + continue + + def _test_same_file(self, generated_file): + expected_file = os.path.join(os.path.dirname(__file__), 'data', generated_file) + generated = _file_lines(generated_file) + expected = _file_lines(expected_file) + generated = '\n'.join(generated) + expected = '\n'.join(expected) + files = "\n *** expected : %s, generated : %s \n" % ( + expected_file, generated_file) + self.assertEqual(expected, generated, '%s%s' % ( + files, '\n'.join(line for line in unified_diff( + expected.splitlines(), generated.splitlines() ))) ) + os.remove(generated_file) + + def test_package_diagram(self): + self._test_same_file('packages_No_Name.dot') + + def test_class_diagram(self): + self._test_same_file('classes_No_Name.dot') + + + +class GetVisibilityTC(unittest.TestCase): + + def test_special(self): + for name in ["__reduce_ex__", "__setattr__"]: + self.assertEqual(get_visibility(name), 'special') + + def test_private(self): + for name in ["__g_", "____dsf", "__23_9"]: + got = get_visibility(name) + self.assertEqual(got, 'private', + 'got %s instead of private for value %s' % (got, name)) + + def test_public(self): + self.assertEqual(get_visibility('simple'), 'public') + + def test_protected(self): + for name in ["_","__", "___", "____", "_____", "___e__", + "_nextsimple", "_filter_it_"]: + got = get_visibility(name) + self.assertEqual(got, 'protected', + 'got %s instead of protected for value %s' % (got, name)) + + +if __name__ == '__main__': + unittest.main() diff --git a/pylint/test/unittest_reporters_json.py b/pylint/test/unittest_reporters_json.py new file mode 100644 index 0000000..1b0ae1d --- /dev/null +++ b/pylint/test/unittest_reporters_json.py @@ -0,0 +1,61 @@ +# Copyright (c) 2003-2015 LOGILAB S.A. (Paris, FRANCE). +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +"""Test for the JSON reporter.""" + +import json +import unittest + +import six + +from pylint.lint import PyLinter +from pylint import checkers +from pylint.reporters.json import JSONReporter + + +class TestJSONReporter(unittest.TestCase): + + def test_simple_json_output(self): + output = six.StringIO() + + reporter = JSONReporter() + linter = PyLinter(reporter=reporter) + checkers.initialize(linter) + + linter.config.persistent = 0 + linter.reporter.set_output(output) + linter.open() + linter.set_current_module('0123') + linter.add_message('line-too-long', line=1, args=(1, 2)) + + # we call this method because we didn't actually run the checkers + reporter.display_results(None) + expected_result = [[ + ("column", 0), + ("line", 1), + ("message", "Line too long (1/2)"), + ("module", "0123"), + ("obj", ""), + ("path", "0123"), + ("symbol", "line-too-long"), + ("type", "convention"), + ]] + report_result = json.loads(output.getvalue()) + report_result = [sorted(report_result[0].items(), + key=lambda item: item[0])] + self.assertEqual(report_result, expected_result) + + +if __name__ == '__main__': + unittest.main() diff --git a/pylint/test/unittest_reporting.py b/pylint/test/unittest_reporting.py new file mode 100644 index 0000000..03c108e --- /dev/null +++ b/pylint/test/unittest_reporting.py @@ -0,0 +1,158 @@ +# Copyright (c) 2003-2014 LOGILAB S.A. (Paris, FRANCE). +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +import os +from os.path import join, dirname, abspath +import unittest +import warnings + +import six + +from logilab.common.ureports import Section +from pylint.lint import PyLinter +from pylint import checkers +from pylint.reporters.text import TextReporter, ParseableTextReporter +from pylint.reporters.html import HTMLReporter + +HERE = abspath(dirname(__file__)) +INPUTDIR = join(HERE, 'input') + +class PyLinterTC(unittest.TestCase): + + def setUp(self): + self.linter = PyLinter(reporter=TextReporter()) + self.linter.disable('I') + self.linter.config.persistent = 0 + # register checkers + checkers.initialize(self.linter) + os.environ.pop('PYLINTRC', None) + + def test_template_option(self): + output = six.StringIO() + self.linter.reporter.set_output(output) + self.linter.set_option('msg-template', '{msg_id}:{line:03d}') + self.linter.open() + self.linter.set_current_module('0123') + self.linter.add_message('C0301', line=1, args=(1, 2)) + self.linter.add_message('line-too-long', line=2, args=(3, 4)) + self.assertMultiLineEqual(output.getvalue(), + '************* Module 0123\n' + 'C0301:001\n' + 'C0301:002\n') + + def test_parseable_output_deprecated(self): + with warnings.catch_warnings(record=True) as cm: + warnings.simplefilter("always") + ParseableTextReporter() + + self.assertEqual(len(cm), 1) + self.assertIsInstance(cm[0].message, DeprecationWarning) + + def test_parseable_output_regression(self): + output = six.StringIO() + with warnings.catch_warnings(record=True): + linter = PyLinter(reporter=ParseableTextReporter()) + + checkers.initialize(linter) + linter.config.persistent = 0 + linter.reporter.set_output(output) + linter.set_option('output-format', 'parseable') + linter.open() + linter.set_current_module('0123') + linter.add_message('line-too-long', line=1, args=(1, 2)) + self.assertMultiLineEqual(output.getvalue(), + '************* Module 0123\n' + '0123:1: [C0301(line-too-long), ] ' + 'Line too long (1/2)\n') + + def test_html_reporter_msg_template(self): + expected = ''' +<html> +<body> +<div> +<div> +<h2>Messages</h2> +<table> +<tr class="header"> +<th>category</th> +<th>msg_id</th> +</tr> +<tr class="even"> +<td>warning</td> +<td>W0332</td> +</tr> +</table> +</div> +</div> +</body> +</html>'''.strip().splitlines() + output = six.StringIO() + linter = PyLinter(reporter=HTMLReporter()) + checkers.initialize(linter) + linter.config.persistent = 0 + linter.reporter.set_output(output) + linter.set_option('msg-template', '{category}{msg_id}') + linter.open() + linter.set_current_module('0123') + linter.add_message('lowercase-l-suffix', line=1) + linter.reporter.display_results(Section()) + self.assertEqual(output.getvalue().splitlines(), expected) + + @unittest.expectedFailure + def test_html_reporter_type(self): + # Integration test for issue #263 + # https://bitbucket.org/logilab/pylint/issue/263/html-report-type-problems + expected = '''<html> +<body> +<div> +<div> +<h2>Messages</h2> +<table> +<tr class="header"> +<th>type</th> +<th>module</th> +<th>object</th> +<th>line</th> +<th>col_offset</th> +<th>message</th> +</tr> +<tr class="even"> +<td>convention</td> +<td>0123</td> +<td> </td> +<td>1</td> +<td>0</td> +<td>Exactly one space required before comparison +a< 5: print "zero"</td> +</tr> +</table> +</div> +</div> +</body> +</html> +''' + output = six.StringIO() + linter = PyLinter(reporter=HTMLReporter()) + checkers.initialize(linter) + linter.config.persistent = 0 + linter.reporter.set_output(output) + linter.open() + linter.set_current_module('0123') + linter.add_message('bad-whitespace', line=1, + args=('Exactly one', 'required', 'before', + 'comparison', 'a< 5: print "zero"')) + linter.reporter.display_results(Section()) + self.assertMultiLineEqual(output.getvalue(), expected) + +if __name__ == '__main__': + unittest.main() diff --git a/pylint/test/unittest_utils.py b/pylint/test/unittest_utils.py new file mode 100644 index 0000000..d631dbb --- /dev/null +++ b/pylint/test/unittest_utils.py @@ -0,0 +1,62 @@ +# Copyright 2013 Google Inc. +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +import unittest + +from astroid import test_utils +from pylint import utils +from pylint import interfaces +from pylint.checkers.utils import check_messages + + +class PyLintASTWalkerTest(unittest.TestCase): + class MockLinter(object): + def __init__(self, msgs): + self._msgs = msgs + + def is_message_enabled(self, msgid): + return self._msgs.get(msgid, True) + + class Checker(object): + def __init__(self): + self.called = set() + + @check_messages('first-message') + def visit_module(self, module): + self.called.add('module') + + @check_messages('second-message') + def visit_callfunc(self, module): + raise NotImplementedError + + @check_messages('second-message', 'third-message') + def visit_assname(self, module): + self.called.add('assname') + + @check_messages('second-message') + def leave_assname(self, module): + raise NotImplementedError + + def testCheckMessages(self): + linter = self.MockLinter({'first-message': True, + 'second-message': False, + 'third-message': True}) + walker = utils.PyLintASTWalker(linter) + checker = self.Checker() + walker.add_checker(checker) + walker.walk(test_utils.build_module("x = func()")) + self.assertEqual(set(['module', 'assname']), checker.called) + + +if __name__ == '__main__': + unittest.main() diff --git a/pylint/testutils.py b/pylint/testutils.py new file mode 100644 index 0000000..2f9af4d --- /dev/null +++ b/pylint/testutils.py @@ -0,0 +1,412 @@ +# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +"""functional/non regression tests for pylint""" +from __future__ import print_function + +import collections +import contextlib +import functools +import os +import sys +import re +import unittest +import tempfile +import tokenize + +from glob import glob +from os import linesep, getcwd, sep +from os.path import abspath, basename, dirname, isdir, join, splitext + +from astroid import test_utils + +from pylint import checkers +from pylint.utils import PyLintASTWalker +from pylint.reporters import BaseReporter +from pylint.interfaces import IReporter +from pylint.lint import PyLinter + +import six +from six.moves import StringIO + + +# Utils + +SYS_VERS_STR = '%d%d%d' % sys.version_info[:3] +TITLE_UNDERLINES = ['', '=', '-', '.'] +PREFIX = abspath(dirname(__file__)) +PY3K = sys.version_info[0] == 3 + +def fix_path(): + sys.path.insert(0, PREFIX) + +def get_tests_info(input_dir, msg_dir, prefix, suffix): + """get python input examples and output messages + + We use following conventions for input files and messages: + for different inputs: + test for python >= x.y -> input = <name>_pyxy.py + test for python < x.y -> input = <name>_py_xy.py + for one input and different messages: + message for python >= x.y -> message = <name>_pyxy.txt + lower versions -> message with highest num + """ + result = [] + for fname in glob(join(input_dir, prefix + '*' + suffix)): + infile = basename(fname) + fbase = splitext(infile)[0] + # filter input files : + pyrestr = fbase.rsplit('_py', 1)[-1] # like _26 or 26 + if pyrestr.isdigit(): # '24', '25'... + if SYS_VERS_STR < pyrestr: + continue + if pyrestr.startswith('_') and pyrestr[1:].isdigit(): + # skip test for higher python versions + if SYS_VERS_STR >= pyrestr[1:]: + continue + messages = glob(join(msg_dir, fbase + '*.txt')) + # the last one will be without ext, i.e. for all or upper versions: + if messages: + for outfile in sorted(messages, reverse=True): + py_rest = outfile.rsplit('_py', 1)[-1][:-4] + if py_rest.isdigit() and SYS_VERS_STR >= py_rest: + break + else: + # This will provide an error message indicating the missing filename. + outfile = join(msg_dir, fbase + '.txt') + result.append((infile, outfile)) + return result + + +class TestReporter(BaseReporter): + """reporter storing plain text messages""" + + __implements____ = IReporter + + def __init__(self): # pylint: disable=super-init-not-called + + self.message_ids = {} + self.reset() + self.path_strip_prefix = getcwd() + sep + + 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 """ + _, _, obj, line, _ = location + self.message_ids[msg_id] = 1 + if obj: + obj = ':%s' % obj + sigle = msg_id[0] + if PY3K and linesep != '\n': + # 2to3 writes os.linesep instead of using + # the previosly used line separators + msg = msg.replace('\r\n', '\n') + self.messages.append('%s:%3s%s: %s' % (sigle, line, obj, msg)) + + def finalize(self): + self.messages.sort() + for msg in self.messages: + print(msg, file=self.out) + result = self.out.getvalue() + self.reset() + return result + + def display_results(self, layout): + """ignore layouts""" + + +class Message(collections.namedtuple('Message', + ['msg_id', 'line', 'node', 'args'])): + def __new__(cls, msg_id, line=None, node=None, args=None): + return tuple.__new__(cls, (msg_id, line, node, args)) + + +class UnittestLinter(object): + """A fake linter class to capture checker messages.""" + # pylint: disable=unused-argument, no-self-use + + def __init__(self): + self._messages = [] + self.stats = {} + + def release_messages(self): + try: + return self._messages + finally: + self._messages = [] + + def add_message(self, msg_id, line=None, node=None, args=None, + confidence=None): + self._messages.append(Message(msg_id, line, node, args)) + + def is_message_enabled(self, *unused_args): + return True + + def add_stats(self, **kwargs): + for name, value in six.iteritems(kwargs): + self.stats[name] = value + return self.stats + + @property + def options_providers(self): + return linter.options_providers + +def set_config(**kwargs): + """Decorator for setting config values on a checker.""" + def _Wrapper(fun): + @functools.wraps(fun) + def _Forward(self): + for key, value in six.iteritems(kwargs): + setattr(self.checker.config, key, value) + if isinstance(self, CheckerTestCase): + # reopen checker in case, it may be interested in configuration change + self.checker.open() + fun(self) + + return _Forward + return _Wrapper + + +class CheckerTestCase(unittest.TestCase): + """A base testcase class for unittesting individual checker classes.""" + CHECKER_CLASS = None + CONFIG = {} + + def setUp(self): + self.linter = UnittestLinter() + self.checker = self.CHECKER_CLASS(self.linter) # pylint: disable=not-callable + for key, value in six.iteritems(self.CONFIG): + setattr(self.checker.config, key, value) + self.checker.open() + + @contextlib.contextmanager + def assertNoMessages(self): + """Assert that no messages are added by the given method.""" + with self.assertAddsMessages(): + yield + + @contextlib.contextmanager + def assertAddsMessages(self, *messages): + """Assert that exactly the given method adds the given messages. + + The list of messages must exactly match *all* the messages added by the + method. Additionally, we check to see whether the args in each message can + actually be substituted into the message string. + """ + yield + got = self.linter.release_messages() + msg = ('Expected messages did not match actual.\n' + 'Expected:\n%s\nGot:\n%s' % ('\n'.join(repr(m) for m in messages), + '\n'.join(repr(m) for m in got))) + self.assertEqual(list(messages), got, msg) + + def walk(self, node): + """recursive walk on the given node""" + walker = PyLintASTWalker(linter) + walker.add_checker(self.checker) + walker.walk(node) + + +# Init +test_reporter = TestReporter() +linter = PyLinter() +linter.set_reporter(test_reporter) +linter.config.persistent = 0 +checkers.initialize(linter) +linter.global_set_option('required-attributes', ('__revision__',)) + +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(r'^func_i\d\d\d\d$') + +def exception_str(self, ex): # pylint: disable=unused-argument + """function used to replace default __str__ method of exception instances""" + return 'in %s\n:: %s' % (ex.file, ', '.join(ex.args)) + +# Test classes + +class LintTestUsingModule(unittest.TestCase): + INPUT_DIR = None + DEFAULT_PACKAGE = 'input' + package = DEFAULT_PACKAGE + linter = linter + module = None + depends = None + output = None + _TEST_TYPE = 'module' + maxDiff = None + + def shortDescription(self): + values = {'mode' : self._TEST_TYPE, + 'input': self.module, + 'pkg': self.package, + 'cls': self.__class__.__name__} + + if self.package == self.DEFAULT_PACKAGE: + msg = '%(mode)s test of input file "%(input)s" (%(cls)s)' + else: + msg = '%(mode)s test of input file "%(input)s" in "%(pkg)s" (%(cls)s)' + return msg % values + + def test_functionality(self): + tocheck = [self.package+'.'+self.module] + if self.depends: + tocheck += [self.package+'.%s' % name.replace('.py', '') + for name, _ in self.depends] + self._test(tocheck) + + def _check_result(self, got): + self.assertMultiLineEqual(self._get_expected().strip()+'\n', + got.strip()+'\n') + + def _test(self, tocheck): + if INFO_TEST_RGX.match(self.module): + self.linter.enable('I') + else: + self.linter.disable('I') + try: + self.linter.check(tocheck) + except Exception as ex: + # need finalization to restore a correct state + self.linter.reporter.finalize() + ex.file = tocheck + print(ex) + ex.__str__ = exception_str + raise + self._check_result(self.linter.reporter.finalize()) + + def _has_output(self): + return not self.module.startswith('func_noerror_') + + def _get_expected(self): + if self._has_output() and self.output: + with open(self.output, 'U') as fobj: + return fobj.read().strip() + '\n' + else: + return '' + +class LintTestUsingFile(LintTestUsingModule): + + _TEST_TYPE = 'file' + + def test_functionality(self): + importable = join(self.INPUT_DIR, self.module) + # python also prefers packages over simple modules. + if not isdir(importable): + importable += '.py' + tocheck = [importable] + if self.depends: + tocheck += [join(self.INPUT_DIR, name) for name, _file in self.depends] + self._test(tocheck) + +class LintTestUpdate(LintTestUsingModule): + + _TEST_TYPE = 'update' + + def _check_result(self, got): + if self._has_output(): + try: + expected = self._get_expected() + except IOError: + expected = '' + if got != expected: + with open(self.output, 'w') as fobj: + fobj.write(got) + +# Callback + +def cb_test_gen(base_class): + def call(input_dir, msg_dir, module_file, messages_file, dependencies): + # pylint: disable=no-init + class LintTC(base_class): + module = module_file.replace('.py', '') + output = messages_file + depends = dependencies or None + INPUT_DIR = input_dir + MSG_DIR = msg_dir + return LintTC + return call + +# Main function + +def make_tests(input_dir, msg_dir, filter_rgx, callbacks): + """generate tests classes from test info + + return the list of generated test classes + """ + if filter_rgx: + is_to_run = re.compile(filter_rgx).search + else: + is_to_run = lambda x: 1 + tests = [] + for module_file, messages_file in ( + get_tests_info(input_dir, msg_dir, 'func_', '') + ): + if not is_to_run(module_file) or module_file.endswith('.pyc'): + continue + base = module_file.replace('func_', '').replace('.py', '') + + dependencies = get_tests_info(input_dir, msg_dir, base, '.py') + + for callback in callbacks: + test = callback(input_dir, msg_dir, module_file, messages_file, + dependencies) + if test: + tests.append(test) + return tests + +def tokenize_str(code): + return list(tokenize.generate_tokens(StringIO(code).readline)) + +@contextlib.contextmanager +def create_tempfile(content=None): + """Create a new temporary file. + + If *content* parameter is given, then it will be written + in the temporary file, before passing it back. + This is a context manager and should be used with a *with* statement. + """ + # Can't use tempfile.NamedTemporaryFile here + # because on Windows the file must be closed before writing to it, + # see http://bugs.python.org/issue14243 + fd, tmp = tempfile.mkstemp() + if content: + if sys.version_info >= (3, 0): + # erff + os.write(fd, bytes(content, 'ascii')) + else: + os.write(fd, content) + try: + yield tmp + finally: + os.close(fd) + os.remove(tmp) + +@contextlib.contextmanager +def create_file_backed_module(code): + """Create an astroid module for the given code, backed by a real file.""" + with create_tempfile() as temp: + module = test_utils.build_module(code) + module.file = temp + yield module diff --git a/pylint/utils.py b/pylint/utils.py new file mode 100644 index 0000000..6685c4a --- /dev/null +++ b/pylint/utils.py @@ -0,0 +1,919 @@ +# Copyright (c) 2003-2014 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +"""some various utilities and helper classes, most of them used in the +main pylint class +""" +from __future__ import print_function + +import collections +import os +import re +import sys +import tokenize +import warnings +from os.path import dirname, basename, splitext, exists, isdir, join, normpath + +import six +from six.moves import zip # pylint: disable=redefined-builtin + +from logilab.common.interface import implements +from logilab.common.textutils import normalize_text +from logilab.common.configuration import rest_format_section +from logilab.common.ureports import Section + +from astroid import nodes, Module +from astroid.modutils import modpath_from_file, get_module_files, \ + file_from_modpath, load_module_from_file + +from pylint.interfaces import IRawChecker, ITokenChecker, UNDEFINED + + +class UnknownMessage(Exception): + """raised when a unregistered message id is encountered""" + +class EmptyReport(Exception): + """raised when a report is empty and so should not be displayed""" + + +MSG_TYPES = { + 'I' : 'info', + 'C' : 'convention', + 'R' : 'refactor', + 'W' : 'warning', + 'E' : 'error', + 'F' : 'fatal' + } +MSG_TYPES_LONG = {v: k for k, v in six.iteritems(MSG_TYPES)} + +MSG_TYPES_STATUS = { + 'I' : 0, + 'C' : 16, + 'R' : 8, + 'W' : 4, + 'E' : 2, + 'F' : 1 + } + +_MSG_ORDER = 'EWRCIF' +MSG_STATE_SCOPE_CONFIG = 0 +MSG_STATE_SCOPE_MODULE = 1 +MSG_STATE_CONFIDENCE = 2 + +OPTION_RGX = re.compile(r'\s*#.*\bpylint:(.*)') + +# The line/node distinction does not apply to fatal errors and reports. +_SCOPE_EXEMPT = 'FR' + +class WarningScope(object): + LINE = 'line-based-msg' + NODE = 'node-based-msg' + +_MsgBase = collections.namedtuple( + '_MsgBase', + ['msg_id', 'symbol', 'msg', 'C', 'category', 'confidence', + 'abspath', 'path', 'module', 'obj', 'line', 'column']) + + +class Message(_MsgBase): + """This class represent a message to be issued by the reporters""" + def __new__(cls, msg_id, symbol, location, msg, confidence): + return _MsgBase.__new__( + cls, msg_id, symbol, msg, msg_id[0], MSG_TYPES[msg_id[0]], + confidence, *location) + + def format(self, template): + """Format the message according to the given template. + + The template format is the one of the format method : + cf. http://docs.python.org/2/library/string.html#formatstrings + """ + # For some reason, _asdict on derived namedtuples does not work with + # Python 3.4. Needs some investigation. + return template.format(**dict(zip(self._fields, self))) + + +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) + +def category_id(cid): + cid = cid.upper() + if cid in MSG_TYPES: + return cid + return MSG_TYPES_LONG.get(cid) + + +def _decoding_readline(stream, module): + return lambda: stream.readline().decode(module.file_encoding, + 'replace') + + +def tokenize_module(module): + with module.stream() as stream: + readline = stream.readline + if sys.version_info < (3, 0): + if module.file_encoding is not None: + readline = _decoding_readline(stream, module) + return list(tokenize.generate_tokens(readline)) + return list(tokenize.tokenize(readline)) + +def build_message_def(checker, msgid, msg_tuple): + if implements(checker, (IRawChecker, ITokenChecker)): + default_scope = WarningScope.LINE + else: + default_scope = WarningScope.NODE + options = {} + if len(msg_tuple) > 3: + (msg, symbol, descr, options) = msg_tuple + elif len(msg_tuple) > 2: + (msg, symbol, descr) = msg_tuple[:3] + else: + # messages should have a symbol, but for backward compatibility + # they may not. + (msg, descr) = msg_tuple + warnings.warn("[pylint 0.26] description of message %s doesn't include " + "a symbolic name" % msgid, DeprecationWarning) + symbol = None + options.setdefault('scope', default_scope) + return MessageDefinition(checker, msgid, msg, descr, symbol, **options) + + +class MessageDefinition(object): + def __init__(self, checker, msgid, msg, descr, symbol, scope, + minversion=None, maxversion=None, old_names=None): + self.checker = checker + assert len(msgid) == 5, 'Invalid message id %s' % msgid + assert msgid[0] in MSG_TYPES, \ + 'Bad message type %s in %r' % (msgid[0], msgid) + self.msgid = msgid + self.msg = msg + self.descr = descr + self.symbol = symbol + self.scope = scope + self.minversion = minversion + self.maxversion = maxversion + self.old_names = old_names or [] + + def may_be_emitted(self): + """return True if message may be emitted using the current interpreter""" + if self.minversion is not None and self.minversion > sys.version_info: + return False + if self.maxversion is not None and self.maxversion <= sys.version_info: + return False + return True + + def format_help(self, checkerref=False): + """return the help string for the given message id""" + desc = self.descr + if checkerref: + desc += ' This message belongs to the %s checker.' % \ + self.checker.name + title = self.msg + if self.symbol: + msgid = '%s (%s)' % (self.symbol, self.msgid) + else: + msgid = self.msgid + if self.minversion or self.maxversion: + restr = [] + if self.minversion: + restr.append('< %s' % '.'.join([str(n) for n in self.minversion])) + if self.maxversion: + restr.append('>= %s' % '.'.join([str(n) for n in self.maxversion])) + restr = ' or '.join(restr) + if checkerref: + desc += " It can't be emitted when using Python %s." % restr + else: + desc += " This message can't be emitted when using Python %s." % restr + desc = normalize_text(' '.join(desc.split()), indent=' ') + if title != '%s': + title = title.splitlines()[0] + return ':%s: *%s*\n%s' % (msgid, title, desc) + return ':%s:\n%s' % (msgid, desc) + + +class MessagesHandlerMixIn(object): + """a mix-in class containing all the messages related methods for the main + lint class + """ + + def __init__(self): + self._msgs_state = {} + self.msg_status = 0 + + def disable(self, msgid, scope='package', line=None, ignore_unknown=False): + """don't output message of the given id""" + assert scope in ('package', 'module') + # handle disable=all by disabling all categories + if msgid == 'all': + for msgid in MSG_TYPES: + self.disable(msgid, scope, line) + return + # msgid is a category? + catid = category_id(msgid) + if catid is not None: + for _msgid in self.msgs_store._msgs_by_category.get(catid): + self.disable(_msgid, scope, line) + return + # msgid is a checker name? + if msgid.lower() in self._checkers: + msgs_store = self.msgs_store + for checker in self._checkers[msgid.lower()]: + for _msgid in checker.msgs: + if _msgid in msgs_store._alternative_names: + self.disable(_msgid, scope, line) + return + # msgid is report id? + if msgid.lower().startswith('rp'): + self.disable_report(msgid) + return + + try: + # msgid is a symbolic or numeric msgid. + msg = self.msgs_store.check_message_id(msgid) + except UnknownMessage: + if ignore_unknown: + return + raise + + if scope == 'module': + self.file_state.set_msg_status(msg, line, False) + if msg.symbol != 'locally-disabled': + self.add_message('locally-disabled', line=line, + args=(msg.symbol, msg.msgid)) + + else: + msgs = self._msgs_state + msgs[msg.msgid] = False + # sync configuration object + self.config.disable = [mid for mid, val in six.iteritems(msgs) + if not val] + + def enable(self, msgid, scope='package', line=None, ignore_unknown=False): + """reenable message of the given id""" + assert scope in ('package', 'module') + catid = category_id(msgid) + # msgid is a category? + if catid is not None: + for msgid in self.msgs_store._msgs_by_category.get(catid): + self.enable(msgid, scope, line) + return + # msgid is a checker name? + if msgid.lower() in self._checkers: + for checker in self._checkers[msgid.lower()]: + for msgid_ in checker.msgs: + self.enable(msgid_, scope, line) + return + # msgid is report id? + if msgid.lower().startswith('rp'): + self.enable_report(msgid) + return + + try: + # msgid is a symbolic or numeric msgid. + msg = self.msgs_store.check_message_id(msgid) + except UnknownMessage: + if ignore_unknown: + return + raise + + if scope == 'module': + self.file_state.set_msg_status(msg, line, True) + self.add_message('locally-enabled', line=line, args=(msg.symbol, msg.msgid)) + else: + msgs = self._msgs_state + msgs[msg.msgid] = True + # sync configuration object + self.config.enable = [mid for mid, val in six.iteritems(msgs) if val] + + def get_message_state_scope(self, msgid, line=None, confidence=UNDEFINED): + """Returns the scope at which a message was enabled/disabled.""" + if self.config.confidence and confidence.name not in self.config.confidence: + return MSG_STATE_CONFIDENCE + try: + if line in self.file_state._module_msgs_state[msgid]: + return MSG_STATE_SCOPE_MODULE + except (KeyError, TypeError): + return MSG_STATE_SCOPE_CONFIG + + def is_message_enabled(self, msg_descr, line=None, confidence=None): + """return true if the message associated to the given message id is + enabled + + msgid may be either a numeric or symbolic message id. + """ + if self.config.confidence and confidence: + if confidence.name not in self.config.confidence: + return False + try: + msgid = self.msgs_store.check_message_id(msg_descr).msgid + except UnknownMessage: + # The linter checks for messages that are not registered + # due to version mismatch, just treat them as message IDs + # for now. + msgid = msg_descr + if line is None: + return self._msgs_state.get(msgid, True) + try: + return self.file_state._module_msgs_state[msgid][line] + except KeyError: + return self._msgs_state.get(msgid, True) + + def add_message(self, msg_descr, line=None, node=None, args=None, confidence=UNDEFINED): + """Adds a message given by ID or name. + + If provided, the message string is expanded using args + + AST checkers should must the node argument (but may optionally + provide line if the line number is different), raw and token checkers + must provide the line argument. + """ + msg_info = self.msgs_store.check_message_id(msg_descr) + msgid = msg_info.msgid + # backward compatibility, message may not have a symbol + symbol = msg_info.symbol or msgid + # Fatal messages and reports are special, the node/scope distinction + # does not apply to them. + if msgid[0] not in _SCOPE_EXEMPT: + if msg_info.scope == WarningScope.LINE: + assert node is None and line is not None, ( + 'Message %s must only provide line, got line=%s, node=%s' % (msgid, line, node)) + elif msg_info.scope == WarningScope.NODE: + # Node-based warnings may provide an override line. + assert node is not None, 'Message %s must provide Node, got None' + + if line is None and node is not None: + line = node.fromlineno + if hasattr(node, 'col_offset'): + col_offset = node.col_offset # XXX measured in bytes for utf-8, divide by two for chars? + else: + col_offset = None + # should this message be displayed + if not self.is_message_enabled(msgid, line, confidence): + self.file_state.handle_ignored_message( + self.get_message_state_scope(msgid, line, confidence), + msgid, line, node, args, confidence) + return + # update stats + msg_cat = MSG_TYPES[msgid[0]] + self.msg_status |= MSG_TYPES_STATUS[msgid[0]] + self.stats[msg_cat] += 1 + self.stats['by_module'][self.current_name][msg_cat] += 1 + try: + self.stats['by_msg'][symbol] += 1 + except KeyError: + self.stats['by_msg'][symbol] = 1 + # expand message ? + msg = msg_info.msg + if args: + msg %= args + # get module and object + if node is None: + module, obj = self.current_name, '' + abspath = self.current_file + else: + module, obj = get_module_and_frameid(node) + abspath = node.root().file + path = abspath.replace(self.reporter.path_strip_prefix, '') + # add the message + self.reporter.handle_message( + Message(msgid, symbol, + (abspath, path, module, obj, line or 1, col_offset or 0), msg, confidence)) + + def print_full_documentation(self): + """output a full documentation in ReST format""" + print("Pylint global options and switches") + print("----------------------------------") + print("") + print("Pylint provides global options and switches.") + print("") + + by_checker = {} + for checker in self.get_checkers(): + if checker.name == 'master': + if checker.options: + for section, options in checker.options_by_section(): + if section is None: + title = 'General options' + else: + title = '%s options' % section.capitalize() + print(title) + print('~' * len(title)) + rest_format_section(sys.stdout, None, options) + print("") + else: + try: + by_checker[checker.name][0] += checker.options_and_values() + by_checker[checker.name][1].update(checker.msgs) + by_checker[checker.name][2] += checker.reports + except KeyError: + by_checker[checker.name] = [list(checker.options_and_values()), + dict(checker.msgs), + list(checker.reports)] + + print("Pylint checkers' options and switches") + print("-------------------------------------") + print("") + print("Pylint checkers can provide three set of features:") + print("") + print("* options that control their execution,") + print("* messages that they can raise,") + print("* reports that they can generate.") + print("") + print("Below is a list of all checkers and their features.") + print("") + + for checker, (options, msgs, reports) in six.iteritems(by_checker): + title = '%s checker' % (checker.replace("_", " ").title()) + print(title) + print('~' * len(title)) + print("") + print("Verbatim name of the checker is ``%s``." % checker) + print("") + if options: + title = 'Options' + print(title) + print('^' * len(title)) + rest_format_section(sys.stdout, None, options) + print("") + if msgs: + title = 'Messages' + print(title) + print('~' * len(title)) + for msgid, msg in sorted(six.iteritems(msgs), + key=lambda kv: (_MSG_ORDER.index(kv[0][0]), kv[1])): + msg = build_message_def(checker, msgid, msg) + print(msg.format_help(checkerref=False)) + print("") + if reports: + title = 'Reports' + print(title) + print('~' * len(title)) + for report in reports: + print(':%s: %s' % report[:2]) + print("") + print("") + + +class FileState(object): + """Hold internal state specific to the currently analyzed file""" + + def __init__(self, modname=None): + self.base_name = modname + self._module_msgs_state = {} + self._raw_module_msgs_state = {} + self._ignored_msgs = collections.defaultdict(set) + self._suppression_mapping = {} + + def collect_block_lines(self, msgs_store, module_node): + """Walk the AST to collect block level options line numbers.""" + for msg, lines in six.iteritems(self._module_msgs_state): + self._raw_module_msgs_state[msg] = lines.copy() + orig_state = self._module_msgs_state.copy() + self._module_msgs_state = {} + self._suppression_mapping = {} + self._collect_block_lines(msgs_store, module_node, orig_state) + + def _collect_block_lines(self, msgs_store, node, msg_state): + """Recursivly walk (depth first) AST to collect block level options line + numbers. + """ + for child in node.get_children(): + self._collect_block_lines(msgs_store, child, msg_state) + first = node.fromlineno + last = node.tolineno + # first child line number used to distinguish between disable + # which are the first child of scoped node with those defined later. + # For instance in the code below: + # + # 1. def meth8(self): + # 2. """test late disabling""" + # 3. # pylint: disable=E1102 + # 4. print self.blip + # 5. # pylint: disable=E1101 + # 6. print self.bla + # + # E1102 should be disabled from line 1 to 6 while E1101 from line 5 to 6 + # + # this is necessary to disable locally messages applying to class / + # function using their fromlineno + if isinstance(node, (nodes.Module, nodes.Class, nodes.Function)) and node.body: + firstchildlineno = node.body[0].fromlineno + else: + firstchildlineno = last + for msgid, lines in six.iteritems(msg_state): + for lineno, state in list(lines.items()): + original_lineno = lineno + if first <= lineno <= last: + # Set state for all lines for this block, if the + # warning is applied to nodes. + if msgs_store.check_message_id(msgid).scope == WarningScope.NODE: + if lineno > firstchildlineno: + state = True + first_, last_ = node.block_range(lineno) + else: + first_ = lineno + last_ = last + for line in range(first_, last_+1): + # do not override existing entries + if not line in self._module_msgs_state.get(msgid, ()): + if line in lines: # state change in the same block + state = lines[line] + original_lineno = line + if not state: + self._suppression_mapping[(msgid, line)] = original_lineno + try: + self._module_msgs_state[msgid][line] = state + except KeyError: + self._module_msgs_state[msgid] = {line: state} + del lines[lineno] + + def set_msg_status(self, msg, line, status): + """Set status (enabled/disable) for a given message at a given line""" + assert line > 0 + try: + self._module_msgs_state[msg.msgid][line] = status + except KeyError: + self._module_msgs_state[msg.msgid] = {line: status} + + def handle_ignored_message(self, state_scope, msgid, line, + node, args, confidence): # pylint: disable=unused-argument + """Report an ignored message. + + state_scope is either MSG_STATE_SCOPE_MODULE or MSG_STATE_SCOPE_CONFIG, + depending on whether the message was disabled locally in the module, + or globally. The other arguments are the same as for add_message. + """ + if state_scope == MSG_STATE_SCOPE_MODULE: + try: + orig_line = self._suppression_mapping[(msgid, line)] + self._ignored_msgs[(msgid, orig_line)].add(line) + except KeyError: + pass + + def iter_spurious_suppression_messages(self, msgs_store): + for warning, lines in six.iteritems(self._raw_module_msgs_state): + for line, enable in six.iteritems(lines): + if not enable and (warning, line) not in self._ignored_msgs: + yield 'useless-suppression', line, \ + (msgs_store.get_msg_display_string(warning),) + # don't use iteritems here, _ignored_msgs may be modified by add_message + for (warning, from_), lines in list(self._ignored_msgs.items()): + for line in lines: + yield 'suppressed-message', line, \ + (msgs_store.get_msg_display_string(warning), from_) + + +class MessagesStore(object): + """The messages store knows information about every possible message but has + no particular state during analysis. + """ + + def __init__(self): + # Primary registry for all active messages (i.e. all messages + # that can be emitted by pylint for the underlying Python + # version). It contains the 1:1 mapping from symbolic names + # to message definition objects. + self._messages = {} + # Maps alternative names (numeric IDs, deprecated names) to + # message definitions. May contain several names for each definition + # object. + self._alternative_names = {} + self._msgs_by_category = collections.defaultdict(list) + + @property + def messages(self): + """The list of all active messages.""" + return six.itervalues(self._messages) + + def add_renamed_message(self, old_id, old_symbol, new_symbol): + """Register the old ID and symbol for a warning that was renamed. + + This allows users to keep using the old ID/symbol in suppressions. + """ + msg = self.check_message_id(new_symbol) + msg.old_names.append((old_id, old_symbol)) + self._alternative_names[old_id] = msg + self._alternative_names[old_symbol] = msg + + 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 two first characters + are the checker id and the two last the message id in this checker + """ + chkid = None + for msgid, msg_tuple in six.iteritems(checker.msgs): + msg = build_message_def(checker, msgid, msg_tuple) + assert msg.symbol not in self._messages, \ + 'Message symbol %r is already defined' % msg.symbol + # avoid duplicate / malformed ids + assert msg.msgid not in self._alternative_names, \ + 'Message id %r is already defined' % msgid + assert chkid is None or chkid == msg.msgid[1:3], \ + 'Inconsistent checker part in message id %r' % msgid + chkid = msg.msgid[1:3] + self._messages[msg.symbol] = msg + self._alternative_names[msg.msgid] = msg + for old_id, old_symbol in msg.old_names: + self._alternative_names[old_id] = msg + self._alternative_names[old_symbol] = msg + self._msgs_by_category[msg.msgid[0]].append(msg.msgid) + + def check_message_id(self, msgid): + """returns the Message object for this message. + + msgid may be either a numeric or symbolic id. + + Raises UnknownMessage if the message id is not defined. + """ + if msgid[1:].isdigit(): + msgid = msgid.upper() + for source in (self._alternative_names, self._messages): + try: + return source[msgid] + except KeyError: + pass + raise UnknownMessage('No such message id %s' % msgid) + + def get_msg_display_string(self, msgid): + """Generates a user-consumable representation of a message. + + Can be just the message ID or the ID and the symbol. + """ + return repr(self.check_message_id(msgid).symbol) + + def help_message(self, msgids): + """display help messages for the given message identifiers""" + for msgid in msgids: + try: + print(self.check_message_id(msgid).format_help(checkerref=True)) + print("") + except UnknownMessage as ex: + print(ex) + print("") + continue + + def list_messages(self): + """output full messages list documentation in ReST format""" + msgs = sorted(six.itervalues(self._messages), key=lambda msg: msg.msgid) + for msg in msgs: + if not msg.may_be_emitted(): + continue + print(msg.format_help(checkerref=False)) + print("") + + +class ReportsHandlerMixIn(object): + """a mix-in class containing all the reports and stats manipulation + related methods for the main lint class + """ + def __init__(self): + self._reports = collections.defaultdict(list) + self._reports_state = {} + + def report_order(self): + """ Return a list of reports, sorted in the order + in which they must be called. + """ + return list(self._reports) + + def register_report(self, reportid, r_title, r_cb, checker): + """register a report + + reportid 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 + """ + reportid = reportid.upper() + self._reports[checker].append((reportid, r_title, r_cb)) + + def enable_report(self, reportid): + """disable the report of the given id""" + reportid = reportid.upper() + self._reports_state[reportid] = True + + def disable_report(self, reportid): + """disable the report of the given id""" + reportid = reportid.upper() + self._reports_state[reportid] = False + + def report_is_enabled(self, reportid): + """return true if the report associated to the given identifier is + enabled + """ + return self._reports_state.get(reportid, True) + + def make_reports(self, stats, old_stats): + """render registered reports""" + sect = Section('Report', + '%s statements analysed.'% (self.stats['statement'])) + for checker in self.report_order(): + for reportid, r_title, r_cb in self._reports[checker]: + if not self.report_is_enabled(reportid): + continue + report_sect = Section(r_title) + try: + r_cb(report_sect, stats, old_stats) + except EmptyReport: + continue + report_sect.report_id = reportid + sect.append(report_sect) + return 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 six.iteritems(kwargs): + if key[-1] == '_': + key = key[:-1] + assert key not in self.stats + self.stats[key] = value + return self.stats + + +def expand_modules(files_or_modules, black_list): + """take a list of files/modules/packages and return the list of tuple + (file, module name) which have to be actually checked + """ + result = [] + errors = [] + for something in files_or_modules: + 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: + errors.append({'key' : 'ignored-builtin-module', 'mod': modname}) + continue + except (ImportError, SyntaxError) as ex: + # FIXME p3k : the SyntaxError is a Python bug and should be + # removed as soon as possible http://bugs.python.org/issue10588 + errors.append({'key': 'fatal', 'mod': modname, 'ex': ex}) + continue + filepath = normpath(filepath) + result.append({'path': filepath, 'name': modname, 'isarg': True, + 'basepath': filepath, 'basename': modname}) + if not (modname.endswith('.__init__') or modname == '__init__') \ + and '__init__.py' in filepath: + for subfilepath in get_module_files(dirname(filepath), black_list): + if filepath == subfilepath: + continue + submodname = '.'.join(modpath_from_file(subfilepath)) + result.append({'path': subfilepath, 'name': submodname, + 'isarg': False, + 'basepath': filepath, 'basename': modname}) + return result, errors + + +class PyLintASTWalker(object): + + def __init__(self, linter): + # callbacks per node types + self.nbstatements = 1 + self.visit_events = collections.defaultdict(list) + self.leave_events = collections.defaultdict(list) + self.linter = linter + + def _is_method_enabled(self, method): + if not hasattr(method, 'checks_msgs'): + return True + for msg_desc in method.checks_msgs: + if self.linter.is_message_enabled(msg_desc): + return True + return False + + def add_checker(self, checker): + """walk to the checker's dir and collect visit and leave methods""" + # XXX : should be possible to merge needed_checkers and add_checker + vcids = set() + lcids = set() + visits = self.visit_events + leaves = self.leave_events + for member in dir(checker): + cid = member[6:] + if cid == 'default': + continue + if member.startswith('visit_'): + v_meth = getattr(checker, member) + # don't use visit_methods with no activated message: + if self._is_method_enabled(v_meth): + visits[cid].append(v_meth) + vcids.add(cid) + elif member.startswith('leave_'): + l_meth = getattr(checker, member) + # don't use leave_methods with no activated message: + if self._is_method_enabled(l_meth): + leaves[cid].append(l_meth) + lcids.add(cid) + visit_default = getattr(checker, 'visit_default', None) + if visit_default: + for cls in nodes.ALL_NODE_CLASSES: + cid = cls.__name__.lower() + if cid not in vcids: + visits[cid].append(visit_default) + # for now we have no "leave_default" method in Pylint + + def walk(self, astroid): + """call visit events of astroid checkers for the given node, recurse on + its children, then leave events. + """ + cid = astroid.__class__.__name__.lower() + if astroid.is_statement: + self.nbstatements += 1 + # generate events for this node on each checker + for cb in self.visit_events.get(cid, ()): + cb(astroid) + # recurse on children + for child in astroid.get_children(): + self.walk(child) + for cb in self.leave_events.get(cid, ()): + cb(astroid) + + +PY_EXTS = ('.py', '.pyc', '.pyo', '.pyw', '.so', '.dll') + +def register_plugins(linter, directory): + """load all module and package in the given directory, looking for a + 'register' function in each one, used to register pylint checkers + """ + imported = {} + for filename in os.listdir(directory): + base, extension = splitext(filename) + if base in imported or base == '__pycache__': + continue + if extension in PY_EXTS and base != '__init__' or ( + not extension and isdir(join(directory, base))): + try: + module = load_module_from_file(join(directory, filename)) + except ValueError: + # empty module name (usually emacs auto-save files) + continue + except ImportError as exc: + print("Problem importing module %s: %s" % (filename, exc), + file=sys.stderr) + else: + if hasattr(module, 'register'): + module.register(linter) + imported[base] = 1 + +def get_global_option(checker, option, default=None): + """ Retrieve an option defined by the given *checker* or + by all known option providers. + + It will look in the list of all options providers + until the given *option* will be found. + If the option wasn't found, the *default* value will be returned. + """ + # First, try in the given checker's config. + # After that, look in the options providers. + + try: + return getattr(checker.config, option.replace("-", "_")) + except AttributeError: + pass + for provider in checker.linter.options_providers: + for options in provider.options: + if options[0] == option: + return getattr(provider.config, option.replace("-", "_")) + return default diff --git a/pyreverse/diadefslib.py b/pyreverse/diadefslib.py deleted file mode 100644 index 46d0f19..0000000 --- a/pyreverse/diadefslib.py +++ /dev/null @@ -1,233 +0,0 @@ -# Copyright (c) 2000-2013 LOGILAB S.A. (Paris, FRANCE). -# http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free Software -# Foundation; either version 2 of the License, or (at your option) any later -# version. -# -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -"""handle diagram generation options for class diagram or default diagrams -""" - -from logilab.common.compat import builtins - -import astroid -from astroid.utils import LocalsVisitor - -from pylint.pyreverse.diagrams import PackageDiagram, ClassDiagram - -BUILTINS_NAME = builtins.__name__ - -# diagram generators ########################################################## - -class DiaDefGenerator(object): - """handle diagram generation options""" - - def __init__(self, linker, handler): - """common Diagram Handler initialization""" - self.config = handler.config - self._set_default_options() - self.linker = linker - self.classdiagram = None # defined by subclasses - - def get_title(self, node): - """get title for objects""" - title = node.name - if self.module_names: - title = '%s.%s' % (node.root().name, title) - return title - - def _set_option(self, option): - """activate some options if not explicitly deactivated""" - # if we have a class diagram, we want more information by default; - # so if the option is None, we return True - if option is None: - if self.config.classes: - return True - else: - return False - return option - - def _set_default_options(self): - """set different default options with _default dictionary""" - self.module_names = self._set_option(self.config.module_names) - all_ancestors = self._set_option(self.config.all_ancestors) - all_associated = self._set_option(self.config.all_associated) - anc_level, ass_level = (0, 0) - if all_ancestors: - anc_level = -1 - if all_associated: - ass_level = -1 - if self.config.show_ancestors is not None: - anc_level = self.config.show_ancestors - if self.config.show_associated is not None: - ass_level = self.config.show_associated - self.anc_level, self.ass_level = anc_level, ass_level - - def _get_levels(self): - """help function for search levels""" - return self.anc_level, self.ass_level - - def show_node(self, node): - """true if builtins and not show_builtins""" - if self.config.show_builtin: - return True - return node.root().name != BUILTINS_NAME - - def add_class(self, node): - """visit one class and add it to diagram""" - self.linker.visit(node) - self.classdiagram.add_object(self.get_title(node), node) - - def get_ancestors(self, node, level): - """return ancestor nodes of a class node""" - if level == 0: - return - for ancestor in node.ancestors(recurs=False): - if not self.show_node(ancestor): - continue - yield ancestor - - def get_associated(self, klass_node, level): - """return associated nodes of a class node""" - if level == 0: - return - for ass_nodes in klass_node.instance_attrs_type.values() + \ - klass_node.locals_type.values(): - for ass_node in ass_nodes: - if isinstance(ass_node, astroid.Instance): - ass_node = ass_node._proxied - if not (isinstance(ass_node, astroid.Class) - and self.show_node(ass_node)): - continue - yield ass_node - - def extract_classes(self, klass_node, anc_level, ass_level): - """extract recursively classes related to klass_node""" - if self.classdiagram.has_node(klass_node) or not self.show_node(klass_node): - return - self.add_class(klass_node) - - for ancestor in self.get_ancestors(klass_node, anc_level): - self.extract_classes(ancestor, anc_level-1, ass_level) - - for ass_node in self.get_associated(klass_node, ass_level): - self.extract_classes(ass_node, anc_level, ass_level-1) - - -class DefaultDiadefGenerator(LocalsVisitor, DiaDefGenerator): - """generate minimum diagram definition for the project : - - * a package diagram including project's modules - * a class diagram including project's classes - """ - - def __init__(self, linker, handler): - DiaDefGenerator.__init__(self, linker, handler) - LocalsVisitor.__init__(self) - - def visit_project(self, node): - """visit an astroid.Project node - - create a diagram definition for packages - """ - mode = self.config.mode - if len(node.modules) > 1: - self.pkgdiagram = PackageDiagram('packages %s' % node.name, mode) - else: - self.pkgdiagram = None - self.classdiagram = ClassDiagram('classes %s' % node.name, mode) - - def leave_project(self, node): - """leave the astroid.Project node - - return the generated diagram definition - """ - if self.pkgdiagram: - return self.pkgdiagram, self.classdiagram - return self.classdiagram, - - def visit_module(self, node): - """visit an astroid.Module node - - add this class to the package diagram definition - """ - if self.pkgdiagram: - self.linker.visit(node) - self.pkgdiagram.add_object(node.name, node) - - def visit_class(self, node): - """visit an astroid.Class node - - add this class to the class diagram definition - """ - anc_level, ass_level = self._get_levels() - self.extract_classes(node, anc_level, ass_level) - - def visit_from(self, node): - """visit astroid.From and catch modules for package diagram - """ - if self.pkgdiagram: - self.pkgdiagram.add_from_depend(node, node.modname) - - -class ClassDiadefGenerator(DiaDefGenerator): - """generate a class diagram definition including all classes related to a - given class - """ - - def __init__(self, linker, handler): - DiaDefGenerator.__init__(self, linker, handler) - - def class_diagram(self, project, klass): - """return a class diagram definition for the given klass and its - related klasses - """ - - self.classdiagram = ClassDiagram(klass, self.config.mode) - if len(project.modules) > 1: - module, klass = klass.rsplit('.', 1) - module = project.get_module(module) - else: - module = project.modules[0] - klass = klass.split('.')[-1] - klass = module.ilookup(klass).next() - - anc_level, ass_level = self._get_levels() - self.extract_classes(klass, anc_level, ass_level) - return self.classdiagram - -# diagram handler ############################################################# - -class DiadefsHandler(object): - """handle diagram definitions : - - get it from user (i.e. xml files) or generate them - """ - - def __init__(self, config): - self.config = config - - def get_diadefs(self, project, linker): - """get the diagrams configuration data - :param linker: astroid.inspector.Linker(IdGeneratorMixIn, LocalsVisitor) - :param project: astroid.manager.Project - """ - - # read and interpret diagram definitions (Diadefs) - diagrams = [] - generator = ClassDiadefGenerator(linker, self) - for klass in self.config.classes: - diagrams.append(generator.class_diagram(project, klass)) - if not diagrams: - diagrams = DefaultDiadefGenerator(linker, self).visit(project) - for diagram in diagrams: - diagram.extract_relationships() - return diagrams diff --git a/pyreverse/diagrams.py b/pyreverse/diagrams.py deleted file mode 100644 index 6dde9a1..0000000 --- a/pyreverse/diagrams.py +++ /dev/null @@ -1,244 +0,0 @@ -# Copyright (c) 2004-2013 LOGILAB S.A. (Paris, FRANCE). -# http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free Software -# Foundation; either version 2 of the License, or (at your option) any later -# version. -# -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -"""diagram objects -""" - -import astroid -from pylint.pyreverse.utils import is_interface, FilterMixIn - -class Figure(object): - """base class for counter handling""" - -class Relationship(Figure): - """a relation ship from an object in the diagram to another - """ - def __init__(self, from_object, to_object, relation_type, name=None): - Figure.__init__(self) - self.from_object = from_object - self.to_object = to_object - self.type = relation_type - self.name = name - - -class DiagramEntity(Figure): - """a diagram object, i.e. a label associated to an astroid node - """ - def __init__(self, title='No name', node=None): - Figure.__init__(self) - self.title = title - self.node = node - -class ClassDiagram(Figure, FilterMixIn): - """main class diagram handling - """ - TYPE = 'class' - def __init__(self, title, mode): - FilterMixIn.__init__(self, mode) - Figure.__init__(self) - self.title = title - self.objects = [] - self.relationships = {} - self._nodes = {} - self.depends = [] - - def get_relationships(self, role): - # sorted to get predictable (hence testable) results - return sorted(self.relationships.get(role, ()), - key=lambda x: (x.from_object.fig_id, x.to_object.fig_id)) - - def add_relationship(self, from_object, to_object, - relation_type, name=None): - """create a relation ship - """ - rel = Relationship(from_object, to_object, relation_type, name) - self.relationships.setdefault(relation_type, []).append(rel) - - def get_relationship(self, from_object, relation_type): - """return a relation ship or None - """ - for rel in self.relationships.get(relation_type, ()): - if rel.from_object is from_object: - return rel - raise KeyError(relation_type) - - def get_attrs(self, node): - """return visible attributes, possibly with class name""" - attrs = [] - for node_name, ass_nodes in node.instance_attrs_type.items() + \ - node.locals_type.items(): - if not self.show_attr(node_name): - continue - names = self.class_names(ass_nodes) - if names: - node_name = "%s : %s" % (node_name, ", ".join(names)) - attrs.append(node_name) - return sorted(attrs) - - def get_methods(self, node): - """return visible methods""" - return [m for m in sorted(node.values(), key=lambda n: n.name) - if isinstance(m, astroid.Function) and self.show_attr(m.name)] - - def add_object(self, title, node): - """create a diagram object - """ - assert node not in self._nodes - ent = DiagramEntity(title, node) - self._nodes[node] = ent - self.objects.append(ent) - - def class_names(self, nodes): - """return class names if needed in diagram""" - names = [] - for ass_node in nodes: - if isinstance(ass_node, astroid.Instance): - ass_node = ass_node._proxied - if isinstance(ass_node, astroid.Class) \ - and hasattr(ass_node, "name") and not self.has_node(ass_node): - if ass_node.name not in names: - ass_name = ass_node.name - names.append(ass_name) - return names - - def nodes(self): - """return the list of underlying nodes - """ - return self._nodes.keys() - - def has_node(self, node): - """return true if the given node is included in the diagram - """ - return node in self._nodes - - def object_from_node(self, node): - """return the diagram object mapped to node - """ - return self._nodes[node] - - def classes(self): - """return all class nodes in the diagram""" - return [o for o in self.objects if isinstance(o.node, astroid.Class)] - - def classe(self, name): - """return a class by its name, raise KeyError if not found - """ - for klass in self.classes(): - if klass.node.name == name: - return klass - raise KeyError(name) - - def extract_relationships(self): - """extract relation ships between nodes in the diagram - """ - for obj in self.classes(): - node = obj.node - obj.attrs = self.get_attrs(node) - obj.methods = self.get_methods(node) - # shape - if is_interface(node): - obj.shape = 'interface' - else: - obj.shape = 'class' - # inheritance link - for par_node in node.ancestors(recurs=False): - try: - par_obj = self.object_from_node(par_node) - self.add_relationship(obj, par_obj, 'specialization') - except KeyError: - continue - # implements link - for impl_node in node.implements: - try: - impl_obj = self.object_from_node(impl_node) - self.add_relationship(obj, impl_obj, 'implements') - except KeyError: - continue - # associations link - for name, values in node.instance_attrs_type.items() + \ - node.locals_type.items(): - for value in values: - if value is astroid.YES: - continue - if isinstance(value, astroid.Instance): - value = value._proxied - try: - ass_obj = self.object_from_node(value) - self.add_relationship(ass_obj, obj, 'association', name) - except KeyError: - continue - - -class PackageDiagram(ClassDiagram): - """package diagram handling - """ - TYPE = 'package' - - def modules(self): - """return all module nodes in the diagram""" - return [o for o in self.objects if isinstance(o.node, astroid.Module)] - - def module(self, name): - """return a module by its name, raise KeyError if not found - """ - for mod in self.modules(): - if mod.node.name == name: - return mod - raise KeyError(name) - - def get_module(self, name, node): - """return a module by its name, looking also for relative imports; - raise KeyError if not found - """ - for mod in self.modules(): - mod_name = mod.node.name - if mod_name == name: - return mod - #search for fullname of relative import modules - package = node.root().name - if mod_name == "%s.%s" % (package, name): - return mod - if mod_name == "%s.%s" % (package.rsplit('.', 1)[0], name): - return mod - raise KeyError(name) - - def add_from_depend(self, node, from_module): - """add dependencies created by from-imports - """ - mod_name = node.root().name - obj = self.module(mod_name) - if from_module not in obj.node.depends: - obj.node.depends.append(from_module) - - def extract_relationships(self): - """extract relation ships between nodes in the diagram - """ - ClassDiagram.extract_relationships(self) - for obj in self.classes(): - # ownership - try: - mod = self.object_from_node(obj.node.root()) - self.add_relationship(obj, mod, 'ownership') - except KeyError: - continue - for obj in self.modules(): - obj.shape = 'package' - # dependencies - for dep_name in obj.node.depends: - try: - dep = self.get_module(dep_name, obj.node) - except KeyError: - continue - self.add_relationship(obj, dep, 'depends') diff --git a/pyreverse/main.py b/pyreverse/main.py deleted file mode 100644 index 7801835..0000000 --- a/pyreverse/main.py +++ /dev/null @@ -1,124 +0,0 @@ -# # Copyright (c) 2000-2013 LOGILAB S.A. (Paris, FRANCE). -# http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free Software -# Foundation; either version 2 of the License, or (at your option) any later -# version. -# -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -""" - %prog [options] <packages> - - create UML diagrams for classes and modules in <packages> -""" - -import sys, os -from logilab.common.configuration import ConfigurationMixIn -from astroid.manager import AstroidManager -from astroid.inspector import Linker - -from pylint.pyreverse.diadefslib import DiadefsHandler -from pylint.pyreverse import writer -from pylint.pyreverse.utils import insert_default_options - -OPTIONS = ( -("filter-mode", - dict(short='f', default='PUB_ONLY', dest='mode', type='string', - action='store', metavar='<mode>', - help="""filter attributes and functions according to - <mode>. Correct modes are : - 'PUB_ONLY' filter all non public attributes - [DEFAULT], equivalent to PRIVATE+SPECIAL_A - 'ALL' no filter - 'SPECIAL' filter Python special functions - except constructor - 'OTHER' filter protected and private - attributes""")), - -("class", -dict(short='c', action="append", metavar="<class>", dest="classes", default=[], - help="create a class diagram with all classes related to <class>;\ - this uses by default the options -ASmy")), - -("show-ancestors", -dict(short="a", action="store", metavar='<ancestor>', type='int', - help='show <ancestor> generations of ancestor classes not in <projects>')), -("all-ancestors", -dict(short="A", default=None, - help="show all ancestors off all classes in <projects>")), -("show-associated", -dict(short='s', action="store", metavar='<ass_level>', type='int', - help='show <ass_level> levels of associated classes not in <projects>')), -("all-associated", -dict(short='S', default=None, - help='show recursively all associated off all associated classes')), - -("show-builtin", -dict(short="b", action="store_true", default=False, - help='include builtin objects in representation of classes')), - -("module-names", -dict(short="m", default=None, type='yn', metavar='[yn]', - help='include module name in representation of classes')), -# TODO : generate dependencies like in pylint -#("package-dependencies", -#dict(short="M", action="store", metavar='<package_depth>', type='int', - #help='show <package_depth> module dependencies beyond modules in \ -#<projects> (for the package diagram)')), -("only-classnames", -dict(short='k', action="store_true", default=False, - help="don't show attributes and methods in the class boxes; \ -this disables -f values")), -("output", dict(short="o", dest="output_format", action="store", - default="dot", metavar="<format>", - help="create a *.<format> output file if format available.")), -) -# FIXME : quiet mode -#( ('quiet', - #dict(help='run quietly', action='store_true', short='q')), ) - -class Run(ConfigurationMixIn): - """base class providing common behaviour for pyreverse commands""" - - options = OPTIONS - - def __init__(self, args): - ConfigurationMixIn.__init__(self, usage=__doc__) - insert_default_options() - self.manager = AstroidManager() - self.register_options_provider(self.manager) - args = self.load_command_line_configuration() - sys.exit(self.run(args)) - - def run(self, args): - """checking arguments and run project""" - if not args: - print self.help() - return 1 - # insert current working directory to the python path to recognize - # dependencies to local modules even if cwd is not in the PYTHONPATH - sys.path.insert(0, os.getcwd()) - try: - project = self.manager.project_from_files(args) - linker = Linker(project, tag=True) - handler = DiadefsHandler(self.config) - diadefs = handler.get_diadefs(project, linker) - finally: - sys.path.pop(0) - - if self.config.output_format == "vcg": - writer.VCGWriter(self.config).write(diadefs) - else: - writer.DotWriter(self.config).write(diadefs) - return 0 - - -if __name__ == '__main__': - Run(sys.argv[1:]) diff --git a/pyreverse/utils.py b/pyreverse/utils.py deleted file mode 100644 index 3d12d41..0000000 --- a/pyreverse/utils.py +++ /dev/null @@ -1,131 +0,0 @@ -# Copyright (c) 2002-2013 LOGILAB S.A. (Paris, FRANCE). -# http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free Software -# Foundation; either version 2 of the License, or (at your option) any later -# version. -# -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -""" -generic classes/functions for pyreverse core/extensions -""" - -import sys -import re -import os - -########### pyreverse option utils ############################## - - -RCFILE = '.pyreverserc' - -def get_default_options(): - """ - Read config file and return list of options - """ - options = [] - home = os.environ.get('HOME', '') - if home: - rcfile = os.path.join(home, RCFILE) - try: - options = open(rcfile).read().split() - except IOError: - pass # ignore if no config file found - return options - -def insert_default_options(): - """insert default options to sys.argv - """ - options = get_default_options() - options.reverse() - for arg in options: - sys.argv.insert(1, arg) - - - -# astroid utilities ########################################################### - -SPECIAL = re.compile('^__[A-Za-z0-9]+[A-Za-z0-9_]*__$') -PRIVATE = re.compile('^__[_A-Za-z0-9]*[A-Za-z0-9]+_?$') -PROTECTED = re.compile('^_[_A-Za-z0-9]*$') - -def get_visibility(name): - """return the visibility from a name: public, protected, private or special - """ - if SPECIAL.match(name): - visibility = 'special' - elif PRIVATE.match(name): - visibility = 'private' - elif PROTECTED.match(name): - visibility = 'protected' - - else: - visibility = 'public' - return visibility - -ABSTRACT = re.compile('^.*Abstract.*') -FINAL = re.compile('^[A-Z_]*$') - -def is_abstract(node): - """return true if the given class node correspond to an abstract class - definition - """ - return ABSTRACT.match(node.name) - -def is_final(node): - """return true if the given class/function node correspond to final - definition - """ - return FINAL.match(node.name) - -def is_interface(node): - # bw compat - return node.type == 'interface' - -def is_exception(node): - # bw compat - return node.type == 'exception' - - -# Helpers ##################################################################### - -_CONSTRUCTOR = 1 -_SPECIAL = 2 -_PROTECTED = 4 -_PRIVATE = 8 -MODES = { - 'ALL' : 0, - 'PUB_ONLY' : _SPECIAL + _PROTECTED + _PRIVATE, - 'SPECIAL' : _SPECIAL, - 'OTHER' : _PROTECTED + _PRIVATE, -} -VIS_MOD = {'special': _SPECIAL, 'protected': _PROTECTED, - 'private': _PRIVATE, 'public': 0} - -class FilterMixIn(object): - """filter nodes according to a mode and nodes' visibility - """ - def __init__(self, mode): - "init filter modes" - __mode = 0 - for nummod in mode.split('+'): - try: - __mode += MODES[nummod] - except KeyError, ex: - print >> sys.stderr, 'Unknown filter mode %s' % ex - self.__mode = __mode - - - def show_attr(self, node): - """return true if the node should be treated - """ - visibility = get_visibility(getattr(node, 'name', node)) - return not (self.__mode & VIS_MOD[visibility]) - diff --git a/pyreverse/writer.py b/pyreverse/writer.py deleted file mode 100644 index 6b35548..0000000 --- a/pyreverse/writer.py +++ /dev/null @@ -1,199 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2008-2013 LOGILAB S.A. (Paris, FRANCE). -# http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free Software -# Foundation; either version 2 of the License, or (at your option) any later -# version. -# -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -"""Utilities for creating VCG and Dot diagrams""" - -from logilab.common.vcgutils import VCGPrinter -from logilab.common.graph import DotBackend - -from pylint.pyreverse.utils import is_exception - -class DiagramWriter(object): - """base class for writing project diagrams - """ - def __init__(self, config, styles): - self.config = config - self.pkg_edges, self.inh_edges, self.imp_edges, self.ass_edges = styles - self.printer = None # defined in set_printer - - def write(self, diadefs): - """write files for <project> according to <diadefs> - """ - for diagram in diadefs: - basename = diagram.title.strip().replace(' ', '_') - file_name = '%s.%s' % (basename, self.config.output_format) - self.set_printer(file_name, basename) - if diagram.TYPE == 'class': - self.write_classes(diagram) - else: - self.write_packages(diagram) - self.close_graph() - - def write_packages(self, diagram): - """write a package diagram""" - # sorted to get predictable (hence testable) results - for i, obj in enumerate(sorted(diagram.modules(), key=lambda x: x.title)): - self.printer.emit_node(i, label=self.get_title(obj), shape='box') - obj.fig_id = i - # package dependencies - for rel in diagram.get_relationships('depends'): - self.printer.emit_edge(rel.from_object.fig_id, rel.to_object.fig_id, - **self.pkg_edges) - - def write_classes(self, diagram): - """write a class diagram""" - # sorted to get predictable (hence testable) results - for i, obj in enumerate(sorted(diagram.objects, key=lambda x: x.title)): - self.printer.emit_node(i, **self.get_values(obj)) - obj.fig_id = i - # inheritance links - for rel in diagram.get_relationships('specialization'): - self.printer.emit_edge(rel.from_object.fig_id, rel.to_object.fig_id, - **self.inh_edges) - # implementation links - for rel in diagram.get_relationships('implements'): - self.printer.emit_edge(rel.from_object.fig_id, rel.to_object.fig_id, - **self.imp_edges) - # generate associations - for rel in diagram.get_relationships('association'): - self.printer.emit_edge(rel.from_object.fig_id, rel.to_object.fig_id, - label=rel.name, **self.ass_edges) - - def set_printer(self, file_name, basename): - """set printer""" - raise NotImplementedError - - def get_title(self, obj): - """get project title""" - raise NotImplementedError - - def get_values(self, obj): - """get label and shape for classes.""" - raise NotImplementedError - - def close_graph(self): - """finalize the graph""" - raise NotImplementedError - - -class DotWriter(DiagramWriter): - """write dot graphs from a diagram definition and a project - """ - - def __init__(self, config): - styles = [dict(arrowtail='none', arrowhead="open"), - dict(arrowtail='none', arrowhead='empty'), - dict(arrowtail='node', arrowhead='empty', style='dashed'), - dict(fontcolor='green', arrowtail='none', - arrowhead='diamond', style='solid'), - ] - DiagramWriter.__init__(self, config, styles) - - def set_printer(self, file_name, basename): - """initialize DotWriter and add options for layout. - """ - layout = dict(rankdir="BT") - self.printer = DotBackend(basename, additionnal_param=layout) - self.file_name = file_name - - def get_title(self, obj): - """get project title""" - return obj.title - - def get_values(self, obj): - """get label and shape for classes. - - The label contains all attributes and methods - """ - label = obj.title - if obj.shape == 'interface': - label = u'«interface»\\n%s' % label - if not self.config.only_classnames: - label = r'%s|%s\l|' % (label, r'\l'.join(obj.attrs)) - for func in obj.methods: - label = r'%s%s()\l' % (label, func.name) - label = '{%s}' % label - if is_exception(obj.node): - return dict(fontcolor='red', label=label, shape='record') - return dict(label=label, shape='record') - - def close_graph(self): - """print the dot graph into <file_name>""" - self.printer.generate(self.file_name) - - -class VCGWriter(DiagramWriter): - """write vcg graphs from a diagram definition and a project - """ - def __init__(self, config): - styles = [dict(arrowstyle='solid', backarrowstyle='none', - backarrowsize=0), - dict(arrowstyle='solid', backarrowstyle='none', - backarrowsize=10), - dict(arrowstyle='solid', backarrowstyle='none', - linestyle='dotted', backarrowsize=10), - dict(arrowstyle='solid', backarrowstyle='none', - textcolor='green'), - ] - DiagramWriter.__init__(self, config, styles) - - def set_printer(self, file_name, basename): - """initialize VCGWriter for a UML graph""" - self.graph_file = open(file_name, 'w+') - self.printer = VCGPrinter(self.graph_file) - self.printer.open_graph(title=basename, layoutalgorithm='dfs', - late_edge_labels='yes', port_sharing='no', - manhattan_edges='yes') - self.printer.emit_node = self.printer.node - self.printer.emit_edge = self.printer.edge - - def get_title(self, obj): - """get project title in vcg format""" - return r'\fb%s\fn' % obj.title - - def get_values(self, obj): - """get label and shape for classes. - - The label contains all attributes and methods - """ - if is_exception(obj.node): - label = r'\fb\f09%s\fn' % obj.title - else: - label = r'\fb%s\fn' % obj.title - if obj.shape == 'interface': - shape = 'ellipse' - else: - shape = 'box' - if not self.config.only_classnames: - attrs = obj.attrs - methods = [func.name for func in obj.methods] - # box width for UML like diagram - maxlen = max(len(name) for name in [obj.title] + methods + attrs) - line = '_' * (maxlen + 2) - label = r'%s\n\f%s' % (label, line) - for attr in attrs: - label = r'%s\n\f08%s' % (label, attr) - if attrs: - label = r'%s\n\f%s' % (label, line) - for func in methods: - label = r'%s\n\f10%s()' % (label, func) - return dict(label=label, shape=shape) - - def close_graph(self): - """close graph and file""" - self.printer.close_graph() - self.graph_file.close() - diff --git a/reporters/__init__.py b/reporters/__init__.py deleted file mode 100644 index a767a05..0000000 --- a/reporters/__init__.py +++ /dev/null @@ -1,138 +0,0 @@ -# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free Software -# Foundation; either version 2 of the License, or (at your option) any later -# version. -# -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -"""utilities methods and classes for reporters""" - -import sys -import locale -import os - -from pylint.utils import MSG_TYPES - -from pylint import utils - -CMPS = ['=', '-', '+'] - -# py3k has no more cmp builtin -if sys.version_info >= (3, 0): - def cmp(a, b): - return (a > b) - (a < b) - -if sys.version_info < (2, 6): - import stringformat - stringformat.init(True) - -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 Message(object): - """This class represent a message to be issued by the reporters""" - - def __init__(self, reporter, msg_id, location, msg): - self.msg_id = msg_id - self.abspath, self.module, self.obj, self.line, self.column = location - self.path = self.abspath.replace(reporter.path_strip_prefix, '') - self.msg = msg - self.C = msg_id[0] - self.category = MSG_TYPES[msg_id[0]] - self.symbol = reporter.linter.check_message_id(msg_id).symbol - - def format(self, template): - """Format the message according to the given template. - - The template format is the one of the format method : - cf. http://docs.python.org/2/library/string.html#formatstrings - """ - return template.format(**(self.__dict__)) - - -class BaseReporter(object): - """base class for reporters - - symbols: show short symbolic names for messages. - """ - - extension = '' - - def __init__(self, output=None): - self.linter = None - # self.include_ids = None # Deprecated - # self.symbols = None # Deprecated - self.section = 0 - self.out = None - self.out_encoding = None - self.encode = None - self.set_output(output) - # Build the path prefix to strip to get relative paths - self.path_strip_prefix = os.getcwd() + os.sep - - def add_message(self, msg_id, location, msg): - """Client API to send a message""" - # Shall we store the message objects somewhere, do some validity checking ? - raise NotImplementedError - - def set_output(self, output=None): - """set output stream""" - self.out = output or sys.stdout - # py3k streams handle their encoding : - if sys.version_info >= (3, 0): - self.encode = lambda x: x - return - - def encode(string): - if not isinstance(string, unicode): - return string - encoding = (getattr(self.out, 'encoding', None) or - locale.getdefaultlocale()[1] or - sys.getdefaultencoding()) - # errors=replace, we don't want to crash when attempting to show - # source code line that can't be encoded with the current locale - # settings - return string.encode(encoding, 'replace') - self.encode = encode - - def writeln(self, string=''): - """write a line in the output buffer""" - print >> self.out, self.encode(string) - - def display_results(self, layout): - """display results encapsulated in the layout tree""" - self.section = 0 - if 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() - - # Event callbacks - - def on_set_current_module(self, module, filepath): - """starting analyzis of a module""" - pass - - def on_close(self, stats, previous_stats): - """global end of analyzis""" - pass - - -def initialize(linter): - """initialize linter with reporters in this package """ - utils.register_plugins(linter, __path__[0]) diff --git a/reporters/guireporter.py b/reporters/guireporter.py deleted file mode 100644 index 331eb17..0000000 --- a/reporters/guireporter.py +++ /dev/null @@ -1,28 +0,0 @@ -""" reporter used by gui.py """ - -import sys - -from pylint.interfaces import IReporter -from pylint.reporters import BaseReporter, Message -from logilab.common.ureports import TextWriter - - -class GUIReporter(BaseReporter): - """saves messages""" - - __implements__ = IReporter - extension = '' - - def __init__(self, gui, output=sys.stdout): - """init""" - BaseReporter.__init__(self, output) - self.gui = gui - - def add_message(self, msg_id, location, msg): - """manage message of different type and in the context of path""" - message = Message(self, msg_id, location, msg) - self.gui.msg_queue.put(message) - - def _display(self, layout): - """launch layouts display""" - TextWriter().format(layout, self.out) diff --git a/reporters/html.py b/reporters/html.py deleted file mode 100644 index 4bf33c5..0000000 --- a/reporters/html.py +++ /dev/null @@ -1,102 +0,0 @@ -# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free Software -# Foundation; either version 2 of the License, or (at your option) any later -# version. -# -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -"""HTML reporter""" - -import sys -from cgi import escape - -import string #To parse formatter - -from logilab.common.ureports import HTMLWriter, Section, Table - -from pylint.interfaces import IReporter -from pylint.reporters import BaseReporter, Message - -class HTMLReporter(BaseReporter): - """report messages and layouts in HTML""" - - __implements__ = IReporter - name = 'html' - extension = 'html' - - def __init__(self, output=sys.stdout): - BaseReporter.__init__(self, output) - self.msgs = [] - #Add placeholders for title and parsed messages - self.header = None - self.msgargs = [] - - def _parse_template(self): - """Helper function to parse the message template""" - self.header = [] - if self.linter.config.msg_template: - msg_template = self.linter.config.msg_template - else: - msg_template = '{category}{module}{obj}{line}{column}{msg}' - - #Parse the message template - str_formatter = string.Formatter() - parsed_str = str_formatter.parse(msg_template) - - for item in parsed_str: - if item[1]: - self.header.append(item[1]) - self.msgargs.append(item[1]) - - def add_message(self, msg_id, location, msg): - """manage message of different type and in the context of path""" - msg = Message(self, msg_id, location, msg) - - #It would be better to do this in init, but currently we do not - #have access to the linter (as it is setup in lint.set_reporter() - #Therfore we try to parse just the once. - if self.header is None: - self._parse_template() - - #We want to add the lines given by the template - thismsg = [str(msg.__dict__.get(x, None)) for x in self.msgargs] - - self.msgs += thismsg - - def set_output(self, output=None): - """set output stream - - messages buffered for old output is processed first""" - if self.out and self.msgs: - self._display(Section()) - BaseReporter.set_output(self, output) - - def _display(self, layout): - """launch layouts display - - overridden from BaseReporter to add insert the messages section - (in add_message, message is not displayed, just collected so it - can be displayed in an html table) - """ - - if self.msgs: - # add stored messages to the layout - msgs = self.header - cols = len(self.header) - msgs += self.msgs - sect = Section('Messages') - layout.append(sect) - sect.append(Table(cols=cols, children=msgs, rheaders=1)) - self.msgs = [] - HTMLWriter().format(layout, self.out) - - -def register(linter): - """Register the reporter classes with the linter.""" - linter.register_reporter(HTMLReporter) diff --git a/reporters/text.py b/reporters/text.py deleted file mode 100644 index bd99837..0000000 --- a/reporters/text.py +++ /dev/null @@ -1,143 +0,0 @@ -# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free Software -# Foundation; either version 2 of the License, or (at your option) any later -# version. -# -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -"""Plain text reporters: - -:text: the default one grouping messages by module -:colorized: an ANSI colorized text reporter -""" - -import warnings - -from logilab.common.ureports import TextWriter -from logilab.common.textutils import colorize_ansi - -from pylint.interfaces import IReporter -from pylint.reporters import BaseReporter, Message - -TITLE_UNDERLINES = ['', '=', '-', '.'] - - -class TextReporter(BaseReporter): - """reports messages and layouts in plain text""" - - __implements__ = IReporter - name = 'text' - extension = 'txt' - line_format = '{C}:{line:3d},{column:2d}: {msg} ({symbol})' - - def __init__(self, output=None): - BaseReporter.__init__(self, output) - self._modules = {} - self._template = None - - def on_set_current_module(self, module, filepath): - self._template = unicode(self.linter.config.msg_template or self.line_format) - - def write_message(self, msg): - """Convenience method to write a formated message with class default template""" - self.writeln(msg.format(self._template)) - - def add_message(self, msg_id, location, msg): - """manage message of different type and in the context of path""" - m = Message(self, msg_id, location, msg) - if m.module not in self._modules: - if m.module: - self.writeln('************* Module %s' % m.module) - self._modules[m.module] = 1 - else: - self.writeln('************* ') - self.write_message(m) - - def _display(self, layout): - """launch layouts display""" - print >> self.out - TextWriter().format(layout, self.out) - - -class ParseableTextReporter(TextReporter): - """a reporter very similar to TextReporter, but display messages in a form - recognized by most text editors : - - <filename>:<linenum>:<msg> - """ - name = 'parseable' - line_format = '{path}:{line}: [{msg_id}({symbol}), {obj}] {msg}' - - def __init__(self, output=None): - warnings.warn('%s output format is deprecated. This is equivalent ' - 'to --msg-template=%s' % (self.name, self.line_format)) - TextReporter.__init__(self, output) - - -class VSTextReporter(ParseableTextReporter): - """Visual studio text reporter""" - name = 'msvs' - line_format = '{path}({line}): [{msg_id}({symbol}){obj}] {msg}' - - -class ColorizedTextReporter(TextReporter): - """Simple TextReporter that colorizes text output""" - - name = 'colorized' - 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=None, 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[0]] - 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 - """ - msg = Message(self, msg_id, location, msg) - if msg.module not in self._modules: - color, style = self._get_decoration('S') - if msg.module: - modsep = colorize_ansi('************* Module %s' % msg.module, - color, style) - else: - modsep = colorize_ansi('************* %s' % msg.module, - color, style) - self.writeln(modsep) - self._modules[msg.module] = 1 - color, style = self._get_decoration(msg.C) - for attr in ('msg', 'symbol', 'category', 'C'): - setattr(msg, attr, colorize_ansi(getattr(msg, attr), color, style)) - self.write_message(msg) - - -def register(linter): - """Register the reporter classes with the linter.""" - linter.register_reporter(TextReporter) - linter.register_reporter(ParseableTextReporter) - linter.register_reporter(VSTextReporter) - linter.register_reporter(ColorizedTextReporter) @@ -1,3 +1,6 @@ +[wheel] +universal = 1 + [bdist_rpm] packager = Sylvain Thenault <sylvain.thenault@logilab.fr> provides = pylint @@ -20,6 +20,7 @@ # with pylint. If not, see <http://www.gnu.org/licenses/>. """Generic Setup script, takes package info from __pkginfo__.py file. """ +from __future__ import absolute_import, print_function __docformat__ = "restructuredtext en" import os @@ -31,47 +32,40 @@ try: if os.environ.get('NO_SETUPTOOLS'): raise ImportError() from setuptools import setup + from setuptools.command import easy_install as easy_install_lib from setuptools.command import install_lib USE_SETUPTOOLS = 1 except ImportError: from distutils.core import setup from distutils.command import install_lib USE_SETUPTOOLS = 0 + easy_install_lib = None -try: - # pylint: disable=no-name-in-module - # python3 - from distutils.command.build_py import build_py_2to3 as build_py -except ImportError: - # pylint: disable=no-name-in-module - # python2.x - from distutils.command.build_py import build_py - -sys.modules.pop('__pkginfo__', None) -# import optional features -__pkginfo__ = __import__("__pkginfo__") -# import required features -from __pkginfo__ import modname, version, license, description, \ - web, author, author_email, classifiers - -distname = getattr(__pkginfo__, 'distname', modname) -scripts = getattr(__pkginfo__, 'scripts', []) -data_files = getattr(__pkginfo__, 'data_files', None) -subpackage_of = getattr(__pkginfo__, 'subpackage_of', None) -include_dirs = getattr(__pkginfo__, 'include_dirs', []) -ext_modules = getattr(__pkginfo__, 'ext_modules', None) -install_requires = getattr(__pkginfo__, 'install_requires', None) -dependency_links = getattr(__pkginfo__, 'dependency_links', []) - -STD_BLACKLIST = ('CVS', '.svn', '.hg', 'debian', 'dist', 'build') - -IGNORED_EXTENSIONS = ('.pyc', '.pyo', '.elc', '~') - -if exists('README'): - long_description = open('README').read() +from distutils.command.build_py import build_py + + +base_dir = os.path.dirname(__file__) + +__pkginfo__ = {} +with open(os.path.join(base_dir, "pylint", "__pkginfo__.py")) as f: + exec(f.read(), __pkginfo__) +modname = __pkginfo__['modname'] +distname = __pkginfo__.get('distname', modname) +scripts = __pkginfo__.get('scripts', []) +data_files = __pkginfo__.get('data_files', None) +include_dirs = __pkginfo__.get('include_dirs', []) +ext_modules = __pkginfo__.get('ext_modules', None) +install_requires = __pkginfo__.get('install_requires', None) +dependency_links = __pkginfo__.get('dependency_links', []) + +readme_path = join(base_dir, 'README') +if exists(readme_path): + with open(readme_path) as stream: + long_description = stream.read() else: long_description = '' + def ensure_scripts(linux_scripts): """Creates the proper script names required for each platform (taken from 4Suite) @@ -81,6 +75,7 @@ def ensure_scripts(linux_scripts): return linux_scripts + [script + '.bat' for script in linux_scripts] return linux_scripts + def get_packages(directory, prefix): """return a list of subpackages for the given directory""" result = [] @@ -95,12 +90,11 @@ def get_packages(directory, prefix): result += get_packages(absfile, result[-1]) return result -EMPTY_FILE = '''"""generated file, don't modify or your data will be lost""" -try: - __import__('pkg_resources').declare_namespace(__name__) -except ImportError: - pass -''' + +def _filter_tests(files): + testdir = join('pylint', 'test') + return [f for f in files if testdir not in f] + class MyInstallLib(install_lib.install_lib): """extend install_lib command to handle package __init__.py and @@ -109,45 +103,42 @@ class MyInstallLib(install_lib.install_lib): def run(self): """overridden 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) + dest = join(self.install_dir, directory) if sys.version_info >= (3, 0): - exclude = set(('func_unknown_encoding.py', - 'func_invalid_encoded_data.py')) + exclude = set(['invalid_encoded_data*', + 'unknown_encoding*']) else: exclude = set() shutil.rmtree(dest, ignore_errors=True) - shutil.copytree(directory, dest) - # since python2.5's copytree doesn't support the ignore - # parameter, the following loop to remove the exclude set - # was added - for (dirpath, dirnames, filenames) in os.walk(dest): - for n in filenames: - if n in exclude: - os.remove(os.path.join(dirpath, n)) - - + shutil.copytree(directory, dest, + ignore=shutil.ignore_patterns(*exclude)) if sys.version_info >= (3, 0): # process manually python file in include_dirs (test data) # pylint: disable=no-name-in-module from distutils.util import run_2to3 - print('running 2to3 on', dest) + print(('running 2to3 on', dest)) run_2to3([dest]) + # override this since pip/easy_install attempt to byte compile test data + # files, some of them being syntactically wrong by design, and this scares + # the end-user + def byte_compile(self, files): + files = _filter_tests(files) + install_lib.install_lib.byte_compile(self, files) + + +if easy_install_lib: + class easy_install(easy_install_lib.easy_install): + # override this since pip/easy_install attempt to byte compile + # test data files, some of them being syntactically wrong by design, + # and this scares the end-user + def byte_compile(self, files): + files = _filter_tests(files) + easy_install_lib.easy_install.byte_compile(self, files) + def install(**kwargs): """setup entry point""" @@ -157,42 +148,37 @@ def install(**kwargs): # install-layout option was introduced in 2.5.3-1~exp1 elif sys.version_info < (2, 5, 4) and '--install-layout=deb' in sys.argv: sys.argv.remove('--install-layout=deb') - if subpackage_of: - package = subpackage_of + '.' + modname - kwargs['package_dir'] = {package : '.'} - packages = [package] + get_packages(os.getcwd(), package) - if USE_SETUPTOOLS: - kwargs['namespace_packages'] = [subpackage_of] - else: - kwargs['package_dir'] = {modname : '.'} - packages = [modname] + get_packages(os.getcwd(), modname) + packages = [modname] + get_packages(join(base_dir, 'pylint'), modname) if USE_SETUPTOOLS: if install_requires: kwargs['install_requires'] = install_requires kwargs['dependency_links'] = dependency_links kwargs['entry_points'] = {'console_scripts': [ - 'pylint = pylint:run_pylint', - 'pylint-gui = pylint:run_pylint_gui', - 'epylint = pylint:run_epylint', - 'pyreverse = pylint:run_pyreverse', - 'symilar = pylint:run_symilar', - ]} + 'pylint = pylint:run_pylint', + 'pylint-gui = pylint:run_pylint_gui', + 'epylint = pylint:run_epylint', + 'pyreverse = pylint:run_pyreverse', + 'symilar = pylint:run_symilar', + ]} kwargs['packages'] = packages + cmdclass = {'install_lib': MyInstallLib, + 'build_py': build_py} + if easy_install_lib: + cmdclass['easy_install'] = easy_install return setup(name=distname, - version=version, + version=__pkginfo__['version'], license=license, - description=description, + description=__pkginfo__['description'], long_description=long_description, - author=author, - author_email=author_email, - url=web, + author=__pkginfo__['author'], + author_email=__pkginfo__['author_email'], + url=__pkginfo__['web'], scripts=ensure_scripts(scripts), - classifiers=classifiers, + classifiers=__pkginfo__['classifiers'], data_files=data_files, ext_modules=ext_modules, - cmdclass={'install_lib': MyInstallLib, - 'build_py': build_py}, + cmdclass=cmdclass, **kwargs) -if __name__ == '__main__' : +if __name__ == '__main__': install() diff --git a/test/fulltest.sh b/test/fulltest.sh deleted file mode 100755 index 95d784f..0000000 --- a/test/fulltest.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/sh - -if [ $@ ] ; then - PYVERSIONS=$@ -else - PYVERSIONS=`pyversions -iv` -fi - -cd `dirname $0` - -for ver in $PYVERSIONS; do - echo "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" - echo `python$ver -V` - echo "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^" - python$ver `which pytest` - echo "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^" - echo `python$ver -V` -OO - python$ver -OO `which pytest` -done diff --git a/test/input/func_3k_removed_stuff_py_30.py b/test/input/func_3k_removed_stuff_py_30.py deleted file mode 100644 index 5d6692f..0000000 --- a/test/input/func_3k_removed_stuff_py_30.py +++ /dev/null @@ -1,15 +0,0 @@ -"""test relative import -""" -__revision__ = apply(map, (str, (1, 2, 3))) -from __future__ import generators - -import func_w0302 - -def function(): - """something""" - print func_w0302 - unic = u"unicode" - low = unic.looower - return low - - diff --git a/test/input/func___name___access.py b/test/input/func___name___access.py deleted file mode 100644 index def8674..0000000 --- a/test/input/func___name___access.py +++ /dev/null @@ -1,21 +0,0 @@ -# pylint: disable=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_abstract_class_instantiated_py30.py b/test/input/func_abstract_class_instantiated_py30.py deleted file mode 100644 index 4bcfd3f..0000000 --- a/test/input/func_abstract_class_instantiated_py30.py +++ /dev/null @@ -1,43 +0,0 @@ -"""Check that instantiating a class with -`abc.ABCMeta` as metaclass fails if it defines -abstract methods. -""" - -# pylint: disable=too-few-public-methods, missing-docstring, abstract-class-not-used - -__revision__ = 0 - -import abc -
-class GoodClass(object, metaclass=abc.ABCMeta):
- pass
-
-class SecondGoodClass(object, metaclass=abc.ABCMeta):
- def test(self):
- """ do nothing. """
- pass
-
-class ThirdGoodClass(object, metaclass=abc.ABCMeta):
- """ This should not raise the warning. """
- def test(self):
- raise NotImplementedError()
-
-class BadClass(object, metaclass=abc.ABCMeta):
- @abc.abstractmethod
- def test(self):
- """ do nothing. """
- pass
-
-class SecondBadClass(object, metaclass=abc.ABCMeta):
- @property
- @abc.abstractmethod
- def test(self):
- """ do nothing. """
-
-def main():
- """ do nothing """
- GoodClass()
- SecondGoodClass()
- ThirdGoodClass()
- BadClass()
- SecondBadClass()
diff --git a/test/input/func_abstract_class_instantiated_py34.py b/test/input/func_abstract_class_instantiated_py34.py deleted file mode 100644 index 1814f34..0000000 --- a/test/input/func_abstract_class_instantiated_py34.py +++ /dev/null @@ -1,55 +0,0 @@ -"""Check that instantiating a class with -`abc.ABCMeta` as metaclass fails if it defines -abstract methods. -""" - -# pylint: disable=too-few-public-methods, missing-docstring, abstract-class-not-used, no-init - -__revision__ = 0 - -import abc -
-class GoodClass(object, metaclass=abc.ABCMeta):
- pass
-
-class SecondGoodClass(object, metaclass=abc.ABCMeta):
- def test(self):
- """ do nothing. """
- pass
-
-class ThirdGoodClass(object, metaclass=abc.ABCMeta):
- """ This should not raise the warning. """
- def test(self):
- raise NotImplementedError()
-
-class FourthGoodClass(abc.ABC):
- """ Neither this. """
- def test(self):
- pass
-
-class BadClass(object, metaclass=abc.ABCMeta):
- @abc.abstractmethod
- def test(self):
- """ do nothing. """
- pass
-
-class SecondBadClass(object, metaclass=abc.ABCMeta):
- @property
- @abc.abstractmethod
- def test(self):
- """ do nothing. """
-
-class ThirdBadClass(abc.ABC):
- @abc.abstractmethod
- def test(self):
- pass
-
-def main():
- """ do nothing """
- GoodClass()
- SecondGoodClass()
- ThirdGoodClass()
- FourthGoodClass()
- BadClass()
- SecondBadClass()
- ThirdBadClass()
diff --git a/test/input/func_all.py b/test/input/func_all.py deleted file mode 100644 index e9f1fc8..0000000 --- a/test/input/func_all.py +++ /dev/null @@ -1,45 +0,0 @@ -"""Test Pylint's use of __all__. - -* NonExistant is not defined in this module, and it is listed in - __all__. An error is expected. - -* This module imports path and republished it in __all__. No errors - are expected. -""" -# pylint: disable=R0903,R0201,W0612 - -__revision__ = 0 - -from os import path - -__all__ = [ - 'Dummy', - 'NonExistant', - 'path', - 'func', - 'inner', - 'InnerKlass'] - - -class Dummy(object): - """A class defined in this module.""" - pass - -DUMMY = Dummy() - -def function(): - """Function docstring - """ - pass - -function() - -class Klass(object): - """A klass which contains a function""" - def func(self): - """A klass method""" - inner = None - - class InnerKlass(object): - """A inner klass""" - pass diff --git a/test/input/func_all_undefined.py b/test/input/func_all_undefined.py deleted file mode 100644 index 4d2301d..0000000 --- a/test/input/func_all_undefined.py +++ /dev/null @@ -1,8 +0,0 @@ -"""Test that non-inferable __all__ variables do not make Pylint crash. - -""" -# pylint: disable=R0903,R0201,W0612 - -__revision__ = 0 - -__all__ = [SomeUndefinedName] diff --git a/test/input/func_arguments.py b/test/input/func_arguments.py deleted file mode 100644 index a22ff8e..0000000 --- a/test/input/func_arguments.py +++ /dev/null @@ -1,35 +0,0 @@ -"""Test function argument checker""" -__revision__ = '' - -def function_1_arg(first_argument): - """one argument function""" - return first_argument - -def function_3_args(first_argument, second_argument, third_argument): - """three arguments function""" - return first_argument, second_argument, third_argument - -def function_default_arg(one=1, two=2): - """fonction with default value""" - return two, one - - -function_1_arg(420) -function_1_arg() -function_1_arg(1337, 347) - -function_3_args(420, 789) -function_3_args() -function_3_args(1337, 347, 456) -function_3_args('bab', 'bebe', None, 5.6) - -function_default_arg(1, two=5) -function_default_arg(two=5) -# repeated keyword is syntax error in python >= 2.6: -# tests are moved to func_keyword_repeat_py25- / func_keyword_repeat_py26 - -function_1_arg(bob=4) -function_default_arg(1, 4, coin="hello") - -function_default_arg(1, one=5) - diff --git a/test/input/func_attrs_definition_order.py b/test/input/func_attrs_definition_order.py deleted file mode 100644 index 3c65d73..0000000 --- a/test/input/func_attrs_definition_order.py +++ /dev/null @@ -1,33 +0,0 @@ -# pylint: disable=R0903 -"""yo""" - -__revision__ = '$I$' - -class Aaaa(object): - """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 - - def __getattr__(self, attr): - # pylint: disable=W0201 - try: - return self.__repo - except AttributeError: - self.__repo = attr - return attr - - - def catchme(self, attr): - """no AttributeError catched""" - # pylint: disable=W0201 - try: - return self._repo - except ValueError: - self._repo = attr - return attr diff --git a/test/input/func_backtick_deprecated_py_30.py b/test/input/func_backtick_deprecated_py_30.py deleted file mode 100644 index bca85cc..0000000 --- a/test/input/func_backtick_deprecated_py_30.py +++ /dev/null @@ -1,4 +0,0 @@ -"""W0333: flag backtick as deprecated""" - -__revision__ = None -print `1` diff --git a/test/input/func_bad_assigment_to_exception_var.py b/test/input/func_bad_assigment_to_exception_var.py deleted file mode 100644 index 6b859ba..0000000 --- a/test/input/func_bad_assigment_to_exception_var.py +++ /dev/null @@ -1,31 +0,0 @@ -# pylint:disable=C0103 -"""ho ho ho""" -__revision__ = 'toto' - -import sys - -e = 1 -e2 = 'yo' -e3 = None -try: - raise e -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_bad_context_manager.py b/test/input/func_bad_context_manager.py deleted file mode 100644 index 2439c47..0000000 --- a/test/input/func_bad_context_manager.py +++ /dev/null @@ -1,62 +0,0 @@ -"""Check that __exit__ special method accepts 3 arguments """ - -# pylint: disable=too-few-public-methods, invalid-name - -__revision__ = 0 - -class FirstGoodContextManager(object): - """ 3 arguments """ - - def __enter__(self): - return self - - def __exit__(self, exc_type, value, tb): - pass - -class SecondGoodContextManager(object): - """ 3 keyword arguments """ - - def __enter__(self): - return self - - def __exit__(self, exc_type=None, value=None, tb=None): - pass - -class ThirdGoodContextManager(object): - """ 1 argument and variable arguments """ - - def __enter__(self): - return self - - def __exit__(self, exc_type, *args): - pass - -class FirstBadContextManager(object): - """ 1 argument """ - - def __enter__(self): - return self - - def __exit__(self, exc_type): - pass - -class SecondBadContextManager(object): - """ Too many arguments """ - - def __enter__(self): - return self - - def __exit__(self, exc_type, value, tb, stack): - pass - -class ThirdBadContextManager(object): - """ Too many arguments and variable arguments """ - - def __enter__(self): - return self - - def __exit__(self, exc_type, value, tb, stack, *args): - pass - - - diff --git a/test/input/func_bad_exception_context_py30.py b/test/input/func_bad_exception_context_py30.py deleted file mode 100644 index d3ab127..0000000 --- a/test/input/func_bad_exception_context_py30.py +++ /dev/null @@ -1,23 +0,0 @@ -"""Check that raise ... from .. uses a proper exception context """ - -# pylint: disable=unreachable - -import socket - -__revision__ = 0 - -class ExceptionSubclass(Exception): - """ subclass """ - -def test(): - """ docstring """ - raise IndexError from 1 - raise IndexError from None - raise IndexError from ZeroDivisionError - raise IndexError from object() - raise IndexError from ExceptionSubclass - raise IndexError from socket.error - raise IndexError() from None - raise IndexError() from ZeroDivisionError - raise IndexError() from ZeroDivisionError() - raise IndexError() from object() diff --git a/test/input/func_bad_open_mode.py b/test/input/func_bad_open_mode.py deleted file mode 100644 index fefa399..0000000 --- a/test/input/func_bad_open_mode.py +++ /dev/null @@ -1,15 +0,0 @@ -"""Warnings for using open() with an invalid mode string.""" - -__revision__ = 0 - -open('foo.bar', 'w', 2) -open('foo.bar', 'rw') -open(name='foo.bar', buffering=10, mode='rw') -open(mode='rw', name='foo.bar') -open('foo.bar', 'U+') -open('foo.bar', 'rb+') -open('foo.bar', 'Uw') -open('foo.bar', 2) -open('foo.bar', buffering=2) -WRITE_MODE = 'w' -open('foo.bar', 'U' + WRITE_MODE + 'z') diff --git a/test/input/func_bad_reversed_sequence.py b/test/input/func_bad_reversed_sequence.py deleted file mode 100644 index a394e53..0000000 --- a/test/input/func_bad_reversed_sequence.py +++ /dev/null @@ -1,59 +0,0 @@ -""" Checks that reversed() receive proper argument """ - -# pylint: disable=too-few-public-methods,no-self-use -from collections import deque - -__revision__ = 0 - -class GoodReversed(object): - """ Implements __reversed__ """ - def __reversed__(self): - return [1, 2, 3] - -class SecondGoodReversed(object): - """ Implements __len__ and __getitem__ """ - def __len__(self): - return 3 - - def __getitem__(self, index): - return index - -class BadReversed(object): - """ implements only len() """ - def __len__(self): - return 3 - -class SecondBadReversed(object): - """ implements only __getitem__ """ - def __getitem__(self, index): - return index - -class ThirdBadReversed(dict): - """ dict subclass """ - -def uninferable(seq): - """ This can't be infered at this moment, - make sure we don't have a false positive. - """ - return reversed(seq) - -def test(): - """ test function """ - seq = reversed() - seq = reversed(None) - seq = reversed([1, 2, 3]) - seq = reversed((1, 2, 3)) - seq = reversed(set()) - seq = reversed({'a': 1, 'b': 2}) - seq = reversed(iter([1, 2, 3])) - seq = reversed(GoodReversed()) - seq = reversed(SecondGoodReversed()) - seq = reversed(BadReversed()) - seq = reversed(SecondBadReversed()) - seq = reversed(range(100)) - seq = reversed(ThirdBadReversed()) - seq = reversed(lambda: None) - seq = reversed(deque([])) - seq = reversed("123") - seq = uninferable([1, 2, 3]) - return seq diff --git a/test/input/func_bad_slots.py b/test/input/func_bad_slots.py deleted file mode 100644 index 16f7a79..0000000 --- a/test/input/func_bad_slots.py +++ /dev/null @@ -1,57 +0,0 @@ -""" Checks that classes uses valid __slots__ """ - -# pylint: disable=too-few-public-methods, missing-docstring - -from collections import deque - -def func(): - if True: - return ("a", "b", "c") - else: - return [str(var) for var in range(3)] - -__revision__ = 0 - -class NotIterable(object): - def __iter_(self): - """ do nothing """ -
-class Good(object):
- __slots__ = ()
-
-class SecondGood(object):
- __slots__ = []
-
-class ThirdGood(object):
- __slots__ = ['a']
-
-class FourthGood(object):
- __slots__ = ('a%s' % i for i in range(10))
-
-class FifthGood(object):
- __slots__ = "a"
-
-class SixthGood(object):
- __slots__ = deque(["a", "b", "c"])
-
-class SeventhGood(object):
- __slots__ = {"a": "b", "c": "d"}
-
-class Bad(object):
- __slots__ = list
-
-class SecondBad(object):
- __slots__ = 1
-
-class ThirdBad(object):
- __slots__ = ('a', 2)
-
-class FourthBad(object):
- __slots__ = NotIterable()
-
-class FifthBad(object):
- __slots__ = ("a", "b", "")
-
-class PotentiallyGood(object):
- __slots__ = func()
-
\ No newline at end of file diff --git a/test/input/func_bad_str_strip_call.py b/test/input/func_bad_str_strip_call.py deleted file mode 100644 index 2d94a6e..0000000 --- a/test/input/func_bad_str_strip_call.py +++ /dev/null @@ -1,9 +0,0 @@ -"""Suspicious str.strip calls.""" -__revision__ = 1 - -''.strip('yo') -''.strip() - -u''.strip('http://') -u''.lstrip('http://') -b''.rstrip('http://') diff --git a/test/input/func_base_stmt_without_effect.py b/test/input/func_base_stmt_without_effect.py deleted file mode 100644 index 6454de9..0000000 --- a/test/input/func_base_stmt_without_effect.py +++ /dev/null @@ -1,41 +0,0 @@ -""" - 'W0104': ('Statement seems to have no effect', - 'Used when a statement doesn\'t have (or at least seems to) \ - any effect.'), - 'W0105': ('String statement has no effect', - 'Used when a string is used as a statement (which of course \ - has no effect). This is a particular case of W0104 with its \ - own message so you can easily disable it if you\'re using \ - those strings as documentation, instead of comments.'), - 'W0106': ('Expression "%s" is assigned to nothing', - 'Used when an expression that is not a function call is assigned\ - to nothing. Probably something else was intended.'), -""" - -__revision__ = '' - -__revision__ - -__revision__ <= 1 - -__revision__.lower() # ok - -[i for i in __revision__] # ko - - -"""inline doc string should use a separated message""" - -__revision__.lower(); # unnecessary ; - -list() and tuple() # W0106 - -def to_be(): - """return 42""" - return "42" - -ANSWER = to_be() # ok -ANSWER == to_be() # W0106, typical typo - -to_be() or not to_be() # W0106, strange conditional function call (or nonsens) -to_be().title # W0106, very strange, maybe typo - diff --git a/test/input/func_base_useless_pass.py b/test/input/func_base_useless_pass.py deleted file mode 100644 index 67c39fa..0000000 --- a/test/input/func_base_useless_pass.py +++ /dev/null @@ -1,9 +0,0 @@ -"""W0107: unnecessary pass statement -""" -__revision__ = None - -try: - A = 2 -except ValueError: - print A - pass diff --git a/test/input/func_block_disable_msg.py b/test/input/func_block_disable_msg.py deleted file mode 100644 index 2551823..0000000 --- a/test/input/func_block_disable_msg.py +++ /dev/null @@ -1,1026 +0,0 @@ -# pylint: disable=C0302 -"""pylint option block-disable""" -__revision__ = None - -class Foo(object): - """block-disable 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=W0613 - print self\ - + "foo" - - def meth3(self): - """test one line disabling""" - # no error - print self.bla # pylint: disable=E1101 - # error - print self.blop - - def meth4(self): - """test re-enabling""" - # pylint: disable=E1101 - # no error - print self.bla - print self.blop - # pylint: enable=E1101 - # error - print self.blip - - def meth5(self): - """test IF sub-block re-enabling""" - # pylint: disable=E1101 - # no error - print self.bla - if self.blop: - # pylint: enable=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=E1101 - # no error - print self.bla - try: - # pylint: enable=E1101 - # error - print self.blip - except UndefinedName: # pylint: disable=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=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=E1101 - # no error - print self.bla - print self.blop - - def meth9(self): - """test re-enabling right after a block with whitespace""" - eris = 5 - - if eris: - print "In block" - - # pylint: disable=E1101 - # no error - print self.bla - print self.blu - # pylint: enable=E1101 - # error - print self.blip - - def meth10(self): - """Test double disable""" - # pylint: disable=E1101 - # no error - print self.bla - # pylint: disable=E1101 - print self.blu - - -class ClassLevelMessage(object): - """shouldn't display to much attributes/not enough methods messages - """ - # pylint: disable=R0902,R0903 - - def __init__(self): - self.attr1 = 1 - self.attr2 = 1 - self.attr3 = 1 - self.attr4 = 1 - self.attr5 = 1 - self.attr6 = 1 - self.attr7 = 1 - self.attr8 = 1 - self.attr9 = 1 - self.attr0 = 1 - - def too_complex_but_thats_ok(self, attr1, attr2): - """THIS Method has too much branches and returns but i don't care - """ - # pylint: disable=R0912,R0911 - try: - attr3 = attr1+attr2 - except ValueError: - attr3 = None - except: - return 'duh', self - if attr1: - for i in attr1: - if attr2: - return i - else: - return 'duh' - elif attr2: - for i in attr2: - if attr2: - return i - else: - return 'duh' - else: - for i in range(15): - if attr3: - return i - else: - return 'doh' - return None - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -print 'hop, too many lines but i don\'t care' diff --git a/test/input/func_break_or_return_in_try_finally.py b/test/input/func_break_or_return_in_try_finally.py deleted file mode 100644 index 12beec2..0000000 --- a/test/input/func_break_or_return_in_try_finally.py +++ /dev/null @@ -1,44 +0,0 @@ -'Exeptions may be silently swallowed' - -__revision__ = None - -def insidious_break_and_return(): - """I found you !""" - for i in range(0, -5, -1): - my_var = 0 - print i - try: - my_var += 1.0/i - if i < -3: - break # :D - else: - return my_var # :D - finally: - if i > -2: - break # :( - else: - return my_var # :( - return None - -def break_and_return(): - """I found you !""" - for i in range(0, -5, -1): - my_var = 0 - if i: - break # :D - try: - my_var += 1.0/i - finally: - for i in xrange(2): - if True: - break # :D - else: - def strange(): - """why not ?""" - if True: - return my_var # :D - strange() - if i: - break # :D - else: - return # :D diff --git a/test/input/func_bug113231.py b/test/input/func_bug113231.py deleted file mode 100644 index b5b5d8a..0000000 --- a/test/input/func_bug113231.py +++ /dev/null @@ -1,24 +0,0 @@ -# pylint: disable=E1101 -# pylint: disable=C0103 -# pylint: disable=R0903 -"""test bugfix for #113231 in logging checker -""" - -__revision__ = '' - -# Muck up the names in an effort to confuse... -import logging as renamed_logging - -class Logger(object): - """Fake logger""" - pass - -logger = renamed_logging.getLogger(__name__) -fake_logger = Logger() - -# Statements that should be flagged: -renamed_logging.warn('%s, %s' % (4, 5)) -logger.warn('%s' % 5) - -# Statements that should not be flagged: -fake_logger.warn('%s' % 5) diff --git a/test/input/func_catching_non_exception.py b/test/input/func_catching_non_exception.py deleted file mode 100644 index d8e0e2d..0000000 --- a/test/input/func_catching_non_exception.py +++ /dev/null @@ -1,56 +0,0 @@ -"""test non-exceptions catched -""" -import socket - -__revision__ = 1 - -class MyException(object): - """ custom 'exception' """ - pass - -class MySecondException(object): - """ custom 'exception' """ - pass - -class MyGoodException(Exception): - """ custom 'exception' """ - pass - -class MySecondGoodException(MyGoodException): - """ custom 'exception' """ - pass - -class SkipException(socket.error): - """ This gave false positives for Python 2, - but not for Python 3, due to exception - hierarchy rewrite. - """ - -class SecondSkipException(SkipException): - """ This too shouldn't give false - positives. """ - -try: - 1 + 1 -except MyException: - print "oups" - -try: - 1 + 2 -except (MyException, MySecondException): - print "oups" - -try: - 1 + 3 -except MyGoodException: - print "should work" - -try: - 1 + 3 -except (MyGoodException, MySecondGoodException): - print "should work" - -try: - 1 + 3 -except (SkipException, SecondSkipException): - print "should work" diff --git a/test/input/func_class_access_protected_members.py b/test/input/func_class_access_protected_members.py deleted file mode 100644 index f2ad5b7..0000000 --- a/test/input/func_class_access_protected_members.py +++ /dev/null @@ -1,36 +0,0 @@ -# pylint: disable=R0903, C0111, W0231 -"""test external access to protected class members""" - -__revision__ = '' - -class MyClass(object): - _cls_protected = 5 - - def __init__(self, other): - MyClass._cls_protected = 6 - self._protected = 1 - self.public = other - self.attr = 0 - - def test(self): - self._protected += self._cls_protected - print self.public._haha - - def clsmeth(cls): - cls._cls_protected += 1 - print cls._cls_protected - clsmeth = classmethod(clsmeth) - -class Subclass(MyClass): - - def __init__(self): - MyClass._protected = 5 - -INST = Subclass() -INST.attr = 1 -print INST.attr -INST._protected = 2 -print INST._protected -INST._cls_protected = 3 -print INST._cls_protected - diff --git a/test/input/func_class_members.py b/test/input/func_class_members.py deleted file mode 100644 index ad147e5..0000000 --- a/test/input/func_class_members.py +++ /dev/null @@ -1,32 +0,0 @@ -# pylint: disable=R0903 -"""test class members""" - -__revision__ = '' - -class MyClass(object): - """class docstring""" - - def __init__(self): - """init""" - self.correct = 1 - - def test(self): - """test""" - self.correct += 2 - self.incorrect += 2 - del self.havenot - self.nonexistent1.truc() - self.nonexistent2[1] = 'hehe' - -class XYZMixin(object): - """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_ctor_arguments.py b/test/input/func_ctor_arguments.py deleted file mode 100644 index 987a930..0000000 --- a/test/input/func_ctor_arguments.py +++ /dev/null @@ -1,63 +0,0 @@ -"""Test function argument checker on __init__ - -Based on test/input/func_arguments.py -""" -# pylint: disable=C0111,R0903,W0231 -__revision__ = '' - -class Class1Arg(object): - def __init__(self, first_argument): - """one argument function""" - -class Class3Arg(object): - def __init__(self, first_argument, second_argument, third_argument): - """three arguments function""" - -class ClassDefaultArg(object): - def __init__(self, one=1, two=2): - """function with default value""" - -class Subclass1Arg(Class1Arg): - pass - -class ClassAllArgs(Class1Arg): - def __init__(self, *args, **kwargs): - pass - -class ClassMultiInheritance(Class1Arg, Class3Arg): - pass - -class ClassNew(object): - def __new__(cls, first_argument, kwarg=None): - return first_argument, kwarg - -Class1Arg(420) -Class1Arg() -Class1Arg(1337, 347) - -Class3Arg(420, 789) -Class3Arg() -Class3Arg(1337, 347, 456) -Class3Arg('bab', 'bebe', None, 5.6) - -ClassDefaultArg(1, two=5) -ClassDefaultArg(two=5) - -Class1Arg(bob=4) -ClassDefaultArg(1, 4, coin="hello") - -ClassDefaultArg(1, one=5) - -Subclass1Arg(420) -Subclass1Arg() -Subclass1Arg(1337, 347) - -ClassAllArgs() -ClassAllArgs(1, 2, 3, even=4, more=5) - -ClassMultiInheritance(1) -ClassMultiInheritance(1, 2, 3) - -ClassNew(1, kwarg=1) -ClassNew(1, 2, 3) -ClassNew(one=2) diff --git a/test/input/func_dangerous_default.py b/test/input/func_dangerous_default.py deleted file mode 100644 index 3e07415..0000000 --- a/test/input/func_dangerous_default.py +++ /dev/null @@ -1,39 +0,0 @@ -"""docstring""" - -__revision__ = '' - -HEHE = {} - -def function1(value=[]): - """docstring""" - print value - -def function2(value=HEHE): - """docstring""" - print value - -def function3(value): - """docstring""" - print value - -def function4(value=set()): - """set is mutable and dangerous.""" - print value - -def function5(value=frozenset()): - """frozenset is immutable and safe.""" - print value - -GLOBAL_SET = set() - -def function6(value=GLOBAL_SET): - """set is mutable and dangerous.""" - print value - -def function7(value=dict()): - """dict is mutable and dangerous.""" - print value - -def function8(value=list()): - """list is mutable and dangerous.""" - print value diff --git a/test/input/func_defining-attr-methods_order.py b/test/input/func_defining-attr-methods_order.py deleted file mode 100644 index d64217d..0000000 --- a/test/input/func_defining-attr-methods_order.py +++ /dev/null @@ -1,33 +0,0 @@ -# pylint: disable=C0103 - -''' Test that y is defined properly, z is not. - Default defining methods are __init__, - __new__, and setUp. - Order of methods should not matter. ''' - -__revision__ = '' - -class A(object): - ''' class A ''' - - def __init__(self): - ''' __init__ docstring filler ''' - self.x = 0 - self.setUp() - - def set_y(self, y): - ''' set_y docstring filler ''' - self.y = y - - def set_x(self, x): - ''' set_x docstring filler ''' - self.x = x - - def set_z(self, z): - ''' set_z docstring filler ''' - self.z = z - - def setUp(self): - ''' setUp docstring filler ''' - self.x = 0 - self.y = 0 diff --git a/test/input/func_deprecated_lambda_py_30.py b/test/input/func_deprecated_lambda_py_30.py deleted file mode 100644 index 973bb32..0000000 --- a/test/input/func_deprecated_lambda_py_30.py +++ /dev/null @@ -1,22 +0,0 @@ -# pylint: disable=missing-docstring,bad-builtin,invalid-name -__revision__ = "$Id$" - -# Don't do this, use a comprehension instead. -assert map(lambda x: x*2, [1, 2, 3]) == [2, 4, 6] - -assert filter(lambda x: x != 1, [1, 2, 3]) == [2, 3] - -# It's still ok to use map and filter with anything but an inline lambda. -double = lambda x: x * 2 -assert map(double, [1, 2, 3]) == [2, 4, 6] - -# It's also ok to pass lambdas to other functions. -assert reduce(lambda x, y: x * y, [1, 2, 3, 4]) == 24 - -# Or to a undefined function or one with varargs -def f(*a): - return len(a) - -f(lambda x, y: x + y, [1, 2, 3]) - -undefined_function(lambda: 2) # pylint: disable=undefined-variable diff --git a/test/input/func_dict_keys.py b/test/input/func_dict_keys.py deleted file mode 100644 index 24c8b1f..0000000 --- a/test/input/func_dict_keys.py +++ /dev/null @@ -1,15 +0,0 @@ -"""Check multiple key definition""" -#pylint: disable=C0103 -__revision__ = 5 - -correct_dict = { - 'tea': 'for two', - 'two': 'for tea', -} - -wrong_dict = { - 'tea': 'for two', - 'two': 'for tea', - 'tea': 'time', - -} diff --git a/test/input/func_disable_linebased.py b/test/input/func_disable_linebased.py deleted file mode 100644 index fe59fa1..0000000 --- a/test/input/func_disable_linebased.py +++ /dev/null @@ -1,14 +0,0 @@ -# This is a very very very very very very very very very very very very very very very very very very very very very long line. -# pylint: disable=line-too-long -"""Make sure enable/disable pragmas work for messages that are applied to lines and not syntax nodes. - -A disable pragma for a message that applies to nodes is applied to the whole -block if it comes before the first statement (excluding the docstring). For -line-based messages, this behavior needs to be altered to really only apply to -the enclosed lines. -""" -# pylint: enable=line-too-long - -__revision__ = '1' - -print 'This is a very long line which the linter will warn about, now that line-too-long has been enabled again.' diff --git a/test/input/func_docstring.py b/test/input/func_docstring.py deleted file mode 100644 index e73d8a3..0000000 --- a/test/input/func_docstring.py +++ /dev/null @@ -1,73 +0,0 @@ -# pylint: disable=R0201 - -__revision__ = '' - -def function0(): - """""" - -def function1(value): - # missing docstring - print value - -def function2(value): - """docstring""" - print value - -def function3(value): - """docstring""" - print value - -class AAAA(object): - # 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 method3(self): - """""" - pass - - def __init__(self): - pass - -class DDDD(AAAA): - """yeah !""" - - def __init__(self): - AAAA.__init__(self) - - def method2(self): - """""" - pass - - def method3(self): - pass - - def method4(self): - pass - -# pylint: disable=missing-docstring -def function4(): - pass - -# pylint: disable=empty-docstring -def function5(): - """""" - pass diff --git a/test/input/func_dotted_ancestor.py b/test/input/func_dotted_ancestor.py deleted file mode 100644 index 2d2f571..0000000 --- a/test/input/func_dotted_ancestor.py +++ /dev/null @@ -1,11 +0,0 @@ -"""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_e0203.py b/test/input/func_e0203.py deleted file mode 100644 index d95a8dd..0000000 --- a/test/input/func_e0203.py +++ /dev/null @@ -1,19 +0,0 @@ -"""check for method without self as first argument -""" - -__revision__ = 0 - - -class Abcd(object): - """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 deleted file mode 100644 index 26cbd52..0000000 --- a/test/input/func_e0204.py +++ /dev/null @@ -1,19 +0,0 @@ -"""check for method without self as first argument -""" - -__revision__ = 0 - - -class Abcd(object): - """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 deleted file mode 100644 index 34208f9..0000000 --- a/test/input/func_e0205.py +++ /dev/null @@ -1,17 +0,0 @@ -# pylint: disable=R0903 -"""check method hidding ancestor attribute -""" - -__revision__ = '' - -class Abcd(object): - """dummy""" - def __init__(self): - self.abcd = 1 - -class Cdef(Abcd): - """dummy""" - def abcd(self): - """test - """ - print self diff --git a/test/input/func_e0601.py b/test/input/func_e0601.py deleted file mode 100644 index 2b704d5..0000000 --- a/test/input/func_e0601.py +++ /dev/null @@ -1,9 +0,0 @@ -"""test local variable used before assignment -""" - -__revision__ = 0 - -def function(): - """dummy""" - print aaaa - aaaa = 1 diff --git a/test/input/func_e12xx.py b/test/input/func_e12xx.py deleted file mode 100644 index 6482c92..0000000 --- a/test/input/func_e12xx.py +++ /dev/null @@ -1,27 +0,0 @@ -# pylint: disable=E1101 -"""Test checking of log format strings -""" - -__revision__ = '' - -import logging - -def pprint(): - """Test string format in logging statements. - """ - # These should all emit lint errors: - logging.info(0, '') # 1205 - logging.info('', '') # 1205 - logging.info('%s%', '') # 1201 - logging.info('%s%s', '') # 1206 - logging.info('%s%a', '', '') # 1200 - logging.info('%s%s', '', '', '') # 1205 - - # These should be okay: - logging.info(1) - logging.info(True) - logging.info('') - logging.info('%s%') - logging.info('%s', '') - logging.info('%s%%', '') - logging.info('%s%s', '', '') diff --git a/test/input/func_e13xx.py b/test/input/func_e13xx.py deleted file mode 100644 index a0d39ef..0000000 --- a/test/input/func_e13xx.py +++ /dev/null @@ -1,21 +0,0 @@ -"""test string format error -""" - -__revision__ = 1 - -PARG_1 = PARG_2 = PARG_3 = 1 - -def pprint(): - """Test string format - """ - print "%s %s" % {'PARG_1': 1, 'PARG_2': 2} # E1306 - print "%s" % (PARG_1, PARG_2) # E1305 - print "%(PARG_1)d %d" % {'PARG_1': 1, 'PARG_2': 2} # E1302 - print "%(PARG_1)d %(PARG_2)d" % {'PARG_1': 1} # E1304 - print "%(PARG_1)d %(PARG_2)d" % {'PARG_1': 1, 'PARG_2':2, 'PARG_3':3}#W1301 - print "%(PARG_1)d %(PARG_2)d" % {'PARG_1': 1, 2:3} # W1300 E1304 - print "%(PARG_1)d %(PARG_2)d" % (2, 3) # 1303 - print "%(PARG_1)d %(PARG_2)d" % [2, 3] # 1303 - print "%2z" % PARG_1 - print "strange format %2" % PARG_2 - diff --git a/test/input/func_exceptions_raise_type_error.py b/test/input/func_exceptions_raise_type_error.py deleted file mode 100644 index 8c414b6..0000000 --- a/test/input/func_exceptions_raise_type_error.py +++ /dev/null @@ -1,14 +0,0 @@ -""" -'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 deleted file mode 100644 index b0e559a..0000000 --- a/test/input/func_f0001.py +++ /dev/null @@ -1,4 +0,0 @@ -"""test astroid error -""" -import whatever -__revision__ = None diff --git a/test/input/func_f0401.py b/test/input/func_f0401.py deleted file mode 100644 index c663431..0000000 --- a/test/input/func_f0401.py +++ /dev/null @@ -1,9 +0,0 @@ -"""test 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 deleted file mode 100644 index b6371f1..0000000 --- a/test/input/func_fixme.py +++ /dev/null @@ -1,9 +0,0 @@ -"""docstring""" - -__revision__ = '' - -# FIXME: beep - -def function(): - '''XXX:bop''' - diff --git a/test/input/func_format.py b/test/input/func_format.py deleted file mode 100644 index 828a180..0000000 --- a/test/input/func_format.py +++ /dev/null @@ -1,108 +0,0 @@ -# pylint:disable=C0103,W0104,W0105 -"""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>''', 0) - -assert boo <= 10, "Note is %.2f. Either you cheated, or pylint's \ -broken!" % boo - -def _gc_debug(gcc): - """bad format undetected w/ py2.5""" - ocount = {} - for obj in gcc.get_objects(): - try: - ocount[obj.__class__]+= 1 - except KeyError: - ocount[obj.__class__]=1 - except AttributeError: - pass - -def hop(context): - """multi-lines string""" - return ['''<a id="sendbutton" href="javascript: $('%(domid)s').submit()"> -<img src="%(sendimgpath)s" alt="%(send)s"/>%(send)s</a>''' % context, - '''<a id="cancelbutton" href="javascript: history.back()"> -<img src="%(cancelimgpath)s" alt="%(cancel)s"/>%(cancel)s</a>''' % context, - ] -titreprojet = '<tr><td colspan="10">\ -<img src="images/drapeau_vert.png" alt="Drapeau vert" />\ -<strong>%s</strong></td></tr>' % aaaa - -with open('a') as a, open('b') as b: - pass - -with open('a') as a, open('b') as b: pass # multiple-statements - -# Well-formatted try-except-finally block. -try: - pass -except IOError, e: - print e -finally: - pass - -try: - pass -except IOError, e: - print e -finally: pass # multiple-statements - -# This is not allowed by the default configuration. -if True: print False diff --git a/test/input/func_genexpr_var_scope_py24.py b/test/input/func_genexpr_var_scope_py24.py deleted file mode 100644 index 2b6faf3..0000000 --- a/test/input/func_genexpr_var_scope_py24.py +++ /dev/null @@ -1,6 +0,0 @@ -"""test name defined in generator expression are not available -outside the genexpr scope -""" - -__revision__ = list(n for n in range(10)) -print n diff --git a/test/input/func_globals.py b/test/input/func_globals.py deleted file mode 100644 index cf58128..0000000 --- a/test/input/func_globals.py +++ /dev/null @@ -1,40 +0,0 @@ -""" -'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 assignment is done', - 'Used when a variable is defined through the "global" statement \ - but no assignment 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 deleted file mode 100644 index d56762f..0000000 --- a/test/input/func_i0010.py +++ /dev/null @@ -1,5 +0,0 @@ -# pylint: errors-only -"""errors-only is not usable as an inline option""" -__revision__ = None - -CONST = "This is not a pylint: inline option." diff --git a/test/input/func_import_syntax_error.py b/test/input/func_import_syntax_error.py deleted file mode 100644 index 67bdae8..0000000 --- a/test/input/func_import_syntax_error.py +++ /dev/null @@ -1 +0,0 @@ -import syntax_error diff --git a/test/input/func_indent.py b/test/input/func_indent.py deleted file mode 100644 index 18159e9..0000000 --- a/test/input/func_indent.py +++ /dev/null @@ -1,22 +0,0 @@ -"""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 deleted file mode 100644 index db94475..0000000 --- a/test/input/func_init_vars.py +++ /dev/null @@ -1,46 +0,0 @@ -"""Checks that class variables are seen as inherited ! -""" - -__revision__ = '' - - -class MyClass(object): - """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 deleted file mode 100644 index 9756183..0000000 --- a/test/input/func_interfaces.py +++ /dev/null @@ -1,114 +0,0 @@ -# pylint:disable=R0201 -"""docstring""" -__revision__ = '' - -class Interface(object): - """base class for interfaces""" - -class IMachin(Interface): - """docstring""" - def truc(self): - """docstring""" - - def troc(self, argument): - """docstring""" - -class Correct1(object): - """docstring""" - __implements__ = IMachin - - def __init__(self): - pass - - def truc(self): - """docstring""" - pass - - def troc(self, argument): - """docstring""" - pass - -class Correct2(object): - """docstring""" - __implements__ = (IMachin,) - - def __init__(self): - pass - - def truc(self): - """docstring""" - pass - - def troc(self, argument): - """docstring""" - print argument - -class MissingMethod(object): - """docstring""" - __implements__ = IMachin, - - def __init__(self): - pass - - def troc(self, argument): - """docstring""" - print argument - - def other(self): - """docstring""" - -class BadArgument(object): - """docstring""" - __implements__ = (IMachin,) - - def __init__(self): - pass - - def truc(self): - """docstring""" - pass - - def troc(self): - """docstring""" - pass - -class InterfaceCantBeFound(object): - """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 InterfaceCanNowBeFound(object): - """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""" - - -class EmptyImplements(object): - """no pb""" - __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_invalid_encoded_data.py b/test/input/func_invalid_encoded_data.py deleted file mode 100644 index a465e3c..0000000 --- a/test/input/func_invalid_encoded_data.py +++ /dev/null @@ -1,6 +0,0 @@ -# coding: utf-8 -"""Test data file with encoding errors.""" - -__revision__ = 0 - -STR = 'СуÑеÑтвительное' diff --git a/test/input/func_method_could_be_function.py b/test/input/func_method_could_be_function.py deleted file mode 100644 index a28e48a..0000000 --- a/test/input/func_method_could_be_function.py +++ /dev/null @@ -1,60 +0,0 @@ -# pylint: disable=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(object): - """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(object): - """same as before without abstract""" - attr = 1 - def method(self): - """regular""" - print self.attr - -class Sub1(Super): - """override method with need for self""" - def method(self): - """no i can not be a function""" - print 42 - - def __len__(self): - """no i can not be a function""" - print 42 - - def __cmp__(self, other): - """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 deleted file mode 100644 index 955fafc..0000000 --- a/test/input/func_method_missing_self.py +++ /dev/null @@ -1,24 +0,0 @@ -"""Checks that missing self in method defs don't crash Pylint ! -""" - -__revision__ = '' - - -class MyClass(object): - """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() - diff --git a/test/input/func_method_without_self_but_self_assignment.py b/test/input/func_method_without_self_but_self_assignment.py deleted file mode 100644 index ef1bb60..0000000 --- a/test/input/func_method_without_self_but_self_assignment.py +++ /dev/null @@ -1,15 +0,0 @@ -# pylint: disable=R0903 -"""regression test: setup() leads to "unable to load module..." -""" - -__revision__ = 1 - -class Example(object): - """bla""" - - def __init__(self): - pass - - def setup(): - "setup without self" - self.foo = 1 diff --git a/test/input/func_missing_super_argument_py_30.py b/test/input/func_missing_super_argument_py_30.py deleted file mode 100644 index ec20857..0000000 --- a/test/input/func_missing_super_argument_py_30.py +++ /dev/null @@ -1,9 +0,0 @@ -"""Check missing super argument for Python 2""" - -__revision__ = 0 - -class MyClass(object): - """ New style class """ - def __init__(self): - super().__init__() -
\ No newline at end of file diff --git a/test/input/func_module___dict__.py b/test/input/func_module___dict__.py deleted file mode 100644 index 0cf1550..0000000 --- a/test/input/func_module___dict__.py +++ /dev/null @@ -1,9 +0,0 @@ -"""http://www.logilab.org/ticket/6949.""" - -__revision__ = None - -print __dict__ is not None - -__dict__ = {} - -print __dict__ is not None diff --git a/test/input/func_name_checking.py b/test/input/func_name_checking.py deleted file mode 100644 index d19d946..0000000 --- a/test/input/func_name_checking.py +++ /dev/null @@ -1,135 +0,0 @@ -# pylint: disable=R0903,R0201,R0921,W0603 -"""Test for the invalid-name (C0103) warning.""" - -__revision__ = 1 - -import collections - -def Run(): - """method without any good name""" - class B(object): - """nested class should not be tested has a variable""" - def __init__(self): - pass - bBb = 1 - return A, bBb, B - -def run(): - """anothrer method without only good name""" - class Aaa(object): - """nested class should not be tested has a variable""" - def __init__(self): - pass - bbb = 1 - return bbb, Aaa - -A = None - -def HOHOHOHO(): - """yo""" - HIHIHI = 1 - print HIHIHI - -class xyz(object): - """yo""" - - zz = 'Bad Class Attribute' - - def __init__(self): - pass - - def Youplapoum(self): - """bad method name""" - - -class Derived(xyz): - """Derived class.""" - zz = 'Not a bad class attribute' - - -def no_nested_args(arg1, arg21, arg22): - """a function which had nested arguments but no more""" - print arg1, arg21, arg22 - - -GOOD_CONST_NAME = '' -benpasceluila = 0 - -class Correct(object): - """yo""" - def __init__(self): - self.cava = 12 - self._Ca_va_Pas = None - - def BadMethodName(self): - """Ignored.""" - -V = [WHAT_Ever_inListComp for WHAT_Ever_inListComp in GOOD_CONST_NAME] - -def class_builder(): - """Function returning a class object.""" - - class EmbeddedClass(object): - """Useless class.""" - - return EmbeddedClass - -BAD_NAME_FOR_CLASS = collections.namedtuple('Named', ['tuple']) -NEXT_BAD_NAME_FOR_CLASS = class_builder() - -GoodName = collections.namedtuple('Named', ['tuple']) -ToplevelClass = class_builder() - -AlsoCorrect = Correct -NOT_CORRECT = Correct - - -def test_globals(): - """Names in global statements are also checked.""" - global NOT_CORRECT - global AlsoCorrect - NOT_CORRECT = 1 - AlsoCorrect = 2 - - -class DerivedFromCorrect(Correct): - """A derived class with an invalid inherited members. - - Derived attributes and methods with invalid names do not trigger warnings. - """ - - def __init__(self): - super(DerivedFromCorrect, self).__init__() - self._Ca_va_Pas = None - - def BadMethodName(self): - """Ignored.""" - -import abc - -class FooClass(object): - """A test case for property names. - - Since by default, the regex for attributes is the same as the one - for method names, we check the warning messages to contain the - string 'attribute'. - """ - @property - def PROPERTY_NAME(self): - """Ignored.""" - pass - - @abc.abstractproperty - def ABSTRACT_PROPERTY_NAME(self): - """Ignored.""" - pass - - @PROPERTY_NAME.setter - def PROPERTY_NAME_SETTER(self): - """Ignored.""" - pass - - -def func_bad_argname(NOT_GOOD): - """Function with a badly named argument.""" - return NOT_GOOD diff --git a/test/input/func_namedtuple.py b/test/input/func_namedtuple.py deleted file mode 100644 index 8cfd048..0000000 --- a/test/input/func_namedtuple.py +++ /dev/null @@ -1,10 +0,0 @@ -"""Test namedtuple attributes. - -Regression test for: -https://bitbucket.org/logilab/pylint/issue/93/pylint-crashes-on-namedtuple-attribute -""" -__revision__ = None - -from collections import namedtuple -Thing = namedtuple('Thing', ()) -print Thing.x diff --git a/test/input/func_names_imported_from_module.py b/test/input/func_names_imported_from_module.py deleted file mode 100644 index 64c8f6c..0000000 --- a/test/input/func_names_imported_from_module.py +++ /dev/null @@ -1,28 +0,0 @@ -#pylint: disable=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 -'SOMEVAR' in os.environ - diff --git a/test/input/func_newstyle___slots__.py b/test/input/func_newstyle___slots__.py deleted file mode 100644 index f01c40e..0000000 --- a/test/input/func_newstyle___slots__.py +++ /dev/null @@ -1,17 +0,0 @@ -# pylint: disable=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 deleted file mode 100644 index ee6e60e..0000000 --- a/test/input/func_newstyle_exceptions.py +++ /dev/null @@ -1,37 +0,0 @@ -# pylint: disable=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 si py < 2.5 !""" - -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 - -def fonctionNotImplemented(): - """raise""" - raise NotImplemented, 'hop' diff --git a/test/input/func_newstyle_property.py b/test/input/func_newstyle_property.py deleted file mode 100644 index 84a07fc..0000000 --- a/test/input/func_newstyle_property.py +++ /dev/null @@ -1,48 +0,0 @@ -# pylint: disable=R0903 -"""test property on old style class and property.setter/deleter usage""" - -__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 - -import logilab.common.decorators - -class SomeClass(object): - """another docstring""" - - def __init__(self): - self._prop = None - - @property - def prop(self): - """I'm the 'prop' property.""" - return self._prop - - @prop.setter - def prop(self, value): - """I'm the 'prop' property.""" - self._prop = value - - @prop.deleter - def prop(self): - """I'm the 'prop' property.""" - del self._prop - - # test regression - @logilab.common.decorators.cached - def noregr(self): - """used to crash in redefined_by_decorator""" - return self.prop diff --git a/test/input/func_newstyle_super.py b/test/input/func_newstyle_super.py deleted file mode 100644 index a0aaa0a..0000000 --- a/test/input/func_newstyle_super.py +++ /dev/null @@ -1,38 +0,0 @@ -# pylint: disable=R0903,import-error -"""check use of super""" - -from unknown import Missing -__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__() - -class Py3kAaaa(NewAaaa): - """new style""" - def __init__(self): - super().__init__() - -class Py3kWrongSuper(Py3kAaaa): - """new style""" - def __init__(self): - super(NewAaaa, self).__init__() - -class WrongNameRegression(Py3kAaaa): - """ test a regression with the message """ - def __init__(self): - super(Missing, self).__init__() diff --git a/test/input/func_no_dummy_redefined.py b/test/input/func_no_dummy_redefined.py deleted file mode 100644 index 8af89d8..0000000 --- a/test/input/func_no_dummy_redefined.py +++ /dev/null @@ -1,14 +0,0 @@ -"""Make sure warnings about redefinitions do not trigger for dummy variables.""" -__revision__ = 0 - - -_, INTERESTING = 'a=b'.split('=') - -value = 10 - - -def clobbering(): - """Clobbers a dummy name from the outer scope.""" - value = 9 - for _ in range(7): - print value diff --git a/test/input/func_no_final_new_line.py b/test/input/func_no_final_new_line.py deleted file mode 100644 index a9c728d..0000000 --- a/test/input/func_no_final_new_line.py +++ /dev/null @@ -1,2 +0,0 @@ -'''hop''' -__revision__ = 0
\ No newline at end of file diff --git a/test/input/func_noerror_9215_lambda_arg_as_decorator.py b/test/input/func_noerror_9215_lambda_arg_as_decorator.py deleted file mode 100644 index cbbc747..0000000 --- a/test/input/func_noerror_9215_lambda_arg_as_decorator.py +++ /dev/null @@ -1,29 +0,0 @@ -"""Demonstrate false undefined variable for lambda functions. - -http://www.logilab.org/ticket/9215 -""" - -__revision__ = None - -def decorator(expr): - """Function returning decorator.""" - def func(function): - """Pass-thru decorator.""" - return function - # use expr - expr(0) - return func - -# this lambda is flagged -# E0602: 16:main.<lambda>: Undefined variable 'x' -@decorator(lambda x: x > 0) -def main(): - """Dummy function.""" - # this one is not flagged - decorator(lambda y: y > 0) - -if __name__ == "__main__": - main() - - - diff --git a/test/input/func_noerror___future___import.py b/test/input/func_noerror___future___import.py deleted file mode 100644 index 5c77516..0000000 --- a/test/input/func_noerror___future___import.py +++ /dev/null @@ -1,5 +0,0 @@ -"""a docstring""" - -from __future__ import generators - -__revision__ = 1 diff --git a/test/input/func_noerror_abstract_method.py b/test/input/func_noerror_abstract_method.py deleted file mode 100644 index 18228c6..0000000 --- a/test/input/func_noerror_abstract_method.py +++ /dev/null @@ -1,20 +0,0 @@ -""" This should not warn about `prop` being abstract in Child """
-
-# pylint: disable=too-few-public-methods,abstract-class-little-used
-
-__revision__ = None
-
-import abc
-
-class Parent(object):
- """ Class """
- __metaclass__ = abc.ABCMeta
-
- @property
- @abc.abstractmethod
- def prop(self):
- """ Abstract """
-
-class Child(Parent):
- """ No warning for the following. """
- prop = property(lambda self: 1)
diff --git a/test/input/func_noerror_abstract_method_py30.py b/test/input/func_noerror_abstract_method_py30.py deleted file mode 100644 index c237cb4..0000000 --- a/test/input/func_noerror_abstract_method_py30.py +++ /dev/null @@ -1,19 +0,0 @@ -""" This should not warn about `prop` being abstract in Child """
-
-# pylint: disable=too-few-public-methods,abstract-class-little-used,no-init,old-style-class
-
-__revision__ = None
-
-import abc
-
-class Parent(metaclass=abc.ABCMeta):
- """ Class """
-
- @property
- @abc.abstractmethod
- def prop(self):
- """ Abstract """
-
-class Child(Parent):
- """ No warning for the following. """
- prop = property(lambda self: 1)
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 deleted file mode 100644 index 3bf966b..0000000 --- a/test/input/func_noerror_access_attr_before_def_false_positive.py +++ /dev/null @@ -1,98 +0,0 @@ -#pylint: disable=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 - for match in matches: - 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(object): - """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_all_no_inference.py b/test/input/func_noerror_all_no_inference.py deleted file mode 100644 index d3af45c..0000000 --- a/test/input/func_noerror_all_no_inference.py +++ /dev/null @@ -1,14 +0,0 @@ -"""Test that non-inferable __all__ variables do not make Pylint crash. - -""" -# pylint: disable=R0903,R0201,W0612 - -__revision__ = 0 - -__all__ = sorted([ - 'Dummy', - 'NonExistant', - 'path', - 'func', - 'inner', - 'InnerKlass']) diff --git a/test/input/func_noerror_base_init_vars.py b/test/input/func_noerror_base_init_vars.py deleted file mode 100644 index 0870cef..0000000 --- a/test/input/func_noerror_base_init_vars.py +++ /dev/null @@ -1,36 +0,0 @@ -# pylint:disable=R0201 -"""Checks that class variables are seen as inherited ! -""" -__revision__ = '' - -class BaseClass(object): - """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 deleted file mode 100644 index 4d6ba0a..0000000 --- a/test/input/func_noerror_builtin_module_test.py +++ /dev/null @@ -1,11 +0,0 @@ -"""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_class_attributes.py b/test/input/func_noerror_class_attributes.py deleted file mode 100644 index bb4ec6d..0000000 --- a/test/input/func_noerror_class_attributes.py +++ /dev/null @@ -1,17 +0,0 @@ -"""Test that valide class attribut doesn't trigger errors""" -__revision__ = 'sponge bob' - -class Clazz(object): - "dummy class" - - def __init__(self): - self.topic = 5 - self._data = 45 - - def change_type(self, new_class): - """Change type""" - self.__class__ = new_class - - def do_nothing(self): - "I do nothing useful" - return self.topic + 56 diff --git a/test/input/func_noerror_class_decorators_py26.py b/test/input/func_noerror_class_decorators_py26.py deleted file mode 100644 index ef7f1ef..0000000 --- a/test/input/func_noerror_class_decorators_py26.py +++ /dev/null @@ -1,9 +0,0 @@ -"""test class decorator are recognized""" -# pylint: disable=R0903,W0232,C - -from logilab.common.deprecation import deprecated, class_moved - -@deprecated('This is a bad class name; use %s to rename' % class_moved) -class Foo: - '''foo goo''' - diff --git a/test/input/func_noerror_classes_protected_member_access.py b/test/input/func_noerror_classes_protected_member_access.py deleted file mode 100644 index bef1bff..0000000 --- a/test/input/func_noerror_classes_protected_member_access.py +++ /dev/null @@ -1,24 +0,0 @@ -""" -#3123: W0212 false positive on static method -""" -__revision__ = 1 - -class A3123(object): - """oypuee""" - _protected = 1 - def __init__(self): - pass - - - def cmeth(cls, val): - """set protected member""" - cls._protected = +val - - cmeth = classmethod(cmeth) - - def smeth(val): - """set protected member""" - A3123._protected += val - - smeth = staticmethod(smeth) - diff --git a/test/input/func_noerror_crash_122793.py b/test/input/func_noerror_crash_122793.py deleted file mode 100644 index 1d14d8e..0000000 --- a/test/input/func_noerror_crash_122793.py +++ /dev/null @@ -1,9 +0,0 @@ -# pylint: disable=C0121 -"""https://www.logilab.org/ticket/122793""" - -def gen(): - """dumb generator""" - yield - -GEN = gen() -GEN.next() diff --git a/test/input/func_noerror_decorator_scope.py b/test/input/func_noerror_decorator_scope.py deleted file mode 100644 index 95e7a47..0000000 --- a/test/input/func_noerror_decorator_scope.py +++ /dev/null @@ -1,19 +0,0 @@ -# -*- pylint: disable=W0232,R0903 -"""Test that decorators sees the class namespace - just like -function default values does but function body doesn't. - -https://www.logilab.net/elo/ticket/3711 - bug finding decorator arguments -https://www.logilab.net/elo/ticket/5626 - name resolution bug inside classes -""" - -__revision__ = 0 - -class Test(object): - """test class""" - ident = lambda x: x - - @ident(ident) - def method(self, val=ident(7), func=ident): - """hop""" - print self - return func(val) 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 deleted file mode 100644 index 2ed0b1f..0000000 --- a/test/input/func_noerror_defined_and_used_on_same_line.py +++ /dev/null @@ -1,30 +0,0 @@ -#pylint: disable=C0111,C0321 -"""pylint complains about 'index' being used before definition""" -from __future__ import with_statement - -__revision__ = None - -print [index - for index in range(10)] - -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 - -FUNC4 = lambda a, b: a != b -FUNC3 = lambda (a, b): a != b - -# test http://www.logilab.org/ticket/6954: - -with open('f') as f: print f.read() diff --git a/test/input/func_noerror_defined_and_used_on_same_line_py27.py b/test/input/func_noerror_defined_and_used_on_same_line_py27.py deleted file mode 100644 index 11b1a2d..0000000 --- a/test/input/func_noerror_defined_and_used_on_same_line_py27.py +++ /dev/null @@ -1,7 +0,0 @@ -#pylint: disable=C0111,C0321 -"""pylint complains about 'index' being used before definition""" - -__revision__ = 1 - -with open('f') as f, open(f.read()) as g: - print g.read() diff --git a/test/input/func_noerror_e1101_but_getattr.py b/test/input/func_noerror_e1101_but_getattr.py deleted file mode 100644 index 03b8fb9..0000000 --- a/test/input/func_noerror_e1101_but_getattr.py +++ /dev/null @@ -1,23 +0,0 @@ -"""don't want E1101 if __getattr__ is defined""" - -__revision__ = None - -class MyString(object): - """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_except_pass.py b/test/input/func_noerror_except_pass.py deleted file mode 100644 index a28c4ea..0000000 --- a/test/input/func_noerror_except_pass.py +++ /dev/null @@ -1,11 +0,0 @@ -""" -#3205: W0704 (except doesn't do anything) false positive if some statements -follow a "pass" -""" -__revision__ = None - -try: - A = 2 -except ValueError: - pass # pylint: disable=W0107 - print A diff --git a/test/input/func_noerror_factory_method.py b/test/input/func_noerror_factory_method.py deleted file mode 100644 index 80ad433..0000000 --- a/test/input/func_noerror_factory_method.py +++ /dev/null @@ -1,23 +0,0 @@ -# pylint: disable=R0903 -"""use new astroid context sensitive inference""" -__revision__ = 1 - -class Super(object): - """super class""" - def __init__(self): - self.bla = None - - def instance(cls): - """factory method""" - return cls() - instance = classmethod(instance) - -class Sub(Super): - """dub class""" - def method(self): - """specific method""" - print 'method called', self - -# should see the Sub.instance() is returning a Sub instance, not a Super -# instance -Sub.instance().method() diff --git a/test/input/func_noerror_function_as_method.py b/test/input/func_noerror_function_as_method.py deleted file mode 100644 index 6d96a65..0000000 --- a/test/input/func_noerror_function_as_method.py +++ /dev/null @@ -1,18 +0,0 @@ -# pylint: disable=R0903 -'''Test that a function is considered a method when looked up through a class. -''' -__revision__ = 1 - -class Clazz(object): - 'test class' - - def __init__(self, value): - self.value = value - -def func(arg1, arg2): - 'function that will be used as a method' - return arg1.value + arg2 - -Clazz.method = func - -print Clazz(1).method(2) diff --git a/test/input/func_noerror_indirect_interface.py b/test/input/func_noerror_indirect_interface.py deleted file mode 100644 index a0f245a..0000000 --- a/test/input/func_noerror_indirect_interface.py +++ /dev/null @@ -1,16 +0,0 @@ -"""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_lambda_use_before_assign.py b/test/input/func_noerror_lambda_use_before_assign.py deleted file mode 100644 index 0ab8c2c..0000000 --- a/test/input/func_noerror_lambda_use_before_assign.py +++ /dev/null @@ -1,7 +0,0 @@ -"""https://www.logilab.net/elo/ticket/18862""" -__revision__ = 1 -def function(): - """hop""" - ggg = lambda: xxx - xxx = 1 - print ggg() diff --git a/test/input/func_noerror_mcs_attr_access.py b/test/input/func_noerror_mcs_attr_access.py deleted file mode 100644 index 8556f8f..0000000 --- a/test/input/func_noerror_mcs_attr_access.py +++ /dev/null @@ -1,20 +0,0 @@ -# pylint: disable=R0903 -"""test attribute access on metaclass""" - - -__revision__ = 'yo' - -class Meta(type): - """the meta class""" - def __init__(cls, name, bases, dictionary): - super(Meta, cls).__init__(name, bases, dictionary) - print cls, cls._meta_args - delattr(cls, '_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 deleted file mode 100644 index 0996c66..0000000 --- a/test/input/func_noerror_nested_classes.py +++ /dev/null @@ -1,18 +0,0 @@ -# pylint: disable=R0903 -"""crash test""" - -__revision__ = 1 - -class Temelekefe(object): - """gloubliboulga""" - - def __init__(self): - """nested class with function raise error""" - class Toto(object): - """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_30.py b/test/input/func_noerror_new_style_class_py_30.py deleted file mode 100644 index 87bfb9e..0000000 --- a/test/input/func_noerror_new_style_class_py_30.py +++ /dev/null @@ -1,45 +0,0 @@ -"""check builtin data descriptors such as mode and name attributes -on a file are correctly handled - -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_no_warning_docstring.py b/test/input/func_noerror_no_warning_docstring.py deleted file mode 100644 index 1af00c2..0000000 --- a/test/input/func_noerror_no_warning_docstring.py +++ /dev/null @@ -1,42 +0,0 @@ -''' Test for inheritence ''' - -__revision__ = 1 - -class AAAA(object): - ''' class AAAA ''' - - def __init__(self): - pass - - def method1(self): - ''' method 1 ''' - print self - - def method2(self): - ''' method 2 ''' - print self - -class BBBB(AAAA): - ''' class BBBB ''' - - def __init__(self): - AAAA.__init__(self) - - # should ignore docstring calling from class AAAA - def method1(self): - AAAA.method1(self) - -class CCCC(BBBB): - ''' class CCCC ''' - - def __init__(self): - BBBB.__init__(self) - - # should ignore docstring since CCCC is inherited from BBBB which is - # inherited from AAAA containing method2 - if __revision__: - def method2(self): - AAAA.method2(self) - else: - def method2(self): - AAAA.method1(self) diff --git a/test/input/func_noerror_overloaded_operator.py b/test/input/func_noerror_overloaded_operator.py deleted file mode 100644 index a013673..0000000 --- a/test/input/func_noerror_overloaded_operator.py +++ /dev/null @@ -1,21 +0,0 @@ -# pylint: disable=C0111,R0903 -"""#3291""" -__revision__ = 1 - -class Myarray(object): - def __init__(self, array): - self.array = array - - def __mul__(self, val): - return Myarray(val) - - def astype(self): - return "ASTYPE", self - -def randint(maximum): - if maximum is not None: - return Myarray([1, 2, 3]) * 2 - else: - return int(5) - -print randint(1).astype() # we don't wan't an error for astype access diff --git a/test/input/func_noerror_socket_member.py b/test/input/func_noerror_socket_member.py deleted file mode 100644 index ffa3e9c..0000000 --- a/test/input/func_noerror_socket_member.py +++ /dev/null @@ -1,25 +0,0 @@ -"""Testing Pylint with the socket module - -Pylint Problem -============== - -Version used: - - - Pylint 0.10.0 - - Logilab common 0.15.0 - - Logilab astroid 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 deleted file mode 100644 index ea5f7d8..0000000 --- a/test/input/func_noerror_static_method.py +++ /dev/null @@ -1,27 +0,0 @@ -"""Checks if static / class methods works fine in Pylint -""" - -__revision__ = '' - -class MyClass(object): - """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_py24.py b/test/input/func_noerror_staticmethod_as_decorator_py24.py deleted file mode 100644 index d671879..0000000 --- a/test/input/func_noerror_staticmethod_as_decorator_py24.py +++ /dev/null @@ -1,35 +0,0 @@ -# pylint: disable=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_super_protected.py b/test/input/func_noerror_super_protected.py deleted file mode 100644 index 5e78eea..0000000 --- a/test/input/func_noerror_super_protected.py +++ /dev/null @@ -1,22 +0,0 @@ -"""Accessing a protected method through super() is ok.""" - -# pylint: disable=missing-docstring,too-few-public-methods - -__revision__ = None - -class Alpha(object): - - _secret = 2 - - def test(self): - print "test %s" % self - - -class Beta(Alpha): - - def test(self): - print super(Beta, self)._secret - super(Beta, self).test() - - -Beta().test() diff --git a/test/input/func_noerror_used_before_assignment.py b/test/input/func_noerror_used_before_assignment.py deleted file mode 100644 index 6ba0b88..0000000 --- a/test/input/func_noerror_used_before_assignment.py +++ /dev/null @@ -1,5 +0,0 @@ -# pylint: disable = line-too-long, multiple-statements, missing-module-attribute -"""https://bitbucket.org/logilab/pylint/issue/111/false-positive-used-before-assignment-with""" - -try: raise IOError(1, "a") -except IOError, err: print err diff --git a/test/input/func_noerror_yield_assign_py25.py b/test/input/func_noerror_yield_assign_py25.py deleted file mode 100644 index 5c365e2..0000000 --- a/test/input/func_noerror_yield_assign_py25.py +++ /dev/null @@ -1,21 +0,0 @@ -"""http://www.logilab.org/ticket/8771""" - -__revision__ = 2 - -def generator(): - """yield as assignment""" - yield 45 - xxxx = yield 123 - print xxxx - -def generator_fp1(seq): - """W0631 false positive""" - for val in seq: - pass - for val in seq: - yield val - -def generator_fp2(): - """E0601 false positive""" - xxxx = 12 - yield xxxx diff --git a/test/input/func_r0903.py b/test/input/func_r0903.py deleted file mode 100644 index ccbad58..0000000 --- a/test/input/func_r0903.py +++ /dev/null @@ -1,13 +0,0 @@ -"""test min methods""" -__revision__ = None - -class Aaaa(object): - """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 deleted file mode 100644 index 28bda5c..0000000 --- a/test/input/func_r0904.py +++ /dev/null @@ -1,73 +0,0 @@ -# pylint: disable=R0201 -"""test max methods""" -__revision__ = None -class Aaaa(object): - """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_r0922.py b/test/input/func_r0922.py deleted file mode 100644 index d4abafe..0000000 --- a/test/input/func_r0922.py +++ /dev/null @@ -1,21 +0,0 @@ -"""test max methods""" -__revision__ = None - -class Aaaa(object): - """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 deleted file mode 100644 index 514f042..0000000 --- a/test/input/func_r0923.py +++ /dev/null @@ -1,32 +0,0 @@ -"""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(object): - """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_raw_escapes.py b/test/input/func_raw_escapes.py deleted file mode 100644 index b08b6f1..0000000 --- a/test/input/func_raw_escapes.py +++ /dev/null @@ -1,19 +0,0 @@ -# pylint:disable=W0105, W0511, C0121 -"""Test for backslash escapes in byte vs unicode strings""" - -# Would be valid in Unicode, but probably not what you want otherwise -BAD_UNICODE = b'\u0042' -BAD_LONG_UNICODE = b'\U00000042' -BAD_NAMED_UNICODE = b'\N{GREEK SMALL LETTER ALPHA}' - -GOOD_UNICODE = u'\u0042' -GOOD_LONG_UNICODE = u'\U00000042' -GOOD_NAMED_UNICODE = u'\N{GREEK SMALL LETTER ALPHA}' - - -# Valid raw strings -RAW_BACKSLASHES = r'raw' -RAW_UNICODE = ur"\u0062\n" - -# In a comment you can have whatever you want: \ \\ \n \m -# even things that look like bad strings: "C:\Program Files" diff --git a/test/input/func_scope_regrtest.py b/test/input/func_scope_regrtest.py deleted file mode 100644 index 5763397..0000000 --- a/test/input/func_scope_regrtest.py +++ /dev/null @@ -1,15 +0,0 @@ -# pylint: disable=R0903,W0232 -"""check for scope problems""" - -__revision__ = None - -class Well(object): - """well""" - class Data(object): - """base hidden class""" - class Sub(Data): - """whaou, is Data found???""" - attr = Data() - def func(self): - """check Sub is not defined here""" - return Sub(), self diff --git a/test/input/func_set_literal_as_default_py27.py b/test/input/func_set_literal_as_default_py27.py deleted file mode 100644 index 5c7200e..0000000 --- a/test/input/func_set_literal_as_default_py27.py +++ /dev/null @@ -1,7 +0,0 @@ -"""docstring""" - -__revision__ = '' - -def function1(value={1}): - """set is mutable and dangerous.""" - print value diff --git a/test/input/func_superfluous_parens.py b/test/input/func_superfluous_parens.py deleted file mode 100644 index 112ab2b..0000000 --- a/test/input/func_superfluous_parens.py +++ /dev/null @@ -1,19 +0,0 @@ -"""Test the superfluous-parens warning.""" - -__revision__ = 1 - -if (3 == 5): - pass -if not (3 == 5): - pass -if not (3 or 5): - pass -for (x) in (1, 2, 3): - print x -if (1) in (1, 2, 3): - pass -if (1, 2) in (1, 2, 3): - pass -DICT = {'a': 1, 'b': 2} -del(DICT['b']) -del DICT['a'] diff --git a/test/input/func_toolonglines.py b/test/input/func_toolonglines.py deleted file mode 100644 index d02e4bb..0000000 --- a/test/input/func_toolonglines.py +++ /dev/null @@ -1,27 +0,0 @@ -########################################################################################## -""" that one is too long tooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo""" - -__revision__ = '' - -# The next line is exactly 80 characters long. -A = '--------------------------------------------------------------------------' - -# The next line is longer than 80 characters, because the file is encoded -# in ASCII. -THIS_IS_A_VERY_LONG_VARIABLE_NAME = 'СущеÑтвительное ЧаÑтица' # With warnings. - -# Do not trigger the line-too-long warning if the only token that makes the -# line longer than 80 characters is a trailing pylint disable. -var = 'This line has a disable pragma and whitespace trailing beyond 80 chars. ' # pylint:disable=invalid-name - -badname = 'This line is already longer than 80 characters even without the pragma.' # pylint:disable=invalid-name - -# http://example.com/this/is/a/very/long/url?but=splitting&urls=is&a=pain&so=they&can=be&long - - -def function(): - """This is a docstring. - - That contains a very, very long line that exceeds the 80 character limit by a good margin. - """ - pass diff --git a/test/input/func_trailing_whitespace.py b/test/input/func_trailing_whitespace.py deleted file mode 100644 index 2d2786d..0000000 --- a/test/input/func_trailing_whitespace.py +++ /dev/null @@ -1,8 +0,0 @@ -"""Regression test for trailing-whitespace (C0303).""" - -__revision__ = 0 - -print 'some trailing whitespace' -print 'trailing whitespace does not count towards the line length limit' -print 'windows line ends are ok'
-print 'but trailing whitespace on win is not'
diff --git a/test/input/func_typecheck_callfunc_assigment.py b/test/input/func_typecheck_callfunc_assigment.py deleted file mode 100644 index a909f17..0000000 --- a/test/input/func_typecheck_callfunc_assigment.py +++ /dev/null @@ -1,56 +0,0 @@ -# pylint: disable=R0921 -"""check assignment to function call where the function doesn't return - - 'E1111': ('Assigning to function call which doesn\'t return', - 'Used when an assignment 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 assignment 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 deleted file mode 100644 index b120ca4..0000000 --- a/test/input/func_typecheck_getattr.py +++ /dev/null @@ -1,69 +0,0 @@ -# pylint: disable= -"""check getattr if inference succeed""" - -__revision__ = None - -class Provider(object): - """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 - self.set_later = 0 - - def set_set_later(self, value): - """set set_later attribute (introduce an inference ambiguity)""" - self.set_later = value - - 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() - # unicode : moved to func_3k_removed_stuff_py_30.py - # - integer = 1 - print integer.whatever - -print object.__init__ -print property.__init__ -print Client().set_later.lower() - -# should detect mixing new style / old style classes -Client.__bases__ += (object,) diff --git a/test/input/func_typecheck_non_callable_call.py b/test/input/func_typecheck_non_callable_call.py deleted file mode 100644 index 8d8a6c2..0000000 --- a/test/input/func_typecheck_non_callable_call.py +++ /dev/null @@ -1,37 +0,0 @@ -# pylint: disable=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_unbalanced_tuple_unpacking.py b/test/input/func_unbalanced_tuple_unpacking.py deleted file mode 100644 index c2e01e9..0000000 --- a/test/input/func_unbalanced_tuple_unpacking.py +++ /dev/null @@ -1,70 +0,0 @@ -"""Check possible unbalanced tuple unpacking """ - -from input.unpacking import unpack - -__revision__ = 0 - -def do_stuff(): - """This is not right.""" - first, second = 1, 2, 3 - return first + second - -def do_stuff1(): - """This is not right.""" - first, second = [1, 2, 3] - return first + second - -def do_stuff2(): - """This is not right.""" - (first, second) = 1, 2, 3 - return first + second - -def do_stuff3(): - """This is not right.""" - first, second = range(100) - return first + second - -def do_stuff4(): - """ This is right """ - first, second = 1, 2 - return first + second - -def do_stuff5(): - """ This is also right """ - first, second = (1, 2) - return first + second - -def do_stuff6(): - """ This is right """ - (first, second) = (1, 2) - return first + second - -def temp(): - """ This is not weird """ - if True: - return [1, 2] - return [2, 3, 4] - -def do_stuff7(): - """ This is not right """ - first, second = temp() - return first + second - -def temp2(): - """ This is weird, but correct """ - if True: - return (1, 2) - else: - if True: - return (2, 3) - return (4, 5) - -def do_stuff8(): - """ This is correct """ - first, second = temp2() - return first + second - -def do_stuff9(): - """ This is not correct """ - first, second = unpack() - return first + second diff --git a/test/input/func_undefined_var.py b/test/input/func_undefined_var.py deleted file mode 100644 index 407f3f6..0000000 --- a/test/input/func_undefined_var.py +++ /dev/null @@ -1,85 +0,0 @@ -"""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 astroid 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 - augvar += 1 - del vardel - -# Warning for Attribute access to undefinde attributes ? -#class Attrs(object): - #"""dummy class for wrong attribute access""" -#AOU = Attrs() -#AOU.number *= 1.3 -#del AOU.badattr - -try: - POUET # don't catch me -except NameError: - POUET = 'something' - -try: - POUETT # don't catch me -except Exception: # pylint:disable = W0703 - POUETT = 'something' - -try: - POUETTT # don't catch me -except: # pylint:disable = W0702 - POUETTT = 'something' - -print POUET, POUETT, POUETTT - - -try: - PLOUF # catch me -except ValueError: - PLOUF = 'something' - -print PLOUF - -def if_branch_test(something): - """hop""" - if something == 0: - if xxx == 1: - pass - else: - print xxx - xxx = 3 - - -def decorator(arg): - """Decorator with one argument.""" - return lambda: list(arg) - - -@decorator(arg=[i * 2 for i in range(15)]) -def func1(): - """A function with a decorator that contains a listcomp.""" - pass - -@decorator(arg=(i * 2 for i in range(15))) -def func2(): - """A function with a decorator that contains a genexpr.""" - pass diff --git a/test/input/func_unknown_encoding.py b/test/input/func_unknown_encoding.py deleted file mode 100644 index 31deabd..0000000 --- a/test/input/func_unknown_encoding.py +++ /dev/null @@ -1,6 +0,0 @@ -# -*- coding: IBO-8859-1 -*- -""" check correct unknown encoding declaration -""" - -__revision__ = 'éééé' - diff --git a/test/input/func_unpack_exception_py_30.py b/test/input/func_unpack_exception_py_30.py deleted file mode 100644 index 356915e..0000000 --- a/test/input/func_unpack_exception_py_30.py +++ /dev/null @@ -1,13 +0,0 @@ -"""Test for W0623, overwriting names in exception handlers.""" - -__revision__ = '' - -def new_style(): - """Some exceptions can be unpacked.""" - try: - pass - except IOError, (errno, message): # this is fine - print errno, message - except IOError, (new_style, tuple): # W0623 twice - print new_style, tuple - diff --git a/test/input/func_unpacking_non_sequence.py b/test/input/func_unpacking_non_sequence.py deleted file mode 100644 index cd35328..0000000 --- a/test/input/func_unpacking_non_sequence.py +++ /dev/null @@ -1,68 +0,0 @@ -"""Check unpacking non-sequences in assignments. """ - -# pylint: disable=too-few-public-methods, invalid-name - -from sys import exit as nonseq_func -from input.unpacking import nonseq - -__revision__ = 0 - -# Working - -class Seq(object): - """ sequence """ - def __init__(self): - self.items = range(2) - - def __getitem__(self, item): - return self.items[item] - - def __len__(self): - return len(self.items) - -class Iter(object): - """ Iterator """ - def __iter__(self): - for number in range(2): - yield number - -def good_unpacking(): - """ returns should be unpackable """ - if True: - return [1, 2] - else: - return (3, 4) - -def good_unpacking2(): - """ returns should be unpackable """ - return good_unpacking() - -a, b = [1, 2] -a, b = (1, 2) -a, b = set([1, 2]) -a, b = {1: 2, 2: 3} -a, b = "xy" -a, b = Seq() -a, b = Iter() -a, b = (number for number in range(2)) -a, b = good_unpacking() -a, b = good_unpacking2() - -# Not working -class NonSeq(object): - """ does nothing """ - -def bad_unpacking(): - """ one return isn't unpackable """ - if True: - return None - return [1, 2] - -a, b = NonSeq() -a, b = ValueError -a, b = None -a, b = 1 -a, b = nonseq -a, b = nonseq() -a, b = bad_unpacking() -a, b = nonseq_func diff --git a/test/input/func_unreachable.py b/test/input/func_unreachable.py deleted file mode 100644 index c5f7eec..0000000 --- a/test/input/func_unreachable.py +++ /dev/null @@ -1,22 +0,0 @@ -"""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_unused_overridden_argument.py b/test/input/func_unused_overridden_argument.py deleted file mode 100644 index b587a12..0000000 --- a/test/input/func_unused_overridden_argument.py +++ /dev/null @@ -1,32 +0,0 @@ -# pylint: disable=R0903 -"""for Sub.inherited, only the warning for "aay" is desired. -The warnings for "aab" and "aac" are most likely false positives though, -because there could be another subclass that overrides the same method and does -use the arguments (eg Sub2) -""" - -__revision__ = 'thx to Maarten ter Huurne' - -class Base(object): - "parent" - def inherited(self, aaa, aab, aac): - "abstract method" - raise NotImplementedError - -class Sub(Base): - "child 1" - def inherited(self, aaa, aab, aac): - "overridden method, though don't use every argument" - return aaa - - def newmethod(self, aax, aay): - "another method, warning for aay desired" - print self, aax - -class Sub2(Base): - "child 1" - - def inherited(self, aaa, aab, aac): - "overridden method, use every argument" - return aaa + aab + aac - diff --git a/test/input/func_use_for_or_listcomp_var.py b/test/input/func_use_for_or_listcomp_var.py deleted file mode 100644 index 3de5a72..0000000 --- a/test/input/func_use_for_or_listcomp_var.py +++ /dev/null @@ -1,27 +0,0 @@ -"""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() - - -for x in []: - pass -for x in range(3): - print (lambda: x)() # OK diff --git a/test/input/func_used_before_assignment_py30.py b/test/input/func_used_before_assignment_py30.py deleted file mode 100644 index b5d0bf3..0000000 --- a/test/input/func_used_before_assignment_py30.py +++ /dev/null @@ -1,28 +0,0 @@ -"""Check for nonlocal and used-before-assignment"""
-# pylint: disable=missing-docstring, unused-variable
-
-__revision__ = 0
-
-def test_ok():
- """ uses nonlocal """
- cnt = 1
- def wrap():
- nonlocal cnt
- cnt = cnt + 1
- wrap()
-
-def test_fail():
- """ doesn't use nonlocal """
- cnt = 1
- def wrap():
- cnt = cnt + 1
- wrap()
-
-def test_fail2():
- """ use nonlocal, but for other variable """
- cnt = 1
- count = 1
- def wrap():
- nonlocal count
- cnt = cnt + 1
- wrap()
diff --git a/test/input/func_useless_else_on_loop.py b/test/input/func_useless_else_on_loop.py deleted file mode 100644 index 289366a..0000000 --- a/test/input/func_useless_else_on_loop.py +++ /dev/null @@ -1,55 +0,0 @@ -"""Check for else branches on loops with break an return only.""" - -__revision__ = 0 - -def test_return_for(): - """else + return is not accetable.""" - for i in range(10): - if i % 2: - return i - else: - print 'math is broken' - -def test_return_while(): - """else + return is not accetable.""" - while True: - return 1 - else: - print 'math is broken' - - -while True: - def short_fun(): - """A function with a loop.""" - for _ in range(10): - break -else: - print 'or else!' - - -while True: - while False: - break -else: - print 'or else!' - -for j in range(10): - pass -else: - print 'fat chance' - for j in range(10): - break - -def test_return_for2(): - """no false positive for break in else - - https://bitbucket.org/logilab/pylint/issue/117/useless-else-on-loop-false-positives - """ - for i in range(10): - for i in range(i): - if i % 2: - break - else: - break - else: - print 'great math' diff --git a/test/input/func_utf8_lines.py b/test/input/func_utf8_lines.py deleted file mode 100644 index ee29db7..0000000 --- a/test/input/func_utf8_lines.py +++ /dev/null @@ -1,8 +0,0 @@ -# coding: utf-8 -"""Test data file files with non-ASCII content.""" - -__revision__ = 1 - -THIS_IS_A_LONG_VARIABLE_NAME = 'СущеÑтвительное ЧаÑтица' # but the line is okay - -THIS_IS_A_VERY_LONG_VARIABLE_NAME = 'СущеÑтвительное ЧаÑтица' # and the line is not okay diff --git a/test/input/func_variables_unused_name_from_wilcard_import.py b/test/input/func_variables_unused_name_from_wilcard_import.py deleted file mode 100644 index c59b731..0000000 --- a/test/input/func_variables_unused_name_from_wilcard_import.py +++ /dev/null @@ -1,3 +0,0 @@ -"""check unused import from a wildcard import""" -from input.func_w0611 import * -__revision__ = None diff --git a/test/input/func_w0103.py b/test/input/func_w0103.py deleted file mode 100644 index f8216d9..0000000 --- a/test/input/func_w0103.py +++ /dev/null @@ -1,8 +0,0 @@ -"""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 deleted file mode 100644 index d601682..0000000 --- a/test/input/func_w0104.py +++ /dev/null @@ -1,12 +0,0 @@ -"""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 deleted file mode 100644 index c951e9d..0000000 --- a/test/input/func_w0105.py +++ /dev/null @@ -1,62 +0,0 @@ -"""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_w0108.py b/test/input/func_w0108.py deleted file mode 100644 index 0d4cc62..0000000 --- a/test/input/func_w0108.py +++ /dev/null @@ -1,40 +0,0 @@ -# pylint: disable=W0142 -"""test suspicious lambda expressions -""" - -__revision__ = '' - -# Some simple examples of the most commonly encountered forms. -_ = lambda: list() # replaceable with "list" -_ = lambda x: hash(x) # replaceable with "hash" -_ = lambda x, y: min(x, y) # replaceable with "min" - -# A function that can take any arguments given to it. -_ANYARGS = lambda *args, **kwargs: 'completely arbitrary return value' - -# Some more complex forms of unnecessary lambda expressions. -_ = lambda *args: _ANYARGS(*args) -_ = lambda **kwargs: _ANYARGS(**kwargs) -_ = lambda *args, **kwargs: _ANYARGS(*args, **kwargs) -_ = lambda x, y, z, *args, **kwargs: _ANYARGS(x, y, z, *args, **kwargs) - -# Lambdas that are *not* unnecessary and should *not* trigger warnings. -_ = lambda x: x -_ = lambda x: x() -_ = lambda x=4: hash(x) -_ = lambda x, y: range(y, x) -_ = lambda x: range(5, x) -_ = lambda x, y: range(x, 5) -_ = lambda x, y, z: x.y(z) -_ = lambda: 5 -_ = lambda **kwargs: _ANYARGS() -_ = lambda **kwargs: _ANYARGS(**dict([('three', 3)])) -_ = lambda **kwargs: _ANYARGS(**{'three': 3}) -_ = lambda dict_arg, **kwargs: _ANYARGS(kwargs, **dict_arg) -_ = lambda *args: _ANYARGS() -_ = lambda *args: _ANYARGS(*list([3, 4])) -_ = lambda *args: _ANYARGS(*[3, 4]) -_ = lambda list_arg, *args: _ANYARGS(args, *list_arg) -_ = lambda: _ANYARGS(*[3]) -_ = lambda: _ANYARGS(**{'three': 3}) -_ = lambda: _ANYARGS(*[3], **{'three': 3}) diff --git a/test/input/func_w0112.py b/test/input/func_w0112.py deleted file mode 100644 index cdf2bd0..0000000 --- a/test/input/func_w0112.py +++ /dev/null @@ -1,37 +0,0 @@ -"""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_w0152.py b/test/input/func_w0152.py deleted file mode 100644 index 8f51dfd..0000000 --- a/test/input/func_w0152.py +++ /dev/null @@ -1,18 +0,0 @@ -"""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) - -def coolfunc(*args, **kwargs): - """magic function""" - return func(*args, **kwargs) diff --git a/test/input/func_w0205.py b/test/input/func_w0205.py deleted file mode 100644 index 6e7bf43..0000000 --- a/test/input/func_w0205.py +++ /dev/null @@ -1,24 +0,0 @@ -"""check different signatures""" - -__revision__ = 0 - -class Abcd(object): - '''dummy''' - def __init__(self): - self.aarg = False - def abcd(self, aaa=1, 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, bbbb=None): - """hehehe""" - print self, aaa, bbbb diff --git a/test/input/func_w0223.py b/test/input/func_w0223.py deleted file mode 100644 index 1563b41..0000000 --- a/test/input/func_w0223.py +++ /dev/null @@ -1,36 +0,0 @@ -# pylint: disable=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(object): - """abstract class - """ - def aaaa(self): - """should be overridden in concrete class""" - raise NotImplementedError() - - - def bbbb(self): - """should be overridden in concrete class""" - raise NotImplementedError() - - def __init__(self): - pass - -class AbstractB(Abstract): - """abstract class - this class is checking that it does not output an error msg for - unimplemeted methods in abstract classes - """ - def cccc(self): - """should be overridden in concrete class""" - raise NotImplementedError() - -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 deleted file mode 100644 index 7c4cc02..0000000 --- a/test/input/func_w0231.py +++ /dev/null @@ -1,60 +0,0 @@ -# pylint: disable=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__() - -class NoInit(object): - """No __init__ defined""" - -class Init(NoInit): - """Don't complain for not calling the super __init__""" - - def __init__(self, arg): - self.arg = arg - -class NewStyleC(object): - """__init__ defined by assignemnt.""" - def xx_init(self): - """Initializer.""" - pass - - __init__ = xx_init - -class AssignedInit(NewStyleC): - """No init called.""" - def __init__(self): - self.arg = 0 diff --git a/test/input/func_w0233.py b/test/input/func_w0233.py deleted file mode 100644 index 857743a..0000000 --- a/test/input/func_w0233.py +++ /dev/null @@ -1,36 +0,0 @@ -# pylint: disable=R0903,W0212,W0403,W0406 -"""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(object): - """ancestor 1""" - - def __init__(self): - print 'init', self - BBBBMixin.__init__(self) - -class BBBBMixin(object): - """ancestor 2""" - - def __init__(self): - print 'init', self - -import nonexistant -import func_w0233 -class CCC(BBBBMixin, func_w0233.AAAA, func_w0233.BBBB, nonexistant.AClass): - """mix different things, some inferable some not""" - def __init__(self): - BBBBMixin.__init__(self) - func_w0233.AAAA.__init__(self) - func_w0233.BBBB.__init__(self) - nonexistant.AClass.__init__(self) - -class DDDD(AAAA): - """call superclass constructor in disjunct branches""" - def __init__(self, value): - if value: - AAAA.__init__(self) - else: - AAAA.__init__(self) diff --git a/test/input/func_w0312.py b/test/input/func_w0312.py deleted file mode 100644 index 06a8110..0000000 --- a/test/input/func_w0312.py +++ /dev/null @@ -1,11 +0,0 @@ -"""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_30.py b/test/input/func_w0331_py_30.py deleted file mode 100644 index 6ee6006..0000000 --- a/test/input/func_w0331_py_30.py +++ /dev/null @@ -1,7 +0,0 @@ -"""check use of <> -""" - -__revision__ = 1 - -if __revision__ <> 2: - __revision__ = 2 diff --git a/test/input/func_w0332_py_30.py b/test/input/func_w0332_py_30.py deleted file mode 100644 index d6fed47..0000000 --- a/test/input/func_w0332_py_30.py +++ /dev/null @@ -1,4 +0,0 @@ -"""check use of l as long int marker -""" - -__revision__ = 1l diff --git a/test/input/func_w0401.py b/test/input/func_w0401.py deleted file mode 100644 index 160b555..0000000 --- a/test/input/func_w0401.py +++ /dev/null @@ -1,8 +0,0 @@ -"""test cyclic import -""" -__revision__ = 0 - -from input import w0401_cycle - -if __revision__: - print w0401_cycle diff --git a/test/input/func_w0401_package/all_the_things.py b/test/input/func_w0401_package/all_the_things.py deleted file mode 100644 index ef0ca99..0000000 --- a/test/input/func_w0401_package/all_the_things.py +++ /dev/null @@ -1,8 +0,0 @@ -"""All the things!""" -__revision__ = None -from .thing1 import THING1 -from .thing2 import THING2 -from .thing2 import THING1_PLUS_THING2 - -_ = (THING1, THING2, THING1_PLUS_THING2) -del _ diff --git a/test/input/func_w0401_package/thing2.py b/test/input/func_w0401_package/thing2.py deleted file mode 100644 index 836cd9c..0000000 --- a/test/input/func_w0401_package/thing2.py +++ /dev/null @@ -1,7 +0,0 @@ -"""The second thing.""" -__revision__ = None -from .all_the_things import THING1 - -THING2 = "I am thing2" -THING1_PLUS_THING2 = "%s, plus %s" % (THING1, THING2) - diff --git a/test/input/func_w0402.py b/test/input/func_w0402.py deleted file mode 100644 index d2707ee..0000000 --- a/test/input/func_w0402.py +++ /dev/null @@ -1,12 +0,0 @@ -"""test wildard import -""" -__revision__ = 0 - -from input.func_fixme import * -# This is an unresolved import which still generates the wildcard-import -# warning. -from unknown.package import * - -def abcd(): - """use imports""" - function() diff --git a/test/input/func_w0403.py b/test/input/func_w0403.py deleted file mode 100644 index 89d362d..0000000 --- a/test/input/func_w0403.py +++ /dev/null @@ -1,12 +0,0 @@ -"""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 deleted file mode 100644 index 0e2f727..0000000 --- a/test/input/func_w0404.py +++ /dev/null @@ -1,27 +0,0 @@ -"""Unittests for W0404 (reimport)""" - -__revision__ = 0 - -import sys - -import xml.etree.ElementTree -from xml.etree import ElementTree - -from multiprocessing import pool -import multiprocessing.pool - -import sys - -def no_reimport(): - """docstring""" - import os - print os - - -def reimport(): - """This function contains a reimport.""" - import sys - del sys - - -del sys, ElementTree, xml.etree.ElementTree, pool, multiprocessing.pool diff --git a/test/input/func_w0405.py b/test/input/func_w0405.py deleted file mode 100644 index 8b96cd9..0000000 --- a/test/input/func_w0405.py +++ /dev/null @@ -1,31 +0,0 @@ -"""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 deleted file mode 100644 index e20508f..0000000 --- a/test/input/func_w0406.py +++ /dev/null @@ -1,9 +0,0 @@ -"""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 deleted file mode 100644 index 6bef753..0000000 --- a/test/input/func_w0611.py +++ /dev/null @@ -1,22 +0,0 @@ -"""check unused import -""" -__revision__ = 1 -import os -import sys - -class NonRegr(object): - """???""" - 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 deleted file mode 100644 index 57e139c..0000000 --- a/test/input/func_w0612.py +++ /dev/null @@ -1,22 +0,0 @@ -"""test unused variable -""" - -__revision__ = 0 - -def function(matches): - """"yo""" - aaaa = 1 - index = -1 - for match in matches: - index += 1 - print match - -def visit_if(self, node): - """increments the branches counter""" - branches = 1 - # don't double count If nodes coming from some 'elif' - if node.orelse and len(node.orelse) > 1: - branches += 1 - self.inc_branch(branches) - self.stmts += branches - diff --git a/test/input/func_w0613.py b/test/input/func_w0613.py deleted file mode 100644 index 1df71dd..0000000 --- a/test/input/func_w0613.py +++ /dev/null @@ -1,43 +0,0 @@ -# pylint: disable=R0903 -"""test unused argument -""" - -__revision__ = 1 - -def function(arg=1): - """ignore arg""" - - -class AAAA(object): - """dummy class""" - - def method(self, arg): - """dummy method""" - print self - def __init__(self, *unused_args, **unused_kwargs): - pass - - @classmethod - def selected(cls, *args, **kwargs): - """called by the registry when the vobject has been selected. - """ - return cls - - def using_inner_function(self, etype, size=1): - """return a fake result set for a particular entity type""" - rset = AAAA([('A',)]*size, '%s X' % etype, - description=[(etype,)]*size) - def inner(row, col=0, etype=etype, req=self, rset=rset): - """inner using all its argument""" - # pylint: disable = E1103 - return req.vreg.etype_class(etype)(req, rset, row, col) - # pylint: disable = W0201 - rset.get_entity = inner - -class BBBB(object): - """dummy class""" - - def __init__(self, arg): - """Constructor with an extra parameter. Should raise a warning""" - self.spam = 1 - diff --git a/test/input/func_w0622.py b/test/input/func_w0622.py deleted file mode 100644 index 5769841..0000000 --- a/test/input/func_w0622.py +++ /dev/null @@ -1,11 +0,0 @@ -# pylint: disable=C0103 -"""test built-in redefinition -""" -__revision__ = 0 - -def function(): - """yo""" - type = 1 - print type - -map = {} diff --git a/test/input/func_w0623_py_30.py b/test/input/func_w0623_py_30.py deleted file mode 100644 index 9bccbc6..0000000 --- a/test/input/func_w0623_py_30.py +++ /dev/null @@ -1,67 +0,0 @@ -"""Test for W0623, overwriting names in exception handlers.""" - -__revision__ = '' - -import exceptions - -class MyError(Exception): - """Special exception class.""" - pass - - -def some_function(): - """A function.""" - exc = None - - try: - {}["a"] - except KeyError, exceptions.RuntimeError: # W0623 - pass - except KeyError, OSError: # W0623 - pass - except KeyError, MyError: # W0623 - pass - except KeyError, exc: # this is fine - print exc - except KeyError, exc1: # this is fine - print exc1 - except KeyError, FOO: # C0103 - print FOO - - try: - pass - except KeyError, exc1: # this is fine - print exc1 - -class MyOtherError(Exception): - """Special exception class.""" - pass - - -exc3 = None - -try: - pass -except KeyError, exceptions.RuntimeError: # W0623 - pass -except KeyError, exceptions.RuntimeError.args: # W0623 - pass -except KeyError, OSError: # W0623 - pass -except KeyError, MyOtherError: # W0623 - pass -except KeyError, exc3: # this is fine - print exc3 -except KeyError, exc4: # this is fine - print exc4 -except KeyError, OOPS: # C0103 - print OOPS - -try: - pass -except KeyError, exc4: # this is fine - print exc4 -except IOError, exc5: # this is fine - print exc5 -except MyOtherError, exc5: # this is fine - print exc5 diff --git a/test/input/func_w0631.py b/test/input/func_w0631.py deleted file mode 100644 index 2524f5e..0000000 --- a/test/input/func_w0631.py +++ /dev/null @@ -1,21 +0,0 @@ -"""Check possible undefined loopvar -""" - -__revision__ = 0 - -def do_stuff(some_random_list): - """This is not right.""" - for var in some_random_list: - pass - print var - - -def do_else(some_random_list): - """This is not right.""" - for var in some_random_list: - if var == 42: - break - else: - var = 84 - print var - diff --git a/test/input/func_w0701_py_30.py b/test/input/func_w0701_py_30.py deleted file mode 100644 index 9c1b727..0000000 --- a/test/input/func_w0701_py_30.py +++ /dev/null @@ -1,12 +0,0 @@ -"""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 deleted file mode 100644 index 38a6417..0000000 --- a/test/input/func_w0702.py +++ /dev/null @@ -1,17 +0,0 @@ -"""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 deleted file mode 100644 index 4040540..0000000 --- a/test/input/func_w0703.py +++ /dev/null @@ -1,9 +0,0 @@ -"""check empty except statement -""" - -__revision__ = 0 - -try: - __revision__ += 1 -except Exception: - print 'error' diff --git a/test/input/func_w0711.py b/test/input/func_w0711.py deleted file mode 100644 index 97c3697..0000000 --- a/test/input/func_w0711.py +++ /dev/null @@ -1,15 +0,0 @@ -"""find binary operations used as exceptions -""" - -__revision__ = 1 - -try: - __revision__ += 1 -except Exception or StandardError: - print "caught1" -except Exception and StandardError: - print "caught2" -except Exception or StandardError: - print "caught3" -except (Exception or StandardError), exc: - print "caught4" diff --git a/test/input/func_w1201.py b/test/input/func_w1201.py deleted file mode 100644 index 9013c2d..0000000 --- a/test/input/func_w1201.py +++ /dev/null @@ -1,21 +0,0 @@ -# pylint: disable=E1101 -"""test checking use of the logging module -""" - -__revision__ = '' - -# Muck up the names in an effort to confuse... -import logging as renamed_logging -import os as logging - -# Statements that should be flagged: -renamed_logging.warn('%s, %s' % (4, 5)) -renamed_logging.exception('%s' % 'Exceptional!') -renamed_logging.log(renamed_logging.INFO, 'msg: %s' % 'Run!') - -# Statements that should not be flagged: -renamed_logging.warn('%s, %s', 4, 5) -renamed_logging.log(renamed_logging.INFO, 'msg: %s', 'Run!') -renamed_logging.warn('%s' + ' the rest of a single string') -logging.warn('%s, %s' % (4, 5)) -logging.log(logging.INFO, 'msg: %s' % 'Run!') diff --git a/test/input/func_with_used_before_assignment.py b/test/input/func_with_used_before_assignment.py deleted file mode 100644 index c79815c..0000000 --- a/test/input/func_with_used_before_assignment.py +++ /dev/null @@ -1,12 +0,0 @@ -''' -Regression test for -https://bitbucket.org/logilab/pylint/issue/128/attributeerror-when-parsing -''' -from __future__ import with_statement -__revision__ = 1 - -def do_nothing(): - """ empty """ - with open("") as ctx.obj: - context.do() - context = None diff --git a/test/input/func_with_without_as_py25.py b/test/input/func_with_without_as_py25.py deleted file mode 100644 index e3b2c71..0000000 --- a/test/input/func_with_without_as_py25.py +++ /dev/null @@ -1,14 +0,0 @@ -'''This is a little non regression test on a with statement -without 'as'. -''' -from __future__ import with_statement -__revision__ = 32321313 - -def do_nothing(arg): - 'ho foo' - print arg - with open('x'): - base.baz - base = 7 - - diff --git a/test/input/func_yield_outside_func.py b/test/input/func_yield_outside_func.py deleted file mode 100644 index c1ea3bd..0000000 --- a/test/input/func_yield_outside_func.py +++ /dev/null @@ -1,5 +0,0 @@ -"""This is gramatically correct, but it's still a SyntaxError""" -__revision__ = None -yield 1 - -LAMBDA_WITH_YIELD = lambda: (yield) diff --git a/test/input/unpacking.py b/test/input/unpacking.py deleted file mode 100644 index 807d9b9..0000000 --- a/test/input/unpacking.py +++ /dev/null @@ -1,11 +0,0 @@ -""" Code for checking the display of the module
-for unbalanced-tuple-unpacking and unpacking-non-sequence
-"""
-
-def unpack():
- """ Return something"""
- return (1, 2, 3)
-
-def nonseq():
- """ Return non sequence """
- return 1
\ No newline at end of file diff --git a/test/input/w0401_cycle.py b/test/input/w0401_cycle.py deleted file mode 100644 index bf731e0..0000000 --- a/test/input/w0401_cycle.py +++ /dev/null @@ -1,9 +0,0 @@ -"""w0401 dependency -""" - -__revision__ = 0 - -import input.func_w0401 - -if __revision__: - print input diff --git a/test/messages/func_3k_removed_stuff_py_30.txt b/test/messages/func_3k_removed_stuff_py_30.txt deleted file mode 100644 index 19e7472..0000000 --- a/test/messages/func_3k_removed_stuff_py_30.txt +++ /dev/null @@ -1,4 +0,0 @@ -E: 12:function: Instance of 'unicode' has no 'looower' member -W: 3: Used builtin function 'apply' -W: 4: __future__ import is not the first non docstring statement -W: 6: Relative import 'func_w0302', should be 'input.func_w0302' diff --git a/test/messages/func___name___access.txt b/test/messages/func___name___access.txt deleted file mode 100644 index 4351425..0000000 --- a/test/messages/func___name___access.txt +++ /dev/null @@ -1,4 +0,0 @@ -C: 8:Aaaa: Old-style class defined. -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_abstract_class_instantiated_py30.txt b/test/messages/func_abstract_class_instantiated_py30.txt deleted file mode 100644 index 28dc11e..0000000 --- a/test/messages/func_abstract_class_instantiated_py30.txt +++ /dev/null @@ -1,2 +0,0 @@ -E: 42:main: Abstract class with abstract methods instantiated
-E: 43:main: Abstract class with abstract methods instantiated
\ No newline at end of file diff --git a/test/messages/func_abstract_class_instantiated_py34.txt b/test/messages/func_abstract_class_instantiated_py34.txt deleted file mode 100644 index 310f8ea..0000000 --- a/test/messages/func_abstract_class_instantiated_py34.txt +++ /dev/null @@ -1,3 +0,0 @@ -E: 53:main: Abstract class with abstract methods instantiated
-E: 54:main: Abstract class with abstract methods instantiated
-E: 55:main: Abstract class with abstract methods instantiated
\ No newline at end of file diff --git a/test/messages/func_all.txt b/test/messages/func_all.txt deleted file mode 100644 index ba0e855..0000000 --- a/test/messages/func_all.txt +++ /dev/null @@ -1,4 +0,0 @@ -E: 17: Undefined variable name 'NonExistant' in __all__ -E: 19: Undefined variable name 'func' in __all__ -E: 20: Undefined variable name 'inner' in __all__ -E: 21: Undefined variable name 'InnerKlass' in __all__ diff --git a/test/messages/func_all_undefined.txt b/test/messages/func_all_undefined.txt deleted file mode 100644 index f4170f2..0000000 --- a/test/messages/func_all_undefined.txt +++ /dev/null @@ -1 +0,0 @@ -E: 8: Undefined variable 'SomeUndefinedName' diff --git a/test/messages/func_arguments.txt b/test/messages/func_arguments.txt deleted file mode 100644 index 642d212..0000000 --- a/test/messages/func_arguments.txt +++ /dev/null @@ -1,11 +0,0 @@ -E: 18: No value for argument 'first_argument' in function call -E: 19: Too many positional arguments for function call -E: 21: No value for argument 'third_argument' in function call -E: 22: No value for argument 'first_argument' in function call -E: 22: No value for argument 'second_argument' in function call -E: 22: No value for argument 'third_argument' in function call -E: 24: Too many positional arguments for function call -E: 31: No value for argument 'first_argument' in function call -E: 31: Unexpected keyword argument 'bob' in function call -E: 32: Unexpected keyword argument 'coin' in function call -E: 34: Argument 'one' passed by position and keyword in function call diff --git a/test/messages/func_backtick_deprecated_py_30.txt b/test/messages/func_backtick_deprecated_py_30.txt deleted file mode 100644 index c78baa1..0000000 --- a/test/messages/func_backtick_deprecated_py_30.txt +++ /dev/null @@ -1,2 +0,0 @@ -W: 4: Use of the `` operator - diff --git a/test/messages/func_bad_assigment_to_exception_var.txt b/test/messages/func_bad_assigment_to_exception_var.txt deleted file mode 100644 index 5c4f11a..0000000 --- a/test/messages/func_bad_assigment_to_exception_var.txt +++ /dev/null @@ -1,5 +0,0 @@ -E: 11: Raising int while only classes, instances or string are allowed -E: 20:func: Raising NoneType while only classes, instances or string are allowed -E: 30: Raising NoneType while only classes, instances or string are allowed -W: 15: Raising a string exception - diff --git a/test/messages/func_bad_context_manager.txt b/test/messages/func_bad_context_manager.txt deleted file mode 100644 index 1b5d5d5..0000000 --- a/test/messages/func_bad_context_manager.txt +++ /dev/null @@ -1,3 +0,0 @@ -E: 40:FirstBadContextManager.__exit__: __exit__ must accept 3 arguments: type, value, traceback -E: 49:SecondBadContextManager.__exit__: __exit__ must accept 3 arguments: type, value, traceback -E: 58:ThirdBadContextManager.__exit__: __exit__ must accept 3 arguments: type, value, traceback diff --git a/test/messages/func_bad_open_mode.txt b/test/messages/func_bad_open_mode.txt deleted file mode 100644 index d785d17..0000000 --- a/test/messages/func_bad_open_mode.txt +++ /dev/null @@ -1,7 +0,0 @@ -W: 6: "rw" is not a valid mode for open. -W: 7: "rw" is not a valid mode for open. -W: 8: "rw" is not a valid mode for open. -W: 9: "U+" is not a valid mode for open. -W: 10: "rb+" is not a valid mode for open. -W: 11: "Uw" is not a valid mode for open. -W: 15: "Uwz" is not a valid mode for open. diff --git a/test/messages/func_bad_reversed_sequence.txt b/test/messages/func_bad_reversed_sequence.txt deleted file mode 100644 index 6e85197..0000000 --- a/test/messages/func_bad_reversed_sequence.txt +++ /dev/null @@ -1,9 +0,0 @@ -E: 42:test: Missing argument to reversed()
-E: 43:test: The first reversed() argument is not a sequence
-E: 46:test: The first reversed() argument is not a sequence
-E: 47:test: The first reversed() argument is not a sequence
-E: 48:test: The first reversed() argument is not a sequence
-E: 51:test: The first reversed() argument is not a sequence
-E: 52:test: The first reversed() argument is not a sequence
-E: 54:test: The first reversed() argument is not a sequence
-E: 55:test: The first reversed() argument is not a sequence
\ No newline at end of file diff --git a/test/messages/func_bad_slots.txt b/test/messages/func_bad_slots.txt deleted file mode 100644 index ec44646..0000000 --- a/test/messages/func_bad_slots.txt +++ /dev/null @@ -1,4 +0,0 @@ -E: 43:SecondBad: Invalid __slots__ object
-E: 47:ThirdBad: Invalid object '2' in __slots__, must contain only non empty strings
-E: 49:FourthBad: Invalid __slots__ object
-E: 53:FifthBad: Invalid object "''" in __slots__, must contain only non empty strings
\ No newline at end of file diff --git a/test/messages/func_bad_str_strip_call.txt b/test/messages/func_bad_str_strip_call.txt deleted file mode 100644 index 44b8780..0000000 --- a/test/messages/func_bad_str_strip_call.txt +++ /dev/null @@ -1,3 +0,0 @@ -E: 7: Suspicious argument in unicode.strip call -E: 8: Suspicious argument in unicode.lstrip call -E: 9: Suspicious argument in str.rstrip call diff --git a/test/messages/func_bad_str_strip_call_py30.txt b/test/messages/func_bad_str_strip_call_py30.txt deleted file mode 100644 index 8ea0372..0000000 --- a/test/messages/func_bad_str_strip_call_py30.txt +++ /dev/null @@ -1,3 +0,0 @@ -E: 7: Suspicious argument in str.strip call -E: 8: Suspicious argument in str.lstrip call -E: 9: Suspicious argument in bytes.rstrip call diff --git a/test/messages/func_base_stmt_without_effect.txt b/test/messages/func_base_stmt_without_effect.txt deleted file mode 100644 index 91d9d5f..0000000 --- a/test/messages/func_base_stmt_without_effect.txt +++ /dev/null @@ -1,10 +0,0 @@ -W: 17: Statement seems to have no effect -W: 19: Statement seems to have no effect -W: 23: Statement seems to have no effect -W: 26: String statement has no effect -W: 28: Unnecessary semicolon -W: 30: Expression "(list()) and (tuple())" is assigned to nothing -W: 37: Expression "ANSWER == to_be()" is assigned to nothing -W: 39: Expression "(to_be()) or (not to_be())" is assigned to nothing -W: 40: Expression "to_be().title" is assigned to nothing - diff --git a/test/messages/func_break_or_return_in_try_finally.txt b/test/messages/func_break_or_return_in_try_finally.txt deleted file mode 100644 index 4b674ae..0000000 --- a/test/messages/func_break_or_return_in_try_finally.txt +++ /dev/null @@ -1,2 +0,0 @@ -W: 18:insidious_break_and_return: break statement in finally block may swallow exception -W: 20:insidious_break_and_return: return statement in finally block may swallow exception diff --git a/test/messages/func_catching_non_exception.txt b/test/messages/func_catching_non_exception.txt deleted file mode 100644 index 19f22ab..0000000 --- a/test/messages/func_catching_non_exception.txt +++ /dev/null @@ -1,3 +0,0 @@ -E: 35: Catching an exception which doesn't inherit from BaseException: MyException -E: 40: Catching an exception which doesn't inherit from BaseException: MyException -E: 40: Catching an exception which doesn't inherit from BaseException: MySecondException
\ No newline at end of file diff --git a/test/messages/func_class_access_protected_members.txt b/test/messages/func_class_access_protected_members.txt deleted file mode 100644 index 5900960..0000000 --- a/test/messages/func_class_access_protected_members.txt +++ /dev/null @@ -1,5 +0,0 @@ -W: 17:MyClass.test: Access to a protected member _haha of a client class -W: 32: Access to a protected member _protected of a client class -W: 33: Access to a protected member _protected of a client class -W: 34: Access to a protected member _cls_protected of a client class -W: 35: Access to a protected member _cls_protected of a client class
\ No newline at end of file diff --git a/test/messages/func_class_members.txt b/test/messages/func_class_members.txt deleted file mode 100644 index 054e21a..0000000 --- a/test/messages/func_class_members.txt +++ /dev/null @@ -1,4 +0,0 @@ -E: 16:MyClass.test: Instance of 'MyClass' has no 'incorrect' member -E: 17:MyClass.test: Instance of 'MyClass' has no 'havenot' member -E: 18:MyClass.test: Instance of 'MyClass' has no 'nonexistent1' member -E: 19:MyClass.test: Instance of 'MyClass' has no 'nonexistent2' member diff --git a/test/messages/func_ctor_arguments.txt b/test/messages/func_ctor_arguments.txt deleted file mode 100644 index b8d62b1..0000000 --- a/test/messages/func_ctor_arguments.txt +++ /dev/null @@ -1,17 +0,0 @@ -E: 35: No value for argument 'first_argument' in constructor call -E: 36: Too many positional arguments for constructor call -E: 38: No value for argument 'third_argument' in constructor call -E: 39: No value for argument 'first_argument' in constructor call -E: 39: No value for argument 'second_argument' in constructor call -E: 39: No value for argument 'third_argument' in constructor call -E: 41: Too many positional arguments for constructor call -E: 46: No value for argument 'first_argument' in constructor call -E: 46: Unexpected keyword argument 'bob' in constructor call -E: 47: Unexpected keyword argument 'coin' in constructor call -E: 49: Argument 'one' passed by position and keyword in constructor call -E: 52: No value for argument 'first_argument' in constructor call -E: 53: Too many positional arguments for constructor call -E: 59: Too many positional arguments for constructor call -E: 62: Too many positional arguments for constructor call -E: 63: No value for argument 'first_argument' in constructor call -E: 63: Unexpected keyword argument 'one' in constructor call diff --git a/test/messages/func_dangerous_default.txt b/test/messages/func_dangerous_default.txt deleted file mode 100644 index 6846c07..0000000 --- a/test/messages/func_dangerous_default.txt +++ /dev/null @@ -1,6 +0,0 @@ -W: 7:function1: Dangerous default value [] as argument -W: 11:function2: Dangerous default value HEHE ({}) as argument -W: 19:function4: Dangerous default value set() (__builtin__.set) as argument -W: 29:function6: Dangerous default value GLOBAL_SET (__builtin__.set) as argument -W: 33:function7: Dangerous default value dict() (__builtin__.dict) as argument -W: 37:function8: Dangerous default value list() (__builtin__.list) as argument diff --git a/test/messages/func_dangerous_default_py30.txt b/test/messages/func_dangerous_default_py30.txt deleted file mode 100644 index b068149..0000000 --- a/test/messages/func_dangerous_default_py30.txt +++ /dev/null @@ -1,6 +0,0 @@ -W: 7:function1: Dangerous default value [] as argument -W: 11:function2: Dangerous default value HEHE ({}) as argument -W: 19:function4: Dangerous default value set() (builtins.set) as argument -W: 29:function6: Dangerous default value GLOBAL_SET (builtins.set) as argument -W: 33:function7: Dangerous default value dict() (builtins.dict) as argument -W: 37:function8: Dangerous default value list() (builtins.list) as argument diff --git a/test/messages/func_defining-attr-methods_order.txt b/test/messages/func_defining-attr-methods_order.txt deleted file mode 100644 index 3594754..0000000 --- a/test/messages/func_defining-attr-methods_order.txt +++ /dev/null @@ -1 +0,0 @@ -W: 28:A.set_z: Attribute 'z' defined outside __init__ diff --git a/test/messages/func_deprecated_lambda_py_30.txt b/test/messages/func_deprecated_lambda_py_30.txt deleted file mode 100644 index e7ecd5b..0000000 --- a/test/messages/func_deprecated_lambda_py_30.txt +++ /dev/null @@ -1,2 +0,0 @@ -W: 5: map/filter on lambda could be replaced by comprehension -W: 7: map/filter on lambda could be replaced by comprehension diff --git a/test/messages/func_dict_keys.txt b/test/messages/func_dict_keys.txt deleted file mode 100644 index 8773298..0000000 --- a/test/messages/func_dict_keys.txt +++ /dev/null @@ -1 +0,0 @@ -W: 10: Duplicate key 'tea' in dictionary diff --git a/test/messages/func_disable_linebased.txt b/test/messages/func_disable_linebased.txt deleted file mode 100644 index e4e74a7..0000000 --- a/test/messages/func_disable_linebased.txt +++ /dev/null @@ -1,2 +0,0 @@ -C: 1: Line too long (127/80) -C: 14: Line too long (113/80) diff --git a/test/messages/func_disable_linebased_py30.txt b/test/messages/func_disable_linebased_py30.txt deleted file mode 100644 index 7054dfc..0000000 --- a/test/messages/func_disable_linebased_py30.txt +++ /dev/null @@ -1,2 +0,0 @@ -C: 1: Line too long (127/80) -C: 14: Line too long (114/80) diff --git a/test/messages/func_docstring.txt b/test/messages/func_docstring.txt deleted file mode 100644 index 932df5e..0000000 --- a/test/messages/func_docstring.txt +++ /dev/null @@ -1,8 +0,0 @@ -C: 1: Missing module docstring -C: 5:function0: Empty function docstring -C: 8:function1: Missing function docstring -C: 20:AAAA: Missing class docstring -C: 36:AAAA.method1: Missing method docstring -C: 43:AAAA.method3: Empty method docstring -C: 56:DDDD.method2: Empty method docstring -C: 63:DDDD.method4: Missing method docstring diff --git a/test/messages/func_e0205.txt b/test/messages/func_e0205.txt deleted file mode 100644 index c7402ce..0000000 --- a/test/messages/func_e0205.txt +++ /dev/null @@ -1,2 +0,0 @@ -E: 14:Cdef.abcd: An attribute defined in input.func_e0205 line 10 hides this method - diff --git a/test/messages/func_e12xx.txt b/test/messages/func_e12xx.txt deleted file mode 100644 index 690e6f4..0000000 --- a/test/messages/func_e12xx.txt +++ /dev/null @@ -1,6 +0,0 @@ -E: 13:pprint: Too many arguments for logging format string -E: 14:pprint: Too many arguments for logging format string -E: 15:pprint: Logging format string ends in middle of conversion specifier -E: 16:pprint: Not enough arguments for logging format string -E: 17:pprint: Unsupported logging format character 'a' (0x61) at index 3 -E: 18:pprint: Too many arguments for logging format string diff --git a/test/messages/func_e13xx.txt b/test/messages/func_e13xx.txt deleted file mode 100644 index c130949..0000000 --- a/test/messages/func_e13xx.txt +++ /dev/null @@ -1,12 +0,0 @@ -E: 11:pprint: Not enough arguments for format string -E: 12:pprint: Too many arguments for format string -E: 13:pprint: Mixing named and unnamed conversion specifiers in format string -E: 14:pprint: Missing key 'PARG_2' in format string dictionary -E: 16:pprint: Missing key 'PARG_2' in format string dictionary -E: 17:pprint: Expected mapping for format string, not Tuple -E: 18:pprint: Expected mapping for format string, not List -E: 19:pprint: Unsupported format character 'z' (0x7a) at index 2 -E: 20:pprint: Format string ends in middle of conversion specifier -W: 15:pprint: Unused key 'PARG_3' in format string dictionary -W: 16:pprint: Format string dictionary key should be a string, not 2 - diff --git a/test/messages/func_empty_module.txt b/test/messages/func_empty_module.txt deleted file mode 100644 index bbc5a0d..0000000 --- a/test/messages/func_empty_module.txt +++ /dev/null @@ -1,2 +0,0 @@ -C: 1: Missing module docstring -C: 1: Missing required attribute "__revision__" diff --git a/test/messages/func_exceptions_raise_type_error.txt b/test/messages/func_exceptions_raise_type_error.txt deleted file mode 100644 index 349b9f5..0000000 --- a/test/messages/func_exceptions_raise_type_error.txt +++ /dev/null @@ -1,2 +0,0 @@ -E: 11: Raising int while only classes, instances or string are allowed -E: 14: Raising NoneType while only classes, instances or string are allowed diff --git a/test/messages/func_f0001.txt b/test/messages/func_f0001.txt deleted file mode 100644 index 911d805..0000000 --- a/test/messages/func_f0001.txt +++ /dev/null @@ -1,2 +0,0 @@ -F: 3: Unable to import 'whatever' -W: 3: Unused import whatever diff --git a/test/messages/func_fixme.txt b/test/messages/func_fixme.txt deleted file mode 100644 index 2544ce8..0000000 --- a/test/messages/func_fixme.txt +++ /dev/null @@ -1,2 +0,0 @@ -W: 5: FIXME: beep -W: 8: XXX:bop''' diff --git a/test/messages/func_format.txt b/test/messages/func_format.txt deleted file mode 100644 index ce5df81..0000000 --- a/test/messages/func_format.txt +++ /dev/null @@ -1,46 +0,0 @@ -C: 6: Exactly one space required before assignment -notpreceded= 1 - ^ -C: 7: Exactly one space required after assignment -notfollowed =1 - ^ -C: 8: Exactly one space required after comparison -notfollowed <=1 - ^^ -C: 19: Exactly one space required after comma -aaaa,bbbb = 1,2 - ^ -C: 19: Exactly one space required after comma -aaaa,bbbb = 1,2 - ^ -C: 24: More than one statement on a single line -C: 26: Exactly one space required after comma - aaaa,bbbb = 1,2 - ^ -C: 26: Exactly one space required after comma - aaaa,bbbb = 1,2 - ^ -C: 27: Exactly one space required after comma - aaaa,bbbb = bbbb,aaaa - ^ -C: 27: Exactly one space required after comma - aaaa,bbbb = bbbb,aaaa - ^ -C: 29: Exactly one space required after comma -bbbb = (1,2,3) - ^ -C: 29: Exactly one space required after comma -bbbb = (1,2,3) - ^ -C: 51: Exactly one space required before assignment - funky= funky+2 - ^ -C: 71: Exactly one space required before assignment - ocount[obj.__class__]+= 1 - ^^ -C: 73: Exactly one space required around assignment - ocount[obj.__class__]=1 - ^ -C: 91: More than one statement on a single line -C:105: More than one statement on a single line -C:108: More than one statement on a single line diff --git a/test/messages/func_genexpr_var_scope_py24.txt b/test/messages/func_genexpr_var_scope_py24.txt deleted file mode 100644 index f599112..0000000 --- a/test/messages/func_genexpr_var_scope_py24.txt +++ /dev/null @@ -1 +0,0 @@ -E: 6: Undefined variable 'n' diff --git a/test/messages/func_globals.txt b/test/messages/func_globals.txt deleted file mode 100644 index d1bb53d..0000000 --- a/test/messages/func_globals.txt +++ /dev/null @@ -1,6 +0,0 @@ -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 assignment 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 deleted file mode 100644 index 2bc4372..0000000 --- a/test/messages/func_i0010.txt +++ /dev/null @@ -1 +0,0 @@ -I: 1: Unable to consider inline option 'errors-only' diff --git a/test/messages/func_i0011.txt b/test/messages/func_i0011.txt deleted file mode 100644 index 87c1f1f..0000000 --- a/test/messages/func_i0011.txt +++ /dev/null @@ -1,2 +0,0 @@ -I: 1: Locally disabling W0404 -I: 1: Useless suppression of 'reimported' diff --git a/test/messages/func_i0012.txt b/test/messages/func_i0012.txt deleted file mode 100644 index bf0c90f..0000000 --- a/test/messages/func_i0012.txt +++ /dev/null @@ -1,2 +0,0 @@ -I: 1: Locally enabling W0404 - diff --git a/test/messages/func_i0014.txt b/test/messages/func_i0014.txt deleted file mode 100644 index d179cd1..0000000 --- a/test/messages/func_i0014.txt +++ /dev/null @@ -1,2 +0,0 @@ -I: 1: Ignoring entire file -I: 1: Used deprecated directive "pylint:disable-all" or "pylint:disable=all" diff --git a/test/messages/func_i0020.txt b/test/messages/func_i0020.txt deleted file mode 100644 index e25be4e..0000000 --- a/test/messages/func_i0020.txt +++ /dev/null @@ -1,2 +0,0 @@ -I: 7: Locally disabling W0612 -I: 8: Suppressed 'unused-variable' (from line 7) diff --git a/test/messages/func_i0022.txt b/test/messages/func_i0022.txt deleted file mode 100644 index 477335c..0000000 --- a/test/messages/func_i0022.txt +++ /dev/null @@ -1,13 +0,0 @@ -I: 5: Locally disabling C0103 -I: 5: Suppressed 'invalid-name' (from line 5) -I: 6: Deprecated pragma "pylint:disable-msg" or "pylint:enable-msg" -I: 6: Deprecated pragma "pylint:disable-msg" or "pylint:enable-msg" -I: 6: Suppressed 'invalid-name' (from line 6) -I: 9: Suppressed 'invalid-name' (from line 8) -I: 12: Deprecated pragma "pylint:disable-msg" or "pylint:enable-msg" -I: 13: Suppressed 'invalid-name' (from line 12) -I: 14: Deprecated pragma "pylint:disable-msg" or "pylint:enable-msg" -I: 16: Deprecated pragma "pylint:disable-msg" or "pylint:enable-msg" -I: 17: Suppressed 'invalid-name' (from line 16) -I: 18: Deprecated pragma "pylint:disable-msg" or "pylint:enable-msg" -I: 21: Suppressed 'invalid-name' (from line 20) diff --git a/test/messages/func_import_syntax_error.txt b/test/messages/func_import_syntax_error.txt deleted file mode 100644 index 8e2a494..0000000 --- a/test/messages/func_import_syntax_error.txt +++ /dev/null @@ -1,4 +0,0 @@ -C: 1: Missing module docstring -C: 1: Missing required attribute "__revision__" -F: 1: Unable to import 'syntax_error' (expected an indented block (<string>, line 2)) -W: 1: Unused import syntax_error diff --git a/test/messages/func_import_syntax_error_py30.txt b/test/messages/func_import_syntax_error_py30.txt deleted file mode 100644 index 3c5a95a..0000000 --- a/test/messages/func_import_syntax_error_py30.txt +++ /dev/null @@ -1,4 +0,0 @@ -C: 1: Missing module docstring -C: 1: Missing required attribute "__revision__" -E: 1: No name 'syntax_error' in module 'input' -W: 1: Unused import syntax_error diff --git a/test/messages/func_indent.txt b/test/messages/func_indent.txt deleted file mode 100644 index aa3645f..0000000 --- a/test/messages/func_indent.txt +++ /dev/null @@ -1,3 +0,0 @@ -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_interfaces.txt b/test/messages/func_interfaces.txt deleted file mode 100644 index 3b444d7..0000000 --- a/test/messages/func_interfaces.txt +++ /dev/null @@ -1,6 +0,0 @@ -E: 46:MissingMethod: Missing method 'truc' from IMachin interface -E: 77:InterfaceCantBeFound: Undefined variable 'undefined' -E: 88:InterfaceCanNowBeFound: Missing method 'troc' from IMachin interface -E: 88:InterfaceCanNowBeFound: Missing method 'truc' from IMachin interface -F: 77:InterfaceCantBeFound: failed to resolve interfaces implemented by InterfaceCantBeFound (undefined) -W: 71:BadArgument.troc: Arguments number differs from IMachin interface method diff --git a/test/messages/func_invalid_encoded_data.txt b/test/messages/func_invalid_encoded_data.txt deleted file mode 100644 index 4a5c17e..0000000 --- a/test/messages/func_invalid_encoded_data.txt +++ /dev/null @@ -1 +0,0 @@ -W: 6: Cannot decode using encoding "utf-8", unexpected byte at position 11 diff --git a/test/messages/func_keyword_repeat_py25.txt b/test/messages/func_keyword_repeat_py25.txt deleted file mode 100644 index b285b97..0000000 --- a/test/messages/func_keyword_repeat_py25.txt +++ /dev/null @@ -1,2 +0,0 @@ -E: 8: Duplicate keyword argument 'two' in function call -E: 9: Duplicate keyword argument 'one' in function call diff --git a/test/messages/func_method_missing_self.txt b/test/messages/func_method_missing_self.txt deleted file mode 100644 index 861871f..0000000 --- a/test/messages/func_method_missing_self.txt +++ /dev/null @@ -1 +0,0 @@ -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 deleted file mode 100644 index da5ee02..0000000 --- a/test/messages/func_method_without_self_but_self_assignment.txt +++ /dev/null @@ -1,2 +0,0 @@ -E: 13:Example.setup: Method has no argument -E: 15:Example.setup: Undefined variable 'self' diff --git a/test/messages/func_missing_super_argument_py_30.txt b/test/messages/func_missing_super_argument_py_30.txt deleted file mode 100644 index 5c97f0d..0000000 --- a/test/messages/func_missing_super_argument_py_30.txt +++ /dev/null @@ -1,2 +0,0 @@ -E: 8:MyClass.__init__: Missing argument to super() -R: 5:MyClass: Too few public methods (0/2) diff --git a/test/messages/func_name_checking.txt b/test/messages/func_name_checking.txt deleted file mode 100644 index e8d0a73..0000000 --- a/test/messages/func_name_checking.txt +++ /dev/null @@ -1,18 +0,0 @@ -C: 10:Run.B: Invalid class name "B" -C: 14:Run: Invalid variable name "bBb" -C: 28:HOHOHOHO: Invalid function name "HOHOHOHO" -C: 30:HOHOHOHO: Invalid variable name "HIHIHI" -C: 33:xyz: Invalid class name "xyz" -C: 36:xyz: Invalid class attribute name "zz" -C: 41:xyz.Youplapoum: Invalid method name "Youplapoum" -C: 56: Invalid constant name "benpasceluila" -C: 62:Correct.__init__: Invalid attribute name "_Ca_va_Pas" -C: 64:Correct.BadMethodName: Invalid method name "BadMethodName" -C: 77: Invalid class name "BAD_NAME_FOR_CLASS" -C: 78: Invalid class name "NEXT_BAD_NAME_FOR_CLASS" -C: 84: Invalid class name "NOT_CORRECT" -C: 90:test_globals: Invalid constant name "AlsoCorrect" -C:118:FooClass.PROPERTY_NAME: Invalid attribute name "PROPERTY_NAME" -C:123:FooClass.ABSTRACT_PROPERTY_NAME: Invalid attribute name "ABSTRACT_PROPERTY_NAME" -C:128:FooClass.PROPERTY_NAME_SETTER: Invalid attribute name "PROPERTY_NAME_SETTER" -C:133:func_bad_argname: Invalid argument name "NOT_GOOD" diff --git a/test/messages/func_namedtuple.txt b/test/messages/func_namedtuple.txt deleted file mode 100644 index d1ef2b1..0000000 --- a/test/messages/func_namedtuple.txt +++ /dev/null @@ -1 +0,0 @@ -E: 10: Class 'Thing' has no 'x' member diff --git a/test/messages/func_names_imported_from_module.txt b/test/messages/func_names_imported_from_module.txt deleted file mode 100644 index 23362ba..0000000 --- a/test/messages/func_names_imported_from_module.txt +++ /dev/null @@ -1,11 +0,0 @@ -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' -F: 6: Unable to import 'logilab.common.tutu' -F: 23: Unable to import 'rie' -W: 27: Statement seems to have no effect diff --git a/test/messages/func_newstyle___slots__.txt b/test/messages/func_newstyle___slots__.txt deleted file mode 100644 index cb69e14..0000000 --- a/test/messages/func_newstyle___slots__.txt +++ /dev/null @@ -1,2 +0,0 @@ -C: 10:HaNonNonNon: Old-style class defined. -E: 10:HaNonNonNon: Use of __slots__ on an old style class diff --git a/test/messages/func_newstyle___slots___py30.txt b/test/messages/func_newstyle___slots___py30.txt deleted file mode 100644 index 0db581b..0000000 --- a/test/messages/func_newstyle___slots___py30.txt +++ /dev/null @@ -1 +0,0 @@ -C: 10:HaNonNonNon: Old-style class defined. diff --git a/test/messages/func_newstyle_exceptions.txt b/test/messages/func_newstyle_exceptions.txt deleted file mode 100644 index 305326c..0000000 --- a/test/messages/func_newstyle_exceptions.txt +++ /dev/null @@ -1,7 +0,0 @@ -E: 25:fonctionNew: Raising a new style class which doesn't inherit from BaseException -E: 33:fonctionNew2: Raising a new style class which doesn't inherit from BaseException -E: 37:fonctionNotImplemented: NotImplemented raised - should raise NotImplementedError -W: 21:fonctionBof: Exception doesn't inherit from standard "Exception" class -W: 29:fonctionBof2: Exception doesn't inherit from standard "Exception" class -W: 29:fonctionBof2: Use raise ErrorClass(args) instead of raise ErrorClass, args. -W: 37:fonctionNotImplemented: Use raise ErrorClass(args) instead of raise ErrorClass, args. diff --git a/test/messages/func_newstyle_exceptions_py30.txt b/test/messages/func_newstyle_exceptions_py30.txt deleted file mode 100644 index ed4ba4d..0000000 --- a/test/messages/func_newstyle_exceptions_py30.txt +++ /dev/null @@ -1,5 +0,0 @@ -E: 21:fonctionBof: Raising a new style class which doesn't inherit from BaseException -E: 25:fonctionNew: Raising a new style class which doesn't inherit from BaseException -E: 29:fonctionBof2: Raising a new style class which doesn't inherit from BaseException -E: 33:fonctionNew2: Raising a new style class which doesn't inherit from BaseException -E: 37:fonctionNotImplemented: NotImplemented raised - should raise NotImplementedError diff --git a/test/messages/func_newstyle_property.txt b/test/messages/func_newstyle_property.txt deleted file mode 100644 index ba6018f..0000000 --- a/test/messages/func_newstyle_property.txt +++ /dev/null @@ -1,2 +0,0 @@ -C: 14:HaNonNonNon: Old-style class defined. -W: 16:HaNonNonNon: Use of "property" on an old style class diff --git a/test/messages/func_newstyle_property_py30.txt b/test/messages/func_newstyle_property_py30.txt deleted file mode 100644 index b1d46a5..0000000 --- a/test/messages/func_newstyle_property_py30.txt +++ /dev/null @@ -1 +0,0 @@ -C: 14:HaNonNonNon: Old-style class defined. diff --git a/test/messages/func_newstyle_super.txt b/test/messages/func_newstyle_super.txt deleted file mode 100644 index b51869b..0000000 --- a/test/messages/func_newstyle_super.txt +++ /dev/null @@ -1,7 +0,0 @@ -C: 7:Aaaa: Old-style class defined. -E: 9:Aaaa.hop: Use of super on an old style class -E: 13:Aaaa.__init__: Use of super on an old style class -E: 23:NewAaaa.__init__: Bad first argument 'object' given to super() -E: 28:Py3kAaaa.__init__: Missing argument to super() -E: 33:Py3kWrongSuper.__init__: Bad first argument 'NewAaaa' given to super() -E: 38:WrongNameRegression.__init__: Bad first argument 'Missing' given to super()
\ No newline at end of file diff --git a/test/messages/func_newstyle_super_py30.txt b/test/messages/func_newstyle_super_py30.txt deleted file mode 100644 index 246d265..0000000 --- a/test/messages/func_newstyle_super_py30.txt +++ /dev/null @@ -1,4 +0,0 @@ -C: 7:Aaaa: Old-style class defined. -E: 23:NewAaaa.__init__: Bad first argument 'object' given to super() -E: 33:Py3kWrongSuper.__init__: Bad first argument 'NewAaaa' given to super() -E: 38:WrongNameRegression.__init__: Bad first argument 'Missing' given to super() diff --git a/test/messages/func_no_final_new_line.txt b/test/messages/func_no_final_new_line.txt deleted file mode 100644 index 5040aa7..0000000 --- a/test/messages/func_no_final_new_line.txt +++ /dev/null @@ -1 +0,0 @@ -C: 2: Final newline missing diff --git a/test/messages/func_r0903.txt b/test/messages/func_r0903.txt deleted file mode 100644 index bd4cc2a..0000000 --- a/test/messages/func_r0903.txt +++ /dev/null @@ -1 +0,0 @@ -R: 4:Aaaa: Too few public methods (1/2) diff --git a/test/messages/func_scope_regrtest.txt b/test/messages/func_scope_regrtest.txt deleted file mode 100644 index 27ae207..0000000 --- a/test/messages/func_scope_regrtest.txt +++ /dev/null @@ -1,2 +0,0 @@ -E: 12:Well.Sub: Undefined variable 'Data' -E: 15:Well.func: Undefined variable 'Sub' diff --git a/test/messages/func_set_literal_as_default_py27.txt b/test/messages/func_set_literal_as_default_py27.txt deleted file mode 100644 index 6a7a893..0000000 --- a/test/messages/func_set_literal_as_default_py27.txt +++ /dev/null @@ -1 +0,0 @@ -W: 5:function1: Dangerous default value {1} as argument diff --git a/test/messages/func_superfluous_parens.txt b/test/messages/func_superfluous_parens.txt deleted file mode 100644 index 77ff186..0000000 --- a/test/messages/func_superfluous_parens.txt +++ /dev/null @@ -1,5 +0,0 @@ -C: 5: Unnecessary parens after 'if' keyword -C: 7: Unnecessary parens after 'not' keyword -C: 11: Unnecessary parens after 'for' keyword -C: 13: Unnecessary parens after 'if' keyword -C: 18: Unnecessary parens after 'del' keyword diff --git a/test/messages/func_toolonglines.txt b/test/messages/func_toolonglines.txt deleted file mode 100644 index a984023..0000000 --- a/test/messages/func_toolonglines.txt +++ /dev/null @@ -1,6 +0,0 @@ -C: 1: Line too long (90/80) -C: 2: Line too long (94/80) -C: 11: Line too long (101/80) -C: 17: Line too long (83/80) -C: 25: Line too long (94/80) -W: 11: Cannot decode using encoding "ascii", unexpected byte at position 37 diff --git a/test/messages/func_toolonglines_py30.txt b/test/messages/func_toolonglines_py30.txt deleted file mode 100644 index 7678258..0000000 --- a/test/messages/func_toolonglines_py30.txt +++ /dev/null @@ -1,4 +0,0 @@ -C: 1: Line too long (90/80) -C: 2: Line too long (94/80) -C: 17: Line too long (83/80) -C: 25: Line too long (94/80) diff --git a/test/messages/func_typecheck_callfunc_assigment.txt b/test/messages/func_typecheck_callfunc_assigment.txt deleted file mode 100644 index 96ad43e..0000000 --- a/test/messages/func_typecheck_callfunc_assigment.txt +++ /dev/null @@ -1,2 +0,0 @@ -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 deleted file mode 100644 index bd91228..0000000 --- a/test/messages/func_typecheck_getattr.txt +++ /dev/null @@ -1,10 +0,0 @@ -C: 19:Client: Old-style class defined. -E: 25:Client.__init__: Class 'Provider' has no 'cattribute' member -E: 35:Client.use_method: Instance of 'Provider' has no 'hophophop' member -E: 40:Client.use_attr: Instance of 'Provider' has no 'attribute' member -E: 52:Client.test_bt_types: Instance of 'list' has no 'apppend' member -E: 54:Client.test_bt_types: Instance of 'dict' has no 'set' member -E: 56:Client.test_bt_types: Instance of 'tuple' has no 'append' member -E: 58:Client.test_bt_types: Instance of 'str' has no 'loower' member -E: 62:Client.test_bt_types: Instance of 'int' has no 'whatever' member -E: 66: Instance of 'int' has no 'lower' member (but some types could not be inferred) diff --git a/test/messages/func_typecheck_non_callable_call.txt b/test/messages/func_typecheck_non_callable_call.txt deleted file mode 100644 index 0218074..0000000 --- a/test/messages/func_typecheck_non_callable_call.txt +++ /dev/null @@ -1,6 +0,0 @@ -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_unbalanced_tuple_unpacking.txt b/test/messages/func_unbalanced_tuple_unpacking.txt deleted file mode 100644 index c9311cc..0000000 --- a/test/messages/func_unbalanced_tuple_unpacking.txt +++ /dev/null @@ -1,6 +0,0 @@ -W: 9:do_stuff: Possible unbalanced tuple unpacking with sequence (1, 2, 3): left side has 2 label(s), right side has 3 value(s) -W: 14:do_stuff1: Possible unbalanced tuple unpacking with sequence [1, 2, 3]: left side has 2 label(s), right side has 3 value(s) -W: 19:do_stuff2: Possible unbalanced tuple unpacking with sequence (1, 2, 3): left side has 2 label(s), right side has 3 value(s) -W: 50:do_stuff7: Possible unbalanced tuple unpacking with sequence defined at line 46: left side has 2 label(s), right side has 3 value(s) -W: 69:do_stuff9: Possible unbalanced tuple unpacking with sequence defined at line 7 of input.unpacking: left side has 2 label(s), right side has 3 value(s) - diff --git a/test/messages/func_undefined_var.txt b/test/messages/func_undefined_var.txt deleted file mode 100644 index 25fb2c3..0000000 --- a/test/messages/func_undefined_var.txt +++ /dev/null @@ -1,10 +0,0 @@ -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' -E: 27:bad_default: Undefined variable 'augvar' -E: 28:bad_default: Undefined variable 'vardel' -E: 56: Using variable 'PLOUF' before assignment -E: 65:if_branch_test: Using variable 'xxx' before assignment -W: 27:bad_default: Unused variable 'augvar' diff --git a/test/messages/func_unknown_encoding_py29.txt b/test/messages/func_unknown_encoding_py29.txt deleted file mode 100644 index ab840d9..0000000 --- a/test/messages/func_unknown_encoding_py29.txt +++ /dev/null @@ -1 +0,0 @@ -E: 1: unknown encoding: IBO-8859-1 diff --git a/test/messages/func_unpack_exception_py_30.txt b/test/messages/func_unpack_exception_py_30.txt deleted file mode 100644 index aaff92f..0000000 --- a/test/messages/func_unpack_exception_py_30.txt +++ /dev/null @@ -1,5 +0,0 @@ -W: 9:new_style: Implicit unpacking of exceptions is not supported in Python 3 -W: 11:new_style: Implicit unpacking of exceptions is not supported in Python 3 -W: 11:new_style: Redefining name 'new_style' from outer scope (line 5) in exception handler -W: 11:new_style: Redefining name 'tuple' from builtins in exception handler - diff --git a/test/messages/func_unpacking_non_sequence.txt b/test/messages/func_unpacking_non_sequence.txt deleted file mode 100644 index 4099e29..0000000 --- a/test/messages/func_unpacking_non_sequence.txt +++ /dev/null @@ -1,9 +0,0 @@ -W: 61: Attempting to unpack a non-sequence defined at line 52 -W: 62: Attempting to unpack a non-sequence -W: 63: Attempting to unpack a non-sequence None -W: 64: Attempting to unpack a non-sequence 1 -W: 65: Attempting to unpack a non-sequence defined at line 9 of input.unpacking -W: 66: Attempting to unpack a non-sequence defined at line 11 of input.unpacking -W: 67: Attempting to unpack a non-sequence defined at line 58 -W: 68: Attempting to unpack a non-sequence - diff --git a/test/messages/func_used_before_assignment_py30.txt b/test/messages/func_used_before_assignment_py30.txt deleted file mode 100644 index 5b6080f..0000000 --- a/test/messages/func_used_before_assignment_py30.txt +++ /dev/null @@ -1,2 +0,0 @@ -E: 18:test_fail.wrap: Using variable 'cnt' before assignment
-E: 27:test_fail2.wrap: Using variable 'cnt' before assignment
\ No newline at end of file diff --git a/test/messages/func_useless_else_on_loop.txt b/test/messages/func_useless_else_on_loop.txt deleted file mode 100644 index 6c2c60c..0000000 --- a/test/messages/func_useless_else_on_loop.txt +++ /dev/null @@ -1,5 +0,0 @@ -W: 10:test_return_for: Else clause on loop without a break statement -W: 17:test_return_while: Else clause on loop without a break statement -W: 26: Else clause on loop without a break statement -W: 33: Else clause on loop without a break statement -W: 38: Else clause on loop without a break statement diff --git a/test/messages/func_utf8_lines.txt b/test/messages/func_utf8_lines.txt deleted file mode 100644 index 9706781..0000000 --- a/test/messages/func_utf8_lines.txt +++ /dev/null @@ -1 +0,0 @@ -C: 8: Line too long (89/80) diff --git a/test/messages/func_variables_unused_name_from_wilcard_import.txt b/test/messages/func_variables_unused_name_from_wilcard_import.txt deleted file mode 100644 index 4768afa..0000000 --- a/test/messages/func_variables_unused_name_from_wilcard_import.txt +++ /dev/null @@ -1,5 +0,0 @@ -W: 2: Unused import NonRegr from wildcard import -W: 2: Unused import os from wildcard import -W: 2: Unused import sys from wildcard import -W: 2: Wildcard import input.func_w0611 - diff --git a/test/messages/func_w0108.txt b/test/messages/func_w0108.txt deleted file mode 100644 index 70e0925..0000000 --- a/test/messages/func_w0108.txt +++ /dev/null @@ -1,8 +0,0 @@ -W: 8:<lambda>: Lambda may not be necessary -W: 9:<lambda>: Lambda may not be necessary -W: 10:<lambda>: Lambda may not be necessary -W: 16:<lambda>: Lambda may not be necessary -W: 17:<lambda>: Lambda may not be necessary -W: 18:<lambda>: Lambda may not be necessary -W: 19:<lambda>: Lambda may not be necessary - diff --git a/test/messages/func_w0152_py29.txt b/test/messages/func_w0152_py29.txt deleted file mode 100644 index ddf2d0b..0000000 --- a/test/messages/func_w0152_py29.txt +++ /dev/null @@ -1,3 +0,0 @@ -W: 5: Used * or ** magic -W: 14: Used * or ** magic - diff --git a/test/messages/func_w0205.txt b/test/messages/func_w0205.txt deleted file mode 100644 index 4b36dab..0000000 --- a/test/messages/func_w0205.txt +++ /dev/null @@ -1,2 +0,0 @@ -W: 22:Cdef.abcd: Signature differs from overridden method - diff --git a/test/messages/func_w0223.txt b/test/messages/func_w0223.txt deleted file mode 100644 index 943cf03..0000000 --- a/test/messages/func_w0223.txt +++ /dev/null @@ -1,4 +0,0 @@ -R: 22:AbstractB: Abstract class not referenced -W: 31:Concret: Method 'bbbb' is abstract in class 'Abstract' but is not overridden - - diff --git a/test/messages/func_w0231.txt b/test/messages/func_w0231.txt deleted file mode 100644 index 8cbf0b7..0000000 --- a/test/messages/func_w0231.txt +++ /dev/null @@ -1,6 +0,0 @@ -C: 7:AAAA: Old-style class defined. -C: 13:BBBB: Old-style class defined. -C: 19:CCCC: Old-style class defined. -W: 19:CCCC: Class has no __init__ method -W: 26:ZZZZ.__init__: __init__ method from base class 'BBBB' is not called -W: 59:AssignedInit.__init__: __init__ method from base class 'NewStyleC' is not called diff --git a/test/messages/func_w0233.txt b/test/messages/func_w0233.txt deleted file mode 100644 index 614ceac..0000000 --- a/test/messages/func_w0233.txt +++ /dev/null @@ -1,4 +0,0 @@ -E: 22:CCC: Module 'input.func_w0233' has no 'BBBB' member -E: 27:CCC.__init__: Module 'input.func_w0233' has no 'BBBB' member -F: 20: Unable to import 'nonexistant' -W: 12:AAAA.__init__: __init__ method from a non direct base class 'BBBBMixin' is called diff --git a/test/messages/func_w0302.txt b/test/messages/func_w0302.txt deleted file mode 100644 index 58f65b3..0000000 --- a/test/messages/func_w0302.txt +++ /dev/null @@ -1,2 +0,0 @@ -C: 1: Too many lines in module (1016) - diff --git a/test/messages/func_w0331_py_30.txt b/test/messages/func_w0331_py_30.txt deleted file mode 100644 index 8134a32..0000000 --- a/test/messages/func_w0331_py_30.txt +++ /dev/null @@ -1 +0,0 @@ -W: 6: Use of the <> operator diff --git a/test/messages/func_w0403_py30.txt b/test/messages/func_w0403_py30.txt deleted file mode 100644 index fc5da23..0000000 --- a/test/messages/func_w0403_py30.txt +++ /dev/null @@ -1,2 +0,0 @@ -F: 8: Unable to import 'Bastion' -F: 11: Unable to import 'stringfile' diff --git a/test/messages/func_w0404.txt b/test/messages/func_w0404.txt deleted file mode 100644 index 73d770f..0000000 --- a/test/messages/func_w0404.txt +++ /dev/null @@ -1,5 +0,0 @@ -W: 8: Reimport 'ElementTree' (imported line 7) -W: 11: Reimport 'multiprocessing.pool' (imported line 10) -W: 13: Reimport 'sys' (imported line 5) -W: 23:reimport: Redefining name 'sys' from outer scope (line 5) -W: 23:reimport: Reimport 'sys' (imported line 5) diff --git a/test/messages/func_w0406.txt b/test/messages/func_w0406.txt deleted file mode 100644 index 6145515..0000000 --- a/test/messages/func_w0406.txt +++ /dev/null @@ -1 +0,0 @@ -W: 5: Module import itself diff --git a/test/messages/func_w0611.txt b/test/messages/func_w0611.txt deleted file mode 100644 index 10952da..0000000 --- a/test/messages/func_w0611.txt +++ /dev/null @@ -1 +0,0 @@ -W: 4: Unused import os diff --git a/test/messages/func_w0612.txt b/test/messages/func_w0612.txt deleted file mode 100644 index f1647a3..0000000 --- a/test/messages/func_w0612.txt +++ /dev/null @@ -1,2 +0,0 @@ -W: 8:function: Unused variable 'aaaa' -W: 9:function: Unused variable 'index' diff --git a/test/messages/func_w0701_py_30.txt b/test/messages/func_w0701_py_30.txt deleted file mode 100644 index d643517..0000000 --- a/test/messages/func_w0701_py_30.txt +++ /dev/null @@ -1,3 +0,0 @@ -W: 8:function1: Raising a string exception -W: 12:function2: Raising a string exception -W: 12:function2: Use raise ErrorClass(args) instead of raise ErrorClass, args. diff --git a/test/messages/func_w0702.txt b/test/messages/func_w0702.txt deleted file mode 100644 index 7e0ea84..0000000 --- a/test/messages/func_w0702.txt +++ /dev/null @@ -1 +0,0 @@ -W: 9: No exception type(s) specified diff --git a/test/messages/func_w0705.txt b/test/messages/func_w0705.txt deleted file mode 100644 index fbc200f..0000000 --- a/test/messages/func_w0705.txt +++ /dev/null @@ -1,5 +0,0 @@ -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 appear last) diff --git a/test/messages/func_w0711.txt b/test/messages/func_w0711.txt deleted file mode 100644 index a158818..0000000 --- a/test/messages/func_w0711.txt +++ /dev/null @@ -1,4 +0,0 @@ -W: 8: Exception to catch is the result of a binary "or" operation -W: 10: Exception to catch is the result of a binary "and" operation -W: 12: Exception to catch is the result of a binary "or" operation -W: 14: Exception to catch is the result of a binary "or" operation diff --git a/test/messages/func_with_used_before_assignment.txt b/test/messages/func_with_used_before_assignment.txt deleted file mode 100644 index dc6e386..0000000 --- a/test/messages/func_with_used_before_assignment.txt +++ /dev/null @@ -1,2 +0,0 @@ -E: 10:do_nothing: Undefined variable 'ctx'
-E: 11:do_nothing: Using variable 'context' before assignment
\ No newline at end of file diff --git a/test/messages/func_yield_outside_func.txt b/test/messages/func_yield_outside_func.txt deleted file mode 100644 index 3c74124..0000000 --- a/test/messages/func_yield_outside_func.txt +++ /dev/null @@ -1 +0,0 @@ -E: 3: Yield outside function diff --git a/test/regrtest_data/absimp/string.py b/test/regrtest_data/absimp/string.py deleted file mode 100644 index a4d01cb..0000000 --- a/test/regrtest_data/absimp/string.py +++ /dev/null @@ -1,7 +0,0 @@ -""" -http://www.logilab.org/ticket/70495 -http://www.logilab.org/ticket/70565 -""" -from __future__ import absolute_import -import string -print string diff --git a/test/regrtest_data/decimal_inference.py b/test/regrtest_data/decimal_inference.py deleted file mode 100644 index b47bc1c..0000000 --- a/test/regrtest_data/decimal_inference.py +++ /dev/null @@ -1,10 +0,0 @@ -"""hum E1011 on .prec member is justifiable since Context instance are built -using setattr/locals :( - -2007/02/17 update: .prec attribute is now detected by astroid :o) -""" -import decimal - -decimal.getcontext().prec = 200 -print decimal.getcontext().prec - diff --git a/test/regrtest_data/precedence_test.py b/test/regrtest_data/precedence_test.py deleted file mode 100644 index 087b5cf..0000000 --- a/test/regrtest_data/precedence_test.py +++ /dev/null @@ -1,21 +0,0 @@ -""" - # 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 deleted file mode 100644 index 974035e..0000000 --- a/test/regrtest_data/pygtk_import.py +++ /dev/null @@ -1,14 +0,0 @@ -#pylint: disable=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 deleted file mode 100644 index 37310cf..0000000 --- a/test/regrtest_data/socketerror_import.py +++ /dev/null @@ -1,6 +0,0 @@ -"""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/smoketest.py b/test/smoketest.py deleted file mode 100644 index 26e2c9a..0000000 --- a/test/smoketest.py +++ /dev/null @@ -1,131 +0,0 @@ -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free Software -# Foundation; either version 2 of the License, or (at your option) any later -# version. - -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -import sys -from os.path import join, dirname, abspath -from cStringIO import StringIO -import tempfile - -from logilab.common.testlib import TestCase, unittest_main, tag - -from pylint.lint import Run -from pylint.reporters.text import * -from pylint.reporters.html import HTMLReporter - -HERE = abspath(dirname(__file__)) - -class RunTC(TestCase): - - def _runtest(self, args, reporter=None, out=None, code=28): - if out is None: - out = StringIO() - if args and args[-1].startswith('pylint.lint'): - try: - import cProfile, pstats - except ImportError: - code += 1 - try: - sys.stderr = sys.stdout = out - try: - Run(args, reporter=reporter) - except SystemExit, ex: - if reporter: - output = reporter.out.getvalue() - elif hasattr(out, 'getvalue'): - output = out.getvalue() - else: - output = None - msg = 'expected output status %s, got %s' % (code, ex.code) - if output is not None: - msg = '%s. Below pylint output: \n%s' % (msg, output) - self.assertEqual(ex.code, code, msg) - else: - self.fail('expected system exit') - finally: - sys.stderr = sys.__stderr__ - sys.stdout = sys.__stdout__ - - @tag('smoke') - def test0(self): - """make pylint checking itself""" - self._runtest(['pylint.__pkginfo__'], reporter=TextReporter(StringIO()), - code=0) - - @tag('smoke') - def test1(self): - """make pylint checking itself""" - self._runtest(['pylint.lint'], reporter=TextReporter(StringIO())) - - @tag('smoke') - def test2(self): - """make pylint checking itself""" - self._runtest(['pylint.lint'], reporter=HTMLReporter(StringIO())) - - @tag('smoke') - def test3(self): - """make pylint checking itself""" - self._runtest(['pylint.lint'], reporter=ColorizedTextReporter(StringIO())) - - @tag('smoke') - def test_no_ext_file(self): - self._runtest([join(HERE, 'input', 'noext')], code=0) - - @tag('smoke') - def test_generated_members(self): - # XXX dual end quotation since optparse buggily remove one... - self._runtest(['--generated-members=objects,DoesNotExist,delay,retry,"[a-zA-Z]+_set{1,2}""', 'pylint.lint']) - - @tag('smoke') - def test_w0704_ignored(self): - self._runtest([join(HERE, 'input', 'ignore_except_pass_by_default.py')], code=0) - - @tag('smoke', 'help', 'config') - def test_generate_config_option(self): - """make pylint checking itself""" - self._runtest(['--generate-rcfile'], reporter=HTMLReporter(StringIO()), - code=0) - - @tag('smoke', 'help') - def test_help_message_option(self): - """make pylint checking itself""" - self._runtest(['--help-msg', 'W0101'], reporter=HTMLReporter(StringIO()), - code=0) - - @tag('smoke', 'help') - def test_error_help_message_option(self): - self._runtest(['--help-msg', 'WX101'], reporter=HTMLReporter(StringIO()), - code=0) - - @tag('smoke', 'usage') - def test_error_missing_arguments(self): - self._runtest([], reporter=HTMLReporter(StringIO()), - code=32) - - @tag('smoke', 'encoding') - def test_no_out_encoding(self): - """test redirection of stdout with non ascii caracters - """ - #This test reproduces bug #48066 ; it happens when stdout is redirected - # through '>' : the sys.stdout.encoding becomes then None, and if the - # output contains non ascii, pylint will crash - if sys.version_info < (3, 0): - strio = tempfile.TemporaryFile() - else: - strio = StringIO() - assert strio.encoding is None - self._runtest([join(HERE, 'regrtest_data/no_stdout_encoding.py')], - out=strio) - - -if __name__ == '__main__': - unittest_main() diff --git a/test/test_base.py b/test/test_base.py deleted file mode 100644 index 972c783..0000000 --- a/test/test_base.py +++ /dev/null @@ -1,223 +0,0 @@ -"""Unittest for the base checker.""" - -import re - -from astroid import test_utils -from pylint.checkers import base -from pylint.testutils import CheckerTestCase, Message, set_config - - -class DocstringTest(CheckerTestCase): - CHECKER_CLASS = base.DocStringChecker - - def test_missing_docstring_module(self): - module = test_utils.build_module("") - with self.assertAddsMessages(Message('missing-docstring', node=module, args=('module',))): - self.checker.visit_module(module) - - def test_empty_docstring_module(self): - module = test_utils.build_module("''''''") - with self.assertAddsMessages(Message('empty-docstring', node=module, args=('module',))): - self.checker.visit_module(module) - - def test_empty_docstring_function(self): - func = test_utils.extract_node(""" - def func(tion): - pass""") - with self.assertAddsMessages(Message('missing-docstring', node=func, args=('function',))): - self.checker.visit_function(func) - - @set_config(docstring_min_length=2) - def test_short_function_no_docstring(self): - func = test_utils.extract_node(""" - def func(tion): - pass""") - with self.assertNoMessages(): - self.checker.visit_function(func) - - @set_config(docstring_min_length=2) - def test_function_no_docstring_by_name(self): - func = test_utils.extract_node(""" - def __fun__(tion): - pass""") - with self.assertNoMessages(): - self.checker.visit_function(func) - - def test_class_no_docstring(self): - klass = test_utils.extract_node(""" - class Klass(object): - pass""") - with self.assertAddsMessages(Message('missing-docstring', node=klass, args=('class',))): - self.checker.visit_class(klass) - - -class NameCheckerTest(CheckerTestCase): - CHECKER_CLASS = base.NameChecker - CONFIG = { - 'bad_names': set(), - } - - @set_config(include_naming_hint=True) - def test_naming_hint(self): - const = test_utils.extract_node(""" - const = "CONSTANT" #@ - """) - with self.assertAddsMessages( - Message('invalid-name', node=const.targets[0], - args=('constant', 'const', ' (hint: (([A-Z_][A-Z0-9_]*)|(__.*__))$)'))): - self.checker.visit_assname(const.targets[0]) - - @set_config(include_naming_hint=True, - const_name_hint='CONSTANT') - def test_naming_hint_configured_hint(self): - const = test_utils.extract_node(""" - const = "CONSTANT" #@ - """) - with self.assertAddsMessages( - Message('invalid-name', node=const.targets[0], - args=('constant', 'const', ' (hint: CONSTANT)'))): - self.checker.visit_assname(const.targets[0]) - - @set_config(attr_rgx=re.compile('[A-Z]+')) - def test_property_names(self): - # If a method is annotated with @property, it's name should - # match the attr regex. Since by default the attribute regex is the same - # as the method regex, we override it here. - methods = test_utils.extract_node(""" - import abc - - class FooClass(object): - @property - def FOO(self): #@ - pass - - @property - def bar(self): #@ - pass - - @abc.abstractproperty - def BAZ(self): #@ - pass - """) - with self.assertNoMessages(): - self.checker.visit_function(methods[0]) - self.checker.visit_function(methods[2]) - with self.assertAddsMessages(Message('invalid-name', node=methods[1], - args=('attribute', 'bar', ''))): - self.checker.visit_function(methods[1]) - - @set_config(attr_rgx=re.compile('[A-Z]+')) - def test_property_setters(self): - method = test_utils.extract_node(""" - class FooClass(object): - @property - def foo(self): pass - - @foo.setter - def FOOSETTER(self): #@ - pass - """) - with self.assertNoMessages(): - self.checker.visit_function(method) - - def test_module_level_names(self): - assign = test_utils.extract_node(""" - import collections - Class = collections.namedtuple("a", ("b", "c")) #@ - """) - with self.assertNoMessages(): - self.checker.visit_assname(assign.targets[0]) - - assign = test_utils.extract_node(""" - class ClassA(object): - pass - ClassB = ClassA - """) - with self.assertNoMessages(): - self.checker.visit_assname(assign.targets[0]) - - module = test_utils.build_module(""" - def A(): - return 1, 2, 3 - CONSTA, CONSTB, CONSTC = A() - CONSTD = A()""") - with self.assertNoMessages(): - self.checker.visit_assname(module.body[1].targets[0].elts[0]) - self.checker.visit_assname(module.body[2].targets[0]) - - assign = test_utils.extract_node(""" - CONST = "12 34 ".rstrip().split()""") - with self.assertNoMessages(): - self.checker.visit_assname(assign.targets[0]) - - -class MultiNamingStyleTest(CheckerTestCase): - CHECKER_CLASS = base.NameChecker - - MULTI_STYLE_RE = re.compile('(?:(?P<UP>[A-Z]+)|(?P<down>[a-z]+))$') - - @set_config(class_rgx=MULTI_STYLE_RE) - def test_multi_name_detection_first(self): - classes = test_utils.extract_node(""" - class CLASSA(object): #@ - pass - class classb(object): #@ - pass - class CLASSC(object): #@ - pass - """) - with self.assertAddsMessages(Message('invalid-name', node=classes[1], args=('class', 'classb', ''))): - for cls in classes: - self.checker.visit_class(cls) - - @set_config(class_rgx=MULTI_STYLE_RE) - def test_multi_name_detection_first_invalid(self): - classes = test_utils.extract_node(""" - class class_a(object): #@ - pass - class classb(object): #@ - pass - class CLASSC(object): #@ - pass - """) - with self.assertAddsMessages(Message('invalid-name', node=classes[0], args=('class', 'class_a', '')), - Message('invalid-name', node=classes[2], args=('class', 'CLASSC', ''))): - for cls in classes: - self.checker.visit_class(cls) - - @set_config(method_rgx=MULTI_STYLE_RE, - function_rgx=MULTI_STYLE_RE, - name_group=('function:method',)) - def test_multi_name_detection_group(self): - function_defs = test_utils.extract_node(""" - class First(object): - def func(self): #@ - pass - - def FUNC(): #@ - pass - """, module_name='test') - with self.assertAddsMessages(Message('invalid-name', node=function_defs[1], args=('function', 'FUNC', ''))): - for func in function_defs: - self.checker.visit_function(func) - - @set_config(function_rgx=re.compile('(?:(?P<ignore>FOO)|(?P<UP>[A-Z]+)|(?P<down>[a-z]+))$')) - def test_multi_name_detection_exempt(self): - function_defs = test_utils.extract_node(""" - def FOO(): #@ - pass - def lower(): #@ - pass - def FOO(): #@ - pass - def UPPER(): #@ - pass - """) - with self.assertAddsMessages(Message('invalid-name', node=function_defs[3], args=('function', 'UPPER', ''))): - for func in function_defs: - self.checker.visit_function(func) - - -if __name__ == '__main__': - from logilab.common.testlib import unittest_main - unittest_main() diff --git a/test/test_format.py b/test/test_format.py deleted file mode 100644 index 9494f75..0000000 --- a/test/test_format.py +++ /dev/null @@ -1,257 +0,0 @@ -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free Software -# Foundation; either version 2 of the License, or (at your option) any later -# version. -# -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -""" Copyright (c) 2000-2011 LOGILAB S.A. (Paris, FRANCE). - http://www.logilab.fr/ -- mailto:contact@logilab.fr - -Check format checker helper functions -""" - -import sys -import re -from os import linesep -import tokenize -import StringIO - -from logilab.common.testlib import TestCase, unittest_main -from astroid import test_utils - -from pylint.checkers.format import * - -from pylint.testutils import CheckerTestCase, Message, set_config - - -def tokenize_str(code): - return list(tokenize.generate_tokens(StringIO.StringIO(code).readline)) - - -class MultiStatementLineTest(CheckerTestCase): - CHECKER_CLASS = FormatChecker - - def testSingleLineIfStmts(self): - stmt = test_utils.extract_node(""" - if True: pass #@ - """) - with self.assertAddsMessages(Message('C0321', node=stmt.body[0])): - self.checker.process_tokens([]) - self.checker.visit_default(stmt.body[0]) - self.checker.config.single_line_if_stmt = True - with self.assertNoMessages(): - self.checker.process_tokens([]) - self.checker.visit_default(stmt.body[0]) - stmt = test_utils.extract_node(""" - if True: pass #@ - else: - pass - """) - with self.assertAddsMessages(Message('C0321', node=stmt.body[0])): - self.checker.process_tokens([]) - self.checker.visit_default(stmt.body[0]) - - def testTryExceptFinallyNoMultipleStatement(self): - tree = test_utils.extract_node(""" - try: #@ - pass - except: - pass - finally: - pass""") - with self.assertNoMessages(): - self.checker.process_tokens([]) - self.checker.visit_default(tree.body[0]) - - - -class SuperfluousParenthesesTest(CheckerTestCase): - CHECKER_CLASS = FormatChecker - - def testCheckKeywordParensHandlesValidCases(self): - self.checker._keywords_with_parens = set() - cases = [ - 'if foo:', - 'if foo():', - 'if (x and y) or z:', - 'assert foo()', - 'assert ()', - 'if (1, 2) in (3, 4):', - 'if (a or b) in c:', - 'return (x for x in x)', - 'if (x for x in x):', - 'for x in (x for x in x):', - 'not (foo or bar)', - 'not (foo or bar) and baz', - ] - with self.assertNoMessages(): - for code in cases: - self.checker._check_keyword_parentheses(tokenize_str(code), 0) - - def testCheckKeywordParensHandlesUnnecessaryParens(self): - self.checker._keywords_with_parens = set() - cases = [ - (Message('C0325', line=1, args='if'), - 'if (foo):', 0), - (Message('C0325', line=1, args='if'), - 'if ((foo, bar)):', 0), - (Message('C0325', line=1, args='if'), - 'if (foo(bar)):', 0), - (Message('C0325', line=1, args='return'), - 'return ((x for x in x))', 0), - (Message('C0325', line=1, args='not'), - 'not (foo)', 0), - (Message('C0325', line=1, args='not'), - 'if not (foo):', 1), - (Message('C0325', line=1, args='if'), - 'if (not (foo)):', 0), - (Message('C0325', line=1, args='not'), - 'if (not (foo)):', 2), - ] - for msg, code, offset in cases: - with self.assertAddsMessages(msg): - self.checker._check_keyword_parentheses(tokenize_str(code), offset) - - def testFuturePrintStatementWithoutParensWarning(self): - code = """from __future__ import print_function -print('Hello world!') -""" - tree = test_utils.build_module(code) - with self.assertNoMessages(): - self.checker.process_module(tree) - self.checker.process_tokens(tokenize_str(code)) - - -class CheckSpaceTest(CheckerTestCase): - CHECKER_CLASS = FormatChecker - - def testParenthesesGood(self): - good_cases = [ - '(a)\n', - '(a * (b + c))\n', - '( #\na)\n', - ] - with self.assertNoMessages(): - for code in good_cases: - self.checker.process_tokens(tokenize_str(code)) - - def testParenthesesBad(self): - with self.assertAddsMessages( - Message('C0326', line=1, - args=('No', 'allowed', 'after', 'bracket', '( a)\n^'))): - self.checker.process_tokens(tokenize_str('( a)\n')) - - with self.assertAddsMessages( - Message('C0326', line=1, - args=('No', 'allowed', 'before', 'bracket', '(a )\n ^'))): - self.checker.process_tokens(tokenize_str('(a )\n')) - - with self.assertAddsMessages( - Message('C0326', line=1, - args=('No', 'allowed', 'before', 'bracket', 'foo (a)\n ^'))): - self.checker.process_tokens(tokenize_str('foo (a)\n')) - - with self.assertAddsMessages( - Message('C0326', line=1, - args=('No', 'allowed', 'before', 'bracket', '{1: 2} [1]\n ^'))): - self.checker.process_tokens(tokenize_str('{1: 2} [1]\n')) - - def testTrailingCommaGood(self): - with self.assertNoMessages(): - self.checker.process_tokens(tokenize_str('(a, )\n')) - self.checker.process_tokens(tokenize_str('(a,)\n')) - - self.checker.config.no_space_check = [] - with self.assertNoMessages(): - self.checker.process_tokens(tokenize_str('(a,)\n')) - - @set_config(no_space_check=[]) - def testTrailingCommaBad(self): - with self.assertAddsMessages( - Message('C0326', line=1, - args=('No', 'allowed', 'before', 'bracket', '(a, )\n ^'))): - self.checker.process_tokens(tokenize_str('(a, )\n')) - - def testComma(self): - with self.assertAddsMessages( - Message('C0326', line=1, - args=('No', 'allowed', 'before', 'comma', '(a , b)\n ^'))): - self.checker.process_tokens(tokenize_str('(a , b)\n')) - - def testSpacesAllowedInsideSlices(self): - good_cases = [ - '[a:b]\n', - '[a : b]\n', - '[a : ]\n', - '[:a]\n', - '[:]\n', - '[::]\n', - ] - with self.assertNoMessages(): - for code in good_cases: - self.checker.process_tokens(tokenize_str(code)) - - def testKeywordSpacingGood(self): - with self.assertNoMessages(): - self.checker.process_tokens(tokenize_str('foo(foo=bar)\n')) - self.checker.process_tokens(tokenize_str('lambda x=1: x\n')) - - def testKeywordSpacingBad(self): - with self.assertAddsMessages( - Message('C0326', line=1, - args=('No', 'allowed', 'before', 'keyword argument assignment', - '(foo =bar)\n ^'))): - self.checker.process_tokens(tokenize_str('(foo =bar)\n')) - - with self.assertAddsMessages( - Message('C0326', line=1, - args=('No', 'allowed', 'after', 'keyword argument assignment', - '(foo= bar)\n ^'))): - self.checker.process_tokens(tokenize_str('(foo= bar)\n')) - - with self.assertAddsMessages( - Message('C0326', line=1, - args=('No', 'allowed', 'around', 'keyword argument assignment', - '(foo = bar)\n ^'))): - self.checker.process_tokens(tokenize_str('(foo = bar)\n')) - - def testOperatorSpacingGood(self): - good_cases = [ - 'a = b\n' - 'a < b\n' - 'a\n< b\n', - ] - with self.assertNoMessages(): - for code in good_cases: - self.checker.process_tokens(tokenize_str(code)) - - def testOperatorSpacingBad(self): - with self.assertAddsMessages( - Message('C0326', line=1, - args=('Exactly one', 'required', 'before', 'comparison', 'a< b\n ^'))): - self.checker.process_tokens(tokenize_str('a< b\n')) - - with self.assertAddsMessages( - Message('C0326', line=1, - args=('Exactly one', 'required', 'after', 'comparison', 'a <b\n ^'))): - self.checker.process_tokens(tokenize_str('a <b\n')) - - with self.assertAddsMessages( - Message('C0326', line=1, - args=('Exactly one', 'required', 'around', 'comparison', 'a<b\n ^'))): - self.checker.process_tokens(tokenize_str('a<b\n')) - - with self.assertAddsMessages( - Message('C0326', line=1, - args=('Exactly one', 'required', 'around', 'comparison', 'a< b\n ^'))): - self.checker.process_tokens(tokenize_str('a< b\n')) - - -if __name__ == '__main__': - unittest_main() diff --git a/test/test_func.py b/test/test_func.py deleted file mode 100644 index 807878a..0000000 --- a/test/test_func.py +++ /dev/null @@ -1,130 +0,0 @@ -# Copyright (c) 2003-2008 LOGILAB S.A. (Paris, FRANCE). -# http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free Software -# Foundation; either version 2 of the License, or (at your option) any later -# version. -# -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -"""functional/non regression tests for pylint""" - -import unittest -import sys -import re - -from os import getcwd -from os.path import abspath, dirname, join - -from logilab.common import testlib - -from pylint.testutils import (make_tests, LintTestUsingModule, LintTestUsingFile, - LintTestUpdate, cb_test_gen, linter, test_reporter) - -PY3K = sys.version_info >= (3, 0) - -# Configure paths -INPUT_DIR = join(dirname(abspath(__file__)), 'input') -MSG_DIR = join(dirname(abspath(__file__)), 'messages') - -# Classes - -quote = "'" if sys.version_info >= (3, 3) else '' - -class LintTestNonExistentModuleTC(LintTestUsingModule): - module = 'nonexistent' - _get_expected = lambda self: 'F: 1: No module named %snonexistent%s\n' % (quote, quote) - tags = testlib.Tags(('generated','pylint_input_%s' % module)) - -class TestTests(testlib.TestCase): - """check that all testable messages have been checked""" - @testlib.tag('coverage') - def test_exhaustivity(self): - # skip fatal messages - todo = [msg.msgid for msg in linter.messages if msg.msgid[0] != 'F'] - for msgid in test_reporter.message_ids: - try: - todo.remove(msgid) - except ValueError: - continue - todo.sort() - if PY3K: - rest = ['E1001', # slots-on-old-class - 'E1002', # super-on-old-class - # XXX : no use case for now : - 'I0001', - 'W0402', # deprecated module - 'W0403', # implicit relative import - 'W0410', # __future__ import not first statement - 'W0710', # nonstandard-exception - 'W1001' # property-on-old-class - ] - self.assertEqual(todo, rest) - else: - self.assertEqual(todo, ['I0001']) - -class LintBuiltinModuleTest(LintTestUsingModule): - output = join(MSG_DIR, 'builtin_module.txt') - module = 'sys' - def test_functionality(self): - self._test(['sys']) - - -def gen_tests(filter_rgx): - if UPDATE: - callbacks = [cb_test_gen(LintTestUpdate)] - else: - callbacks = [cb_test_gen(LintTestUsingModule)] - if not MODULES_ONLY: - callbacks.append(cb_test_gen(LintTestUsingFile)) - tests = make_tests(INPUT_DIR, MSG_DIR, filter_rgx, callbacks) - - if UPDATE: - return tests - - if filter_rgx: - is_to_run = re.compile(filter_rgx).search - else: - is_to_run = lambda x: 1 - - if is_to_run('nonexistent'): - tests.append(LintTestNonExistentModuleTC) - - tests.append(LintBuiltinModuleTest) - - if not filter_rgx: - # test all features are tested :) - tests.append(TestTests) - - return tests - -# Create suite - -FILTER_RGX = None -MODULES_ONLY = False -UPDATE = False - -def suite(): - return testlib.TestSuite([unittest.makeSuite(test, suiteClass=testlib.TestSuite) - for test in gen_tests(FILTER_RGX)]) - - -if __name__=='__main__': - if '-m' in sys.argv: - MODULES_ONLY = True - sys.argv.remove('-m') - - if '-u' in sys.argv: - UPDATE = True - sys.argv.remove('-u') - - if len(sys.argv) > 1: - FILTER_RGX = sys.argv[1] - del sys.argv[1] - testlib.unittest_main(defaultTest='suite') diff --git a/test/test_import_graph.py b/test/test_import_graph.py deleted file mode 100644 index a1dfdc8..0000000 --- a/test/test_import_graph.py +++ /dev/null @@ -1,65 +0,0 @@ -import sys -import os -from logilab.common.testlib import unittest_main, TestCase -from os.path import exists -from cStringIO import StringIO - -from pylint.checkers import initialize, imports -from pylint.lint import PyLinter - -from pylint.testutils import TestReporter - -class DependenciesGraphTC(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.assertEqual(open(self.dest).read().strip(), - ''' -digraph "dependencies_graph" { -rankdir=LR -charset="utf-8" -URL="." node[shape="box"] -"hoho" []; -"yep" []; -"labas" []; -"yep" -> "hoho" []; -"hoho" -> "labas" []; -"yep" -> "labas" []; -} -'''.strip()) - -class ImportCheckerTC(TestCase): - def setUp(self): - self.linter = l = PyLinter(reporter=TestReporter()) - initialize(l) - - def test_checker_dep_graphs(self): - l = self.linter - l.global_set_option('persistent', False) - l.global_set_option('enable', 'imports') - 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') - l.global_set_option('int-import-graph', 'int_import.dot') - # ignore this file causing spurious MemoryError w/ some python version (>=2.3?) - l.global_set_option('ignore', ('func_unknown_encoding.py',)) - try: - l.check('input') - self.assertTrue(exists('import.dot')) - self.assertTrue(exists('ext_import.dot')) - self.assertTrue(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_logging.py b/test/test_logging.py deleted file mode 100644 index fe7e638..0000000 --- a/test/test_logging.py +++ /dev/null @@ -1,49 +0,0 @@ -# Copyright 2014 Google Inc. All Rights Reserved. -""" -Unittest for the logging checker. -""" -from logilab.common.testlib import unittest_main -from astroid import test_utils - -from pylint.checkers import logging - -from pylint.testutils import CheckerTestCase, Message, set_config - - -class LoggingModuleDetectionTest(CheckerTestCase): - CHECKER_CLASS = logging.LoggingChecker - - def test_detects_standard_logging_module(self): - stmts = test_utils.extract_node(""" - import logging #@ - logging.warn('%s' % '%s') #@ - """) - self.checker.visit_module(None) - self.checker.visit_import(stmts[0]) - with self.assertAddsMessages(Message('W1201', node=stmts[1])): - self.checker.visit_callfunc(stmts[1]) - - def test_detects_renamed_standard_logging_module(self): - stmts = test_utils.extract_node(""" - import logging as blogging #@ - blogging.warn('%s' % '%s') #@ - """) - self.checker.visit_module(None) - self.checker.visit_import(stmts[0]) - with self.assertAddsMessages(Message('W1201', node=stmts[1])): - self.checker.visit_callfunc(stmts[1]) - - @set_config(logging_modules=['logging', 'my.logging']) - def test_nonstandard_logging_module(self): - stmts = test_utils.extract_node(""" - from my import logging as blogging #@ - blogging.warn('%s' % '%s') #@ - """) - self.checker.visit_module(None) - self.checker.visit_import(stmts[0]) - with self.assertAddsMessages(Message('W1201', node=stmts[1])): - self.checker.visit_callfunc(stmts[1]) - - -if __name__ == '__main__': - unittest_main() diff --git a/test/test_misc.py b/test/test_misc.py deleted file mode 100644 index 5a10936..0000000 --- a/test/test_misc.py +++ /dev/null @@ -1,86 +0,0 @@ -# Copyright 2013 Google Inc. All Rights Reserved. -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free Software -# Foundation; either version 2 of the License, or (at your option) any later -# version. -# -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -""" -Tests for the misc checker. -""" -import sys -import tempfile -import os -import contextlib - -from logilab.common.testlib import unittest_main -from astroid import test_utils -from pylint.checkers import misc, variables -from pylint.testutils import CheckerTestCase, Message, linter, set_config - - -@contextlib.contextmanager -def create_file_backed_module(code): - # Can't use tempfile.NamedTemporaryFile here - # because on Windows the file must be closed before writing to it, - # see http://bugs.python.org/issue14243 - fd, tmp = tempfile.mkstemp() - if sys.version_info >= (3, 0): - # erff - os.write(fd, bytes(code, 'ascii')) - else: - os.write(fd, code) - - try: - module = test_utils.build_module(code) - module.file = tmp - yield module - finally: - os.close(fd) - os.remove(tmp) - - -class FixmeTest(CheckerTestCase): - CHECKER_CLASS = misc.EncodingChecker - - def test_fixme(self): - with create_file_backed_module( - """a = 1 - # FIXME """) as module: - with self.assertAddsMessages( - Message(msg_id='W0511', line=2, args=u'FIXME')): - self.checker.process_module(module) - - @set_config(notes=[]) - def test_empty_fixme_regex(self): - with create_file_backed_module( - """a = 1 - # fixme - """) as module: - with self.assertNoMessages(): - self.checker.process_module(module) - -class MissingSubmoduleTest(CheckerTestCase): - CHECKER_CLASS = variables.VariablesChecker - - def test_package_all(self): - regr_data = os.path.join(os.path.dirname(os.path.abspath(__file__)), - 'regrtest_data') - sys.path.insert(0, regr_data) - try: - linter.check(os.path.join(regr_data, 'package_all')) - got = linter.reporter.finalize().strip() - self.assertEqual(got, "E: 3: Undefined variable name " - "'missing' in __all__") - finally: - sys.path.pop(0) - -if __name__ == '__main__': - unittest_main() diff --git a/test/test_regr.py b/test/test_regr.py deleted file mode 100644 index 0349481..0000000 --- a/test/test_regr.py +++ /dev/null @@ -1,169 +0,0 @@ -# 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., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -"""non regression tests for pylint, which requires a too specific configuration -to be incorporated in the automatic functional test framework -""" - -import sys -import os -from os.path import abspath, dirname, join - -from logilab.common.testlib import TestCase, unittest_main - -from pylint.testutils import TestReporter -from pylint.lint import PyLinter -from pylint import checkers - -test_reporter = TestReporter() -linter = PyLinter() -linter.set_reporter(test_reporter) -linter.disable('I') -linter.config.persistent = 0 -checkers.initialize(linter) - -REGR_DATA = join(dirname(abspath(__file__)), 'regrtest_data') -sys.path.insert(1, REGR_DATA) - -class NonRegrTC(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.assertEqual(got, '') - - def test_package___init___precedence(self): - linter.check('precedence_test') - got = linter.reporter.finalize().strip() - self.assertEqual(got, '') - - def test_check_package___init__(self): - for variation in ('package.__init__', join(REGR_DATA, 'package', '__init__.py')): - linter.check(variation) - got = linter.reporter.finalize().strip() - checked = linter.stats['by_module'].keys() - self.assertEqual(checked, ['package.__init__'], - '%s: %s' % (variation, checked)) - cwd = os.getcwd() - os.chdir(join(REGR_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.assertEqual(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.skipTest('test skipped: gtk is not available') - except RuntimeError: # RuntimeError when missing display - self.skipTest('no display, can\'t run this test') - linter.check(join(REGR_DATA, 'pygtk_import.py')) - got = linter.reporter.finalize().strip() - self.assertEqual(got, '') - - def test_gtk_enum_crash(self): - try: - import gtk - except ImportError: - self.skipTest('test skipped: gtk is not available') - except RuntimeError: # RuntimeError when missing display - self.skipTest('no display, can\'t run this test') - linter.check(join(REGR_DATA, 'pygtk_enum_crash.py')) - got = linter.reporter.finalize().strip() - self.assertEqual(got, '') - - def test_numarray_inference(self): - try: - from numarray import random_array - except ImportError: - self.skipTest('test skipped: numarray.random_array is not available') - linter.check(join(REGR_DATA, 'numarray_inf.py')) - got = linter.reporter.finalize().strip() - self.assertEqual(got, "E: 5: Instance of 'int' has no 'astype' member (but some types could not be inferred)") - - def test_numarray_import(self): - try: - import numarray - except ImportError: - self.skipTest('test skipped: numarray is not available') - linter.check(join(REGR_DATA, 'numarray_import.py')) - got = linter.reporter.finalize().strip() - self.assertEqual(got, '') - - def test_socketerror_import(self): - linter.check(join(REGR_DATA, 'socketerror_import.py')) - got = linter.reporter.finalize().strip() - self.assertEqual(got, '') - - def test_class__doc__usage(self): - linter.check(join(REGR_DATA, 'classdoc_usage.py')) - got = linter.reporter.finalize().strip() - self.assertEqual(got, '') - - def test_package_import_relative_subpackage_no_attribute_error(self): - linter.check('import_package_subpackage_module') - got = linter.reporter.finalize().strip() - self.assertEqual(got, '') - - def test_import_assign_crash(self): - linter.check(join(REGR_DATA, 'import_assign.py')) - - def test_special_attr_scope_lookup_crash(self): - linter.check(join(REGR_DATA, 'special_attr_scope_lookup_crash.py')) - - def test_module_global_crash(self): - linter.check(join(REGR_DATA, 'module_global.py')) - got = linter.reporter.finalize().strip() - self.assertEqual(got, '') - - def test_decimal_inference(self): - linter.check(join(REGR_DATA, 'decimal_inference.py')) - got = linter.reporter.finalize().strip() - self.assertEqual(got, "") - - def test_descriptor_crash(self): - for fname in os.listdir(REGR_DATA): - if fname.endswith('_crash.py'): - linter.check(join(REGR_DATA, fname)) - linter.reporter.finalize().strip() - - def test_try_finally_disable_msg_crash(self): - linter.check(join(REGR_DATA, 'try_finally_disable_msg_crash')) - - def test___path__(self): - linter.check('pylint.checkers.__init__') - messages = linter.reporter.finalize().strip() - self.assertFalse('__path__' in messages, messages) - - def test_absolute_import(self): - linter.check(join(REGR_DATA, 'absimp', 'string.py')) - got = linter.reporter.finalize().strip() - self.assertEqual(got, "") - -if __name__ == '__main__': - unittest_main() diff --git a/test/test_similar.py b/test/test_similar.py deleted file mode 100644 index d797a5f..0000000 --- a/test/test_similar.py +++ /dev/null @@ -1,142 +0,0 @@ -import sys -from logilab.common.testlib import TestCase, unittest_main - -from cStringIO import StringIO -from os.path import join, dirname, abspath - -from pylint.checkers import similar - -SIMILAR1 = join(dirname(abspath(__file__)), 'input', 'similar1') -SIMILAR2 = join(dirname(abspath(__file__)), 'input', 'similar2') - -class SimilarTC(TestCase): - """test the similar command line utility""" - - def test_ignore_comments(self): - sys.stdout = StringIO() - try: - similar.Run(['--ignore-comments', SIMILAR1, SIMILAR2]) - except SystemExit, ex: - self.assertEqual(ex.code, 0) - output = sys.stdout.getvalue() - else: - self.fail('not system exit') - finally: - sys.stdout = sys.__stdout__ - self.assertMultiLineEqual(output.strip(), (""" -10 similar lines in 2 files -==%s:0 -==%s:0 - import one - from two import two - three - four - five - six - seven - eight - nine - ''' ten -TOTAL lines=44 duplicates=10 percent=22.73 -""" % (SIMILAR1, SIMILAR2)).strip()) - - - def test_ignore_docsrings(self): - sys.stdout = StringIO() - try: - similar.Run(['--ignore-docstrings', SIMILAR1, SIMILAR2]) - except SystemExit, ex: - self.assertEqual(ex.code, 0) - output = sys.stdout.getvalue() - else: - self.fail('not system exit') - finally: - sys.stdout = sys.__stdout__ - self.assertMultiLineEqual(output.strip(), (""" -8 similar lines in 2 files -==%s:6 -==%s:6 - seven - eight - nine - ''' ten - ELEVEN - twelve ''' - thirteen - fourteen - -5 similar lines in 2 files -==%s:0 -==%s:0 - import one - from two import two - three - four - five -TOTAL lines=44 duplicates=13 percent=29.55 -""" % ((SIMILAR1, SIMILAR2) * 2)).strip()) - - - def test_ignore_imports(self): - sys.stdout = StringIO() - try: - similar.Run(['--ignore-imports', SIMILAR1, SIMILAR2]) - except SystemExit, ex: - self.assertEqual(ex.code, 0) - output = sys.stdout.getvalue() - else: - self.fail('not system exit') - finally: - sys.stdout = sys.__stdout__ - self.assertMultiLineEqual(output.strip(), """ -TOTAL lines=44 duplicates=0 percent=0.00 -""".strip()) - - - def test_ignore_nothing(self): - sys.stdout = StringIO() - try: - similar.Run([SIMILAR1, SIMILAR2]) - except SystemExit, ex: - self.assertEqual(ex.code, 0) - output = sys.stdout.getvalue() - else: - self.fail('not system exit') - finally: - sys.stdout = sys.__stdout__ - self.assertMultiLineEqual(output.strip(), (""" -5 similar lines in 2 files -==%s:0 -==%s:0 - import one - from two import two - three - four - five -TOTAL lines=44 duplicates=5 percent=11.36 -""" % (SIMILAR1, SIMILAR2)).strip()) - - def test_help(self): - sys.stdout = StringIO() - try: - similar.Run(['--help']) - except SystemExit, ex: - self.assertEqual(ex.code, 0) - else: - self.fail('not system exit') - finally: - sys.stdout = sys.__stdout__ - - def test_no_args(self): - sys.stdout = StringIO() - try: - similar.Run([]) - except SystemExit, ex: - self.assertEqual(ex.code, 1) - else: - self.fail('not system exit') - finally: - sys.stdout = sys.__stdout__ - -if __name__ == '__main__': - unittest_main() diff --git a/test/test_utils.py b/test/test_utils.py deleted file mode 100644 index d8c4c67..0000000 --- a/test/test_utils.py +++ /dev/null @@ -1,63 +0,0 @@ -# Copyright 2013 Google Inc. -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free Software -# Foundation; either version 2 of the License, or (at your option) any later -# version. -# -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -from logilab.common.testlib import TestCase - -from astroid import test_utils -from pylint import utils -from pylint.checkers.utils import check_messages - - -class PyLintASTWalkerTest(TestCase): - class MockLinter(object): - def __init__(self, msgs): - self._msgs = msgs - - def is_message_enabled(self, msgid): - return self._msgs.get(msgid, True) - - class Checker(object): - def __init__(self): - self.called = set() - - @check_messages('first-message') - def visit_module(self, module): - self.called.add('module') - - @check_messages('second-message') - def visit_callfunc(self, module): - raise NotImplementedError - - @check_messages('second-message', 'third-message') - def visit_assname(self, module): - self.called.add('assname') - - @check_messages('second-message') - def leave_assname(self, module): - raise NotImplementedError - - def testCheckMessages(self): - linter = self.MockLinter({'first-message': True, - 'second-message': False, - 'third-message': True}) - walker = utils.PyLintASTWalker(linter) - checker = self.Checker() - walker.add_checker(checker) - walker.walk(test_utils.build_module("x = func()")) - self.assertEqual(set(['module', 'assname']), checker.called) - - -if __name__ == '__main__': - from logilab.common.testlib import unittest_main - unittest_main() - diff --git a/test/unittest_checkers_utils.py b/test/unittest_checkers_utils.py deleted file mode 100644 index d8ebd3b..0000000 --- a/test/unittest_checkers_utils.py +++ /dev/null @@ -1,74 +0,0 @@ -# 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., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 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 astroid import test_utils - -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.assertEqual(utils.is_native_builtin('min'), True) -## self.assertEqual(utils.is_native_builtin('__path__'), True) -## self.assertEqual(utils.is_native_builtin('__file__'), True) -## self.assertEqual(utils.is_native_builtin('whatever'), False) -## self.assertEqual(utils.is_native_builtin('mybuiltin'), False) - - def test_is_builtin(self): - self.assertEqual(utils.is_builtin('min'), True) - self.assertEqual(utils.is_builtin('__builtins__'), True) - self.assertEqual(utils.is_builtin('__path__'), False) - self.assertEqual(utils.is_builtin('__file__'), False) - self.assertEqual(utils.is_builtin('whatever'), False) - self.assertEqual(utils.is_builtin('mybuiltin'), False) - - def testGetArgumentFromCall(self): - node = test_utils.extract_node('foo(bar=3)') - self.assertIsNotNone(utils.get_argument_from_call(node, keyword='bar')) - with self.assertRaises(utils.NoSuchArgumentError): - node = test_utils.extract_node('foo(3)') - utils.get_argument_from_call(node, keyword='bar') - with self.assertRaises(utils.NoSuchArgumentError): - node = test_utils.extract_node('foo(one=a, two=b, three=c)') - utils.get_argument_from_call(node, position=1) - node = test_utils.extract_node('foo(a, b, c)') - self.assertIsNotNone(utils.get_argument_from_call(node, position=1)) - node = test_utils.extract_node('foo(a, not_this_one=1, this_one=2)') - arg = utils.get_argument_from_call(node, position=1, keyword='this_one') - self.assertEqual(2, arg.value) - node = test_utils.extract_node('foo(a)') - with self.assertRaises(utils.NoSuchArgumentError): - utils.get_argument_from_call(node, position=1) - with self.assertRaises(ValueError): - utils.get_argument_from_call(node, None, None) - - name = utils.get_argument_from_call(node, position=0) - self.assertEqual(name.name, 'a') - -if __name__ == '__main__': - unittest.main() - diff --git a/test/unittest_lint.py b/test/unittest_lint.py deleted file mode 100644 index a7213bd..0000000 --- a/test/unittest_lint.py +++ /dev/null @@ -1,535 +0,0 @@ -# Copyright (c) 2003-2014 LOGILAB S.A. (Paris, FRANCE). -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free Software -# Foundation; either version 2 of the License, or (at your option) any later -# version. -# -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -import sys -import os -import tempfile -from shutil import rmtree -from os import getcwd, chdir -from os.path import join, basename, dirname, isdir, abspath -from cStringIO import StringIO - -from logilab.common.testlib import TestCase, unittest_main, create_files -from logilab.common.compat import reload - -from pylint import config -from pylint.lint import PyLinter, Run, UnknownMessage, preprocess_options, \ - ArgumentPreprocessingError -from pylint.utils import MSG_STATE_SCOPE_CONFIG, MSG_STATE_SCOPE_MODULE, \ - PyLintASTWalker, MessageDefinition, build_message_def, tokenize_module -from pylint.testutils import TestReporter -from pylint.reporters import text -from pylint import checkers - -if sys.platform == 'win32': - HOME = 'USERPROFILE' -else: - HOME = 'HOME' - -class GetNoteMessageTC(TestCase): - def test(self): - msg = None - for note in range(-1, 11): - note_msg = config.get_note_message(note) - self.assertNotEqual(msg, note_msg) - msg = note_msg - - -HERE = abspath(dirname(__file__)) -INPUTDIR = join(HERE, 'input') - - -class PyLinterTC(TestCase): - - def setUp(self): - self.linter = PyLinter() - self.linter.disable('I') - self.linter.config.persistent = 0 - # register checkers - checkers.initialize(self.linter) - self.linter.set_reporter(TestReporter()) - - def _compare_messages(self, desc, msg, checkerref=False): - # replace \r\n with \n, because - # logilab.common.textutils.normalize_text - # uses os.linesep, which will - # not properly compare with triple - # quoted multilines used in these tests - self.assertMultiLineEqual(desc, - msg.format_help(checkerref=checkerref) - .replace('\r\n', '\n')) - - def test_check_message_id(self): - self.assertIsInstance(self.linter.check_message_id('F0001'), - MessageDefinition) - self.assertRaises(UnknownMessage, - self.linter.check_message_id, 'YB12') - - def test_message_help(self): - msg = self.linter.check_message_id('F0001') - self._compare_messages( - ''':fatal (F0001): - Used when an error occurred preventing the analysis of a module (unable to - find it for instance). This message belongs to the master checker.''', - msg, checkerref=True) - self._compare_messages( - ''':fatal (F0001): - Used when an error occurred preventing the analysis of a module (unable to - find it for instance).''', - msg, checkerref=False) - - def test_message_help_minmax(self): - # build the message manually to be python version independant - msg = build_message_def(self.linter._checkers['typecheck'][0], - 'E1122', checkers.typecheck.MSGS['E1122']) - self._compare_messages( - ''':duplicate-keyword-arg (E1122): *Duplicate keyword argument %r in %s call* - Used when a function call passes the same keyword argument multiple times. - This message belongs to the typecheck checker. It can't be emitted when using - Python >= 2.6.''', - msg, checkerref=True) - self._compare_messages( - ''':duplicate-keyword-arg (E1122): *Duplicate keyword argument %r in %s call* - Used when a function call passes the same keyword argument multiple times. - This message can't be emitted when using Python >= 2.6.''', - msg, checkerref=False) - - def test_enable_message(self): - linter = self.linter - linter.open() - linter.set_current_module('toto') - self.assertTrue(linter.is_message_enabled('W0101')) - self.assertTrue(linter.is_message_enabled('W0102')) - linter.disable('W0101', scope='package') - linter.disable('W0102', scope='module', line=1) - self.assertFalse(linter.is_message_enabled('W0101')) - self.assertFalse(linter.is_message_enabled('W0102', 1)) - linter.set_current_module('tutu') - self.assertFalse(linter.is_message_enabled('W0101')) - self.assertTrue(linter.is_message_enabled('W0102')) - linter.enable('W0101', scope='package') - linter.enable('W0102', scope='module', line=1) - self.assertTrue(linter.is_message_enabled('W0101')) - self.assertTrue(linter.is_message_enabled('W0102', 1)) - - def test_enable_message_category(self): - linter = self.linter - linter.open() - linter.set_current_module('toto') - self.assertTrue(linter.is_message_enabled('W0101')) - self.assertTrue(linter.is_message_enabled('C0121')) - linter.disable('W', scope='package') - linter.disable('C', scope='module', line=1) - self.assertFalse(linter.is_message_enabled('W0101')) - self.assertTrue(linter.is_message_enabled('C0121')) - self.assertFalse(linter.is_message_enabled('C0121', line=1)) - linter.set_current_module('tutu') - self.assertFalse(linter.is_message_enabled('W0101')) - self.assertTrue(linter.is_message_enabled('C0121')) - linter.enable('W', scope='package') - linter.enable('C', scope='module', line=1) - self.assertTrue(linter.is_message_enabled('W0101')) - self.assertTrue(linter.is_message_enabled('C0121')) - self.assertTrue(linter.is_message_enabled('C0121', line=1)) - - def test_message_state_scope(self): - linter = self.linter - linter.open() - linter.disable('C0121') - self.assertEqual(MSG_STATE_SCOPE_CONFIG, - linter.get_message_state_scope('C0121')) - linter.disable('W0101', scope='module', line=3) - self.assertEqual(MSG_STATE_SCOPE_CONFIG, - linter.get_message_state_scope('C0121')) - self.assertEqual(MSG_STATE_SCOPE_MODULE, - linter.get_message_state_scope('W0101', 3)) - linter.enable('W0102', scope='module', line=3) - self.assertEqual(MSG_STATE_SCOPE_MODULE, - linter.get_message_state_scope('W0102', 3)) - - def test_enable_message_block(self): - linter = self.linter - linter.open() - filepath = join(INPUTDIR, 'func_block_disable_msg.py') - linter.set_current_module('func_block_disable_msg') - astroid = linter.get_ast(filepath, 'func_block_disable_msg') - linter.process_tokens(tokenize_module(astroid)) - orig_state = linter._module_msgs_state.copy() - linter._module_msgs_state = {} - linter._suppression_mapping = {} - linter.collect_block_lines(astroid, orig_state) - # global (module level) - self.assertTrue(linter.is_message_enabled('W0613')) - self.assertTrue(linter.is_message_enabled('E1101')) - # meth1 - self.assertTrue(linter.is_message_enabled('W0613', 13)) - # meth2 - self.assertFalse(linter.is_message_enabled('W0613', 18)) - # meth3 - self.assertFalse(linter.is_message_enabled('E1101', 24)) - self.assertTrue(linter.is_message_enabled('E1101', 26)) - # meth4 - self.assertFalse(linter.is_message_enabled('E1101', 32)) - self.assertTrue(linter.is_message_enabled('E1101', 36)) - # meth5 - self.assertFalse(linter.is_message_enabled('E1101', 42)) - self.assertFalse(linter.is_message_enabled('E1101', 43)) - self.assertTrue(linter.is_message_enabled('E1101', 46)) - self.assertFalse(linter.is_message_enabled('E1101', 49)) - self.assertFalse(linter.is_message_enabled('E1101', 51)) - # meth6 - self.assertFalse(linter.is_message_enabled('E1101', 57)) - self.assertTrue(linter.is_message_enabled('E1101', 61)) - self.assertFalse(linter.is_message_enabled('E1101', 64)) - self.assertFalse(linter.is_message_enabled('E1101', 66)) - - self.assertTrue(linter.is_message_enabled('E0602', 57)) - self.assertTrue(linter.is_message_enabled('E0602', 61)) - self.assertFalse(linter.is_message_enabled('E0602', 62)) - self.assertTrue(linter.is_message_enabled('E0602', 64)) - self.assertTrue(linter.is_message_enabled('E0602', 66)) - # meth7 - self.assertFalse(linter.is_message_enabled('E1101', 70)) - self.assertTrue(linter.is_message_enabled('E1101', 72)) - self.assertTrue(linter.is_message_enabled('E1101', 75)) - self.assertTrue(linter.is_message_enabled('E1101', 77)) - - self.assertEqual(17, linter._suppression_mapping['W0613', 18]) - self.assertEqual(30, linter._suppression_mapping['E1101', 33]) - self.assertTrue(('E1101', 46) not in linter._suppression_mapping) - self.assertEqual(1, linter._suppression_mapping['C0302', 18]) - self.assertEqual(1, linter._suppression_mapping['C0302', 50]) - # This is tricky. While the disable in line 106 is disabling - # both 108 and 110, this is usually not what the user wanted. - # Therefore, we report the closest previous disable comment. - self.assertEqual(106, linter._suppression_mapping['E1101', 108]) - self.assertEqual(109, linter._suppression_mapping['E1101', 110]) - - def test_enable_by_symbol(self): - """messages can be controlled by symbolic names. - - The state is consistent across symbols and numbers. - """ - linter = self.linter - linter.open() - linter.set_current_module('toto') - self.assertTrue(linter.is_message_enabled('W0101')) - self.assertTrue(linter.is_message_enabled('unreachable')) - self.assertTrue(linter.is_message_enabled('W0102')) - self.assertTrue(linter.is_message_enabled('dangerous-default-value')) - linter.disable('unreachable', scope='package') - linter.disable('dangerous-default-value', scope='module', line=1) - self.assertFalse(linter.is_message_enabled('W0101')) - self.assertFalse(linter.is_message_enabled('unreachable')) - self.assertFalse(linter.is_message_enabled('W0102', 1)) - self.assertFalse(linter.is_message_enabled('dangerous-default-value', 1)) - linter.set_current_module('tutu') - self.assertFalse(linter.is_message_enabled('W0101')) - self.assertFalse(linter.is_message_enabled('unreachable')) - self.assertTrue(linter.is_message_enabled('W0102')) - self.assertTrue(linter.is_message_enabled('dangerous-default-value')) - linter.enable('unreachable', scope='package') - linter.enable('dangerous-default-value', scope='module', line=1) - self.assertTrue(linter.is_message_enabled('W0101')) - self.assertTrue(linter.is_message_enabled('unreachable')) - self.assertTrue(linter.is_message_enabled('W0102', 1)) - self.assertTrue(linter.is_message_enabled('dangerous-default-value', 1)) - - def test_list_messages(self): - sys.stdout = StringIO() - try: - self.linter.list_messages() - output = sys.stdout.getvalue() - finally: - sys.stdout = sys.__stdout__ - # cursory examination of the output: we're mostly testing it completes - self.assertIn(':empty-docstring (C0112): *Empty %s docstring*', output) - - def test_lint_ext_module_with_file_output(self): - self.linter.set_reporter(text.TextReporter()) - if sys.version_info < (3, 0): - strio = 'StringIO' - else: - strio = 'io' - self.linter.config.files_output = True - pylint_strio = 'pylint_%s.txt' % strio - try: - self.linter.check(strio) - self.assertTrue(os.path.exists(pylint_strio)) - self.assertTrue(os.path.exists('pylint_global.txt')) - finally: - try: - os.remove(pylint_strio) - os.remove('pylint_global.txt') - except: - pass - - def test_lint_should_analyze_file(self): - self.linter.set_reporter(text.TextReporter()) - self.linter.config.files_output = True - self.linter.should_analyze_file = lambda *args: False - self.linter.check('os') - self.assertFalse(os.path.exists('pylint_os.txt')) - - def test_enable_report(self): - self.assertEqual(self.linter.report_is_enabled('RP0001'), True) - self.linter.disable('RP0001') - self.assertEqual(self.linter.report_is_enabled('RP0001'), False) - self.linter.enable('RP0001') - self.assertEqual(self.linter.report_is_enabled('RP0001'), True) - - def test_report_output_format_aliased(self): - text.register(self.linter) - self.linter.set_option('output-format', 'text') - self.assertEqual(self.linter.reporter.__class__.__name__, 'TextReporter') - - def test_report_output_format_custom(self): - this_module = sys.modules[__name__] - class TestReporter(object): - pass - this_module.TestReporter = TestReporter - class_name = ".".join((this_module.__name__, 'TestReporter')) - self.linter.set_option('output-format', class_name) - self.assertEqual(self.linter.reporter.__class__.__name__, 'TestReporter') - - def test_set_option_1(self): - linter = self.linter - linter.set_option('disable', 'C0111,W0142') - self.assertFalse(linter.is_message_enabled('C0111')) - self.assertFalse(linter.is_message_enabled('W0142')) - self.assertTrue(linter.is_message_enabled('W0113')) - self.assertFalse(linter.is_message_enabled('missing-docstring')) - self.assertFalse(linter.is_message_enabled('star-args')) - # no name for W0113 - - def test_set_option_2(self): - linter = self.linter - linter.set_option('disable', ('C0111', 'W0142') ) - self.assertFalse(linter.is_message_enabled('C0111')) - self.assertFalse(linter.is_message_enabled('W0142')) - self.assertTrue(linter.is_message_enabled('W0113')) - self.assertFalse(linter.is_message_enabled('missing-docstring')) - self.assertFalse(linter.is_message_enabled('star-args')) - # no name for W0113 - - def test_enable_checkers(self): - self.linter.disable('design') - self.assertFalse('design' in [c.name for c in self.linter.prepare_checkers()]) - self.linter.enable('design') - self.assertTrue('design' in [c.name for c in self.linter.prepare_checkers()]) - - def test_errors_only(self): - linter = self.linter - self.linter.error_mode() - checkers = self.linter.prepare_checkers() - checker_names = set(c.name for c in checkers) - should_not = set(('design', 'format', 'imports', 'metrics', - 'miscellaneous', 'similarities')) - self.assertSetEqual(set(), should_not & checker_names) - - def test_disable_similar(self): - self.linter.set_option('disable', 'RP0801') - self.linter.set_option('disable', 'R0801') - self.assertFalse('similarities' in [c.name for c in self.linter.prepare_checkers()]) - - def test_disable_alot(self): - """check that we disabled a lot of checkers""" - self.linter.set_option('reports', False) - self.linter.set_option('disable', 'R,C,W') - checker_names = [c.name for c in self.linter.prepare_checkers()] - for cname in ('design', 'metrics', 'similarities', - 'imports'): # as a Fatal message that should be ignored - self.assertFalse(cname in checker_names, cname) - - def test_addmessage(self): - self.linter.set_reporter(TestReporter()) - self.linter.open() - self.linter.set_current_module('0123') - self.linter.add_message('C0301', line=1, args=(1, 2)) - self.linter.add_message('line-too-long', line=2, args=(3, 4)) - self.assertEqual( - ['C: 1: Line too long (1/2)', 'C: 2: Line too long (3/4)'], - self.linter.reporter.messages) - - def test_add_renamed_message(self): - self.linter.add_renamed_message('C9999', 'old-bad-name', 'invalid-name') - self.assertEqual('invalid-name', - self.linter.check_message_id('C9999').symbol) - self.assertEqual('invalid-name', - self.linter.check_message_id('old-bad-name').symbol) - - def test_renamed_message_register(self): - class Checker(object): - msgs = {'W1234': ('message', 'msg-symbol', 'msg-description', - {'old_names': [('W0001', 'old-symbol')]})} - self.linter.register_messages(Checker()) - self.assertEqual('msg-symbol', - self.linter.check_message_id('W0001').symbol) - self.assertEqual('msg-symbol', - self.linter.check_message_id('old-symbol').symbol) - - def test_init_hooks_called_before_load_plugins(self): - self.assertRaises(RuntimeError, - Run, ['--load-plugins', 'unexistant', '--init-hooks', 'raise RuntimeError']) - self.assertRaises(RuntimeError, - Run, ['--init-hooks', 'raise RuntimeError', '--load-plugins', 'unexistant']) - -class ConfigTC(TestCase): - - def setUp(self): - os.environ.pop('PYLINTRC', None) - - def test_pylint_home(self): - uhome = os.path.expanduser('~') - if uhome == '~': - expected = '.pylint.d' - else: - expected = os.path.join(uhome, '.pylint.d') - self.assertEqual(config.PYLINT_HOME, expected) - - try: - pylintd = join(tempfile.gettempdir(), '.pylint.d') - os.environ['PYLINTHOME'] = pylintd - try: - reload(config) - self.assertEqual(config.PYLINT_HOME, pylintd) - finally: - try: - os.remove(pylintd) - except: - pass - finally: - del os.environ['PYLINTHOME'] - - def test_pylintrc(self): - fake_home = tempfile.mkdtemp('fake-home') - home = os.environ[HOME] - try: - os.environ[HOME] = fake_home - self.assertEqual(config.find_pylintrc(), None) - os.environ['PYLINTRC'] = join(tempfile.gettempdir(), '.pylintrc') - self.assertEqual(config.find_pylintrc(), None) - os.environ['PYLINTRC'] = '.' - self.assertEqual(config.find_pylintrc(), None) - finally: - os.environ.pop('PYLINTRC', '') - os.environ[HOME] = home - rmtree(fake_home, ignore_errors=True) - reload(config) - - def test_pylintrc_parentdir(self): - chroot = tempfile.mkdtemp() - - # Get real path of tempfile, otherwise test fail on mac os x - cdir = getcwd() - chdir(chroot) - chroot = abspath('.') - chdir(cdir) - - try: - create_files(['a/pylintrc', 'a/b/__init__.py', 'a/b/pylintrc', - 'a/b/c/__init__.py', 'a/b/c/d/__init__.py'], chroot) - os.chdir(chroot) - fake_home = tempfile.mkdtemp('fake-home') - home = os.environ[HOME] - try: - os.environ[HOME] = fake_home - self.assertEqual(config.find_pylintrc(), None) - finally: - os.environ[HOME] = home - os.rmdir(fake_home) - results = {'a' : join(chroot, 'a', 'pylintrc'), - 'a/b' : join(chroot, 'a', 'b', 'pylintrc'), - 'a/b/c' : join(chroot, 'a', 'b', 'pylintrc'), - 'a/b/c/d' : join(chroot, 'a', 'b', 'pylintrc'), - } - for basedir, expected in results.items(): - os.chdir(join(chroot, basedir)) - self.assertEqual(config.find_pylintrc(), expected) - finally: - os.chdir(HERE) - rmtree(chroot) - - def test_pylintrc_parentdir_no_package(self): - chroot = tempfile.mkdtemp() - - # Get real path of tempfile, otherwise test fail on mac os x - cdir = getcwd() - chdir(chroot) - chroot = abspath('.') - chdir(cdir) - - fake_home = tempfile.mkdtemp('fake-home') - home = os.environ[HOME] - os.environ[HOME] = fake_home - try: - create_files(['a/pylintrc', 'a/b/pylintrc', 'a/b/c/d/__init__.py'], chroot) - os.chdir(chroot) - self.assertEqual(config.find_pylintrc(), None) - results = {'a' : join(chroot, 'a', 'pylintrc'), - 'a/b' : join(chroot, 'a', 'b', 'pylintrc'), - 'a/b/c' : None, - 'a/b/c/d' : None, - } - for basedir, expected in results.items(): - os.chdir(join(chroot, basedir)) - self.assertEqual(config.find_pylintrc(), expected) - finally: - os.environ[HOME] = home - rmtree(fake_home, ignore_errors=True) - os.chdir(HERE) - rmtree(chroot) - - -class PreprocessOptionsTC(TestCase): - def _callback(self, name, value): - self.args.append((name, value)) - - def test_value_equal(self): - self.args = [] - preprocess_options(['--foo', '--bar=baz', '--qu=ux'], - {'foo' : (self._callback, False), - 'qu' : (self._callback, True)}) - self.assertEqual( - [('foo', None), ('qu', 'ux')], self.args) - - def test_value_space(self): - self.args = [] - preprocess_options(['--qu', 'ux'], - {'qu' : (self._callback, True)}) - self.assertEqual( - [('qu', 'ux')], self.args) - - def test_error_missing_expected_value(self): - self.assertRaises( - ArgumentPreprocessingError, - preprocess_options, - ['--foo', '--bar', '--qu=ux'], - {'bar' : (None, True)}) - self.assertRaises( - ArgumentPreprocessingError, - preprocess_options, - ['--foo', '--bar'], - {'bar' : (None, True)}) - - def test_error_unexpected_value(self): - self.assertRaises( - ArgumentPreprocessingError, - preprocess_options, - ['--foo', '--bar=spam', '--qu=ux'], - {'bar' : (None, False)}) - - -if __name__ == '__main__': - unittest_main() diff --git a/test/unittest_pyreverse_diadefs.py b/test/unittest_pyreverse_diadefs.py deleted file mode 100644 index 0f67e36..0000000 --- a/test/unittest_pyreverse_diadefs.py +++ /dev/null @@ -1,169 +0,0 @@ -# Copyright (c) 2000-2013 LOGILAB S.A. (Paris, FRANCE). -# http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free Software -# Foundation; either version 2 of the License, or (at your option) any later -# version. -# -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -""" -unittest for the extensions.diadefslib modules -""" - -import unittest -import sys - -import astroid -from astroid import MANAGER -from astroid.inspector import Linker - -from pylint.pyreverse.diadefslib import * - -from unittest_pyreverse_writer import Config, get_project - -PROJECT = get_project('data') -HANDLER = DiadefsHandler(Config()) - -def _process_classes(classes): - """extract class names of a list""" - return sorted([(isinstance(c.node, astroid.Class), c.title) for c in classes]) - -def _process_relations(relations): - """extract relation indices from a relation list""" - result = [] - for rel_type, rels in relations.iteritems(): - for rel in rels: - result.append( (rel_type, rel.from_object.title, - rel.to_object.title) ) - result.sort() - return result - - -class DiaDefGeneratorTC(unittest.TestCase): - def test_option_values(self): - """test for ancestor, associated and module options""" - handler = DiadefsHandler(Config()) - df_h = DiaDefGenerator(Linker(PROJECT), handler) - cl_config = Config() - cl_config.classes = ['Specialization'] - cl_h = DiaDefGenerator(Linker(PROJECT), DiadefsHandler(cl_config) ) - self.assertEqual( (0, 0), df_h._get_levels()) - self.assertEqual( False, df_h.module_names) - self.assertEqual( (-1, -1), cl_h._get_levels()) - self.assertEqual( True, cl_h.module_names) - for hndl in [df_h, cl_h]: - hndl.config.all_ancestors = True - hndl.config.all_associated = True - hndl.config.module_names = True - hndl._set_default_options() - self.assertEqual( (-1, -1), hndl._get_levels()) - self.assertEqual( True, hndl.module_names) - handler = DiadefsHandler( Config()) - df_h = DiaDefGenerator(Linker(PROJECT), handler) - cl_config = Config() - cl_config.classes = ['Specialization'] - cl_h = DiaDefGenerator(Linker(PROJECT), DiadefsHandler(cl_config) ) - for hndl in [df_h, cl_h]: - hndl.config.show_ancestors = 2 - hndl.config.show_associated = 1 - hndl.config.module_names = False - hndl._set_default_options() - self.assertEqual( (2, 1), hndl._get_levels()) - self.assertEqual( False, hndl.module_names) - #def test_default_values(self): - """test efault values for package or class diagrams""" - # TODO : should test difference between default values for package - # or class diagrams - -class DefaultDiadefGeneratorTC(unittest.TestCase): - def test_known_values1(self): - dd = DefaultDiadefGenerator(Linker(PROJECT), HANDLER).visit(PROJECT) - self.assertEqual(len(dd), 2) - keys = [d.TYPE for d in dd] - self.assertEqual(keys, ['package', 'class']) - pd = dd[0] - self.assertEqual(pd.title, 'packages No Name') - modules = sorted([(isinstance(m.node, astroid.Module), m.title) - for m in pd.objects]) - self.assertEqual(modules, [(True, 'data'), - (True, 'data.clientmodule_test'), - (True, 'data.suppliermodule_test')]) - cd = dd[1] - self.assertEqual(cd.title, 'classes No Name') - classes = _process_classes(cd.objects) - self.assertEqual(classes, [(True, 'Ancestor'), - (True, 'DoNothing'), - (True, 'Interface'), - (True, 'Specialization')] - ) - - _should_rels = [('association', 'DoNothing', 'Ancestor'), - ('association', 'DoNothing', 'Specialization'), - ('implements', 'Ancestor', 'Interface'), - ('specialization', 'Specialization', 'Ancestor')] - def test_exctract_relations(self): - """test extract_relations between classes""" - cd = DefaultDiadefGenerator(Linker(PROJECT), HANDLER).visit(PROJECT)[1] - cd.extract_relationships() - relations = _process_relations(cd.relationships) - self.assertEqual(relations, self._should_rels) - - def test_functional_relation_extraction(self): - """functional test of relations extraction; - different classes possibly in different modules""" - # XXX should be catching pyreverse environnement problem but doesn't - # pyreverse doesn't extracts the relations but this test ok - project = get_project('data') - handler = DiadefsHandler(Config()) - diadefs = handler.get_diadefs(project, Linker(project, tag=True) ) - cd = diadefs[1] - relations = _process_relations(cd.relationships) - self.assertEqual(relations, self._should_rels) - - def test_known_values2(self): - project = get_project('data.clientmodule_test') - dd = DefaultDiadefGenerator(Linker(project), HANDLER).visit(project) - self.assertEqual(len(dd), 1) - keys = [d.TYPE for d in dd] - self.assertEqual(keys, ['class']) - cd = dd[0] - self.assertEqual(cd.title, 'classes No Name') - classes = _process_classes(cd.objects) - self.assertEqual(classes, [(True, 'Ancestor'), - (True, 'DoNothing'), - (True, 'Specialization')] - ) - -class ClassDiadefGeneratorTC(unittest.TestCase): - def test_known_values1(self): - HANDLER.config.classes = ['Specialization'] - cdg = ClassDiadefGenerator(Linker(PROJECT), HANDLER) - special = 'data.clientmodule_test.Specialization' - cd = cdg.class_diagram(PROJECT, special) - self.assertEqual(cd.title, special) - classes = _process_classes(cd.objects) - self.assertEqual(classes, [(True, 'data.clientmodule_test.Ancestor'), - (True, special), - (True, 'data.suppliermodule_test.DoNothing'), - ]) - - def test_known_values2(self): - HANDLER.config.module_names = False - cd = ClassDiadefGenerator(Linker(PROJECT), HANDLER).class_diagram(PROJECT, 'data.clientmodule_test.Specialization') - self.assertEqual(cd.title, 'data.clientmodule_test.Specialization') - classes = _process_classes(cd.objects) - self.assertEqual(classes, [(True, 'Ancestor'), - (True, 'DoNothing'), - (True, 'Specialization') - ]) - - -if __name__ == '__main__': - unittest.main() diff --git a/test/unittest_pyreverse_writer.py b/test/unittest_pyreverse_writer.py deleted file mode 100644 index 19d94ab..0000000 --- a/test/unittest_pyreverse_writer.py +++ /dev/null @@ -1,136 +0,0 @@ -# Copyright (c) 2000-2013 LOGILAB S.A. (Paris, FRANCE). -# http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free Software -# Foundation; either version 2 of the License, or (at your option) any later -# version. -# -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -""" -unittest for visitors.diadefs and extensions.diadefslib modules -""" - - -import os -import sys -import codecs -from os.path import join, dirname, abspath -from difflib import unified_diff - -from logilab.common.testlib import TestCase, unittest_main - -from astroid import MANAGER -from astroid.inspector import Linker - -from pylint.pyreverse.diadefslib import DefaultDiadefGenerator, DiadefsHandler -from pylint.pyreverse.writer import DotWriter -from pylint.pyreverse.utils import get_visibility - - -_DEFAULTS = { - 'all_ancestors': None, 'show_associated': None, - 'module_names': None, - 'output_format': 'dot', 'diadefs_file': None, 'quiet': 0, - 'show_ancestors': None, 'classes': (), 'all_associated': None, - 'mode': 'PUB_ONLY', 'show_builtin': False, 'only_classnames': False - } - -class Config(object): - """config object for tests""" - def __init__(self): - for attr, value in _DEFAULTS.items(): - setattr(self, attr, value) - - -def _file_lines(path): - # we don't care about the actual encoding, but python3 forces us to pick one - lines = [line.strip() for line in codecs.open(path, encoding='latin1').readlines() - if (line.find('squeleton generated by ') == -1 and - not line.startswith('__revision__ = "$Id:'))] - return [line for line in lines if line] - -def get_project(module, name=None): - """return a astroid project representation""" - # flush cache - MANAGER._modules_by_name = {} - def _astroid_wrapper(func, modname): - return func(modname) - return MANAGER.project_from_files([module], _astroid_wrapper, - project_name=name) - -CONFIG = Config() - -class DotWriterTC(TestCase): - - @classmethod - def setUpClass(cls): - project = get_project(cls.datadir) - linker = Linker(project) - handler = DiadefsHandler(CONFIG) - dd = DefaultDiadefGenerator(linker, handler).visit(project) - for diagram in dd: - diagram.extract_relationships() - writer = DotWriter(CONFIG) - writer.write(dd) - - @classmethod - def tearDownClass(cls): - for fname in ('packages_No_Name.dot', 'classes_No_Name.dot',): - try: - os.remove(fname) - except: - continue - - def _test_same_file(self, generated_file): - expected_file = self.datapath(generated_file) - generated = _file_lines(generated_file) - expected = _file_lines(expected_file) - generated = '\n'.join(generated) - expected = '\n'.join(expected) - files = "\n *** expected : %s, generated : %s \n" % ( - expected_file, generated_file) - self.assertEqual(expected, generated, '%s%s' % ( - files, '\n'.join(line for line in unified_diff( - expected.splitlines(), generated.splitlines() ))) ) - os.remove(generated_file) - - def test_package_diagram(self): - self._test_same_file('packages_No_Name.dot') - - def test_class_diagram(self): - self._test_same_file('classes_No_Name.dot') - - - -class GetVisibilityTC(TestCase): - - def test_special(self): - for name in ["__reduce_ex__", "__setattr__"]: - self.assertEqual(get_visibility(name), 'special') - - def test_private(self): - for name in ["__g_", "____dsf", "__23_9"]: - got = get_visibility(name) - self.assertEqual(got, 'private', - 'got %s instead of private for value %s' % (got, name)) - - def test_public(self): - self.assertEqual(get_visibility('simple'), 'public') - - def test_protected(self): - for name in ["_","__", "___", "____", "_____", "___e__", - "_nextsimple", "_filter_it_"]: - got = get_visibility(name) - self.assertEqual(got, 'protected', - 'got %s instead of protected for value %s' % (got, name)) - - -if __name__ == '__main__': - unittest_main() diff --git a/test/unittest_reporting.py b/test/unittest_reporting.py deleted file mode 100644 index 3dd0d0a..0000000 --- a/test/unittest_reporting.py +++ /dev/null @@ -1,53 +0,0 @@ -# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free Software -# Foundation; either version 2 of the License, or (at your option) any later -# version. -# -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -import os -from os.path import join, dirname, abspath -from cStringIO import StringIO - -from logilab.common.testlib import TestCase, unittest_main - -from pylint.lint import PyLinter -from pylint import checkers -from pylint.reporters.text import TextReporter - -HERE = abspath(dirname(__file__)) -INPUTDIR = join(HERE, 'input') - -class PyLinterTC(TestCase): - - def setUp(self): - self.linter = PyLinter(reporter=TextReporter()) - self.linter.disable('I') - self.linter.config.persistent = 0 - # register checkers - checkers.initialize(self.linter) - os.environ.pop('PYLINTRC', None) - - def test_template_option(self): - output = StringIO() - self.linter.reporter.set_output(output) - self.linter.set_option('msg-template', '{msg_id}:{line:03d}') - self.linter.open() - self.linter.set_current_module('0123') - self.linter.add_message('C0301', line=1, args=(1, 2)) - self.linter.add_message('line-too-long', line=2, args=(3, 4)) - self.assertMultiLineEqual(output.getvalue(), - '************* Module 0123\n' - 'C0301:001\n' - 'C0301:002\n') - - -if __name__ == '__main__': - unittest_main() diff --git a/testutils.py b/testutils.py deleted file mode 100644 index 1658e10..0000000 --- a/testutils.py +++ /dev/null @@ -1,355 +0,0 @@ -# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). -# http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free Software -# Foundation; either version 2 of the License, or (at your option) any later -# version. -# -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -"""functional/non regression tests for pylint""" -from __future__ import with_statement - -import collections -import contextlib -import functools -import sys -import re - -from glob import glob -from os import linesep -from os.path import abspath, basename, dirname, isdir, join, splitext -from cStringIO import StringIO - -from logilab.common import testlib - -from pylint import checkers -from pylint.reporters import BaseReporter -from pylint.interfaces import IReporter -from pylint.lint import PyLinter - - -# Utils - -SYS_VERS_STR = '%d%d' % sys.version_info[:2] -TITLE_UNDERLINES = ['', '=', '-', '.'] -PREFIX = abspath(dirname(__file__)) -PY3K = sys.version_info[0] == 3 - -def fix_path(): - sys.path.insert(0, PREFIX) - -def get_tests_info(input_dir, msg_dir, prefix, suffix): - """get python input examples and output messages - - We use following conventions for input files and messages: - for different inputs: - test for python >= x.y -> input = <name>_pyxy.py - test for python < x.y -> input = <name>_py_xy.py - for one input and different messages: - message for python >= x.y -> message = <name>_pyxy.txt - lower versions -> message with highest num - """ - result = [] - for fname in glob(join(input_dir, prefix + '*' + suffix)): - infile = basename(fname) - fbase = splitext(infile)[0] - # filter input files : - pyrestr = fbase.rsplit('_py', 1)[-1] # like _26 or 26 - if pyrestr.isdigit(): # '24', '25'... - if SYS_VERS_STR < pyrestr: - continue - if pyrestr.startswith('_') and pyrestr[1:].isdigit(): - # skip test for higher python versions - if SYS_VERS_STR >= pyrestr[1:]: - continue - messages = glob(join(msg_dir, fbase + '*.txt')) - # the last one will be without ext, i.e. for all or upper versions: - if messages: - for outfile in sorted(messages, reverse=True): - py_rest = outfile.rsplit('_py', 1)[-1][:-4] - if py_rest.isdigit() and SYS_VERS_STR >= py_rest: - break - else: - # This will provide an error message indicating the missing filename. - outfile = join(msg_dir, fbase + '.txt') - result.append((infile, outfile)) - return result - - -class TestReporter(BaseReporter): - """reporter storing 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, obj, line, _ = location - self.message_ids[msg_id] = 1 - if obj: - obj = ':%s' % obj - sigle = msg_id[0] - if PY3K and linesep != '\n': - # 2to3 writes os.linesep instead of using - # the previosly used line separators - msg = msg.replace('\r\n', '\n') - self.messages.append('%s:%3s%s: %s' % (sigle, line, obj, 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""" - - -class Message(collections.namedtuple('Message', - ['msg_id', 'line', 'node', 'args'])): - def __new__(cls, msg_id, line=None, node=None, args=None): - return tuple.__new__(cls, (msg_id, line, node, args)) - - -class UnittestLinter(object): - """A fake linter class to capture checker messages.""" - - def __init__(self): - self._messages = [] - self.stats = {} - - def release_messages(self): - try: - return self._messages - finally: - self._messages = [] - - def add_message(self, msg_id, line=None, node=None, args=None): - self._messages.append(Message(msg_id, line, node, args)) - - def is_message_enabled(self, *unused_args): - return True - - def add_stats(self, **kwargs): - for name, value in kwargs.iteritems(): - self.stats[name] = value - return self.stats - - -def set_config(**kwargs): - """Decorator for setting config values on a checker.""" - def _Wrapper(fun): - @functools.wraps(fun) - def _Forward(self): - for key, value in kwargs.iteritems(): - setattr(self.checker.config, key, value) - if isinstance(self, CheckerTestCase): - # reopen checker in case, it may be interested in configuration change - self.checker.open() - fun(self) - - return _Forward - return _Wrapper - - -class CheckerTestCase(testlib.TestCase): - """A base testcase class for unittesting individual checker classes.""" - CHECKER_CLASS = None - CONFIG = {} - - def setUp(self): - self.linter = UnittestLinter() - self.checker = self.CHECKER_CLASS(self.linter) # pylint: disable=not-callable - for key, value in self.CONFIG.iteritems(): - setattr(self.checker.config, key, value) - self.checker.open() - - @contextlib.contextmanager - def assertNoMessages(self): - """Assert that no messages are added by the given method.""" - with self.assertAddsMessages(): - yield - - @contextlib.contextmanager - def assertAddsMessages(self, *messages): - """Assert that exactly the given method adds the given messages. - - The list of messages must exactly match *all* the messages added by the - method. Additionally, we check to see whether the args in each message can - actually be substituted into the message string. - """ - yield - got = self.linter.release_messages() - msg = ('Expected messages did not match actual.\n' - 'Expected:\n%s\nGot:\n%s' % ('\n'.join(repr(m) for m in messages), - '\n'.join(repr(m) for m in got))) - self.assertEqual(list(messages), got, msg) - - -# Init -test_reporter = TestReporter() -linter = PyLinter() -linter.set_reporter(test_reporter) -linter.config.persistent = 0 -checkers.initialize(linter) -linter.global_set_option('required-attributes', ('__revision__',)) - -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(r'^func_i\d\d\d\d$') - -def exception_str(self, ex): - """function used to replace default __str__ method of exception instances""" - return 'in %s\n:: %s' % (ex.file, ', '.join(ex.args)) - -# Test classes - -class LintTestUsingModule(testlib.TestCase): - INPUT_DIR = None - DEFAULT_PACKAGE = 'input' - package = DEFAULT_PACKAGE - linter = linter - module = None - depends = None - output = None - _TEST_TYPE = 'module' - - def shortDescription(self): - values = {'mode' : self._TEST_TYPE, - 'input': self.module, - 'pkg': self.package, - 'cls': self.__class__.__name__} - - if self.package == self.DEFAULT_PACKAGE: - msg = '%(mode)s test of input file "%(input)s" (%(cls)s)' - else: - msg = '%(mode)s test of input file "%(input)s" in "%(pkg)s" (%(cls)s)' - return msg % values - - def test_functionality(self): - tocheck = [self.package+'.'+self.module] - if self.depends: - tocheck += [self.package+'.%s' % name.replace('.py', '') - for name, file in self.depends] - self._test(tocheck) - - def _check_result(self, got): - self.assertMultiLineEqual(self._get_expected(), got) - - def _test(self, tocheck): - if INFO_TEST_RGX.match(self.module): - self.linter.enable('I') - else: - self.linter.disable('I') - try: - self.linter.check(tocheck) - except Exception, ex: - # need finalization to restore a correct state - self.linter.reporter.finalize() - ex.file = tocheck - print ex - ex.__str__ = exception_str - raise - self._check_result(self.linter.reporter.finalize()) - - def _has_output(self): - return not self.module.startswith('func_noerror_') - - def _get_expected(self): - if self._has_output() and self.output: - with open(self.output, 'U') as fobj: - return fobj.read().strip() + '\n' - else: - return '' - -class LintTestUsingFile(LintTestUsingModule): - - _TEST_TYPE = 'file' - - def test_functionality(self): - importable = join(self.INPUT_DIR, self.module) - # python also prefers packages over simple modules. - if not isdir(importable): - importable += '.py' - tocheck = [importable] - if self.depends: - tocheck += [join(self.INPUT_DIR, name) for name, _file in self.depends] - self._test(tocheck) - -class LintTestUpdate(LintTestUsingModule): - - _TEST_TYPE = 'update' - - def _check_result(self, got): - if self._has_output(): - if got != self._get_expected(): - if not self.output: - self.output = join(self.MSG_DIR, '%s.txt' % (self.module,)) - with open(self.output, 'w') as fobj: - fobj.write(got) - -# Callback - -def cb_test_gen(base_class): - def call(input_dir, msg_dir, module_file, messages_file, dependencies): - class LintTC(base_class): - module = module_file.replace('.py', '') - output = messages_file - depends = dependencies or None - tags = testlib.Tags(('generated', 'pylint_input_%s' % module)) - INPUT_DIR = input_dir - MSG_DIR = msg_dir - return LintTC - return call - -# Main function - -def make_tests(input_dir, msg_dir, filter_rgx, callbacks): - """generate tests classes from test info - - return the list of generated test classes - """ - if filter_rgx: - is_to_run = re.compile(filter_rgx).search - else: - is_to_run = lambda x: 1 - tests = [] - for module_file, messages_file in ( - get_tests_info(input_dir, msg_dir, 'func_', '') - ): - if not is_to_run(module_file): - continue - base = module_file.replace('func_', '').replace('.py', '') - - dependencies = get_tests_info(input_dir, msg_dir, base, '.py') - - for callback in callbacks: - test = callback(input_dir, msg_dir, module_file, messages_file, - dependencies) - if test: - tests.append(test) - - - return tests @@ -1,10 +1,11 @@ [tox] # official list is -#envlist = py25, py26, py27, py32, py33 -# though 2.5 support is known to be broken... +#envlist = py27, py32, py33, py34 envlist = py27, py33 [testenv] deps = logilab-common hg+https://bitbucket.org/logilab/astroid/ -commands = pytest -t {envsitepackagesdir}/pylint/test/ + six +commands = python -Wi -m unittest discover -s {envsitepackagesdir}/pylint/test/ -p {posargs:*test_*}.py +changedir = {toxworkdir} diff --git a/utils.py b/utils.py deleted file mode 100644 index e1cb3a8..0000000 --- a/utils.py +++ /dev/null @@ -1,742 +0,0 @@ -# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE). -# http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free Software -# Foundation; either version 2 of the License, or (at your option) any later -# version. -# -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -"""some various utilities and helper classes, most of them used in the -main pylint class -""" - -import re -import sys -import tokenize -import os -from warnings import warn -from os.path import dirname, basename, splitext, exists, isdir, join, normpath - -from logilab.common.interface import implements -from logilab.common.modutils import modpath_from_file, get_module_files, \ - file_from_modpath, load_module_from_file -from logilab.common.textutils import normalize_text -from logilab.common.configuration import rest_format_section -from logilab.common.ureports import Section - -from astroid import nodes, Module - -from pylint.interfaces import IRawChecker, ITokenChecker - - -class UnknownMessage(Exception): - """raised when a unregistered message id is encountered""" - -class EmptyReport(Exception): - """raised when a report is empty and so should not be displayed""" - - -MSG_TYPES = { - 'I' : 'info', - 'C' : 'convention', - 'R' : 'refactor', - 'W' : 'warning', - 'E' : 'error', - 'F' : 'fatal' - } -MSG_TYPES_LONG = dict([(v, k) for k, v in MSG_TYPES.iteritems()]) - -MSG_TYPES_STATUS = { - 'I' : 0, - 'C' : 16, - 'R' : 8, - 'W' : 4, - 'E' : 2, - 'F' : 1 - } - -_MSG_ORDER = 'EWRCIF' -MSG_STATE_SCOPE_CONFIG = 0 -MSG_STATE_SCOPE_MODULE = 1 - -OPTION_RGX = re.compile(r'\s*#.*\bpylint:(.*)') - -# The line/node distinction does not apply to fatal errors and reports. -_SCOPE_EXEMPT = 'FR' - -class WarningScope(object): - LINE = 'line-based-msg' - NODE = 'node-based-msg' - - -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) - -def category_id(id): - id = id.upper() - if id in MSG_TYPES: - return id - return MSG_TYPES_LONG.get(id) - - -def tokenize_module(module): - stream = module.file_stream - stream.seek(0) - readline = stream.readline - if sys.version_info < (3, 0): - if module.file_encoding is not None: - readline = lambda: stream.readline().decode(module.file_encoding, - 'replace') - return list(tokenize.generate_tokens(readline)) - return list(tokenize.tokenize(readline)) - -def build_message_def(checker, msgid, msg_tuple): - if implements(checker, (IRawChecker, ITokenChecker)): - default_scope = WarningScope.LINE - else: - default_scope = WarningScope.NODE - options = {} - if len(msg_tuple) > 3: - (msg, symbol, descr, options) = msg_tuple - elif len(msg_tuple) > 2: - (msg, symbol, descr) = msg_tuple[:3] - else: - # messages should have a symbol, but for backward compatibility - # they may not. - (msg, descr) = msg_tuple - warn("[pylint 0.26] description of message %s doesn't include " - "a symbolic name" % msgid, DeprecationWarning) - symbol = None - options.setdefault('scope', default_scope) - return MessageDefinition(checker, msgid, msg, descr, symbol, **options) - - -class MessageDefinition(object): - def __init__(self, checker, msgid, msg, descr, symbol, scope, - minversion=None, maxversion=None, old_names=None): - self.checker = checker - assert len(msgid) == 5, 'Invalid message id %s' % msgid - assert msgid[0] in MSG_TYPES, \ - 'Bad message type %s in %r' % (msgid[0], msgid) - self.msgid = msgid - self.msg = msg - self.descr = descr - self.symbol = symbol - self.scope = scope - self.minversion = minversion - self.maxversion = maxversion - self.old_names = old_names or [] - - def may_be_emitted(self): - """return True if message may be emitted using the current interpreter""" - if self.minversion is not None and self.minversion > sys.version_info: - return False - if self.maxversion is not None and self.maxversion <= sys.version_info: - return False - return True - - def format_help(self, checkerref=False): - """return the help string for the given message id""" - desc = self.descr - if checkerref: - desc += ' This message belongs to the %s checker.' % \ - self.checker.name - title = self.msg - if self.symbol: - msgid = '%s (%s)' % (self.symbol, self.msgid) - else: - msgid = self.msgid - if self.minversion or self.maxversion: - restr = [] - if self.minversion: - restr.append('< %s' % '.'.join([str(n) for n in self.minversion])) - if self.maxversion: - restr.append('>= %s' % '.'.join([str(n) for n in self.maxversion])) - restr = ' or '.join(restr) - if checkerref: - desc += " It can't be emitted when using Python %s." % restr - else: - desc += " This message can't be emitted when using Python %s." % restr - desc = normalize_text(' '.join(desc.split()), indent=' ') - if title != '%s': - title = title.splitlines()[0] - return ':%s: *%s*\n%s' % (msgid, title, desc) - return ':%s:\n%s' % (msgid, desc) - - -class MessagesHandlerMixIn(object): - """a mix-in class containing all the messages related methods for the main - lint class - """ - - def __init__(self): - # Primary registry for all active messages (i.e. all messages - # that can be emitted by pylint for the underlying Python - # version). It contains the 1:1 mapping from symbolic names - # to message definition objects. - self._messages = {} - # Maps alternative names (numeric IDs, deprecated names) to - # message definitions. May contain several names for each definition - # object. - self._alternative_names = {} - self._msgs_state = {} - self._module_msgs_state = {} # None - self._raw_module_msgs_state = {} - self._msgs_by_category = {} - self.msg_status = 0 - self._ignored_msgs = {} - self._suppression_mapping = {} - - def add_renamed_message(self, old_id, old_symbol, new_symbol): - """Register the old ID and symbol for a warning that was renamed. - - This allows users to keep using the old ID/symbol in suppressions. - """ - msg = self.check_message_id(new_symbol) - msg.old_names.append((old_id, old_symbol)) - self._alternative_names[old_id] = msg - self._alternative_names[old_symbol] = msg - - 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 two first characters - are the checker id and the two last the message id in this checker - """ - chkid = None - for msgid, msg_tuple in checker.msgs.iteritems(): - msg = build_message_def(checker, msgid, msg_tuple) - assert msg.symbol not in self._messages, \ - 'Message symbol %r is already defined' % msg.symbol - # avoid duplicate / malformed ids - assert msg.msgid not in self._alternative_names, \ - 'Message id %r is already defined' % msgid - assert chkid is None or chkid == msg.msgid[1:3], \ - 'Inconsistent checker part in message id %r' % msgid - chkid = msg.msgid[1:3] - if not msg.may_be_emitted(): - self._msgs_state[msg.msgid] = False - continue - self._messages[msg.symbol] = msg - self._alternative_names[msg.msgid] = msg - for old_id, old_symbol in msg.old_names: - self._alternative_names[old_id] = msg - self._alternative_names[old_symbol] = msg - self._msgs_by_category.setdefault(msg.msgid[0], []).append(msg.msgid) - - def disable(self, msgid, scope='package', line=None, ignore_unknown=False): - """don't output message of the given id""" - assert scope in ('package', 'module') - # handle disable=all by disabling all categories - if msgid == 'all': - for msgid in MSG_TYPES: - self.disable(msgid, scope, line) - return - # msgid is a category? - catid = category_id(msgid) - if catid is not None: - for _msgid in self._msgs_by_category.get(catid): - self.disable(_msgid, scope, line) - return - # msgid is a checker name? - if msgid.lower() in self._checkers: - for checker in self._checkers[msgid.lower()]: - for _msgid in checker.msgs: - if _msgid in self._alternative_names: - self.disable(_msgid, scope, line) - return - # msgid is report id? - if msgid.lower().startswith('rp'): - self.disable_report(msgid) - return - - try: - # msgid is a symbolic or numeric msgid. - msg = self.check_message_id(msgid) - except UnknownMessage: - if ignore_unknown: - return - raise - - if scope == 'module': - assert line > 0 - try: - self._module_msgs_state[msg.msgid][line] = False - except KeyError: - self._module_msgs_state[msg.msgid] = {line: False} - if msgid != 'I0011': - self.add_message('I0011', line=line, args=msg.msgid) - - else: - msgs = self._msgs_state - msgs[msg.msgid] = False - # sync configuration object - self.config.disable_msg = [mid for mid, val in msgs.iteritems() - if not val] - - def enable(self, msgid, scope='package', line=None, ignore_unknown=False): - """reenable message of the given id""" - assert scope in ('package', 'module') - catid = category_id(msgid) - # msgid is a category? - if catid is not None: - for msgid in self._msgs_by_category.get(catid): - self.enable(msgid, scope, line) - return - # msgid is a checker name? - if msgid.lower() in self._checkers: - for checker in self._checkers[msgid.lower()]: - for msgid_ in checker.msgs: - self.enable(msgid_, scope, line) - return - # msgid is report id? - if msgid.lower().startswith('rp'): - self.enable_report(msgid) - return - - try: - # msgid is a symbolic or numeric msgid. - msg = self.check_message_id(msgid) - except UnknownMessage: - if ignore_unknown: - return - raise - - if scope == 'module': - assert line > 0 - try: - self._module_msgs_state[msg.msgid][line] = True - except KeyError: - self._module_msgs_state[msg.msgid] = {line: True} - self.add_message('I0012', line=line, args=msg.msgid) - else: - msgs = self._msgs_state - msgs[msg.msgid] = True - # sync configuration object - self.config.enable = [mid for mid, val in msgs.iteritems() if val] - - def check_message_id(self, msgid): - """returns the Message object for this message. - - msgid may be either a numeric or symbolic id. - - Raises UnknownMessage if the message id is not defined. - """ - if msgid[1:].isdigit(): - msgid = msgid.upper() - for source in (self._alternative_names, self._messages): - try: - return source[msgid] - except KeyError: - pass - raise UnknownMessage('No such message id %s' % msgid) - - def get_msg_display_string(self, msgid): - """Generates a user-consumable representation of a message. - - Can be just the message ID or the ID and the symbol. - """ - return repr(self.check_message_id(msgid).symbol) - - def get_message_state_scope(self, msgid, line=None): - """Returns the scope at which a message was enabled/disabled.""" - try: - if line in self._module_msgs_state[msgid]: - return MSG_STATE_SCOPE_MODULE - except (KeyError, TypeError): - return MSG_STATE_SCOPE_CONFIG - - def is_message_enabled(self, msg_descr, line=None): - """return true if the message associated to the given message id is - enabled - - msgid may be either a numeric or symbolic message id. - """ - try: - msgid = self.check_message_id(msg_descr).msgid - except UnknownMessage: - # The linter checks for messages that are not registered - # due to version mismatch, just treat them as message IDs - # for now. - msgid = msg_descr - if line is None: - return self._msgs_state.get(msgid, True) - try: - return self._module_msgs_state[msgid][line] - except (KeyError, TypeError): - return self._msgs_state.get(msgid, True) - - def handle_ignored_message(self, state_scope, msgid, line, node, args): - """Report an ignored message. - - state_scope is either MSG_STATE_SCOPE_MODULE or MSG_STATE_SCOPE_CONFIG, - depending on whether the message was disabled locally in the module, - or globally. The other arguments are the same as for add_message. - """ - if state_scope == MSG_STATE_SCOPE_MODULE: - try: - orig_line = self._suppression_mapping[(msgid, line)] - self._ignored_msgs.setdefault((msgid, orig_line), set()).add(line) - except KeyError: - pass - - def add_message(self, msg_descr, line=None, node=None, args=None): - """Adds a message given by ID or name. - - If provided, the message string is expanded using args - - AST checkers should must the node argument (but may optionally - provide line if the line number is different), raw and token checkers - must provide the line argument. - """ - msg_info = self.check_message_id(msg_descr) - msgid = msg_info.msgid - # backward compatibility, message may not have a symbol - symbol = msg_info.symbol or msgid - # Fatal messages and reports are special, the node/scope distinction - # does not apply to them. - if msgid[0] not in _SCOPE_EXEMPT: - if msg_info.scope == WarningScope.LINE: - assert node is None and line is not None, ( - 'Message %s must only provide line, got line=%s, node=%s' % (msgid, line, node)) - elif msg_info.scope == WarningScope.NODE: - # Node-based warnings may provide an override line. - assert node is not None, 'Message %s must provide Node, got None' - - if line is None and node is not None: - line = node.fromlineno - if hasattr(node, 'col_offset'): - col_offset = node.col_offset # XXX measured in bytes for utf-8, divide by two for chars? - else: - col_offset = None - # should this message be displayed - if not self.is_message_enabled(msgid, line): - self.handle_ignored_message( - self.get_message_state_scope(msgid, line), msgid, line, node, args) - return - # update stats - msg_cat = MSG_TYPES[msgid[0]] - self.msg_status |= MSG_TYPES_STATUS[msgid[0]] - self.stats[msg_cat] += 1 - self.stats['by_module'][self.current_name][msg_cat] += 1 - try: - self.stats['by_msg'][symbol] += 1 - except KeyError: - self.stats['by_msg'][symbol] = 1 - # expand message ? - msg = msg_info.msg - 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(msgid, (path, module, obj, line or 1, col_offset or 0), msg) - - def help_message(self, msgids): - """display help messages for the given message identifiers""" - for msgid in msgids: - try: - print self.check_message_id(msgid).format_help(checkerref=True) - print - except UnknownMessage, ex: - print ex - print - continue - - def print_full_documentation(self): - """output a full documentation in ReST format""" - by_checker = {} - for checker in self.get_checkers(): - if checker.name == 'master': - prefix = 'Main ' - print "Options" - print '-------\n' - if checker.options: - for section, options in checker.options_by_section(): - if section is None: - title = 'General options' - else: - title = '%s options' % section.capitalize() - print title - print '~' * len(title) - rest_format_section(sys.stdout, None, options) - print - else: - try: - by_checker[checker.name][0] += checker.options_and_values() - by_checker[checker.name][1].update(checker.msgs) - by_checker[checker.name][2] += checker.reports - except KeyError: - by_checker[checker.name] = [list(checker.options_and_values()), - dict(checker.msgs), - list(checker.reports)] - for checker, (options, msgs, reports) in by_checker.iteritems(): - prefix = '' - title = '%s checker' % checker - print title - print '-' * len(title) - print - if options: - title = 'Options' - print title - print '~' * len(title) - rest_format_section(sys.stdout, None, options) - print - if msgs: - title = ('%smessages' % prefix).capitalize() - print title - print '~' * len(title) - for msgid, msg in sorted(msgs.iteritems(), - key=lambda (k, v): (_MSG_ORDER.index(k[0]), k)): - msg = build_message_def(checker, msgid, msg) - print msg.format_help(checkerref=False) - print - if reports: - title = ('%sreports' % prefix).capitalize() - print title - print '~' * len(title) - for report in reports: - print ':%s: %s' % report[:2] - print - print - - @property - def messages(self): - """The list of all active messages.""" - return self._messages.itervalues() - - def list_messages(self): - """output full messages list documentation in ReST format""" - msgs = sorted(self._messages.itervalues(), key=lambda msg: msg.msgid) - for msg in msgs: - print msg.format_help(checkerref=False) - print - - -class ReportsHandlerMixIn(object): - """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, reportid, r_title, r_cb, checker): - """register a report - - reportid 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 - """ - reportid = reportid.upper() - self._reports.setdefault(checker, []).append((reportid, r_title, r_cb)) - - def enable_report(self, reportid): - """disable the report of the given id""" - reportid = reportid.upper() - self._reports_state[reportid] = True - - def disable_report(self, reportid): - """disable the report of the given id""" - reportid = reportid.upper() - self._reports_state[reportid] = False - - def report_is_enabled(self, reportid): - """return true if the report associated to the given identifier is - enabled - """ - return self._reports_state.get(reportid, True) - - def make_reports(self, stats, old_stats): - """render registered reports""" - sect = Section('Report', - '%s statements analysed.'% (self.stats['statement'])) - for checker in self._reports: - for reportid, r_title, r_cb in self._reports[checker]: - if not self.report_is_enabled(reportid): - continue - report_sect = Section(r_title) - try: - r_cb(report_sect, stats, old_stats) - except EmptyReport: - continue - report_sect.report_id = reportid - sect.append(report_sect) - return 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.iteritems(): - if key[-1] == '_': - key = key[:-1] - assert key not in self.stats - self.stats[key] = value - return self.stats - - -def expand_modules(files_or_modules, black_list): - """take a list of files/modules/packages and return the list of tuple - (file, module name) which have to be actually checked - """ - result = [] - errors = [] - for something in files_or_modules: - 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: - errors.append({'key' : 'F0003', 'mod': modname}) - continue - except (ImportError, SyntaxError), ex: - # FIXME p3k : the SyntaxError is a Python bug and should be - # removed as soon as possible http://bugs.python.org/issue10588 - errors.append({'key': 'F0001', 'mod': modname, 'ex': ex}) - continue - filepath = normpath(filepath) - result.append({'path': filepath, 'name': modname, - 'basepath': filepath, 'basename': modname}) - if not (modname.endswith('.__init__') or modname == '__init__') \ - and '__init__.py' in filepath: - for subfilepath in get_module_files(dirname(filepath), black_list): - if filepath == subfilepath: - continue - submodname = '.'.join(modpath_from_file(subfilepath)) - result.append({'path': subfilepath, 'name': submodname, - 'basepath': filepath, 'basename': modname}) - return result, errors - - -class PyLintASTWalker(object): - - def __init__(self, linter): - # callbacks per node types - self.nbstatements = 1 - self.visit_events = {} - self.leave_events = {} - self.linter = linter - - def _is_method_enabled(self, method): - if not hasattr(method, 'checks_msgs'): - return True - - for msg_desc in method.checks_msgs: - if self.linter.is_message_enabled(msg_desc): - return True - return False - - def add_checker(self, checker): - """walk to the checker's dir and collect visit and leave methods""" - # XXX : should be possible to merge needed_checkers and add_checker - vcids = set() - lcids = set() - visits = self.visit_events - leaves = self.leave_events - for member in dir(checker): - cid = member[6:] - if cid == 'default': - continue - if member.startswith('visit_'): - v_meth = getattr(checker, member) - # don't use visit_methods with no activated message: - if self._is_method_enabled(v_meth): - visits.setdefault(cid, []).append(v_meth) - vcids.add(cid) - elif member.startswith('leave_'): - l_meth = getattr(checker, member) - # don't use leave_methods with no activated message: - if self._is_method_enabled(l_meth): - leaves.setdefault(cid, []).append(l_meth) - lcids.add(cid) - visit_default = getattr(checker, 'visit_default', None) - if visit_default: - for cls in nodes.ALL_NODE_CLASSES: - cid = cls.__name__.lower() - if cid not in vcids: - visits.setdefault(cid, []).append(visit_default) - # for now we have no "leave_default" method in Pylint - - def walk(self, astroid): - """call visit events of astroid checkers for the given node, recurse on - its children, then leave events. - """ - cid = astroid.__class__.__name__.lower() - if astroid.is_statement: - self.nbstatements += 1 - # generate events for this node on each checker - for cb in self.visit_events.get(cid, ()): - cb(astroid) - # recurse on children - for child in astroid.get_children(): - self.walk(child) - for cb in self.leave_events.get(cid, ()): - cb(astroid) - - -PY_EXTS = ('.py', '.pyc', '.pyo', '.pyw', '.so', '.dll') - -def register_plugins(linter, directory): - """load all module and package in the given directory, looking for a - 'register' function in each one, used to register pylint checkers - """ - imported = {} - for filename in os.listdir(directory): - base, extension = splitext(filename) - if base in imported or base == '__pycache__': - continue - if extension in PY_EXTS and base != '__init__' or ( - not extension and isdir(join(directory, base))): - try: - module = load_module_from_file(join(directory, filename)) - except ValueError: - # empty module name (usually emacs auto-save files) - continue - except ImportError, exc: - print >> sys.stderr, "Problem importing module %s: %s" % (filename, exc) - else: - if hasattr(module, 'register'): - module.register(linter) - imported[base] = 1 - |